graphddb 0.7.10 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +6 -6
  2. package/dist/cdc/index.d.ts +389 -5
  3. package/dist/cdc/index.js +4 -4
  4. package/dist/{chunk-AD6ZQTTE.js → chunk-GS4C5VGO.js} +2 -6
  5. package/dist/{chunk-DFUKGU2Q.js → chunk-HNY2EJPV.js} +216 -229
  6. package/dist/{chunk-3ZU2VW3L.js → chunk-L2NEDS7U.js} +582 -781
  7. package/dist/chunk-L4QRCHRQ.js +278 -0
  8. package/dist/chunk-LAT64YCZ.js +1987 -0
  9. package/dist/chunk-S2NI4PBW.js +187 -0
  10. package/dist/{chunk-EOJDN3SA.js → chunk-T44OB5GU.js} +3757 -6138
  11. package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
  12. package/dist/cli.js +63 -254
  13. package/dist/index.d.ts +23 -1550
  14. package/dist/index.js +94 -1850
  15. package/dist/internal/index.d.ts +84 -0
  16. package/dist/internal/index.js +701 -0
  17. package/dist/{maintenance-view-adapter-BAZ9uBGe.d.ts → key-DZtjAQDh.d.ts} +573 -1817
  18. package/dist/linter/index.d.ts +39 -7
  19. package/dist/linter/index.js +22 -4
  20. package/dist/{registry-LWE54Sdc.d.ts → linter-DQY7gUEk.d.ts} +22 -22
  21. package/dist/prepared-artifact-HFealr1q.d.ts +281 -0
  22. package/dist/spec/index.d.ts +506 -5
  23. package/dist/spec/index.js +22 -18
  24. package/dist/testing/index.d.ts +2 -3
  25. package/dist/testing/index.js +4 -4
  26. package/dist/transform/index.d.ts +1 -1
  27. package/dist/transform/index.js +4 -4
  28. package/dist/{types-BQLzTEqh.d.ts → types-2PMXEn5x.d.ts} +8 -10
  29. package/dist/types-DW__-Icc.d.ts +450 -0
  30. package/docs/cdc-projection.md +5 -5
  31. package/docs/class-hydration.md +1 -1
  32. package/docs/cqrs-contract.md +79 -20
  33. package/docs/design-patterns.md +5 -5
  34. package/docs/docs-generation.md +6 -6
  35. package/docs/middleware.md +15 -15
  36. package/docs/mutation-command-derivation.md +52 -42
  37. package/docs/prepared-statements.md +14 -14
  38. package/docs/python-bridge.md +96 -65
  39. package/docs/spec.md +153 -124
  40. package/docs/testing.md +9 -8
  41. package/package.json +14 -4
  42. package/dist/chunk-3UD3XIF2.js +0 -860
  43. package/dist/chunk-MMVHOUM4.js +0 -24
  44. package/dist/from-change-Ty95KA8C.d.ts +0 -327
  45. package/dist/index-Dc7d8mWI.d.ts +0 -1089
  46. package/dist/relation-depth-BRS513Tq.d.ts +0 -36
package/docs/spec.md CHANGED
@@ -28,7 +28,7 @@ own specs:
28
28
  local development and tests.
29
29
  - [`testing.md`](./testing.md) — the in-memory test adapter (`graphddb/testing`).
30
30
  - [`middleware.md`](./middleware.md) — host-side read & write middleware / hooks
31
- (`DDBModel.use`): read R1–R5, write W1–W5, registration, per-call `context`,
31
+ (`graphddb.config.use`): read R1–R5, write W1–W5, registration, per-call `context`,
32
32
  ordering, cancellation / recovery, and Python parity.
33
33
 
34
34
  For a comparison against other DynamoDB libraries, see
@@ -59,7 +59,7 @@ For a comparison against other DynamoDB libraries, see
59
59
  19. [Update](#19-update)
60
60
  20. [Delete](#20-delete)
61
61
  21. [Conditional writes](#21-conditional-writes)
62
- 22. [The unified envelope — `DDBModel.query` / `DDBModel.mutate`](#22-the-unified-envelope--ddbmodelquery--ddbmodelmutate)
62
+ 22. [The unified envelope — `graphddb.query` / `graphddb.mutate`](#22-the-unified-envelope--graphddbquery--graphddbmutate)
63
63
  23. [Batch operations](#23-batch-operations)
64
64
  24. [The Linter and design rules](#24-the-linter-and-design-rules)
65
65
  25. [Runtime responsibilities (pipeline)](#25-runtime-responsibilities-pipeline)
@@ -113,30 +113,29 @@ applied by the caller on the already-typed result (`result.items.filter(...)`,
113
113
 
114
114
  ## 2. Connection and client configuration
115
115
 
116
- GraphDDB uses a process-global client (`ClientManager`) and an optional
117
- table-name remap (`TableMapping`). Configure them via the static methods on
118
- `DDBModel` before issuing any operation.
116
+ GraphDDB uses a process-global client and an optional table-name remap.
117
+ Configure them via the `graphddb.config.*` surface before issuing any operation.
119
118
 
120
119
  ```ts
121
120
  import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
122
- import { DDBModel } from 'graphddb';
121
+ import { graphddb } from 'graphddb';
123
122
 
124
- DDBModel.setClient(new DynamoDBClient({ region: 'us-east-1' }));
123
+ graphddb.config.client(new DynamoDBClient({ region: 'us-east-1' }));
125
124
 
126
125
  // Optional: remap declared table names → physical table names (e.g. per stage).
127
- DDBModel.setTableMapping({ UserPermissions: 'UserPermissions-prod' });
126
+ graphddb.config.tables({ UserPermissions: 'UserPermissions-prod' });
128
127
  ```
129
128
 
130
129
  | Method | Signature | Behavior |
131
130
  | --- | --- | --- |
132
- | `DDBModel.setClient` | `(client: DynamoDBClient) => void` | Stores the client globally and discards any cached document client. |
133
- | `DDBModel.setTableMapping` | `(mapping: Record<string, string>) => void` | Replaces the declared-name → physical-name map. |
131
+ | `graphddb.config.client` | `(client: DynamoDBClient) => void` | Stores the client globally and discards any cached document client. |
132
+ | `graphddb.config.tables` | `(mapping: Record<string, string>) => void` | Replaces the declared-name → physical-name map. |
134
133
 
135
134
  - The low-level marshalling uses a `DynamoDBDocumentClient` (`@aws-sdk/lib-dynamodb`),
136
135
  created lazily from the configured client and cached. Values you pass and
137
136
  receive are plain JS (un-marshalled).
138
137
  - If no client is set, the first operation throws:
139
- `DynamoDB client is not configured. Call DDBModel.setClient(new DynamoDBClient({...})) first.`
138
+ `DynamoDB client is not configured. Call graphddb.config.client(new DynamoDBClient({...})) first.`
140
139
  - `TableMapping.resolve(name)` returns the mapped physical name, or the declared
141
140
  name unchanged when no mapping entry exists.
142
141
 
@@ -637,7 +636,7 @@ composed snapshot×2 IR. (A self-co-located form is a deferred follow-up.)
637
636
  External projection's old `defineProjection` / `ProjectionSinkDrain` runtime was
638
637
  **removed in 0.4.0** and is now a typed-consumer-IF contract (#153):
639
638
  the source model is marked `@cdcProjected()`, `Model.fromChange(event)` parses a CDC
640
- change event into `[oldRecord, newRecord]`, and `DDBModel.subscribe(handlers)` returns a
639
+ change event into `[oldRecord, newRecord]`, and `graphddb.subscribe(handlers)` returns a
641
640
  `ChangeHandler` the consumer mounts on its own stream (subscription / sink delivery /
642
641
  idempotency stay with the consumer). See [`cdc-projection.md`](./cdc-projection.md).
643
642
 
@@ -715,10 +714,13 @@ export const GroupMembership = GroupMembershipModel.asModel();
715
714
  | `updateItem(entity, changes, options?)` | write | partial update (§19) |
716
715
  | `deleteItem(key, options?)` | write | delete (§20) |
717
716
 
718
- The static, class-level methods (`setClient`, `setTableMapping`, `query`,
719
- `mutate`, `batchGet`, `batchWrite`, `asModel`) live on `DDBModel` itself. The
720
- unified-envelope `DDBModel.query` / `DDBModel.mutate` (§22) drive multi-route
721
- reads and lifecycle-aware writes.
717
+ The per-model methods above come from `Model.asModel()`. Host-runtime config
718
+ (client / table mapping / retry / middleware) lives on `graphddb.config.*` (not
719
+ `DDBModel`), and the unified-envelope execution verbs are `graphddb.query` /
720
+ `graphddb.mutate` (§22) — they drive multi-route reads and lifecycle-aware writes,
721
+ with batch read folded into `graphddb.query({ key: K[] })` and batch write into
722
+ `graphddb.mutate({ mode: 'parallel' })`. `DDBModel` itself carries only
723
+ `mutate` / `prepare` / `subscribe` / `asModel`.
722
724
 
723
725
  ---
724
726
 
@@ -979,9 +981,9 @@ filter: cond`${User.col.age} > ${18} and attribute_exists(${User.col.email})`
979
981
  `and` / `or`, or as `not`.
980
982
  - `cond` is **not read-only**: it is equally a valid `WriteCondition<M>` (issue
981
983
  #114-B), so it may be used as a write `condition` on `putItem` / `updateItem` /
982
- `deleteItem`, on a transaction item / `ConditionCheck`, and in the declarative
983
- `mutation(...)` / public command `condition` (where its value slots may carry
984
- `param.*` bindings — see [cqrs-contract](./cqrs-contract.md)).
984
+ `deleteItem`, on a transaction item / `ConditionCheck`, and in a declarative
985
+ `graphddb.publishCommand` descriptor `condition` (where its value slots may
986
+ carry `param.*` bindings — see [cqrs-contract](./cqrs-contract.md)).
985
987
  - **Nesting constraint (serializable surfaces):** a `cond` used as a **whole**
986
988
  condition is fully supported everywhere. A `cond` **nested inside** a
987
989
  declarative `and` / `or` / `not` group works **in-process** (TS/memory), but
@@ -1142,7 +1144,7 @@ Behavior:
1142
1144
  - The input type `EntityInput<T>` includes scalar and embedded fields and
1143
1145
  **excludes relations** (relations are not persisted by `putItem`).
1144
1146
  - `putItem` / `updateItem` / `deleteItem` are the **raw base operations**. For
1145
- lifecycle-aware single writes, go through `DDBModel.mutate(...)` (§22) rather
1147
+ lifecycle-aware single writes, go through `graphddb.mutate(...)` (§22) rather
1146
1148
  than the raw primitives.
1147
1149
  - The runtime serializes each declared field, computes `{ pk, sk }` from the
1148
1150
  primary key, and writes `{ PK: '<prefix><pk>', SK: String(sk), ...fields }`.
@@ -1206,9 +1208,9 @@ same `UpdateExpression`** — the index follows the row instead of silently rott
1206
1208
  `{ rederive: 'read-modify-write' }` to read the current item, merge, re-derive
1207
1209
  from the full image, and write back under an optimistic condition.
1208
1210
 
1209
- Transaction updates (`defineTransaction` … `tx.update`) apply the same rule: the
1210
- affected index keys are re-derived into the update at build time, or the build
1211
- throws when a composing field is unavailable.
1211
+ Transactional updates (`graphddb.mutate({ mode: 'transaction' })` with an
1212
+ `update` op) apply the same rule: the affected index keys are re-derived into the
1213
+ update at build time, or the build throws when a composing field is unavailable.
1212
1214
 
1213
1215
  ### Key resolution (3-stage)
1214
1216
 
@@ -1334,20 +1336,20 @@ the AWS SDK's `ConditionalCheckFailedException`.
1334
1336
 
1335
1337
  ---
1336
1338
 
1337
- ## 22. The unified envelope — `DDBModel.query` / `DDBModel.mutate`
1339
+ ## 22. The unified envelope — `graphddb.query` / `graphddb.mutate`
1338
1340
 
1339
1341
  Multi-route reads and lifecycle-aware writes are issued through a single
1340
1342
  in-process **unified envelope**: a **descriptor map** keyed by caller-chosen
1341
1343
  aliases, where each value is a route descriptor naming a model and an operation.
1342
1344
  The result is an object with the same aliases.
1343
1345
 
1344
- ### Read envelope — `DDBModel.query(map)`
1346
+ ### Read envelope — `graphddb.query(map)`
1345
1347
 
1346
- `DDBModel.query(map)` runs each alias route **independently and in parallel**
1348
+ `graphddb.query(map)` runs each alias route **independently and in parallel**
1347
1349
  (there is no cross-route consistency — these are parallel independent reads):
1348
1350
 
1349
1351
  ```ts
1350
- const { user, members } = await DDBModel.query({
1352
+ const { user, members } = await graphddb.query({
1351
1353
  user: { query: User, key: { userId: 'u1' }, select: { name: true } },
1352
1354
  members: { list: GroupMembership, key: { groupId: 'eng' }, select: { role: true }, options: { limit: 20 } },
1353
1355
  });
@@ -1362,14 +1364,14 @@ A **read route descriptor** is `{ query | list: Model, key, select, options? }`
1362
1364
  - `options` for `query`: `{ consistentRead?, maxDepth? }`; for `list`:
1363
1365
  `{ limit?, after?, order?, filter? }`.
1364
1366
 
1365
- ### Write envelope — `DDBModel.mutate(map, { mode })`
1367
+ ### Write envelope — `graphddb.mutate(map, { mode })`
1366
1368
 
1367
- `DDBModel.mutate(map, { mode })` performs lifecycle-aware writes. This is the
1369
+ `graphddb.mutate(map, { mode })` performs lifecycle-aware writes. This is the
1368
1370
  path for **single** writes too — the raw primitives `putItem` / `updateItem` /
1369
1371
  `deleteItem` (§18–20) are base ops, not lifecycle-aware.
1370
1372
 
1371
1373
  ```ts
1372
- const res = await DDBModel.mutate({
1374
+ const res = await graphddb.mutate({
1373
1375
  a: { create: GroupMembership, key: { groupId: 'eng', userId: 'u1' }, input: { role: 'admin' } },
1374
1376
  b: { update: User, key: { userId: 'u1' }, input: { name: 'Ann' }, condition: { status: 'active' }, result: { select: { name: true } } },
1375
1377
  }, { mode: 'transaction' });
@@ -1408,47 +1410,47 @@ no cross-route consistency.
1408
1410
 
1409
1411
  ## 23. Batch operations
1410
1412
 
1411
- ```ts
1412
- DDBModel.batchGet(requests: BatchGetRequest[]): Promise<BatchGetResult>;
1413
- DDBModel.batchWrite(requests: BatchWriteRequest[]): Promise<void>;
1414
- ```
1413
+ 0.8.0 folds the standalone `DDBModel.batchGet` / `batchWrite` statics into the
1414
+ two execution verbs:
1415
1415
 
1416
- ```ts
1417
- interface BatchGetRequest { model: ModelStatic<T>; keys: Record<string, unknown>[]; }
1418
-
1419
- type BatchWriteRequest =
1420
- | { type: 'put'; model; item: Record<string, unknown>; options?: PutOptions }
1421
- | { type: 'delete'; model; key: Record<string, unknown>; options?: DeleteOptions };
1422
- ```
1416
+ - **Batch read** = `graphddb.query({ key: K[] })` — a pure point read over an
1417
+ array of keys routes to a chunked `BatchGetItem`.
1418
+ - **Batch write** = `graphddb.mutate({ mode: 'parallel' })` — a non-atomic write
1419
+ envelope routes to a chunked `BatchWriteItem` (per-op partial success).
1423
1420
 
1424
1421
  ```ts
1425
- const result = await DDBModel.batchGet([
1426
- { model: User, keys: [{ userId: 'alice' }, { userId: 'bob' }] },
1427
- ]);
1428
- const users = result.get(User); // Partial<UserModel>[]
1429
-
1430
- await DDBModel.batchWrite([
1431
- { type: 'put', model: User, item: { userId: 'dave', /* */ } },
1432
- { type: 'delete', model: GroupMembership, key: { groupId: 'eng', userId: 'bob' } },
1433
- ]);
1422
+ // Batch read: an array of keys for one model → chunked BatchGetItem.
1423
+ const users = await graphddb.query({
1424
+ users: { query: User, key: [{ userId: 'alice' }, { userId: 'bob' }], select: { userId: true } },
1425
+ });
1426
+ // users.users → (Partial<UserModel> | null)[]
1427
+
1428
+ // Batch write: mode 'parallel' chunked BatchWriteItem, per-op partial success.
1429
+ await graphddb.mutate(
1430
+ {
1431
+ add: { create: User, key: { userId: 'dave' }, input: { /* … */ } },
1432
+ remove: { remove: GroupMembership, key: { groupId: 'eng', userId: 'bob' } },
1433
+ },
1434
+ { mode: 'parallel' },
1435
+ );
1434
1436
  ```
1435
1437
 
1436
1438
  Behavior and limits:
1437
1439
 
1438
- - **`batchGet`** groups keys by physical table, fetches all scalar fields,
1439
- hydrates them (merging back any embedded maps), and returns a `BatchGetResult`
1440
- whose `.get(model)` yields the items for that model (`Partial<T>[]`). Keys are
1441
- matched back to items by `PK::SK`.
1442
- - **`batchWrite`** groups by table, builds put/delete inputs, and writes.
1440
+ - **Batch read** groups keys by physical table, fetches the requested scalar
1441
+ fields, hydrates them (merging back any embedded maps), and returns the items in
1442
+ the query envelope. Keys are matched back to items by `PK::SK`.
1443
+ - **Batch write** (`mode: 'parallel'`) groups by table, builds put/delete inputs,
1444
+ and writes with per-op partial success (`{ ok }` | `{ error }`).
1443
1445
  - **Chunking:** keys/items are split into chunks of `BATCH_GET_MAX_KEYS = 100`
1444
1446
  (get) and `BATCH_WRITE_MAX_ITEMS = 25` (write) and issued per chunk.
1445
1447
  - **Retry:** unprocessed keys/items are retried with exponential backoff
1446
1448
  (`50·2^(n-1)` ms, capped at 1000 ms) up to `BATCH_MAX_RETRY_ATTEMPTS = 10`
1447
1449
  retries, after which it throws (rather than looping forever) — typically a sign
1448
1450
  of sustained throttling.
1449
- - Item-level `condition` options on `batchWrite` puts/deletes are accepted in the
1450
- request type but DynamoDB BatchWrite does not support per-item conditions; use
1451
- transactions for conditional atomic writes.
1451
+ - Per-op `condition`s are not supported on `mode: 'parallel'` (DynamoDB
1452
+ BatchWrite has no per-item conditions); use `mode: 'transaction'` for
1453
+ conditional atomic writes.
1452
1454
 
1453
1455
  ---
1454
1456
 
@@ -1482,16 +1484,26 @@ resolver instead.
1482
1484
 
1483
1485
  ### Customizing
1484
1486
 
1487
+ The linter (`Linter` + the built-in rules) is a build/dev-time surface served by
1488
+ the dedicated `graphddb/linter` subpath:
1489
+
1485
1490
  ```ts
1486
- import { MetadataRegistry, Linter, noScanRule, requireLimitRule } from 'graphddb';
1491
+ import { Linter, noScanRule, requireLimitRule } from 'graphddb/linter';
1487
1492
 
1488
1493
  const linter = new Linter();
1489
1494
  linter.addRule(noScanRule);
1490
1495
  linter.addRule(requireLimitRule);
1491
- MetadataRegistry.linter = linter; // set BEFORE defining/registering models
1492
- // MetadataRegistry.resetLinter(); // restore the default set
1493
1496
  ```
1494
1497
 
1498
+ Attaching a custom `Linter` to the entity registry is an advanced build-time
1499
+ concern: the `MetadataRegistry` the linter runs against is internal machinery
1500
+ (0.8.0 demoted it off the public root), so the registry-wiring step
1501
+ (`MetadataRegistry.linter = linter`, set **before** defining/registering models;
1502
+ `MetadataRegistry.resetLinter()` to restore the default set) lives inside
1503
+ graphddb's own codegen/spec tooling. Application code selects rules through
1504
+ `graphddb/linter` and relies on the default set that runs automatically at model
1505
+ registration.
1506
+
1495
1507
  > The `user-permissions` example installs a custom linter without `gsi-ambiguity`
1496
1508
  > on purpose: `GroupMembership` uses `[userId]` as its GSI input, a subset of its
1497
1509
  > PK input `[groupId, userId]`. The runtime resolves this unambiguously (exact
@@ -1545,7 +1557,7 @@ position (§12). Post-load TypeScript filtering is not part of this pipeline (§
1545
1557
 
1546
1558
  | Condition | Message (abridged) | Where |
1547
1559
  | --- | --- | --- |
1548
- | No client configured | `DynamoDB client is not configured. Call DDBModel.setClient(new DynamoDBClient({...})) first.` | first op |
1560
+ | No client configured | `DynamoDB client is not configured. Call graphddb.config.client(new DynamoDBClient({...})) first.` | first op |
1549
1561
  | Class not `@model`-decorated | `No metadata registered for '<name>'. Did you apply @model()?` | metadata access |
1550
1562
  | No matching access pattern | `No access pattern found for fields […]. Available: …` | key resolution |
1551
1563
  | `query()` on non-unique GSI | `Cannot use query() with non-unique GSI '<i>'. Use list() instead.` | `query` |
@@ -1562,68 +1574,85 @@ position (§12). Post-load TypeScript filtering is not part of this pipeline (§
1562
1574
 
1563
1575
  ---
1564
1576
 
1565
- ## 27. Public API surface
1566
-
1567
- The package entry point (`graphddb`) exports:
1568
-
1569
- **Base class & model API**
1570
- `DDBModel`, type `ModelStatic`.
1571
-
1572
- **Decorators & key builders**
1573
- `model`, `field`, `string`, `number`, `boolean`, `datetime`, `binary`,
1574
- `stringSet`, `numberSet`, `list`, `map`, `literal`, `key`, `k`, `isKeySegment`,
1575
- `gsi`, `embedded`, `hasMany`, `belongsTo`, `hasOne`, `aggregate`, `count`, `max`;
1576
- types `ModelOptions`, `KeyDefinitionMarker`, `KeySegment`, `KeySlot`,
1577
- `SegmentSpec`, `KeyStructure`, `SegmentedKey`, `GsiOptions`,
1578
- `GsiDefinitionMarker`. (`aggregate` / `count` / `max` are the Epic #118 §5.2
1579
- scalar-aggregate declaration surface; their runtime is #141.)
1580
-
1581
- **Maintained access path (Epic #118)**
1582
- `buildMaintenanceGraph` and the `preview` / `identity` projection helpers (the
1583
- function-form projection IR for relation `projection` options); maintenance IR
1584
- types (`MaintainTrigger`, `MaintainEffect`, `SnapshotEffect`, `CollectionEffect`,
1585
- `ProjectionTransform`, …) and relation-option types (`RelationPattern`,
1586
- `RelationReadOptions`, `RelationWriteOptions`, `RelationProjection`,
1587
- `RelationOptions`, `AggregateValue`, `AggregateOptions`, `AggregateMetadata`).
1588
-
1589
- **Client**
1590
- `ClientManager`, `TableMapping`.
1591
-
1592
- **Type-safe query/select types**
1593
- `SelectableOf`, `RelationSelect`, `RelationSpec`, `QueryResult`, `PrimaryKeyOf`,
1594
- `QueryKeyOf`, `UniqueQueryKeyOf`, `PartialQueryKeyOf`, `QueryOptions`,
1595
- `ListOptions`, `FilterInput`, `SelectBuilder`, `RelationBuilder`, `SelectOf`,
1596
- `Updatable`.
1597
-
1598
- **Filter / select runtime**
1599
- `cond`, `isColumn`, `isSelectBuilder`; types `RawCondition`, `CondSlot`, `Column`,
1600
- `ColumnMap`.
1601
-
1602
- **Metadata**
1603
- `MetadataRegistry`, `derivePrefix`, `validateGsiAmbiguity`; entity-metadata
1604
- types.
1605
-
1606
- **Linter**
1607
- `Linter`, `createDefaultLinter`, `noScanRule`, `requireLimitRule`,
1608
- `queryBoundaryRule`, `gsiAmbiguityRule`, `missingGsiRule`, `relationDepthRule`,
1609
- plus the Epic #118 maintenance/preset rules (`samePartitionPresetRule`,
1610
- `missingContextRule`, `embeddedSnapshotSizeRule`, `hotPartitionRule`,
1611
- `fanOutRule`, `multiMaintainerSameRowRule`); types `LintRule`, `LintResult`. The
1612
- default set (`createDefaultLinter`) includes every rule **except**
1613
- `queryBoundaryRule`; the maintenance rules are no-ops for relations that declare
1614
- no `write.maintainedOn`.
1615
-
1616
- **Lower-level building blocks** (for advanced/transaction/batch use)
1617
- `executePut`, `executeUpdate`, `executeDelete`, `executeQuery`, `executeList`,
1618
- `executeExplain`, `executeTransaction`, `executeBatchGet`, `executeBatchWrite`,
1619
- `buildPutInput`, `buildUpdateInput`, `buildDeleteInput`, `serializeFieldValue`,
1620
- `TransactionContext`, `BatchGetResult`, `attachModelClass`, `MAX_TRANSACT_ITEMS`
1621
- (25), `BATCH_GET_MAX_KEYS` (100), `BATCH_WRITE_MAX_ITEMS` (25); expression
1622
- builders `buildUpdateExpression`, `buildConditionExpression`,
1623
- `compileFilterExpression`, `evaluateFilter`; planner `resolveKey`,
1624
- `buildProjection`, `plan`; executor `execute`; `hydrate`; pagination
1625
- `encodeCursor` / `decodeCursor`; relation `detectRelationFields`,
1626
- `getImplicitKeyFields`, `validateDepth`, `resolveRelations`.
1577
+ ## 27. Public API surface (0.8.0)
1578
+
1579
+ The 0.8.0 surface is **two pillars** from the root entry point plus a handful of
1580
+ dedicated subpaths. Everything the old flat root barrel exported for runtime
1581
+ plumbing (executors, planner, expression builders, `ClientManager` /
1582
+ `TableMapping`, `MetadataRegistry`, pagination codec, spec/CDC/linter builders,
1583
+ the `define*` DSL) is either folded into the two pillars or moved to a subpath —
1584
+ see the 0.8.0 migration table in `CHANGELOG.md`.
1585
+
1586
+ ```ts
1587
+ import { graphddb, DDBModel } from 'graphddb';
1588
+ ```
1589
+
1590
+ **Pillar 1 `graphddb.*` (the namespace)**
1591
+ - **Authoring**: `graphddb.publishQuery`, `graphddb.publishCommand` (the sole CQRS
1592
+ read/write authoring verbs).
1593
+ - **Execution**: `graphddb.query`, `graphddb.mutate`, `graphddb.prepare`,
1594
+ `graphddb.subscribe`. `mutate({ mode: 'transaction' })` is the atomic path
1595
+ (folds in the old `DDBModel.transaction` / `batchWrite`); a pure point batch
1596
+ read is `query({ key: K[] })` (routes to chunked `BatchGetItem`; folds in the
1597
+ old `DDBModel.batchGet`); a batch write is `mutate({ mode: 'parallel' })`.
1598
+ - **Config**: `graphddb.config.*` `client` / `getClient` / `tables` / `retry` /
1599
+ `use` / `clearMiddleware` (the host-runtime config surface; folds in the old
1600
+ `DDBModel.setClient` / `setTableMapping` / `setRetryPolicy` / `use` /
1601
+ `clearMiddleware`).
1602
+
1603
+ **Pillar 2 — `DDBModel`**
1604
+ `DDBModel`, type `ModelStatic`; the CDC binding types `CdcModelRegistry`,
1605
+ `CdcSubscribeHandlers`. Execution/config statics are gone from `DDBModel` — use
1606
+ `graphddb.*` above.
1607
+
1608
+ **Kept named authoring primitives (schema DSL + CQRS body helpers)**
1609
+ - Decorators & key builders: `model`, `field`, `string`, `number`, `boolean`,
1610
+ `datetime`, `binary`, `stringSet`, `numberSet`, `list`, `map`, `literal`, `ttl`,
1611
+ `key`, `k`, `gsi`, `embedded`, `hasMany`, `belongsTo`, `hasOne`, `refs`,
1612
+ `aggregate`, `count`, `max`, `maintainedFrom`, `whenMember`, `cdcProjected`;
1613
+ with their option/marker types (`ModelOptions`, `KeyStructure`, `SegmentedKey`,
1614
+ `GsiOptions`, `RefsOptions`, `MaintainedFromOptions`, …).
1615
+ - Authoring helpers: `entityWrites` (+ `getEntityWrites`), `param`, `cond`, `when`,
1616
+ `mutate` (the authoring twin, usable as a `publishCommand` method body), and the
1617
+ projection helpers `preview` / `identity`. Types: `Param*` (for `param.*`),
1618
+ `RawCondition` / `CondSlot` / `Column` / `ColumnMap` (for `cond`), and the
1619
+ type-safe query/select types (`QueryResult`, `SelectableOf`, `QueryOptions`,
1620
+ `ListOptions`, `FilterInput`, `SelectBuilder`, `Updatable`, …).
1621
+ - Middleware types: `Middleware` / `WriteMiddleware` and their context types (the
1622
+ hooks are registered at runtime via `graphddb.config.use` /
1623
+ `clearMiddleware`).
1624
+
1625
+ **Subpaths**
1626
+ - `graphddb/internal` — the AOT prepared-plan loader
1627
+ (`import { loadPreparedPlan } from 'graphddb/internal'`) and the artifact
1628
+ document types (`PreparedPlanDocument`, …). This is what
1629
+ `graphddb transform prepared --aot` injects into rewritten user modules, so it
1630
+ must resolve from the installed package.
1631
+ - `graphddb/spec` — the static spec / bridge builders + serializer + spec types
1632
+ (`buildBridgeBundle`, `SPEC_VERSION*`, `Manifest*`, `OperationsDocument`, …),
1633
+ the advanced build-time surface external codegen tooling uses.
1634
+ - `graphddb/linter` — the design `Linter`, `createDefaultLinter`, and the built-in
1635
+ rules (`noScanRule`, `requireLimitRule`, `queryBoundaryRule`, `gsiAmbiguityRule`,
1636
+ `missingGsiRule`, `relationDepthRule`, plus the Epic #118 maintenance/preset
1637
+ rules `samePartitionPresetRule`, `missingContextRule`, `embeddedSnapshotSizeRule`,
1638
+ `hotPartitionRule`, `fanOutRule`, `multiMaintainerSameRowRule`); types
1639
+ `LintRule`, `LintResult`. The default set (`createDefaultLinter`) includes every
1640
+ rule **except** `queryBoundaryRule`; the maintenance rules are no-ops for
1641
+ relations that declare no `write.maintainedOn`.
1642
+ - `graphddb/cdc` — the dev/test CDC emulator (`createCdcEmulator`), the
1643
+ maintenance drain, the CDC-projection parse/route core, and the CDC types
1644
+ (`ChangeEvent`, `ChangeBatch`, …).
1645
+ - `graphddb/transform` — the source-transform / prepared-statement compiler used
1646
+ by the `graphddb transform` CLI.
1647
+ - `graphddb/testing` — the in-memory test doubles.
1648
+
1649
+ Runtime internals that the old flat barrel exported (`execute*` op primitives,
1650
+ `build*Input`, `serializeFieldValue`, expression builders, `plan` / `resolveKey`
1651
+ / `buildProjection`, `execute` / `DynamoExecutor`, `hydrate`, `encodeCursor` /
1652
+ `decodeCursor`, `MetadataRegistry` / `derivePrefix` / `validateGsiAmbiguity`,
1653
+ `ClientManager` / `TableMapping`, `buildMaintenanceGraph`, the relation-traversal
1654
+ helpers) are no longer part of the public surface — the two pillars delegate to
1655
+ them internally.
1627
1656
 
1628
1657
  ---
1629
1658
 
package/docs/testing.md CHANGED
@@ -48,11 +48,11 @@ string. Because the seam sits at the *structured operation boundary*, a fake
48
48
  needs no expression-string parser.
49
49
 
50
50
  `createTestDB()` installs a `MemoryExecutor` (`src/memory/memory-executor.ts`)
51
- over a `MemoryStore` (`src/memory/memory-store.ts`) on the `ClientManager` seam
52
- via `ClientManager.setExecutor()`. Every `Model.putItem / query / list / updateItem /
53
- deleteItem / batchGet` call, the planner, hydrator, and relation traversal then run
54
- unchanged against the in-memory store. `db.testing.close()` (or
55
- `ClientManager.resetExecutor()`) restores the default `DynamoExecutor`.
51
+ over a `MemoryStore` (`src/memory/memory-store.ts`) on the internal executor seam
52
+ (`ClientManager`). Every `Model.putItem / query / list / updateItem / deleteItem`
53
+ call, a batch read (`graphddb.query({ key: K[] })`), the planner, hydrator, and
54
+ relation traversal then run unchanged against the in-memory store.
55
+ `db.testing.close()` restores the default `DynamoExecutor`.
56
56
 
57
57
  The store keeps items in their **physical** form — exactly the bytes GraphDDB
58
58
  writes (`PK` / `SK` / `GSI*PK` / `GSI*SK` plus attributes), with no marshalling
@@ -110,9 +110,10 @@ interface CreateTestDBOptions {
110
110
  }
111
111
  ```
112
112
 
113
- `createTestDB()` is the recommended entry point. For callers that wire the seam
114
- manually, `graphddb/testing` also exports `MemoryExecutor` and `MemoryStore`
115
- (install with `ClientManager.setExecutor(new MemoryExecutor(store))`).
113
+ `createTestDB()` is the recommended entry point it installs the in-memory
114
+ executor on the internal seam for you. `graphddb/testing` also exports the
115
+ `MemoryExecutor` and `MemoryStore` building blocks for advanced use (e.g.
116
+ inspecting or pre-seeding the store directly).
116
117
 
117
118
  Tests must call `db.testing.close()` when done (typically in `afterEach`) to
118
119
  detach CDC and restore the default executor. `db.testing.reset()` clears the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphddb",
3
- "version": "0.7.10",
3
+ "version": "0.8.1",
4
4
  "description": "Graph data modeling on DynamoDB with adjacency list pattern",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,6 +33,10 @@
33
33
  "./transform": {
34
34
  "types": "./dist/transform/index.d.ts",
35
35
  "import": "./dist/transform/index.js"
36
+ },
37
+ "./internal": {
38
+ "types": "./dist/internal/index.d.ts",
39
+ "import": "./dist/internal/index.js"
36
40
  }
37
41
  },
38
42
  "bin": {
@@ -48,8 +52,8 @@
48
52
  "test:types": "vitest run --config vitest.typecheck.config.ts && npm run test:types:examples",
49
53
  "test:types:examples": "tsc --noEmit -p tsconfig.examples.json",
50
54
  "test:integration": "vitest run --config vitest.integration.config.ts",
51
- "gen:py:fixtures": "tsx src/cli.ts generate python --entry python/tests/fixtures/models.ts --queries python/tests/fixtures/definitions.ts --commands python/tests/fixtures/definitions.ts --contracts python/tests/fixtures/definitions.ts --out python/tests/fixtures/generated",
52
- "gen:py:fixtures:multi": "tsx src/cli.ts generate python --entry python/tests/fixtures/models.ts --queries python/tests/fixtures/definitions_multi.ts --commands python/tests/fixtures/definitions_multi.ts --out python/tests/fixtures/generated_multi",
55
+ "gen:py:fixtures": "tsx src/cli.ts generate python --entry python/tests/fixtures/models.ts --transactions python/tests/fixtures/definitions.ts --contracts python/tests/fixtures/definitions.ts --out python/tests/fixtures/generated",
56
+ "gen:py:fixtures:multi": "tsx src/cli.ts generate python --entry python/tests/fixtures/models.ts --contracts python/tests/fixtures/definitions_multi.ts --out python/tests/fixtures/generated_multi",
53
57
  "test:py": "python3 -m pytest python/tests -m \"not integration\"",
54
58
  "test:py:integration": "python3 -m pytest python/tests -m integration",
55
59
  "test:conformance": "tsx conformance/run.ts",
@@ -80,7 +84,7 @@
80
84
  {
81
85
  "name": "runtime core (DDBModel + authoring)",
82
86
  "path": "dist/index.js",
83
- "import": "{ DDBModel, model, string, number, key, k, executeQuery, executeList, executeExplain }",
87
+ "import": "{ DDBModel, model, string, number, key, k }",
84
88
  "limit": "42 KB"
85
89
  },
86
90
  {
@@ -99,6 +103,12 @@
99
103
  "path": "dist/spec/index.js",
100
104
  "import": "{ buildBridgeBundle }",
101
105
  "limit": "20 KB"
106
+ },
107
+ {
108
+ "name": "graphddb/internal (AOT prepared-plan loader)",
109
+ "path": "dist/internal/index.js",
110
+ "import": "{ loadPreparedPlan }",
111
+ "limit": "30 KB"
102
112
  }
103
113
  ],
104
114
  "peerDependencies": {