@objectstack/metadata 4.0.4 → 4.1.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Metadata Loading, Persistence & Customization Layer for ObjectStack.**
4
4
 
5
- `@objectstack/metadata` is the central service responsible for loading, validating, persisting and watching all metadata definitions (Objects, Views, Flows, Apps, Agents, etc.) in the ObjectStack platform.
5
+ `@objectstack/metadata` is the central service responsible for loading, validating, optionally persisting, and watching all metadata definitions (Objects, Views, Flows, Apps, Agents, etc.) in the ObjectStack platform.
6
6
 
7
7
  It implements the **`IMetadataService`** contract from `@objectstack/spec` and acts as the single source of truth that all other packages depend on.
8
8
 
@@ -40,17 +40,22 @@ It implements the **`IMetadataService`** contract from `@objectstack/spec` and a
40
40
 
41
41
  ## Core Concepts
42
42
 
43
- ### 1. Metadata Sources (Three-Scope Model)
43
+ ### 1. Metadata Sources (Runtime Boundary)
44
44
 
45
- ObjectStack adopts a three-scope layered model for metadata:
45
+ ObjectStack separates ObjectOS runtime reads from control-plane metadata
46
+ persistence:
46
47
 
47
- | Scope | Storage | Mutability | Description |
48
- |:-----------|:-------------|:-------------|:-------------------------------------------|
49
- | `system` | Filesystem | Read-only | Defined in code, shipped with packages |
50
- | `platform` | Database | Admin-editable | Created/modified by admins via UI |
51
- | `user` | Database | User-editable | Personal customizations per user |
48
+ | Runtime context | Storage | Mutability | Description |
49
+ |:----------------|:--------|:-----------|:------------|
50
+ | ObjectOS local/dev | Filesystem or local artifact | Read-only at boot | `MetadataPlugin` scans files or hydrates from `dist/objectstack.json`. |
51
+ | ObjectOS production | Artifact API response | Read-only at boot | Metadata is immutable for a `commitId` / `checksum`; project DB stores business rows only. |
52
+ | Control plane / tooling | `DatabaseLoader` when explicitly configured | Writable | Stores project metadata revisions, overlays, and history outside the ObjectOS project DB. |
52
53
 
53
- Resolution order: **system** merge(**platform**) merge(**user**).
54
+ `MetadataPlugin` does **not** automatically bridge ObjectQL to
55
+ `DatabaseLoader`, and it does not register `sys_metadata` /
56
+ `sys_metadata_history` into the ObjectOS manifest. Database-backed metadata
57
+ persistence remains available through `MetadataManager.setDatabaseDriver()` or
58
+ `setDataEngine()` for control-plane services that opt in explicitly.
54
59
 
55
60
  ### 2. Loaders
56
61
 
@@ -61,7 +66,7 @@ Loaders are pluggable data sources that know how to read/write metadata from dif
61
66
  | `FilesystemLoader` | `file:` | ✅ | ✅ | ✅ | Implemented |
62
67
  | `MemoryLoader` | `memory:` | ✅ | ✅ | ❌ | Implemented |
63
68
  | `RemoteLoader` | `http:` | ✅ | ✅ | ❌ | Implemented |
64
- | `DatabaseLoader` | `datasource:` | ✅ | ✅ | ❌ | Implemented |
69
+ | `DatabaseLoader` | `datasource:` | ✅ | ✅ | ❌ | Implemented (read-through LRU cache) |
65
70
 
66
71
  ### 3. Serializers
67
72
 
@@ -69,7 +74,7 @@ Serializers convert metadata objects to/from different file formats:
69
74
 
70
75
  - **JSONSerializer** — `.json` files with optional key sorting
71
76
  - **YAMLSerializer** — `.yaml`/`.yml` files (JSON_SCHEMA for security)
72
- - **TypeScriptSerializer** — `.ts`/`.js` module exports (for `defineObject()`, `defineView()`, etc.)
77
+ - **TypeScriptSerializer** — `.ts`/`.js` module exports (for `ObjectSchema.create()`, `defineView()`, etc.)
73
78
 
74
79
  ### 4. Overlay / Customization System
75
80
 
@@ -110,7 +115,76 @@ Integrates with the ObjectStack kernel plugin system:
110
115
 
111
116
  - Registers as the primary `IMetadataService` provider
112
117
  - Auto-loads all metadata types from the filesystem on startup (sorted by `loadOrder`)
118
+ - Can hydrate runtime metadata from a local project artifact (`dist/objectstack.json`)
113
119
  - Supports YAML, JSON, TypeScript, and JavaScript metadata formats
120
+ - Keeps ObjectOS metadata read-only; database persistence is not auto-enabled
121
+
122
+ #### Bootstrap Modes
123
+
124
+ `MetadataPluginConfigSchema.bootstrap` controls how the plugin primes metadata
125
+ on `start()`. Pick the mode that matches the deployment target:
126
+
127
+ | Mode | When to use | Behavior |
128
+ |:-----|:------------|:---------|
129
+ | `eager` (default) | Local dev, traditional servers | Scans filesystem (or hydrates the artifact source if set) at boot. |
130
+ | `lazy` | Cold-start sensitive runtimes, on-demand workloads | Skips the filesystem priming pass — reads flow through `MetadataManager.load*` / `list*` and registered loaders (incl. the DatabaseLoader read-through cache). An `artifactSource` is still honored if provided. |
131
+ | `artifact-only` | **Edge / serverless / read-only production** | Refuses to touch the filesystem. Requires `artifactSource.mode = 'local-file'`. Throws if no artifact source is configured. |
132
+
133
+ ```typescript
134
+ // Edge / serverless: fail-fast if no artifact is wired
135
+ new MetadataPlugin({
136
+ config: { bootstrap: 'artifact-only' },
137
+ artifactSource: { mode: 'local-file', path: './dist/objectstack.json' },
138
+ });
139
+
140
+ // On-demand reads via DatabaseLoader, no FS scan
141
+ new MetadataPlugin({
142
+ config: { bootstrap: 'lazy' },
143
+ });
144
+ ```
145
+
146
+ #### Persistence Write Gates
147
+
148
+ `MetadataManagerConfigSchema.persistence` is a two-axis runtime gate that lets
149
+ you freeze metadata mutation in sealed deployments while leaving reads open.
150
+ Both flags default to `true` so dev / Studio flows are unaffected.
151
+
152
+ | Flag | Effect when `false` |
153
+ |:-----|:--------------------|
154
+ | `persistence.writable` | `MetadataManager.register()` becomes a no-op (or throws when `validation.throwOnError` is set). Use for read-only project kernels booted from a compiled artifact. |
155
+ | `persistence.overlayWritable` | `MetadataManager.saveOverlay()` is rejected. Use to disable Studio overlays in fully-frozen production deployments. |
156
+
157
+ ```typescript
158
+ new MetadataManager({
159
+ persistence: { writable: false, overlayWritable: false },
160
+ });
161
+ ```
162
+
163
+ #### DatabaseLoader Read-Through Cache
164
+
165
+ `DatabaseLoader` wraps `load` / `loadMany` / `list` / `stat` results in a
166
+ generic LRU cache (see `src/utils/lru-cache.ts`). Writes invalidate the
167
+ affected entries, so reads always observe writes made through the same loader
168
+ instance; out-of-band SQL writes are honored within `ttl` milliseconds.
169
+
170
+ Configuration lives under `cache.databaseLoader`:
171
+
172
+ ```typescript
173
+ new MetadataManager({
174
+ datasource: 'default',
175
+ cache: {
176
+ enabled: true,
177
+ databaseLoader: {
178
+ enabled: true,
179
+ maxSize: 500, // Max cached (type, name) entries
180
+ ttl: 60_000, // Cache TTL in milliseconds
181
+ },
182
+ },
183
+ });
184
+ ```
185
+
186
+ The cache exposes diagnostic counters (`size` / `hits` / `misses` / `hitRate`)
187
+ through `LRUCache.stats()` for `metrics` endpoints.
114
188
 
115
189
  ## Metadata Types
116
190
 
@@ -194,7 +268,7 @@ manager.watchService('object', (event) => {
194
268
  ```typescript
195
269
  import { MetadataPlugin } from '@objectstack/metadata/node';
196
270
 
197
- const plugin = MetadataPlugin({
271
+ const plugin = new MetadataPlugin({
198
272
  rootDir: './src',
199
273
  watch: process.env.NODE_ENV === 'development',
200
274
  });
@@ -202,6 +276,19 @@ const plugin = MetadataPlugin({
202
276
  kernel.use(plugin);
203
277
  ```
204
278
 
279
+ ### With Local Artifact Boot
280
+
281
+ ```typescript
282
+ import { MetadataPlugin } from '@objectstack/metadata/node';
283
+
284
+ const plugin = new MetadataPlugin({
285
+ watch: false,
286
+ artifactSource: { mode: 'local-file', path: './dist/objectstack.json' },
287
+ });
288
+
289
+ kernel.use(plugin);
290
+ ```
291
+
205
292
  ## Package Publishing
206
293
 
207
294
  ObjectStack supports **package-level metadata publishing** — all metadata items within a package are published atomically.