graphddb 0.1.0 → 0.2.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 +63 -9
- package/dist/{chunk-6LEHSX45.js → chunk-F27INYI2.js} +2839 -1081
- package/dist/cli.js +28 -5
- package/dist/index.d.ts +59 -3318
- package/dist/index.js +22 -1822
- package/dist/testing/index.d.ts +2 -2
- package/dist/types-D6qLpw2M.d.ts +4514 -0
- package/package.json +7 -2
- package/dist/types-CDrWiPxp.d.ts +0 -1203
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
BatchGetResult,
|
|
3
|
+
DDBModel,
|
|
2
4
|
EDGE_WRITES_MARKER,
|
|
3
5
|
ENTITY_WRITES_MARKER,
|
|
4
6
|
LIFECYCLE_CONTRACT_MARKER,
|
|
@@ -16,19 +18,19 @@ import {
|
|
|
16
18
|
buildContracts,
|
|
17
19
|
buildManifest,
|
|
18
20
|
buildOperations,
|
|
19
|
-
|
|
21
|
+
buildProjection,
|
|
20
22
|
buildQuerySpec,
|
|
21
|
-
buildRelation,
|
|
22
|
-
buildRelationExecutionPlan,
|
|
23
23
|
buildTransactionSpec,
|
|
24
24
|
buildTransactions,
|
|
25
25
|
collectContractBoundaryViolations,
|
|
26
26
|
collectContractN1Violations,
|
|
27
|
-
|
|
27
|
+
compileFilterExpression,
|
|
28
28
|
compileFragment,
|
|
29
29
|
compileMutationPlan,
|
|
30
30
|
compileSingleFragmentPlan,
|
|
31
|
+
cond,
|
|
31
32
|
contractOfMethodSpec,
|
|
33
|
+
decodeCursor,
|
|
32
34
|
definePlan,
|
|
33
35
|
defineTransaction,
|
|
34
36
|
defineTransactions,
|
|
@@ -38,12 +40,22 @@ import {
|
|
|
38
40
|
deriveModelEdgeWriteItems,
|
|
39
41
|
detectRelationFields,
|
|
40
42
|
edgeWrites,
|
|
43
|
+
encodeCursor,
|
|
41
44
|
entityWrites,
|
|
45
|
+
evaluateFilter,
|
|
46
|
+
execute,
|
|
47
|
+
executeBatchGet,
|
|
48
|
+
executeBatchWrite,
|
|
49
|
+
executeCommandMethod,
|
|
50
|
+
executeExplain,
|
|
51
|
+
executeList,
|
|
52
|
+
executeListInternal,
|
|
53
|
+
executeQuery,
|
|
42
54
|
from,
|
|
43
55
|
getEdgeWrites,
|
|
44
56
|
getEntityWrites,
|
|
45
57
|
getImplicitKeyFields,
|
|
46
|
-
|
|
58
|
+
hydrate,
|
|
47
59
|
isCommandModelContract,
|
|
48
60
|
isCommandPlan,
|
|
49
61
|
isContractComposeNode,
|
|
@@ -59,7 +71,6 @@ import {
|
|
|
59
71
|
isParam,
|
|
60
72
|
isPlannedCommandMethod,
|
|
61
73
|
isQueryModelContract,
|
|
62
|
-
isRecordingContractMethod,
|
|
63
74
|
isSelectBuilder,
|
|
64
75
|
isTransactionRef,
|
|
65
76
|
lifecyclePhaseForIntent,
|
|
@@ -67,20 +78,17 @@ import {
|
|
|
67
78
|
mintContractKeyFieldRef,
|
|
68
79
|
mintContractParamRef,
|
|
69
80
|
mutation,
|
|
70
|
-
normalizeSelectSpec,
|
|
71
|
-
normalizeTopLevelSelect,
|
|
72
81
|
param,
|
|
73
|
-
|
|
82
|
+
plan,
|
|
74
83
|
publicCommandModel,
|
|
75
84
|
publicQueryModel,
|
|
76
85
|
query,
|
|
77
|
-
range,
|
|
78
|
-
recordContractOp,
|
|
79
86
|
resolveLifecycle,
|
|
80
|
-
|
|
87
|
+
resolveRelations,
|
|
88
|
+
validateDepth,
|
|
81
89
|
when,
|
|
82
90
|
wholeKeysSentinel
|
|
83
|
-
} from "./chunk-
|
|
91
|
+
} from "./chunk-F27INYI2.js";
|
|
84
92
|
import {
|
|
85
93
|
CdcEmulator,
|
|
86
94
|
createCdcEmulator
|
|
@@ -88,7 +96,6 @@ import {
|
|
|
88
96
|
import {
|
|
89
97
|
BATCH_GET_MAX_KEYS,
|
|
90
98
|
BATCH_WRITE_MAX_ITEMS,
|
|
91
|
-
ChangeCaptureRegistry,
|
|
92
99
|
ClientManager,
|
|
93
100
|
DynamoExecutor,
|
|
94
101
|
Linter,
|
|
@@ -96,17 +103,14 @@ import {
|
|
|
96
103
|
MetadataRegistry,
|
|
97
104
|
TableMapping,
|
|
98
105
|
TransactionContext,
|
|
99
|
-
attachHiddenKey,
|
|
100
106
|
attachModelClass,
|
|
101
107
|
buildConditionExpression,
|
|
102
108
|
buildDeleteInput,
|
|
103
109
|
buildPutInput,
|
|
104
110
|
buildUpdateExpression,
|
|
105
111
|
buildUpdateInput,
|
|
106
|
-
captureWrite,
|
|
107
112
|
collapseSameKeyItems,
|
|
108
113
|
commitTransaction,
|
|
109
|
-
createColumnMap,
|
|
110
114
|
createDefaultLinter,
|
|
111
115
|
execItemKeySignature,
|
|
112
116
|
executeDelete,
|
|
@@ -125,7 +129,6 @@ import {
|
|
|
125
129
|
requireLimitRule,
|
|
126
130
|
resolveKey,
|
|
127
131
|
resolveModelClass,
|
|
128
|
-
resolveSegmentedKey,
|
|
129
132
|
serializeFieldValue
|
|
130
133
|
} from "./chunk-347U24SB.js";
|
|
131
134
|
|
|
@@ -335,1373 +338,6 @@ function hasOne(targetFactory, keyBinding) {
|
|
|
335
338
|
};
|
|
336
339
|
}
|
|
337
340
|
|
|
338
|
-
// src/planner/projection.ts
|
|
339
|
-
function buildProjection(select, additionalFields) {
|
|
340
|
-
const paths = [];
|
|
341
|
-
const names = {};
|
|
342
|
-
let nameCounter = 0;
|
|
343
|
-
function addPath(segments) {
|
|
344
|
-
const aliased = segments.map((seg) => {
|
|
345
|
-
const alias = `#p${nameCounter++}`;
|
|
346
|
-
names[alias] = seg;
|
|
347
|
-
return alias;
|
|
348
|
-
});
|
|
349
|
-
paths.push(aliased.join("."));
|
|
350
|
-
}
|
|
351
|
-
function traverse(selectObj, parentSegments) {
|
|
352
|
-
for (const [fieldName, value] of Object.entries(selectObj)) {
|
|
353
|
-
if (value === true) {
|
|
354
|
-
addPath([...parentSegments, fieldName]);
|
|
355
|
-
} else if (typeof value === "object" && value !== null) {
|
|
356
|
-
if ("select" in value || isSelectBuilder(value)) {
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
traverse(value, [...parentSegments, fieldName]);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
traverse(select, []);
|
|
364
|
-
if (additionalFields) {
|
|
365
|
-
for (const fieldName of additionalFields) {
|
|
366
|
-
addPath([fieldName]);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
if (paths.length === 0) {
|
|
370
|
-
return void 0;
|
|
371
|
-
}
|
|
372
|
-
return {
|
|
373
|
-
projectionExpression: paths.join(", "),
|
|
374
|
-
expressionAttributeNames: names
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// src/select/cond.ts
|
|
379
|
-
var RAW_COND = /* @__PURE__ */ Symbol.for("graphddb.rawCond");
|
|
380
|
-
function isRawCondition(value) {
|
|
381
|
-
return typeof value === "object" && value !== null && value[RAW_COND] === true;
|
|
382
|
-
}
|
|
383
|
-
function cond(template, ...parts) {
|
|
384
|
-
return {
|
|
385
|
-
[RAW_COND]: true,
|
|
386
|
-
template,
|
|
387
|
-
parts
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
function compileRawCondition(raw, allocName, allocValue) {
|
|
391
|
-
let out = raw.template[0];
|
|
392
|
-
for (let i = 0; i < raw.parts.length; i++) {
|
|
393
|
-
const part = raw.parts[i];
|
|
394
|
-
if (isColumn(part)) {
|
|
395
|
-
out += allocName(part.name);
|
|
396
|
-
} else {
|
|
397
|
-
out += allocValue(part);
|
|
398
|
-
}
|
|
399
|
-
out += raw.template[i + 1];
|
|
400
|
-
}
|
|
401
|
-
return out;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// src/expression/filter-expression.ts
|
|
405
|
-
var LOGICAL_KEYS = /* @__PURE__ */ new Set(["and", "or", "not"]);
|
|
406
|
-
var OPERATOR_KEYS = /* @__PURE__ */ new Set([
|
|
407
|
-
"eq",
|
|
408
|
-
"ne",
|
|
409
|
-
"gt",
|
|
410
|
-
"ge",
|
|
411
|
-
"lt",
|
|
412
|
-
"le",
|
|
413
|
-
"between",
|
|
414
|
-
"in",
|
|
415
|
-
"beginsWith",
|
|
416
|
-
"contains",
|
|
417
|
-
"notContains",
|
|
418
|
-
"attributeExists",
|
|
419
|
-
"attributeType",
|
|
420
|
-
"size"
|
|
421
|
-
]);
|
|
422
|
-
function nameAlias(ctx, column) {
|
|
423
|
-
for (const [alias2, col] of Object.entries(ctx.names)) {
|
|
424
|
-
if (col === column) return alias2;
|
|
425
|
-
}
|
|
426
|
-
const alias = `#f${ctx.nameCounter.n++}`;
|
|
427
|
-
ctx.names[alias] = column;
|
|
428
|
-
return alias;
|
|
429
|
-
}
|
|
430
|
-
function valueAlias(ctx, field2, raw) {
|
|
431
|
-
const alias = `:vf${ctx.valueCounter.n++}`;
|
|
432
|
-
const fieldMeta = ctx.fieldMap.get(field2);
|
|
433
|
-
ctx.values[alias] = fieldMeta ? serializeFieldValue(raw, fieldMeta) : raw;
|
|
434
|
-
return alias;
|
|
435
|
-
}
|
|
436
|
-
function compileField(ctx, field2, condition) {
|
|
437
|
-
const nAlias = nameAlias(ctx, field2);
|
|
438
|
-
if (!isOperatorObject(condition)) {
|
|
439
|
-
const vAlias = valueAlias(ctx, field2, condition);
|
|
440
|
-
return `${nAlias} = ${vAlias}`;
|
|
441
|
-
}
|
|
442
|
-
const ops = condition;
|
|
443
|
-
const clauses = [];
|
|
444
|
-
for (const [op, value] of Object.entries(ops)) {
|
|
445
|
-
switch (op) {
|
|
446
|
-
case "eq":
|
|
447
|
-
clauses.push(`${nAlias} = ${valueAlias(ctx, field2, value)}`);
|
|
448
|
-
break;
|
|
449
|
-
case "ne":
|
|
450
|
-
clauses.push(`${nAlias} <> ${valueAlias(ctx, field2, value)}`);
|
|
451
|
-
break;
|
|
452
|
-
case "gt":
|
|
453
|
-
clauses.push(`${nAlias} > ${valueAlias(ctx, field2, value)}`);
|
|
454
|
-
break;
|
|
455
|
-
case "ge":
|
|
456
|
-
clauses.push(`${nAlias} >= ${valueAlias(ctx, field2, value)}`);
|
|
457
|
-
break;
|
|
458
|
-
case "lt":
|
|
459
|
-
clauses.push(`${nAlias} < ${valueAlias(ctx, field2, value)}`);
|
|
460
|
-
break;
|
|
461
|
-
case "le":
|
|
462
|
-
clauses.push(`${nAlias} <= ${valueAlias(ctx, field2, value)}`);
|
|
463
|
-
break;
|
|
464
|
-
case "between": {
|
|
465
|
-
const [lo, hi] = value;
|
|
466
|
-
clauses.push(
|
|
467
|
-
`${nAlias} BETWEEN ${valueAlias(ctx, field2, lo)} AND ${valueAlias(ctx, field2, hi)}`
|
|
468
|
-
);
|
|
469
|
-
break;
|
|
470
|
-
}
|
|
471
|
-
case "in": {
|
|
472
|
-
const arr = value;
|
|
473
|
-
const aliases = arr.map((v) => valueAlias(ctx, field2, v));
|
|
474
|
-
clauses.push(`${nAlias} IN (${aliases.join(", ")})`);
|
|
475
|
-
break;
|
|
476
|
-
}
|
|
477
|
-
case "beginsWith":
|
|
478
|
-
clauses.push(`begins_with(${nAlias}, ${valueAlias(ctx, field2, value)})`);
|
|
479
|
-
break;
|
|
480
|
-
case "contains":
|
|
481
|
-
clauses.push(`contains(${nAlias}, ${valueAlias(ctx, field2, value)})`);
|
|
482
|
-
break;
|
|
483
|
-
case "notContains":
|
|
484
|
-
clauses.push(
|
|
485
|
-
`NOT contains(${nAlias}, ${valueAlias(ctx, field2, value)})`
|
|
486
|
-
);
|
|
487
|
-
break;
|
|
488
|
-
case "attributeExists":
|
|
489
|
-
clauses.push(
|
|
490
|
-
value === false ? `attribute_not_exists(${nAlias})` : `attribute_exists(${nAlias})`
|
|
491
|
-
);
|
|
492
|
-
break;
|
|
493
|
-
case "attributeType":
|
|
494
|
-
clauses.push(
|
|
495
|
-
`attribute_type(${nAlias}, ${valueAlias(ctx, field2, value)})`
|
|
496
|
-
);
|
|
497
|
-
break;
|
|
498
|
-
case "size":
|
|
499
|
-
clauses.push(`size(${nAlias}) = ${valueAlias(ctx, field2, value)}`);
|
|
500
|
-
break;
|
|
501
|
-
default:
|
|
502
|
-
throw new Error(`Unknown filter operator '${op}' on field '${field2}'`);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
return joinAnd(clauses);
|
|
506
|
-
}
|
|
507
|
-
function isOperatorObject(value) {
|
|
508
|
-
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || value instanceof Uint8Array || value instanceof Set) {
|
|
509
|
-
return false;
|
|
510
|
-
}
|
|
511
|
-
const keys = Object.keys(value);
|
|
512
|
-
if (keys.length === 0) return false;
|
|
513
|
-
return keys.every((k2) => OPERATOR_KEYS.has(k2));
|
|
514
|
-
}
|
|
515
|
-
function joinAnd(clauses) {
|
|
516
|
-
if (clauses.length === 1) return clauses[0];
|
|
517
|
-
return clauses.map((c) => wrap(c)).join(" AND ");
|
|
518
|
-
}
|
|
519
|
-
function wrap(expr) {
|
|
520
|
-
if (isAlreadyWrapped(expr)) return expr;
|
|
521
|
-
if (expr.includes(" AND ") || expr.includes(" OR ")) {
|
|
522
|
-
return `(${expr})`;
|
|
523
|
-
}
|
|
524
|
-
return expr;
|
|
525
|
-
}
|
|
526
|
-
function isAlreadyWrapped(expr) {
|
|
527
|
-
if (!expr.startsWith("(") || !expr.endsWith(")")) return false;
|
|
528
|
-
let depth = 0;
|
|
529
|
-
for (let i = 0; i < expr.length; i++) {
|
|
530
|
-
if (expr[i] === "(") depth++;
|
|
531
|
-
else if (expr[i] === ")") {
|
|
532
|
-
depth--;
|
|
533
|
-
if (depth === 0 && i < expr.length - 1) return false;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
return depth === 0;
|
|
537
|
-
}
|
|
538
|
-
function compileRaw(ctx, raw) {
|
|
539
|
-
return compileRawCondition(
|
|
540
|
-
raw,
|
|
541
|
-
(column) => nameAlias(ctx, column),
|
|
542
|
-
(value) => {
|
|
543
|
-
const alias = `:vf${ctx.valueCounter.n++}`;
|
|
544
|
-
ctx.values[alias] = value;
|
|
545
|
-
return alias;
|
|
546
|
-
}
|
|
547
|
-
);
|
|
548
|
-
}
|
|
549
|
-
function compileNode(ctx, node) {
|
|
550
|
-
if (isRawCondition(node)) {
|
|
551
|
-
return compileRaw(ctx, node);
|
|
552
|
-
}
|
|
553
|
-
const clauses = [];
|
|
554
|
-
for (const [key2, value] of Object.entries(node)) {
|
|
555
|
-
if (value === void 0) continue;
|
|
556
|
-
if (LOGICAL_KEYS.has(key2)) {
|
|
557
|
-
if (key2 === "and" || key2 === "or") {
|
|
558
|
-
const parts = value.map((sub) => compileNode(ctx, sub)).filter((s) => s.length > 0);
|
|
559
|
-
if (parts.length === 0) continue;
|
|
560
|
-
if (parts.length === 1) {
|
|
561
|
-
clauses.push(parts[0]);
|
|
562
|
-
} else {
|
|
563
|
-
const sep = key2 === "and" ? " AND " : " OR ";
|
|
564
|
-
const joined = parts.map((p) => wrap(p)).join(sep);
|
|
565
|
-
clauses.push(`(${joined})`);
|
|
566
|
-
}
|
|
567
|
-
} else {
|
|
568
|
-
const inner = compileNode(ctx, value);
|
|
569
|
-
if (inner.length > 0) clauses.push(`NOT ${wrap(inner)}`);
|
|
570
|
-
}
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
const clause = compileField(ctx, key2, value);
|
|
574
|
-
if (clause.length > 0) clauses.push(clause);
|
|
575
|
-
}
|
|
576
|
-
return joinAnd(clauses);
|
|
577
|
-
}
|
|
578
|
-
function compileFilterExpression(filter, metadata) {
|
|
579
|
-
if (!filter || Object.keys(filter).length === 0) {
|
|
580
|
-
return void 0;
|
|
581
|
-
}
|
|
582
|
-
const ctx = {
|
|
583
|
-
names: {},
|
|
584
|
-
values: {},
|
|
585
|
-
fieldMap: new Map(metadata.fields.map((f) => [f.propertyName, f])),
|
|
586
|
-
nameCounter: { n: 0 },
|
|
587
|
-
valueCounter: { n: 0 }
|
|
588
|
-
};
|
|
589
|
-
const expression = compileNode(ctx, filter);
|
|
590
|
-
if (expression.length === 0) {
|
|
591
|
-
return void 0;
|
|
592
|
-
}
|
|
593
|
-
return {
|
|
594
|
-
filterExpression: expression,
|
|
595
|
-
expressionAttributeNames: ctx.names,
|
|
596
|
-
expressionAttributeValues: ctx.values
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
function evaluateFilter(item, filter) {
|
|
600
|
-
for (const [key2, value] of Object.entries(filter)) {
|
|
601
|
-
if (value === void 0) continue;
|
|
602
|
-
if (key2 === "and") {
|
|
603
|
-
if (!value.every((s) => evaluateFilter(item, s)))
|
|
604
|
-
return false;
|
|
605
|
-
continue;
|
|
606
|
-
}
|
|
607
|
-
if (key2 === "or") {
|
|
608
|
-
if (!value.some((s) => evaluateFilter(item, s)))
|
|
609
|
-
return false;
|
|
610
|
-
continue;
|
|
611
|
-
}
|
|
612
|
-
if (key2 === "not") {
|
|
613
|
-
if (evaluateFilter(item, value)) return false;
|
|
614
|
-
continue;
|
|
615
|
-
}
|
|
616
|
-
if (!evaluateFieldCondition(item[key2], item, key2, value)) {
|
|
617
|
-
return false;
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
return true;
|
|
621
|
-
}
|
|
622
|
-
function cmp(a, b) {
|
|
623
|
-
if (a instanceof Date && b instanceof Date) {
|
|
624
|
-
return a.getTime() - b.getTime();
|
|
625
|
-
}
|
|
626
|
-
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
627
|
-
if (typeof a === "string" && typeof b === "string")
|
|
628
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
629
|
-
if (a === b) return 0;
|
|
630
|
-
return a < b ? -1 : 1;
|
|
631
|
-
}
|
|
632
|
-
function eq(a, b) {
|
|
633
|
-
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
634
|
-
return a === b;
|
|
635
|
-
}
|
|
636
|
-
function evaluateFieldCondition(actual, item, field2, condition) {
|
|
637
|
-
if (!isOperatorObject(condition)) {
|
|
638
|
-
return eq(actual, condition);
|
|
639
|
-
}
|
|
640
|
-
const ops = condition;
|
|
641
|
-
for (const [op, value] of Object.entries(ops)) {
|
|
642
|
-
switch (op) {
|
|
643
|
-
case "eq":
|
|
644
|
-
if (!eq(actual, value)) return false;
|
|
645
|
-
break;
|
|
646
|
-
case "ne":
|
|
647
|
-
if (eq(actual, value)) return false;
|
|
648
|
-
break;
|
|
649
|
-
case "gt":
|
|
650
|
-
if (!(cmp(actual, value) > 0)) return false;
|
|
651
|
-
break;
|
|
652
|
-
case "ge":
|
|
653
|
-
if (!(cmp(actual, value) >= 0)) return false;
|
|
654
|
-
break;
|
|
655
|
-
case "lt":
|
|
656
|
-
if (!(cmp(actual, value) < 0)) return false;
|
|
657
|
-
break;
|
|
658
|
-
case "le":
|
|
659
|
-
if (!(cmp(actual, value) <= 0)) return false;
|
|
660
|
-
break;
|
|
661
|
-
case "between": {
|
|
662
|
-
const [lo, hi] = value;
|
|
663
|
-
if (!(cmp(actual, lo) >= 0 && cmp(actual, hi) <= 0)) return false;
|
|
664
|
-
break;
|
|
665
|
-
}
|
|
666
|
-
case "in":
|
|
667
|
-
if (!value.some((v) => eq(actual, v))) return false;
|
|
668
|
-
break;
|
|
669
|
-
case "beginsWith":
|
|
670
|
-
if (typeof actual !== "string" || !actual.startsWith(value))
|
|
671
|
-
return false;
|
|
672
|
-
break;
|
|
673
|
-
case "contains":
|
|
674
|
-
if (typeof actual !== "string" || !actual.includes(value))
|
|
675
|
-
return false;
|
|
676
|
-
break;
|
|
677
|
-
case "notContains":
|
|
678
|
-
if (typeof actual === "string" && actual.includes(value))
|
|
679
|
-
return false;
|
|
680
|
-
break;
|
|
681
|
-
case "attributeExists": {
|
|
682
|
-
const exists = field2 in item && actual !== void 0 && actual !== null;
|
|
683
|
-
if (value === false ? exists : !exists) return false;
|
|
684
|
-
break;
|
|
685
|
-
}
|
|
686
|
-
case "attributeType":
|
|
687
|
-
break;
|
|
688
|
-
case "size": {
|
|
689
|
-
const len = typeof actual === "string" || Array.isArray(actual) ? actual.length : void 0;
|
|
690
|
-
if (len !== value) return false;
|
|
691
|
-
break;
|
|
692
|
-
}
|
|
693
|
-
default:
|
|
694
|
-
throw new Error(`Unknown filter operator '${op}' on field '${field2}'`);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
return true;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// src/planner/planner.ts
|
|
701
|
-
function plan(metadata, input) {
|
|
702
|
-
const queryFields = Object.keys(input.key);
|
|
703
|
-
const resolved = resolveKey(queryFields, metadata);
|
|
704
|
-
if (input.consistentRead && resolved.type === "gsi") {
|
|
705
|
-
throw new Error(
|
|
706
|
-
"consistentRead is not supported for GSI queries. Use a primary key query instead."
|
|
707
|
-
);
|
|
708
|
-
}
|
|
709
|
-
const tableName = TableMapping.resolve(metadata.tableName);
|
|
710
|
-
const built = buildKeyCondition(metadata, resolved, input.key);
|
|
711
|
-
const keyCondition = built.keyCondition;
|
|
712
|
-
const additionalProjectionFields = input.updatable ? Array.from(
|
|
713
|
-
/* @__PURE__ */ new Set([...input.additionalProjectionFields ?? [], "PK", "SK"])
|
|
714
|
-
) : input.additionalProjectionFields;
|
|
715
|
-
const projection = buildProjection(input.select, additionalProjectionFields);
|
|
716
|
-
const compiledFilter = compileFilterExpression(input.filter, metadata);
|
|
717
|
-
const isGetItem = resolved.type === "pk" && !resolved.partial && built.skValue !== void 0 && // GetItem does not support FilterExpression — fall back to Query when a
|
|
718
|
-
// server-side filter is requested so the condition can be applied.
|
|
719
|
-
!compiledFilter;
|
|
720
|
-
const projectionFields = projection ? {
|
|
721
|
-
projectionExpression: projection.projectionExpression,
|
|
722
|
-
expressionAttributeNames: projection.expressionAttributeNames
|
|
723
|
-
} : {};
|
|
724
|
-
const filterFields = compiledFilter ? {
|
|
725
|
-
filterExpression: compiledFilter.filterExpression,
|
|
726
|
-
filterExpressionAttributeNames: compiledFilter.expressionAttributeNames,
|
|
727
|
-
filterExpressionAttributeValues: compiledFilter.expressionAttributeValues
|
|
728
|
-
} : {};
|
|
729
|
-
const consistentRead = input.consistentRead && resolved.type === "pk" ? { consistentRead: true } : {};
|
|
730
|
-
let op;
|
|
731
|
-
if (isGetItem) {
|
|
732
|
-
op = {
|
|
733
|
-
type: "GetItem",
|
|
734
|
-
tableName,
|
|
735
|
-
keyCondition,
|
|
736
|
-
...projectionFields,
|
|
737
|
-
...consistentRead
|
|
738
|
-
};
|
|
739
|
-
} else {
|
|
740
|
-
const queryOp = {
|
|
741
|
-
type: "Query",
|
|
742
|
-
tableName,
|
|
743
|
-
keyCondition,
|
|
744
|
-
scanIndexForward: (input.order ?? "ASC") === "ASC",
|
|
745
|
-
...projectionFields,
|
|
746
|
-
...filterFields,
|
|
747
|
-
...consistentRead
|
|
748
|
-
};
|
|
749
|
-
if (resolved.type === "gsi") {
|
|
750
|
-
queryOp.indexName = resolved.indexName;
|
|
751
|
-
}
|
|
752
|
-
if (built.rangeCondition) {
|
|
753
|
-
queryOp.rangeCondition = built.rangeCondition;
|
|
754
|
-
}
|
|
755
|
-
if (input.limit != null) {
|
|
756
|
-
queryOp.limit = input.limit;
|
|
757
|
-
}
|
|
758
|
-
if (input.after) {
|
|
759
|
-
queryOp.exclusiveStartKey = input.after;
|
|
760
|
-
}
|
|
761
|
-
op = queryOp;
|
|
762
|
-
}
|
|
763
|
-
return { operations: [op] };
|
|
764
|
-
}
|
|
765
|
-
function buildKeyCondition(metadata, resolved, queryKey) {
|
|
766
|
-
if (resolved.type === "pk") {
|
|
767
|
-
if (!metadata.primaryKey) throw new Error("Primary key not defined");
|
|
768
|
-
return buildFromSegments(
|
|
769
|
-
metadata.primaryKey.segmented,
|
|
770
|
-
metadata.primaryKey.inputFieldNames,
|
|
771
|
-
queryKey,
|
|
772
|
-
"PK",
|
|
773
|
-
"SK"
|
|
774
|
-
);
|
|
775
|
-
}
|
|
776
|
-
const gsiDef = metadata.gsiDefinitions.find(
|
|
777
|
-
(g) => g.indexName === resolved.indexName
|
|
778
|
-
);
|
|
779
|
-
if (!gsiDef) throw new Error(`GSI '${resolved.indexName}' not found`);
|
|
780
|
-
return buildFromSegments(
|
|
781
|
-
gsiDef.segmented,
|
|
782
|
-
gsiDef.inputFieldNames,
|
|
783
|
-
queryKey,
|
|
784
|
-
`${gsiDef.indexName}PK`,
|
|
785
|
-
`${gsiDef.indexName}SK`
|
|
786
|
-
);
|
|
787
|
-
}
|
|
788
|
-
function buildFromSegments(segmented, inputFieldNames, queryKey, pkAttr, skAttr) {
|
|
789
|
-
const keyInput = {};
|
|
790
|
-
for (const fieldName of inputFieldNames) {
|
|
791
|
-
keyInput[fieldName] = queryKey[fieldName];
|
|
792
|
-
}
|
|
793
|
-
const { pk, sk, skTruncated } = resolveSegmentedKey(segmented, keyInput);
|
|
794
|
-
const keyCondition = { [pkAttr]: pk };
|
|
795
|
-
if (sk === void 0) {
|
|
796
|
-
return { keyCondition };
|
|
797
|
-
}
|
|
798
|
-
if (skTruncated) {
|
|
799
|
-
return {
|
|
800
|
-
keyCondition,
|
|
801
|
-
rangeCondition: { operator: "begins_with", key: skAttr, value: sk }
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
keyCondition[skAttr] = sk;
|
|
805
|
-
return { keyCondition, skValue: sk };
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
// src/executor/executor.ts
|
|
809
|
-
async function execute(operation) {
|
|
810
|
-
return ClientManager.getExecutor().execute(operation);
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// src/hydrator/hydrator.ts
|
|
814
|
-
function hydrate(rawItems, select, metadata, updatable = false) {
|
|
815
|
-
return rawItems.map((item) => hydrateItem(item, select, metadata, updatable));
|
|
816
|
-
}
|
|
817
|
-
function hydrateItem(raw, select, metadata, updatable = false) {
|
|
818
|
-
const fieldMap = new Map(
|
|
819
|
-
metadata.fields.map((f) => [f.propertyName, f])
|
|
820
|
-
);
|
|
821
|
-
const embeddedMap = new Map(
|
|
822
|
-
metadata.embeddedFields.map((e) => [e.propertyName, e])
|
|
823
|
-
);
|
|
824
|
-
const result = {};
|
|
825
|
-
for (const [fieldName, selectValue] of Object.entries(select)) {
|
|
826
|
-
if (selectValue === true) {
|
|
827
|
-
const value = raw[fieldName];
|
|
828
|
-
if (value !== void 0) {
|
|
829
|
-
const fieldMeta = fieldMap.get(fieldName);
|
|
830
|
-
result[fieldName] = deserializeValue(value, fieldMeta);
|
|
831
|
-
}
|
|
832
|
-
} else if (typeof selectValue === "object" && selectValue !== null) {
|
|
833
|
-
if ("select" in selectValue || isSelectBuilder(selectValue)) {
|
|
834
|
-
continue;
|
|
835
|
-
}
|
|
836
|
-
const rawEmb = raw[fieldName];
|
|
837
|
-
if (rawEmb && typeof rawEmb === "object") {
|
|
838
|
-
result[fieldName] = hydrateEmbedded(
|
|
839
|
-
rawEmb,
|
|
840
|
-
selectValue,
|
|
841
|
-
metadata,
|
|
842
|
-
fieldName
|
|
843
|
-
);
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
if (updatable) {
|
|
848
|
-
attachHiddenKey(result, raw);
|
|
849
|
-
}
|
|
850
|
-
return result;
|
|
851
|
-
}
|
|
852
|
-
function hydrateEmbedded(raw, select, _metadata, _fieldName) {
|
|
853
|
-
const result = {};
|
|
854
|
-
for (const [key2, selectValue] of Object.entries(select)) {
|
|
855
|
-
if (selectValue === true) {
|
|
856
|
-
if (raw[key2] !== void 0) {
|
|
857
|
-
result[key2] = raw[key2];
|
|
858
|
-
}
|
|
859
|
-
} else if (typeof selectValue === "object" && selectValue !== null && !("select" in selectValue)) {
|
|
860
|
-
const nested = raw[key2];
|
|
861
|
-
if (nested && typeof nested === "object") {
|
|
862
|
-
result[key2] = hydrateEmbedded(
|
|
863
|
-
nested,
|
|
864
|
-
selectValue,
|
|
865
|
-
_metadata,
|
|
866
|
-
key2
|
|
867
|
-
);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
return result;
|
|
872
|
-
}
|
|
873
|
-
function deserializeValue(value, fieldMeta) {
|
|
874
|
-
if (!fieldMeta) return value;
|
|
875
|
-
if (fieldMeta.options?.deserialize) {
|
|
876
|
-
return fieldMeta.options.deserialize(value);
|
|
877
|
-
}
|
|
878
|
-
if (fieldMeta.options?.format === "datetime" && typeof value === "string") {
|
|
879
|
-
return new Date(value);
|
|
880
|
-
}
|
|
881
|
-
if (fieldMeta.options?.format === "date" && typeof value === "string") {
|
|
882
|
-
return /* @__PURE__ */ new Date(value + "T00:00:00.000Z");
|
|
883
|
-
}
|
|
884
|
-
return value;
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
// src/pagination/cursor.ts
|
|
888
|
-
function encodeCursor(lastEvaluatedKey) {
|
|
889
|
-
const json = JSON.stringify(lastEvaluatedKey);
|
|
890
|
-
const bytes = new TextEncoder().encode(json);
|
|
891
|
-
let binary2 = "";
|
|
892
|
-
for (const b of bytes) {
|
|
893
|
-
binary2 += String.fromCharCode(b);
|
|
894
|
-
}
|
|
895
|
-
return btoa(binary2).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
896
|
-
}
|
|
897
|
-
function decodeCursor(cursor) {
|
|
898
|
-
let base64 = cursor.replace(/-/g, "+").replace(/_/g, "/");
|
|
899
|
-
while (base64.length % 4 !== 0) {
|
|
900
|
-
base64 += "=";
|
|
901
|
-
}
|
|
902
|
-
const binary2 = atob(base64);
|
|
903
|
-
const bytes = new Uint8Array(binary2.length);
|
|
904
|
-
for (let i = 0; i < binary2.length; i++) {
|
|
905
|
-
bytes[i] = binary2.charCodeAt(i);
|
|
906
|
-
}
|
|
907
|
-
const json = new TextDecoder().decode(bytes);
|
|
908
|
-
return JSON.parse(json);
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// src/operations/list.ts
|
|
912
|
-
async function executeListInternal(modelClass, key2, options) {
|
|
913
|
-
const metadata = MetadataRegistry.get(modelClass);
|
|
914
|
-
const normalized = normalizeTopLevelSelect(options.select);
|
|
915
|
-
const select = normalized.select;
|
|
916
|
-
const filter = options.filter ?? normalized.filter;
|
|
917
|
-
const after = options.after ?? normalized.after;
|
|
918
|
-
const limit = options.limit ?? normalized.limit;
|
|
919
|
-
const order = options.order ?? normalized.order;
|
|
920
|
-
const exclusiveStartKey = after ? decodeCursor(after) : void 0;
|
|
921
|
-
const implicitKeys = getImplicitKeyFields(select, metadata);
|
|
922
|
-
const executionPlan = plan(metadata, {
|
|
923
|
-
key: key2,
|
|
924
|
-
select,
|
|
925
|
-
limit,
|
|
926
|
-
after: exclusiveStartKey,
|
|
927
|
-
order,
|
|
928
|
-
filter,
|
|
929
|
-
additionalProjectionFields: implicitKeys.length > 0 ? implicitKeys : void 0,
|
|
930
|
-
updatable: options.updatable ?? false
|
|
931
|
-
});
|
|
932
|
-
const operation = executionPlan.operations[0];
|
|
933
|
-
const result = await execute(operation);
|
|
934
|
-
const rawItems = result.items;
|
|
935
|
-
const hydrated = hydrate(rawItems, select, metadata, options.updatable ?? false);
|
|
936
|
-
const cursor = result.lastEvaluatedKey ? encodeCursor(result.lastEvaluatedKey) : null;
|
|
937
|
-
return { items: hydrated, rawItems, cursor };
|
|
938
|
-
}
|
|
939
|
-
async function executeList(modelClass, key2, options) {
|
|
940
|
-
const { items, cursor } = await executeListInternal(modelClass, key2, options);
|
|
941
|
-
return { items, cursor };
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// src/planner/relation-planner.ts
|
|
945
|
-
function buildRelationQueryKey(rel, rawParentItem) {
|
|
946
|
-
const queryKey = {};
|
|
947
|
-
for (const [targetField, sourceField] of Object.entries(rel.keyBinding)) {
|
|
948
|
-
queryKey[targetField] = rawParentItem[sourceField];
|
|
949
|
-
}
|
|
950
|
-
return queryKey;
|
|
951
|
-
}
|
|
952
|
-
function serializeQueryKey(queryKey) {
|
|
953
|
-
const entries = Object.entries(queryKey).sort(([a], [b]) => a.localeCompare(b));
|
|
954
|
-
return JSON.stringify(entries);
|
|
955
|
-
}
|
|
956
|
-
function planGetItemForQueryKey(metadata, queryKey, select, additionalProjectionFields) {
|
|
957
|
-
const executionPlan = plan(metadata, {
|
|
958
|
-
key: queryKey,
|
|
959
|
-
select,
|
|
960
|
-
additionalProjectionFields
|
|
961
|
-
});
|
|
962
|
-
const operation = executionPlan.operations[0];
|
|
963
|
-
if (operation.type !== "GetItem") {
|
|
964
|
-
throw new Error(
|
|
965
|
-
`Relation target requires GetItem access pattern, got ${operation.type}`
|
|
966
|
-
);
|
|
967
|
-
}
|
|
968
|
-
return operation;
|
|
969
|
-
}
|
|
970
|
-
function planBatchGetForQueryKeys(metadata, queryKeys, select, additionalProjectionFields) {
|
|
971
|
-
if (queryKeys.length === 0) {
|
|
972
|
-
return [];
|
|
973
|
-
}
|
|
974
|
-
const keyFields = [
|
|
975
|
-
...new Set(queryKeys.flatMap((queryKey) => Object.keys(queryKey)))
|
|
976
|
-
].filter((field2) => !(field2 in select && select[field2] === true));
|
|
977
|
-
const projectionFields = [
|
|
978
|
-
.../* @__PURE__ */ new Set([
|
|
979
|
-
...(additionalProjectionFields ?? []).filter(
|
|
980
|
-
(field2) => !(field2 in select && select[field2] === true)
|
|
981
|
-
),
|
|
982
|
-
...keyFields
|
|
983
|
-
])
|
|
984
|
-
];
|
|
985
|
-
const samplePlan = planGetItemForQueryKey(
|
|
986
|
-
metadata,
|
|
987
|
-
queryKeys[0],
|
|
988
|
-
select,
|
|
989
|
-
projectionFields.length > 0 ? projectionFields : void 0
|
|
990
|
-
);
|
|
991
|
-
const uniqueKeys = dedupeDynamoKeys(
|
|
992
|
-
queryKeys.map(
|
|
993
|
-
(queryKey) => planGetItemForQueryKey(
|
|
994
|
-
metadata,
|
|
995
|
-
queryKey,
|
|
996
|
-
select,
|
|
997
|
-
projectionFields.length > 0 ? projectionFields : void 0
|
|
998
|
-
).keyCondition
|
|
999
|
-
)
|
|
1000
|
-
);
|
|
1001
|
-
const operations = [];
|
|
1002
|
-
for (let i = 0; i < uniqueKeys.length; i += BATCH_GET_MAX_KEYS) {
|
|
1003
|
-
operations.push({
|
|
1004
|
-
type: "BatchGetItem",
|
|
1005
|
-
tableName: samplePlan.tableName,
|
|
1006
|
-
keys: uniqueKeys.slice(i, i + BATCH_GET_MAX_KEYS),
|
|
1007
|
-
...samplePlan.projectionExpression ? { projectionExpression: samplePlan.projectionExpression } : {},
|
|
1008
|
-
...samplePlan.expressionAttributeNames ? { expressionAttributeNames: samplePlan.expressionAttributeNames } : {},
|
|
1009
|
-
...samplePlan.consistentRead ? { consistentRead: true } : {}
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
return operations;
|
|
1013
|
-
}
|
|
1014
|
-
function dedupeDynamoKeys(keys) {
|
|
1015
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1016
|
-
const result = [];
|
|
1017
|
-
for (const key2 of keys) {
|
|
1018
|
-
const serialized = JSON.stringify(
|
|
1019
|
-
Object.entries(key2).sort(([a], [b]) => a.localeCompare(b))
|
|
1020
|
-
);
|
|
1021
|
-
if (seen.has(serialized)) continue;
|
|
1022
|
-
seen.add(serialized);
|
|
1023
|
-
result.push(key2);
|
|
1024
|
-
}
|
|
1025
|
-
return result;
|
|
1026
|
-
}
|
|
1027
|
-
function planRelationOperations(metadata, select, context = { estimatedParentCount: 1 }) {
|
|
1028
|
-
const operations = [];
|
|
1029
|
-
const relations = detectRelationFields(select, metadata);
|
|
1030
|
-
for (const rel of relations) {
|
|
1031
|
-
const selectSpec = normalizeSelectSpec(select[rel.propertyName]);
|
|
1032
|
-
const targetClass = rel.targetFactory();
|
|
1033
|
-
const targetMeta = MetadataRegistry.get(targetClass);
|
|
1034
|
-
if (rel.type === "hasMany") {
|
|
1035
|
-
const limit = selectSpec.limit ?? rel.options?.limit?.default ?? 20;
|
|
1036
|
-
const tableName = TableMapping.resolve(targetMeta.tableName);
|
|
1037
|
-
const listPlan = plan(targetMeta, {
|
|
1038
|
-
key: buildPlaceholderHasManyKey(rel, targetMeta),
|
|
1039
|
-
select: selectSpec.select,
|
|
1040
|
-
limit
|
|
1041
|
-
});
|
|
1042
|
-
operations.push(...listPlan.operations);
|
|
1043
|
-
operations.push(
|
|
1044
|
-
...planRelationOperations(targetMeta, selectSpec.select, {
|
|
1045
|
-
estimatedParentCount: limit
|
|
1046
|
-
})
|
|
1047
|
-
);
|
|
1048
|
-
continue;
|
|
1049
|
-
}
|
|
1050
|
-
const estimatedCount = context.estimatedParentCount ?? 1;
|
|
1051
|
-
operations.push(
|
|
1052
|
-
...planBatchGetForExplain(targetMeta, selectSpec.select, estimatedCount)
|
|
1053
|
-
);
|
|
1054
|
-
operations.push(
|
|
1055
|
-
...planRelationOperations(targetMeta, selectSpec.select, {
|
|
1056
|
-
estimatedParentCount: 1
|
|
1057
|
-
})
|
|
1058
|
-
);
|
|
1059
|
-
}
|
|
1060
|
-
return operations;
|
|
1061
|
-
}
|
|
1062
|
-
function buildPlaceholderHasManyKey(rel, targetMeta) {
|
|
1063
|
-
const queryKey = {};
|
|
1064
|
-
for (const targetField of Object.keys(rel.keyBinding)) {
|
|
1065
|
-
queryKey[targetField] = `__${targetField}__`;
|
|
1066
|
-
}
|
|
1067
|
-
if (targetMeta.primaryKey) {
|
|
1068
|
-
for (const fieldName of targetMeta.primaryKey.inputFieldNames) {
|
|
1069
|
-
if (!(fieldName in queryKey)) {
|
|
1070
|
-
queryKey[fieldName] = `__${fieldName}__`;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
return queryKey;
|
|
1075
|
-
}
|
|
1076
|
-
function planBatchGetForExplain(metadata, select, estimatedKeyCount) {
|
|
1077
|
-
const placeholderKeys = Array.from(
|
|
1078
|
-
{ length: estimatedKeyCount },
|
|
1079
|
-
(_, index) => buildExplainPlaceholderKey(metadata, index)
|
|
1080
|
-
);
|
|
1081
|
-
return planBatchGetForQueryKeys(metadata, placeholderKeys, select);
|
|
1082
|
-
}
|
|
1083
|
-
function buildExplainPlaceholderKey(metadata, index) {
|
|
1084
|
-
const queryKey = {};
|
|
1085
|
-
if (metadata.primaryKey) {
|
|
1086
|
-
for (const fieldName of metadata.primaryKey.inputFieldNames) {
|
|
1087
|
-
queryKey[fieldName] = `__explain_${fieldName}_${index}__`;
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
return queryKey;
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// src/relation/traversal.ts
|
|
1094
|
-
function evaluateFilterClientSide(item, filter, _targetMeta) {
|
|
1095
|
-
return evaluateFilter(item, filter);
|
|
1096
|
-
}
|
|
1097
|
-
function relationSpec(value) {
|
|
1098
|
-
return normalizeSelectSpec(value);
|
|
1099
|
-
}
|
|
1100
|
-
function validateDepth(select, metadata, maxDepth, currentDepth = 1) {
|
|
1101
|
-
for (const [fieldName, value] of Object.entries(select)) {
|
|
1102
|
-
if (typeof value === "object" && value !== null && ("select" in value || isSelectBuilder(value))) {
|
|
1103
|
-
const rel = metadata.relations.find((r) => r.propertyName === fieldName);
|
|
1104
|
-
if (!rel) continue;
|
|
1105
|
-
if (currentDepth > maxDepth) {
|
|
1106
|
-
throw new Error(
|
|
1107
|
-
`Relation traversal depth exceeded: '${fieldName}' at depth ${currentDepth} exceeds maxDepth ${maxDepth}. Set maxDepth: ${currentDepth} to allow this.`
|
|
1108
|
-
);
|
|
1109
|
-
}
|
|
1110
|
-
const targetClass = rel.targetFactory();
|
|
1111
|
-
const targetMeta = MetadataRegistry.get(targetClass);
|
|
1112
|
-
const innerSelect = relationSpec(value).select;
|
|
1113
|
-
validateDepth(innerSelect, targetMeta, maxDepth, currentDepth + 1);
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
function relationResultPath(parentPath, rel) {
|
|
1118
|
-
const base = `${parentPath}.${rel.propertyName}`;
|
|
1119
|
-
return rel.type === "hasMany" ? `${base}.items` : base;
|
|
1120
|
-
}
|
|
1121
|
-
function stageRelations(relations, parentPath, plan2) {
|
|
1122
|
-
if (plan2 === void 0 || relations.length === 0) {
|
|
1123
|
-
return relations.length > 0 ? { stages: [[...relations]], concurrency: RELATION_TRAVERSAL_CONCURRENCY } : { stages: [], concurrency: RELATION_TRAVERSAL_CONCURRENCY };
|
|
1124
|
-
}
|
|
1125
|
-
const byStage = /* @__PURE__ */ new Map();
|
|
1126
|
-
const unstaged = [];
|
|
1127
|
-
for (const rel of relations) {
|
|
1128
|
-
const stage = stageOfResultPath(plan2, relationResultPath(parentPath, rel));
|
|
1129
|
-
if (stage === void 0) {
|
|
1130
|
-
unstaged.push(rel);
|
|
1131
|
-
continue;
|
|
1132
|
-
}
|
|
1133
|
-
const bucket = byStage.get(stage);
|
|
1134
|
-
if (bucket) bucket.push(rel);
|
|
1135
|
-
else byStage.set(stage, [rel]);
|
|
1136
|
-
}
|
|
1137
|
-
const stages = [...byStage.keys()].sort((a, b) => a - b).map((s) => byStage.get(s));
|
|
1138
|
-
if (unstaged.length > 0) stages.push(unstaged);
|
|
1139
|
-
return { stages, concurrency: plan2.plan.concurrency };
|
|
1140
|
-
}
|
|
1141
|
-
async function runStaged(relations, parentPath, plan2, worker) {
|
|
1142
|
-
const { stages, concurrency } = stageRelations(relations, parentPath, plan2);
|
|
1143
|
-
for (const stage of stages) {
|
|
1144
|
-
await mapWithConcurrency(stage, concurrency, (rel) => worker(rel));
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
function hasCompleteQueryKey(queryKey) {
|
|
1148
|
-
return Object.values(queryKey).every((value) => value != null);
|
|
1149
|
-
}
|
|
1150
|
-
function matchesQueryKey(item, queryKey) {
|
|
1151
|
-
return Object.entries(queryKey).every(
|
|
1152
|
-
([field2, value]) => item[field2] === value
|
|
1153
|
-
);
|
|
1154
|
-
}
|
|
1155
|
-
async function fetchBelongsToHasOneBatch(rel, rawParentItems, selectSpec, targetMeta, options, currentDepth, parentPath) {
|
|
1156
|
-
const results = rawParentItems.map(
|
|
1157
|
-
() => null
|
|
1158
|
-
);
|
|
1159
|
-
const queryKeys = [];
|
|
1160
|
-
const parentIndexes = [];
|
|
1161
|
-
for (let i = 0; i < rawParentItems.length; i++) {
|
|
1162
|
-
const queryKey = buildRelationQueryKey(rel, rawParentItems[i]);
|
|
1163
|
-
if (!hasCompleteQueryKey(queryKey)) {
|
|
1164
|
-
continue;
|
|
1165
|
-
}
|
|
1166
|
-
queryKeys.push(queryKey);
|
|
1167
|
-
parentIndexes.push(i);
|
|
1168
|
-
}
|
|
1169
|
-
if (queryKeys.length === 0) {
|
|
1170
|
-
return results;
|
|
1171
|
-
}
|
|
1172
|
-
const implicitKeys = getImplicitKeyFields(selectSpec.select, targetMeta);
|
|
1173
|
-
const projectionExtras = options.updatable ? [...implicitKeys, "PK", "SK"] : implicitKeys;
|
|
1174
|
-
const batchOps = planBatchGetForQueryKeys(
|
|
1175
|
-
targetMeta,
|
|
1176
|
-
queryKeys,
|
|
1177
|
-
selectSpec.select,
|
|
1178
|
-
projectionExtras.length > 0 ? projectionExtras : void 0
|
|
1179
|
-
);
|
|
1180
|
-
const executor = ClientManager.getExecutor();
|
|
1181
|
-
const queryKeyToRaw = /* @__PURE__ */ new Map();
|
|
1182
|
-
for (const operation of batchOps) {
|
|
1183
|
-
const batchResult = await executor.batchGet(operation);
|
|
1184
|
-
for (const item of batchResult.items) {
|
|
1185
|
-
for (const queryKey of queryKeys) {
|
|
1186
|
-
if (!matchesQueryKey(item, queryKey)) {
|
|
1187
|
-
continue;
|
|
1188
|
-
}
|
|
1189
|
-
queryKeyToRaw.set(serializeQueryKey(queryKey), item);
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
const nestedRelations = detectRelationFields(selectSpec.select, targetMeta);
|
|
1194
|
-
const ownPath = relationResultPath(parentPath, rel);
|
|
1195
|
-
await mapWithConcurrency(
|
|
1196
|
-
queryKeys,
|
|
1197
|
-
options.plan?.plan.concurrency ?? RELATION_TRAVERSAL_CONCURRENCY,
|
|
1198
|
-
async (queryKey, j) => {
|
|
1199
|
-
const parentIndex = parentIndexes[j];
|
|
1200
|
-
const raw = queryKeyToRaw.get(serializeQueryKey(queryKey));
|
|
1201
|
-
if (!raw) {
|
|
1202
|
-
return;
|
|
1203
|
-
}
|
|
1204
|
-
const updatable = options.updatable ?? false;
|
|
1205
|
-
let resolvedItem = hydrate([raw], selectSpec.select, targetMeta, updatable)[0] ?? null;
|
|
1206
|
-
if (resolvedItem && nestedRelations.length > 0) {
|
|
1207
|
-
resolvedItem = await resolveRelations(
|
|
1208
|
-
resolvedItem,
|
|
1209
|
-
raw,
|
|
1210
|
-
selectSpec.select,
|
|
1211
|
-
targetMeta,
|
|
1212
|
-
options,
|
|
1213
|
-
currentDepth + 1,
|
|
1214
|
-
ownPath
|
|
1215
|
-
);
|
|
1216
|
-
if (resolvedItem && updatable) {
|
|
1217
|
-
attachHiddenKey(resolvedItem, raw);
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
if (resolvedItem && !passesPostLoad(resolvedItem, selectSpec, targetMeta)) {
|
|
1221
|
-
results[parentIndex] = null;
|
|
1222
|
-
} else {
|
|
1223
|
-
results[parentIndex] = resolvedItem;
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
);
|
|
1227
|
-
return results;
|
|
1228
|
-
}
|
|
1229
|
-
function passesPostLoad(item, spec, targetMeta) {
|
|
1230
|
-
if (spec.filter && !evaluateFilterClientSide(item, spec.filter, targetMeta)) {
|
|
1231
|
-
return false;
|
|
1232
|
-
}
|
|
1233
|
-
return true;
|
|
1234
|
-
}
|
|
1235
|
-
async function resolveSingleValueRelationsBatch(parentItems, rawParentItems, select, metadata, options, currentDepth, parentPath) {
|
|
1236
|
-
const results = parentItems.map((item, i) => {
|
|
1237
|
-
const copy = { ...item };
|
|
1238
|
-
if (options.updatable) {
|
|
1239
|
-
attachHiddenKey(copy, rawParentItems[i]);
|
|
1240
|
-
}
|
|
1241
|
-
return copy;
|
|
1242
|
-
});
|
|
1243
|
-
const relations = detectRelationFields(select, metadata).filter(
|
|
1244
|
-
(rel) => rel.type === "belongsTo" || rel.type === "hasOne"
|
|
1245
|
-
);
|
|
1246
|
-
await runStaged(relations, parentPath, options.plan, async (rel) => {
|
|
1247
|
-
const selectSpec = relationSpec(select[rel.propertyName]);
|
|
1248
|
-
const targetClass = rel.targetFactory();
|
|
1249
|
-
const targetMeta = MetadataRegistry.get(targetClass);
|
|
1250
|
-
const batchResults = await fetchBelongsToHasOneBatch(
|
|
1251
|
-
rel,
|
|
1252
|
-
rawParentItems,
|
|
1253
|
-
selectSpec,
|
|
1254
|
-
targetMeta,
|
|
1255
|
-
options,
|
|
1256
|
-
currentDepth,
|
|
1257
|
-
parentPath
|
|
1258
|
-
);
|
|
1259
|
-
for (let i = 0; i < results.length; i++) {
|
|
1260
|
-
results[i][rel.propertyName] = batchResults[i];
|
|
1261
|
-
}
|
|
1262
|
-
});
|
|
1263
|
-
return results;
|
|
1264
|
-
}
|
|
1265
|
-
async function resolveRelations(parentItem, rawParentItem, select, metadata, options, currentDepth = 1, currentPath = "$") {
|
|
1266
|
-
const result = { ...parentItem };
|
|
1267
|
-
const relations = detectRelationFields(select, metadata);
|
|
1268
|
-
const plan2 = options.plan ?? (currentPath === "$" ? buildRelationExecutionPlan(select, metadata) : void 0);
|
|
1269
|
-
const opts = plan2 === options.plan ? options : { ...options, plan: plan2 };
|
|
1270
|
-
await runStaged(relations, currentPath, opts.plan, async (rel) => {
|
|
1271
|
-
const selectSpec = relationSpec(select[rel.propertyName]);
|
|
1272
|
-
const ownPath = relationResultPath(currentPath, rel);
|
|
1273
|
-
if (rel.type === "hasMany") {
|
|
1274
|
-
const targetClass2 = rel.targetFactory();
|
|
1275
|
-
const queryKey = buildRelationQueryKey(rel, rawParentItem);
|
|
1276
|
-
const limit = selectSpec.limit ?? rel.options?.limit?.default ?? 20;
|
|
1277
|
-
const order = selectSpec.order ?? rel.options?.order ?? "ASC";
|
|
1278
|
-
const listResult = await executeListInternal(targetClass2, queryKey, {
|
|
1279
|
-
select: selectSpec.select,
|
|
1280
|
-
limit,
|
|
1281
|
-
after: selectSpec.after,
|
|
1282
|
-
order,
|
|
1283
|
-
filter: selectSpec.filter,
|
|
1284
|
-
updatable: opts.updatable
|
|
1285
|
-
});
|
|
1286
|
-
let items = listResult.items;
|
|
1287
|
-
const rawItems = listResult.rawItems;
|
|
1288
|
-
const targetMeta2 = MetadataRegistry.get(targetClass2);
|
|
1289
|
-
const nestedRelations = detectRelationFields(selectSpec.select, targetMeta2);
|
|
1290
|
-
if (nestedRelations.length > 0) {
|
|
1291
|
-
items = await resolveSingleValueRelationsBatch(
|
|
1292
|
-
items,
|
|
1293
|
-
rawItems,
|
|
1294
|
-
selectSpec.select,
|
|
1295
|
-
targetMeta2,
|
|
1296
|
-
opts,
|
|
1297
|
-
currentDepth + 1,
|
|
1298
|
-
ownPath
|
|
1299
|
-
);
|
|
1300
|
-
}
|
|
1301
|
-
result[rel.propertyName] = {
|
|
1302
|
-
items,
|
|
1303
|
-
cursor: listResult.cursor
|
|
1304
|
-
};
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
const targetClass = rel.targetFactory();
|
|
1308
|
-
const targetMeta = MetadataRegistry.get(targetClass);
|
|
1309
|
-
const [resolvedItem] = await fetchBelongsToHasOneBatch(
|
|
1310
|
-
rel,
|
|
1311
|
-
[rawParentItem],
|
|
1312
|
-
selectSpec,
|
|
1313
|
-
targetMeta,
|
|
1314
|
-
opts,
|
|
1315
|
-
currentDepth,
|
|
1316
|
-
currentPath
|
|
1317
|
-
);
|
|
1318
|
-
result[rel.propertyName] = resolvedItem;
|
|
1319
|
-
});
|
|
1320
|
-
return result;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
// src/operations/query.ts
|
|
1324
|
-
async function executeQuery(modelClass, key2, selectSpec, options) {
|
|
1325
|
-
const metadata = MetadataRegistry.get(modelClass);
|
|
1326
|
-
const queryFields = Object.keys(key2);
|
|
1327
|
-
const resolved = resolveKey(queryFields, metadata);
|
|
1328
|
-
if (resolved.type === "gsi" && !resolved.unique) {
|
|
1329
|
-
throw new Error(
|
|
1330
|
-
`Cannot use query() with non-unique GSI '${resolved.indexName}'. Use list() instead.`
|
|
1331
|
-
);
|
|
1332
|
-
}
|
|
1333
|
-
const normalized = normalizeTopLevelSelect(selectSpec);
|
|
1334
|
-
const select = normalized.select;
|
|
1335
|
-
const filter = normalized.filter;
|
|
1336
|
-
const maxDepth = options?.maxDepth ?? 1;
|
|
1337
|
-
const relations = detectRelationFields(select, metadata);
|
|
1338
|
-
if (relations.length > 0) {
|
|
1339
|
-
validateDepth(select, metadata, maxDepth);
|
|
1340
|
-
}
|
|
1341
|
-
const implicitKeys = getImplicitKeyFields(select, metadata);
|
|
1342
|
-
const updatable = options?.updatable ?? false;
|
|
1343
|
-
const executionPlan = plan(metadata, {
|
|
1344
|
-
key: key2,
|
|
1345
|
-
select,
|
|
1346
|
-
filter,
|
|
1347
|
-
consistentRead: options?.consistentRead,
|
|
1348
|
-
additionalProjectionFields: implicitKeys.length > 0 ? implicitKeys : void 0,
|
|
1349
|
-
updatable
|
|
1350
|
-
});
|
|
1351
|
-
const operation = executionPlan.operations[0];
|
|
1352
|
-
const canParallelizeRelations = relations.length > 0 && relations.every(
|
|
1353
|
-
(rel) => Object.values(rel.keyBinding).every(
|
|
1354
|
-
(sourceField) => key2[sourceField] != null
|
|
1355
|
-
)
|
|
1356
|
-
);
|
|
1357
|
-
const parentPromise = execute(operation);
|
|
1358
|
-
const relationPromise = canParallelizeRelations ? resolveRelations({}, key2, select, metadata, { maxDepth, updatable }, 1) : null;
|
|
1359
|
-
let result;
|
|
1360
|
-
try {
|
|
1361
|
-
result = await parentPromise;
|
|
1362
|
-
} catch (err) {
|
|
1363
|
-
if (relationPromise) await relationPromise.catch(() => void 0);
|
|
1364
|
-
throw err;
|
|
1365
|
-
}
|
|
1366
|
-
if (result.items.length === 0) {
|
|
1367
|
-
if (relationPromise) await relationPromise.catch(() => void 0);
|
|
1368
|
-
return null;
|
|
1369
|
-
}
|
|
1370
|
-
const rawItem = result.items[0];
|
|
1371
|
-
const hydrated = hydrate(result.items, select, metadata, updatable);
|
|
1372
|
-
let hydratedItem = hydrated[0] ?? null;
|
|
1373
|
-
if (hydratedItem && relations.length > 0) {
|
|
1374
|
-
if (relationPromise) {
|
|
1375
|
-
const resolvedRelations = await relationPromise;
|
|
1376
|
-
for (const rel of relations) {
|
|
1377
|
-
hydratedItem[rel.propertyName] = resolvedRelations[rel.propertyName];
|
|
1378
|
-
}
|
|
1379
|
-
} else {
|
|
1380
|
-
hydratedItem = await resolveRelations(
|
|
1381
|
-
hydratedItem,
|
|
1382
|
-
rawItem,
|
|
1383
|
-
select,
|
|
1384
|
-
metadata,
|
|
1385
|
-
{ maxDepth, updatable },
|
|
1386
|
-
1
|
|
1387
|
-
);
|
|
1388
|
-
if (hydratedItem && updatable) {
|
|
1389
|
-
attachHiddenKey(hydratedItem, rawItem);
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
if (options?.hydrate) {
|
|
1394
|
-
return options.hydrate(hydratedItem);
|
|
1395
|
-
}
|
|
1396
|
-
return hydratedItem;
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
// src/operations/explain.ts
|
|
1400
|
-
function executeExplain(modelClass, key2, options) {
|
|
1401
|
-
const metadata = MetadataRegistry.get(modelClass);
|
|
1402
|
-
const exclusiveStartKey = options?.after ? decodeCursor(options.after) : void 0;
|
|
1403
|
-
const normalized = normalizeTopLevelSelect(options?.select);
|
|
1404
|
-
const select = normalized.select ?? {};
|
|
1405
|
-
const filter = options?.filter ?? normalized.filter;
|
|
1406
|
-
const mainPlan = plan(metadata, {
|
|
1407
|
-
key: key2,
|
|
1408
|
-
select,
|
|
1409
|
-
limit: options?.limit,
|
|
1410
|
-
after: exclusiveStartKey,
|
|
1411
|
-
order: options?.order,
|
|
1412
|
-
consistentRead: options?.consistentRead,
|
|
1413
|
-
filter
|
|
1414
|
-
});
|
|
1415
|
-
const relationOps = planRelationOperations(metadata, select, {
|
|
1416
|
-
estimatedParentCount: 1
|
|
1417
|
-
});
|
|
1418
|
-
return {
|
|
1419
|
-
operations: [...mainPlan.operations, ...relationOps]
|
|
1420
|
-
};
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
// src/operations/batch.ts
|
|
1424
|
-
var BatchGetResult = class {
|
|
1425
|
-
groups;
|
|
1426
|
-
constructor(groups) {
|
|
1427
|
-
this.groups = groups;
|
|
1428
|
-
}
|
|
1429
|
-
get(model2) {
|
|
1430
|
-
const modelClass = resolveModelClass(model2);
|
|
1431
|
-
return this.groups.get(modelClass) ?? [];
|
|
1432
|
-
}
|
|
1433
|
-
};
|
|
1434
|
-
function buildScalarSelect(metadata) {
|
|
1435
|
-
const select = {};
|
|
1436
|
-
for (const field2 of metadata.fields) {
|
|
1437
|
-
select[field2.propertyName] = true;
|
|
1438
|
-
}
|
|
1439
|
-
return select;
|
|
1440
|
-
}
|
|
1441
|
-
function buildDynamoKey(modelClass, keyObj) {
|
|
1442
|
-
const deleteInput = buildDeleteInput(modelClass, keyObj);
|
|
1443
|
-
return deleteInput.Key;
|
|
1444
|
-
}
|
|
1445
|
-
function hydrateBatchItems(rawItems, metadata) {
|
|
1446
|
-
if (rawItems.length === 0) {
|
|
1447
|
-
return [];
|
|
1448
|
-
}
|
|
1449
|
-
const select = buildScalarSelect(metadata);
|
|
1450
|
-
const hydrated = hydrate(rawItems, select, metadata);
|
|
1451
|
-
if (metadata.embeddedFields.length === 0) {
|
|
1452
|
-
return hydrated;
|
|
1453
|
-
}
|
|
1454
|
-
return hydrated.map((item, index) => {
|
|
1455
|
-
const raw = rawItems[index];
|
|
1456
|
-
const merged = { ...item };
|
|
1457
|
-
for (const emb of metadata.embeddedFields) {
|
|
1458
|
-
const rawEmb = raw?.[emb.propertyName];
|
|
1459
|
-
if (rawEmb !== void 0 && rawEmb !== null) {
|
|
1460
|
-
merged[emb.propertyName] = rawEmb;
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
return merged;
|
|
1464
|
-
});
|
|
1465
|
-
}
|
|
1466
|
-
async function executeBatchGetForTable(tableName, entries) {
|
|
1467
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1468
|
-
if (entries.length === 0) {
|
|
1469
|
-
return groups;
|
|
1470
|
-
}
|
|
1471
|
-
const keys = entries.map((entry) => entry.dynamoKey);
|
|
1472
|
-
const { items: rawItems } = await ClientManager.getExecutor().batchGet({
|
|
1473
|
-
tableName,
|
|
1474
|
-
keys
|
|
1475
|
-
});
|
|
1476
|
-
const entriesByModel = /* @__PURE__ */ new Map();
|
|
1477
|
-
for (const entry of entries) {
|
|
1478
|
-
const existing = entriesByModel.get(entry.modelClass) ?? [];
|
|
1479
|
-
existing.push(entry);
|
|
1480
|
-
entriesByModel.set(entry.modelClass, existing);
|
|
1481
|
-
}
|
|
1482
|
-
for (const [modelClass, modelEntries] of entriesByModel) {
|
|
1483
|
-
const metadata = modelEntries[0].metadata;
|
|
1484
|
-
const expectedKeys = new Set(
|
|
1485
|
-
modelEntries.map(
|
|
1486
|
-
(entry) => `${String(entry.dynamoKey.PK)}::${String(entry.dynamoKey.SK)}`
|
|
1487
|
-
)
|
|
1488
|
-
);
|
|
1489
|
-
const modelRawItems = rawItems.filter(
|
|
1490
|
-
(item) => expectedKeys.has(`${String(item.PK)}::${String(item.SK)}`)
|
|
1491
|
-
);
|
|
1492
|
-
groups.set(modelClass, hydrateBatchItems(modelRawItems, metadata));
|
|
1493
|
-
}
|
|
1494
|
-
return groups;
|
|
1495
|
-
}
|
|
1496
|
-
async function executeBatchGet(requests) {
|
|
1497
|
-
const entriesByTable = /* @__PURE__ */ new Map();
|
|
1498
|
-
for (const request of requests) {
|
|
1499
|
-
const modelClass = resolveModelClass(request.model);
|
|
1500
|
-
const metadata = MetadataRegistry.get(modelClass);
|
|
1501
|
-
const tableName = TableMapping.resolve(metadata.tableName);
|
|
1502
|
-
for (const keyObj of request.keys) {
|
|
1503
|
-
const entry = {
|
|
1504
|
-
modelClass,
|
|
1505
|
-
metadata,
|
|
1506
|
-
dynamoKey: buildDynamoKey(modelClass, keyObj)
|
|
1507
|
-
};
|
|
1508
|
-
const existing = entriesByTable.get(tableName) ?? [];
|
|
1509
|
-
existing.push(entry);
|
|
1510
|
-
entriesByTable.set(tableName, existing);
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
const mergedGroups = /* @__PURE__ */ new Map();
|
|
1514
|
-
for (const [, entries] of entriesByTable) {
|
|
1515
|
-
const tableName = TableMapping.resolve(entries[0].metadata.tableName);
|
|
1516
|
-
const tableGroups = await executeBatchGetForTable(tableName, entries);
|
|
1517
|
-
for (const [modelClass, items] of tableGroups) {
|
|
1518
|
-
const existing = mergedGroups.get(modelClass) ?? [];
|
|
1519
|
-
mergedGroups.set(modelClass, existing.concat(items));
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
return new BatchGetResult(mergedGroups);
|
|
1523
|
-
}
|
|
1524
|
-
function toExecItem(entry) {
|
|
1525
|
-
if (entry.request.type === "put") {
|
|
1526
|
-
return { type: "put", item: entry.request.putInput.Item };
|
|
1527
|
-
}
|
|
1528
|
-
return { type: "delete", key: entry.request.deleteInput.Key };
|
|
1529
|
-
}
|
|
1530
|
-
async function executeBatchWrite(requests) {
|
|
1531
|
-
const entriesByTable = /* @__PURE__ */ new Map();
|
|
1532
|
-
for (const request of requests) {
|
|
1533
|
-
const modelClass = resolveModelClass(request.model);
|
|
1534
|
-
const metadata = MetadataRegistry.get(modelClass);
|
|
1535
|
-
const tableName = TableMapping.resolve(metadata.tableName);
|
|
1536
|
-
if (request.type === "put") {
|
|
1537
|
-
const putInput = buildPutInput(modelClass, request.item, request.options);
|
|
1538
|
-
const entry2 = {
|
|
1539
|
-
tableName,
|
|
1540
|
-
modelName: modelClass.name,
|
|
1541
|
-
request: { type: "put", putInput }
|
|
1542
|
-
};
|
|
1543
|
-
const existing2 = entriesByTable.get(tableName) ?? [];
|
|
1544
|
-
existing2.push(entry2);
|
|
1545
|
-
entriesByTable.set(tableName, existing2);
|
|
1546
|
-
continue;
|
|
1547
|
-
}
|
|
1548
|
-
const deleteInput = buildDeleteInput(modelClass, request.key, request.options);
|
|
1549
|
-
const entry = {
|
|
1550
|
-
tableName,
|
|
1551
|
-
modelName: modelClass.name,
|
|
1552
|
-
request: { type: "delete", deleteInput }
|
|
1553
|
-
};
|
|
1554
|
-
const existing = entriesByTable.get(tableName) ?? [];
|
|
1555
|
-
existing.push(entry);
|
|
1556
|
-
entriesByTable.set(tableName, existing);
|
|
1557
|
-
}
|
|
1558
|
-
const executor = ClientManager.getExecutor();
|
|
1559
|
-
for (const [tableName, entries] of entriesByTable) {
|
|
1560
|
-
await executor.batchWrite(tableName, entries.map(toExecItem));
|
|
1561
|
-
}
|
|
1562
|
-
if (ChangeCaptureRegistry.hasSubscribers()) {
|
|
1563
|
-
for (const [, entries] of entriesByTable) {
|
|
1564
|
-
for (const entry of entries) {
|
|
1565
|
-
if (entry.request.type === "put") {
|
|
1566
|
-
const item = entry.request.putInput.Item;
|
|
1567
|
-
captureWrite({
|
|
1568
|
-
table: entry.tableName,
|
|
1569
|
-
...entry.modelName ? { model: entry.modelName } : {},
|
|
1570
|
-
op: "put",
|
|
1571
|
-
keys: { pk: String(item.PK), sk: String(item.SK ?? "") },
|
|
1572
|
-
newItem: item
|
|
1573
|
-
});
|
|
1574
|
-
} else {
|
|
1575
|
-
const dkey = entry.request.deleteInput.Key;
|
|
1576
|
-
captureWrite({
|
|
1577
|
-
table: entry.tableName,
|
|
1578
|
-
...entry.modelName ? { model: entry.modelName } : {},
|
|
1579
|
-
op: "delete",
|
|
1580
|
-
keys: { pk: String(dkey.PK), sk: String(dkey.SK ?? "") }
|
|
1581
|
-
});
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
// src/model/DDBModel.ts
|
|
1589
|
-
var DDBModel = class {
|
|
1590
|
-
static setClient(client) {
|
|
1591
|
-
ClientManager.setClient(client);
|
|
1592
|
-
}
|
|
1593
|
-
static setTableMapping(mapping) {
|
|
1594
|
-
TableMapping.set(mapping);
|
|
1595
|
-
}
|
|
1596
|
-
static async transaction(fn) {
|
|
1597
|
-
await executeTransaction(fn);
|
|
1598
|
-
}
|
|
1599
|
-
static async batchGet(requests) {
|
|
1600
|
-
return executeBatchGet(requests);
|
|
1601
|
-
}
|
|
1602
|
-
static async batchWrite(requests) {
|
|
1603
|
-
return executeBatchWrite(requests);
|
|
1604
|
-
}
|
|
1605
|
-
static asModel() {
|
|
1606
|
-
const modelClass = this;
|
|
1607
|
-
const modelStatic = {
|
|
1608
|
-
col: createColumnMap(),
|
|
1609
|
-
project(select) {
|
|
1610
|
-
return buildProject(select);
|
|
1611
|
-
},
|
|
1612
|
-
relation(select) {
|
|
1613
|
-
return buildRelation(select);
|
|
1614
|
-
},
|
|
1615
|
-
// The public `query` is overloaded (plain / updatable / hydrate, the last
|
|
1616
|
-
// introducing a fresh return-type parameter `R`, issue #53). An overloaded
|
|
1617
|
-
// method on an object literal cannot contextually infer its single
|
|
1618
|
-
// implementation signature, so the implementation is written with explicit
|
|
1619
|
-
// runtime-facing parameter types and assigned through the interface's
|
|
1620
|
-
// method type — the public overloads (the only caller-visible surface)
|
|
1621
|
-
// are untouched.
|
|
1622
|
-
query: ((key2, select, options) => {
|
|
1623
|
-
if (isRecordingContractMethod()) {
|
|
1624
|
-
recordContractOp("query", modelStatic, {
|
|
1625
|
-
keys: key2,
|
|
1626
|
-
select
|
|
1627
|
-
});
|
|
1628
|
-
return Promise.resolve(null);
|
|
1629
|
-
}
|
|
1630
|
-
return executeQuery(modelClass, key2, select, options);
|
|
1631
|
-
}),
|
|
1632
|
-
async list(key2, options) {
|
|
1633
|
-
if (isRecordingContractMethod()) {
|
|
1634
|
-
recordContractOp("list", modelStatic, {
|
|
1635
|
-
keys: key2,
|
|
1636
|
-
select: options?.select
|
|
1637
|
-
});
|
|
1638
|
-
return { items: [], cursor: null };
|
|
1639
|
-
}
|
|
1640
|
-
const result = await executeList(
|
|
1641
|
-
modelClass,
|
|
1642
|
-
key2,
|
|
1643
|
-
options
|
|
1644
|
-
);
|
|
1645
|
-
return result;
|
|
1646
|
-
},
|
|
1647
|
-
explain(key2, options) {
|
|
1648
|
-
return executeExplain(
|
|
1649
|
-
modelClass,
|
|
1650
|
-
key2,
|
|
1651
|
-
options
|
|
1652
|
-
);
|
|
1653
|
-
},
|
|
1654
|
-
async put(item, options) {
|
|
1655
|
-
if (isRecordingContractMethod()) {
|
|
1656
|
-
recordContractOp("put", modelStatic, {
|
|
1657
|
-
item,
|
|
1658
|
-
condition: options?.condition,
|
|
1659
|
-
batch: options?.batch
|
|
1660
|
-
});
|
|
1661
|
-
return;
|
|
1662
|
-
}
|
|
1663
|
-
await executePut(
|
|
1664
|
-
modelClass,
|
|
1665
|
-
item,
|
|
1666
|
-
options
|
|
1667
|
-
);
|
|
1668
|
-
},
|
|
1669
|
-
async update(entity, changes, options) {
|
|
1670
|
-
if (isRecordingContractMethod()) {
|
|
1671
|
-
recordContractOp("update", modelStatic, {
|
|
1672
|
-
keys: entity,
|
|
1673
|
-
changes,
|
|
1674
|
-
condition: options?.condition,
|
|
1675
|
-
batch: options?.batch
|
|
1676
|
-
});
|
|
1677
|
-
return;
|
|
1678
|
-
}
|
|
1679
|
-
await executeUpdate(
|
|
1680
|
-
modelClass,
|
|
1681
|
-
entity,
|
|
1682
|
-
changes,
|
|
1683
|
-
options
|
|
1684
|
-
);
|
|
1685
|
-
},
|
|
1686
|
-
async delete(key2, options) {
|
|
1687
|
-
if (isRecordingContractMethod()) {
|
|
1688
|
-
recordContractOp("delete", modelStatic, {
|
|
1689
|
-
keys: key2,
|
|
1690
|
-
condition: options?.condition,
|
|
1691
|
-
batch: options?.batch
|
|
1692
|
-
});
|
|
1693
|
-
return;
|
|
1694
|
-
}
|
|
1695
|
-
await executeDelete(modelClass, key2, options);
|
|
1696
|
-
}
|
|
1697
|
-
};
|
|
1698
|
-
return attachModelClass(
|
|
1699
|
-
modelStatic,
|
|
1700
|
-
modelClass
|
|
1701
|
-
);
|
|
1702
|
-
}
|
|
1703
|
-
};
|
|
1704
|
-
|
|
1705
341
|
// src/operations/declarative-transaction.ts
|
|
1706
342
|
var PLACEHOLDER_RE = /\{[^{}]+\}/g;
|
|
1707
343
|
function resolveTemplate(template, params, element) {
|
|
@@ -1997,8 +633,7 @@ async function executePointSingle(op, key2, params) {
|
|
|
1997
633
|
}
|
|
1998
634
|
async function executeRangeSingle(op, key2, params) {
|
|
1999
635
|
const innerAfter = params.after !== void 0 ? decodePerKeyCursor(params.after, key2) : void 0;
|
|
2000
|
-
const result = await executeListInternal(modelClassOf(op), key2, {
|
|
2001
|
-
select: selectOf(op),
|
|
636
|
+
const result = await executeListInternal(modelClassOf(op), key2, selectOf(op), {
|
|
2002
637
|
limit: params.limit,
|
|
2003
638
|
after: innerAfter,
|
|
2004
639
|
order: params.order
|
|
@@ -2149,437 +784,6 @@ async function executeRangeFanout(op, keys, params = {}) {
|
|
|
2149
784
|
return out;
|
|
2150
785
|
}
|
|
2151
786
|
|
|
2152
|
-
// src/runtime/command-runtime.ts
|
|
2153
|
-
function resolveCommandMethod(contract, methodName) {
|
|
2154
|
-
const method = contract.methods[methodName];
|
|
2155
|
-
if (method === void 0) {
|
|
2156
|
-
throw new Error(
|
|
2157
|
-
`Contract runtime: command method '${methodName}' does not exist on this contract. Available methods: ${Object.keys(contract.methods).sort().join(", ") || "(none)"}.`
|
|
2158
|
-
);
|
|
2159
|
-
}
|
|
2160
|
-
return method;
|
|
2161
|
-
}
|
|
2162
|
-
function modelClassOf2(op) {
|
|
2163
|
-
return op.entity.modelClass;
|
|
2164
|
-
}
|
|
2165
|
-
function modelStaticOf(op) {
|
|
2166
|
-
return op.entity.modelClass.asModel();
|
|
2167
|
-
}
|
|
2168
|
-
function renderLeaf(leaf, key2, params, context) {
|
|
2169
|
-
if (isContractParamRef(leaf)) {
|
|
2170
|
-
if (!(leaf.field in params)) {
|
|
2171
|
-
throw new Error(
|
|
2172
|
-
`Contract runtime: ${context} references params field '${leaf.field}', which was not supplied. Pass it in the method's params argument.`
|
|
2173
|
-
);
|
|
2174
|
-
}
|
|
2175
|
-
return params[leaf.field];
|
|
2176
|
-
}
|
|
2177
|
-
if (isContractKeyFieldRef(leaf)) {
|
|
2178
|
-
if (leaf.field in key2) return key2[leaf.field];
|
|
2179
|
-
if (leaf.field in params) return params[leaf.field];
|
|
2180
|
-
throw new Error(
|
|
2181
|
-
`Contract runtime: ${context} references key field '${leaf.field}', which was not supplied. Pass it in the method's key or params argument.`
|
|
2182
|
-
);
|
|
2183
|
-
}
|
|
2184
|
-
return leaf;
|
|
2185
|
-
}
|
|
2186
|
-
function renderRecord(structure, key2, params, context) {
|
|
2187
|
-
const out = {};
|
|
2188
|
-
if (structure === void 0) return out;
|
|
2189
|
-
for (const [field2, leaf] of Object.entries(structure)) {
|
|
2190
|
-
out[field2] = renderLeaf(leaf, key2, params, `${context} field '${field2}'`);
|
|
2191
|
-
}
|
|
2192
|
-
return out;
|
|
2193
|
-
}
|
|
2194
|
-
function renderCondition(condition, key2, params, context) {
|
|
2195
|
-
if (condition === void 0) return void 0;
|
|
2196
|
-
if (typeof condition === "object" && condition !== null) {
|
|
2197
|
-
const obj = condition;
|
|
2198
|
-
if (obj.notExists === true) return { notExists: true };
|
|
2199
|
-
if (typeof obj.attributeExists === "string") {
|
|
2200
|
-
return { attributeExists: obj.attributeExists };
|
|
2201
|
-
}
|
|
2202
|
-
if (typeof obj.attributeNotExists === "string") {
|
|
2203
|
-
return { attributeNotExists: obj.attributeNotExists };
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
return renderRecord(
|
|
2207
|
-
condition,
|
|
2208
|
-
key2,
|
|
2209
|
-
params,
|
|
2210
|
-
`${context} condition`
|
|
2211
|
-
);
|
|
2212
|
-
}
|
|
2213
|
-
function renderWrite(op, key2, params, contextLabel) {
|
|
2214
|
-
if (!isContractKeyRef(op.keys) && op.operation !== "put") {
|
|
2215
|
-
throw new Error(
|
|
2216
|
-
`Contract runtime: ${contextLabel} did not pass the contract key directly into its '${op.operation}' key position. A command method must call \`Model.${op.operation}(keys, \u2026)\` with the key whole.`
|
|
2217
|
-
);
|
|
2218
|
-
}
|
|
2219
|
-
const base = {
|
|
2220
|
-
operation: op.operation,
|
|
2221
|
-
modelClass: modelClassOf2(op),
|
|
2222
|
-
modelStatic: modelStaticOf(op),
|
|
2223
|
-
key: key2,
|
|
2224
|
-
condition: renderCondition(op.condition, key2, params, contextLabel)
|
|
2225
|
-
};
|
|
2226
|
-
if (op.operation === "put") {
|
|
2227
|
-
return { ...base, item: renderRecord(op.item, key2, params, `${contextLabel} item`) };
|
|
2228
|
-
}
|
|
2229
|
-
if (op.operation === "update") {
|
|
2230
|
-
return { ...base, changes: renderRecord(op.changes, key2, params, `${contextLabel} changes`) };
|
|
2231
|
-
}
|
|
2232
|
-
return base;
|
|
2233
|
-
}
|
|
2234
|
-
async function executeSingleWrite(w) {
|
|
2235
|
-
const options = w.condition !== void 0 ? { condition: w.condition } : void 0;
|
|
2236
|
-
if (w.operation === "put") {
|
|
2237
|
-
await executePut(w.modelClass, w.item ?? {}, options);
|
|
2238
|
-
return;
|
|
2239
|
-
}
|
|
2240
|
-
if (w.operation === "update") {
|
|
2241
|
-
await executeUpdate(w.modelClass, w.key, w.changes ?? {}, options);
|
|
2242
|
-
return;
|
|
2243
|
-
}
|
|
2244
|
-
await executeDelete(w.modelClass, w.key, options);
|
|
2245
|
-
}
|
|
2246
|
-
async function readBackProjection(write, returnSelection) {
|
|
2247
|
-
const select = {};
|
|
2248
|
-
for (const field2 of Object.keys(returnSelection)) {
|
|
2249
|
-
if (returnSelection[field2] === true) select[field2] = true;
|
|
2250
|
-
}
|
|
2251
|
-
const key2 = readBackKey(write);
|
|
2252
|
-
return executeQuery(write.modelClass, key2, select, { consistentRead: true });
|
|
2253
|
-
}
|
|
2254
|
-
function readBackKey(write) {
|
|
2255
|
-
if (write.operation !== "put") return write.key;
|
|
2256
|
-
const metadata = MetadataRegistry.get(write.modelClass);
|
|
2257
|
-
const item = write.item ?? {};
|
|
2258
|
-
const key2 = {};
|
|
2259
|
-
const fields = metadata.primaryKey ? metadata.primaryKey.inputFieldNames : Object.keys(item);
|
|
2260
|
-
for (const field2 of fields) {
|
|
2261
|
-
if (field2 in item) key2[field2] = item[field2];
|
|
2262
|
-
}
|
|
2263
|
-
return key2;
|
|
2264
|
-
}
|
|
2265
|
-
function renderConditionCheck(check, key2, params, contextLabel) {
|
|
2266
|
-
const modelClass = check.entity.modelClass;
|
|
2267
|
-
const referencedKey = {};
|
|
2268
|
-
for (const [targetField, inputField] of Object.entries(check.keyBinding)) {
|
|
2269
|
-
if (inputField in params) {
|
|
2270
|
-
referencedKey[targetField] = params[inputField];
|
|
2271
|
-
} else if (inputField in key2) {
|
|
2272
|
-
referencedKey[targetField] = key2[inputField];
|
|
2273
|
-
} else {
|
|
2274
|
-
throw new Error(
|
|
2275
|
-
`Contract runtime: ${contextLabel} asserts '${check.entity.name}' exists, keyed by input field '${inputField}', which was not supplied. Pass it in the method's params (or key) argument.`
|
|
2276
|
-
);
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
2279
|
-
const { TableName, Key } = buildDeleteInput(
|
|
2280
|
-
modelClass,
|
|
2281
|
-
referencedKey
|
|
2282
|
-
);
|
|
2283
|
-
const cond2 = buildConditionExpression({ attributeExists: "PK" });
|
|
2284
|
-
const checkInput = {
|
|
2285
|
-
TableName,
|
|
2286
|
-
Key,
|
|
2287
|
-
ConditionExpression: cond2.expression
|
|
2288
|
-
};
|
|
2289
|
-
if (Object.keys(cond2.names).length > 0) checkInput.ExpressionAttributeNames = cond2.names;
|
|
2290
|
-
if (Object.keys(cond2.values).length > 0) checkInput.ExpressionAttributeValues = cond2.values;
|
|
2291
|
-
return checkInput;
|
|
2292
|
-
}
|
|
2293
|
-
var RUNTIME_TOKEN_RE = /\{([^{}]+)\}/g;
|
|
2294
|
-
function renderTemplate(template, key2, params, contextLabel) {
|
|
2295
|
-
const resolveToken = (token) => {
|
|
2296
|
-
if (token in params) return params[token];
|
|
2297
|
-
if (token in key2) return key2[token];
|
|
2298
|
-
throw new Error(
|
|
2299
|
-
`Contract runtime: ${contextLabel} references input field '${token}', which was not supplied. Pass it in the method's params (or key) argument` + (token.startsWith("old.") ? ` \u2014 an \`old.*\` token is the pre-mutation value of a foreign key on an onUpdateKeyChange (pass it as \`{ '${token}': \u2026 }\`).` : `.`)
|
|
2300
|
-
);
|
|
2301
|
-
};
|
|
2302
|
-
const whole = /^\{([^{}]+)\}$/.exec(template);
|
|
2303
|
-
if (whole !== null) return resolveToken(whole[1]);
|
|
2304
|
-
return template.replace(RUNTIME_TOKEN_RE, (_m, token) => String(resolveToken(token)));
|
|
2305
|
-
}
|
|
2306
|
-
function renderTemplateRecord(record, key2, params, contextLabel) {
|
|
2307
|
-
const out = {};
|
|
2308
|
-
for (const [field2, template] of Object.entries(record ?? {})) {
|
|
2309
|
-
out[field2] = renderTemplate(template, key2, params, contextLabel);
|
|
2310
|
-
}
|
|
2311
|
-
return out;
|
|
2312
|
-
}
|
|
2313
|
-
function renderEdgeWriteItems(edge, key2, params, contextLabel, modelBySignature) {
|
|
2314
|
-
const adjacency = edge.entity.modelClass;
|
|
2315
|
-
return edge.items.map((item) => {
|
|
2316
|
-
if (item.type === "Put") {
|
|
2317
|
-
const rendered = renderTemplateRecord(item.item, key2, params, `${contextLabel} edge Put`);
|
|
2318
|
-
const out2 = { Put: buildPutInput(adjacency, rendered) };
|
|
2319
|
-
modelBySignature?.set(execItemKeySignature(out2), adjacency.name);
|
|
2320
|
-
return out2;
|
|
2321
|
-
}
|
|
2322
|
-
const renderedKey = renderTemplateRecord(
|
|
2323
|
-
item.keyCondition,
|
|
2324
|
-
key2,
|
|
2325
|
-
params,
|
|
2326
|
-
`${contextLabel} edge Delete`
|
|
2327
|
-
);
|
|
2328
|
-
const del = {
|
|
2329
|
-
TableName: TableMapping.resolve(item.tableName),
|
|
2330
|
-
Key: renderedKey
|
|
2331
|
-
};
|
|
2332
|
-
const out = { Delete: del };
|
|
2333
|
-
modelBySignature?.set(execItemKeySignature(out), adjacency.name);
|
|
2334
|
-
return out;
|
|
2335
|
-
});
|
|
2336
|
-
}
|
|
2337
|
-
function renderDerivedUpdate(update, key2, params, contextLabel, modelBySignature) {
|
|
2338
|
-
const metadata = MetadataRegistry.get(update.entity.modelClass);
|
|
2339
|
-
if (!metadata.primaryKey) {
|
|
2340
|
-
throw new Error(
|
|
2341
|
-
`Contract runtime: ${contextLabel} derives a counter update on '${update.entity.name}', which has no primary key.`
|
|
2342
|
-
);
|
|
2343
|
-
}
|
|
2344
|
-
const keyInput = {};
|
|
2345
|
-
for (const [targetField, inputField] of Object.entries(update.keyBinding)) {
|
|
2346
|
-
keyInput[targetField] = renderTemplate(
|
|
2347
|
-
`{${inputField}}`,
|
|
2348
|
-
key2,
|
|
2349
|
-
params,
|
|
2350
|
-
`${contextLabel} derived-update key`
|
|
2351
|
-
);
|
|
2352
|
-
}
|
|
2353
|
-
const { TableName, Key } = buildDeleteInput(
|
|
2354
|
-
update.entity.modelClass,
|
|
2355
|
-
keyInput
|
|
2356
|
-
);
|
|
2357
|
-
const updateInput = {
|
|
2358
|
-
TableName,
|
|
2359
|
-
Key,
|
|
2360
|
-
UpdateExpression: "ADD #a0 :a0",
|
|
2361
|
-
ExpressionAttributeNames: { "#a0": update.attribute },
|
|
2362
|
-
ExpressionAttributeValues: { ":a0": update.amount }
|
|
2363
|
-
};
|
|
2364
|
-
const out = { Update: updateInput };
|
|
2365
|
-
modelBySignature?.set(execItemKeySignature(out), update.entity.modelClass.name);
|
|
2366
|
-
return out;
|
|
2367
|
-
}
|
|
2368
|
-
function renderUniqueGuardItems(guard, key2, params, contextLabel) {
|
|
2369
|
-
return guard.items.map((item) => {
|
|
2370
|
-
if (item.type === "Put") {
|
|
2371
|
-
const Item = renderTemplateRecord(item.item, key2, params, `${contextLabel} guard Put`);
|
|
2372
|
-
const put = { TableName: TableMapping.resolve(item.tableName), Item };
|
|
2373
|
-
const cond2 = buildConditionExpression({ attributeNotExists: "PK" });
|
|
2374
|
-
put.ConditionExpression = cond2.expression;
|
|
2375
|
-
if (Object.keys(cond2.names).length > 0) put.ExpressionAttributeNames = cond2.names;
|
|
2376
|
-
if (Object.keys(cond2.values).length > 0) put.ExpressionAttributeValues = cond2.values;
|
|
2377
|
-
return { Put: put };
|
|
2378
|
-
}
|
|
2379
|
-
const Key = renderTemplateRecord(item.keyCondition, key2, params, `${contextLabel} guard Delete`);
|
|
2380
|
-
const del = { TableName: TableMapping.resolve(item.tableName), Key };
|
|
2381
|
-
return { Delete: del };
|
|
2382
|
-
});
|
|
2383
|
-
}
|
|
2384
|
-
function renderOutboxEventItems(event, key2, params, contextLabel) {
|
|
2385
|
-
return event.items.map((item) => {
|
|
2386
|
-
const Item = renderTemplateRecord(item.item, key2, params, `${contextLabel} outbox Put`);
|
|
2387
|
-
const put = { TableName: TableMapping.resolve(item.tableName), Item };
|
|
2388
|
-
return { Put: put };
|
|
2389
|
-
});
|
|
2390
|
-
}
|
|
2391
|
-
function renderIdempotencyGuardItems(guard, key2, params, contextLabel) {
|
|
2392
|
-
return guard.items.map((item) => {
|
|
2393
|
-
const Item = renderTemplateRecord(item.item, key2, params, `${contextLabel} idempotency Put`);
|
|
2394
|
-
const put = { TableName: TableMapping.resolve(item.tableName), Item };
|
|
2395
|
-
const cond2 = buildConditionExpression({ attributeNotExists: "PK" });
|
|
2396
|
-
put.ConditionExpression = cond2.expression;
|
|
2397
|
-
if (Object.keys(cond2.names).length > 0) put.ExpressionAttributeNames = cond2.names;
|
|
2398
|
-
if (Object.keys(cond2.values).length > 0) put.ExpressionAttributeValues = cond2.values;
|
|
2399
|
-
return { Put: put };
|
|
2400
|
-
});
|
|
2401
|
-
}
|
|
2402
|
-
async function executeReferentialTransaction(writes, edgeItems, updateItems, guardItemsRaw, outboxItems, idempotencyItems, checks, methodName, modelBySignature = /* @__PURE__ */ new Map()) {
|
|
2403
|
-
const writeItems = writes.map((w) => {
|
|
2404
|
-
const options = w.condition !== void 0 ? { condition: w.condition } : void 0;
|
|
2405
|
-
let item;
|
|
2406
|
-
if (w.operation === "put") {
|
|
2407
|
-
item = { Put: buildPutInput(w.modelClass, w.item ?? {}, options) };
|
|
2408
|
-
} else if (w.operation === "update") {
|
|
2409
|
-
item = { Update: buildUpdateInput(w.modelClass, w.key, w.changes ?? {}, options) };
|
|
2410
|
-
} else {
|
|
2411
|
-
item = { Delete: buildDeleteInput(w.modelClass, w.key, options) };
|
|
2412
|
-
}
|
|
2413
|
-
modelBySignature.set(execItemKeySignature(item), w.modelClass.name);
|
|
2414
|
-
return item;
|
|
2415
|
-
});
|
|
2416
|
-
const checkItems = checks.map((c) => ({ ConditionCheck: c.check }));
|
|
2417
|
-
await commitTransaction(
|
|
2418
|
-
[
|
|
2419
|
-
...writeItems,
|
|
2420
|
-
...edgeItems,
|
|
2421
|
-
...updateItems,
|
|
2422
|
-
...guardItemsRaw,
|
|
2423
|
-
...outboxItems,
|
|
2424
|
-
...idempotencyItems,
|
|
2425
|
-
...checkItems
|
|
2426
|
-
],
|
|
2427
|
-
{
|
|
2428
|
-
modelBySignature,
|
|
2429
|
-
limitError: (count) => new Error(
|
|
2430
|
-
`Contract runtime: command method '${methodName}' composes ${count} items (writes + edges + derived updates + uniqueness guards + outbox events + idempotency guard + ConditionChecks) into one TransactWriteItems, but DynamoDB caps a transaction at ${MAX_TRANSACT_ITEMS} items and a transaction is atomic \u2014 it cannot be split. Reduce the fragment / effect count.`
|
|
2431
|
-
)
|
|
2432
|
-
}
|
|
2433
|
-
);
|
|
2434
|
-
}
|
|
2435
|
-
async function executeTransactWrites(writes, methodName) {
|
|
2436
|
-
if (writes.length === 0) return;
|
|
2437
|
-
const modelBySignature = /* @__PURE__ */ new Map();
|
|
2438
|
-
const transactItems = writes.map((w) => {
|
|
2439
|
-
const options = w.condition !== void 0 ? { condition: w.condition } : void 0;
|
|
2440
|
-
let item;
|
|
2441
|
-
if (w.operation === "put") {
|
|
2442
|
-
item = { Put: buildPutInput(w.modelClass, w.item ?? {}, options) };
|
|
2443
|
-
} else if (w.operation === "update") {
|
|
2444
|
-
item = { Update: buildUpdateInput(w.modelClass, w.key, w.changes ?? {}, options) };
|
|
2445
|
-
} else {
|
|
2446
|
-
item = { Delete: buildDeleteInput(w.modelClass, w.key, options) };
|
|
2447
|
-
}
|
|
2448
|
-
modelBySignature.set(execItemKeySignature(item), w.modelClass.name);
|
|
2449
|
-
return item;
|
|
2450
|
-
});
|
|
2451
|
-
await commitTransaction(transactItems, {
|
|
2452
|
-
modelBySignature,
|
|
2453
|
-
limitError: (count) => new Error(
|
|
2454
|
-
`Contract runtime: command method '${methodName}' resolves an array of ${count} keys to a 'transact' (TransactWriteItems) batch, but DynamoDB caps a transaction at ${MAX_TRANSACT_ITEMS} items and a transaction is atomic \u2014 it cannot be split across requests without breaking atomicity. Pass \u2264${MAX_TRANSACT_ITEMS} keys, or declare \`batch: 'batchWrite'\` (a non-atomic BatchWriteItem, chunked automatically) if atomicity is not required.`
|
|
2455
|
-
)
|
|
2456
|
-
});
|
|
2457
|
-
}
|
|
2458
|
-
async function executeBatchWrites(writes, methodName) {
|
|
2459
|
-
if (writes.length === 0) return;
|
|
2460
|
-
const requests = writes.map((w) => {
|
|
2461
|
-
if (w.operation === "put") {
|
|
2462
|
-
return { type: "put", model: w.modelStatic, item: w.item ?? {} };
|
|
2463
|
-
}
|
|
2464
|
-
if (w.operation === "delete") {
|
|
2465
|
-
return { type: "delete", model: w.modelStatic, key: w.key };
|
|
2466
|
-
}
|
|
2467
|
-
throw new Error(
|
|
2468
|
-
`Contract runtime: command method '${methodName}' resolves to an 'update', but \`batch: 'batchWrite'\` only supports put / delete (DynamoDB's BatchWriteItem has no Update request). Declare \`batch: 'transact'\` for a batched update.`
|
|
2469
|
-
);
|
|
2470
|
-
});
|
|
2471
|
-
await executeBatchWrite(requests);
|
|
2472
|
-
}
|
|
2473
|
-
async function executeCommandMethod(contract, methodName, keyOrKeys, params = {}) {
|
|
2474
|
-
const method = resolveCommandMethod(contract, methodName);
|
|
2475
|
-
if (Array.isArray(keyOrKeys)) {
|
|
2476
|
-
const keys = keyOrKeys;
|
|
2477
|
-
if (method.ops !== void 0 && method.ops.length > 1) {
|
|
2478
|
-
throw new Error(
|
|
2479
|
-
`Contract runtime: command method '${methodName}' is a multi-fragment mutation (it composes ${method.ops.length} fragments into one atomic TransactWriteItems) and accepts a single key only. Call it once per target, not with a key array.`
|
|
2480
|
-
);
|
|
2481
|
-
}
|
|
2482
|
-
const hasDerivedEffects2 = method.conditionChecks !== void 0 && method.conditionChecks.length > 0 || method.edgeWrites !== void 0 && method.edgeWrites.length > 0 || method.derivedUpdates !== void 0 && method.derivedUpdates.length > 0 || method.uniqueGuards !== void 0 && method.uniqueGuards.length > 0 || method.outboxEvents !== void 0 && method.outboxEvents.length > 0 || method.idempotencyGuard !== void 0;
|
|
2483
|
-
if (hasDerivedEffects2) {
|
|
2484
|
-
throw new Error(
|
|
2485
|
-
`Contract runtime: command method '${methodName}' carries derived effects (referential integrity / edge writes / derived counters / uniqueness guards / outbox events / idempotency guard \u2014 it composes its write + those items into one atomic TransactWriteItems) and accepts a single key only. Call it once per target, not with a key array.`
|
|
2486
|
-
);
|
|
2487
|
-
}
|
|
2488
|
-
if (method.batch === void 0) {
|
|
2489
|
-
throw new Error(
|
|
2490
|
-
`Contract runtime: command method '${methodName}' was called with an array of keys, but it declares no batched-write form. Declare \`batch: 'transact'\` (atomic TransactWriteItems, \u226425) or \`batch: 'batchWrite'\` (non-atomic BatchWriteItem) on the method body, or call it with a single key.`
|
|
2491
|
-
);
|
|
2492
|
-
}
|
|
2493
|
-
const writes = keys.map(
|
|
2494
|
-
(key3, i) => renderWrite(method.op, key3, params, `command method '${methodName}' (key #${i})`)
|
|
2495
|
-
);
|
|
2496
|
-
if (method.batch === "transact") {
|
|
2497
|
-
await executeTransactWrites(writes, methodName);
|
|
2498
|
-
} else {
|
|
2499
|
-
await executeBatchWrites(writes, methodName);
|
|
2500
|
-
}
|
|
2501
|
-
return;
|
|
2502
|
-
}
|
|
2503
|
-
const key2 = keyOrKeys;
|
|
2504
|
-
const conditionChecks = method.conditionChecks ?? [];
|
|
2505
|
-
const edgeWrites2 = method.edgeWrites ?? [];
|
|
2506
|
-
const derivedUpdates = method.derivedUpdates ?? [];
|
|
2507
|
-
const uniqueGuards = method.uniqueGuards ?? [];
|
|
2508
|
-
const outboxEvents = method.outboxEvents ?? [];
|
|
2509
|
-
const idempotencyGuard = method.idempotencyGuard;
|
|
2510
|
-
const hasDerivedEffects = conditionChecks.length > 0 || edgeWrites2.length > 0 || derivedUpdates.length > 0 || uniqueGuards.length > 0 || outboxEvents.length > 0 || idempotencyGuard !== void 0;
|
|
2511
|
-
const isMultiFragment = method.ops !== void 0 && method.ops.length > 1;
|
|
2512
|
-
if (isMultiFragment || hasDerivedEffects) {
|
|
2513
|
-
const ops = method.ops ?? [method.op];
|
|
2514
|
-
const writes = ops.map(
|
|
2515
|
-
(opn, i) => renderWrite(opn, key2, params, `command method '${methodName}' (fragment #${i})`)
|
|
2516
|
-
);
|
|
2517
|
-
if (hasDerivedEffects) {
|
|
2518
|
-
const modelBySignature = /* @__PURE__ */ new Map();
|
|
2519
|
-
const edgeItems = edgeWrites2.flatMap(
|
|
2520
|
-
(edge, i) => renderEdgeWriteItems(
|
|
2521
|
-
edge,
|
|
2522
|
-
key2,
|
|
2523
|
-
params,
|
|
2524
|
-
`command method '${methodName}' (edge #${i})`,
|
|
2525
|
-
modelBySignature
|
|
2526
|
-
)
|
|
2527
|
-
);
|
|
2528
|
-
const updateItems = derivedUpdates.map(
|
|
2529
|
-
(update, i) => renderDerivedUpdate(
|
|
2530
|
-
update,
|
|
2531
|
-
key2,
|
|
2532
|
-
params,
|
|
2533
|
-
`command method '${methodName}' (derive #${i})`,
|
|
2534
|
-
modelBySignature
|
|
2535
|
-
)
|
|
2536
|
-
);
|
|
2537
|
-
const guardItems = uniqueGuards.flatMap(
|
|
2538
|
-
(guard, i) => renderUniqueGuardItems(guard, key2, params, `command method '${methodName}' (unique #${i})`)
|
|
2539
|
-
);
|
|
2540
|
-
const outboxItems = outboxEvents.flatMap(
|
|
2541
|
-
(event, i) => renderOutboxEventItems(event, key2, params, `command method '${methodName}' (emit #${i})`)
|
|
2542
|
-
);
|
|
2543
|
-
const idempotencyItems = idempotencyGuard !== void 0 ? renderIdempotencyGuardItems(
|
|
2544
|
-
idempotencyGuard,
|
|
2545
|
-
key2,
|
|
2546
|
-
params,
|
|
2547
|
-
`command method '${methodName}' (idempotency)`
|
|
2548
|
-
) : [];
|
|
2549
|
-
const checks = conditionChecks.map((check, i) => ({
|
|
2550
|
-
check: renderConditionCheck(
|
|
2551
|
-
check,
|
|
2552
|
-
key2,
|
|
2553
|
-
params,
|
|
2554
|
-
`command method '${methodName}' (requires #${i})`
|
|
2555
|
-
)
|
|
2556
|
-
}));
|
|
2557
|
-
await executeReferentialTransaction(
|
|
2558
|
-
writes,
|
|
2559
|
-
edgeItems,
|
|
2560
|
-
updateItems,
|
|
2561
|
-
guardItems,
|
|
2562
|
-
outboxItems,
|
|
2563
|
-
idempotencyItems,
|
|
2564
|
-
checks,
|
|
2565
|
-
methodName,
|
|
2566
|
-
modelBySignature
|
|
2567
|
-
);
|
|
2568
|
-
} else {
|
|
2569
|
-
await executeTransactWrites(writes, methodName);
|
|
2570
|
-
}
|
|
2571
|
-
if (method.returnSelection !== void 0 && Object.keys(method.returnSelection).length > 0) {
|
|
2572
|
-
return readBackProjection(writes[0], method.returnSelection);
|
|
2573
|
-
}
|
|
2574
|
-
return;
|
|
2575
|
-
}
|
|
2576
|
-
const write = renderWrite(method.op, key2, params, `command method '${methodName}'`);
|
|
2577
|
-
await executeSingleWrite(write);
|
|
2578
|
-
if (method.returnSelection !== void 0 && Object.keys(method.returnSelection).length > 0) {
|
|
2579
|
-
return readBackProjection(write, method.returnSelection);
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
|
|
2583
787
|
// src/linter/rules/query-boundary.ts
|
|
2584
788
|
var queryBoundaryRule = {
|
|
2585
789
|
id: "query-boundary",
|
|
@@ -2770,7 +974,6 @@ export {
|
|
|
2770
974
|
buildUpdateInput,
|
|
2771
975
|
collectContractBoundaryViolations,
|
|
2772
976
|
collectContractN1Violations,
|
|
2773
|
-
command,
|
|
2774
977
|
compileFilterExpression,
|
|
2775
978
|
compileFragment,
|
|
2776
979
|
compileMutationPlan,
|
|
@@ -2830,7 +1033,6 @@ export {
|
|
|
2830
1033
|
hasOne,
|
|
2831
1034
|
hydrate,
|
|
2832
1035
|
isColumn,
|
|
2833
|
-
isCommandBatchMode,
|
|
2834
1036
|
isCommandModelContract,
|
|
2835
1037
|
isCommandPlan,
|
|
2836
1038
|
isContractComposeNode,
|
|
@@ -2865,12 +1067,10 @@ export {
|
|
|
2865
1067
|
numberSet,
|
|
2866
1068
|
param,
|
|
2867
1069
|
plan,
|
|
2868
|
-
point,
|
|
2869
1070
|
publicCommandModel,
|
|
2870
1071
|
publicQueryModel,
|
|
2871
1072
|
query,
|
|
2872
1073
|
queryBoundaryRule,
|
|
2873
|
-
range,
|
|
2874
1074
|
relationDepthRule,
|
|
2875
1075
|
requireLimitRule,
|
|
2876
1076
|
resolveKey,
|