graphddb 0.1.1 → 0.2.1

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
+ conditionParamName,
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,
@@ -56,10 +68,8 @@ import {
56
68
  isLifecycleContract,
57
69
  isMutationFragment,
58
70
  isMutationInputRef,
59
- isParam,
60
71
  isPlannedCommandMethod,
61
72
  isQueryModelContract,
62
- isRecordingContractMethod,
63
73
  isSelectBuilder,
64
74
  isTransactionRef,
65
75
  lifecyclePhaseForIntent,
@@ -67,28 +77,23 @@ import {
67
77
  mintContractKeyFieldRef,
68
78
  mintContractParamRef,
69
79
  mutation,
70
- normalizeSelectSpec,
71
- normalizeTopLevelSelect,
72
- param,
73
- point,
80
+ plan,
74
81
  publicCommandModel,
75
82
  publicQueryModel,
76
83
  query,
77
- range,
78
- recordContractOp,
79
84
  resolveLifecycle,
80
- stageOfResultPath,
85
+ resolveRelations,
86
+ validateDepth,
81
87
  when,
82
88
  wholeKeysSentinel
83
- } from "./chunk-6LEHSX45.js";
89
+ } from "./chunk-4B4MUPUJ.js";
84
90
  import {
85
91
  CdcEmulator,
86
92
  createCdcEmulator
87
- } from "./chunk-UNRQ5YJT.js";
93
+ } from "./chunk-SBNP62H7.js";
88
94
  import {
89
95
  BATCH_GET_MAX_KEYS,
90
96
  BATCH_WRITE_MAX_ITEMS,
91
- ChangeCaptureRegistry,
92
97
  ClientManager,
93
98
  DynamoExecutor,
94
99
  Linter,
@@ -96,17 +101,16 @@ import {
96
101
  MetadataRegistry,
97
102
  TableMapping,
98
103
  TransactionContext,
99
- attachHiddenKey,
100
104
  attachModelClass,
101
105
  buildConditionExpression,
102
106
  buildDeleteInput,
103
107
  buildPutInput,
104
108
  buildUpdateExpression,
105
109
  buildUpdateInput,
106
- captureWrite,
107
110
  collapseSameKeyItems,
111
+ collectRawConditionParams,
108
112
  commitTransaction,
109
- createColumnMap,
113
+ cond,
110
114
  createDefaultLinter,
111
115
  execItemKeySignature,
112
116
  executeDelete,
@@ -117,17 +121,20 @@ import {
117
121
  gsiAmbiguityRule,
118
122
  isColumn,
119
123
  isKeySegment,
124
+ isParam,
125
+ isRawCondition,
120
126
  k,
121
127
  key,
122
128
  missingGsiRule,
123
129
  noScanRule,
130
+ param,
124
131
  relationDepthRule,
125
132
  requireLimitRule,
133
+ resolveConditionTree,
126
134
  resolveKey,
127
135
  resolveModelClass,
128
- resolveSegmentedKey,
129
136
  serializeFieldValue
130
- } from "./chunk-347U24SB.js";
137
+ } from "./chunk-PWV7JDMR.js";
131
138
 
132
139
  // src/metadata/prefix.ts
133
140
  function derivePrefix(customPrefix, className) {
@@ -276,1431 +283,64 @@ var numberSet = createFieldDecorator("NS");
276
283
  var list = createFieldDecorator("L");
277
284
  var map = createFieldDecorator("M");
278
285
 
279
- // src/decorators/embedded.ts
280
- function embedded(modelFactory) {
281
- return function(_value, context) {
282
- collectEmbedded({
283
- propertyName: String(context.name),
284
- modelFactory
285
- });
286
- };
287
- }
288
-
289
- // src/decorators/relation.ts
290
- var RESERVED_RELATION_PROPERTY_NAMES = /* @__PURE__ */ new Set([
291
- "items"
292
- ]);
293
- function assertRelationPropertyNameAllowed(propertyName) {
294
- if (RESERVED_RELATION_PROPERTY_NAMES.has(propertyName)) {
295
- throw new Error(
296
- `Relation property name '${propertyName}' is reserved: it collides with the relation result-path grammar token '${propertyName}', which the planner and runtimes use to mark a hasMany connection (a relation writes to '$.<prop>.items'). A relation named '${propertyName}' would be mis-staged at runtime. Rename the relation property (reserved names: ${[...RESERVED_RELATION_PROPERTY_NAMES].map((n) => `'${n}'`).join(", ")}).`
297
- );
298
- }
299
- }
300
- function hasMany(targetFactory, keyBinding, options) {
301
- return function(_value, context) {
302
- const propertyName = String(context.name);
303
- assertRelationPropertyNameAllowed(propertyName);
304
- collectRelation({
305
- type: "hasMany",
306
- propertyName,
307
- targetFactory,
308
- keyBinding,
309
- options
310
- });
311
- };
312
- }
313
- function belongsTo(targetFactory, keyBinding) {
314
- return function(_value, context) {
315
- const propertyName = String(context.name);
316
- assertRelationPropertyNameAllowed(propertyName);
317
- collectRelation({
318
- type: "belongsTo",
319
- propertyName,
320
- targetFactory,
321
- keyBinding
322
- });
323
- };
324
- }
325
- function hasOne(targetFactory, keyBinding) {
326
- return function(_value, context) {
327
- const propertyName = String(context.name);
328
- assertRelationPropertyNameAllowed(propertyName);
329
- collectRelation({
330
- type: "hasOne",
331
- propertyName,
332
- targetFactory,
333
- keyBinding
334
- });
335
- };
336
- }
337
-
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
- }
286
+ // src/decorators/embedded.ts
287
+ function embedded(modelFactory) {
288
+ return function(_value, context) {
289
+ collectEmbedded({
290
+ propertyName: String(context.name),
291
+ modelFactory
292
+ });
293
+ };
1586
294
  }
1587
295
 
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
296
+ // src/decorators/relation.ts
297
+ var RESERVED_RELATION_PROPERTY_NAMES = /* @__PURE__ */ new Set([
298
+ "items"
299
+ ]);
300
+ function assertRelationPropertyNameAllowed(propertyName) {
301
+ if (RESERVED_RELATION_PROPERTY_NAMES.has(propertyName)) {
302
+ throw new Error(
303
+ `Relation property name '${propertyName}' is reserved: it collides with the relation result-path grammar token '${propertyName}', which the planner and runtimes use to mark a hasMany connection (a relation writes to '$.<prop>.items'). A relation named '${propertyName}' would be mis-staged at runtime. Rename the relation property (reserved names: ${[...RESERVED_RELATION_PROPERTY_NAMES].map((n) => `'${n}'`).join(", ")}).`
1701
304
  );
1702
305
  }
1703
- };
306
+ }
307
+ function hasMany(targetFactory, keyBinding, options) {
308
+ return function(_value, context) {
309
+ const propertyName = String(context.name);
310
+ assertRelationPropertyNameAllowed(propertyName);
311
+ collectRelation({
312
+ type: "hasMany",
313
+ propertyName,
314
+ targetFactory,
315
+ keyBinding,
316
+ options
317
+ });
318
+ };
319
+ }
320
+ function belongsTo(targetFactory, keyBinding) {
321
+ return function(_value, context) {
322
+ const propertyName = String(context.name);
323
+ assertRelationPropertyNameAllowed(propertyName);
324
+ collectRelation({
325
+ type: "belongsTo",
326
+ propertyName,
327
+ targetFactory,
328
+ keyBinding
329
+ });
330
+ };
331
+ }
332
+ function hasOne(targetFactory, keyBinding) {
333
+ return function(_value, context) {
334
+ const propertyName = String(context.name);
335
+ assertRelationPropertyNameAllowed(propertyName);
336
+ collectRelation({
337
+ type: "hasOne",
338
+ propertyName,
339
+ targetFactory,
340
+ keyBinding
341
+ });
342
+ };
343
+ }
1704
344
 
1705
345
  // src/operations/declarative-transaction.ts
1706
346
  var PLACEHOLDER_RE = /\{[^{}]+\}/g;
@@ -1777,6 +417,12 @@ function whenHolds(when2, params, element) {
1777
417
  const right = resolveTemplate(when2.right, params, element);
1778
418
  return when2.op === "eq" ? left === right : left !== right;
1779
419
  }
420
+ function resolveRawConditionValue(val, params, element) {
421
+ if (val !== null && typeof val === "object" && typeof val.$param === "string") {
422
+ return resolveValue(`{${val.$param}}`, params, element);
423
+ }
424
+ return val;
425
+ }
1780
426
  function applyCondition(condition, target, params, element) {
1781
427
  if (!condition) return;
1782
428
  const names = target.ExpressionAttributeNames ??= {};
@@ -1792,6 +438,25 @@ function applyCondition(condition, target, params, element) {
1792
438
  return;
1793
439
  }
1794
440
  const values = target.ExpressionAttributeValues ??= {};
441
+ if (condition.kind === "raw") {
442
+ Object.assign(names, condition.names);
443
+ for (const [alias, val] of Object.entries(condition.values)) {
444
+ values[alias] = resolveRawConditionValue(val, params, element);
445
+ }
446
+ target.ConditionExpression = condition.expression;
447
+ return;
448
+ }
449
+ if (condition.kind === "expr") {
450
+ const concrete = resolveConditionTree(
451
+ condition.declarative,
452
+ (name) => resolveValue(`{${name}}`, params, element)
453
+ );
454
+ const compiled = buildConditionExpression(concrete);
455
+ Object.assign(names, compiled.names);
456
+ Object.assign(values, compiled.values);
457
+ target.ConditionExpression = compiled.expression;
458
+ return;
459
+ }
1795
460
  const clauses = [];
1796
461
  let i = 0;
1797
462
  for (const [field2, tmpl] of Object.entries(condition.fields)) {
@@ -1997,8 +662,7 @@ async function executePointSingle(op, key2, params) {
1997
662
  }
1998
663
  async function executeRangeSingle(op, key2, params) {
1999
664
  const innerAfter = params.after !== void 0 ? decodePerKeyCursor(params.after, key2) : void 0;
2000
- const result = await executeListInternal(modelClassOf(op), key2, {
2001
- select: selectOf(op),
665
+ const result = await executeListInternal(modelClassOf(op), key2, selectOf(op), {
2002
666
  limit: params.limit,
2003
667
  after: innerAfter,
2004
668
  order: params.order
@@ -2149,437 +813,6 @@ async function executeRangeFanout(op, keys, params = {}) {
2149
813
  return out;
2150
814
  }
2151
815
 
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
816
  // src/linter/rules/query-boundary.ts
2584
817
  var queryBoundaryRule = {
2585
818
  id: "query-boundary",
@@ -2628,11 +861,88 @@ function entityRef(model2) {
2628
861
  }
2629
862
  function collectConditionParams(condition, out) {
2630
863
  if (condition === void 0) return;
864
+ if (isRawCondition(condition)) {
865
+ for (const { name, param: param2 } of collectRawConditionParams(condition)) {
866
+ out[name] = descriptorFromParam(name, param2, out[name]);
867
+ }
868
+ return;
869
+ }
2631
870
  const obj = condition;
2632
871
  if (obj.notExists === true) return;
2633
872
  if (typeof obj.attributeExists === "string") return;
2634
873
  if (typeof obj.attributeNotExists === "string") return;
2635
- collectParams(condition, out);
874
+ collectConditionTreeParams(obj, out);
875
+ }
876
+ var SINGLE_OPERAND_OPS = /* @__PURE__ */ new Set([
877
+ "eq",
878
+ "ne",
879
+ "gt",
880
+ "ge",
881
+ "lt",
882
+ "le",
883
+ "beginsWith",
884
+ "contains",
885
+ "notContains",
886
+ "attributeType",
887
+ "size"
888
+ ]);
889
+ function collectConditionTreeParams(obj, out) {
890
+ for (const [key2, value] of Object.entries(obj)) {
891
+ if (value === void 0) continue;
892
+ if (key2 === "and" || key2 === "or") {
893
+ for (const sub of value) {
894
+ if (isRawCondition(sub)) {
895
+ for (const { name, param: param2 } of collectRawConditionParams(sub)) {
896
+ out[name] = descriptorFromParam(name, param2, out[name]);
897
+ }
898
+ continue;
899
+ }
900
+ collectConditionTreeParams(sub, out);
901
+ }
902
+ continue;
903
+ }
904
+ if (key2 === "not") {
905
+ if (isRawCondition(value)) {
906
+ for (const { name, param: param2 } of collectRawConditionParams(value)) {
907
+ out[name] = descriptorFromParam(name, param2, out[name]);
908
+ }
909
+ continue;
910
+ }
911
+ collectConditionTreeParams(value, out);
912
+ continue;
913
+ }
914
+ if (isParam(value)) {
915
+ out[key2] = descriptorFromParam(key2, value, out[key2]);
916
+ continue;
917
+ }
918
+ if (value === null || typeof value !== "object" || value instanceof Date) {
919
+ continue;
920
+ }
921
+ const ops = value;
922
+ for (const [op, opVal] of Object.entries(ops)) {
923
+ if (op === "between") {
924
+ const [lo, hi] = opVal;
925
+ if (isParam(lo)) {
926
+ const n = conditionParamName(key2, op, 0);
927
+ out[n] = descriptorFromParam(n, lo, out[n]);
928
+ }
929
+ if (isParam(hi)) {
930
+ const n = conditionParamName(key2, op, 1);
931
+ out[n] = descriptorFromParam(n, hi, out[n]);
932
+ }
933
+ } else if (op === "in") {
934
+ opVal.forEach((v, i) => {
935
+ if (isParam(v)) {
936
+ const n = conditionParamName(key2, op, i);
937
+ out[n] = descriptorFromParam(n, v, out[n]);
938
+ }
939
+ });
940
+ } else if (SINGLE_OPERAND_OPS.has(op) && isParam(opVal)) {
941
+ const n = conditionParamName(key2, op);
942
+ out[n] = descriptorFromParam(n, opVal, out[n]);
943
+ }
944
+ }
945
+ }
2636
946
  }
2637
947
  function makeDefinition(model2, operation, key2, select, changes, condition) {
2638
948
  const params = collectParams(key2);
@@ -2770,7 +1080,6 @@ export {
2770
1080
  buildUpdateInput,
2771
1081
  collectContractBoundaryViolations,
2772
1082
  collectContractN1Violations,
2773
- command,
2774
1083
  compileFilterExpression,
2775
1084
  compileFragment,
2776
1085
  compileMutationPlan,
@@ -2830,7 +1139,6 @@ export {
2830
1139
  hasOne,
2831
1140
  hydrate,
2832
1141
  isColumn,
2833
- isCommandBatchMode,
2834
1142
  isCommandModelContract,
2835
1143
  isCommandPlan,
2836
1144
  isContractComposeNode,
@@ -2865,12 +1173,10 @@ export {
2865
1173
  numberSet,
2866
1174
  param,
2867
1175
  plan,
2868
- point,
2869
1176
  publicCommandModel,
2870
1177
  publicQueryModel,
2871
1178
  query,
2872
1179
  queryBoundaryRule,
2873
- range,
2874
1180
  relationDepthRule,
2875
1181
  requireLimitRule,
2876
1182
  resolveKey,