graphddb 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-7OCXY4R6.js} +1 -1
- package/dist/{chunk-L2NEDS7U.js → chunk-GWWRXIHF.js} +260 -12
- package/dist/{chunk-T44OB5GU.js → chunk-HLFNCKFV.js} +105 -11
- package/dist/{chunk-LAT64YCZ.js → chunk-MBJ4JVRM.js} +680 -25
- package/dist/{chunk-S2NI4PBW.js → chunk-PHXUFAY2.js} +7 -5
- package/dist/{chunk-GS4C5VGO.js → chunk-VECUS35D.js} +2 -2
- package/dist/{chunk-L4QRCHRQ.js → chunk-WOFRHRXY.js} +6 -25
- 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-CWytoEaE.d.ts} +93 -5
- package/dist/linter/index.d.ts +6 -5
- package/dist/{linter-DQY7gUEk.d.ts → linter-jEwmZotm.d.ts} +1 -1
- package/dist/{prepared-artifact-HFealr1q.d.ts → prepared-artifact-CwH5ezFq.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 +32 -5
- package/dist/transform/index.d.ts +2 -1
- package/dist/transform/index.js +2 -2
- package/dist/{types-2PMXEn5x.d.ts → types-CgXS-4Ox.d.ts} +299 -28
- package/dist/{types-DW__-Icc.d.ts → types-nk5okD7d.d.ts} +1 -1
- package/docs/python-bridge.md +33 -49
- package/package.json +11 -3
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Recorded, Component, ComponentGraphIR, PortSchema, BehaviorClass } from 'behavior-contracts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GraphDDB's first-class SCP catalog — issue #297 SP1 (SCP authoring adoption),
|
|
5
|
+
* promoting the Phase-1 executor catalog (#288, `src/executor/catalog.ts`) into
|
|
6
|
+
* THE behavior-contracts–typed catalog the spec emitter is validated against.
|
|
7
|
+
*
|
|
8
|
+
* ## Source of truth (SP1 decision)
|
|
9
|
+
*
|
|
10
|
+
* ONE catalog lives here, in the spec layer — the layer that emits `components[]`
|
|
11
|
+
* (`src/spec/components.ts`) — and `src/executor/catalog.ts` re-exports it
|
|
12
|
+
* unchanged (the C4 handler registry keeps resolving against the same object).
|
|
13
|
+
* The Phase-1 module described itself as the C2 "the only per-DSL difference is
|
|
14
|
+
* the catalog" surface but its Port schemas mirrored the OLD-path executor
|
|
15
|
+
* inputs (`DynamoDBOperation` / `PutInput`), written before the Phase-3 emitter
|
|
16
|
+
* existed. Since #290 P3-5 the portable IR's actual wiring is the emitted
|
|
17
|
+
* component-graph ports, so promoting the catalog to LOAD-BEARING means the
|
|
18
|
+
* schemas here describe the EMITTED Port contract (what `readPorts` /
|
|
19
|
+
* `transactionItemPorts` in `components.ts` actually wire, and what a
|
|
20
|
+
* `runBehavior` handler actually receives) — the two latent Phase-1↔Phase-3
|
|
21
|
+
* inconsistencies are fixed loudly, not papered over:
|
|
22
|
+
*
|
|
23
|
+
* 1. **`ConditionCheck` was missing.** The emitter has emitted `ConditionCheck`
|
|
24
|
+
* componentRefs since Phase 3 (`buildTransactionComponent`), but the Phase-1
|
|
25
|
+
* catalog declared only 7 entries without it — so the "catalog" did not cover
|
|
26
|
+
* the emitted vocabulary. It is now a first-class entry.
|
|
27
|
+
* 2. **`TransactWrite` is not portable.** No portable document references
|
|
28
|
+
* `TransactWrite` (a transaction is emitted as a Component whose body
|
|
29
|
+
* references the per-item write primitives; the atomic TransactWriteItems
|
|
30
|
+
* envelope is the TS runtime's execution concern) — so its Phase-1
|
|
31
|
+
* `portableToIR: true` claim was false since P3-5. It stays a catalog entry
|
|
32
|
+
* (the executor-side atomic envelope, and a member of the write set) but is
|
|
33
|
+
* now honestly `portableToIR: false`.
|
|
34
|
+
*
|
|
35
|
+
* ## Port schema conventions
|
|
36
|
+
*
|
|
37
|
+
* A `CatalogEntry.inputPorts` names the STATIC ports of the emitted wiring. Two
|
|
38
|
+
* port families are dynamic by construction and are validated by
|
|
39
|
+
* {@link assertComponentsInCatalog} against documented patterns instead:
|
|
40
|
+
*
|
|
41
|
+
* - **Read key-attribute ports**: one port per `keyExpr` attribute, named by the
|
|
42
|
+
* physical key attribute — graphddb's fixed single-table convention
|
|
43
|
+
* `PK` / `SK` / `<index>PK` / `<index>SK` ({@link KEY_ATTR_PORT}).
|
|
44
|
+
* - **Write record ports**: a transaction item's `item` / `key` / `changes` /
|
|
45
|
+
* `add` records and the `condition.equals` / `condition.params` /
|
|
46
|
+
* `condition.values` slots are flattened one-port-per-field under a
|
|
47
|
+
* `<group>.<field>` key ({@link WRITE_PORT_FAMILIES}).
|
|
48
|
+
*
|
|
49
|
+
* ## Dynamic-port opt-in (`additionalPorts`, SP3)
|
|
50
|
+
*
|
|
51
|
+
* The seven read/write-item entries carry `additionalPorts: true` (bc#26): their
|
|
52
|
+
* dynamic port families above are BY DESIGN not statically enumerable, so bc's
|
|
53
|
+
* shared port check (`checkPortNames` — used by both the `catalogComponents`
|
|
54
|
+
* authoring recorder and `buildComponentDefinition`) must not reject them. This
|
|
55
|
+
* is an honest declaration, not a loosening: the dynamic families are still
|
|
56
|
+
* validated fail-closed by {@link assertComponentsInCatalog} (emit/registration
|
|
57
|
+
* time), and every DECLARED port keeps bc's required/type checks regardless of
|
|
58
|
+
* the opt-in. It is exactly this opt-in that makes `GRAPHDDB_CATALOG` directly
|
|
59
|
+
* consumable by the shared authoring surface (`catalogComponents(GRAPHDDB_CATALOG)`,
|
|
60
|
+
* bc#24 / #297 SP3) — the C2 contract that the catalog IS the whole per-DSL
|
|
61
|
+
* difference. `TransactWrite` stays fail-closed (its one port is static).
|
|
62
|
+
*
|
|
63
|
+
* ## Write set (SP1b) — and where `ConditionCheck` sits
|
|
64
|
+
*
|
|
65
|
+
* {@link WRITE_CATALOG_NAMES} is the CQRS write catalog: `PutItem` /
|
|
66
|
+
* `UpdateItem` / `DeleteItem` / `TransactWrite` / `ConditionCheck`.
|
|
67
|
+
* `ConditionCheck` mutates nothing — but it is classified WRITE deliberately:
|
|
68
|
+
* it exists only inside `TransactWriteItems` (DynamoDB accepts it in no read
|
|
69
|
+
* API), it acquires the item's transactional lock, and its failure aborts the
|
|
70
|
+
* sibling writes. A component referencing it therefore can only ever execute on
|
|
71
|
+
* the command path — for effect ROUTING (which is all the write set decides,
|
|
72
|
+
* bc#24) it is command-side infrastructure, not a read. The read set is exactly
|
|
73
|
+
* `GetItem` / `Query` / `BatchGetItem`; every catalog name is in exactly one set.
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The catalog-name literal union — one entry per DynamoDB primitive GraphDDB
|
|
78
|
+
* declares: the `ReadOperationType` (`GetItem` / `Query` / `BatchGetItem`) and
|
|
79
|
+
* `WriteOperationType` (`PutItem` / `UpdateItem` / `DeleteItem`) enum members
|
|
80
|
+
* from `src/spec/types.ts`, the transaction-item assertion primitive
|
|
81
|
+
* (`ConditionCheck`), and the atomic envelope (`TransactWrite`).
|
|
82
|
+
*/
|
|
83
|
+
type CatalogName = 'GetItem' | 'Query' | 'BatchGetItem' | 'PutItem' | 'UpdateItem' | 'DeleteItem' | 'ConditionCheck' | 'TransactWrite';
|
|
84
|
+
/** GraphDDB's CQRS effect vocabulary, mapped 1:1 from bc's `BehaviorEffect`. */
|
|
85
|
+
type ContractEffect = 'query' | 'command';
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* `publishBehaviors` — the shared-SCP authoring entry (issue #297 SP3; pairs
|
|
89
|
+
* graphddb with behavior-contracts bc#24 and connects to #296 P4-1).
|
|
90
|
+
*
|
|
91
|
+
* A user authors a **`SemanticBehavior` class** on behavior-contracts' SHARED
|
|
92
|
+
* authoring surface — `$` input wiring, `when` / `.map` structured control, the
|
|
93
|
+
* expression builders (`eq` / `concat` / `coalesce` / …), and the leaf
|
|
94
|
+
* components minted from graphddb's catalog via
|
|
95
|
+
* `catalogComponents(GRAPHDDB_CATALOG)` ({@link behaviorComponents}) — and
|
|
96
|
+
* publishes it here:
|
|
97
|
+
*
|
|
98
|
+
* ```ts
|
|
99
|
+
* import { graphddb, behaviorComponents } from 'graphddb';
|
|
100
|
+
* import { SemanticBehavior, concat, type In } from 'behavior-contracts';
|
|
101
|
+
*
|
|
102
|
+
* const G = behaviorComponents();
|
|
103
|
+
* class ArticleBehaviors extends SemanticBehavior {
|
|
104
|
+
* getArticleWithTags($: In<{ articleId: string }>) {
|
|
105
|
+
* const a = G.GetItem({ table: 'Articles', PK: concat('ARTICLE#', $.articleId), SK: 'META',
|
|
106
|
+
* projection: ['articleId', 'title', 'tagRefs'] });
|
|
107
|
+
* const tags = a.tagRefs.map(($t: Recorded) =>
|
|
108
|
+
* G.BatchGetItem({ table: 'Articles', PK: concat('TAG#', $t.tagId), SK: 'META',
|
|
109
|
+
* projection: ['tagId', 'label'] }), { batched: true });
|
|
110
|
+
* return { article: a, tags };
|
|
111
|
+
* }
|
|
112
|
+
* }
|
|
113
|
+
* const Articles = graphddb.publishBehaviors(ArticleBehaviors);
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* ## Responsibility split (C2 — "the difference is the catalog only")
|
|
117
|
+
*
|
|
118
|
+
* - **behavior-contracts does ALL composition and lowering**: `compileBehaviors`
|
|
119
|
+
* evaluates each public method once (throwing-sentinel recording + native
|
|
120
|
+
* control-syntax source scan), lowers `$` references, component calls, `when`
|
|
121
|
+
* and `.map` into the portable component-graph IR, derives the plan, and
|
|
122
|
+
* self-checks the result with `assertPortableComponentGraph`. graphddb adds
|
|
123
|
+
* **no generic lowering of its own**.
|
|
124
|
+
* - **graphddb supplies only the catalog and the effects**: the leaf vocabulary
|
|
125
|
+
* is `GRAPHDDB_CATALOG` (#297 SP1 — the same load-bearing catalog the spec
|
|
126
|
+
* emitter is validated against), the dynamic port families are validated by
|
|
127
|
+
* the same {@link assertComponentsInCatalog}, and the Query/Command
|
|
128
|
+
* classification is DERIVED from the lowered graph per SP2/SP4
|
|
129
|
+
* ({@link deriveContractEffect}) — the author never declares an effect
|
|
130
|
+
* (authoring is effect-agnostic; a method that references a write-catalog
|
|
131
|
+
* primitive IS a command).
|
|
132
|
+
*
|
|
133
|
+
* The compiled components register additively into the operations document
|
|
134
|
+
* through `buildOperations`' `contractInputs.behaviors` (each component emitted
|
|
135
|
+
* as `<RegistrationKey>__<method>`), and the TS runtime executes them through
|
|
136
|
+
* bc's `runBehavior` with graphddb's real DynamoDB handlers
|
|
137
|
+
* (`src/executor/behavior-exec.ts` — the Phase-1 handler-registry seam's
|
|
138
|
+
* behavior-execution counterpart, mirroring `python/graphddb_runtime/behavior_exec.py`).
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* One published behavior method: the bc-lowered component (name = the method
|
|
143
|
+
* name), plus its graph-DERIVED CQRS effect (#297 SP2/SP4 — the classification
|
|
144
|
+
* source of truth is the component graph; `publishBehaviors` has no
|
|
145
|
+
* query/command vocabulary for the author to declare).
|
|
146
|
+
*/
|
|
147
|
+
interface BehaviorMethodSpec {
|
|
148
|
+
/** The method (= root Behavior = component) name. */
|
|
149
|
+
readonly name: string;
|
|
150
|
+
/** Graph-derived Query/Command classification (never author-declared). */
|
|
151
|
+
readonly effect: ContractEffect;
|
|
152
|
+
/** The lowered portable component (bc `compileBehaviors` output). */
|
|
153
|
+
readonly component: Component;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* A resolved behavior contract — `publishBehaviors` output. Carries the whole
|
|
157
|
+
* bc-lowered {@link ComponentGraphIR} (directly `runBehavior`-executable; the
|
|
158
|
+
* component names are the method names) and the per-method specs with their
|
|
159
|
+
* derived effects. Register it into the operations document via
|
|
160
|
+
* `buildOperations(..., { behaviors: { Name: contract } })`.
|
|
161
|
+
*/
|
|
162
|
+
interface BehaviorModelContract {
|
|
163
|
+
/** Discriminates from the publishQuery/publishCommand contract objects. */
|
|
164
|
+
readonly kind: 'behaviors';
|
|
165
|
+
/** The authored class name (diagnostics only). */
|
|
166
|
+
readonly className: string;
|
|
167
|
+
/** The bc-lowered portable IR (all methods; `runBehavior`-executable). */
|
|
168
|
+
readonly ir: ComponentGraphIR;
|
|
169
|
+
/** The lowered components (`ir.components`, one per public method). */
|
|
170
|
+
readonly components: readonly Component[];
|
|
171
|
+
/** Method name → lowered component + derived effect. */
|
|
172
|
+
readonly methods: Readonly<Record<string, BehaviorMethodSpec>>;
|
|
173
|
+
}
|
|
174
|
+
/** Options passed through to bc `compileBehaviors` (additive; all optional). */
|
|
175
|
+
interface PublishBehaviorsOptions {
|
|
176
|
+
/**
|
|
177
|
+
* Input Port schema overrides (method → port → schema): build-time evaluation
|
|
178
|
+
* derives port NAMES from `$` accesses; TS types are erased, so supply the
|
|
179
|
+
* type strings here if the registered document should carry them.
|
|
180
|
+
*/
|
|
181
|
+
readonly inputPorts?: Record<string, Record<string, PortSchema>>;
|
|
182
|
+
/** Plan concurrency recorded on the lowered components (bc default: 16). */
|
|
183
|
+
readonly concurrency?: number;
|
|
184
|
+
}
|
|
185
|
+
/** The `catalogComponents(GRAPHDDB_CATALOG)` leaf-function map (typed). */
|
|
186
|
+
type BehaviorComponentFns = Record<CatalogName, (ports: Record<string, unknown>) => Recorded>;
|
|
187
|
+
/**
|
|
188
|
+
* The graphddb leaf components for the shared authoring surface:
|
|
189
|
+
* `catalogComponents(GRAPHDDB_CATALOG)` bound once, lazily. Call it at module
|
|
190
|
+
* scope of an authoring module (`const G = behaviorComponents()`) and invoke
|
|
191
|
+
* the leaves inside `SemanticBehavior` method bodies. `TransactWrite` is in the
|
|
192
|
+
* map but is `portableToIR: false`, so wiring it is rejected at record time —
|
|
193
|
+
* the atomic envelope is a TS-runtime execution concern, never authored.
|
|
194
|
+
*/
|
|
195
|
+
declare function behaviorComponents(): BehaviorComponentFns;
|
|
196
|
+
/**
|
|
197
|
+
* Publish a `SemanticBehavior` class as a graphddb behavior contract (#297 SP3).
|
|
198
|
+
*
|
|
199
|
+
* Pipeline (all loud, nothing dropped):
|
|
200
|
+
* 1. bc `compileBehaviors(cls)` lowers every public method into one portable
|
|
201
|
+
* component (bc owns ALL composition/lowering and self-checks portability);
|
|
202
|
+
* 2. graphddb validates every lowered component against `GRAPHDDB_CATALOG` —
|
|
203
|
+
* the SAME {@link assertComponentsInCatalog} the spec emitter runs (SP1c),
|
|
204
|
+
* covering the dynamic key-attribute / write record-family ports bc's
|
|
205
|
+
* static schema check cannot enumerate;
|
|
206
|
+
* 3. the CQRS effect of every method is DERIVED from its graph
|
|
207
|
+
* ({@link deriveContractEffect} — SP2/SP4; the author never declares it).
|
|
208
|
+
*
|
|
209
|
+
* @throws bc `AuthoringFailure` for a non-declarative body (native control
|
|
210
|
+
* syntax, coercion, unknown/missing ports, …); an `Error` from the catalog
|
|
211
|
+
* validation for an undeclared dynamic port.
|
|
212
|
+
*/
|
|
213
|
+
declare function publishBehaviors(cls: BehaviorClass, options?: PublishBehaviorsOptions): BehaviorModelContract;
|
|
214
|
+
/** Runtime guard: is `value` a {@link BehaviorModelContract}? */
|
|
215
|
+
declare function isBehaviorModelContract(value: unknown): value is BehaviorModelContract;
|
|
216
|
+
|
|
217
|
+
export { type BehaviorModelContract as B, type PublishBehaviorsOptions as P, type BehaviorMethodSpec as a, type BehaviorComponentFns as b, behaviorComponents as c, isBehaviorModelContract as i, publishBehaviors as p };
|
package/dist/cdc/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { w as CdcEmulatorOptions, x as ChangeHandler, y as Unsubscribe, F as FaultSpec, z as ConcurrentRecomputeRef, v as ChangeEvent, A as EventLog, G as ReplayOptions, H as ShardId, I as MaintainTrigger, E as EffectPath, g as ProjectionMap, j as MembershipPredicate } from '../key-
|
|
2
|
-
export { J as BatchResult, K as CdcMode, L as ChangeBatch, N as ChangeEventName, O as ClockMode, V as StartingPosition, X as StreamViewType, Y as SubscribeHandler, Z as SubscribeHandlers, _ as buildSubscribeHandler } from '../key-
|
|
3
|
-
import { E as EntityMetadata } from '../types-
|
|
4
|
-
import '../types-
|
|
1
|
+
import { w as CdcEmulatorOptions, x as ChangeHandler, y as Unsubscribe, F as FaultSpec, z as ConcurrentRecomputeRef, v as ChangeEvent, A as EventLog, G as ReplayOptions, H as ShardId, I as MaintainTrigger, E as EffectPath, g as ProjectionMap, j as MembershipPredicate } from '../key-CWytoEaE.js';
|
|
2
|
+
export { J as BatchResult, K as CdcMode, L as ChangeBatch, N as ChangeEventName, O as ClockMode, V as StartingPosition, X as StreamViewType, Y as SubscribeHandler, Z as SubscribeHandlers, _ as buildSubscribeHandler } from '../key-CWytoEaE.js';
|
|
3
|
+
import { E as EntityMetadata } from '../types-nk5okD7d.js';
|
|
4
|
+
import '../types-CgXS-4Ox.js';
|
|
5
|
+
import 'behavior-contracts';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* CDC emulator (issue #72). Subscribes to the core write-capture seam
|
package/dist/cdc/index.js
CHANGED
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
createMaintenanceDrainHandler,
|
|
9
9
|
hashString,
|
|
10
10
|
shardIdFor
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-VECUS35D.js";
|
|
12
12
|
import {
|
|
13
13
|
buildSubscribeHandler,
|
|
14
14
|
parseChange
|
|
15
|
-
} from "../chunk-
|
|
16
|
-
import "../chunk-
|
|
15
|
+
} from "../chunk-7OCXY4R6.js";
|
|
16
|
+
import "../chunk-GWWRXIHF.js";
|
|
17
17
|
import "../chunk-XTWXMOHD.js";
|
|
18
18
|
export {
|
|
19
19
|
CdcEmulator,
|
|
@@ -227,10 +227,35 @@ var param = {
|
|
|
227
227
|
const param2 = { kind: "array", element };
|
|
228
228
|
if (options?.description !== void 0) param2.description = options.description;
|
|
229
229
|
return param2;
|
|
230
|
+
},
|
|
231
|
+
/**
|
|
232
|
+
* A placeholder for a **map / embedded object** value written to a single
|
|
233
|
+
* DynamoDB `M` attribute (issue #295). Free-form by design: the model-side
|
|
234
|
+
* vocabulary (`@map` / `@embedded`) records no nested schema, so the param is
|
|
235
|
+
* typed as the language-native map in generated bindings (`dict` /
|
|
236
|
+
* `map[string]any` / `serde_json::Value` / `array`) while the optional type
|
|
237
|
+
* parameter `T` keeps the TS authoring surface precise.
|
|
238
|
+
*
|
|
239
|
+
* Legal at **value positions only** (a write descriptor's `input`, a
|
|
240
|
+
* transaction fragment value, a batch-write per-item payload field) — a map
|
|
241
|
+
* cannot compose a key, so a `param.map()` in a key position is rejected
|
|
242
|
+
* loudly at build time.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* putLeaf: {
|
|
247
|
+
* upsert: TreeNode,
|
|
248
|
+
* key: { part: param.string(), child: param.string() },
|
|
249
|
+
* input: { value: param.map<AvgValue>(), computedAt: param.number() },
|
|
250
|
+
* }
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
map(options) {
|
|
254
|
+
return makeParam("map", void 0, options?.description);
|
|
230
255
|
}
|
|
231
256
|
};
|
|
232
257
|
function isParam(value) {
|
|
233
|
-
return typeof value === "object" && value !== null && "kind" in value && (value.kind === "string" || value.kind === "number" || value.kind === "literal" || value.kind === "array");
|
|
258
|
+
return typeof value === "object" && value !== null && "kind" in value && (value.kind === "string" || value.kind === "number" || value.kind === "literal" || value.kind === "array" || value.kind === "map");
|
|
234
259
|
}
|
|
235
260
|
|
|
236
261
|
// src/client/lib-dynamodb.ts
|
|
@@ -337,17 +362,220 @@ async function executeBatchGet(docClient, operation) {
|
|
|
337
362
|
return { items: allItems };
|
|
338
363
|
}
|
|
339
364
|
|
|
365
|
+
// src/spec/catalog.ts
|
|
366
|
+
import { classifyBehaviorEffect } from "behavior-contracts";
|
|
367
|
+
var P = (type, required) => required ? { type, required } : { type };
|
|
368
|
+
var READ_PORTS = {
|
|
369
|
+
table: P("string", true),
|
|
370
|
+
indexName: P("string"),
|
|
371
|
+
rangeKey: P("string"),
|
|
372
|
+
rangeOperator: P("string"),
|
|
373
|
+
rangeValue: P("expr"),
|
|
374
|
+
limit: P("number"),
|
|
375
|
+
filter: P("string"),
|
|
376
|
+
projection: P("string[]", true),
|
|
377
|
+
implicitSourceField: P("string")
|
|
378
|
+
};
|
|
379
|
+
var WRITE_PORTS = {
|
|
380
|
+
table: P("string", true),
|
|
381
|
+
entity: P("string", true),
|
|
382
|
+
literalKey: P("boolean"),
|
|
383
|
+
maintain: P("string"),
|
|
384
|
+
"condition.kind": P("string"),
|
|
385
|
+
"condition.field": P("string"),
|
|
386
|
+
"condition.declarative": P("string"),
|
|
387
|
+
"condition.expression": P("string"),
|
|
388
|
+
"condition.names": P("string")
|
|
389
|
+
};
|
|
390
|
+
var entry = (name, inputPorts, shape, portableToIR) => ({
|
|
391
|
+
name,
|
|
392
|
+
inputPorts,
|
|
393
|
+
output: { shape },
|
|
394
|
+
portableToIR,
|
|
395
|
+
// Dynamic port families (key-attribute / write record ports) are validated by
|
|
396
|
+
// assertComponentsInCatalog, not statically enumerable — see the module doc
|
|
397
|
+
// ("Dynamic-port opt-in"). TransactWrite overrides this back to fail-closed.
|
|
398
|
+
additionalPorts: true
|
|
399
|
+
});
|
|
400
|
+
var GRAPHDDB_CATALOG = {
|
|
401
|
+
GetItem: entry("GetItem", READ_PORTS, "item", true),
|
|
402
|
+
Query: entry(
|
|
403
|
+
"Query",
|
|
404
|
+
// `cardinality: 'one'` is the root-collapse port only the Query root emits.
|
|
405
|
+
{ ...READ_PORTS, cardinality: P("string") },
|
|
406
|
+
"items",
|
|
407
|
+
true
|
|
408
|
+
),
|
|
409
|
+
BatchGetItem: entry("BatchGetItem", READ_PORTS, "items", true),
|
|
410
|
+
PutItem: entry("PutItem", WRITE_PORTS, "item", true),
|
|
411
|
+
UpdateItem: entry("UpdateItem", WRITE_PORTS, "item", true),
|
|
412
|
+
DeleteItem: entry("DeleteItem", WRITE_PORTS, "item", true),
|
|
413
|
+
ConditionCheck: entry("ConditionCheck", WRITE_PORTS, "item", true),
|
|
414
|
+
// The executor-side atomic envelope: never referenced by the portable IR
|
|
415
|
+
// (a transaction IS a component over the item primitives) — TS-runtime-only.
|
|
416
|
+
// Its single port is static, so it keeps bc's fail-closed default (no
|
|
417
|
+
// additionalPorts opt-in).
|
|
418
|
+
TransactWrite: {
|
|
419
|
+
...entry("TransactWrite", { TransactItems: P("map[]", true) }, "item", false),
|
|
420
|
+
additionalPorts: false
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
function catalogEntry(name) {
|
|
424
|
+
return GRAPHDDB_CATALOG[name];
|
|
425
|
+
}
|
|
426
|
+
var CATALOG_NAMES = [
|
|
427
|
+
"GetItem",
|
|
428
|
+
"Query",
|
|
429
|
+
"BatchGetItem",
|
|
430
|
+
"PutItem",
|
|
431
|
+
"UpdateItem",
|
|
432
|
+
"DeleteItem",
|
|
433
|
+
"ConditionCheck",
|
|
434
|
+
"TransactWrite"
|
|
435
|
+
];
|
|
436
|
+
var WRITE_CATALOG_NAMES = [
|
|
437
|
+
"PutItem",
|
|
438
|
+
"UpdateItem",
|
|
439
|
+
"DeleteItem",
|
|
440
|
+
"ConditionCheck",
|
|
441
|
+
"TransactWrite"
|
|
442
|
+
];
|
|
443
|
+
var WRITE_SET = new Set(WRITE_CATALOG_NAMES);
|
|
444
|
+
var KEY_ATTR_PORT = /^[A-Za-z0-9_]*(?:PK|SK)$/;
|
|
445
|
+
var WRITE_PORT_FAMILIES = [
|
|
446
|
+
"item.",
|
|
447
|
+
"key.",
|
|
448
|
+
"changes.",
|
|
449
|
+
"add.",
|
|
450
|
+
"condition.equals.",
|
|
451
|
+
"condition.params.",
|
|
452
|
+
"condition.values."
|
|
453
|
+
];
|
|
454
|
+
function assertComponentsInCatalog(components) {
|
|
455
|
+
const errs = [];
|
|
456
|
+
for (const c of components) {
|
|
457
|
+
for (const n of c.body) {
|
|
458
|
+
if ("cond" in n) continue;
|
|
459
|
+
const ref = "map" in n ? n.map : n;
|
|
460
|
+
const entryDef = GRAPHDDB_CATALOG[ref.component];
|
|
461
|
+
if (entryDef === void 0) {
|
|
462
|
+
errs.push(`${c.name}/${n.id}: references '${ref.component}', not a GRAPHDDB_CATALOG entry (known: ${CATALOG_NAMES.join(", ")})`);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const ports = ref.ports;
|
|
466
|
+
for (const [p, s] of Object.entries(entryDef.inputPorts)) {
|
|
467
|
+
if (s.required === true && !(p in ports)) {
|
|
468
|
+
errs.push(`${c.name}/${n.id} (${ref.component}): required port '${p}' is not wired`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const isWriteItem = WRITE_SET.has(ref.component);
|
|
472
|
+
for (const p of Object.keys(ports)) {
|
|
473
|
+
if (p in entryDef.inputPorts) continue;
|
|
474
|
+
if (isWriteItem ? WRITE_PORT_FAMILIES.some((f) => p.startsWith(f)) : KEY_ATTR_PORT.test(p)) continue;
|
|
475
|
+
errs.push(`${c.name}/${n.id} (${ref.component}): port '${p}' is not declared by the catalog entry (nor a ${isWriteItem ? "write record-family" : "key-attribute"} port)`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (errs.length > 0) {
|
|
480
|
+
throw new Error(`graphddb catalog validation failed (${errs.length} violation${errs.length === 1 ? "" : "s"}):
|
|
481
|
+
- ${errs.join("\n - ")}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function deriveContractEffect(component) {
|
|
485
|
+
return classifyBehaviorEffect(component, WRITE_CATALOG_NAMES) === "writing" ? "command" : "query";
|
|
486
|
+
}
|
|
487
|
+
function deriveComponentEffects(components) {
|
|
488
|
+
const out = /* @__PURE__ */ new Map();
|
|
489
|
+
for (const c of components) out.set(c.name, deriveContractEffect(c));
|
|
490
|
+
return out;
|
|
491
|
+
}
|
|
492
|
+
function assertPublishSplitMatchesDerived(derived, split, contracts) {
|
|
493
|
+
const errs = [];
|
|
494
|
+
const check = (name, declared, at) => {
|
|
495
|
+
const truth = derived.get(name);
|
|
496
|
+
if (truth === void 0) {
|
|
497
|
+
errs.push(`${at}: no emitted component '${name}'`);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (truth !== declared) {
|
|
501
|
+
const verb = declared === "query" ? "publishQuery" : "publishCommand";
|
|
502
|
+
const fix = truth === "command" ? "publishCommand" : "publishQuery";
|
|
503
|
+
errs.push(
|
|
504
|
+
`${at}: published under ${verb} (declares '${declared}') but component '${name}' derives '${truth}' from its graph \u2014 the component graph is the source of truth; publish it with ${fix}`
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
for (const name of derived.keys()) {
|
|
509
|
+
if (split.behaviors.has(name)) continue;
|
|
510
|
+
if (split.queries.has(name)) check(name, "query", "components");
|
|
511
|
+
else if (split.transactions.has(name)) check(name, "command", "components");
|
|
512
|
+
else errs.push(`components: '${name}' has no declared publish origin (queries / transactions / behaviors) to verify against`);
|
|
513
|
+
}
|
|
514
|
+
for (const [cname, contract] of Object.entries(contracts)) {
|
|
515
|
+
for (const [mname, m] of Object.entries(contract.methods)) {
|
|
516
|
+
const at = `contract ${cname}.${mname}`;
|
|
517
|
+
if (contract.kind === "query") {
|
|
518
|
+
const q = m;
|
|
519
|
+
if (!split.queries.has(q.operation)) errs.push(`${at}: operation '${q.operation}' is not in queries`);
|
|
520
|
+
else check(q.operation, "query", at);
|
|
521
|
+
} else {
|
|
522
|
+
const cm = m;
|
|
523
|
+
for (const target of [cm.single, cm.batch]) {
|
|
524
|
+
if (target === void 0) continue;
|
|
525
|
+
if (target.mode === "transaction" || target.mode === "batchWrite") {
|
|
526
|
+
if (!split.transactions.has(target.transaction)) errs.push(`${at}: transaction '${target.transaction}' is not in transactions`);
|
|
527
|
+
else check(target.transaction, "command", at);
|
|
528
|
+
} else if (!split.commands.has(target.operation)) {
|
|
529
|
+
errs.push(`${at}: operation '${target.operation}' is not in commands`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (cm.returnSelection !== void 0) check(`${cname}__${mname}__readback`, "query", `${at} readback`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (errs.length > 0) {
|
|
537
|
+
throw new Error(
|
|
538
|
+
`graphddb CQRS classification: the declared publish split contradicts the graph-derived truth (${errs.length}):
|
|
539
|
+
- ${errs.join("\n - ")}`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// src/executor/handler-registry.ts
|
|
545
|
+
function resolveReadHandler(registry, name) {
|
|
546
|
+
const handler = registry[name];
|
|
547
|
+
if (handler === void 0) {
|
|
548
|
+
throw new Error(
|
|
549
|
+
`graphddb handler registry: no handler bound for catalog component '${name}' (known: ${Object.keys(registry).join(", ")})`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
return handler;
|
|
553
|
+
}
|
|
554
|
+
function assertHandlersInCatalog(registry) {
|
|
555
|
+
for (const name of Object.keys(registry)) {
|
|
556
|
+
if (catalogEntry(name) === void 0) {
|
|
557
|
+
throw new Error(
|
|
558
|
+
`graphddb handler registry: handler '${name}' has no catalog entry (every bound handler must declare a Port schema in GRAPHDDB_CATALOG)`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
340
564
|
// src/executor/dynamo-executor.ts
|
|
565
|
+
var READ_HANDLERS = {
|
|
566
|
+
GetItem: (docClient, operation) => executeGetItem(docClient, operation),
|
|
567
|
+
Query: (docClient, operation) => executeQuery(docClient, operation),
|
|
568
|
+
BatchGetItem: (docClient, operation) => {
|
|
569
|
+
const op = operation;
|
|
570
|
+
return executeBatchGet(docClient, op);
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
assertHandlersInCatalog(READ_HANDLERS);
|
|
341
574
|
var DynamoExecutor = class {
|
|
342
575
|
async execute(operation, _options) {
|
|
343
576
|
const docClient = await ClientManager.getDocumentClient();
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
if (operation.type === "BatchGetItem") {
|
|
348
|
-
return this.batchGet(operation);
|
|
349
|
-
}
|
|
350
|
-
return executeQuery(docClient, operation);
|
|
577
|
+
const handler = resolveReadHandler(READ_HANDLERS, operation.type);
|
|
578
|
+
return handler(docClient, operation);
|
|
351
579
|
}
|
|
352
580
|
async batchGet(input, _options) {
|
|
353
581
|
const docClient = await ClientManager.getDocumentClient();
|
|
@@ -1842,9 +2070,21 @@ function buildUpdateInput(modelClass, entity, changes, options) {
|
|
|
1842
2070
|
const embeddedFieldNames = new Set(
|
|
1843
2071
|
meta.embeddedFields.map((e) => e.propertyName)
|
|
1844
2072
|
);
|
|
1845
|
-
const
|
|
1846
|
-
const
|
|
1847
|
-
const
|
|
2073
|
+
const addEntries = Object.entries(options?.add ?? {});
|
|
2074
|
+
const addNames = {};
|
|
2075
|
+
const addValues = {};
|
|
2076
|
+
const addClauses = [];
|
|
2077
|
+
addEntries.forEach(([field, delta], i) => {
|
|
2078
|
+
const nameKey = `#add${i}`;
|
|
2079
|
+
const valueKey = `:add${i}`;
|
|
2080
|
+
addNames[nameKey] = field;
|
|
2081
|
+
addValues[valueKey] = delta;
|
|
2082
|
+
addClauses.push(`${nameKey} ${valueKey}`);
|
|
2083
|
+
});
|
|
2084
|
+
const hasChanges = Object.keys(serializedChanges).length > 0;
|
|
2085
|
+
const updateResult = hasChanges || addClauses.length === 0 ? buildUpdateExpression(serializedChanges, embeddedFieldNames) : { expression: "", names: {}, values: {} };
|
|
2086
|
+
const names = { ...updateResult.names, ...addNames };
|
|
2087
|
+
const values = { ...updateResult.values, ...addValues };
|
|
1848
2088
|
const gsiSetClauses = buildGsiRederiveSets(
|
|
1849
2089
|
modelClass,
|
|
1850
2090
|
meta,
|
|
@@ -1862,13 +2102,16 @@ function buildUpdateInput(modelClass, entity, changes, options) {
|
|
|
1862
2102
|
Object.assign(names, condResult.names);
|
|
1863
2103
|
Object.assign(values, condResult.values);
|
|
1864
2104
|
}
|
|
2105
|
+
const setPortion = appendSetClauses(updateResult.expression, gsiSetClauses);
|
|
2106
|
+
const addPortion = addClauses.length > 0 ? `ADD ${addClauses.join(", ")}` : "";
|
|
2107
|
+
const updateExpression = [setPortion, addPortion].filter((s) => s !== "").join(" ");
|
|
1865
2108
|
const updateInput = {
|
|
1866
2109
|
TableName: tableName,
|
|
1867
2110
|
Key: {
|
|
1868
2111
|
PK: pk,
|
|
1869
2112
|
SK: sk
|
|
1870
2113
|
},
|
|
1871
|
-
UpdateExpression:
|
|
2114
|
+
UpdateExpression: updateExpression,
|
|
1872
2115
|
ExpressionAttributeNames: names
|
|
1873
2116
|
};
|
|
1874
2117
|
if (Object.keys(values).length > 0) {
|
|
@@ -2448,6 +2691,11 @@ export {
|
|
|
2448
2691
|
isParam,
|
|
2449
2692
|
BATCH_GET_MAX_KEYS,
|
|
2450
2693
|
chunkArray,
|
|
2694
|
+
GRAPHDDB_CATALOG,
|
|
2695
|
+
assertComponentsInCatalog,
|
|
2696
|
+
deriveContractEffect,
|
|
2697
|
+
deriveComponentEffects,
|
|
2698
|
+
assertPublishSplitMatchesDerived,
|
|
2451
2699
|
ClientManager,
|
|
2452
2700
|
isRawCondition,
|
|
2453
2701
|
cond,
|