graphddb 0.7.9 → 0.8.0
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.
- package/README.md +6 -6
- package/dist/cdc/index.d.ts +389 -4
- package/dist/cdc/index.js +4 -3
- package/dist/{chunk-ZPNRLOKA.js → chunk-GS4C5VGO.js} +4 -6
- package/dist/chunk-HNY2EJPV.js +1184 -0
- package/dist/{chunk-NYM7K2ST.js → chunk-I4LEJ4TF.js} +3812 -6724
- package/dist/{chunk-PFFPLD4B.js → chunk-L2NEDS7U.js} +725 -2112
- package/dist/chunk-L4QRCHRQ.js +278 -0
- package/dist/chunk-LGHSZIEE.js +187 -0
- package/dist/chunk-N4NWYNGZ.js +1987 -0
- package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
- package/dist/cli.js +63 -252
- package/dist/index.d.ts +23 -1548
- package/dist/index.js +100 -1778
- package/dist/internal/index.d.ts +84 -0
- package/dist/internal/index.js +701 -0
- package/dist/{maintenance-view-adapter-BATUh_I8.d.ts → key-DR7_lpyk.d.ts} +538 -2975
- package/dist/linter/index.d.ts +39 -6
- package/dist/linter/index.js +22 -4
- package/dist/{registry-CXhP4TaE.d.ts → linter-C-vypgut.d.ts} +22 -22
- package/dist/prepared-artifact-BpPgkXEo.d.ts +281 -0
- package/dist/spec/index.d.ts +506 -4
- package/dist/spec/index.js +36 -17
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +4 -3
- package/dist/transform/index.d.ts +460 -1
- package/dist/transform/index.js +2085 -2
- package/dist/types-2PMXEn5x.d.ts +1205 -0
- package/dist/types-BXLzIcQD.d.ts +450 -0
- package/docs/cdc-projection.md +5 -5
- package/docs/class-hydration.md +1 -1
- package/docs/cqrs-contract.md +28 -20
- package/docs/design-patterns.md +5 -5
- package/docs/docs-generation.md +6 -6
- package/docs/middleware.md +15 -15
- package/docs/mutation-command-derivation.md +52 -42
- package/docs/prepared-statements.md +14 -14
- package/docs/python-bridge.md +113 -66
- package/docs/spec.md +153 -124
- package/docs/testing.md +9 -8
- package/package.json +20 -5
- package/dist/chunk-MMVHOUM4.js +0 -24
- package/dist/from-change-DanwjE5b.d.ts +0 -327
- package/dist/index-CtPJSMrc.d.ts +0 -934
- package/dist/relation-depth-Dg3yhl7S.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
|
-
(`
|
|
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 — `
|
|
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
|
|
117
|
-
|
|
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 {
|
|
121
|
+
import { graphddb } from 'graphddb';
|
|
123
122
|
|
|
124
|
-
|
|
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
|
-
|
|
126
|
+
graphddb.config.tables({ UserPermissions: 'UserPermissions-prod' });
|
|
128
127
|
```
|
|
129
128
|
|
|
130
129
|
| Method | Signature | Behavior |
|
|
131
130
|
| --- | --- | --- |
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
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
|
|
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 `
|
|
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
|
|
719
|
-
|
|
720
|
-
unified-envelope `
|
|
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
|
|
983
|
-
`
|
|
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 `
|
|
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
|
-
|
|
1210
|
-
affected index keys are re-derived into the
|
|
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 — `
|
|
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 — `
|
|
1346
|
+
### Read envelope — `graphddb.query(map)`
|
|
1345
1347
|
|
|
1346
|
-
`
|
|
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
|
|
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 — `
|
|
1367
|
+
### Write envelope — `graphddb.mutate(map, { mode })`
|
|
1366
1368
|
|
|
1367
|
-
`
|
|
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
|
|
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
|
-
|
|
1412
|
-
|
|
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
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
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
|
-
|
|
1426
|
-
|
|
1427
|
-
]
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
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
|
-
-
|
|
1439
|
-
hydrates them (merging back any embedded maps), and returns
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
-
|
|
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
|
-
-
|
|
1450
|
-
|
|
1451
|
-
|
|
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 {
|
|
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
|
|
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
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
`
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
**
|
|
1582
|
-
`
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
`
|
|
1586
|
-
`
|
|
1587
|
-
`
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
`
|
|
1594
|
-
`
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
`
|
|
1600
|
-
`
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
`
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
`
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
`
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
`
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
`
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
`
|
|
1624
|
-
`
|
|
1625
|
-
`
|
|
1626
|
-
`
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
unchanged against the in-memory store.
|
|
55
|
-
`
|
|
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
|
|
114
|
-
|
|
115
|
-
|
|
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.
|
|
3
|
+
"version": "0.8.0",
|
|
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,11 +52,12 @@
|
|
|
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 --
|
|
52
|
-
"gen:py:fixtures:multi": "tsx src/cli.ts generate python --entry python/tests/fixtures/models.ts --
|
|
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",
|
|
60
|
+
"test:vectors": "tsx conformance/vectors-run.ts",
|
|
56
61
|
"validate:cfn": "node scripts/validate-cfn.mjs",
|
|
57
62
|
"gen:cli": "cli-contracts generate",
|
|
58
63
|
"gen:cli:check": "cli-contracts generate --dry-run",
|
|
@@ -67,6 +72,10 @@
|
|
|
67
72
|
"embedoc:watch": "embedoc watch",
|
|
68
73
|
"embedoc:generate": "embedoc generate --all",
|
|
69
74
|
"embedoc:check": "node scripts/embedoc-drift-check.mjs",
|
|
75
|
+
"vendor:bc-php": "node scripts/vendor-behavior-contracts-php.mjs",
|
|
76
|
+
"vendor:bc-php:check": "node scripts/vendor-behavior-contracts-php.mjs --check",
|
|
77
|
+
"vendor:bc-vectors": "node scripts/vendor-behavior-contracts-vectors.mjs",
|
|
78
|
+
"vendor:bc-vectors:check": "node scripts/vendor-behavior-contracts-vectors.mjs --check",
|
|
70
79
|
"bench:crosslang": "tsx benchmark/crosslang/run.ts",
|
|
71
80
|
"bench:crosslang:integration": "tsx benchmark/crosslang/integration.ts",
|
|
72
81
|
"bench:crosslang:test": "vitest run benchmark/crosslang/__tests__"
|
|
@@ -75,7 +84,7 @@
|
|
|
75
84
|
{
|
|
76
85
|
"name": "runtime core (DDBModel + authoring)",
|
|
77
86
|
"path": "dist/index.js",
|
|
78
|
-
"import": "{ DDBModel, model, string, number, key, k
|
|
87
|
+
"import": "{ DDBModel, model, string, number, key, k }",
|
|
79
88
|
"limit": "42 KB"
|
|
80
89
|
},
|
|
81
90
|
{
|
|
@@ -94,6 +103,12 @@
|
|
|
94
103
|
"path": "dist/spec/index.js",
|
|
95
104
|
"import": "{ buildBridgeBundle }",
|
|
96
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"
|
|
97
112
|
}
|
|
98
113
|
],
|
|
99
114
|
"peerDependencies": {
|
|
@@ -135,7 +150,7 @@
|
|
|
135
150
|
},
|
|
136
151
|
"license": "MIT",
|
|
137
152
|
"dependencies": {
|
|
138
|
-
"behavior-contracts": "0.1.
|
|
153
|
+
"behavior-contracts": "0.1.3",
|
|
139
154
|
"commander": "^15.0.0",
|
|
140
155
|
"handlebars": "^4.7.9"
|
|
141
156
|
},
|
package/dist/chunk-MMVHOUM4.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// src/linter/rules/query-boundary.ts
|
|
2
|
-
var queryBoundaryRule = {
|
|
3
|
-
id: "query-boundary",
|
|
4
|
-
severity: "error",
|
|
5
|
-
check(metadata) {
|
|
6
|
-
const results = [];
|
|
7
|
-
for (const gsi of metadata.gsiDefinitions) {
|
|
8
|
-
if (!gsi.unique) {
|
|
9
|
-
results.push({
|
|
10
|
-
ruleId: "query-boundary",
|
|
11
|
-
severity: "error",
|
|
12
|
-
message: `GSI '${gsi.indexName}' on entity '${metadata.prefix}' is not marked as unique. Using query() on a non-unique GSI may return multiple items. Use list() instead, or mark the GSI as unique if it guarantees single results.`,
|
|
13
|
-
entity: metadata.prefix,
|
|
14
|
-
field: gsi.indexName
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return results;
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export {
|
|
23
|
-
queryBoundaryRule
|
|
24
|
-
};
|