@objectstack/metadata 0.9.2 → 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.
Files changed (38) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +7 -452
  3. package/dist/index.d.ts +3 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +2 -1
  6. package/dist/loaders/filesystem-loader.d.ts +2 -1
  7. package/dist/loaders/filesystem-loader.d.ts.map +1 -1
  8. package/dist/loaders/filesystem-loader.js +83 -1
  9. package/dist/loaders/loader-interface.d.ts +9 -1
  10. package/dist/loaders/loader-interface.d.ts.map +1 -1
  11. package/dist/loaders/memory-loader.d.ts +19 -0
  12. package/dist/loaders/memory-loader.d.ts.map +1 -0
  13. package/dist/loaders/memory-loader.js +71 -0
  14. package/dist/loaders/remote-loader.d.ts +22 -0
  15. package/dist/loaders/remote-loader.d.ts.map +1 -0
  16. package/dist/loaders/remote-loader.js +103 -0
  17. package/dist/metadata-manager.d.ts +25 -23
  18. package/dist/metadata-manager.d.ts.map +1 -1
  19. package/dist/metadata-manager.js +104 -156
  20. package/dist/node-metadata-manager.d.ts +26 -0
  21. package/dist/node-metadata-manager.d.ts.map +1 -0
  22. package/dist/node-metadata-manager.js +98 -0
  23. package/dist/node.d.ts +8 -0
  24. package/dist/node.d.ts.map +1 -0
  25. package/dist/node.js +7 -0
  26. package/dist/plugin.d.ts +2 -2
  27. package/dist/plugin.d.ts.map +1 -1
  28. package/dist/plugin.js +55 -55
  29. package/package.json +17 -4
  30. package/src/index.ts +3 -2
  31. package/src/loaders/filesystem-loader.ts +106 -2
  32. package/src/loaders/loader-interface.ts +17 -0
  33. package/src/loaders/memory-loader.ts +101 -0
  34. package/src/loaders/remote-loader.ts +138 -0
  35. package/src/metadata-manager.ts +121 -193
  36. package/src/node-metadata-manager.ts +124 -0
  37. package/src/node.ts +8 -0
  38. package/src/plugin.ts +5 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @objectstack/metadata
2
2
 
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @objectstack/spec@1.0.1
8
+ - @objectstack/core@1.0.1
9
+ - @objectstack/types@1.0.1
10
+
11
+ ## 1.0.0
12
+
13
+ ### Major Changes
14
+
15
+ - Major version release for ObjectStack Protocol v1.0.
16
+ - Stabilized Protocol Definitions
17
+ - Enhanced Runtime Plugin Support
18
+ - Fixed Type Compliance across Monorepo
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies
23
+ - @objectstack/spec@1.0.0
24
+ - @objectstack/core@1.0.0
25
+ - @objectstack/types@1.0.0
26
+
3
27
  ## 0.9.2
4
28
 
5
29
  ### Patch Changes
package/README.md CHANGED
@@ -1,458 +1,13 @@
1
1
  # @objectstack/metadata
2
2
 
3
- Metadata loading, saving, and persistence for ObjectStack.
3
+ The **Model Definition** layer. It provides classes and utilities for parsing and validating ObjectStack metadata schemas.
4
4
 
5
- ## Overview
5
+ ## Features
6
6
 
7
- The `@objectstack/metadata` package provides a unified interface for managing metadata across the ObjectStack ecosystem. It handles:
7
+ - **Schema Loading**: Load metadata from JSON/YAML or TypeScript files.
8
+ - **Reference Resolution**: Resolves cross-object references and inheritance.
9
+ - **Validation**: Strict validation against the Zod schemas in `@objectstack/spec`.
8
10
 
9
- - **Metadata Serialization/Deserialization** - Support for JSON, YAML, TypeScript, and JavaScript formats
10
- - **File System Operations** - Load, save, and watch metadata files
11
- - **Validation** - Integration with Zod schemas from `@objectstack/spec`
12
- - **Caching** - ETag-based caching for performance optimization
13
- - **File Watching** - Development mode with automatic reload on file changes
11
+ ## Usage
14
12
 
15
- ## 🤖 AI Development Context
16
-
17
- **Role**: Metadata IO & Persistence
18
- **Usage**:
19
- - Use `MetadataManager` to read/write `.object.ts`, `.view.yaml` files.
20
- - Handles format loading (TS, JSON, YAML).
21
-
22
- ## Installation
23
-
24
- ```bash
25
- pnpm add @objectstack/metadata
26
- ```
27
-
28
- ## Quick Start
29
-
30
- ```typescript
31
- import { MetadataManager } from '@objectstack/metadata';
32
- import type { ServiceObject } from '@objectstack/spec/data';
33
-
34
- // Create manager
35
- const manager = new MetadataManager({
36
- rootDir: './metadata',
37
- formats: ['typescript', 'json', 'yaml'],
38
- cache: { enabled: true, ttl: 3600 },
39
- watch: process.env.NODE_ENV === 'development',
40
- });
41
-
42
- // Load metadata
43
- const customer = await manager.load<ServiceObject>('object', 'customer');
44
-
45
- // Save metadata
46
- await manager.save('object', 'project', projectObject, {
47
- format: 'typescript',
48
- prettify: true,
49
- });
50
-
51
- // Load multiple items
52
- const objects = await manager.loadMany<ServiceObject>('object', {
53
- patterns: ['**/*.object.ts', '**/*.object.json'],
54
- });
55
-
56
- // Watch for changes
57
- manager.watch('object', (event) => {
58
- console.log(`Object ${event.type}:`, event.name);
59
- });
60
- ```
61
-
62
- ## API
63
-
64
- ### MetadataManager
65
-
66
- Main class for metadata operations.
67
-
68
- #### Constructor
69
-
70
- ```typescript
71
- new MetadataManager(config: MetadataManagerConfig)
72
- ```
73
-
74
- **Config options:**
75
- - `rootDir` - Root directory for metadata files
76
- - `formats` - Enabled serialization formats (default: `['typescript', 'json', 'yaml']`)
77
- - `cache` - Cache configuration with `enabled` and `ttl` options
78
- - `watch` - Enable file watching (default: `false`)
79
- - `watchOptions` - File watcher options (`ignored`, `persistent`, `ignoreInitial`)
80
-
81
- #### Methods
82
-
83
- **load<T>(type: string, name: string, options?: MetadataLoadOptions): Promise<T | null>**
84
-
85
- Load a single metadata item.
86
-
87
- ```typescript
88
- const customer = await manager.load<ServiceObject>('object', 'customer');
89
- ```
90
-
91
- **loadMany<T>(type: string, options?: MetadataLoadOptions): Promise<T[]>**
92
-
93
- Load multiple metadata items matching patterns.
94
-
95
- ```typescript
96
- const objects = await manager.loadMany<ServiceObject>('object', {
97
- patterns: ['**/*.object.ts'],
98
- limit: 100,
99
- });
100
- ```
101
-
102
- **save<T>(type: string, name: string, data: T, options?: MetadataSaveOptions): Promise<MetadataSaveResult>**
103
-
104
- Save metadata to disk.
105
-
106
- ```typescript
107
- await manager.save('object', 'customer', customerObject, {
108
- format: 'typescript',
109
- prettify: true,
110
- backup: true,
111
- });
112
- ```
113
-
114
- **exists(type: string, name: string): Promise<boolean>**
115
-
116
- Check if metadata item exists.
117
-
118
- ```typescript
119
- const exists = await manager.exists('object', 'customer');
120
- ```
121
-
122
- **list(type: string): Promise<string[]>**
123
-
124
- List all items of a type.
125
-
126
- ```typescript
127
- const objectNames = await manager.list('object');
128
- ```
129
-
130
- **watch(type: string, callback: WatchCallback): void**
131
-
132
- Watch for metadata changes.
133
-
134
- ```typescript
135
- manager.watch('object', (event) => {
136
- if (event.type === 'added') {
137
- console.log('New object:', event.name);
138
- }
139
- });
140
- ```
141
-
142
- **stopWatching(): Promise<void>**
143
-
144
- Stop all file watching.
145
-
146
- ## Serialization Formats
147
-
148
- ### JSON
149
-
150
- ```json
151
- {
152
- "name": "customer",
153
- "label": "Customer",
154
- "fields": {
155
- "name": { "type": "text", "label": "Name" }
156
- }
157
- }
158
- ```
159
-
160
- ### YAML
161
-
162
- ```yaml
163
- name: customer
164
- label: Customer
165
- fields:
166
- name:
167
- type: text
168
- label: Name
169
- ```
170
-
171
- ### TypeScript
172
-
173
- ```typescript
174
- import type { ServiceObject } from '@objectstack/spec/data';
175
-
176
- export const metadata: ServiceObject = {
177
- name: 'customer',
178
- label: 'Customer',
179
- fields: {
180
- name: { type: 'text', label: 'Name' },
181
- },
182
- };
183
-
184
- export default metadata;
185
- ```
186
-
187
- ## Architecture
188
-
189
- The metadata package is designed as a Layer 3 package in the ObjectStack architecture:
190
-
191
- ```
192
- @objectstack/metadata (Layer 3)
193
- ├── Dependencies:
194
- │ ├── @objectstack/spec (validation)
195
- │ ├── @objectstack/core (logging, DI)
196
- │ ├── @objectstack/types (shared types)
197
- │ ├── glob (file pattern matching)
198
- │ ├── js-yaml (YAML support)
199
- │ └── chokidar (file watching)
200
- └── Used By:
201
- ├── @objectstack/cli (code generation)
202
- ├── @objectstack/runtime (manifest loading)
203
- └── @objectstack/objectql (registry persistence)
204
- ```
205
-
206
- ## Common Workflows
207
-
208
- ### Development Workflow with File Watching
209
-
210
- ```typescript
211
- import { MetadataManager } from '@objectstack/metadata';
212
-
213
- const manager = new MetadataManager({
214
- rootDir: './metadata',
215
- watch: true, // Enable file watching
216
- cache: { enabled: true, ttl: 3600 }
217
- });
218
-
219
- // Watch for changes and reload
220
- manager.watch('object', async (event) => {
221
- if (event.type === 'modified' || event.type === 'added') {
222
- console.log(`Reloading object: ${event.name}`);
223
- const updated = await manager.load('object', event.name);
224
-
225
- // Notify the system to reload
226
- await objectQL.reloadObject(event.name, updated);
227
- }
228
- });
229
-
230
- // Hot module replacement for development
231
- console.log('Watching metadata files for changes...');
232
- ```
233
-
234
- ### Metadata Migration Workflow
235
-
236
- ```typescript
237
- import { MetadataManager } from '@objectstack/metadata';
238
-
239
- async function migrateMetadata() {
240
- const manager = new MetadataManager({
241
- rootDir: './metadata'
242
- });
243
-
244
- // 1. Load all existing objects
245
- const objects = await manager.loadMany('object');
246
-
247
- // 2. Transform metadata (e.g., rename field)
248
- const transformed = objects.map(obj => ({
249
- ...obj,
250
- fields: Object.entries(obj.fields).reduce((acc, [key, field]) => {
251
- // Rename 'description' to 'notes'
252
- const newKey = key === 'description' ? 'notes' : key;
253
- acc[newKey] = field;
254
- return acc;
255
- }, {})
256
- }));
257
-
258
- // 3. Save with backup
259
- for (const obj of transformed) {
260
- await manager.save('object', obj.name, obj, {
261
- format: 'typescript',
262
- backup: true, // Create .bak file
263
- prettify: true
264
- });
265
- }
266
-
267
- console.log(`Migrated ${objects.length} objects`);
268
- }
269
- ```
270
-
271
- ### Multi-Format Support Workflow
272
-
273
- ```typescript
274
- import { MetadataManager } from '@objectstack/metadata';
275
-
276
- const manager = new MetadataManager({
277
- rootDir: './metadata',
278
- formats: ['typescript', 'json', 'yaml']
279
- });
280
-
281
- // Load from any format - manager auto-detects
282
- const customer = await manager.load('object', 'customer');
283
- // Tries: customer.object.ts, customer.object.json, customer.object.yaml
284
-
285
- // Save in preferred format
286
- await manager.save('object', 'customer', customer, {
287
- format: 'typescript' // Convert to TypeScript
288
- });
289
-
290
- // Generate documentation from metadata
291
- const allObjects = await manager.loadMany('object');
292
- const docs = allObjects.map(obj => `
293
- ## ${obj.label}
294
-
295
- **Name:** ${obj.name}
296
-
297
- **Fields:**
298
- ${Object.entries(obj.fields).map(([name, field]) =>
299
- `- **${field.label}** (\`${name}\`): ${field.type}`
300
- ).join('\n')}
301
- `).join('\n\n');
302
-
303
- fs.writeFileSync('docs/objects.md', docs);
304
- ```
305
-
306
- ### Validation and Testing Workflow
307
-
308
- ```typescript
309
- import { MetadataManager } from '@objectstack/metadata';
310
- import { ObjectSchema } from '@objectstack/spec/data';
311
-
312
- async function validateAllMetadata() {
313
- const manager = new MetadataManager({
314
- rootDir: './metadata'
315
- });
316
-
317
- const objects = await manager.loadMany('object');
318
- const errors = [];
319
-
320
- for (const obj of objects) {
321
- const result = ObjectSchema.safeParse(obj);
322
-
323
- if (!result.success) {
324
- errors.push({
325
- name: obj.name,
326
- issues: result.error.issues
327
- });
328
- }
329
- }
330
-
331
- if (errors.length > 0) {
332
- console.error('Validation errors found:');
333
- errors.forEach(({ name, issues }) => {
334
- console.error(`\n${name}:`);
335
- issues.forEach(issue => {
336
- console.error(` - ${issue.path.join('.')}: ${issue.message}`);
337
- });
338
- });
339
- process.exit(1);
340
- }
341
-
342
- console.log(`✅ All ${objects.length} objects validated successfully`);
343
- }
344
- ```
345
-
346
- ### Metadata Versioning Workflow
347
-
348
- ```typescript
349
- import { MetadataManager } from '@objectstack/metadata';
350
- import { execSync } from 'child_process';
351
-
352
- async function versionMetadata() {
353
- const manager = new MetadataManager({
354
- rootDir: './metadata'
355
- });
356
-
357
- const objects = await manager.loadMany('object');
358
-
359
- // Get git user name safely
360
- let modifiedBy = 'unknown';
361
- try {
362
- modifiedBy = execSync('git config user.name', { encoding: 'utf-8' }).trim();
363
- } catch (error) {
364
- console.warn('Could not get git user name, using "unknown"');
365
- }
366
-
367
- // Add version metadata
368
- const versioned = objects.map(obj => ({
369
- ...obj,
370
- metadata: {
371
- ...obj.metadata,
372
- version: '2.0.0',
373
- lastModified: new Date().toISOString(),
374
- modifiedBy
375
- }
376
- }));
377
-
378
- // Save versioned metadata
379
- for (const obj of versioned) {
380
- await manager.save('object', obj.name, obj, {
381
- format: 'typescript',
382
- prettify: true
383
- });
384
- }
385
-
386
- // Commit to version control (if git is available)
387
- try {
388
- execSync('git add metadata/', { stdio: 'inherit' });
389
- execSync('git commit -m "Version bump to 2.0.0"', { stdio: 'inherit' });
390
- } catch (error) {
391
- console.warn('Git commit failed, changes are staged but not committed');
392
- }
393
- }
394
- ```
395
-
396
- ### Import/Export Workflow
397
-
398
- ```typescript
399
- import { MetadataManager } from '@objectstack/metadata';
400
-
401
- async function exportToJSON() {
402
- const manager = new MetadataManager({
403
- rootDir: './metadata'
404
- });
405
-
406
- // Load all metadata
407
- const [objects, views, apps] = await Promise.all([
408
- manager.loadMany('object'),
409
- manager.loadMany('view'),
410
- manager.loadMany('app')
411
- ]);
412
-
413
- // Create unified export
414
- const exportData = {
415
- version: '1.0.0',
416
- exported: new Date().toISOString(),
417
- objects,
418
- views,
419
- apps
420
- };
421
-
422
- // Save as single JSON file
423
- fs.writeFileSync(
424
- 'export/metadata-export.json',
425
- JSON.stringify(exportData, null, 2)
426
- );
427
-
428
- console.log('Export complete!');
429
- }
430
-
431
- async function importFromJSON(filePath: string) {
432
- const manager = new MetadataManager({
433
- rootDir: './metadata'
434
- });
435
-
436
- const importData = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
437
-
438
- // Import objects
439
- for (const obj of importData.objects) {
440
- await manager.save('object', obj.name, obj, {
441
- format: 'typescript'
442
- });
443
- }
444
-
445
- // Import views
446
- for (const view of importData.views) {
447
- await manager.save('view', view.name, view, {
448
- format: 'typescript'
449
- });
450
- }
451
-
452
- console.log('Import complete!');
453
- }
454
- ```
455
-
456
- ## License
457
-
458
- MIT
13
+ Internal package used by `@objectstack/runtime` to process configuration.
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 { FilesystemLoader } from './loaders/filesystem-loader.js';
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';
@@ -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;AAG5E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAGlE,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"}
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 { FilesystemLoader } from './loaders/filesystem-loader.js';
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,EACf,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;IAYnD,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM,CAAC;IAbjB,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAMvC;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;IAwB3C;;OAEG;YACW,QAAQ;IAkBtB;;OAEG;IACH,OAAO,CAAC,YAAY;IAkBpB;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;;;OAIG;IACH,OAAO,CAAC,YAAY;CAIrB"}
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"}
@@ -14,6 +14,13 @@ export class FilesystemLoader {
14
14
  this.logger = logger;
15
15
  this.contract = {
16
16
  name: 'filesystem',
17
+ protocol: 'file',
18
+ capabilities: {
19
+ read: true,
20
+ write: true,
21
+ watch: true,
22
+ list: true,
23
+ },
17
24
  supportedFormats: ['json', 'yaml', 'typescript', 'javascript'],
18
25
  supportsWatch: true,
19
26
  supportsWrite: true,
@@ -82,7 +89,7 @@ export class FilesystemLoader {
82
89
  if (useCache) {
83
90
  this.cache.set(cacheKey, {
84
91
  data,
85
- etag: stats.etag,
92
+ etag: stats.etag || '',
86
93
  timestamp: Date.now(),
87
94
  });
88
95
  }
@@ -205,6 +212,81 @@ export class FilesystemLoader {
205
212
  return [];
206
213
  }
207
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
+ }
208
290
  /**
209
291
  * Find file for a given type and name
210
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,EACvB,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;CACvC"}
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"}