graphddb 0.7.8 → 0.7.10
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/cdc/index.d.ts +3 -2
- package/dist/cdc/index.js +3 -2
- package/dist/chunk-3UD3XIF2.js +860 -0
- package/dist/{chunk-HFFIB77D.js → chunk-3ZU2VW3L.js} +2 -1189
- package/dist/{chunk-IA6MW2HP.js → chunk-AD6ZQTTE.js} +4 -2
- package/dist/chunk-DFUKGU2Q.js +1197 -0
- package/dist/{chunk-NZRCBEWS.js → chunk-EOJDN3SA.js} +302 -728
- package/dist/cli.js +389 -29
- package/dist/{from-change-w2Ih8fkm.d.ts → from-change-Ty95KA8C.d.ts} +1 -1
- package/dist/{index-Deugy2sa.d.ts → index-Dc7d8mWI.d.ts} +161 -6
- package/dist/index.d.ts +127 -115
- package/dist/index.js +196 -59
- package/dist/linter/index.d.ts +5 -4
- package/dist/{maintenance-view-adapter-BCbgKG5d.d.ts → maintenance-view-adapter-BAZ9uBGe.d.ts} +122 -1091
- package/dist/{registry-pAnFcc62.d.ts → registry-LWE54Sdc.d.ts} +1 -1
- package/dist/{relation-depth-C9t4s9bt.d.ts → relation-depth-BRS513Tq.d.ts} +1 -1
- package/dist/spec/index.d.ts +4 -3
- package/dist/spec/index.js +18 -3
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.js +3 -2
- package/dist/transform/index.d.ts +460 -1
- package/dist/transform/index.js +2084 -1
- package/dist/types-BQLzTEqh.d.ts +1207 -0
- package/docs/python-bridge.md +17 -1
- package/package.json +7 -1
|
@@ -0,0 +1,1207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parameter placeholders for the parameterized query / command definition DSL
|
|
3
|
+
* (issue #41, Python-bridge Phase 0a).
|
|
4
|
+
*
|
|
5
|
+
* A `Param<T>` is a **branded placeholder** standing in for a value that is not
|
|
6
|
+
* known at definition time but is supplied later (at execution, e.g. from
|
|
7
|
+
* Python). It carries:
|
|
8
|
+
*
|
|
9
|
+
* - the **TypeScript value type** `T` it represents (`string`, `number`, or a
|
|
10
|
+
* string-literal union), preserved through the IR and the inferred return
|
|
11
|
+
* types of `defineQueries` / `defineCommands`; and
|
|
12
|
+
* - a **runtime descriptor** (`kind` + optional `literals`) that the static
|
|
13
|
+
* planner (#42) and the code generator read to emit `operations.json`.
|
|
14
|
+
*
|
|
15
|
+
* Placeholders are created with `param.string()`, `param.number()`, and
|
|
16
|
+
* `param.literal(...)`. They are *only* legal at value positions inside a
|
|
17
|
+
* parameterized key / changes structure passed to the `define*` entry points —
|
|
18
|
+
* never to the live `Model.query` / `Model.putItem` runtime API, whose types remain
|
|
19
|
+
* uncontaminated by params.
|
|
20
|
+
*/
|
|
21
|
+
/** Brand tag preventing a plain object from structurally matching a {@link Param}. */
|
|
22
|
+
declare const PARAM_BRAND: unique symbol;
|
|
23
|
+
/** Runtime discriminant for a parameter placeholder. */
|
|
24
|
+
type ParamKind = 'string' | 'number' | 'literal' | 'array';
|
|
25
|
+
/**
|
|
26
|
+
* A branded parameter placeholder representing a value of TypeScript type `T`
|
|
27
|
+
* that is bound at execution time rather than definition time.
|
|
28
|
+
*
|
|
29
|
+
* The brand (`[PARAM_BRAND]: T`) keeps `Param<string>` and `Param<number>`
|
|
30
|
+
* distinct and prevents a bare value from being mistaken for a placeholder.
|
|
31
|
+
*
|
|
32
|
+
* @typeParam T - The value type this placeholder stands in for.
|
|
33
|
+
*/
|
|
34
|
+
interface Param<out T> {
|
|
35
|
+
/** @internal Type brand carrying the represented value type. */
|
|
36
|
+
readonly [PARAM_BRAND]: T;
|
|
37
|
+
/** Runtime discriminant: `'string'` | `'number'` | `'literal'` | `'array'`. */
|
|
38
|
+
readonly kind: ParamKind;
|
|
39
|
+
/**
|
|
40
|
+
* For `param.literal(...)`, the allowed literal values (preserved at runtime
|
|
41
|
+
* for the generator). `undefined` for `string` / `number`.
|
|
42
|
+
*/
|
|
43
|
+
readonly literals?: readonly T[];
|
|
44
|
+
/**
|
|
45
|
+
* For `param.array({...})`, the descriptor of each element's fields. Lets the
|
|
46
|
+
* transaction planner (#46) type `forEach` element references and emit the
|
|
47
|
+
* `{item.<field>}` templates. `undefined` for scalar params.
|
|
48
|
+
*/
|
|
49
|
+
readonly element?: Readonly<Record<string, Param<unknown>>>;
|
|
50
|
+
/**
|
|
51
|
+
* Optional human-readable description of the parameter (issue #154), supplied
|
|
52
|
+
* via `param.string({ description })` etc. Pure documentation metadata — it does
|
|
53
|
+
* NOT affect the runtime descriptor (`kind` / `literals`) the planner reads for
|
|
54
|
+
* execution. When present it is propagated to the param's {@link
|
|
55
|
+
* import('./ir.js').ParamDescriptor} and then to the serialized
|
|
56
|
+
* {@link import('../spec/types.js').ParamSpec} in `operations.json`. Absent →
|
|
57
|
+
* unchanged output (backward compatible).
|
|
58
|
+
*/
|
|
59
|
+
readonly description?: string;
|
|
60
|
+
}
|
|
61
|
+
/** A `Param` whose represented value type is `string`. */
|
|
62
|
+
type StringParam = Param<string>;
|
|
63
|
+
/** A `Param` whose represented value type is `number`. */
|
|
64
|
+
type NumberParam = Param<number>;
|
|
65
|
+
/** A `Param` whose represented value type is the literal union `L`. */
|
|
66
|
+
type LiteralParam<L extends string | number> = Param<L>;
|
|
67
|
+
/** The element-field descriptor shape accepted by {@link param.array}. */
|
|
68
|
+
type ArrayElementShape = Record<string, Param<unknown>>;
|
|
69
|
+
/**
|
|
70
|
+
* The TypeScript element type implied by an {@link ArrayElementShape} `E`: each
|
|
71
|
+
* field carries the value type its placeholder represents.
|
|
72
|
+
*/
|
|
73
|
+
type ElementOf<E extends ArrayElementShape> = {
|
|
74
|
+
[K in keyof E]: E[K] extends Param<infer V> ? V : never;
|
|
75
|
+
};
|
|
76
|
+
/** A `Param` standing in for an **array** whose elements have shape `E`. The
|
|
77
|
+
* `kind` is narrowed to the `'array'` literal so type-level dispatch on array
|
|
78
|
+
* params works (`ParamProxy`'s array arm matches — pre-#263 the wide
|
|
79
|
+
* `ParamKind` made `p.<arrayParam>` type as a scalar ref and `ElementProxy`
|
|
80
|
+
* collapse to `never`; runtime behavior was always correct). */
|
|
81
|
+
type ArrayParam<E extends ArrayElementShape> = Param<ElementOf<E>[]> & {
|
|
82
|
+
readonly kind: 'array';
|
|
83
|
+
readonly element: E;
|
|
84
|
+
};
|
|
85
|
+
/** Allowed scalar value types a placeholder may stand in for. */
|
|
86
|
+
type ParamValue = string | number;
|
|
87
|
+
/**
|
|
88
|
+
* Options accepted by the placeholder factories (issue #154). Currently carries
|
|
89
|
+
* only the optional human-readable {@link Param.description} — pure documentation
|
|
90
|
+
* metadata that does not affect the runtime descriptor. The object form is
|
|
91
|
+
* additive: `param.string()` (no options) is unchanged.
|
|
92
|
+
*/
|
|
93
|
+
interface ParamOptions {
|
|
94
|
+
/** Optional human-readable description, propagated to `operations.json`. */
|
|
95
|
+
readonly description?: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Placeholder factory. Each call returns a branded {@link Param} carrying both
|
|
99
|
+
* the TypeScript value type and a runtime descriptor. Each scalar factory accepts
|
|
100
|
+
* an optional {@link ParamOptions} (issue #154) carrying a `description` — pure
|
|
101
|
+
* documentation propagated to `operations.json`; the no-argument call is unchanged.
|
|
102
|
+
*/
|
|
103
|
+
declare const param: {
|
|
104
|
+
/** A placeholder for a `string` value. */
|
|
105
|
+
readonly string: (options?: ParamOptions) => StringParam;
|
|
106
|
+
/** A placeholder for a `number` value. */
|
|
107
|
+
readonly number: (options?: ParamOptions) => NumberParam;
|
|
108
|
+
/**
|
|
109
|
+
* A placeholder constrained to one of the given literal values. The value
|
|
110
|
+
* type of the resulting {@link Param} is the **union of the literals**, so it
|
|
111
|
+
* is preserved precisely in the IR and the inferred definition types. A final
|
|
112
|
+
* plain-object argument is read as {@link ParamOptions} (a `description`),
|
|
113
|
+
* distinguished from the `string` / `number` literal values.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* param.literal('active', 'disabled'); // Param<'active' | 'disabled'>
|
|
118
|
+
* param.literal('active', 'disabled', { description: 'Account state.' });
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
readonly literal: <const L extends readonly [ParamValue, ...ParamValue[]]>(...valuesAndOptions: [...L] | [...L, ParamOptions]) => LiteralParam<L[number]>;
|
|
122
|
+
/**
|
|
123
|
+
* A placeholder for an **array** parameter whose elements have the given field
|
|
124
|
+
* shape. Used by `defineTransaction`'s `tx.forEach(p.<arrayParam>, …)` to bind
|
|
125
|
+
* each element's fields to `{item.<field>}` templates.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* param.array({ userId: param.string(), role: param.string() });
|
|
130
|
+
* // Param<{ userId: string; role: string }[]>
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
readonly array: <const E extends ArrayElementShape>(element: E, options?: ParamOptions) => ArrayParam<E>;
|
|
134
|
+
};
|
|
135
|
+
/** Runtime type guard: is `value` a parameter placeholder? */
|
|
136
|
+
declare function isParam(value: unknown): value is Param<unknown>;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Serializable static operation specs and manifest (issue #42, Python-bridge
|
|
140
|
+
* Phase 0b).
|
|
141
|
+
*
|
|
142
|
+
* Everything in this module is **JSON-serializable** by construction (only
|
|
143
|
+
* strings / numbers / booleans / arrays / plain objects — no functions, classes,
|
|
144
|
+
* `Date`, `Set`, or `undefined`-valued fields are emitted). These types mirror
|
|
145
|
+
* the `graphddb_manifest.json` / `graphddb_operations.json` shapes sketched in
|
|
146
|
+
* `docs/python-bridge.md`. The generator (#43) writes them to disk; the
|
|
147
|
+
* Python runtime (#44+) interprets them.
|
|
148
|
+
*
|
|
149
|
+
* ## Templates and placeholders
|
|
150
|
+
*
|
|
151
|
+
* Key-condition / range / item / change *values* are **template strings** with
|
|
152
|
+
* two placeholder forms:
|
|
153
|
+
*
|
|
154
|
+
* - `{paramName}` — bound from the caller-supplied params at execution time.
|
|
155
|
+
* - `{result.sourceField}` — bound from a field of the **prior** operation's
|
|
156
|
+
* result item(s), used to chain relation operations.
|
|
157
|
+
*
|
|
158
|
+
* A value with no `{...}` is a literal (e.g. a fixed sort-key discriminator
|
|
159
|
+
* `PROFILE`).
|
|
160
|
+
*/
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The schema version emitted in every manifest / operations document.
|
|
164
|
+
*
|
|
165
|
+
* `1.1` (issue #208, B案): the operation IR gained the **list fan-out binding
|
|
166
|
+
* form** ({@link SourceListSpec} on a `BatchGetItem` relation op), lifting the
|
|
167
|
+
* #197 `refs` loud-reject. The op vocabulary (the physical DynamoDB API surface)
|
|
168
|
+
* is unchanged — only the dataflow (source→key) binding grammar grew. Purely
|
|
169
|
+
* additive: every `1.0` document is a valid `1.1` document, and a document that
|
|
170
|
+
* uses no `refs` relation serializes byte-identically apart from this version.
|
|
171
|
+
*/
|
|
172
|
+
declare const SPEC_VERSION: "1.1";
|
|
173
|
+
/**
|
|
174
|
+
* The schema version stamped on an operations document that **actually
|
|
175
|
+
* contains** an SCP Expression IR node (issue #261, Phase 3 S1): a
|
|
176
|
+
* `{ kind: 'scpExpr' }` write condition or a `guard` (transaction item /
|
|
177
|
+
* contract command method). The vocabulary is purely **additive** — every
|
|
178
|
+
* `1.1` document is a valid `1.2` document — and the stamp is **conditional**:
|
|
179
|
+
* a bundle with no SCP node keeps `1.1` and serializes byte-identically to the
|
|
180
|
+
* pre-#261 output. Runtimes that do not yet EVALUATE the expression vocabulary
|
|
181
|
+
* keep `SPEC_VERSION_SUPPORTED` at `1.1`, so an expression-bearing document is
|
|
182
|
+
* loud-rejected (fail-closed) everywhere until the evaluation sub-issues
|
|
183
|
+
* (#265/#266/#267) land and bump the supported constant with the wiring.
|
|
184
|
+
*/
|
|
185
|
+
declare const SPEC_VERSION_SCP: "1.2";
|
|
186
|
+
/**
|
|
187
|
+
* The HIGHEST spec-IR minor this **TS runtime** understands and will EXECUTE
|
|
188
|
+
* (issue #265, Phase 3 S5). The shared `validateEnvelope` fail-closed rule is
|
|
189
|
+
* "major must match, minor must be ≤ supported" (envelope.ts): a bundle stamped
|
|
190
|
+
* at or below this version loads; a newer minor (or a bad major) is loud-rejected.
|
|
191
|
+
*
|
|
192
|
+
* S1 kept this at `1.1` so an expression-bearing `1.2` bundle was rejected
|
|
193
|
+
* everywhere (fail-closed) until evaluation landed. S5 wires the TS guard
|
|
194
|
+
* evaluator to `behavior-contracts` and moves it to `1.2`, so a guard-bearing
|
|
195
|
+
* `1.2` bundle is now ACCEPTED and EVALUATED on TS. A `1.2` bundle carrying an
|
|
196
|
+
* unknown `exprVersion` / operator still fail-closes (the expr evaluator rejects
|
|
197
|
+
* it). The py/rust/go runtimes bump in S6, PHP in S7 — the whole release column
|
|
198
|
+
* moves to `1.2` together.
|
|
199
|
+
*/
|
|
200
|
+
declare const SPEC_VERSION_SUPPORTED: "1.2";
|
|
201
|
+
/** A spec version an operations document may carry (see {@link SPEC_VERSION_SCP}). */
|
|
202
|
+
type SpecVersion = typeof SPEC_VERSION | typeof SPEC_VERSION_SCP;
|
|
203
|
+
/** Field type as carried in the manifest (derived from the DynamoDB type). */
|
|
204
|
+
type ManifestFieldType = 'string' | 'number' | 'boolean' | 'binary' | 'stringSet' | 'numberSet' | 'list' | 'map';
|
|
205
|
+
interface ManifestField {
|
|
206
|
+
readonly type: ManifestFieldType;
|
|
207
|
+
/**
|
|
208
|
+
* Semantic format for `string` fields that carry a serialized temporal value.
|
|
209
|
+
* `datetime` → ISO 8601 instant, `date` → `YYYY-MM-DD`. Absent for plain
|
|
210
|
+
* fields. The Python runtime uses this to restore `datetime` on hydration,
|
|
211
|
+
* mirroring the TS hydrator's `format`-driven `Date` reconstruction.
|
|
212
|
+
*/
|
|
213
|
+
readonly format?: 'datetime' | 'date';
|
|
214
|
+
/**
|
|
215
|
+
* Optional human-readable description of the field (issue #154), from a field
|
|
216
|
+
* decorator option (`@string({ description })`). Pure documentation — absent
|
|
217
|
+
* unless declared, so a field with no description serializes byte-identically
|
|
218
|
+
* to the pre-#154 manifest. Surfaces as a field comment in the generated Python.
|
|
219
|
+
*/
|
|
220
|
+
readonly description?: string;
|
|
221
|
+
}
|
|
222
|
+
/** A key (PK or GSI) template descriptor in the manifest. */
|
|
223
|
+
interface ManifestKey {
|
|
224
|
+
/** Input field names the key is composed from (in declaration order). */
|
|
225
|
+
readonly inputFields: readonly string[];
|
|
226
|
+
/** Template for the partition-key value, e.g. `EMAIL#{email}`. */
|
|
227
|
+
readonly pkTemplate: string;
|
|
228
|
+
/** Template for the sort-key value, or `null` when the key has no sort key. */
|
|
229
|
+
readonly skTemplate: string | null;
|
|
230
|
+
}
|
|
231
|
+
interface ManifestGsi extends ManifestKey {
|
|
232
|
+
readonly indexName: string;
|
|
233
|
+
readonly unique: boolean;
|
|
234
|
+
/**
|
|
235
|
+
* Optional human-readable description of the index (issue #166, follow-up of #154),
|
|
236
|
+
* from `gsi(name, key, { description })`. Pure documentation — absent unless declared,
|
|
237
|
+
* so a GSI with no description serializes byte-identically to the pre-#166 manifest.
|
|
238
|
+
* The Python codegen surfaces it as the docstring of a generated query method that
|
|
239
|
+
* reads through this index.
|
|
240
|
+
*/
|
|
241
|
+
readonly description?: string;
|
|
242
|
+
}
|
|
243
|
+
interface ManifestRelation {
|
|
244
|
+
readonly type: 'hasMany' | 'hasOne' | 'belongsTo' | 'refs';
|
|
245
|
+
/** Target entity (model class) name. */
|
|
246
|
+
readonly target: string;
|
|
247
|
+
/** target field → source field (on this entity). */
|
|
248
|
+
readonly keyBinding: Readonly<Record<string, string>>;
|
|
249
|
+
/**
|
|
250
|
+
* List-valued source descriptor for a `refs` relation (issue #197). Present IFF
|
|
251
|
+
* `type === 'refs'`: `from` is the parent LIST attribute holding the inline
|
|
252
|
+
* reference elements (e.g. `'tagRefs'`), `key` the field read off each element
|
|
253
|
+
* (e.g. `'tagId'`). Absent for every scalar-keyed relation (byte-identical to
|
|
254
|
+
* the pre-#197 manifest). Carried so a manifest consumer can reconstruct where a
|
|
255
|
+
* `refs` relation's BatchGet keys come from.
|
|
256
|
+
*/
|
|
257
|
+
readonly refs?: Readonly<{
|
|
258
|
+
from: string;
|
|
259
|
+
key: string;
|
|
260
|
+
}>;
|
|
261
|
+
/**
|
|
262
|
+
* Optional human-readable description of the relation (issue #166, follow-up of
|
|
263
|
+
* #154), from `@hasMany/@belongsTo/@hasOne(..., { description })`. Pure documentation
|
|
264
|
+
* — absent unless declared, so a relation with no description serializes
|
|
265
|
+
* byte-identically to the pre-#166 manifest. The Python codegen surfaces it as a
|
|
266
|
+
* trailing `# …` comment on the relation's field in the generated result type.
|
|
267
|
+
*/
|
|
268
|
+
readonly description?: string;
|
|
269
|
+
}
|
|
270
|
+
interface ManifestEntity {
|
|
271
|
+
/** Declared (logical) table name. */
|
|
272
|
+
readonly table: string;
|
|
273
|
+
/** Physical table name (after `TableMapping` resolution). */
|
|
274
|
+
readonly physicalName: string;
|
|
275
|
+
/** PK prefix, e.g. `USER#`. */
|
|
276
|
+
readonly prefix: string;
|
|
277
|
+
readonly fields: Readonly<Record<string, ManifestField>>;
|
|
278
|
+
/** Primary key, or `null` when the entity has none. */
|
|
279
|
+
readonly key: ManifestKey | null;
|
|
280
|
+
readonly gsis: readonly ManifestGsi[];
|
|
281
|
+
readonly relations: Readonly<Record<string, ManifestRelation>>;
|
|
282
|
+
/**
|
|
283
|
+
* Optional human-readable description of the entity (issue #154), from
|
|
284
|
+
* `@model({ description })`. Pure documentation — absent unless declared, so an
|
|
285
|
+
* entity with no description serializes byte-identically to the pre-#154
|
|
286
|
+
* manifest. Surfaces as the generated Python class docstring.
|
|
287
|
+
*/
|
|
288
|
+
readonly description?: string;
|
|
289
|
+
/**
|
|
290
|
+
* The physical attribute name of the model's DynamoDB **Time-To-Live** field,
|
|
291
|
+
* when one is declared via `@ttl` (issue #172, Epic #167 — CFn generator C4).
|
|
292
|
+
* Unlike the maintenance/stream signals (a maintenance-IR concern kept OFF the
|
|
293
|
+
* manifest), TTL is a **physical schema** fact, and the CloudFormation emitter
|
|
294
|
+
* consumes the manifest — so the TTL attribute name flows here and the emitter
|
|
295
|
+
* renders `TimeToLiveSpecification { AttributeName: <this>, Enabled: true }`. The
|
|
296
|
+
* attribute is deliberately NOT added to the table's key `AttributeDefinitions`
|
|
297
|
+
* (that list holds ONLY key/index attributes; `TimeToLiveSpecification` legitimately
|
|
298
|
+
* names a non-key attribute). **Absent** when the model declares no `@ttl`, so a
|
|
299
|
+
* TTL-free model serializes byte-identically to the pre-#172 manifest (backward
|
|
300
|
+
* compatible). DynamoDB permits exactly one TTL attribute per physical table; the
|
|
301
|
+
* single-per-table rule is enforced at manifest build.
|
|
302
|
+
*/
|
|
303
|
+
readonly ttlAttribute?: string;
|
|
304
|
+
}
|
|
305
|
+
interface ManifestTable {
|
|
306
|
+
readonly physicalName: string;
|
|
307
|
+
}
|
|
308
|
+
interface Manifest {
|
|
309
|
+
/**
|
|
310
|
+
* Always the base {@link SPEC_VERSION}: the manifest carries entity/schema
|
|
311
|
+
* metadata only — the SCP Expression IR vocabulary (issue #261) lives in the
|
|
312
|
+
* operations document, so the conditional `1.2` stamp never applies here and
|
|
313
|
+
* every manifest stays byte-identical.
|
|
314
|
+
*/
|
|
315
|
+
readonly version: typeof SPEC_VERSION;
|
|
316
|
+
readonly tables: Readonly<Record<string, ManifestTable>>;
|
|
317
|
+
readonly entities: Readonly<Record<string, ManifestEntity>>;
|
|
318
|
+
}
|
|
319
|
+
interface ParamSpec {
|
|
320
|
+
readonly type: ParamKind;
|
|
321
|
+
readonly required: boolean;
|
|
322
|
+
/** Allowed literal values for `literal` params; omitted otherwise. */
|
|
323
|
+
readonly literals?: readonly (string | number)[];
|
|
324
|
+
/**
|
|
325
|
+
* For an `array` param (transaction `forEach` source), the per-element field
|
|
326
|
+
* descriptors (field name → element param spec). Omitted for scalar params.
|
|
327
|
+
*/
|
|
328
|
+
readonly element?: Readonly<Record<string, ParamSpec>>;
|
|
329
|
+
/**
|
|
330
|
+
* Optional human-readable description of the parameter (issue #154), from a
|
|
331
|
+
* `param.*({ description })` placeholder. Pure documentation — absent unless
|
|
332
|
+
* declared, so a param with no description serializes byte-identically to the
|
|
333
|
+
* pre-#154 spec. The Python runtime never reads it for execution.
|
|
334
|
+
*/
|
|
335
|
+
readonly description?: string;
|
|
336
|
+
}
|
|
337
|
+
/** A `begins_with` range condition on a sort key (templated value). */
|
|
338
|
+
interface RangeConditionSpec {
|
|
339
|
+
readonly operator: 'begins_with';
|
|
340
|
+
readonly key: string;
|
|
341
|
+
/** Template value, e.g. `GROUP#` or `GROUP#{result.groupId}`. */
|
|
342
|
+
readonly value: string;
|
|
343
|
+
}
|
|
344
|
+
/** Server-side declarative filter, carried verbatim for the runtime to compile. */
|
|
345
|
+
interface FilterSpec {
|
|
346
|
+
/** The declarative {@link FilterInput} tree (JSON-safe). */
|
|
347
|
+
readonly declarative: unknown;
|
|
348
|
+
}
|
|
349
|
+
type ReadOperationType = 'Query' | 'GetItem' | 'BatchGetItem';
|
|
350
|
+
/**
|
|
351
|
+
* A single static read operation. Relation chains are expressed as multiple
|
|
352
|
+
* operations whose `resultPath` / templated key conditions wire them together.
|
|
353
|
+
*/
|
|
354
|
+
interface OperationSpec {
|
|
355
|
+
readonly type: ReadOperationType;
|
|
356
|
+
readonly tableName: string;
|
|
357
|
+
readonly indexName?: string;
|
|
358
|
+
/**
|
|
359
|
+
* Key condition: attribute name → template value. For a `BatchGetItem` this is
|
|
360
|
+
* the per-key shape (one key per resolved source item at runtime).
|
|
361
|
+
*/
|
|
362
|
+
readonly keyCondition: Readonly<Record<string, string>>;
|
|
363
|
+
readonly rangeCondition?: RangeConditionSpec;
|
|
364
|
+
/** Projected fields (entity field names). */
|
|
365
|
+
readonly projection: readonly string[];
|
|
366
|
+
readonly limit?: number;
|
|
367
|
+
readonly filter?: FilterSpec;
|
|
368
|
+
/**
|
|
369
|
+
* Where the result of this operation is placed in the assembled result.
|
|
370
|
+
* `$` is the root; `$.groups.items` a relation connection's items, etc.
|
|
371
|
+
*/
|
|
372
|
+
readonly resultPath: string;
|
|
373
|
+
/**
|
|
374
|
+
* For relation operations: the source field on the prior result whose value(s)
|
|
375
|
+
* drive this operation's `{result.*}` placeholders. Absent on the root op.
|
|
376
|
+
*/
|
|
377
|
+
readonly sourceField?: string;
|
|
378
|
+
/**
|
|
379
|
+
* The **list fan-out binding form** (issue #208, B案 — the shared-IR
|
|
380
|
+
* generalization that lifted the #197 `refs` loud-reject). Present only on a
|
|
381
|
+
* `BatchGetItem` relation operation that resolves a `refs` relation: instead of
|
|
382
|
+
* reading ONE scalar `parent[sourceField]`, the runtime reads the parent **list
|
|
383
|
+
* attribute** {@link SourceListSpec.from} and binds `{result.<sourceField>}`
|
|
384
|
+
* once per element — `element[key]` for an object element, the element itself
|
|
385
|
+
* for a bare scalar — skipping null/undefined refs. All element keys across all
|
|
386
|
+
* parents fan into ONE deduped `BatchGetItem` (the existing chunk/retry
|
|
387
|
+
* machinery), and the operation's `resultPath` (`…<prop>.items`) receives a
|
|
388
|
+
* connection `{ items, cursor: null }` whose items are the resolved child
|
|
389
|
+
* bodies in **first-seen element order**, deduped, with missing bodies dropped
|
|
390
|
+
* — byte-matching the TS in-process `fetchRefsList` semantics. Absent on every
|
|
391
|
+
* scalar-bound relation op (a pre-#208 document is byte-identical).
|
|
392
|
+
*/
|
|
393
|
+
readonly sourceList?: SourceListSpec;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* The list-valued source descriptor of a fan-out `BatchGetItem` operation (issue
|
|
397
|
+
* #208 B案; the operation-IR mirror of the manifest's `ManifestRelation.refs`).
|
|
398
|
+
*/
|
|
399
|
+
interface SourceListSpec {
|
|
400
|
+
/** The parent LIST attribute holding the inline reference elements (e.g. `tagRefs`). */
|
|
401
|
+
readonly from: string;
|
|
402
|
+
/**
|
|
403
|
+
* The field read off each element (e.g. `tagId`). Always equals the operation's
|
|
404
|
+
* `sourceField` (the `{result.<sourceField>}` token name); carried explicitly so
|
|
405
|
+
* the op is self-describing.
|
|
406
|
+
*/
|
|
407
|
+
readonly key: string;
|
|
408
|
+
/**
|
|
409
|
+
* `true` when the parent list attribute was NOT selected by the query and was
|
|
410
|
+
* projected onto the parent operation **solely** to drive this fan-out. The
|
|
411
|
+
* runtime must then delete `from` from every parent node after ALL relation
|
|
412
|
+
* operations are applied — mirroring the TS hydrator, which projects implicit
|
|
413
|
+
* relation-source fields but strips them from the assembled result. Absent when
|
|
414
|
+
* the query's select projects the attribute itself (it stays in the result).
|
|
415
|
+
*/
|
|
416
|
+
readonly implicit?: boolean;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* The **execution plan** for a multi-operation read (issue #70a). It is the
|
|
420
|
+
* Single Source of Truth for *how* a query's {@link OperationSpec}[] is staged:
|
|
421
|
+
* which operations are independent (may run concurrently) and which must wait for
|
|
422
|
+
* a prior operation's result. Every runtime that honors it produces identical
|
|
423
|
+
* effects with identical concurrency discipline — the staging is **serialized,
|
|
424
|
+
* not re-derived** (cf. the contract decided-facts discipline).
|
|
425
|
+
*
|
|
426
|
+
* - `groups` is an ordered list of **stages**; each stage is a list of indices
|
|
427
|
+
* into the query's `operations[]`. Operations **within** a stage are mutually
|
|
428
|
+
* independent and may be issued concurrently; stages run **in order** because a
|
|
429
|
+
* later stage reads a `{result.*}` value produced by an earlier one. Stage 0 is
|
|
430
|
+
* always `[0]` (the root). Every operation index appears in exactly one stage,
|
|
431
|
+
* and the stages are a topological layering of the result-dependency graph.
|
|
432
|
+
* - `concurrency` is the declared in-flight bound a runtime applies **within**
|
|
433
|
+
* each stage (the shared {@link RELATION_TRAVERSAL_CONCURRENCY} = 16): at most
|
|
434
|
+
* this many operations of a stage are issued at once.
|
|
435
|
+
*
|
|
436
|
+
* ## Derivation (see `buildReadOperations` / `deriveExecutionPlan`)
|
|
437
|
+
*
|
|
438
|
+
* The planner emits `operations[]` in a deterministic pre-order: the root at
|
|
439
|
+
* index 0, then each relation child immediately followed by its own descendants.
|
|
440
|
+
* A child's key templates reference `{result.<sourceField>}` of the **operation
|
|
441
|
+
* whose `resultPath` is the child's parent path** — that producer is the child's
|
|
442
|
+
* sole dependency. The stage of an operation is therefore `1 + stage(parent)`:
|
|
443
|
+
* the root is stage 0; sibling relations that share a parent path land in the
|
|
444
|
+
* **same** stage (no inter-`{result.*}` dependency between siblings, so they are
|
|
445
|
+
* independent and concurrency-eligible); a grandchild that reads its parent's
|
|
446
|
+
* `{result.*}` lands one stage later. This mirrors exactly the level-by-level
|
|
447
|
+
* fan-out the TS relation runtime performs at runtime (`resolveRelations` issues
|
|
448
|
+
* sibling relations together under the same bound) — #70a only *records* it.
|
|
449
|
+
*
|
|
450
|
+
* Absent on a single-operation spec and on specs produced before #70a; a consumer
|
|
451
|
+
* without a plan falls back to one-operation-per-stage **sequential** execution
|
|
452
|
+
* (the pre-#70 behavior), so an absent plan never changes results.
|
|
453
|
+
*/
|
|
454
|
+
interface ExecutionPlanSpec {
|
|
455
|
+
/** Ordered stages; each stage is indices into `operations[]` (stage 0 = `[0]`). */
|
|
456
|
+
readonly groups: readonly (readonly number[])[];
|
|
457
|
+
/** The declared in-flight bound applied within each stage (16). */
|
|
458
|
+
readonly concurrency: number;
|
|
459
|
+
}
|
|
460
|
+
/** A query (read) definition's full execution spec. */
|
|
461
|
+
interface QuerySpec {
|
|
462
|
+
readonly params: Readonly<Record<string, ParamSpec>>;
|
|
463
|
+
readonly operations: readonly OperationSpec[];
|
|
464
|
+
/**
|
|
465
|
+
* Result cardinality of the **root** (`$`) operation, derived from the
|
|
466
|
+
* definition kind: `'one'` for `defineQuery` (a single entity object, with any
|
|
467
|
+
* relations attached directly), `'many'` for `defineList` (a `{ items, cursor }`
|
|
468
|
+
* connection). The relation runtime (#45) uses this to shape the root result:
|
|
469
|
+
* a `'one'` Query root takes the first matched item rather than returning a
|
|
470
|
+
* connection. Absent in specs produced before #45; consumers should treat an
|
|
471
|
+
* absent value as `'many'` for a `Query`/`BatchGetItem` root and `'one'` for a
|
|
472
|
+
* `GetItem` root (the pre-#45 behavior).
|
|
473
|
+
*/
|
|
474
|
+
readonly cardinality?: 'one' | 'many';
|
|
475
|
+
/**
|
|
476
|
+
* The staged execution plan (issue #70a): which {@link operations} are
|
|
477
|
+
* independent (concurrency-eligible) vs. result-dependent. Present only when the
|
|
478
|
+
* query has more than one operation (a relation chain); a single-operation read
|
|
479
|
+
* needs no plan. See {@link ExecutionPlanSpec} for the derivation and the
|
|
480
|
+
* backward-compatible (plan-absent → sequential) fallback.
|
|
481
|
+
*/
|
|
482
|
+
readonly executionPlan?: ExecutionPlanSpec;
|
|
483
|
+
/**
|
|
484
|
+
* Optional human-readable description of the query (issue #154), from
|
|
485
|
+
* `defineQuery(..., { description })`. Pure documentation — absent unless
|
|
486
|
+
* declared, so a query with no description serializes byte-identically to the
|
|
487
|
+
* pre-#154 spec. Surfaces as the generated Python repository-method docstring.
|
|
488
|
+
*/
|
|
489
|
+
readonly description?: string;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* The Expression IR vocabulary version (behavior-contracts `expression-ir.md`
|
|
493
|
+
* §9). A runtime handed an unknown `exprVersion` MUST fail closed.
|
|
494
|
+
*/
|
|
495
|
+
declare const EXPR_VERSION: 1;
|
|
496
|
+
/**
|
|
497
|
+
* The closed operator vocabulary of Expression IR v1 (expression-ir.md §3).
|
|
498
|
+
* An operator node is a **single-key** object — key = operator name, value =
|
|
499
|
+
* the argument array. Anything outside this set is invalid IR (fail-closed).
|
|
500
|
+
*/
|
|
501
|
+
type ExpressionOperator = 'add' | 'sub' | 'mul' | 'div' | 'mod' | 'neg' | 'concat' | 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge' | 'and' | 'or' | 'not' | 'coalesce' | 'cond' | 'len';
|
|
502
|
+
/**
|
|
503
|
+
* A scalar literal carried as-is (expression-ir.md §2.1). `number` follows the
|
|
504
|
+
* §2.3 classification rule: a fractional value is a float; an integer value is
|
|
505
|
+
* an int and MUST be within the safe-integer range (beyond it the literal is
|
|
506
|
+
* carried as {@link ExpressionIntNode}). NaN / ±Infinity / undefined cannot
|
|
507
|
+
* exist in the IR.
|
|
508
|
+
*/
|
|
509
|
+
type ExpressionScalar = string | number | boolean | null;
|
|
510
|
+
/** Strict member access (`a.b.c`); a null intermediate is a Failure (§2.2). */
|
|
511
|
+
interface ExpressionRefNode {
|
|
512
|
+
readonly ref: readonly string[];
|
|
513
|
+
}
|
|
514
|
+
/** Null-propagating member access (`a?.b`); a null intermediate → null (§2.2). */
|
|
515
|
+
interface ExpressionRefOptNode {
|
|
516
|
+
readonly refOpt: readonly string[];
|
|
517
|
+
}
|
|
518
|
+
/** Object construction (`{ x: <expr> }`, §2.3). Keys sort canonically. */
|
|
519
|
+
interface ExpressionObjNode {
|
|
520
|
+
readonly obj: Readonly<Record<string, ExpressionNode>>;
|
|
521
|
+
}
|
|
522
|
+
/** Array construction (`[a, b]`, §2.3). */
|
|
523
|
+
interface ExpressionArrNode {
|
|
524
|
+
readonly arr: readonly ExpressionNode[];
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* An int literal beyond the safe-integer range (±2^53−1), carried as a string
|
|
528
|
+
* because a raw JSON number would lose precision in TS (§2.3). Must fit i64.
|
|
529
|
+
*/
|
|
530
|
+
interface ExpressionIntNode {
|
|
531
|
+
readonly int: string;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* An **integer-valued** float literal (TS surface `1.0`), wrapped because JSON
|
|
535
|
+
* does not preserve the decimal point (§2.3). A fractional float (`1.5`) is a
|
|
536
|
+
* plain JSON number.
|
|
537
|
+
*/
|
|
538
|
+
interface ExpressionFloatNode {
|
|
539
|
+
readonly float: number;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* An operator node: a single-key object mapping one {@link ExpressionOperator}
|
|
543
|
+
* to its argument array (§2.1). The mapped-union form makes each member carry
|
|
544
|
+
* **exactly** its one operator key.
|
|
545
|
+
*/
|
|
546
|
+
type ExpressionOpNode = {
|
|
547
|
+
[K in ExpressionOperator]: {
|
|
548
|
+
readonly [P in K]: readonly ExpressionNode[];
|
|
549
|
+
};
|
|
550
|
+
}[ExpressionOperator];
|
|
551
|
+
/**
|
|
552
|
+
* One Expression IR node (expression-ir.md §2): a scalar literal, a reference,
|
|
553
|
+
* a construction form, a wrapped int/float literal, or an operator node.
|
|
554
|
+
*/
|
|
555
|
+
type ExpressionNode = ExpressionScalar | ExpressionRefNode | ExpressionRefOptNode | ExpressionObjNode | ExpressionArrNode | ExpressionIntNode | ExpressionFloatNode | ExpressionOpNode;
|
|
556
|
+
/**
|
|
557
|
+
* A serialized SCP expression (issue #261): the versioned envelope around one
|
|
558
|
+
* Expression IR node. This is the payload of a `{ kind: 'scpExpr' }` write
|
|
559
|
+
* condition and of every `guard` slot. The embedded `expr` is stored in
|
|
560
|
+
* **canonical form** (§2.3/§2.4: key-sorted at every level — code-point order —
|
|
561
|
+
* with `{int:"…"}` / `{float:n}` literal wrapping), produced by
|
|
562
|
+
* `canonicalizeExpressionSpec` in `src/spec/expression.ts`, so the document
|
|
563
|
+
* serializes deterministically through the ordinary JSON emitters.
|
|
564
|
+
*/
|
|
565
|
+
interface ExpressionSpec {
|
|
566
|
+
readonly exprVersion: typeof EXPR_VERSION;
|
|
567
|
+
readonly expr: ExpressionNode;
|
|
568
|
+
}
|
|
569
|
+
type WriteOperationType = 'PutItem' | 'UpdateItem' | 'DeleteItem';
|
|
570
|
+
/**
|
|
571
|
+
* A condition expression on a write, as a JSON-serializable spec (issues #46,
|
|
572
|
+
* #81). The supported subset:
|
|
573
|
+
*
|
|
574
|
+
* - `{ kind: 'notExists' }` → `attribute_not_exists(PK)` (legacy whole-row guard).
|
|
575
|
+
* - `{ kind: 'attributeExists'; field }` → `attribute_exists(<field>)` — the named
|
|
576
|
+
* attribute (any field, incl. PK/SK) must be present. The foundation for
|
|
577
|
+
* referential-integrity derivation.
|
|
578
|
+
* - `{ kind: 'attributeNotExists'; field }` → `attribute_not_exists(<field>)` — the
|
|
579
|
+
* named attribute must be absent (field-level uniqueness / first-write guard).
|
|
580
|
+
* - `{ kind: 'equals'; fields }` → `#f = :v AND …` field equality (each value a
|
|
581
|
+
* `{param}` / literal template).
|
|
582
|
+
* - `{ kind: 'expr'; declarative }` → the full declarative operator tree (issue
|
|
583
|
+
* #114-A): comparison (`gt`/`ge`/`lt`/`le`/`ne`), `between`, `in`,
|
|
584
|
+
* `begins_with`/`contains`/`notContains`, `attributeType`, and the logical
|
|
585
|
+
* `and`/`or`/`not` groups, mirroring the read-side {@link FilterSpec}. The tree
|
|
586
|
+
* is JSON-safe: each leaf value is either a native literal (string / number /
|
|
587
|
+
* boolean) or a {@link ConditionParamLeaf} (`{ $param }`) marking a caller
|
|
588
|
+
* param bound at execution time. The runtime resolves the param leaves against
|
|
589
|
+
* the caller params, then compiles the tree to a DynamoDB `ConditionExpression`
|
|
590
|
+
* with the SAME mechanics the filter compiler uses (so TS and Python emit an
|
|
591
|
+
* identical expression / semantics).
|
|
592
|
+
*/
|
|
593
|
+
type ConditionSpec = {
|
|
594
|
+
readonly kind: 'notExists';
|
|
595
|
+
} | {
|
|
596
|
+
readonly kind: 'attributeExists';
|
|
597
|
+
readonly field: string;
|
|
598
|
+
} | {
|
|
599
|
+
readonly kind: 'attributeNotExists';
|
|
600
|
+
readonly field: string;
|
|
601
|
+
} | {
|
|
602
|
+
readonly kind: 'equals';
|
|
603
|
+
readonly fields: Readonly<Record<string, string>>;
|
|
604
|
+
} | {
|
|
605
|
+
readonly kind: 'expr';
|
|
606
|
+
readonly declarative: unknown;
|
|
607
|
+
} | {
|
|
608
|
+
/**
|
|
609
|
+
* A raw DynamoDB condition produced by the `cond` escape hatch (issue
|
|
610
|
+
* #114-B), for write conditions that the declarative operator subset
|
|
611
|
+
* cannot express. It is **pre-compiled and deterministic**: the
|
|
612
|
+
* `expression` is a finished DynamoDB `ConditionExpression` whose name
|
|
613
|
+
* placeholders are stable `#cr_<field>` aliases (reused per distinct
|
|
614
|
+
* column) and whose value placeholders are sequential `:cr0`, `:cr1`, …
|
|
615
|
+
* (assigned in template order), so the serialized golden is stable. The
|
|
616
|
+
* names map binds each `#cr_<field>` alias to its entity field; the values
|
|
617
|
+
* map binds each `:crN` alias to either a native literal (an embedded
|
|
618
|
+
* `cond` value — in-process the concrete value) or a {@link ConditionParamLeaf}
|
|
619
|
+
* (`{ $param }`) marker bound from a caller param at execution time (the
|
|
620
|
+
* public-contract slot). The runtime substitutes the param leaves, then
|
|
621
|
+
* attaches `expression` / `names` / serialized `values` to the write —
|
|
622
|
+
* TS and Python build the identical DynamoDB expression.
|
|
623
|
+
*/
|
|
624
|
+
readonly kind: 'raw';
|
|
625
|
+
readonly expression: string;
|
|
626
|
+
readonly names: Readonly<Record<string, string>>;
|
|
627
|
+
readonly values: Readonly<Record<string, unknown>>;
|
|
628
|
+
} | {
|
|
629
|
+
/**
|
|
630
|
+
* An SCP Expression IR write condition (issue #261, Phase 3 S1): the
|
|
631
|
+
* condition is a behavior-contracts `expression-ir.md` §2 expression,
|
|
632
|
+
* carried as a versioned {@link ExpressionSpec} in canonical form. A
|
|
633
|
+
* document containing this variant is stamped spec version
|
|
634
|
+
* {@link SPEC_VERSION_SCP} (`1.2`, conditional stamping), so a runtime
|
|
635
|
+
* that does not yet evaluate the expression vocabulary loud-rejects the
|
|
636
|
+
* whole document (fail-closed) instead of silently dropping the
|
|
637
|
+
* condition. Evaluation wiring is the Phase 3 S5/S6/S7 sub-issues.
|
|
638
|
+
*/
|
|
639
|
+
readonly kind: 'scpExpr';
|
|
640
|
+
readonly expression: ExpressionSpec;
|
|
641
|
+
};
|
|
642
|
+
interface CommandSpec {
|
|
643
|
+
readonly type: WriteOperationType;
|
|
644
|
+
readonly tableName: string;
|
|
645
|
+
readonly entity: string;
|
|
646
|
+
readonly params: Readonly<Record<string, ParamSpec>>;
|
|
647
|
+
/**
|
|
648
|
+
* Key condition for `UpdateItem` / `DeleteItem` (attribute name → template).
|
|
649
|
+
* Absent for `PutItem` (the whole item is built from `item`).
|
|
650
|
+
*/
|
|
651
|
+
readonly keyCondition?: Readonly<Record<string, string>>;
|
|
652
|
+
/** The full item template for `PutItem` (field name → template / literal). */
|
|
653
|
+
readonly item?: Readonly<Record<string, string>>;
|
|
654
|
+
/** Field-level changes for `UpdateItem` (field name → template / literal). */
|
|
655
|
+
readonly changes?: Readonly<Record<string, string>>;
|
|
656
|
+
/** Optional write condition (subset). */
|
|
657
|
+
readonly condition?: ConditionSpec;
|
|
658
|
+
/**
|
|
659
|
+
* Optional human-readable description of the command (issue #154), from
|
|
660
|
+
* `definePut`/`defineUpdate`/`defineDelete(..., { description })`. Pure
|
|
661
|
+
* documentation — absent unless declared, so a command with no description
|
|
662
|
+
* serializes byte-identically to the pre-#154 spec. Surfaces as the generated
|
|
663
|
+
* Python repository-method docstring.
|
|
664
|
+
*/
|
|
665
|
+
readonly description?: string;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* A declarative `when` guard on a transaction item (issue #46). Compares a
|
|
669
|
+
* templated left-hand value against a templated right-hand value; the item is
|
|
670
|
+
* skipped when the comparison does not hold. Both values are template strings
|
|
671
|
+
* (`{param}` / `{item.<field>}` / literal), resolved by the runtime before the
|
|
672
|
+
* comparison. The runtime compares the resolved **string** values.
|
|
673
|
+
*/
|
|
674
|
+
interface WhenSpec {
|
|
675
|
+
readonly op: 'eq' | 'ne';
|
|
676
|
+
/** Templated left-hand value (typically an `{item.<field>}` or `{param}`). */
|
|
677
|
+
readonly left: string;
|
|
678
|
+
/** Templated right-hand value (literal or another reference). */
|
|
679
|
+
readonly right: string;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* The kind of a transaction item. The three write kinds (`Put` / `Update` /
|
|
683
|
+
* `Delete`) mutate an item; `ConditionCheck` (issue #81) is a **read-only
|
|
684
|
+
* assertion** on another item — it asserts a {@link ConditionSpec} holds for the
|
|
685
|
+
* keyed row without modifying it, and its failure cancels the **whole**
|
|
686
|
+
* `TransactWriteItems` atomically. It is the foundation for referential-integrity
|
|
687
|
+
* derivation (proposal: `requires <Entity> exists` → a `ConditionCheck` with
|
|
688
|
+
* `attribute_exists`).
|
|
689
|
+
*/
|
|
690
|
+
type TransactionItemType = 'Put' | 'Update' | 'Delete' | 'ConditionCheck';
|
|
691
|
+
/**
|
|
692
|
+
* A value leaf of a transaction `item` / `changes` / `add` map (issue #245).
|
|
693
|
+
*
|
|
694
|
+
* - a **string** carries a template: a `{param}` / `{item.field}` placeholder, a
|
|
695
|
+
* composite (`PREFIX#{param}`), or a plain string literal — resolved by the
|
|
696
|
+
* runtimes' template machinery (a whole-placeholder string keeps the bound
|
|
697
|
+
* param's *type*, a composite / plain string resolves to a string);
|
|
698
|
+
* - a **number** / **boolean** carries a *typed literal* verbatim, so a numeric
|
|
699
|
+
* literal in a `put` item / `update` changes (`version: 0`) survives to DynamoDB
|
|
700
|
+
* as an `N` / `BOOL` instead of being stringified to an `S` (the pre-#245 bug).
|
|
701
|
+
*
|
|
702
|
+
* A `Date` literal is serialized to its ISO-8601 **string** at build time (a
|
|
703
|
+
* `@datetime` / `@date` field is stored as `S`), so it stays a string leaf.
|
|
704
|
+
*/
|
|
705
|
+
type TransactionValueLeaf = string | number | boolean;
|
|
706
|
+
/**
|
|
707
|
+
* A single templated item in a transaction. When `forEach` is present, the item
|
|
708
|
+
* is expanded **once per element** of the named array param, with each element's
|
|
709
|
+
* fields bound to the item's `{item.<field>}` placeholders.
|
|
710
|
+
*
|
|
711
|
+
* Field presence by `type`:
|
|
712
|
+
*
|
|
713
|
+
* | type | item | keyCondition | changes | add | condition |
|
|
714
|
+
* |----------------|------|--------------|---------|-----|--------------------------|
|
|
715
|
+
* | `Put` | ✓ | — | — | — | optional |
|
|
716
|
+
* | `Update` | — | ✓ | ✓ / — | ✓ / — | optional |
|
|
717
|
+
* | `Delete` | — | ✓ | — | — | optional |
|
|
718
|
+
* | `ConditionCheck` | — | ✓ | — | — | **required** (the assert) |
|
|
719
|
+
*
|
|
720
|
+
* An `Update` carries `changes` (a `SET` of named fields) and/or `add` (an atomic
|
|
721
|
+
* `ADD` of named numeric fields, issue #85) — at least one of the two. The two are
|
|
722
|
+
* distinct DynamoDB update actions: `SET #f = :v` **overwrites**, while
|
|
723
|
+
* `ADD #f :delta` **atomically increments** without a read (concurrency-safe), so
|
|
724
|
+
* a derived counter (`User.postCount += 1`) MUST be an `add`, never a `changes`
|
|
725
|
+
* `SET` (which would clobber a concurrent increment).
|
|
726
|
+
*/
|
|
727
|
+
interface TransactionItemSpec {
|
|
728
|
+
readonly type: TransactionItemType;
|
|
729
|
+
readonly tableName: string;
|
|
730
|
+
readonly entity: string;
|
|
731
|
+
/**
|
|
732
|
+
* Put: the full item template (field → template / typed literal). A string
|
|
733
|
+
* value is a template ({@link TransactionValueLeaf}); a `number` / `boolean` is
|
|
734
|
+
* a typed literal carried verbatim (issue #245).
|
|
735
|
+
*/
|
|
736
|
+
readonly item?: Readonly<Record<string, TransactionValueLeaf>>;
|
|
737
|
+
/** Update / Delete / ConditionCheck: the key template (attribute → template). */
|
|
738
|
+
readonly keyCondition?: Readonly<Record<string, string>>;
|
|
739
|
+
/**
|
|
740
|
+
* Update: field-level `SET` change templates (overwrite). A string value is a
|
|
741
|
+
* template; a `number` / `boolean` is a typed literal carried verbatim (#245).
|
|
742
|
+
*/
|
|
743
|
+
readonly changes?: Readonly<Record<string, TransactionValueLeaf>>;
|
|
744
|
+
/**
|
|
745
|
+
* Update: field-level atomic `ADD` deltas (issue #85, derived counters). Each
|
|
746
|
+
* value is a numeric template (`{param}` / a literal number string) or a typed
|
|
747
|
+
* numeric literal (issue #245); the runtime applies `ADD #field :delta`, an
|
|
748
|
+
* atomic increment that needs no prior read and is safe under concurrency. A
|
|
749
|
+
* negative delta decrements (e.g. `-1` on a remove).
|
|
750
|
+
*/
|
|
751
|
+
readonly add?: Readonly<Record<string, TransactionValueLeaf>>;
|
|
752
|
+
/**
|
|
753
|
+
* The write / assertion condition (subset). Optional on `Put` / `Update` /
|
|
754
|
+
* `Delete`; **required** on a `ConditionCheck` (the assertion it makes).
|
|
755
|
+
*/
|
|
756
|
+
readonly condition?: ConditionSpec;
|
|
757
|
+
/** Optional declarative guard; the item is skipped when it does not hold. */
|
|
758
|
+
readonly when?: WhenSpec;
|
|
759
|
+
/**
|
|
760
|
+
* Optional SCP expression guard (issue #261, Phase 3 S1): a boolean
|
|
761
|
+
* behavior-contracts Expression IR expression, carried beside the legacy
|
|
762
|
+
* {@link when} comparison. The item is skipped when the expression does not
|
|
763
|
+
* hold. A document carrying a `guard` is stamped {@link SPEC_VERSION_SCP}
|
|
764
|
+
* (`1.2`), so a runtime that does not yet evaluate the expression vocabulary
|
|
765
|
+
* loud-rejects the whole document rather than silently skipping the guard
|
|
766
|
+
* (fail-closed; evaluation wiring is Phase 3 S5/S6/S7). `when` removal is
|
|
767
|
+
* the 0.8.0 reorg (#251/#252).
|
|
768
|
+
*/
|
|
769
|
+
readonly guard?: ExpressionSpec;
|
|
770
|
+
/** When present, expand this item once per element of the named array param. */
|
|
771
|
+
readonly forEach?: {
|
|
772
|
+
readonly source: string;
|
|
773
|
+
};
|
|
774
|
+
/**
|
|
775
|
+
* Marks a **raw marker-row** write (issue #86 uniqueness guard): the row is NOT a
|
|
776
|
+
* modeled entity, so its primary key is carried **literally** rather than derived
|
|
777
|
+
* from manifest metadata. When `true`:
|
|
778
|
+
*
|
|
779
|
+
* - a `Put` writes its `item` record **verbatim** as the stored item — the `item`
|
|
780
|
+
* already carries the synthetic `PK` / `SK` templates (`UNIQUE#…`), so the
|
|
781
|
+
* runtimes do NOT prepend a model prefix or compute GSI attributes;
|
|
782
|
+
* - a `Delete` uses its `keyCondition` (the `PK` / `SK` templates) as the row Key
|
|
783
|
+
* verbatim.
|
|
784
|
+
*
|
|
785
|
+
* The companion {@link entity} is the {@link MARKER_ROW_ENTITY} sentinel (there is
|
|
786
|
+
* no manifest entity to resolve). Absent / `false` on every modeled write (a base
|
|
787
|
+
* entity / edge / counter / ConditionCheck item), so those paths are unchanged.
|
|
788
|
+
*/
|
|
789
|
+
readonly literalKey?: boolean;
|
|
790
|
+
/**
|
|
791
|
+
* A **relation-side maintenance write** (Epic #118, issue #129 — the JSON-SSoT /
|
|
792
|
+
* Python mirror of the TS in-process `renderMaintainWriteItem`). Present ONLY on an
|
|
793
|
+
* `Update` item that materializes a maintained access path: a projected
|
|
794
|
+
* {@link DerivedMaintainWrite} of a just-written source row into a SEPARATE owner
|
|
795
|
+
* (destination) row, in the SAME atomic transaction as the source write.
|
|
796
|
+
*
|
|
797
|
+
* The item's {@link keyCondition} carries the owner row's key templates (bound from
|
|
798
|
+
* the source payload — `{sourceInputField}`), so the same key derivation / collapse
|
|
799
|
+
* signature the other `Update` items use applies unchanged. This `maintain` payload
|
|
800
|
+
* carries the projection transform IR (`identity` / `preview`) the runtimes apply
|
|
801
|
+
* IDENTICALLY when rendering the `SET … = :v` (snapshot) / `list_append(…)`
|
|
802
|
+
* (collection) `UpdateExpression`, so the maintained row is byte-consistent across
|
|
803
|
+
* TS and Python. The `changes` / `add` fields are NOT used when this is present.
|
|
804
|
+
*/
|
|
805
|
+
readonly maintain?: MaintainItemSpec;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* The transform op applied to one projected maintenance attribute (Epic #118).
|
|
809
|
+
* Mirrors `ProjectionTransformOp` from `src/define/entity-writes.ts`: `identity`
|
|
810
|
+
* copies the source value through unchanged; `preview` keeps the first `n`
|
|
811
|
+
* characters of (the string form of) the source value. The runtimes apply this
|
|
812
|
+
* IDENTICALLY so the maintained row is byte-consistent.
|
|
813
|
+
*/
|
|
814
|
+
type MaintainProjectionOp = 'identity' | 'preview';
|
|
815
|
+
/**
|
|
816
|
+
* One projected maintenance attribute: the source value is read from the mutation
|
|
817
|
+
* input field {@link inputField} (payload 同梱 — the just-written source row image),
|
|
818
|
+
* then run through {@link op} with {@link args} (e.g. `preview`'s length bound).
|
|
819
|
+
*/
|
|
820
|
+
interface MaintainProjectionEntry {
|
|
821
|
+
/** The transform op applied to the source value (`identity` / `preview`). */
|
|
822
|
+
readonly op: MaintainProjectionOp;
|
|
823
|
+
/** The op's positional arguments (`[n]` for `preview`; empty for `identity`). */
|
|
824
|
+
readonly args: readonly unknown[];
|
|
825
|
+
/** The mutation-input field the source value is read from (`{inputField}`). */
|
|
826
|
+
readonly inputField: string;
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* The bounded-collection options a `collection` maintenance write carries. Phase 1
|
|
830
|
+
* is **append-only**: `maxItems` / `orderBy` are recorded for the future async trim
|
|
831
|
+
* (#130) but NOT applied synchronously — a single `UpdateExpression` cannot
|
|
832
|
+
* read-modify-write a bounded/ordered list.
|
|
833
|
+
*/
|
|
834
|
+
interface MaintainCollectionSpec {
|
|
835
|
+
/** The target attribute that holds the maintained collection. */
|
|
836
|
+
readonly field: string;
|
|
837
|
+
/** Cap on the collection size (recorded for #130; not trimmed in Phase 1). */
|
|
838
|
+
readonly maxItems?: number;
|
|
839
|
+
/** The mutation-input field the items are ordered by (recorded for #130). */
|
|
840
|
+
readonly orderBy?: string;
|
|
841
|
+
/** The direction `orderBy` sorts by, from the relation's `read.order` (recorded for #130). */
|
|
842
|
+
readonly orderDir?: 'ASC' | 'DESC';
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* The maintenance payload an `Update` {@link TransactionItemSpec} carries when it
|
|
846
|
+
* materializes a maintained access path (Epic #118 / #129). A `snapshot` mirrors
|
|
847
|
+
* the projection onto the owner row via `SET`; a `collection` appends the projection
|
|
848
|
+
* as an item into the bounded list named by {@link collection} via `list_append`.
|
|
849
|
+
*/
|
|
850
|
+
interface MaintainItemSpec {
|
|
851
|
+
/**
|
|
852
|
+
* Whether the write mirrors a single row (`snapshot`), appends a collection item
|
|
853
|
+
* (`collection`), or applies a scalar `ADD` counter (`counter`, Epic #118 / #141).
|
|
854
|
+
*/
|
|
855
|
+
readonly kind: 'snapshot' | 'collection' | 'counter';
|
|
856
|
+
/** The relation property / `@aggregate` field that declared this maintenance (documentary). */
|
|
857
|
+
readonly relationProperty: string;
|
|
858
|
+
/** The trigger (`<sourceLogicalName>.<event>`) that fired this maintenance. */
|
|
859
|
+
readonly trigger: string;
|
|
860
|
+
/** target attribute → its projection transform (source field + op + args). Empty for `counter`. */
|
|
861
|
+
readonly projection: Readonly<Record<string, MaintainProjectionEntry>>;
|
|
862
|
+
/** The bounded-collection options; present only for `kind: 'collection'`. */
|
|
863
|
+
readonly collection?: MaintainCollectionSpec;
|
|
864
|
+
/**
|
|
865
|
+
* The scalar counter `ADD`; present only for `kind: 'counter'` (#141). The runtimes
|
|
866
|
+
* render `ADD #attr :delta` — an atomic, concurrency-safe increment that MERGES with
|
|
867
|
+
* a same-row counter ADD (#92), the maintenance-pipeline analogue of the
|
|
868
|
+
* self-lifecycle derived counter's `add` slot. `delta` is a numeric template string
|
|
869
|
+
* (the compile-time constant `+1` created / `-1` removed).
|
|
870
|
+
*/
|
|
871
|
+
readonly counter?: MaintainCounterSpec;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* The scalar counter a `counter` maintenance write applies (Epic #118 / #141): an
|
|
875
|
+
* atomic `ADD #attribute :delta`. `delta` is a literal numeric template string (the
|
|
876
|
+
* compile-time constant the trigger fixed — `"1"` created / `"-1"` removed); the
|
|
877
|
+
* runtimes serialize it as a number. Only `count()` is realized synchronously
|
|
878
|
+
* (`max(field)` is rejected for the synchronous path — #130 / Phase 2).
|
|
879
|
+
*/
|
|
880
|
+
interface MaintainCounterSpec {
|
|
881
|
+
/** The target attribute the counter increments (e.g. `postCount`). */
|
|
882
|
+
readonly attribute: string;
|
|
883
|
+
/** The signed `ADD` delta as a numeric template string (`"1"` / `"-1"`). */
|
|
884
|
+
readonly delta: string;
|
|
885
|
+
}
|
|
886
|
+
/** A declarative transaction definition's full execution spec. */
|
|
887
|
+
interface TransactionSpec {
|
|
888
|
+
readonly params: Readonly<Record<string, ParamSpec>>;
|
|
889
|
+
readonly items: readonly TransactionItemSpec[];
|
|
890
|
+
/**
|
|
891
|
+
* A static upper bound on the expanded item count when computable (no `forEach`
|
|
892
|
+
* present → the exact count; with `forEach` it is left absent because the
|
|
893
|
+
* element count is only known at execution — the runtime then enforces the
|
|
894
|
+
* DynamoDB ≤25 limit after expansion).
|
|
895
|
+
*/
|
|
896
|
+
readonly maxItems?: number;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Whether a contract is a public **read** (`'query'`) or **write** (`'command'`)
|
|
900
|
+
* interface. Mirrors {@link QueryModelContract} / {@link CommandModelContract}'s
|
|
901
|
+
* `kind` discriminant from the Contract IR (#58).
|
|
902
|
+
*/
|
|
903
|
+
type ContractKind = 'query' | 'command';
|
|
904
|
+
/**
|
|
905
|
+
* The resolution kind of a contract query method, **derived** by the planner from
|
|
906
|
+
* the internal op (never hand-written; see proposal "N+1 Safety"):
|
|
907
|
+
*
|
|
908
|
+
* - `'point'` — target keys are known (unique-key `query` / `GetItem`); a key
|
|
909
|
+
* array coalesces to one `BatchGetItem`.
|
|
910
|
+
* - `'range'` — target key set is unknown (partition `list` / `Query`); one
|
|
911
|
+
* request per partition key, so it is only ever safe for a single key.
|
|
912
|
+
*/
|
|
913
|
+
type ContractResolution = 'point' | 'range';
|
|
914
|
+
/**
|
|
915
|
+
* What input arity a contract method accepts, **derived** from its resolution:
|
|
916
|
+
*
|
|
917
|
+
* - `'either'` — a single key **or** an array (a `point` read / a known-key
|
|
918
|
+
* write; the array form is one `BatchGetItem` / batched write).
|
|
919
|
+
* - `'single'` — a single key only (a `range` read; an array would be an N+1
|
|
920
|
+
* fan-out and is rejected by construction).
|
|
921
|
+
* - `'array'` — an array only (reserved; not produced by the current resolvers).
|
|
922
|
+
*/
|
|
923
|
+
type ContractInputArity = 'single' | 'array' | 'either';
|
|
924
|
+
/**
|
|
925
|
+
* The per-key result cardinality of a contract query method, **derived** from the
|
|
926
|
+
* internal op kind (proposal "Cardinality matrix"):
|
|
927
|
+
*
|
|
928
|
+
* - `'one'` — at most one item per key (a `point` read).
|
|
929
|
+
* - `'many'` — a connection (`{ items, cursor }`) per key (a `range` read).
|
|
930
|
+
*
|
|
931
|
+
* This is the per-key shape; the input arity (single vs. array) then decides
|
|
932
|
+
* whether the overall result is bare or keyed.
|
|
933
|
+
*/
|
|
934
|
+
type ContractCardinality = 'one' | 'many';
|
|
935
|
+
/**
|
|
936
|
+
* The category of a contract command method's declared result, part of the
|
|
937
|
+
* contract (it surfaces in OpenAPI and every binding; proposal "Return values"):
|
|
938
|
+
*
|
|
939
|
+
* - `'void'` — fire-and-forget write (no body).
|
|
940
|
+
* - `'result'` — an outcome / status object (e.g. `{ ok, version }`).
|
|
941
|
+
* - `'entity'` — the updated entity (the post-write projection).
|
|
942
|
+
*/
|
|
943
|
+
type ContractCommandResult = 'void' | 'result' | 'entity';
|
|
944
|
+
/** The Key of a contract: the field names that compose its access / join key. */
|
|
945
|
+
interface ContractKeySpec {
|
|
946
|
+
/** The key field names, in declaration order (e.g. `["articleId"]`). */
|
|
947
|
+
readonly fields: readonly string[];
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* One External Query (Query Composition) binding on a contract query method
|
|
951
|
+
* (proposal "External Query"). It is a **build-time, in-process** read dependency
|
|
952
|
+
* on another contract referenced by name in the same SSoT — there is no protocol
|
|
953
|
+
* or transport. The runtime collects every parent key produced at this level and
|
|
954
|
+
* resolves the referenced contract **once, batched**, for all of them.
|
|
955
|
+
*
|
|
956
|
+
* A composed child MUST be `point` (proposal "N+1 Safety"): the parent step may
|
|
957
|
+
* yield N records, so a `range` child would be an N+1 fan-out.
|
|
958
|
+
*/
|
|
959
|
+
interface ComposeSpec {
|
|
960
|
+
/** The result property the composed value is attached to (e.g. `billing`). */
|
|
961
|
+
readonly as: string;
|
|
962
|
+
/** The referenced contract name (a symbol in the same SSoT). */
|
|
963
|
+
readonly contract: string;
|
|
964
|
+
/** The referenced method on that contract (e.g. `get`). */
|
|
965
|
+
readonly method: string;
|
|
966
|
+
/**
|
|
967
|
+
* An optional **logical** owner label (the bounded context that owns the
|
|
968
|
+
* referenced contract) — not a network address. Omitted when unlabeled.
|
|
969
|
+
*/
|
|
970
|
+
readonly context?: string;
|
|
971
|
+
/**
|
|
972
|
+
* The child-key binding: child key field → a `from` source path on the parent
|
|
973
|
+
* result, e.g. `{ accountId: "$.billingAccountId" }`.
|
|
974
|
+
*/
|
|
975
|
+
readonly bind: Readonly<Record<string, string>>;
|
|
976
|
+
/**
|
|
977
|
+
* The composed child's resolution. It MUST be `'point'` in a valid contract — a
|
|
978
|
+
* composed child coalesces to one `BatchGetItem` across all parent keys, so a
|
|
979
|
+
* `range` child (a partition `Query` that cannot be batched across the parent's
|
|
980
|
+
* keys) is an N+1 fan-out. The N+1 static checker (#60,
|
|
981
|
+
* {@link assertContractN1Safe}) rejects a `'range'` value on the build path; the
|
|
982
|
+
* field is widened to {@link ContractResolution} so the offending node can be
|
|
983
|
+
* carried into the assembled {@link ContractSpec} for the checker to inspect and
|
|
984
|
+
* reject with a clear message, rather than being silently unrepresentable.
|
|
985
|
+
*/
|
|
986
|
+
readonly resolution: ContractResolution;
|
|
987
|
+
/** The composed child's per-key cardinality (`'one'` for a valid `point`). */
|
|
988
|
+
readonly cardinality: ContractCardinality;
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* The **composition execution plan** for one query contract method (issue #70a),
|
|
992
|
+
* the contract-layer mirror of {@link ExecutionPlanSpec}. It records how a method's
|
|
993
|
+
* External Query compositions (`compose[]`) are staged relative to the parent read
|
|
994
|
+
* and to one another, and the batching discipline each composed child obeys:
|
|
995
|
+
*
|
|
996
|
+
* - `stages` is an ordered list whose entries are indices into the method's
|
|
997
|
+
* `compose[]`. The parent read (the referenced `operation`) is the implicit
|
|
998
|
+
* stage before all of these. Every compose child binds its child key purely from
|
|
999
|
+
* the **parent** result (`from('$.…')` paths), so sibling compositions are
|
|
1000
|
+
* mutually independent: they form a single stage `[0, 1, …]` and may be resolved
|
|
1001
|
+
* concurrently. (A composed child cannot, today, bind from another composed
|
|
1002
|
+
* child — the #63 DSL only exposes parent `from` paths — so there is never a
|
|
1003
|
+
* cross-composition dependency; the field is a list of stages so the shape can
|
|
1004
|
+
* carry one if a future primitive introduces it.)
|
|
1005
|
+
* - `concurrency` is the declared in-flight bound applied **within** a stage (the
|
|
1006
|
+
* shared {@link RELATION_TRAVERSAL_CONCURRENCY} = 16).
|
|
1007
|
+
* - `batchChunkSize` is the per-request key cap for a composed child read (100,
|
|
1008
|
+
* the DynamoDB `BatchGetItem` limit): a composition resolves **one batched
|
|
1009
|
+
* call** across all parent records (chunked at this size), never an N+1 fan-out
|
|
1010
|
+
* (the composed child is `point`, enforced by the #60 N+1 checker).
|
|
1011
|
+
*
|
|
1012
|
+
* Emitted only when the method declares at least one composition. A method without
|
|
1013
|
+
* compositions carries no plan, and a runtime without one resolves any
|
|
1014
|
+
* compositions sequentially (the pre-#70 behavior) — so an absent plan never
|
|
1015
|
+
* changes results.
|
|
1016
|
+
*/
|
|
1017
|
+
interface CompositionPlanSpec {
|
|
1018
|
+
/** Ordered stages of indices into `compose[]` (independent siblings share a stage). */
|
|
1019
|
+
readonly stages: readonly (readonly number[])[];
|
|
1020
|
+
/** The declared in-flight bound applied within each composition stage (16). */
|
|
1021
|
+
readonly concurrency: number;
|
|
1022
|
+
/** Per-request key cap for a composed child's batched read (100, BatchGetItem limit). */
|
|
1023
|
+
readonly batchChunkSize: number;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* The serialized spec of one **query** contract method. Carries the decided facts
|
|
1027
|
+
* (`resolution` / `inputArity` / `cardinality`) the planner derived in #58, a
|
|
1028
|
+
* reference into `queries` by name (the internal op), and any External Query
|
|
1029
|
+
* compositions. The decided facts are **serialized, not re-derived** — every
|
|
1030
|
+
* runtime honors them.
|
|
1031
|
+
*/
|
|
1032
|
+
interface QueryContractMethodSpec {
|
|
1033
|
+
/** Derived: `'point'` (unique-key query) or `'range'` (partition list). */
|
|
1034
|
+
readonly resolution: ContractResolution;
|
|
1035
|
+
/** Derived: `'either'` for `point`, `'single'` for `range`. */
|
|
1036
|
+
readonly inputArity: ContractInputArity;
|
|
1037
|
+
/** Derived per-key result shape: `'one'` (point) or `'many'` (range). */
|
|
1038
|
+
readonly cardinality: ContractCardinality;
|
|
1039
|
+
/** The referenced read op name in `queries` (e.g. `ArticleById__get`). */
|
|
1040
|
+
readonly operation: string;
|
|
1041
|
+
/** External Query compositions on this method; omitted when there are none. */
|
|
1042
|
+
readonly compose?: readonly ComposeSpec[];
|
|
1043
|
+
/**
|
|
1044
|
+
* The composition staging + batch plan (issue #70a); present only when `compose`
|
|
1045
|
+
* is. See {@link CompositionPlanSpec}: independent compositions form one
|
|
1046
|
+
* concurrency-eligible stage after the parent read, each resolved as one batched
|
|
1047
|
+
* call (chunk ≤100). Absent → resolve compositions sequentially (pre-#70).
|
|
1048
|
+
*/
|
|
1049
|
+
readonly compositionPlan?: CompositionPlanSpec;
|
|
1050
|
+
/**
|
|
1051
|
+
* Optional human-readable description of the read use case (issue #154), from the
|
|
1052
|
+
* descriptor's `description`. Pure documentation — absent unless declared, so a
|
|
1053
|
+
* method with no description serializes byte-identically to the pre-#154 spec.
|
|
1054
|
+
* The same string appears in the TS contract IR and (via the SSoT) in any
|
|
1055
|
+
* generated binding, so TS↔Python carry an identical description.
|
|
1056
|
+
*/
|
|
1057
|
+
readonly description?: string;
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* How a contract command method resolves a single key vs. an array of keys to the
|
|
1061
|
+
* underlying write surface (proposal "Consistency with the existing write
|
|
1062
|
+
* surface"). A single key → one write op (or one transaction); an array → a
|
|
1063
|
+
* batched write — a `TransactWriteItems` when atomicity / per-item conditions are
|
|
1064
|
+
* required, or a `BatchWriteItem` when neither is.
|
|
1065
|
+
*/
|
|
1066
|
+
type CommandResolutionTarget =
|
|
1067
|
+
/** One write op, referenced by name in `commands`. */
|
|
1068
|
+
{
|
|
1069
|
+
readonly mode: 'op';
|
|
1070
|
+
readonly operation: string;
|
|
1071
|
+
}
|
|
1072
|
+
/** One transaction, referenced by name in `transactions`. */
|
|
1073
|
+
| {
|
|
1074
|
+
readonly mode: 'transaction';
|
|
1075
|
+
readonly transaction: string;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* A non-atomic **parallel** fan-out over the per-key write op (#101). The op
|
|
1079
|
+
* MAY carry a condition (guarded create / conditioned update). Unconditioned
|
|
1080
|
+
* `put`/`delete` ops coalesce into a `BatchWriteItem` (with `UnprocessedItems`
|
|
1081
|
+
* retry); conditioned/update ops issue individual conditional writes
|
|
1082
|
+
* concurrently. Per-op success/failure is reported (partial success); a
|
|
1083
|
+
* per-op failure never aborts the others. References the per-key write op in
|
|
1084
|
+
* `commands`.
|
|
1085
|
+
*/
|
|
1086
|
+
| {
|
|
1087
|
+
readonly mode: 'parallel';
|
|
1088
|
+
readonly operation: string;
|
|
1089
|
+
};
|
|
1090
|
+
/**
|
|
1091
|
+
* The serialized spec of one **command** contract method. Symmetric to
|
|
1092
|
+
* {@link QueryContractMethodSpec}: it carries the declared `result` type and the
|
|
1093
|
+
* `single` / `batch` resolution targets into the existing write specs.
|
|
1094
|
+
*/
|
|
1095
|
+
interface CommandContractMethodSpec {
|
|
1096
|
+
/** Derived input arity: writes accept `'either'` (single key or key array). */
|
|
1097
|
+
readonly inputArity: ContractInputArity;
|
|
1098
|
+
/** The declared result type (`void` | a `Result` | the updated entity). */
|
|
1099
|
+
readonly result: ContractCommandResult;
|
|
1100
|
+
/** How a single key resolves (one op / one transaction). */
|
|
1101
|
+
readonly single: CommandResolutionTarget;
|
|
1102
|
+
/**
|
|
1103
|
+
* How an array of keys resolves (a transaction / `BatchWriteItem`); omitted
|
|
1104
|
+
* when the contract does not declare a batched write form.
|
|
1105
|
+
*/
|
|
1106
|
+
readonly batch?: CommandResolutionTarget;
|
|
1107
|
+
/**
|
|
1108
|
+
* The **return projection** of a `mutation`-derived command method (issue #83;
|
|
1109
|
+
* proposal §3, "return selection as a read projection"). A JSON-safe boolean
|
|
1110
|
+
* field map: after the write commits, the runtime issues a **`GetItem` with
|
|
1111
|
+
* `ConsistentRead`** of the written entity's primary key and applies this
|
|
1112
|
+
* projection via the existing read-projection machinery, returning the projected
|
|
1113
|
+
* item. This is the **uniform** return mechanism for both a single-op command
|
|
1114
|
+
* and a future transaction (a `TransactWriteItems` cannot return item images),
|
|
1115
|
+
* so TS and Python return an **identical** projected item.
|
|
1116
|
+
*
|
|
1117
|
+
* The consistent read-back is issued against the read op named
|
|
1118
|
+
* `<Contract>__<method>__readback` (a synthesized {@link QuerySpec}: a `GetItem`
|
|
1119
|
+
* on the written entity's primary key projecting these fields). Present only on
|
|
1120
|
+
* a `.plan(mutation)` method; a hand-written #64 command omits it (its
|
|
1121
|
+
* {@link result} stays `'void'` / `'entity'` and it returns no projected item).
|
|
1122
|
+
*/
|
|
1123
|
+
readonly returnSelection?: Readonly<Record<string, boolean>>;
|
|
1124
|
+
/**
|
|
1125
|
+
* Optional SCP expression guard on the contract-method op (issue #261,
|
|
1126
|
+
* Phase 3 S1) — the same guard slot a {@link TransactionItemSpec} carries:
|
|
1127
|
+
* a boolean behavior-contracts Expression IR expression gating the method's
|
|
1128
|
+
* write. Vocabulary-only in S1 (populated by the #262 lowerer); a document
|
|
1129
|
+
* carrying it is stamped {@link SPEC_VERSION_SCP} (`1.2`) so pre-evaluation
|
|
1130
|
+
* runtimes loud-reject it (fail-closed).
|
|
1131
|
+
*/
|
|
1132
|
+
readonly guard?: ExpressionSpec;
|
|
1133
|
+
/**
|
|
1134
|
+
* Optional human-readable description of the write use case (issue #154), from
|
|
1135
|
+
* the descriptor's `description`. Pure documentation — absent unless declared, so
|
|
1136
|
+
* a method with no description serializes byte-identically to the pre-#154 spec.
|
|
1137
|
+
*/
|
|
1138
|
+
readonly description?: string;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* A serialized contract — a keyed public read (`'query'`) or write (`'command'`)
|
|
1142
|
+
* interface holding one or more named methods. Each method references an existing
|
|
1143
|
+
* operation spec by name and adds the decided contract facts. The existing
|
|
1144
|
+
* operation-spec shapes are unchanged, so the current runtime keeps working.
|
|
1145
|
+
*/
|
|
1146
|
+
interface ContractSpec {
|
|
1147
|
+
/** Whether this is a read (`'query'`) or write (`'command'`) contract. */
|
|
1148
|
+
readonly kind: ContractKind;
|
|
1149
|
+
/** The contract Key (the access pattern / join / batch key). */
|
|
1150
|
+
readonly key: ContractKeySpec;
|
|
1151
|
+
/**
|
|
1152
|
+
* The named methods (use cases over the same Key). A query contract's methods
|
|
1153
|
+
* are {@link QueryContractMethodSpec}; a command contract's are
|
|
1154
|
+
* {@link CommandContractMethodSpec}. The map is keyed by contract `kind`.
|
|
1155
|
+
*/
|
|
1156
|
+
readonly methods: Readonly<Record<string, QueryContractMethodSpec>> | Readonly<Record<string, CommandContractMethodSpec>>;
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* One bounded context's membership (issue #59, "context ownership"). Lists the
|
|
1160
|
+
* Models and Contracts that belong to the same context, so the future boundary
|
|
1161
|
+
* lint (#61) can tell **own** from **foreign**: a contract may resolve its own
|
|
1162
|
+
* context's Models directly, but may only reach another context through that
|
|
1163
|
+
* context's published Contract (direct use of a foreign Model is a build error).
|
|
1164
|
+
*
|
|
1165
|
+
* This issue only **serializes / emits** this declaration; it does not enforce
|
|
1166
|
+
* it. Both lists are entity / contract *names* (matching `manifest.entities` keys
|
|
1167
|
+
* and `contracts` keys respectively).
|
|
1168
|
+
*/
|
|
1169
|
+
interface ContextSpec {
|
|
1170
|
+
/** Model (entity) names owned by this context (sorted; manifest entity keys). */
|
|
1171
|
+
readonly models: readonly string[];
|
|
1172
|
+
/** Contract names owned by this context (sorted; `contracts` map keys). */
|
|
1173
|
+
readonly contracts: readonly string[];
|
|
1174
|
+
}
|
|
1175
|
+
interface OperationsDocument {
|
|
1176
|
+
/**
|
|
1177
|
+
* `1.1` unless the document actually contains an SCP Expression IR node (a
|
|
1178
|
+
* `scpExpr` condition or a `guard`), in which case it is stamped `1.2`
|
|
1179
|
+
* (issue #261, conditional stamping — see {@link SPEC_VERSION_SCP}).
|
|
1180
|
+
*/
|
|
1181
|
+
readonly version: SpecVersion;
|
|
1182
|
+
readonly queries: Readonly<Record<string, QuerySpec>>;
|
|
1183
|
+
readonly commands: Readonly<Record<string, CommandSpec>>;
|
|
1184
|
+
/** Declarative transactions (issue #46). Absent in pre-#46 specs. */
|
|
1185
|
+
readonly transactions?: Readonly<Record<string, TransactionSpec>>;
|
|
1186
|
+
/**
|
|
1187
|
+
* The CQRS Contract layer (issue #59): contract name → {@link ContractSpec}.
|
|
1188
|
+
* Layered **on top of** the existing `queries` / `commands` / `transactions`
|
|
1189
|
+
* (each method references one of them by name). **Absent** when the input
|
|
1190
|
+
* defines no contracts — so a contract-free input produces a byte-identical
|
|
1191
|
+
* pre-#59 operations document (backward compatibility).
|
|
1192
|
+
*/
|
|
1193
|
+
readonly contracts?: Readonly<Record<string, ContractSpec>>;
|
|
1194
|
+
/**
|
|
1195
|
+
* Context-ownership declarations (issue #59): context name →
|
|
1196
|
+
* {@link ContextSpec}. Used by the boundary lint (#61) to tell own from
|
|
1197
|
+
* foreign. **Absent** when no contexts are declared (backward compatibility).
|
|
1198
|
+
*/
|
|
1199
|
+
readonly contexts?: Readonly<Record<string, ContextSpec>>;
|
|
1200
|
+
}
|
|
1201
|
+
/** The full `{ manifest, operations }` bundle produced by the static planner. */
|
|
1202
|
+
interface BridgeBundle {
|
|
1203
|
+
readonly manifest: Manifest;
|
|
1204
|
+
readonly operations: OperationsDocument;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
export { type WriteOperationType as $, type ExpressionScalar as A, type BridgeBundle as B, type ContractSpec as C, type ManifestEntity as D, type ExpressionSpec as E, type FilterSpec as F, type ManifestField as G, type ManifestFieldType as H, type ManifestGsi as I, type ManifestKey as J, type ManifestRelation as K, type ManifestTable as L, type Manifest as M, type OperationSpec as N, type OperationsDocument as O, type ParamSpec as P, type QuerySpec as Q, type QueryContractMethodSpec as R, SPEC_VERSION as S, type TransactionSpec as T, type RangeConditionSpec as U, type ReadOperationType 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 ParamKind as a0, type LiteralParam as a1, type NumberParam as a2, type StringParam as a3, isParam as a4, param as a5, type ContextSpec as b, type Param as c, type ConditionSpec as d, type SpecVersion as e, type CommandContractMethodSpec as f, type CommandResolutionTarget as g, type ComposeSpec as h, type CompositionPlanSpec as i, type ContractCardinality as j, type ContractCommandResult as k, type ContractInputArity as l, type ContractKeySpec as m, type ContractKind as n, type ContractResolution as o, EXPR_VERSION as p, type ExecutionPlanSpec as q, type ExpressionArrNode as r, type ExpressionFloatNode as s, type ExpressionIntNode as t, type ExpressionNode as u, type ExpressionObjNode as v, type ExpressionOpNode as w, type ExpressionOperator as x, type ExpressionRefNode as y, type ExpressionRefOptNode as z };
|