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.
- package/README.md +6 -6
- package/dist/cdc/index.d.ts +389 -5
- package/dist/cdc/index.js +4 -4
- package/dist/{chunk-AD6ZQTTE.js → chunk-GS4C5VGO.js} +2 -6
- package/dist/{chunk-DFUKGU2Q.js → chunk-HNY2EJPV.js} +216 -229
- package/dist/{chunk-EOJDN3SA.js → chunk-I4LEJ4TF.js} +3744 -6144
- package/dist/{chunk-3ZU2VW3L.js → chunk-L2NEDS7U.js} +582 -781
- package/dist/chunk-L4QRCHRQ.js +278 -0
- package/dist/chunk-LGHSZIEE.js +187 -0
- package/dist/chunk-N4NWYNGZ.js +1987 -0
- package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
- package/dist/cli.js +63 -254
- package/dist/index.d.ts +23 -1550
- package/dist/index.js +94 -1850
- package/dist/internal/index.d.ts +84 -0
- package/dist/internal/index.js +701 -0
- package/dist/{maintenance-view-adapter-BAZ9uBGe.d.ts → key-DR7_lpyk.d.ts} +504 -1910
- package/dist/linter/index.d.ts +39 -7
- package/dist/linter/index.js +22 -4
- package/dist/{registry-LWE54Sdc.d.ts → linter-C-vypgut.d.ts} +22 -22
- package/dist/prepared-artifact-BpPgkXEo.d.ts +281 -0
- package/dist/spec/index.d.ts +506 -5
- package/dist/spec/index.js +22 -18
- package/dist/testing/index.d.ts +2 -3
- package/dist/testing/index.js +4 -4
- package/dist/transform/index.d.ts +1 -1
- package/dist/transform/index.js +4 -4
- package/dist/{types-BQLzTEqh.d.ts → types-2PMXEn5x.d.ts} +8 -10
- package/dist/types-BXLzIcQD.d.ts +450 -0
- package/docs/cdc-projection.md +5 -5
- package/docs/class-hydration.md +1 -1
- package/docs/cqrs-contract.md +28 -20
- package/docs/design-patterns.md +5 -5
- package/docs/docs-generation.md +6 -6
- package/docs/middleware.md +15 -15
- package/docs/mutation-command-derivation.md +52 -42
- package/docs/prepared-statements.md +14 -14
- package/docs/python-bridge.md +96 -65
- package/docs/spec.md +153 -124
- package/docs/testing.md +9 -8
- package/package.json +14 -4
- package/dist/chunk-3UD3XIF2.js +0 -860
- package/dist/chunk-MMVHOUM4.js +0 -24
- package/dist/from-change-Ty95KA8C.d.ts +0 -327
- package/dist/index-Dc7d8mWI.d.ts +0 -1089
- 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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
165
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
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
|
|
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/
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|