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/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
- buildProject,
21
+ buildProjection,
20
22
  buildQuerySpec,
21
- buildRelation,
22
- buildRelationExecutionPlan,
23
23
  buildTransactionSpec,
24
24
  buildTransactions,
25
25
  collectContractBoundaryViolations,
26
26
  collectContractN1Violations,
27
- command,
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
- isCommandBatchMode,
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
- point,
82
+ plan,
74
83
  publicCommandModel,
75
84
  publicQueryModel,
76
85
  query,
77
- range,
78
- recordContractOp,
79
86
  resolveLifecycle,
80
- stageOfResultPath,
87
+ resolveRelations,
88
+ validateDepth,
81
89
  when,
82
90
  wholeKeysSentinel
83
- } from "./chunk-6LEHSX45.js";
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,