graphddb 0.8.1 → 0.9.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/dist/behaviors-DDltNivc.d.ts +217 -0
- package/dist/cdc/index.d.ts +5 -4
- package/dist/cdc/index.js +3 -3
- package/dist/{chunk-HNY2EJPV.js → chunk-KOIJ4SNO.js} +1 -1
- package/dist/{chunk-S2NI4PBW.js → chunk-NWTEUWJD.js} +7 -5
- package/dist/{chunk-LAT64YCZ.js → chunk-SSVG6NQK.js} +657 -18
- package/dist/{chunk-L4QRCHRQ.js → chunk-WOFRHRXY.js} +6 -25
- package/dist/{chunk-T44OB5GU.js → chunk-XZTA4VJH.js} +23 -7
- package/dist/{chunk-GS4C5VGO.js → chunk-YGQ2NO6J.js} +2 -2
- package/dist/{chunk-L2NEDS7U.js → chunk-ZNU7OI5I.js} +241 -8
- package/dist/cli.js +366 -80
- package/dist/index.d.ts +131 -5
- package/dist/index.js +121 -5
- package/dist/internal/index.d.ts +6 -5
- package/dist/internal/index.js +7 -7
- package/dist/{key-DZtjAQDh.d.ts → key-Dne-a20Q.d.ts} +40 -5
- package/dist/linter/index.d.ts +6 -5
- package/dist/{linter-DQY7gUEk.d.ts → linter-Cr1Hnw7n.d.ts} +1 -1
- package/dist/{prepared-artifact-HFealr1q.d.ts → prepared-artifact-BXkARxwO.d.ts} +4 -4
- package/dist/spec/index.d.ts +225 -24
- package/dist/spec/index.js +17 -9
- package/dist/testing/index.d.ts +3 -2
- package/dist/testing/index.js +3 -3
- package/dist/transform/index.d.ts +2 -1
- package/dist/transform/index.js +2 -2
- package/dist/{types-DW__-Icc.d.ts → types-BnFsTZwl.d.ts} +1 -1
- package/dist/{types-2PMXEn5x.d.ts → types-CfxzTEFL.d.ts} +262 -20
- package/docs/python-bridge.md +33 -49
- package/package.json +11 -3
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Component } from 'behavior-contracts';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Parameter placeholders for the parameterized query / command definition DSL
|
|
3
5
|
* (issue #41, Python-bridge Phase 0a).
|
|
@@ -21,7 +23,7 @@
|
|
|
21
23
|
/** Brand tag preventing a plain object from structurally matching a {@link Param}. */
|
|
22
24
|
declare const PARAM_BRAND: unique symbol;
|
|
23
25
|
/** Runtime discriminant for a parameter placeholder. */
|
|
24
|
-
type ParamKind = 'string' | 'number' | 'literal' | 'array';
|
|
26
|
+
type ParamKind = 'string' | 'number' | 'literal' | 'array' | 'map';
|
|
25
27
|
/**
|
|
26
28
|
* A branded parameter placeholder representing a value of TypeScript type `T`
|
|
27
29
|
* that is bound at execution time rather than definition time.
|
|
@@ -64,6 +66,28 @@ type StringParam = Param<string>;
|
|
|
64
66
|
type NumberParam = Param<number>;
|
|
65
67
|
/** A `Param` whose represented value type is the literal union `L`. */
|
|
66
68
|
type LiteralParam<L extends string | number> = Param<L>;
|
|
69
|
+
/**
|
|
70
|
+
* A `Param` standing in for a **map / embedded object** value (issue #295): a
|
|
71
|
+
* JSON-serializable object written to a single DynamoDB `M` attribute. The
|
|
72
|
+
* `kind` is narrowed to the `'map'` literal so type-level dispatch works, and
|
|
73
|
+
* the represented value type `T` (default `Record<string, unknown>`) lets a
|
|
74
|
+
* typed domain value class ride the placeholder:
|
|
75
|
+
*
|
|
76
|
+
* ```ts
|
|
77
|
+
* type AvgValue = Record<string, Record<string, { sum: number; count: number }>>;
|
|
78
|
+
* param.map<AvgValue>(); // Param<AvgValue> bound to a `@map value!: AvgValue` field
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* The value crosses every boundary as a **native DynamoDB `M` attribute**
|
|
82
|
+
* (never a JSON-string blob): the placeholder renders to a `{field}` token /
|
|
83
|
+
* `{ref:["field"]}` Expression-IR reference in the portable IR, and each
|
|
84
|
+
* runtime's (already recursive) attribute marshaller serializes the supplied
|
|
85
|
+
* object to `M`. Generated bindings type it as the language's native free-form
|
|
86
|
+
* map (`dict` / `map[string]any` / `serde_json::Value` / `array`).
|
|
87
|
+
*/
|
|
88
|
+
type MapParam<T extends Record<string, unknown> = Record<string, unknown>> = Param<T> & {
|
|
89
|
+
readonly kind: 'map';
|
|
90
|
+
};
|
|
67
91
|
/** The element-field descriptor shape accepted by {@link param.array}. */
|
|
68
92
|
type ArrayElementShape = Record<string, Param<unknown>>;
|
|
69
93
|
/**
|
|
@@ -131,6 +155,29 @@ declare const param: {
|
|
|
131
155
|
* ```
|
|
132
156
|
*/
|
|
133
157
|
readonly array: <const E extends ArrayElementShape>(element: E, options?: ParamOptions) => ArrayParam<E>;
|
|
158
|
+
/**
|
|
159
|
+
* A placeholder for a **map / embedded object** value written to a single
|
|
160
|
+
* DynamoDB `M` attribute (issue #295). Free-form by design: the model-side
|
|
161
|
+
* vocabulary (`@map` / `@embedded`) records no nested schema, so the param is
|
|
162
|
+
* typed as the language-native map in generated bindings (`dict` /
|
|
163
|
+
* `map[string]any` / `serde_json::Value` / `array`) while the optional type
|
|
164
|
+
* parameter `T` keeps the TS authoring surface precise.
|
|
165
|
+
*
|
|
166
|
+
* Legal at **value positions only** (a write descriptor's `input`, a
|
|
167
|
+
* transaction fragment value, a batch-write per-item payload field) — a map
|
|
168
|
+
* cannot compose a key, so a `param.map()` in a key position is rejected
|
|
169
|
+
* loudly at build time.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* putLeaf: {
|
|
174
|
+
* upsert: TreeNode,
|
|
175
|
+
* key: { part: param.string(), child: param.string() },
|
|
176
|
+
* input: { value: param.map<AvgValue>(), computedAt: param.number() },
|
|
177
|
+
* }
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
readonly map: <T extends Record<string, unknown> = Record<string, unknown>>(options?: ParamOptions) => MapParam<T>;
|
|
134
181
|
};
|
|
135
182
|
|
|
136
183
|
/**
|
|
@@ -181,6 +228,27 @@ declare const SPEC_VERSION: "1.1";
|
|
|
181
228
|
* (#265/#266/#267) land and bump the supported constant with the wiring.
|
|
182
229
|
*/
|
|
183
230
|
declare const SPEC_VERSION_SCP: "1.2";
|
|
231
|
+
/**
|
|
232
|
+
* The schema version stamped on an operations document whose **read operations**
|
|
233
|
+
* carry the SCP IR Phase 2 Expression-IR key wiring (`keyExpr` / `valueExpr`,
|
|
234
|
+
* issue #289). This is the **operations IR 2.0 milestone** (the "2.0-rc" of the
|
|
235
|
+
* migration plan §3 Phase 2 / M3): a MAJOR bump (`1.x` → `2.x`), because the key
|
|
236
|
+
* wiring's serialization changed from a `{param}` string template to the portable
|
|
237
|
+
* Expression IR `concat`/`ref` closed vocabulary (behavior-contracts
|
|
238
|
+
* `expression-ir.md`). The BEHAVIOR is identical — the change is purely in IR
|
|
239
|
+
* serialization — but a major bump is correct: a pre-#289 runtime (supported
|
|
240
|
+
* `1.2`) `validateEnvelope`-rejects a `2.0` document (its `keyExpr` reader does
|
|
241
|
+
* not exist), which is the intended fail-closed break. Stamped **only** when a
|
|
242
|
+
* document actually carries a read op (a `queries` entry); a commands/transactions
|
|
243
|
+
* -only document has no read-path key wiring and keeps its `1.1`/`1.2` stamp,
|
|
244
|
+
* serializing byte-identically.
|
|
245
|
+
*
|
|
246
|
+
* The stamp string is the integer `<major>.<minor>` form `validateEnvelope`
|
|
247
|
+
* requires (`2.0`); "2.0-rc" is the migration MILESTONE label (dual-emit proven,
|
|
248
|
+
* flip landed), not the wire version — a `-rc` suffix is not a valid envelope
|
|
249
|
+
* version and would fail-closed in graphddb's own runtime.
|
|
250
|
+
*/
|
|
251
|
+
declare const SPEC_VERSION_KEY_EXPR: "2.0";
|
|
184
252
|
/**
|
|
185
253
|
* The HIGHEST spec-IR minor this **TS runtime** understands and will EXECUTE
|
|
186
254
|
* (issue #265, Phase 3 S5). The shared `validateEnvelope` fail-closed rule is
|
|
@@ -194,10 +262,19 @@ declare const SPEC_VERSION_SCP: "1.2";
|
|
|
194
262
|
* unknown `exprVersion` / operator still fail-closes (the expr evaluator rejects
|
|
195
263
|
* it). The py/rust/go runtimes bump in S6, PHP in S7 — the whole release column
|
|
196
264
|
* moves to `1.2` together.
|
|
265
|
+
*
|
|
266
|
+
* **SCP IR Phase 2 (#289): bumped to `2.0` (major).** The read-op key wiring is
|
|
267
|
+
* now portable Expression IR (`keyExpr` / `valueExpr`), a MAJOR serialization
|
|
268
|
+
* change. `validateEnvelope`'s rule is "major must match, minor ≤ supported", so
|
|
269
|
+
* a `2.0` runtime accepts `2.0` documents and **rejects every `1.x` document**
|
|
270
|
+
* (major mismatch) — and every `1.x` runtime rejects a `2.0` document. That is
|
|
271
|
+
* the intended breaking cut: the whole operations IR moves to `2.0` at once
|
|
272
|
+
* (migration plan §4). All 4 runtimes bump their supported constant to `2.0`
|
|
273
|
+
* together (one release column), mirroring the `1.2` bump discipline.
|
|
197
274
|
*/
|
|
198
|
-
declare const SPEC_VERSION_SUPPORTED: "
|
|
199
|
-
/** A spec version an operations document may carry (see {@link
|
|
200
|
-
type SpecVersion = typeof SPEC_VERSION | typeof SPEC_VERSION_SCP;
|
|
275
|
+
declare const SPEC_VERSION_SUPPORTED: "2.0";
|
|
276
|
+
/** A spec version an operations document may carry (see {@link SPEC_VERSION_KEY_EXPR}). */
|
|
277
|
+
type SpecVersion = typeof SPEC_VERSION | typeof SPEC_VERSION_SCP | typeof SPEC_VERSION_KEY_EXPR;
|
|
201
278
|
/** Field type as carried in the manifest (derived from the DynamoDB type). */
|
|
202
279
|
type ManifestFieldType = 'string' | 'number' | 'boolean' | 'binary' | 'stringSet' | 'numberSet' | 'list' | 'map';
|
|
203
280
|
interface ManifestField {
|
|
@@ -305,12 +382,17 @@ interface ManifestTable {
|
|
|
305
382
|
}
|
|
306
383
|
interface Manifest {
|
|
307
384
|
/**
|
|
308
|
-
*
|
|
309
|
-
* metadata only
|
|
310
|
-
*
|
|
311
|
-
*
|
|
385
|
+
* The IR SSoT version. Historically the base {@link SPEC_VERSION} (`1.1`): the
|
|
386
|
+
* manifest carries entity/schema metadata only, so the conditional `1.2`
|
|
387
|
+
* Expression-IR stamp never applied here. **SCP IR Phase 2 (#289): moved to
|
|
388
|
+
* {@link SPEC_VERSION_KEY_EXPR} (`2.0`).** The operations IR took a MAJOR bump
|
|
389
|
+
* (portable Expression IR key wiring), and `validateEnvelope`'s "major must
|
|
390
|
+
* match" rule is applied to the manifest AND the operations document against a
|
|
391
|
+
* single supported constant — so the manifest version must track the same major
|
|
392
|
+
* or every load would fail major-mismatch. The manifest CONTENT is unchanged;
|
|
393
|
+
* only its version stamp moves to `2.0` with the rest of the IR SSoT.
|
|
312
394
|
*/
|
|
313
|
-
readonly version: typeof
|
|
395
|
+
readonly version: typeof SPEC_VERSION_KEY_EXPR;
|
|
314
396
|
readonly tables: Readonly<Record<string, ManifestTable>>;
|
|
315
397
|
readonly entities: Readonly<Record<string, ManifestEntity>>;
|
|
316
398
|
}
|
|
@@ -332,12 +414,31 @@ interface ParamSpec {
|
|
|
332
414
|
*/
|
|
333
415
|
readonly description?: string;
|
|
334
416
|
}
|
|
335
|
-
/** A `begins_with` range condition on a sort key (
|
|
417
|
+
/** A `begins_with` range condition on a sort key (Expression-IR value). */
|
|
336
418
|
interface RangeConditionSpec {
|
|
337
419
|
readonly operator: 'begins_with';
|
|
338
420
|
readonly key: string;
|
|
339
|
-
/**
|
|
340
|
-
|
|
421
|
+
/**
|
|
422
|
+
* The range value as **Expression IR** (SCP IR Phase 2, issue #289): e.g.
|
|
423
|
+
* `"USER#"` (a bare literal) or `{concat:["USER#",{ref:["result","groupId"]}]}`.
|
|
424
|
+
* The runtime evaluates it with `evaluateExpression` — the same closed
|
|
425
|
+
* `concat`/`ref` vocabulary the key wiring uses (`key-expr.ts`). Replaces the
|
|
426
|
+
* pre-#289 `{param}` string template (dual-emitted and proven byte-identical
|
|
427
|
+
* through conformance before the template path was removed, P2-3).
|
|
428
|
+
*
|
|
429
|
+
* Optional only because the builders construct the raw range condition with
|
|
430
|
+
* `value` first and the `lowerOperationKeyExprs` pass fills `valueExpr` — every
|
|
431
|
+
* serialized range condition carries `valueExpr`.
|
|
432
|
+
*/
|
|
433
|
+
readonly valueExpr?: ExpressionNode;
|
|
434
|
+
/**
|
|
435
|
+
* @deprecated Phase 2 dual-emit (issue #289 P2-3): the pre-#289 `{param}`
|
|
436
|
+
* string template (`GROUP#{result.groupId}`), emitted ALONGSIDE
|
|
437
|
+
* {@link valueExpr} only during the migration so the runtimes' Expression-IR
|
|
438
|
+
* reading can be proven byte-identical to the template render before the
|
|
439
|
+
* template path is deleted. Removed at the flip.
|
|
440
|
+
*/
|
|
441
|
+
readonly value?: string;
|
|
341
442
|
}
|
|
342
443
|
/** Server-side declarative filter, carried verbatim for the runtime to compile. */
|
|
343
444
|
interface FilterSpec {
|
|
@@ -354,10 +455,28 @@ interface OperationSpec {
|
|
|
354
455
|
readonly tableName: string;
|
|
355
456
|
readonly indexName?: string;
|
|
356
457
|
/**
|
|
357
|
-
* Key condition
|
|
358
|
-
*
|
|
458
|
+
* Key condition as **Expression IR** (SCP IR Phase 2, issue #289): attribute
|
|
459
|
+
* name → an Expression IR node (`concat`/`ref`) that evaluates to the key
|
|
460
|
+
* value, e.g. `PK: {concat:["ARTICLE#",{ref:["input","articleId"]}]}`. For a
|
|
461
|
+
* `BatchGetItem` this is the per-key shape (one key per resolved source item).
|
|
462
|
+
* The runtime evaluates each node with `evaluateExpression` against the
|
|
463
|
+
* `{input, result}` scope — the universal Port-wiring vocabulary
|
|
464
|
+
* (scp-ir-architecture.md §5), lowered from the pre-#289 `{param}` string
|
|
465
|
+
* template by `key-expr.ts`.
|
|
466
|
+
*
|
|
467
|
+
* Optional only because the builders construct the raw op with `keyCondition`
|
|
468
|
+
* first and a single `lowerOperationKeyExprs` pass fills `keyExpr` from it
|
|
469
|
+
* (operations.ts) — every serialized op carries `keyExpr`.
|
|
470
|
+
*/
|
|
471
|
+
readonly keyExpr?: Readonly<Record<string, ExpressionNode>>;
|
|
472
|
+
/**
|
|
473
|
+
* @deprecated Phase 2 dual-emit (issue #289 P2-3): the pre-#289 `{param}`
|
|
474
|
+
* string-template key condition (`ARTICLE#{articleId}`), emitted ALONGSIDE
|
|
475
|
+
* {@link keyExpr} only during the migration so the runtimes' Expression-IR key
|
|
476
|
+
* reading can be proven byte-identical to the template render before the
|
|
477
|
+
* template path is deleted. Removed at the flip.
|
|
359
478
|
*/
|
|
360
|
-
readonly keyCondition
|
|
479
|
+
readonly keyCondition?: Readonly<Record<string, string>>;
|
|
361
480
|
readonly rangeCondition?: RangeConditionSpec;
|
|
362
481
|
/** Projected fields (entity field names). */
|
|
363
482
|
readonly projection: readonly string[];
|
|
@@ -412,6 +531,25 @@ interface SourceListSpec {
|
|
|
412
531
|
* the query's select projects the attribute itself (it stays in the result).
|
|
413
532
|
*/
|
|
414
533
|
readonly implicit?: boolean;
|
|
534
|
+
/**
|
|
535
|
+
* **SCP IR Phase 2 prep toward the universal Map node (issue #289 P2-5).** The
|
|
536
|
+
* shared IR expresses a fan-out as an SCP `map` node
|
|
537
|
+
* (`{ map: { over: <ref to the list>, as: "$el", component, ports } }`,
|
|
538
|
+
* scp-ir-architecture.md §5) — a single static node iterating a list Port. This
|
|
539
|
+
* `refs`/`sourceList` binding form is the DynamoDB-specific precursor of that
|
|
540
|
+
* Map. Phase 2 only PREPARES the migration (the full `map`-node emission +
|
|
541
|
+
* runtime expansion is Phase 3): {@link over} records the Expression-IR
|
|
542
|
+
* reference to the parent's list attribute the Map iterates
|
|
543
|
+
* (`{ ref: ["result", from] }`), and {@link as} records the per-element binding
|
|
544
|
+
* name (`"$<key>"`) the Phase-3 `map` body will reference. Both are ADDITIVE and
|
|
545
|
+
* DESCRIPTIVE only — the Phase-2 runtimes still execute the fan-out via
|
|
546
|
+
* {@link from}/{@link key} (unchanged behavior); Phase 3 lowers the body onto
|
|
547
|
+
* `over`/`as`. Carrying them now freezes the intended Map shape for review and
|
|
548
|
+
* lets a Phase-3 runtime resolve it without re-deriving.
|
|
549
|
+
*/
|
|
550
|
+
readonly over?: ExpressionNode;
|
|
551
|
+
/** The Phase-3 Map element binding name (`"$<key>"`); see {@link over}. */
|
|
552
|
+
readonly as?: string;
|
|
415
553
|
}
|
|
416
554
|
/**
|
|
417
555
|
* The **execution plan** for a multi-operation read (issue #70a). It is the
|
|
@@ -489,8 +627,15 @@ interface QuerySpec {
|
|
|
489
627
|
/**
|
|
490
628
|
* The Expression IR vocabulary version (behavior-contracts `expression-ir.md`
|
|
491
629
|
* §9). A runtime handed an unknown `exprVersion` MUST fail closed.
|
|
630
|
+
*
|
|
631
|
+
* **SCP IR Phase 2 (#289, G6): bumped `1` → `2`.** graphddb now follows the
|
|
632
|
+
* behavior-contracts `exprVersion: 2` vocabulary (the v1→v2 change is the `obj`
|
|
633
|
+
* node `__proto__` `FORBIDDEN_KEY` fail-closed hardening — additive/backward
|
|
634
|
+
* compatible, `expression-ir.md` §9), closing the minor-version lag the migration
|
|
635
|
+
* plan flagged (§2 G6). Every embedded {@link ExpressionSpec} — guards, scpExpr
|
|
636
|
+
* conditions, and the new `keyExpr` key wiring — stamps `exprVersion: 2`.
|
|
492
637
|
*/
|
|
493
|
-
declare const EXPR_VERSION:
|
|
638
|
+
declare const EXPR_VERSION: 2;
|
|
494
639
|
/**
|
|
495
640
|
* The closed operator vocabulary of Expression IR v1 (expression-ir.md §3).
|
|
496
641
|
* An operator node is a **single-key** object — key = operator name, value =
|
|
@@ -892,6 +1037,25 @@ interface TransactionSpec {
|
|
|
892
1037
|
* DynamoDB ≤25 limit after expansion).
|
|
893
1038
|
*/
|
|
894
1039
|
readonly maxItems?: number;
|
|
1040
|
+
/**
|
|
1041
|
+
* Marks a **BatchWriteItem** bulk form (issue #298): the spec is executed
|
|
1042
|
+
* NON-ATOMICALLY through DynamoDB `BatchWriteItem` — chunked ≤25 per request
|
|
1043
|
+
* with `UnprocessedItems` exponential retry, 1×WCU, no all-or-nothing
|
|
1044
|
+
* guarantee — instead of one atomic `TransactWriteItems`. Synthesized only by
|
|
1045
|
+
* the command-contract serializer for a `mode: 'batchWrite'` method (the
|
|
1046
|
+
* 0.9.0 default for an unconditional put / delete): a single `forEach`-over-
|
|
1047
|
+
* `items` write item whose element carries the FULL per-item payload (key +
|
|
1048
|
+
* input fields), so each item binds its own values.
|
|
1049
|
+
*
|
|
1050
|
+
* Fail-closed on both sides: the emitter only stamps this on a spec whose
|
|
1051
|
+
* every item is an **unconditional** `Put` / `Delete` `forEach` (DynamoDB's
|
|
1052
|
+
* `BatchWriteItem` carries no `ConditionExpression` and no `Update`), and the
|
|
1053
|
+
* runtimes' `executeTransaction` loud-rejects a marked spec (it must go
|
|
1054
|
+
* through `executeBatchWrite`) while `executeBatchWrite` loud-rejects an
|
|
1055
|
+
* unmarked one. Absent on every atomic transaction, so those serialize
|
|
1056
|
+
* byte-identically to the pre-#298 spec.
|
|
1057
|
+
*/
|
|
1058
|
+
readonly batchWrite?: true;
|
|
895
1059
|
}
|
|
896
1060
|
/**
|
|
897
1061
|
* Whether a contract is a public **read** (`'query'`) or **write** (`'command'`)
|
|
@@ -1084,6 +1248,20 @@ type CommandResolutionTarget =
|
|
|
1084
1248
|
| {
|
|
1085
1249
|
readonly mode: 'parallel';
|
|
1086
1250
|
readonly operation: string;
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* A non-atomic **BatchWriteItem** bulk write with per-item values (issue
|
|
1254
|
+
* #298; the 0.9.0 DEFAULT for an unconditional put / delete). References the
|
|
1255
|
+
* synthesized `<Contract>__<method>__batchWrite` {@link TransactionSpec} in
|
|
1256
|
+
* `transactions` (stamped {@link TransactionSpec.batchWrite}): a single
|
|
1257
|
+
* `forEach`-over-`items` write whose array element carries the FULL per-item
|
|
1258
|
+
* payload (key + input fields). The runtimes execute it through their
|
|
1259
|
+
* `BatchWriteItem` machinery — any array size, chunked ≤25 per request with
|
|
1260
|
+
* `UnprocessedItems` exponential retry, 1×WCU, no conditions, non-atomic.
|
|
1261
|
+
*/
|
|
1262
|
+
| {
|
|
1263
|
+
readonly mode: 'batchWrite';
|
|
1264
|
+
readonly transaction: string;
|
|
1087
1265
|
};
|
|
1088
1266
|
/**
|
|
1089
1267
|
* The serialized spec of one **command** contract method. Symmetric to
|
|
@@ -1170,6 +1348,50 @@ interface ContextSpec {
|
|
|
1170
1348
|
/** Contract names owned by this context (sorted; `contracts` map keys). */
|
|
1171
1349
|
readonly contracts: readonly string[];
|
|
1172
1350
|
}
|
|
1351
|
+
/**
|
|
1352
|
+
* The PORTABLE form of a read operation — what `operations.json` actually
|
|
1353
|
+
* carries since SCP IR Phase 3 (#290 P3-5): the physical **root** op minus the
|
|
1354
|
+
* proprietary relation wiring (`resultPath` / `sourceField` / `sourceList`).
|
|
1355
|
+
* That wiring now exists ONLY inside the build pipeline ({@link OperationSpec},
|
|
1356
|
+
* feeding the `components[]` lowering and the TS AOT prepared plan) and never
|
|
1357
|
+
* reaches the portable IR — relation execution is expressed exclusively by the
|
|
1358
|
+
* universal SCP component graph (`components[]`).
|
|
1359
|
+
*/
|
|
1360
|
+
type PortableOperationSpec = Omit<OperationSpec, 'resultPath' | 'sourceField' | 'sourceList'>;
|
|
1361
|
+
/**
|
|
1362
|
+
* The PORTABLE form of a query (#290 P3-5). `operations` carries exactly ONE
|
|
1363
|
+
* entry — the root physical op (the still-live single-op surfaces: the contract
|
|
1364
|
+
* key-array point-batch reads its `keyExpr` / `projection`; `explain` renders
|
|
1365
|
+
* it). Relation fan-out and staging live ONLY in `components[]`, so the old
|
|
1366
|
+
* relation operations and the `executionPlan` are not emitted.
|
|
1367
|
+
*/
|
|
1368
|
+
interface PortableQuerySpec {
|
|
1369
|
+
readonly params: Readonly<Record<string, ParamSpec>>;
|
|
1370
|
+
/** Exactly one entry: the root physical operation. */
|
|
1371
|
+
readonly operations: readonly PortableOperationSpec[];
|
|
1372
|
+
/** See {@link QuerySpec.cardinality}. */
|
|
1373
|
+
readonly cardinality?: 'one' | 'many';
|
|
1374
|
+
/** See {@link QuerySpec.description}. */
|
|
1375
|
+
readonly description?: string;
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* The PORTABLE form of a declarative transaction (#290 P3-5): only the input
|
|
1379
|
+
* contract (`params`, read by every runtime's `validate_params` surface). The
|
|
1380
|
+
* old templated item bodies (`items[]` with `forEach` / `{item.*}` templates)
|
|
1381
|
+
* are not emitted — the write plan lives ONLY in the transaction's
|
|
1382
|
+
* `components[]` entry, which the runtimes execute via `runBehavior`.
|
|
1383
|
+
*/
|
|
1384
|
+
interface PortableTransactionSpec {
|
|
1385
|
+
readonly params: Readonly<Record<string, ParamSpec>>;
|
|
1386
|
+
/**
|
|
1387
|
+
* Marks the `BatchWriteItem` bulk form (issue #298 — see
|
|
1388
|
+
* {@link TransactionSpec.batchWrite}). Portable ON PURPOSE: it is the
|
|
1389
|
+
* runtimes' fail-closed dispatch signal (`executeBatchWrite` requires it,
|
|
1390
|
+
* `executeTransaction` rejects it) and the codegen signal for the per-item
|
|
1391
|
+
* `*BatchWrite` generated-method shape. Absent on every atomic transaction.
|
|
1392
|
+
*/
|
|
1393
|
+
readonly batchWrite?: true;
|
|
1394
|
+
}
|
|
1173
1395
|
interface OperationsDocument {
|
|
1174
1396
|
/**
|
|
1175
1397
|
* `1.1` unless the document actually contains an SCP Expression IR node (a
|
|
@@ -1177,10 +1399,18 @@ interface OperationsDocument {
|
|
|
1177
1399
|
* (issue #261, conditional stamping — see {@link SPEC_VERSION_SCP}).
|
|
1178
1400
|
*/
|
|
1179
1401
|
readonly version: SpecVersion;
|
|
1180
|
-
|
|
1402
|
+
/**
|
|
1403
|
+
* Portable query specs (#290 P3-5): the root physical op + params only; the
|
|
1404
|
+
* relation wiring lives in {@link components}.
|
|
1405
|
+
*/
|
|
1406
|
+
readonly queries: Readonly<Record<string, PortableQuerySpec>>;
|
|
1181
1407
|
readonly commands: Readonly<Record<string, CommandSpec>>;
|
|
1182
|
-
/**
|
|
1183
|
-
|
|
1408
|
+
/**
|
|
1409
|
+
* Declarative transactions (issue #46). Absent in pre-#46 specs. Since #290
|
|
1410
|
+
* P3-5 only the params contract is emitted; the write plan lives in
|
|
1411
|
+
* {@link components}.
|
|
1412
|
+
*/
|
|
1413
|
+
readonly transactions?: Readonly<Record<string, PortableTransactionSpec>>;
|
|
1184
1414
|
/**
|
|
1185
1415
|
* The CQRS Contract layer (issue #59): contract name → {@link ContractSpec}.
|
|
1186
1416
|
* Layered **on top of** the existing `queries` / `commands` / `transactions`
|
|
@@ -1195,6 +1425,18 @@ interface OperationsDocument {
|
|
|
1195
1425
|
* foreign. **Absent** when no contexts are declared (backward compatibility).
|
|
1196
1426
|
*/
|
|
1197
1427
|
readonly contexts?: Readonly<Record<string, ContextSpec>>;
|
|
1428
|
+
/**
|
|
1429
|
+
* The universal SCP **component-graph** (issue #290, Phase 3). The queries /
|
|
1430
|
+
* transactions lifted into behavior-contracts `Component[]` (name /
|
|
1431
|
+
* inputPorts / body / output / plan, `scp-ir-architecture.md` §5). Since the
|
|
1432
|
+
* P3-5 flip this is the ONLY carrier of relation fan-out and transaction
|
|
1433
|
+
* write plans in the portable IR — every spec-consumer runtime
|
|
1434
|
+
* (py/php/rust/go) executes reads and transactions via `runBehavior` over
|
|
1435
|
+
* this graph, and the typed-binding / docs generators reconstruct the result
|
|
1436
|
+
* shape from it. **Absent** when the input defines no queries or
|
|
1437
|
+
* transactions (backward compatible).
|
|
1438
|
+
*/
|
|
1439
|
+
readonly components?: readonly Component[];
|
|
1198
1440
|
}
|
|
1199
1441
|
/** The full `{ manifest, operations }` bundle produced by the static planner. */
|
|
1200
1442
|
interface BridgeBundle {
|
|
@@ -1202,4 +1444,4 @@ interface BridgeBundle {
|
|
|
1202
1444
|
readonly operations: OperationsDocument;
|
|
1203
1445
|
}
|
|
1204
1446
|
|
|
1205
|
-
export { type
|
|
1447
|
+
export { type WriteOperationType as $, type ManifestEntity as A, type BridgeBundle as B, type ContractSpec as C, type ManifestField as D, type ExpressionSpec as E, type FilterSpec as F, type ManifestFieldType as G, type ManifestGsi as H, type ManifestKey as I, type ManifestRelation as J, type ManifestTable as K, type OperationSpec as L, type Manifest as M, type QueryContractMethodSpec as N, type OperationsDocument as O, type ParamSpec as P, type QuerySpec as Q, type RangeConditionSpec as R, SPEC_VERSION_KEY_EXPR as S, type TransactionSpec as T, type ReadOperationType as U, SPEC_VERSION as V, SPEC_VERSION_SCP as W, SPEC_VERSION_SUPPORTED as X, type TransactionItemSpec as Y, type TransactionItemType as Z, type WhenSpec as _, type CommandSpec as a, type LiteralParam as a0, type MapParam as a1, type NumberParam as a2, type Param as a3, type ParamKind as a4, type StringParam as a5, param as a6, type ContextSpec as b, type ConditionSpec as c, type SpecVersion as d, type CommandContractMethodSpec as e, type CommandResolutionTarget as f, type ComposeSpec as g, type CompositionPlanSpec as h, type ContractCardinality as i, type ContractCommandResult as j, type ContractInputArity as k, type ContractKeySpec as l, type ContractKind as m, type ContractResolution as n, EXPR_VERSION as o, type ExecutionPlanSpec as p, type ExpressionArrNode as q, type ExpressionFloatNode as r, type ExpressionIntNode as s, type ExpressionNode as t, type ExpressionObjNode as u, type ExpressionOpNode as v, type ExpressionOperator as w, type ExpressionRefNode as x, type ExpressionRefOptNode as y, type ExpressionScalar as z };
|
package/docs/python-bridge.md
CHANGED
|
@@ -276,63 +276,46 @@ Every operation carries a `params` map of `ParamSpec`:
|
|
|
276
276
|
|
|
277
277
|
#### Queries
|
|
278
278
|
|
|
279
|
-
A `QuerySpec` has `params`, an
|
|
280
|
-
(`"one"` or `"many"`).
|
|
279
|
+
A `QuerySpec` has `params`, an `operations` array carrying exactly **one**
|
|
280
|
+
entry — the root physical op — and a `cardinality` (`"one"` or `"many"`).
|
|
281
|
+
Since SCP IR Phase 3 (#290 P3-5) relation fan-out is NOT part of `queries`:
|
|
282
|
+
it lives exclusively in the universal `components[]` graph (below). The root
|
|
283
|
+
`OperationSpec`:
|
|
281
284
|
|
|
282
285
|
```jsonc
|
|
283
286
|
{
|
|
284
287
|
"type": "Query", // "Query" | "GetItem" | "BatchGetItem"
|
|
285
288
|
"tableName": "UserPermissions",
|
|
286
289
|
"indexName": "GSI1", // omitted for base-table access
|
|
287
|
-
"keyCondition": { "GSI1PK": "EMAIL#{email}", "GSI1SK": "PROFILE" },
|
|
288
|
-
"rangeCondition": { "operator": "begins_with", "key": "SK", "value": "USER#" },
|
|
289
290
|
"projection": ["email", "name", "status", "userId"],
|
|
291
|
+
"keyExpr": { "GSI1PK": { "concat": ["EMAIL#", { "ref": ["input", "email"] }] },
|
|
292
|
+
"GSI1SK": "PROFILE" },
|
|
293
|
+
"rangeCondition": { "operator": "begins_with", "key": "SK", "valueExpr": "USER#" },
|
|
290
294
|
"limit": 20,
|
|
291
|
-
"filter": { /* declarative FilterExpression */ }
|
|
292
|
-
"resultPath": "$", // "$" or e.g. "$.groups.items"
|
|
293
|
-
"sourceField": "userId" // present on relation child operations
|
|
295
|
+
"filter": { /* declarative FilterExpression */ }
|
|
294
296
|
}
|
|
295
297
|
```
|
|
296
298
|
|
|
297
299
|
The operation type is chosen by the planner from the key shape: a full PK+SK is
|
|
298
300
|
a `GetItem`; a partial key with a contiguous sort-key prefix is a `Query` with a
|
|
299
301
|
`begins_with` `rangeCondition`; a partition-key-only access is a `Query`. The
|
|
300
|
-
`projection` is the set of scalar fields selected `true`.
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
A relation traversal is expressed as additional `OperationSpec` nodes in the same
|
|
319
|
-
query's `operations` array. Each child references its parent through
|
|
320
|
-
`{result.sourceField}` in its `keyCondition`, and its `resultPath` nests the
|
|
321
|
-
result under the parent (`$.groups.items` for a `hasMany`, `$.account` for a
|
|
322
|
-
`belongsTo` / `hasOne`). `belongsTo` / `hasOne` children are emitted as
|
|
323
|
-
`BatchGetItem`; `hasMany` children as `Query`.
|
|
324
|
-
|
|
325
|
-
A query may carry an `executionPlan`:
|
|
326
|
-
|
|
327
|
-
```jsonc
|
|
328
|
-
{ "groups": [[1, 2], [3]], "concurrency": 16 }
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
`groups` is an ordered list of stages of operation indices (the root, index 0,
|
|
332
|
-
is implicit and runs first). Operations within a stage are mutually independent
|
|
333
|
-
and may run concurrently; later stages may read `{result.*}` produced by earlier
|
|
334
|
-
ones. The same `deriveExecutionPlan` routine is used by the generator and by the
|
|
335
|
-
TypeScript runtime, so the staging cannot drift between them.
|
|
302
|
+
`projection` is the set of scalar fields selected `true`. The key wiring is
|
|
303
|
+
portable Expression IR (`keyExpr` / `valueExpr`, #289), derived by **symbolic
|
|
304
|
+
evaluation** of the model's key mapping. The runtime executes the root op via
|
|
305
|
+
its `components[]` entry; the emitted root op remains the validation /
|
|
306
|
+
`explain` / contract point-batch surface.
|
|
307
|
+
|
|
308
|
+
#### Relation chaining and staging (`components[]`)
|
|
309
|
+
|
|
310
|
+
A relation traversal is expressed as the query's entry in the top-level
|
|
311
|
+
`components[]` — the universal SCP component graph (behavior-contracts
|
|
312
|
+
`Component`): the root op becomes a `componentRef` body node, each relation a
|
|
313
|
+
`componentRef` / `map` node whose id is the dotted result path
|
|
314
|
+
(`groups.items`, `groups.items.group`), wired to its producer via `parent`
|
|
315
|
+
and executed by `run_behavior` with the DynamoDB catalog handlers. A
|
|
316
|
+
multi-node component carries a `plan` (`{ "groups": [[0], [1, 2]],
|
|
317
|
+
"concurrency": 16 }`) staging mutually-independent nodes; the shared executor
|
|
318
|
+
honors it.
|
|
336
319
|
|
|
337
320
|
#### Commands
|
|
338
321
|
|
|
@@ -581,8 +564,9 @@ resuming with a cursor for the wrong key is rejected.
|
|
|
581
564
|
|
|
582
565
|
### 7.6 Relations, Batch, and Concurrency
|
|
583
566
|
|
|
584
|
-
Relation
|
|
585
|
-
|
|
567
|
+
Relation nodes of the query's `components[]` entry are resolved against the
|
|
568
|
+
parents produced by earlier nodes and folded into the result at their node id
|
|
569
|
+
path:
|
|
586
570
|
|
|
587
571
|
- **`belongsTo` / `hasOne`** — source values from all parents are deduplicated
|
|
588
572
|
and fetched in a single `BatchGetItem`, then matched back to each parent (a
|
|
@@ -592,10 +576,10 @@ operations and merged into the result at their `resultPath`:
|
|
|
592
576
|
|
|
593
577
|
`BatchGetItem` requests are chunked at 100 keys, deduplicated, and retried on
|
|
594
578
|
`UnprocessedKeys` with capped exponential backoff (up to 10 attempts).
|
|
595
|
-
`BatchWriteItem` is chunked at 25 with the same `UnprocessedItems` retry.
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
579
|
+
`BatchWriteItem` is chunked at 25 with the same `UnprocessedItems` retry. Node
|
|
580
|
+
staging follows the component's `plan` inside the shared `run_behavior`
|
|
581
|
+
executor (deterministic in-order reference semantics). Result order is
|
|
582
|
+
preserved.
|
|
599
583
|
|
|
600
584
|
### 7.7 Transactions
|
|
601
585
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphddb",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Graph data modeling on DynamoDB with adjacency list pattern",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -54,6 +54,9 @@
|
|
|
54
54
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
55
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
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",
|
|
57
|
+
"gen:py:behaviors:fixture": "tsx src/cli.ts generate behaviors --lang python --entry python/tests/fixtures/models.ts --transactions python/tests/fixtures/definitions.ts --contracts python/tests/fixtures/definitions.ts --out python/tests/fixtures/generated_behaviors",
|
|
58
|
+
"gen:rust:behaviors:fixture": "tsx src/cli.ts generate behaviors --lang rust --entry python/tests/fixtures/models.ts --transactions python/tests/fixtures/definitions.ts --contracts python/tests/fixtures/definitions.ts --out rust/graphddb_runtime/tests/generated_behaviors",
|
|
59
|
+
"gen:php:behaviors:fixture": "tsx src/cli.ts generate behaviors --lang php --entry python/tests/fixtures/models.ts --transactions python/tests/fixtures/definitions.ts --contracts python/tests/fixtures/definitions.ts --out php/tests/fixtures/generated_behaviors",
|
|
57
60
|
"test:py": "python3 -m pytest python/tests -m \"not integration\"",
|
|
58
61
|
"test:py:integration": "python3 -m pytest python/tests -m integration",
|
|
59
62
|
"test:conformance": "tsx conformance/run.ts",
|
|
@@ -71,7 +74,9 @@
|
|
|
71
74
|
"embedoc:build": "embedoc build",
|
|
72
75
|
"embedoc:watch": "embedoc watch",
|
|
73
76
|
"embedoc:generate": "embedoc generate --all",
|
|
77
|
+
"prepare": "git config core.hooksPath .githooks 2>/dev/null || true",
|
|
74
78
|
"embedoc:check": "node scripts/embedoc-drift-check.mjs",
|
|
79
|
+
"deps:check": "node scripts/check-no-local-deps.mjs",
|
|
75
80
|
"vendor:bc-php": "node scripts/vendor-behavior-contracts-php.mjs",
|
|
76
81
|
"vendor:bc-php:check": "node scripts/vendor-behavior-contracts-php.mjs --check",
|
|
77
82
|
"vendor:bc-vectors": "node scripts/vendor-behavior-contracts-vectors.mjs",
|
|
@@ -80,6 +85,9 @@
|
|
|
80
85
|
"bench:crosslang:integration": "tsx benchmark/crosslang/integration.ts",
|
|
81
86
|
"bench:crosslang:test": "vitest run benchmark/crosslang/__tests__"
|
|
82
87
|
},
|
|
88
|
+
"size-limit-notes": {
|
|
89
|
+
"graphddb/spec (build-time)": "#290 transitional loop CLOSED at the final sub-pass (P3-5): the components[] emitter is now the PERMANENT (and only) portable carrier of relation fan-out + transaction write plans, so the sub-pass-1 dual-emit bump does not fully revert — the old operation/transaction builders remain as the emitter INTERNAL lowering input (they also feed the TS declarative executor and the AOT prepared plan); only their serialization into operations.json was removed, replaced by the small portable projection. Measured 22.38->22.51 KB across the flip (all first-party src/spec, zero new dependencies, all runtime bundles flat); budget stays 23 KB as the steady-state ceiling. Any future growth must be traced per the size-limit-gate discipline, never blanket-raised. #297 SP1+SP2 traced growth: 22.51->25.02 KB (+2.51 brotli), two traced components — (1) first-party src/spec/catalog.ts: the bc-typed GRAPHDDB_CATALOG + the SP1c emitter validation + the SP2 CQRS effect-derivation invariant, both wired into buildOperations so a catalog/effect mismatch is a BUILD error (load-bearing by design, must ship in this bundle); (2) behavior-contracts/dist/authoring.js partial inclusion, 5.66 KB minified (esbuild metafile-traced): the SP2-mandated classifyBehaviorEffect/referencedComponents live in bc's authoring module, whose top-level `export const add = binOp('add')`-style const-initializer calls esbuild cannot prove pure — bc 0.1.6 ships no sideEffects:false / __PURE__ annotations, so the two tiny helpers drag the authoring-DSL scaffolding along. NOT a new dependency (behavior-contracts already supplied this bundle's contract types); an upstream bc annotation fix reclaims (2), and the ceiling must be re-tightened when it lands. New ceiling 25.5 KB keeps the same ~0.5 KB headroom discipline as the previous 23 KB ceiling. #297 SP3+SP4 traced growth: 25.04->25.26 KB (+0.22 brotli), all first-party src/spec — the SP4 inversion (deriveComponentEffects + assertPublishSplitMatchesDerived with method-naming errors, replacing the SP2 symmetric assertion) plus the additive contractInputs.behaviors registration loop in buildOperations (rename + collision check + re-validation); the publishBehaviors authoring machinery itself is type-only from this bundle (src/define/behaviors.ts never enters dist/spec). Runtime-core bucket flat (39.08->39.07: the eager graphddb.publishBehaviors member tree-shakes out of a { DDBModel }-only consumer — bc 0.1.7 ships sideEffects:false); import-* bucket 47.8->55.4 (+7.6: bc compileBehaviors recorder/scanner + src/define/behaviors.ts + src/executor/behavior-exec.ts, all reachable only via publishBehaviors / behaviorComponents / behavior-exec). #297 SP5 traced growth: 25.26->28.84 KB (+3.58 brotli), one traced component — behavior-contracts/dist/builder.js (11.69 KB minified) + its guard.js dependency (3.51 KB minified), esbuild metafile-traced: SP5 moved components[] IR assembly/validation/canonicalization OUT of src/spec/components.ts and INTO bc's data-registration builder (buildComponentDefinition, bc#26 — the owner-decided split: extraction stays graphddb, IR shape is bc's), and the builder internally applies assertPortableComponentGraph (guard.js) as its fail-closed self-check, so both are load-bearing in the spec build path BY DESIGN (this is the seam the sub-pass exists to create, not a dependency leak). First-party src/spec shrank slightly (the hand-assembled Component return + inline plan emission were replaced by the registration call). New ceiling 29.3 KB keeps the same ~0.5 KB headroom discipline."
|
|
90
|
+
},
|
|
83
91
|
"size-limit": [
|
|
84
92
|
{
|
|
85
93
|
"name": "runtime core (DDBModel + authoring)",
|
|
@@ -102,7 +110,7 @@
|
|
|
102
110
|
"name": "graphddb/spec (build-time)",
|
|
103
111
|
"path": "dist/spec/index.js",
|
|
104
112
|
"import": "{ buildBridgeBundle }",
|
|
105
|
-
"limit": "
|
|
113
|
+
"limit": "29.3 KB"
|
|
106
114
|
},
|
|
107
115
|
{
|
|
108
116
|
"name": "graphddb/internal (AOT prepared-plan loader)",
|
|
@@ -150,7 +158,7 @@
|
|
|
150
158
|
},
|
|
151
159
|
"license": "MIT",
|
|
152
160
|
"dependencies": {
|
|
153
|
-
"behavior-contracts": "0.
|
|
161
|
+
"behavior-contracts": "0.2.0",
|
|
154
162
|
"commander": "^15.0.0",
|
|
155
163
|
"handlebars": "^4.7.9"
|
|
156
164
|
},
|