@objectstack/metadata 0.9.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @objectstack/metadata
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - Major version release for ObjectStack Protocol v1.0.
8
+ - Stabilized Protocol Definitions
9
+ - Enhanced Runtime Plugin Support
10
+ - Fixed Type Compliance across Monorepo
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies
15
+ - @objectstack/spec@1.0.0
16
+ - @objectstack/core@1.0.0
17
+ - @objectstack/types@1.0.0
18
+
19
+ ## 0.9.2
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies
24
+ - @objectstack/spec@0.9.2
25
+ - @objectstack/core@0.9.2
26
+ - @objectstack/types@0.9.2
27
+
3
28
  ## 0.9.1
4
29
 
5
30
  ### Patch Changes
package/README.md CHANGED
@@ -1,451 +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
- ## Installation
16
-
17
- ```bash
18
- pnpm add @objectstack/metadata
19
- ```
20
-
21
- ## Quick Start
22
-
23
- ```typescript
24
- import { MetadataManager } from '@objectstack/metadata';
25
- import type { ServiceObject } from '@objectstack/spec/data';
26
-
27
- // Create manager
28
- const manager = new MetadataManager({
29
- rootDir: './metadata',
30
- formats: ['typescript', 'json', 'yaml'],
31
- cache: { enabled: true, ttl: 3600 },
32
- watch: process.env.NODE_ENV === 'development',
33
- });
34
-
35
- // Load metadata
36
- const customer = await manager.load<ServiceObject>('object', 'customer');
37
-
38
- // Save metadata
39
- await manager.save('object', 'project', projectObject, {
40
- format: 'typescript',
41
- prettify: true,
42
- });
43
-
44
- // Load multiple items
45
- const objects = await manager.loadMany<ServiceObject>('object', {
46
- patterns: ['**/*.object.ts', '**/*.object.json'],
47
- });
48
-
49
- // Watch for changes
50
- manager.watch('object', (event) => {
51
- console.log(`Object ${event.type}:`, event.name);
52
- });
53
- ```
54
-
55
- ## API
56
-
57
- ### MetadataManager
58
-
59
- Main class for metadata operations.
60
-
61
- #### Constructor
62
-
63
- ```typescript
64
- new MetadataManager(config: MetadataManagerConfig)
65
- ```
66
-
67
- **Config options:**
68
- - `rootDir` - Root directory for metadata files
69
- - `formats` - Enabled serialization formats (default: `['typescript', 'json', 'yaml']`)
70
- - `cache` - Cache configuration with `enabled` and `ttl` options
71
- - `watch` - Enable file watching (default: `false`)
72
- - `watchOptions` - File watcher options (`ignored`, `persistent`, `ignoreInitial`)
73
-
74
- #### Methods
75
-
76
- **load<T>(type: string, name: string, options?: MetadataLoadOptions): Promise<T | null>**
77
-
78
- Load a single metadata item.
79
-
80
- ```typescript
81
- const customer = await manager.load<ServiceObject>('object', 'customer');
82
- ```
83
-
84
- **loadMany<T>(type: string, options?: MetadataLoadOptions): Promise<T[]>**
85
-
86
- Load multiple metadata items matching patterns.
87
-
88
- ```typescript
89
- const objects = await manager.loadMany<ServiceObject>('object', {
90
- patterns: ['**/*.object.ts'],
91
- limit: 100,
92
- });
93
- ```
94
-
95
- **save<T>(type: string, name: string, data: T, options?: MetadataSaveOptions): Promise<MetadataSaveResult>**
96
-
97
- Save metadata to disk.
98
-
99
- ```typescript
100
- await manager.save('object', 'customer', customerObject, {
101
- format: 'typescript',
102
- prettify: true,
103
- backup: true,
104
- });
105
- ```
106
-
107
- **exists(type: string, name: string): Promise<boolean>**
108
-
109
- Check if metadata item exists.
110
-
111
- ```typescript
112
- const exists = await manager.exists('object', 'customer');
113
- ```
114
-
115
- **list(type: string): Promise<string[]>**
116
-
117
- List all items of a type.
118
-
119
- ```typescript
120
- const objectNames = await manager.list('object');
121
- ```
122
-
123
- **watch(type: string, callback: WatchCallback): void**
124
-
125
- Watch for metadata changes.
126
-
127
- ```typescript
128
- manager.watch('object', (event) => {
129
- if (event.type === 'added') {
130
- console.log('New object:', event.name);
131
- }
132
- });
133
- ```
134
-
135
- **stopWatching(): Promise<void>**
136
-
137
- Stop all file watching.
138
-
139
- ## Serialization Formats
140
-
141
- ### JSON
142
-
143
- ```json
144
- {
145
- "name": "customer",
146
- "label": "Customer",
147
- "fields": {
148
- "name": { "type": "text", "label": "Name" }
149
- }
150
- }
151
- ```
152
-
153
- ### YAML
154
-
155
- ```yaml
156
- name: customer
157
- label: Customer
158
- fields:
159
- name:
160
- type: text
161
- label: Name
162
- ```
163
-
164
- ### TypeScript
165
-
166
- ```typescript
167
- import type { ServiceObject } from '@objectstack/spec/data';
168
-
169
- export const metadata: ServiceObject = {
170
- name: 'customer',
171
- label: 'Customer',
172
- fields: {
173
- name: { type: 'text', label: 'Name' },
174
- },
175
- };
176
-
177
- export default metadata;
178
- ```
179
-
180
- ## Architecture
181
-
182
- The metadata package is designed as a Layer 3 package in the ObjectStack architecture:
183
-
184
- ```
185
- @objectstack/metadata (Layer 3)
186
- ├── Dependencies:
187
- │ ├── @objectstack/spec (validation)
188
- │ ├── @objectstack/core (logging, DI)
189
- │ ├── @objectstack/types (shared types)
190
- │ ├── glob (file pattern matching)
191
- │ ├── js-yaml (YAML support)
192
- │ └── chokidar (file watching)
193
- └── Used By:
194
- ├── @objectstack/cli (code generation)
195
- ├── @objectstack/runtime (manifest loading)
196
- └── @objectstack/objectql (registry persistence)
197
- ```
198
-
199
- ## Common Workflows
200
-
201
- ### Development Workflow with File Watching
202
-
203
- ```typescript
204
- import { MetadataManager } from '@objectstack/metadata';
205
-
206
- const manager = new MetadataManager({
207
- rootDir: './metadata',
208
- watch: true, // Enable file watching
209
- cache: { enabled: true, ttl: 3600 }
210
- });
211
-
212
- // Watch for changes and reload
213
- manager.watch('object', async (event) => {
214
- if (event.type === 'modified' || event.type === 'added') {
215
- console.log(`Reloading object: ${event.name}`);
216
- const updated = await manager.load('object', event.name);
217
-
218
- // Notify the system to reload
219
- await objectQL.reloadObject(event.name, updated);
220
- }
221
- });
222
-
223
- // Hot module replacement for development
224
- console.log('Watching metadata files for changes...');
225
- ```
226
-
227
- ### Metadata Migration Workflow
228
-
229
- ```typescript
230
- import { MetadataManager } from '@objectstack/metadata';
231
-
232
- async function migrateMetadata() {
233
- const manager = new MetadataManager({
234
- rootDir: './metadata'
235
- });
236
-
237
- // 1. Load all existing objects
238
- const objects = await manager.loadMany('object');
239
-
240
- // 2. Transform metadata (e.g., rename field)
241
- const transformed = objects.map(obj => ({
242
- ...obj,
243
- fields: Object.entries(obj.fields).reduce((acc, [key, field]) => {
244
- // Rename 'description' to 'notes'
245
- const newKey = key === 'description' ? 'notes' : key;
246
- acc[newKey] = field;
247
- return acc;
248
- }, {})
249
- }));
250
-
251
- // 3. Save with backup
252
- for (const obj of transformed) {
253
- await manager.save('object', obj.name, obj, {
254
- format: 'typescript',
255
- backup: true, // Create .bak file
256
- prettify: true
257
- });
258
- }
259
-
260
- console.log(`Migrated ${objects.length} objects`);
261
- }
262
- ```
263
-
264
- ### Multi-Format Support Workflow
265
-
266
- ```typescript
267
- import { MetadataManager } from '@objectstack/metadata';
268
-
269
- const manager = new MetadataManager({
270
- rootDir: './metadata',
271
- formats: ['typescript', 'json', 'yaml']
272
- });
273
-
274
- // Load from any format - manager auto-detects
275
- const customer = await manager.load('object', 'customer');
276
- // Tries: customer.object.ts, customer.object.json, customer.object.yaml
277
-
278
- // Save in preferred format
279
- await manager.save('object', 'customer', customer, {
280
- format: 'typescript' // Convert to TypeScript
281
- });
282
-
283
- // Generate documentation from metadata
284
- const allObjects = await manager.loadMany('object');
285
- const docs = allObjects.map(obj => `
286
- ## ${obj.label}
287
-
288
- **Name:** ${obj.name}
289
-
290
- **Fields:**
291
- ${Object.entries(obj.fields).map(([name, field]) =>
292
- `- **${field.label}** (\`${name}\`): ${field.type}`
293
- ).join('\n')}
294
- `).join('\n\n');
295
-
296
- fs.writeFileSync('docs/objects.md', docs);
297
- ```
298
-
299
- ### Validation and Testing Workflow
300
-
301
- ```typescript
302
- import { MetadataManager } from '@objectstack/metadata';
303
- import { ObjectSchema } from '@objectstack/spec/data';
304
-
305
- async function validateAllMetadata() {
306
- const manager = new MetadataManager({
307
- rootDir: './metadata'
308
- });
309
-
310
- const objects = await manager.loadMany('object');
311
- const errors = [];
312
-
313
- for (const obj of objects) {
314
- const result = ObjectSchema.safeParse(obj);
315
-
316
- if (!result.success) {
317
- errors.push({
318
- name: obj.name,
319
- issues: result.error.issues
320
- });
321
- }
322
- }
323
-
324
- if (errors.length > 0) {
325
- console.error('Validation errors found:');
326
- errors.forEach(({ name, issues }) => {
327
- console.error(`\n${name}:`);
328
- issues.forEach(issue => {
329
- console.error(` - ${issue.path.join('.')}: ${issue.message}`);
330
- });
331
- });
332
- process.exit(1);
333
- }
334
-
335
- console.log(`✅ All ${objects.length} objects validated successfully`);
336
- }
337
- ```
338
-
339
- ### Metadata Versioning Workflow
340
-
341
- ```typescript
342
- import { MetadataManager } from '@objectstack/metadata';
343
- import { execSync } from 'child_process';
344
-
345
- async function versionMetadata() {
346
- const manager = new MetadataManager({
347
- rootDir: './metadata'
348
- });
349
-
350
- const objects = await manager.loadMany('object');
351
-
352
- // Get git user name safely
353
- let modifiedBy = 'unknown';
354
- try {
355
- modifiedBy = execSync('git config user.name', { encoding: 'utf-8' }).trim();
356
- } catch (error) {
357
- console.warn('Could not get git user name, using "unknown"');
358
- }
359
-
360
- // Add version metadata
361
- const versioned = objects.map(obj => ({
362
- ...obj,
363
- metadata: {
364
- ...obj.metadata,
365
- version: '2.0.0',
366
- lastModified: new Date().toISOString(),
367
- modifiedBy
368
- }
369
- }));
370
-
371
- // Save versioned metadata
372
- for (const obj of versioned) {
373
- await manager.save('object', obj.name, obj, {
374
- format: 'typescript',
375
- prettify: true
376
- });
377
- }
378
-
379
- // Commit to version control (if git is available)
380
- try {
381
- execSync('git add metadata/', { stdio: 'inherit' });
382
- execSync('git commit -m "Version bump to 2.0.0"', { stdio: 'inherit' });
383
- } catch (error) {
384
- console.warn('Git commit failed, changes are staged but not committed');
385
- }
386
- }
387
- ```
388
-
389
- ### Import/Export Workflow
390
-
391
- ```typescript
392
- import { MetadataManager } from '@objectstack/metadata';
393
-
394
- async function exportToJSON() {
395
- const manager = new MetadataManager({
396
- rootDir: './metadata'
397
- });
398
-
399
- // Load all metadata
400
- const [objects, views, apps] = await Promise.all([
401
- manager.loadMany('object'),
402
- manager.loadMany('view'),
403
- manager.loadMany('app')
404
- ]);
405
-
406
- // Create unified export
407
- const exportData = {
408
- version: '1.0.0',
409
- exported: new Date().toISOString(),
410
- objects,
411
- views,
412
- apps
413
- };
414
-
415
- // Save as single JSON file
416
- fs.writeFileSync(
417
- 'export/metadata-export.json',
418
- JSON.stringify(exportData, null, 2)
419
- );
420
-
421
- console.log('Export complete!');
422
- }
423
-
424
- async function importFromJSON(filePath: string) {
425
- const manager = new MetadataManager({
426
- rootDir: './metadata'
427
- });
428
-
429
- const importData = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
430
-
431
- // Import objects
432
- for (const obj of importData.objects) {
433
- await manager.save('object', obj.name, obj, {
434
- format: 'typescript'
435
- });
436
- }
437
-
438
- // Import views
439
- for (const view of importData.views) {
440
- await manager.save('view', view.name, view, {
441
- format: 'typescript'
442
- });
443
- }
444
-
445
- console.log('Import complete!');
446
- }
447
- ```
448
-
449
- ## License
450
-
451
- MIT
13
+ Internal package used by `@objectstack/runtime` to process configuration.
@@ -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,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;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;IAwB3C;;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
  }
package/dist/plugin.d.ts CHANGED
@@ -9,7 +9,7 @@ export declare class MetadataPlugin implements Plugin {
9
9
  private manager;
10
10
  private options;
11
11
  constructor(options?: MetadataPluginOptions);
12
- init(ctx: PluginContext): Promise<void>;
13
- start(ctx: PluginContext): Promise<void>;
12
+ init: (ctx: PluginContext) => Promise<void>;
13
+ start: (ctx: PluginContext) => Promise<void>;
14
14
  }
15
15
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAI1D,MAAM,WAAW,qBAAqB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,cAAe,YAAW,MAAM;IACzC,IAAI,SAA8B;IAClC,OAAO,SAAW;IAElB,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,OAAO,CAAwB;gBAE3B,OAAO,GAAE,qBAA0B;IAezC,IAAI,CAAC,GAAG,EAAE,aAAa;IAQvB,KAAK,CAAC,GAAG,EAAE,aAAa;CAiDjC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAI1D,MAAM,WAAW,qBAAqB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,cAAe,YAAW,MAAM;IACzC,IAAI,SAA8B;IAClC,OAAO,SAAW;IAElB,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,OAAO,CAAwB;gBAE3B,OAAO,GAAE,qBAA0B;IAe/C,IAAI,GAAU,KAAK,aAAa,mBAM/B;IAED,KAAK,GAAU,KAAK,aAAa,mBAgDhC;CACJ"}
package/dist/plugin.js CHANGED
@@ -4,6 +4,59 @@ export class MetadataPlugin {
4
4
  constructor(options = {}) {
5
5
  this.name = 'com.objectstack.metadata';
6
6
  this.version = '1.0.0';
7
+ this.init = async (ctx) => {
8
+ ctx.logger.info('Initializing Metadata Manager', { root: this.options.rootDir || process.cwd() });
9
+ // Register Metadata Manager as a service
10
+ // This allows other plugins to query raw metadata or listen to changes
11
+ ctx.registerService('metadata', this.manager);
12
+ };
13
+ this.start = async (ctx) => {
14
+ ctx.logger.info('Loading metadata...');
15
+ // Define metadata types directly from the Protocol Definition
16
+ // This ensures the loader is always in sync with the Spec
17
+ const metadataTypes = Object.keys(ObjectStackDefinitionSchema.shape)
18
+ .filter(key => key !== 'manifest'); // Manifest is handled separately
19
+ for (const type of metadataTypes) {
20
+ try {
21
+ // Try to load metadata of this type
22
+ const items = await this.manager.loadMany(type, {
23
+ recursive: true
24
+ });
25
+ if (items.length > 0) {
26
+ ctx.logger.info(`Loaded ${items.length} ${type}`);
27
+ // Helper: Register with ObjectQL Registry
28
+ const ql = ctx.getService('objectql');
29
+ if (ql && ql.registry) {
30
+ items.forEach((item) => {
31
+ // Determine key field (id or name)
32
+ const keyField = item.id ? 'id' : 'name';
33
+ // Map plural type to singular/registry type if needed
34
+ // For now, we use the singular form for standard types:
35
+ // objects -> object, apps -> app, etc.
36
+ // But Registry seems to accept arbitrary strings.
37
+ // To match Protocol standard, we might want to normalize.
38
+ // Let's use the directory name (plural) as the type for now,
39
+ // OR map 'objects' -> 'object' specifically.
40
+ let registryType = type;
41
+ if (type === 'objects')
42
+ registryType = 'object';
43
+ if (type === 'apps')
44
+ registryType = 'app';
45
+ if (type === 'plugins')
46
+ registryType = 'plugin';
47
+ if (type === 'functions')
48
+ registryType = 'function';
49
+ ql.registry.registerItem(registryType, item, keyField);
50
+ });
51
+ }
52
+ }
53
+ }
54
+ catch (e) {
55
+ // Ignore missing directories or errors
56
+ // ctx.logger.debug(`No metadata found for type: ${type}`);
57
+ }
58
+ }
59
+ };
7
60
  this.options = {
8
61
  watch: true,
9
62
  ...options
@@ -15,57 +68,4 @@ export class MetadataPlugin {
15
68
  formats: ['yaml', 'json', 'typescript', 'javascript']
16
69
  });
17
70
  }
18
- async init(ctx) {
19
- ctx.logger.info('Initializing Metadata Manager', { root: this.options.rootDir || process.cwd() });
20
- // Register Metadata Manager as a service
21
- // This allows other plugins to query raw metadata or listen to changes
22
- ctx.registerService('metadata', this.manager);
23
- }
24
- async start(ctx) {
25
- ctx.logger.info('Loading metadata...');
26
- // Define metadata types directly from the Protocol Definition
27
- // This ensures the loader is always in sync with the Spec
28
- const metadataTypes = Object.keys(ObjectStackDefinitionSchema.shape)
29
- .filter(key => key !== 'manifest'); // Manifest is handled separately
30
- for (const type of metadataTypes) {
31
- try {
32
- // Try to load metadata of this type
33
- const items = await this.manager.loadMany(type, {
34
- recursive: true
35
- });
36
- if (items.length > 0) {
37
- ctx.logger.info(`Loaded ${items.length} ${type}`);
38
- // Helper: Register with ObjectQL Registry
39
- const ql = ctx.getService('objectql');
40
- if (ql && ql.registry) {
41
- items.forEach((item) => {
42
- // Determine key field (id or name)
43
- const keyField = item.id ? 'id' : 'name';
44
- // Map plural type to singular/registry type if needed
45
- // For now, we use the singular form for standard types:
46
- // objects -> object, apps -> app, etc.
47
- // But Registry seems to accept arbitrary strings.
48
- // To match Protocol standard, we might want to normalize.
49
- // Let's use the directory name (plural) as the type for now,
50
- // OR map 'objects' -> 'object' specifically.
51
- let registryType = type;
52
- if (type === 'objects')
53
- registryType = 'object';
54
- if (type === 'apps')
55
- registryType = 'app';
56
- if (type === 'plugins')
57
- registryType = 'plugin';
58
- if (type === 'functions')
59
- registryType = 'function';
60
- ql.registry.registerItem(registryType, item, keyField);
61
- });
62
- }
63
- }
64
- }
65
- catch (e) {
66
- // Ignore missing directories or errors
67
- // ctx.logger.debug(`No metadata found for type: ${type}`);
68
- }
69
- }
70
- }
71
71
  }
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@objectstack/metadata",
3
- "version": "0.9.1",
3
+ "version": "1.0.0",
4
+ "license": "Apache-2.0",
4
5
  "description": "Metadata loading, saving, and persistence for ObjectStack",
5
6
  "main": "src/index.ts",
6
7
  "types": "src/index.ts",
@@ -16,9 +17,9 @@
16
17
  "js-yaml": "^4.1.0",
17
18
  "chokidar": "^3.5.3",
18
19
  "zod": "^4.3.6",
19
- "@objectstack/core": "0.9.1",
20
- "@objectstack/spec": "0.9.1",
21
- "@objectstack/types": "0.9.1"
20
+ "@objectstack/core": "1.0.0",
21
+ "@objectstack/spec": "1.0.0",
22
+ "@objectstack/types": "1.0.0"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@types/js-yaml": "^4.0.9",
@@ -22,6 +22,13 @@ import type { MetadataSerializer } from '../serializers/serializer-interface.js'
22
22
  export class FilesystemLoader implements MetadataLoader {
23
23
  readonly contract: MetadataLoaderContract = {
24
24
  name: 'filesystem',
25
+ protocol: 'file',
26
+ capabilities: {
27
+ read: true,
28
+ write: true,
29
+ watch: true,
30
+ list: true,
31
+ },
25
32
  supportedFormats: ['json', 'yaml', 'typescript', 'javascript'],
26
33
  supportsWatch: true,
27
34
  supportsWrite: true,
@@ -99,7 +106,7 @@ export class FilesystemLoader implements MetadataLoader {
99
106
 
100
107
  // Load and deserialize
101
108
  const content = await fs.readFile(filePath, 'utf-8');
102
- const serializer = this.getSerializer(stats.format);
109
+ const serializer = this.getSerializer(stats.format!);
103
110
 
104
111
  if (!serializer) {
105
112
  throw new Error(`No serializer found for format: ${stats.format}`);
@@ -111,7 +118,7 @@ export class FilesystemLoader implements MetadataLoader {
111
118
  if (useCache) {
112
119
  this.cache.set(cacheKey, {
113
120
  data,
114
- etag: stats.etag,
121
+ etag: stats.etag || '',
115
122
  timestamp: Date.now(),
116
123
  });
117
124
  }
package/src/plugin.ts CHANGED
@@ -29,7 +29,7 @@ export class MetadataPlugin implements Plugin {
29
29
  });
30
30
  }
31
31
 
32
- async init(ctx: PluginContext) {
32
+ init = async (ctx: PluginContext) => {
33
33
  ctx.logger.info('Initializing Metadata Manager', { root: this.options.rootDir || process.cwd() });
34
34
 
35
35
  // Register Metadata Manager as a service
@@ -37,7 +37,7 @@ export class MetadataPlugin implements Plugin {
37
37
  ctx.registerService('metadata', this.manager);
38
38
  }
39
39
 
40
- async start(ctx: PluginContext) {
40
+ start = async (ctx: PluginContext) => {
41
41
  ctx.logger.info('Loading metadata...');
42
42
 
43
43
  // Define metadata types directly from the Protocol Definition