@asaidimu/anansi 1.1.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/LICENSE.md +21 -0
- package/README.md +104 -0
- package/index.cjs +3209 -0
- package/index.d.cts +1920 -0
- package/index.d.ts +1920 -0
- package/index.js +3170 -0
- package/package.json +57 -0
package/index.cjs
ADDED
|
@@ -0,0 +1,3209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
MigrationTypes: () => migrations_exports,
|
|
34
|
+
PersistenceTypes: () => persistence_exports,
|
|
35
|
+
RegistryType: () => registry_exports,
|
|
36
|
+
SchemaTypes: () => schema_definition_exports,
|
|
37
|
+
crypto: () => crypto_exports,
|
|
38
|
+
merge: () => merge_exports,
|
|
39
|
+
migration: () => migration_exports,
|
|
40
|
+
patch: () => patch_exports,
|
|
41
|
+
persistence: () => persistence_exports2,
|
|
42
|
+
registry: () => registry_exports2,
|
|
43
|
+
schema: () => schema_exports,
|
|
44
|
+
typegen: () => typegen_exports,
|
|
45
|
+
validator: () => validator_exports,
|
|
46
|
+
version: () => version_exports
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
|
|
50
|
+
// src/types/migrations.ts
|
|
51
|
+
var migrations_exports = {};
|
|
52
|
+
|
|
53
|
+
// src/types/persistence.ts
|
|
54
|
+
var persistence_exports = {};
|
|
55
|
+
|
|
56
|
+
// src/types/registry.ts
|
|
57
|
+
var registry_exports = {};
|
|
58
|
+
|
|
59
|
+
// src/types/schema-definition.ts
|
|
60
|
+
var schema_definition_exports = {};
|
|
61
|
+
|
|
62
|
+
// src/lib/persistence/index.ts
|
|
63
|
+
var persistence_exports2 = {};
|
|
64
|
+
__export(persistence_exports2, {
|
|
65
|
+
default: () => createEphemeralPersistence
|
|
66
|
+
});
|
|
67
|
+
var import_events2 = require("@asaidimu/events");
|
|
68
|
+
|
|
69
|
+
// src/lib/persistence/collection.ts
|
|
70
|
+
var import_events = require("@asaidimu/events");
|
|
71
|
+
var import_query = require("@asaidimu/query");
|
|
72
|
+
|
|
73
|
+
// src/tools/patch.ts
|
|
74
|
+
var patch_exports = {};
|
|
75
|
+
__export(patch_exports, {
|
|
76
|
+
JsonPatchError: () => JsonPatchError,
|
|
77
|
+
applyPatch: () => applyPatch,
|
|
78
|
+
createPatch: () => createPatch,
|
|
79
|
+
normalizePath: () => normalizePath,
|
|
80
|
+
schemaChangeToPatch: () => schemaChangeToPatch
|
|
81
|
+
});
|
|
82
|
+
var JsonPatchError = class extends Error {
|
|
83
|
+
constructor(message, operation) {
|
|
84
|
+
super(message);
|
|
85
|
+
this.operation = operation;
|
|
86
|
+
this.name = "JsonPatchError";
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
function parseJsonPointer(path) {
|
|
90
|
+
const normalized = normalizePath(path);
|
|
91
|
+
if (normalized === "") return [];
|
|
92
|
+
return normalized.substring(1).split("/").map(unescapeJsonPointer);
|
|
93
|
+
}
|
|
94
|
+
function normalizePath(path) {
|
|
95
|
+
if (path === "" || path === "/") return "";
|
|
96
|
+
if (path.startsWith("/")) {
|
|
97
|
+
return "/" + path.substring(1).split("/").map(escapeJsonPointer).join("/");
|
|
98
|
+
}
|
|
99
|
+
return "/" + path.split(".").map(escapeJsonPointer).join("/");
|
|
100
|
+
}
|
|
101
|
+
function escapeJsonPointer(part) {
|
|
102
|
+
return part.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
103
|
+
}
|
|
104
|
+
function unescapeJsonPointer(part) {
|
|
105
|
+
return part.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
106
|
+
}
|
|
107
|
+
var pathCache = /* @__PURE__ */ new Map();
|
|
108
|
+
function navigateTo(obj, parts) {
|
|
109
|
+
let current = obj;
|
|
110
|
+
for (const part of parts) {
|
|
111
|
+
if (current === null || typeof current !== "object") {
|
|
112
|
+
throw new JsonPatchError(`Invalid path - parent not found at ${part}`);
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(current)) {
|
|
115
|
+
const index = part === "-" ? current.length : parseInt(part);
|
|
116
|
+
if (isNaN(index) || index < 0 || index > current.length) {
|
|
117
|
+
throw new JsonPatchError(`Invalid array index: ${part}`);
|
|
118
|
+
}
|
|
119
|
+
current = current[index];
|
|
120
|
+
} else {
|
|
121
|
+
if (!current.hasOwnProperty(part)) {
|
|
122
|
+
throw new JsonPatchError(`Property ${part} not found`);
|
|
123
|
+
}
|
|
124
|
+
current = current[part];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return current;
|
|
128
|
+
}
|
|
129
|
+
function getValueAtPath(obj, path) {
|
|
130
|
+
const parts = pathCache.get(path) || parseJsonPointer(path);
|
|
131
|
+
pathCache.set(path, parts);
|
|
132
|
+
if (parts.length === 0) return obj;
|
|
133
|
+
const parent = navigateTo(obj, parts.slice(0, -1));
|
|
134
|
+
const key = parts[parts.length - 1];
|
|
135
|
+
if (Array.isArray(parent)) {
|
|
136
|
+
const index = parseInt(key);
|
|
137
|
+
if (isNaN(index) || index < 0 || index >= parent.length) {
|
|
138
|
+
throw new JsonPatchError(`Invalid array index: ${key}`);
|
|
139
|
+
}
|
|
140
|
+
return parent[index];
|
|
141
|
+
}
|
|
142
|
+
return parent[key];
|
|
143
|
+
}
|
|
144
|
+
function applyRemoveValue(obj, path, value) {
|
|
145
|
+
const parts = pathCache.get(path) || parseJsonPointer(path);
|
|
146
|
+
pathCache.set(path, parts);
|
|
147
|
+
const parent = navigateTo(obj, parts.slice(0, -1));
|
|
148
|
+
const key = parts[parts.length - 1];
|
|
149
|
+
if (Array.isArray(parent)) {
|
|
150
|
+
parent.splice(0, parent.length, ...parent.filter((item) => item !== value));
|
|
151
|
+
} else {
|
|
152
|
+
if (parent[key] === value) {
|
|
153
|
+
delete parent[key];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return obj;
|
|
157
|
+
}
|
|
158
|
+
function applyAdd(obj, path, value) {
|
|
159
|
+
const parts = pathCache.get(path) || parseJsonPointer(path);
|
|
160
|
+
pathCache.set(path, parts);
|
|
161
|
+
if (parts.length === 0) return value;
|
|
162
|
+
const parentPath = parts.slice(0, -1);
|
|
163
|
+
const key = parts[parts.length - 1];
|
|
164
|
+
const parent = navigateTo(obj, parentPath);
|
|
165
|
+
if (Array.isArray(parent)) {
|
|
166
|
+
if (key === "-") {
|
|
167
|
+
parent.push(value);
|
|
168
|
+
} else {
|
|
169
|
+
const index = parseInt(key);
|
|
170
|
+
if (index < 0 || index > parent.length) {
|
|
171
|
+
throw new JsonPatchError(`Invalid array index: ${key}`);
|
|
172
|
+
}
|
|
173
|
+
parent.splice(index, 0, value);
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
parent[key] = value;
|
|
177
|
+
}
|
|
178
|
+
return obj;
|
|
179
|
+
}
|
|
180
|
+
function applyRemove(obj, path) {
|
|
181
|
+
const parts = pathCache.get(path) || parseJsonPointer(path);
|
|
182
|
+
pathCache.set(path, parts);
|
|
183
|
+
if (parts.length === 0) return void 0;
|
|
184
|
+
const parent = navigateTo(obj, parts.slice(0, -1));
|
|
185
|
+
const key = parts[parts.length - 1];
|
|
186
|
+
if (Array.isArray(parent)) {
|
|
187
|
+
const index = parseInt(key);
|
|
188
|
+
parent.splice(index, 1);
|
|
189
|
+
} else {
|
|
190
|
+
delete parent[key];
|
|
191
|
+
}
|
|
192
|
+
return obj;
|
|
193
|
+
}
|
|
194
|
+
function applyPatch(target, patches) {
|
|
195
|
+
let result = JSON.parse(JSON.stringify(target));
|
|
196
|
+
for (const patch of patches) {
|
|
197
|
+
try {
|
|
198
|
+
switch (patch.op) {
|
|
199
|
+
case "add":
|
|
200
|
+
result = applyAdd(result, patch.path, patch.value);
|
|
201
|
+
break;
|
|
202
|
+
case "remove":
|
|
203
|
+
result = applyRemove(result, patch.path);
|
|
204
|
+
break;
|
|
205
|
+
case "removeValue":
|
|
206
|
+
result = applyRemoveValue(result, patch.path, patch.value);
|
|
207
|
+
break;
|
|
208
|
+
case "replace":
|
|
209
|
+
result = applyAdd(
|
|
210
|
+
applyRemove(result, patch.path),
|
|
211
|
+
patch.path,
|
|
212
|
+
patch.value
|
|
213
|
+
);
|
|
214
|
+
break;
|
|
215
|
+
case "copy": {
|
|
216
|
+
const value = getValueAtPath(result, patch.from);
|
|
217
|
+
result = applyAdd(
|
|
218
|
+
result,
|
|
219
|
+
patch.path,
|
|
220
|
+
JSON.parse(JSON.stringify(value))
|
|
221
|
+
);
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case "move": {
|
|
225
|
+
const value = getValueAtPath(result, patch.from);
|
|
226
|
+
result = applyAdd(result, patch.path, value);
|
|
227
|
+
result = applyRemove(result, patch.from);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case "test": {
|
|
231
|
+
const actual = getValueAtPath(result, patch.path);
|
|
232
|
+
if (JSON.stringify(actual) !== JSON.stringify(patch.value)) {
|
|
233
|
+
throw new JsonPatchError("Test operation failed");
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
default:
|
|
238
|
+
throw new JsonPatchError(
|
|
239
|
+
`Unsupported operation: ${patch.op}`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (error instanceof JsonPatchError) {
|
|
244
|
+
error.operation = patch;
|
|
245
|
+
}
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
function createPatch(oldObj, newObj) {
|
|
252
|
+
const patches = [];
|
|
253
|
+
generatePatches(oldObj, newObj, "", patches);
|
|
254
|
+
return patches;
|
|
255
|
+
}
|
|
256
|
+
function generatePatches(oldObj, newObj, path, patches) {
|
|
257
|
+
if (oldObj === newObj) return;
|
|
258
|
+
if (typeof oldObj !== typeof newObj || Array.isArray(oldObj) !== Array.isArray(newObj)) {
|
|
259
|
+
patches.push({ op: "replace", path, value: newObj });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (typeof oldObj === "object" && oldObj !== null) {
|
|
263
|
+
if (Array.isArray(oldObj)) {
|
|
264
|
+
handleArrays(oldObj, newObj, path, patches);
|
|
265
|
+
} else {
|
|
266
|
+
handleObjects(oldObj, newObj, path, patches);
|
|
267
|
+
}
|
|
268
|
+
} else if (oldObj !== newObj) {
|
|
269
|
+
patches.push({ op: "replace", path, value: newObj });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function handleArrays(oldArr, newArr, path, patches) {
|
|
273
|
+
const maxLen = Math.max(oldArr.length, newArr.length);
|
|
274
|
+
for (let i = 0; i < maxLen; i++) {
|
|
275
|
+
const currentPath = `${path}/${i}`;
|
|
276
|
+
if (i >= oldArr.length) {
|
|
277
|
+
patches.push({ op: "add", path: `${path}/-`, value: newArr[i] });
|
|
278
|
+
} else if (i >= newArr.length) {
|
|
279
|
+
patches.push({ op: "remove", path: currentPath });
|
|
280
|
+
} else {
|
|
281
|
+
generatePatches(oldArr[i], newArr[i], currentPath, patches);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function handleObjects(oldObj, newObj, path, patches) {
|
|
286
|
+
const seen = /* @__PURE__ */ new Set();
|
|
287
|
+
const oldKeys = Object.keys(oldObj);
|
|
288
|
+
const newKeys = Object.keys(newObj);
|
|
289
|
+
for (const key of oldKeys) {
|
|
290
|
+
const escapedKey = escapeJsonPointer(key);
|
|
291
|
+
const currentPath = path ? `${path}/${escapedKey}` : `/${escapedKey}`;
|
|
292
|
+
if (!newObj.hasOwnProperty(key)) {
|
|
293
|
+
patches.push({ op: "remove", path: currentPath });
|
|
294
|
+
} else {
|
|
295
|
+
generatePatches(
|
|
296
|
+
oldObj[key],
|
|
297
|
+
newObj[key],
|
|
298
|
+
currentPath,
|
|
299
|
+
patches
|
|
300
|
+
);
|
|
301
|
+
seen.add(key);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
for (const key of newKeys) {
|
|
305
|
+
if (!seen.has(key)) {
|
|
306
|
+
const escapedKey = escapeJsonPointer(key);
|
|
307
|
+
const currentPath = path ? `${path}/${escapedKey}` : `/${escapedKey}`;
|
|
308
|
+
patches.push({
|
|
309
|
+
op: "add",
|
|
310
|
+
path: currentPath,
|
|
311
|
+
value: newObj[key]
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function schemaChangeToPatch(change, schema) {
|
|
317
|
+
const patches = [];
|
|
318
|
+
switch (change.type) {
|
|
319
|
+
case "addField":
|
|
320
|
+
patches.push({
|
|
321
|
+
op: "add",
|
|
322
|
+
path: `/fields/${change.name}`,
|
|
323
|
+
value: change.definition
|
|
324
|
+
});
|
|
325
|
+
break;
|
|
326
|
+
case "removeField":
|
|
327
|
+
patches.push({
|
|
328
|
+
op: "remove",
|
|
329
|
+
path: `/fields/${change.name}`
|
|
330
|
+
});
|
|
331
|
+
break;
|
|
332
|
+
case "modifyField": {
|
|
333
|
+
const fieldPath = `/fields/${change.name}`;
|
|
334
|
+
Object.entries(change.changes).forEach(([key, value]) => {
|
|
335
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
336
|
+
patches.push({
|
|
337
|
+
op: "replace",
|
|
338
|
+
path: `${fieldPath}/${key}`,
|
|
339
|
+
value
|
|
340
|
+
});
|
|
341
|
+
} else {
|
|
342
|
+
patches.push({
|
|
343
|
+
op: "replace",
|
|
344
|
+
path: `${fieldPath}/${key}`,
|
|
345
|
+
value
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
case "deprecateField":
|
|
352
|
+
patches.push({
|
|
353
|
+
op: "add",
|
|
354
|
+
path: `/fields/${change.name}/deprecated`,
|
|
355
|
+
value: true
|
|
356
|
+
});
|
|
357
|
+
break;
|
|
358
|
+
case "addIndex":
|
|
359
|
+
if (!schema.indexes) {
|
|
360
|
+
patches.push({
|
|
361
|
+
op: "add",
|
|
362
|
+
path: "/indexes",
|
|
363
|
+
value: []
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
patches.push({
|
|
367
|
+
op: "add",
|
|
368
|
+
path: "/indexes/-",
|
|
369
|
+
value: change.definition
|
|
370
|
+
});
|
|
371
|
+
break;
|
|
372
|
+
case "removeIndex": {
|
|
373
|
+
const indexIndex = schema.indexes?.findIndex(
|
|
374
|
+
(idx) => idx.name === change.name
|
|
375
|
+
);
|
|
376
|
+
if (indexIndex !== void 0 && indexIndex >= 0) {
|
|
377
|
+
patches.push({
|
|
378
|
+
op: "remove",
|
|
379
|
+
path: `/indexes/${indexIndex}`
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case "modifyIndex": {
|
|
385
|
+
const indexIndex = schema.indexes?.findIndex(
|
|
386
|
+
(idx) => idx.name === change.name
|
|
387
|
+
);
|
|
388
|
+
if (indexIndex !== void 0 && indexIndex >= 0) {
|
|
389
|
+
Object.entries(change.changes).forEach(([key, value]) => {
|
|
390
|
+
patches.push({
|
|
391
|
+
op: "replace",
|
|
392
|
+
path: `/indexes/${indexIndex}/${key}`,
|
|
393
|
+
value
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
case "addConstraint":
|
|
400
|
+
if (!schema.constraints) {
|
|
401
|
+
patches.push({
|
|
402
|
+
op: "add",
|
|
403
|
+
path: "/constraints",
|
|
404
|
+
value: []
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
if (Array.isArray(change.constraint)) {
|
|
408
|
+
change.constraint.forEach((constraint) => {
|
|
409
|
+
patches.push({
|
|
410
|
+
op: "add",
|
|
411
|
+
path: "/constraints/-",
|
|
412
|
+
value: constraint
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
} else {
|
|
416
|
+
patches.push({
|
|
417
|
+
op: "add",
|
|
418
|
+
path: "/constraints/-",
|
|
419
|
+
value: change.constraint
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
break;
|
|
423
|
+
case "removeConstraint": {
|
|
424
|
+
const constraintIndex = schema.constraints?.findIndex(
|
|
425
|
+
(c) => Array.isArray(c) ? c.some((rule) => rule.name === change.name) : c.name === change.name
|
|
426
|
+
);
|
|
427
|
+
if (constraintIndex !== void 0 && constraintIndex >= 0) {
|
|
428
|
+
patches.push({
|
|
429
|
+
op: "remove",
|
|
430
|
+
path: `/constraints/${constraintIndex}`
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
case "modifyConstraint": {
|
|
436
|
+
const constraintPath = findConstraintPath(schema, change.name);
|
|
437
|
+
if (constraintPath) {
|
|
438
|
+
Object.entries(change.changes).forEach(([key, value]) => {
|
|
439
|
+
patches.push({
|
|
440
|
+
op: "replace",
|
|
441
|
+
path: `${constraintPath}/${key}`,
|
|
442
|
+
value
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return patches;
|
|
450
|
+
}
|
|
451
|
+
function findConstraintPath(schema, name) {
|
|
452
|
+
if (!schema.constraints) return null;
|
|
453
|
+
for (let i = 0; i < schema.constraints.length; i++) {
|
|
454
|
+
const constraint = schema.constraints[i];
|
|
455
|
+
if (constraint.name === name) {
|
|
456
|
+
return `/constraints/${i}`;
|
|
457
|
+
}
|
|
458
|
+
if (isConstraintGroup(constraint)) {
|
|
459
|
+
const path = searchRules(constraint.rules, name);
|
|
460
|
+
if (path) {
|
|
461
|
+
return `/constraints/${i}${path}`;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
function isConstraintGroup(obj) {
|
|
468
|
+
return obj && "operator" in obj && "rules" in obj;
|
|
469
|
+
}
|
|
470
|
+
function searchRules(rules, name) {
|
|
471
|
+
for (let i = 0; i < rules.length; i++) {
|
|
472
|
+
const rule = rules[i];
|
|
473
|
+
if ("name" in rule && rule.name === name) {
|
|
474
|
+
return `/rules/${i}`;
|
|
475
|
+
}
|
|
476
|
+
if (isConstraintGroup(rule)) {
|
|
477
|
+
const path = searchRules(rule.rules, name);
|
|
478
|
+
if (path) {
|
|
479
|
+
return `/rules/${i}${path}`;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/tools/merge.ts
|
|
487
|
+
var merge_exports = {};
|
|
488
|
+
__export(merge_exports, {
|
|
489
|
+
deepMerge: () => deepMerge
|
|
490
|
+
});
|
|
491
|
+
function deepMerge(target, update) {
|
|
492
|
+
const output = { ...target };
|
|
493
|
+
if (isObject(target) && isObject(update)) {
|
|
494
|
+
Object.keys(update).forEach((key) => {
|
|
495
|
+
if (isObject(update[key])) {
|
|
496
|
+
if (!(key in target)) {
|
|
497
|
+
Object.assign(output, { [key]: update[key] });
|
|
498
|
+
} else {
|
|
499
|
+
output[key] = deepMerge(
|
|
500
|
+
target[key],
|
|
501
|
+
update[key]
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
} else {
|
|
505
|
+
Object.assign(output, { [key]: update[key] });
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
return output;
|
|
510
|
+
}
|
|
511
|
+
function isObject(item) {
|
|
512
|
+
return item && typeof item === "object" && !Array.isArray(item);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// src/tools/validator.ts
|
|
516
|
+
var validator_exports = {};
|
|
517
|
+
__export(validator_exports, {
|
|
518
|
+
createStandardSchemaValidator: () => createStandardSchemaValidator
|
|
519
|
+
});
|
|
520
|
+
function createStandardSchemaValidator(schema, constraintsMap) {
|
|
521
|
+
const validateTypeWithErrors = (value, fieldName, fieldDef, path) => {
|
|
522
|
+
const issues = [];
|
|
523
|
+
switch (fieldDef.type) {
|
|
524
|
+
case "string":
|
|
525
|
+
if (typeof value !== "string") {
|
|
526
|
+
issues.push({
|
|
527
|
+
message: `Expected type string but received ${typeof value}.`,
|
|
528
|
+
path
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
break;
|
|
532
|
+
case "number":
|
|
533
|
+
if (typeof value !== "number") {
|
|
534
|
+
issues.push({
|
|
535
|
+
message: `Expected type number but received ${typeof value}.`,
|
|
536
|
+
path
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
break;
|
|
540
|
+
case "boolean":
|
|
541
|
+
if (typeof value !== "boolean") {
|
|
542
|
+
issues.push({
|
|
543
|
+
message: `Expected type boolean but received ${typeof value}.`,
|
|
544
|
+
path
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
break;
|
|
548
|
+
case "array":
|
|
549
|
+
if (!Array.isArray(value)) {
|
|
550
|
+
issues.push({
|
|
551
|
+
message: `Expected an array but received ${typeof value}.`,
|
|
552
|
+
path
|
|
553
|
+
});
|
|
554
|
+
} else if (!fieldDef.itemsType) {
|
|
555
|
+
issues.push({
|
|
556
|
+
message: `Expected itemsType for array ${fieldName}`,
|
|
557
|
+
path
|
|
558
|
+
});
|
|
559
|
+
} else {
|
|
560
|
+
value.forEach((item, index) => {
|
|
561
|
+
issues.push(
|
|
562
|
+
...validateTypeWithErrors(
|
|
563
|
+
item,
|
|
564
|
+
`Array: ${fieldName}`,
|
|
565
|
+
{
|
|
566
|
+
type: fieldDef.itemsType,
|
|
567
|
+
nestedSchema: fieldDef.nestedSchema
|
|
568
|
+
},
|
|
569
|
+
[...path, index]
|
|
570
|
+
)
|
|
571
|
+
);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
break;
|
|
575
|
+
case "object":
|
|
576
|
+
if (typeof value !== "object" || value === null) {
|
|
577
|
+
issues.push({
|
|
578
|
+
message: `Expected an object but received ${value === null ? "null" : typeof value}.`,
|
|
579
|
+
path
|
|
580
|
+
});
|
|
581
|
+
} else if (fieldDef.nestedSchema) {
|
|
582
|
+
const nestedSchema = {
|
|
583
|
+
name: fieldDef.description ? `${fieldDef.description}-schema` : "nested-schema",
|
|
584
|
+
version: "1.0",
|
|
585
|
+
fields: fieldDef.nestedSchema
|
|
586
|
+
};
|
|
587
|
+
issues.push(
|
|
588
|
+
...validateData(
|
|
589
|
+
nestedSchema,
|
|
590
|
+
value,
|
|
591
|
+
path
|
|
592
|
+
)
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
break;
|
|
596
|
+
case "dynamic":
|
|
597
|
+
break;
|
|
598
|
+
default:
|
|
599
|
+
issues.push({ message: `Unknown field type: ${fieldDef.type}`, path });
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
602
|
+
return issues;
|
|
603
|
+
};
|
|
604
|
+
const validateFieldConstraints = (fieldName, fieldDef, data, path) => {
|
|
605
|
+
const issues = [];
|
|
606
|
+
if (!fieldDef.constraints) return issues;
|
|
607
|
+
fieldDef.constraints.forEach((constraint) => {
|
|
608
|
+
const predicate = constraintsMap[constraint.predicate];
|
|
609
|
+
if (!predicate) {
|
|
610
|
+
issues.push({
|
|
611
|
+
message: `Missing predicate for constraint: ${constraint.name}`,
|
|
612
|
+
path
|
|
613
|
+
});
|
|
614
|
+
} else {
|
|
615
|
+
const valid = constraint.type === "schema" ? predicate({ data, arguments: constraint.parameters }) : predicate({
|
|
616
|
+
data,
|
|
617
|
+
field: fieldName,
|
|
618
|
+
arguments: constraint.parameters
|
|
619
|
+
});
|
|
620
|
+
if (!valid) {
|
|
621
|
+
issues.push({
|
|
622
|
+
message: `Constraint '${constraint.name}' failed for field '${fieldName}'.`,
|
|
623
|
+
path
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
return issues;
|
|
629
|
+
};
|
|
630
|
+
const validateField = (fieldName, fieldDef, value, data, path) => {
|
|
631
|
+
return [
|
|
632
|
+
...validateTypeWithErrors(value, fieldName, fieldDef, path),
|
|
633
|
+
...validateFieldConstraints(fieldName, fieldDef, data, path)
|
|
634
|
+
];
|
|
635
|
+
};
|
|
636
|
+
const evaluateRuleWithErrors = (rule, data, fieldName) => {
|
|
637
|
+
if ("operator" in rule) {
|
|
638
|
+
return applyLogicalOperator(
|
|
639
|
+
rule.operator,
|
|
640
|
+
rule.rules.map((r) => evaluateRuleWithErrors(r, data, fieldName))
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
const predicate = constraintsMap[rule.predicate];
|
|
644
|
+
if (!predicate) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
return rule.type === "schema" ? predicate({ data, field: rule.field, arguments: rule.parameters }) : predicate({ data, field: fieldName, arguments: rule.parameters });
|
|
648
|
+
};
|
|
649
|
+
const applyLogicalOperator = (operator, results) => {
|
|
650
|
+
switch (operator) {
|
|
651
|
+
case "and":
|
|
652
|
+
return results.every(Boolean);
|
|
653
|
+
case "or":
|
|
654
|
+
return results.some(Boolean);
|
|
655
|
+
case "not":
|
|
656
|
+
return results.length === 1 ? !results[0] : false;
|
|
657
|
+
case "nor":
|
|
658
|
+
return !results.some(Boolean);
|
|
659
|
+
case "xor":
|
|
660
|
+
return results.filter(Boolean).length === 1;
|
|
661
|
+
default:
|
|
662
|
+
console.error(`Unknown logical operator: ${operator}`);
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
const ruleToString = (rule) => {
|
|
667
|
+
if ("operator" in rule) {
|
|
668
|
+
return `(${rule.rules.map(ruleToString).join(` ${rule.operator} `)})`;
|
|
669
|
+
}
|
|
670
|
+
return rule.name;
|
|
671
|
+
};
|
|
672
|
+
const validateData = (schemaToValidate, data, path = []) => {
|
|
673
|
+
const issues = [];
|
|
674
|
+
for (const [fieldName, fieldDef] of Object.entries(schemaToValidate.fields)) {
|
|
675
|
+
if (fieldDef.required && data[fieldName] === void 0) {
|
|
676
|
+
issues.push({
|
|
677
|
+
message: `Field '${fieldName}' is required.`,
|
|
678
|
+
path: [...path, fieldName]
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
for (const [fieldName, fieldDef] of Object.entries(schemaToValidate.fields)) {
|
|
683
|
+
const value = data[fieldName];
|
|
684
|
+
if (value === void 0) continue;
|
|
685
|
+
issues.push(
|
|
686
|
+
...validateField(fieldName, fieldDef, value, data, [...path, fieldName])
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
if (schemaToValidate.constraints) {
|
|
690
|
+
schemaToValidate.constraints.forEach((rule) => {
|
|
691
|
+
if (!evaluateRuleWithErrors(rule, data)) {
|
|
692
|
+
issues.push({
|
|
693
|
+
message: `Schema constraint failed: ${ruleToString(rule)}`,
|
|
694
|
+
path
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
return issues;
|
|
700
|
+
};
|
|
701
|
+
return {
|
|
702
|
+
"~standard": {
|
|
703
|
+
version: 1,
|
|
704
|
+
vendor: "@asaidimu/anansi",
|
|
705
|
+
validate: (value) => {
|
|
706
|
+
if (typeof value !== "object" || value === null) {
|
|
707
|
+
return {
|
|
708
|
+
issues: [{ message: "Value must be a non-null object", path: [] }]
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
const issues = validateData(schema, value);
|
|
712
|
+
if (issues.length === 0) {
|
|
713
|
+
return { value };
|
|
714
|
+
}
|
|
715
|
+
return { issues };
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// src/lib/migration/index.ts
|
|
722
|
+
var migration_exports = {};
|
|
723
|
+
__export(migration_exports, {
|
|
724
|
+
MigrationError: () => MigrationError,
|
|
725
|
+
MigrationErrorCode: () => MigrationErrorCode,
|
|
726
|
+
default: () => MigrationEngine
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
// src/lib/schema/validator.ts
|
|
730
|
+
var import_zod = require("zod");
|
|
731
|
+
|
|
732
|
+
// src/lib/schema/error.ts
|
|
733
|
+
var SchemaValidationError = class extends Error {
|
|
734
|
+
constructor(message, errors) {
|
|
735
|
+
super(message);
|
|
736
|
+
this.errors = errors;
|
|
737
|
+
this.name = "SchemaValidationError";
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
// src/lib/schema/validator.ts
|
|
742
|
+
var LogicalOperatorSchema = import_zod.z.enum(["and", "or", "not", "nor", "xor"]);
|
|
743
|
+
var FieldTypeSchema = import_zod.z.enum(["string", "number", "boolean", "array", "object", "dynamic"]);
|
|
744
|
+
var IndexTypeSchema = import_zod.z.enum(["normal", "unique", "btree", "hash", "spatial", "fulltext", "gi", "expression", "composite"]);
|
|
745
|
+
var ConstraintParametersSchema = import_zod.z.custom(() => {
|
|
746
|
+
return true;
|
|
747
|
+
});
|
|
748
|
+
var ConstraintSchema = import_zod.z.object({
|
|
749
|
+
type: import_zod.z.string().optional(),
|
|
750
|
+
name: import_zod.z.string(),
|
|
751
|
+
predicate: import_zod.z.string().optional(),
|
|
752
|
+
parameters: ConstraintParametersSchema.optional(),
|
|
753
|
+
description: import_zod.z.string().optional(),
|
|
754
|
+
field: import_zod.z.string().optional(),
|
|
755
|
+
errorMessage: import_zod.z.string().optional()
|
|
756
|
+
});
|
|
757
|
+
var ConstraintGroupSchema = import_zod.z.object({
|
|
758
|
+
operator: LogicalOperatorSchema,
|
|
759
|
+
rules: import_zod.z.array(import_zod.z.union([ConstraintSchema, import_zod.z.lazy(() => ConstraintGroupSchema)]))
|
|
760
|
+
});
|
|
761
|
+
var FieldDefinitionSchema = import_zod.z.object({
|
|
762
|
+
type: FieldTypeSchema,
|
|
763
|
+
required: import_zod.z.boolean().optional(),
|
|
764
|
+
constraints: import_zod.z.array(ConstraintSchema).optional(),
|
|
765
|
+
default: import_zod.z.any().optional(),
|
|
766
|
+
itemsType: FieldTypeSchema.optional(),
|
|
767
|
+
nestedSchema: import_zod.z.record(import_zod.z.lazy(() => FieldDefinitionSchema)).optional(),
|
|
768
|
+
deprecated: import_zod.z.boolean().optional(),
|
|
769
|
+
reference: import_zod.z.object({ schema: import_zod.z.string(), field: import_zod.z.string() }).optional(),
|
|
770
|
+
description: import_zod.z.string().optional(),
|
|
771
|
+
unique: import_zod.z.boolean().optional()
|
|
772
|
+
});
|
|
773
|
+
var PartialIndexConditionSchema = import_zod.z.object({
|
|
774
|
+
operator: LogicalOperatorSchema,
|
|
775
|
+
field: import_zod.z.string(),
|
|
776
|
+
value: import_zod.z.any().optional(),
|
|
777
|
+
conditions: import_zod.z.array(import_zod.z.lazy(() => PartialIndexConditionSchema)).optional()
|
|
778
|
+
});
|
|
779
|
+
var IndexDefinitionSchema = import_zod.z.object({
|
|
780
|
+
fields: import_zod.z.array(import_zod.z.string()),
|
|
781
|
+
type: IndexTypeSchema,
|
|
782
|
+
unique: import_zod.z.boolean().optional(),
|
|
783
|
+
partial: PartialIndexConditionSchema.optional(),
|
|
784
|
+
description: import_zod.z.string().optional(),
|
|
785
|
+
order: import_zod.z.enum(["asc", "desc"]).optional(),
|
|
786
|
+
name: import_zod.z.string().optional()
|
|
787
|
+
});
|
|
788
|
+
var SchemaConstraintSchema = import_zod.z.array(import_zod.z.union([ConstraintSchema, ConstraintGroupSchema]));
|
|
789
|
+
var SchemaDefinitionSchema = import_zod.z.object({
|
|
790
|
+
name: import_zod.z.string(),
|
|
791
|
+
version: import_zod.z.string(),
|
|
792
|
+
description: import_zod.z.string().optional(),
|
|
793
|
+
fields: import_zod.z.record(FieldDefinitionSchema),
|
|
794
|
+
indexes: import_zod.z.array(IndexDefinitionSchema).optional(),
|
|
795
|
+
constraints: SchemaConstraintSchema.optional(),
|
|
796
|
+
metadata: import_zod.z.record(import_zod.z.any()).optional(),
|
|
797
|
+
dependencies: import_zod.z.array(import_zod.z.string()).optional(),
|
|
798
|
+
migrations: import_zod.z.array(import_zod.z.any()).optional()
|
|
799
|
+
});
|
|
800
|
+
var SchemaChangeSchema = import_zod.z.union([
|
|
801
|
+
import_zod.z.object({ type: import_zod.z.literal("addField"), name: import_zod.z.string(), definition: FieldDefinitionSchema }),
|
|
802
|
+
import_zod.z.object({ type: import_zod.z.literal("removeField"), name: import_zod.z.string() }),
|
|
803
|
+
import_zod.z.object({ type: import_zod.z.literal("modifyField"), name: import_zod.z.string(), changes: FieldDefinitionSchema.partial() }),
|
|
804
|
+
import_zod.z.object({ type: import_zod.z.literal("addIndex"), definition: IndexDefinitionSchema }),
|
|
805
|
+
import_zod.z.object({ type: import_zod.z.literal("removeIndex"), name: import_zod.z.string() }),
|
|
806
|
+
import_zod.z.object({ type: import_zod.z.literal("modifyIndex"), name: import_zod.z.string(), changes: IndexDefinitionSchema.partial() }),
|
|
807
|
+
import_zod.z.object({ type: import_zod.z.literal("addConstraint"), constraint: import_zod.z.union([ConstraintSchema, ConstraintGroupSchema]) }),
|
|
808
|
+
import_zod.z.object({ type: import_zod.z.literal("removeConstraint"), name: import_zod.z.string() }),
|
|
809
|
+
import_zod.z.object({ type: import_zod.z.literal("modifyConstraint"), name: import_zod.z.string(), changes: ConstraintSchema.partial() }),
|
|
810
|
+
import_zod.z.object({ type: import_zod.z.literal("deprecateField"), name: import_zod.z.string() })
|
|
811
|
+
]);
|
|
812
|
+
var MigrationSchema = import_zod.z.object({
|
|
813
|
+
id: import_zod.z.string(),
|
|
814
|
+
schemaVersion: import_zod.z.string(),
|
|
815
|
+
changes: import_zod.z.array(SchemaChangeSchema),
|
|
816
|
+
description: import_zod.z.string(),
|
|
817
|
+
status: import_zod.z.enum(["pending", "applied", "failed"]),
|
|
818
|
+
rollback: import_zod.z.array(SchemaChangeSchema).optional(),
|
|
819
|
+
transform: import_zod.z.unknown(),
|
|
820
|
+
createdAt: import_zod.z.string(),
|
|
821
|
+
checksum: import_zod.z.string().optional()
|
|
822
|
+
});
|
|
823
|
+
function validateMigration(change) {
|
|
824
|
+
try {
|
|
825
|
+
MigrationSchema.parse(change);
|
|
826
|
+
return true;
|
|
827
|
+
} catch (error) {
|
|
828
|
+
throw new SchemaValidationError("Invalid migration definition", error);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
function validateSchemaChange(change) {
|
|
832
|
+
try {
|
|
833
|
+
SchemaChangeSchema.parse(change);
|
|
834
|
+
return true;
|
|
835
|
+
} catch (error) {
|
|
836
|
+
throw new SchemaValidationError("Invalid schema definition", error);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function validateSchemaDefinition(schema) {
|
|
840
|
+
try {
|
|
841
|
+
SchemaDefinitionSchema.parse(schema);
|
|
842
|
+
return true;
|
|
843
|
+
} catch (error) {
|
|
844
|
+
throw new SchemaValidationError("Invalid schema definition", error);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
var validate = validateSchemaDefinition;
|
|
848
|
+
|
|
849
|
+
// src/tools/crypto.ts
|
|
850
|
+
var crypto_exports = {};
|
|
851
|
+
__export(crypto_exports, {
|
|
852
|
+
generateSHA256Hash: () => generateSHA256Hash
|
|
853
|
+
});
|
|
854
|
+
var generateSHA256Hash = async (input) => {
|
|
855
|
+
if (typeof window !== "undefined" && crypto.subtle) {
|
|
856
|
+
const encoder = new TextEncoder();
|
|
857
|
+
const data = encoder.encode(input);
|
|
858
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
859
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
860
|
+
return hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
861
|
+
} else {
|
|
862
|
+
const { createHash } = await import("crypto");
|
|
863
|
+
return createHash("sha256").update(input).digest("hex");
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
// src/tools/version.ts
|
|
868
|
+
var version_exports = {};
|
|
869
|
+
__export(version_exports, {
|
|
870
|
+
calculateNextVersion: () => calculateNextVersion,
|
|
871
|
+
compareSemanticVersions: () => compareSemanticVersions,
|
|
872
|
+
sortSemanticVars: () => sortSemanticVars
|
|
873
|
+
});
|
|
874
|
+
function parseVersion(version) {
|
|
875
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
876
|
+
if (!match) {
|
|
877
|
+
throw new Error(
|
|
878
|
+
`Invalid version format: ${version}. Expected format: major.minor.patch`
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
return {
|
|
882
|
+
major: parseInt(match[1], 10),
|
|
883
|
+
minor: parseInt(match[2], 10),
|
|
884
|
+
patch: parseInt(match[3], 10)
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
function determineFieldType(constraint, schema) {
|
|
888
|
+
if (schema && "field" in constraint && constraint.field) {
|
|
889
|
+
const fieldDef = schema.fields[constraint.field];
|
|
890
|
+
if (fieldDef) {
|
|
891
|
+
return fieldDef.type;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if ("parameters" in constraint) {
|
|
895
|
+
const params = constraint.parameters;
|
|
896
|
+
if (params instanceof RegExp || Array.isArray(params) && typeof params[0] === "string") {
|
|
897
|
+
return "string";
|
|
898
|
+
}
|
|
899
|
+
if (typeof params === "number" || Array.isArray(params) && typeof params[0] === "number") {
|
|
900
|
+
return "number";
|
|
901
|
+
}
|
|
902
|
+
if (typeof params === "boolean") {
|
|
903
|
+
return "boolean";
|
|
904
|
+
}
|
|
905
|
+
if (typeof params === "object" && params !== null) {
|
|
906
|
+
if ("minItems" in params || "maxItems" in params) {
|
|
907
|
+
return "array";
|
|
908
|
+
}
|
|
909
|
+
if ("schema" in params) {
|
|
910
|
+
return "object";
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return void 0;
|
|
915
|
+
}
|
|
916
|
+
function isBreakingFieldChange(changes) {
|
|
917
|
+
if (changes.required === true) return true;
|
|
918
|
+
if (changes.type !== void 0) return true;
|
|
919
|
+
if (changes.itemsType !== void 0) return true;
|
|
920
|
+
if (changes.nestedSchema !== void 0) return true;
|
|
921
|
+
if (changes.reference !== void 0) return true;
|
|
922
|
+
if (changes.unique === true) return true;
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
function isBreakingConstraintParameters(oldParams, newParams, fieldType) {
|
|
926
|
+
switch (fieldType) {
|
|
927
|
+
case "string":
|
|
928
|
+
if (oldParams instanceof RegExp && newParams instanceof RegExp) {
|
|
929
|
+
return oldParams.source !== newParams.source;
|
|
930
|
+
}
|
|
931
|
+
if (Array.isArray(oldParams) && Array.isArray(newParams)) {
|
|
932
|
+
return newParams.length < oldParams.length || !oldParams.every(
|
|
933
|
+
(val) => newParams.includes(val)
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
break;
|
|
937
|
+
case "number":
|
|
938
|
+
if (typeof oldParams === "object" && typeof newParams === "object") {
|
|
939
|
+
if ("precision" in oldParams && "precision" in newParams) {
|
|
940
|
+
return newParams.precision < oldParams.precision || (newParams.scale ?? 0) < (oldParams.scale ?? 0);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
if (Array.isArray(oldParams) && Array.isArray(newParams)) {
|
|
944
|
+
return newParams.length < oldParams.length || !oldParams.every(
|
|
945
|
+
(val) => newParams.includes(val)
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
break;
|
|
949
|
+
case "array":
|
|
950
|
+
if (typeof oldParams === "object" && typeof newParams === "object" && "minItems" in oldParams && "maxItems" in oldParams && "minItems" in newParams && "maxItems" in newParams) {
|
|
951
|
+
return newParams.minItems > oldParams.minItems || newParams.maxItems < oldParams.maxItems;
|
|
952
|
+
}
|
|
953
|
+
break;
|
|
954
|
+
case "object":
|
|
955
|
+
if (typeof oldParams === "object" && typeof newParams === "object" && "schema" in oldParams && "schema" in newParams) {
|
|
956
|
+
return Object.keys(newParams.schema).length > Object.keys(oldParams.schema).length;
|
|
957
|
+
}
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
function analyzeConstraintGroupChanges(oldGroup, newGroup) {
|
|
963
|
+
const operatorPriority = {
|
|
964
|
+
or: 1,
|
|
965
|
+
xor: 2,
|
|
966
|
+
and: 3,
|
|
967
|
+
not: 4,
|
|
968
|
+
nor: 4
|
|
969
|
+
};
|
|
970
|
+
if (newGroup.operator && operatorPriority[newGroup.operator] > operatorPriority[oldGroup.operator]) {
|
|
971
|
+
return true;
|
|
972
|
+
}
|
|
973
|
+
if (newGroup.rules && newGroup.rules.length > oldGroup.rules.length) {
|
|
974
|
+
return true;
|
|
975
|
+
}
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
function isBreakingConstraintChange(changes, oldConstraint, schema) {
|
|
979
|
+
if (!oldConstraint) {
|
|
980
|
+
return true;
|
|
981
|
+
}
|
|
982
|
+
if ("rules" in oldConstraint && "rules" in changes) {
|
|
983
|
+
return analyzeConstraintGroupChanges(
|
|
984
|
+
oldConstraint,
|
|
985
|
+
changes
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
if ("predicate" in changes && changes.predicate !== void 0) {
|
|
989
|
+
return true;
|
|
990
|
+
}
|
|
991
|
+
if ("parameters" in changes && changes.parameters !== void 0) {
|
|
992
|
+
const fieldType = determineFieldType(
|
|
993
|
+
oldConstraint,
|
|
994
|
+
schema
|
|
995
|
+
);
|
|
996
|
+
if (fieldType) {
|
|
997
|
+
return isBreakingConstraintParameters(
|
|
998
|
+
oldConstraint.parameters,
|
|
999
|
+
changes.parameters,
|
|
1000
|
+
fieldType
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
return true;
|
|
1004
|
+
}
|
|
1005
|
+
return false;
|
|
1006
|
+
}
|
|
1007
|
+
function getChangeImpact(change, currentSchema) {
|
|
1008
|
+
switch (change.type) {
|
|
1009
|
+
case "removeField":
|
|
1010
|
+
case "removeIndex":
|
|
1011
|
+
return "major";
|
|
1012
|
+
case "modifyField":
|
|
1013
|
+
if (isBreakingFieldChange(change.changes)) {
|
|
1014
|
+
return "major";
|
|
1015
|
+
}
|
|
1016
|
+
if (change.changes.deprecated) {
|
|
1017
|
+
return "minor";
|
|
1018
|
+
}
|
|
1019
|
+
return "patch";
|
|
1020
|
+
case "modifyIndex":
|
|
1021
|
+
if (change.changes.unique !== void 0 || change.changes.fields !== void 0) {
|
|
1022
|
+
return "major";
|
|
1023
|
+
}
|
|
1024
|
+
return "minor";
|
|
1025
|
+
case "addConstraint":
|
|
1026
|
+
return "major";
|
|
1027
|
+
case "removeConstraint":
|
|
1028
|
+
return "minor";
|
|
1029
|
+
case "modifyConstraint":
|
|
1030
|
+
const oldConstraint = currentSchema?.constraints?.find(
|
|
1031
|
+
(c) => "name" in c && c.name === change.name
|
|
1032
|
+
);
|
|
1033
|
+
if (isBreakingConstraintChange(change.changes, oldConstraint, currentSchema)) {
|
|
1034
|
+
return "major";
|
|
1035
|
+
}
|
|
1036
|
+
return "minor";
|
|
1037
|
+
case "addField":
|
|
1038
|
+
case "addIndex":
|
|
1039
|
+
case "deprecateField":
|
|
1040
|
+
return "minor";
|
|
1041
|
+
default:
|
|
1042
|
+
throw new Error(`Unhandled change type: ${JSON.stringify(change)}`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
function validateFieldChanges(changes) {
|
|
1046
|
+
const modifiedFields = /* @__PURE__ */ new Set();
|
|
1047
|
+
const removedFields = /* @__PURE__ */ new Set();
|
|
1048
|
+
const addedFields = /* @__PURE__ */ new Set();
|
|
1049
|
+
const deprecatedFields = /* @__PURE__ */ new Set();
|
|
1050
|
+
for (const change of changes) {
|
|
1051
|
+
switch (change.type) {
|
|
1052
|
+
case "addField":
|
|
1053
|
+
if (removedFields.has(change.name)) {
|
|
1054
|
+
throw new Error(
|
|
1055
|
+
`Cannot add previously removed field: ${change.name}`
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
if (modifiedFields.has(change.name)) {
|
|
1059
|
+
throw new Error(`Cannot add already modified field: ${change.name}`);
|
|
1060
|
+
}
|
|
1061
|
+
if (deprecatedFields.has(change.name)) {
|
|
1062
|
+
throw new Error(`Cannot add deprecated field: ${change.name}`);
|
|
1063
|
+
}
|
|
1064
|
+
addedFields.add(change.name);
|
|
1065
|
+
break;
|
|
1066
|
+
case "removeField":
|
|
1067
|
+
if (addedFields.has(change.name)) {
|
|
1068
|
+
throw new Error(`Cannot remove newly added field: ${change.name}`);
|
|
1069
|
+
}
|
|
1070
|
+
if (modifiedFields.has(change.name)) {
|
|
1071
|
+
throw new Error(`Cannot remove modified field: ${change.name}`);
|
|
1072
|
+
}
|
|
1073
|
+
if (deprecatedFields.has(change.name)) {
|
|
1074
|
+
throw new Error(
|
|
1075
|
+
`Cannot remove field that is being deprecated: ${change.name}`
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
removedFields.add(change.name);
|
|
1079
|
+
break;
|
|
1080
|
+
case "modifyField":
|
|
1081
|
+
if (removedFields.has(change.name)) {
|
|
1082
|
+
throw new Error(`Cannot modify removed field: ${change.name}`);
|
|
1083
|
+
}
|
|
1084
|
+
if (addedFields.has(change.name)) {
|
|
1085
|
+
throw new Error(`Cannot modify newly added field: ${change.name}`);
|
|
1086
|
+
}
|
|
1087
|
+
if (deprecatedFields.has(change.name)) {
|
|
1088
|
+
throw new Error(
|
|
1089
|
+
`Cannot modify field that is being deprecated: ${change.name}`
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
modifiedFields.add(change.name);
|
|
1093
|
+
break;
|
|
1094
|
+
case "deprecateField":
|
|
1095
|
+
if (removedFields.has(change.name)) {
|
|
1096
|
+
throw new Error(`Cannot deprecate removed field: ${change.name}`);
|
|
1097
|
+
}
|
|
1098
|
+
if (addedFields.has(change.name)) {
|
|
1099
|
+
throw new Error(`Cannot deprecate newly added field: ${change.name}`);
|
|
1100
|
+
}
|
|
1101
|
+
if (modifiedFields.has(change.name)) {
|
|
1102
|
+
throw new Error(`Cannot deprecate modified field: ${change.name}`);
|
|
1103
|
+
}
|
|
1104
|
+
deprecatedFields.add(change.name);
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
function validateConstraintChanges(changes) {
|
|
1110
|
+
const modifiedConstraints = /* @__PURE__ */ new Set();
|
|
1111
|
+
const removedConstraints = /* @__PURE__ */ new Set();
|
|
1112
|
+
const addedConstraints = /* @__PURE__ */ new Set();
|
|
1113
|
+
for (const change of changes) {
|
|
1114
|
+
switch (change.type) {
|
|
1115
|
+
case "addConstraint":
|
|
1116
|
+
const c = change.constraint;
|
|
1117
|
+
const name = "name" in c ? c.name : c.name;
|
|
1118
|
+
if (removedConstraints.has(name)) {
|
|
1119
|
+
throw new Error(
|
|
1120
|
+
`Cannot add previously removed constraint: ${name}`
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
if (modifiedConstraints.has(name)) {
|
|
1124
|
+
throw new Error(`Cannot add already modified constraint: ${name}`);
|
|
1125
|
+
}
|
|
1126
|
+
addedConstraints.add(name);
|
|
1127
|
+
break;
|
|
1128
|
+
case "removeConstraint":
|
|
1129
|
+
if (addedConstraints.has(change.name)) {
|
|
1130
|
+
throw new Error(
|
|
1131
|
+
`Cannot remove newly added constraint: ${change.name}`
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
if (modifiedConstraints.has(change.name)) {
|
|
1135
|
+
throw new Error(`Cannot remove modified constraint: ${change.name}`);
|
|
1136
|
+
}
|
|
1137
|
+
removedConstraints.add(change.name);
|
|
1138
|
+
break;
|
|
1139
|
+
case "modifyConstraint":
|
|
1140
|
+
if (removedConstraints.has(change.name)) {
|
|
1141
|
+
throw new Error(`Cannot modify removed constraint: ${change.name}`);
|
|
1142
|
+
}
|
|
1143
|
+
if (addedConstraints.has(change.name)) {
|
|
1144
|
+
throw new Error(
|
|
1145
|
+
`Cannot modify newly added constraint: ${change.name}`
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
modifiedConstraints.add(change.name);
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
function calculateNextVersion(currentVersion, changes, currentSchema) {
|
|
1154
|
+
if (changes.length === 0) {
|
|
1155
|
+
throw new Error("No changes provided");
|
|
1156
|
+
}
|
|
1157
|
+
validateFieldChanges(changes);
|
|
1158
|
+
validateConstraintChanges(changes);
|
|
1159
|
+
const version = parseVersion(currentVersion);
|
|
1160
|
+
let highestImpact = "patch";
|
|
1161
|
+
for (const change of changes) {
|
|
1162
|
+
const impact = getChangeImpact(change, currentSchema);
|
|
1163
|
+
if (impact === "major") {
|
|
1164
|
+
highestImpact = "major";
|
|
1165
|
+
break;
|
|
1166
|
+
} else if (impact === "minor" && highestImpact === "patch") {
|
|
1167
|
+
highestImpact = "minor";
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
switch (highestImpact) {
|
|
1171
|
+
case "major":
|
|
1172
|
+
return `${version.major + 1}.0.0`;
|
|
1173
|
+
case "minor":
|
|
1174
|
+
return `${version.major}.${version.minor + 1}.0`;
|
|
1175
|
+
case "patch":
|
|
1176
|
+
return `${version.major}.${version.minor}.${version.patch + 1}`;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
function compareSemanticVersions(a, b) {
|
|
1180
|
+
const parseVersion2 = (version) => version.split(".").map((part) => parseInt(part, 10) || 0);
|
|
1181
|
+
const [aMajor, aMinor, aPatch] = parseVersion2(a);
|
|
1182
|
+
const [bMajor, bMinor, bPatch] = parseVersion2(b);
|
|
1183
|
+
return aMajor - bMajor || aMinor - bMinor || aPatch - bPatch;
|
|
1184
|
+
}
|
|
1185
|
+
function sortSemanticVars(vars) {
|
|
1186
|
+
return vars.sort(compareSemanticVersions);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// src/lib/migration/index.ts
|
|
1190
|
+
var MigrationError = class extends Error {
|
|
1191
|
+
constructor(message, code, migrationId, cause) {
|
|
1192
|
+
super(message);
|
|
1193
|
+
this.code = code;
|
|
1194
|
+
this.migrationId = migrationId;
|
|
1195
|
+
this.cause = cause;
|
|
1196
|
+
this.name = "MigrationError";
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
var MigrationErrorCode = /* @__PURE__ */ ((MigrationErrorCode2) => {
|
|
1200
|
+
MigrationErrorCode2["INVALID_SCHEMA"] = "INVALID_SCHEMA";
|
|
1201
|
+
MigrationErrorCode2["INVALID_MIGRATION"] = "INVALID_MIGRATION";
|
|
1202
|
+
MigrationErrorCode2["CHECKSUM_MISMATCH"] = "CHECKSUM_MISMATCH";
|
|
1203
|
+
MigrationErrorCode2["TIMEOUT"] = "TIMEOUT";
|
|
1204
|
+
MigrationErrorCode2["MEMORY_LIMIT"] = "MEMORY_LIMIT";
|
|
1205
|
+
MigrationErrorCode2["CONCURRENT_OPERATION"] = "CONCURRENT_OPERATION";
|
|
1206
|
+
MigrationErrorCode2["TRANSFORM_ERROR"] = "TRANSFORM_ERROR";
|
|
1207
|
+
MigrationErrorCode2["VERSION_NOT_FOUND"] = "VERSION_NOT_FOUND";
|
|
1208
|
+
MigrationErrorCode2["CIRCULAR_DEPENDENCY"] = "CIRCULAR_DEPENDENCY";
|
|
1209
|
+
MigrationErrorCode2["STREAM_ERROR"] = "STREAM_ERROR";
|
|
1210
|
+
MigrationErrorCode2["ROLLBACK_ERROR"] = "ROLLBACK_ERROR";
|
|
1211
|
+
MigrationErrorCode2["MISSING_TRANSFORM"] = "MISSING_TRANSFORM";
|
|
1212
|
+
return MigrationErrorCode2;
|
|
1213
|
+
})(MigrationErrorCode || {});
|
|
1214
|
+
var MigrationEngine = class _MigrationEngine {
|
|
1215
|
+
currentSchema;
|
|
1216
|
+
history = [];
|
|
1217
|
+
migrations = [];
|
|
1218
|
+
isProcessing = false;
|
|
1219
|
+
/**
|
|
1220
|
+
* @constructor
|
|
1221
|
+
* @param {SchemaDefinition} currentSchema - The current schema definition
|
|
1222
|
+
* @param {Array<Migration<any>>} [migrations] - Optional array of migrations
|
|
1223
|
+
*/
|
|
1224
|
+
constructor(currentSchema, migrations, history) {
|
|
1225
|
+
try {
|
|
1226
|
+
if (!validateSchemaDefinition(currentSchema)) {
|
|
1227
|
+
throw new MigrationError(
|
|
1228
|
+
"Invalid initial schema",
|
|
1229
|
+
"INVALID_SCHEMA" /* INVALID_SCHEMA */
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
this.currentSchema = currentSchema;
|
|
1233
|
+
if (migrations) {
|
|
1234
|
+
if (!migrations.every((migration) => validateMigration(migration))) {
|
|
1235
|
+
throw new MigrationError(
|
|
1236
|
+
"Invalid migration configuration",
|
|
1237
|
+
"INVALID_MIGRATION" /* INVALID_MIGRATION */
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
this.migrations = migrations.sort(
|
|
1241
|
+
(a, b) => compareSemanticVersions(a.schemaVersion, b.schemaVersion)
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
if (history) {
|
|
1245
|
+
this.history = history.sort(
|
|
1246
|
+
(a, b) => compareSemanticVersions(a.version, b.version)
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
if (error instanceof MigrationError) throw error;
|
|
1251
|
+
throw new MigrationError(
|
|
1252
|
+
"Failed to initialize MigrationEngine",
|
|
1253
|
+
"INVALID_SCHEMA" /* INVALID_SCHEMA */,
|
|
1254
|
+
void 0,
|
|
1255
|
+
error
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Gets the current state of the migration helper
|
|
1261
|
+
* @returns {Object} Current state containing schema, history, and migrations
|
|
1262
|
+
* @example
|
|
1263
|
+
* ```javascript
|
|
1264
|
+
* const state = migrationEngine.data();
|
|
1265
|
+
* // state contains currentSchema, history, and migrations
|
|
1266
|
+
* ```
|
|
1267
|
+
*/
|
|
1268
|
+
data() {
|
|
1269
|
+
const result = {
|
|
1270
|
+
schema: this.currentSchema,
|
|
1271
|
+
history: this.history,
|
|
1272
|
+
migrations: this.migrations
|
|
1273
|
+
};
|
|
1274
|
+
return result;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Generates a SHA-256 checksum for a migration
|
|
1278
|
+
* @private
|
|
1279
|
+
* @param {Omit<Migration<any>, "checksum">} migration - The migration object
|
|
1280
|
+
* @returns {Promise<string>} The generated checksum
|
|
1281
|
+
* @throws {MigrationError} If checksum generation fails
|
|
1282
|
+
*/
|
|
1283
|
+
async generateChecksum(migration) {
|
|
1284
|
+
try {
|
|
1285
|
+
const payload = JSON.stringify({
|
|
1286
|
+
id: migration.id,
|
|
1287
|
+
schemaVersion: migration.schemaVersion,
|
|
1288
|
+
changes: migration.changes,
|
|
1289
|
+
description: migration.description,
|
|
1290
|
+
rollback: migration.rollback,
|
|
1291
|
+
createdAt: migration.createdAt
|
|
1292
|
+
});
|
|
1293
|
+
return await generateSHA256Hash(payload);
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
throw new MigrationError(
|
|
1296
|
+
"Checksum generation failed",
|
|
1297
|
+
"CHECKSUM_MISMATCH" /* CHECKSUM_MISMATCH */,
|
|
1298
|
+
migration.id,
|
|
1299
|
+
error
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Adds a new migration to the engine
|
|
1305
|
+
* @async
|
|
1306
|
+
* @param {Object} opts - Options for the new migration
|
|
1307
|
+
* @param {SchemaChange<any>[]} opts.changes - Array of schema changes
|
|
1308
|
+
* @param {string} opts.description - Description of the migration
|
|
1309
|
+
* @param {SchemaChange<any>[]} [opts.rollback] - Optional rollback changes
|
|
1310
|
+
* @param {DataTransform<any, any>} [opts.transform] - Optional data transform
|
|
1311
|
+
* @throws {MigrationError} If adding the migration fails
|
|
1312
|
+
*/
|
|
1313
|
+
async add(opts) {
|
|
1314
|
+
if (this.isProcessing) {
|
|
1315
|
+
throw new MigrationError(
|
|
1316
|
+
"Concurrent operation",
|
|
1317
|
+
"CONCURRENT_OPERATION" /* CONCURRENT_OPERATION */
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
if (!opts.changes?.length) {
|
|
1321
|
+
throw new MigrationError(
|
|
1322
|
+
"Migration must include changes",
|
|
1323
|
+
"INVALID_MIGRATION" /* INVALID_MIGRATION */
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
try {
|
|
1327
|
+
opts.changes.forEach((change) => validateSchemaChange(change));
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
throw new MigrationError(
|
|
1330
|
+
"Invalid schema changes",
|
|
1331
|
+
"INVALID_MIGRATION" /* INVALID_MIGRATION */,
|
|
1332
|
+
void 0,
|
|
1333
|
+
error
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
const newMigration = {
|
|
1337
|
+
id: Date.now().toString(),
|
|
1338
|
+
schemaVersion: this.currentSchema.version,
|
|
1339
|
+
changes: opts.changes,
|
|
1340
|
+
description: opts.description,
|
|
1341
|
+
status: "pending",
|
|
1342
|
+
rollback: opts.rollback,
|
|
1343
|
+
transform: opts.transform,
|
|
1344
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1345
|
+
checksum: ""
|
|
1346
|
+
};
|
|
1347
|
+
newMigration.checksum = await this.generateChecksum(newMigration);
|
|
1348
|
+
this.migrations.push(newMigration);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Performs a dry run of the migration
|
|
1352
|
+
* @async
|
|
1353
|
+
* @param {ReadableStream<any>} input - Input data stream
|
|
1354
|
+
* @param {"forward" | "backward"} direction - Direction of migration
|
|
1355
|
+
* @param {version} version - Version to rollback to
|
|
1356
|
+
* @returns {Promise<Object>} Object containing newSchema and dataPreview
|
|
1357
|
+
* @throws {MigrationError} If dry run fails
|
|
1358
|
+
*/
|
|
1359
|
+
async dryRun(input, direction, version) {
|
|
1360
|
+
if (this.isProcessing) {
|
|
1361
|
+
throw new MigrationError(
|
|
1362
|
+
"Concurrent operation",
|
|
1363
|
+
"CONCURRENT_OPERATION" /* CONCURRENT_OPERATION */
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
try {
|
|
1367
|
+
this.isProcessing = true;
|
|
1368
|
+
const simulatedSchema = { ...this.currentSchema };
|
|
1369
|
+
const relevantMigrations = this.getRelevantMigrations(direction, version);
|
|
1370
|
+
const tempSchema = relevantMigrations.reduce((acc, migration) => {
|
|
1371
|
+
const changes = direction === "forward" ? migration.changes : migration.rollback || [];
|
|
1372
|
+
return this.applySchemaChanges(acc, changes, migration.id);
|
|
1373
|
+
}, simulatedSchema);
|
|
1374
|
+
const previewStream = await _MigrationEngine.processMigrationList(
|
|
1375
|
+
input,
|
|
1376
|
+
direction,
|
|
1377
|
+
relevantMigrations
|
|
1378
|
+
);
|
|
1379
|
+
return { newSchema: tempSchema, dataPreview: previewStream };
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
if (error instanceof MigrationError) throw error;
|
|
1382
|
+
throw new MigrationError(
|
|
1383
|
+
"Dry run failed",
|
|
1384
|
+
"INVALID_SCHEMA" /* INVALID_SCHEMA */,
|
|
1385
|
+
void 0,
|
|
1386
|
+
error
|
|
1387
|
+
);
|
|
1388
|
+
} finally {
|
|
1389
|
+
this.isProcessing = false;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Gets relevant migrations based on direction
|
|
1394
|
+
* @private
|
|
1395
|
+
* @param {"forward" | "backward"} direction - Direction of migration
|
|
1396
|
+
* @returns {Array<Migration<any>>} Relevant migrations
|
|
1397
|
+
*/
|
|
1398
|
+
getRelevantMigrations(direction, version) {
|
|
1399
|
+
return [...this.migrations].filter((m) => {
|
|
1400
|
+
const dir = direction === "forward" ? "pending" : "applied";
|
|
1401
|
+
const vs = version ? compareSemanticVersions(m.schemaVersion, version) >= 0 : true;
|
|
1402
|
+
return m.status === dir && vs;
|
|
1403
|
+
}).sort(
|
|
1404
|
+
(a, b) => direction === "forward" ? a.id.localeCompare(b.id) : b.id.localeCompare(a.id)
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Applies schema changes to a given schema
|
|
1409
|
+
* @private
|
|
1410
|
+
* @param {SchemaDefinition} schema - The schema to modify
|
|
1411
|
+
* @param {SchemaChange<any>[]} changes - Array of schema changes
|
|
1412
|
+
* @param {string} [migrationId] - ID of the migration
|
|
1413
|
+
* @returns {SchemaDefinition} Modified schema
|
|
1414
|
+
* @throws {MigrationError} If applying changes fails
|
|
1415
|
+
*/
|
|
1416
|
+
applySchemaChanges(schema, changes, migrationId) {
|
|
1417
|
+
try {
|
|
1418
|
+
const version = calculateNextVersion(schema.version, changes);
|
|
1419
|
+
const patches = changes.map((change) => {
|
|
1420
|
+
try {
|
|
1421
|
+
return schemaChangeToPatch(change, schema);
|
|
1422
|
+
} catch (error) {
|
|
1423
|
+
throw new MigrationError(
|
|
1424
|
+
"Invalid schema change",
|
|
1425
|
+
"INVALID_SCHEMA" /* INVALID_SCHEMA */,
|
|
1426
|
+
migrationId,
|
|
1427
|
+
error
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
return patches.reduce(
|
|
1432
|
+
(acc, patch) => {
|
|
1433
|
+
try {
|
|
1434
|
+
return applyPatch(acc, patch);
|
|
1435
|
+
} catch (error) {
|
|
1436
|
+
throw new MigrationError(
|
|
1437
|
+
"Failed to apply patch",
|
|
1438
|
+
"INVALID_SCHEMA" /* INVALID_SCHEMA */,
|
|
1439
|
+
migrationId,
|
|
1440
|
+
error
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
},
|
|
1444
|
+
{ ...schema, version }
|
|
1445
|
+
);
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
if (error instanceof MigrationError) throw error;
|
|
1448
|
+
throw new MigrationError(
|
|
1449
|
+
"Schema update failed",
|
|
1450
|
+
"INVALID_SCHEMA" /* INVALID_SCHEMA */,
|
|
1451
|
+
migrationId,
|
|
1452
|
+
error
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Prepares the list of pending migrations for application
|
|
1458
|
+
* @async
|
|
1459
|
+
* @returns {Promise<Array<Migration<any>>} List of pending migrations
|
|
1460
|
+
* @throws {MigrationError} If preparation fails
|
|
1461
|
+
*/
|
|
1462
|
+
async prepareMigration() {
|
|
1463
|
+
const pendingMigrations = this.migrations.filter(
|
|
1464
|
+
(m) => m.status === "pending"
|
|
1465
|
+
);
|
|
1466
|
+
await this.validateMigrations(pendingMigrations);
|
|
1467
|
+
return pendingMigrations;
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Applies pending migrations
|
|
1471
|
+
* @async
|
|
1472
|
+
* @param {ReadableStream<any>} input - Input data stream
|
|
1473
|
+
* @returns {Promise<ReadableStream<any>>} Transformed data stream
|
|
1474
|
+
* @throws {MigrationError} If migration fails
|
|
1475
|
+
*/
|
|
1476
|
+
async migrate(input) {
|
|
1477
|
+
if (this.isProcessing) {
|
|
1478
|
+
throw new MigrationError(
|
|
1479
|
+
"Concurrent operation",
|
|
1480
|
+
"CONCURRENT_OPERATION" /* CONCURRENT_OPERATION */
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
const pendingMigrations = await this.prepareMigration();
|
|
1484
|
+
try {
|
|
1485
|
+
this.isProcessing = true;
|
|
1486
|
+
this.transformSchema("forward");
|
|
1487
|
+
const result = await _MigrationEngine.processMigrationList(
|
|
1488
|
+
input,
|
|
1489
|
+
"forward",
|
|
1490
|
+
pendingMigrations
|
|
1491
|
+
);
|
|
1492
|
+
this.markMigrationsApplied(pendingMigrations);
|
|
1493
|
+
return result;
|
|
1494
|
+
} finally {
|
|
1495
|
+
this.isProcessing = false;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Validates migrations by checking their checksums
|
|
1500
|
+
* @private
|
|
1501
|
+
* @async
|
|
1502
|
+
* @param {Array<Migration<any>>} migrations - Migrations to validate
|
|
1503
|
+
* @throws {MigrationError} If validation fails
|
|
1504
|
+
*/
|
|
1505
|
+
async validateMigrations(migrations) {
|
|
1506
|
+
await Promise.all(
|
|
1507
|
+
migrations.map(async (migration) => {
|
|
1508
|
+
const currentChecksum = await this.generateChecksum(migration);
|
|
1509
|
+
if (migration.checksum !== currentChecksum) {
|
|
1510
|
+
throw new MigrationError(
|
|
1511
|
+
"Checksum mismatch",
|
|
1512
|
+
"CHECKSUM_MISMATCH" /* CHECKSUM_MISMATCH */,
|
|
1513
|
+
migration.id
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
})
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Marks migrations as applied
|
|
1521
|
+
* @private
|
|
1522
|
+
* @param {Array<Migration<any>>} migrations - Migrations to mark as applied
|
|
1523
|
+
*/
|
|
1524
|
+
markMigrationsApplied(migrations) {
|
|
1525
|
+
this.migrations = this.migrations.map(
|
|
1526
|
+
(m) => migrations.some((mm) => mm.id === m.id) ? { ...m, status: "applied" } : m
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Rolls back the last applied migration
|
|
1531
|
+
* @async
|
|
1532
|
+
* @param {ReadableStream<any>} input - Input data stream
|
|
1533
|
+
* @returns {Promise<ReadableStream<any>>} Transformed data stream
|
|
1534
|
+
*/
|
|
1535
|
+
async rollback(input) {
|
|
1536
|
+
if (this.isProcessing) {
|
|
1537
|
+
throw new MigrationError(
|
|
1538
|
+
"Concurrent operation",
|
|
1539
|
+
"CONCURRENT_OPERATION" /* CONCURRENT_OPERATION */
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
const lastApplied = this.migrations.filter((m) => m.status === "applied").slice(-1)[0];
|
|
1543
|
+
if (!lastApplied) return input;
|
|
1544
|
+
return this.rollbackToVersion(
|
|
1545
|
+
this.history[this.history.length - 1]?.version || this.currentSchema.version,
|
|
1546
|
+
input
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Rolls back to a specific schema version
|
|
1551
|
+
* @async
|
|
1552
|
+
* @param {string} targetVersion - Target schema version
|
|
1553
|
+
* @param {ReadableStream<any>} input - Input data stream
|
|
1554
|
+
* @returns {Promise<ReadableStream<any>>} Transformed data stream
|
|
1555
|
+
* @throws {Error} If target version is not found
|
|
1556
|
+
*/
|
|
1557
|
+
async rollbackToVersion(targetVersion, input) {
|
|
1558
|
+
if (this.isProcessing) {
|
|
1559
|
+
throw new MigrationError(
|
|
1560
|
+
"Concurrent operation",
|
|
1561
|
+
"CONCURRENT_OPERATION" /* CONCURRENT_OPERATION */
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
try {
|
|
1565
|
+
const versionIndex = this.history.findIndex(
|
|
1566
|
+
(s) => s.version === targetVersion
|
|
1567
|
+
);
|
|
1568
|
+
if (versionIndex === -1) {
|
|
1569
|
+
throw new Error(`Version ${targetVersion} not found in history`);
|
|
1570
|
+
}
|
|
1571
|
+
const migrations = this.migrations.filter(
|
|
1572
|
+
(m) => m.schemaVersion === targetVersion && m.status === "applied"
|
|
1573
|
+
).sort((a, b) => b.id.localeCompare(a.id));
|
|
1574
|
+
const stepsBack = this.history.length - versionIndex;
|
|
1575
|
+
if (stepsBack < 0) return input;
|
|
1576
|
+
for (let i = 0; i < stepsBack; i++) {
|
|
1577
|
+
this.transformSchema("backward");
|
|
1578
|
+
}
|
|
1579
|
+
const transformedStream = await _MigrationEngine.processMigrationList(
|
|
1580
|
+
input,
|
|
1581
|
+
"backward",
|
|
1582
|
+
migrations
|
|
1583
|
+
);
|
|
1584
|
+
this.migrations = this.migrations.map((m) => {
|
|
1585
|
+
if (m.schemaVersion === targetVersion && m.status === "applied") {
|
|
1586
|
+
return { ...m, status: "pending" };
|
|
1587
|
+
}
|
|
1588
|
+
return m;
|
|
1589
|
+
});
|
|
1590
|
+
return transformedStream;
|
|
1591
|
+
} finally {
|
|
1592
|
+
this.isProcessing = false;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* Processes a list of migrations on a data stream, applying transformations
|
|
1597
|
+
* in the specified direction (forward or backward).
|
|
1598
|
+
*
|
|
1599
|
+
* @static
|
|
1600
|
+
* @async
|
|
1601
|
+
* @param {ReadableStream<any>} input - The input data stream to process
|
|
1602
|
+
* @param {"forward" | "backward"} direction - Direction of migration (either "forward" or "backward")
|
|
1603
|
+
* @param {Array<Migration<any>>} migrations - Array of Migration objects to process
|
|
1604
|
+
* @returns {Promise<ReadableStream<any>>} Transformed data stream
|
|
1605
|
+
* @throws {MigrationError} If any migration processing fails
|
|
1606
|
+
*/
|
|
1607
|
+
static async processMigrationList(input, direction, migrations) {
|
|
1608
|
+
const transformEntries = await Promise.all(
|
|
1609
|
+
migrations.map(async (migration) => {
|
|
1610
|
+
try {
|
|
1611
|
+
const transform = await this.resolveTransform(migration, direction);
|
|
1612
|
+
return { migration, transform };
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
throw new MigrationError(
|
|
1615
|
+
`Failed to resolve transform for migration ${migration.id}`,
|
|
1616
|
+
"TRANSFORM_ERROR" /* TRANSFORM_ERROR */,
|
|
1617
|
+
migration.id,
|
|
1618
|
+
error
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
})
|
|
1622
|
+
);
|
|
1623
|
+
const validTransformEntries = transformEntries.filter(
|
|
1624
|
+
(entry) => Boolean(entry.transform)
|
|
1625
|
+
);
|
|
1626
|
+
return validTransformEntries.reduce((stream, { migration, transform }) => {
|
|
1627
|
+
return stream.pipeThrough(
|
|
1628
|
+
new TransformStream({
|
|
1629
|
+
async transform(chunk, controller) {
|
|
1630
|
+
try {
|
|
1631
|
+
const result = await transform(chunk);
|
|
1632
|
+
controller.enqueue(result);
|
|
1633
|
+
} catch (error) {
|
|
1634
|
+
controller.error(
|
|
1635
|
+
new MigrationError(
|
|
1636
|
+
`Data transformation failed for migration ${migration.id}`,
|
|
1637
|
+
"TRANSFORM_ERROR" /* TRANSFORM_ERROR */,
|
|
1638
|
+
migration.id,
|
|
1639
|
+
error
|
|
1640
|
+
)
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
})
|
|
1645
|
+
);
|
|
1646
|
+
}, input);
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Resolves the transform function for a given migration in the specified direction.
|
|
1650
|
+
*
|
|
1651
|
+
* @private
|
|
1652
|
+
* @async
|
|
1653
|
+
* @param {Migration<any>} migration - The migration to resolve the transform for
|
|
1654
|
+
* @param {"forward" | "backward"} direction - Direction of migration
|
|
1655
|
+
* @returns {Promise<TransformFunction<any, any>>} Resolved transform function
|
|
1656
|
+
* @throws {MigrationError} If transform resolution fails
|
|
1657
|
+
*/
|
|
1658
|
+
static async resolveTransform(migration, direction) {
|
|
1659
|
+
if (!migration.transform) {
|
|
1660
|
+
return null;
|
|
1661
|
+
}
|
|
1662
|
+
if (typeof migration.transform === "string") {
|
|
1663
|
+
if (migration.transform.startsWith("http://") || migration.transform.startsWith("https://")) {
|
|
1664
|
+
return this.resolveRemoteTransform(migration.transform, direction);
|
|
1665
|
+
}
|
|
1666
|
+
return this.resolveLocalTransform(migration.transform, direction);
|
|
1667
|
+
}
|
|
1668
|
+
return migration.transform[direction];
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Resolves a transform function from a remote URL.
|
|
1672
|
+
*
|
|
1673
|
+
* @private
|
|
1674
|
+
* @async
|
|
1675
|
+
* @param {string} url - URL of the transform module
|
|
1676
|
+
* @param {"forward" | "backward"} direction - Direction of migration
|
|
1677
|
+
* @returns {Promise<TransformFunction<any, any>>} Resolved transform function
|
|
1678
|
+
* @throws {MigrationError} If resolution fails
|
|
1679
|
+
*/
|
|
1680
|
+
static async resolveRemoteTransform(url, direction) {
|
|
1681
|
+
try {
|
|
1682
|
+
const response = await fetch(url);
|
|
1683
|
+
if (!response.ok) {
|
|
1684
|
+
throw new MigrationError(
|
|
1685
|
+
`Failed to fetch transform module: ${url}`,
|
|
1686
|
+
"TRANSFORM_ERROR" /* TRANSFORM_ERROR */,
|
|
1687
|
+
void 0
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
const moduleText = await response.text();
|
|
1691
|
+
if (typeof window !== "undefined") {
|
|
1692
|
+
const blob = new Blob([moduleText], {
|
|
1693
|
+
type: "application/javascript"
|
|
1694
|
+
});
|
|
1695
|
+
const url2 = URL.createObjectURL(blob);
|
|
1696
|
+
const module2 = (await import(url2)).default;
|
|
1697
|
+
return module2[direction];
|
|
1698
|
+
} else {
|
|
1699
|
+
const { runInNewContext } = await import("vm");
|
|
1700
|
+
const sandbox = { module: { exports: {} }, console };
|
|
1701
|
+
runInNewContext(moduleText, sandbox, url);
|
|
1702
|
+
return sandbox.module.exports[direction];
|
|
1703
|
+
}
|
|
1704
|
+
} catch (error) {
|
|
1705
|
+
throw new MigrationError(
|
|
1706
|
+
`Failed to load remote transform module: ${url}`,
|
|
1707
|
+
"TRANSFORM_ERROR" /* TRANSFORM_ERROR */,
|
|
1708
|
+
void 0,
|
|
1709
|
+
error
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Resolves a transform function from a local module path.
|
|
1715
|
+
*
|
|
1716
|
+
* @private
|
|
1717
|
+
* @async
|
|
1718
|
+
* @param {string} path - Local module path
|
|
1719
|
+
* @param {"forward" | "backward"} direction - Direction of migration
|
|
1720
|
+
* @returns {Promise<TransformFunction<any, any>>} Resolved transform function
|
|
1721
|
+
* @throws {MigrationError} If resolution fails
|
|
1722
|
+
*/
|
|
1723
|
+
static async resolveLocalTransform(path, direction) {
|
|
1724
|
+
try {
|
|
1725
|
+
const module2 = await import(path);
|
|
1726
|
+
const dataTransform = module2.default;
|
|
1727
|
+
return dataTransform[direction];
|
|
1728
|
+
} catch (error) {
|
|
1729
|
+
throw new MigrationError(
|
|
1730
|
+
`Failed to import local transform module: ${path}`,
|
|
1731
|
+
"TRANSFORM_ERROR" /* TRANSFORM_ERROR */,
|
|
1732
|
+
void 0,
|
|
1733
|
+
error
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
/**
|
|
1738
|
+
* Transforms the schema either forward or backward
|
|
1739
|
+
* @private
|
|
1740
|
+
* @param {"forward" | "backward"} direction - Direction of transformation
|
|
1741
|
+
* @throws {Error} If transformation fails
|
|
1742
|
+
*/
|
|
1743
|
+
transformSchema(direction) {
|
|
1744
|
+
try {
|
|
1745
|
+
if (direction === "backward") {
|
|
1746
|
+
const schema = this.history.pop();
|
|
1747
|
+
if (!schema) throw new Error("No previous version");
|
|
1748
|
+
this.currentSchema = schema;
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
const changes = this.migrations.filter((m) => m.status === "pending").flatMap((m) => m.changes);
|
|
1752
|
+
if (!changes.length) return;
|
|
1753
|
+
this.history.push(structuredClone(this.currentSchema));
|
|
1754
|
+
this.currentSchema = changes.reduce(
|
|
1755
|
+
(acc, change) => this.applySchemaChanges(acc, [change]),
|
|
1756
|
+
this.currentSchema
|
|
1757
|
+
);
|
|
1758
|
+
} catch (error) {
|
|
1759
|
+
if (error instanceof MigrationError) throw error;
|
|
1760
|
+
throw new MigrationError(
|
|
1761
|
+
"Schema transformation failed",
|
|
1762
|
+
"INVALID_SCHEMA" /* INVALID_SCHEMA */,
|
|
1763
|
+
void 0,
|
|
1764
|
+
error
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
|
|
1770
|
+
// src/lib/schema/helpers.ts
|
|
1771
|
+
var createSchemaMigrationHelper = (schema) => {
|
|
1772
|
+
const migrate = [];
|
|
1773
|
+
const rollback = [];
|
|
1774
|
+
return {
|
|
1775
|
+
/**
|
|
1776
|
+
* Adds a new field to the schema.
|
|
1777
|
+
* @param {string} fieldName - The name of the field to add.
|
|
1778
|
+
* @param {FieldDefinition<any>} fieldDefinition - The definition of the field to add.
|
|
1779
|
+
*/
|
|
1780
|
+
addField: (fieldName, fieldDefinition) => {
|
|
1781
|
+
migrate.push({ type: "addField", name: fieldName, definition: fieldDefinition });
|
|
1782
|
+
rollback.push({ type: "removeField", name: fieldName });
|
|
1783
|
+
},
|
|
1784
|
+
/**
|
|
1785
|
+
* Removes a field from the schema.
|
|
1786
|
+
* @param {string} fieldName - The name of the field to remove.
|
|
1787
|
+
*/
|
|
1788
|
+
removeField: (fieldName) => {
|
|
1789
|
+
migrate.push({ type: "removeField", name: fieldName });
|
|
1790
|
+
const originalField = schema.fields[fieldName];
|
|
1791
|
+
if (originalField) {
|
|
1792
|
+
rollback.push({ type: "addField", name: fieldName, definition: originalField });
|
|
1793
|
+
}
|
|
1794
|
+
},
|
|
1795
|
+
/**
|
|
1796
|
+
* Modifies an existing field in the schema.
|
|
1797
|
+
* @param {string} fieldName - The name of the field to modify.
|
|
1798
|
+
* @param {Partial<FieldDefinition<any>>} changes - The changes to apply to the field.
|
|
1799
|
+
*/
|
|
1800
|
+
modifyField: (fieldName, changes) => {
|
|
1801
|
+
migrate.push({ type: "modifyField", name: fieldName, changes });
|
|
1802
|
+
const originalField = schema.fields[fieldName];
|
|
1803
|
+
rollback.push({ type: "modifyField", name: fieldName, changes: originalField });
|
|
1804
|
+
},
|
|
1805
|
+
/**
|
|
1806
|
+
* Deprecates a field.
|
|
1807
|
+
* @param {string} fieldName - The name of the field to deprecate.
|
|
1808
|
+
*/
|
|
1809
|
+
deprecateField: (fieldName) => {
|
|
1810
|
+
migrate.push({ type: "deprecateField", name: fieldName });
|
|
1811
|
+
rollback.push({
|
|
1812
|
+
type: "modifyField",
|
|
1813
|
+
name: fieldName,
|
|
1814
|
+
changes: { deprecated: false }
|
|
1815
|
+
});
|
|
1816
|
+
},
|
|
1817
|
+
/**
|
|
1818
|
+
* Adds a new index to the schema.
|
|
1819
|
+
* @param {IndexDefinition} indexDefinition - The definition of the index to add.
|
|
1820
|
+
*/
|
|
1821
|
+
addIndex: (indexDefinition) => {
|
|
1822
|
+
migrate.push({ type: "addIndex", definition: indexDefinition });
|
|
1823
|
+
rollback.push({ type: "removeIndex", name: indexDefinition.name });
|
|
1824
|
+
},
|
|
1825
|
+
/**
|
|
1826
|
+
* Removes an index from the schema.
|
|
1827
|
+
* @param {string} indexName - The name of the index to remove.
|
|
1828
|
+
*/
|
|
1829
|
+
removeIndex: (indexName) => {
|
|
1830
|
+
migrate.push({ type: "removeIndex", name: indexName });
|
|
1831
|
+
const originalIndex = schema.indexes?.find((index) => index.name === indexName);
|
|
1832
|
+
if (originalIndex) {
|
|
1833
|
+
rollback.push({ type: "addIndex", definition: originalIndex });
|
|
1834
|
+
}
|
|
1835
|
+
},
|
|
1836
|
+
/**
|
|
1837
|
+
* Modifies an existing index in the schema.
|
|
1838
|
+
* @param {string} indexName - The name of the index to modify.
|
|
1839
|
+
* @param {Partial<IndexDefinition>} changes - The changes to apply to the index.
|
|
1840
|
+
*/
|
|
1841
|
+
modifyIndex: (indexName, changes) => {
|
|
1842
|
+
migrate.push({ type: "modifyIndex", name: indexName, changes });
|
|
1843
|
+
const originalIndex = schema.indexes?.find((index) => index.name === indexName);
|
|
1844
|
+
if (originalIndex) {
|
|
1845
|
+
rollback.push({ type: "modifyIndex", name: indexName, changes: originalIndex });
|
|
1846
|
+
}
|
|
1847
|
+
},
|
|
1848
|
+
/**
|
|
1849
|
+
* Adds a new constraint to the schema.
|
|
1850
|
+
* @param {SchemaConstraint<any>} constraint - The constraint to add.
|
|
1851
|
+
*/
|
|
1852
|
+
addConstraint: (constraint) => {
|
|
1853
|
+
migrate.push({ type: "addConstraint", constraint });
|
|
1854
|
+
rollback.push({ type: "removeConstraint", name: constraint.name });
|
|
1855
|
+
},
|
|
1856
|
+
/**
|
|
1857
|
+
* Removes a constraint from the schema.
|
|
1858
|
+
* @param {string} constraintName - The name of the constraint to remove.
|
|
1859
|
+
*/
|
|
1860
|
+
removeConstraint: (constraintName) => {
|
|
1861
|
+
migrate.push({ type: "removeConstraint", name: constraintName });
|
|
1862
|
+
const originalConstraint = schema.constraints?.find((c) => "name" in c && c.name === constraintName);
|
|
1863
|
+
if (originalConstraint) {
|
|
1864
|
+
rollback.push({ type: "addConstraint", constraint: originalConstraint });
|
|
1865
|
+
}
|
|
1866
|
+
},
|
|
1867
|
+
/**
|
|
1868
|
+
* Modifies an existing constraint in the schema.
|
|
1869
|
+
* @param {string} constraintName - The name of the constraint to modify.
|
|
1870
|
+
* @param {Partial<SchemaConstraint<any>>} changes - The changes to apply to the constraint.
|
|
1871
|
+
*/
|
|
1872
|
+
modifyConstraint: (constraintName, changes) => {
|
|
1873
|
+
migrate.push({ type: "modifyConstraint", name: constraintName, changes });
|
|
1874
|
+
const originalConstraint = schema.constraints?.find((c) => "name" in c && c.name === constraintName);
|
|
1875
|
+
if (originalConstraint) {
|
|
1876
|
+
rollback.push({ type: "modifyConstraint", name: constraintName, changes: originalConstraint });
|
|
1877
|
+
}
|
|
1878
|
+
},
|
|
1879
|
+
/**
|
|
1880
|
+
* Returns the migration changes and their corresponding rollback changes.
|
|
1881
|
+
* @returns {Object} An object containing the migrate and rollback changes.
|
|
1882
|
+
*/
|
|
1883
|
+
changes: () => ({
|
|
1884
|
+
migrate,
|
|
1885
|
+
rollback
|
|
1886
|
+
})
|
|
1887
|
+
};
|
|
1888
|
+
};
|
|
1889
|
+
|
|
1890
|
+
// src/lib/persistence/collection.ts
|
|
1891
|
+
var EmphemeralCollection = class {
|
|
1892
|
+
definition;
|
|
1893
|
+
// Stores the schema definition for the collection.
|
|
1894
|
+
collectionEventBus;
|
|
1895
|
+
// Event bus for collection-related events.
|
|
1896
|
+
validator;
|
|
1897
|
+
collectionData = [];
|
|
1898
|
+
// Array to store the collection data.
|
|
1899
|
+
predicateMap;
|
|
1900
|
+
// Map of predicate names to their implementations.
|
|
1901
|
+
match;
|
|
1902
|
+
project;
|
|
1903
|
+
sort;
|
|
1904
|
+
paginate;
|
|
1905
|
+
migrator;
|
|
1906
|
+
/**
|
|
1907
|
+
* @constructor
|
|
1908
|
+
* @param {Record<string, Function>} functionMap Map of function names to their implementations.
|
|
1909
|
+
* @param {Record<string, (params: any) => boolean>} predicateMap Map of predicate names to their implementations.
|
|
1910
|
+
*/
|
|
1911
|
+
constructor(schema, functionMap, predicateMap) {
|
|
1912
|
+
this.definition = schema;
|
|
1913
|
+
this.collectionEventBus = (0, import_events.createEventBus)();
|
|
1914
|
+
this.predicateMap = predicateMap;
|
|
1915
|
+
this.validator = createStandardSchemaValidator(schema, predicateMap)["~standard"];
|
|
1916
|
+
this.match = (0, import_query.createMatcher)(functionMap);
|
|
1917
|
+
this.project = (0, import_query.createProjector)(functionMap);
|
|
1918
|
+
this.sort = (0, import_query.createSorter)();
|
|
1919
|
+
this.paginate = (0, import_query.createPaginator)();
|
|
1920
|
+
this.migrator = new MigrationEngine(schema);
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* @method schema
|
|
1924
|
+
* @description Returns the schema definition for the collection.
|
|
1925
|
+
* @returns {SchemaDefinition | null} The schema definition, or null if not set.
|
|
1926
|
+
*/
|
|
1927
|
+
schema() {
|
|
1928
|
+
return this.definition;
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* @method create
|
|
1932
|
+
* @description Creates new data in the collection.
|
|
1933
|
+
* @param {{ data: DataType | DataType[] }} The data to create.
|
|
1934
|
+
* @returns {Promise<DataType | DataType[]>} The created data.
|
|
1935
|
+
*/
|
|
1936
|
+
async create({ data }) {
|
|
1937
|
+
const dataArray = Array.isArray(data) ? data : [data];
|
|
1938
|
+
const allIssues = dataArray.reduce(
|
|
1939
|
+
(acc, item) => {
|
|
1940
|
+
const validationResult = this.validate(item);
|
|
1941
|
+
if (validationResult.issues) {
|
|
1942
|
+
acc.push(...validationResult.issues);
|
|
1943
|
+
}
|
|
1944
|
+
return acc;
|
|
1945
|
+
},
|
|
1946
|
+
[]
|
|
1947
|
+
);
|
|
1948
|
+
if (allIssues.length > 0) {
|
|
1949
|
+
this.emitCollectionEvent("create:failed", {
|
|
1950
|
+
operation: "create",
|
|
1951
|
+
collection: this.definition?.name || "",
|
|
1952
|
+
input: dataArray,
|
|
1953
|
+
type: "create:failed",
|
|
1954
|
+
timestamp: Date.now(),
|
|
1955
|
+
issues: allIssues
|
|
1956
|
+
});
|
|
1957
|
+
throw new Error(`Invalid data: ${JSON.stringify(allIssues)}`);
|
|
1958
|
+
}
|
|
1959
|
+
this.collectionData.push(...dataArray);
|
|
1960
|
+
this.emitCollectionEvent("create:success", {
|
|
1961
|
+
operation: "create",
|
|
1962
|
+
collection: this.definition?.name || "",
|
|
1963
|
+
input: dataArray,
|
|
1964
|
+
output: dataArray,
|
|
1965
|
+
type: "create:success",
|
|
1966
|
+
timestamp: Date.now()
|
|
1967
|
+
});
|
|
1968
|
+
return data;
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* @method read
|
|
1972
|
+
* @description Reads data from the collection based on the provided query.
|
|
1973
|
+
* @param {QueryDSL<T>} [query] Optional query to filter the data.
|
|
1974
|
+
* @returns {Promise<T[]>} An array of data matching the query.
|
|
1975
|
+
*/
|
|
1976
|
+
async read({
|
|
1977
|
+
query
|
|
1978
|
+
}) {
|
|
1979
|
+
this.emitCollectionEvent("read:start", {
|
|
1980
|
+
operation: "read",
|
|
1981
|
+
collection: this.definition?.name,
|
|
1982
|
+
query,
|
|
1983
|
+
type: "read:start",
|
|
1984
|
+
timestamp: Date.now()
|
|
1985
|
+
});
|
|
1986
|
+
let result = this.collectionData;
|
|
1987
|
+
if (query.filters) {
|
|
1988
|
+
result = result.filter(
|
|
1989
|
+
(item) => this.match.match(item, query.filters)
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
if (query.projection) {
|
|
1993
|
+
result = result.map(
|
|
1994
|
+
(item) => this.project.project(item, query.projection)
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
if (query.sort) {
|
|
1998
|
+
result = this.sort.sort(result, query.sort);
|
|
1999
|
+
}
|
|
2000
|
+
if (query.pagination) {
|
|
2001
|
+
const paginator = this.paginate.paginate(result, query.pagination);
|
|
2002
|
+
result = (await paginator.next()).value;
|
|
2003
|
+
}
|
|
2004
|
+
this.emitCollectionEvent("read:success", {
|
|
2005
|
+
operation: "read",
|
|
2006
|
+
collection: this.definition?.name,
|
|
2007
|
+
query,
|
|
2008
|
+
output: result,
|
|
2009
|
+
type: "read:success",
|
|
2010
|
+
timestamp: Date.now()
|
|
2011
|
+
});
|
|
2012
|
+
return result;
|
|
2013
|
+
}
|
|
2014
|
+
/**
|
|
2015
|
+
* @method update
|
|
2016
|
+
* @description Updates data in the collection based on the provided query and update data.
|
|
2017
|
+
* @param {{ data?: Partial<T>; patch?: PatchOperation | Array<PatchOperation>; query: QueryFilter }} The update parameters.
|
|
2018
|
+
* @returns {Array<T>} The updated data.
|
|
2019
|
+
*/
|
|
2020
|
+
async update({
|
|
2021
|
+
data,
|
|
2022
|
+
patch,
|
|
2023
|
+
query
|
|
2024
|
+
}) {
|
|
2025
|
+
this.emitCollectionEvent("update:start", {
|
|
2026
|
+
operation: "update",
|
|
2027
|
+
collection: this.definition?.name,
|
|
2028
|
+
input: data,
|
|
2029
|
+
query,
|
|
2030
|
+
type: "update:start",
|
|
2031
|
+
timestamp: Date.now()
|
|
2032
|
+
});
|
|
2033
|
+
const results = [];
|
|
2034
|
+
this.collectionData.forEach((i, index) => {
|
|
2035
|
+
if (!this.match.match(i, query)) return;
|
|
2036
|
+
let result = i;
|
|
2037
|
+
if (data) {
|
|
2038
|
+
result = deepMerge(result, data);
|
|
2039
|
+
}
|
|
2040
|
+
if (patch) {
|
|
2041
|
+
const changes = Array.isArray(patch) ? patch : [patch];
|
|
2042
|
+
result = applyPatch(result, changes);
|
|
2043
|
+
}
|
|
2044
|
+
this.collectionData[index] = result;
|
|
2045
|
+
results.push(result);
|
|
2046
|
+
});
|
|
2047
|
+
this.emitCollectionEvent("update:success", {
|
|
2048
|
+
operation: "update",
|
|
2049
|
+
collection: this.definition?.name,
|
|
2050
|
+
input: data,
|
|
2051
|
+
query,
|
|
2052
|
+
output: results,
|
|
2053
|
+
type: "update:success",
|
|
2054
|
+
timestamp: Date.now()
|
|
2055
|
+
});
|
|
2056
|
+
return results;
|
|
2057
|
+
}
|
|
2058
|
+
/**
|
|
2059
|
+
* @method delete
|
|
2060
|
+
* @description Deletes data from the collection based on the provided query.
|
|
2061
|
+
* @param {{ query: QueryFilter<T> }} The query to use for deleting data.
|
|
2062
|
+
* @returns {number} The number of deleted items.
|
|
2063
|
+
*/
|
|
2064
|
+
async delete({ query }) {
|
|
2065
|
+
this.emitCollectionEvent("delete:start", {
|
|
2066
|
+
operation: "delete",
|
|
2067
|
+
collection: this.definition?.name,
|
|
2068
|
+
query,
|
|
2069
|
+
type: "delete:start",
|
|
2070
|
+
timestamp: Date.now()
|
|
2071
|
+
});
|
|
2072
|
+
const beforeCount = this.collectionData.length;
|
|
2073
|
+
const remainingItems = this.collectionData.filter(
|
|
2074
|
+
(item) => !this.match.match(item, query)
|
|
2075
|
+
);
|
|
2076
|
+
const deletedCount = beforeCount - remainingItems.length;
|
|
2077
|
+
this.collectionData = remainingItems;
|
|
2078
|
+
this.emitCollectionEvent("delete:success", {
|
|
2079
|
+
operation: "delete",
|
|
2080
|
+
collection: this.definition?.name,
|
|
2081
|
+
query,
|
|
2082
|
+
context: { deletedCount },
|
|
2083
|
+
type: "delete:success",
|
|
2084
|
+
timestamp: Date.now()
|
|
2085
|
+
});
|
|
2086
|
+
return deletedCount;
|
|
2087
|
+
}
|
|
2088
|
+
/**
|
|
2089
|
+
* @method validate
|
|
2090
|
+
* @description Validates data against the collection's schema.
|
|
2091
|
+
* @param {T} data The data to validate.
|
|
2092
|
+
* @returns {{ valid: boolean; issues: StandardSchemaV1.Issue[] | null }} An object containing the validation result.
|
|
2093
|
+
*/
|
|
2094
|
+
validate(data) {
|
|
2095
|
+
if (!this.definition) {
|
|
2096
|
+
throw new Error("No schema exists. Call setSchema first.");
|
|
2097
|
+
}
|
|
2098
|
+
if (!this.validator) {
|
|
2099
|
+
this.validator = createStandardSchemaValidator(
|
|
2100
|
+
this.definition,
|
|
2101
|
+
this.predicateMap
|
|
2102
|
+
)["~standard"];
|
|
2103
|
+
}
|
|
2104
|
+
const results = this.validator.validate(
|
|
2105
|
+
data
|
|
2106
|
+
);
|
|
2107
|
+
return { valid: !results.issues, issues: results.issues || null };
|
|
2108
|
+
}
|
|
2109
|
+
/**
|
|
2110
|
+
* @method emitCollectionEvent
|
|
2111
|
+
* @description Emits a collection-related event.
|
|
2112
|
+
* @param {PersistenceEventType} type The type of the event.
|
|
2113
|
+
* @param {PersistenceEvent<SchemaDefinition>} payload The event payload.
|
|
2114
|
+
*/
|
|
2115
|
+
emitCollectionEvent(type, payload) {
|
|
2116
|
+
this.collectionEventBus.emit({ name: type, payload });
|
|
2117
|
+
}
|
|
2118
|
+
/**
|
|
2119
|
+
* @method subscribeToCollectionEvents
|
|
2120
|
+
* @description Subscribes to collection-related events.
|
|
2121
|
+
* @param {PersistenceEventType} event The type of the event to subscribe to.
|
|
2122
|
+
* @param {(payload: PersistenceEvent<SchemaDefinition>) => void} callback The callback function.
|
|
2123
|
+
* @returns {() => void} A function to unsubscribe.
|
|
2124
|
+
*/
|
|
2125
|
+
subscribe(event, callback) {
|
|
2126
|
+
return this.collectionEventBus.subscribe(event, callback);
|
|
2127
|
+
}
|
|
2128
|
+
async rollback(version, dryRun = true) {
|
|
2129
|
+
this.emitCollectionEvent("rollback:start", {
|
|
2130
|
+
operation: "rollback",
|
|
2131
|
+
collection: this.definition?.name,
|
|
2132
|
+
input: { version },
|
|
2133
|
+
type: "rollback:start",
|
|
2134
|
+
timestamp: Date.now(),
|
|
2135
|
+
context: {
|
|
2136
|
+
dryRun
|
|
2137
|
+
}
|
|
2138
|
+
});
|
|
2139
|
+
if (dryRun) {
|
|
2140
|
+
const input = new ReadableStream({
|
|
2141
|
+
start(controller) {
|
|
2142
|
+
controller.close();
|
|
2143
|
+
}
|
|
2144
|
+
});
|
|
2145
|
+
const result = await this.migrator.dryRun(input, "backward", version);
|
|
2146
|
+
this.emitCollectionEvent("migrate:success", {
|
|
2147
|
+
operation: "rollback",
|
|
2148
|
+
collection: this.definition?.name,
|
|
2149
|
+
input: { version },
|
|
2150
|
+
type: "migrate:success",
|
|
2151
|
+
timestamp: Date.now(),
|
|
2152
|
+
context: {
|
|
2153
|
+
dryRun
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
return result;
|
|
2157
|
+
}
|
|
2158
|
+
try {
|
|
2159
|
+
const data = this.collectionData;
|
|
2160
|
+
const input = new ReadableStream({
|
|
2161
|
+
start(controller) {
|
|
2162
|
+
data.forEach((i) => controller.enqueue(i));
|
|
2163
|
+
controller.close();
|
|
2164
|
+
}
|
|
2165
|
+
});
|
|
2166
|
+
const output = Boolean(version) ? await this.migrator.rollbackToVersion(version, input) : await this.migrator.rollback(input);
|
|
2167
|
+
const reader = output.getReader();
|
|
2168
|
+
let value = await reader.read();
|
|
2169
|
+
const result = [];
|
|
2170
|
+
do {
|
|
2171
|
+
if (value.value) {
|
|
2172
|
+
result.push(value.value);
|
|
2173
|
+
}
|
|
2174
|
+
value = await reader.read();
|
|
2175
|
+
} while (!value.done);
|
|
2176
|
+
this.collectionData = result;
|
|
2177
|
+
this.definition = this.migrator.data().schema;
|
|
2178
|
+
this.validator = createStandardSchemaValidator(this.definition, this.predicateMap)["~standard"];
|
|
2179
|
+
this.emitCollectionEvent("rollback:failed", {
|
|
2180
|
+
operation: "rollback",
|
|
2181
|
+
collection: this.definition?.name,
|
|
2182
|
+
input: { version },
|
|
2183
|
+
type: "rollback:failed",
|
|
2184
|
+
timestamp: Date.now(),
|
|
2185
|
+
context: {
|
|
2186
|
+
dryRun
|
|
2187
|
+
}
|
|
2188
|
+
});
|
|
2189
|
+
} catch (error) {
|
|
2190
|
+
this.emitCollectionEvent("migrate:failed", {
|
|
2191
|
+
operation: "rollback",
|
|
2192
|
+
collection: this.definition?.name,
|
|
2193
|
+
input: { version },
|
|
2194
|
+
type: "migrate:failed",
|
|
2195
|
+
timestamp: Date.now(),
|
|
2196
|
+
error,
|
|
2197
|
+
context: {
|
|
2198
|
+
dryRun,
|
|
2199
|
+
error
|
|
2200
|
+
}
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
async migrate(description, cb, dryRun = true) {
|
|
2205
|
+
const helper = createSchemaMigrationHelper(this.definition);
|
|
2206
|
+
const transform = cb(helper);
|
|
2207
|
+
const { migrate, rollback } = helper.changes();
|
|
2208
|
+
this.emitCollectionEvent("migrate:start", {
|
|
2209
|
+
operation: "migrate",
|
|
2210
|
+
collection: this.definition?.name,
|
|
2211
|
+
input: { migrate, rollback },
|
|
2212
|
+
type: "migrate:start",
|
|
2213
|
+
timestamp: Date.now(),
|
|
2214
|
+
context: {
|
|
2215
|
+
dryRun
|
|
2216
|
+
}
|
|
2217
|
+
});
|
|
2218
|
+
await this.migrator.add({
|
|
2219
|
+
description,
|
|
2220
|
+
changes: migrate,
|
|
2221
|
+
rollback,
|
|
2222
|
+
transform
|
|
2223
|
+
});
|
|
2224
|
+
try {
|
|
2225
|
+
if (dryRun) {
|
|
2226
|
+
const input2 = new ReadableStream({
|
|
2227
|
+
start(controller) {
|
|
2228
|
+
controller.close();
|
|
2229
|
+
}
|
|
2230
|
+
});
|
|
2231
|
+
const result2 = await this.migrator.dryRun(input2, "forward");
|
|
2232
|
+
this.emitCollectionEvent("migrate:success", {
|
|
2233
|
+
operation: "migrate",
|
|
2234
|
+
collection: this.definition?.name,
|
|
2235
|
+
input: { migrate, rollback },
|
|
2236
|
+
type: "migrate:success",
|
|
2237
|
+
timestamp: Date.now(),
|
|
2238
|
+
context: {
|
|
2239
|
+
dryRun
|
|
2240
|
+
}
|
|
2241
|
+
});
|
|
2242
|
+
return result2;
|
|
2243
|
+
}
|
|
2244
|
+
const data = this.collectionData;
|
|
2245
|
+
const input = new ReadableStream({
|
|
2246
|
+
start(controller) {
|
|
2247
|
+
data.forEach((i) => controller.enqueue(i));
|
|
2248
|
+
controller.close();
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2251
|
+
const output = await this.migrator.migrate(input);
|
|
2252
|
+
const reader = output.getReader();
|
|
2253
|
+
let value = await reader.read();
|
|
2254
|
+
const result = [];
|
|
2255
|
+
do {
|
|
2256
|
+
if (value.value) {
|
|
2257
|
+
result.push(value.value);
|
|
2258
|
+
}
|
|
2259
|
+
value = await reader.read();
|
|
2260
|
+
} while (!value.done);
|
|
2261
|
+
this.collectionData = result;
|
|
2262
|
+
this.definition = this.migrator.data().schema;
|
|
2263
|
+
this.validator = createStandardSchemaValidator(this.definition, this.predicateMap)["~standard"];
|
|
2264
|
+
this.emitCollectionEvent("migrate:success", {
|
|
2265
|
+
operation: "migrate",
|
|
2266
|
+
collection: this.definition?.name,
|
|
2267
|
+
input: { migrate, rollback },
|
|
2268
|
+
type: "migrate:success",
|
|
2269
|
+
timestamp: Date.now(),
|
|
2270
|
+
context: {
|
|
2271
|
+
dryRun
|
|
2272
|
+
}
|
|
2273
|
+
});
|
|
2274
|
+
} catch (error) {
|
|
2275
|
+
this.emitCollectionEvent("migrate:failed", {
|
|
2276
|
+
operation: "migrate",
|
|
2277
|
+
collection: this.definition?.name,
|
|
2278
|
+
input: { migrate, rollback },
|
|
2279
|
+
type: "migrate:failed",
|
|
2280
|
+
timestamp: Date.now(),
|
|
2281
|
+
error,
|
|
2282
|
+
context: {
|
|
2283
|
+
dryRun,
|
|
2284
|
+
error
|
|
2285
|
+
}
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
};
|
|
2290
|
+
|
|
2291
|
+
// src/lib/persistence/index.ts
|
|
2292
|
+
function createEphemeralPersistence(functionMap, predicateMap) {
|
|
2293
|
+
const bus = (0, import_events2.createEventBus)();
|
|
2294
|
+
const data = /* @__PURE__ */ new Map();
|
|
2295
|
+
function subscribe(event, callback) {
|
|
2296
|
+
return bus.subscribe(event, callback);
|
|
2297
|
+
}
|
|
2298
|
+
function transact(callback) {
|
|
2299
|
+
return callback({
|
|
2300
|
+
createCollection,
|
|
2301
|
+
deleteCollection,
|
|
2302
|
+
collections,
|
|
2303
|
+
schema,
|
|
2304
|
+
collection
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2307
|
+
async function createCollection(schema2) {
|
|
2308
|
+
if (data.has(schema2.name)) {
|
|
2309
|
+
bus.emit({
|
|
2310
|
+
name: "collection:create:failed",
|
|
2311
|
+
payload: {
|
|
2312
|
+
operation: "create",
|
|
2313
|
+
collection: schema2.name,
|
|
2314
|
+
input: schema2,
|
|
2315
|
+
type: "collection:create:failed",
|
|
2316
|
+
timestamp: Date.now()
|
|
2317
|
+
}
|
|
2318
|
+
});
|
|
2319
|
+
throw new Error("Collection exists!");
|
|
2320
|
+
}
|
|
2321
|
+
const collection2 = new EmphemeralCollection(
|
|
2322
|
+
schema2,
|
|
2323
|
+
functionMap,
|
|
2324
|
+
predicateMap
|
|
2325
|
+
);
|
|
2326
|
+
data.set(schema2.name, collection2);
|
|
2327
|
+
bus.emit({
|
|
2328
|
+
name: "collection:create:success",
|
|
2329
|
+
payload: {
|
|
2330
|
+
operation: "create",
|
|
2331
|
+
collection: schema2.name,
|
|
2332
|
+
input: schema2,
|
|
2333
|
+
type: "collection:create:success",
|
|
2334
|
+
timestamp: Date.now()
|
|
2335
|
+
}
|
|
2336
|
+
});
|
|
2337
|
+
return collection2;
|
|
2338
|
+
}
|
|
2339
|
+
async function deleteCollection(id) {
|
|
2340
|
+
if (data.has(id)) {
|
|
2341
|
+
data.delete(id);
|
|
2342
|
+
bus.emit({
|
|
2343
|
+
name: "collection:delete:success",
|
|
2344
|
+
payload: {
|
|
2345
|
+
operation: "delete",
|
|
2346
|
+
collection: id,
|
|
2347
|
+
input: id,
|
|
2348
|
+
type: "collection:delete:success",
|
|
2349
|
+
timestamp: Date.now()
|
|
2350
|
+
}
|
|
2351
|
+
});
|
|
2352
|
+
} else {
|
|
2353
|
+
bus.emit({
|
|
2354
|
+
name: "collection:delete:failed",
|
|
2355
|
+
payload: {
|
|
2356
|
+
operation: "delete",
|
|
2357
|
+
collection: id,
|
|
2358
|
+
input: id,
|
|
2359
|
+
type: "collection:delete:failed",
|
|
2360
|
+
timestamp: Date.now()
|
|
2361
|
+
}
|
|
2362
|
+
});
|
|
2363
|
+
throw new Error("Collection not found!");
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
async function collections() {
|
|
2367
|
+
return Array.from(data.keys());
|
|
2368
|
+
}
|
|
2369
|
+
async function schema(id) {
|
|
2370
|
+
const collection2 = data.get(id);
|
|
2371
|
+
if (!collection2) throw new Error("Collection not found!");
|
|
2372
|
+
return collection2.schema();
|
|
2373
|
+
}
|
|
2374
|
+
function collection(id) {
|
|
2375
|
+
const coll = data.get(id);
|
|
2376
|
+
if (!coll) throw new Error("Collection not found!");
|
|
2377
|
+
return coll;
|
|
2378
|
+
}
|
|
2379
|
+
return {
|
|
2380
|
+
transact,
|
|
2381
|
+
createCollection,
|
|
2382
|
+
deleteCollection,
|
|
2383
|
+
collections,
|
|
2384
|
+
schema,
|
|
2385
|
+
collection,
|
|
2386
|
+
subscribe
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
// src/lib/registry/index.ts
|
|
2391
|
+
var registry_exports2 = {};
|
|
2392
|
+
__export(registry_exports2, {
|
|
2393
|
+
default: () => createRegistry
|
|
2394
|
+
});
|
|
2395
|
+
var import_lightning_fs = __toESM(require("@isomorphic-git/lightning-fs"), 1);
|
|
2396
|
+
var import_buffer = require("buffer");
|
|
2397
|
+
var import_isomorphic_git = __toESM(require("isomorphic-git"), 1);
|
|
2398
|
+
var import_web = __toESM(require("isomorphic-git/http/web"), 1);
|
|
2399
|
+
|
|
2400
|
+
// src/lib/registry/repository.ts
|
|
2401
|
+
async function createGithubRepository({
|
|
2402
|
+
token,
|
|
2403
|
+
repoConfig
|
|
2404
|
+
}) {
|
|
2405
|
+
try {
|
|
2406
|
+
const response = await fetch("https://api.github.com/user/repos", {
|
|
2407
|
+
method: "POST",
|
|
2408
|
+
headers: {
|
|
2409
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
2410
|
+
Accept: "application/vnd.github+json",
|
|
2411
|
+
Authorization: `Bearer ${token}`,
|
|
2412
|
+
"Content-Type": "application/json"
|
|
2413
|
+
},
|
|
2414
|
+
body: JSON.stringify(repoConfig),
|
|
2415
|
+
redirect: "follow"
|
|
2416
|
+
});
|
|
2417
|
+
if (!response.ok) {
|
|
2418
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
2419
|
+
}
|
|
2420
|
+
const data = await response.json();
|
|
2421
|
+
return data;
|
|
2422
|
+
} catch (error) {
|
|
2423
|
+
console.error("Error creating repository:", error);
|
|
2424
|
+
throw error;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
// src/lib/registry/utils.ts
|
|
2429
|
+
function serializeGenerator(fn) {
|
|
2430
|
+
const fnStr = fn.toString();
|
|
2431
|
+
const params = fnStr.substring(fnStr.indexOf("(") + 1, fnStr.indexOf(")"));
|
|
2432
|
+
const body = fnStr.substring(fnStr.indexOf("{") + 1, fnStr.lastIndexOf("}")).trim();
|
|
2433
|
+
return JSON.stringify({ params, body });
|
|
2434
|
+
}
|
|
2435
|
+
function deserializeGenerator(serialized) {
|
|
2436
|
+
const { params, body } = JSON.parse(serialized);
|
|
2437
|
+
const GeneratorFunction = Object.getPrototypeOf(function* () {
|
|
2438
|
+
}).constructor;
|
|
2439
|
+
return new GeneratorFunction(params, body);
|
|
2440
|
+
}
|
|
2441
|
+
function serializeFunction(fn) {
|
|
2442
|
+
const fnStr = fn.toString();
|
|
2443
|
+
const params = fnStr.substring(fnStr.indexOf("(") + 1, fnStr.indexOf(")"));
|
|
2444
|
+
const body = fnStr.substring(fnStr.indexOf("{") + 1, fnStr.lastIndexOf("}")).trim();
|
|
2445
|
+
return JSON.stringify({ params, body });
|
|
2446
|
+
}
|
|
2447
|
+
function deserializeFunction(serialized) {
|
|
2448
|
+
const { params, body } = JSON.parse(serialized);
|
|
2449
|
+
return new Function(params, body);
|
|
2450
|
+
}
|
|
2451
|
+
function authUrl(repoUrl, username, password) {
|
|
2452
|
+
const u = new URL(repoUrl);
|
|
2453
|
+
u.username = username;
|
|
2454
|
+
u.password = password;
|
|
2455
|
+
return u.toString();
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
// src/lib/registry/index.ts
|
|
2459
|
+
window.Buffer = import_buffer.Buffer;
|
|
2460
|
+
async function createRegistry(credentials, dir = "/registry", proxy = "https://cors.isomorphic-git.org") {
|
|
2461
|
+
const fs = new import_lightning_fs.default(dir);
|
|
2462
|
+
const pfs = fs.promises;
|
|
2463
|
+
const main = "main";
|
|
2464
|
+
async function init() {
|
|
2465
|
+
await pfs.mkdir(dir);
|
|
2466
|
+
await import_isomorphic_git.default.init({ fs, dir, defaultBranch: main });
|
|
2467
|
+
await pfs.writeFile(
|
|
2468
|
+
`${dir}/registry.json`,
|
|
2469
|
+
JSON.stringify({
|
|
2470
|
+
schemas: {},
|
|
2471
|
+
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2472
|
+
updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
2473
|
+
})
|
|
2474
|
+
);
|
|
2475
|
+
await import_isomorphic_git.default.add({
|
|
2476
|
+
fs,
|
|
2477
|
+
dir,
|
|
2478
|
+
filepath: `registry.json`
|
|
2479
|
+
});
|
|
2480
|
+
await pfs.writeFile(
|
|
2481
|
+
`${dir}/registry.lock`,
|
|
2482
|
+
JSON.stringify({
|
|
2483
|
+
hashes: [],
|
|
2484
|
+
updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
2485
|
+
})
|
|
2486
|
+
);
|
|
2487
|
+
await import_isomorphic_git.default.add({
|
|
2488
|
+
fs,
|
|
2489
|
+
dir,
|
|
2490
|
+
filepath: `registry.lock`
|
|
2491
|
+
});
|
|
2492
|
+
await import_isomorphic_git.default.commit({
|
|
2493
|
+
fs,
|
|
2494
|
+
dir,
|
|
2495
|
+
author: { name: "bot", email: "bot@registry.domain" },
|
|
2496
|
+
message: "initial commit"
|
|
2497
|
+
});
|
|
2498
|
+
await createGithubRepository({
|
|
2499
|
+
token: credentials.password,
|
|
2500
|
+
repoConfig: {
|
|
2501
|
+
name: credentials.repository,
|
|
2502
|
+
private: true
|
|
2503
|
+
}
|
|
2504
|
+
});
|
|
2505
|
+
await import_isomorphic_git.default.addRemote({
|
|
2506
|
+
fs,
|
|
2507
|
+
dir,
|
|
2508
|
+
remote: "origin",
|
|
2509
|
+
url: `https://github.com/${credentials.username}/${credentials.repository}.git`
|
|
2510
|
+
});
|
|
2511
|
+
await import_isomorphic_git.default.push({
|
|
2512
|
+
fs,
|
|
2513
|
+
dir,
|
|
2514
|
+
http: import_web.default,
|
|
2515
|
+
remote: "origin",
|
|
2516
|
+
url: authUrl(
|
|
2517
|
+
`https://github.com/${credentials.username}/${credentials.repository}.git`,
|
|
2518
|
+
credentials.username,
|
|
2519
|
+
credentials.password
|
|
2520
|
+
),
|
|
2521
|
+
corsProxy: proxy,
|
|
2522
|
+
ref: main,
|
|
2523
|
+
onAuth: () => ({
|
|
2524
|
+
username: credentials.username,
|
|
2525
|
+
password: credentials.password
|
|
2526
|
+
})
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
async function clone() {
|
|
2530
|
+
await import_isomorphic_git.default.clone({
|
|
2531
|
+
fs,
|
|
2532
|
+
dir,
|
|
2533
|
+
http: import_web.default,
|
|
2534
|
+
corsProxy: proxy,
|
|
2535
|
+
url: authUrl(
|
|
2536
|
+
`https://github.com/${credentials.username}/${credentials.repository}.git`,
|
|
2537
|
+
credentials.username,
|
|
2538
|
+
credentials.password
|
|
2539
|
+
),
|
|
2540
|
+
onAuth: () => ({
|
|
2541
|
+
username: credentials.username,
|
|
2542
|
+
password: credentials.password
|
|
2543
|
+
}),
|
|
2544
|
+
singleBranch: true,
|
|
2545
|
+
depth: 1
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
async function create(schema2) {
|
|
2549
|
+
await import_isomorphic_git.default.branch({
|
|
2550
|
+
fs,
|
|
2551
|
+
dir,
|
|
2552
|
+
ref: schema2.name,
|
|
2553
|
+
checkout: true
|
|
2554
|
+
});
|
|
2555
|
+
const dirs = [schema2.name, `${schema2.name}/migrations`];
|
|
2556
|
+
for (const filepath of dirs) {
|
|
2557
|
+
await pfs.mkdir(`${dir}/${filepath}`);
|
|
2558
|
+
}
|
|
2559
|
+
const migrations2 = await Promise.all(
|
|
2560
|
+
(schema2.migrations || []).map(async (migration) => {
|
|
2561
|
+
let content = { ...migration };
|
|
2562
|
+
if (migration.transform && typeof migration.transform !== "string") {
|
|
2563
|
+
content.transform = {
|
|
2564
|
+
forward: serializeFunction(migration.transform.forward),
|
|
2565
|
+
backward: serializeFunction(migration.transform.backward)
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
content = JSON.stringify(content);
|
|
2569
|
+
const hash = await generateSHA256Hash(content);
|
|
2570
|
+
return {
|
|
2571
|
+
hash,
|
|
2572
|
+
checksum: migration.checksum,
|
|
2573
|
+
content,
|
|
2574
|
+
id: migration.id
|
|
2575
|
+
};
|
|
2576
|
+
})
|
|
2577
|
+
);
|
|
2578
|
+
await pfs.writeFile(`${dir}/${schema2.name}/migrations/.nomedia`, "");
|
|
2579
|
+
for (const { hash, content } of migrations2) {
|
|
2580
|
+
await pfs.writeFile(
|
|
2581
|
+
`${dir}/${schema2.name}/migrations/${hash}.json`,
|
|
2582
|
+
content
|
|
2583
|
+
);
|
|
2584
|
+
}
|
|
2585
|
+
const schemaContent = JSON.stringify({
|
|
2586
|
+
...schema2,
|
|
2587
|
+
migrations: [],
|
|
2588
|
+
mock: schema2.mock ? serializeGenerator(schema2.mock) : null
|
|
2589
|
+
});
|
|
2590
|
+
const schemaHash = await generateSHA256Hash(schemaContent);
|
|
2591
|
+
await pfs.writeFile(`${dir}/${schema2.name}/schema.json`, schemaContent);
|
|
2592
|
+
const index = {
|
|
2593
|
+
schema: schema2.name,
|
|
2594
|
+
version: schema2.version,
|
|
2595
|
+
history: [
|
|
2596
|
+
{
|
|
2597
|
+
version: schema2.version,
|
|
2598
|
+
hash: schemaHash,
|
|
2599
|
+
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2600
|
+
description: schema2.description || "",
|
|
2601
|
+
migrations: migrations2.map(({ id }) => id),
|
|
2602
|
+
changelog: [],
|
|
2603
|
+
predicates: []
|
|
2604
|
+
}
|
|
2605
|
+
],
|
|
2606
|
+
migrations: migrations2.reduce(
|
|
2607
|
+
(acc, { hash, checksum, id }) => {
|
|
2608
|
+
acc[id] = { hash, checksum };
|
|
2609
|
+
return acc;
|
|
2610
|
+
},
|
|
2611
|
+
{}
|
|
2612
|
+
)
|
|
2613
|
+
};
|
|
2614
|
+
await pfs.writeFile(
|
|
2615
|
+
`${dir}/${schema2.name}/index.json`,
|
|
2616
|
+
JSON.stringify(index)
|
|
2617
|
+
);
|
|
2618
|
+
await import_isomorphic_git.default.add({ fs, dir, filepath: schema2.name });
|
|
2619
|
+
await import_isomorphic_git.default.commit({
|
|
2620
|
+
fs,
|
|
2621
|
+
dir,
|
|
2622
|
+
author: { name: "bot", email: "bot@registry.domain" },
|
|
2623
|
+
message: `chore: created schema: ${schema2.name}`
|
|
2624
|
+
});
|
|
2625
|
+
await import_isomorphic_git.default.tag({
|
|
2626
|
+
fs,
|
|
2627
|
+
dir,
|
|
2628
|
+
ref: `${schema2.name}-${schema2.version}`
|
|
2629
|
+
});
|
|
2630
|
+
await import_isomorphic_git.default.checkout({
|
|
2631
|
+
fs,
|
|
2632
|
+
dir,
|
|
2633
|
+
ref: main
|
|
2634
|
+
});
|
|
2635
|
+
await import_isomorphic_git.default.merge({
|
|
2636
|
+
fs,
|
|
2637
|
+
dir,
|
|
2638
|
+
author: { name: "bot", email: "bot@registry.domain" },
|
|
2639
|
+
theirs: `${schema2.name}`,
|
|
2640
|
+
message: `add schema: ${schema2.name}`
|
|
2641
|
+
});
|
|
2642
|
+
await import_isomorphic_git.default.checkout({ fs, dir, ref: main });
|
|
2643
|
+
const registry = JSON.parse(
|
|
2644
|
+
(await pfs.readFile(`${dir}/registry.json`)).toString()
|
|
2645
|
+
);
|
|
2646
|
+
registry.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
2647
|
+
registry.schemas[schema2.name] = {
|
|
2648
|
+
name: schema2.name,
|
|
2649
|
+
version: schema2.version,
|
|
2650
|
+
description: schema2.description || "",
|
|
2651
|
+
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2652
|
+
updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
2653
|
+
};
|
|
2654
|
+
await pfs.writeFile(`${dir}/registry.json`, JSON.stringify(registry));
|
|
2655
|
+
const files = (await import_isomorphic_git.default.listFiles({
|
|
2656
|
+
fs,
|
|
2657
|
+
dir
|
|
2658
|
+
})).filter((i) => i !== "registry.lock");
|
|
2659
|
+
const hashes = await Promise.all(
|
|
2660
|
+
files.map(async (file) => {
|
|
2661
|
+
return [file, await generateSHA256Hash(file)];
|
|
2662
|
+
})
|
|
2663
|
+
);
|
|
2664
|
+
await pfs.writeFile(
|
|
2665
|
+
`${dir}/registry.lock`,
|
|
2666
|
+
JSON.stringify({
|
|
2667
|
+
updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2668
|
+
hashes
|
|
2669
|
+
})
|
|
2670
|
+
);
|
|
2671
|
+
await import_isomorphic_git.default.add({
|
|
2672
|
+
fs,
|
|
2673
|
+
dir,
|
|
2674
|
+
filepath: `registry.lock`
|
|
2675
|
+
});
|
|
2676
|
+
await import_isomorphic_git.default.add({
|
|
2677
|
+
fs,
|
|
2678
|
+
dir,
|
|
2679
|
+
filepath: `registry.json`
|
|
2680
|
+
});
|
|
2681
|
+
await import_isomorphic_git.default.commit({
|
|
2682
|
+
fs,
|
|
2683
|
+
dir,
|
|
2684
|
+
author: { name: "bot", email: "bot@registry.domain" },
|
|
2685
|
+
message: `fix: created schema: ${schema2.name}`
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2688
|
+
async function update(schema2) {
|
|
2689
|
+
await import_isomorphic_git.default.checkout({
|
|
2690
|
+
fs,
|
|
2691
|
+
dir,
|
|
2692
|
+
ref: schema2.name
|
|
2693
|
+
});
|
|
2694
|
+
const migrations2 = await Promise.all(
|
|
2695
|
+
(schema2.migrations || []).map(async (migration) => {
|
|
2696
|
+
let content = { ...migration };
|
|
2697
|
+
if (migration.transform && typeof migration.transform !== "string") {
|
|
2698
|
+
content.transform = {
|
|
2699
|
+
forward: serializeFunction(migration.transform.forward),
|
|
2700
|
+
backward: serializeFunction(migration.transform.backward)
|
|
2701
|
+
};
|
|
2702
|
+
}
|
|
2703
|
+
content = JSON.stringify(content);
|
|
2704
|
+
const hash = await generateSHA256Hash(content);
|
|
2705
|
+
return {
|
|
2706
|
+
hash,
|
|
2707
|
+
checksum: migration.checksum,
|
|
2708
|
+
content,
|
|
2709
|
+
id: migration.id
|
|
2710
|
+
};
|
|
2711
|
+
})
|
|
2712
|
+
);
|
|
2713
|
+
for (const { hash, content } of migrations2) {
|
|
2714
|
+
await pfs.writeFile(
|
|
2715
|
+
`${dir}/${schema2.name}/migrations/${hash}.json`,
|
|
2716
|
+
content
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
const schemaContent = JSON.stringify({
|
|
2720
|
+
...schema2,
|
|
2721
|
+
migrations: [],
|
|
2722
|
+
mock: schema2.mock ? serializeGenerator(schema2.mock) : null
|
|
2723
|
+
});
|
|
2724
|
+
const schemaHash = await generateSHA256Hash(schemaContent);
|
|
2725
|
+
await pfs.writeFile(`${dir}/${schema2.name}/schema.json`, schemaContent);
|
|
2726
|
+
const indexPath = `${dir}/${schema2.name}/index.json`;
|
|
2727
|
+
const existingIndex = JSON.parse(
|
|
2728
|
+
(await pfs.readFile(indexPath)).toString()
|
|
2729
|
+
);
|
|
2730
|
+
const newHistoryEntry = {
|
|
2731
|
+
version: schema2.version,
|
|
2732
|
+
hash: schemaHash,
|
|
2733
|
+
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2734
|
+
description: schema2.description || "",
|
|
2735
|
+
migrations: migrations2.map(({ id }) => id),
|
|
2736
|
+
changelog: [],
|
|
2737
|
+
predicates: []
|
|
2738
|
+
};
|
|
2739
|
+
existingIndex.history.push(newHistoryEntry);
|
|
2740
|
+
existingIndex.migrations = {
|
|
2741
|
+
...existingIndex.migrations,
|
|
2742
|
+
...migrations2.reduce(
|
|
2743
|
+
(acc, { hash, checksum, id }) => {
|
|
2744
|
+
acc[id] = { hash, checksum };
|
|
2745
|
+
return acc;
|
|
2746
|
+
},
|
|
2747
|
+
{}
|
|
2748
|
+
)
|
|
2749
|
+
};
|
|
2750
|
+
await pfs.writeFile(indexPath, JSON.stringify(existingIndex));
|
|
2751
|
+
await import_isomorphic_git.default.add({ fs, dir, filepath: schema2.name });
|
|
2752
|
+
await import_isomorphic_git.default.commit({
|
|
2753
|
+
fs,
|
|
2754
|
+
dir,
|
|
2755
|
+
author: { name: "bot", email: "bot@registry.domain" },
|
|
2756
|
+
message: `chore: updated schema: ${schema2.name} to version ${schema2.version}`
|
|
2757
|
+
});
|
|
2758
|
+
await import_isomorphic_git.default.tag({
|
|
2759
|
+
fs,
|
|
2760
|
+
dir,
|
|
2761
|
+
ref: `${schema2.name}-${schema2.version}`
|
|
2762
|
+
});
|
|
2763
|
+
await import_isomorphic_git.default.checkout({
|
|
2764
|
+
fs,
|
|
2765
|
+
dir,
|
|
2766
|
+
ref: main
|
|
2767
|
+
});
|
|
2768
|
+
await import_isomorphic_git.default.merge({
|
|
2769
|
+
fs,
|
|
2770
|
+
dir,
|
|
2771
|
+
author: { name: "bot", email: "bot@registry.domain" },
|
|
2772
|
+
theirs: `${schema2.name}`,
|
|
2773
|
+
message: `update schema: ${schema2.name} to version ${schema2.version}`
|
|
2774
|
+
});
|
|
2775
|
+
const registry = JSON.parse(
|
|
2776
|
+
(await pfs.readFile(`${dir}/registry.json`)).toString()
|
|
2777
|
+
);
|
|
2778
|
+
registry.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
2779
|
+
registry.schemas[schema2.name] = {
|
|
2780
|
+
name: schema2.name,
|
|
2781
|
+
version: schema2.version,
|
|
2782
|
+
description: schema2.description || "",
|
|
2783
|
+
created: registry.schemas[schema2.name]?.created || (/* @__PURE__ */ new Date()).toISOString(),
|
|
2784
|
+
updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
2785
|
+
};
|
|
2786
|
+
await pfs.writeFile(`${dir}/registry.json`, JSON.stringify(registry));
|
|
2787
|
+
const files = (await import_isomorphic_git.default.listFiles({
|
|
2788
|
+
fs,
|
|
2789
|
+
dir
|
|
2790
|
+
})).filter((i) => i !== "registry.lock");
|
|
2791
|
+
const hashes = await Promise.all(
|
|
2792
|
+
files.map(async (file) => {
|
|
2793
|
+
return [file, await generateSHA256Hash(file)];
|
|
2794
|
+
})
|
|
2795
|
+
);
|
|
2796
|
+
await pfs.writeFile(
|
|
2797
|
+
`${dir}/registry.lock`,
|
|
2798
|
+
JSON.stringify({
|
|
2799
|
+
updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2800
|
+
hashes
|
|
2801
|
+
})
|
|
2802
|
+
);
|
|
2803
|
+
await import_isomorphic_git.default.add({
|
|
2804
|
+
fs,
|
|
2805
|
+
dir,
|
|
2806
|
+
filepath: `registry.lock`
|
|
2807
|
+
});
|
|
2808
|
+
await import_isomorphic_git.default.add({
|
|
2809
|
+
fs,
|
|
2810
|
+
dir,
|
|
2811
|
+
filepath: `registry.json`
|
|
2812
|
+
});
|
|
2813
|
+
await import_isomorphic_git.default.commit({
|
|
2814
|
+
fs,
|
|
2815
|
+
dir,
|
|
2816
|
+
author: { name: "bot", email: "bot@registry.domain" },
|
|
2817
|
+
message: `fix: updated schema: ${schema2.name} to version ${schema2.version}`
|
|
2818
|
+
});
|
|
2819
|
+
await import_isomorphic_git.default.checkout({ fs, dir, ref: main });
|
|
2820
|
+
}
|
|
2821
|
+
async function delete_(name) {
|
|
2822
|
+
await import_isomorphic_git.default.checkout({
|
|
2823
|
+
fs,
|
|
2824
|
+
dir,
|
|
2825
|
+
ref: main,
|
|
2826
|
+
force: true
|
|
2827
|
+
});
|
|
2828
|
+
await import_isomorphic_git.default.deleteBranch({
|
|
2829
|
+
fs,
|
|
2830
|
+
dir,
|
|
2831
|
+
ref: name
|
|
2832
|
+
});
|
|
2833
|
+
const registry = JSON.parse(
|
|
2834
|
+
(await pfs.readFile(`${dir}/registry.json`)).toString()
|
|
2835
|
+
);
|
|
2836
|
+
delete registry.schemas[name];
|
|
2837
|
+
registry.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
2838
|
+
await pfs.writeFile(`${dir}/registry.json`, JSON.stringify(registry));
|
|
2839
|
+
const files = (await import_isomorphic_git.default.listFiles({
|
|
2840
|
+
fs,
|
|
2841
|
+
dir
|
|
2842
|
+
})).filter((i) => i !== "registry.lock");
|
|
2843
|
+
const hashes = await Promise.all(
|
|
2844
|
+
files.map(async (file) => {
|
|
2845
|
+
return [file, await generateSHA256Hash(file)];
|
|
2846
|
+
})
|
|
2847
|
+
);
|
|
2848
|
+
await pfs.writeFile(
|
|
2849
|
+
`${dir}/registry.lock`,
|
|
2850
|
+
JSON.stringify({
|
|
2851
|
+
updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2852
|
+
hashes
|
|
2853
|
+
})
|
|
2854
|
+
);
|
|
2855
|
+
await import_isomorphic_git.default.add({
|
|
2856
|
+
fs,
|
|
2857
|
+
dir,
|
|
2858
|
+
filepath: `registry.json`
|
|
2859
|
+
});
|
|
2860
|
+
await import_isomorphic_git.default.add({
|
|
2861
|
+
fs,
|
|
2862
|
+
dir,
|
|
2863
|
+
filepath: `registry.lock`
|
|
2864
|
+
});
|
|
2865
|
+
await import_isomorphic_git.default.commit({
|
|
2866
|
+
fs,
|
|
2867
|
+
dir,
|
|
2868
|
+
author: { name: "bot", email: "bot@registry.domain" },
|
|
2869
|
+
message: `chore: deleted schema: ${name}`
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
async function list() {
|
|
2873
|
+
await import_isomorphic_git.default.checkout({
|
|
2874
|
+
fs,
|
|
2875
|
+
dir,
|
|
2876
|
+
ref: main,
|
|
2877
|
+
force: true
|
|
2878
|
+
});
|
|
2879
|
+
const registry = JSON.parse(
|
|
2880
|
+
(await pfs.readFile(`${dir}/registry.json`)).toString()
|
|
2881
|
+
);
|
|
2882
|
+
const result = Object.entries(registry.schemas).map(
|
|
2883
|
+
([name, { version }]) => ({ name, version })
|
|
2884
|
+
);
|
|
2885
|
+
return result;
|
|
2886
|
+
}
|
|
2887
|
+
async function schema(name, version, migrations2 = false) {
|
|
2888
|
+
await import_isomorphic_git.default.checkout({
|
|
2889
|
+
fs,
|
|
2890
|
+
dir,
|
|
2891
|
+
ref: main,
|
|
2892
|
+
force: true
|
|
2893
|
+
});
|
|
2894
|
+
const registry = JSON.parse(
|
|
2895
|
+
(await pfs.readFile(`${dir}/registry.json`)).toString()
|
|
2896
|
+
);
|
|
2897
|
+
if (!registry.schemas[name]) {
|
|
2898
|
+
return null;
|
|
2899
|
+
}
|
|
2900
|
+
let ref = name;
|
|
2901
|
+
if (version) {
|
|
2902
|
+
ref = await import_isomorphic_git.default.resolveRef({ fs, dir, ref: `${name}-${version}` });
|
|
2903
|
+
}
|
|
2904
|
+
await import_isomorphic_git.default.checkout({
|
|
2905
|
+
fs,
|
|
2906
|
+
dir,
|
|
2907
|
+
ref,
|
|
2908
|
+
force: true
|
|
2909
|
+
});
|
|
2910
|
+
const schema2 = JSON.parse(
|
|
2911
|
+
(await pfs.readFile(`${dir}/${name}/schema.json`)).toString()
|
|
2912
|
+
);
|
|
2913
|
+
if (schema2.mock) {
|
|
2914
|
+
schema2.mock = deserializeGenerator(schema2.mock);
|
|
2915
|
+
}
|
|
2916
|
+
if (migrations2) {
|
|
2917
|
+
const migrations3 = await Promise.all(
|
|
2918
|
+
(await pfs.readdir(`${dir}/${name}/migrations`)).filter((i) => i !== ".nomedia").map(
|
|
2919
|
+
async (i) => (await pfs.readFile(`${dir}/${name}/migrations/${i}`)).toString()
|
|
2920
|
+
)
|
|
2921
|
+
);
|
|
2922
|
+
schema2.migrations = migrations3.map((m) => {
|
|
2923
|
+
const migration = JSON.parse(m);
|
|
2924
|
+
migration.transform = {
|
|
2925
|
+
forward: deserializeFunction(migration.transform.forward),
|
|
2926
|
+
backward: deserializeFunction(migration.transform.backward)
|
|
2927
|
+
};
|
|
2928
|
+
return migration;
|
|
2929
|
+
});
|
|
2930
|
+
}
|
|
2931
|
+
await import_isomorphic_git.default.checkout({
|
|
2932
|
+
fs,
|
|
2933
|
+
dir,
|
|
2934
|
+
ref: main,
|
|
2935
|
+
force: true
|
|
2936
|
+
});
|
|
2937
|
+
return schema2;
|
|
2938
|
+
}
|
|
2939
|
+
async function stats(schema2) {
|
|
2940
|
+
await import_isomorphic_git.default.checkout({
|
|
2941
|
+
fs,
|
|
2942
|
+
dir,
|
|
2943
|
+
ref: main,
|
|
2944
|
+
force: true
|
|
2945
|
+
});
|
|
2946
|
+
if (schema2) {
|
|
2947
|
+
const index = JSON.parse(
|
|
2948
|
+
(await pfs.readFile(`${dir}/${schema2}/index.json`)).toString()
|
|
2949
|
+
);
|
|
2950
|
+
return index;
|
|
2951
|
+
} else {
|
|
2952
|
+
const index = JSON.parse(
|
|
2953
|
+
(await pfs.readFile(`${dir}/registry.json`)).toString()
|
|
2954
|
+
);
|
|
2955
|
+
return index;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
async function sync() {
|
|
2959
|
+
await import_isomorphic_git.default.pull({
|
|
2960
|
+
fs,
|
|
2961
|
+
dir,
|
|
2962
|
+
http: import_web.default,
|
|
2963
|
+
remote: "origin",
|
|
2964
|
+
corsProxy: proxy,
|
|
2965
|
+
ref: main,
|
|
2966
|
+
author: { name: "bot", email: "bot@registry.domain" },
|
|
2967
|
+
fastForwardOnly: true,
|
|
2968
|
+
url: authUrl(
|
|
2969
|
+
`https://github.com/${credentials.username}/${credentials.repository}.git`,
|
|
2970
|
+
credentials.username,
|
|
2971
|
+
credentials.password
|
|
2972
|
+
),
|
|
2973
|
+
onAuth: () => ({
|
|
2974
|
+
username: credentials.username,
|
|
2975
|
+
password: credentials.password
|
|
2976
|
+
})
|
|
2977
|
+
});
|
|
2978
|
+
await import_isomorphic_git.default.push({
|
|
2979
|
+
fs,
|
|
2980
|
+
http: import_web.default,
|
|
2981
|
+
dir,
|
|
2982
|
+
corsProxy: proxy,
|
|
2983
|
+
remote: "origin",
|
|
2984
|
+
ref: main,
|
|
2985
|
+
url: authUrl(
|
|
2986
|
+
`https://github.com/${credentials.username}/${credentials.repository}.git`,
|
|
2987
|
+
credentials.username,
|
|
2988
|
+
credentials.password
|
|
2989
|
+
),
|
|
2990
|
+
onAuth: () => ({
|
|
2991
|
+
username: credentials.username,
|
|
2992
|
+
password: credentials.password
|
|
2993
|
+
})
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2996
|
+
async function predicates(name, version) {
|
|
2997
|
+
throw new Error("predicates not implemented");
|
|
2998
|
+
}
|
|
2999
|
+
async function migrations(name, version) {
|
|
3000
|
+
await import_isomorphic_git.default.checkout({
|
|
3001
|
+
fs,
|
|
3002
|
+
dir,
|
|
3003
|
+
ref: main,
|
|
3004
|
+
force: true
|
|
3005
|
+
});
|
|
3006
|
+
const registry = JSON.parse(
|
|
3007
|
+
(await pfs.readFile(`${dir}/registry.json`)).toString()
|
|
3008
|
+
);
|
|
3009
|
+
if (!registry.schemas[name]) {
|
|
3010
|
+
return [];
|
|
3011
|
+
}
|
|
3012
|
+
let effectiveVersion = version || registry.schemas[name].version;
|
|
3013
|
+
const index = JSON.parse(
|
|
3014
|
+
(await pfs.readFile(`${dir}/${name}/index.json`)).toString()
|
|
3015
|
+
);
|
|
3016
|
+
const compatibleMigrations = [];
|
|
3017
|
+
for (const migrationId of Object.keys(index.migrations)) {
|
|
3018
|
+
const { hash } = index.migrations[migrationId];
|
|
3019
|
+
const migrationFile = `${dir}/${name}/migrations/${hash}.json`;
|
|
3020
|
+
const migrationContent = JSON.parse(
|
|
3021
|
+
(await pfs.readFile(migrationFile)).toString()
|
|
3022
|
+
);
|
|
3023
|
+
if (compareSemanticVersions(
|
|
3024
|
+
migrationContent.schemaVersion,
|
|
3025
|
+
effectiveVersion
|
|
3026
|
+
) <= 0) {
|
|
3027
|
+
migrationContent.transform = {
|
|
3028
|
+
forward: deserializeFunction(migrationContent.transform.forward),
|
|
3029
|
+
backward: deserializeFunction(migrationContent.transform.backward)
|
|
3030
|
+
};
|
|
3031
|
+
compatibleMigrations.push(migrationContent);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
return compatibleMigrations.sort(
|
|
3035
|
+
(a, b) => compareSemanticVersions(a.schemaVersion, b.schemaVersion)
|
|
3036
|
+
);
|
|
3037
|
+
}
|
|
3038
|
+
async function history(name) {
|
|
3039
|
+
await import_isomorphic_git.default.checkout({
|
|
3040
|
+
fs,
|
|
3041
|
+
dir,
|
|
3042
|
+
ref: main,
|
|
3043
|
+
force: true
|
|
3044
|
+
});
|
|
3045
|
+
const registry = JSON.parse(
|
|
3046
|
+
(await pfs.readFile(`${dir}/registry.json`)).toString()
|
|
3047
|
+
);
|
|
3048
|
+
if (!registry.schemas[name]) {
|
|
3049
|
+
return [];
|
|
3050
|
+
}
|
|
3051
|
+
const index = JSON.parse(
|
|
3052
|
+
(await pfs.readFile(`${dir}/${name}/index.json`)).toString()
|
|
3053
|
+
);
|
|
3054
|
+
const historicalSchemas = [];
|
|
3055
|
+
for (const historyEntry of index.history) {
|
|
3056
|
+
const schemaDefinition = await schema(name, historyEntry.version, true);
|
|
3057
|
+
if (schemaDefinition) {
|
|
3058
|
+
historicalSchemas.push(schemaDefinition);
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
return historicalSchemas.sort(
|
|
3062
|
+
(a, b) => compareSemanticVersions(a.version, b.version)
|
|
3063
|
+
);
|
|
3064
|
+
}
|
|
3065
|
+
return {
|
|
3066
|
+
init,
|
|
3067
|
+
clone,
|
|
3068
|
+
create,
|
|
3069
|
+
update,
|
|
3070
|
+
delete: delete_,
|
|
3071
|
+
list,
|
|
3072
|
+
schema,
|
|
3073
|
+
stats,
|
|
3074
|
+
sync,
|
|
3075
|
+
predicates,
|
|
3076
|
+
migrations,
|
|
3077
|
+
history
|
|
3078
|
+
};
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
// src/lib/schema/index.ts
|
|
3082
|
+
var schema_exports = {};
|
|
3083
|
+
__export(schema_exports, {
|
|
3084
|
+
MigrationSchema: () => MigrationSchema,
|
|
3085
|
+
createSchemaMigrationHelper: () => createSchemaMigrationHelper,
|
|
3086
|
+
validate: () => validate,
|
|
3087
|
+
validateMigration: () => validateMigration,
|
|
3088
|
+
validateSchemaChange: () => validateSchemaChange,
|
|
3089
|
+
validateSchemaDefinition: () => validateSchemaDefinition
|
|
3090
|
+
});
|
|
3091
|
+
|
|
3092
|
+
// src/tools/typegen.ts
|
|
3093
|
+
var typegen_exports = {};
|
|
3094
|
+
__export(typegen_exports, {
|
|
3095
|
+
default: () => typegen_default,
|
|
3096
|
+
schemaToTypes: () => schemaToTypes
|
|
3097
|
+
});
|
|
3098
|
+
function convertFieldTypeToTS(field, parentType, fieldName) {
|
|
3099
|
+
switch (field.type) {
|
|
3100
|
+
case "string":
|
|
3101
|
+
return "string";
|
|
3102
|
+
case "number":
|
|
3103
|
+
return "number";
|
|
3104
|
+
case "boolean":
|
|
3105
|
+
return "boolean";
|
|
3106
|
+
case "array":
|
|
3107
|
+
if (field.itemsType) {
|
|
3108
|
+
if (field.itemsType === "object" && field.nestedSchema) {
|
|
3109
|
+
const nestedTypeName = `${parentType}ItemsItem`;
|
|
3110
|
+
return `${nestedTypeName}[]`;
|
|
3111
|
+
}
|
|
3112
|
+
return `${field.itemsType}[]`;
|
|
3113
|
+
}
|
|
3114
|
+
return "any[]";
|
|
3115
|
+
case "object":
|
|
3116
|
+
if (field.nestedSchema) {
|
|
3117
|
+
return `${parentType}${capitalize(fieldName)}`;
|
|
3118
|
+
}
|
|
3119
|
+
return "Record<string, any>";
|
|
3120
|
+
case "dynamic":
|
|
3121
|
+
return "any";
|
|
3122
|
+
default:
|
|
3123
|
+
return "any";
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
function generateNestedTypes(fields, parentName) {
|
|
3127
|
+
let types = "";
|
|
3128
|
+
for (const [fieldName, field] of Object.entries(fields)) {
|
|
3129
|
+
if (field.type === "object" && field.nestedSchema) {
|
|
3130
|
+
const typeName = `${parentName}${capitalize(fieldName)}`;
|
|
3131
|
+
types += `
|
|
3132
|
+
export interface ${typeName} {
|
|
3133
|
+
${generateTypeProperties(field.nestedSchema, typeName)}
|
|
3134
|
+
}`;
|
|
3135
|
+
const nestedTypes = generateNestedTypes(field.nestedSchema, typeName);
|
|
3136
|
+
if (nestedTypes) {
|
|
3137
|
+
types += `
|
|
3138
|
+
${nestedTypes}`;
|
|
3139
|
+
}
|
|
3140
|
+
} else if (field.type === "array" && field.itemsType === "object" && field.nestedSchema) {
|
|
3141
|
+
const typeName = `${parentName}ItemsItem`;
|
|
3142
|
+
types += `
|
|
3143
|
+
export interface ${typeName} {
|
|
3144
|
+
${generateTypeProperties(field.nestedSchema, typeName)}
|
|
3145
|
+
}`;
|
|
3146
|
+
const nestedTypes = generateNestedTypes(field.nestedSchema, typeName);
|
|
3147
|
+
if (nestedTypes) {
|
|
3148
|
+
types += `
|
|
3149
|
+
${nestedTypes}`;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
return types;
|
|
3154
|
+
}
|
|
3155
|
+
function generateTypeProperties(fields, parentType) {
|
|
3156
|
+
return Object.entries(fields).map(([fieldName, field]) => {
|
|
3157
|
+
const lines = [];
|
|
3158
|
+
if (field.description) {
|
|
3159
|
+
lines.push(` /** ${field.description} */`);
|
|
3160
|
+
}
|
|
3161
|
+
if (field.deprecated) {
|
|
3162
|
+
lines.push(" /** @deprecated */");
|
|
3163
|
+
}
|
|
3164
|
+
const optional = !field.required ? "?" : "";
|
|
3165
|
+
const tsType = convertFieldTypeToTS(field, parentType, fieldName);
|
|
3166
|
+
lines.push(` ${fieldName}${optional}: ${tsType};`);
|
|
3167
|
+
return lines.join("\n");
|
|
3168
|
+
}).join("\n");
|
|
3169
|
+
}
|
|
3170
|
+
function capitalize(str) {
|
|
3171
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
3172
|
+
}
|
|
3173
|
+
function schemaToTypes(schema) {
|
|
3174
|
+
const mainTypeName = capitalize(schema.name);
|
|
3175
|
+
let output = `// Generated from schema version ${schema.version}
|
|
3176
|
+
`;
|
|
3177
|
+
if (schema.description) {
|
|
3178
|
+
output += `/** ${schema.description} */
|
|
3179
|
+
`;
|
|
3180
|
+
}
|
|
3181
|
+
output += `export interface ${mainTypeName} {
|
|
3182
|
+
${generateTypeProperties(schema.fields, mainTypeName)}
|
|
3183
|
+
}`;
|
|
3184
|
+
const nestedTypes = generateNestedTypes(schema.fields, mainTypeName);
|
|
3185
|
+
if (nestedTypes) {
|
|
3186
|
+
output += `
|
|
3187
|
+
${nestedTypes}`;
|
|
3188
|
+
}
|
|
3189
|
+
output += "\n";
|
|
3190
|
+
return output;
|
|
3191
|
+
}
|
|
3192
|
+
var typegen_default = schemaToTypes;
|
|
3193
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3194
|
+
0 && (module.exports = {
|
|
3195
|
+
MigrationTypes,
|
|
3196
|
+
PersistenceTypes,
|
|
3197
|
+
RegistryType,
|
|
3198
|
+
SchemaTypes,
|
|
3199
|
+
crypto,
|
|
3200
|
+
merge,
|
|
3201
|
+
migration,
|
|
3202
|
+
patch,
|
|
3203
|
+
persistence,
|
|
3204
|
+
registry,
|
|
3205
|
+
schema,
|
|
3206
|
+
typegen,
|
|
3207
|
+
validator,
|
|
3208
|
+
version
|
|
3209
|
+
});
|