graphddb 0.7.9 → 0.8.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.
Files changed (45) hide show
  1. package/README.md +6 -6
  2. package/dist/cdc/index.d.ts +389 -4
  3. package/dist/cdc/index.js +4 -3
  4. package/dist/{chunk-ZPNRLOKA.js → chunk-GS4C5VGO.js} +4 -6
  5. package/dist/chunk-HNY2EJPV.js +1184 -0
  6. package/dist/{chunk-NYM7K2ST.js → chunk-I4LEJ4TF.js} +3812 -6724
  7. package/dist/{chunk-PFFPLD4B.js → chunk-L2NEDS7U.js} +725 -2112
  8. package/dist/chunk-L4QRCHRQ.js +278 -0
  9. package/dist/chunk-LGHSZIEE.js +187 -0
  10. package/dist/chunk-N4NWYNGZ.js +1987 -0
  11. package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
  12. package/dist/cli.js +63 -252
  13. package/dist/index.d.ts +23 -1548
  14. package/dist/index.js +100 -1778
  15. package/dist/internal/index.d.ts +84 -0
  16. package/dist/internal/index.js +701 -0
  17. package/dist/{maintenance-view-adapter-BATUh_I8.d.ts → key-DR7_lpyk.d.ts} +538 -2975
  18. package/dist/linter/index.d.ts +39 -6
  19. package/dist/linter/index.js +22 -4
  20. package/dist/{registry-CXhP4TaE.d.ts → linter-C-vypgut.d.ts} +22 -22
  21. package/dist/prepared-artifact-BpPgkXEo.d.ts +281 -0
  22. package/dist/spec/index.d.ts +506 -4
  23. package/dist/spec/index.js +36 -17
  24. package/dist/testing/index.d.ts +2 -2
  25. package/dist/testing/index.js +4 -3
  26. package/dist/transform/index.d.ts +460 -1
  27. package/dist/transform/index.js +2085 -2
  28. package/dist/types-2PMXEn5x.d.ts +1205 -0
  29. package/dist/types-BXLzIcQD.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 +28 -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 +113 -66
  39. package/docs/spec.md +153 -124
  40. package/docs/testing.md +9 -8
  41. package/package.json +20 -5
  42. package/dist/chunk-MMVHOUM4.js +0 -24
  43. package/dist/from-change-DanwjE5b.d.ts +0 -327
  44. package/dist/index-CtPJSMrc.d.ts +0 -934
  45. package/dist/relation-depth-Dg3yhl7S.d.ts +0 -36
@@ -0,0 +1,278 @@
1
+ // src/spec/types.ts
2
+ var SPEC_VERSION = "1.1";
3
+ var SPEC_VERSION_SCP = "1.2";
4
+ var SPEC_VERSION_SUPPORTED = SPEC_VERSION_SCP;
5
+ var EXPR_VERSION = 1;
6
+ var MARKER_ROW_ENTITY = "__marker__";
7
+
8
+ // src/define/transaction.ts
9
+ var TX_REF_BRAND = /* @__PURE__ */ Symbol("graphddb:txref");
10
+ function isTransactionRef(value) {
11
+ return typeof value === "object" && value !== null && value[TX_REF_BRAND] === true;
12
+ }
13
+ var MARKER_TAGS = [
14
+ // pass 0: short, begins with 'A'.
15
+ "Axqv",
16
+ // pass 1: long, begins with 'Z'. The two tags share NO common prefix and NO
17
+ // common substring of length >= 2 (their alphabets are disjoint), and differ
18
+ // in length, so any fragment-extracting / length-dependent transform
19
+ // (`slice(0,n)`, `[i]`, `charAt`, `padStart`, `padEnd`) yields *different*
20
+ // fragments across the two passes -- the differential check then rejects it.
21
+ // (Convergence-to-constant escapes that even disjoint markers cannot separate
22
+ // -- `slice(0,0)` -> '' in both passes, `includes('x')` -> a constant boolean
23
+ // -- are caught instead by the per-access coercion ledger; see
24
+ // {@link makeFieldRef} and {@link defineTransaction}.)
25
+ "Zmnoprstuwy0123456789WIDEMARKERBODYFILLERZ"
26
+ ];
27
+ var MARKER_PASS_COUNT = MARKER_TAGS.length;
28
+ var when = {
29
+ eq(left, right) {
30
+ assertRef(left, "when.eq");
31
+ return { op: "eq", left, right };
32
+ },
33
+ ne(left, right) {
34
+ assertRef(left, "when.ne");
35
+ return { op: "ne", left, right };
36
+ }
37
+ };
38
+ function assertRef(value, context) {
39
+ if (!isTransactionRef(value)) {
40
+ throw new Error(
41
+ `defineTransaction: ${context} expects a field reference (e.g. \`p.status\` or an element field) on its left-hand side.`
42
+ );
43
+ }
44
+ }
45
+ function isTransactionDefinition(value) {
46
+ return typeof value === "object" && value !== null && value.__isTransactionDefinition === true;
47
+ }
48
+ function isMultiWriteBody(def) {
49
+ let writes = 0;
50
+ for (const instr of def.instructions) {
51
+ if (instr.kind === "forEach") {
52
+ return true;
53
+ }
54
+ if (instr.operation === "conditionCheck") return true;
55
+ writes += 1;
56
+ if (writes > 1) return true;
57
+ }
58
+ return writes > 1;
59
+ }
60
+ var SCP_LOWERED_MARKER = "graphddb:scp-lowered/v1";
61
+
62
+ // src/spec/expression.ts
63
+ var UNARY_OPS = /* @__PURE__ */ new Set(["neg", "not", "len"]);
64
+ var BINARY_OPS = /* @__PURE__ */ new Set([
65
+ "add",
66
+ "sub",
67
+ "mul",
68
+ "div",
69
+ "mod",
70
+ "concat",
71
+ "eq",
72
+ "ne",
73
+ "lt",
74
+ "le",
75
+ "gt",
76
+ "ge",
77
+ "and",
78
+ "or",
79
+ "coalesce"
80
+ ]);
81
+ var TERNARY_OPS = /* @__PURE__ */ new Set(["cond"]);
82
+ var ALL_OPS = /* @__PURE__ */ new Set([...UNARY_OPS, ...BINARY_OPS, ...TERNARY_OPS]);
83
+ var I64_MIN = -(2n ** 63n);
84
+ var I64_MAX = 2n ** 63n - 1n;
85
+ function cmpCodePoints(a, b) {
86
+ const ia = a[Symbol.iterator]();
87
+ const ib = b[Symbol.iterator]();
88
+ for (; ; ) {
89
+ const ra = ia.next();
90
+ const rb = ib.next();
91
+ if (ra.done && rb.done) return 0;
92
+ if (ra.done) return -1;
93
+ if (rb.done) return 1;
94
+ const ca = ra.value.codePointAt(0);
95
+ const cb = rb.value.codePointAt(0);
96
+ if (ca !== cb) return ca < cb ? -1 : 1;
97
+ }
98
+ }
99
+ function invalid(context, path, message) {
100
+ throw new Error(`${context}: invalid SCP expression at ${path}: ${message}`);
101
+ }
102
+ function isPlainObject(v) {
103
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
104
+ const proto = Object.getPrototypeOf(v);
105
+ return proto === Object.prototype || proto === null;
106
+ }
107
+ function canonicalizeNode(node, context, path) {
108
+ if (node === null) return null;
109
+ if (typeof node === "string" || typeof node === "boolean") return node;
110
+ if (typeof node === "number") {
111
+ if (!Number.isFinite(node)) {
112
+ invalid(context, path, `non-finite number literal (${node}).`);
113
+ }
114
+ if (Number.isInteger(node) && !Number.isSafeInteger(node)) {
115
+ invalid(
116
+ context,
117
+ path,
118
+ `integral number literal ${node} exceeds the safe-integer range; carry it as {int:"${node}"} (expression-ir.md \xA72.3).`
119
+ );
120
+ }
121
+ return node;
122
+ }
123
+ if (Array.isArray(node)) {
124
+ invalid(context, path, "a bare array is not an expression (use {arr:[\u2026]}).");
125
+ }
126
+ if (!isPlainObject(node)) {
127
+ invalid(context, path, `expected a literal or a single-key node object.`);
128
+ }
129
+ const keys = Object.keys(node);
130
+ if (keys.length !== 1) {
131
+ invalid(
132
+ context,
133
+ path,
134
+ `a node object must have exactly one key, got [${keys.join(", ")}].`
135
+ );
136
+ }
137
+ const key = keys[0];
138
+ const arg = node[key];
139
+ if (key === "int") {
140
+ if (typeof arg !== "string" || !/^-?[0-9]+$/.test(arg)) {
141
+ invalid(context, `${path}.int`, '{int:"\u2026"} expects an integer string.');
142
+ }
143
+ const v = BigInt(arg);
144
+ if (v < I64_MIN || v > I64_MAX) {
145
+ invalid(context, `${path}.int`, `int literal ${arg} exceeds i64.`);
146
+ }
147
+ if (v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER)) {
148
+ return Number(v);
149
+ }
150
+ return { int: v.toString() };
151
+ }
152
+ if (key === "float") {
153
+ if (typeof arg !== "number" || !Number.isFinite(arg)) {
154
+ invalid(context, `${path}.float`, "{float:n} expects a finite number.");
155
+ }
156
+ return Number.isInteger(arg) ? { float: arg } : arg;
157
+ }
158
+ if (key === "ref" || key === "refOpt") {
159
+ if (!Array.isArray(arg) || arg.length === 0) {
160
+ invalid(context, `${path}.${key}`, `{${key}:[\u2026]} expects a non-empty path array.`);
161
+ }
162
+ const segs = arg.map((seg, i) => {
163
+ if (typeof seg !== "string" || seg.length === 0) {
164
+ invalid(
165
+ context,
166
+ `${path}.${key}[${i}]`,
167
+ "a reference path segment must be a non-empty string."
168
+ );
169
+ }
170
+ return seg;
171
+ });
172
+ return key === "ref" ? { ref: segs } : { refOpt: segs };
173
+ }
174
+ if (key === "obj") {
175
+ if (!isPlainObject(arg)) {
176
+ invalid(context, `${path}.obj`, "{obj:{\u2026}} expects a plain object.");
177
+ }
178
+ const out = {};
179
+ for (const k of Object.keys(arg).sort(cmpCodePoints)) {
180
+ out[k] = canonicalizeNode(arg[k], context, `${path}.obj.${k}`);
181
+ }
182
+ return { obj: out };
183
+ }
184
+ if (key === "arr") {
185
+ if (!Array.isArray(arg)) {
186
+ invalid(context, `${path}.arr`, "{arr:[\u2026]} expects an array.");
187
+ }
188
+ return {
189
+ arr: arg.map((el, i) => canonicalizeNode(el, context, `${path}.arr[${i}]`))
190
+ };
191
+ }
192
+ if (!ALL_OPS.has(key)) {
193
+ invalid(
194
+ context,
195
+ path,
196
+ `unknown operator '${key}' \u2014 outside the Expression IR v1 closed set.`
197
+ );
198
+ }
199
+ const op = key;
200
+ if (!Array.isArray(arg)) {
201
+ invalid(context, `${path}.${op}`, `'${op}' expects an argument array.`);
202
+ }
203
+ const arity = UNARY_OPS.has(op) ? 1 : TERNARY_OPS.has(op) ? 3 : 2;
204
+ if (arg.length !== arity) {
205
+ invalid(
206
+ context,
207
+ `${path}.${op}`,
208
+ `'${op}' expects ${arity} argument(s), got ${arg.length}.`
209
+ );
210
+ }
211
+ const args = arg.map((a, i) => canonicalizeNode(a, context, `${path}.${op}[${i}]`));
212
+ return { [op]: args };
213
+ }
214
+ function canonicalizeExpressionSpec(input, context) {
215
+ if (!isPlainObject(input)) {
216
+ throw new Error(
217
+ `${context}: an SCP expression must be a plain \`{ exprVersion: 1, expr: <node> }\` object.`
218
+ );
219
+ }
220
+ const keys = Object.keys(input).sort();
221
+ if (keys.length !== 2 || keys[0] !== "expr" || keys[1] !== "exprVersion") {
222
+ throw new Error(
223
+ `${context}: an SCP expression envelope must have exactly the keys \`exprVersion\` and \`expr\`; got [${Object.keys(input).join(", ")}].`
224
+ );
225
+ }
226
+ if (input.exprVersion !== EXPR_VERSION) {
227
+ throw new Error(
228
+ `${context}: unsupported exprVersion ${JSON.stringify(input.exprVersion)} \u2014 this build understands exprVersion ${EXPR_VERSION} only (fail-closed).`
229
+ );
230
+ }
231
+ return {
232
+ exprVersion: EXPR_VERSION,
233
+ expr: canonicalizeNode(input.expr, context, "$")
234
+ };
235
+ }
236
+ function isScpExprConditionInput(condition) {
237
+ return "scpExpr" in condition;
238
+ }
239
+ function conditionHasScp(condition) {
240
+ return condition?.kind === "scpExpr";
241
+ }
242
+ function operationsContainScpNodes(doc) {
243
+ for (const command of Object.values(doc.commands)) {
244
+ if (conditionHasScp(command.condition)) return true;
245
+ }
246
+ for (const transaction of Object.values(doc.transactions ?? {})) {
247
+ for (const item of transaction.items) {
248
+ if (item.guard !== void 0 || conditionHasScp(item.condition)) return true;
249
+ }
250
+ }
251
+ for (const contract of Object.values(doc.contracts ?? {})) {
252
+ if (contract.kind !== "command") continue;
253
+ for (const method of Object.values(contract.methods)) {
254
+ if (method.guard !== void 0) return true;
255
+ }
256
+ }
257
+ return false;
258
+ }
259
+ function operationsSpecVersion(doc) {
260
+ return operationsContainScpNodes(doc) ? SPEC_VERSION_SCP : SPEC_VERSION;
261
+ }
262
+
263
+ export {
264
+ SPEC_VERSION,
265
+ SPEC_VERSION_SCP,
266
+ SPEC_VERSION_SUPPORTED,
267
+ EXPR_VERSION,
268
+ MARKER_ROW_ENTITY,
269
+ isTransactionRef,
270
+ when,
271
+ isTransactionDefinition,
272
+ isMultiWriteBody,
273
+ SCP_LOWERED_MARKER,
274
+ canonicalizeExpressionSpec,
275
+ isScpExprConditionInput,
276
+ operationsContainScpNodes,
277
+ operationsSpecVersion
278
+ };
@@ -0,0 +1,187 @@
1
+ import {
2
+ evaluateKey
3
+ } from "./chunk-I4LEJ4TF.js";
4
+ import {
5
+ SPEC_VERSION
6
+ } from "./chunk-L4QRCHRQ.js";
7
+ import {
8
+ MetadataRegistry
9
+ } from "./chunk-L2NEDS7U.js";
10
+ import {
11
+ TableMapping
12
+ } from "./chunk-XTWXMOHD.js";
13
+
14
+ // src/spec/manifest.ts
15
+ var DYNAMO_TYPE_TO_FIELD_TYPE = {
16
+ S: "string",
17
+ N: "number",
18
+ BOOL: "boolean",
19
+ B: "binary",
20
+ SS: "stringSet",
21
+ NS: "numberSet",
22
+ L: "list",
23
+ M: "map"
24
+ };
25
+ function sortedRecord(record) {
26
+ const out = {};
27
+ for (const key of Object.keys(record).sort()) {
28
+ out[key] = record[key];
29
+ }
30
+ return out;
31
+ }
32
+ function buildKey(key) {
33
+ if (!key) return null;
34
+ const { pk, sk } = evaluateKey(key.segmented, "param");
35
+ return {
36
+ inputFields: [...key.inputFieldNames],
37
+ pkTemplate: pk,
38
+ skTemplate: sk ?? null
39
+ };
40
+ }
41
+ function buildGsis(metadata) {
42
+ return [...metadata.gsiDefinitions].sort((a, b) => a.indexName.localeCompare(b.indexName)).map((gsi) => {
43
+ const { pk, sk } = evaluateKey(gsi.segmented, "param");
44
+ return {
45
+ indexName: gsi.indexName,
46
+ unique: gsi.unique,
47
+ inputFields: [...gsi.inputFieldNames],
48
+ pkTemplate: pk,
49
+ skTemplate: sk ?? null,
50
+ // Purely-documentary GSI description (issue #166); emitted only when declared
51
+ // so an undescribed index is byte-identical to the pre-#166 manifest.
52
+ ...gsi.description !== void 0 ? { description: gsi.description } : {}
53
+ };
54
+ });
55
+ }
56
+ function buildRelations(metadata) {
57
+ const relations = {};
58
+ for (const rel of metadata.relations) {
59
+ const targetClass = rel.targetFactory();
60
+ relations[rel.propertyName] = {
61
+ type: rel.type,
62
+ target: targetClass.name,
63
+ keyBinding: { ...rel.keyBinding },
64
+ // List-valued source descriptor for a `refs` relation (issue #197); emitted
65
+ // only for `refs` so a scalar-keyed relation is byte-identical to the pre-#197
66
+ // manifest.
67
+ ...rel.refs !== void 0 ? { refs: { ...rel.refs } } : {},
68
+ // Purely-documentary relation description (issue #166); emitted only when
69
+ // declared so an undescribed relation is byte-identical to the pre-#166 manifest.
70
+ ...rel.options?.description !== void 0 ? { description: rel.options.description } : {}
71
+ };
72
+ }
73
+ return sortedRecord(relations);
74
+ }
75
+ function buildFields(metadata) {
76
+ const fields = {};
77
+ for (const field of metadata.fields) {
78
+ const format = field.options?.format;
79
+ const description = field.options?.description;
80
+ fields[field.propertyName] = {
81
+ type: DYNAMO_TYPE_TO_FIELD_TYPE[field.dynamoType],
82
+ ...format ? { format } : {},
83
+ ...description !== void 0 ? { description } : {}
84
+ };
85
+ }
86
+ return sortedRecord(fields);
87
+ }
88
+ function buildManifestEntity(metadata) {
89
+ return buildEntity(metadata);
90
+ }
91
+ function buildEntity(metadata) {
92
+ return {
93
+ table: metadata.tableName,
94
+ physicalName: TableMapping.resolve(metadata.tableName),
95
+ prefix: metadata.prefix,
96
+ fields: buildFields(metadata),
97
+ key: buildKey(metadata.primaryKey),
98
+ gsis: buildGsis(metadata),
99
+ relations: buildRelations(metadata),
100
+ ...metadata.description !== void 0 ? { description: metadata.description } : {},
101
+ // TTL (issue #172, Epic #167 — C4): a physical-schema fact carried on the
102
+ // manifest so the CFn emitter can render `TimeToLiveSpecification`. Absent
103
+ // when no `@ttl` field is declared (byte-identical to the pre-#172 manifest).
104
+ ...metadata.ttlAttribute !== void 0 ? { ttlAttribute: metadata.ttlAttribute } : {}
105
+ };
106
+ }
107
+ function buildManifest(registry = MetadataRegistry) {
108
+ const all = registry.getAll();
109
+ const entitiesByName = /* @__PURE__ */ new Map();
110
+ for (const [cls, metadata] of all) {
111
+ entitiesByName.set(cls.name, metadata);
112
+ }
113
+ const entities = {};
114
+ const tables = {};
115
+ const ttlByPhysicalTable = /* @__PURE__ */ new Map();
116
+ for (const name of [...entitiesByName.keys()].sort()) {
117
+ const metadata = entitiesByName.get(name);
118
+ entities[name] = buildEntity(metadata);
119
+ const physicalName = TableMapping.resolve(metadata.tableName);
120
+ tables[metadata.tableName] = {
121
+ physicalName
122
+ };
123
+ if (metadata.ttlAttribute !== void 0) {
124
+ const existing = ttlByPhysicalTable.get(physicalName);
125
+ if (existing && existing.attr !== metadata.ttlAttribute) {
126
+ throw new Error(
127
+ `Manifest build: physical table '${physicalName}' has more than one TTL attribute \u2014 entity '${existing.entity}' declares \`@ttl\` on '${existing.attr}' and entity '${name}' on '${metadata.ttlAttribute}'. DynamoDB allows exactly one TTL attribute per table; align the \`@ttl\` attribute across all entities sharing this table (or drop one).`
128
+ );
129
+ }
130
+ if (!existing) {
131
+ ttlByPhysicalTable.set(physicalName, {
132
+ entity: name,
133
+ attr: metadata.ttlAttribute
134
+ });
135
+ }
136
+ }
137
+ }
138
+ return {
139
+ version: SPEC_VERSION,
140
+ tables: sortedRecord(tables),
141
+ entities
142
+ };
143
+ }
144
+
145
+ // src/spec/prepared-artifact.ts
146
+ var PREPARED_FORMAT_VERSION = "2";
147
+ function canonicalJson(value) {
148
+ return JSON.stringify(value, (_k, v) => {
149
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) {
150
+ const sorted = {};
151
+ for (const key of Object.keys(v).sort()) {
152
+ sorted[key] = v[key];
153
+ }
154
+ return sorted;
155
+ }
156
+ return v;
157
+ });
158
+ }
159
+ var FNV64_OFFSET_BASIS = 0xcbf29ce484222325n;
160
+ var FNV64_PRIME = 0x100000001b3n;
161
+ var U64_MASK = 0xffffffffffffffffn;
162
+ function fnv1a64Hex(input) {
163
+ let hash = FNV64_OFFSET_BASIS;
164
+ for (let i = 0; i < input.length; i++) {
165
+ const unit = input.charCodeAt(i);
166
+ hash ^= BigInt(unit & 255);
167
+ hash = hash * FNV64_PRIME & U64_MASK;
168
+ hash ^= BigInt(unit >>> 8);
169
+ hash = hash * FNV64_PRIME & U64_MASK;
170
+ }
171
+ return hash.toString(16).padStart(16, "0");
172
+ }
173
+ function entityFingerprint(metadata) {
174
+ return fnv1a64Hex(canonicalJson(buildManifestEntity(metadata)));
175
+ }
176
+ function planFingerprint(plan) {
177
+ return fnv1a64Hex(canonicalJson(plan));
178
+ }
179
+
180
+ export {
181
+ buildManifestEntity,
182
+ buildManifest,
183
+ PREPARED_FORMAT_VERSION,
184
+ canonicalJson,
185
+ entityFingerprint,
186
+ planFingerprint
187
+ };