@objectstack/metadata 0.9.2 → 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,21 @@
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
+
3
19
  ## 0.9.2
4
20
 
5
21
  ### 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.
@@ -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.2",
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.2",
20
- "@objectstack/spec": "0.9.2",
21
- "@objectstack/types": "0.9.2"
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