graphddb 0.7.10 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +6 -6
  2. package/dist/cdc/index.d.ts +389 -5
  3. package/dist/cdc/index.js +4 -4
  4. package/dist/{chunk-AD6ZQTTE.js → chunk-GS4C5VGO.js} +2 -6
  5. package/dist/{chunk-DFUKGU2Q.js → chunk-HNY2EJPV.js} +216 -229
  6. package/dist/{chunk-EOJDN3SA.js → chunk-I4LEJ4TF.js} +3744 -6144
  7. package/dist/{chunk-3ZU2VW3L.js → chunk-L2NEDS7U.js} +582 -781
  8. package/dist/chunk-L4QRCHRQ.js +278 -0
  9. package/dist/chunk-LGHSZIEE.js +187 -0
  10. package/dist/chunk-N4NWYNGZ.js +1987 -0
  11. package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
  12. package/dist/cli.js +63 -254
  13. package/dist/index.d.ts +23 -1550
  14. package/dist/index.js +94 -1850
  15. package/dist/internal/index.d.ts +84 -0
  16. package/dist/internal/index.js +701 -0
  17. package/dist/{maintenance-view-adapter-BAZ9uBGe.d.ts → key-DR7_lpyk.d.ts} +504 -1910
  18. package/dist/linter/index.d.ts +39 -7
  19. package/dist/linter/index.js +22 -4
  20. package/dist/{registry-LWE54Sdc.d.ts → linter-C-vypgut.d.ts} +22 -22
  21. package/dist/prepared-artifact-BpPgkXEo.d.ts +281 -0
  22. package/dist/spec/index.d.ts +506 -5
  23. package/dist/spec/index.js +22 -18
  24. package/dist/testing/index.d.ts +2 -3
  25. package/dist/testing/index.js +4 -4
  26. package/dist/transform/index.d.ts +1 -1
  27. package/dist/transform/index.js +4 -4
  28. package/dist/{types-BQLzTEqh.d.ts → types-2PMXEn5x.d.ts} +8 -10
  29. package/dist/types-BXLzIcQD.d.ts +450 -0
  30. package/docs/cdc-projection.md +5 -5
  31. package/docs/class-hydration.md +1 -1
  32. package/docs/cqrs-contract.md +28 -20
  33. package/docs/design-patterns.md +5 -5
  34. package/docs/docs-generation.md +6 -6
  35. package/docs/middleware.md +15 -15
  36. package/docs/mutation-command-derivation.md +52 -42
  37. package/docs/prepared-statements.md +14 -14
  38. package/docs/python-bridge.md +96 -65
  39. package/docs/spec.md +153 -124
  40. package/docs/testing.md +9 -8
  41. package/package.json +14 -4
  42. package/dist/chunk-3UD3XIF2.js +0 -860
  43. package/dist/chunk-MMVHOUM4.js +0 -24
  44. package/dist/from-change-Ty95KA8C.d.ts +0 -327
  45. package/dist/index-Dc7d8mWI.d.ts +0 -1089
  46. package/dist/relation-depth-BRS513Tq.d.ts +0 -36
package/dist/index.js CHANGED
@@ -1,191 +1,83 @@
1
1
  import {
2
- queryBoundaryRule
3
- } from "./chunk-MMVHOUM4.js";
4
- import {
5
- BatchGetResult,
6
2
  DDBModel,
7
- EDGE_WRITES_MARKER,
8
- LOGICAL_EFFECT_CATEGORIES,
9
- OLD_VALUE_NAMESPACE,
10
- PREPARED_FORMAT_VERSION,
11
- PREPARE_CACHE_MAX,
12
- PreparedReadStatement,
13
- PreparedWriteStatement,
14
- RELATION_TRAVERSAL_CONCURRENCY,
15
- assertBundleSerializable,
16
- assertContractBoundaries,
17
- assertContractN1Safe,
18
- assertJsonSerializable,
19
- assertSupportedCondition,
20
- buildBridgeBundle,
21
- buildContexts,
22
- buildContracts,
23
- buildManifest,
24
- buildManifestEntity,
25
- buildOperations,
26
- buildProjection,
27
- buildQuerySpec,
28
- buildTransactionSpec,
29
- buildTransactions,
30
- collectContractBoundaryViolations,
31
- collectContractN1Violations,
32
- compileFilterExpression,
33
- compileFragment,
34
- compileMutationPlan,
35
- compileSingleFragmentPlan,
36
- conditionParamName,
37
- contractOfMethodSpec,
38
- decodeCursor,
39
- definePlan,
40
- deriveCompositionPlan,
41
- deriveEdgeWriteItems,
42
- deriveEdgeWriteItemsFor,
43
- deriveModelEdgeWriteItems,
44
- edgeWrites,
45
- encodeCursor,
46
- entityFingerprint,
47
- evaluateFilter,
48
- execute,
49
- executeBatchGet,
50
- executeBatchWrite,
51
- executeCommandMethod,
52
- executeExplain,
53
- executeList,
54
- executeListInternal,
55
- executeLogicalParallelWrites,
56
- executeLogicalWriteOps,
57
- executeQuery,
58
- from,
59
- getEdgeWrites,
60
- isCommandModelContract,
61
- isCommandPlan,
62
- isContractComposeNode,
63
- isContractFromRef,
64
- isContractKeyFieldRef,
65
- isContractKeyRef,
66
- isContractParamRef,
67
- isEdgeWritesDefinition,
68
- isMutationFragment,
69
- isMutationInputRef,
70
- isPlannedCommandMethod,
71
- isPreparedParamRef,
72
- isQueryModelContract,
73
- mapWithConcurrency,
74
- mintContractKeyFieldRef,
75
- mintContractParamRef,
76
- mutation,
77
- plan,
78
- prepare,
79
- publicCommandModel,
80
- publicQueryModel,
81
- query,
82
- resolveLifecycle,
83
- resolveRelations,
84
- serializeEffectParams,
85
- validateDepth,
86
- wholeKeysSentinel
87
- } from "./chunk-EOJDN3SA.js";
88
- import {
89
- CdcEmulator,
90
- MAINT_OUTBOX_PK_PREFIX,
91
- MaintenanceDrain,
92
- compareDesc,
93
- createCdcEmulator,
94
- createMaintenanceDrain,
95
- createMaintenanceDrainHandler,
96
- orderAndTrimCollection,
97
- pathField,
98
- projectFrom
99
- } from "./chunk-AD6ZQTTE.js";
3
+ mutate,
4
+ publishCommand,
5
+ publishQuery
6
+ } from "./chunk-I4LEJ4TF.js";
100
7
  import {
101
- ENTITY_WRITES_MARKER,
102
- LIFECYCLE_CONTRACT_MARKER,
103
- buildMaintenanceGraph,
104
- buildSubscribeHandler,
105
- collectViewDefinitions,
106
- detectRelationFields,
107
8
  entityWrites,
108
9
  getEntityWrites,
109
- getImplicitKeyFields,
110
- hydrate,
111
10
  identity,
112
- isEntityWritesDefinition,
113
- isLifecycleContract,
114
- isMaintainTrigger,
115
- isSelectBuilder,
116
- lifecyclePhaseForIntent,
117
- maintainTrigger,
118
- parseChange,
119
11
  preview
120
- } from "./chunk-DFUKGU2Q.js";
12
+ } from "./chunk-HNY2EJPV.js";
121
13
  import {
122
- EXPR_VERSION,
123
- MARKER_ROW_ENTITY,
124
- SCP_LOWERED_MARKER,
125
- SPEC_VERSION,
126
- SPEC_VERSION_SCP,
127
- SPEC_VERSION_SUPPORTED,
128
- canonicalizeExpressionSpec,
129
- defineScpTransaction,
130
- defineTransaction,
131
- defineTransactions,
132
- isTransactionRef,
133
- operationsContainScpNodes,
134
- operationsSpecVersion,
135
14
  when
136
- } from "./chunk-3UD3XIF2.js";
15
+ } from "./chunk-L4QRCHRQ.js";
137
16
  import {
138
- BATCH_GET_MAX_KEYS,
139
- BATCH_WRITE_MAX_ITEMS,
140
17
  ClientManager,
141
- DEFAULT_MAX_ATTEMPTS,
142
- DEFAULT_RETRY_POLICY,
143
- DynamoExecutor,
144
- MAX_TRANSACT_ITEMS,
145
18
  MetadataRegistry,
146
- RetryingExecutor,
147
- TransactionContext,
148
- attachModelClass,
149
- buildConditionExpression,
150
- buildDeleteInput,
151
- buildPutInput,
152
- buildUpdateExpression,
153
- buildUpdateInput,
154
- collapseSameKeyItems,
155
- collectRawConditionParams,
156
- commitTransaction,
157
19
  cond,
158
- execItemKeySignature,
159
- executeDelete,
160
- executePut,
161
- executeTransaction,
162
- executeUpdate,
163
20
  gsi,
164
- isParam,
165
- isRawCondition,
166
- isRetryableError,
167
- isRetryableTransactionCancellation,
168
- param,
169
- resolveConditionTree,
170
- resolveModelClass,
171
- serializeFieldValue
172
- } from "./chunk-3ZU2VW3L.js";
21
+ param
22
+ } from "./chunk-L2NEDS7U.js";
173
23
  import {
174
- Linter,
175
24
  TableMapping,
176
- createDefaultLinter,
177
- gsiAmbiguityRule,
178
- isColumn,
179
- isKeySegment,
180
25
  k,
181
- key,
182
- missingGsiRule,
183
- noScanRule,
184
- relationDepthRule,
185
- requireLimitRule,
186
- resolveKey,
187
- segmentFieldNames
188
- } from "./chunk-PDUVTYC5.js";
26
+ key
27
+ } from "./chunk-XTWXMOHD.js";
28
+
29
+ // src/config/index.ts
30
+ var config = {
31
+ /**
32
+ * Register the `DynamoDBClient` GraphDDB sends through.
33
+ *
34
+ * IMPORTANT (issue #111): GraphDDB owns single-op throttle / transient-error
35
+ * retry (see {@link config.retry}), so a client left at the AWS SDK default
36
+ * `maxAttempts: 3` retries MULTIPLICATIVELY underneath the library. Construct the
37
+ * client with `maxAttempts: 1` so the library is the single source of truth for
38
+ * retry — e.g. `graphddb.config.client(new DynamoDBClient({ maxAttempts: 1 }))`.
39
+ * GraphDDB does NOT mutate a client you pass in.
40
+ */
41
+ client(client) {
42
+ ClientManager.setClient(client);
43
+ },
44
+ /**
45
+ * Read back the registered `DynamoDBClient` — the resolved client last passed to
46
+ * {@link config.client}. Throws a clear error if no client has been registered
47
+ * yet (call {@link config.client} first).
48
+ */
49
+ getClient() {
50
+ return ClientManager.getClient();
51
+ },
52
+ /** Set the declared-model-name → physical-DynamoDB-table-name map. */
53
+ tables(mapping) {
54
+ TableMapping.set(mapping);
55
+ },
56
+ /**
57
+ * Configure the global single-op retry policy (issue #111) — exponential backoff
58
+ * + jitter with a bounded attempt cap, applied to every GetItem / Query / Put /
59
+ * Update / Delete / relation-fan-out send and to `TransactWriteItems`. A sensible
60
+ * default is ALWAYS on; call this only to tune it (or pass `null` to restore the
61
+ * default). A per-call `retry?: RetryPolicy | false` on an operation's options
62
+ * overrides this globally for that call (`false` disables retry).
63
+ */
64
+ retry(policy) {
65
+ ClientManager.setRetryPolicy(policy);
66
+ },
67
+ /**
68
+ * Register a {@link Middleware} (issue #50; read path #138, write path #139).
69
+ * One registered object may carry read and/or write hooks. Ordering: `before*`
70
+ * runs first-registered-first (FIFO); `after*` / `onError` run
71
+ * last-registered-first (LIFO). A host-runtime global; never serialized.
72
+ */
73
+ use(mw) {
74
+ ClientManager.use(mw);
75
+ },
76
+ /** Remove every registered middleware (read AND write; teardown / tests). See {@link config.use}. */
77
+ clearMiddleware() {
78
+ ClientManager.clearMiddleware();
79
+ }
80
+ };
189
81
 
190
82
  // src/metadata/prefix.ts
191
83
  function derivePrefix(customPrefix, className) {
@@ -201,36 +93,6 @@ function derivePrefix(customPrefix, className) {
201
93
  return `${base}#`;
202
94
  }
203
95
 
204
- // src/metadata/validation.ts
205
- function isSubsetOf(a, b) {
206
- const setB = new Set(b);
207
- return a.length > 0 && a.every((f) => setB.has(f));
208
- }
209
- function validateGsiAmbiguity(primaryKey, gsiDefinitions) {
210
- const patterns = [];
211
- if (primaryKey) {
212
- patterns.push({
213
- label: "primaryKey",
214
- fieldNames: primaryKey.inputFieldNames
215
- });
216
- }
217
- for (const gsi2 of gsiDefinitions) {
218
- patterns.push({ label: gsi2.indexName, fieldNames: gsi2.inputFieldNames });
219
- }
220
- for (let i = 0; i < patterns.length; i++) {
221
- for (let j = i + 1; j < patterns.length; j++) {
222
- const a = patterns[i];
223
- const b = patterns[j];
224
- if (isSubsetOf(a.fieldNames, b.fieldNames) || isSubsetOf(b.fieldNames, a.fieldNames)) {
225
- const smaller = a.fieldNames.length <= b.fieldNames.length ? a : b;
226
- throw new Error(
227
- `Ambiguous GSI resolution: field set [${smaller.fieldNames.join(", ")}] matches both '${a.label}' and '${b.label}'. Each access pattern must have a unique field combination.`
228
- );
229
- }
230
- }
231
- }
232
- }
233
-
234
96
  // src/decorators/collector.ts
235
97
  var pendingFields = [];
236
98
  var pendingEmbedded = [];
@@ -545,7 +407,7 @@ function refs(targetFactory, options) {
545
407
  `@refs relation '${propertyName}': options must supply \`from\` (the parent list attribute, e.g. 'tagRefs') and \`key\` (the ref field on each element, e.g. 'tagId').`
546
408
  );
547
409
  }
548
- const { from: from2, key: key2, to, ...relationOptions } = options;
410
+ const { from, key: key2, to, ...relationOptions } = options;
549
411
  const targetKeyField = to ?? key2;
550
412
  collectRelation({
551
413
  type: "refs",
@@ -554,7 +416,7 @@ function refs(targetFactory, options) {
554
416
  // Scalar keyBinding: target child key field ← element key name. Per resolved
555
417
  // element the existing scalar key-building machinery reuses this unchanged.
556
418
  keyBinding: { [targetKeyField]: key2 },
557
- refs: { from: from2, key: key2 },
419
+ refs: { from, key: key2 },
558
420
  ...Object.keys(relationOptions).length > 0 ? { options: relationOptions } : {}
559
421
  });
560
422
  };
@@ -754,1688 +616,70 @@ function cdcProjected() {
754
616
  };
755
617
  }
756
618
 
757
- // src/operations/declarative-transaction.ts
758
- import { renderTemplate, TemplateFailure } from "behavior-contracts";
759
-
760
- // src/runtime/behavior-contracts-adapter.ts
761
- function toSharedValue(v) {
762
- if (v === null || v === void 0) return null;
763
- switch (typeof v) {
764
- case "string":
765
- case "boolean":
766
- case "bigint":
767
- return v;
768
- case "number":
769
- return Number.isInteger(v) ? BigInt(v) : v;
770
- default:
771
- break;
772
- }
773
- if (v instanceof Date) return v.toJSON();
774
- if (Array.isArray(v)) return v.map((e) => toSharedValue(e));
775
- if (typeof v === "object") {
776
- const out = {};
777
- for (const [k2, field2] of Object.entries(v)) {
778
- if (field2 === void 0) continue;
779
- out[k2] = toSharedValue(field2);
780
- }
781
- return out;
782
- }
783
- return v;
784
- }
785
- function toSharedParams(params) {
786
- const out = {};
787
- for (const [name, value] of Object.entries(params)) {
788
- if (value === void 0) continue;
789
- out[name] = toSharedValue(value);
790
- }
791
- return out;
792
- }
793
-
794
- // src/runtime/guard-eval.ts
795
- import { evaluateExpression, ExprFailure } from "behavior-contracts";
796
- var GUARD_SCOPE_ROOTS = /* @__PURE__ */ new Set(["input", "item"]);
797
- function assertGuardScopes(node, context) {
798
- if (node === null || typeof node !== "object") return;
799
- const obj = node;
800
- const ref = obj.ref ?? obj.refOpt;
801
- if (Array.isArray(ref)) {
802
- const root = ref[0];
803
- if (typeof root === "string" && !GUARD_SCOPE_ROOTS.has(root)) {
804
- throw new Error(
805
- `${context}: this guard references '${root}', but a client-side guard may only reference the transaction 'input' / fan-out 'item' scopes. An expression that reads a DynamoDB attribute must lower to a ConditionExpression (write-condition class), not be evaluated client-side (issue #265). Refusing to evaluate a stale check.`
806
- );
807
- }
808
- return;
809
- }
810
- for (const value of Object.values(obj)) {
811
- if (Array.isArray(value)) {
812
- for (const child of value) assertGuardScopes(child, context);
813
- } else if (value !== null && typeof value === "object") {
814
- for (const child of Object.values(value)) {
815
- assertGuardScopes(child, context);
816
- }
817
- }
818
- }
819
- }
820
- function evaluateGuard(guard, params, element, context) {
821
- assertGuardScopes(guard.expr, context);
822
- const scope = { input: toSharedParams(params) };
823
- if (element !== void 0) {
824
- scope.item = toSharedValue(element);
825
- }
826
- let result;
827
- try {
828
- result = evaluateExpression(guard.expr, scope);
829
- } catch (exc) {
830
- if (exc instanceof ExprFailure) {
831
- throw new Error(`${context}: guard evaluation failed (${exc.code}): ${exc.message}`);
832
- }
833
- throw exc;
834
- }
835
- if (typeof result !== "boolean") {
836
- throw new Error(
837
- `${context}: a guard expression must evaluate to a boolean, got ${result === null ? "null" : typeof result} (issue #265).`
838
- );
839
- }
840
- return result;
841
- }
842
-
843
- // src/operations/declarative-transaction.ts
844
- var PLACEHOLDER_RE = /\{[^{}]+\}/g;
845
- function hasNonCoreScope(template) {
846
- return (template.match(PLACEHOLDER_RE) ?? []).some((m) => {
847
- const name = m.slice(1, -1);
848
- return name.startsWith("item.") || name.startsWith("result.");
849
- });
850
- }
851
- function resolveTemplate(template, params, element) {
852
- if (hasNonCoreScope(template)) {
853
- return template.replace(PLACEHOLDER_RE, (match) => {
854
- const name = match.slice(1, -1);
855
- if (name.startsWith("item.")) {
856
- const field2 = name.slice("item.".length);
857
- if (!element || !(field2 in element)) {
858
- throw new Error(
859
- `declarative transaction: element field '${field2}' is not bound; template '${template}' is only valid inside a forEach over the array param that supplies '${field2}'.`
860
- );
861
- }
862
- return String(element[field2]);
863
- }
864
- if (!(name in params) || params[name] === void 0 || params[name] === null) {
865
- throw new Error(
866
- `declarative transaction: template '${template}' references unbound parameter '${name}'.`
867
- );
868
- }
869
- return String(params[name]);
870
- });
871
- }
872
- try {
873
- return renderTemplate(template, toSharedParams(params));
874
- } catch (exc) {
875
- if (exc instanceof TemplateFailure && exc.code === "UNBOUND_PARAM") {
876
- throw new Error(`declarative transaction: ${exc.message}.`);
877
- }
878
- throw exc;
879
- }
880
- }
881
- function isWholePlaceholder(template) {
882
- return /^\{[^{}]+\}$/.test(template);
883
- }
884
- function resolveValue(template, params, element) {
885
- if (typeof template !== "string") return template;
886
- if (isWholePlaceholder(template)) {
887
- const name = template.slice(1, -1);
888
- if (name.startsWith("item.")) {
889
- const field2 = name.slice("item.".length);
890
- if (element && field2 in element) return element[field2];
891
- } else if (name in params) {
892
- return params[name];
893
- }
894
- }
895
- return resolveTemplate(template, params, element);
896
- }
897
- function resolveRecord(record, params, element) {
898
- const out = {};
899
- for (const [field2, tmpl] of Object.entries(record ?? {})) {
900
- out[field2] = resolveValue(tmpl, params, element);
901
- }
902
- return out;
903
- }
904
- function fillKeyTemplate(template, item) {
905
- return template.replace(PLACEHOLDER_RE, (match) => {
906
- const name = match.slice(1, -1);
907
- return item[name] === void 0 ? "" : String(item[name]);
908
- });
909
- }
910
- function keyTemplateFillable(template, item) {
911
- const matches = template.match(PLACEHOLDER_RE) ?? [];
912
- return matches.every((m) => m.slice(1, -1) in item);
913
- }
914
- function addKeyAttributes(entity, item) {
915
- if (entity.key) {
916
- item.PK = fillKeyTemplate(entity.key.pkTemplate, item);
917
- if (entity.key.skTemplate !== null) {
918
- item.SK = fillKeyTemplate(entity.key.skTemplate, item);
919
- }
920
- }
921
- for (const gsi2 of entity.gsis) {
922
- if (keyTemplateFillable(gsi2.pkTemplate, item)) {
923
- item[`${gsi2.indexName}PK`] = fillKeyTemplate(gsi2.pkTemplate, item);
924
- }
925
- if (gsi2.skTemplate !== null && keyTemplateFillable(gsi2.skTemplate, item)) {
926
- item[`${gsi2.indexName}SK`] = fillKeyTemplate(gsi2.skTemplate, item);
927
- }
928
- }
929
- }
930
- function whenHolds(when2, params, element) {
931
- const left = resolveTemplate(when2.left, params, element);
932
- const right = resolveTemplate(when2.right, params, element);
933
- return when2.op === "eq" ? left === right : left !== right;
934
- }
935
- function resolveRawConditionValue(val, params, element) {
936
- if (val !== null && typeof val === "object" && typeof val.$param === "string") {
937
- return resolveValue(`{${val.$param}}`, params, element);
938
- }
939
- return val;
940
- }
941
- function applyCondition(condition, target, params, element) {
942
- if (!condition) return;
943
- const names = target.ExpressionAttributeNames ??= {};
944
- if (condition.kind === "notExists") {
945
- names["#pk"] = "PK";
946
- target.ConditionExpression = "attribute_not_exists(#pk)";
947
- return;
948
- }
949
- if (condition.kind === "attributeExists" || condition.kind === "attributeNotExists") {
950
- const fn = condition.kind === "attributeExists" ? "attribute_exists" : "attribute_not_exists";
951
- names["#ce"] = condition.field;
952
- target.ConditionExpression = `${fn}(#ce)`;
953
- return;
954
- }
955
- const values = target.ExpressionAttributeValues ??= {};
956
- if (condition.kind === "raw") {
957
- Object.assign(names, condition.names);
958
- for (const [alias, val] of Object.entries(condition.values)) {
959
- values[alias] = resolveRawConditionValue(val, params, element);
960
- }
961
- target.ConditionExpression = condition.expression;
962
- return;
963
- }
964
- if (condition.kind === "scpExpr") {
965
- throw new Error(
966
- "declarative transaction: this runtime does not evaluate scpExpr write conditions yet (spec 1.2 expression vocabulary, issue #261); evaluation lands with Phase 3 S5 (#265). Refusing to execute the write with its condition ignored."
967
- );
968
- }
969
- if (condition.kind === "expr") {
970
- const concrete = resolveConditionTree(
971
- condition.declarative,
972
- (name) => resolveValue(`{${name}}`, params, element)
973
- );
974
- const compiled = buildConditionExpression(concrete);
975
- Object.assign(names, compiled.names);
976
- Object.assign(values, compiled.values);
977
- target.ConditionExpression = compiled.expression;
978
- return;
979
- }
980
- const clauses = [];
981
- let i = 0;
982
- for (const [field2, tmpl] of Object.entries(condition.fields)) {
983
- const n = `#e${i}`;
984
- const v = `:e${i}`;
985
- names[n] = field2;
986
- values[v] = resolveValue(tmpl, params, element);
987
- clauses.push(`${n} = ${v}`);
988
- i++;
989
- }
990
- target.ConditionExpression = clauses.join(" AND ");
991
- }
992
- function entityByName(manifest, name) {
993
- const entity = manifest.entities[name];
994
- if (!entity) {
995
- throw new Error(`declarative transaction: unknown entity '${name}' in manifest.`);
996
- }
997
- return entity;
998
- }
999
- function physicalTableName(manifest, logical) {
1000
- return manifest.tables[logical]?.physicalName ?? logical;
1001
- }
1002
- function applyMaintainTransform(op, args, value) {
1003
- if (op === "identity") return value;
1004
- if (op === "preview") {
1005
- const n = args[0];
1006
- if (typeof n !== "number" || !Number.isInteger(n) || n <= 0) {
1007
- throw new Error(
1008
- `declarative transaction: a maintenance \`preview\` projection has a non-positive-integer length bound (${JSON.stringify(n)}).`
1009
- );
1010
- }
1011
- if (value === null || value === void 0) return value;
1012
- return String(value).slice(0, n);
1013
- }
1014
- throw new Error(
1015
- `declarative transaction: unknown maintenance projection op '${String(op)}' (expected 'identity' / 'preview').`
1016
- );
1017
- }
1018
- function buildMaintainProjection(maintain, params, element) {
1019
- const out = {};
1020
- for (const [attr, transform] of Object.entries(maintain.projection)) {
1021
- const value = resolveValue(`{${transform.inputField}}`, params, element);
1022
- out[attr] = applyMaintainTransform(transform.op, transform.args, value);
1023
- }
1024
- return out;
1025
- }
1026
- function buildMaintainUpdateItem(spec, maintain, table, key2, params, element) {
1027
- const update = { TableName: table, Key: key2 };
1028
- if (maintain.kind === "counter") {
1029
- const counter = maintain.counter;
1030
- if (counter === void 0) {
1031
- throw new Error(
1032
- `declarative transaction: a 'counter' maintenance write on '${spec.entity}' has no \`counter\` payload.`
1033
- );
1034
- }
1035
- update.UpdateExpression = "ADD #a0 :a0";
1036
- update.ExpressionAttributeNames = { "#a0": counter.attribute };
1037
- update.ExpressionAttributeValues = {
1038
- ":a0": typeof counter.delta === "string" ? Number(counter.delta) : counter.delta
1039
- };
1040
- return { Update: update };
1041
- }
1042
- const projection = buildMaintainProjection(maintain, params, element);
1043
- if (maintain.kind === "collection") {
1044
- const field2 = maintain.collection?.field;
1045
- if (field2 === void 0) {
1046
- throw new Error(
1047
- `declarative transaction: a 'collection' maintenance write on '${spec.entity}' has no \`collection.field\`.`
1048
- );
1049
- }
1050
- update.UpdateExpression = "SET #c = list_append(if_not_exists(#c, :empty), :item)";
1051
- update.ExpressionAttributeNames = { "#c": field2 };
1052
- update.ExpressionAttributeValues = { ":empty": [], ":item": [projection] };
1053
- } else {
1054
- const names = {};
1055
- const values = {};
1056
- const sets = [];
1057
- let i = 0;
1058
- for (const [attr, value] of Object.entries(projection)) {
1059
- names[`#m${i}`] = attr;
1060
- values[`:m${i}`] = value;
1061
- sets.push(`#m${i} = :m${i}`);
1062
- i++;
1063
- }
1064
- if (sets.length === 0) {
1065
- throw new Error(
1066
- `declarative transaction: a 'snapshot' maintenance write on '${spec.entity}' has an empty projection.`
1067
- );
1068
- }
1069
- update.UpdateExpression = `SET ${sets.join(", ")}`;
1070
- update.ExpressionAttributeNames = names;
1071
- update.ExpressionAttributeValues = values;
1072
- }
1073
- return { Update: update };
1074
- }
1075
- function buildTransactItem(spec, manifest, params, element) {
1076
- const literal2 = spec.literalKey === true;
1077
- const entity = literal2 ? void 0 : entityByName(manifest, spec.entity);
1078
- const table = literal2 ? physicalTableName(manifest, spec.tableName) : entity.physicalName;
1079
- if (spec.type === "Put") {
1080
- const item = resolveRecord(spec.item, params, element);
1081
- if (entity) addKeyAttributes(entity, item);
1082
- const put = { TableName: table, Item: item };
1083
- applyCondition(spec.condition, put, params, element);
1084
- return { Put: put };
1085
- }
1086
- const key2 = resolveRecord(spec.keyCondition, params, element);
1087
- if (spec.type === "Delete") {
1088
- const del = { TableName: table, Key: key2 };
1089
- applyCondition(spec.condition, del, params, element);
1090
- return { Delete: del };
1091
- }
1092
- if (spec.type === "ConditionCheck") {
1093
- if (!spec.condition) {
1094
- throw new Error(
1095
- `declarative transaction: ConditionCheck on '${spec.entity}' has no condition; a ConditionCheck must carry the assertion it makes.`
1096
- );
1097
- }
1098
- const check = { TableName: table, Key: key2 };
1099
- applyCondition(spec.condition, check, params, element);
1100
- return { ConditionCheck: check };
1101
- }
1102
- if (spec.maintain !== void 0) {
1103
- return buildMaintainUpdateItem(spec, spec.maintain, table, key2, params, element);
1104
- }
1105
- const changes = resolveRecord(spec.changes, params, element);
1106
- const adds = resolveRecord(spec.add, params, element);
1107
- const names = {};
1108
- const values = {};
1109
- const sets = [];
1110
- const addClauses = [];
1111
- let i = 0;
1112
- for (const [field2, value] of Object.entries(changes)) {
1113
- const n = `#c${i}`;
1114
- const v = `:c${i}`;
1115
- names[n] = field2;
1116
- values[v] = value;
1117
- sets.push(`${n} = ${v}`);
1118
- i++;
1119
- }
1120
- let j = 0;
1121
- for (const [field2, value] of Object.entries(adds)) {
1122
- const n = `#a${j}`;
1123
- const v = `:a${j}`;
1124
- names[n] = field2;
1125
- values[v] = typeof value === "string" ? Number(value) : value;
1126
- addClauses.push(`${n} ${v}`);
1127
- j++;
1128
- }
1129
- const update = { TableName: table, Key: key2 };
1130
- const exprParts = [];
1131
- if (sets.length > 0) exprParts.push("SET " + sets.join(", "));
1132
- if (addClauses.length > 0) exprParts.push("ADD " + addClauses.join(", "));
1133
- if (exprParts.length > 0) {
1134
- update.UpdateExpression = exprParts.join(" ");
1135
- update.ExpressionAttributeNames = names;
1136
- update.ExpressionAttributeValues = values;
1137
- }
1138
- applyCondition(spec.condition, update, params, element);
1139
- return { Update: update };
1140
- }
1141
- function expandTransactionItems(spec, manifest, params) {
1142
- const expanded = [];
1143
- const emit = (item, element) => {
1144
- const model2 = item.literalKey === true || item.entity === MARKER_ROW_ENTITY ? void 0 : item.entity;
1145
- expanded.push({ item: buildTransactItem(item, manifest, params, element), model: model2 });
1146
- };
1147
- for (const item of spec.items) {
1148
- if (item.forEach) {
1149
- const source = params[item.forEach.source];
1150
- if (!Array.isArray(source)) {
1151
- throw new Error(
1152
- `declarative transaction: forEach source '${item.forEach.source}' must be an array param; got ${typeof source}.`
1153
- );
1154
- }
1155
- for (const element of source) {
1156
- if (item.when && !whenHolds(item.when, params, element)) continue;
1157
- if (item.guard !== void 0 && !evaluateGuard(item.guard, params, element, "declarative transaction guard"))
1158
- continue;
1159
- emit(item, element);
1160
- }
1161
- } else {
1162
- if (item.when && !whenHolds(item.when, params, void 0)) continue;
1163
- if (item.guard !== void 0 && !evaluateGuard(item.guard, params, void 0, "declarative transaction guard"))
1164
- continue;
1165
- emit(item, void 0);
1166
- }
1167
- }
1168
- return expanded;
1169
- }
1170
- function expandTransaction(spec, manifest, params) {
1171
- const expanded = expandTransactionItems(spec, manifest, params).map((e) => e.item);
1172
- const out = collapseSameKeyItems(
1173
- expanded
1174
- );
1175
- if (out.length > MAX_TRANSACT_ITEMS) {
1176
- throw new Error(
1177
- `declarative transaction: expanded to ${out.length} items, exceeds the DynamoDB TransactWriteItems limit of ${MAX_TRANSACT_ITEMS}.`
1178
- );
1179
- }
1180
- return out;
1181
- }
1182
- async function executeDeclarativeTransaction(spec, manifest, params) {
1183
- const expanded = expandTransactionItems(spec, manifest, params);
1184
- if (expanded.length === 0) return;
1185
- const modelBySignature = /* @__PURE__ */ new Map();
1186
- for (const { item, model: model2 } of expanded) {
1187
- if (model2 !== void 0) {
1188
- modelBySignature.set(
1189
- execItemKeySignature(item),
1190
- model2
1191
- );
1192
- }
1193
- }
1194
- await commitTransaction(
1195
- expanded.map((e) => e.item),
1196
- {
1197
- modelBySignature,
1198
- limitError: (count2) => new Error(
1199
- `declarative transaction: expanded to ${count2} items, exceeds the DynamoDB TransactWriteItems limit of ${MAX_TRANSACT_ITEMS}.`
1200
- )
1201
- }
1202
- );
1203
- }
1204
-
1205
- // src/runtime/per-key-cursor.ts
1206
- import { canonicalValue } from "behavior-contracts";
1207
- function serializeContractKey(key2) {
1208
- return canonicalValue(toSharedValue(key2));
1209
- }
1210
- function encodePerKeyCursor(key2, inner) {
1211
- if (inner === null) return null;
1212
- const envelope = {
1213
- key: serializeContractKey(key2),
1214
- inner
1215
- };
1216
- return encodeCursor(envelope);
1217
- }
1218
- function decodePerKeyCursor(cursor, expectedKey) {
1219
- let envelope;
1220
- try {
1221
- envelope = decodeCursor(cursor);
1222
- } catch {
1223
- throw new Error(
1224
- `Invalid per-key cursor: the value passed as \`after\` is not a cursor minted by this runtime (it failed to decode).`
1225
- );
1226
- }
1227
- if (typeof envelope !== "object" || envelope === null || typeof envelope.key !== "string" || typeof envelope.inner !== "string") {
1228
- throw new Error(
1229
- `Invalid per-key cursor: the decoded envelope is missing its key / inner fields. A range cursor must be a per-key envelope minted by this runtime.`
1230
- );
1231
- }
1232
- const expected = serializeContractKey(expectedKey);
1233
- if (envelope.key !== expected) {
1234
- throw new Error(
1235
- `Per-key cursor mismatch: the supplied \`after\` cursor belongs to key ${envelope.key}, but the method is being called for key ${expected}. A range cursor may only resume pagination of the same key it was issued for.`
1236
- );
1237
- }
1238
- return envelope.inner;
1239
- }
1240
-
1241
- // src/runtime/contract-runtime.ts
1242
- var CONTRACT_RANGE_FANOUT_CONCURRENCY = RELATION_TRAVERSAL_CONCURRENCY;
1243
- function resolveQueryMethod(contract, methodName) {
1244
- const method = contract.methods[methodName];
1245
- if (method === void 0) {
1246
- throw new Error(
1247
- `Contract runtime: query method '${methodName}' does not exist on this contract. Available methods: ${Object.keys(contract.methods).sort().join(", ") || "(none)"}.`
1248
- );
1249
- }
1250
- return method;
1251
- }
1252
- function modelClassOf(op) {
1253
- return op.entity.modelClass;
1254
- }
1255
- function selectOf(op) {
1256
- return op.select ?? {};
1257
- }
1258
- async function executePointSingle(op, key2, params) {
1259
- return executeQuery(modelClassOf(op), key2, selectOf(op), {
1260
- consistentRead: params.consistentRead
1261
- });
1262
- }
1263
- async function executeRangeSingle(op, key2, params) {
1264
- const innerAfter = params.after !== void 0 ? decodePerKeyCursor(params.after, key2) : void 0;
1265
- const result = await executeListInternal(modelClassOf(op), key2, selectOf(op), {
1266
- limit: params.limit,
1267
- after: innerAfter,
1268
- order: params.order
1269
- });
1270
- return {
1271
- items: result.items,
1272
- cursor: encodePerKeyCursor(key2, result.cursor)
1273
- };
1274
- }
1275
- async function executePointBatch(op, keys, _params) {
1276
- return executeKeyedBatchGet(modelClassOf(op), keys, selectOf(op));
1277
- }
1278
- async function executeKeyedBatchGet(modelClass, keys, select) {
1279
- const metadata = MetadataRegistry.get(modelClass);
1280
- const tableName = TableMapping.resolve(metadata.tableName);
1281
- const resolved = keys.map((key2) => {
1282
- const dynamoKey = buildDeleteInput(modelClass, key2).Key;
1283
- return { id: serializeContractKey(key2), dynamoKey, marker: dynamoKeyMarker(dynamoKey) };
1284
- });
1285
- const uniqueByMarker = /* @__PURE__ */ new Map();
1286
- for (const r of resolved) {
1287
- if (!uniqueByMarker.has(r.marker)) uniqueByMarker.set(r.marker, r.dynamoKey);
1288
- }
1289
- const rawByMarker = /* @__PURE__ */ new Map();
1290
- const uniqueKeys = [...uniqueByMarker.values()];
1291
- if (uniqueKeys.length > 0) {
1292
- const { items } = await ClientManager.getExecutor().batchGet({
1293
- tableName,
1294
- keys: uniqueKeys
1295
- });
1296
- for (const raw of items) {
1297
- rawByMarker.set(dynamoKeyMarker(raw), raw);
1298
- }
1299
- }
1300
- const out = /* @__PURE__ */ new Map();
1301
- for (const r of resolved) {
1302
- if (out.has(r.id)) continue;
1303
- const raw = rawByMarker.get(r.marker);
1304
- out.set(r.id, raw !== void 0 ? hydrateOne(raw, select, metadata) : null);
1305
- }
1306
- return out;
1307
- }
1308
- function dynamoKeyMarker(keyOrItem) {
1309
- return `${String(keyOrItem.PK)}::${String(keyOrItem.SK ?? "")}`;
1310
- }
1311
- function hydrateOne(raw, select, metadata) {
1312
- return hydrate([raw], select, metadata)[0];
1313
- }
1314
- async function executeQueryMethod(contract, methodName, keyOrKeys, params = {}) {
1315
- const method = resolveQueryMethod(contract, methodName);
1316
- if (Array.isArray(keyOrKeys)) {
1317
- if (method.inputArity === "single") {
1318
- const kind = method.resolution === "range" ? "range" : "unique-GSI point";
1319
- throw new Error(
1320
- `Contract runtime: ${kind} method '${methodName}' was called with an array of keys, but its inputArity is 'single'. A ${kind} resolution is one ${method.resolution === "range" ? "partition" : "GSI"} Query per key, so an array would be an N+1 fan-out \u2014 forbidden by the contract's N+1 rule. Loop in application code if you genuinely need N independent reads.`
1321
- );
1322
- }
1323
- const keyed = await executePointBatch(method.op, keyOrKeys, params);
1324
- await resolveCompositions(method.op, [...keyed.values()].filter(isRecord));
1325
- return keyed;
1326
- }
1327
- const key2 = keyOrKeys;
1328
- if (method.resolution === "point") {
1329
- const item = await executePointSingle(method.op, key2, params);
1330
- if (item !== null) await resolveCompositions(method.op, [item]);
1331
- return item;
1332
- }
1333
- const connection = await executeRangeSingle(method.op, key2, params);
1334
- await resolveCompositions(method.op, connection.items.filter(isRecord));
1335
- return connection;
1336
- }
1337
- function isRecord(value) {
1338
- return typeof value === "object" && value !== null;
1339
- }
1340
- async function resolveCompositions(op, parents, plan2) {
1341
- const compose = op.compose;
1342
- if (compose === void 0 || compose.length === 0 || parents.length === 0) return;
1343
- const resolved = plan2 ?? deriveCompositionPlan(compose.length);
1344
- for (const stage of resolved.stages) {
1345
- await mapWithConcurrency(
1346
- stage.map((i) => compose[i]),
1347
- resolved.concurrency,
1348
- (edge) => resolveComposition(edge, parents)
1349
- );
1350
- }
1351
- }
1352
- async function resolveComposition(edge, parents) {
1353
- const childMethod = edge.method;
1354
- if (childMethod.resolution !== "point") {
1355
- throw new Error(
1356
- `Contract runtime: composed child '${edge.as}' resolves to a 'range' method, but an External Query child must be 'point' so it coalesces to one BatchGetItem across all parent records. A range child is an N+1 fan-out (it is rejected at build time by the N+1 checker).`
1357
- );
1358
- }
1359
- const childOp = childMethod.op;
1360
- const childSelect = selectOf(childOp);
1361
- const bindFields = Object.keys(edge.bind);
1362
- const bound = [];
1363
- const childKeys = [];
1364
- for (const parent of parents) {
1365
- const childKey = {};
1366
- let complete = true;
1367
- for (const field2 of bindFields) {
1368
- const value = readFromPath(parent, edge.bind[field2].path);
1369
- if (value === void 0 || value === null) {
1370
- complete = false;
1371
- break;
1372
- }
1373
- childKey[field2] = value;
1374
- }
1375
- if (!complete) {
1376
- parent[edge.as] = null;
1377
- continue;
1378
- }
1379
- bound.push({ parent, childKey, keyId: serializeContractKey(childKey) });
1380
- childKeys.push(childKey);
1381
- }
1382
- if (childKeys.length === 0) return;
1383
- const childResults = await executeKeyedBatchGet(
1384
- modelClassOf(childOp),
1385
- childKeys,
1386
- childSelect
1387
- );
1388
- for (const { parent, keyId } of bound) {
1389
- const child = childResults.get(keyId);
1390
- parent[edge.as] = child ?? null;
1391
- }
1392
- }
1393
- function readFromPath(record, path) {
1394
- if (path === "$") return record;
1395
- const segments = path.slice(2).split(".");
1396
- let current = record;
1397
- for (const segment of segments) {
1398
- if (typeof current !== "object" || current === null) return void 0;
1399
- current = current[segment];
1400
- }
1401
- return current;
1402
- }
1403
- async function executeRangeFanout(op, keys, params = {}) {
1404
- const connections = await mapWithConcurrency(
1405
- keys,
1406
- CONTRACT_RANGE_FANOUT_CONCURRENCY,
1407
- (key2) => executeRangeSingle(op, key2, params)
1408
- );
1409
- const out = /* @__PURE__ */ new Map();
1410
- keys.forEach((key2, i) => {
1411
- out.set(serializeContractKey(key2), connections[i]);
1412
- });
1413
- return out;
1414
- }
1415
-
1416
- // src/runtime/prepared-loader.ts
1417
- import { validateEnvelope, EnvelopeFailure } from "behavior-contracts";
1418
- var LOAD_LABEL = "graphddb.loadPreparedPlan";
1419
- function slotOfBind(bind) {
1420
- if ("param" in bind) return { kind: "param", name: bind.param };
1421
- if ("literal" in bind) return { kind: "literal", value: bind.literal };
1422
- if ("literalDate" in bind) return { kind: "literal", value: new Date(bind.literalDate) };
1423
- return { kind: "literal", value: BigInt(bind.literalBigInt) };
1424
- }
1425
- function slotsOfBindMap(map2) {
1426
- const out = {};
1427
- for (const [field2, bind] of Object.entries(map2 ?? {})) out[field2] = slotOfBind(bind);
1428
- return out;
1429
- }
1430
- function bindValues(slots, params) {
1431
- const out = {};
1432
- for (const [field2, slot] of Object.entries(slots)) {
1433
- out[field2] = slot.kind === "param" ? params[slot.name] : slot.value;
1434
- }
1435
- return out;
1436
- }
1437
- function resolveModelByName(name, planId) {
1438
- for (const modelClass of MetadataRegistry.getAll().keys()) {
1439
- if (modelClass.name === name) return modelClass;
1440
- }
1441
- throw new Error(
1442
- `${LOAD_LABEL}: static plan '${planId}' targets entity '${name}', which is not a registered model in this process. Ensure the model module is imported before the plan executes, or regenerate the artifact (graphddb transform prepared --aot).`
1443
- );
1444
- }
1445
- function bindEntities(plan2, planId) {
1446
- const classes = /* @__PURE__ */ new Map();
1447
- const entities = {};
1448
- const tables = {};
1449
- for (const [name, expected] of Object.entries(plan2.entities)) {
1450
- const modelClass = resolveModelByName(name, planId);
1451
- const metadata = MetadataRegistry.get(modelClass);
1452
- const actual = entityFingerprint(metadata);
1453
- if (actual !== expected) {
1454
- throw new Error(
1455
- `${LOAD_LABEL}: static plan '${planId}' is STALE \u2014 entity '${name}' has changed since the plan artifact was generated (declaration fingerprint mismatch: expected ${expected}, live ${actual}). A stale static plan never executes. Regenerate the artifact: \`graphddb transform prepared --aot <artifact> --write\` (CI: run the check mode to catch this at build time).`
1456
- );
1457
- }
1458
- classes.set(name, modelClass);
1459
- const entity = buildManifestEntity(metadata);
1460
- entities[name] = entity;
1461
- tables[metadata.tableName] = { physicalName: TableMapping.resolve(metadata.tableName) };
1462
- }
1463
- return { classes, manifest: { version: SPEC_VERSION, tables, entities } };
1464
- }
1465
- function renderSpecItem(transaction, index, manifest, params) {
1466
- const single = {
1467
- params: transaction.params,
1468
- items: [transaction.items[index]]
1469
- };
1470
- return expandTransactionItems(single, manifest, params);
1471
- }
1472
- function renderEffectsForOp(op, boundParams) {
1473
- const rendered = serializeEffectParams([op.modelClass], boundParams);
1474
- const effects = {};
1475
- const checks = [];
1476
- const modelBySignature = /* @__PURE__ */ new Map();
1477
- const { transaction, categories } = op.spec;
1478
- for (let i = 0; i < transaction.items.length; i++) {
1479
- const category = categories[i];
1480
- if (category === "base") continue;
1481
- for (const out of renderSpecItem(transaction, i, op.manifest, rendered)) {
1482
- const item = out.item;
1483
- if (category === "check") {
1484
- if (item.ConditionCheck !== void 0) checks.push(item.ConditionCheck);
1485
- continue;
1486
- }
1487
- const family = category;
1488
- if (!LOGICAL_EFFECT_CATEGORIES.includes(family)) {
1489
- throw new Error(
1490
- `${LOAD_LABEL}: unknown effect category '${String(category)}' in a static plan (artifact format skew \u2014 regenerate the artifact).`
1491
- );
1492
- }
1493
- (effects[family] ??= []).push(item);
1494
- if (out.model !== void 0) {
1495
- modelBySignature.set(execItemKeySignature(item), out.model);
1496
- }
1497
- }
1498
- }
1499
- return { effects, checks, modelBySignature };
1500
- }
1501
- var INTENT_TO_KIND = { create: "put", update: "update", remove: "delete" };
1502
- var AotPreparedWriteStatement = class {
1503
- /** @internal */
1504
- constructor(ops) {
1505
- this.ops = ops;
1506
- }
1507
- ops;
1508
- async execute(params = {}, options = {}) {
1509
- const logical = this.ops.map((op) => {
1510
- const bound = {
1511
- ...bindValues(op.inputSlots, params),
1512
- ...bindValues(op.keySlots, params)
1513
- };
1514
- const condition = op.conditionSlots !== void 0 ? bindValues(op.conditionSlots, params) : void 0;
1515
- return {
1516
- kind: INTENT_TO_KIND[op.spec.intent],
1517
- modelClass: op.modelClass,
1518
- modelStatic: op.modelStatic,
1519
- keyFields: op.spec.keyFields,
1520
- params: bound,
1521
- // A `create` guards with attribute_not_exists — the same condition the
1522
- // compiled base op carries; an explicit gate on a create was rejected at
1523
- // BUILD time (mirroring the compiled core's `withCondition` reject).
1524
- ...op.spec.intent === "create" ? { condition: { notExists: true } } : condition !== void 0 ? { condition } : {},
1525
- ...op.spec.result !== void 0 ? { result: op.spec.result } : {},
1526
- renderEffects: (postW1) => renderEffectsForOp(op, postW1)
1527
- };
1528
- });
1529
- const mode = options.mode ?? "transaction";
1530
- const runOpts = {
1531
- label: LOAD_LABEL,
1532
- ...options.retry !== void 0 ? { retry: options.retry } : {},
1533
- ...options.context !== void 0 ? { context: options.context } : {}
1534
- };
1535
- if (mode === "transaction") {
1536
- const { readBacks } = await executeLogicalWriteOps(logical, runOpts);
1537
- const out2 = {};
1538
- this.ops.forEach((op, i) => {
1539
- if (op.spec.result !== void 0) out2[op.alias] = readBacks[i];
1540
- });
1541
- return out2;
1542
- }
1543
- const settled = await executeLogicalParallelWrites(logical, runOpts);
1544
- const out = {};
1545
- this.ops.forEach((op, i) => {
1546
- const res = settled[i];
1547
- out[op.alias] = res.ok ? { ok: true, value: op.spec.result !== void 0 ? res.value : void 0 } : { ok: false, error: res.error };
1548
- });
1549
- return out;
1550
- }
1551
- };
1552
- function bindReadPlan(plan2, planId) {
1553
- const { classes } = bindEntities(plan2, planId);
1554
- const routes = (plan2.reads ?? []).map((r) => {
1555
- const modelClass = classes.get(r.entity);
1556
- if (modelClass === void 0) {
1557
- throw new Error(
1558
- `${LOAD_LABEL}: static plan '${planId}' route '${r.alias}' targets entity '${r.entity}', which the plan does not fingerprint (artifact skew \u2014 regenerate).`
1559
- );
1560
- }
1561
- const consistentReadSlot = r.consistentRead !== void 0 ? slotOfBind(r.consistentRead) : void 0;
1562
- const limitSlot = r.limit !== void 0 ? slotOfBind(r.limit) : void 0;
1563
- const afterSlot = r.after !== void 0 ? slotOfBind(r.after) : void 0;
1564
- return {
1565
- alias: r.alias,
1566
- kind: r.op,
1567
- modelClass,
1568
- select: r.select,
1569
- keySlots: slotsOfBindMap(r.key),
1570
- ...r.maxDepth !== void 0 ? { maxDepth: r.maxDepth } : {},
1571
- ...r.order !== void 0 ? { order: r.order } : {},
1572
- ...r.filter !== void 0 ? { filter: r.filter } : {},
1573
- ...consistentReadSlot !== void 0 ? { consistentReadSlot } : {},
1574
- ...limitSlot !== void 0 ? { limitSlot } : {},
1575
- ...afterSlot !== void 0 ? { afterSlot } : {}
1576
- };
1577
- });
1578
- return new PreparedReadStatement(routes);
1579
- }
1580
- function bindWritePlan(plan2, planId) {
1581
- const { classes, manifest } = bindEntities(plan2, planId);
1582
- const ops = (plan2.writes ?? []).map((w) => {
1583
- const modelClass = classes.get(w.entity);
1584
- if (modelClass === void 0) {
1585
- throw new Error(
1586
- `${LOAD_LABEL}: static plan '${planId}' op '${w.alias}' targets entity '${w.entity}', which the plan does not fingerprint (artifact skew \u2014 regenerate).`
1587
- );
1588
- }
1589
- const modelStatic = modelClass.asModel();
1590
- return {
1591
- alias: w.alias,
1592
- spec: w,
1593
- modelClass,
1594
- modelStatic,
1595
- keySlots: slotsOfBindMap(w.key),
1596
- inputSlots: slotsOfBindMap(w.input),
1597
- ...w.condition !== void 0 ? { conditionSlots: slotsOfBindMap(w.condition) } : {},
1598
- manifest
1599
- };
1600
- });
1601
- return new AotPreparedWriteStatement(ops);
1602
- }
1603
- function bindPlan(doc, planId) {
1604
- if (doc === null || typeof doc !== "object" || doc.plans === void 0) {
1605
- throw new Error(`${LOAD_LABEL}: not a prepared-plan document.`);
1606
- }
1607
- if (doc.formatVersion !== PREPARED_FORMAT_VERSION) {
1608
- if (doc.formatVersion === "1") {
1609
- throw new Error(
1610
- `${LOAD_LABEL}: this artifact uses prepared-plan format version '1' (generated by graphddb <= 0.7.2). This runtime speaks format '${PREPARED_FORMAT_VERSION}' (the entity fingerprints changed algorithm), so the artifact must be regenerated with the transform: \`graphddb transform prepared --aot <artifact> --write\`.`
1611
- );
1612
- }
1613
- throw new Error(
1614
- `${LOAD_LABEL}: unsupported artifact format version '${String(doc.formatVersion)}' (this runtime supports '${PREPARED_FORMAT_VERSION}'). Regenerate the artifact: \`graphddb transform prepared --aot <artifact> --write\`.`
1615
- );
1616
- }
1617
- try {
1618
- validateEnvelope(doc, SPEC_VERSION_SUPPORTED, { field: "specVersion", label: LOAD_LABEL });
1619
- } catch (exc) {
1620
- if (exc instanceof EnvelopeFailure) {
1621
- throw new Error(
1622
- `${LOAD_LABEL}: the artifact was compiled against operation-IR version '${String(doc.specVersion)}' but this runtime speaks '${SPEC_VERSION_SUPPORTED}'. A version-skewed static plan never executes \u2014 regenerate the artifact.`
1623
- );
1624
- }
1625
- throw exc;
1626
- }
1627
- const plan2 = doc.plans[planId];
1628
- if (plan2 === void 0) {
1629
- const available = Object.keys(doc.plans).sort().slice(0, 8).join(", ");
1630
- throw new Error(
1631
- `${LOAD_LABEL}: the artifact carries no plan '${planId}' (available: ${available}${Object.keys(doc.plans).length > 8 ? ", \u2026" : ""}). The source and the artifact have drifted \u2014 regenerate: \`graphddb transform prepared --aot <artifact> --write\`.`
1632
- );
1633
- }
1634
- return plan2.kind === "read" ? bindReadPlan(plan2, planId) : bindWritePlan(plan2, planId);
1635
- }
1636
- var BOUND = /* @__PURE__ */ new WeakMap();
1637
- var LazyAotHandle = class {
1638
- constructor(doc, planId) {
1639
- this.doc = doc;
1640
- this.planId = planId;
1641
- }
1642
- doc;
1643
- planId;
1644
- bound;
1645
- resolve() {
1646
- if (this.bound !== void 0) return this.bound;
1647
- let perDoc = BOUND.get(this.doc);
1648
- if (perDoc === void 0) {
1649
- perDoc = /* @__PURE__ */ new Map();
1650
- BOUND.set(this.doc, perDoc);
1651
- }
1652
- let handle = perDoc.get(this.planId);
1653
- if (handle === void 0) {
1654
- handle = bindPlan(this.doc, this.planId);
1655
- perDoc.set(this.planId, handle);
1656
- }
1657
- this.bound = handle;
1658
- return handle;
1659
- }
1660
- async execute(params, options) {
1661
- const target = this.resolve();
1662
- return target.execute(params, options);
1663
- }
1664
- };
1665
- function loadPreparedPlan(doc, planId, body) {
1666
- void body;
1667
- return new LazyAotHandle(doc, planId);
1668
- }
1669
-
1670
- // src/relation/maintenance-rebuild.ts
1671
- function toClass(m) {
1672
- try {
1673
- return resolveModelClass(m);
1674
- } catch {
1675
- return m;
1676
- }
1677
- }
1678
- var MaintenanceRebuilder = class {
1679
- graph;
1680
- constructor(opts = {}) {
1681
- const classes = (opts.models ?? []).map(toClass);
1682
- const explicitViews = opts.views;
1683
- const allClasses = [
1684
- ...classes,
1685
- ...(explicitViews ?? []).map((v) => v.viewClass)
1686
- ];
1687
- const scoped = allClasses.length > 0 ? new Map(allClasses.map((c) => [c, MetadataRegistry.get(c)])) : void 0;
1688
- this.graph = buildMaintenanceGraph(scoped, explicitViews);
1689
- }
1690
- /**
1691
- * Re-derive the materialized value for one owner row × maintainer from the source
1692
- * rows and report whether it differs from what the owner row currently stores. Does
1693
- * NOT write anything (a read-only drift probe).
1694
- *
1695
- * @param owner The owner model class declaring the maintainer.
1696
- * @param relationProperty The relation / `@aggregate` property the value lives under.
1697
- * @param ownerKeyInput The owner row's key input (e.g. `{ threadId: 't1' }`).
1698
- */
1699
- async detectDrift(owner, relationProperty, ownerKeyInput) {
1700
- const { item, ownerMeta, ownerKey } = this.resolve(owner, relationProperty);
1701
- const expected = await this.recompute(item, ownerMeta, ownerKeyInput);
1702
- const ownerRow = await this.readOwnerRow(ownerMeta, ownerKey(ownerKeyInput));
1703
- const actual = this.actualValue(item.effect, ownerRow);
1704
- const ownerAbsentAndEmpty = ownerRow === void 0 && isEmptyMaintained(item.effect, expected);
1705
- return {
1706
- relationProperty,
1707
- ownerEntity: item.ownerEntity,
1708
- ownerKey: ownerKey(ownerKeyInput),
1709
- kind: item.effect.kind,
1710
- drifted: !ownerAbsentAndEmpty && !deepEqual(normalize(expected), normalize(actual)),
1711
- expected,
1712
- actual
1713
- };
1714
- }
1715
- /**
1716
- * Re-derive the materialized value for one owner row × maintainer from the source
1717
- * rows and WRITE the absolute re-derived value onto the owner row when it drifts (or
1718
- * always, when `force`). Returns the drift verdict plus whether a repair write ran.
1719
- *
1720
- * @param options.force Write even when no drift is detected (default: write only on drift).
1721
- */
1722
- async rebuild(owner, relationProperty, ownerKeyInput, options = {}) {
1723
- const report = await this.detectDrift(owner, relationProperty, ownerKeyInput);
1724
- if (!report.drifted && options.force !== true) {
1725
- return { ...report, repaired: false };
1726
- }
1727
- const { item, ownerMeta } = this.resolve(owner, relationProperty);
1728
- await this.writeValue(item.effect, ownerMeta, report.ownerKey, report.expected);
1729
- return { ...report, repaired: true };
1730
- }
1731
- /** Resolve an owner model + property to its {@link MaintainItem} + owner-key builder. */
1732
- resolve(owner, relationProperty) {
1733
- const ownerClass = toClass(owner);
1734
- const ownerMeta = MetadataRegistry.get(ownerClass);
1735
- const item = this.graph.items.find(
1736
- (i) => i.relationProperty === relationProperty && (i.ownerClass === ownerClass || i.ownerClass.name === ownerClass.name)
1737
- );
1738
- if (item === void 0) {
1739
- throw new Error(
1740
- `Maintenance rebuild: no maintainer found for property '${relationProperty}' on '${ownerClass.name}'. The rebuilder repairs declared \`maintainedOn\` maintainers (snapshot / collection / counter); '${relationProperty}' declares none, or its model is not in this rebuilder's scope.`
1741
- );
1742
- }
1743
- const ownerKey = (input) => {
1744
- const { Key } = buildDeleteInput(
1745
- ownerClass,
1746
- input
1747
- );
1748
- return Key;
1749
- };
1750
- return { item, ownerMeta, ownerKey };
1751
- }
1752
- /**
1753
- * Scan every source row bound to this owner row and re-derive the maintained value:
1754
- *
1755
- * - **collection** — project each source row, order + trim by the SAME read-side
1756
- * rules (`orderBy` / `orderDir` / `maxItems`) the drain uses, return the list;
1757
- * - **snapshot** — project the single bound source row (or `undefined` when none);
1758
- * - **counter `count`** — the COUNT of source rows (absolute, not an ADD);
1759
- * - **counter `max`** — the maximum source value of the tracked field.
1760
- */
1761
- async recompute(item, _ownerMeta, ownerKeyInput) {
1762
- const effect = item.effect;
1763
- if (effect.kind === "membership") {
1764
- throw new Error(
1765
- `Maintenance rebuild: sparse-view membership maintainer '${item.relationProperty}' on '${item.ownerEntity}' is not yet supported by the rebuild planner (#133 follow-up). A membership row appears/disappears per source row by predicate; rebuild it by re-running the source events through the drain.`
1766
- );
1767
- }
1768
- const sources = await this.scanSourceRows(item, ownerKeyInput);
1769
- if (effect.kind === "collection") {
1770
- return this.recomputeCollection(effect, sources);
1771
- }
1772
- if (effect.kind === "snapshot") {
1773
- return this.recomputeSnapshot(effect, sources);
1774
- }
1775
- return this.recomputeCounter(effect, sources);
1776
- }
1777
- /**
1778
- * The source rows bound to one owner row. The maintenance key binding is
1779
- * `{ ownerField: '$.entity.<sourceField>' }` (the destination owner-key field ← the
1780
- * source row's field). INVERTED for a rebuild: the owner row supplies the value, and
1781
- * the source partition is queried for rows whose `<sourceField>` matches — exactly the
1782
- * set the live `hasMany` / `@aggregate` read aggregates. Queries the source via the
1783
- * same {@link plan} the read planner uses (a partition Query), paginating fully.
1784
- */
1785
- async scanSourceRows(item, ownerKeyInput) {
1786
- const sourceMeta = MetadataRegistry.get(item.sourceClass);
1787
- const queryKey = {};
1788
- for (const [ownerField, srcPath] of Object.entries(item.effect.keys)) {
1789
- const sourceField = pathField(srcPath);
1790
- queryKey[sourceField] = ownerKeyInput[ownerField];
1791
- }
1792
- const executor = ClientManager.getExecutor();
1793
- const rows = [];
1794
- let after;
1795
- do {
1796
- const execPlan = plan(sourceMeta, {
1797
- key: queryKey,
1798
- // Select all declared source fields so every projection / value source path
1799
- // and the sort-key leaf is present on the scanned row.
1800
- select: selectAll(sourceMeta),
1801
- consistentRead: false,
1802
- ...after ? { after } : {}
1803
- });
1804
- const op = execPlan.operations[0];
1805
- const res = await executor.execute(op);
1806
- for (const it of res.items) rows.push(it);
1807
- after = res.lastEvaluatedKey;
1808
- } while (after !== void 0);
1809
- return rows;
1810
- }
1811
- /** Re-derive a bounded collection: project, then order + trim by the read-side rules. */
1812
- recomputeCollection(effect, sources) {
1813
- if (effect.collection.orderBy === void 0 && effect.collection.maxItems !== void 0) {
1814
- throw new Error(
1815
- `Maintenance rebuild: relation '${effect.collection.field}' maintains an UNORDERED bounded collection (\`maxItems: ${effect.collection.maxItems}\` with no \`read.order\`). Its trimmed contents depend on stream ARRIVAL order, which the source rows do not record, so the value cannot be faithfully rebuilt from source \u2014 a rebuild would keep a different subset than the drain converged to. Declare a \`read.order\` on the relation so the maintained collection orders by the source's sort-key field (then the rebuild and the drain agree), or rebuild it unbounded.`
1816
- );
1817
- }
1818
- const projected = sources.map((row) => projectFrom(effect.project, row));
1819
- return orderAndTrimCollection(projected, effect.collection);
1820
- }
1821
- /**
1822
- * Re-derive a single-row snapshot. A snapshot mirrors ONE source row (a `belongsTo` /
1823
- * `hasOne`, normally 1:1). In the unusual case where MORE THAN ONE source row binds, the
1824
- * live single-value read takes the FIRST row in the source's natural query order
1825
- * (`ScanIndexForward`, default ASC = the smallest sort-key leaf), so re-derive the same
1826
- * row — {@link pickFirst} — for a verdict consistent with the live read. Returns
1827
- * `undefined` when no source row is bound (the snapshot should be absent).
1828
- */
1829
- recomputeSnapshot(effect, sources) {
1830
- if (sources.length === 0) return void 0;
1831
- const chosen = sources.length === 1 ? sources[0] : pickFirst(sources, this.sourceMetaOf(effect));
1832
- return projectFrom(effect.project, chosen);
1833
- }
1834
- /** The source entity metadata a maintain effect projects from (its relation target). */
1835
- sourceMetaOf(effect) {
1836
- const item = this.graph.items.find((i) => i.effect === effect);
1837
- return item ? MetadataRegistry.get(item.sourceClass) : void 0;
1838
- }
1839
- /** Re-derive a counter: the absolute COUNT of source rows, or the MAX of the tracked field. */
1840
- recomputeCounter(effect, sources) {
1841
- if (effect.value.op === "count") {
1842
- return sources.length;
1843
- }
1844
- const field2 = effect.value.field;
1845
- let best;
1846
- for (const row of sources) {
1847
- const v = row[field2];
1848
- if (v === void 0 || v === null) continue;
1849
- if (best === void 0 || compareDesc(v, best) < 0) best = v;
1850
- }
1851
- return best;
1852
- }
1853
- /** Read the owner row (consistent) for the drift comparison / actual value. */
1854
- async readOwnerRow(ownerMeta, ownerKey) {
1855
- const res = await ClientManager.getExecutor().execute({
1856
- type: "GetItem",
1857
- tableName: TableMapping.resolve(ownerMeta.tableName),
1858
- keyCondition: ownerKey,
1859
- consistentRead: true
1860
- });
1861
- return res.items[0];
1862
- }
1863
- /** The value currently stored on the owner row for this maintainer. */
1864
- actualValue(effect, ownerRow) {
1865
- if (ownerRow === void 0) return void 0;
1866
- if (effect.kind === "collection") {
1867
- return ownerRow[effect.collection.field];
1868
- }
1869
- if (effect.kind === "counter") {
1870
- return ownerRow[effect.attribute];
1871
- }
1872
- const out = {};
1873
- let anyPresent = false;
1874
- for (const attr of Object.keys(effect.project)) {
1875
- if (Object.prototype.hasOwnProperty.call(ownerRow, attr)) {
1876
- out[attr] = ownerRow[attr];
1877
- anyPresent = true;
1878
- }
1879
- }
1880
- return anyPresent ? out : void 0;
1881
- }
1882
- /** Write the re-derived ABSOLUTE value onto the owner row (the repair). */
1883
- async writeValue(effect, ownerMeta, ownerKey, expected) {
1884
- const TableName = TableMapping.resolve(ownerMeta.tableName);
1885
- const executor = ClientManager.getExecutor();
1886
- if (effect.kind === "collection") {
1887
- await executor.update({
1888
- TableName,
1889
- Key: ownerKey,
1890
- UpdateExpression: "SET #c = :list",
1891
- ExpressionAttributeNames: { "#c": effect.collection.field },
1892
- ExpressionAttributeValues: { ":list": expected ?? [] }
1893
- });
1894
- return;
1895
- }
1896
- if (effect.kind === "counter") {
1897
- if (expected === void 0 || expected === null) {
1898
- if (effect.value.op === "count") {
1899
- await executor.update({
1900
- TableName,
1901
- Key: ownerKey,
1902
- UpdateExpression: "SET #a = :v",
1903
- ExpressionAttributeNames: { "#a": effect.attribute },
1904
- ExpressionAttributeValues: { ":v": 0 }
1905
- });
1906
- } else {
1907
- await executor.update({
1908
- TableName,
1909
- Key: ownerKey,
1910
- UpdateExpression: "REMOVE #a",
1911
- ExpressionAttributeNames: { "#a": effect.attribute }
1912
- });
1913
- }
1914
- return;
1915
- }
1916
- await executor.update({
1917
- TableName,
1918
- Key: ownerKey,
1919
- UpdateExpression: "SET #a = :v",
1920
- ExpressionAttributeNames: { "#a": effect.attribute },
1921
- ExpressionAttributeValues: { ":v": expected }
1922
- });
1923
- return;
1924
- }
1925
- if (expected === void 0) {
1926
- const names2 = {};
1927
- const removes = [];
1928
- let i2 = 0;
1929
- for (const attr of Object.keys(effect.project)) {
1930
- names2[`#m${i2}`] = attr;
1931
- removes.push(`#m${i2}`);
1932
- i2 += 1;
1933
- }
1934
- if (removes.length === 0) return;
1935
- await executor.update({
1936
- TableName,
1937
- Key: ownerKey,
1938
- UpdateExpression: `REMOVE ${removes.join(", ")}`,
1939
- ExpressionAttributeNames: names2
1940
- });
1941
- return;
1942
- }
1943
- const projection = expected;
1944
- const names = {};
1945
- const values = {};
1946
- const sets = [];
1947
- let i = 0;
1948
- for (const [attr, value] of Object.entries(projection)) {
1949
- names[`#m${i}`] = attr;
1950
- values[`:m${i}`] = value;
1951
- sets.push(`#m${i} = :m${i}`);
1952
- i += 1;
1953
- }
1954
- if (sets.length === 0) return;
1955
- await executor.update({
1956
- TableName,
1957
- Key: ownerKey,
1958
- UpdateExpression: `SET ${sets.join(", ")}`,
1959
- ExpressionAttributeNames: names,
1960
- ExpressionAttributeValues: values
1961
- });
619
+ // src/index.ts
620
+ var graphddb = {
621
+ publishQuery,
622
+ publishCommand,
623
+ get query() {
624
+ return DDBModel.query;
625
+ },
626
+ get mutate() {
627
+ return DDBModel.mutate;
628
+ },
629
+ get prepare() {
630
+ return DDBModel.prepare;
631
+ },
632
+ get subscribe() {
633
+ return DDBModel.subscribe;
634
+ },
635
+ // ── Phase 4 S7 (#284): the host-runtime config surface. A LAZY getter for the
636
+ // same tree-shaking reason as the four execution verbs above — an eager
637
+ // `config: configImpl` reference would pin the `ClientManager` / executor /
638
+ // middleware impl graph into the size-limited core entry for a consumer that
639
+ // only imports `{ DDBModel }` and never touches `graphddb.config`. The getter
640
+ // defers the read so the config machinery is tree-shaken out of such a consumer
641
+ // and pulled in lazily by a real `graphddb.config.*` user. Do NOT revert this to
642
+ // an eager reference without re-checking `npm run size`.
643
+ get config() {
644
+ return config;
1962
645
  }
1963
646
  };
1964
- function selectAll(meta) {
1965
- const select = {};
1966
- for (const f of meta.fields) select[f.propertyName] = true;
1967
- return select;
1968
- }
1969
- function pickFirst(sources, sourceMeta) {
1970
- const skFields = sourceMeta?.primaryKey ? segmentFieldNames(sourceMeta.primaryKey.segmented.skSegments) : [];
1971
- const orderField = skFields[skFields.length - 1];
1972
- if (orderField === void 0) return sources[0];
1973
- return [...sources].sort((a, b) => compareDesc(b[orderField], a[orderField]))[0];
1974
- }
1975
- function normalize(value) {
1976
- if (value === void 0 || value === null) return void 0;
1977
- if (typeof value === "number") return value;
1978
- if (typeof value === "string" && /^-?\d+$/.test(value)) {
1979
- const n = Number(value);
1980
- if (Number.isSafeInteger(n)) return n;
1981
- }
1982
- if (Array.isArray(value)) return value.map(normalize);
1983
- if (typeof value === "object") {
1984
- const out = {};
1985
- for (const [k2, v] of Object.entries(value)) {
1986
- const nv = normalize(v);
1987
- if (nv === void 0) continue;
1988
- out[k2] = nv;
1989
- }
1990
- return out;
1991
- }
1992
- return value;
1993
- }
1994
- function isEmptyMaintained(effect, expected) {
1995
- if (effect.kind === "collection") {
1996
- return Array.isArray(expected) && expected.length === 0;
1997
- }
1998
- if (effect.kind === "counter") {
1999
- return effect.value.op === "count" ? expected === 0 : expected === void 0 || expected === null;
2000
- }
2001
- return expected === void 0;
2002
- }
2003
- function deepEqual(a, b) {
2004
- if (a === b) return true;
2005
- if (a === void 0 || b === void 0 || a === null || b === null) return a === b;
2006
- if (Array.isArray(a) && Array.isArray(b)) {
2007
- if (a.length !== b.length) return false;
2008
- return a.every((v, i) => deepEqual(v, b[i]));
2009
- }
2010
- if (typeof a === "object" && typeof b === "object") {
2011
- const ka = Object.keys(a);
2012
- const kb = Object.keys(b);
2013
- if (ka.length !== kb.length) return false;
2014
- return ka.every(
2015
- (k2) => deepEqual(
2016
- a[k2],
2017
- b[k2]
2018
- )
2019
- );
2020
- }
2021
- return false;
2022
- }
2023
- function createMaintenanceRebuilder(opts = {}) {
2024
- return new MaintenanceRebuilder(opts);
2025
- }
2026
-
2027
- // src/define/define.ts
2028
- function collectParams(structure, out = {}) {
2029
- if (structure === null || typeof structure !== "object") return out;
2030
- for (const [key2, value] of Object.entries(structure)) {
2031
- if (isParam(value)) {
2032
- out[key2] = descriptorFromParam(key2, value, out[key2]);
2033
- } else if (value !== null && typeof value === "object") {
2034
- collectParams(value, out);
2035
- }
2036
- }
2037
- return out;
2038
- }
2039
- function descriptorFromParam(name, value, existing) {
2040
- const descriptor = {
2041
- kind: value.kind,
2042
- ...value.literals !== void 0 ? { literals: value.literals } : {},
2043
- // Pure-documentation description (issue #154); carried through verbatim. Does
2044
- // not participate in the conflicting-kind check below (it is not part of the
2045
- // execution-relevant descriptor identity).
2046
- ...value.description !== void 0 ? { description: value.description } : {},
2047
- required: true
2048
- };
2049
- if (existing !== void 0 && existing.kind !== descriptor.kind) {
2050
- throw new Error(
2051
- `Parameter '${name}' is declared with conflicting types ('${existing.kind}' and '${descriptor.kind}'). A parameter name must have a single type across a definition.`
2052
- );
2053
- }
2054
- return descriptor;
2055
- }
2056
- function entityRef(model2) {
2057
- const modelClass = resolveModelClass(model2);
2058
- return { name: modelClass.name, modelClass };
2059
- }
2060
- function collectConditionParams(condition, out) {
2061
- if (condition === void 0) return;
2062
- if (isRawCondition(condition)) {
2063
- for (const { name, param: param2 } of collectRawConditionParams(condition)) {
2064
- out[name] = descriptorFromParam(name, param2, out[name]);
2065
- }
2066
- return;
2067
- }
2068
- const obj = condition;
2069
- if (obj.notExists === true) return;
2070
- if ("scpExpr" in obj) return;
2071
- if (typeof obj.attributeExists === "string") return;
2072
- if (typeof obj.attributeNotExists === "string") return;
2073
- collectConditionTreeParams(obj, out);
2074
- }
2075
- var SINGLE_OPERAND_OPS = /* @__PURE__ */ new Set([
2076
- "eq",
2077
- "ne",
2078
- "gt",
2079
- "ge",
2080
- "lt",
2081
- "le",
2082
- "beginsWith",
2083
- "contains",
2084
- "notContains",
2085
- "attributeType",
2086
- "size"
2087
- ]);
2088
- function collectConditionTreeParams(obj, out) {
2089
- for (const [key2, value] of Object.entries(obj)) {
2090
- if (value === void 0) continue;
2091
- if (key2 === "and" || key2 === "or") {
2092
- for (const sub of value) {
2093
- if (isRawCondition(sub)) {
2094
- for (const { name, param: param2 } of collectRawConditionParams(sub)) {
2095
- out[name] = descriptorFromParam(name, param2, out[name]);
2096
- }
2097
- continue;
2098
- }
2099
- collectConditionTreeParams(sub, out);
2100
- }
2101
- continue;
2102
- }
2103
- if (key2 === "not") {
2104
- if (isRawCondition(value)) {
2105
- for (const { name, param: param2 } of collectRawConditionParams(value)) {
2106
- out[name] = descriptorFromParam(name, param2, out[name]);
2107
- }
2108
- continue;
2109
- }
2110
- collectConditionTreeParams(value, out);
2111
- continue;
2112
- }
2113
- if (isParam(value)) {
2114
- out[key2] = descriptorFromParam(key2, value, out[key2]);
2115
- continue;
2116
- }
2117
- if (value === null || typeof value !== "object" || value instanceof Date) {
2118
- continue;
2119
- }
2120
- const ops = value;
2121
- for (const [op, opVal] of Object.entries(ops)) {
2122
- if (op === "between") {
2123
- const [lo, hi] = opVal;
2124
- if (isParam(lo)) {
2125
- const n = conditionParamName(key2, op, 0);
2126
- out[n] = descriptorFromParam(n, lo, out[n]);
2127
- }
2128
- if (isParam(hi)) {
2129
- const n = conditionParamName(key2, op, 1);
2130
- out[n] = descriptorFromParam(n, hi, out[n]);
2131
- }
2132
- } else if (op === "in") {
2133
- opVal.forEach((v, i) => {
2134
- if (isParam(v)) {
2135
- const n = conditionParamName(key2, op, i);
2136
- out[n] = descriptorFromParam(n, v, out[n]);
2137
- }
2138
- });
2139
- } else if (SINGLE_OPERAND_OPS.has(op) && isParam(opVal)) {
2140
- const n = conditionParamName(key2, op);
2141
- out[n] = descriptorFromParam(n, opVal, out[n]);
2142
- }
2143
- }
2144
- }
2145
- }
2146
- function makeDefinition(model2, operation, key2, select, changes, condition, description) {
2147
- const params = collectParams(key2);
2148
- if (changes !== void 0) collectParams(changes, params);
2149
- collectConditionParams(condition, params);
2150
- return {
2151
- __isOperationDefinition: true,
2152
- entity: entityRef(model2),
2153
- operation,
2154
- key: key2,
2155
- select,
2156
- changes,
2157
- ...condition !== void 0 ? { condition } : {},
2158
- ...description !== void 0 ? { description } : {},
2159
- params
2160
- };
2161
- }
2162
- function defineQuery(model2, key2, select, options) {
2163
- return makeDefinition(
2164
- model2,
2165
- "query",
2166
- key2,
2167
- select,
2168
- void 0,
2169
- void 0,
2170
- options?.description
2171
- );
2172
- }
2173
- function defineList(model2, key2, select, options) {
2174
- return makeDefinition(
2175
- model2,
2176
- "list",
2177
- key2,
2178
- select,
2179
- void 0,
2180
- void 0,
2181
- options?.description
2182
- );
2183
- }
2184
- function definePut(model2, item, options) {
2185
- return makeDefinition(
2186
- model2,
2187
- "put",
2188
- item,
2189
- void 0,
2190
- void 0,
2191
- options?.condition,
2192
- options?.description
2193
- );
2194
- }
2195
- function defineUpdate(model2, key2, changes, options) {
2196
- return makeDefinition(
2197
- model2,
2198
- "update",
2199
- key2,
2200
- void 0,
2201
- changes,
2202
- options?.condition,
2203
- options?.description
2204
- );
2205
- }
2206
- function defineDelete(model2, key2, options) {
2207
- return makeDefinition(
2208
- model2,
2209
- "delete",
2210
- key2,
2211
- void 0,
2212
- void 0,
2213
- options?.condition,
2214
- options?.description
2215
- );
2216
- }
2217
- function defineQueries(definitions) {
2218
- for (const [name, def] of Object.entries(definitions)) {
2219
- assertDefinition(name, def);
2220
- if (def.operation !== "query" && def.operation !== "list") {
2221
- throw new Error(
2222
- `defineQueries: '${name}' is a '${def.operation}' operation; only query/list definitions are allowed. Use defineCommands for writes.`
2223
- );
2224
- }
2225
- }
2226
- return definitions;
2227
- }
2228
- function defineCommands(definitions) {
2229
- for (const [name, def] of Object.entries(definitions)) {
2230
- assertDefinition(name, def);
2231
- if (def.operation !== "put" && def.operation !== "update" && def.operation !== "delete") {
2232
- throw new Error(
2233
- `defineCommands: '${name}' is a '${def.operation}' operation; only put/update/delete definitions are allowed. Use defineQueries for reads.`
2234
- );
2235
- }
2236
- }
2237
- return definitions;
2238
- }
2239
- function assertDefinition(name, def) {
2240
- if (def === null || typeof def !== "object" || def.__isOperationDefinition !== true) {
2241
- throw new Error(
2242
- `'${name}' is not a definition. Use defineQuery / defineList / definePut / defineUpdate / defineDelete to build entries.`
2243
- );
2244
- }
2245
- }
2246
647
  export {
2247
- AotPreparedWriteStatement,
2248
- BATCH_GET_MAX_KEYS,
2249
- BATCH_WRITE_MAX_ITEMS,
2250
- BatchGetResult,
2251
- CONTRACT_RANGE_FANOUT_CONCURRENCY,
2252
- CdcEmulator,
2253
- ClientManager,
2254
648
  DDBModel,
2255
- DEFAULT_MAX_ATTEMPTS,
2256
- DEFAULT_RETRY_POLICY,
2257
- DynamoExecutor,
2258
- EDGE_WRITES_MARKER,
2259
- ENTITY_WRITES_MARKER,
2260
- EXPR_VERSION,
2261
- LIFECYCLE_CONTRACT_MARKER,
2262
- Linter,
2263
- MAINT_OUTBOX_PK_PREFIX,
2264
- MAX_TRANSACT_ITEMS,
2265
- MaintenanceDrain,
2266
- MaintenanceRebuilder,
2267
- MetadataRegistry,
2268
- OLD_VALUE_NAMESPACE,
2269
- PREPARE_CACHE_MAX,
2270
- PreparedReadStatement,
2271
- PreparedWriteStatement,
2272
- RetryingExecutor,
2273
- SCP_LOWERED_MARKER,
2274
- SPEC_VERSION,
2275
- SPEC_VERSION_SCP,
2276
- TableMapping,
2277
- TransactionContext,
2278
649
  aggregate,
2279
- assertBundleSerializable,
2280
- assertContractBoundaries,
2281
- assertContractN1Safe,
2282
- assertJsonSerializable,
2283
- assertSupportedCondition,
2284
- attachModelClass,
2285
650
  belongsTo,
2286
651
  binary,
2287
652
  boolean,
2288
- buildBridgeBundle,
2289
- buildConditionExpression,
2290
- buildContexts,
2291
- buildContracts,
2292
- buildDeleteInput,
2293
- buildMaintenanceGraph,
2294
- buildManifest,
2295
- buildOperations,
2296
- buildProjection,
2297
- buildPutInput,
2298
- buildQuerySpec,
2299
- buildSubscribeHandler,
2300
- buildTransactionSpec,
2301
- buildTransactions,
2302
- buildUpdateExpression,
2303
- buildUpdateInput,
2304
- canonicalizeExpressionSpec,
2305
653
  cdcProjected,
2306
- collectContractBoundaryViolations,
2307
- collectContractN1Violations,
2308
- collectViewDefinitions,
2309
- compileFilterExpression,
2310
- compileFragment,
2311
- compileMutationPlan,
2312
- compileSingleFragmentPlan,
2313
654
  cond,
2314
- contractOfMethodSpec,
2315
655
  count,
2316
- createCdcEmulator,
2317
- createDefaultLinter,
2318
- createMaintenanceDrain,
2319
- createMaintenanceDrainHandler,
2320
- createMaintenanceRebuilder,
2321
656
  datetime,
2322
- decodeCursor,
2323
- decodePerKeyCursor,
2324
- defineCommands,
2325
- defineDelete,
2326
- defineList,
2327
- definePlan,
2328
- definePut,
2329
- defineQueries,
2330
- defineQuery,
2331
- defineScpTransaction,
2332
- defineTransaction,
2333
- defineTransactions,
2334
- defineUpdate,
2335
- deriveEdgeWriteItems,
2336
- deriveEdgeWriteItemsFor,
2337
- deriveModelEdgeWriteItems,
2338
- derivePrefix,
2339
- detectRelationFields,
2340
- edgeWrites,
2341
657
  embedded,
2342
- encodeCursor,
2343
- encodePerKeyCursor,
2344
658
  entityWrites,
2345
- evaluateFilter,
2346
- execute,
2347
- executeBatchGet,
2348
- executeBatchWrite,
2349
- executeCommandMethod,
2350
- executeDeclarativeTransaction,
2351
- executeDelete,
2352
- executeExplain,
2353
- executeKeyedBatchGet,
2354
- executeList,
2355
- executePut,
2356
- executeQuery,
2357
- executeQueryMethod,
2358
- executeRangeFanout,
2359
- executeTransaction,
2360
- executeUpdate,
2361
- expandTransaction,
2362
659
  field,
2363
- from,
2364
- getEdgeWrites,
2365
660
  getEntityWrites,
2366
- getImplicitKeyFields,
661
+ graphddb,
2367
662
  gsi,
2368
- gsiAmbiguityRule,
2369
663
  hasMany,
2370
664
  hasOne,
2371
- hydrate,
2372
665
  identity,
2373
- isColumn,
2374
- isCommandModelContract,
2375
- isCommandPlan,
2376
- isContractComposeNode,
2377
- isContractFromRef,
2378
- isContractKeyFieldRef,
2379
- isContractKeyRef,
2380
- isContractParamRef,
2381
- isEdgeWritesDefinition,
2382
- isEntityWritesDefinition,
2383
- isKeySegment,
2384
- isLifecycleContract,
2385
- isMaintainTrigger,
2386
- isMutationFragment,
2387
- isMutationInputRef,
2388
- isParam,
2389
- isPlannedCommandMethod,
2390
- isPreparedParamRef,
2391
- isQueryModelContract,
2392
- isRetryableError,
2393
- isRetryableTransactionCancellation,
2394
- isSelectBuilder,
2395
- isTransactionRef,
2396
666
  k,
2397
667
  key,
2398
- lifecyclePhaseForIntent,
2399
668
  list,
2400
669
  literal,
2401
- loadPreparedPlan,
2402
- maintainTrigger,
2403
670
  maintainedFrom,
2404
671
  map,
2405
672
  max,
2406
- mintContractKeyFieldRef,
2407
- mintContractParamRef,
2408
- missingGsiRule,
2409
673
  model,
2410
- mutation,
2411
- noScanRule,
674
+ mutate,
2412
675
  number,
2413
676
  numberSet,
2414
- operationsContainScpNodes,
2415
- operationsSpecVersion,
2416
677
  param,
2417
- parseChange,
2418
- plan,
2419
- prepare,
2420
678
  preview,
2421
- publicCommandModel,
2422
- publicQueryModel,
2423
- query,
2424
- queryBoundaryRule,
2425
679
  refs,
2426
- relationDepthRule,
2427
- requireLimitRule,
2428
- resolveKey,
2429
- resolveLifecycle,
2430
- resolveRelations,
2431
- serializeContractKey,
2432
- serializeFieldValue,
2433
680
  string,
2434
681
  stringSet,
2435
682
  ttl,
2436
- validateDepth,
2437
- validateGsiAmbiguity,
2438
683
  when,
2439
- whenMember,
2440
- wholeKeysSentinel
684
+ whenMember
2441
685
  };