@objectstack/metadata 1.0.0 → 1.0.1
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/CHANGELOG.md +8 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/loaders/filesystem-loader.d.ts +2 -1
- package/dist/loaders/filesystem-loader.d.ts.map +1 -1
- package/dist/loaders/filesystem-loader.js +75 -0
- package/dist/loaders/loader-interface.d.ts +9 -1
- package/dist/loaders/loader-interface.d.ts.map +1 -1
- package/dist/loaders/memory-loader.d.ts +19 -0
- package/dist/loaders/memory-loader.d.ts.map +1 -0
- package/dist/loaders/memory-loader.js +71 -0
- package/dist/loaders/remote-loader.d.ts +22 -0
- package/dist/loaders/remote-loader.d.ts.map +1 -0
- package/dist/loaders/remote-loader.js +103 -0
- package/dist/metadata-manager.d.ts +25 -23
- package/dist/metadata-manager.d.ts.map +1 -1
- package/dist/metadata-manager.js +104 -156
- package/dist/node-metadata-manager.d.ts +26 -0
- package/dist/node-metadata-manager.d.ts.map +1 -0
- package/dist/node-metadata-manager.js +98 -0
- package/dist/node.d.ts +8 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +7 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +2 -2
- package/package.json +16 -4
- package/src/index.ts +3 -2
- package/src/loaders/filesystem-loader.ts +97 -0
- package/src/loaders/loader-interface.ts +17 -0
- package/src/loaders/memory-loader.ts +101 -0
- package/src/loaders/remote-loader.ts +138 -0
- package/src/metadata-manager.ts +121 -193
- package/src/node-metadata-manager.ts +124 -0
- package/src/node.ts +8 -0
- package/src/plugin.ts +3 -3
package/CHANGELOG.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Metadata loading, saving, and persistence for ObjectStack
|
|
5
5
|
*/
|
|
6
|
-
export { MetadataManager, type WatchCallback } from './metadata-manager.js';
|
|
6
|
+
export { MetadataManager, type WatchCallback, type MetadataManagerOptions } from './metadata-manager.js';
|
|
7
7
|
export { MetadataPlugin } from './plugin.js';
|
|
8
8
|
export { type MetadataLoader } from './loaders/loader-interface.js';
|
|
9
|
-
export {
|
|
9
|
+
export { MemoryLoader } from './loaders/memory-loader.js';
|
|
10
|
+
export { RemoteLoader } from './loaders/remote-loader.js';
|
|
10
11
|
export { type MetadataSerializer, type SerializeOptions } from './serializers/serializer-interface.js';
|
|
11
12
|
export { JSONSerializer } from './serializers/json-serializer.js';
|
|
12
13
|
export { YAMLSerializer } from './serializers/yaml-serializer.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAGzG,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG1D,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACvG,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAG9E,YAAY,EACV,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
export { MetadataManager } from './metadata-manager.js';
|
|
8
8
|
// Plugin
|
|
9
9
|
export { MetadataPlugin } from './plugin.js';
|
|
10
|
-
export {
|
|
10
|
+
export { MemoryLoader } from './loaders/memory-loader.js';
|
|
11
|
+
export { RemoteLoader } from './loaders/remote-loader.js';
|
|
11
12
|
export { JSONSerializer } from './serializers/json-serializer.js';
|
|
12
13
|
export { YAMLSerializer } from './serializers/yaml-serializer.js';
|
|
13
14
|
export * as Migration from './migration/index.js';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Loads metadata from the filesystem using glob patterns
|
|
5
5
|
*/
|
|
6
|
-
import type { MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataLoaderContract, MetadataFormat } from '@objectstack/spec/system';
|
|
6
|
+
import type { MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataLoaderContract, MetadataFormat, MetadataSaveOptions, MetadataSaveResult } from '@objectstack/spec/system';
|
|
7
7
|
import type { Logger } from '@objectstack/core';
|
|
8
8
|
import type { MetadataLoader } from './loader-interface.js';
|
|
9
9
|
import type { MetadataSerializer } from '../serializers/serializer-interface.js';
|
|
@@ -19,6 +19,7 @@ export declare class FilesystemLoader implements MetadataLoader {
|
|
|
19
19
|
exists(type: string, name: string): Promise<boolean>;
|
|
20
20
|
stat(type: string, name: string): Promise<MetadataStats | null>;
|
|
21
21
|
list(type: string): Promise<string[]>;
|
|
22
|
+
save(type: string, name: string, data: any, options?: MetadataSaveOptions): Promise<MetadataSaveResult>;
|
|
22
23
|
/**
|
|
23
24
|
* Find file for a given type and name
|
|
24
25
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem-loader.d.ts","sourceRoot":"","sources":["../../src/loaders/filesystem-loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,sBAAsB,EACtB,cAAc,
|
|
1
|
+
{"version":3,"file":"filesystem-loader.d.ts","sourceRoot":"","sources":["../../src/loaders/filesystem-loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,sBAAsB,EACtB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAEjF,qBAAa,gBAAiB,YAAW,cAAc;IAmBnD,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM,CAAC;IApBjB,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAavC;IAEF,OAAO,CAAC,KAAK,CAAqE;gBAGxE,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,EACpD,MAAM,CAAC,EAAE,MAAM,YAAA;IAGnB,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IA8FxB,QAAQ,CAAC,CAAC,GAAG,GAAG,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,CAAC,EAAE,CAAC;IAwDT,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKpD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IA+B/D,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAwBrC,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,GAAG,EACT,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IA0F9B;;OAEG;YACW,QAAQ;IAkBtB;;OAEG;IACH,OAAO,CAAC,YAAY;IAkBpB;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;;;OAIG;IACH,OAAO,CAAC,YAAY;CAIrB"}
|
|
@@ -212,6 +212,81 @@ export class FilesystemLoader {
|
|
|
212
212
|
return [];
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
|
+
async save(type, name, data, options) {
|
|
216
|
+
const startTime = Date.now();
|
|
217
|
+
const { format = 'typescript', prettify = true, indent = 2, sortKeys = false, backup = false, overwrite = true, atomic = true, path: customPath, } = options || {};
|
|
218
|
+
try {
|
|
219
|
+
// Get serializer
|
|
220
|
+
const serializer = this.getSerializer(format);
|
|
221
|
+
if (!serializer) {
|
|
222
|
+
throw new Error(`No serializer found for format: ${format}`);
|
|
223
|
+
}
|
|
224
|
+
// Determine file path
|
|
225
|
+
const typeDir = path.join(this.rootDir, type);
|
|
226
|
+
const fileName = `${name}${serializer.getExtension()}`;
|
|
227
|
+
const filePath = customPath || path.join(typeDir, fileName);
|
|
228
|
+
// Check if file exists
|
|
229
|
+
if (!overwrite) {
|
|
230
|
+
try {
|
|
231
|
+
await fs.access(filePath);
|
|
232
|
+
throw new Error(`File already exists: ${filePath}`);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
// File doesn't exist, continue
|
|
236
|
+
if (error.code !== 'ENOENT') {
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Create directory if it doesn't exist
|
|
242
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
243
|
+
// Create backup if requested
|
|
244
|
+
let backupPath;
|
|
245
|
+
if (backup) {
|
|
246
|
+
try {
|
|
247
|
+
await fs.access(filePath);
|
|
248
|
+
backupPath = `${filePath}.bak`;
|
|
249
|
+
await fs.copyFile(filePath, backupPath);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// File doesn't exist, no backup needed
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Serialize data
|
|
256
|
+
const content = serializer.serialize(data, {
|
|
257
|
+
prettify,
|
|
258
|
+
indent,
|
|
259
|
+
sortKeys,
|
|
260
|
+
});
|
|
261
|
+
// Write to disk (atomic or direct)
|
|
262
|
+
if (atomic) {
|
|
263
|
+
const tempPath = `${filePath}.tmp`;
|
|
264
|
+
await fs.writeFile(tempPath, content, 'utf-8');
|
|
265
|
+
await fs.rename(tempPath, filePath);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
269
|
+
}
|
|
270
|
+
// Update cache logic if needed (e.g., invalidate or update)
|
|
271
|
+
// For now, we rely on the watcher to pick up changes
|
|
272
|
+
return {
|
|
273
|
+
success: true,
|
|
274
|
+
path: filePath,
|
|
275
|
+
// format, // Not in schema
|
|
276
|
+
size: Buffer.byteLength(content, 'utf-8'),
|
|
277
|
+
backupPath,
|
|
278
|
+
saveTime: Date.now() - startTime,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
this.logger?.error('Failed to save metadata', undefined, {
|
|
283
|
+
type,
|
|
284
|
+
name,
|
|
285
|
+
error: error instanceof Error ? error.message : String(error),
|
|
286
|
+
});
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
215
290
|
/**
|
|
216
291
|
* Find file for a given type and name
|
|
217
292
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Defines the contract for loading metadata from various sources
|
|
5
5
|
*/
|
|
6
|
-
import type { MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataLoaderContract } from '@objectstack/spec/system';
|
|
6
|
+
import type { MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataLoaderContract, MetadataSaveOptions, MetadataSaveResult } from '@objectstack/spec/system';
|
|
7
7
|
/**
|
|
8
8
|
* Abstract interface for metadata loaders
|
|
9
9
|
* Implementations can load from filesystem, HTTP, S3, databases, etc.
|
|
@@ -48,5 +48,13 @@ export interface MetadataLoader {
|
|
|
48
48
|
* @returns Array of item names
|
|
49
49
|
*/
|
|
50
50
|
list(type: string): Promise<string[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Save metadata item
|
|
53
|
+
* @param type The metadata type
|
|
54
|
+
* @param name The item name
|
|
55
|
+
* @param data The data to save
|
|
56
|
+
* @param options Save options
|
|
57
|
+
*/
|
|
58
|
+
save?(type: string, name: string, data: any, options?: MetadataSaveOptions): Promise<MetadataSaveResult>;
|
|
51
59
|
}
|
|
52
60
|
//# sourceMappingURL=loader-interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader-interface.d.ts","sourceRoot":"","sources":["../../src/loaders/loader-interface.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,sBAAsB,
|
|
1
|
+
{"version":3,"file":"loader-interface.d.ts","sourceRoot":"","sources":["../../src/loaders/loader-interface.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAElC;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAE1C;;;;;;OAMG;IACH,IAAI,CACF,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAE/B;;;;;OAKG;IACH,QAAQ,CAAC,CAAC,GAAG,GAAG,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAEhB;;;;;OAKG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAErD;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAEhE;;;;OAIG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAEtC;;;;;;OAMG;IACH,IAAI,CAAC,CACH,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,GAAG,EACT,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAAC;CAChC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Metadata Loader
|
|
3
|
+
*
|
|
4
|
+
* Stores metadata in memory only. Changes are lost when process restarts.
|
|
5
|
+
* Useful for testing, temporary overrides, or "dirty" edits.
|
|
6
|
+
*/
|
|
7
|
+
import type { MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataLoaderContract, MetadataSaveOptions, MetadataSaveResult } from '@objectstack/spec/system';
|
|
8
|
+
import type { MetadataLoader } from './loader-interface.js';
|
|
9
|
+
export declare class MemoryLoader implements MetadataLoader {
|
|
10
|
+
readonly contract: MetadataLoaderContract;
|
|
11
|
+
private storage;
|
|
12
|
+
load(type: string, name: string, _options?: MetadataLoadOptions): Promise<MetadataLoadResult>;
|
|
13
|
+
loadMany<T = any>(type: string, _options?: MetadataLoadOptions): Promise<T[]>;
|
|
14
|
+
exists(type: string, name: string): Promise<boolean>;
|
|
15
|
+
stat(type: string, name: string): Promise<MetadataStats | null>;
|
|
16
|
+
list(type: string): Promise<string[]>;
|
|
17
|
+
save(type: string, name: string, data: any, _options?: MetadataSaveOptions): Promise<MetadataSaveResult>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=memory-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-loader.d.ts","sourceRoot":"","sources":["../../src/loaders/memory-loader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,qBAAa,YAAa,YAAW,cAAc;IACjD,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CASvC;IAGF,OAAO,CAAC,OAAO,CAAuC;IAEhD,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,kBAAkB,CAAC;IAgBxB,QAAQ,CAAC,CAAC,GAAG,GAAG,EACpB,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,CAAC,EAAE,CAAC;IAMT,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIpD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAW/D,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAMrC,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,GAAG,EACT,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,kBAAkB,CAAC;CAa/B"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Metadata Loader
|
|
3
|
+
*
|
|
4
|
+
* Stores metadata in memory only. Changes are lost when process restarts.
|
|
5
|
+
* Useful for testing, temporary overrides, or "dirty" edits.
|
|
6
|
+
*/
|
|
7
|
+
export class MemoryLoader {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.contract = {
|
|
10
|
+
name: 'memory',
|
|
11
|
+
protocol: 'memory',
|
|
12
|
+
capabilities: {
|
|
13
|
+
read: true,
|
|
14
|
+
write: true,
|
|
15
|
+
watch: false,
|
|
16
|
+
list: true,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
// Storage: Type -> Name -> Data
|
|
20
|
+
this.storage = new Map();
|
|
21
|
+
}
|
|
22
|
+
async load(type, name, _options) {
|
|
23
|
+
const typeStore = this.storage.get(type);
|
|
24
|
+
const data = typeStore?.get(name);
|
|
25
|
+
if (data) {
|
|
26
|
+
return {
|
|
27
|
+
data,
|
|
28
|
+
source: 'memory',
|
|
29
|
+
format: 'json',
|
|
30
|
+
loadTime: 0,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return { data: null };
|
|
34
|
+
}
|
|
35
|
+
async loadMany(type, _options) {
|
|
36
|
+
const typeStore = this.storage.get(type);
|
|
37
|
+
if (!typeStore)
|
|
38
|
+
return [];
|
|
39
|
+
return Array.from(typeStore.values());
|
|
40
|
+
}
|
|
41
|
+
async exists(type, name) {
|
|
42
|
+
return this.storage.get(type)?.has(name) ?? false;
|
|
43
|
+
}
|
|
44
|
+
async stat(type, name) {
|
|
45
|
+
if (await this.exists(type, name)) {
|
|
46
|
+
return {
|
|
47
|
+
size: 0, // In-memory
|
|
48
|
+
mtime: new Date(),
|
|
49
|
+
format: 'json',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
async list(type) {
|
|
55
|
+
const typeStore = this.storage.get(type);
|
|
56
|
+
if (!typeStore)
|
|
57
|
+
return [];
|
|
58
|
+
return Array.from(typeStore.keys());
|
|
59
|
+
}
|
|
60
|
+
async save(type, name, data, _options) {
|
|
61
|
+
if (!this.storage.has(type)) {
|
|
62
|
+
this.storage.set(type, new Map());
|
|
63
|
+
}
|
|
64
|
+
this.storage.get(type).set(name, data);
|
|
65
|
+
return {
|
|
66
|
+
success: true,
|
|
67
|
+
path: `memory://${type}/${name}`,
|
|
68
|
+
saveTime: 0,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote Metadata Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads metadata from an HTTP API.
|
|
5
|
+
* This loader is stateless and delegates storage to the remote server.
|
|
6
|
+
*/
|
|
7
|
+
import type { MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataLoaderContract, MetadataSaveOptions, MetadataSaveResult } from '@objectstack/spec/system';
|
|
8
|
+
import type { MetadataLoader } from './loader-interface.js';
|
|
9
|
+
export declare class RemoteLoader implements MetadataLoader {
|
|
10
|
+
private baseUrl;
|
|
11
|
+
private authToken?;
|
|
12
|
+
readonly contract: MetadataLoaderContract;
|
|
13
|
+
constructor(baseUrl: string, authToken?: string | undefined);
|
|
14
|
+
private get headers();
|
|
15
|
+
load(type: string, name: string, _options?: MetadataLoadOptions): Promise<MetadataLoadResult>;
|
|
16
|
+
loadMany<T = any>(type: string, _options?: MetadataLoadOptions): Promise<T[]>;
|
|
17
|
+
exists(type: string, name: string): Promise<boolean>;
|
|
18
|
+
stat(type: string, name: string): Promise<MetadataStats | null>;
|
|
19
|
+
list(type: string): Promise<string[]>;
|
|
20
|
+
save(type: string, name: string, data: any, _options?: MetadataSaveOptions): Promise<MetadataSaveResult>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=remote-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-loader.d.ts","sourceRoot":"","sources":["../../src/loaders/remote-loader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,qBAAa,YAAa,YAAW,cAAc;IAYrC,OAAO,CAAC,OAAO;IAAU,OAAO,CAAC,SAAS,CAAC;IAXvD,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CASvC;gBAEkB,OAAO,EAAE,MAAM,EAAU,SAAS,CAAC,EAAE,MAAM,YAAA;IAE/D,OAAO,KAAK,OAAO,GAKlB;IAEK,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,kBAAkB,CAAC;IA4BxB,QAAQ,CAAC,CAAC,GAAG,GAAG,EACpB,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,CAAC,EAAE,CAAC;IAaT,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQpD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAgB/D,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAKrC,IAAI,CACR,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,GAAG,EACT,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,kBAAkB,CAAC;CAiB/B"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote Metadata Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads metadata from an HTTP API.
|
|
5
|
+
* This loader is stateless and delegates storage to the remote server.
|
|
6
|
+
*/
|
|
7
|
+
export class RemoteLoader {
|
|
8
|
+
constructor(baseUrl, authToken) {
|
|
9
|
+
this.baseUrl = baseUrl;
|
|
10
|
+
this.authToken = authToken;
|
|
11
|
+
this.contract = {
|
|
12
|
+
name: 'remote',
|
|
13
|
+
protocol: 'http',
|
|
14
|
+
capabilities: {
|
|
15
|
+
read: true,
|
|
16
|
+
write: true,
|
|
17
|
+
watch: false, // Could implement SSE/WebSocket in future
|
|
18
|
+
list: true,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
get headers() {
|
|
23
|
+
return {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
...(this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async load(type, name, _options) {
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetch(`${this.baseUrl}/${type}/${name}`, {
|
|
31
|
+
method: 'GET',
|
|
32
|
+
headers: this.headers,
|
|
33
|
+
});
|
|
34
|
+
if (response.status === 404) {
|
|
35
|
+
return { data: null };
|
|
36
|
+
}
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
throw new Error(`Remote load failed: ${response.statusText}`);
|
|
39
|
+
}
|
|
40
|
+
const data = await response.json();
|
|
41
|
+
return {
|
|
42
|
+
data,
|
|
43
|
+
source: this.baseUrl,
|
|
44
|
+
format: 'json',
|
|
45
|
+
loadTime: 0,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error(`RemoteLoader error loading ${type}/${name}`, error);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async loadMany(type, _options) {
|
|
54
|
+
const response = await fetch(`${this.baseUrl}/${type}`, {
|
|
55
|
+
method: 'GET',
|
|
56
|
+
headers: this.headers,
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
return (await response.json());
|
|
62
|
+
}
|
|
63
|
+
async exists(type, name) {
|
|
64
|
+
const response = await fetch(`${this.baseUrl}/${type}/${name}`, {
|
|
65
|
+
method: 'HEAD',
|
|
66
|
+
headers: this.headers,
|
|
67
|
+
});
|
|
68
|
+
return response.ok;
|
|
69
|
+
}
|
|
70
|
+
async stat(type, name) {
|
|
71
|
+
// Basic implementation using HEAD
|
|
72
|
+
const response = await fetch(`${this.baseUrl}/${type}/${name}`, {
|
|
73
|
+
method: 'HEAD',
|
|
74
|
+
headers: this.headers,
|
|
75
|
+
});
|
|
76
|
+
if (!response.ok)
|
|
77
|
+
return null;
|
|
78
|
+
return {
|
|
79
|
+
size: Number(response.headers.get('content-length') || 0),
|
|
80
|
+
mtime: new Date(response.headers.get('last-modified') || Date.now()),
|
|
81
|
+
format: 'json',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async list(type) {
|
|
85
|
+
const items = await this.loadMany(type);
|
|
86
|
+
return items.map(i => i.name);
|
|
87
|
+
}
|
|
88
|
+
async save(type, name, data, _options) {
|
|
89
|
+
const response = await fetch(`${this.baseUrl}/${type}/${name}`, {
|
|
90
|
+
method: 'PUT',
|
|
91
|
+
headers: this.headers,
|
|
92
|
+
body: JSON.stringify(data),
|
|
93
|
+
});
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new Error(`Remote save failed: ${response.statusText}`);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
success: true,
|
|
99
|
+
path: `${this.baseUrl}/${type}/${name}`,
|
|
100
|
+
saveTime: 0,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -1,35 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Metadata Manager
|
|
3
3
|
*
|
|
4
|
-
* Main orchestrator for metadata loading, saving, and persistence
|
|
4
|
+
* Main orchestrator for metadata loading, saving, and persistence.
|
|
5
|
+
* Browser-compatible (Pure).
|
|
5
6
|
*/
|
|
6
|
-
import type { MetadataManagerConfig, MetadataLoadOptions, MetadataSaveOptions, MetadataSaveResult, MetadataWatchEvent } from '@objectstack/spec/system';
|
|
7
|
+
import type { MetadataManagerConfig, MetadataLoadOptions, MetadataSaveOptions, MetadataSaveResult, MetadataWatchEvent, MetadataFormat } from '@objectstack/spec/system';
|
|
8
|
+
import { type Logger } from '@objectstack/core';
|
|
9
|
+
import type { MetadataSerializer } from './serializers/serializer-interface.js';
|
|
10
|
+
import type { MetadataLoader } from './loaders/loader-interface.js';
|
|
7
11
|
/**
|
|
8
12
|
* Watch callback function
|
|
9
13
|
*/
|
|
10
14
|
export type WatchCallback = (event: MetadataWatchEvent) => void | Promise<void>;
|
|
15
|
+
export interface MetadataManagerOptions extends MetadataManagerConfig {
|
|
16
|
+
loaders?: MetadataLoader[];
|
|
17
|
+
}
|
|
11
18
|
/**
|
|
12
19
|
* Main metadata manager class
|
|
13
20
|
*/
|
|
14
21
|
export declare class MetadataManager {
|
|
15
|
-
private
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
private loaders;
|
|
23
|
+
protected serializers: Map<MetadataFormat, MetadataSerializer>;
|
|
24
|
+
protected logger: Logger;
|
|
25
|
+
protected watchCallbacks: Map<string, Set<WatchCallback>>;
|
|
26
|
+
protected config: MetadataManagerOptions;
|
|
27
|
+
constructor(config: MetadataManagerOptions);
|
|
28
|
+
/**
|
|
29
|
+
* Register a new metadata loader (data source)
|
|
30
|
+
*/
|
|
31
|
+
registerLoader(loader: MetadataLoader): void;
|
|
22
32
|
/**
|
|
23
33
|
* Load a single metadata item
|
|
34
|
+
* Iterates through registered loaders until found
|
|
24
35
|
*/
|
|
25
36
|
load<T = any>(type: string, name: string, options?: MetadataLoadOptions): Promise<T | null>;
|
|
26
37
|
/**
|
|
27
38
|
* Load multiple metadata items
|
|
39
|
+
* Aggregates results from all loaders
|
|
28
40
|
*/
|
|
29
41
|
loadMany<T = any>(type: string, options?: MetadataLoadOptions): Promise<T[]>;
|
|
30
42
|
/**
|
|
31
43
|
* Save metadata to disk
|
|
32
44
|
*/
|
|
45
|
+
/**
|
|
46
|
+
* Save metadata item
|
|
47
|
+
*/
|
|
33
48
|
save<T = any>(type: string, name: string, data: T, options?: MetadataSaveOptions): Promise<MetadataSaveResult>;
|
|
34
49
|
/**
|
|
35
50
|
* Check if metadata item exists
|
|
@@ -51,19 +66,6 @@ export declare class MetadataManager {
|
|
|
51
66
|
* Stop all watching
|
|
52
67
|
*/
|
|
53
68
|
stopWatching(): Promise<void>;
|
|
54
|
-
|
|
55
|
-
* Start watching for file changes
|
|
56
|
-
*/
|
|
57
|
-
private startWatching;
|
|
58
|
-
/**
|
|
59
|
-
* Handle file change events
|
|
60
|
-
*/
|
|
61
|
-
private handleFileEvent;
|
|
62
|
-
/**
|
|
63
|
-
* Generate ETag for content
|
|
64
|
-
* Uses SHA-256 hash truncated to 32 characters for reasonable collision resistance
|
|
65
|
-
* while keeping ETag headers compact (full 64-char hash is overkill for this use case)
|
|
66
|
-
*/
|
|
67
|
-
private generateETag;
|
|
69
|
+
protected notifyWatchers(type: string, event: MetadataWatchEvent): void;
|
|
68
70
|
}
|
|
69
71
|
//# sourceMappingURL=metadata-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata-manager.d.ts","sourceRoot":"","sources":["../src/metadata-manager.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"metadata-manager.d.ts","sourceRoot":"","sources":["../src/metadata-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAI9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAEpE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEhF,MAAM,WAAW,sBAAuB,SAAQ,qBAAqB;IACnE,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAA0C;IAEzD,SAAS,CAAC,WAAW,EAAE,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;IAC/D,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,cAAc,kCAAyC;IACjE,SAAS,CAAC,MAAM,EAAE,sBAAsB,CAAC;gBAE7B,MAAM,EAAE,sBAAsB;IA4B1C;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,cAAc;IAKrC;;;OAGG;IACG,IAAI,CAAC,CAAC,GAAG,GAAG,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAgBpB;;;OAGG;IACG,QAAQ,CAAC,CAAC,GAAG,GAAG,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,CAAC,EAAE,CAAC;IAiBf;;OAEG;IACH;;OAEG;IACG,IAAI,CAAC,CAAC,GAAG,GAAG,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IA0D9B;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS1D;;OAEG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAS3C;;OAEG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI;IAOlD;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI;IAUpD;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAInC,SAAS,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB;CAejE"}
|