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
|
@@ -4,17 +4,22 @@ import {
|
|
|
4
4
|
ClientManager,
|
|
5
5
|
MAX_TRANSACT_ITEMS,
|
|
6
6
|
MetadataRegistry,
|
|
7
|
+
NO_MIDDLEWARE,
|
|
7
8
|
TableMapping,
|
|
8
9
|
attachHiddenKey,
|
|
9
10
|
attachModelClass,
|
|
10
11
|
buildConditionExpression,
|
|
11
12
|
buildDeleteInput,
|
|
12
13
|
buildPutInput,
|
|
14
|
+
buildReadRuntime,
|
|
13
15
|
buildUpdateInput,
|
|
16
|
+
buildWriteRuntime,
|
|
14
17
|
captureWrite,
|
|
18
|
+
chunkArray,
|
|
15
19
|
commitTransaction,
|
|
16
20
|
compileRawCondition,
|
|
17
21
|
createColumnMap,
|
|
22
|
+
ctxModelFor,
|
|
18
23
|
execItemKeySignature,
|
|
19
24
|
executeDelete,
|
|
20
25
|
executePut,
|
|
@@ -32,7 +37,7 @@ import {
|
|
|
32
37
|
serializeFieldValue,
|
|
33
38
|
serializeRawCondition,
|
|
34
39
|
skTemplate
|
|
35
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-4TYK3AV6.js";
|
|
36
41
|
|
|
37
42
|
// src/spec/types.ts
|
|
38
43
|
var SPEC_VERSION = "1.0";
|
|
@@ -686,8 +691,8 @@ function buildFromSegments(segmented, inputFieldNames, queryKey, pkAttr, skAttr)
|
|
|
686
691
|
}
|
|
687
692
|
|
|
688
693
|
// src/executor/executor.ts
|
|
689
|
-
async function execute(operation) {
|
|
690
|
-
return ClientManager.getExecutor().execute(operation);
|
|
694
|
+
async function execute(operation, options) {
|
|
695
|
+
return ClientManager.getExecutor().execute(operation, options);
|
|
691
696
|
}
|
|
692
697
|
|
|
693
698
|
// src/hydrator/hydrator.ts
|
|
@@ -810,20 +815,71 @@ async function executeListInternal(modelClass, key, selectSpec, options = {}) {
|
|
|
810
815
|
updatable: options.updatable ?? false
|
|
811
816
|
});
|
|
812
817
|
const operation = executionPlan.operations[0];
|
|
813
|
-
const
|
|
814
|
-
const
|
|
818
|
+
const mw = options.mw ?? NO_MIDDLEWARE;
|
|
819
|
+
const sendList = (op) => execute(op, options.retry !== void 0 ? { retry: options.retry } : void 0).then(
|
|
820
|
+
(r) => r
|
|
821
|
+
);
|
|
822
|
+
let lastEvaluatedKey;
|
|
823
|
+
let rawItems;
|
|
824
|
+
if (mw.active) {
|
|
825
|
+
let captured;
|
|
826
|
+
const opModel = ctxModelFor(modelClass);
|
|
827
|
+
rawItems = await mw.runOp(
|
|
828
|
+
operation,
|
|
829
|
+
options.relationPath ?? [],
|
|
830
|
+
opModel,
|
|
831
|
+
async (op) => {
|
|
832
|
+
captured = await sendList(op);
|
|
833
|
+
return captured.items;
|
|
834
|
+
}
|
|
835
|
+
);
|
|
836
|
+
lastEvaluatedKey = captured?.lastEvaluatedKey;
|
|
837
|
+
} else {
|
|
838
|
+
const result = await sendList(operation);
|
|
839
|
+
rawItems = result.items;
|
|
840
|
+
lastEvaluatedKey = result.lastEvaluatedKey;
|
|
841
|
+
}
|
|
815
842
|
const hydrated = hydrate(rawItems, select, metadata, options.updatable ?? false);
|
|
816
|
-
const cursor =
|
|
843
|
+
const cursor = lastEvaluatedKey ? encodeCursor(lastEvaluatedKey) : null;
|
|
817
844
|
return { items: hydrated, rawItems, cursor };
|
|
818
845
|
}
|
|
819
846
|
async function executeList(modelClass, key, selectSpec, options = {}) {
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
847
|
+
const mw = buildReadRuntime(options.context);
|
|
848
|
+
if (!mw.active) {
|
|
849
|
+
const { items, cursor } = await executeListInternal(
|
|
850
|
+
modelClass,
|
|
851
|
+
key,
|
|
852
|
+
selectSpec,
|
|
853
|
+
options
|
|
854
|
+
);
|
|
855
|
+
return { items, cursor };
|
|
856
|
+
}
|
|
857
|
+
const ctxModel = ctxModelFor(modelClass);
|
|
858
|
+
const opts = { ...options };
|
|
859
|
+
const params = {
|
|
860
|
+
key: { ...key },
|
|
861
|
+
select: selectSpec && typeof selectSpec === "object" ? selectSpec : {},
|
|
862
|
+
options: opts
|
|
863
|
+
};
|
|
864
|
+
let reqCtx;
|
|
865
|
+
try {
|
|
866
|
+
reqCtx = await mw.runRequestBefore("list", ctxModel, params);
|
|
867
|
+
const { items, cursor } = await executeListInternal(
|
|
868
|
+
modelClass,
|
|
869
|
+
params.key,
|
|
870
|
+
params.select,
|
|
871
|
+
// Thread the SAME runtime + an empty (root) relation path so R2/R3 fire on
|
|
872
|
+
// the partition Query; carry the (possibly R1-mutated) per-call options.
|
|
873
|
+
{ ...params.options, mw, relationPath: [] }
|
|
874
|
+
);
|
|
875
|
+
const result = { items, cursor };
|
|
876
|
+
return await mw.runRequestAfter(reqCtx, result);
|
|
877
|
+
} catch (err) {
|
|
878
|
+
return mw.runRequestError(
|
|
879
|
+
reqCtx ?? mw.requestCtx("list", ctxModel, params),
|
|
880
|
+
err
|
|
881
|
+
);
|
|
882
|
+
}
|
|
827
883
|
}
|
|
828
884
|
|
|
829
885
|
// src/planner/relation-planner.ts
|
|
@@ -1171,9 +1227,23 @@ async function fetchBelongsToHasOneBatch(rel, rawParentItems, selectSpec, target
|
|
|
1171
1227
|
);
|
|
1172
1228
|
const executor = ClientManager.getExecutor();
|
|
1173
1229
|
const queryKeyToRaw = /* @__PURE__ */ new Map();
|
|
1230
|
+
const mw = options.mw ?? NO_MIDDLEWARE;
|
|
1231
|
+
const opPath = [...options.relationPath ?? [], rel.propertyName];
|
|
1232
|
+
const opModel = mw.active ? ctxModelFor(rel.targetFactory()) : void 0;
|
|
1174
1233
|
for (const operation of batchOps) {
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1234
|
+
const items = mw.active ? await mw.runOp(
|
|
1235
|
+
operation,
|
|
1236
|
+
opPath,
|
|
1237
|
+
opModel,
|
|
1238
|
+
(op) => executor.batchGet(
|
|
1239
|
+
op,
|
|
1240
|
+
options.retry !== void 0 ? { retry: options.retry } : void 0
|
|
1241
|
+
).then((r) => r.items)
|
|
1242
|
+
) : (await executor.batchGet(
|
|
1243
|
+
operation,
|
|
1244
|
+
options.retry !== void 0 ? { retry: options.retry } : void 0
|
|
1245
|
+
)).items;
|
|
1246
|
+
for (const item of items) {
|
|
1177
1247
|
for (const queryKey of queryKeys) {
|
|
1178
1248
|
if (!matchesQueryKey(item, queryKey)) {
|
|
1179
1249
|
continue;
|
|
@@ -1259,9 +1329,11 @@ async function resolveRelations(parentItem, rawParentItem, select, metadata, opt
|
|
|
1259
1329
|
const relations = detectRelationFields(select, metadata);
|
|
1260
1330
|
const plan2 = options.plan ?? (currentPath === "$" ? buildRelationExecutionPlan(select, metadata) : void 0);
|
|
1261
1331
|
const opts = plan2 === options.plan ? options : { ...options, plan: plan2 };
|
|
1332
|
+
const basePath = opts.relationPath ?? [];
|
|
1262
1333
|
await runStaged(relations, currentPath, opts.plan, async (rel) => {
|
|
1263
1334
|
const selectSpec = relationSpec(select[rel.propertyName]);
|
|
1264
1335
|
const ownPath = relationResultPath(currentPath, rel);
|
|
1336
|
+
const relPath = [...basePath, rel.propertyName];
|
|
1265
1337
|
if (rel.type === "hasMany") {
|
|
1266
1338
|
const targetClass2 = rel.targetFactory();
|
|
1267
1339
|
const queryKey = buildRelationQueryKey(rel, rawParentItem);
|
|
@@ -1272,7 +1344,10 @@ async function resolveRelations(parentItem, rawParentItem, select, metadata, opt
|
|
|
1272
1344
|
after: selectSpec.after,
|
|
1273
1345
|
order,
|
|
1274
1346
|
filter: selectSpec.filter,
|
|
1275
|
-
updatable: opts.updatable
|
|
1347
|
+
updatable: opts.updatable,
|
|
1348
|
+
retry: opts.retry,
|
|
1349
|
+
mw: opts.mw,
|
|
1350
|
+
relationPath: relPath
|
|
1276
1351
|
});
|
|
1277
1352
|
let items = listResult.items;
|
|
1278
1353
|
const rawItems = listResult.rawItems;
|
|
@@ -1284,7 +1359,7 @@ async function resolveRelations(parentItem, rawParentItem, select, metadata, opt
|
|
|
1284
1359
|
rawItems,
|
|
1285
1360
|
selectSpec.select,
|
|
1286
1361
|
targetMeta2,
|
|
1287
|
-
opts,
|
|
1362
|
+
{ ...opts, relationPath: relPath },
|
|
1288
1363
|
currentDepth + 1,
|
|
1289
1364
|
ownPath
|
|
1290
1365
|
);
|
|
@@ -1313,6 +1388,36 @@ async function resolveRelations(parentItem, rawParentItem, select, metadata, opt
|
|
|
1313
1388
|
|
|
1314
1389
|
// src/operations/query.ts
|
|
1315
1390
|
async function executeQuery(modelClass, key, selectSpec, options) {
|
|
1391
|
+
const mw = buildReadRuntime(options?.context);
|
|
1392
|
+
if (!mw.active) {
|
|
1393
|
+
return executeQueryInner(modelClass, key, selectSpec, options, mw);
|
|
1394
|
+
}
|
|
1395
|
+
const ctxModel = ctxModelFor(modelClass);
|
|
1396
|
+
const opts = { ...options ?? {} };
|
|
1397
|
+
const params = {
|
|
1398
|
+
key: { ...key },
|
|
1399
|
+
select: selectSpec && typeof selectSpec === "object" ? selectSpec : {},
|
|
1400
|
+
options: opts
|
|
1401
|
+
};
|
|
1402
|
+
let reqCtx;
|
|
1403
|
+
try {
|
|
1404
|
+
reqCtx = await mw.runRequestBefore("query", ctxModel, params);
|
|
1405
|
+
const result = await executeQueryInner(
|
|
1406
|
+
modelClass,
|
|
1407
|
+
params.key,
|
|
1408
|
+
params.select,
|
|
1409
|
+
params.options,
|
|
1410
|
+
mw
|
|
1411
|
+
);
|
|
1412
|
+
return await mw.runRequestAfter(reqCtx, result);
|
|
1413
|
+
} catch (err) {
|
|
1414
|
+
return mw.runRequestError(
|
|
1415
|
+
reqCtx ?? mw.requestCtx("query", ctxModel, params),
|
|
1416
|
+
err
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
async function executeQueryInner(modelClass, key, selectSpec, options, mw) {
|
|
1316
1421
|
const metadata = MetadataRegistry.get(modelClass);
|
|
1317
1422
|
const queryFields = Object.keys(key);
|
|
1318
1423
|
const resolved = resolveKey(queryFields, metadata);
|
|
@@ -1345,8 +1450,26 @@ async function executeQuery(modelClass, key, selectSpec, options) {
|
|
|
1345
1450
|
(sourceField) => key[sourceField] != null
|
|
1346
1451
|
)
|
|
1347
1452
|
);
|
|
1348
|
-
const
|
|
1349
|
-
const
|
|
1453
|
+
const retry = options?.retry;
|
|
1454
|
+
const sendRoot = () => execute(operation, retry !== void 0 ? { retry } : void 0);
|
|
1455
|
+
const parentPromise = mw.active ? (() => {
|
|
1456
|
+
const opModel = ctxModelFor(modelClass);
|
|
1457
|
+
return mw.runOp(operation, [], opModel, async (op) => {
|
|
1458
|
+
const r = await execute(
|
|
1459
|
+
op,
|
|
1460
|
+
retry !== void 0 ? { retry } : void 0
|
|
1461
|
+
);
|
|
1462
|
+
return r.items;
|
|
1463
|
+
}).then((items) => ({ items }));
|
|
1464
|
+
})() : sendRoot();
|
|
1465
|
+
const relationPromise = canParallelizeRelations ? resolveRelations(
|
|
1466
|
+
{},
|
|
1467
|
+
key,
|
|
1468
|
+
select,
|
|
1469
|
+
metadata,
|
|
1470
|
+
{ maxDepth, updatable, retry, mw, relationPath: [] },
|
|
1471
|
+
1
|
|
1472
|
+
) : null;
|
|
1350
1473
|
let result;
|
|
1351
1474
|
try {
|
|
1352
1475
|
result = await parentPromise;
|
|
@@ -1373,7 +1496,7 @@ async function executeQuery(modelClass, key, selectSpec, options) {
|
|
|
1373
1496
|
rawItem,
|
|
1374
1497
|
select,
|
|
1375
1498
|
metadata,
|
|
1376
|
-
{ maxDepth, updatable },
|
|
1499
|
+
{ maxDepth, updatable, retry, mw, relationPath: [] },
|
|
1377
1500
|
1
|
|
1378
1501
|
);
|
|
1379
1502
|
if (hydratedItem && updatable) {
|
|
@@ -1454,16 +1577,37 @@ function hydrateBatchItems(rawItems, metadata) {
|
|
|
1454
1577
|
return merged;
|
|
1455
1578
|
});
|
|
1456
1579
|
}
|
|
1457
|
-
async function executeBatchGetForTable(tableName, entries) {
|
|
1580
|
+
async function executeBatchGetForTable(tableName, entries, mw, opModel) {
|
|
1458
1581
|
const groups = /* @__PURE__ */ new Map();
|
|
1459
1582
|
if (entries.length === 0) {
|
|
1460
1583
|
return groups;
|
|
1461
1584
|
}
|
|
1462
1585
|
const keys = entries.map((entry) => entry.dynamoKey);
|
|
1463
|
-
const
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1586
|
+
const sendChunk = (op) => execute(op).then((r) => r.items);
|
|
1587
|
+
let rawItems;
|
|
1588
|
+
if (mw.active) {
|
|
1589
|
+
rawItems = [];
|
|
1590
|
+
for (const chunk of chunkArray(keys, BATCH_GET_MAX_KEYS)) {
|
|
1591
|
+
const operation = {
|
|
1592
|
+
type: "BatchGetItem",
|
|
1593
|
+
tableName,
|
|
1594
|
+
keys: chunk
|
|
1595
|
+
};
|
|
1596
|
+
const chunkItems = await mw.runOp(
|
|
1597
|
+
operation,
|
|
1598
|
+
[],
|
|
1599
|
+
opModel,
|
|
1600
|
+
(op) => sendChunk(op)
|
|
1601
|
+
);
|
|
1602
|
+
rawItems.push(...chunkItems);
|
|
1603
|
+
}
|
|
1604
|
+
} else {
|
|
1605
|
+
const { items } = await ClientManager.getExecutor().batchGet({
|
|
1606
|
+
tableName,
|
|
1607
|
+
keys
|
|
1608
|
+
});
|
|
1609
|
+
rawItems = items;
|
|
1610
|
+
}
|
|
1467
1611
|
const entriesByModel = /* @__PURE__ */ new Map();
|
|
1468
1612
|
for (const entry of entries) {
|
|
1469
1613
|
const existing = entriesByModel.get(entry.modelClass) ?? [];
|
|
@@ -1484,12 +1628,42 @@ async function executeBatchGetForTable(tableName, entries) {
|
|
|
1484
1628
|
}
|
|
1485
1629
|
return groups;
|
|
1486
1630
|
}
|
|
1487
|
-
async function executeBatchGet(requests) {
|
|
1631
|
+
async function executeBatchGet(requests, options) {
|
|
1632
|
+
const mw = buildReadRuntime(options?.context);
|
|
1633
|
+
if (!mw.active || requests.length === 0) {
|
|
1634
|
+
return executeBatchGetInner(requests, mw);
|
|
1635
|
+
}
|
|
1636
|
+
const firstModel = ctxModelFor(resolveModelClass(requests[0].model));
|
|
1637
|
+
const reqList = [...requests];
|
|
1638
|
+
const params = {
|
|
1639
|
+
key: {},
|
|
1640
|
+
select: {},
|
|
1641
|
+
options: {},
|
|
1642
|
+
requests: reqList
|
|
1643
|
+
};
|
|
1644
|
+
let reqCtx;
|
|
1645
|
+
try {
|
|
1646
|
+
reqCtx = await mw.runRequestBefore("batchGet", firstModel, params);
|
|
1647
|
+
const effectiveRequests = params.requests;
|
|
1648
|
+
const result = await executeBatchGetInner(effectiveRequests, mw);
|
|
1649
|
+
return await mw.runRequestAfter(reqCtx, result);
|
|
1650
|
+
} catch (err) {
|
|
1651
|
+
return mw.runRequestError(
|
|
1652
|
+
reqCtx ?? mw.requestCtx("batchGet", firstModel, params),
|
|
1653
|
+
err
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
async function executeBatchGetInner(requests, mw) {
|
|
1488
1658
|
const entriesByTable = /* @__PURE__ */ new Map();
|
|
1659
|
+
const firstModelClassByTable = /* @__PURE__ */ new Map();
|
|
1489
1660
|
for (const request of requests) {
|
|
1490
1661
|
const modelClass = resolveModelClass(request.model);
|
|
1491
1662
|
const metadata = MetadataRegistry.get(modelClass);
|
|
1492
1663
|
const tableName = TableMapping.resolve(metadata.tableName);
|
|
1664
|
+
if (!firstModelClassByTable.has(tableName)) {
|
|
1665
|
+
firstModelClassByTable.set(tableName, modelClass);
|
|
1666
|
+
}
|
|
1493
1667
|
for (const keyObj of request.keys) {
|
|
1494
1668
|
const entry = {
|
|
1495
1669
|
modelClass,
|
|
@@ -1504,7 +1678,13 @@ async function executeBatchGet(requests) {
|
|
|
1504
1678
|
const mergedGroups = /* @__PURE__ */ new Map();
|
|
1505
1679
|
for (const [, entries] of entriesByTable) {
|
|
1506
1680
|
const tableName = TableMapping.resolve(entries[0].metadata.tableName);
|
|
1507
|
-
const
|
|
1681
|
+
const opModel = mw.active ? ctxModelFor(firstModelClassByTable.get(tableName) ?? entries[0].modelClass) : void 0;
|
|
1682
|
+
const tableGroups = await executeBatchGetForTable(
|
|
1683
|
+
tableName,
|
|
1684
|
+
entries,
|
|
1685
|
+
mw,
|
|
1686
|
+
opModel
|
|
1687
|
+
);
|
|
1508
1688
|
for (const [modelClass, items] of tableGroups) {
|
|
1509
1689
|
const existing = mergedGroups.get(modelClass) ?? [];
|
|
1510
1690
|
mergedGroups.set(modelClass, existing.concat(items));
|
|
@@ -1894,8 +2074,11 @@ function renderWrite(op, key, params, contextLabel) {
|
|
|
1894
2074
|
}
|
|
1895
2075
|
return base;
|
|
1896
2076
|
}
|
|
1897
|
-
async function executeSingleWrite(w) {
|
|
1898
|
-
const options = w.condition !== void 0
|
|
2077
|
+
async function executeSingleWrite(w, retry) {
|
|
2078
|
+
const options = w.condition !== void 0 || retry !== void 0 ? {
|
|
2079
|
+
...w.condition !== void 0 ? { condition: w.condition } : {},
|
|
2080
|
+
...retry !== void 0 ? { retry } : {}
|
|
2081
|
+
} : void 0;
|
|
1899
2082
|
if (w.operation === "put") {
|
|
1900
2083
|
await executePut(w.modelClass, w.item ?? {}, options);
|
|
1901
2084
|
return;
|
|
@@ -2062,7 +2245,7 @@ function renderIdempotencyGuardItems(guard, key, params, contextLabel) {
|
|
|
2062
2245
|
return { Put: put };
|
|
2063
2246
|
});
|
|
2064
2247
|
}
|
|
2065
|
-
async function executeReferentialTransaction(writes, edgeItems, updateItems, guardItemsRaw, outboxItems, idempotencyItems, checks, methodName, modelBySignature = /* @__PURE__ */ new Map()) {
|
|
2248
|
+
async function executeReferentialTransaction(writes, edgeItems, updateItems, guardItemsRaw, outboxItems, idempotencyItems, checks, methodName, modelBySignature = /* @__PURE__ */ new Map(), retry, middleware) {
|
|
2066
2249
|
const writeItems = writes.map((w) => {
|
|
2067
2250
|
const options = w.condition !== void 0 ? { condition: w.condition } : void 0;
|
|
2068
2251
|
let item;
|
|
@@ -2089,6 +2272,8 @@ async function executeReferentialTransaction(writes, edgeItems, updateItems, gua
|
|
|
2089
2272
|
],
|
|
2090
2273
|
{
|
|
2091
2274
|
modelBySignature,
|
|
2275
|
+
...retry !== void 0 ? { retry } : {},
|
|
2276
|
+
...middleware !== void 0 ? { middleware } : {},
|
|
2092
2277
|
limitError: (count) => new Error(
|
|
2093
2278
|
`Contract runtime: command method '${methodName}' composes ${count} items (writes + edges + derived updates + uniqueness guards + outbox events + idempotency guard + ConditionChecks) into one TransactWriteItems, but DynamoDB caps a transaction at ${MAX_TRANSACT_ITEMS} items and a transaction is atomic \u2014 it cannot be split. Reduce the fragment / effect count.`
|
|
2094
2279
|
)
|
|
@@ -2302,7 +2487,24 @@ function readBackForCompiledOp(op, write) {
|
|
|
2302
2487
|
}
|
|
2303
2488
|
async function executeCompiledWriteOp(ops, options) {
|
|
2304
2489
|
const label = options.label;
|
|
2305
|
-
const
|
|
2490
|
+
const writeMw = buildWriteRuntime(options.context);
|
|
2491
|
+
const writeCtxs = [];
|
|
2492
|
+
const txId = { id: /* @__PURE__ */ Symbol("graphddb.mutate") };
|
|
2493
|
+
let rewrites = [];
|
|
2494
|
+
if (writeMw.active) {
|
|
2495
|
+
const w1ed = [];
|
|
2496
|
+
for (const op of ops) {
|
|
2497
|
+
const r = await runEnvelopeW1(op, writeMw, txId, writeCtxs);
|
|
2498
|
+
w1ed.push(r.op);
|
|
2499
|
+
rewrites.push(r.rewritten ?? null);
|
|
2500
|
+
}
|
|
2501
|
+
ops = w1ed;
|
|
2502
|
+
} else {
|
|
2503
|
+
rewrites = ops.map(() => null);
|
|
2504
|
+
}
|
|
2505
|
+
const renderedOps = ops.map(
|
|
2506
|
+
(op, i) => rewrites[i] !== null ? rewrittenCompiledOp(rewrites[i]) : renderCompiledOp(op, `${label} (op #${i})`)
|
|
2507
|
+
);
|
|
2306
2508
|
const rendered = renderedOps.map((r) => r.write);
|
|
2307
2509
|
const modelBySignature = /* @__PURE__ */ new Map();
|
|
2308
2510
|
const edgeItems = [];
|
|
@@ -2321,8 +2523,13 @@ async function executeCompiledWriteOp(ops, options) {
|
|
|
2321
2523
|
checkItems.push(...r.checkItems);
|
|
2322
2524
|
}
|
|
2323
2525
|
const hasDerivedEffects = renderedOps.some((r) => r.hasDerivedEffects);
|
|
2526
|
+
const origins = writeMw.active ? rendered.map((w) => ({ model: ctxModelFor(w.modelClass), kind: w.operation })) : [];
|
|
2324
2527
|
if (rendered.length === 1 && !hasDerivedEffects) {
|
|
2325
|
-
|
|
2528
|
+
if (writeMw.active) {
|
|
2529
|
+
await persistSingleWriteWithHooks(rendered[0], writeMw, origins, txId, options.retry);
|
|
2530
|
+
} else {
|
|
2531
|
+
await executeSingleWrite(rendered[0], options.retry);
|
|
2532
|
+
}
|
|
2326
2533
|
} else {
|
|
2327
2534
|
await executeReferentialTransaction(
|
|
2328
2535
|
rendered,
|
|
@@ -2333,23 +2540,116 @@ async function executeCompiledWriteOp(ops, options) {
|
|
|
2333
2540
|
idempotencyItems,
|
|
2334
2541
|
checkItems,
|
|
2335
2542
|
label,
|
|
2336
|
-
modelBySignature
|
|
2543
|
+
modelBySignature,
|
|
2544
|
+
options.retry,
|
|
2545
|
+
writeMw.active ? { runtime: writeMw, origins, transaction: txId } : void 0
|
|
2337
2546
|
);
|
|
2338
2547
|
}
|
|
2548
|
+
if (writeMw.active) {
|
|
2549
|
+
for (let i = writeCtxs.length - 1; i >= 0; i--) {
|
|
2550
|
+
await writeMw.runWriteAfter(writeCtxs[i], {});
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2339
2553
|
const readBacks = await Promise.all(
|
|
2340
2554
|
ops.map((op, i) => readBackForCompiledOp(op, rendered[i]))
|
|
2341
2555
|
);
|
|
2342
2556
|
return { readBacks };
|
|
2343
2557
|
}
|
|
2558
|
+
async function persistSingleWriteWithHooks(w, runtime, origins, transaction, retry) {
|
|
2559
|
+
const options = w.condition !== void 0 || retry !== void 0 ? {
|
|
2560
|
+
...w.condition !== void 0 ? { condition: w.condition } : {},
|
|
2561
|
+
...retry !== void 0 ? { retry } : {}
|
|
2562
|
+
} : void 0;
|
|
2563
|
+
let item;
|
|
2564
|
+
if (w.operation === "put") {
|
|
2565
|
+
item = { Put: buildPutInput(w.modelClass, w.item ?? {}, options) };
|
|
2566
|
+
} else if (w.operation === "update") {
|
|
2567
|
+
item = { Update: buildUpdateInput(w.modelClass, w.key, w.changes ?? {}, options) };
|
|
2568
|
+
} else {
|
|
2569
|
+
item = { Delete: buildDeleteInput(w.modelClass, w.key, options) };
|
|
2570
|
+
}
|
|
2571
|
+
const persistCtx = runtime.persistCtx([item], origins, transaction);
|
|
2572
|
+
await runtime.runPersist(persistCtx, async (items) => {
|
|
2573
|
+
const sent = items[0];
|
|
2574
|
+
const sendOpts = retry !== void 0 ? { retry } : void 0;
|
|
2575
|
+
const exec = ClientManager.getExecutor();
|
|
2576
|
+
if ("Put" in sent) return exec.put(sent.Put, sendOpts);
|
|
2577
|
+
if ("Update" in sent) return exec.update(sent.Update, sendOpts);
|
|
2578
|
+
if ("Delete" in sent) return exec.delete(sent.Delete, sendOpts);
|
|
2579
|
+
throw new Error("graphddb middleware: a single-op persist received a ConditionCheck.");
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
async function runEnvelopeW1(op, runtime, transaction, writeCtxs) {
|
|
2583
|
+
const kind = op.fragment.op.operation;
|
|
2584
|
+
const keyFields = new Set(op.fragment.keyFields);
|
|
2585
|
+
const input = {};
|
|
2586
|
+
if (kind === "put") {
|
|
2587
|
+
input.item = { ...op.params };
|
|
2588
|
+
} else {
|
|
2589
|
+
const key = {};
|
|
2590
|
+
const changes = {};
|
|
2591
|
+
for (const [k, v] of Object.entries(op.params)) {
|
|
2592
|
+
if (keyFields.has(k)) key[k] = v;
|
|
2593
|
+
else changes[k] = v;
|
|
2594
|
+
}
|
|
2595
|
+
input.key = key;
|
|
2596
|
+
input.changes = changes;
|
|
2597
|
+
}
|
|
2598
|
+
const modelClass = op.fragment.op.entity.modelClass;
|
|
2599
|
+
const ctx = runtime.writeCtx(kind, ctxModelFor(modelClass), input, transaction);
|
|
2600
|
+
await runtime.runWriteBefore(ctx);
|
|
2601
|
+
writeCtxs.push(ctx);
|
|
2602
|
+
if (ctx.kind !== kind) {
|
|
2603
|
+
return { op, rewritten: renderRewrittenWrite(modelClass, ctx) };
|
|
2604
|
+
}
|
|
2605
|
+
const merged = ctx.kind === "put" ? { ...ctx.input.item ?? {} } : { ...ctx.input.key ?? {}, ...ctx.input.changes ?? {} };
|
|
2606
|
+
return { op: { ...op, params: merged } };
|
|
2607
|
+
}
|
|
2608
|
+
function renderRewrittenWrite(modelClass, ctx) {
|
|
2609
|
+
const modelStatic = modelClass.asModel();
|
|
2610
|
+
const condition = ctx.input.options?.condition;
|
|
2611
|
+
const base = {
|
|
2612
|
+
modelClass,
|
|
2613
|
+
modelStatic,
|
|
2614
|
+
...condition !== void 0 ? { condition } : {}
|
|
2615
|
+
};
|
|
2616
|
+
if (ctx.kind === "put") {
|
|
2617
|
+
const item = ctx.input.item ?? {};
|
|
2618
|
+
return { ...base, operation: "put", key: item, item };
|
|
2619
|
+
}
|
|
2620
|
+
if (ctx.kind === "update") {
|
|
2621
|
+
return {
|
|
2622
|
+
...base,
|
|
2623
|
+
operation: "update",
|
|
2624
|
+
key: ctx.input.key ?? {},
|
|
2625
|
+
changes: ctx.input.changes ?? {}
|
|
2626
|
+
};
|
|
2627
|
+
}
|
|
2628
|
+
return { ...base, operation: "delete", key: ctx.input.key ?? {} };
|
|
2629
|
+
}
|
|
2630
|
+
function rewrittenCompiledOp(write) {
|
|
2631
|
+
return {
|
|
2632
|
+
write,
|
|
2633
|
+
edgeItems: [],
|
|
2634
|
+
updateItems: [],
|
|
2635
|
+
guardItems: [],
|
|
2636
|
+
outboxItems: [],
|
|
2637
|
+
idempotencyItems: [],
|
|
2638
|
+
checkItems: [],
|
|
2639
|
+
modelBySignature: /* @__PURE__ */ new Map(),
|
|
2640
|
+
hasDerivedEffects: false
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2344
2643
|
async function executeCompiledParallelWrites(ops, options) {
|
|
2345
2644
|
const label = options.label;
|
|
2346
2645
|
const results = new Array(ops.length);
|
|
2347
2646
|
const renderedOps = ops.map((op, i) => renderCompiledOp(op, `${label} (op #${i})`));
|
|
2348
2647
|
const coalescibleIdx = [];
|
|
2349
2648
|
const individualIdx = [];
|
|
2649
|
+
const hooksActive = buildWriteRuntime(options.context).active;
|
|
2350
2650
|
renderedOps.forEach((r, i) => {
|
|
2351
2651
|
const w = r.write;
|
|
2352
|
-
if (!r.hasDerivedEffects && w.condition === void 0 && (w.operation === "put" || w.operation === "delete")) {
|
|
2652
|
+
if (!hooksActive && !r.hasDerivedEffects && w.condition === void 0 && (w.operation === "put" || w.operation === "delete")) {
|
|
2353
2653
|
coalescibleIdx.push(i);
|
|
2354
2654
|
} else {
|
|
2355
2655
|
individualIdx.push(i);
|
|
@@ -2380,7 +2680,9 @@ async function executeCompiledParallelWrites(ops, options) {
|
|
|
2380
2680
|
...individualIdx.map(async (i) => {
|
|
2381
2681
|
try {
|
|
2382
2682
|
const { readBacks } = await executeCompiledWriteOp([ops[i]], {
|
|
2383
|
-
label: `${label} (op #${i})
|
|
2683
|
+
label: `${label} (op #${i})`,
|
|
2684
|
+
...options.retry !== void 0 ? { retry: options.retry } : {},
|
|
2685
|
+
...options.context !== void 0 ? { context: options.context } : {}
|
|
2384
2686
|
});
|
|
2385
2687
|
results[i] = { ok: true, value: readBacks[0] };
|
|
2386
2688
|
} catch (e) {
|
|
@@ -2438,14 +2740,16 @@ async function executeReadRoute(alias, route) {
|
|
|
2438
2740
|
if (isQuery) {
|
|
2439
2741
|
return executeQuery(modelClass, route.key, route.select, {
|
|
2440
2742
|
consistentRead: opt.consistentRead,
|
|
2441
|
-
maxDepth: opt.maxDepth
|
|
2743
|
+
maxDepth: opt.maxDepth,
|
|
2744
|
+
context: opt.context
|
|
2442
2745
|
});
|
|
2443
2746
|
}
|
|
2444
2747
|
return executeList(modelClass, route.key, route.select, {
|
|
2445
2748
|
limit: opt.limit,
|
|
2446
2749
|
after: opt.after,
|
|
2447
2750
|
order: opt.order,
|
|
2448
|
-
filter: opt.filter
|
|
2751
|
+
filter: opt.filter,
|
|
2752
|
+
context: opt.context
|
|
2449
2753
|
});
|
|
2450
2754
|
}
|
|
2451
2755
|
var INTENT_KEYS = ["create", "update", "remove"];
|
|
@@ -2459,9 +2763,9 @@ async function executeWriteEnvelope(envelope, options = {}) {
|
|
|
2459
2763
|
}
|
|
2460
2764
|
const ops = aliases.flatMap((alias) => normalizeWriteOp(alias, envelope[alias]));
|
|
2461
2765
|
if (mode === "transaction") {
|
|
2462
|
-
return executeTransactionMode(ops);
|
|
2766
|
+
return executeTransactionMode(ops, options.retry, options.context);
|
|
2463
2767
|
}
|
|
2464
|
-
return executeParallelMode(ops);
|
|
2768
|
+
return executeParallelMode(ops, options.retry, options.context);
|
|
2465
2769
|
}
|
|
2466
2770
|
function normalizeWriteOp(alias, d) {
|
|
2467
2771
|
if (d === null || typeof d !== "object") {
|
|
@@ -2534,10 +2838,12 @@ function mapToRefs($, fields) {
|
|
|
2534
2838
|
for (const f of fields) out[f] = $[f];
|
|
2535
2839
|
return out;
|
|
2536
2840
|
}
|
|
2537
|
-
async function executeTransactionMode(ops) {
|
|
2841
|
+
async function executeTransactionMode(ops, retry, context) {
|
|
2538
2842
|
const compiled = ops.map(compileWriteOp);
|
|
2539
2843
|
const { readBacks } = await executeCompiledWriteOp(compiled, {
|
|
2540
|
-
label: "DDBModel.mutate"
|
|
2844
|
+
label: "DDBModel.mutate",
|
|
2845
|
+
...retry !== void 0 ? { retry } : {},
|
|
2846
|
+
...context !== void 0 ? { context } : {}
|
|
2541
2847
|
});
|
|
2542
2848
|
const out = {};
|
|
2543
2849
|
ops.forEach((op, i) => {
|
|
@@ -2552,10 +2858,12 @@ async function executeTransactionMode(ops) {
|
|
|
2552
2858
|
});
|
|
2553
2859
|
return out;
|
|
2554
2860
|
}
|
|
2555
|
-
async function executeParallelMode(ops) {
|
|
2861
|
+
async function executeParallelMode(ops, retry, context) {
|
|
2556
2862
|
const compiled = ops.map(compileWriteOp);
|
|
2557
2863
|
const settled = await executeCompiledParallelWrites(compiled, {
|
|
2558
|
-
label: "DDBModel.mutate"
|
|
2864
|
+
label: "DDBModel.mutate",
|
|
2865
|
+
...retry !== void 0 ? { retry } : {},
|
|
2866
|
+
...context !== void 0 ? { context } : {}
|
|
2559
2867
|
});
|
|
2560
2868
|
const out = {};
|
|
2561
2869
|
ops.forEach((op, i) => {
|
|
@@ -2587,14 +2895,64 @@ function resolveModelStatic(ref, alias) {
|
|
|
2587
2895
|
|
|
2588
2896
|
// src/model/DDBModel.ts
|
|
2589
2897
|
var DDBModel = class {
|
|
2898
|
+
/**
|
|
2899
|
+
* Register the `DynamoDBClient` GraphDDB sends through.
|
|
2900
|
+
*
|
|
2901
|
+
* IMPORTANT (issue #111): GraphDDB now owns single-op throttle / transient-error
|
|
2902
|
+
* retry (see {@link setRetryPolicy}), so a client left at the AWS SDK default
|
|
2903
|
+
* `maxAttempts: 3` retries MULTIPLICATIVELY underneath the library. Construct the
|
|
2904
|
+
* client with `maxAttempts: 1` so the library is the single source of truth for
|
|
2905
|
+
* retry — e.g. `new DynamoDBClient({ maxAttempts: 1 })`. GraphDDB does NOT mutate
|
|
2906
|
+
* a client you pass in.
|
|
2907
|
+
*/
|
|
2590
2908
|
static setClient(client) {
|
|
2591
2909
|
ClientManager.setClient(client);
|
|
2592
2910
|
}
|
|
2593
2911
|
static setTableMapping(mapping) {
|
|
2594
2912
|
TableMapping.set(mapping);
|
|
2595
2913
|
}
|
|
2596
|
-
|
|
2597
|
-
|
|
2914
|
+
/**
|
|
2915
|
+
* Configure the global single-op retry policy (issue #111) — exponential backoff
|
|
2916
|
+
* + jitter with a bounded attempt cap, applied to every GetItem / Query / Put /
|
|
2917
|
+
* Update / Delete / relation-fan-out send and to `TransactWriteItems`. Mirrors
|
|
2918
|
+
* {@link setClient} / {@link setTableMapping}. A sensible default is ALWAYS on; call
|
|
2919
|
+
* this only to tune it (or pass `null` to restore the default). A per-call
|
|
2920
|
+
* `retry?: RetryPolicy | false` on an operation's options overrides this globally
|
|
2921
|
+
* for that call (`false` disables retry).
|
|
2922
|
+
*/
|
|
2923
|
+
static setRetryPolicy(policy) {
|
|
2924
|
+
ClientManager.setRetryPolicy(policy);
|
|
2925
|
+
}
|
|
2926
|
+
/**
|
|
2927
|
+
* Register a {@link Middleware} (issue #50; read path #138, write path #139).
|
|
2928
|
+
* One registered object may carry read and/or write hooks.
|
|
2929
|
+
*
|
|
2930
|
+
* - **Read** R1–R5 — request entry (`read.before`), each physical op before send
|
|
2931
|
+
* / after raw fetch (`read.op.before` / `read.op.afterFetch`, INCLUDING every
|
|
2932
|
+
* relation fan-out fetch), the final hydrated result (`read.afterFetch`), and on
|
|
2933
|
+
* error (`read.op.onError` / `read.onError`).
|
|
2934
|
+
* - **Write** W1–W5 — the logical write before effect derivation (`write.before`,
|
|
2935
|
+
* may mutate `ctx.input` / `ctx.kind` incl. a delete→update rewrite), after commit
|
|
2936
|
+
* (`write.after`), the physical persist on the fully composed batch
|
|
2937
|
+
* (`write.persist.before` / `write.persist.after`, fires ONCE per atomic
|
|
2938
|
+
* transaction), and on error (`write.persist.onError` / `write.onError`).
|
|
2939
|
+
*
|
|
2940
|
+
* Hooks may observe, mutate `ctx`, `throw` to cancel, or (on `onError`) recover by
|
|
2941
|
+
* returning a value; the library imposes no semantics. Ordering: `before*` runs
|
|
2942
|
+
* first-registered-first (FIFO); `after*` / `onError` run last-registered-first
|
|
2943
|
+
* (LIFO). Mirrors {@link setRetryPolicy} — a host-runtime global stored on
|
|
2944
|
+
* {@link ClientManager}; never serialized (the bridge #48 is unaffected). Per-call
|
|
2945
|
+
* host context flows in via the read's / write's `{ context }` option.
|
|
2946
|
+
*/
|
|
2947
|
+
static use(mw) {
|
|
2948
|
+
ClientManager.use(mw);
|
|
2949
|
+
}
|
|
2950
|
+
/** Remove every registered middleware (read AND write; teardown / tests). See {@link use}. */
|
|
2951
|
+
static clearMiddleware() {
|
|
2952
|
+
ClientManager.clearMiddleware();
|
|
2953
|
+
}
|
|
2954
|
+
static async transaction(fn, options) {
|
|
2955
|
+
await executeTransaction(fn, options);
|
|
2598
2956
|
}
|
|
2599
2957
|
/**
|
|
2600
2958
|
* In-process multi-route READ (issue #101 — the unified envelope). Each alias
|
|
@@ -2609,8 +2967,20 @@ var DDBModel = class {
|
|
|
2609
2967
|
static mutate(envelope, options) {
|
|
2610
2968
|
return executeWriteEnvelope(envelope, options);
|
|
2611
2969
|
}
|
|
2612
|
-
|
|
2613
|
-
|
|
2970
|
+
/**
|
|
2971
|
+
* Multi-key, multi-model batch read. Each {@link BatchGetRequest} contributes its
|
|
2972
|
+
* keys to a chunked `BatchGetItem` per physical table; results are re-grouped by
|
|
2973
|
+
* model on the returned {@link BatchGetResult}.
|
|
2974
|
+
*
|
|
2975
|
+
* Fires the read hooks (issue #142): request-level R1/R4/R5 with kind
|
|
2976
|
+
* `'batchGet'`, and op-level R2/R3/R5 for each underlying physical `BatchGetItem`
|
|
2977
|
+
* op. The optional `{ context }` is exposed to every hook as `ctx.context` and
|
|
2978
|
+
* threaded UNCHANGED to every op (absent ⇒ `{}`), so a global hook (e.g. a
|
|
2979
|
+
* tenant-scope R2 FilterExpression) applies across the whole batch. With no
|
|
2980
|
+
* middleware registered the read path is unchanged (zero-overhead fast path).
|
|
2981
|
+
*/
|
|
2982
|
+
static async batchGet(requests, options) {
|
|
2983
|
+
return executeBatchGet(requests, options);
|
|
2614
2984
|
}
|
|
2615
2985
|
static async batchWrite(requests) {
|
|
2616
2986
|
return executeBatchWrite(requests);
|