graphddb 0.7.10 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +6 -6
  2. package/dist/cdc/index.d.ts +389 -5
  3. package/dist/cdc/index.js +4 -4
  4. package/dist/{chunk-AD6ZQTTE.js → chunk-GS4C5VGO.js} +2 -6
  5. package/dist/{chunk-DFUKGU2Q.js → chunk-HNY2EJPV.js} +216 -229
  6. package/dist/{chunk-3ZU2VW3L.js → chunk-L2NEDS7U.js} +582 -781
  7. package/dist/chunk-L4QRCHRQ.js +278 -0
  8. package/dist/chunk-LAT64YCZ.js +1987 -0
  9. package/dist/chunk-S2NI4PBW.js +187 -0
  10. package/dist/{chunk-EOJDN3SA.js → chunk-T44OB5GU.js} +3757 -6138
  11. package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
  12. package/dist/cli.js +63 -254
  13. package/dist/index.d.ts +23 -1550
  14. package/dist/index.js +94 -1850
  15. package/dist/internal/index.d.ts +84 -0
  16. package/dist/internal/index.js +701 -0
  17. package/dist/{maintenance-view-adapter-BAZ9uBGe.d.ts → key-DZtjAQDh.d.ts} +573 -1817
  18. package/dist/linter/index.d.ts +39 -7
  19. package/dist/linter/index.js +22 -4
  20. package/dist/{registry-LWE54Sdc.d.ts → linter-DQY7gUEk.d.ts} +22 -22
  21. package/dist/prepared-artifact-HFealr1q.d.ts +281 -0
  22. package/dist/spec/index.d.ts +506 -5
  23. package/dist/spec/index.js +22 -18
  24. package/dist/testing/index.d.ts +2 -3
  25. package/dist/testing/index.js +4 -4
  26. package/dist/transform/index.d.ts +1 -1
  27. package/dist/transform/index.js +4 -4
  28. package/dist/{types-BQLzTEqh.d.ts → types-2PMXEn5x.d.ts} +8 -10
  29. package/dist/types-DW__-Icc.d.ts +450 -0
  30. package/docs/cdc-projection.md +5 -5
  31. package/docs/class-hydration.md +1 -1
  32. package/docs/cqrs-contract.md +79 -20
  33. package/docs/design-patterns.md +5 -5
  34. package/docs/docs-generation.md +6 -6
  35. package/docs/middleware.md +15 -15
  36. package/docs/mutation-command-derivation.md +52 -42
  37. package/docs/prepared-statements.md +14 -14
  38. package/docs/python-bridge.md +96 -65
  39. package/docs/spec.md +153 -124
  40. package/docs/testing.md +9 -8
  41. package/package.json +14 -4
  42. package/dist/chunk-3UD3XIF2.js +0 -860
  43. package/dist/chunk-MMVHOUM4.js +0 -24
  44. package/dist/from-change-Ty95KA8C.d.ts +0 -327
  45. package/dist/index-Dc7d8mWI.d.ts +0 -1089
  46. package/dist/relation-depth-BRS513Tq.d.ts +0 -36
@@ -1,11 +1,149 @@
1
1
  import {
2
2
  MetadataRegistry,
3
3
  attachHiddenKey
4
- } from "./chunk-3ZU2VW3L.js";
4
+ } from "./chunk-L2NEDS7U.js";
5
5
  import {
6
6
  resolveKey,
7
7
  segmentFieldNames
8
- } from "./chunk-PDUVTYC5.js";
8
+ } from "./chunk-XTWXMOHD.js";
9
+
10
+ // src/define/entity-writes.ts
11
+ var MAINTAIN_TRIGGER_RE = /^[^.\s$[\]*]+\.(?:created|updated|removed)$/;
12
+ function isMaintainTrigger(value) {
13
+ return typeof value === "string" && MAINTAIN_TRIGGER_RE.test(value);
14
+ }
15
+ function makeProjectionTransform(path, op, args) {
16
+ if (op === "preview") {
17
+ const [n] = args;
18
+ if (typeof n !== "number" || !Number.isInteger(n) || n <= 0) {
19
+ throw new Error(
20
+ `transform: the \`preview\` op requires a positive integer length bound (e.g. \`preview('$.body', 200)\`), but received ${JSON.stringify(n)}.`
21
+ );
22
+ }
23
+ return { kind: "transform", path, op, args: [n] };
24
+ }
25
+ if (op === "identity") {
26
+ if (args.length > 0) {
27
+ throw new Error(
28
+ `transform: the \`identity\` op takes no arguments, but received ${JSON.stringify(args)}.`
29
+ );
30
+ }
31
+ return { kind: "transform", path, op, args: [] };
32
+ }
33
+ throw new Error(
34
+ `transform: unknown projection op ${JSON.stringify(op)} (expected \`identity\` or \`preview\`).`
35
+ );
36
+ }
37
+ function pathOf(value) {
38
+ return typeof value === "string" ? value : value.path;
39
+ }
40
+ function preview(path, n) {
41
+ return makeProjectionTransform(pathOf(path), "preview", [n]);
42
+ }
43
+ function identity(path) {
44
+ return makeProjectionTransform(pathOf(path), "identity", []);
45
+ }
46
+ var LIFECYCLE_CONTRACT_MARKER = /* @__PURE__ */ Symbol(
47
+ "graphddb:lifecycleContract"
48
+ );
49
+ function isLifecycleContract(value) {
50
+ return typeof value === "object" && value !== null && value[LIFECYCLE_CONTRACT_MARKER] === true;
51
+ }
52
+ var ENTITY_WRITES_MARKER = /* @__PURE__ */ Symbol("graphddb:entityWrites");
53
+ function isEntityWritesDefinition(value) {
54
+ return typeof value === "object" && value !== null && value[ENTITY_WRITES_MARKER] === true;
55
+ }
56
+ function freezeEffects(effects) {
57
+ const out = {};
58
+ if (effects.requires !== void 0) out.requires = effects.requires;
59
+ if (effects.unique !== void 0) out.unique = effects.unique;
60
+ if (effects.edges !== void 0) out.edges = effects.edges;
61
+ if (effects.derive !== void 0) out.derive = effects.derive;
62
+ if (effects.emits !== void 0) out.emits = effects.emits;
63
+ if (effects.idempotency !== void 0) out.idempotency = effects.idempotency;
64
+ return out;
65
+ }
66
+ var recorder = {
67
+ lifecycle(effects = {}) {
68
+ return {
69
+ [LIFECYCLE_CONTRACT_MARKER]: true,
70
+ effects: freezeEffects(effects)
71
+ };
72
+ },
73
+ exists(targetFactory, keys) {
74
+ return { kind: "requires", targetFactory, keys };
75
+ },
76
+ unique(spec) {
77
+ return { kind: "unique", name: spec.name, scope: spec.scope, fields: spec.fields };
78
+ },
79
+ putEdge(targetFactory, relationProperty) {
80
+ return { kind: "putEdge", targetFactory, relationProperty };
81
+ },
82
+ deleteEdge(targetFactory, relationProperty) {
83
+ return { kind: "deleteEdge", targetFactory, relationProperty };
84
+ },
85
+ dualEdge(forward, inverse) {
86
+ return {
87
+ kind: "putEdge",
88
+ targetFactory: forward.target,
89
+ relationProperty: forward.relationProperty,
90
+ inverse: {
91
+ adjacencyFactory: inverse.adjacency,
92
+ targetFactory: inverse.target,
93
+ relationProperty: inverse.relationProperty
94
+ }
95
+ };
96
+ },
97
+ increment(targetFactory, keys, attribute, amount) {
98
+ return { kind: "derive", targetFactory, keys, attribute, amount };
99
+ },
100
+ transform(path, op, ...args) {
101
+ return makeProjectionTransform(path, op, args);
102
+ },
103
+ event(name, payload) {
104
+ return { kind: "event", name, payload };
105
+ },
106
+ idempotentBy(token) {
107
+ return { kind: "idempotency", token };
108
+ }
109
+ };
110
+ function entityWrites(builder) {
111
+ const shape = builder(recorder);
112
+ for (const phase of ["create", "update", "remove"]) {
113
+ const contract = shape[phase];
114
+ if (contract !== void 0 && !isLifecycleContract(contract)) {
115
+ throw new Error(
116
+ `entityWrites: the '${phase}' lifecycle must be built with \`w.lifecycle({...})\` (it carries the \xA72 effect arrays), but received ${JSON.stringify(contract)}.`
117
+ );
118
+ }
119
+ }
120
+ if (shape.create === void 0 && shape.update === void 0 && shape.remove === void 0) {
121
+ throw new Error(
122
+ "entityWrites(...) must declare at least one lifecycle (`create` / `update` / `remove`); an empty save contract declares nothing."
123
+ );
124
+ }
125
+ return {
126
+ [ENTITY_WRITES_MARKER]: true,
127
+ ...shape.create !== void 0 ? { create: shape.create } : {},
128
+ ...shape.update !== void 0 ? { update: shape.update } : {},
129
+ ...shape.remove !== void 0 ? { remove: shape.remove } : {}
130
+ };
131
+ }
132
+ function getEntityWrites(modelClass) {
133
+ for (const name of Object.getOwnPropertyNames(modelClass)) {
134
+ let value;
135
+ try {
136
+ value = modelClass[name];
137
+ } catch {
138
+ continue;
139
+ }
140
+ if (isEntityWritesDefinition(value)) return value;
141
+ }
142
+ return void 0;
143
+ }
144
+ function lifecyclePhaseForIntent(intent) {
145
+ return intent === "upsert" ? "create" : intent;
146
+ }
9
147
 
10
148
  // src/types/select-builder.ts
11
149
  var SELECT_BUILDER = /* @__PURE__ */ Symbol.for("graphddb.selectBuilder");
@@ -226,150 +364,98 @@ function deserializeValue(value, fieldMeta) {
226
364
  return value;
227
365
  }
228
366
 
229
- // src/define/entity-writes.ts
230
- var MAINTAIN_TRIGGER_RE = /^[^.\s$[\]*]+\.(?:created|updated|removed)$/;
231
- function maintainTrigger(value) {
232
- if (typeof value !== "string" || !MAINTAIN_TRIGGER_RE.test(value)) {
233
- throw new Error(
234
- `maintainTrigger: a maintenance trigger must be an \`Entity.event\` string where event is one of \`created\` / \`updated\` / \`removed\` (e.g. \`"Post.created"\`), but received ${JSON.stringify(value)}.`
235
- );
367
+ // src/cdc/from-change.ts
368
+ function buildFullSelect(metadata) {
369
+ const select = {};
370
+ for (const field of metadata.fields) {
371
+ select[field.propertyName] = true;
236
372
  }
237
- return value;
238
- }
239
- function isMaintainTrigger(value) {
240
- return typeof value === "string" && MAINTAIN_TRIGGER_RE.test(value);
373
+ return select;
241
374
  }
242
- function makeProjectionTransform(path, op, args) {
243
- if (op === "preview") {
244
- const [n] = args;
245
- if (typeof n !== "number" || !Number.isInteger(n) || n <= 0) {
246
- throw new Error(
247
- `transform: the \`preview\` op requires a positive integer length bound (e.g. \`preview('$.body', 200)\`), but received ${JSON.stringify(n)}.`
248
- );
249
- }
250
- return { kind: "transform", path, op, args: [n] };
375
+ function eventOwnedBy(event, metadata, modelName) {
376
+ if (event.model !== void 0) {
377
+ return event.model === modelName;
251
378
  }
252
- if (op === "identity") {
253
- if (args.length > 0) {
254
- throw new Error(
255
- `transform: the \`identity\` op takes no arguments, but received ${JSON.stringify(args)}.`
256
- );
379
+ return metadata.prefix.length > 0 && event.keys.pk.startsWith(metadata.prefix);
380
+ }
381
+ function hydrateImage(image, select, metadata, modelClass) {
382
+ if (image === void 0) return null;
383
+ const [hydrated] = hydrate([image], select, metadata);
384
+ const instance = new modelClass();
385
+ Object.assign(instance, hydrated);
386
+ for (const emb of metadata.embeddedFields) {
387
+ const rawEmb = image[emb.propertyName];
388
+ if (rawEmb !== void 0 && rawEmb !== null) {
389
+ instance[emb.propertyName] = rawEmb;
257
390
  }
258
- return { kind: "transform", path, op, args: [] };
259
391
  }
260
- throw new Error(
261
- `transform: unknown projection op ${JSON.stringify(op)} (expected \`identity\` or \`preview\`).`
262
- );
263
- }
264
- function pathOf(value) {
265
- return typeof value === "string" ? value : value.path;
266
- }
267
- function preview(path, n) {
268
- return makeProjectionTransform(pathOf(path), "preview", [n]);
269
- }
270
- function identity(path) {
271
- return makeProjectionTransform(pathOf(path), "identity", []);
272
- }
273
- var LIFECYCLE_CONTRACT_MARKER = /* @__PURE__ */ Symbol(
274
- "graphddb:lifecycleContract"
275
- );
276
- function isLifecycleContract(value) {
277
- return typeof value === "object" && value !== null && value[LIFECYCLE_CONTRACT_MARKER] === true;
278
- }
279
- var ENTITY_WRITES_MARKER = /* @__PURE__ */ Symbol("graphddb:entityWrites");
280
- function isEntityWritesDefinition(value) {
281
- return typeof value === "object" && value !== null && value[ENTITY_WRITES_MARKER] === true;
392
+ return instance;
282
393
  }
283
- function freezeEffects(effects) {
284
- const out = {};
285
- if (effects.requires !== void 0) out.requires = effects.requires;
286
- if (effects.unique !== void 0) out.unique = effects.unique;
287
- if (effects.edges !== void 0) out.edges = effects.edges;
288
- if (effects.derive !== void 0) out.derive = effects.derive;
289
- if (effects.emits !== void 0) out.emits = effects.emits;
290
- if (effects.idempotency !== void 0) out.idempotency = effects.idempotency;
291
- return out;
394
+ function parseChange(event, metadata, modelName, modelClass) {
395
+ if (!eventOwnedBy(event, metadata, modelName)) {
396
+ return [null, null];
397
+ }
398
+ const select = buildFullSelect(metadata);
399
+ const oldRecord = hydrateImage(event.oldImage, select, metadata, modelClass);
400
+ const newRecord = hydrateImage(event.newImage, select, metadata, modelClass);
401
+ return [oldRecord, newRecord];
292
402
  }
293
- var recorder = {
294
- lifecycle(effects = {}) {
295
- return {
296
- [LIFECYCLE_CONTRACT_MARKER]: true,
297
- effects: freezeEffects(effects)
298
- };
299
- },
300
- exists(targetFactory, keys) {
301
- return { kind: "requires", targetFactory, keys };
302
- },
303
- unique(spec) {
304
- return { kind: "unique", name: spec.name, scope: spec.scope, fields: spec.fields };
305
- },
306
- putEdge(targetFactory, relationProperty) {
307
- return { kind: "putEdge", targetFactory, relationProperty };
308
- },
309
- deleteEdge(targetFactory, relationProperty) {
310
- return { kind: "deleteEdge", targetFactory, relationProperty };
311
- },
312
- dualEdge(forward, inverse) {
313
- return {
314
- kind: "putEdge",
315
- targetFactory: forward.target,
316
- relationProperty: forward.relationProperty,
317
- inverse: {
318
- adjacencyFactory: inverse.adjacency,
319
- targetFactory: inverse.target,
320
- relationProperty: inverse.relationProperty
321
- }
322
- };
323
- },
324
- increment(targetFactory, keys, attribute, amount) {
325
- return { kind: "derive", targetFactory, keys, attribute, amount };
326
- },
327
- transform(path, op, ...args) {
328
- return makeProjectionTransform(path, op, args);
329
- },
330
- event(name, payload) {
331
- return { kind: "event", name, payload };
332
- },
333
- idempotentBy(token) {
334
- return { kind: "idempotency", token };
403
+
404
+ // src/cdc/subscribe.ts
405
+ function resolveRoutableModels(handlers) {
406
+ const byName = /* @__PURE__ */ new Map();
407
+ for (const [cls, meta] of MetadataRegistry.getAll()) {
408
+ byName.set(cls.name, { cls, meta });
335
409
  }
336
- };
337
- function entityWrites(builder) {
338
- const shape = builder(recorder);
339
- for (const phase of ["create", "update", "remove"]) {
340
- const contract = shape[phase];
341
- if (contract !== void 0 && !isLifecycleContract(contract)) {
410
+ const routable = /* @__PURE__ */ new Map();
411
+ for (const modelName of Object.keys(handlers)) {
412
+ const entry = byName.get(modelName);
413
+ if (!entry) {
342
414
  throw new Error(
343
- `entityWrites: the '${phase}' lifecycle must be built with \`w.lifecycle({...})\` (it carries the \xA72 effect arrays), but received ${JSON.stringify(contract)}.`
415
+ `DDBModel.subscribe: no model named '${modelName}' is registered. Did you import the model module (so its @model decorator ran) and spell the handler key exactly as the class name?`
344
416
  );
345
417
  }
418
+ if (!entry.meta.cdcProjected) {
419
+ throw new Error(
420
+ `DDBModel.subscribe: model '${modelName}' is not @cdcProjected, so its change events cannot be parsed. Add @cdcProjected() to the model, or remove its handler.`
421
+ );
422
+ }
423
+ routable.set(modelName, {
424
+ modelClass: entry.cls,
425
+ metadata: entry.meta,
426
+ modelName
427
+ });
346
428
  }
347
- if (shape.create === void 0 && shape.update === void 0 && shape.remove === void 0) {
348
- throw new Error(
349
- "entityWrites(...) must declare at least one lifecycle (`create` / `update` / `remove`); an empty save contract declares nothing."
429
+ return routable;
430
+ }
431
+ async function dispatchEvent(event, routable, handlers) {
432
+ for (const { modelClass, metadata, modelName } of routable.values()) {
433
+ const [oldRecord, newRecord] = parseChange(
434
+ event,
435
+ metadata,
436
+ modelName,
437
+ modelClass
350
438
  );
439
+ if (oldRecord === null && newRecord === null) continue;
440
+ const handler = handlers[modelName];
441
+ await handler(oldRecord, newRecord);
442
+ return true;
351
443
  }
352
- return {
353
- [ENTITY_WRITES_MARKER]: true,
354
- ...shape.create !== void 0 ? { create: shape.create } : {},
355
- ...shape.update !== void 0 ? { update: shape.update } : {},
356
- ...shape.remove !== void 0 ? { remove: shape.remove } : {}
357
- };
444
+ return false;
358
445
  }
359
- function getEntityWrites(modelClass) {
360
- for (const name of Object.getOwnPropertyNames(modelClass)) {
361
- let value;
362
- try {
363
- value = modelClass[name];
364
- } catch {
365
- continue;
446
+ function buildSubscribeHandler(handlers) {
447
+ const routable = resolveRoutableModels(handlers);
448
+ return async (batch) => {
449
+ const batchItemFailures = [];
450
+ for (const event of batch.records) {
451
+ try {
452
+ await dispatchEvent(event, routable, handlers);
453
+ } catch {
454
+ batchItemFailures.push(event.sequenceNumber);
455
+ }
366
456
  }
367
- if (isEntityWritesDefinition(value)) return value;
368
- }
369
- return void 0;
370
- }
371
- function lifecyclePhaseForIntent(intent) {
372
- return intent;
457
+ return { batchItemFailures };
458
+ };
373
459
  }
374
460
 
375
461
  // src/relation/maintenance-view-adapter.ts
@@ -1073,100 +1159,6 @@ function buildMaintenanceGraphImpl(registry, views) {
1073
1159
  };
1074
1160
  }
1075
1161
 
1076
- // src/cdc/from-change.ts
1077
- function buildFullSelect(metadata) {
1078
- const select = {};
1079
- for (const field of metadata.fields) {
1080
- select[field.propertyName] = true;
1081
- }
1082
- return select;
1083
- }
1084
- function eventOwnedBy(event, metadata, modelName) {
1085
- if (event.model !== void 0) {
1086
- return event.model === modelName;
1087
- }
1088
- return metadata.prefix.length > 0 && event.keys.pk.startsWith(metadata.prefix);
1089
- }
1090
- function hydrateImage(image, select, metadata, modelClass) {
1091
- if (image === void 0) return null;
1092
- const [hydrated] = hydrate([image], select, metadata);
1093
- const instance = new modelClass();
1094
- Object.assign(instance, hydrated);
1095
- for (const emb of metadata.embeddedFields) {
1096
- const rawEmb = image[emb.propertyName];
1097
- if (rawEmb !== void 0 && rawEmb !== null) {
1098
- instance[emb.propertyName] = rawEmb;
1099
- }
1100
- }
1101
- return instance;
1102
- }
1103
- function parseChange(event, metadata, modelName, modelClass) {
1104
- if (!eventOwnedBy(event, metadata, modelName)) {
1105
- return [null, null];
1106
- }
1107
- const select = buildFullSelect(metadata);
1108
- const oldRecord = hydrateImage(event.oldImage, select, metadata, modelClass);
1109
- const newRecord = hydrateImage(event.newImage, select, metadata, modelClass);
1110
- return [oldRecord, newRecord];
1111
- }
1112
-
1113
- // src/cdc/subscribe.ts
1114
- function resolveRoutableModels(handlers) {
1115
- const byName = /* @__PURE__ */ new Map();
1116
- for (const [cls, meta] of MetadataRegistry.getAll()) {
1117
- byName.set(cls.name, { cls, meta });
1118
- }
1119
- const routable = /* @__PURE__ */ new Map();
1120
- for (const modelName of Object.keys(handlers)) {
1121
- const entry = byName.get(modelName);
1122
- if (!entry) {
1123
- throw new Error(
1124
- `DDBModel.subscribe: no model named '${modelName}' is registered. Did you import the model module (so its @model decorator ran) and spell the handler key exactly as the class name?`
1125
- );
1126
- }
1127
- if (!entry.meta.cdcProjected) {
1128
- throw new Error(
1129
- `DDBModel.subscribe: model '${modelName}' is not @cdcProjected, so its change events cannot be parsed. Add @cdcProjected() to the model, or remove its handler.`
1130
- );
1131
- }
1132
- routable.set(modelName, {
1133
- modelClass: entry.cls,
1134
- metadata: entry.meta,
1135
- modelName
1136
- });
1137
- }
1138
- return routable;
1139
- }
1140
- async function dispatchEvent(event, routable, handlers) {
1141
- for (const { modelClass, metadata, modelName } of routable.values()) {
1142
- const [oldRecord, newRecord] = parseChange(
1143
- event,
1144
- metadata,
1145
- modelName,
1146
- modelClass
1147
- );
1148
- if (oldRecord === null && newRecord === null) continue;
1149
- const handler = handlers[modelName];
1150
- await handler(oldRecord, newRecord);
1151
- return true;
1152
- }
1153
- return false;
1154
- }
1155
- function buildSubscribeHandler(handlers) {
1156
- const routable = resolveRoutableModels(handlers);
1157
- return async (batch) => {
1158
- const batchItemFailures = [];
1159
- for (const event of batch.records) {
1160
- try {
1161
- await dispatchEvent(event, routable, handlers);
1162
- } catch {
1163
- batchItemFailures.push(event.sequenceNumber);
1164
- }
1165
- }
1166
- return { batchItemFailures };
1167
- };
1168
- }
1169
-
1170
1162
  export {
1171
1163
  SELECT_SPEC,
1172
1164
  isSelectBuilder,
@@ -1179,18 +1171,13 @@ export {
1179
1171
  validateInlineSnapshotSelect,
1180
1172
  getImplicitKeyFields,
1181
1173
  hydrate,
1182
- maintainTrigger,
1183
- isMaintainTrigger,
1184
1174
  preview,
1185
1175
  identity,
1186
- LIFECYCLE_CONTRACT_MARKER,
1187
1176
  isLifecycleContract,
1188
- ENTITY_WRITES_MARKER,
1189
1177
  isEntityWritesDefinition,
1190
1178
  entityWrites,
1191
1179
  getEntityWrites,
1192
1180
  lifecyclePhaseForIntent,
1193
- collectViewDefinitions,
1194
1181
  buildMaintenanceGraph,
1195
1182
  parseChange,
1196
1183
  buildSubscribeHandler