graphddb 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdc/index.d.ts +2 -2
- package/dist/cdc/index.js +2 -2
- package/dist/{chunk-M6URQOAW.js → chunk-5NXNEW43.js} +1 -1
- package/dist/{chunk-CCIVET5K.js → chunk-F2DI3GTI.js} +88 -0
- package/dist/{chunk-Y7XV5QL2.js → chunk-N5NQM3SO.js} +180 -46
- package/dist/cli.js +7 -2
- package/dist/{from-change-DQK2Jm9R.d.ts → from-change-CFzBy7aU.d.ts} +1 -1
- package/dist/{index-CtDBo8Se.d.ts → index-CtJbTrfB.d.ts} +2 -2
- package/dist/index.d.ts +53 -8
- package/dist/index.js +29 -5
- package/dist/linter/index.d.ts +4 -4
- package/dist/{maintenance-view-adapter-D5t9taTE.d.ts → maintenance-view-adapter-CFeasCKo.d.ts} +147 -6
- package/dist/{registry-BD_5Rm5C.d.ts → registry-DbqmFyab.d.ts} +1 -1
- package/dist/{relation-depth-DLkhG0xX.d.ts → relation-depth-0TiWr5OW.d.ts} +1 -1
- package/dist/spec/index.d.ts +3 -3
- package/dist/spec/index.js +2 -2
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +2 -2
- package/docs/design-patterns.md +150 -27
- package/package.json +10 -2
package/dist/cdc/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { C as CdcEmulator, M as MAINT_OUTBOX_PK_PREFIX, a as MaintenanceDrain, b as MaintenanceDrainOptions, c as createCdcEmulator, d as createMaintenanceDrain, e as createMaintenanceDrainHandler, p as parseChange } from '../from-change-
|
|
2
|
-
export { B as BatchResult, C as CdcEmulatorOptions, a as CdcMode, b as ChangeBatch, c as ChangeEvent, d as ChangeEventName, e as ChangeHandler, f as ClockMode, g as ConcurrentRecomputeRef, E as EventLog, F as FaultSpec, R as ReplayOptions, S as ShardId, h as StartingPosition, i as StreamViewType, j as SubscribeHandler, k as SubscribeHandlers, U as Unsubscribe, l as buildSubscribeHandler } from '../maintenance-view-adapter-
|
|
1
|
+
export { C as CdcEmulator, M as MAINT_OUTBOX_PK_PREFIX, a as MaintenanceDrain, b as MaintenanceDrainOptions, c as createCdcEmulator, d as createMaintenanceDrain, e as createMaintenanceDrainHandler, p as parseChange } from '../from-change-CFzBy7aU.js';
|
|
2
|
+
export { B as BatchResult, C as CdcEmulatorOptions, a as CdcMode, b as ChangeBatch, c as ChangeEvent, d as ChangeEventName, e as ChangeHandler, f as ClockMode, g as ConcurrentRecomputeRef, E as EventLog, F as FaultSpec, R as ReplayOptions, S as ShardId, h as StartingPosition, i as StreamViewType, j as SubscribeHandler, k as SubscribeHandlers, U as Unsubscribe, l as buildSubscribeHandler } from '../maintenance-view-adapter-CFeasCKo.js';
|
|
3
3
|
import '@aws-sdk/client-dynamodb';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/cdc/index.js
CHANGED
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
createMaintenanceDrainHandler,
|
|
9
9
|
hashString,
|
|
10
10
|
shardIdFor
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-5NXNEW43.js";
|
|
12
12
|
import {
|
|
13
13
|
buildSubscribeHandler,
|
|
14
14
|
parseChange
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-F2DI3GTI.js";
|
|
16
16
|
import "../chunk-PDUVTYC5.js";
|
|
17
17
|
export {
|
|
18
18
|
CdcEmulator,
|
|
@@ -245,6 +245,73 @@ function buildRelation(select) {
|
|
|
245
245
|
});
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
// src/relation/detect.ts
|
|
249
|
+
function isRelationSpec(value) {
|
|
250
|
+
if (typeof value !== "object" || value === null) return false;
|
|
251
|
+
return "select" in value || isSelectBuilder(value);
|
|
252
|
+
}
|
|
253
|
+
function isInlineSnapshotSpec(value) {
|
|
254
|
+
return typeof value === "object" && value !== null && !isSelectBuilder(value) && value.inline === true && !("select" in value);
|
|
255
|
+
}
|
|
256
|
+
function detectRelationFields(select, metadata) {
|
|
257
|
+
const result = [];
|
|
258
|
+
for (const [fieldName, value] of Object.entries(select)) {
|
|
259
|
+
if (isRelationSpec(value)) {
|
|
260
|
+
const rel = metadata.relations.find((r) => r.propertyName === fieldName);
|
|
261
|
+
if (rel) {
|
|
262
|
+
result.push(rel);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
function validateInlineSnapshotSelect(select, metadata, resolveTargetMeta) {
|
|
269
|
+
for (const [fieldName, value] of Object.entries(select)) {
|
|
270
|
+
if (isInlineSnapshotSpec(value)) {
|
|
271
|
+
const rel = metadata.relations.find((r) => r.propertyName === fieldName);
|
|
272
|
+
if (!rel) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`Inline read '{ ${fieldName}: { inline: true } }' is only valid on an embeddedSnapshot relation, but '${fieldName}' is not a relation on '${metadata.prefix}'. Use '${fieldName}: true' for a scalar / embedded field.`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (rel.options?.pattern !== "embeddedSnapshot") {
|
|
278
|
+
throw new Error(
|
|
279
|
+
`Inline read '{ ${fieldName}: { inline: true } }' is only valid on an embeddedSnapshot relation. '${fieldName}' is a plain '${rel.type}' relation (no 'pattern: embeddedSnapshot'); use '{ ${fieldName}: { select: \u2026 } }' to traverse to its child rows instead.`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (typeof value === "object" && value !== null && isRelationSpec(value)) {
|
|
285
|
+
const rel = metadata.relations.find((r) => r.propertyName === fieldName);
|
|
286
|
+
if (!rel) continue;
|
|
287
|
+
const targetMeta = resolveTargetMeta(rel);
|
|
288
|
+
const nested = normalizeSelectSpec(value).select;
|
|
289
|
+
if (nested) {
|
|
290
|
+
validateInlineSnapshotSelect(nested, targetMeta, resolveTargetMeta);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function getImplicitKeyFields(select, metadata) {
|
|
296
|
+
const relations = detectRelationFields(select, metadata);
|
|
297
|
+
const needed = /* @__PURE__ */ new Set();
|
|
298
|
+
for (const rel of relations) {
|
|
299
|
+
if (rel.type === "refs" && rel.refs) {
|
|
300
|
+
const listField = rel.refs.from;
|
|
301
|
+
if (!(listField in select) || select[listField] !== true) {
|
|
302
|
+
needed.add(listField);
|
|
303
|
+
}
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
for (const sourceField of Object.values(rel.keyBinding)) {
|
|
307
|
+
if (!(sourceField in select) || select[sourceField] !== true) {
|
|
308
|
+
needed.add(sourceField);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return [...needed];
|
|
313
|
+
}
|
|
314
|
+
|
|
248
315
|
// src/define/param.ts
|
|
249
316
|
function isParamOptions(value) {
|
|
250
317
|
return typeof value === "object" && value !== null;
|
|
@@ -2718,6 +2785,11 @@ function hydrateItem(raw, select, metadata, updatable = false) {
|
|
|
2718
2785
|
const fieldMeta = fieldMap.get(fieldName);
|
|
2719
2786
|
result[fieldName] = deserializeValue(value, fieldMeta);
|
|
2720
2787
|
}
|
|
2788
|
+
} else if (isInlineSnapshotSpec(selectValue)) {
|
|
2789
|
+
const value = raw[fieldName];
|
|
2790
|
+
if (value !== void 0) {
|
|
2791
|
+
result[fieldName] = value;
|
|
2792
|
+
}
|
|
2721
2793
|
} else if (typeof selectValue === "object" && selectValue !== null) {
|
|
2722
2794
|
if ("select" in selectValue || isSelectBuilder(selectValue)) {
|
|
2723
2795
|
continue;
|
|
@@ -2856,6 +2928,18 @@ var recorder = {
|
|
|
2856
2928
|
deleteEdge(targetFactory, relationProperty) {
|
|
2857
2929
|
return { kind: "deleteEdge", targetFactory, relationProperty };
|
|
2858
2930
|
},
|
|
2931
|
+
dualEdge(forward, inverse) {
|
|
2932
|
+
return {
|
|
2933
|
+
kind: "putEdge",
|
|
2934
|
+
targetFactory: forward.target,
|
|
2935
|
+
relationProperty: forward.relationProperty,
|
|
2936
|
+
inverse: {
|
|
2937
|
+
adjacencyFactory: inverse.adjacency,
|
|
2938
|
+
targetFactory: inverse.target,
|
|
2939
|
+
relationProperty: inverse.relationProperty
|
|
2940
|
+
}
|
|
2941
|
+
};
|
|
2942
|
+
},
|
|
2859
2943
|
increment(targetFactory, keys, attribute, amount) {
|
|
2860
2944
|
return { kind: "derive", targetFactory, keys, attribute, amount };
|
|
2861
2945
|
},
|
|
@@ -3713,6 +3797,10 @@ export {
|
|
|
3713
3797
|
normalizeTopLevelSelect,
|
|
3714
3798
|
buildProject,
|
|
3715
3799
|
buildRelation,
|
|
3800
|
+
isInlineSnapshotSpec,
|
|
3801
|
+
detectRelationFields,
|
|
3802
|
+
validateInlineSnapshotSelect,
|
|
3803
|
+
getImplicitKeyFields,
|
|
3716
3804
|
param,
|
|
3717
3805
|
isParam,
|
|
3718
3806
|
BATCH_GET_MAX_KEYS,
|
|
@@ -22,14 +22,17 @@ import {
|
|
|
22
22
|
commitTransaction,
|
|
23
23
|
compileRawCondition,
|
|
24
24
|
ctxModelFor,
|
|
25
|
+
detectRelationFields,
|
|
25
26
|
execItemKeySignature,
|
|
26
27
|
executeDelete,
|
|
27
28
|
executePut,
|
|
28
29
|
executeTransaction,
|
|
29
30
|
executeUpdate,
|
|
30
31
|
getEntityWrites,
|
|
32
|
+
getImplicitKeyFields,
|
|
31
33
|
hydrate,
|
|
32
34
|
isEntityWritesDefinition,
|
|
35
|
+
isInlineSnapshotSpec,
|
|
33
36
|
isLifecycleContract,
|
|
34
37
|
isParam,
|
|
35
38
|
isRawCondition,
|
|
@@ -44,8 +47,9 @@ import {
|
|
|
44
47
|
resolveSegmentedKey,
|
|
45
48
|
serializeFieldValue,
|
|
46
49
|
serializeRawCondition,
|
|
47
|
-
skTemplate
|
|
48
|
-
|
|
50
|
+
skTemplate,
|
|
51
|
+
validateInlineSnapshotSelect
|
|
52
|
+
} from "./chunk-F2DI3GTI.js";
|
|
49
53
|
import {
|
|
50
54
|
TableMapping,
|
|
51
55
|
createColumnMap,
|
|
@@ -116,6 +120,10 @@ function buildRelations(metadata) {
|
|
|
116
120
|
type: rel.type,
|
|
117
121
|
target: targetClass.name,
|
|
118
122
|
keyBinding: { ...rel.keyBinding },
|
|
123
|
+
// List-valued source descriptor for a `refs` relation (issue #197); emitted
|
|
124
|
+
// only for `refs` so a scalar-keyed relation is byte-identical to the pre-#197
|
|
125
|
+
// manifest.
|
|
126
|
+
...rel.refs !== void 0 ? { refs: { ...rel.refs } } : {},
|
|
119
127
|
// Purely-documentary relation description (issue #166); emitted only when
|
|
120
128
|
// declared so an undescribed relation is byte-identical to the pre-#166 manifest.
|
|
121
129
|
...rel.options?.description !== void 0 ? { description: rel.options.description } : {}
|
|
@@ -190,36 +198,6 @@ function buildManifest(registry = MetadataRegistry) {
|
|
|
190
198
|
};
|
|
191
199
|
}
|
|
192
200
|
|
|
193
|
-
// src/relation/detect.ts
|
|
194
|
-
function isRelationSpec(value) {
|
|
195
|
-
if (typeof value !== "object" || value === null) return false;
|
|
196
|
-
return "select" in value || isSelectBuilder(value);
|
|
197
|
-
}
|
|
198
|
-
function detectRelationFields(select, metadata) {
|
|
199
|
-
const result = [];
|
|
200
|
-
for (const [fieldName, value] of Object.entries(select)) {
|
|
201
|
-
if (isRelationSpec(value)) {
|
|
202
|
-
const rel = metadata.relations.find((r) => r.propertyName === fieldName);
|
|
203
|
-
if (rel) {
|
|
204
|
-
result.push(rel);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return result;
|
|
209
|
-
}
|
|
210
|
-
function getImplicitKeyFields(select, metadata) {
|
|
211
|
-
const relations = detectRelationFields(select, metadata);
|
|
212
|
-
const needed = /* @__PURE__ */ new Set();
|
|
213
|
-
for (const rel of relations) {
|
|
214
|
-
for (const sourceField of Object.values(rel.keyBinding)) {
|
|
215
|
-
if (!(sourceField in select) || select[sourceField] !== true) {
|
|
216
|
-
needed.add(sourceField);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return [...needed];
|
|
221
|
-
}
|
|
222
|
-
|
|
223
201
|
// src/planner/projection.ts
|
|
224
202
|
function buildProjection(select, additionalFields) {
|
|
225
203
|
const paths = [];
|
|
@@ -237,6 +215,8 @@ function buildProjection(select, additionalFields) {
|
|
|
237
215
|
for (const [fieldName, value] of Object.entries(selectObj)) {
|
|
238
216
|
if (value === true) {
|
|
239
217
|
addPath([...parentSegments, fieldName]);
|
|
218
|
+
} else if (isInlineSnapshotSpec(value)) {
|
|
219
|
+
addPath([...parentSegments, fieldName]);
|
|
240
220
|
} else if (typeof value === "object" && value !== null) {
|
|
241
221
|
if ("select" in value || isSelectBuilder(value)) {
|
|
242
222
|
continue;
|
|
@@ -702,6 +682,11 @@ async function executeListInternal(modelClass, key, selectSpec, options = {}) {
|
|
|
702
682
|
const after = options.after ?? normalized.after;
|
|
703
683
|
const limit = options.limit ?? normalized.limit;
|
|
704
684
|
const order = options.order ?? normalized.order;
|
|
685
|
+
validateInlineSnapshotSelect(
|
|
686
|
+
select,
|
|
687
|
+
metadata,
|
|
688
|
+
(rel) => MetadataRegistry.get(rel.targetFactory())
|
|
689
|
+
);
|
|
705
690
|
const exclusiveStartKey = after ? decodeCursor(after) : void 0;
|
|
706
691
|
const implicitKeys = getImplicitKeyFields(select, metadata);
|
|
707
692
|
const executionPlan = plan(metadata, {
|
|
@@ -970,7 +955,7 @@ function relationResultPaths(select, metadata, parentPath = "$") {
|
|
|
970
955
|
const childSelect = normalizeSelectSpec(select[rel.propertyName]).select ?? {};
|
|
971
956
|
const targetMeta = MetadataRegistry.get(rel.targetFactory());
|
|
972
957
|
const childPath = `${parentPath}.${rel.propertyName}`;
|
|
973
|
-
const isMany = rel.type === "hasMany";
|
|
958
|
+
const isMany = rel.type === "hasMany" || rel.type === "refs";
|
|
974
959
|
const resultPath = isMany ? `${childPath}.items` : childPath;
|
|
975
960
|
out.push({
|
|
976
961
|
propertyName: rel.propertyName,
|
|
@@ -1064,7 +1049,7 @@ function validateDepth(select, metadata, maxDepth, currentDepth = 1) {
|
|
|
1064
1049
|
}
|
|
1065
1050
|
function relationResultPath(parentPath, rel) {
|
|
1066
1051
|
const base = `${parentPath}.${rel.propertyName}`;
|
|
1067
|
-
return rel.type === "hasMany" ? `${base}.items` : base;
|
|
1052
|
+
return rel.type === "hasMany" || rel.type === "refs" ? `${base}.items` : base;
|
|
1068
1053
|
}
|
|
1069
1054
|
function stageRelations(relations, parentPath, plan2) {
|
|
1070
1055
|
if (plan2 === void 0 || relations.length === 0) {
|
|
@@ -1188,6 +1173,95 @@ async function fetchBelongsToHasOneBatch(rel, rawParentItems, selectSpec, target
|
|
|
1188
1173
|
);
|
|
1189
1174
|
return results;
|
|
1190
1175
|
}
|
|
1176
|
+
async function fetchRefsList(rel, rawParentItem, selectSpec, targetMeta, options, currentDepth, ownPath) {
|
|
1177
|
+
const refsBinding = rel.refs;
|
|
1178
|
+
if (!refsBinding) {
|
|
1179
|
+
throw new Error(
|
|
1180
|
+
`Relation '${rel.propertyName}' is type 'refs' but carries no refs binding.`
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
const [targetKeyField] = Object.keys(rel.keyBinding);
|
|
1184
|
+
const rawList = rawParentItem[refsBinding.from];
|
|
1185
|
+
const elements = Array.isArray(rawList) ? rawList : [];
|
|
1186
|
+
const queryKeys = [];
|
|
1187
|
+
for (const element of elements) {
|
|
1188
|
+
const refValue = element != null && typeof element === "object" ? element[refsBinding.key] : element;
|
|
1189
|
+
if (refValue == null) continue;
|
|
1190
|
+
queryKeys.push({ [targetKeyField]: refValue });
|
|
1191
|
+
}
|
|
1192
|
+
if (queryKeys.length === 0) {
|
|
1193
|
+
return [];
|
|
1194
|
+
}
|
|
1195
|
+
const implicitKeys = getImplicitKeyFields(selectSpec.select, targetMeta);
|
|
1196
|
+
const projectionExtras = options.updatable ? [...implicitKeys, "PK", "SK"] : implicitKeys;
|
|
1197
|
+
const batchOps = planBatchGetForQueryKeys(
|
|
1198
|
+
targetMeta,
|
|
1199
|
+
queryKeys,
|
|
1200
|
+
selectSpec.select,
|
|
1201
|
+
projectionExtras.length > 0 ? projectionExtras : void 0
|
|
1202
|
+
);
|
|
1203
|
+
const executor = ClientManager.getExecutor();
|
|
1204
|
+
const queryKeyToRaw = /* @__PURE__ */ new Map();
|
|
1205
|
+
const mw = options.mw ?? NO_MIDDLEWARE;
|
|
1206
|
+
const opPath = [...options.relationPath ?? [], rel.propertyName];
|
|
1207
|
+
const opModel = mw.active ? ctxModelFor(rel.targetFactory()) : void 0;
|
|
1208
|
+
for (const operation of batchOps) {
|
|
1209
|
+
const items = mw.active ? await mw.runOp(
|
|
1210
|
+
operation,
|
|
1211
|
+
opPath,
|
|
1212
|
+
opModel,
|
|
1213
|
+
(op) => executor.batchGet(
|
|
1214
|
+
op,
|
|
1215
|
+
options.retry !== void 0 ? { retry: options.retry } : void 0
|
|
1216
|
+
).then((r) => r.items)
|
|
1217
|
+
) : (await executor.batchGet(
|
|
1218
|
+
operation,
|
|
1219
|
+
options.retry !== void 0 ? { retry: options.retry } : void 0
|
|
1220
|
+
)).items;
|
|
1221
|
+
for (const item of items) {
|
|
1222
|
+
for (const queryKey of queryKeys) {
|
|
1223
|
+
if (!matchesQueryKey(item, queryKey)) continue;
|
|
1224
|
+
queryKeyToRaw.set(serializeQueryKey(queryKey), item);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
const nestedRelations = detectRelationFields(selectSpec.select, targetMeta);
|
|
1229
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1230
|
+
const orderedKeys = [];
|
|
1231
|
+
for (const queryKey of queryKeys) {
|
|
1232
|
+
const serialized = serializeQueryKey(queryKey);
|
|
1233
|
+
if (seen.has(serialized)) continue;
|
|
1234
|
+
seen.add(serialized);
|
|
1235
|
+
orderedKeys.push(queryKey);
|
|
1236
|
+
}
|
|
1237
|
+
const resolved = orderedKeys.map(() => null);
|
|
1238
|
+
await mapWithConcurrency(
|
|
1239
|
+
orderedKeys,
|
|
1240
|
+
options.plan?.plan.concurrency ?? RELATION_TRAVERSAL_CONCURRENCY,
|
|
1241
|
+
async (queryKey, index) => {
|
|
1242
|
+
const raw = queryKeyToRaw.get(serializeQueryKey(queryKey));
|
|
1243
|
+
if (!raw) return;
|
|
1244
|
+
const updatable = options.updatable ?? false;
|
|
1245
|
+
let item = hydrate([raw], selectSpec.select, targetMeta, updatable)[0] ?? null;
|
|
1246
|
+
if (item && nestedRelations.length > 0) {
|
|
1247
|
+
item = await resolveRelations(
|
|
1248
|
+
item,
|
|
1249
|
+
raw,
|
|
1250
|
+
selectSpec.select,
|
|
1251
|
+
targetMeta,
|
|
1252
|
+
options,
|
|
1253
|
+
currentDepth + 1,
|
|
1254
|
+
ownPath
|
|
1255
|
+
);
|
|
1256
|
+
if (item && updatable) attachHiddenKey(item, raw);
|
|
1257
|
+
}
|
|
1258
|
+
if (item && passesPostLoad(item, selectSpec, targetMeta)) {
|
|
1259
|
+
resolved[index] = item;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
);
|
|
1263
|
+
return resolved.filter((item) => item !== null);
|
|
1264
|
+
}
|
|
1191
1265
|
function passesPostLoad(item, spec, targetMeta) {
|
|
1192
1266
|
if (spec.filter && !evaluateFilterClientSide(item, spec.filter, targetMeta)) {
|
|
1193
1267
|
return false;
|
|
@@ -1270,6 +1344,21 @@ async function resolveRelations(parentItem, rawParentItem, select, metadata, opt
|
|
|
1270
1344
|
};
|
|
1271
1345
|
return;
|
|
1272
1346
|
}
|
|
1347
|
+
if (rel.type === "refs") {
|
|
1348
|
+
const targetClass2 = rel.targetFactory();
|
|
1349
|
+
const targetMeta2 = MetadataRegistry.get(targetClass2);
|
|
1350
|
+
const items = await fetchRefsList(
|
|
1351
|
+
rel,
|
|
1352
|
+
rawParentItem,
|
|
1353
|
+
selectSpec,
|
|
1354
|
+
targetMeta2,
|
|
1355
|
+
opts,
|
|
1356
|
+
currentDepth,
|
|
1357
|
+
ownPath
|
|
1358
|
+
);
|
|
1359
|
+
result[rel.propertyName] = { items, cursor: null };
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1273
1362
|
const targetClass = rel.targetFactory();
|
|
1274
1363
|
const targetMeta = MetadataRegistry.get(targetClass);
|
|
1275
1364
|
const [resolvedItem] = await fetchBelongsToHasOneBatch(
|
|
@@ -1329,6 +1418,11 @@ async function executeQueryInner(modelClass, key, selectSpec, options, mw) {
|
|
|
1329
1418
|
const normalized = normalizeTopLevelSelect(selectSpec);
|
|
1330
1419
|
const select = normalized.select;
|
|
1331
1420
|
const filter = normalized.filter;
|
|
1421
|
+
validateInlineSnapshotSelect(
|
|
1422
|
+
select,
|
|
1423
|
+
metadata,
|
|
1424
|
+
(rel) => MetadataRegistry.get(rel.targetFactory())
|
|
1425
|
+
);
|
|
1332
1426
|
const maxDepth = options?.maxDepth ?? 1;
|
|
1333
1427
|
const relations = detectRelationFields(select, metadata);
|
|
1334
1428
|
if (relations.length > 0) {
|
|
@@ -1346,8 +1440,13 @@ async function executeQueryInner(modelClass, key, selectSpec, options, mw) {
|
|
|
1346
1440
|
});
|
|
1347
1441
|
const operation = executionPlan.operations[0];
|
|
1348
1442
|
const canParallelizeRelations = relations.length > 0 && relations.every(
|
|
1349
|
-
(rel) =>
|
|
1350
|
-
(
|
|
1443
|
+
(rel) => (
|
|
1444
|
+
// A `refs` relation (issue #197) resolves from the parent row's inline
|
|
1445
|
+
// id-list (`refs.from`), which only exists on the FETCHED parent — never the
|
|
1446
|
+
// input key — so it can never be dispatched before the parent read.
|
|
1447
|
+
rel.type !== "refs" && Object.values(rel.keyBinding).every(
|
|
1448
|
+
(sourceField) => key[sourceField] != null
|
|
1449
|
+
)
|
|
1351
1450
|
)
|
|
1352
1451
|
);
|
|
1353
1452
|
const retry = options?.retry;
|
|
@@ -1887,7 +1986,7 @@ function deriveOneConditionCheck(fragment, req) {
|
|
|
1887
1986
|
return { entity: { name: targetName, modelClass: targetClass }, keyBinding };
|
|
1888
1987
|
}
|
|
1889
1988
|
function deriveEdgeWrites(fragment, edges, lifecycle) {
|
|
1890
|
-
return edges.
|
|
1989
|
+
return edges.flatMap((edge) => deriveOneEdgeWrite(fragment, edge, lifecycle));
|
|
1891
1990
|
}
|
|
1892
1991
|
function deriveOneEdgeWrite(fragment, edge, lifecycle) {
|
|
1893
1992
|
const targetClass = edge.targetFactory();
|
|
@@ -1903,16 +2002,48 @@ function deriveOneEdgeWrite(fragment, edge, lifecycle) {
|
|
|
1903
2002
|
);
|
|
1904
2003
|
}
|
|
1905
2004
|
const adjacencyClass = relation.targetFactory();
|
|
1906
|
-
|
|
2005
|
+
if (edge.inverse === void 0) {
|
|
2006
|
+
const items = deriveEdgeWriteItemsFor(
|
|
2007
|
+
adjacencyClass,
|
|
2008
|
+
{ targetFactory: edge.targetFactory, relationProperty: edge.relationProperty },
|
|
2009
|
+
lifecycle
|
|
2010
|
+
);
|
|
2011
|
+
return [
|
|
2012
|
+
{
|
|
2013
|
+
entity: { name: adjacencyClass.name, modelClass: adjacencyClass },
|
|
2014
|
+
relation: { target: targetClass.name, property: edge.relationProperty },
|
|
2015
|
+
items
|
|
2016
|
+
}
|
|
2017
|
+
];
|
|
2018
|
+
}
|
|
2019
|
+
const inverseAdjacency = edge.inverse.adjacencyFactory();
|
|
2020
|
+
const allItems = deriveEdgeWriteItemsFor(
|
|
1907
2021
|
adjacencyClass,
|
|
1908
|
-
{
|
|
2022
|
+
{
|
|
2023
|
+
targetFactory: edge.targetFactory,
|
|
2024
|
+
relationProperty: edge.relationProperty,
|
|
2025
|
+
inverse: edge.inverse
|
|
2026
|
+
},
|
|
1909
2027
|
lifecycle
|
|
1910
2028
|
);
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
2029
|
+
const forwardItems = allItems.filter((it) => it.entity === adjacencyClass.name);
|
|
2030
|
+
const inverseItems = allItems.filter((it) => it.entity === inverseAdjacency.name);
|
|
2031
|
+
const inverseTargetClass = edge.inverse.targetFactory();
|
|
2032
|
+
return [
|
|
2033
|
+
{
|
|
2034
|
+
entity: { name: adjacencyClass.name, modelClass: adjacencyClass },
|
|
2035
|
+
relation: { target: targetClass.name, property: edge.relationProperty },
|
|
2036
|
+
items: forwardItems
|
|
2037
|
+
},
|
|
2038
|
+
{
|
|
2039
|
+
entity: { name: inverseAdjacency.name, modelClass: inverseAdjacency },
|
|
2040
|
+
relation: {
|
|
2041
|
+
target: inverseTargetClass.name,
|
|
2042
|
+
property: edge.inverse.relationProperty
|
|
2043
|
+
},
|
|
2044
|
+
items: inverseItems
|
|
2045
|
+
}
|
|
2046
|
+
];
|
|
1916
2047
|
}
|
|
1917
2048
|
function deriveUpdates(fragment, derive) {
|
|
1918
2049
|
return derive.map((d) => deriveOneUpdate(fragment, d));
|
|
@@ -6669,6 +6800,11 @@ function buildRelationOperations(metadata, select, parentPath) {
|
|
|
6669
6800
|
const targetClass = rel.targetFactory();
|
|
6670
6801
|
const targetMeta = MetadataRegistry.get(targetClass);
|
|
6671
6802
|
const childPath = `${parentPath}.${rel.propertyName}`;
|
|
6803
|
+
if (rel.type === "refs") {
|
|
6804
|
+
throw new Error(
|
|
6805
|
+
`Relation '${rel.propertyName}' is a 'refs' relation (issue #197): its read fans an inline parent-row id-list ('${rel.refs?.from}[].${rel.refs?.key}') out into one BatchGet. The static operations spec / Python codegen cannot represent a parent-list key fan-out (operation key conditions bind a single scalar '{result.*}'), so a 'refs' relation is resolvable ONLY through the TS in-process runtime (Model.query). Remove '${rel.propertyName}' from selects that are compiled to operations.json / generated Python, or resolve it with a separate BatchGet in the generated-code consumer.`
|
|
6806
|
+
);
|
|
6807
|
+
}
|
|
6672
6808
|
if (rel.type === "hasMany") {
|
|
6673
6809
|
const limit = spec.limit ?? rel.options?.limit?.default ?? 20;
|
|
6674
6810
|
ops.push(
|
|
@@ -6946,8 +7082,6 @@ export {
|
|
|
6946
7082
|
SPEC_VERSION,
|
|
6947
7083
|
MARKER_ROW_ENTITY,
|
|
6948
7084
|
buildManifest,
|
|
6949
|
-
detectRelationFields,
|
|
6950
|
-
getImplicitKeyFields,
|
|
6951
7085
|
buildProjection,
|
|
6952
7086
|
compileFilterExpression,
|
|
6953
7087
|
evaluateFilter,
|
package/dist/cli.js
CHANGED
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
buildBridgeBundle,
|
|
4
4
|
buildManifest,
|
|
5
5
|
buildOperations
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-N5NQM3SO.js";
|
|
7
7
|
import {
|
|
8
8
|
MetadataRegistry,
|
|
9
9
|
normalizeSelectSpec
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-F2DI3GTI.js";
|
|
11
11
|
import {
|
|
12
12
|
createDefaultLinter
|
|
13
13
|
} from "./chunk-PDUVTYC5.js";
|
|
@@ -744,6 +744,11 @@ function collectResultTypes(manifest, entity, select, typeName, out, seen) {
|
|
|
744
744
|
const value = select[field];
|
|
745
745
|
const relation = meta?.relations[field];
|
|
746
746
|
if (relation) {
|
|
747
|
+
if (relation.type === "refs") {
|
|
748
|
+
throw new Error(
|
|
749
|
+
`Relation '${field}' on '${relation.target}' is a 'refs' relation (issue #197): it is resolvable only through the TS in-process runtime, not the Python runtime (its parent-list BatchGet fan-out has no static-spec / Python representation). Remove '${field}' from selects compiled to generated Python types.`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
747
752
|
const spec = normalizeSelectSpec(value);
|
|
748
753
|
const childSelect = spec.select ?? {};
|
|
749
754
|
const itemTypeName = `${typeName}${toPascalCase(field)}Item`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CdcEmulatorOptions, e as ChangeHandler, U as Unsubscribe, F as FaultSpec, g as ConcurrentRecomputeRef, c as ChangeEvent, E as EventLog, R as ReplayOptions, S as ShardId, V as ViewDefinition, m as EntityMetadata } from './maintenance-view-adapter-
|
|
1
|
+
import { C as CdcEmulatorOptions, e as ChangeHandler, U as Unsubscribe, F as FaultSpec, g as ConcurrentRecomputeRef, c as ChangeEvent, E as EventLog, R as ReplayOptions, S as ShardId, V as ViewDefinition, m as EntityMetadata } from './maintenance-view-adapter-CFeasCKo.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* CDC emulator (issue #72). Subscribes to the core write-capture seam
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { x as ParamDescriptor, y as EntityRef, z as ConditionInput, A as Param, M as ModelStatic, w as DDBModel, Q as QueryModelContract, G as QueryMethodSpec, H as CommandModelContract, I as CommandMethodSpec, J as ContractSpec, K as QuerySpec, L as CommandSpec, N as TransactionSpec, O as ContextSpec, X as DefinitionMap, Y as OperationsDocument, Z as AnyOperationDefinition, _ as Manifest, $ as BridgeBundle, a0 as ConditionSpec } from './maintenance-view-adapter-
|
|
2
|
-
import { M as MetadataRegistry } from './registry-
|
|
1
|
+
import { x as ParamDescriptor, y as EntityRef, z as ConditionInput, A as Param, M as ModelStatic, w as DDBModel, Q as QueryModelContract, G as QueryMethodSpec, H as CommandModelContract, I as CommandMethodSpec, J as ContractSpec, K as QuerySpec, L as CommandSpec, N as TransactionSpec, O as ContextSpec, X as DefinitionMap, Y as OperationsDocument, Z as AnyOperationDefinition, _ as Manifest, $ as BridgeBundle, a0 as ConditionSpec } from './maintenance-view-adapter-CFeasCKo.js';
|
|
2
|
+
import { M as MetadataRegistry } from './registry-DbqmFyab.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Declarative transaction definition DSL (issue #46, Python-bridge Phase 4).
|