@objectstack/core 17.0.0-rc.0 → 17.0.0-rc.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.
- package/CHANGELOG.md +3047 -0
- package/dist/index.cjs +216 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +222 -18
- package/dist/index.d.ts +222 -18
- package/dist/index.js +206 -91
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -62,6 +62,7 @@ __export(index_exports, {
|
|
|
62
62
|
ServiceLifecycle: () => ServiceLifecycle,
|
|
63
63
|
UnknownFilterTokenError: () => UnknownFilterTokenError,
|
|
64
64
|
UnresolvedFilterTokenError: () => UnresolvedFilterTokenError,
|
|
65
|
+
assertInitServiceRequirements: () => assertInitServiceRequirements,
|
|
65
66
|
bucketKeyToCalendarRange: () => bucketKeyToCalendarRange,
|
|
66
67
|
buildPermissionsFromGrants: () => buildPermissionsFromGrants,
|
|
67
68
|
bulkWrite: () => bulkWrite,
|
|
@@ -80,6 +81,7 @@ __export(index_exports, {
|
|
|
80
81
|
deepMerge: () => deepMerge,
|
|
81
82
|
defaultIsTransientError: () => defaultIsTransientError,
|
|
82
83
|
derivePosture: () => derivePosture,
|
|
84
|
+
describeInitOrderFault: () => describeInitOrderFault,
|
|
83
85
|
evaluateAuthGate: () => evaluateAuthGate,
|
|
84
86
|
extractApiKey: () => extractApiKey,
|
|
85
87
|
filterTokenContextFrom: () => filterTokenContextFrom,
|
|
@@ -93,6 +95,7 @@ __export(index_exports, {
|
|
|
93
95
|
isGrantActive: () => isGrantActive,
|
|
94
96
|
isGrantExpired: () => isGrantExpired,
|
|
95
97
|
isNode: () => isNode,
|
|
98
|
+
nextUtcCalendarDay: () => import_data.nextUtcCalendarDay,
|
|
96
99
|
parseScopes: () => parseScopes,
|
|
97
100
|
parseSignature: () => parseSignature,
|
|
98
101
|
postureVisibleRows: () => postureVisibleRows,
|
|
@@ -103,10 +106,13 @@ __export(index_exports, {
|
|
|
103
106
|
resolveFilterTokens: () => resolveFilterTokens,
|
|
104
107
|
resolveLocale: () => resolveLocale,
|
|
105
108
|
resolveLocalizationContext: () => resolveLocalizationContext,
|
|
109
|
+
resolvePluginOrder: () => resolvePluginOrder,
|
|
106
110
|
resolveUserAuthzGrants: () => resolveUserAuthzGrants,
|
|
107
111
|
safeExit: () => safeExit,
|
|
108
112
|
shouldDenyAnonymous: () => shouldDenyAnonymous,
|
|
109
113
|
signPayload: () => signPayload,
|
|
114
|
+
utcInstantMs: () => import_data.utcInstantMs,
|
|
115
|
+
validateInitServiceContract: () => validateInitServiceContract,
|
|
110
116
|
verifyPayload: () => verifyPayload,
|
|
111
117
|
verifyPlatformSignature: () => verifyPlatformSignature,
|
|
112
118
|
verifyPluginArtifact: () => verifyPluginArtifact,
|
|
@@ -117,6 +123,89 @@ __export(index_exports, {
|
|
|
117
123
|
});
|
|
118
124
|
module.exports = __toCommonJS(index_exports);
|
|
119
125
|
|
|
126
|
+
// src/plugin-order.ts
|
|
127
|
+
function resolvePluginOrder(plugins) {
|
|
128
|
+
const resolved = [];
|
|
129
|
+
const visited = /* @__PURE__ */ new Set();
|
|
130
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
131
|
+
const visit = (pluginName) => {
|
|
132
|
+
if (visited.has(pluginName)) return;
|
|
133
|
+
if (visiting.has(pluginName)) {
|
|
134
|
+
throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
|
|
135
|
+
}
|
|
136
|
+
const plugin = plugins.get(pluginName);
|
|
137
|
+
if (!plugin) {
|
|
138
|
+
throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
|
|
139
|
+
}
|
|
140
|
+
visiting.add(pluginName);
|
|
141
|
+
for (const dep of plugin.dependencies ?? []) {
|
|
142
|
+
if (!plugins.has(dep)) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
visit(dep);
|
|
148
|
+
}
|
|
149
|
+
for (const dep of plugin.optionalDependencies ?? []) {
|
|
150
|
+
if (plugins.has(dep)) visit(dep);
|
|
151
|
+
}
|
|
152
|
+
visiting.delete(pluginName);
|
|
153
|
+
visited.add(pluginName);
|
|
154
|
+
resolved.push(plugin);
|
|
155
|
+
};
|
|
156
|
+
for (const pluginName of plugins.keys()) {
|
|
157
|
+
visit(pluginName);
|
|
158
|
+
}
|
|
159
|
+
return resolved;
|
|
160
|
+
}
|
|
161
|
+
function validateInitServiceContract(ordered, isServiceRegistered) {
|
|
162
|
+
const providerSlot = /* @__PURE__ */ new Map();
|
|
163
|
+
ordered.forEach((plugin, slot) => {
|
|
164
|
+
for (const service of plugin.providesServices ?? []) {
|
|
165
|
+
if (!providerSlot.has(service)) {
|
|
166
|
+
providerSlot.set(service, { plugin: plugin.name, slot });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
const violations = [];
|
|
171
|
+
ordered.forEach((plugin, slot) => {
|
|
172
|
+
for (const service of plugin.requiresServices ?? []) {
|
|
173
|
+
if (isServiceRegistered(service)) continue;
|
|
174
|
+
const provider = providerSlot.get(service);
|
|
175
|
+
if (provider && provider.slot > slot) {
|
|
176
|
+
violations.push(
|
|
177
|
+
`'${plugin.name}' requires service '${service}' during init, but '${service}' is provided by '${provider.plugin}', which initializes later (slot ${provider.slot} vs ${slot}). Registration order is not a contract \u2014 declare '${provider.plugin}' in '${plugin.name}'.dependencies (hard) or .optionalDependencies (order-if-present) so the kernel hoists it.`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
if (violations.length > 0) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`[Kernel] Plugin ordering contract violated (#4131):
|
|
185
|
+
- ${violations.join("\n - ")}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function describeInitOrderFault(currentlyInitializing, plugins, serviceName) {
|
|
190
|
+
if (!currentlyInitializing) return "";
|
|
191
|
+
let providerHint = "";
|
|
192
|
+
for (const plugin of plugins) {
|
|
193
|
+
if (plugin.providesServices?.includes(serviceName)) {
|
|
194
|
+
providerHint = ` '${serviceName}' is provided by composed plugin '${plugin.name}', which has not initialized yet \u2014 declare it in the requiring plugin's dependencies/optionalDependencies.`;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return ` (while plugin '${currentlyInitializing}' was initializing \u2014 a composition/ordering fault, #4131.${providerHint})`;
|
|
199
|
+
}
|
|
200
|
+
function assertInitServiceRequirements(plugin, isServiceRegistered) {
|
|
201
|
+
for (const service of plugin.requiresServices ?? []) {
|
|
202
|
+
if (isServiceRegistered(service)) continue;
|
|
203
|
+
throw new Error(
|
|
204
|
+
`[Kernel] Plugin '${plugin.name}' requires service '${service}' at init, but no such service is registered at this point of the boot. No composed plugin that initializes earlier provides it \u2014 compose a provider (and, if it initializes later without declaring '${service}' in providesServices, order it ahead via this plugin's dependencies/optionalDependencies) (#4131).`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
120
209
|
// src/kernel-base.ts
|
|
121
210
|
var ObjectKernelBase = class {
|
|
122
211
|
constructor(logger) {
|
|
@@ -167,7 +256,9 @@ var ObjectKernelBase = class {
|
|
|
167
256
|
if (this.services instanceof Map) {
|
|
168
257
|
const service = this.services.get(name);
|
|
169
258
|
if (!service) {
|
|
170
|
-
throw new Error(
|
|
259
|
+
throw new Error(
|
|
260
|
+
`[Kernel] Service '${name}' not found${this.describeInitOrderFault(name)}`
|
|
261
|
+
);
|
|
171
262
|
}
|
|
172
263
|
return service;
|
|
173
264
|
} else {
|
|
@@ -218,40 +309,37 @@ var ObjectKernelBase = class {
|
|
|
218
309
|
};
|
|
219
310
|
}
|
|
220
311
|
/**
|
|
221
|
-
* Resolve plugin dependencies using topological sort
|
|
312
|
+
* Resolve plugin dependencies using topological sort — `dependencies`
|
|
313
|
+
* hard, `optionalDependencies` order-if-present (ADR-0116, #4131). One
|
|
314
|
+
* implementation shared with ObjectKernel via `plugin-order.ts`.
|
|
222
315
|
* @returns Ordered list of plugins (dependencies first)
|
|
223
316
|
*/
|
|
224
317
|
resolveDependencies() {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
};
|
|
251
|
-
for (const pluginName of this.plugins.keys()) {
|
|
252
|
-
visit(pluginName);
|
|
253
|
-
}
|
|
254
|
-
return resolved;
|
|
318
|
+
return resolvePluginOrder(this.plugins);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Whether a service is registered on this kernel right now. Backs the
|
|
322
|
+
* init-service contract checks (#4131).
|
|
323
|
+
*/
|
|
324
|
+
hasRegisteredService(name) {
|
|
325
|
+
return this.services.has(name);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Pre-Phase-1 ordering validation (ADR-0116, #4131): a plugin whose
|
|
329
|
+
* `requiresServices` names a service provided only by a LATER plugin is
|
|
330
|
+
* a named boot error before any init side effects.
|
|
331
|
+
*/
|
|
332
|
+
validateInitServices(ordered) {
|
|
333
|
+
validateInitServiceContract(ordered, (name) => this.hasRegisteredService(name));
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* When a getService miss happens while a plugin's init() is running,
|
|
337
|
+
* append the structural diagnosis (#4131): which plugin was initializing,
|
|
338
|
+
* and — when a composed plugin declares the service — who provides it.
|
|
339
|
+
* Empty string outside Phase 1, so non-boot messages stay unchanged.
|
|
340
|
+
*/
|
|
341
|
+
describeInitOrderFault(serviceName) {
|
|
342
|
+
return describeInitOrderFault(this.currentlyInitializing, this.plugins.values(), serviceName);
|
|
255
343
|
}
|
|
256
344
|
/**
|
|
257
345
|
* Run plugin init phase
|
|
@@ -260,12 +348,16 @@ var ObjectKernelBase = class {
|
|
|
260
348
|
async runPluginInit(plugin) {
|
|
261
349
|
const pluginName = plugin.name;
|
|
262
350
|
this.logger.info(`Initializing plugin: ${pluginName}`);
|
|
351
|
+
assertInitServiceRequirements(plugin, (name) => this.hasRegisteredService(name));
|
|
352
|
+
this.currentlyInitializing = pluginName;
|
|
263
353
|
try {
|
|
264
354
|
await plugin.init(this.context);
|
|
265
355
|
this.logger.info(`Plugin initialized: ${pluginName}`);
|
|
266
356
|
} catch (error) {
|
|
267
357
|
this.logger.error(`Plugin init failed: ${pluginName}`, error);
|
|
268
358
|
throw error;
|
|
359
|
+
} finally {
|
|
360
|
+
this.currentlyInitializing = void 0;
|
|
269
361
|
}
|
|
270
362
|
}
|
|
271
363
|
/**
|
|
@@ -1112,7 +1204,11 @@ function createMemoryCache() {
|
|
|
1112
1204
|
let hits = 0;
|
|
1113
1205
|
let misses = 0;
|
|
1114
1206
|
return {
|
|
1115
|
-
|
|
1207
|
+
__serviceInfo: {
|
|
1208
|
+
status: "degraded",
|
|
1209
|
+
handlerReady: false,
|
|
1210
|
+
message: "In-process Map cache \u2014 not shared across instances, lost on restart. Register a cache plugin (e.g. Redis) for a real one."
|
|
1211
|
+
},
|
|
1116
1212
|
_serviceName: "cache",
|
|
1117
1213
|
async get(key) {
|
|
1118
1214
|
const entry = store.get(key);
|
|
@@ -1147,7 +1243,11 @@ function createMemoryQueue() {
|
|
|
1147
1243
|
const handlers = /* @__PURE__ */ new Map();
|
|
1148
1244
|
let msgId = 0;
|
|
1149
1245
|
return {
|
|
1150
|
-
|
|
1246
|
+
__serviceInfo: {
|
|
1247
|
+
status: "degraded",
|
|
1248
|
+
handlerReady: false,
|
|
1249
|
+
message: "Synchronous in-process delivery \u2014 no durability, retry, or cross-instance fan-out. Register a queue plugin (e.g. BullMQ) for a real one."
|
|
1250
|
+
},
|
|
1151
1251
|
_serviceName: "queue",
|
|
1152
1252
|
async publish(queue, data) {
|
|
1153
1253
|
const id = `fallback-msg-${++msgId}`;
|
|
@@ -1174,7 +1274,11 @@ function createMemoryQueue() {
|
|
|
1174
1274
|
function createMemoryJob() {
|
|
1175
1275
|
const jobs = /* @__PURE__ */ new Map();
|
|
1176
1276
|
return {
|
|
1177
|
-
|
|
1277
|
+
__serviceInfo: {
|
|
1278
|
+
status: "degraded",
|
|
1279
|
+
handlerReady: false,
|
|
1280
|
+
message: "In-process job registry \u2014 trigger() runs handlers, but scheduled jobs never fire on their own (no timer). Register a job plugin (e.g. Agenda) for real scheduling."
|
|
1281
|
+
},
|
|
1178
1282
|
_serviceName: "job",
|
|
1179
1283
|
async schedule(name, schedule, handler) {
|
|
1180
1284
|
jobs.set(name, { schedule, handler });
|
|
@@ -1253,7 +1357,15 @@ function createMemoryI18n() {
|
|
|
1253
1357
|
return void 0;
|
|
1254
1358
|
}
|
|
1255
1359
|
return {
|
|
1256
|
-
|
|
1360
|
+
// [#4058] `degraded` (ADR-0076 D12): translations, locale fallback and
|
|
1361
|
+
// interpolation are all real — what is missing is persistence and the
|
|
1362
|
+
// authoring surface service-i18n adds. `handlerReady` left at the
|
|
1363
|
+
// `degraded` default (true): the dispatcher's `/i18n` domain does serve
|
|
1364
|
+
// this implementation.
|
|
1365
|
+
__serviceInfo: {
|
|
1366
|
+
status: "degraded",
|
|
1367
|
+
message: "In-memory translations \u2014 real lookup and locale fallback, but nothing is persisted. Register I18nServicePlugin from @objectstack/service-i18n for the full implementation."
|
|
1368
|
+
},
|
|
1257
1369
|
_serviceName: "i18n",
|
|
1258
1370
|
t(key, locale, params) {
|
|
1259
1371
|
const data = resolveTranslations(locale) ?? mergedLocale(defaultLocale);
|
|
@@ -1310,7 +1422,14 @@ function createMemoryMetadata() {
|
|
|
1310
1422
|
return map;
|
|
1311
1423
|
}
|
|
1312
1424
|
return {
|
|
1313
|
-
|
|
1425
|
+
// [#4058] `degraded` (ADR-0076 D12): the registry is real — everything
|
|
1426
|
+
// registered is listable and readable back — it simply never reaches disk
|
|
1427
|
+
// or a database. `handlerReady` keeps the `degraded` default (true): the
|
|
1428
|
+
// dispatcher's `/meta` domain serves this implementation.
|
|
1429
|
+
__serviceInfo: {
|
|
1430
|
+
status: "degraded",
|
|
1431
|
+
message: "In-memory metadata registry \u2014 real reads and writes, no persistence (lost on restart). Register MetadataPlugin for a persisted registry."
|
|
1432
|
+
},
|
|
1314
1433
|
_serviceName: "metadata",
|
|
1315
1434
|
async register(type, name, data) {
|
|
1316
1435
|
getTypeMap(type).set(name, data);
|
|
@@ -1526,24 +1645,12 @@ var ObjectKernel = class {
|
|
|
1526
1645
|
this.services.set(name, loaderService);
|
|
1527
1646
|
return loaderService;
|
|
1528
1647
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
});
|
|
1534
|
-
throw new Error(`Service '${name}' is async - use await`);
|
|
1535
|
-
}
|
|
1536
|
-
return service2;
|
|
1537
|
-
} catch (error) {
|
|
1538
|
-
if (error.message?.includes("is async")) {
|
|
1539
|
-
throw error;
|
|
1540
|
-
}
|
|
1541
|
-
const isNotFoundError = error.message === `Service '${name}' not found`;
|
|
1542
|
-
if (!isNotFoundError) {
|
|
1543
|
-
throw error;
|
|
1544
|
-
}
|
|
1545
|
-
throw new Error(`[Kernel] Service '${name}' not found`);
|
|
1648
|
+
if (!this.pluginLoader.hasService(name)) {
|
|
1649
|
+
throw new Error(
|
|
1650
|
+
`[Kernel] Service '${name}' not found${this.describeInitOrderFault(name)}`
|
|
1651
|
+
);
|
|
1546
1652
|
}
|
|
1653
|
+
throw new Error(`Service '${name}' is async - use await`);
|
|
1547
1654
|
},
|
|
1548
1655
|
replaceService: (name, implementation) => {
|
|
1549
1656
|
const hasService = this.services.has(name) || this.pluginLoader.hasService(name);
|
|
@@ -1702,6 +1809,7 @@ var ObjectKernel = class {
|
|
|
1702
1809
|
this.logger.warn("Circular service dependencies detected:", { cycles });
|
|
1703
1810
|
}
|
|
1704
1811
|
const orderedPlugins = this.resolveDependencies();
|
|
1812
|
+
validateInitServiceContract(orderedPlugins, (name) => this.hasAnyService(name));
|
|
1705
1813
|
this.logger.info("Phase 1: Init plugins");
|
|
1706
1814
|
for (const plugin of orderedPlugins) {
|
|
1707
1815
|
await this.initPluginWithTimeout(plugin);
|
|
@@ -1847,13 +1955,36 @@ var ObjectKernel = class {
|
|
|
1847
1955
|
async initPluginWithTimeout(plugin) {
|
|
1848
1956
|
const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout;
|
|
1849
1957
|
this.logger.debug(`Init: ${plugin.name}`, { plugin: plugin.name });
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1958
|
+
assertInitServiceRequirements(plugin, (name) => this.hasAnyService(name));
|
|
1959
|
+
this.currentlyInitializing = plugin.name;
|
|
1960
|
+
try {
|
|
1961
|
+
const initPromise = plugin.init(this.context);
|
|
1962
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1963
|
+
setTimeout(() => {
|
|
1964
|
+
reject(new Error(`Plugin ${plugin.name} init timeout after ${timeout}ms`));
|
|
1965
|
+
}, timeout);
|
|
1966
|
+
});
|
|
1967
|
+
await Promise.race([initPromise, timeoutPromise]);
|
|
1968
|
+
} finally {
|
|
1969
|
+
this.currentlyInitializing = void 0;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Whether a service is resolvable on this kernel right now — direct
|
|
1974
|
+
* registration or a loader-registered factory. Backs the init-service
|
|
1975
|
+
* contract checks (#4131).
|
|
1976
|
+
*/
|
|
1977
|
+
hasAnyService(name) {
|
|
1978
|
+
return this.services.has(name) || this.pluginLoader.hasService(name);
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
* When a getService miss happens while a plugin's init() is running,
|
|
1982
|
+
* append the structural diagnosis (#4131): which plugin was initializing,
|
|
1983
|
+
* and — when a composed plugin declares the service — who provides it.
|
|
1984
|
+
* Empty string outside Phase 1, so non-boot messages stay unchanged.
|
|
1985
|
+
*/
|
|
1986
|
+
describeInitOrderFault(serviceName) {
|
|
1987
|
+
return describeInitOrderFault(this.currentlyInitializing, this.plugins.values(), serviceName);
|
|
1857
1988
|
}
|
|
1858
1989
|
async startPluginWithTimeout(plugin) {
|
|
1859
1990
|
if (!plugin.start) {
|
|
@@ -1927,35 +2058,13 @@ var ObjectKernel = class {
|
|
|
1927
2058
|
}
|
|
1928
2059
|
}
|
|
1929
2060
|
}
|
|
2061
|
+
/**
|
|
2062
|
+
* Topological order over `dependencies` (hard) + `optionalDependencies`
|
|
2063
|
+
* (order-if-present) — ADR-0116, #4131. One implementation shared with
|
|
2064
|
+
* LiteKernel via `plugin-order.ts`.
|
|
2065
|
+
*/
|
|
1930
2066
|
resolveDependencies() {
|
|
1931
|
-
|
|
1932
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1933
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
1934
|
-
const visit = (pluginName) => {
|
|
1935
|
-
if (visited.has(pluginName)) return;
|
|
1936
|
-
if (visiting.has(pluginName)) {
|
|
1937
|
-
throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
|
|
1938
|
-
}
|
|
1939
|
-
const plugin = this.plugins.get(pluginName);
|
|
1940
|
-
if (!plugin) {
|
|
1941
|
-
throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
|
|
1942
|
-
}
|
|
1943
|
-
visiting.add(pluginName);
|
|
1944
|
-
const deps = plugin.dependencies || [];
|
|
1945
|
-
for (const dep of deps) {
|
|
1946
|
-
if (!this.plugins.has(dep)) {
|
|
1947
|
-
throw new Error(`[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`);
|
|
1948
|
-
}
|
|
1949
|
-
visit(dep);
|
|
1950
|
-
}
|
|
1951
|
-
visiting.delete(pluginName);
|
|
1952
|
-
visited.add(pluginName);
|
|
1953
|
-
resolved.push(plugin);
|
|
1954
|
-
};
|
|
1955
|
-
for (const pluginName of this.plugins.keys()) {
|
|
1956
|
-
visit(pluginName);
|
|
1957
|
-
}
|
|
1958
|
-
return resolved;
|
|
2067
|
+
return resolvePluginOrder(this.plugins);
|
|
1959
2068
|
}
|
|
1960
2069
|
registerShutdownSignals() {
|
|
1961
2070
|
const signals = ["SIGINT", "SIGTERM", "SIGQUIT"];
|
|
@@ -2021,6 +2130,7 @@ var LiteKernel = class extends ObjectKernelBase {
|
|
|
2021
2130
|
this.state = "initializing";
|
|
2022
2131
|
this.logger.info("Bootstrap started");
|
|
2023
2132
|
const orderedPlugins = this.resolveDependencies();
|
|
2133
|
+
this.validateInitServices(orderedPlugins);
|
|
2024
2134
|
this.logger.info("Phase 1: Init plugins");
|
|
2025
2135
|
for (const plugin of orderedPlugins) {
|
|
2026
2136
|
await this.runPluginInit(plugin);
|
|
@@ -2621,6 +2731,11 @@ function createApiRegistryPlugin(config = {}) {
|
|
|
2621
2731
|
} = config;
|
|
2622
2732
|
return {
|
|
2623
2733
|
name: "com.objectstack.core.api-registry",
|
|
2734
|
+
/**
|
|
2735
|
+
* Services init() registers on every path (ADR-0116, #4131) — lets the
|
|
2736
|
+
* kernel name this plugin when a consumer requires one before it inits.
|
|
2737
|
+
*/
|
|
2738
|
+
providesServices: ["api-registry"],
|
|
2624
2739
|
type: "standard",
|
|
2625
2740
|
version: "1.0.0",
|
|
2626
2741
|
init: async (ctx) => {
|
|
@@ -4683,14 +4798,13 @@ function evaluateAuthGate(sessionUser, path) {
|
|
|
4683
4798
|
|
|
4684
4799
|
// src/security/anonymous-deny.ts
|
|
4685
4800
|
var ANONYMOUS_DENY_STATUS = 401;
|
|
4686
|
-
var ANONYMOUS_DENY_CODE = "
|
|
4801
|
+
var ANONYMOUS_DENY_CODE = "UNAUTHENTICATED";
|
|
4687
4802
|
var ANONYMOUS_DENY_MESSAGE = "Authentication is required to access this endpoint.";
|
|
4688
4803
|
var ANONYMOUS_DENY_BODY = {
|
|
4689
4804
|
error: ANONYMOUS_DENY_CODE,
|
|
4690
4805
|
message: ANONYMOUS_DENY_MESSAGE
|
|
4691
4806
|
};
|
|
4692
4807
|
function shouldDenyAnonymous(input) {
|
|
4693
|
-
if (!input.requireAuth) return false;
|
|
4694
4808
|
if (typeof input.method === "string" && input.method.toUpperCase() === "OPTIONS") {
|
|
4695
4809
|
return false;
|
|
4696
4810
|
}
|
|
@@ -4702,6 +4816,7 @@ function shouldDenyAnonymous(input) {
|
|
|
4702
4816
|
}
|
|
4703
4817
|
|
|
4704
4818
|
// src/utils/datetime.ts
|
|
4819
|
+
var import_data = require("@objectstack/spec/data");
|
|
4705
4820
|
function calendarPartsInTz(d, tz) {
|
|
4706
4821
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
4707
4822
|
timeZone: tz,
|
|
@@ -4943,7 +5058,7 @@ async function bulkWrite(rows, opts) {
|
|
|
4943
5058
|
}
|
|
4944
5059
|
|
|
4945
5060
|
// src/utils/filter-tokens.ts
|
|
4946
|
-
var
|
|
5061
|
+
var import_data2 = require("@objectstack/spec/data");
|
|
4947
5062
|
var UnknownFilterTokenError = class extends Error {
|
|
4948
5063
|
constructor(token, suggestion) {
|
|
4949
5064
|
super(
|
|
@@ -5094,7 +5209,7 @@ function resolveFilterToken(token, ctx = {}) {
|
|
|
5094
5209
|
}
|
|
5095
5210
|
const period = resolvePeriodToken(token, today);
|
|
5096
5211
|
if (period !== void 0) return period;
|
|
5097
|
-
const param = (0,
|
|
5212
|
+
const param = (0, import_data2.parseDateMacroParam)(token);
|
|
5098
5213
|
if (param) {
|
|
5099
5214
|
const sign = param.direction === "ago" ? -1 : 1;
|
|
5100
5215
|
if (param.unit === "minute" || param.unit === "hour") {
|
|
@@ -5105,7 +5220,7 @@ function resolveFilterToken(token, ctx = {}) {
|
|
|
5105
5220
|
return void 0;
|
|
5106
5221
|
}
|
|
5107
5222
|
function hasFilterToken(node) {
|
|
5108
|
-
if (typeof node === "string") return (0,
|
|
5223
|
+
if (typeof node === "string") return (0, import_data2.classifyFilterToken)(node) !== null;
|
|
5109
5224
|
if (Array.isArray(node)) return node.some(hasFilterToken);
|
|
5110
5225
|
if (node && typeof node === "object" && !(node instanceof Date)) {
|
|
5111
5226
|
return Object.values(node).some(hasFilterToken);
|
|
@@ -5118,7 +5233,7 @@ function resolveFilterTokens(filter, ctx = {}) {
|
|
|
5118
5233
|
const pinned = { ...ctx, now: ctx.now ?? /* @__PURE__ */ new Date() };
|
|
5119
5234
|
const walk = (node) => {
|
|
5120
5235
|
if (typeof node === "string") {
|
|
5121
|
-
const cls = (0,
|
|
5236
|
+
const cls = (0, import_data2.classifyFilterToken)(node);
|
|
5122
5237
|
if (!cls) return node;
|
|
5123
5238
|
if (cls.kind === "unknown") throw new UnknownFilterTokenError(cls.token, cls.suggestion);
|
|
5124
5239
|
const resolved = resolveFilterToken(cls.token, pinned);
|
|
@@ -6092,6 +6207,7 @@ var NamespaceResolver = class {
|
|
|
6092
6207
|
ServiceLifecycle,
|
|
6093
6208
|
UnknownFilterTokenError,
|
|
6094
6209
|
UnresolvedFilterTokenError,
|
|
6210
|
+
assertInitServiceRequirements,
|
|
6095
6211
|
bucketKeyToCalendarRange,
|
|
6096
6212
|
buildPermissionsFromGrants,
|
|
6097
6213
|
bulkWrite,
|
|
@@ -6110,6 +6226,7 @@ var NamespaceResolver = class {
|
|
|
6110
6226
|
deepMerge,
|
|
6111
6227
|
defaultIsTransientError,
|
|
6112
6228
|
derivePosture,
|
|
6229
|
+
describeInitOrderFault,
|
|
6113
6230
|
evaluateAuthGate,
|
|
6114
6231
|
extractApiKey,
|
|
6115
6232
|
filterTokenContextFrom,
|
|
@@ -6123,6 +6240,7 @@ var NamespaceResolver = class {
|
|
|
6123
6240
|
isGrantActive,
|
|
6124
6241
|
isGrantExpired,
|
|
6125
6242
|
isNode,
|
|
6243
|
+
nextUtcCalendarDay,
|
|
6126
6244
|
parseScopes,
|
|
6127
6245
|
parseSignature,
|
|
6128
6246
|
postureVisibleRows,
|
|
@@ -6133,10 +6251,13 @@ var NamespaceResolver = class {
|
|
|
6133
6251
|
resolveFilterTokens,
|
|
6134
6252
|
resolveLocale,
|
|
6135
6253
|
resolveLocalizationContext,
|
|
6254
|
+
resolvePluginOrder,
|
|
6136
6255
|
resolveUserAuthzGrants,
|
|
6137
6256
|
safeExit,
|
|
6138
6257
|
shouldDenyAnonymous,
|
|
6139
6258
|
signPayload,
|
|
6259
|
+
utcInstantMs,
|
|
6260
|
+
validateInitServiceContract,
|
|
6140
6261
|
verifyPayload,
|
|
6141
6262
|
verifyPlatformSignature,
|
|
6142
6263
|
verifyPluginArtifact,
|