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

graphddb

v1.5.0

Published

Graph data modeling on DynamoDB with adjacency list pattern

Readme

GraphDDB

Portable, contract-first DynamoDB access patterns.

GraphDDB lets you define your DynamoDB access patterns once in TypeScript, validate them at build time, inspect the exact execution plan before anything runs, and execute the same contract from five languages — TypeScript, Python, Rust, Go, and PHP. It is not an ORM: it is a contract compiler + runtime for explicit DynamoDB access patterns.

The access patterns become a portable, executable artifact. TypeScript is the single source of truth; behavior-contracts compiles each declaration statically and emits the execution module for every language. What CI pins is zero drift in those artifacts (npm run codegen:check) and byte-identical primitive results across three runtimes (npm run test:vectors). GraphQL-like traversal is one of the authoring surfaces — ergonomic, but not the point. The point is that PK / GSI / projection / relation / cursor / operation limits stay explicit, verifiable, and portable rather than hidden behind an abstraction.

✨ What GraphDDB does — in three layers

1. Define explicit DynamoDB access patterns

  • ✅ Type-safe models —— the TS class is the single source of truth. Keys / GSIs / field types are checked at compile time.
  • ✅ Graph-style traversal —— nest @hasMany / @belongsTo / @hasOne inside select (an ergonomic authoring surface — arguments → key lookup, selection → projection).
  • ✅ CQRS contracts —— public Query/Command contracts as the read/write authoring surface (N+1-safe, context boundaries, composition across contracts).
  • ✅ Maintained access paths —— declare embedded snapshots / aggregate counters kept synchronized atomically with the source write.

2. Validate & explain before anything touches DynamoDB

  • ✅ Compile-time safety —— undefined relations, missing GSIs, limit-less lists, and over-depth traversals are rejected at compile time / by the linter — equally for human- and AI-written code.
  • ✅ Execution plan (Explain) —— explain() returns the exact DynamoDB operations, so RCU shape is inspectable up front.
  • ✅ Automatic access-pattern resolution —— the runtime picks the PK / GSI from the fields you give; selected fields are the only ones read and the only ones in the result type.
  • ✅ In-memory testing —— unit-test planner / traversal / transaction / CDC with no Docker, no DynamoDB Local.

3. Execute the same contract, portably, from five languages

  • ✅ One declaration, every language —— the models and the published contracts are declared once in TypeScript; nothing about an access pattern is restated per language.
  • ✅ 5 language runtimes —— an access pattern is authored once as a @behavior over graphddb's @leaf catalog and compiled by behavior-contracts into a module for TypeScript / Python / Rust / Go / PHP, each binding that language's leaf transports.
  • ✅ Shared vectors —— CI pins that TS / Python / PHP evaluate behavior-contracts' own shared vectors identically (npm run test:vectors). The physical request each language's @leaf transport issues is asserted per language.

Operational surface (all built on the layers above): Middleware (host-side read/write hooks) · CDC emulator (local DynamoDB-Streams-equivalent change events) · CDC projection (@cdcProjected() + fromChange / subscribe into typed records) · Prepared statements (compile-once / execute-many) · CloudFormation generation · Docs generation (Markdown + Mermaid, Handlebars-overridable).

🤔 Philosophy

  • Access patterns as portable contracts —— an access pattern is defined once and becomes a language-neutral, executable artifact. The same contract is validated, explained, and then executed identically across languages, instead of being re-implemented per stack.
  • Native DynamoDB access patterns —— express access patterns directly in code instead of hiding them behind an ORM abstraction. Because PK / GSI, projection, and limits stay visible, the correct design becomes the easiest path.
  • Compile-time safety —— undefined relations, missing GSIs, limit-less lists, and traversals exceeding the allowed depth are rejected before execution at compile time / by the linter. The same constraints apply equally to human-written and AI-generated code.
  • Runtime transparency —— the runtime is not a black box. explain() shows the execution plan, and the plan reveals the shape of RCU usage.
  • Graph-style traversal is a surface, not the essence —— GraphQL's query model (arguments → key lookup, selection → projection, relation → Query/Get/BatchGet) is a convenient way to author access patterns; the essence is the explicit, portable contract underneath.

📦 Install

npm install graphddb
# AWS SDK v3 is a peer dependency. Install the clients you use:
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb

tsx is an optional peer dependency, needed only when running the code-generation CLI (graphddb generate …) directly against TypeScript definitions (library consumers do not need it):

npm install -D tsx

Node.js ≥ 22 is required.

🚀 Quick Start

import { DDBModel, graphddb, model, string, key, k } from 'graphddb';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';

@model({ table: 'AppTable', prefix: 'USER' })
class UserModel extends DDBModel {
  static readonly keys = key<{ userId: string }>((c) => ({
    pk: k`USER#${c.userId}`,
    sk: k`PROFILE`,
  }));

  @string userId!: string;
  @string name!: string;
}
const User = UserModel.asModel();

// Connect the client once. GraphDDB owns throttle / transient retries, so set
// `maxAttempts: 1` to avoid double retries (SDK × library) — see "Retry & throttling" below.
graphddb.config.client(new DynamoDBClient({ maxAttempts: 1 }));

// READ —— only the fields you select appear in the result type.
const user = await User.query({ userId: 'alice' }, { name: true });

// WRITE —— a unified envelope that writes multiple items atomically (default mode: 'transaction').
await graphddb.mutate({
  user: { update: User, key: { userId: 'alice' }, input: { name: 'Alice B.' } },
});

// Raw base operations on a single item (named after the DynamoDB API).
await User.putItem({ userId: 'alice', name: 'Alice' });

Reads center on query (a single PK / unique GSI lookup) / list (multiple items, with a cursor). Writes center on graphddb.mutate (a unified envelope), while putItem / updateItem / deleteItem are positioned secondarily as raw base operations named after the DynamoDB API. For the full API and options, see docs/spec.md.

// query: the 1st argument says "how to find it", the 2nd says "what to return". The return type is inferred from select.
const alice = await User.query({ userId: 'alice' }, { name: true });

// list: keys that return multiple items use a cursor-based list.
const page = await GroupMembership.list({ groupId: 'eng' }, { userId: true, role: true }, { limit: 20 });
// page.items / page.cursor

🔎 Inspect Execution Plans (Explain)

This is what sets GraphDDB apart. explain() returns the DynamoDB operations before they run. select is translated into a ProjectionExpression, so only the requested attributes are read. The plan can be used for testing, debugging, and RCU estimation:

const plan = GroupMembership.explain(
  { groupId: 'eng' },
  { select: { userId: true, role: true }, limit: 10 },
);
// {
//   operations: [{
//     type: "Query",
//     tableName: "UserPermissions",
//     keyCondition: { PK: "GROUP#eng" },
//     rangeCondition: { operator: "begins_with", key: "SK", value: "USER#" },
//     limit: 10,
//   }]
// }

For the complete shape of the plan (such as the rangeCondition separation on a GSI partial match), see docs/spec.md.

🧪 Testing (strongly recommended)

The in-memory executor in graphddb/testing lets you fully unit-test planner / relation traversal / transaction / CDC with no Docker and no DynamoDB Local. Because it never touches a real DynamoDB and runs in-process, you can verify model-mapping, query-plans, relation resolution, and differential aggregation while keeping CI fast and deterministic. See docs/testing.md.

🔗 Relation Traversal

A relation is a query bound to the value of the parent entity. Declare it with @hasMany / @belongsTo / @hasOne and traverse it by nesting inside select. Independent relation subqueries are dispatched in parallel, and N belongsTo resolutions are aggregated into a single BatchGetItem (no N+1):

const alice = await User.query(
  { email: '[email protected]' },
  {
    name: true,
    groups: {
      select: {
        role: true,
        group: { select: { name: true } },   // membership -> Group (belongsTo, BatchGet)
      },
      limit: 20,
    },
  },
  { maxDepth: 2 },   // traversal defaults to depth=1. Deeper traversal must be explicitly allowed.
);

hasMany / list require a default / max limit. For the complete relation model, see docs/spec.md.

🧩 Maintained Access Paths

Declaring a relation or scalar as a maintained access path keeps a separate row in sync with the lifecycle of the source entity (all of it composed into the same atomic transaction as the source write):

  • embedded snapshot —— pattern: 'embeddedSnapshot' on @hasMany / @hasOne keeps a projected snapshot / bounded collection synchronized onto the owner row.
  • aggregate counter —— @aggregate + count() keeps a scalar aggregate synchronized with an atomic ADD ±1.
  • materialized view / sparse view —— @model({ kind }) + @maintainedFrom declare a view as its own model (later phases).
  • versioned history —— maintenance of versioned history rows.

Synchronized maintenance (updateMode: 'mutation') works in both TS and Python (conformance-verified). For how to declare them, the pattern mapping table, and per-phase coverage, see docs/design-patterns.md. Examples: embedded-snapshot-pattern / aggregate-counter.

⚙️ Runtime

The runtime's responsibilities are short. For details, see docs/spec.md:

  • access pattern resolution —— pick the PK / GSI from the fields you give.
  • projection —— compile select into a ProjectionExpression.
  • relation traversal —— dispatch bound queries in parallel (N+1-safe BatchGet).
  • retry / throttling —— retry throttle / transient errors with full-jitter exponential backoff, no configuration required (set the client to maxAttempts: 1, since the library owns retries).
  • hydration —— reconstruct raw items into typed partial entities.

🛠 More Capabilities

Each capability is summarized here only. For details, operator lists, and procedures, see each doc:

  • Filtering —— a declarative, type-safe server-side filter (compatible with FilterExpression / AppSync ModelFilterInput). Model.col + the cond raw escape hatch are shared by read filters / write conditions. For the operator list, see docs/spec.md.
  • Sort-key range —— list(key, select, { range: { day: { lt: d } }, order: 'DESC', limit: 1 }) bounds the sort-key segment right after the ones the key supplies in the KeyConditionExpression (the segments after it are free; the same on a GSI, a relation's child list, the envelope and a prepared statement; limit counts inside the range). See docs/spec.md §11.
  • Middleware / Hooks —— graphddb.config.use for host-side hooks on reads / writes (logging, metrics, tenant / authorization scoping). Host-only and non-serialized. For hook points, see docs/middleware.md.
  • Class hydration —— options.hydrate loads read results into host-language domain objects (opt-in, Phase 1: query top level). docs/class-hydration.md.
  • Multi-language bridge —— TS is the SSoT for the models; a behavior compiles to a module for each of the 5 languages, and each runtime hosts that language's leaf transports. docs/python-bridge.md.
  • Prepared statements —— graphddb.prepare($ => ({...})) → .execute(params) (unified read/write). "Contract-free precompilation": compile a declarative route once and execute it repeatedly with different params. graphddb transform prepared --write rewrites inline call sites to a module-scope lazy slot (the plan compiles once per module, at first use); without the transform, behavior stays identical through structural memoization. no-runtime-capture is loudly enforced by a build-time lint (graphddb transform prepared) + a runtime guard. docs/prepared-statements.md.
  • CQRS contracts —— public Query/Command contracts (cardinality matrix, N+1 safety, context boundaries, composition across contracts). docs/cqrs-contract.md / docs/mutation-command-derivation.md.
  • CDC emulator —— change events equivalent to DynamoDB Streams, to drive and test differential aggregation. docs/cdc-emulator.md.
  • CDC projection —— a typed-consumer-IF that parses the CDC change events of a @cdcProjected() source model into typed records. Model.fromChange(event) returns [old, new], and graphddb.subscribe(handlers) (the sibling of query / mutate) produces a ChangeHandler the consumer mounts on its own stream. graphddb owns parse→typed record only; subscription, sink delivery, and idempotency are the consumer's. docs/cdc-projection.md.
  • Multi-language execution —— an operation is a behavior: the consumer authors it as a @behavior method over graphddb's @leaf catalog in ordinary TypeScript, and bc generate reads that source statically — never executing it — and emits the module each language runs. graphddb supplies only the leaf transports the module binds: one declared port set → one physical DynamoDB request. The module takes one of two shapes. Python / PHP / TypeScript carry the IR in the module as a native literal and execute it with the shared run_behavior. Go / Rust get typed-native code — direct field access on concrete structs, a local error type, no run_behavior, zero bc-runtime import; because it has the same shape a careful engineer hand-writes against the SDK (build key → call → unmarshal into a struct → fan out), it runs at SDK-floor parity with the hand-written SDK baseline. TypeScript can also emit straight-line native code. Runtime-freeness is guaranteed by behavior-contracts at generation time: a shape it cannot cover natively fails closed, never silently boxed. Generated Rust modules use std::sync::LazyLock, so Rust >= 1.80 is required. Measured results: benchmark/crosslang/CROSS-LANG.md.
  • CloudFormation generation —— graphddb generate cloudformation emits a CloudFormation template for the DynamoDB table(s) a model set maps onto (GSI union / Streams / TTL / PITR / PROVISIONED / Auto Scaling). docs/cloudformation.md.
  • Docs generation —— graphddb generate docs renders a human-facing model specification (Table Overview / ER diagram / Key Schema / Property Matrix / Entity Details / Access Patterns / Mutation Contracts / Maintained Access Paths / CDC / CloudFormation) as Markdown + Mermaid. The layout is defined by bundled Handlebars templates that users can customize with --template-dir (per-partial override) and --helpers (extra helpers). docs/docs-generation.md.

📚 Documentation

The full documentation is bundled in the npm package (under docs/). After installation you can browse every doc at node_modules/graphddb/docs/, and if you add it as a devDependency you have all the documentation available offline at hand. On GitHub, follow the links in the table below.

| Document | Description | |----------|-------------| | Specification | Core API: entities, structured keys/GSIs, query/filter, relations, batch/transaction, design rules, runtime behavior. | | Design patterns | Mapping representative DynamoDB design patterns onto graphddb features (how to declare pattern / @model({ kind }) + @maintainedFrom / @aggregate, read & write maintenance, per-phase coverage). | | Middleware / Hooks | The graphddb.config.use host-side middleware: hook points read R1–R5 / write W1–W5, { context }, ordering. | | Prepared statements | graphddb.prepare / .execute (unified read/write prepared statements), the no-runtime-capture constraint, graphddb transform prepared (compile-time hoisting + build-time lint): choosing between lazy slot > structural memoization > ad-hoc. | | CQRS contract layer | Public Query/Command contracts, cardinality matrix, N+1 safety, composition across contracts, context boundaries. | | Mutation → command derivation | The internal write-plan composition DSL behind the Command IF (entityWrites, fragment composition, derivation of atomic TransactWriteItems). | | Class hydration | Opt-in options.hydrate that loads read results into host objects (Phase 1: query top level). | | Multi-language bridge | TS as the SSoT for the models, and the Python / Rust / Go / PHP runtimes that host the leaf transports a behavior module binds. | | In-memory test adapter | Docker-free in-process testing with graphddb/testing and MemoryInspector. | | CDC emulator | A change-event emulator for differential aggregation patterns (dev/test). | | CDC projection | The @cdcProjected() + fromChange / subscribe typed-consumer-IF: a contract that parses CDC change events into typed records. Boundary (parse→typed is graphddb; subscription, sink delivery, idempotency are the consumer's). | | CloudFormation generation | The generate cloudformation command: emit a CloudFormation template for the DynamoDB table(s) a model set maps onto (GSI union / Streams / TTL / PITR / TableClass / SSE and KMS key / deletion policy and protection / Output exports / PROVISIONED / Auto Scaling). All options, derivation rules, and scope boundary. | | Docs generation | The generate docs command: generate a model specification (Markdown + Mermaid) from the TS models. All options, output sections, and customizing the Handlebars templates with --template-dir / --helpers. | | Benchmark | Library comparison against hand-written AWS SDK, ElectroDB, DynamoDB Toolbox, and OneTable. |

💡 Examples

| Example | Description | |---------|-------------| | user-permissions | Manages users, groups, and permissions with Single Table Design. Includes adjacency lists, inverted indexes, relation traversal, and explain. | | aggregate-tree-pattern | Differential tree aggregation (siteScoreAverage) driven by the CDC emulator: dirty propagation, throttled sweeps, recomputation. | | embedded-snapshot-pattern | Treating a relation as a maintained access path: pattern: 'embeddedSnapshot' keeps a denormalized collection / snapshot synchronized onto the owner row in the same atomic transaction as the source write. | | aggregate-counter | The @aggregate counter from RFC §5.2: the ThreadPost.created / removed lifecycle keeps ThreadCounter.postCount synchronized via an atomic ADD ±1 within the same TransactWriteItems as the source write. |

🏛 Architecture

A query travels from the TS Model through the Planner / Runtime to DynamoDB:

Query execution pipeline: TS Model → Planner → Runtime → DynamoDB

From the same TS Model (the SSoT), multiple targets are derived. A behavior's execution module is emitted for all 5 languages by behavior-contracts' static compilation (in Python and PHP the emitted module calls graphddb's leaf transports; Go and Rust are native codegen), and the model layer feeds the CQRS contracts, the CloudFormation template, and the documentation:

SSoT derivation: TS Model → TS runtime / CQRS / CloudFormation / docs, with the behavior module feeding the 4 language runtimes

| Runtime | Package | Distribution | |---------|---------|--------------| | TypeScript | graphddb | npm | | Python | graphddb-runtime | PyPI | | Rust | graphddb_runtime | crates.io | | Go | github.com/foo-log-inc/graphddb/go | go module (go/vX.Y.Z tags) | | PHP | graphddb/runtime | bundled in-repo (not on composer) |

A behavior reaches DynamoDB the same way in every language — the module's shape is what differs (see "Multi-language execution" under More Capabilities):

How a behavior executes: authoring → bc generate → the generated module → embedded IR (Python/PHP) / typed-native (Go/Rust) / in-process (TypeScript)

The figures are self-contained SVGs drawn with diagram-contracts (Draw TSX). Sources live in diagrams/; regenerate with diagrams/build.sh.

📄 License

MIT