graphddb 0.2.3 → 0.2.5
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 +339 -284
- package/dist/{chunk-FWDJSGF3.js → chunk-4TYK3AV6.js} +911 -173
- package/dist/{chunk-CVUDLLRS.js → chunk-ITHQ2EDH.js} +1 -1
- package/dist/{chunk-NJEC76TX.js → chunk-SGYBE2OV.js} +417 -47
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +269 -12
- package/dist/index.js +13 -3
- package/dist/testing/index.d.ts +5 -5
- package/dist/testing/index.js +9 -6
- package/dist/{types-SKxZQbQO.d.ts → types--tdHK3vi.d.ts} +828 -165
- package/package.json +1 -1
|
@@ -651,6 +651,210 @@ async function batchWriteChunkWithRetry(docClient, tableName, entries, toCommand
|
|
|
651
651
|
}
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
+
// src/executor/retry-policy.ts
|
|
655
|
+
var DEFAULT_MAX_ATTEMPTS = 10;
|
|
656
|
+
var DEFAULT_RETRY_POLICY = {
|
|
657
|
+
maxAttempts: DEFAULT_MAX_ATTEMPTS,
|
|
658
|
+
computeDelay: computeBackoffDelay,
|
|
659
|
+
jitter: "full",
|
|
660
|
+
isRetryable: isRetryableError
|
|
661
|
+
};
|
|
662
|
+
var THROTTLE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
663
|
+
"ProvisionedThroughputExceededException",
|
|
664
|
+
"ThrottlingException",
|
|
665
|
+
"ThrottlingError",
|
|
666
|
+
"RequestLimitExceeded",
|
|
667
|
+
"RequestThrottled",
|
|
668
|
+
"RequestThrottledException",
|
|
669
|
+
"TooManyRequestsException"
|
|
670
|
+
]);
|
|
671
|
+
var TRANSIENT_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
672
|
+
"InternalServerError",
|
|
673
|
+
"InternalServerErrorException",
|
|
674
|
+
"InternalFailure",
|
|
675
|
+
"ServiceUnavailable",
|
|
676
|
+
"ServiceUnavailableException"
|
|
677
|
+
]);
|
|
678
|
+
var NON_RETRYABLE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
679
|
+
"ValidationException",
|
|
680
|
+
"ConditionalCheckFailedException",
|
|
681
|
+
"ResourceNotFoundException",
|
|
682
|
+
"AccessDeniedException",
|
|
683
|
+
"ResourceInUseException",
|
|
684
|
+
"ItemCollectionSizeLimitExceededException"
|
|
685
|
+
]);
|
|
686
|
+
function errorName(error) {
|
|
687
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
688
|
+
const e = error;
|
|
689
|
+
if (typeof e.name === "string") return e.name;
|
|
690
|
+
if (typeof e.__type === "string") {
|
|
691
|
+
const t = e.__type;
|
|
692
|
+
const hash = t.lastIndexOf("#");
|
|
693
|
+
return hash >= 0 ? t.slice(hash + 1) : t;
|
|
694
|
+
}
|
|
695
|
+
return void 0;
|
|
696
|
+
}
|
|
697
|
+
function httpStatusCode(error) {
|
|
698
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
699
|
+
const meta = error.$metadata;
|
|
700
|
+
const code = meta?.httpStatusCode;
|
|
701
|
+
return typeof code === "number" ? code : void 0;
|
|
702
|
+
}
|
|
703
|
+
function isRetryableError(error) {
|
|
704
|
+
const name = errorName(error);
|
|
705
|
+
if (name && NON_RETRYABLE_ERROR_NAMES.has(name)) return false;
|
|
706
|
+
if (name === "TransactionCanceledException") return false;
|
|
707
|
+
if (name && THROTTLE_ERROR_NAMES.has(name)) return true;
|
|
708
|
+
if (name && TRANSIENT_ERROR_NAMES.has(name)) return true;
|
|
709
|
+
if (typeof error === "object" && error !== null) {
|
|
710
|
+
const retryable = error.$retryable;
|
|
711
|
+
if (retryable != null && retryable !== false) return true;
|
|
712
|
+
}
|
|
713
|
+
const status = httpStatusCode(error);
|
|
714
|
+
if (status === 429 || status != null && status >= 500) return true;
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
var THROTTLE_CANCELLATION_CODES = /* @__PURE__ */ new Set([
|
|
718
|
+
"ThrottlingError",
|
|
719
|
+
"ProvisionedThroughputExceeded",
|
|
720
|
+
"TransactionConflict"
|
|
721
|
+
]);
|
|
722
|
+
function isRetryableTransactionCancellation(error) {
|
|
723
|
+
if (typeof error !== "object" || error === null) return false;
|
|
724
|
+
const reasons = error.CancellationReasons;
|
|
725
|
+
if (!Array.isArray(reasons) || reasons.length === 0) return false;
|
|
726
|
+
let sawThrottle = false;
|
|
727
|
+
for (const reason of reasons) {
|
|
728
|
+
const code = reason && typeof reason === "object" ? reason.Code : void 0;
|
|
729
|
+
if (typeof code !== "string" || code === "None") {
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (code === "ConditionalCheckFailed") {
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
if (THROTTLE_CANCELLATION_CODES.has(code)) {
|
|
736
|
+
sawThrottle = true;
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
return sawThrottle;
|
|
742
|
+
}
|
|
743
|
+
function resolveRetryPolicy(override, global) {
|
|
744
|
+
if (override === false) return null;
|
|
745
|
+
const o = override ?? void 0;
|
|
746
|
+
const g = global ?? void 0;
|
|
747
|
+
const maxAttempts = o?.maxAttempts ?? g?.maxAttempts ?? DEFAULT_RETRY_POLICY.maxAttempts;
|
|
748
|
+
if (maxAttempts <= 1) return null;
|
|
749
|
+
return {
|
|
750
|
+
maxAttempts,
|
|
751
|
+
computeDelay: o?.computeDelay ?? g?.computeDelay ?? DEFAULT_RETRY_POLICY.computeDelay,
|
|
752
|
+
jitter: o?.jitter ?? g?.jitter ?? DEFAULT_RETRY_POLICY.jitter,
|
|
753
|
+
isRetryable: o?.isRetryable ?? g?.isRetryable ?? DEFAULT_RETRY_POLICY.isRetryable,
|
|
754
|
+
// onRetry has no DEFAULT (observability is opt-in): per-call wins, else global.
|
|
755
|
+
onRetry: o?.onRetry ?? g?.onRetry
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function applyJitter(base, jitter) {
|
|
759
|
+
if (jitter === "none") return base;
|
|
760
|
+
return Math.random() * base;
|
|
761
|
+
}
|
|
762
|
+
function sleep2(ms) {
|
|
763
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
764
|
+
}
|
|
765
|
+
async function runWithRetry(policy, operation, send, classify) {
|
|
766
|
+
if (policy === null) {
|
|
767
|
+
return send();
|
|
768
|
+
}
|
|
769
|
+
let attempt = 1;
|
|
770
|
+
for (; ; ) {
|
|
771
|
+
try {
|
|
772
|
+
return await send();
|
|
773
|
+
} catch (error) {
|
|
774
|
+
const forced = classify?.(error);
|
|
775
|
+
const retryable = forced ?? policy.isRetryable(error);
|
|
776
|
+
if (!retryable || attempt >= policy.maxAttempts) {
|
|
777
|
+
throw error;
|
|
778
|
+
}
|
|
779
|
+
const base = policy.computeDelay(attempt);
|
|
780
|
+
const delayMs = applyJitter(base, policy.jitter);
|
|
781
|
+
policy.onRetry?.({ attempt, error, delayMs, operation });
|
|
782
|
+
await sleep2(delayMs);
|
|
783
|
+
attempt += 1;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/executor/retrying-executor.ts
|
|
789
|
+
var RetryingExecutor = class {
|
|
790
|
+
/**
|
|
791
|
+
* @param inner the wrapped executor (the real {@link DynamoExecutor}).
|
|
792
|
+
* @param getGlobalPolicy reads the globally-configured policy (or `null` for the
|
|
793
|
+
* built-in default) at CALL time, so a `DDBModel.setRetryPolicy(...)` after the
|
|
794
|
+
* executor is constructed still takes effect.
|
|
795
|
+
*/
|
|
796
|
+
constructor(inner, getGlobalPolicy) {
|
|
797
|
+
this.inner = inner;
|
|
798
|
+
this.getGlobalPolicy = getGlobalPolicy;
|
|
799
|
+
}
|
|
800
|
+
inner;
|
|
801
|
+
getGlobalPolicy;
|
|
802
|
+
async execute(operation, options) {
|
|
803
|
+
const policy = resolveRetryPolicy(options?.retry, this.getGlobalPolicy());
|
|
804
|
+
const kind = operation.type === "Query" ? "query" : operation.type === "BatchGetItem" ? "batchGet" : "get";
|
|
805
|
+
return runWithRetry(policy, kind, () => this.inner.execute(operation, options));
|
|
806
|
+
}
|
|
807
|
+
async batchGet(input, options) {
|
|
808
|
+
const policy = resolveRetryPolicy(options?.retry, this.getGlobalPolicy());
|
|
809
|
+
return runWithRetry(policy, "batchGet", () => this.inner.batchGet(input, options));
|
|
810
|
+
}
|
|
811
|
+
async put(input, options) {
|
|
812
|
+
const policy = resolveRetryPolicy(options?.retry, this.getGlobalPolicy());
|
|
813
|
+
return runWithRetry(policy, "put", () => this.inner.put(input, options));
|
|
814
|
+
}
|
|
815
|
+
async update(input, options) {
|
|
816
|
+
const policy = resolveRetryPolicy(options?.retry, this.getGlobalPolicy());
|
|
817
|
+
return runWithRetry(policy, "update", () => this.inner.update(input, options));
|
|
818
|
+
}
|
|
819
|
+
async delete(input, options) {
|
|
820
|
+
const policy = resolveRetryPolicy(options?.retry, this.getGlobalPolicy());
|
|
821
|
+
return runWithRetry(policy, "delete", () => this.inner.delete(input, options));
|
|
822
|
+
}
|
|
823
|
+
async batchWrite(tableName, items, options) {
|
|
824
|
+
return this.inner.batchWrite(tableName, items, options);
|
|
825
|
+
}
|
|
826
|
+
async transactWrite(items, options) {
|
|
827
|
+
const policy = resolveRetryPolicy(options?.retry, this.getGlobalPolicy());
|
|
828
|
+
return runWithRetry(
|
|
829
|
+
policy,
|
|
830
|
+
"transactWrite",
|
|
831
|
+
() => this.inner.transactWrite(items, options),
|
|
832
|
+
(error) => errorName(error) === "TransactionCanceledException" ? isRetryableTransactionCancellation(error) : void 0
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
// src/middleware/registry.ts
|
|
838
|
+
var MiddlewareRegistry = class {
|
|
839
|
+
list = [];
|
|
840
|
+
/** Register a middleware (appended last — it runs last in FIFO `before*`). */
|
|
841
|
+
use(mw) {
|
|
842
|
+
this.list.push(mw);
|
|
843
|
+
}
|
|
844
|
+
/** Remove all registered middleware (teardown / tests). */
|
|
845
|
+
clear() {
|
|
846
|
+
this.list = [];
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* A point-in-time, registration-ordered snapshot. A read takes this once at
|
|
850
|
+
* entry (R1) and threads it down, so its hook set is stable for the whole read
|
|
851
|
+
* even if middleware is registered / cleared mid-flight.
|
|
852
|
+
*/
|
|
853
|
+
snapshot() {
|
|
854
|
+
return this.list.slice();
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
|
|
654
858
|
// src/client/ClientManager.ts
|
|
655
859
|
var ClientManager = class {
|
|
656
860
|
static client = null;
|
|
@@ -663,6 +867,22 @@ var ClientManager = class {
|
|
|
663
867
|
*/
|
|
664
868
|
static executor = null;
|
|
665
869
|
static defaultExecutor = null;
|
|
870
|
+
/**
|
|
871
|
+
* The globally-configured single-op retry policy (issue #111). `null` ⇒ the
|
|
872
|
+
* always-on built-in {@link DEFAULT_RETRY_POLICY} applies (retry is enabled out
|
|
873
|
+
* of the box). Set via {@link DDBModel.setRetryPolicy} / {@link setRetryPolicy};
|
|
874
|
+
* read at call time by the default {@link RetryingExecutor}.
|
|
875
|
+
*/
|
|
876
|
+
static retryPolicy = null;
|
|
877
|
+
/**
|
|
878
|
+
* The host-runtime read-middleware registry (issue #50 / #138) — a process-wide
|
|
879
|
+
* ordered list of {@link Middleware}, the same global-config + injectable-seam
|
|
880
|
+
* pattern as {@link retryPolicy} / {@link executor}. Registered via
|
|
881
|
+
* {@link DDBModel.use} / {@link use}, snapshotted per read, and NEVER serialized
|
|
882
|
+
* (it never touches the planner / spec / `operations.json`, so the bridge #48 is
|
|
883
|
+
* unaffected). {@link reset} clears it.
|
|
884
|
+
*/
|
|
885
|
+
static middleware = new MiddlewareRegistry();
|
|
666
886
|
static setClient(client) {
|
|
667
887
|
this.client = client;
|
|
668
888
|
this.documentClient = null;
|
|
@@ -697,23 +917,66 @@ var ClientManager = class {
|
|
|
697
917
|
return this.executor;
|
|
698
918
|
}
|
|
699
919
|
if (!this.defaultExecutor) {
|
|
700
|
-
this.defaultExecutor = new
|
|
920
|
+
this.defaultExecutor = new RetryingExecutor(
|
|
921
|
+
new DynamoExecutor(),
|
|
922
|
+
() => this.retryPolicy
|
|
923
|
+
);
|
|
701
924
|
}
|
|
702
925
|
return this.defaultExecutor;
|
|
703
926
|
}
|
|
704
|
-
/**
|
|
927
|
+
/**
|
|
928
|
+
* Inject a custom executor (test adapter / memory engine). It is used AS-IS —
|
|
929
|
+
* not wrapped in a {@link RetryingExecutor} — so the in-memory adapter (which
|
|
930
|
+
* never throttles) bypasses the retry layer entirely (issue #111).
|
|
931
|
+
*/
|
|
705
932
|
static setExecutor(executor) {
|
|
706
933
|
this.executor = executor;
|
|
707
934
|
}
|
|
708
|
-
/** Restore the default {@link DynamoExecutor}. */
|
|
935
|
+
/** Restore the default {@link RetryingExecutor}-wrapped {@link DynamoExecutor}. */
|
|
709
936
|
static resetExecutor() {
|
|
710
937
|
this.executor = null;
|
|
711
938
|
}
|
|
939
|
+
/**
|
|
940
|
+
* Configure the global single-op retry policy (issue #111). Mirrors
|
|
941
|
+
* {@link setClient} / {@link setTableMapping}: a process-wide setting the default
|
|
942
|
+
* {@link RetryingExecutor} reads. `null` restores the always-on built-in default.
|
|
943
|
+
* A per-call `retry?:` override on an operation option bag takes precedence.
|
|
944
|
+
*/
|
|
945
|
+
static setRetryPolicy(policy) {
|
|
946
|
+
this.retryPolicy = policy;
|
|
947
|
+
}
|
|
948
|
+
/** The globally-configured retry policy, or `null` for the built-in default. */
|
|
949
|
+
static getRetryPolicy() {
|
|
950
|
+
return this.retryPolicy;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Register a read {@link Middleware} (issue #50 / #138). Appended last, so its
|
|
954
|
+
* `before*` hooks run last (FIFO) and its `afterFetch` / `onError` hooks run
|
|
955
|
+
* first (LIFO). Mirrors {@link setRetryPolicy}; backs {@link DDBModel.use}.
|
|
956
|
+
*/
|
|
957
|
+
static use(mw) {
|
|
958
|
+
this.middleware.use(mw);
|
|
959
|
+
}
|
|
960
|
+
/** Remove every registered read middleware (teardown / tests). Backs {@link DDBModel.clearMiddleware}. */
|
|
961
|
+
static clearMiddleware() {
|
|
962
|
+
this.middleware.clear();
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* A point-in-time, registration-ordered snapshot of the registered read
|
|
966
|
+
* middleware. A read takes this once at entry (R1) and threads it down, so the
|
|
967
|
+
* read's hook set stays stable even if middleware is registered / cleared
|
|
968
|
+
* mid-flight.
|
|
969
|
+
*/
|
|
970
|
+
static getMiddleware() {
|
|
971
|
+
return this.middleware.snapshot();
|
|
972
|
+
}
|
|
712
973
|
/** @internal — for testing only */
|
|
713
974
|
static reset() {
|
|
714
975
|
this.client = null;
|
|
715
976
|
this.documentClient = null;
|
|
716
977
|
this.executor = null;
|
|
978
|
+
this.retryPolicy = null;
|
|
979
|
+
this.middleware.clear();
|
|
717
980
|
}
|
|
718
981
|
};
|
|
719
982
|
|
|
@@ -742,7 +1005,7 @@ async function executeBatchGet(docClient, operation) {
|
|
|
742
1005
|
|
|
743
1006
|
// src/executor/dynamo-executor.ts
|
|
744
1007
|
var DynamoExecutor = class {
|
|
745
|
-
async execute(operation) {
|
|
1008
|
+
async execute(operation, _options) {
|
|
746
1009
|
const docClient = await ClientManager.getDocumentClient();
|
|
747
1010
|
if (operation.type === "GetItem") {
|
|
748
1011
|
return executeGetItem(docClient, operation);
|
|
@@ -752,7 +1015,7 @@ var DynamoExecutor = class {
|
|
|
752
1015
|
}
|
|
753
1016
|
return executeQuery(docClient, operation);
|
|
754
1017
|
}
|
|
755
|
-
async batchGet(input) {
|
|
1018
|
+
async batchGet(input, _options) {
|
|
756
1019
|
const docClient = await ClientManager.getDocumentClient();
|
|
757
1020
|
return executeBatchGet(docClient, { type: "BatchGetItem", ...input });
|
|
758
1021
|
}
|
|
@@ -786,7 +1049,7 @@ var DynamoExecutor = class {
|
|
|
786
1049
|
);
|
|
787
1050
|
return toWriteResult(result);
|
|
788
1051
|
}
|
|
789
|
-
async batchWrite(tableName, items) {
|
|
1052
|
+
async batchWrite(tableName, items, _options) {
|
|
790
1053
|
const docClient = await ClientManager.getDocumentClient();
|
|
791
1054
|
for (const chunk of chunkArray(items, BATCH_WRITE_MAX_ITEMS)) {
|
|
792
1055
|
await batchWriteChunkWithRetry(
|
|
@@ -798,7 +1061,7 @@ var DynamoExecutor = class {
|
|
|
798
1061
|
);
|
|
799
1062
|
}
|
|
800
1063
|
}
|
|
801
|
-
async transactWrite(items) {
|
|
1064
|
+
async transactWrite(items, _options) {
|
|
802
1065
|
if (items.length === 0) return;
|
|
803
1066
|
const docClient = await ClientManager.getDocumentClient();
|
|
804
1067
|
const { TransactWriteCommand } = await loadLibDynamoDB();
|
|
@@ -1229,6 +1492,69 @@ function serializeFieldValue(value, fieldMeta) {
|
|
|
1229
1492
|
return value;
|
|
1230
1493
|
}
|
|
1231
1494
|
|
|
1495
|
+
// src/expression/update-expression.ts
|
|
1496
|
+
function isPlainObject(value) {
|
|
1497
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
1498
|
+
}
|
|
1499
|
+
function flattenChanges(obj, parentPath, embeddedFieldNames) {
|
|
1500
|
+
const sets = [];
|
|
1501
|
+
const removes = [];
|
|
1502
|
+
for (const [fieldName, value] of Object.entries(obj)) {
|
|
1503
|
+
const currentPath = [...parentPath, fieldName];
|
|
1504
|
+
if (value === void 0) {
|
|
1505
|
+
removes.push({ path: currentPath });
|
|
1506
|
+
} else if (embeddedFieldNames.has(currentPath[0]) && isPlainObject(value)) {
|
|
1507
|
+
const nested = flattenChanges(value, currentPath, embeddedFieldNames);
|
|
1508
|
+
sets.push(...nested.sets);
|
|
1509
|
+
removes.push(...nested.removes);
|
|
1510
|
+
} else {
|
|
1511
|
+
sets.push({ path: currentPath, value });
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
return { sets, removes };
|
|
1515
|
+
}
|
|
1516
|
+
function buildUpdateExpression(changes, embeddedFieldNames) {
|
|
1517
|
+
const { sets, removes } = flattenChanges(changes, [], embeddedFieldNames);
|
|
1518
|
+
if (sets.length === 0 && removes.length === 0) {
|
|
1519
|
+
throw new Error("No changes provided for update.");
|
|
1520
|
+
}
|
|
1521
|
+
const names = {};
|
|
1522
|
+
const values = {};
|
|
1523
|
+
let valueCounter = 0;
|
|
1524
|
+
const setClauses = [];
|
|
1525
|
+
for (const op of sets) {
|
|
1526
|
+
const pathExpr = op.path.map((segment) => {
|
|
1527
|
+
const nameKey = `#${segment}`;
|
|
1528
|
+
names[nameKey] = segment;
|
|
1529
|
+
return nameKey;
|
|
1530
|
+
}).join(".");
|
|
1531
|
+
const valueKey = `:val${valueCounter++}`;
|
|
1532
|
+
values[valueKey] = op.value;
|
|
1533
|
+
setClauses.push(`${pathExpr} = ${valueKey}`);
|
|
1534
|
+
}
|
|
1535
|
+
const removeClauses = [];
|
|
1536
|
+
for (const op of removes) {
|
|
1537
|
+
const pathExpr = op.path.map((segment) => {
|
|
1538
|
+
const nameKey = `#${segment}`;
|
|
1539
|
+
names[nameKey] = segment;
|
|
1540
|
+
return nameKey;
|
|
1541
|
+
}).join(".");
|
|
1542
|
+
removeClauses.push(pathExpr);
|
|
1543
|
+
}
|
|
1544
|
+
const parts = [];
|
|
1545
|
+
if (setClauses.length > 0) {
|
|
1546
|
+
parts.push(`SET ${setClauses.join(", ")}`);
|
|
1547
|
+
}
|
|
1548
|
+
if (removeClauses.length > 0) {
|
|
1549
|
+
parts.push(`REMOVE ${removeClauses.join(", ")}`);
|
|
1550
|
+
}
|
|
1551
|
+
return {
|
|
1552
|
+
expression: parts.join(" "),
|
|
1553
|
+
names,
|
|
1554
|
+
values
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1232
1558
|
// src/metadata/segments.ts
|
|
1233
1559
|
var SEGMENT_DELIMITER = "#";
|
|
1234
1560
|
function segmentComplete(segment, values) {
|
|
@@ -1368,6 +1694,34 @@ function missingGsiFields(gsi2, available) {
|
|
|
1368
1694
|
return gsi2.inputFieldNames.filter((f) => available[f] === void 0);
|
|
1369
1695
|
}
|
|
1370
1696
|
|
|
1697
|
+
// src/hydrator/hidden-key.ts
|
|
1698
|
+
var GRAPHDDB_KEY = /* @__PURE__ */ Symbol("graphddb:key");
|
|
1699
|
+
function attachHiddenKey(result, raw) {
|
|
1700
|
+
const pk = raw.PK;
|
|
1701
|
+
if (typeof pk !== "string") {
|
|
1702
|
+
throw new Error(
|
|
1703
|
+
"Cannot attach updatable key: raw item is missing a string `PK` attribute."
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
const sk = raw.SK;
|
|
1707
|
+
const value = {
|
|
1708
|
+
PK: pk,
|
|
1709
|
+
SK: typeof sk === "string" ? sk : ""
|
|
1710
|
+
};
|
|
1711
|
+
Object.defineProperty(result, GRAPHDDB_KEY, {
|
|
1712
|
+
value,
|
|
1713
|
+
enumerable: false,
|
|
1714
|
+
writable: false,
|
|
1715
|
+
configurable: false
|
|
1716
|
+
});
|
|
1717
|
+
return result;
|
|
1718
|
+
}
|
|
1719
|
+
function readHiddenKey(entity) {
|
|
1720
|
+
const value = entity[GRAPHDDB_KEY];
|
|
1721
|
+
if (value === void 0) return void 0;
|
|
1722
|
+
return value;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1371
1725
|
// src/capture/registry.ts
|
|
1372
1726
|
var ChangeCaptureRegistryImpl = class {
|
|
1373
1727
|
subscribers = /* @__PURE__ */ new Set();
|
|
@@ -1437,6 +1791,278 @@ function captureWrite(record) {
|
|
|
1437
1791
|
ChangeCaptureRegistry.emit(record);
|
|
1438
1792
|
}
|
|
1439
1793
|
|
|
1794
|
+
// src/middleware/runtime.ts
|
|
1795
|
+
function opKind(operation) {
|
|
1796
|
+
return operation.type;
|
|
1797
|
+
}
|
|
1798
|
+
var MiddlewareRuntime = class {
|
|
1799
|
+
/** The frozen, registration-ordered middleware snapshot for this read. */
|
|
1800
|
+
chain;
|
|
1801
|
+
/** The host-injected per-call context (`{}` when the read passed none). */
|
|
1802
|
+
context;
|
|
1803
|
+
constructor(chain, context) {
|
|
1804
|
+
this.chain = chain;
|
|
1805
|
+
this.context = context;
|
|
1806
|
+
}
|
|
1807
|
+
/** `true` when at least one middleware is registered for this read. */
|
|
1808
|
+
get active() {
|
|
1809
|
+
return this.chain.length > 0;
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Build the request-level context (R1 / R4 / R5 share it). Pure — no hooks run
|
|
1813
|
+
* — so a caller can create it up front and still reference it from R5 even if
|
|
1814
|
+
* R1 throws mid-chain.
|
|
1815
|
+
*/
|
|
1816
|
+
requestCtx(kind, model, params) {
|
|
1817
|
+
return { kind, model, context: this.context, state: {}, params };
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* R1 — request entry, before key-resolution / plan. Builds the
|
|
1821
|
+
* {@link ReadRequestCtx} then runs every `read.before` FIFO; a hook may mutate
|
|
1822
|
+
* `ctx.params` and may `throw` to cancel the read. Returns the (possibly
|
|
1823
|
+
* hook-mutated) ctx so the caller can re-read `params` and reuse the SAME ctx
|
|
1824
|
+
* for R4 / R5 (shared `state`).
|
|
1825
|
+
*/
|
|
1826
|
+
async runRequestBefore(kind, model, params) {
|
|
1827
|
+
const ctx = this.requestCtx(kind, model, params);
|
|
1828
|
+
for (const mw of this.chain) {
|
|
1829
|
+
const before = mw.read?.before;
|
|
1830
|
+
if (before) await before(ctx);
|
|
1831
|
+
}
|
|
1832
|
+
return ctx;
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* R4 — final hydrated, relation-merged result. Runs every `read.afterFetch`
|
|
1836
|
+
* LIFO, threading each hook's return value into the next (onion), and returns
|
|
1837
|
+
* the final (possibly replaced) result. `ctx` is the SAME object R1 produced,
|
|
1838
|
+
* so a `before`/`afterFetch` pair shares `ctx.state`.
|
|
1839
|
+
*/
|
|
1840
|
+
async runRequestAfter(ctx, result) {
|
|
1841
|
+
let current = result;
|
|
1842
|
+
for (let i = this.chain.length - 1; i >= 0; i--) {
|
|
1843
|
+
const afterFetch = this.chain[i].read?.afterFetch;
|
|
1844
|
+
if (afterFetch) current = await afterFetch(ctx, current);
|
|
1845
|
+
}
|
|
1846
|
+
return current;
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* R5 (request-level) — runs every `read.onError` LIFO. A hook may **recover**
|
|
1850
|
+
* by RETURNING a non-`undefined` value: it becomes the read's resolved result
|
|
1851
|
+
* in place of throwing, and the FIRST such hook (in LIFO order) wins and
|
|
1852
|
+
* short-circuits the rest of the chain. If every hook declines (returns
|
|
1853
|
+
* `undefined`/`void`), the original error rethrows — preserving the
|
|
1854
|
+
* "failed read still rejects" default. (Proposal R5 + appendix A: onError may
|
|
1855
|
+
* "rethrow / recover"; hooks are unrestricted.)
|
|
1856
|
+
*
|
|
1857
|
+
* The recovered value is returned as `T` (the read's result type); its shape
|
|
1858
|
+
* is the host's responsibility — a hook recovering a `query` should return an
|
|
1859
|
+
* item / `null`, one recovering a `list` a `{ items, cursor }`.
|
|
1860
|
+
*/
|
|
1861
|
+
async runRequestError(ctx, err) {
|
|
1862
|
+
for (let i = this.chain.length - 1; i >= 0; i--) {
|
|
1863
|
+
const onError = this.chain[i].read?.onError;
|
|
1864
|
+
if (!onError) continue;
|
|
1865
|
+
const recovered = await onError(ctx, err);
|
|
1866
|
+
if (recovered !== void 0) return recovered;
|
|
1867
|
+
}
|
|
1868
|
+
throw err;
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* Drive ONE physical op (root read or any fan-out fetch) through R2 → send →
|
|
1872
|
+
* R3, with R5 (op-level) on failure:
|
|
1873
|
+
*
|
|
1874
|
+
* 1. R2 (`read.op.before`, FIFO) — may mutate `ctx.operation`, may `throw`;
|
|
1875
|
+
* 2. the caller's `send` runs against the (possibly mutated) operation;
|
|
1876
|
+
* 3. R3 (`read.op.afterFetch`, LIFO) — transforms the raw items (onion);
|
|
1877
|
+
* 4. on any throw from `send` (or R2 / R3), R5 (`read.op.onError`, LIFO) runs:
|
|
1878
|
+
* a hook may **recover** by RETURNING an `Item[]` (those become this op's
|
|
1879
|
+
* items and the pipeline continues); the FIRST hook that returns items wins
|
|
1880
|
+
* and short-circuits the chain. If none recovers, the original error
|
|
1881
|
+
* rethrows.
|
|
1882
|
+
*
|
|
1883
|
+
* Returns the (possibly R3-replaced, or R5-recovered) items. The `send` closure
|
|
1884
|
+
* receives the final operation so a caller that built its operation from
|
|
1885
|
+
* `ctx.operation` before this call still observes an R2 mutation (the caller
|
|
1886
|
+
* MUST send `ctx.operation`, which this method passes through).
|
|
1887
|
+
*/
|
|
1888
|
+
async runOp(operation, relationPath, model, send) {
|
|
1889
|
+
const ctx = {
|
|
1890
|
+
kind: opKind(operation),
|
|
1891
|
+
model,
|
|
1892
|
+
context: this.context,
|
|
1893
|
+
state: {},
|
|
1894
|
+
operation,
|
|
1895
|
+
relationPath
|
|
1896
|
+
};
|
|
1897
|
+
try {
|
|
1898
|
+
for (const mw of this.chain) {
|
|
1899
|
+
const before = mw.read?.op?.before;
|
|
1900
|
+
if (before) await before(ctx);
|
|
1901
|
+
}
|
|
1902
|
+
let items = await send(ctx.operation);
|
|
1903
|
+
for (let i = this.chain.length - 1; i >= 0; i--) {
|
|
1904
|
+
const afterFetch = this.chain[i].read?.op?.afterFetch;
|
|
1905
|
+
if (afterFetch) items = await afterFetch(ctx, items);
|
|
1906
|
+
}
|
|
1907
|
+
return items;
|
|
1908
|
+
} catch (err) {
|
|
1909
|
+
for (let i = this.chain.length - 1; i >= 0; i--) {
|
|
1910
|
+
const onError = this.chain[i].read?.op?.onError;
|
|
1911
|
+
if (!onError) continue;
|
|
1912
|
+
const recovered = await onError(ctx, err);
|
|
1913
|
+
if (recovered !== void 0) return recovered;
|
|
1914
|
+
}
|
|
1915
|
+
throw err;
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
};
|
|
1919
|
+
var NO_MIDDLEWARE = new MiddlewareRuntime([], {});
|
|
1920
|
+
var WriteRuntime = class {
|
|
1921
|
+
chain;
|
|
1922
|
+
context;
|
|
1923
|
+
constructor(chain, context) {
|
|
1924
|
+
this.chain = chain;
|
|
1925
|
+
this.context = context;
|
|
1926
|
+
}
|
|
1927
|
+
/** `true` when at least one middleware is registered for this write. */
|
|
1928
|
+
get active() {
|
|
1929
|
+
return this.chain.length > 0;
|
|
1930
|
+
}
|
|
1931
|
+
/**
|
|
1932
|
+
* `true` when at least one registered middleware sets a `write.after` (W2) hook.
|
|
1933
|
+
* The single-op write path (issue #139) uses this to decide whether to request
|
|
1934
|
+
* the pre-write image (`ReturnValues: ALL_OLD`) so W2 receives a real
|
|
1935
|
+
* `{ oldImage, newImage }` rather than `{}` — the cost is paid only when a W2
|
|
1936
|
+
* hook actually exists.
|
|
1937
|
+
*/
|
|
1938
|
+
get hasWriteAfter() {
|
|
1939
|
+
return this.chain.some((mw) => mw.write?.after !== void 0);
|
|
1940
|
+
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Build a W1/W2/W5 logical-write context (pure — no hooks run). The caller can
|
|
1943
|
+
* create it up front and reuse the SAME object across W1 → W2 / W5 (shared
|
|
1944
|
+
* `state`). `transaction` is set when the op is part of an atomic batch.
|
|
1945
|
+
*/
|
|
1946
|
+
writeCtx(kind, model, input, transaction) {
|
|
1947
|
+
return transaction !== void 0 ? { kind, model, context: this.context, state: {}, input, transaction } : { kind, model, context: this.context, state: {}, input };
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* W1 — logical write, before effect derivation. Runs every `write.before` FIFO;
|
|
1951
|
+
* a hook may mutate `ctx.input` / `ctx.kind` (incl. the delete→update rewrite)
|
|
1952
|
+
* and may `throw` to cancel (in a transaction, the throw propagates and aborts
|
|
1953
|
+
* the whole batch). The caller re-reads `ctx.kind` / `ctx.input` after this
|
|
1954
|
+
* returns so a rewrite flows into derivation.
|
|
1955
|
+
*/
|
|
1956
|
+
async runWriteBefore(ctx) {
|
|
1957
|
+
for (const mw of this.chain) {
|
|
1958
|
+
const before = mw.write?.before;
|
|
1959
|
+
if (before) await before(ctx);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* W2 — logical write, after commit. Runs every `write.after` LIFO with the
|
|
1964
|
+
* committed change. Observe-only (no return threading); the SAME ctx W1
|
|
1965
|
+
* produced is passed so a before/after pair shares `ctx.state`.
|
|
1966
|
+
*/
|
|
1967
|
+
async runWriteAfter(ctx, change2) {
|
|
1968
|
+
for (let i = this.chain.length - 1; i >= 0; i--) {
|
|
1969
|
+
const after = this.chain[i].write?.after;
|
|
1970
|
+
if (after) await after(ctx, change2);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* W5 (logical-level) — runs every `write.onError` LIFO. A hook may **recover**
|
|
1975
|
+
* by RETURNING a non-`undefined` value (the first such hook wins and
|
|
1976
|
+
* short-circuits the chain); the value becomes the write's resolved value.
|
|
1977
|
+
* Otherwise the original error rethrows (symmetric with the read `onError`).
|
|
1978
|
+
*/
|
|
1979
|
+
async runWriteError(ctx, err) {
|
|
1980
|
+
for (let i = this.chain.length - 1; i >= 0; i--) {
|
|
1981
|
+
const onError = this.chain[i].write?.onError;
|
|
1982
|
+
if (!onError) continue;
|
|
1983
|
+
const recovered = await onError(ctx, err);
|
|
1984
|
+
if (recovered !== void 0) return recovered;
|
|
1985
|
+
}
|
|
1986
|
+
throw err;
|
|
1987
|
+
}
|
|
1988
|
+
/** Build a W3/W4/W5 persist context (pure — no hooks run). */
|
|
1989
|
+
persistCtx(items, origins, transaction) {
|
|
1990
|
+
return transaction !== void 0 ? { items, origins, context: this.context, state: {}, transaction } : { items, origins, context: this.context, state: {} };
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Drive the PHYSICAL persist of one composed batch through W3 → send → W4, with
|
|
1994
|
+
* W5 (persist-level) on failure:
|
|
1995
|
+
*
|
|
1996
|
+
* 1. W3 (`write.persist.before`, FIFO) — may mutate `ctx.items`, may `throw`;
|
|
1997
|
+
* 2. the caller's `send` runs against the (possibly mutated) `ctx.items`;
|
|
1998
|
+
* 3. W4 (`write.persist.after`, LIFO) — observes the executor results;
|
|
1999
|
+
* 4. on any throw, W5 (`write.persist.onError`, LIFO) runs: a hook may
|
|
2000
|
+
* **recover** by returning a value (treated as the send's result; W4 still
|
|
2001
|
+
* runs with it). If none recovers, the original error rethrows.
|
|
2002
|
+
*
|
|
2003
|
+
* The `send` closure receives the final `ctx.items` so a caller that sends what
|
|
2004
|
+
* this method passes through observes a W3 mutation on the ACTUAL send. Returns
|
|
2005
|
+
* the persist result (the executor's, or a W5-recovered value).
|
|
2006
|
+
*/
|
|
2007
|
+
async runPersist(ctx, send) {
|
|
2008
|
+
let results;
|
|
2009
|
+
try {
|
|
2010
|
+
for (const mw of this.chain) {
|
|
2011
|
+
const before = mw.write?.persist?.before;
|
|
2012
|
+
if (before) await before(ctx);
|
|
2013
|
+
}
|
|
2014
|
+
results = await send(ctx.items);
|
|
2015
|
+
} catch (err) {
|
|
2016
|
+
let recovered;
|
|
2017
|
+
let didRecover = false;
|
|
2018
|
+
for (let i = this.chain.length - 1; i >= 0; i--) {
|
|
2019
|
+
const onError = this.chain[i].write?.persist?.onError;
|
|
2020
|
+
if (!onError) continue;
|
|
2021
|
+
const r = await onError(ctx, err);
|
|
2022
|
+
if (r !== void 0) {
|
|
2023
|
+
recovered = r;
|
|
2024
|
+
didRecover = true;
|
|
2025
|
+
break;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
if (!didRecover) throw err;
|
|
2029
|
+
results = recovered;
|
|
2030
|
+
}
|
|
2031
|
+
for (let i = this.chain.length - 1; i >= 0; i--) {
|
|
2032
|
+
const after = this.chain[i].write?.persist?.after;
|
|
2033
|
+
if (after) await after(ctx, results);
|
|
2034
|
+
}
|
|
2035
|
+
return results;
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
var NO_WRITE_MIDDLEWARE = new WriteRuntime([], {});
|
|
2039
|
+
|
|
2040
|
+
// src/middleware/context.ts
|
|
2041
|
+
function buildReadRuntime(context) {
|
|
2042
|
+
const chain = ClientManager.getMiddleware();
|
|
2043
|
+
if (chain.length === 0) return NO_MIDDLEWARE;
|
|
2044
|
+
return new MiddlewareRuntime(chain, context ?? {});
|
|
2045
|
+
}
|
|
2046
|
+
function buildWriteRuntime(context) {
|
|
2047
|
+
const chain = ClientManager.getMiddleware();
|
|
2048
|
+
if (chain.length === 0) return NO_WRITE_MIDDLEWARE;
|
|
2049
|
+
return new WriteRuntime(chain, context ?? {});
|
|
2050
|
+
}
|
|
2051
|
+
var ctxModelCache = /* @__PURE__ */ new WeakMap();
|
|
2052
|
+
function ctxModelFor(modelClass) {
|
|
2053
|
+
const cached2 = ctxModelCache.get(modelClass);
|
|
2054
|
+
if (cached2) return cached2;
|
|
2055
|
+
const asModel = modelClass.asModel;
|
|
2056
|
+
if (typeof asModel !== "function") {
|
|
2057
|
+
throw new Error(
|
|
2058
|
+
"graphddb middleware: cannot resolve a ModelStatic for the read target (the model class has no static asModel()). This is an internal invariant."
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
const resolved = asModel.call(modelClass);
|
|
2062
|
+
ctxModelCache.set(modelClass, resolved);
|
|
2063
|
+
return resolved;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
1440
2066
|
// src/operations/put.ts
|
|
1441
2067
|
function buildPutInput(modelClass, item, options) {
|
|
1442
2068
|
const meta = MetadataRegistry.get(modelClass);
|
|
@@ -1503,12 +2129,21 @@ function buildPutInput(modelClass, item, options) {
|
|
|
1503
2129
|
return putInput;
|
|
1504
2130
|
}
|
|
1505
2131
|
async function executePut(modelClass, item, options) {
|
|
2132
|
+
const mw = writeRuntimeFor(options);
|
|
2133
|
+
if (mw) {
|
|
2134
|
+
await executeSingleWriteWithHooks({ kind: "put", modelClass, item, options }, mw);
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
await executePutCore(modelClass, item, options);
|
|
2138
|
+
}
|
|
2139
|
+
async function executePutCore(modelClass, item, options, persistVia, forceOldImage) {
|
|
1506
2140
|
const putInput = buildPutInput(modelClass, item, options);
|
|
1507
|
-
const wantsOld = ChangeCaptureRegistry.wantsOldImage(modelClass);
|
|
1508
|
-
const
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
);
|
|
2141
|
+
const wantsOld = ChangeCaptureRegistry.wantsOldImage(modelClass) || forceOldImage === true;
|
|
2142
|
+
const send = (input) => ClientManager.getExecutor().put(input, {
|
|
2143
|
+
...wantsOld ? { returnOldImage: true } : {},
|
|
2144
|
+
...options?.retry !== void 0 ? { retry: options.retry } : {}
|
|
2145
|
+
});
|
|
2146
|
+
const result = persistVia ? await persistVia(putInput, send) : await send(putInput);
|
|
1512
2147
|
if (ChangeCaptureRegistry.hasSubscribers()) {
|
|
1513
2148
|
const oldItem = result.oldItem;
|
|
1514
2149
|
captureWrite({
|
|
@@ -1522,95 +2157,163 @@ async function executePut(modelClass, item, options) {
|
|
|
1522
2157
|
}
|
|
1523
2158
|
}
|
|
1524
2159
|
|
|
1525
|
-
// src/
|
|
1526
|
-
function
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
for (const [fieldName, value] of Object.entries(obj)) {
|
|
1533
|
-
const currentPath = [...parentPath, fieldName];
|
|
1534
|
-
if (value === void 0) {
|
|
1535
|
-
removes.push({ path: currentPath });
|
|
1536
|
-
} else if (embeddedFieldNames.has(currentPath[0]) && isPlainObject(value)) {
|
|
1537
|
-
const nested = flattenChanges(value, currentPath, embeddedFieldNames);
|
|
1538
|
-
sets.push(...nested.sets);
|
|
1539
|
-
removes.push(...nested.removes);
|
|
1540
|
-
} else {
|
|
1541
|
-
sets.push({ path: currentPath, value });
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
return { sets, removes };
|
|
1545
|
-
}
|
|
1546
|
-
function buildUpdateExpression(changes, embeddedFieldNames) {
|
|
1547
|
-
const { sets, removes } = flattenChanges(changes, [], embeddedFieldNames);
|
|
1548
|
-
if (sets.length === 0 && removes.length === 0) {
|
|
1549
|
-
throw new Error("No changes provided for update.");
|
|
2160
|
+
// src/operations/delete.ts
|
|
2161
|
+
function buildDeleteInput(modelClass, keyObj, options) {
|
|
2162
|
+
const meta = MetadataRegistry.get(modelClass);
|
|
2163
|
+
if (!meta.primaryKey) {
|
|
2164
|
+
throw new Error(
|
|
2165
|
+
`No primary key defined for '${modelClass.name}'. Ensure the model has a static \`keys = key(...)\` definition.`
|
|
2166
|
+
);
|
|
1550
2167
|
}
|
|
1551
|
-
const
|
|
1552
|
-
const
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
setClauses.push(`${pathExpr} = ${valueKey}`);
|
|
2168
|
+
const tableName = TableMapping.resolve(meta.tableName);
|
|
2169
|
+
const fieldMap = new Map(
|
|
2170
|
+
meta.fields.map((f) => [f.propertyName, f])
|
|
2171
|
+
);
|
|
2172
|
+
const keyInput = {};
|
|
2173
|
+
for (const fieldName of meta.primaryKey.inputFieldNames) {
|
|
2174
|
+
let value = keyObj[fieldName];
|
|
2175
|
+
const fieldMeta = fieldMap.get(fieldName);
|
|
2176
|
+
if (fieldMeta && value !== void 0) {
|
|
2177
|
+
value = serializeFieldValue(value, fieldMeta);
|
|
2178
|
+
}
|
|
2179
|
+
keyInput[fieldName] = value;
|
|
1564
2180
|
}
|
|
1565
|
-
const
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
2181
|
+
const { pk, sk } = resolveSegmentedKey(
|
|
2182
|
+
meta.primaryKey.segmented,
|
|
2183
|
+
keyInput,
|
|
2184
|
+
`${modelClass.name} primary key`
|
|
2185
|
+
);
|
|
2186
|
+
const deleteInput = {
|
|
2187
|
+
TableName: tableName,
|
|
2188
|
+
Key: {
|
|
2189
|
+
PK: pk,
|
|
2190
|
+
SK: sk ?? ""
|
|
2191
|
+
}
|
|
2192
|
+
};
|
|
2193
|
+
if (options?.condition) {
|
|
2194
|
+
const condResult = buildConditionExpression(options.condition);
|
|
2195
|
+
deleteInput.ConditionExpression = condResult.expression;
|
|
2196
|
+
if (Object.keys(condResult.names).length > 0) {
|
|
2197
|
+
deleteInput.ExpressionAttributeNames = condResult.names;
|
|
2198
|
+
}
|
|
2199
|
+
if (Object.keys(condResult.values).length > 0) {
|
|
2200
|
+
deleteInput.ExpressionAttributeValues = condResult.values;
|
|
2201
|
+
}
|
|
1573
2202
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
2203
|
+
return deleteInput;
|
|
2204
|
+
}
|
|
2205
|
+
async function executeDelete(modelClass, keyObj, options) {
|
|
2206
|
+
const mw = writeRuntimeFor(options);
|
|
2207
|
+
if (mw) {
|
|
2208
|
+
await executeSingleWriteWithHooks({ kind: "delete", modelClass, key: keyObj, options }, mw);
|
|
2209
|
+
return;
|
|
1577
2210
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
2211
|
+
await executeDeleteCore(modelClass, keyObj, options);
|
|
2212
|
+
}
|
|
2213
|
+
async function executeDeleteCore(modelClass, keyObj, options, persistVia, forceOldImage) {
|
|
2214
|
+
const deleteInput = buildDeleteInput(modelClass, keyObj, options);
|
|
2215
|
+
const wantsOld = ChangeCaptureRegistry.wantsOldImage(modelClass) || forceOldImage === true;
|
|
2216
|
+
const send = (input) => ClientManager.getExecutor().delete(input, {
|
|
2217
|
+
...wantsOld ? { returnOldImage: true } : {},
|
|
2218
|
+
...options?.retry !== void 0 ? { retry: options.retry } : {}
|
|
2219
|
+
});
|
|
2220
|
+
const result = persistVia ? await persistVia(deleteInput, send) : await send(deleteInput);
|
|
2221
|
+
if (ChangeCaptureRegistry.hasSubscribers()) {
|
|
2222
|
+
const oldItem = result.oldItem;
|
|
2223
|
+
captureWrite({
|
|
2224
|
+
table: deleteInput.TableName,
|
|
2225
|
+
model: modelClass.name,
|
|
2226
|
+
op: "delete",
|
|
2227
|
+
keys: { pk: String(deleteInput.Key.PK), sk: String(deleteInput.Key.SK ?? "") },
|
|
2228
|
+
...oldItem ? { oldItem } : {}
|
|
2229
|
+
});
|
|
1580
2230
|
}
|
|
1581
|
-
return {
|
|
1582
|
-
expression: parts.join(" "),
|
|
1583
|
-
names,
|
|
1584
|
-
values
|
|
1585
|
-
};
|
|
1586
2231
|
}
|
|
1587
2232
|
|
|
1588
|
-
// src/
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
2233
|
+
// src/operations/write-hooks.ts
|
|
2234
|
+
function writeRuntimeFor(options) {
|
|
2235
|
+
const mw = buildWriteRuntime(options?.context);
|
|
2236
|
+
return mw.active ? mw : void 0;
|
|
2237
|
+
}
|
|
2238
|
+
async function executeSingleWriteWithHooks(req, mw) {
|
|
2239
|
+
const input = {};
|
|
2240
|
+
if (req.item !== void 0) input.item = { ...req.item };
|
|
2241
|
+
if (req.key !== void 0) input.key = { ...req.key };
|
|
2242
|
+
if (req.changes !== void 0) input.changes = { ...req.changes };
|
|
2243
|
+
if (req.options !== void 0) input.options = { ...req.options };
|
|
2244
|
+
const ctx = mw.writeCtx(req.kind, ctxModelFor(req.modelClass), input);
|
|
2245
|
+
try {
|
|
2246
|
+
await mw.runWriteBefore(ctx);
|
|
2247
|
+
const change2 = await persistRewrittenWrite(req.modelClass, ctx, mw);
|
|
2248
|
+
await mw.runWriteAfter(ctx, change2);
|
|
2249
|
+
} catch (err) {
|
|
2250
|
+
await mw.runWriteError(ctx, err);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
async function persistRewrittenWrite(modelClass, ctx, mw) {
|
|
2254
|
+
const opts = ctx.input.options;
|
|
2255
|
+
const origin = { model: ctx.model, kind: ctx.kind };
|
|
2256
|
+
const forceOldImage = mw.hasWriteAfter;
|
|
2257
|
+
const cap = {};
|
|
2258
|
+
if (ctx.kind === "put") {
|
|
2259
|
+
await executePutCore(
|
|
2260
|
+
modelClass,
|
|
2261
|
+
ctx.input.item ?? {},
|
|
2262
|
+
opts,
|
|
2263
|
+
(input, send) => runPersist(mw, { Put: input }, origin, cap, (it) => {
|
|
2264
|
+
const put = it.Put;
|
|
2265
|
+
cap.newImage = put.Item;
|
|
2266
|
+
return send(put);
|
|
2267
|
+
}),
|
|
2268
|
+
forceOldImage
|
|
1595
2269
|
);
|
|
2270
|
+
return change(cap, true);
|
|
2271
|
+
}
|
|
2272
|
+
if (ctx.kind === "update") {
|
|
2273
|
+
await executeUpdateCore(
|
|
2274
|
+
modelClass,
|
|
2275
|
+
ctx.input.key ?? {},
|
|
2276
|
+
ctx.input.changes ?? {},
|
|
2277
|
+
opts,
|
|
2278
|
+
(input, send) => runPersist(mw, { Update: input }, origin, cap, (it) => {
|
|
2279
|
+
const upd = it.Update;
|
|
2280
|
+
const old = cap.result?.oldItem;
|
|
2281
|
+
cap.newImage = { ...old ?? upd.Key, ...ctx.input.changes ?? {} };
|
|
2282
|
+
return send(upd);
|
|
2283
|
+
}),
|
|
2284
|
+
forceOldImage
|
|
2285
|
+
);
|
|
2286
|
+
return change(cap, true);
|
|
1596
2287
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
2288
|
+
await executeDeleteCore(
|
|
2289
|
+
modelClass,
|
|
2290
|
+
ctx.input.key ?? {},
|
|
2291
|
+
opts,
|
|
2292
|
+
(input, send) => runPersist(
|
|
2293
|
+
mw,
|
|
2294
|
+
{ Delete: input },
|
|
2295
|
+
origin,
|
|
2296
|
+
cap,
|
|
2297
|
+
(it) => send(it.Delete)
|
|
2298
|
+
),
|
|
2299
|
+
forceOldImage
|
|
2300
|
+
);
|
|
2301
|
+
return change(cap, false);
|
|
1609
2302
|
}
|
|
1610
|
-
function
|
|
1611
|
-
const
|
|
1612
|
-
|
|
1613
|
-
|
|
2303
|
+
function change(cap, withNew) {
|
|
2304
|
+
const out = {};
|
|
2305
|
+
const oldImage = cap.result?.oldItem;
|
|
2306
|
+
if (oldImage !== void 0) out.oldImage = oldImage;
|
|
2307
|
+
if (withNew && cap.newImage !== void 0) {
|
|
2308
|
+
out.newImage = cap.newImage;
|
|
2309
|
+
}
|
|
2310
|
+
return out;
|
|
2311
|
+
}
|
|
2312
|
+
async function runPersist(mw, item, origin, cap, send) {
|
|
2313
|
+
const ctx = mw.persistCtx([item], [origin]);
|
|
2314
|
+
const result = await mw.runPersist(ctx, async (items) => send(items[0]));
|
|
2315
|
+
cap.result = result;
|
|
2316
|
+
return result;
|
|
1614
2317
|
}
|
|
1615
2318
|
|
|
1616
2319
|
// src/operations/update.ts
|
|
@@ -1800,12 +2503,15 @@ function needsReadForRederive(meta, entity, serializedChanges, fieldMap) {
|
|
|
1800
2503
|
return affected.some((gsi2) => missingGsiFields(gsi2, available).length > 0);
|
|
1801
2504
|
}
|
|
1802
2505
|
async function rederiveByReadModifyWrite(modelClass, entity, changes, options, pk, sk, tableName) {
|
|
1803
|
-
const result = await ClientManager.getExecutor().execute(
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
2506
|
+
const result = await ClientManager.getExecutor().execute(
|
|
2507
|
+
{
|
|
2508
|
+
type: "GetItem",
|
|
2509
|
+
tableName,
|
|
2510
|
+
keyCondition: { PK: pk, SK: sk },
|
|
2511
|
+
consistentRead: true
|
|
2512
|
+
},
|
|
2513
|
+
options?.retry !== void 0 ? { retry: options.retry } : void 0
|
|
2514
|
+
);
|
|
1809
2515
|
const current = result.items[0];
|
|
1810
2516
|
if (!current) {
|
|
1811
2517
|
throw new Error(
|
|
@@ -1821,6 +2527,17 @@ async function rederiveByReadModifyWrite(modelClass, entity, changes, options, p
|
|
|
1821
2527
|
return buildUpdateInput(modelClass, enriched, changes, guarded);
|
|
1822
2528
|
}
|
|
1823
2529
|
async function executeUpdate(modelClass, entity, changes, options) {
|
|
2530
|
+
const mw = writeRuntimeFor(options);
|
|
2531
|
+
if (mw) {
|
|
2532
|
+
await executeSingleWriteWithHooks(
|
|
2533
|
+
{ kind: "update", modelClass, key: entity, changes, options },
|
|
2534
|
+
mw
|
|
2535
|
+
);
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
await executeUpdateCore(modelClass, entity, changes, options);
|
|
2539
|
+
}
|
|
2540
|
+
async function executeUpdateCore(modelClass, entity, changes, options, persistVia, forceOldImage) {
|
|
1824
2541
|
let updateInput;
|
|
1825
2542
|
if (options?.rederive === "read-modify-write") {
|
|
1826
2543
|
const meta = MetadataRegistry.get(modelClass);
|
|
@@ -1852,11 +2569,12 @@ async function executeUpdate(modelClass, entity, changes, options) {
|
|
|
1852
2569
|
} else {
|
|
1853
2570
|
updateInput = buildUpdateInput(modelClass, entity, changes, options);
|
|
1854
2571
|
}
|
|
1855
|
-
const wantsOld = ChangeCaptureRegistry.wantsOldImage(modelClass);
|
|
1856
|
-
const
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
);
|
|
2572
|
+
const wantsOld = ChangeCaptureRegistry.wantsOldImage(modelClass) || forceOldImage === true;
|
|
2573
|
+
const send = (input) => ClientManager.getExecutor().update(input, {
|
|
2574
|
+
...wantsOld ? { returnOldImage: true } : {},
|
|
2575
|
+
...options?.retry !== void 0 ? { retry: options.retry } : {}
|
|
2576
|
+
});
|
|
2577
|
+
const result = persistVia ? await persistVia(updateInput, send) : await send(updateInput);
|
|
1860
2578
|
if (ChangeCaptureRegistry.hasSubscribers()) {
|
|
1861
2579
|
const oldItem = result.oldItem;
|
|
1862
2580
|
const keys = { pk: String(updateInput.Key.PK), sk: String(updateInput.Key.SK ?? "") };
|
|
@@ -1876,70 +2594,6 @@ async function executeUpdate(modelClass, entity, changes, options) {
|
|
|
1876
2594
|
}
|
|
1877
2595
|
}
|
|
1878
2596
|
|
|
1879
|
-
// src/operations/delete.ts
|
|
1880
|
-
function buildDeleteInput(modelClass, keyObj, options) {
|
|
1881
|
-
const meta = MetadataRegistry.get(modelClass);
|
|
1882
|
-
if (!meta.primaryKey) {
|
|
1883
|
-
throw new Error(
|
|
1884
|
-
`No primary key defined for '${modelClass.name}'. Ensure the model has a static \`keys = key(...)\` definition.`
|
|
1885
|
-
);
|
|
1886
|
-
}
|
|
1887
|
-
const tableName = TableMapping.resolve(meta.tableName);
|
|
1888
|
-
const fieldMap = new Map(
|
|
1889
|
-
meta.fields.map((f) => [f.propertyName, f])
|
|
1890
|
-
);
|
|
1891
|
-
const keyInput = {};
|
|
1892
|
-
for (const fieldName of meta.primaryKey.inputFieldNames) {
|
|
1893
|
-
let value = keyObj[fieldName];
|
|
1894
|
-
const fieldMeta = fieldMap.get(fieldName);
|
|
1895
|
-
if (fieldMeta && value !== void 0) {
|
|
1896
|
-
value = serializeFieldValue(value, fieldMeta);
|
|
1897
|
-
}
|
|
1898
|
-
keyInput[fieldName] = value;
|
|
1899
|
-
}
|
|
1900
|
-
const { pk, sk } = resolveSegmentedKey(
|
|
1901
|
-
meta.primaryKey.segmented,
|
|
1902
|
-
keyInput,
|
|
1903
|
-
`${modelClass.name} primary key`
|
|
1904
|
-
);
|
|
1905
|
-
const deleteInput = {
|
|
1906
|
-
TableName: tableName,
|
|
1907
|
-
Key: {
|
|
1908
|
-
PK: pk,
|
|
1909
|
-
SK: sk ?? ""
|
|
1910
|
-
}
|
|
1911
|
-
};
|
|
1912
|
-
if (options?.condition) {
|
|
1913
|
-
const condResult = buildConditionExpression(options.condition);
|
|
1914
|
-
deleteInput.ConditionExpression = condResult.expression;
|
|
1915
|
-
if (Object.keys(condResult.names).length > 0) {
|
|
1916
|
-
deleteInput.ExpressionAttributeNames = condResult.names;
|
|
1917
|
-
}
|
|
1918
|
-
if (Object.keys(condResult.values).length > 0) {
|
|
1919
|
-
deleteInput.ExpressionAttributeValues = condResult.values;
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
return deleteInput;
|
|
1923
|
-
}
|
|
1924
|
-
async function executeDelete(modelClass, keyObj, options) {
|
|
1925
|
-
const deleteInput = buildDeleteInput(modelClass, keyObj, options);
|
|
1926
|
-
const wantsOld = ChangeCaptureRegistry.wantsOldImage(modelClass);
|
|
1927
|
-
const result = await ClientManager.getExecutor().delete(
|
|
1928
|
-
deleteInput,
|
|
1929
|
-
wantsOld ? { returnOldImage: true } : void 0
|
|
1930
|
-
);
|
|
1931
|
-
if (ChangeCaptureRegistry.hasSubscribers()) {
|
|
1932
|
-
const oldItem = result.oldItem;
|
|
1933
|
-
captureWrite({
|
|
1934
|
-
table: deleteInput.TableName,
|
|
1935
|
-
model: modelClass.name,
|
|
1936
|
-
op: "delete",
|
|
1937
|
-
keys: { pk: String(deleteInput.Key.PK), sk: String(deleteInput.Key.SK ?? "") },
|
|
1938
|
-
...oldItem ? { oldItem } : {}
|
|
1939
|
-
});
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
2597
|
// src/runtime/same-key-collapse.ts
|
|
1944
2598
|
function collapseSameKey(items, view) {
|
|
1945
2599
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -2002,9 +2656,21 @@ async function commitTransaction(items, options = {}) {
|
|
|
2002
2656
|
`Transaction exceeds DynamoDB limit of ${MAX_TRANSACT_ITEMS} items`
|
|
2003
2657
|
);
|
|
2004
2658
|
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2659
|
+
const sendBatch = (items2) => ClientManager.getExecutor().transactWrite(
|
|
2660
|
+
items2,
|
|
2661
|
+
options.retry !== void 0 ? { retry: options.retry } : void 0
|
|
2662
|
+
);
|
|
2663
|
+
let committed = collapsed;
|
|
2664
|
+
if (options.middleware) {
|
|
2665
|
+
const { runtime, origins, transaction } = options.middleware;
|
|
2666
|
+
const persistCtx = runtime.persistCtx(collapsed, origins, transaction);
|
|
2667
|
+
await runtime.runPersist(persistCtx, async (items2) => sendBatch(items2));
|
|
2668
|
+
committed = persistCtx.items;
|
|
2669
|
+
} else {
|
|
2670
|
+
await sendBatch(collapsed);
|
|
2671
|
+
}
|
|
2672
|
+
captureTransactItems(committed, options.modelBySignature ?? EMPTY_MODEL_MAP);
|
|
2673
|
+
return committed;
|
|
2008
2674
|
}
|
|
2009
2675
|
var EMPTY_MODEL_MAP = /* @__PURE__ */ new Map();
|
|
2010
2676
|
function captureTransactItems(items, modelBySignature) {
|
|
@@ -2121,6 +2787,14 @@ function resolveModelClass(model) {
|
|
|
2121
2787
|
var TransactionContext = class {
|
|
2122
2788
|
items = [];
|
|
2123
2789
|
captureMeta = [];
|
|
2790
|
+
/**
|
|
2791
|
+
* The logical write ops, parallel to {@link items} but EXCLUDING `conditionCheck`
|
|
2792
|
+
* entries (those are read-only assertions, recorded only in `items`). Used by the
|
|
2793
|
+
* write-hook path (#139) to run W1 on each logical op and recompose the batch.
|
|
2794
|
+
* A `null` entry marks an `items` slot that has no logical op (a ConditionCheck),
|
|
2795
|
+
* so the eager and logical arrays can be re-aligned at commit.
|
|
2796
|
+
*/
|
|
2797
|
+
logicalOps = [];
|
|
2124
2798
|
get itemCount() {
|
|
2125
2799
|
return this.items.length;
|
|
2126
2800
|
}
|
|
@@ -2129,6 +2803,7 @@ var TransactionContext = class {
|
|
|
2129
2803
|
const modelClass = resolveModelClass(model);
|
|
2130
2804
|
const putInput = buildPutInput(modelClass, item, options);
|
|
2131
2805
|
this.items.push({ Put: putInput });
|
|
2806
|
+
this.logicalOps.push({ kind: "put", modelClass, item, options });
|
|
2132
2807
|
this.captureMeta.push({
|
|
2133
2808
|
modelName: modelClass.name,
|
|
2134
2809
|
op: "put",
|
|
@@ -2142,6 +2817,7 @@ var TransactionContext = class {
|
|
|
2142
2817
|
const modelClass = resolveModelClass(model);
|
|
2143
2818
|
const updateInput = buildUpdateInput(modelClass, entity, changes, options);
|
|
2144
2819
|
this.items.push({ Update: updateInput });
|
|
2820
|
+
this.logicalOps.push({ kind: "update", modelClass, key: entity, changes, options });
|
|
2145
2821
|
this.captureMeta.push({
|
|
2146
2822
|
modelName: modelClass.name,
|
|
2147
2823
|
op: "update",
|
|
@@ -2154,6 +2830,7 @@ var TransactionContext = class {
|
|
|
2154
2830
|
const modelClass = resolveModelClass(model);
|
|
2155
2831
|
const deleteInput = buildDeleteInput(modelClass, key2, options);
|
|
2156
2832
|
this.items.push({ Delete: deleteInput });
|
|
2833
|
+
this.logicalOps.push({ kind: "delete", modelClass, key: key2, options });
|
|
2157
2834
|
this.captureMeta.push({
|
|
2158
2835
|
modelName: modelClass.name,
|
|
2159
2836
|
op: "delete",
|
|
@@ -2195,11 +2872,16 @@ var TransactionContext = class {
|
|
|
2195
2872
|
check.ExpressionAttributeValues = cond2.values;
|
|
2196
2873
|
}
|
|
2197
2874
|
this.items.push({ ConditionCheck: check });
|
|
2875
|
+
this.logicalOps.push(null);
|
|
2198
2876
|
}
|
|
2199
2877
|
/** @internal */
|
|
2200
2878
|
getTransactItems() {
|
|
2201
2879
|
return this.items;
|
|
2202
2880
|
}
|
|
2881
|
+
/** @internal — the recorded logical write ops (issue #139 W1), `null` per ConditionCheck slot. */
|
|
2882
|
+
getLogicalOps() {
|
|
2883
|
+
return this.logicalOps;
|
|
2884
|
+
}
|
|
2203
2885
|
/** @internal — capture descriptors for the write-capture seam (issue #72). */
|
|
2204
2886
|
getCaptureMeta() {
|
|
2205
2887
|
return this.captureMeta;
|
|
@@ -2212,13 +2894,47 @@ var TransactionContext = class {
|
|
|
2212
2894
|
}
|
|
2213
2895
|
}
|
|
2214
2896
|
};
|
|
2215
|
-
async function executeTransaction(fn) {
|
|
2897
|
+
async function executeTransaction(fn, options) {
|
|
2216
2898
|
const tx = new TransactionContext();
|
|
2217
2899
|
await fn(tx);
|
|
2218
|
-
|
|
2900
|
+
let transactItems = tx.getTransactItems();
|
|
2219
2901
|
if (transactItems.length === 0) {
|
|
2220
2902
|
return;
|
|
2221
2903
|
}
|
|
2904
|
+
const writeMw = buildWriteRuntime(options?.context);
|
|
2905
|
+
if (writeMw.active) {
|
|
2906
|
+
const txId = { id: /* @__PURE__ */ Symbol("graphddb.transaction") };
|
|
2907
|
+
const logical = tx.getLogicalOps();
|
|
2908
|
+
const items = [...transactItems];
|
|
2909
|
+
const origins = [];
|
|
2910
|
+
const writeCtxs = [];
|
|
2911
|
+
const modelBySignature2 = /* @__PURE__ */ new Map();
|
|
2912
|
+
for (let i = 0; i < logical.length; i++) {
|
|
2913
|
+
const op = logical[i];
|
|
2914
|
+
if (op === null) continue;
|
|
2915
|
+
const input = {};
|
|
2916
|
+
if (op.item !== void 0) input.item = { ...op.item };
|
|
2917
|
+
if (op.key !== void 0) input.key = { ...op.key };
|
|
2918
|
+
if (op.changes !== void 0) input.changes = { ...op.changes };
|
|
2919
|
+
if (op.options !== void 0) input.options = { ...op.options };
|
|
2920
|
+
const ctx = writeMw.writeCtx(op.kind, ctxModelFor(op.modelClass), input, txId);
|
|
2921
|
+
await writeMw.runWriteBefore(ctx);
|
|
2922
|
+
const built = buildLogicalItem(op.modelClass, ctx);
|
|
2923
|
+
items[i] = built;
|
|
2924
|
+
modelBySignature2.set(execItemKeySignature(built), op.modelClass.name);
|
|
2925
|
+
origins.push({ model: ctx.model, kind: ctx.kind });
|
|
2926
|
+
writeCtxs.push(ctx);
|
|
2927
|
+
}
|
|
2928
|
+
transactItems = items;
|
|
2929
|
+
await commitTransaction(transactItems, {
|
|
2930
|
+
modelBySignature: modelBySignature2,
|
|
2931
|
+
middleware: { runtime: writeMw, origins, transaction: txId }
|
|
2932
|
+
});
|
|
2933
|
+
for (let j = writeCtxs.length - 1; j >= 0; j--) {
|
|
2934
|
+
await writeMw.runWriteAfter(writeCtxs[j], {});
|
|
2935
|
+
}
|
|
2936
|
+
return;
|
|
2937
|
+
}
|
|
2222
2938
|
const modelBySignature = /* @__PURE__ */ new Map();
|
|
2223
2939
|
for (const meta of tx.getCaptureMeta()) {
|
|
2224
2940
|
if (meta.modelName) {
|
|
@@ -2232,6 +2948,18 @@ async function executeTransaction(fn) {
|
|
|
2232
2948
|
modelBySignature
|
|
2233
2949
|
});
|
|
2234
2950
|
}
|
|
2951
|
+
function buildLogicalItem(modelClass, ctx) {
|
|
2952
|
+
const opts = ctx.input.options;
|
|
2953
|
+
if (ctx.kind === "put") {
|
|
2954
|
+
return { Put: buildPutInput(modelClass, ctx.input.item ?? {}, opts) };
|
|
2955
|
+
}
|
|
2956
|
+
if (ctx.kind === "update") {
|
|
2957
|
+
return {
|
|
2958
|
+
Update: buildUpdateInput(modelClass, ctx.input.key ?? {}, ctx.input.changes ?? {}, opts)
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
return { Delete: buildDeleteInput(modelClass, ctx.input.key ?? {}, opts) };
|
|
2962
|
+
}
|
|
2235
2963
|
|
|
2236
2964
|
export {
|
|
2237
2965
|
isColumn,
|
|
@@ -2258,7 +2986,13 @@ export {
|
|
|
2258
2986
|
isParam,
|
|
2259
2987
|
BATCH_GET_MAX_KEYS,
|
|
2260
2988
|
BATCH_WRITE_MAX_ITEMS,
|
|
2989
|
+
chunkArray,
|
|
2261
2990
|
DynamoExecutor,
|
|
2991
|
+
DEFAULT_MAX_ATTEMPTS,
|
|
2992
|
+
DEFAULT_RETRY_POLICY,
|
|
2993
|
+
isRetryableError,
|
|
2994
|
+
isRetryableTransactionCancellation,
|
|
2995
|
+
RetryingExecutor,
|
|
2262
2996
|
ClientManager,
|
|
2263
2997
|
isRawCondition,
|
|
2264
2998
|
cond,
|
|
@@ -2270,14 +3004,18 @@ export {
|
|
|
2270
3004
|
serializeFieldValue,
|
|
2271
3005
|
ChangeCaptureRegistry,
|
|
2272
3006
|
captureWrite,
|
|
2273
|
-
|
|
2274
|
-
|
|
3007
|
+
NO_MIDDLEWARE,
|
|
3008
|
+
buildReadRuntime,
|
|
3009
|
+
buildWriteRuntime,
|
|
3010
|
+
ctxModelFor,
|
|
2275
3011
|
buildUpdateExpression,
|
|
2276
3012
|
attachHiddenKey,
|
|
2277
3013
|
buildUpdateInput,
|
|
2278
3014
|
executeUpdate,
|
|
2279
3015
|
buildDeleteInput,
|
|
2280
3016
|
executeDelete,
|
|
3017
|
+
buildPutInput,
|
|
3018
|
+
executePut,
|
|
2281
3019
|
MAX_TRANSACT_ITEMS,
|
|
2282
3020
|
collapseSameKeyItems,
|
|
2283
3021
|
commitTransaction,
|