npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@unified-product-graph/sdk

v0.9.13

Published

Unified Product Graph: programmatic SDK for reading and writing .upg product knowledge graphs

Readme

@unified-product-graph/sdk

Programmatic SDK for reading and writing Unified Product Graph .upg files.

ESM only. Use import, not require.

npm install @unified-product-graph/sdk
import { UPGClient } from '@unified-product-graph/sdk'

const upg = new UPGClient({ file: './product.upg' })

// Nodes
await upg.nodes.create({ type: 'feature', title: 'Dark mode' })
await upg.nodes.createMany({ nodes: [/* ...atomic, parent_ref chaining... */] })
await upg.nodes.list({ type: 'feature' })
await upg.nodes.get('node-id')                       // node only
await upg.nodes.get('node-id', { withEdges: true })  // { node, edges_out, edges_in }
await upg.nodes.inspect('node-id')                   // same as withEdges
await upg.nodes.update('node-id', { status: 'active' })
await upg.nodes.update('node-id', { unset_properties: ['effort'] }) // delete a key
await upg.nodes.delete('node-id')

// Edges (see "Edges: inference & direction" below — this is the #1 gotcha)
await upg.edges.connect('src-id', 'tgt-id')
await upg.edges.resolve('solution', 'feature')       // { type } | null — ask before you connect
await upg.edges.list({ source: 'node-id' })

// Schema introspection (no separate core import needed)
upg.schema.validChildren('feature_area')             // → ['feature', 'feature_area', ...]
upg.schema.edgesFrom('feature')                      // → outgoing canonical edges
upg.schema.edgeFor('solution', 'feature')            // → 'solution_becomes_feature' | null

// Product metadata
await upg.product.update({ stage: 'build' })

// One disk write for a batch of mutations
await upg.transaction(async () => {
  const a = await upg.nodes.create({ type: 'persona', title: 'A' })
  await upg.nodes.create({ type: 'job', title: 'J', parent_id: a.node.id })
})

// Graph-level
await upg.health()
await upg.search('dark mode')
const v = await upg.verify()
if (!v.ok) { /* v.tampered / v.quarantined / v.orphanedEdges explain why */ }

Edges: inference & direction

upg.edges.connect(src, tgt) is the part people get wrong. Three rules:

  1. The edge type is INFERRED from (source.type, target.type) when you don't pass type.

  2. Direction matters. solution → feature resolves to solution_becomes_feature; the reverse has no canonical edge. An explicit type does not override direction — the type's declared source/target must match the nodes (a "wrong-way" link is inexpressible; reorient the call).

  3. Most type pairs have NO canonical edge. For those, connect returns an error object — it does not throw:

    const r = await upg.edges.connect(a, b)
    if ('error' in r) {
      // r.error, r.no_canonical_edge_for?: { source_type, target_type }
    } else {
      // r.edge, r.warning?
    }

    (Contrast nodes.create, which THROWS UnknownEntityTypeError for an unknown type and WriteValidationError for an invalid status.)

Use upg.edges.resolve(srcType, tgtType) or upg.schema.edgeFor(a, b) to discover the edge and direction before connecting, instead of probing.

Write validation (one posture, everywhere)

Single and batch tools share one validation pass:

| dimension | posture | |-----------|---------| | entity type | strict — unknown rejects (alias → warning) | | status | strict — must be in the type's lifecycle phases, else rejects | | properties | permissive — unknown keys are stored with a warning |

Pass { strict: true } to promote unknown-property warnings to rejections at authoring time. unset_properties: [...] deletes keys (writing { key: null } only stores a literal null).

Intelligence layer

Beyond CRUD, the SDK ships the functions that power the plan / trace / prioritise / reflect / inspect approach tools:

  • executePlan(store, { exhaustive? } | regionId) — missing-entity backlog. Defaults to the product's active regions (pass { exhaustive: true } for the full type universe).
  • executeTrace(store, anchorId, path) — walk a typed path. path lists the entity types AFTER the anchor, e.g. executeTrace(store, personaId, ['job', 'need']) walks persona → job → need.
  • executePrioritise(framework, ids, store) — framework-driven scoring (pass a framework OBJECT, e.g. UPG_FRAMEWORKS_BY_ID['rice-scoring']). Returns one of three result kinds: execution (ranked scores), fallback (no numeric formula — e.g. a bucketing framework like MoSCoW), or type_mismatch (candidates whose type ≠ the framework's target). The type_mismatch result names the target and the offending types so you know exactly why nothing ranked:
    import { frameworkTargetTypes, executePrioritise, UPG_FRAMEWORKS_BY_ID } from '@unified-product-graph/sdk'
    
    const rice = UPG_FRAMEWORKS_BY_ID['rice-scoring']
    frameworkTargetTypes(rice)            // → ['feature']  — pick candidates of this type up front
    const res = executePrioritise(rice, featureIds, store)
    if (res.kind === 'type_mismatch') console.log(res.hint)  // "rice-scoring scores feature; 3 need mismatched"
    else if (res.kind === 'execution') console.log(res.ranked)
  • frameworkTargetTypes(framework)string[] — the entity types a framework scores. Use it to select the right candidates before calling executePrioritise.
  • executeReflect(store, mode) — modes: assumptions, alternatives, blind-spots, load-bearing (omit to run all four). An unknown mode throws ReflectModeError.

Why this exists

@unified-product-graph/core is the spec and type package. This SDK is the programmatic client that builds on top of it. The Unified Product Graph CLI and MCP server are both thin frontends over this SDK.

Documentation

License

MIT © The Product Creator