@objectstack/core 16.1.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 +470 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +325 -21
- package/dist/index.d.ts +325 -21
- package/dist/index.js +460 -103
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -60,6 +60,9 @@ __export(index_exports, {
|
|
|
60
60
|
SecurePluginContext: () => SecurePluginContext,
|
|
61
61
|
SemanticVersionManager: () => SemanticVersionManager,
|
|
62
62
|
ServiceLifecycle: () => ServiceLifecycle,
|
|
63
|
+
UnknownFilterTokenError: () => UnknownFilterTokenError,
|
|
64
|
+
UnresolvedFilterTokenError: () => UnresolvedFilterTokenError,
|
|
65
|
+
assertInitServiceRequirements: () => assertInitServiceRequirements,
|
|
63
66
|
bucketKeyToCalendarRange: () => bucketKeyToCalendarRange,
|
|
64
67
|
buildPermissionsFromGrants: () => buildPermissionsFromGrants,
|
|
65
68
|
bulkWrite: () => bulkWrite,
|
|
@@ -78,8 +81,10 @@ __export(index_exports, {
|
|
|
78
81
|
deepMerge: () => deepMerge,
|
|
79
82
|
defaultIsTransientError: () => defaultIsTransientError,
|
|
80
83
|
derivePosture: () => derivePosture,
|
|
84
|
+
describeInitOrderFault: () => describeInitOrderFault,
|
|
81
85
|
evaluateAuthGate: () => evaluateAuthGate,
|
|
82
86
|
extractApiKey: () => extractApiKey,
|
|
87
|
+
filterTokenContextFrom: () => filterTokenContextFrom,
|
|
83
88
|
generateApiKey: () => generateApiKey,
|
|
84
89
|
generateEd25519KeyPair: () => generateEd25519KeyPair,
|
|
85
90
|
getEnv: () => getEnv,
|
|
@@ -90,18 +95,24 @@ __export(index_exports, {
|
|
|
90
95
|
isGrantActive: () => isGrantActive,
|
|
91
96
|
isGrantExpired: () => isGrantExpired,
|
|
92
97
|
isNode: () => isNode,
|
|
98
|
+
nextUtcCalendarDay: () => import_data.nextUtcCalendarDay,
|
|
93
99
|
parseScopes: () => parseScopes,
|
|
94
100
|
parseSignature: () => parseSignature,
|
|
95
101
|
postureVisibleRows: () => postureVisibleRows,
|
|
96
102
|
readAuthoredTranslationLayer: () => readAuthoredTranslationLayer,
|
|
97
103
|
resolveApiKeyPrincipal: () => resolveApiKeyPrincipal,
|
|
98
104
|
resolveAuthzContext: () => resolveAuthzContext,
|
|
105
|
+
resolveFilterToken: () => resolveFilterToken,
|
|
106
|
+
resolveFilterTokens: () => resolveFilterTokens,
|
|
99
107
|
resolveLocale: () => resolveLocale,
|
|
100
108
|
resolveLocalizationContext: () => resolveLocalizationContext,
|
|
109
|
+
resolvePluginOrder: () => resolvePluginOrder,
|
|
101
110
|
resolveUserAuthzGrants: () => resolveUserAuthzGrants,
|
|
102
111
|
safeExit: () => safeExit,
|
|
103
112
|
shouldDenyAnonymous: () => shouldDenyAnonymous,
|
|
104
113
|
signPayload: () => signPayload,
|
|
114
|
+
utcInstantMs: () => import_data.utcInstantMs,
|
|
115
|
+
validateInitServiceContract: () => validateInitServiceContract,
|
|
105
116
|
verifyPayload: () => verifyPayload,
|
|
106
117
|
verifyPlatformSignature: () => verifyPlatformSignature,
|
|
107
118
|
verifyPluginArtifact: () => verifyPluginArtifact,
|
|
@@ -112,6 +123,89 @@ __export(index_exports, {
|
|
|
112
123
|
});
|
|
113
124
|
module.exports = __toCommonJS(index_exports);
|
|
114
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
|
+
|
|
115
209
|
// src/kernel-base.ts
|
|
116
210
|
var ObjectKernelBase = class {
|
|
117
211
|
constructor(logger) {
|
|
@@ -162,7 +256,9 @@ var ObjectKernelBase = class {
|
|
|
162
256
|
if (this.services instanceof Map) {
|
|
163
257
|
const service = this.services.get(name);
|
|
164
258
|
if (!service) {
|
|
165
|
-
throw new Error(
|
|
259
|
+
throw new Error(
|
|
260
|
+
`[Kernel] Service '${name}' not found${this.describeInitOrderFault(name)}`
|
|
261
|
+
);
|
|
166
262
|
}
|
|
167
263
|
return service;
|
|
168
264
|
} else {
|
|
@@ -213,40 +309,37 @@ var ObjectKernelBase = class {
|
|
|
213
309
|
};
|
|
214
310
|
}
|
|
215
311
|
/**
|
|
216
|
-
* 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`.
|
|
217
315
|
* @returns Ordered list of plugins (dependencies first)
|
|
218
316
|
*/
|
|
219
317
|
resolveDependencies() {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
};
|
|
246
|
-
for (const pluginName of this.plugins.keys()) {
|
|
247
|
-
visit(pluginName);
|
|
248
|
-
}
|
|
249
|
-
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);
|
|
250
343
|
}
|
|
251
344
|
/**
|
|
252
345
|
* Run plugin init phase
|
|
@@ -255,12 +348,16 @@ var ObjectKernelBase = class {
|
|
|
255
348
|
async runPluginInit(plugin) {
|
|
256
349
|
const pluginName = plugin.name;
|
|
257
350
|
this.logger.info(`Initializing plugin: ${pluginName}`);
|
|
351
|
+
assertInitServiceRequirements(plugin, (name) => this.hasRegisteredService(name));
|
|
352
|
+
this.currentlyInitializing = pluginName;
|
|
258
353
|
try {
|
|
259
354
|
await plugin.init(this.context);
|
|
260
355
|
this.logger.info(`Plugin initialized: ${pluginName}`);
|
|
261
356
|
} catch (error) {
|
|
262
357
|
this.logger.error(`Plugin init failed: ${pluginName}`, error);
|
|
263
358
|
throw error;
|
|
359
|
+
} finally {
|
|
360
|
+
this.currentlyInitializing = void 0;
|
|
264
361
|
}
|
|
265
362
|
}
|
|
266
363
|
/**
|
|
@@ -535,7 +632,7 @@ function createLogger(config) {
|
|
|
535
632
|
}
|
|
536
633
|
|
|
537
634
|
// src/kernel.ts
|
|
538
|
-
var
|
|
635
|
+
var import_system2 = require("@objectstack/spec/system");
|
|
539
636
|
|
|
540
637
|
// src/security/plugin-config-validator.ts
|
|
541
638
|
var import_zod = require("zod");
|
|
@@ -1107,7 +1204,11 @@ function createMemoryCache() {
|
|
|
1107
1204
|
let hits = 0;
|
|
1108
1205
|
let misses = 0;
|
|
1109
1206
|
return {
|
|
1110
|
-
|
|
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
|
+
},
|
|
1111
1212
|
_serviceName: "cache",
|
|
1112
1213
|
async get(key) {
|
|
1113
1214
|
const entry = store.get(key);
|
|
@@ -1142,7 +1243,11 @@ function createMemoryQueue() {
|
|
|
1142
1243
|
const handlers = /* @__PURE__ */ new Map();
|
|
1143
1244
|
let msgId = 0;
|
|
1144
1245
|
return {
|
|
1145
|
-
|
|
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
|
+
},
|
|
1146
1251
|
_serviceName: "queue",
|
|
1147
1252
|
async publish(queue, data) {
|
|
1148
1253
|
const id = `fallback-msg-${++msgId}`;
|
|
@@ -1169,7 +1274,11 @@ function createMemoryQueue() {
|
|
|
1169
1274
|
function createMemoryJob() {
|
|
1170
1275
|
const jobs = /* @__PURE__ */ new Map();
|
|
1171
1276
|
return {
|
|
1172
|
-
|
|
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
|
+
},
|
|
1173
1282
|
_serviceName: "job",
|
|
1174
1283
|
async schedule(name, schedule, handler) {
|
|
1175
1284
|
jobs.set(name, { schedule, handler });
|
|
@@ -1248,7 +1357,15 @@ function createMemoryI18n() {
|
|
|
1248
1357
|
return void 0;
|
|
1249
1358
|
}
|
|
1250
1359
|
return {
|
|
1251
|
-
|
|
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
|
+
},
|
|
1252
1369
|
_serviceName: "i18n",
|
|
1253
1370
|
t(key, locale, params) {
|
|
1254
1371
|
const data = resolveTranslations(locale) ?? mergedLocale(defaultLocale);
|
|
@@ -1305,7 +1422,14 @@ function createMemoryMetadata() {
|
|
|
1305
1422
|
return map;
|
|
1306
1423
|
}
|
|
1307
1424
|
return {
|
|
1308
|
-
|
|
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
|
+
},
|
|
1309
1433
|
_serviceName: "metadata",
|
|
1310
1434
|
async register(type, name, data) {
|
|
1311
1435
|
getTypeMap(type).set(name, data);
|
|
@@ -1348,6 +1472,7 @@ function createMemoryMetadata() {
|
|
|
1348
1472
|
}
|
|
1349
1473
|
|
|
1350
1474
|
// src/fallbacks/authored-translation-sync.ts
|
|
1475
|
+
var import_system = require("@objectstack/spec/system");
|
|
1351
1476
|
var OWNER_PROP = "__authoredTranslationSyncOwner";
|
|
1352
1477
|
var LOCALE_LIKE = /^[a-z]{2,3}([_-]([A-Za-z]{4}|[A-Za-z]{2}|[0-9]{3}))?$/;
|
|
1353
1478
|
async function readAuthoredTranslationLayer(engine, logger) {
|
|
@@ -1377,14 +1502,32 @@ async function readAuthoredTranslationLayer(engine, logger) {
|
|
|
1377
1502
|
continue;
|
|
1378
1503
|
}
|
|
1379
1504
|
if (!data || typeof data !== "object") continue;
|
|
1380
|
-
const
|
|
1505
|
+
const legacyKeys = import_system.LEGACY_OBJECT_FIRST_KEYS.filter((key) => data[key] !== void 0);
|
|
1506
|
+
if (legacyKeys.length > 0) {
|
|
1507
|
+
logger?.warn?.(
|
|
1508
|
+
`[i18n] authored translation '${row?.name}' uses the retired object-first shape (${legacyKeys.join(", ")}) \u2014 nothing resolves from it; re-author it under 'objects.<object_name>' with a top-level 'locale' \u2014 skipped`
|
|
1509
|
+
);
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
const locale = typeof data?.locale === "string" && data.locale || (typeof row?.name === "string" && LOCALE_LIKE.test(row.name) ? row.name : void 0) || void 0;
|
|
1381
1513
|
if (!locale) {
|
|
1382
1514
|
logger?.warn?.(
|
|
1383
|
-
`[i18n] authored translation '${row?.name}' has no resolvable locale (set
|
|
1515
|
+
`[i18n] authored translation '${row?.name}' has no resolvable locale (set the top-level 'locale', or name the item after its BCP-47 locale) \u2014 skipped`
|
|
1384
1516
|
);
|
|
1385
1517
|
continue;
|
|
1386
1518
|
}
|
|
1387
|
-
const {
|
|
1519
|
+
const {
|
|
1520
|
+
name: _n,
|
|
1521
|
+
locale: _l,
|
|
1522
|
+
_packageId: _p,
|
|
1523
|
+
_packageVersion: _pv,
|
|
1524
|
+
_provenance: _pr,
|
|
1525
|
+
_lock: _lk,
|
|
1526
|
+
_lockReason: _lr,
|
|
1527
|
+
_lockDocsUrl: _ld,
|
|
1528
|
+
_lockSource: _ls,
|
|
1529
|
+
...payload
|
|
1530
|
+
} = data;
|
|
1388
1531
|
byLocale[locale] = deepMerge(byLocale[locale] ?? {}, payload);
|
|
1389
1532
|
}
|
|
1390
1533
|
return byLocale;
|
|
@@ -1502,24 +1645,12 @@ var ObjectKernel = class {
|
|
|
1502
1645
|
this.services.set(name, loaderService);
|
|
1503
1646
|
return loaderService;
|
|
1504
1647
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
});
|
|
1510
|
-
throw new Error(`Service '${name}' is async - use await`);
|
|
1511
|
-
}
|
|
1512
|
-
return service2;
|
|
1513
|
-
} catch (error) {
|
|
1514
|
-
if (error.message?.includes("is async")) {
|
|
1515
|
-
throw error;
|
|
1516
|
-
}
|
|
1517
|
-
const isNotFoundError = error.message === `Service '${name}' not found`;
|
|
1518
|
-
if (!isNotFoundError) {
|
|
1519
|
-
throw error;
|
|
1520
|
-
}
|
|
1521
|
-
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
|
+
);
|
|
1522
1652
|
}
|
|
1653
|
+
throw new Error(`Service '${name}' is async - use await`);
|
|
1523
1654
|
},
|
|
1524
1655
|
replaceService: (name, implementation) => {
|
|
1525
1656
|
const hasService = this.services.has(name) || this.pluginLoader.hasService(name);
|
|
@@ -1608,7 +1739,7 @@ var ObjectKernel = class {
|
|
|
1608
1739
|
*/
|
|
1609
1740
|
preInjectCoreFallbacks() {
|
|
1610
1741
|
if (this.config.skipSystemValidation) return;
|
|
1611
|
-
for (const [serviceName, criticality] of Object.entries(
|
|
1742
|
+
for (const [serviceName, criticality] of Object.entries(import_system2.ServiceRequirementDef)) {
|
|
1612
1743
|
if (criticality !== "core") continue;
|
|
1613
1744
|
const hasService = this.services.has(serviceName) || this.pluginLoader.hasService(serviceName);
|
|
1614
1745
|
if (!hasService) {
|
|
@@ -1632,7 +1763,7 @@ var ObjectKernel = class {
|
|
|
1632
1763
|
this.logger.debug("Validating system service requirements...");
|
|
1633
1764
|
const missingServices = [];
|
|
1634
1765
|
const missingCoreServices = [];
|
|
1635
|
-
for (const [serviceName, criticality] of Object.entries(
|
|
1766
|
+
for (const [serviceName, criticality] of Object.entries(import_system2.ServiceRequirementDef)) {
|
|
1636
1767
|
const hasService = this.services.has(serviceName) || this.pluginLoader.hasService(serviceName);
|
|
1637
1768
|
if (!hasService) {
|
|
1638
1769
|
if (criticality === "required") {
|
|
@@ -1678,6 +1809,7 @@ var ObjectKernel = class {
|
|
|
1678
1809
|
this.logger.warn("Circular service dependencies detected:", { cycles });
|
|
1679
1810
|
}
|
|
1680
1811
|
const orderedPlugins = this.resolveDependencies();
|
|
1812
|
+
validateInitServiceContract(orderedPlugins, (name) => this.hasAnyService(name));
|
|
1681
1813
|
this.logger.info("Phase 1: Init plugins");
|
|
1682
1814
|
for (const plugin of orderedPlugins) {
|
|
1683
1815
|
await this.initPluginWithTimeout(plugin);
|
|
@@ -1823,13 +1955,36 @@ var ObjectKernel = class {
|
|
|
1823
1955
|
async initPluginWithTimeout(plugin) {
|
|
1824
1956
|
const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout;
|
|
1825
1957
|
this.logger.debug(`Init: ${plugin.name}`, { plugin: plugin.name });
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
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);
|
|
1833
1988
|
}
|
|
1834
1989
|
async startPluginWithTimeout(plugin) {
|
|
1835
1990
|
if (!plugin.start) {
|
|
@@ -1903,35 +2058,13 @@ var ObjectKernel = class {
|
|
|
1903
2058
|
}
|
|
1904
2059
|
}
|
|
1905
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
|
+
*/
|
|
1906
2066
|
resolveDependencies() {
|
|
1907
|
-
|
|
1908
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1909
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
1910
|
-
const visit = (pluginName) => {
|
|
1911
|
-
if (visited.has(pluginName)) return;
|
|
1912
|
-
if (visiting.has(pluginName)) {
|
|
1913
|
-
throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
|
|
1914
|
-
}
|
|
1915
|
-
const plugin = this.plugins.get(pluginName);
|
|
1916
|
-
if (!plugin) {
|
|
1917
|
-
throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
|
|
1918
|
-
}
|
|
1919
|
-
visiting.add(pluginName);
|
|
1920
|
-
const deps = plugin.dependencies || [];
|
|
1921
|
-
for (const dep of deps) {
|
|
1922
|
-
if (!this.plugins.has(dep)) {
|
|
1923
|
-
throw new Error(`[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`);
|
|
1924
|
-
}
|
|
1925
|
-
visit(dep);
|
|
1926
|
-
}
|
|
1927
|
-
visiting.delete(pluginName);
|
|
1928
|
-
visited.add(pluginName);
|
|
1929
|
-
resolved.push(plugin);
|
|
1930
|
-
};
|
|
1931
|
-
for (const pluginName of this.plugins.keys()) {
|
|
1932
|
-
visit(pluginName);
|
|
1933
|
-
}
|
|
1934
|
-
return resolved;
|
|
2067
|
+
return resolvePluginOrder(this.plugins);
|
|
1935
2068
|
}
|
|
1936
2069
|
registerShutdownSignals() {
|
|
1937
2070
|
const signals = ["SIGINT", "SIGTERM", "SIGQUIT"];
|
|
@@ -1997,6 +2130,7 @@ var LiteKernel = class extends ObjectKernelBase {
|
|
|
1997
2130
|
this.state = "initializing";
|
|
1998
2131
|
this.logger.info("Bootstrap started");
|
|
1999
2132
|
const orderedPlugins = this.resolveDependencies();
|
|
2133
|
+
this.validateInitServices(orderedPlugins);
|
|
2000
2134
|
this.logger.info("Phase 1: Init plugins");
|
|
2001
2135
|
for (const plugin of orderedPlugins) {
|
|
2002
2136
|
await this.runPluginInit(plugin);
|
|
@@ -2597,6 +2731,11 @@ function createApiRegistryPlugin(config = {}) {
|
|
|
2597
2731
|
} = config;
|
|
2598
2732
|
return {
|
|
2599
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"],
|
|
2600
2739
|
type: "standard",
|
|
2601
2740
|
version: "1.0.0",
|
|
2602
2741
|
init: async (ctx) => {
|
|
@@ -4411,7 +4550,8 @@ async function resolveAuthzContext(input) {
|
|
|
4411
4550
|
positions: [],
|
|
4412
4551
|
permissions: [],
|
|
4413
4552
|
systemPermissions: [],
|
|
4414
|
-
org_user_ids: []
|
|
4553
|
+
org_user_ids: [],
|
|
4554
|
+
accessible_org_ids: []
|
|
4415
4555
|
};
|
|
4416
4556
|
let userId;
|
|
4417
4557
|
let tenantId;
|
|
@@ -4447,6 +4587,7 @@ async function resolveAuthzContext(input) {
|
|
|
4447
4587
|
ctx.permissions = grants.permissions;
|
|
4448
4588
|
ctx.systemPermissions = grants.systemPermissions;
|
|
4449
4589
|
ctx.org_user_ids = grants.org_user_ids;
|
|
4590
|
+
ctx.accessible_org_ids = grants.accessible_org_ids;
|
|
4450
4591
|
if (grants.tabPermissions) ctx.tabPermissions = grants.tabPermissions;
|
|
4451
4592
|
if (grants.posture) ctx.posture = grants.posture;
|
|
4452
4593
|
if (grants.email && !ctx.email) ctx.email = grants.email;
|
|
@@ -4458,7 +4599,8 @@ async function resolveUserAuthzGrants(ql, userId, opts = {}) {
|
|
|
4458
4599
|
positions: [],
|
|
4459
4600
|
permissions: Array.isArray(opts.seedPermissions) ? [...opts.seedPermissions] : [],
|
|
4460
4601
|
systemPermissions: [],
|
|
4461
|
-
org_user_ids: [userId]
|
|
4602
|
+
org_user_ids: [userId],
|
|
4603
|
+
accessible_org_ids: []
|
|
4462
4604
|
};
|
|
4463
4605
|
if (opts.seedEmail) grants.email = opts.seedEmail;
|
|
4464
4606
|
if (!ql || typeof ql.find !== "function") return grants;
|
|
@@ -4476,9 +4618,17 @@ async function resolveUserAuthzGrants(ql, userId, opts = {}) {
|
|
|
4476
4618
|
const u = await getUserRow();
|
|
4477
4619
|
if (u?.email) grants.email = String(u.email);
|
|
4478
4620
|
}
|
|
4479
|
-
const
|
|
4480
|
-
const members = await tryFind(ql, "sys_member",
|
|
4621
|
+
const nowMs = opts.nowMs ?? Date.now();
|
|
4622
|
+
const members = await tryFind(ql, "sys_member", { user_id: userId }, 200);
|
|
4623
|
+
const accessibleOrgIds = /* @__PURE__ */ new Set();
|
|
4481
4624
|
for (const m of members) {
|
|
4625
|
+
if (!isGrantActive(m, nowMs)) continue;
|
|
4626
|
+
const org = m.organization_id ?? m.organizationId;
|
|
4627
|
+
if (typeof org === "string" && org) accessibleOrgIds.add(org);
|
|
4628
|
+
}
|
|
4629
|
+
grants.accessible_org_ids = Array.from(accessibleOrgIds);
|
|
4630
|
+
const activeMembers = tenantId ? members.filter((m) => (m.organization_id ?? m.organizationId) === tenantId) : members;
|
|
4631
|
+
for (const m of activeMembers) {
|
|
4482
4632
|
if (m.role && typeof m.role === "string") {
|
|
4483
4633
|
for (const raw of m.role.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
4484
4634
|
const r = (0, import_spec.mapMembershipRole)(raw);
|
|
@@ -4486,7 +4636,6 @@ async function resolveUserAuthzGrants(ql, userId, opts = {}) {
|
|
|
4486
4636
|
}
|
|
4487
4637
|
}
|
|
4488
4638
|
}
|
|
4489
|
-
const nowMs = opts.nowMs ?? Date.now();
|
|
4490
4639
|
const userPositionRows = await tryFind(ql, "sys_user_position", { user_id: userId }, 200);
|
|
4491
4640
|
for (const ur of userPositionRows) {
|
|
4492
4641
|
const org = ur.organization_id ?? null;
|
|
@@ -4558,7 +4707,9 @@ async function resolveUserAuthzGrants(ql, userId, opts = {}) {
|
|
|
4558
4707
|
}
|
|
4559
4708
|
grants.posture = derivePosture({
|
|
4560
4709
|
isPlatformAdmin: hasPlatformAdminGrant,
|
|
4561
|
-
|
|
4710
|
+
// [ADR-0105 D4] Either org-admin capability set resolves the rung — the
|
|
4711
|
+
// wall-less variant differs only by withholding the superuser bits.
|
|
4712
|
+
isTenantAdmin: import_spec.ORGANIZATION_ADMIN_GRANTS.some((n) => grants.permissions.includes(n))
|
|
4562
4713
|
});
|
|
4563
4714
|
if (!grants.permissions.includes("ai_seat")) {
|
|
4564
4715
|
const aiAccess = (await getUserRow())?.ai_access;
|
|
@@ -4647,14 +4798,13 @@ function evaluateAuthGate(sessionUser, path) {
|
|
|
4647
4798
|
|
|
4648
4799
|
// src/security/anonymous-deny.ts
|
|
4649
4800
|
var ANONYMOUS_DENY_STATUS = 401;
|
|
4650
|
-
var ANONYMOUS_DENY_CODE = "
|
|
4801
|
+
var ANONYMOUS_DENY_CODE = "UNAUTHENTICATED";
|
|
4651
4802
|
var ANONYMOUS_DENY_MESSAGE = "Authentication is required to access this endpoint.";
|
|
4652
4803
|
var ANONYMOUS_DENY_BODY = {
|
|
4653
4804
|
error: ANONYMOUS_DENY_CODE,
|
|
4654
4805
|
message: ANONYMOUS_DENY_MESSAGE
|
|
4655
4806
|
};
|
|
4656
4807
|
function shouldDenyAnonymous(input) {
|
|
4657
|
-
if (!input.requireAuth) return false;
|
|
4658
4808
|
if (typeof input.method === "string" && input.method.toUpperCase() === "OPTIONS") {
|
|
4659
4809
|
return false;
|
|
4660
4810
|
}
|
|
@@ -4666,6 +4816,7 @@ function shouldDenyAnonymous(input) {
|
|
|
4666
4816
|
}
|
|
4667
4817
|
|
|
4668
4818
|
// src/utils/datetime.ts
|
|
4819
|
+
var import_data = require("@objectstack/spec/data");
|
|
4669
4820
|
function calendarPartsInTz(d, tz) {
|
|
4670
4821
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
4671
4822
|
timeZone: tz,
|
|
@@ -4689,8 +4840,8 @@ function calendarPartsInTzOrUtc(d, tz) {
|
|
|
4689
4840
|
day: d.getUTCDate()
|
|
4690
4841
|
};
|
|
4691
4842
|
}
|
|
4692
|
-
function zonedDateStartToUtcMs(
|
|
4693
|
-
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(
|
|
4843
|
+
function zonedDateStartToUtcMs(ymd2, tz) {
|
|
4844
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd2);
|
|
4694
4845
|
const wallAsUtc = m ? Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])) : NaN;
|
|
4695
4846
|
if (!tz || tz === "UTC" || Number.isNaN(wallAsUtc)) return wallAsUtc;
|
|
4696
4847
|
try {
|
|
@@ -4906,6 +5057,209 @@ async function bulkWrite(rows, opts) {
|
|
|
4906
5057
|
return results;
|
|
4907
5058
|
}
|
|
4908
5059
|
|
|
5060
|
+
// src/utils/filter-tokens.ts
|
|
5061
|
+
var import_data2 = require("@objectstack/spec/data");
|
|
5062
|
+
var UnknownFilterTokenError = class extends Error {
|
|
5063
|
+
constructor(token, suggestion) {
|
|
5064
|
+
super(
|
|
5065
|
+
`Unresolvable filter placeholder "{${token}}". ` + (suggestion ? `Did you mean "{${suggestion}}"? ` : "Resolvable placeholders are the context tokens ({current_user_id}, {current_org_id}) and the date macros ({today}, {current_quarter_start}, {30_days_ago}, \u2026). ") + "Sending it to the data engine verbatim would compare it as a literal string and match nothing, which is indistinguishable from an empty result."
|
|
5066
|
+
);
|
|
5067
|
+
this.status = 400;
|
|
5068
|
+
this.code = "FILTER_TOKEN_UNKNOWN";
|
|
5069
|
+
this.name = "UnknownFilterTokenError";
|
|
5070
|
+
this.token = token;
|
|
5071
|
+
this.suggestion = suggestion;
|
|
5072
|
+
}
|
|
5073
|
+
};
|
|
5074
|
+
var UnresolvedFilterTokenError = class extends Error {
|
|
5075
|
+
constructor(token, detail) {
|
|
5076
|
+
super(`Filter placeholder "{${token}}" cannot be resolved: ${detail}`);
|
|
5077
|
+
/** 400, not 500 — see {@link UnknownFilterTokenError}. */
|
|
5078
|
+
this.status = 400;
|
|
5079
|
+
this.code = "FILTER_TOKEN_UNRESOLVED";
|
|
5080
|
+
this.name = "UnresolvedFilterTokenError";
|
|
5081
|
+
this.token = token;
|
|
5082
|
+
}
|
|
5083
|
+
};
|
|
5084
|
+
function ymd(year, month, day) {
|
|
5085
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
5086
|
+
return `${year}-${p(month)}-${p(day)}`;
|
|
5087
|
+
}
|
|
5088
|
+
function proxyDay(now, timezone) {
|
|
5089
|
+
const { year, month, day } = calendarPartsInTzOrUtc(now, timezone);
|
|
5090
|
+
return new Date(Date.UTC(year, month - 1, day));
|
|
5091
|
+
}
|
|
5092
|
+
var asYmd = (d) => ymd(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
|
|
5093
|
+
function startOfPeriod(kind, d) {
|
|
5094
|
+
const r = new Date(d.getTime());
|
|
5095
|
+
switch (kind) {
|
|
5096
|
+
case "week": {
|
|
5097
|
+
const dow = (r.getUTCDay() + 6) % 7;
|
|
5098
|
+
r.setUTCDate(r.getUTCDate() - dow);
|
|
5099
|
+
return r;
|
|
5100
|
+
}
|
|
5101
|
+
case "month":
|
|
5102
|
+
return new Date(Date.UTC(r.getUTCFullYear(), r.getUTCMonth(), 1));
|
|
5103
|
+
case "quarter":
|
|
5104
|
+
return new Date(Date.UTC(r.getUTCFullYear(), Math.floor(r.getUTCMonth() / 3) * 3, 1));
|
|
5105
|
+
case "year":
|
|
5106
|
+
return new Date(Date.UTC(r.getUTCFullYear(), 0, 1));
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
5109
|
+
function daysInMonth(year, month) {
|
|
5110
|
+
return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
|
|
5111
|
+
}
|
|
5112
|
+
function addMonthsClamped(d, n) {
|
|
5113
|
+
const year = d.getUTCFullYear();
|
|
5114
|
+
const month = d.getUTCMonth() + n;
|
|
5115
|
+
const targetYear = year + Math.floor(month / 12);
|
|
5116
|
+
const targetMonth = (month % 12 + 12) % 12;
|
|
5117
|
+
const day = Math.min(d.getUTCDate(), daysInMonth(targetYear, targetMonth));
|
|
5118
|
+
return new Date(Date.UTC(
|
|
5119
|
+
targetYear,
|
|
5120
|
+
targetMonth,
|
|
5121
|
+
day,
|
|
5122
|
+
d.getUTCHours(),
|
|
5123
|
+
d.getUTCMinutes(),
|
|
5124
|
+
d.getUTCSeconds(),
|
|
5125
|
+
d.getUTCMilliseconds()
|
|
5126
|
+
));
|
|
5127
|
+
}
|
|
5128
|
+
function addPeriods(kind, d, n) {
|
|
5129
|
+
switch (kind) {
|
|
5130
|
+
case "week": {
|
|
5131
|
+
const r = new Date(d.getTime());
|
|
5132
|
+
r.setUTCDate(r.getUTCDate() + n * 7);
|
|
5133
|
+
return r;
|
|
5134
|
+
}
|
|
5135
|
+
case "month":
|
|
5136
|
+
return addMonthsClamped(d, n);
|
|
5137
|
+
case "quarter":
|
|
5138
|
+
return addMonthsClamped(d, n * 3);
|
|
5139
|
+
case "year":
|
|
5140
|
+
return addMonthsClamped(d, n * 12);
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
function addUnits(unit, d, n) {
|
|
5144
|
+
const r = new Date(d.getTime());
|
|
5145
|
+
switch (unit) {
|
|
5146
|
+
case "minute":
|
|
5147
|
+
r.setUTCMinutes(r.getUTCMinutes() + n);
|
|
5148
|
+
return r;
|
|
5149
|
+
case "hour":
|
|
5150
|
+
r.setUTCHours(r.getUTCHours() + n);
|
|
5151
|
+
return r;
|
|
5152
|
+
case "day":
|
|
5153
|
+
r.setUTCDate(r.getUTCDate() + n);
|
|
5154
|
+
return r;
|
|
5155
|
+
case "week":
|
|
5156
|
+
r.setUTCDate(r.getUTCDate() + n * 7);
|
|
5157
|
+
return r;
|
|
5158
|
+
// Month/year steps clamp rather than overflow — see addMonthsClamped.
|
|
5159
|
+
case "month":
|
|
5160
|
+
return addMonthsClamped(d, n);
|
|
5161
|
+
case "year":
|
|
5162
|
+
return addMonthsClamped(d, n * 12);
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
var PERIOD_RE = /^(?:(current|last|next)_)?(week|month|quarter|year)_(start|end)$/;
|
|
5166
|
+
function resolvePeriodToken(token, today) {
|
|
5167
|
+
const m = PERIOD_RE.exec(token);
|
|
5168
|
+
if (!m) return void 0;
|
|
5169
|
+
const rel = m[1] ?? "current";
|
|
5170
|
+
const kind = m[2];
|
|
5171
|
+
const bound = m[3];
|
|
5172
|
+
const offset = rel === "last" ? -1 : rel === "next" ? 1 : 0;
|
|
5173
|
+
const periodStart = startOfPeriod(kind, addPeriods(kind, startOfPeriod(kind, today), offset));
|
|
5174
|
+
if (bound === "start") return asYmd(periodStart);
|
|
5175
|
+
const next = addPeriods(kind, periodStart, 1);
|
|
5176
|
+
next.setUTCDate(next.getUTCDate() - 1);
|
|
5177
|
+
return asYmd(next);
|
|
5178
|
+
}
|
|
5179
|
+
function resolveFilterToken(token, ctx = {}) {
|
|
5180
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
5181
|
+
if (token === "current_user_id") {
|
|
5182
|
+
if (!ctx.userId) {
|
|
5183
|
+
throw new UnresolvedFilterTokenError(
|
|
5184
|
+
token,
|
|
5185
|
+
"the request has no authenticated user. A filter scoped to the signed-in user cannot run for an anonymous or system caller \u2014 gate the surface on authentication, or drop the token from the filter."
|
|
5186
|
+
);
|
|
5187
|
+
}
|
|
5188
|
+
return ctx.userId;
|
|
5189
|
+
}
|
|
5190
|
+
if (token === "current_org_id") {
|
|
5191
|
+
if (!ctx.orgId) {
|
|
5192
|
+
throw new UnresolvedFilterTokenError(
|
|
5193
|
+
token,
|
|
5194
|
+
"the request carries no active organization (ExecutionContext.tenantId is unset). Set the active org on the request, or drop the token from the filter."
|
|
5195
|
+
);
|
|
5196
|
+
}
|
|
5197
|
+
return ctx.orgId;
|
|
5198
|
+
}
|
|
5199
|
+
const today = proxyDay(now, ctx.timezone);
|
|
5200
|
+
switch (token) {
|
|
5201
|
+
case "now":
|
|
5202
|
+
return now.toISOString();
|
|
5203
|
+
case "today":
|
|
5204
|
+
return asYmd(today);
|
|
5205
|
+
case "yesterday":
|
|
5206
|
+
return asYmd(addUnits("day", today, -1));
|
|
5207
|
+
case "tomorrow":
|
|
5208
|
+
return asYmd(addUnits("day", today, 1));
|
|
5209
|
+
}
|
|
5210
|
+
const period = resolvePeriodToken(token, today);
|
|
5211
|
+
if (period !== void 0) return period;
|
|
5212
|
+
const param = (0, import_data2.parseDateMacroParam)(token);
|
|
5213
|
+
if (param) {
|
|
5214
|
+
const sign = param.direction === "ago" ? -1 : 1;
|
|
5215
|
+
if (param.unit === "minute" || param.unit === "hour") {
|
|
5216
|
+
return addUnits(param.unit, now, sign * param.n).toISOString();
|
|
5217
|
+
}
|
|
5218
|
+
return asYmd(addUnits(param.unit, today, sign * param.n));
|
|
5219
|
+
}
|
|
5220
|
+
return void 0;
|
|
5221
|
+
}
|
|
5222
|
+
function hasFilterToken(node) {
|
|
5223
|
+
if (typeof node === "string") return (0, import_data2.classifyFilterToken)(node) !== null;
|
|
5224
|
+
if (Array.isArray(node)) return node.some(hasFilterToken);
|
|
5225
|
+
if (node && typeof node === "object" && !(node instanceof Date)) {
|
|
5226
|
+
return Object.values(node).some(hasFilterToken);
|
|
5227
|
+
}
|
|
5228
|
+
return false;
|
|
5229
|
+
}
|
|
5230
|
+
function resolveFilterTokens(filter, ctx = {}) {
|
|
5231
|
+
if (filter == null) return filter;
|
|
5232
|
+
if (!hasFilterToken(filter)) return filter;
|
|
5233
|
+
const pinned = { ...ctx, now: ctx.now ?? /* @__PURE__ */ new Date() };
|
|
5234
|
+
const walk = (node) => {
|
|
5235
|
+
if (typeof node === "string") {
|
|
5236
|
+
const cls = (0, import_data2.classifyFilterToken)(node);
|
|
5237
|
+
if (!cls) return node;
|
|
5238
|
+
if (cls.kind === "unknown") throw new UnknownFilterTokenError(cls.token, cls.suggestion);
|
|
5239
|
+
const resolved = resolveFilterToken(cls.token, pinned);
|
|
5240
|
+
if (resolved === void 0) throw new UnknownFilterTokenError(cls.token);
|
|
5241
|
+
return resolved;
|
|
5242
|
+
}
|
|
5243
|
+
if (Array.isArray(node)) return node.map(walk);
|
|
5244
|
+
if (node && typeof node === "object") {
|
|
5245
|
+
if (node instanceof Date) return node;
|
|
5246
|
+
const out = {};
|
|
5247
|
+
for (const [k, v] of Object.entries(node)) out[k] = walk(v);
|
|
5248
|
+
return out;
|
|
5249
|
+
}
|
|
5250
|
+
return node;
|
|
5251
|
+
};
|
|
5252
|
+
return walk(filter);
|
|
5253
|
+
}
|
|
5254
|
+
function filterTokenContextFrom(execCtx, now) {
|
|
5255
|
+
return {
|
|
5256
|
+
now,
|
|
5257
|
+
timezone: execCtx?.timezone,
|
|
5258
|
+
userId: execCtx?.userId,
|
|
5259
|
+
orgId: execCtx?.tenantId
|
|
5260
|
+
};
|
|
5261
|
+
}
|
|
5262
|
+
|
|
4909
5263
|
// src/health-monitor.ts
|
|
4910
5264
|
var PluginHealthMonitor = class {
|
|
4911
5265
|
constructor(logger) {
|
|
@@ -5851,6 +6205,9 @@ var NamespaceResolver = class {
|
|
|
5851
6205
|
SecurePluginContext,
|
|
5852
6206
|
SemanticVersionManager,
|
|
5853
6207
|
ServiceLifecycle,
|
|
6208
|
+
UnknownFilterTokenError,
|
|
6209
|
+
UnresolvedFilterTokenError,
|
|
6210
|
+
assertInitServiceRequirements,
|
|
5854
6211
|
bucketKeyToCalendarRange,
|
|
5855
6212
|
buildPermissionsFromGrants,
|
|
5856
6213
|
bulkWrite,
|
|
@@ -5869,8 +6226,10 @@ var NamespaceResolver = class {
|
|
|
5869
6226
|
deepMerge,
|
|
5870
6227
|
defaultIsTransientError,
|
|
5871
6228
|
derivePosture,
|
|
6229
|
+
describeInitOrderFault,
|
|
5872
6230
|
evaluateAuthGate,
|
|
5873
6231
|
extractApiKey,
|
|
6232
|
+
filterTokenContextFrom,
|
|
5874
6233
|
generateApiKey,
|
|
5875
6234
|
generateEd25519KeyPair,
|
|
5876
6235
|
getEnv,
|
|
@@ -5881,18 +6240,24 @@ var NamespaceResolver = class {
|
|
|
5881
6240
|
isGrantActive,
|
|
5882
6241
|
isGrantExpired,
|
|
5883
6242
|
isNode,
|
|
6243
|
+
nextUtcCalendarDay,
|
|
5884
6244
|
parseScopes,
|
|
5885
6245
|
parseSignature,
|
|
5886
6246
|
postureVisibleRows,
|
|
5887
6247
|
readAuthoredTranslationLayer,
|
|
5888
6248
|
resolveApiKeyPrincipal,
|
|
5889
6249
|
resolveAuthzContext,
|
|
6250
|
+
resolveFilterToken,
|
|
6251
|
+
resolveFilterTokens,
|
|
5890
6252
|
resolveLocale,
|
|
5891
6253
|
resolveLocalizationContext,
|
|
6254
|
+
resolvePluginOrder,
|
|
5892
6255
|
resolveUserAuthzGrants,
|
|
5893
6256
|
safeExit,
|
|
5894
6257
|
shouldDenyAnonymous,
|
|
5895
6258
|
signPayload,
|
|
6259
|
+
utcInstantMs,
|
|
6260
|
+
validateInitServiceContract,
|
|
5896
6261
|
verifyPayload,
|
|
5897
6262
|
verifyPlatformSignature,
|
|
5898
6263
|
verifyPluginArtifact,
|