graphddb 0.7.9 → 0.8.0

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