graphddb 0.7.10 → 0.8.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.
Files changed (46) hide show
  1. package/README.md +6 -6
  2. package/dist/cdc/index.d.ts +389 -5
  3. package/dist/cdc/index.js +4 -4
  4. package/dist/{chunk-AD6ZQTTE.js → chunk-GS4C5VGO.js} +2 -6
  5. package/dist/{chunk-DFUKGU2Q.js → chunk-HNY2EJPV.js} +216 -229
  6. package/dist/{chunk-3ZU2VW3L.js → chunk-L2NEDS7U.js} +582 -781
  7. package/dist/chunk-L4QRCHRQ.js +278 -0
  8. package/dist/chunk-LAT64YCZ.js +1987 -0
  9. package/dist/chunk-S2NI4PBW.js +187 -0
  10. package/dist/{chunk-EOJDN3SA.js → chunk-T44OB5GU.js} +3757 -6138
  11. package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
  12. package/dist/cli.js +63 -254
  13. package/dist/index.d.ts +23 -1550
  14. package/dist/index.js +94 -1850
  15. package/dist/internal/index.d.ts +84 -0
  16. package/dist/internal/index.js +701 -0
  17. package/dist/{maintenance-view-adapter-BAZ9uBGe.d.ts → key-DZtjAQDh.d.ts} +573 -1817
  18. package/dist/linter/index.d.ts +39 -7
  19. package/dist/linter/index.js +22 -4
  20. package/dist/{registry-LWE54Sdc.d.ts → linter-DQY7gUEk.d.ts} +22 -22
  21. package/dist/prepared-artifact-HFealr1q.d.ts +281 -0
  22. package/dist/spec/index.d.ts +506 -5
  23. package/dist/spec/index.js +22 -18
  24. package/dist/testing/index.d.ts +2 -3
  25. package/dist/testing/index.js +4 -4
  26. package/dist/transform/index.d.ts +1 -1
  27. package/dist/transform/index.js +4 -4
  28. package/dist/{types-BQLzTEqh.d.ts → types-2PMXEn5x.d.ts} +8 -10
  29. package/dist/types-DW__-Icc.d.ts +450 -0
  30. package/docs/cdc-projection.md +5 -5
  31. package/docs/class-hydration.md +1 -1
  32. package/docs/cqrs-contract.md +79 -20
  33. package/docs/design-patterns.md +5 -5
  34. package/docs/docs-generation.md +6 -6
  35. package/docs/middleware.md +15 -15
  36. package/docs/mutation-command-derivation.md +52 -42
  37. package/docs/prepared-statements.md +14 -14
  38. package/docs/python-bridge.md +96 -65
  39. package/docs/spec.md +153 -124
  40. package/docs/testing.md +9 -8
  41. package/package.json +14 -4
  42. package/dist/chunk-3UD3XIF2.js +0 -860
  43. package/dist/chunk-MMVHOUM4.js +0 -24
  44. package/dist/from-change-Ty95KA8C.d.ts +0 -327
  45. package/dist/index-Dc7d8mWI.d.ts +0 -1089
  46. package/dist/relation-depth-BRS513Tq.d.ts +0 -36
@@ -5,7 +5,7 @@ import {
5
5
  createDefaultLinter,
6
6
  isColumn,
7
7
  isKeyDefinition
8
- } from "./chunk-PDUVTYC5.js";
8
+ } from "./chunk-XTWXMOHD.js";
9
9
 
10
10
  // src/decorators/gsi.ts
11
11
  var GSI_MARKER = /* @__PURE__ */ Symbol("graphddb:gsi");
@@ -314,6 +314,194 @@ async function batchWriteChunkWithRetry(docClient, tableName, entries, toCommand
314
314
  }
315
315
  }
316
316
 
317
+ // src/executor/batch-executor.ts
318
+ async function executeBatchGet(docClient, operation) {
319
+ if (operation.keys.length === 0) {
320
+ return { items: [] };
321
+ }
322
+ const chunks = chunkArray(operation.keys, BATCH_GET_MAX_KEYS);
323
+ const chunkResults = await Promise.all(
324
+ chunks.map(
325
+ (chunk) => batchGetChunkWithRetry(docClient, operation.tableName, {
326
+ Keys: chunk,
327
+ ...operation.projectionExpression ? { ProjectionExpression: operation.projectionExpression } : {},
328
+ ...operation.expressionAttributeNames ? { ExpressionAttributeNames: operation.expressionAttributeNames } : {},
329
+ ...operation.consistentRead ? { ConsistentRead: true } : {}
330
+ })
331
+ )
332
+ );
333
+ const allItems = [];
334
+ for (const chunkItems of chunkResults) {
335
+ allItems.push(...chunkItems);
336
+ }
337
+ return { items: allItems };
338
+ }
339
+
340
+ // src/executor/dynamo-executor.ts
341
+ var DynamoExecutor = class {
342
+ async execute(operation, _options) {
343
+ const docClient = await ClientManager.getDocumentClient();
344
+ if (operation.type === "GetItem") {
345
+ return executeGetItem(docClient, operation);
346
+ }
347
+ if (operation.type === "BatchGetItem") {
348
+ return this.batchGet(operation);
349
+ }
350
+ return executeQuery(docClient, operation);
351
+ }
352
+ async batchGet(input, _options) {
353
+ const docClient = await ClientManager.getDocumentClient();
354
+ return executeBatchGet(docClient, { type: "BatchGetItem", ...input });
355
+ }
356
+ async put(input, options) {
357
+ const docClient = await ClientManager.getDocumentClient();
358
+ const { PutCommand } = await loadLibDynamoDB();
359
+ const result = await docClient.send(
360
+ new PutCommand(
361
+ options?.returnOldImage ? { ...input, ReturnValues: "ALL_OLD" } : input
362
+ )
363
+ );
364
+ return toWriteResult(result);
365
+ }
366
+ async update(input, options) {
367
+ const docClient = await ClientManager.getDocumentClient();
368
+ const { UpdateCommand } = await loadLibDynamoDB();
369
+ const result = await docClient.send(
370
+ new UpdateCommand(
371
+ options?.returnOldImage ? { ...input, ReturnValues: "ALL_OLD" } : input
372
+ )
373
+ );
374
+ return toWriteResult(result);
375
+ }
376
+ async delete(input, options) {
377
+ const docClient = await ClientManager.getDocumentClient();
378
+ const { DeleteCommand } = await loadLibDynamoDB();
379
+ const result = await docClient.send(
380
+ new DeleteCommand(
381
+ options?.returnOldImage ? { ...input, ReturnValues: "ALL_OLD" } : input
382
+ )
383
+ );
384
+ return toWriteResult(result);
385
+ }
386
+ async batchWrite(tableName, items, _options) {
387
+ const docClient = await ClientManager.getDocumentClient();
388
+ for (const chunk of chunkArray(items, BATCH_WRITE_MAX_ITEMS)) {
389
+ await batchWriteChunkWithRetry(
390
+ docClient,
391
+ tableName,
392
+ chunk,
393
+ toBatchWriteCommandItem,
394
+ matchUnprocessed
395
+ );
396
+ }
397
+ }
398
+ async transactWrite(items, _options) {
399
+ if (items.length === 0) return;
400
+ const docClient = await ClientManager.getDocumentClient();
401
+ const { TransactWriteCommand } = await loadLibDynamoDB();
402
+ await docClient.send(
403
+ new TransactWriteCommand({ TransactItems: items })
404
+ );
405
+ }
406
+ };
407
+ function toWriteResult(result) {
408
+ const oldItem = result.Attributes;
409
+ return oldItem ? { oldItem } : {};
410
+ }
411
+ function toBatchWriteCommandItem(item) {
412
+ if (item.type === "put") {
413
+ return { PutRequest: { Item: item.item } };
414
+ }
415
+ return { DeleteRequest: { Key: item.key } };
416
+ }
417
+ function matchUnprocessed(unprocessed, items) {
418
+ return unprocessed.map((u) => {
419
+ if (u.PutRequest?.Item) {
420
+ const { PK, SK } = u.PutRequest.Item;
421
+ const match = items.find(
422
+ (it) => it.type === "put" && it.item.PK === PK && it.item.SK === SK
423
+ );
424
+ if (!match) {
425
+ throw new Error("Unable to match unprocessed BatchWrite put request");
426
+ }
427
+ return match;
428
+ }
429
+ if (u.DeleteRequest?.Key) {
430
+ const { PK, SK } = u.DeleteRequest.Key;
431
+ const match = items.find(
432
+ (it) => it.type === "delete" && it.key.PK === PK && it.key.SK === SK
433
+ );
434
+ if (!match) {
435
+ throw new Error("Unable to match unprocessed BatchWrite delete request");
436
+ }
437
+ return match;
438
+ }
439
+ throw new Error("Unsupported unprocessed BatchWrite request shape");
440
+ });
441
+ }
442
+ async function executeGetItem(docClient, operation) {
443
+ const { GetCommand } = await loadLibDynamoDB();
444
+ const cmd = new GetCommand({
445
+ TableName: operation.tableName,
446
+ Key: operation.keyCondition,
447
+ ...operation.projectionExpression ? { ProjectionExpression: operation.projectionExpression } : {},
448
+ ...operation.expressionAttributeNames ? { ExpressionAttributeNames: operation.expressionAttributeNames } : {},
449
+ ...operation.consistentRead ? { ConsistentRead: true } : {}
450
+ });
451
+ const result = await docClient.send(cmd);
452
+ if (result.Item) {
453
+ return { items: [result.Item] };
454
+ }
455
+ return { items: [] };
456
+ }
457
+ async function executeQuery(docClient, operation) {
458
+ const { QueryCommand } = await loadLibDynamoDB();
459
+ const keyEntries = Object.entries(operation.keyCondition);
460
+ const conditionParts = [];
461
+ const exprAttrNames = {
462
+ ...operation.expressionAttributeNames ?? {},
463
+ ...operation.filterExpressionAttributeNames ?? {}
464
+ };
465
+ const exprAttrValues = {
466
+ ...operation.filterExpressionAttributeValues ?? {}
467
+ };
468
+ for (let i = 0; i < keyEntries.length; i++) {
469
+ const [attrName, value] = keyEntries[i];
470
+ const nameAlias2 = `#k${i}`;
471
+ const valueAlias2 = `:k${i}`;
472
+ exprAttrNames[nameAlias2] = attrName;
473
+ exprAttrValues[valueAlias2] = value;
474
+ conditionParts.push(`${nameAlias2} = ${valueAlias2}`);
475
+ }
476
+ if (operation.rangeCondition) {
477
+ const rc = operation.rangeCondition;
478
+ const idx = keyEntries.length;
479
+ const nameAlias2 = `#k${idx}`;
480
+ const valueAlias2 = `:k${idx}`;
481
+ exprAttrNames[nameAlias2] = rc.key;
482
+ exprAttrValues[valueAlias2] = rc.value;
483
+ conditionParts.push(`begins_with(${nameAlias2}, ${valueAlias2})`);
484
+ }
485
+ const cmd = new QueryCommand({
486
+ TableName: operation.tableName,
487
+ KeyConditionExpression: conditionParts.join(" AND "),
488
+ ExpressionAttributeNames: exprAttrNames,
489
+ ExpressionAttributeValues: exprAttrValues,
490
+ ...operation.indexName ? { IndexName: operation.indexName } : {},
491
+ ...operation.filterExpression ? { FilterExpression: operation.filterExpression } : {},
492
+ ...operation.projectionExpression ? { ProjectionExpression: operation.projectionExpression } : {},
493
+ ...operation.scanIndexForward != null ? { ScanIndexForward: operation.scanIndexForward } : {},
494
+ ...operation.limit != null ? { Limit: operation.limit } : {},
495
+ ...operation.exclusiveStartKey ? { ExclusiveStartKey: operation.exclusiveStartKey } : {},
496
+ ...operation.consistentRead ? { ConsistentRead: true } : {}
497
+ });
498
+ const result = await docClient.send(cmd);
499
+ return {
500
+ items: result.Items ?? [],
501
+ lastEvaluatedKey: result.LastEvaluatedKey
502
+ };
503
+ }
504
+
317
505
  // src/executor/retry-policy.ts
318
506
  var DEFAULT_MAX_ATTEMPTS = 10;
319
507
  var DEFAULT_RETRY_POLICY = {
@@ -453,7 +641,7 @@ var RetryingExecutor = class {
453
641
  /**
454
642
  * @param inner the wrapped executor (the real {@link DynamoExecutor}).
455
643
  * @param getGlobalPolicy reads the globally-configured policy (or `null` for the
456
- * built-in default) at CALL time, so a `DDBModel.setRetryPolicy(...)` after the
644
+ * built-in default) at CALL time, so a `graphddb.config.retry(...)` after the
457
645
  * executor is constructed still takes effect.
458
646
  */
459
647
  constructor(inner, getGlobalPolicy) {
@@ -533,7 +721,7 @@ var ClientManager = class {
533
721
  /**
534
722
  * The globally-configured single-op retry policy (issue #111). `null` ⇒ the
535
723
  * always-on built-in {@link DEFAULT_RETRY_POLICY} applies (retry is enabled out
536
- * of the box). Set via {@link DDBModel.setRetryPolicy} / {@link setRetryPolicy};
724
+ * of the box). Set via `graphddb.config.retry` / {@link setRetryPolicy};
537
725
  * read at call time by the default {@link RetryingExecutor}.
538
726
  */
539
727
  static retryPolicy = null;
@@ -541,7 +729,7 @@ var ClientManager = class {
541
729
  * The host-runtime read-middleware registry (issue #50 / #138) — a process-wide
542
730
  * ordered list of {@link Middleware}, the same global-config + injectable-seam
543
731
  * pattern as {@link retryPolicy} / {@link executor}. Registered via
544
- * {@link DDBModel.use} / {@link use}, snapshotted per read, and NEVER serialized
732
+ * `graphddb.config.use` / {@link use}, snapshotted per read, and NEVER serialized
545
733
  * (it never touches the planner / spec / `operations.json`, so the bridge #48 is
546
734
  * unaffected). {@link reset} clears it.
547
735
  */
@@ -553,7 +741,7 @@ var ClientManager = class {
553
741
  static getClient() {
554
742
  if (!this.client) {
555
743
  throw new Error(
556
- "DynamoDB client is not configured. Call DDBModel.setClient(new DynamoDBClient({...})) first."
744
+ "DynamoDB client is not configured. Call graphddb.config.client(new DynamoDBClient({...})) first."
557
745
  );
558
746
  }
559
747
  return this.client;
@@ -615,12 +803,12 @@ var ClientManager = class {
615
803
  /**
616
804
  * Register a read {@link Middleware} (issue #50 / #138). Appended last, so its
617
805
  * `before*` hooks run last (FIFO) and its `afterFetch` / `onError` hooks run
618
- * first (LIFO). Mirrors {@link setRetryPolicy}; backs {@link DDBModel.use}.
806
+ * first (LIFO). Mirrors {@link setRetryPolicy}; backs `graphddb.config.use`.
619
807
  */
620
808
  static use(mw) {
621
809
  this.middleware.use(mw);
622
810
  }
623
- /** Remove every registered read middleware (teardown / tests). Backs {@link DDBModel.clearMiddleware}. */
811
+ /** Remove every registered read middleware (teardown / tests). Backs `graphddb.config.clearMiddleware`. */
624
812
  static clearMiddleware() {
625
813
  this.middleware.clear();
626
814
  }
@@ -643,216 +831,28 @@ var ClientManager = class {
643
831
  }
644
832
  };
645
833
 
646
- // src/executor/batch-executor.ts
647
- async function executeBatchGet(docClient, operation) {
648
- if (operation.keys.length === 0) {
649
- return { items: [] };
650
- }
651
- const chunks = chunkArray(operation.keys, BATCH_GET_MAX_KEYS);
652
- const chunkResults = await Promise.all(
653
- chunks.map(
654
- (chunk) => batchGetChunkWithRetry(docClient, operation.tableName, {
655
- Keys: chunk,
656
- ...operation.projectionExpression ? { ProjectionExpression: operation.projectionExpression } : {},
657
- ...operation.expressionAttributeNames ? { ExpressionAttributeNames: operation.expressionAttributeNames } : {},
658
- ...operation.consistentRead ? { ConsistentRead: true } : {}
659
- })
660
- )
661
- );
662
- const allItems = [];
663
- for (const chunkItems of chunkResults) {
664
- allItems.push(...chunkItems);
665
- }
666
- return { items: allItems };
834
+ // src/select/cond.ts
835
+ var RAW_COND = /* @__PURE__ */ Symbol.for("graphddb.rawCond");
836
+ function isRawCondition(value) {
837
+ return typeof value === "object" && value !== null && value[RAW_COND] === true;
667
838
  }
668
-
669
- // src/executor/dynamo-executor.ts
670
- var DynamoExecutor = class {
671
- async execute(operation, _options) {
672
- const docClient = await ClientManager.getDocumentClient();
673
- if (operation.type === "GetItem") {
674
- return executeGetItem(docClient, operation);
675
- }
676
- if (operation.type === "BatchGetItem") {
677
- return this.batchGet(operation);
839
+ function cond(template, ...parts) {
840
+ return {
841
+ [RAW_COND]: true,
842
+ template,
843
+ parts
844
+ };
845
+ }
846
+ function compileRawCondition(raw, allocName, allocValue) {
847
+ let out = raw.template[0];
848
+ for (let i = 0; i < raw.parts.length; i++) {
849
+ const part = raw.parts[i];
850
+ if (isColumn(part)) {
851
+ out += allocName(part.name);
852
+ } else {
853
+ out += allocValue(part);
678
854
  }
679
- return executeQuery(docClient, operation);
680
- }
681
- async batchGet(input, _options) {
682
- const docClient = await ClientManager.getDocumentClient();
683
- return executeBatchGet(docClient, { type: "BatchGetItem", ...input });
684
- }
685
- async put(input, options) {
686
- const docClient = await ClientManager.getDocumentClient();
687
- const { PutCommand } = await loadLibDynamoDB();
688
- const result = await docClient.send(
689
- new PutCommand(
690
- options?.returnOldImage ? { ...input, ReturnValues: "ALL_OLD" } : input
691
- )
692
- );
693
- return toWriteResult(result);
694
- }
695
- async update(input, options) {
696
- const docClient = await ClientManager.getDocumentClient();
697
- const { UpdateCommand } = await loadLibDynamoDB();
698
- const result = await docClient.send(
699
- new UpdateCommand(
700
- options?.returnOldImage ? { ...input, ReturnValues: "ALL_OLD" } : input
701
- )
702
- );
703
- return toWriteResult(result);
704
- }
705
- async delete(input, options) {
706
- const docClient = await ClientManager.getDocumentClient();
707
- const { DeleteCommand } = await loadLibDynamoDB();
708
- const result = await docClient.send(
709
- new DeleteCommand(
710
- options?.returnOldImage ? { ...input, ReturnValues: "ALL_OLD" } : input
711
- )
712
- );
713
- return toWriteResult(result);
714
- }
715
- async batchWrite(tableName, items, _options) {
716
- const docClient = await ClientManager.getDocumentClient();
717
- for (const chunk of chunkArray(items, BATCH_WRITE_MAX_ITEMS)) {
718
- await batchWriteChunkWithRetry(
719
- docClient,
720
- tableName,
721
- chunk,
722
- toBatchWriteCommandItem,
723
- matchUnprocessed
724
- );
725
- }
726
- }
727
- async transactWrite(items, _options) {
728
- if (items.length === 0) return;
729
- const docClient = await ClientManager.getDocumentClient();
730
- const { TransactWriteCommand } = await loadLibDynamoDB();
731
- await docClient.send(
732
- new TransactWriteCommand({ TransactItems: items })
733
- );
734
- }
735
- };
736
- function toWriteResult(result) {
737
- const oldItem = result.Attributes;
738
- return oldItem ? { oldItem } : {};
739
- }
740
- function toBatchWriteCommandItem(item) {
741
- if (item.type === "put") {
742
- return { PutRequest: { Item: item.item } };
743
- }
744
- return { DeleteRequest: { Key: item.key } };
745
- }
746
- function matchUnprocessed(unprocessed, items) {
747
- return unprocessed.map((u) => {
748
- if (u.PutRequest?.Item) {
749
- const { PK, SK } = u.PutRequest.Item;
750
- const match = items.find(
751
- (it) => it.type === "put" && it.item.PK === PK && it.item.SK === SK
752
- );
753
- if (!match) {
754
- throw new Error("Unable to match unprocessed BatchWrite put request");
755
- }
756
- return match;
757
- }
758
- if (u.DeleteRequest?.Key) {
759
- const { PK, SK } = u.DeleteRequest.Key;
760
- const match = items.find(
761
- (it) => it.type === "delete" && it.key.PK === PK && it.key.SK === SK
762
- );
763
- if (!match) {
764
- throw new Error("Unable to match unprocessed BatchWrite delete request");
765
- }
766
- return match;
767
- }
768
- throw new Error("Unsupported unprocessed BatchWrite request shape");
769
- });
770
- }
771
- async function executeGetItem(docClient, operation) {
772
- const { GetCommand } = await loadLibDynamoDB();
773
- const cmd = new GetCommand({
774
- TableName: operation.tableName,
775
- Key: operation.keyCondition,
776
- ...operation.projectionExpression ? { ProjectionExpression: operation.projectionExpression } : {},
777
- ...operation.expressionAttributeNames ? { ExpressionAttributeNames: operation.expressionAttributeNames } : {},
778
- ...operation.consistentRead ? { ConsistentRead: true } : {}
779
- });
780
- const result = await docClient.send(cmd);
781
- if (result.Item) {
782
- return { items: [result.Item] };
783
- }
784
- return { items: [] };
785
- }
786
- async function executeQuery(docClient, operation) {
787
- const { QueryCommand } = await loadLibDynamoDB();
788
- const keyEntries = Object.entries(operation.keyCondition);
789
- const conditionParts = [];
790
- const exprAttrNames = {
791
- ...operation.expressionAttributeNames ?? {},
792
- ...operation.filterExpressionAttributeNames ?? {}
793
- };
794
- const exprAttrValues = {
795
- ...operation.filterExpressionAttributeValues ?? {}
796
- };
797
- for (let i = 0; i < keyEntries.length; i++) {
798
- const [attrName, value] = keyEntries[i];
799
- const nameAlias2 = `#k${i}`;
800
- const valueAlias2 = `:k${i}`;
801
- exprAttrNames[nameAlias2] = attrName;
802
- exprAttrValues[valueAlias2] = value;
803
- conditionParts.push(`${nameAlias2} = ${valueAlias2}`);
804
- }
805
- if (operation.rangeCondition) {
806
- const rc = operation.rangeCondition;
807
- const idx = keyEntries.length;
808
- const nameAlias2 = `#k${idx}`;
809
- const valueAlias2 = `:k${idx}`;
810
- exprAttrNames[nameAlias2] = rc.key;
811
- exprAttrValues[valueAlias2] = rc.value;
812
- conditionParts.push(`begins_with(${nameAlias2}, ${valueAlias2})`);
813
- }
814
- const cmd = new QueryCommand({
815
- TableName: operation.tableName,
816
- KeyConditionExpression: conditionParts.join(" AND "),
817
- ExpressionAttributeNames: exprAttrNames,
818
- ExpressionAttributeValues: exprAttrValues,
819
- ...operation.indexName ? { IndexName: operation.indexName } : {},
820
- ...operation.filterExpression ? { FilterExpression: operation.filterExpression } : {},
821
- ...operation.projectionExpression ? { ProjectionExpression: operation.projectionExpression } : {},
822
- ...operation.scanIndexForward != null ? { ScanIndexForward: operation.scanIndexForward } : {},
823
- ...operation.limit != null ? { Limit: operation.limit } : {},
824
- ...operation.exclusiveStartKey ? { ExclusiveStartKey: operation.exclusiveStartKey } : {},
825
- ...operation.consistentRead ? { ConsistentRead: true } : {}
826
- });
827
- const result = await docClient.send(cmd);
828
- return {
829
- items: result.Items ?? [],
830
- lastEvaluatedKey: result.LastEvaluatedKey
831
- };
832
- }
833
-
834
- // src/select/cond.ts
835
- var RAW_COND = /* @__PURE__ */ Symbol.for("graphddb.rawCond");
836
- function isRawCondition(value) {
837
- return typeof value === "object" && value !== null && value[RAW_COND] === true;
838
- }
839
- function cond(template, ...parts) {
840
- return {
841
- [RAW_COND]: true,
842
- template,
843
- parts
844
- };
845
- }
846
- function compileRawCondition(raw, allocName, allocValue) {
847
- let out = raw.template[0];
848
- for (let i = 0; i < raw.parts.length; i++) {
849
- const part = raw.parts[i];
850
- if (isColumn(part)) {
851
- out += allocName(part.name);
852
- } else {
853
- out += allocValue(part);
854
- }
855
- out += raw.template[i + 1];
855
+ out += raw.template[i + 1];
856
856
  }
857
857
  return out;
858
858
  }
@@ -896,17 +896,6 @@ function serializeRawCondition(raw, renderSlot) {
896
896
  }
897
897
  return { expression: out, names, values };
898
898
  }
899
- function collectRawConditionParams(raw) {
900
- const out = [];
901
- let paramIndex = 0;
902
- for (const part of raw.parts) {
903
- if (isColumn(part)) continue;
904
- if (isParam(part)) {
905
- out.push({ name: condParamName(paramIndex++), param: part });
906
- }
907
- }
908
- return out;
909
- }
910
899
 
911
900
  // src/expression/condition-expression.ts
912
901
  var CONDITION_OPERATOR_KEYS = /* @__PURE__ */ new Set([
@@ -1156,69 +1145,6 @@ function serializeFieldValue(value, fieldMeta) {
1156
1145
  return value;
1157
1146
  }
1158
1147
 
1159
- // src/expression/update-expression.ts
1160
- function isPlainObject(value) {
1161
- return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
1162
- }
1163
- function flattenChanges(obj, parentPath, embeddedFieldNames) {
1164
- const sets = [];
1165
- const removes = [];
1166
- for (const [fieldName, value] of Object.entries(obj)) {
1167
- const currentPath = [...parentPath, fieldName];
1168
- if (value === void 0) {
1169
- removes.push({ path: currentPath });
1170
- } else if (embeddedFieldNames.has(currentPath[0]) && isPlainObject(value)) {
1171
- const nested = flattenChanges(value, currentPath, embeddedFieldNames);
1172
- sets.push(...nested.sets);
1173
- removes.push(...nested.removes);
1174
- } else {
1175
- sets.push({ path: currentPath, value });
1176
- }
1177
- }
1178
- return { sets, removes };
1179
- }
1180
- function buildUpdateExpression(changes, embeddedFieldNames) {
1181
- const { sets, removes } = flattenChanges(changes, [], embeddedFieldNames);
1182
- if (sets.length === 0 && removes.length === 0) {
1183
- throw new Error("No changes provided for update.");
1184
- }
1185
- const names = {};
1186
- const values = {};
1187
- let valueCounter = 0;
1188
- const setClauses = [];
1189
- for (const op of sets) {
1190
- const pathExpr = op.path.map((segment) => {
1191
- const nameKey = `#${segment}`;
1192
- names[nameKey] = segment;
1193
- return nameKey;
1194
- }).join(".");
1195
- const valueKey = `:val${valueCounter++}`;
1196
- values[valueKey] = op.value;
1197
- setClauses.push(`${pathExpr} = ${valueKey}`);
1198
- }
1199
- const removeClauses = [];
1200
- for (const op of removes) {
1201
- const pathExpr = op.path.map((segment) => {
1202
- const nameKey = `#${segment}`;
1203
- names[nameKey] = segment;
1204
- return nameKey;
1205
- }).join(".");
1206
- removeClauses.push(pathExpr);
1207
- }
1208
- const parts = [];
1209
- if (setClauses.length > 0) {
1210
- parts.push(`SET ${setClauses.join(", ")}`);
1211
- }
1212
- if (removeClauses.length > 0) {
1213
- parts.push(`REMOVE ${removeClauses.join(", ")}`);
1214
- }
1215
- return {
1216
- expression: parts.join(" "),
1217
- names,
1218
- values
1219
- };
1220
- }
1221
-
1222
1148
  // src/metadata/segments.ts
1223
1149
  var SEGMENT_DELIMITER = "#";
1224
1150
  function segmentComplete(segment, values) {
@@ -1358,52 +1284,24 @@ function missingGsiFields(gsi2, available) {
1358
1284
  return gsi2.inputFieldNames.filter((f) => available[f] === void 0);
1359
1285
  }
1360
1286
 
1361
- // src/hydrator/hidden-key.ts
1362
- var GRAPHDDB_KEY = /* @__PURE__ */ Symbol("graphddb:key");
1363
- function attachHiddenKey(result, raw) {
1364
- const pk = raw.PK;
1365
- if (typeof pk !== "string") {
1366
- throw new Error(
1367
- "Cannot attach updatable key: raw item is missing a string `PK` attribute."
1368
- );
1369
- }
1370
- const sk = raw.SK;
1371
- const value = {
1372
- PK: pk,
1373
- SK: typeof sk === "string" ? sk : ""
1374
- };
1375
- Object.defineProperty(result, GRAPHDDB_KEY, {
1376
- value,
1377
- enumerable: false,
1378
- writable: false,
1379
- configurable: false
1380
- });
1381
- return result;
1382
- }
1383
- function readHiddenKey(entity) {
1384
- const value = entity[GRAPHDDB_KEY];
1385
- if (value === void 0) return void 0;
1386
- return value;
1387
- }
1388
-
1389
- // src/capture/registry.ts
1390
- var ChangeCaptureRegistryImpl = class {
1391
- subscribers = /* @__PURE__ */ new Set();
1392
- /**
1393
- * Stream-enable opt-in, keyed by the model class. A model is only eligible to
1394
- * have its pre-write image fetched (`ReturnValues: ALL_OLD`) when it has been
1395
- * explicitly stream-enabled here. Default is disabled.
1396
- */
1397
- streamEnabled = /* @__PURE__ */ new Set();
1398
- /** Register an internal subscriber. Returns an idempotent unsubscribe. */
1399
- register(subscriber) {
1400
- this.subscribers.add(subscriber);
1401
- let active = true;
1402
- return () => {
1403
- if (!active) return;
1404
- active = false;
1405
- this.subscribers.delete(subscriber);
1406
- };
1287
+ // src/capture/registry.ts
1288
+ var ChangeCaptureRegistryImpl = class {
1289
+ subscribers = /* @__PURE__ */ new Set();
1290
+ /**
1291
+ * Stream-enable opt-in, keyed by the model class. A model is only eligible to
1292
+ * have its pre-write image fetched (`ReturnValues: ALL_OLD`) when it has been
1293
+ * explicitly stream-enabled here. Default is disabled.
1294
+ */
1295
+ streamEnabled = /* @__PURE__ */ new Set();
1296
+ /** Register an internal subscriber. Returns an idempotent unsubscribe. */
1297
+ register(subscriber) {
1298
+ this.subscribers.add(subscriber);
1299
+ let active = true;
1300
+ return () => {
1301
+ if (!active) return;
1302
+ active = false;
1303
+ this.subscribers.delete(subscriber);
1304
+ };
1407
1305
  }
1408
1306
  /** True when at least one subscriber is live. Cheap fast-path gate. */
1409
1307
  hasSubscribers() {
@@ -1727,115 +1625,115 @@ function ctxModelFor(modelClass) {
1727
1625
  return resolved;
1728
1626
  }
1729
1627
 
1730
- // src/operations/put.ts
1731
- function buildPutInput(modelClass, item, options) {
1732
- const meta = MetadataRegistry.get(modelClass);
1733
- if (!meta.primaryKey) {
1734
- throw new Error(
1735
- `No primary key defined for '${modelClass.name}'. Ensure the model has a static \`keys = key(...)\` definition.`
1736
- );
1737
- }
1738
- const tableName = TableMapping.resolve(meta.tableName);
1739
- const serialized = {};
1740
- for (const fieldMeta of meta.fields) {
1741
- const value = item[fieldMeta.propertyName];
1742
- if (value !== void 0) {
1743
- serialized[fieldMeta.propertyName] = serializeFieldValue(value, fieldMeta);
1628
+ // src/expression/update-expression.ts
1629
+ function isPlainObject(value) {
1630
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
1631
+ }
1632
+ function flattenChanges(obj, parentPath, embeddedFieldNames) {
1633
+ const sets = [];
1634
+ const removes = [];
1635
+ for (const [fieldName, value] of Object.entries(obj)) {
1636
+ const currentPath = [...parentPath, fieldName];
1637
+ if (value === void 0) {
1638
+ removes.push({ path: currentPath });
1639
+ } else if (embeddedFieldNames.has(currentPath[0]) && isPlainObject(value)) {
1640
+ const nested = flattenChanges(value, currentPath, embeddedFieldNames);
1641
+ sets.push(...nested.sets);
1642
+ removes.push(...nested.removes);
1643
+ } else {
1644
+ sets.push({ path: currentPath, value });
1744
1645
  }
1745
1646
  }
1746
- const keyInput = {};
1747
- for (const fieldName of meta.primaryKey.inputFieldNames) {
1748
- keyInput[fieldName] = serialized[fieldName] ?? item[fieldName];
1647
+ return { sets, removes };
1648
+ }
1649
+ function buildUpdateExpression(changes, embeddedFieldNames) {
1650
+ const { sets, removes } = flattenChanges(changes, [], embeddedFieldNames);
1651
+ if (sets.length === 0 && removes.length === 0) {
1652
+ throw new Error("No changes provided for update.");
1749
1653
  }
1750
- const { pk, sk } = resolveSegmentedKey(
1751
- meta.primaryKey.segmented,
1752
- keyInput,
1753
- `${modelClass.name} primary key`
1754
- );
1755
- const dynamoItem = {
1756
- PK: pk,
1757
- SK: sk ?? "",
1758
- ...serialized
1759
- };
1760
- for (const emb of meta.embeddedFields) {
1761
- const value = item[emb.propertyName];
1762
- if (value !== void 0) {
1763
- dynamoItem[emb.propertyName] = value;
1764
- }
1654
+ const names = {};
1655
+ const values = {};
1656
+ let valueCounter = 0;
1657
+ const setClauses = [];
1658
+ for (const op of sets) {
1659
+ const pathExpr = op.path.map((segment) => {
1660
+ const nameKey = `#${segment}`;
1661
+ names[nameKey] = segment;
1662
+ return nameKey;
1663
+ }).join(".");
1664
+ const valueKey = `:val${valueCounter++}`;
1665
+ values[valueKey] = op.value;
1666
+ setClauses.push(`${pathExpr} = ${valueKey}`);
1765
1667
  }
1766
- const available = {};
1767
- for (const fieldName of meta.fields.map((f) => f.propertyName)) {
1768
- available[fieldName] = serialized[fieldName] ?? item[fieldName];
1668
+ const removeClauses = [];
1669
+ for (const op of removes) {
1670
+ const pathExpr = op.path.map((segment) => {
1671
+ const nameKey = `#${segment}`;
1672
+ names[nameKey] = segment;
1673
+ return nameKey;
1674
+ }).join(".");
1675
+ removeClauses.push(pathExpr);
1769
1676
  }
1770
- for (const gsi2 of meta.gsiDefinitions) {
1771
- const { pkAttr, pk: gsiPk, skAttr, sk: gsiSk } = deriveGsiKey(
1772
- gsi2,
1773
- available,
1774
- `${modelClass.name} GSI '${gsi2.indexName}'`
1775
- );
1776
- dynamoItem[pkAttr] = gsiPk;
1777
- dynamoItem[skAttr] = gsiSk;
1677
+ const parts = [];
1678
+ if (setClauses.length > 0) {
1679
+ parts.push(`SET ${setClauses.join(", ")}`);
1778
1680
  }
1779
- const putInput = {
1780
- TableName: tableName,
1781
- Item: dynamoItem
1782
- };
1783
- if (options?.condition) {
1784
- const condResult = buildConditionExpression(options.condition);
1785
- putInput.ConditionExpression = condResult.expression;
1786
- if (Object.keys(condResult.names).length > 0) {
1787
- putInput.ExpressionAttributeNames = condResult.names;
1788
- }
1789
- if (Object.keys(condResult.values).length > 0) {
1790
- putInput.ExpressionAttributeValues = condResult.values;
1791
- }
1681
+ if (removeClauses.length > 0) {
1682
+ parts.push(`REMOVE ${removeClauses.join(", ")}`);
1792
1683
  }
1793
- return putInput;
1684
+ return {
1685
+ expression: parts.join(" "),
1686
+ names,
1687
+ values
1688
+ };
1794
1689
  }
1795
- async function executePut(modelClass, item, options) {
1796
- const mw = writeRuntimeFor(options);
1797
- if (mw) {
1798
- await executeSingleWriteWithHooks({ kind: "put", modelClass, item, options }, mw);
1799
- return;
1690
+
1691
+ // src/hydrator/hidden-key.ts
1692
+ var GRAPHDDB_KEY = /* @__PURE__ */ Symbol("graphddb:key");
1693
+ function attachHiddenKey(result, raw) {
1694
+ const pk = raw.PK;
1695
+ if (typeof pk !== "string") {
1696
+ throw new Error(
1697
+ "Cannot attach updatable key: raw item is missing a string `PK` attribute."
1698
+ );
1800
1699
  }
1801
- await executePutCore(modelClass, item, options);
1802
- }
1803
- async function executePutCore(modelClass, item, options, persistVia, forceOldImage) {
1804
- const putInput = buildPutInput(modelClass, item, options);
1805
- const wantsOld = ChangeCaptureRegistry.wantsOldImage(modelClass) || forceOldImage === true;
1806
- const send = (input) => ClientManager.getExecutor().put(input, {
1807
- ...wantsOld ? { returnOldImage: true } : {},
1808
- ...options?.retry !== void 0 ? { retry: options.retry } : {}
1700
+ const sk = raw.SK;
1701
+ const value = {
1702
+ PK: pk,
1703
+ SK: typeof sk === "string" ? sk : ""
1704
+ };
1705
+ Object.defineProperty(result, GRAPHDDB_KEY, {
1706
+ value,
1707
+ enumerable: false,
1708
+ writable: false,
1709
+ configurable: false
1809
1710
  });
1810
- const result = persistVia ? await persistVia(putInput, send) : await send(putInput);
1811
- if (ChangeCaptureRegistry.hasSubscribers()) {
1812
- const oldItem = result.oldItem;
1813
- captureWrite({
1814
- table: putInput.TableName,
1815
- model: modelClass.name,
1816
- op: "put",
1817
- keys: { pk: String(putInput.Item.PK), sk: String(putInput.Item.SK ?? "") },
1818
- newItem: putInput.Item,
1819
- ...oldItem ? { oldItem } : {}
1820
- });
1821
- }
1711
+ return result;
1712
+ }
1713
+ function readHiddenKey(entity) {
1714
+ const value = entity[GRAPHDDB_KEY];
1715
+ if (value === void 0) return void 0;
1716
+ return value;
1822
1717
  }
1823
1718
 
1824
- // src/operations/delete.ts
1825
- function buildDeleteInput(modelClass, keyObj, options) {
1826
- const meta = MetadataRegistry.get(modelClass);
1827
- if (!meta.primaryKey) {
1719
+ // src/operations/update.ts
1720
+ function resolveUpdateKey(modelClass, primaryKey, entity, fieldMap) {
1721
+ const hidden = readHiddenKey(entity);
1722
+ if (hidden) {
1723
+ return { pk: hidden.PK, sk: hidden.SK };
1724
+ }
1725
+ const missing = primaryKey.inputFieldNames.filter(
1726
+ (f) => entity[f] === void 0
1727
+ );
1728
+ if (missing.length > 0) {
1729
+ const fieldList = missing.join(", ");
1828
1730
  throw new Error(
1829
- `No primary key defined for '${modelClass.name}'. Ensure the model has a static \`keys = key(...)\` definition.`
1731
+ `Cannot resolve update key for '${modelClass.name}': missing key field(s) [${fieldList}]. Provide an explicit key (update({ ${fieldList} }, changes)), select all key fields, or query with { updatable: true }.`
1830
1732
  );
1831
1733
  }
1832
- const tableName = TableMapping.resolve(meta.tableName);
1833
- const fieldMap = new Map(
1834
- meta.fields.map((f) => [f.propertyName, f])
1835
- );
1836
1734
  const keyInput = {};
1837
- for (const fieldName of meta.primaryKey.inputFieldNames) {
1838
- let value = keyObj[fieldName];
1735
+ for (const fieldName of primaryKey.inputFieldNames) {
1736
+ let value = entity[fieldName];
1839
1737
  const fieldMeta = fieldMap.get(fieldName);
1840
1738
  if (fieldMeta && value !== void 0) {
1841
1739
  value = serializeFieldValue(value, fieldMeta);
@@ -1843,173 +1741,11 @@ function buildDeleteInput(modelClass, keyObj, options) {
1843
1741
  keyInput[fieldName] = value;
1844
1742
  }
1845
1743
  const { pk, sk } = resolveSegmentedKey(
1846
- meta.primaryKey.segmented,
1744
+ primaryKey.segmented,
1847
1745
  keyInput,
1848
1746
  `${modelClass.name} primary key`
1849
1747
  );
1850
- const deleteInput = {
1851
- TableName: tableName,
1852
- Key: {
1853
- PK: pk,
1854
- SK: sk ?? ""
1855
- }
1856
- };
1857
- if (options?.condition) {
1858
- const condResult = buildConditionExpression(options.condition);
1859
- deleteInput.ConditionExpression = condResult.expression;
1860
- if (Object.keys(condResult.names).length > 0) {
1861
- deleteInput.ExpressionAttributeNames = condResult.names;
1862
- }
1863
- if (Object.keys(condResult.values).length > 0) {
1864
- deleteInput.ExpressionAttributeValues = condResult.values;
1865
- }
1866
- }
1867
- return deleteInput;
1868
- }
1869
- async function executeDelete(modelClass, keyObj, options) {
1870
- const mw = writeRuntimeFor(options);
1871
- if (mw) {
1872
- await executeSingleWriteWithHooks({ kind: "delete", modelClass, key: keyObj, options }, mw);
1873
- return;
1874
- }
1875
- await executeDeleteCore(modelClass, keyObj, options);
1876
- }
1877
- async function executeDeleteCore(modelClass, keyObj, options, persistVia, forceOldImage) {
1878
- const deleteInput = buildDeleteInput(modelClass, keyObj, options);
1879
- const wantsOld = ChangeCaptureRegistry.wantsOldImage(modelClass) || forceOldImage === true;
1880
- const send = (input) => ClientManager.getExecutor().delete(input, {
1881
- ...wantsOld ? { returnOldImage: true } : {},
1882
- ...options?.retry !== void 0 ? { retry: options.retry } : {}
1883
- });
1884
- const result = persistVia ? await persistVia(deleteInput, send) : await send(deleteInput);
1885
- if (ChangeCaptureRegistry.hasSubscribers()) {
1886
- const oldItem = result.oldItem;
1887
- captureWrite({
1888
- table: deleteInput.TableName,
1889
- model: modelClass.name,
1890
- op: "delete",
1891
- keys: { pk: String(deleteInput.Key.PK), sk: String(deleteInput.Key.SK ?? "") },
1892
- ...oldItem ? { oldItem } : {}
1893
- });
1894
- }
1895
- }
1896
-
1897
- // src/operations/write-hooks.ts
1898
- function writeRuntimeFor(options) {
1899
- const mw = buildWriteRuntime(options?.context);
1900
- return mw.active ? mw : void 0;
1901
- }
1902
- async function executeSingleWriteWithHooks(req, mw) {
1903
- const input = {};
1904
- if (req.item !== void 0) input.item = { ...req.item };
1905
- if (req.key !== void 0) input.key = { ...req.key };
1906
- if (req.changes !== void 0) input.changes = { ...req.changes };
1907
- if (req.options !== void 0) input.options = { ...req.options };
1908
- const ctx = mw.writeCtx(req.kind, ctxModelFor(req.modelClass), input);
1909
- try {
1910
- await mw.runWriteBefore(ctx);
1911
- const change2 = await persistRewrittenWrite(req.modelClass, ctx, mw);
1912
- await mw.runWriteAfter(ctx, change2);
1913
- } catch (err) {
1914
- await mw.runWriteError(ctx, err);
1915
- }
1916
- }
1917
- async function persistRewrittenWrite(modelClass, ctx, mw) {
1918
- const opts = ctx.input.options;
1919
- const origin = { model: ctx.model, kind: ctx.kind };
1920
- const forceOldImage = mw.hasWriteAfter;
1921
- const cap = {};
1922
- if (ctx.kind === "put") {
1923
- await executePutCore(
1924
- modelClass,
1925
- ctx.input.item ?? {},
1926
- opts,
1927
- (input, send) => runPersist(mw, { Put: input }, origin, cap, (it) => {
1928
- const put = it.Put;
1929
- cap.newImage = put.Item;
1930
- return send(put);
1931
- }),
1932
- forceOldImage
1933
- );
1934
- return change(cap, true);
1935
- }
1936
- if (ctx.kind === "update") {
1937
- await executeUpdateCore(
1938
- modelClass,
1939
- ctx.input.key ?? {},
1940
- ctx.input.changes ?? {},
1941
- opts,
1942
- (input, send) => runPersist(mw, { Update: input }, origin, cap, (it) => {
1943
- const upd = it.Update;
1944
- const old = cap.result?.oldItem;
1945
- cap.newImage = { ...old ?? upd.Key, ...ctx.input.changes ?? {} };
1946
- return send(upd);
1947
- }),
1948
- forceOldImage
1949
- );
1950
- return change(cap, true);
1951
- }
1952
- await executeDeleteCore(
1953
- modelClass,
1954
- ctx.input.key ?? {},
1955
- opts,
1956
- (input, send) => runPersist(
1957
- mw,
1958
- { Delete: input },
1959
- origin,
1960
- cap,
1961
- (it) => send(it.Delete)
1962
- ),
1963
- forceOldImage
1964
- );
1965
- return change(cap, false);
1966
- }
1967
- function change(cap, withNew) {
1968
- const out = {};
1969
- const oldImage = cap.result?.oldItem;
1970
- if (oldImage !== void 0) out.oldImage = oldImage;
1971
- if (withNew && cap.newImage !== void 0) {
1972
- out.newImage = cap.newImage;
1973
- }
1974
- return out;
1975
- }
1976
- async function runPersist(mw, item, origin, cap, send) {
1977
- const ctx = mw.persistCtx([item], [origin]);
1978
- const result = await mw.runPersist(ctx, async (items) => send(items[0]));
1979
- cap.result = result;
1980
- return result;
1981
- }
1982
-
1983
- // src/operations/update.ts
1984
- function resolveUpdateKey(modelClass, primaryKey, entity, fieldMap) {
1985
- const hidden = readHiddenKey(entity);
1986
- if (hidden) {
1987
- return { pk: hidden.PK, sk: hidden.SK };
1988
- }
1989
- const missing = primaryKey.inputFieldNames.filter(
1990
- (f) => entity[f] === void 0
1991
- );
1992
- if (missing.length > 0) {
1993
- const fieldList = missing.join(", ");
1994
- throw new Error(
1995
- `Cannot resolve update key for '${modelClass.name}': missing key field(s) [${fieldList}]. Provide an explicit key (update({ ${fieldList} }, changes)), select all key fields, or query with { updatable: true }.`
1996
- );
1997
- }
1998
- const keyInput = {};
1999
- for (const fieldName of primaryKey.inputFieldNames) {
2000
- let value = entity[fieldName];
2001
- const fieldMeta = fieldMap.get(fieldName);
2002
- if (fieldMeta && value !== void 0) {
2003
- value = serializeFieldValue(value, fieldMeta);
2004
- }
2005
- keyInput[fieldName] = value;
2006
- }
2007
- const { pk, sk } = resolveSegmentedKey(
2008
- primaryKey.segmented,
2009
- keyInput,
2010
- `${modelClass.name} primary key`
2011
- );
2012
- return { pk, sk: sk ?? "" };
1748
+ return { pk, sk: sk ?? "" };
2013
1749
  }
2014
1750
  function buildGsiRederiveSets(modelClass, meta, entity, serializedChanges, fieldMap, names, values, options) {
2015
1751
  const changedFields = new Set(Object.keys(serializedChanges));
@@ -2258,6 +1994,259 @@ async function executeUpdateCore(modelClass, entity, changes, options, persistVi
2258
1994
  }
2259
1995
  }
2260
1996
 
1997
+ // src/operations/delete.ts
1998
+ function buildDeleteInput(modelClass, keyObj, options) {
1999
+ const meta = MetadataRegistry.get(modelClass);
2000
+ if (!meta.primaryKey) {
2001
+ throw new Error(
2002
+ `No primary key defined for '${modelClass.name}'. Ensure the model has a static \`keys = key(...)\` definition.`
2003
+ );
2004
+ }
2005
+ const tableName = TableMapping.resolve(meta.tableName);
2006
+ const fieldMap = new Map(
2007
+ meta.fields.map((f) => [f.propertyName, f])
2008
+ );
2009
+ const keyInput = {};
2010
+ for (const fieldName of meta.primaryKey.inputFieldNames) {
2011
+ let value = keyObj[fieldName];
2012
+ const fieldMeta = fieldMap.get(fieldName);
2013
+ if (fieldMeta && value !== void 0) {
2014
+ value = serializeFieldValue(value, fieldMeta);
2015
+ }
2016
+ keyInput[fieldName] = value;
2017
+ }
2018
+ const { pk, sk } = resolveSegmentedKey(
2019
+ meta.primaryKey.segmented,
2020
+ keyInput,
2021
+ `${modelClass.name} primary key`
2022
+ );
2023
+ const deleteInput = {
2024
+ TableName: tableName,
2025
+ Key: {
2026
+ PK: pk,
2027
+ SK: sk ?? ""
2028
+ }
2029
+ };
2030
+ if (options?.condition) {
2031
+ const condResult = buildConditionExpression(options.condition);
2032
+ deleteInput.ConditionExpression = condResult.expression;
2033
+ if (Object.keys(condResult.names).length > 0) {
2034
+ deleteInput.ExpressionAttributeNames = condResult.names;
2035
+ }
2036
+ if (Object.keys(condResult.values).length > 0) {
2037
+ deleteInput.ExpressionAttributeValues = condResult.values;
2038
+ }
2039
+ }
2040
+ return deleteInput;
2041
+ }
2042
+ async function executeDelete(modelClass, keyObj, options) {
2043
+ const mw = writeRuntimeFor(options);
2044
+ if (mw) {
2045
+ await executeSingleWriteWithHooks({ kind: "delete", modelClass, key: keyObj, options }, mw);
2046
+ return;
2047
+ }
2048
+ await executeDeleteCore(modelClass, keyObj, options);
2049
+ }
2050
+ async function executeDeleteCore(modelClass, keyObj, options, persistVia, forceOldImage) {
2051
+ const deleteInput = buildDeleteInput(modelClass, keyObj, options);
2052
+ const wantsOld = ChangeCaptureRegistry.wantsOldImage(modelClass) || forceOldImage === true;
2053
+ const send = (input) => ClientManager.getExecutor().delete(input, {
2054
+ ...wantsOld ? { returnOldImage: true } : {},
2055
+ ...options?.retry !== void 0 ? { retry: options.retry } : {}
2056
+ });
2057
+ const result = persistVia ? await persistVia(deleteInput, send) : await send(deleteInput);
2058
+ if (ChangeCaptureRegistry.hasSubscribers()) {
2059
+ const oldItem = result.oldItem;
2060
+ captureWrite({
2061
+ table: deleteInput.TableName,
2062
+ model: modelClass.name,
2063
+ op: "delete",
2064
+ keys: { pk: String(deleteInput.Key.PK), sk: String(deleteInput.Key.SK ?? "") },
2065
+ ...oldItem ? { oldItem } : {}
2066
+ });
2067
+ }
2068
+ }
2069
+
2070
+ // src/operations/write-hooks.ts
2071
+ function writeRuntimeFor(options) {
2072
+ const mw = buildWriteRuntime(options?.context);
2073
+ return mw.active ? mw : void 0;
2074
+ }
2075
+ async function executeSingleWriteWithHooks(req, mw) {
2076
+ const input = {};
2077
+ if (req.item !== void 0) input.item = { ...req.item };
2078
+ if (req.key !== void 0) input.key = { ...req.key };
2079
+ if (req.changes !== void 0) input.changes = { ...req.changes };
2080
+ if (req.options !== void 0) input.options = { ...req.options };
2081
+ const ctx = mw.writeCtx(req.kind, ctxModelFor(req.modelClass), input);
2082
+ try {
2083
+ await mw.runWriteBefore(ctx);
2084
+ const change2 = await persistRewrittenWrite(req.modelClass, ctx, mw);
2085
+ await mw.runWriteAfter(ctx, change2);
2086
+ } catch (err) {
2087
+ await mw.runWriteError(ctx, err);
2088
+ }
2089
+ }
2090
+ async function persistRewrittenWrite(modelClass, ctx, mw) {
2091
+ const opts = ctx.input.options;
2092
+ const origin = { model: ctx.model, kind: ctx.kind };
2093
+ const forceOldImage = mw.hasWriteAfter;
2094
+ const cap = {};
2095
+ if (ctx.kind === "put") {
2096
+ await executePutCore(
2097
+ modelClass,
2098
+ ctx.input.item ?? {},
2099
+ opts,
2100
+ (input, send) => runPersist(mw, { Put: input }, origin, cap, (it) => {
2101
+ const put = it.Put;
2102
+ cap.newImage = put.Item;
2103
+ return send(put);
2104
+ }),
2105
+ forceOldImage
2106
+ );
2107
+ return change(cap, true);
2108
+ }
2109
+ if (ctx.kind === "update") {
2110
+ await executeUpdateCore(
2111
+ modelClass,
2112
+ ctx.input.key ?? {},
2113
+ ctx.input.changes ?? {},
2114
+ opts,
2115
+ (input, send) => runPersist(mw, { Update: input }, origin, cap, (it) => {
2116
+ const upd = it.Update;
2117
+ const old = cap.result?.oldItem;
2118
+ cap.newImage = { ...old ?? upd.Key, ...ctx.input.changes ?? {} };
2119
+ return send(upd);
2120
+ }),
2121
+ forceOldImage
2122
+ );
2123
+ return change(cap, true);
2124
+ }
2125
+ await executeDeleteCore(
2126
+ modelClass,
2127
+ ctx.input.key ?? {},
2128
+ opts,
2129
+ (input, send) => runPersist(
2130
+ mw,
2131
+ { Delete: input },
2132
+ origin,
2133
+ cap,
2134
+ (it) => send(it.Delete)
2135
+ ),
2136
+ forceOldImage
2137
+ );
2138
+ return change(cap, false);
2139
+ }
2140
+ function change(cap, withNew) {
2141
+ const out = {};
2142
+ const oldImage = cap.result?.oldItem;
2143
+ if (oldImage !== void 0) out.oldImage = oldImage;
2144
+ if (withNew && cap.newImage !== void 0) {
2145
+ out.newImage = cap.newImage;
2146
+ }
2147
+ return out;
2148
+ }
2149
+ async function runPersist(mw, item, origin, cap, send) {
2150
+ const ctx = mw.persistCtx([item], [origin]);
2151
+ const result = await mw.runPersist(ctx, async (items) => send(items[0]));
2152
+ cap.result = result;
2153
+ return result;
2154
+ }
2155
+
2156
+ // src/operations/put.ts
2157
+ function buildPutInput(modelClass, item, options) {
2158
+ const meta = MetadataRegistry.get(modelClass);
2159
+ if (!meta.primaryKey) {
2160
+ throw new Error(
2161
+ `No primary key defined for '${modelClass.name}'. Ensure the model has a static \`keys = key(...)\` definition.`
2162
+ );
2163
+ }
2164
+ const tableName = TableMapping.resolve(meta.tableName);
2165
+ const serialized = {};
2166
+ for (const fieldMeta of meta.fields) {
2167
+ const value = item[fieldMeta.propertyName];
2168
+ if (value !== void 0) {
2169
+ serialized[fieldMeta.propertyName] = serializeFieldValue(value, fieldMeta);
2170
+ }
2171
+ }
2172
+ const keyInput = {};
2173
+ for (const fieldName of meta.primaryKey.inputFieldNames) {
2174
+ keyInput[fieldName] = serialized[fieldName] ?? item[fieldName];
2175
+ }
2176
+ const { pk, sk } = resolveSegmentedKey(
2177
+ meta.primaryKey.segmented,
2178
+ keyInput,
2179
+ `${modelClass.name} primary key`
2180
+ );
2181
+ const dynamoItem = {
2182
+ PK: pk,
2183
+ SK: sk ?? "",
2184
+ ...serialized
2185
+ };
2186
+ for (const emb of meta.embeddedFields) {
2187
+ const value = item[emb.propertyName];
2188
+ if (value !== void 0) {
2189
+ dynamoItem[emb.propertyName] = value;
2190
+ }
2191
+ }
2192
+ const available = {};
2193
+ for (const fieldName of meta.fields.map((f) => f.propertyName)) {
2194
+ available[fieldName] = serialized[fieldName] ?? item[fieldName];
2195
+ }
2196
+ for (const gsi2 of meta.gsiDefinitions) {
2197
+ const { pkAttr, pk: gsiPk, skAttr, sk: gsiSk } = deriveGsiKey(
2198
+ gsi2,
2199
+ available,
2200
+ `${modelClass.name} GSI '${gsi2.indexName}'`
2201
+ );
2202
+ dynamoItem[pkAttr] = gsiPk;
2203
+ dynamoItem[skAttr] = gsiSk;
2204
+ }
2205
+ const putInput = {
2206
+ TableName: tableName,
2207
+ Item: dynamoItem
2208
+ };
2209
+ if (options?.condition) {
2210
+ const condResult = buildConditionExpression(options.condition);
2211
+ putInput.ConditionExpression = condResult.expression;
2212
+ if (Object.keys(condResult.names).length > 0) {
2213
+ putInput.ExpressionAttributeNames = condResult.names;
2214
+ }
2215
+ if (Object.keys(condResult.values).length > 0) {
2216
+ putInput.ExpressionAttributeValues = condResult.values;
2217
+ }
2218
+ }
2219
+ return putInput;
2220
+ }
2221
+ async function executePut(modelClass, item, options) {
2222
+ const mw = writeRuntimeFor(options);
2223
+ if (mw) {
2224
+ await executeSingleWriteWithHooks({ kind: "put", modelClass, item, options }, mw);
2225
+ return;
2226
+ }
2227
+ await executePutCore(modelClass, item, options);
2228
+ }
2229
+ async function executePutCore(modelClass, item, options, persistVia, forceOldImage) {
2230
+ const putInput = buildPutInput(modelClass, item, options);
2231
+ const wantsOld = ChangeCaptureRegistry.wantsOldImage(modelClass) || forceOldImage === true;
2232
+ const send = (input) => ClientManager.getExecutor().put(input, {
2233
+ ...wantsOld ? { returnOldImage: true } : {},
2234
+ ...options?.retry !== void 0 ? { retry: options.retry } : {}
2235
+ });
2236
+ const result = persistVia ? await persistVia(putInput, send) : await send(putInput);
2237
+ if (ChangeCaptureRegistry.hasSubscribers()) {
2238
+ const oldItem = result.oldItem;
2239
+ captureWrite({
2240
+ table: putInput.TableName,
2241
+ model: modelClass.name,
2242
+ op: "put",
2243
+ keys: { pk: String(putInput.Item.PK), sk: String(putInput.Item.SK ?? "") },
2244
+ newItem: putInput.Item,
2245
+ ...oldItem ? { oldItem } : {}
2246
+ });
2247
+ }
2248
+ }
2249
+
2261
2250
  // src/runtime/same-key-collapse.ts
2262
2251
  function collapseSameKey(items, view) {
2263
2252
  const groups = /* @__PURE__ */ new Map();
@@ -2448,182 +2437,6 @@ function resolveModelClass(model) {
2448
2437
  }
2449
2438
  return modelClass;
2450
2439
  }
2451
- var TransactionContext = class {
2452
- items = [];
2453
- captureMeta = [];
2454
- /**
2455
- * The logical write ops, parallel to {@link items} but EXCLUDING `conditionCheck`
2456
- * entries (those are read-only assertions, recorded only in `items`). Used by the
2457
- * write-hook path (#139) to run W1 on each logical op and recompose the batch.
2458
- * A `null` entry marks an `items` slot that has no logical op (a ConditionCheck),
2459
- * so the eager and logical arrays can be re-aligned at commit.
2460
- */
2461
- logicalOps = [];
2462
- get itemCount() {
2463
- return this.items.length;
2464
- }
2465
- put(model, item, options) {
2466
- this.assertWithinLimit();
2467
- const modelClass = resolveModelClass(model);
2468
- const putInput = buildPutInput(modelClass, item, options);
2469
- this.items.push({ Put: putInput });
2470
- this.logicalOps.push({ kind: "put", modelClass, item, options });
2471
- this.captureMeta.push({
2472
- modelName: modelClass.name,
2473
- op: "put",
2474
- table: putInput.TableName,
2475
- keys: { pk: String(putInput.Item.PK), sk: String(putInput.Item.SK ?? "") },
2476
- newItem: putInput.Item
2477
- });
2478
- }
2479
- update(model, entity, changes, options) {
2480
- this.assertWithinLimit();
2481
- const modelClass = resolveModelClass(model);
2482
- const updateInput = buildUpdateInput(modelClass, entity, changes, options);
2483
- this.items.push({ Update: updateInput });
2484
- this.logicalOps.push({ kind: "update", modelClass, key: entity, changes, options });
2485
- this.captureMeta.push({
2486
- modelName: modelClass.name,
2487
- op: "update",
2488
- table: updateInput.TableName,
2489
- keys: { pk: String(updateInput.Key.PK), sk: String(updateInput.Key.SK ?? "") }
2490
- });
2491
- }
2492
- delete(model, key, options) {
2493
- this.assertWithinLimit();
2494
- const modelClass = resolveModelClass(model);
2495
- const deleteInput = buildDeleteInput(modelClass, key, options);
2496
- this.items.push({ Delete: deleteInput });
2497
- this.logicalOps.push({ kind: "delete", modelClass, key, options });
2498
- this.captureMeta.push({
2499
- modelName: modelClass.name,
2500
- op: "delete",
2501
- table: deleteInput.TableName,
2502
- keys: { pk: String(deleteInput.Key.PK), sk: String(deleteInput.Key.SK ?? "") }
2503
- });
2504
- }
2505
- /**
2506
- * Add a read-only `ConditionCheck` assertion on a keyed item (issue #81). The
2507
- * item is **not** mutated; the `options.condition` (e.g.
2508
- * `{ attributeExists: 'PK' }` to require the row exists) is asserted, and a
2509
- * failed assertion cancels the **whole** `TransactWriteItems` atomically. This
2510
- * is the foundation for referential-integrity derivation (proposal: `requires
2511
- * <Entity> exists`). A ConditionCheck emits no change-capture record (it writes
2512
- * nothing).
2513
- *
2514
- * @throws if `options.condition` is missing — a ConditionCheck without an
2515
- * assertion is meaningless.
2516
- */
2517
- conditionCheck(model, key, options) {
2518
- if (!options || !options.condition) {
2519
- throw new Error(
2520
- "TransactionContext.conditionCheck requires a `condition` (the read-only assertion it makes); e.g. `{ condition: { attributeExists: 'PK' } }`."
2521
- );
2522
- }
2523
- this.assertWithinLimit();
2524
- const modelClass = resolveModelClass(model);
2525
- const { TableName, Key } = buildDeleteInput(modelClass, key);
2526
- const cond2 = buildConditionExpression(options.condition);
2527
- const check = {
2528
- TableName,
2529
- Key,
2530
- ConditionExpression: cond2.expression
2531
- };
2532
- if (Object.keys(cond2.names).length > 0) {
2533
- check.ExpressionAttributeNames = cond2.names;
2534
- }
2535
- if (Object.keys(cond2.values).length > 0) {
2536
- check.ExpressionAttributeValues = cond2.values;
2537
- }
2538
- this.items.push({ ConditionCheck: check });
2539
- this.logicalOps.push(null);
2540
- }
2541
- /** @internal */
2542
- getTransactItems() {
2543
- return this.items;
2544
- }
2545
- /** @internal — the recorded logical write ops (issue #139 W1), `null` per ConditionCheck slot. */
2546
- getLogicalOps() {
2547
- return this.logicalOps;
2548
- }
2549
- /** @internal — capture descriptors for the write-capture seam (issue #72). */
2550
- getCaptureMeta() {
2551
- return this.captureMeta;
2552
- }
2553
- assertWithinLimit() {
2554
- if (this.items.length >= MAX_TRANSACT_ITEMS) {
2555
- throw new Error(
2556
- `Transaction exceeds DynamoDB limit of ${MAX_TRANSACT_ITEMS} items`
2557
- );
2558
- }
2559
- }
2560
- };
2561
- async function executeTransaction(fn, options) {
2562
- const tx = new TransactionContext();
2563
- await fn(tx);
2564
- let transactItems = tx.getTransactItems();
2565
- if (transactItems.length === 0) {
2566
- return;
2567
- }
2568
- const writeMw = buildWriteRuntime(options?.context);
2569
- if (writeMw.active) {
2570
- const txId = { id: /* @__PURE__ */ Symbol("graphddb.transaction") };
2571
- const logical = tx.getLogicalOps();
2572
- const items = [...transactItems];
2573
- const origins = [];
2574
- const writeCtxs = [];
2575
- const modelBySignature2 = /* @__PURE__ */ new Map();
2576
- for (let i = 0; i < logical.length; i++) {
2577
- const op = logical[i];
2578
- if (op === null) continue;
2579
- const input = {};
2580
- if (op.item !== void 0) input.item = { ...op.item };
2581
- if (op.key !== void 0) input.key = { ...op.key };
2582
- if (op.changes !== void 0) input.changes = { ...op.changes };
2583
- if (op.options !== void 0) input.options = { ...op.options };
2584
- const ctx = writeMw.writeCtx(op.kind, ctxModelFor(op.modelClass), input, txId);
2585
- await writeMw.runWriteBefore(ctx);
2586
- const built = buildLogicalItem(op.modelClass, ctx);
2587
- items[i] = built;
2588
- modelBySignature2.set(execItemKeySignature(built), op.modelClass.name);
2589
- origins.push({ model: ctx.model, kind: ctx.kind });
2590
- writeCtxs.push(ctx);
2591
- }
2592
- transactItems = items;
2593
- await commitTransaction(transactItems, {
2594
- modelBySignature: modelBySignature2,
2595
- middleware: { runtime: writeMw, origins, transaction: txId }
2596
- });
2597
- for (let j = writeCtxs.length - 1; j >= 0; j--) {
2598
- await writeMw.runWriteAfter(writeCtxs[j], {});
2599
- }
2600
- return;
2601
- }
2602
- const modelBySignature = /* @__PURE__ */ new Map();
2603
- for (const meta of tx.getCaptureMeta()) {
2604
- if (meta.modelName) {
2605
- modelBySignature.set(
2606
- `${meta.table}#${meta.keys.pk}#${meta.keys.sk}`,
2607
- meta.modelName
2608
- );
2609
- }
2610
- }
2611
- await commitTransaction(transactItems, {
2612
- modelBySignature
2613
- });
2614
- }
2615
- function buildLogicalItem(modelClass, ctx) {
2616
- const opts = ctx.input.options;
2617
- if (ctx.kind === "put") {
2618
- return { Put: buildPutInput(modelClass, ctx.input.item ?? {}, opts) };
2619
- }
2620
- if (ctx.kind === "update") {
2621
- return {
2622
- Update: buildUpdateInput(modelClass, ctx.input.key ?? {}, ctx.input.changes ?? {}, opts)
2623
- };
2624
- }
2625
- return { Delete: buildDeleteInput(modelClass, ctx.input.key ?? {}, opts) };
2626
- }
2627
2440
 
2628
2441
  export {
2629
2442
  gsi,
@@ -2634,21 +2447,13 @@ export {
2634
2447
  param,
2635
2448
  isParam,
2636
2449
  BATCH_GET_MAX_KEYS,
2637
- BATCH_WRITE_MAX_ITEMS,
2638
2450
  chunkArray,
2639
- DynamoExecutor,
2640
- DEFAULT_MAX_ATTEMPTS,
2641
- DEFAULT_RETRY_POLICY,
2642
- isRetryableError,
2643
- isRetryableTransactionCancellation,
2644
- RetryingExecutor,
2645
2451
  ClientManager,
2646
2452
  isRawCondition,
2647
2453
  cond,
2648
2454
  compileRawCondition,
2649
2455
  condParamName,
2650
2456
  serializeRawCondition,
2651
- collectRawConditionParams,
2652
2457
  CONDITION_OPERATOR_KEYS,
2653
2458
  resolveConditionTree,
2654
2459
  buildConditionExpression,
@@ -2659,7 +2464,6 @@ export {
2659
2464
  buildReadRuntime,
2660
2465
  buildWriteRuntime,
2661
2466
  ctxModelFor,
2662
- buildUpdateExpression,
2663
2467
  attachHiddenKey,
2664
2468
  buildUpdateInput,
2665
2469
  executeUpdate,
@@ -2668,11 +2472,8 @@ export {
2668
2472
  buildPutInput,
2669
2473
  executePut,
2670
2474
  MAX_TRANSACT_ITEMS,
2671
- collapseSameKeyItems,
2672
2475
  commitTransaction,
2673
2476
  execItemKeySignature,
2674
2477
  attachModelClass,
2675
- resolveModelClass,
2676
- TransactionContext,
2677
- executeTransaction
2478
+ resolveModelClass
2678
2479
  };