@objectstack/metadata 1.0.2 → 1.0.5
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/.turbo/turbo-build.log +22 -0
- package/CHANGELOG.md +31 -0
- package/dist/index.d.mts +306 -0
- package/dist/index.d.ts +305 -16
- package/dist/index.js +1078 -15
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1040 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +7 -7
- package/tsconfig.json +1 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/loaders/filesystem-loader.d.ts +0 -42
- package/dist/loaders/filesystem-loader.d.ts.map +0 -1
- package/dist/loaders/filesystem-loader.js +0 -342
- package/dist/loaders/loader-interface.d.ts +0 -60
- package/dist/loaders/loader-interface.d.ts.map +0 -1
- package/dist/loaders/loader-interface.js +0 -6
- package/dist/loaders/memory-loader.d.ts +0 -19
- package/dist/loaders/memory-loader.d.ts.map +0 -1
- package/dist/loaders/memory-loader.js +0 -71
- package/dist/loaders/remote-loader.d.ts +0 -22
- package/dist/loaders/remote-loader.d.ts.map +0 -1
- package/dist/loaders/remote-loader.js +0 -103
- package/dist/metadata-manager.d.ts +0 -71
- package/dist/metadata-manager.d.ts.map +0 -1
- package/dist/metadata-manager.js +0 -211
- package/dist/migration/executor.d.ts +0 -9
- package/dist/migration/executor.d.ts.map +0 -1
- package/dist/migration/executor.js +0 -49
- package/dist/migration/index.d.ts +0 -2
- package/dist/migration/index.d.ts.map +0 -1
- package/dist/migration/index.js +0 -1
- package/dist/node-metadata-manager.d.ts +0 -26
- package/dist/node-metadata-manager.d.ts.map +0 -1
- package/dist/node-metadata-manager.js +0 -98
- package/dist/node.d.ts +0 -8
- package/dist/node.d.ts.map +0 -1
- package/dist/node.js +0 -7
- package/dist/plugin.d.ts +0 -15
- package/dist/plugin.d.ts.map +0 -1
- package/dist/plugin.js +0 -71
- package/dist/serializers/json-serializer.d.ts +0 -20
- package/dist/serializers/json-serializer.d.ts.map +0 -1
- package/dist/serializers/json-serializer.js +0 -53
- package/dist/serializers/serializer-interface.d.ts +0 -57
- package/dist/serializers/serializer-interface.d.ts.map +0 -1
- package/dist/serializers/serializer-interface.js +0 -6
- package/dist/serializers/serializers.test.d.ts +0 -2
- package/dist/serializers/serializers.test.d.ts.map +0 -1
- package/dist/serializers/serializers.test.js +0 -62
- package/dist/serializers/typescript-serializer.d.ts +0 -18
- package/dist/serializers/typescript-serializer.d.ts.map +0 -1
- package/dist/serializers/typescript-serializer.js +0 -103
- package/dist/serializers/yaml-serializer.d.ts +0 -16
- package/dist/serializers/yaml-serializer.d.ts.map +0 -1
- package/dist/serializers/yaml-serializer.js +0 -35
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1040 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/metadata-manager.ts
|
|
8
|
+
import { createLogger } from "@objectstack/core";
|
|
9
|
+
|
|
10
|
+
// src/serializers/json-serializer.ts
|
|
11
|
+
var JSONSerializer = class {
|
|
12
|
+
serialize(item, options) {
|
|
13
|
+
const { prettify = true, indent = 2, sortKeys = false } = options || {};
|
|
14
|
+
if (sortKeys) {
|
|
15
|
+
const sorted = this.sortObjectKeys(item);
|
|
16
|
+
return prettify ? JSON.stringify(sorted, null, indent) : JSON.stringify(sorted);
|
|
17
|
+
}
|
|
18
|
+
return prettify ? JSON.stringify(item, null, indent) : JSON.stringify(item);
|
|
19
|
+
}
|
|
20
|
+
deserialize(content, schema) {
|
|
21
|
+
const parsed = JSON.parse(content);
|
|
22
|
+
if (schema) {
|
|
23
|
+
return schema.parse(parsed);
|
|
24
|
+
}
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
getExtension() {
|
|
28
|
+
return ".json";
|
|
29
|
+
}
|
|
30
|
+
canHandle(format) {
|
|
31
|
+
return format === "json";
|
|
32
|
+
}
|
|
33
|
+
getFormat() {
|
|
34
|
+
return "json";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Recursively sort object keys
|
|
38
|
+
*/
|
|
39
|
+
sortObjectKeys(obj) {
|
|
40
|
+
if (obj === null || typeof obj !== "object") {
|
|
41
|
+
return obj;
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(obj)) {
|
|
44
|
+
return obj.map((item) => this.sortObjectKeys(item));
|
|
45
|
+
}
|
|
46
|
+
const sorted = {};
|
|
47
|
+
const keys = Object.keys(obj).sort();
|
|
48
|
+
for (const key of keys) {
|
|
49
|
+
sorted[key] = this.sortObjectKeys(obj[key]);
|
|
50
|
+
}
|
|
51
|
+
return sorted;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/serializers/yaml-serializer.ts
|
|
56
|
+
import * as yaml from "js-yaml";
|
|
57
|
+
var YAMLSerializer = class {
|
|
58
|
+
serialize(item, options) {
|
|
59
|
+
const { indent = 2, sortKeys = false } = options || {};
|
|
60
|
+
return yaml.dump(item, {
|
|
61
|
+
indent,
|
|
62
|
+
sortKeys,
|
|
63
|
+
lineWidth: -1,
|
|
64
|
+
// Disable line wrapping
|
|
65
|
+
noRefs: true
|
|
66
|
+
// Disable YAML references
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
deserialize(content, schema) {
|
|
70
|
+
const parsed = yaml.load(content, { schema: yaml.JSON_SCHEMA });
|
|
71
|
+
if (schema) {
|
|
72
|
+
return schema.parse(parsed);
|
|
73
|
+
}
|
|
74
|
+
return parsed;
|
|
75
|
+
}
|
|
76
|
+
getExtension() {
|
|
77
|
+
return ".yaml";
|
|
78
|
+
}
|
|
79
|
+
canHandle(format) {
|
|
80
|
+
return format === "yaml";
|
|
81
|
+
}
|
|
82
|
+
getFormat() {
|
|
83
|
+
return "yaml";
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// src/serializers/typescript-serializer.ts
|
|
88
|
+
var TypeScriptSerializer = class {
|
|
89
|
+
constructor(format = "typescript") {
|
|
90
|
+
this.format = format;
|
|
91
|
+
}
|
|
92
|
+
serialize(item, options) {
|
|
93
|
+
const { prettify = true, indent = 2 } = options || {};
|
|
94
|
+
const jsonStr = JSON.stringify(item, null, prettify ? indent : 0);
|
|
95
|
+
if (this.format === "typescript") {
|
|
96
|
+
return `import type { ServiceObject } from '@objectstack/spec/data';
|
|
97
|
+
|
|
98
|
+
export const metadata: ServiceObject = ${jsonStr};
|
|
99
|
+
|
|
100
|
+
export default metadata;
|
|
101
|
+
`;
|
|
102
|
+
} else {
|
|
103
|
+
return `export const metadata = ${jsonStr};
|
|
104
|
+
|
|
105
|
+
export default metadata;
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
deserialize(content, schema) {
|
|
110
|
+
let objectStart = content.indexOf("export const");
|
|
111
|
+
if (objectStart === -1) {
|
|
112
|
+
objectStart = content.indexOf("export default");
|
|
113
|
+
}
|
|
114
|
+
if (objectStart === -1) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
'Could not parse TypeScript/JavaScript module. Expected export pattern: "export const metadata = {...};" or "export default {...};"'
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const braceStart = content.indexOf("{", objectStart);
|
|
120
|
+
if (braceStart === -1) {
|
|
121
|
+
throw new Error("Could not find object literal in export statement");
|
|
122
|
+
}
|
|
123
|
+
let braceCount = 0;
|
|
124
|
+
let braceEnd = -1;
|
|
125
|
+
let inString = false;
|
|
126
|
+
let stringChar = "";
|
|
127
|
+
for (let i = braceStart; i < content.length; i++) {
|
|
128
|
+
const char = content[i];
|
|
129
|
+
const prevChar = i > 0 ? content[i - 1] : "";
|
|
130
|
+
if ((char === '"' || char === "'") && prevChar !== "\\") {
|
|
131
|
+
if (!inString) {
|
|
132
|
+
inString = true;
|
|
133
|
+
stringChar = char;
|
|
134
|
+
} else if (char === stringChar) {
|
|
135
|
+
inString = false;
|
|
136
|
+
stringChar = "";
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!inString) {
|
|
140
|
+
if (char === "{") braceCount++;
|
|
141
|
+
if (char === "}") {
|
|
142
|
+
braceCount--;
|
|
143
|
+
if (braceCount === 0) {
|
|
144
|
+
braceEnd = i;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (braceEnd === -1) {
|
|
151
|
+
throw new Error("Could not find matching closing brace for object literal");
|
|
152
|
+
}
|
|
153
|
+
const objectLiteral = content.substring(braceStart, braceEnd + 1);
|
|
154
|
+
try {
|
|
155
|
+
const parsed = JSON.parse(objectLiteral);
|
|
156
|
+
if (schema) {
|
|
157
|
+
return schema.parse(parsed);
|
|
158
|
+
}
|
|
159
|
+
return parsed;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Failed to parse object literal as JSON: ${error instanceof Error ? error.message : String(error)}. Make sure the TypeScript/JavaScript object uses JSON-compatible syntax (no functions, comments, or trailing commas).`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
getExtension() {
|
|
167
|
+
return this.format === "typescript" ? ".ts" : ".js";
|
|
168
|
+
}
|
|
169
|
+
canHandle(format) {
|
|
170
|
+
return format === "typescript" || format === "javascript";
|
|
171
|
+
}
|
|
172
|
+
getFormat() {
|
|
173
|
+
return this.format;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// src/metadata-manager.ts
|
|
178
|
+
var MetadataManager = class {
|
|
179
|
+
constructor(config) {
|
|
180
|
+
this.loaders = /* @__PURE__ */ new Map();
|
|
181
|
+
this.watchCallbacks = /* @__PURE__ */ new Map();
|
|
182
|
+
this.config = config;
|
|
183
|
+
this.logger = createLogger({ level: "info", format: "pretty" });
|
|
184
|
+
this.serializers = /* @__PURE__ */ new Map();
|
|
185
|
+
const formats = config.formats || ["typescript", "json", "yaml"];
|
|
186
|
+
if (formats.includes("json")) {
|
|
187
|
+
this.serializers.set("json", new JSONSerializer());
|
|
188
|
+
}
|
|
189
|
+
if (formats.includes("yaml")) {
|
|
190
|
+
this.serializers.set("yaml", new YAMLSerializer());
|
|
191
|
+
}
|
|
192
|
+
if (formats.includes("typescript")) {
|
|
193
|
+
this.serializers.set("typescript", new TypeScriptSerializer("typescript"));
|
|
194
|
+
}
|
|
195
|
+
if (formats.includes("javascript")) {
|
|
196
|
+
this.serializers.set("javascript", new TypeScriptSerializer("javascript"));
|
|
197
|
+
}
|
|
198
|
+
if (config.loaders && config.loaders.length > 0) {
|
|
199
|
+
config.loaders.forEach((loader) => this.registerLoader(loader));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Register a new metadata loader (data source)
|
|
204
|
+
*/
|
|
205
|
+
registerLoader(loader) {
|
|
206
|
+
this.loaders.set(loader.contract.name, loader);
|
|
207
|
+
this.logger.info(`Registered metadata loader: ${loader.contract.name} (${loader.contract.protocol})`);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Load a single metadata item
|
|
211
|
+
* Iterates through registered loaders until found
|
|
212
|
+
*/
|
|
213
|
+
async load(type, name, options) {
|
|
214
|
+
for (const loader of this.loaders.values()) {
|
|
215
|
+
try {
|
|
216
|
+
const result = await loader.load(type, name, options);
|
|
217
|
+
if (result.data) {
|
|
218
|
+
return result.data;
|
|
219
|
+
}
|
|
220
|
+
} catch (e) {
|
|
221
|
+
this.logger.warn(`Loader ${loader.contract.name} failed to load ${type}:${name}`, { error: e });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Load multiple metadata items
|
|
228
|
+
* Aggregates results from all loaders
|
|
229
|
+
*/
|
|
230
|
+
async loadMany(type, options) {
|
|
231
|
+
const results = [];
|
|
232
|
+
for (const loader of this.loaders.values()) {
|
|
233
|
+
try {
|
|
234
|
+
const items = await loader.loadMany(type, options);
|
|
235
|
+
for (const item of items) {
|
|
236
|
+
results.push(item);
|
|
237
|
+
}
|
|
238
|
+
} catch (e) {
|
|
239
|
+
this.logger.warn(`Loader ${loader.contract.name} failed to loadMany ${type}`, { error: e });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return results;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Save metadata to disk
|
|
246
|
+
*/
|
|
247
|
+
/**
|
|
248
|
+
* Save metadata item
|
|
249
|
+
*/
|
|
250
|
+
async save(type, name, data, options) {
|
|
251
|
+
const targetLoader = options?.loader;
|
|
252
|
+
let loader;
|
|
253
|
+
if (targetLoader) {
|
|
254
|
+
loader = this.loaders.get(targetLoader);
|
|
255
|
+
if (!loader) {
|
|
256
|
+
throw new Error(`Loader not found: ${targetLoader}`);
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
for (const l of this.loaders.values()) {
|
|
260
|
+
if (!l.save) continue;
|
|
261
|
+
try {
|
|
262
|
+
if (await l.exists(type, name)) {
|
|
263
|
+
loader = l;
|
|
264
|
+
this.logger.info(`Updating existing metadata in loader: ${l.contract.name}`);
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
} catch (e) {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (!loader) {
|
|
271
|
+
const fsLoader = this.loaders.get("filesystem");
|
|
272
|
+
if (fsLoader && fsLoader.save) {
|
|
273
|
+
loader = fsLoader;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (!loader) {
|
|
277
|
+
for (const l of this.loaders.values()) {
|
|
278
|
+
if (l.save) {
|
|
279
|
+
loader = l;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (!loader) {
|
|
286
|
+
throw new Error(`No loader available for saving type: ${type}`);
|
|
287
|
+
}
|
|
288
|
+
if (!loader.save) {
|
|
289
|
+
throw new Error(`Loader '${loader.contract?.name}' does not support saving`);
|
|
290
|
+
}
|
|
291
|
+
return loader.save(type, name, data, options);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Check if metadata item exists
|
|
295
|
+
*/
|
|
296
|
+
async exists(type, name) {
|
|
297
|
+
for (const loader of this.loaders.values()) {
|
|
298
|
+
if (await loader.exists(type, name)) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* List all items of a type
|
|
306
|
+
*/
|
|
307
|
+
async list(type) {
|
|
308
|
+
const items = /* @__PURE__ */ new Set();
|
|
309
|
+
for (const loader of this.loaders.values()) {
|
|
310
|
+
const result = await loader.list(type);
|
|
311
|
+
result.forEach((item) => items.add(item));
|
|
312
|
+
}
|
|
313
|
+
return Array.from(items);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Watch for metadata changes
|
|
317
|
+
*/
|
|
318
|
+
watch(type, callback) {
|
|
319
|
+
if (!this.watchCallbacks.has(type)) {
|
|
320
|
+
this.watchCallbacks.set(type, /* @__PURE__ */ new Set());
|
|
321
|
+
}
|
|
322
|
+
this.watchCallbacks.get(type).add(callback);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Unwatch metadata changes
|
|
326
|
+
*/
|
|
327
|
+
unwatch(type, callback) {
|
|
328
|
+
const callbacks = this.watchCallbacks.get(type);
|
|
329
|
+
if (callbacks) {
|
|
330
|
+
callbacks.delete(callback);
|
|
331
|
+
if (callbacks.size === 0) {
|
|
332
|
+
this.watchCallbacks.delete(type);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Stop all watching
|
|
338
|
+
*/
|
|
339
|
+
async stopWatching() {
|
|
340
|
+
}
|
|
341
|
+
notifyWatchers(type, event) {
|
|
342
|
+
const callbacks = this.watchCallbacks.get(type);
|
|
343
|
+
if (!callbacks) return;
|
|
344
|
+
for (const callback of callbacks) {
|
|
345
|
+
try {
|
|
346
|
+
void callback(event);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
this.logger.error("Watch callback error", void 0, {
|
|
349
|
+
type,
|
|
350
|
+
error: error instanceof Error ? error.message : String(error)
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// src/node-metadata-manager.ts
|
|
358
|
+
import * as path2 from "path";
|
|
359
|
+
import { watch as chokidarWatch } from "chokidar";
|
|
360
|
+
|
|
361
|
+
// src/loaders/filesystem-loader.ts
|
|
362
|
+
import * as fs from "fs/promises";
|
|
363
|
+
import * as path from "path";
|
|
364
|
+
import { glob } from "glob";
|
|
365
|
+
import { createHash } from "crypto";
|
|
366
|
+
var FilesystemLoader = class {
|
|
367
|
+
constructor(rootDir, serializers, logger) {
|
|
368
|
+
this.rootDir = rootDir;
|
|
369
|
+
this.serializers = serializers;
|
|
370
|
+
this.logger = logger;
|
|
371
|
+
this.contract = {
|
|
372
|
+
name: "filesystem",
|
|
373
|
+
protocol: "file",
|
|
374
|
+
capabilities: {
|
|
375
|
+
read: true,
|
|
376
|
+
write: true,
|
|
377
|
+
watch: true,
|
|
378
|
+
list: true
|
|
379
|
+
},
|
|
380
|
+
supportedFormats: ["json", "yaml", "typescript", "javascript"],
|
|
381
|
+
supportsWatch: true,
|
|
382
|
+
supportsWrite: true,
|
|
383
|
+
supportsCache: true
|
|
384
|
+
};
|
|
385
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
386
|
+
}
|
|
387
|
+
async load(type, name, options) {
|
|
388
|
+
const startTime = Date.now();
|
|
389
|
+
const { validate: _validate = true, useCache = true, ifNoneMatch } = options || {};
|
|
390
|
+
try {
|
|
391
|
+
const filePath = await this.findFile(type, name);
|
|
392
|
+
if (!filePath) {
|
|
393
|
+
return {
|
|
394
|
+
data: null,
|
|
395
|
+
fromCache: false,
|
|
396
|
+
notModified: false,
|
|
397
|
+
loadTime: Date.now() - startTime
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
const stats = await this.stat(type, name);
|
|
401
|
+
if (!stats) {
|
|
402
|
+
return {
|
|
403
|
+
data: null,
|
|
404
|
+
fromCache: false,
|
|
405
|
+
notModified: false,
|
|
406
|
+
loadTime: Date.now() - startTime
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
if (useCache && ifNoneMatch && stats.etag === ifNoneMatch) {
|
|
410
|
+
return {
|
|
411
|
+
data: null,
|
|
412
|
+
fromCache: true,
|
|
413
|
+
notModified: true,
|
|
414
|
+
etag: stats.etag,
|
|
415
|
+
stats,
|
|
416
|
+
loadTime: Date.now() - startTime
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const cacheKey = `${type}:${name}`;
|
|
420
|
+
if (useCache && this.cache.has(cacheKey)) {
|
|
421
|
+
const cached = this.cache.get(cacheKey);
|
|
422
|
+
if (cached.etag === stats.etag) {
|
|
423
|
+
return {
|
|
424
|
+
data: cached.data,
|
|
425
|
+
fromCache: true,
|
|
426
|
+
notModified: false,
|
|
427
|
+
etag: stats.etag,
|
|
428
|
+
stats,
|
|
429
|
+
loadTime: Date.now() - startTime
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
434
|
+
const serializer = this.getSerializer(stats.format);
|
|
435
|
+
if (!serializer) {
|
|
436
|
+
throw new Error(`No serializer found for format: ${stats.format}`);
|
|
437
|
+
}
|
|
438
|
+
const data = serializer.deserialize(content);
|
|
439
|
+
if (useCache) {
|
|
440
|
+
this.cache.set(cacheKey, {
|
|
441
|
+
data,
|
|
442
|
+
etag: stats.etag || "",
|
|
443
|
+
timestamp: Date.now()
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
data,
|
|
448
|
+
fromCache: false,
|
|
449
|
+
notModified: false,
|
|
450
|
+
etag: stats.etag,
|
|
451
|
+
stats,
|
|
452
|
+
loadTime: Date.now() - startTime
|
|
453
|
+
};
|
|
454
|
+
} catch (error) {
|
|
455
|
+
this.logger?.error("Failed to load metadata", void 0, {
|
|
456
|
+
type,
|
|
457
|
+
name,
|
|
458
|
+
error: error instanceof Error ? error.message : String(error)
|
|
459
|
+
});
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
async loadMany(type, options) {
|
|
464
|
+
const { patterns = ["**/*"], recursive: _recursive = true, limit } = options || {};
|
|
465
|
+
const typeDir = path.join(this.rootDir, type);
|
|
466
|
+
const items = [];
|
|
467
|
+
try {
|
|
468
|
+
const globPatterns = patterns.map(
|
|
469
|
+
(pattern) => path.join(typeDir, pattern)
|
|
470
|
+
);
|
|
471
|
+
for (const pattern of globPatterns) {
|
|
472
|
+
const files = await glob(pattern, {
|
|
473
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*"],
|
|
474
|
+
nodir: true
|
|
475
|
+
});
|
|
476
|
+
for (const file of files) {
|
|
477
|
+
if (limit && items.length >= limit) {
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
const content = await fs.readFile(file, "utf-8");
|
|
482
|
+
const format = this.detectFormat(file);
|
|
483
|
+
const serializer = this.getSerializer(format);
|
|
484
|
+
if (serializer) {
|
|
485
|
+
const data = serializer.deserialize(content);
|
|
486
|
+
items.push(data);
|
|
487
|
+
}
|
|
488
|
+
} catch (error) {
|
|
489
|
+
this.logger?.warn("Failed to load file", {
|
|
490
|
+
file,
|
|
491
|
+
error: error instanceof Error ? error.message : String(error)
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (limit && items.length >= limit) {
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return items;
|
|
500
|
+
} catch (error) {
|
|
501
|
+
this.logger?.error("Failed to load many", void 0, {
|
|
502
|
+
type,
|
|
503
|
+
patterns,
|
|
504
|
+
error: error instanceof Error ? error.message : String(error)
|
|
505
|
+
});
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async exists(type, name) {
|
|
510
|
+
const filePath = await this.findFile(type, name);
|
|
511
|
+
return filePath !== null;
|
|
512
|
+
}
|
|
513
|
+
async stat(type, name) {
|
|
514
|
+
const filePath = await this.findFile(type, name);
|
|
515
|
+
if (!filePath) {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
const stats = await fs.stat(filePath);
|
|
520
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
521
|
+
const etag = this.generateETag(content);
|
|
522
|
+
const format = this.detectFormat(filePath);
|
|
523
|
+
return {
|
|
524
|
+
size: stats.size,
|
|
525
|
+
modifiedAt: stats.mtime,
|
|
526
|
+
etag,
|
|
527
|
+
format,
|
|
528
|
+
path: filePath
|
|
529
|
+
};
|
|
530
|
+
} catch (error) {
|
|
531
|
+
this.logger?.error("Failed to stat file", void 0, {
|
|
532
|
+
type,
|
|
533
|
+
name,
|
|
534
|
+
filePath,
|
|
535
|
+
error: error instanceof Error ? error.message : String(error)
|
|
536
|
+
});
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async list(type) {
|
|
541
|
+
const typeDir = path.join(this.rootDir, type);
|
|
542
|
+
try {
|
|
543
|
+
const files = await glob("**/*", {
|
|
544
|
+
cwd: typeDir,
|
|
545
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*"],
|
|
546
|
+
nodir: true
|
|
547
|
+
});
|
|
548
|
+
return files.map((file) => {
|
|
549
|
+
const ext = path.extname(file);
|
|
550
|
+
const basename3 = path.basename(file, ext);
|
|
551
|
+
return basename3;
|
|
552
|
+
});
|
|
553
|
+
} catch (error) {
|
|
554
|
+
this.logger?.error("Failed to list", void 0, {
|
|
555
|
+
type,
|
|
556
|
+
error: error instanceof Error ? error.message : String(error)
|
|
557
|
+
});
|
|
558
|
+
return [];
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async save(type, name, data, options) {
|
|
562
|
+
const startTime = Date.now();
|
|
563
|
+
const {
|
|
564
|
+
format = "typescript",
|
|
565
|
+
prettify = true,
|
|
566
|
+
indent = 2,
|
|
567
|
+
sortKeys = false,
|
|
568
|
+
backup = false,
|
|
569
|
+
overwrite = true,
|
|
570
|
+
atomic = true,
|
|
571
|
+
path: customPath
|
|
572
|
+
} = options || {};
|
|
573
|
+
try {
|
|
574
|
+
const serializer = this.getSerializer(format);
|
|
575
|
+
if (!serializer) {
|
|
576
|
+
throw new Error(`No serializer found for format: ${format}`);
|
|
577
|
+
}
|
|
578
|
+
const typeDir = path.join(this.rootDir, type);
|
|
579
|
+
const fileName = `${name}${serializer.getExtension()}`;
|
|
580
|
+
const filePath = customPath || path.join(typeDir, fileName);
|
|
581
|
+
if (!overwrite) {
|
|
582
|
+
try {
|
|
583
|
+
await fs.access(filePath);
|
|
584
|
+
throw new Error(`File already exists: ${filePath}`);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
if (error.code !== "ENOENT") {
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
592
|
+
let backupPath;
|
|
593
|
+
if (backup) {
|
|
594
|
+
try {
|
|
595
|
+
await fs.access(filePath);
|
|
596
|
+
backupPath = `${filePath}.bak`;
|
|
597
|
+
await fs.copyFile(filePath, backupPath);
|
|
598
|
+
} catch {
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const content = serializer.serialize(data, {
|
|
602
|
+
prettify,
|
|
603
|
+
indent,
|
|
604
|
+
sortKeys
|
|
605
|
+
});
|
|
606
|
+
if (atomic) {
|
|
607
|
+
const tempPath = `${filePath}.tmp`;
|
|
608
|
+
await fs.writeFile(tempPath, content, "utf-8");
|
|
609
|
+
await fs.rename(tempPath, filePath);
|
|
610
|
+
} else {
|
|
611
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
success: true,
|
|
615
|
+
path: filePath,
|
|
616
|
+
// format, // Not in schema
|
|
617
|
+
size: Buffer.byteLength(content, "utf-8"),
|
|
618
|
+
backupPath,
|
|
619
|
+
saveTime: Date.now() - startTime
|
|
620
|
+
};
|
|
621
|
+
} catch (error) {
|
|
622
|
+
this.logger?.error("Failed to save metadata", void 0, {
|
|
623
|
+
type,
|
|
624
|
+
name,
|
|
625
|
+
error: error instanceof Error ? error.message : String(error)
|
|
626
|
+
});
|
|
627
|
+
throw error;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Find file for a given type and name
|
|
632
|
+
*/
|
|
633
|
+
async findFile(type, name) {
|
|
634
|
+
const typeDir = path.join(this.rootDir, type);
|
|
635
|
+
const extensions = [".json", ".yaml", ".yml", ".ts", ".js"];
|
|
636
|
+
for (const ext of extensions) {
|
|
637
|
+
const filePath = path.join(typeDir, `${name}${ext}`);
|
|
638
|
+
try {
|
|
639
|
+
await fs.access(filePath);
|
|
640
|
+
return filePath;
|
|
641
|
+
} catch {
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Detect format from file extension
|
|
648
|
+
*/
|
|
649
|
+
detectFormat(filePath) {
|
|
650
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
651
|
+
switch (ext) {
|
|
652
|
+
case ".json":
|
|
653
|
+
return "json";
|
|
654
|
+
case ".yaml":
|
|
655
|
+
case ".yml":
|
|
656
|
+
return "yaml";
|
|
657
|
+
case ".ts":
|
|
658
|
+
return "typescript";
|
|
659
|
+
case ".js":
|
|
660
|
+
return "javascript";
|
|
661
|
+
default:
|
|
662
|
+
return "json";
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Get serializer for format
|
|
667
|
+
*/
|
|
668
|
+
getSerializer(format) {
|
|
669
|
+
return this.serializers.get(format);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Generate ETag for content
|
|
673
|
+
* Uses SHA-256 hash truncated to 32 characters for reasonable collision resistance
|
|
674
|
+
* while keeping ETag headers compact (full 64-char hash is overkill for this use case)
|
|
675
|
+
*/
|
|
676
|
+
generateETag(content) {
|
|
677
|
+
const hash = createHash("sha256").update(content).digest("hex").substring(0, 32);
|
|
678
|
+
return `"${hash}"`;
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
// src/node-metadata-manager.ts
|
|
683
|
+
var NodeMetadataManager = class extends MetadataManager {
|
|
684
|
+
constructor(config) {
|
|
685
|
+
super(config);
|
|
686
|
+
if (!config.loaders || config.loaders.length === 0) {
|
|
687
|
+
const rootDir = config.rootDir || process.cwd();
|
|
688
|
+
this.registerLoader(new FilesystemLoader(rootDir, this.serializers, this.logger));
|
|
689
|
+
}
|
|
690
|
+
if (config.watch) {
|
|
691
|
+
this.startWatching();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Stop all watching
|
|
696
|
+
*/
|
|
697
|
+
async stopWatching() {
|
|
698
|
+
if (this.watcher) {
|
|
699
|
+
await this.watcher.close();
|
|
700
|
+
this.watcher = void 0;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Start watching for file changes
|
|
705
|
+
*/
|
|
706
|
+
startWatching() {
|
|
707
|
+
const rootDir = this.config.rootDir || process.cwd();
|
|
708
|
+
const { ignored = ["**/node_modules/**", "**/*.test.*"], persistent = true } = this.config.watchOptions || {};
|
|
709
|
+
this.watcher = chokidarWatch(rootDir, {
|
|
710
|
+
ignored,
|
|
711
|
+
persistent,
|
|
712
|
+
ignoreInitial: true
|
|
713
|
+
});
|
|
714
|
+
this.watcher.on("add", async (filePath) => {
|
|
715
|
+
await this.handleFileEvent("added", filePath);
|
|
716
|
+
});
|
|
717
|
+
this.watcher.on("change", async (filePath) => {
|
|
718
|
+
await this.handleFileEvent("changed", filePath);
|
|
719
|
+
});
|
|
720
|
+
this.watcher.on("unlink", async (filePath) => {
|
|
721
|
+
await this.handleFileEvent("deleted", filePath);
|
|
722
|
+
});
|
|
723
|
+
this.logger.info("File watcher started", { rootDir });
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Handle file change events
|
|
727
|
+
*/
|
|
728
|
+
async handleFileEvent(eventType, filePath) {
|
|
729
|
+
const rootDir = this.config.rootDir || process.cwd();
|
|
730
|
+
const relativePath = path2.relative(rootDir, filePath);
|
|
731
|
+
const parts = relativePath.split(path2.sep);
|
|
732
|
+
if (parts.length < 2) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const type = parts[0];
|
|
736
|
+
const fileName = parts[parts.length - 1];
|
|
737
|
+
const name = path2.basename(fileName, path2.extname(fileName));
|
|
738
|
+
let data = void 0;
|
|
739
|
+
if (eventType !== "deleted") {
|
|
740
|
+
try {
|
|
741
|
+
data = await this.load(type, name, { useCache: false });
|
|
742
|
+
} catch (error) {
|
|
743
|
+
this.logger.error("Failed to load changed file", void 0, {
|
|
744
|
+
filePath,
|
|
745
|
+
error: error instanceof Error ? error.message : String(error)
|
|
746
|
+
});
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
const event = {
|
|
751
|
+
type: eventType,
|
|
752
|
+
metadataType: type,
|
|
753
|
+
name,
|
|
754
|
+
path: filePath,
|
|
755
|
+
data,
|
|
756
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
757
|
+
};
|
|
758
|
+
this.notifyWatchers(type, event);
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// src/plugin.ts
|
|
763
|
+
import { ObjectStackDefinitionSchema } from "@objectstack/spec";
|
|
764
|
+
var MetadataPlugin = class {
|
|
765
|
+
constructor(options = {}) {
|
|
766
|
+
this.name = "com.objectstack.metadata";
|
|
767
|
+
this.version = "1.0.0";
|
|
768
|
+
this.init = async (ctx) => {
|
|
769
|
+
ctx.logger.info("Initializing Metadata Manager", { root: this.options.rootDir || process.cwd() });
|
|
770
|
+
ctx.registerService("metadata", this.manager);
|
|
771
|
+
};
|
|
772
|
+
this.start = async (ctx) => {
|
|
773
|
+
ctx.logger.info("Loading metadata...");
|
|
774
|
+
const metadataTypes = Object.keys(ObjectStackDefinitionSchema.shape).filter((key) => key !== "manifest");
|
|
775
|
+
for (const type of metadataTypes) {
|
|
776
|
+
try {
|
|
777
|
+
const items = await this.manager.loadMany(type, {
|
|
778
|
+
recursive: true
|
|
779
|
+
});
|
|
780
|
+
if (items.length > 0) {
|
|
781
|
+
ctx.logger.info(`Loaded ${items.length} ${type}`);
|
|
782
|
+
const ql = ctx.getService("objectql");
|
|
783
|
+
if (ql && ql.registry) {
|
|
784
|
+
items.forEach((item) => {
|
|
785
|
+
const keyField = item.id ? "id" : "name";
|
|
786
|
+
let registryType = type;
|
|
787
|
+
if (type === "objects") registryType = "object";
|
|
788
|
+
if (type === "apps") registryType = "app";
|
|
789
|
+
if (type === "plugins") registryType = "plugin";
|
|
790
|
+
if (type === "functions") registryType = "function";
|
|
791
|
+
ql.registry.registerItem(registryType, item, keyField);
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
} catch (e) {
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
this.options = {
|
|
800
|
+
watch: true,
|
|
801
|
+
...options
|
|
802
|
+
};
|
|
803
|
+
const rootDir = this.options.rootDir || process.cwd();
|
|
804
|
+
this.manager = new NodeMetadataManager({
|
|
805
|
+
rootDir,
|
|
806
|
+
watch: this.options.watch ?? true,
|
|
807
|
+
formats: ["yaml", "json", "typescript", "javascript"]
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
// src/loaders/memory-loader.ts
|
|
813
|
+
var MemoryLoader = class {
|
|
814
|
+
constructor() {
|
|
815
|
+
this.contract = {
|
|
816
|
+
name: "memory",
|
|
817
|
+
protocol: "memory",
|
|
818
|
+
capabilities: {
|
|
819
|
+
read: true,
|
|
820
|
+
write: true,
|
|
821
|
+
watch: false,
|
|
822
|
+
list: true
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
// Storage: Type -> Name -> Data
|
|
826
|
+
this.storage = /* @__PURE__ */ new Map();
|
|
827
|
+
}
|
|
828
|
+
async load(type, name, _options) {
|
|
829
|
+
const typeStore = this.storage.get(type);
|
|
830
|
+
const data = typeStore?.get(name);
|
|
831
|
+
if (data) {
|
|
832
|
+
return {
|
|
833
|
+
data,
|
|
834
|
+
source: "memory",
|
|
835
|
+
format: "json",
|
|
836
|
+
loadTime: 0
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
return { data: null };
|
|
840
|
+
}
|
|
841
|
+
async loadMany(type, _options) {
|
|
842
|
+
const typeStore = this.storage.get(type);
|
|
843
|
+
if (!typeStore) return [];
|
|
844
|
+
return Array.from(typeStore.values());
|
|
845
|
+
}
|
|
846
|
+
async exists(type, name) {
|
|
847
|
+
return this.storage.get(type)?.has(name) ?? false;
|
|
848
|
+
}
|
|
849
|
+
async stat(type, name) {
|
|
850
|
+
if (await this.exists(type, name)) {
|
|
851
|
+
return {
|
|
852
|
+
size: 0,
|
|
853
|
+
// In-memory
|
|
854
|
+
mtime: /* @__PURE__ */ new Date(),
|
|
855
|
+
format: "json"
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
async list(type) {
|
|
861
|
+
const typeStore = this.storage.get(type);
|
|
862
|
+
if (!typeStore) return [];
|
|
863
|
+
return Array.from(typeStore.keys());
|
|
864
|
+
}
|
|
865
|
+
async save(type, name, data, _options) {
|
|
866
|
+
if (!this.storage.has(type)) {
|
|
867
|
+
this.storage.set(type, /* @__PURE__ */ new Map());
|
|
868
|
+
}
|
|
869
|
+
this.storage.get(type).set(name, data);
|
|
870
|
+
return {
|
|
871
|
+
success: true,
|
|
872
|
+
path: `memory://${type}/${name}`,
|
|
873
|
+
saveTime: 0
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
// src/loaders/remote-loader.ts
|
|
879
|
+
var RemoteLoader = class {
|
|
880
|
+
constructor(baseUrl, authToken) {
|
|
881
|
+
this.baseUrl = baseUrl;
|
|
882
|
+
this.authToken = authToken;
|
|
883
|
+
this.contract = {
|
|
884
|
+
name: "remote",
|
|
885
|
+
protocol: "http",
|
|
886
|
+
capabilities: {
|
|
887
|
+
read: true,
|
|
888
|
+
write: true,
|
|
889
|
+
watch: false,
|
|
890
|
+
// Could implement SSE/WebSocket in future
|
|
891
|
+
list: true
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
get headers() {
|
|
896
|
+
return {
|
|
897
|
+
"Content-Type": "application/json",
|
|
898
|
+
...this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
async load(type, name, _options) {
|
|
902
|
+
try {
|
|
903
|
+
const response = await fetch(`${this.baseUrl}/${type}/${name}`, {
|
|
904
|
+
method: "GET",
|
|
905
|
+
headers: this.headers
|
|
906
|
+
});
|
|
907
|
+
if (response.status === 404) {
|
|
908
|
+
return { data: null };
|
|
909
|
+
}
|
|
910
|
+
if (!response.ok) {
|
|
911
|
+
throw new Error(`Remote load failed: ${response.statusText}`);
|
|
912
|
+
}
|
|
913
|
+
const data = await response.json();
|
|
914
|
+
return {
|
|
915
|
+
data,
|
|
916
|
+
source: this.baseUrl,
|
|
917
|
+
format: "json",
|
|
918
|
+
loadTime: 0
|
|
919
|
+
};
|
|
920
|
+
} catch (error) {
|
|
921
|
+
console.error(`RemoteLoader error loading ${type}/${name}`, error);
|
|
922
|
+
throw error;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
async loadMany(type, _options) {
|
|
926
|
+
const response = await fetch(`${this.baseUrl}/${type}`, {
|
|
927
|
+
method: "GET",
|
|
928
|
+
headers: this.headers
|
|
929
|
+
});
|
|
930
|
+
if (!response.ok) {
|
|
931
|
+
return [];
|
|
932
|
+
}
|
|
933
|
+
return await response.json();
|
|
934
|
+
}
|
|
935
|
+
async exists(type, name) {
|
|
936
|
+
const response = await fetch(`${this.baseUrl}/${type}/${name}`, {
|
|
937
|
+
method: "HEAD",
|
|
938
|
+
headers: this.headers
|
|
939
|
+
});
|
|
940
|
+
return response.ok;
|
|
941
|
+
}
|
|
942
|
+
async stat(type, name) {
|
|
943
|
+
const response = await fetch(`${this.baseUrl}/${type}/${name}`, {
|
|
944
|
+
method: "HEAD",
|
|
945
|
+
headers: this.headers
|
|
946
|
+
});
|
|
947
|
+
if (!response.ok) return null;
|
|
948
|
+
return {
|
|
949
|
+
size: Number(response.headers.get("content-length") || 0),
|
|
950
|
+
mtime: new Date(response.headers.get("last-modified") || Date.now()),
|
|
951
|
+
format: "json"
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
async list(type) {
|
|
955
|
+
const items = await this.loadMany(type);
|
|
956
|
+
return items.map((i) => i.name);
|
|
957
|
+
}
|
|
958
|
+
async save(type, name, data, _options) {
|
|
959
|
+
const response = await fetch(`${this.baseUrl}/${type}/${name}`, {
|
|
960
|
+
method: "PUT",
|
|
961
|
+
headers: this.headers,
|
|
962
|
+
body: JSON.stringify(data)
|
|
963
|
+
});
|
|
964
|
+
if (!response.ok) {
|
|
965
|
+
throw new Error(`Remote save failed: ${response.statusText}`);
|
|
966
|
+
}
|
|
967
|
+
return {
|
|
968
|
+
success: true,
|
|
969
|
+
path: `${this.baseUrl}/${type}/${name}`,
|
|
970
|
+
saveTime: 0
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
// src/migration/index.ts
|
|
976
|
+
var migration_exports = {};
|
|
977
|
+
__export(migration_exports, {
|
|
978
|
+
MigrationExecutor: () => MigrationExecutor
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
// src/migration/executor.ts
|
|
982
|
+
var MigrationExecutor = class {
|
|
983
|
+
constructor(driver) {
|
|
984
|
+
this.driver = driver;
|
|
985
|
+
}
|
|
986
|
+
async executeChangeSet(changeSet) {
|
|
987
|
+
console.log(`Executing ChangeSet: ${changeSet.name} (${changeSet.id})`);
|
|
988
|
+
for (const op of changeSet.operations) {
|
|
989
|
+
try {
|
|
990
|
+
await this.executeOperation(op);
|
|
991
|
+
} catch (e) {
|
|
992
|
+
console.error(`Failed to execute operation ${op.type}:`, e);
|
|
993
|
+
throw e;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
async executeOperation(op) {
|
|
998
|
+
switch (op.type) {
|
|
999
|
+
case "create_object":
|
|
1000
|
+
console.log(` > Create Object: ${op.object.name}`);
|
|
1001
|
+
await this.driver.createCollection(op.object.name, op.object);
|
|
1002
|
+
break;
|
|
1003
|
+
case "add_field":
|
|
1004
|
+
console.log(` > Add Field: ${op.objectName}.${op.fieldName}`);
|
|
1005
|
+
await this.driver.addColumn(op.objectName, op.fieldName, op.field);
|
|
1006
|
+
break;
|
|
1007
|
+
case "remove_field":
|
|
1008
|
+
console.log(` > Remove Field: ${op.objectName}.${op.fieldName}`);
|
|
1009
|
+
await this.driver.dropColumn(op.objectName, op.fieldName);
|
|
1010
|
+
break;
|
|
1011
|
+
case "delete_object":
|
|
1012
|
+
console.log(` > Delete Object: ${op.objectName}`);
|
|
1013
|
+
await this.driver.dropCollection(op.objectName);
|
|
1014
|
+
break;
|
|
1015
|
+
case "execute_sql":
|
|
1016
|
+
console.log(` > Execute SQL`);
|
|
1017
|
+
await this.driver.executeRaw(op.sql);
|
|
1018
|
+
break;
|
|
1019
|
+
case "modify_field":
|
|
1020
|
+
console.warn(` ! Modify Field: ${op.objectName}.${op.fieldName} (Not fully implemented)`);
|
|
1021
|
+
break;
|
|
1022
|
+
case "rename_object":
|
|
1023
|
+
console.warn(` ! Rename Object: ${op.oldName} -> ${op.newName} (Not fully implemented)`);
|
|
1024
|
+
break;
|
|
1025
|
+
default:
|
|
1026
|
+
throw new Error(`Unknown operation type`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
export {
|
|
1031
|
+
JSONSerializer,
|
|
1032
|
+
MemoryLoader,
|
|
1033
|
+
MetadataManager,
|
|
1034
|
+
MetadataPlugin,
|
|
1035
|
+
migration_exports as Migration,
|
|
1036
|
+
RemoteLoader,
|
|
1037
|
+
TypeScriptSerializer,
|
|
1038
|
+
YAMLSerializer
|
|
1039
|
+
};
|
|
1040
|
+
//# sourceMappingURL=index.mjs.map
|