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.
- package/README.md +6 -6
- package/dist/cdc/index.d.ts +389 -5
- package/dist/cdc/index.js +4 -4
- package/dist/{chunk-AD6ZQTTE.js → chunk-GS4C5VGO.js} +2 -6
- package/dist/{chunk-DFUKGU2Q.js → chunk-HNY2EJPV.js} +216 -229
- package/dist/{chunk-3ZU2VW3L.js → chunk-L2NEDS7U.js} +582 -781
- package/dist/chunk-L4QRCHRQ.js +278 -0
- package/dist/chunk-LAT64YCZ.js +1987 -0
- package/dist/chunk-S2NI4PBW.js +187 -0
- package/dist/{chunk-EOJDN3SA.js → chunk-T44OB5GU.js} +3757 -6138
- package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
- package/dist/cli.js +63 -254
- package/dist/index.d.ts +23 -1550
- package/dist/index.js +94 -1850
- package/dist/internal/index.d.ts +84 -0
- package/dist/internal/index.js +701 -0
- package/dist/{maintenance-view-adapter-BAZ9uBGe.d.ts → key-DZtjAQDh.d.ts} +573 -1817
- package/dist/linter/index.d.ts +39 -7
- package/dist/linter/index.js +22 -4
- package/dist/{registry-LWE54Sdc.d.ts → linter-DQY7gUEk.d.ts} +22 -22
- package/dist/prepared-artifact-HFealr1q.d.ts +281 -0
- package/dist/spec/index.d.ts +506 -5
- package/dist/spec/index.js +22 -18
- package/dist/testing/index.d.ts +2 -3
- package/dist/testing/index.js +4 -4
- package/dist/transform/index.d.ts +1 -1
- package/dist/transform/index.js +4 -4
- package/dist/{types-BQLzTEqh.d.ts → types-2PMXEn5x.d.ts} +8 -10
- package/dist/types-DW__-Icc.d.ts +450 -0
- package/docs/cdc-projection.md +5 -5
- package/docs/class-hydration.md +1 -1
- package/docs/cqrs-contract.md +79 -20
- package/docs/design-patterns.md +5 -5
- package/docs/docs-generation.md +6 -6
- package/docs/middleware.md +15 -15
- package/docs/mutation-command-derivation.md +52 -42
- package/docs/prepared-statements.md +14 -14
- package/docs/python-bridge.md +96 -65
- package/docs/spec.md +153 -124
- package/docs/testing.md +9 -8
- package/package.json +14 -4
- package/dist/chunk-3UD3XIF2.js +0 -860
- package/dist/chunk-MMVHOUM4.js +0 -24
- package/dist/from-change-Ty95KA8C.d.ts +0 -327
- package/dist/index-Dc7d8mWI.d.ts +0 -1089
- package/dist/relation-depth-BRS513Tq.d.ts +0 -36
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
createDefaultLinter,
|
|
6
6
|
isColumn,
|
|
7
7
|
isKeyDefinition
|
|
8
|
-
} from "./chunk-
|
|
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 `
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
|
|
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/
|
|
1362
|
-
var
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
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/
|
|
1731
|
-
function
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
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
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
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
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
)
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
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
|
|
1767
|
-
for (const
|
|
1768
|
-
|
|
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
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
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
|
-
|
|
1780
|
-
|
|
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
|
|
1684
|
+
return {
|
|
1685
|
+
expression: parts.join(" "),
|
|
1686
|
+
names,
|
|
1687
|
+
values
|
|
1688
|
+
};
|
|
1794
1689
|
}
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
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
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
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
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
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/
|
|
1825
|
-
function
|
|
1826
|
-
const
|
|
1827
|
-
if (
|
|
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
|
-
`
|
|
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
|
|
1838
|
-
let value =
|
|
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
|
-
|
|
1744
|
+
primaryKey.segmented,
|
|
1847
1745
|
keyInput,
|
|
1848
1746
|
`${modelClass.name} primary key`
|
|
1849
1747
|
);
|
|
1850
|
-
|
|
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
|
};
|