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