-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathmetadata-loader.zod.ts
More file actions
184 lines (167 loc) · 8.62 KB
/
Copy pathmetadata-loader.zod.ts
File metadata and controls
184 lines (167 loc) · 8.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
import { z } from 'zod';
import { retiredKey } from '../shared/retired-key';
/**
* # Metadata Manager Configuration
*
* How the runtime `MetadataManager` is wired: which datasource backs `sys_metadata`, what to fall back to when that datasource is unreachable, cache / watch / validation settings, and the persistence write gates.
*
* The loader and watch *envelope* types (`MetadataFormat`, `MetadataStats`, `MetadataLoadOptions`, `MetadataWatchEvent`, `MetadataLoaderContract`, …) are NOT here — they live in `@objectstack/spec/system` (`system/metadata-persistence.zod`), which is their single source.
*/
// Until #4411 this file ALSO declared its own copy of all eleven of those
// envelope types. Each name existed twice across two subpath entries
// (`@objectstack/spec/kernel` and `@objectstack/spec/system`) with a different
// shape, so which one you got depended on your import path — a coin-flip an
// auto-import or a model completion has no way to win on purpose, and the
// stricter-looking, more heavily documented copy was the DEAD one. Every
// consumer in this repo, `cloud` and `objectui` imported the `system` copy;
// the kernel copies had zero runtime consumers and only their own test
// parsing them, so they were removed under ADR-0049 enforce-or-remove.
// Manager *wiring* stays here; the *envelope* is owned by `system`.
import { lazySchema } from '../shared/lazy-schema';
// `MetadataManagerConfig.formats` is the ONLY surviving use of a format enum in
// this file. It reads the `shared` copy rather than declaring a fourth one —
// same four members, and `shared` is a leaf module so there is no cycle back
// through `system`. Deliberately NOT the `system` enum: that one is a wider
// superset (`yml`/`ts`/`js` aliases) and adopting it here would silently widen
// what this config accepts.
import { MetadataFormatSchema } from '../shared/metadata-types.zod';
/**
* Metadata Fallback Strategy
* Determines behavior when the primary datasource is unavailable.
*/
export const MetadataFallbackStrategySchema = lazySchema(() => z.enum([
'filesystem', // Fall back to filesystem-based loading
'memory', // Fall back to in-memory storage
'none', // No fallback — fail immediately
]));
/**
* Metadata Manager Configuration
*/
export const MetadataManagerConfigSchema = lazySchema(() => z.object({
/**
* Datasource Name Reference
* References a DatasourceSchema.name (e.g. 'default').
* At runtime, resolved from kernel service `driver.{name}` to obtain the actual driver.
*/
datasource: z.string().optional().describe('Datasource name reference for database persistence'),
/**
* Metadata Table Name
* The database table used for metadata storage when datasource is configured.
*/
tableName: z.string().default('sys_metadata').describe('Database table name for metadata storage'),
/**
* Fallback Strategy
* Determines behavior when the primary datasource is unavailable.
*/
fallback: MetadataFallbackStrategySchema.default('none').describe('Fallback strategy when datasource is unavailable'),
/**
* Root directory for metadata (for filesystem loaders)
*/
rootDir: z.string().optional().describe('Root directory path'),
/**
* Enabled serialization formats
*/
formats: z.array(MetadataFormatSchema).default(['typescript', 'json', 'yaml']).describe('Enabled formats'),
/**
* Cache configuration
*/
cache: z.object({
enabled: z.boolean().default(true).describe('Enable caching'),
/**
* Renamed from `ttl` (#14478): the unit lived only in this description
* while `databaseLoader.ttl`, fourteen lines below, was in MILLISECONDS —
* one word, two magnitudes 1000× apart. The unit now lives in the key.
* Tombstoned rather than deleted because this nested object is not
* `.strict()` — a plain deletion would strip the old key in silence.
*/
ttlSeconds: z.number().int().min(0).default(3600).describe('Cache TTL in seconds'),
ttl: retiredKey(
'`cache.ttl` was removed from `MetadataManagerConfig` in @objectstack/spec 17 — ' +
'its unit (seconds) lived only in the description, while the nested `cache.databaseLoader.ttl` ' +
'spelled the same word in milliseconds, so one key name meant two magnitudes 1000× apart. ' +
'Rename the key to `ttlSeconds`; the value (seconds) is unchanged.',
),
maxSize: z.number().int().min(0).optional().describe('Max cache size in bytes'),
/**
* DatabaseLoader read-through cache.
*
* The DatabaseLoader caches `load`/`loadMany`/`list`/`stat` results in an
* LRU keyed by `(type, name)`. All write paths invalidate the affected
* entry, so reads always observe writes made through the same loader
* instance. External writes (out-of-band SQL) are honored within `ttlMs`
* milliseconds.
*/
databaseLoader: z.object({
enabled: z.boolean().default(true).describe('Enable DatabaseLoader cache'),
maxSize: z.number().int().min(0).default(500).describe('Max cached entries'),
ttlMs: z.number().int().min(0).default(60_000).describe('Cache TTL in milliseconds'),
ttl: retiredKey(
'`cache.databaseLoader.ttl` was removed from `MetadataManagerConfig` in @objectstack/spec 17 ' +
'— its unit (milliseconds) lived only in the description, while the outer `cache.ttl` ' +
'spelled the same word in seconds, so one key name meant two magnitudes 1000× apart. ' +
'Rename the key to `ttlMs`; the value (milliseconds) is unchanged.',
),
}).optional().describe('DatabaseLoader read-through cache'),
}).optional().describe('Cache settings'),
/**
* Watch for file changes
*/
watch: z.boolean().default(false).describe('Enable file watching'),
/**
* Watch options
*/
watchOptions: z.object({
ignored: z.array(z.string()).optional().describe('Patterns to ignore'),
persistent: z.boolean().default(true).describe('Keep process running'),
ignoreInitial: z.boolean().default(true).describe('Ignore initial add events'),
}).optional().describe('File watcher options'),
/**
* Validation settings
*/
validation: z.object({
strict: z.boolean().default(true).describe('Strict validation'),
throwOnError: z.boolean().default(true).describe('Throw on validation error'),
}).optional().describe('Validation settings'),
/**
* Loader-specific options
*/
loaderOptions: z.record(z.string(), z.unknown()).optional().describe('Loader-specific configuration'),
/**
* Persistence Write Gate
*
* Controls whether the metadata layer accepts mutations at runtime. Read
* paths are always permitted.
*
* - `writable: false` — `MetadataManager.register()` becomes a no-op
* (or throws, depending on `validation.throwOnError`). Useful for
* read-only project kernels booted from a compiled artifact, where the
* running process must never write back to `sys_metadata`.
*
* Defaults to `true` so existing dev / Studio flows are unaffected.
*
* `overlayWritable` was REMOVED in v17 (#13135, ADR-0049 enforce-or-remove):
* the only thing it ever gated was `MetadataManager.saveOverlay()` — a
* method of the paper metadata-customization protocol, reachable only from
* its own unit tests (no route or UI ever called it) and removed with that
* protocol. Tombstoned rather than deleted because this nested object is
* not `.strict()` — a plain deletion would strip the key in silence.
*/
persistence: z.object({
writable: z.boolean().default(true).describe('Allow base metadata writes via register()'),
overlayWritable: retiredKey(
'`persistence.overlayWritable` was removed from `MetadataManagerConfig` in ' +
'@objectstack/spec 17 (ADR-0049 enforce-or-remove) — the only thing it gated was ' +
'`MetadataManager.saveOverlay()`, a paper-protocol method no route or UI ever called, ' +
'removed with the metadata-customization protocol (ADR-0126 supersedes it on the record). ' +
'Delete the key. The base write gate that remains is `persistence.writable`; the real ' +
"org-overlay writes (ADR-0005) ride the REST meta write doors' `manage_metadata` " +
'permission gate, not this flag.',
),
}).optional().describe('Persistence write gates'),
}));
// Export types
export type MetadataManagerConfig = z.input<typeof MetadataManagerConfigSchema>;
/** Post-parse shape of {@link MetadataManagerConfig} — defaults applied, transforms run (ADR-0122). */
export type MetadataManagerConfigParsed = z.infer<typeof MetadataManagerConfigSchema>;
export type MetadataFallbackStrategy = z.input<typeof MetadataFallbackStrategySchema>;