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.
- package/README.md +6 -6
- package/dist/cdc/index.d.ts +389 -4
- package/dist/cdc/index.js +4 -3
- package/dist/{chunk-ZPNRLOKA.js → chunk-GS4C5VGO.js} +4 -6
- package/dist/chunk-HNY2EJPV.js +1184 -0
- package/dist/{chunk-NYM7K2ST.js → chunk-I4LEJ4TF.js} +3812 -6724
- package/dist/{chunk-PFFPLD4B.js → chunk-L2NEDS7U.js} +725 -2112
- package/dist/chunk-L4QRCHRQ.js +278 -0
- package/dist/chunk-LGHSZIEE.js +187 -0
- package/dist/chunk-N4NWYNGZ.js +1987 -0
- package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
- package/dist/cli.js +63 -252
- package/dist/index.d.ts +23 -1548
- package/dist/index.js +100 -1778
- package/dist/internal/index.d.ts +84 -0
- package/dist/internal/index.js +701 -0
- package/dist/{maintenance-view-adapter-BATUh_I8.d.ts → key-DR7_lpyk.d.ts} +538 -2975
- package/dist/linter/index.d.ts +39 -6
- package/dist/linter/index.js +22 -4
- package/dist/{registry-CXhP4TaE.d.ts → linter-C-vypgut.d.ts} +22 -22
- package/dist/prepared-artifact-BpPgkXEo.d.ts +281 -0
- package/dist/spec/index.d.ts +506 -4
- package/dist/spec/index.js +36 -17
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +4 -3
- package/dist/transform/index.d.ts +460 -1
- package/dist/transform/index.js +2085 -2
- package/dist/types-2PMXEn5x.d.ts +1205 -0
- package/dist/types-BXLzIcQD.d.ts +450 -0
- package/docs/cdc-projection.md +5 -5
- package/docs/class-hydration.md +1 -1
- package/docs/cqrs-contract.md +28 -20
- package/docs/design-patterns.md +5 -5
- package/docs/docs-generation.md +6 -6
- package/docs/middleware.md +15 -15
- package/docs/mutation-command-derivation.md +52 -42
- package/docs/prepared-statements.md +14 -14
- package/docs/python-bridge.md +113 -66
- package/docs/spec.md +153 -124
- package/docs/testing.md +9 -8
- package/package.json +20 -5
- package/dist/chunk-MMVHOUM4.js +0 -24
- package/dist/from-change-DanwjE5b.d.ts +0 -327
- package/dist/index-CtPJSMrc.d.ts +0 -934
- 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
|
+
};
|