@telorun/kernel 0.9.1 → 0.10.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/dist/controller-loader.d.ts +11 -3
- package/dist/controller-loader.d.ts.map +1 -1
- package/dist/controller-loader.js +2 -2
- package/dist/controller-loader.js.map +1 -1
- package/dist/controller-loaders/npm-loader.d.ts +128 -13
- package/dist/controller-loaders/npm-loader.d.ts.map +1 -1
- package/dist/controller-loaders/npm-loader.js +764 -216
- package/dist/controller-loaders/npm-loader.js.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.js +1 -0
- package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
- package/dist/generated/runtime-deps.json +6 -0
- package/dist/kernel.d.ts +57 -5
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +137 -23
- package/dist/kernel.js.map +1 -1
- package/dist/resource-context.d.ts +1 -0
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +3 -0
- package/dist/resource-context.js.map +1 -1
- package/package.json +16 -5
- package/src/controller-loader.ts +13 -3
- package/src/controller-loaders/npm-loader.ts +843 -229
- package/src/controllers/resource-definition/resource-definition-controller.ts +1 -0
- package/src/kernel.ts +157 -22
- package/src/resource-context.ts +4 -0
|
@@ -47,6 +47,7 @@ class ResourceDefinition implements ResourceInstance {
|
|
|
47
47
|
// here at the call site.
|
|
48
48
|
const loader = new ControllerLoader({
|
|
49
49
|
emit: (e) => ctx.emit(e.name, e.payload),
|
|
50
|
+
entryUrl: ctx.getEntryUrl(),
|
|
50
51
|
});
|
|
51
52
|
const controllerInstance = await loader.load(
|
|
52
53
|
this.resource.controllers,
|
package/src/kernel.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AnalysisRegistry,
|
|
3
|
+
flattenForAnalyzer,
|
|
4
|
+
flattenLoadedModule,
|
|
3
5
|
isModuleKind,
|
|
4
6
|
Loader,
|
|
5
7
|
StaticAnalyzer,
|
|
@@ -48,6 +50,24 @@ function findEnclosingPolicy(ctx: IEvaluationContext): ControllerPolicy | undefi
|
|
|
48
50
|
return findEnclosingModule(ctx)?.getControllerPolicy();
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
function throwInvalidState(operation: string, reason: string): never {
|
|
54
|
+
throw new RuntimeError(
|
|
55
|
+
"ERR_KERNEL_STATE_INVALID",
|
|
56
|
+
`Cannot ${operation}(): ${reason}`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseRef(ref: string): { kind: string; name: string } {
|
|
61
|
+
const lastDot = ref.lastIndexOf(".");
|
|
62
|
+
if (lastDot <= 0 || lastDot === ref.length - 1) {
|
|
63
|
+
throw new RuntimeError(
|
|
64
|
+
"ERR_INVALID_VALUE",
|
|
65
|
+
`Invalid resource reference '${ref}': expected '<Kind>.<Name>' (e.g. 'Http.Server.Main') or pass { kind, name } directly.`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return { kind: ref.slice(0, lastDot), name: ref.slice(lastDot + 1) };
|
|
69
|
+
}
|
|
70
|
+
|
|
51
71
|
export interface KernelOptions {
|
|
52
72
|
stdin?: NodeJS.ReadableStream;
|
|
53
73
|
stdout?: NodeJS.WritableStream;
|
|
@@ -96,6 +116,13 @@ export class Kernel implements IKernel {
|
|
|
96
116
|
private readonly sharedSchemaValidator = new SchemaValidator();
|
|
97
117
|
private rootContext!: ModuleContext;
|
|
98
118
|
private staticManifests: ResourceManifest[] = [];
|
|
119
|
+
private _entryUrl?: string;
|
|
120
|
+
// Lifecycle state — guards boot/runTargets/teardown/invoke transitions.
|
|
121
|
+
// teardown() is the only idempotent method; everything else throws on misuse.
|
|
122
|
+
private _bootCalled = false;
|
|
123
|
+
private _isBooted = false;
|
|
124
|
+
private _targetsRan = false;
|
|
125
|
+
private _isTornDown = false;
|
|
99
126
|
|
|
100
127
|
readonly stdin: NodeJS.ReadableStream;
|
|
101
128
|
readonly stdout: NodeJS.WritableStream;
|
|
@@ -140,12 +167,15 @@ export class Kernel implements IKernel {
|
|
|
140
167
|
this.registry.registerDefinition(definition);
|
|
141
168
|
}
|
|
142
169
|
|
|
143
|
-
loadModule(url: string, options?: LoadOptions): Promise<ResourceManifest[]> {
|
|
144
|
-
|
|
170
|
+
async loadModule(url: string, options?: LoadOptions): Promise<ResourceManifest[]> {
|
|
171
|
+
const lm = await this.loader.loadModule(url, options);
|
|
172
|
+
return flattenLoadedModule(lm);
|
|
145
173
|
}
|
|
146
174
|
|
|
147
|
-
loadManifests(url: string): Promise<ResourceManifest[]> {
|
|
148
|
-
|
|
175
|
+
async loadManifests(url: string): Promise<ResourceManifest[]> {
|
|
176
|
+
const graph = await this.loader.loadGraph(url);
|
|
177
|
+
if (graph.errors.length > 0) throw graph.errors[0].error;
|
|
178
|
+
return flattenForAnalyzer(graph);
|
|
149
179
|
}
|
|
150
180
|
|
|
151
181
|
/** Returns the live analysis registry backed by this kernel's known definitions and aliases.
|
|
@@ -194,6 +224,7 @@ export class Kernel implements IKernel {
|
|
|
194
224
|
*/
|
|
195
225
|
async load(url: string): Promise<void> {
|
|
196
226
|
const sourceUrl = await this.loader.resolveEntryPoint(url);
|
|
227
|
+
this._entryUrl = sourceUrl;
|
|
197
228
|
this.rootContext = new ModuleContext(
|
|
198
229
|
sourceUrl,
|
|
199
230
|
{},
|
|
@@ -218,7 +249,11 @@ export class Kernel implements IKernel {
|
|
|
218
249
|
|
|
219
250
|
// Static analysis pre-flight: validates schemas and invocation context compatibility.
|
|
220
251
|
// All errors are fatal — kernel does not start if analysis fails.
|
|
221
|
-
const
|
|
252
|
+
const analysisGraph = await this.loader.loadGraph(sourceUrl);
|
|
253
|
+
if (analysisGraph.errors.length > 0) {
|
|
254
|
+
throw analysisGraph.errors[0].error;
|
|
255
|
+
}
|
|
256
|
+
const staticManifests = flattenForAnalyzer(analysisGraph);
|
|
222
257
|
this.staticManifests = staticManifests;
|
|
223
258
|
|
|
224
259
|
// Register module identities for x-telo-ref resolution (Phase 3 prerequisite).
|
|
@@ -256,8 +291,11 @@ export class Kernel implements IKernel {
|
|
|
256
291
|
);
|
|
257
292
|
}
|
|
258
293
|
|
|
259
|
-
// Load runtime configuration — root module gets access to host env
|
|
260
|
-
|
|
294
|
+
// Load runtime configuration — root module gets access to host env.
|
|
295
|
+
// Imports are loaded separately via the import-controller; this load is
|
|
296
|
+
// entry-only with compile-time CEL.
|
|
297
|
+
const lm = await this.loader.loadModule(sourceUrl, { compile: true });
|
|
298
|
+
const allManifests = flattenLoadedModule(lm);
|
|
261
299
|
|
|
262
300
|
// Phase 2: normalize inline resources — extract inline values from x-telo-ref slots
|
|
263
301
|
// into first-class named manifests and replace them in-place with {kind, name} references.
|
|
@@ -278,9 +316,25 @@ export class Kernel implements IKernel {
|
|
|
278
316
|
}
|
|
279
317
|
|
|
280
318
|
/**
|
|
281
|
-
*
|
|
319
|
+
* Initialize every resource declared in the manifest. Does not run targets
|
|
320
|
+
* and does not wait — returns as soon as the kernel is ready to accept
|
|
321
|
+
* `invoke()` calls.
|
|
322
|
+
*
|
|
323
|
+
* Throws ERR_KERNEL_STATE_INVALID if `load()` was not called first, on
|
|
324
|
+
* second call, or after teardown.
|
|
282
325
|
*/
|
|
283
|
-
async
|
|
326
|
+
async boot(): Promise<void> {
|
|
327
|
+
if (this._isTornDown) {
|
|
328
|
+
throwInvalidState("boot", "kernel has been torn down");
|
|
329
|
+
}
|
|
330
|
+
if (this._bootCalled) {
|
|
331
|
+
throwInvalidState("boot", "boot() already called");
|
|
332
|
+
}
|
|
333
|
+
if (this._entryUrl === undefined) {
|
|
334
|
+
throwInvalidState("boot", "load() has not been called");
|
|
335
|
+
}
|
|
336
|
+
this._bootCalled = true;
|
|
337
|
+
|
|
284
338
|
// Call register hooks for controllers actually loaded at this point (built-ins).
|
|
285
339
|
// User-module kinds load their controllers during Phase 3 (Telo.Definition.init),
|
|
286
340
|
// and registerController() fires their register hook there.
|
|
@@ -321,25 +375,103 @@ export class Kernel implements IKernel {
|
|
|
321
375
|
this.rootContext.setInitOrder(order);
|
|
322
376
|
}
|
|
323
377
|
|
|
324
|
-
|
|
378
|
+
await this.rootContext.initializeResources();
|
|
379
|
+
await this.eventBus.emit("Kernel.Initialized", {});
|
|
380
|
+
|
|
381
|
+
this._isBooted = true;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Run the manifest's `targets` (Telo.Service / Telo.Runnable instances).
|
|
386
|
+
* Emits Kernel.Starting before, Kernel.Started after.
|
|
387
|
+
*
|
|
388
|
+
* Throws ERR_KERNEL_STATE_INVALID if called before `boot()` completes, after
|
|
389
|
+
* teardown, or a second time.
|
|
390
|
+
*/
|
|
391
|
+
async runTargets(): Promise<void> {
|
|
392
|
+
if (this._isTornDown) {
|
|
393
|
+
throwInvalidState("runTargets", "kernel has been torn down");
|
|
394
|
+
}
|
|
395
|
+
if (!this._isBooted) {
|
|
396
|
+
throwInvalidState("runTargets", "boot() has not completed");
|
|
397
|
+
}
|
|
398
|
+
if (this._targetsRan) {
|
|
399
|
+
throwInvalidState("runTargets", "runTargets() already called");
|
|
400
|
+
}
|
|
401
|
+
this._targetsRan = true;
|
|
402
|
+
|
|
403
|
+
await this.eventBus.emit("Kernel.Starting", {});
|
|
404
|
+
await this.rootContext.runTargets();
|
|
405
|
+
await this.eventBus.emit("Kernel.Started", {});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Tear down every initialized resource. Emits Kernel.Stopping before,
|
|
410
|
+
* Kernel.Stopped after. Idempotent — second call is a no-op (does not
|
|
411
|
+
* re-emit). Tolerates partial state — a boot() that threw mid-init still
|
|
412
|
+
* cleans up whichever resources had initialized.
|
|
413
|
+
*/
|
|
414
|
+
async teardown(): Promise<void> {
|
|
415
|
+
if (this._isTornDown) return;
|
|
416
|
+
this._isTornDown = true;
|
|
417
|
+
|
|
418
|
+
await this.eventBus.emit("Kernel.Stopping", {});
|
|
419
|
+
if (this.rootContext) {
|
|
420
|
+
await this.rootContext.teardownResources();
|
|
421
|
+
}
|
|
422
|
+
await this.eventBus.emit("Kernel.Stopped", { exitCode: this._exitCode });
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Convenience: boot → runTargets → waitForIdle → teardown. The try wraps
|
|
427
|
+
* boot() and runTargets() too — init-time failures still drive teardown and
|
|
428
|
+
* still emit Kernel.Stopping / Kernel.Stopped, matching pre-split semantics.
|
|
429
|
+
*/
|
|
430
|
+
async start(): Promise<void> {
|
|
325
431
|
try {
|
|
326
|
-
await this.
|
|
327
|
-
await this.
|
|
328
|
-
await this.eventBus.emit("Kernel.Starting", {});
|
|
329
|
-
await this.rootContext.runTargets();
|
|
330
|
-
await this.eventBus.emit("Kernel.Started", {});
|
|
432
|
+
await this.boot();
|
|
433
|
+
await this.runTargets();
|
|
331
434
|
await this.waitForIdle();
|
|
332
435
|
} finally {
|
|
333
|
-
await this.
|
|
334
|
-
await this.rootContext.teardownResources();
|
|
335
|
-
await this.eventBus.emit("Kernel.Stopped", { exitCode: this._exitCode });
|
|
436
|
+
await this.teardown();
|
|
336
437
|
}
|
|
337
438
|
}
|
|
338
439
|
|
|
440
|
+
/**
|
|
441
|
+
* Invoke a Telo.Invocable resource by `<kind>.<name>` (dot-form) or
|
|
442
|
+
* `{ kind, name }`. Resolves through the root module context, so the same
|
|
443
|
+
* dispatch, error path, and event emission that controller-to-controller
|
|
444
|
+
* invokes use apply here too.
|
|
445
|
+
*/
|
|
446
|
+
async invoke<TInputs = any, TOutput = any>(
|
|
447
|
+
ref: string | { kind: string; name: string },
|
|
448
|
+
inputs: TInputs,
|
|
449
|
+
): Promise<TOutput> {
|
|
450
|
+
if (this._isTornDown) {
|
|
451
|
+
throwInvalidState("invoke", "kernel has been torn down");
|
|
452
|
+
}
|
|
453
|
+
if (!this._isBooted) {
|
|
454
|
+
throwInvalidState("invoke", "boot() has not completed");
|
|
455
|
+
}
|
|
456
|
+
const parsed = typeof ref === "string" ? parseRef(ref) : ref;
|
|
457
|
+
return (await this.rootContext.invoke(parsed.kind, parsed.name, inputs)) as TOutput;
|
|
458
|
+
}
|
|
459
|
+
|
|
339
460
|
async emitRuntimeEvent(event: string, payload?: any): Promise<void> {
|
|
340
461
|
await this.eventBus.emit(event, payload);
|
|
341
462
|
}
|
|
342
463
|
|
|
464
|
+
/**
|
|
465
|
+
* URL of the entry manifest passed to `load()`, or `undefined` before
|
|
466
|
+
* `load()` has been called. Used by controllers and the controller-loader
|
|
467
|
+
* to anchor per-manifest install roots so every resource in the process
|
|
468
|
+
* shares a single `node_modules` tree (and therefore one realpath for
|
|
469
|
+
* `@telorun/sdk`).
|
|
470
|
+
*/
|
|
471
|
+
getEntryUrl(): string | undefined {
|
|
472
|
+
return this._entryUrl;
|
|
473
|
+
}
|
|
474
|
+
|
|
343
475
|
get exitCode(): number {
|
|
344
476
|
return this._exitCode;
|
|
345
477
|
}
|
|
@@ -383,11 +515,14 @@ export class Kernel implements IKernel {
|
|
|
383
515
|
}
|
|
384
516
|
|
|
385
517
|
/**
|
|
386
|
-
* Force-resolve waitForIdle()
|
|
387
|
-
* Used
|
|
388
|
-
*
|
|
518
|
+
* Force-resolve any pending `waitForIdle()` even when holds are still active.
|
|
519
|
+
* Used by external signal handlers (SIGINT/SIGTERM) to unblock graceful exit
|
|
520
|
+
* so `start()`'s waitForIdle returns and its finally clause runs `teardown()`.
|
|
521
|
+
*
|
|
522
|
+
* Does not tear down on its own — call `teardown()` directly if you're not
|
|
523
|
+
* inside `start()`.
|
|
389
524
|
*/
|
|
390
|
-
|
|
525
|
+
forceIdle(): void {
|
|
391
526
|
const resolvers = this.idleResolvers.splice(0);
|
|
392
527
|
for (const resolve of resolvers) resolve();
|
|
393
528
|
}
|
package/src/resource-context.ts
CHANGED
|
@@ -262,6 +262,10 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
262
262
|
return this.moduleContext.getControllerPolicy();
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
getEntryUrl(): string | undefined {
|
|
266
|
+
return this.kernel.getEntryUrl();
|
|
267
|
+
}
|
|
268
|
+
|
|
265
269
|
on(event: string, handler: (payload?: any) => void | Promise<void>): void {
|
|
266
270
|
this.kernel.on(event, handler);
|
|
267
271
|
}
|