@telorun/kernel 0.2.4 → 0.2.5
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/boot-context-registry.d.ts.map +1 -1
- package/dist/boot-context-registry.js +6 -6
- package/dist/boot-context-registry.js.map +1 -1
- package/dist/capabilities/capabilities/component.yaml +2 -1
- package/dist/capabilities/capabilities/executable.yaml +2 -1
- package/dist/capabilities/capabilities/handler.yaml +2 -1
- package/dist/capabilities/capabilities/listener.yaml +2 -1
- package/dist/capabilities/capabilities/provider.yaml +2 -1
- package/dist/capabilities/capabilities/template.yaml +2 -1
- package/dist/capabilities/capabilities/type.yaml +2 -1
- package/dist/capabilities/component.yaml +1 -1
- package/dist/capabilities/executable.yaml +1 -1
- package/dist/capabilities/handler.yaml +1 -1
- package/dist/capabilities/listener.yaml +1 -1
- package/dist/capabilities/provider.yaml +1 -1
- package/dist/capabilities/template.yaml +1 -1
- package/dist/capabilities/type.yaml +1 -1
- package/dist/controller-loader.d.ts +1 -1
- package/dist/controller-loader.d.ts.map +1 -1
- package/dist/controller-loader.js +4 -2
- package/dist/controller-loader.js.map +1 -1
- package/dist/controller-registry.d.ts +1 -2
- package/dist/controller-registry.d.ts.map +1 -1
- package/dist/controller-registry.js.map +1 -1
- package/dist/controllers/module/import-controller.d.ts +38 -0
- package/dist/controllers/module/import-controller.d.ts.map +1 -0
- package/dist/controllers/module/import-controller.js +119 -0
- package/dist/controllers/module/import-controller.js.map +1 -0
- package/dist/controllers/module/module-controller.d.ts +57 -11
- package/dist/controllers/module/module-controller.d.ts.map +1 -1
- package/dist/controllers/module/module-controller.js +46 -82
- package/dist/controllers/module/module-controller.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 +12 -4
- package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
- package/dist/evaluation-context.d.ts +91 -0
- package/dist/evaluation-context.d.ts.map +1 -0
- package/dist/evaluation-context.js +220 -0
- package/dist/evaluation-context.js.map +1 -0
- package/dist/execution-context.d.ts +13 -0
- package/dist/execution-context.d.ts.map +1 -0
- package/dist/execution-context.js +14 -0
- package/dist/execution-context.js.map +1 -0
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +23 -31
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +212 -333
- package/dist/kernel.js.map +1 -1
- package/dist/loader.d.ts +2 -2
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +29 -9
- package/dist/loader.js.map +1 -1
- package/dist/manifest-adapters/local-file-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/local-file-adapter.js +21 -12
- package/dist/manifest-adapters/local-file-adapter.js.map +1 -1
- package/dist/manifest-schemas.d.ts +1 -25
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +3 -22
- package/dist/manifest-schemas.js.map +1 -1
- package/dist/module-context-registry.d.ts +48 -0
- package/dist/module-context-registry.d.ts.map +1 -0
- package/dist/module-context-registry.js +91 -0
- package/dist/module-context-registry.js.map +1 -0
- package/dist/module-context.d.ts +31 -0
- package/dist/module-context.d.ts.map +1 -0
- package/dist/module-context.js +67 -0
- package/dist/module-context.js.map +1 -0
- package/dist/registry.d.ts +1 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +1 -1
- package/dist/resource-context.d.ts +25 -5
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +74 -28
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-valiator.d.ts.map +1 -1
- package/dist/schema-valiator.js +3 -1
- package/dist/schema-valiator.js.map +1 -1
- package/dist/snapshot-serializer.d.ts +1 -2
- package/dist/snapshot-serializer.d.ts.map +1 -1
- package/dist/snapshot-serializer.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +9 -6
- package/src/boot-context-registry.ts +169 -0
- package/src/capabilities/component.yaml +4 -0
- package/src/capabilities/executable.yaml +8 -0
- package/src/capabilities/handler.yaml +4 -0
- package/src/capabilities/listener.yaml +4 -0
- package/src/capabilities/provider.yaml +4 -0
- package/src/capabilities/template.yaml +4 -0
- package/src/capabilities/type.yaml +4 -0
- package/src/controller-loader.ts +298 -0
- package/src/controller-registry.ts +206 -0
- package/src/controllers/capability/capability-controller.ts +41 -0
- package/src/controllers/module/import-controller.ts +143 -0
- package/src/controllers/module/module-controller.ts +67 -0
- package/src/controllers/module/module.json +48 -0
- package/src/controllers/resource-definition/resource-definition-controller.ts +87 -0
- package/src/controllers/resource-definition/resource-definition.json +18 -0
- package/src/event-stream.ts +121 -0
- package/src/events.ts +99 -0
- package/src/index.ts +7 -0
- package/src/kernel.ts +558 -0
- package/src/loader.ts +245 -0
- package/src/manifest-adapters/http-adapter.ts +35 -0
- package/src/manifest-adapters/local-file-adapter.ts +69 -0
- package/src/manifest-adapters/manifest-adapter.ts +33 -0
- package/src/manifest-adapters/registry-adapter.ts +56 -0
- package/src/manifest-schemas.ts +49 -0
- package/src/registry.ts +137 -0
- package/src/resource-context.ts +266 -0
- package/src/resource-uri.ts +200 -0
- package/src/schema-valiator.ts +57 -0
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -109
- package/dist/cli.js.map +0 -1
- package/dist/expressions.d.ts +0 -20
- package/dist/expressions.d.ts.map +0 -1
- package/dist/expressions.js +0 -253
- package/dist/expressions.js.map +0 -1
- package/dist/template-definition.d.ts +0 -38
- package/dist/template-definition.d.ts.map +0 -1
- package/dist/template-definition.js +0 -26
- package/dist/template-definition.js.map +0 -1
- package/dist/template-expander.d.ts +0 -19
- package/dist/template-expander.d.ts.map +0 -1
- package/dist/template-expander.js +0 -425
- package/dist/template-expander.js.map +0 -1
- /package/{dist/src → src}/controllers/module/module.yaml +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { ControllerInstance, ResourceDefinition, RuntimeResource } from "@telorun/sdk";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ControllerRegistry: Manages controller loading and dispatch
|
|
6
|
+
* Maps fully-qualified resource kinds to their controller implementations
|
|
7
|
+
*/
|
|
8
|
+
export class ControllerRegistry {
|
|
9
|
+
private controllersByKind: Map<string, ControllerInstance> = new Map();
|
|
10
|
+
private definitionsByKind: Map<string, ResourceDefinition> = new Map();
|
|
11
|
+
private controllerLoaders: Map<string, () => Promise<ControllerInstance>> = new Map();
|
|
12
|
+
private capabilitySchemas: Map<string, Record<string, any> | null> = new Map();
|
|
13
|
+
|
|
14
|
+
registerCapability(name: string, schema?: Record<string, any>): void {
|
|
15
|
+
this.capabilitySchemas.set(name, schema ?? null);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
isCapabilityRegistered(name: string): boolean {
|
|
19
|
+
return this.capabilitySchemas.has(name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getCapabilitySchema(name: string): Record<string, any> | null | undefined {
|
|
23
|
+
return this.capabilitySchemas.get(name);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Register a controller definition
|
|
28
|
+
*/
|
|
29
|
+
registerDefinition(
|
|
30
|
+
definition: ResourceDefinition,
|
|
31
|
+
// baseDir?: string,
|
|
32
|
+
// namespace?: string | null,
|
|
33
|
+
): void {
|
|
34
|
+
// Construct fully qualified kind: Namespace.Name
|
|
35
|
+
// Only add namespace if name is not already qualified (doesn't contain a dot)
|
|
36
|
+
const namespace = definition.metadata.module;
|
|
37
|
+
const baseDir = null;
|
|
38
|
+
const name = definition.metadata.name;
|
|
39
|
+
const kind = namespace && !name.includes(".") ? `${namespace}.${name}` : name;
|
|
40
|
+
|
|
41
|
+
this.definitionsByKind.set(kind, definition);
|
|
42
|
+
|
|
43
|
+
// If definition has controllers, register loader for them
|
|
44
|
+
if (definition.controllers && definition.controllers.length > 0 && baseDir) {
|
|
45
|
+
this.registerControllerLoader(kind, definition, baseDir);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get a controller instance for a kind
|
|
51
|
+
* Lazy-loads controller code on first access
|
|
52
|
+
* Throws if controller not found
|
|
53
|
+
*/
|
|
54
|
+
getController(kind: string): ControllerInstance {
|
|
55
|
+
// Return cached instance if available
|
|
56
|
+
if (this.controllersByKind.has(kind)) {
|
|
57
|
+
return this.controllersByKind.get(kind)!;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Load controller if loader is registered
|
|
61
|
+
// const loader = this.controllerLoaders.get(kind);
|
|
62
|
+
// if (loader) {
|
|
63
|
+
// const controller = await loader();
|
|
64
|
+
// this.controllersByKind.set(kind, controller);
|
|
65
|
+
// return controller;
|
|
66
|
+
// }
|
|
67
|
+
|
|
68
|
+
throw new Error(`No controller registered for kind: ${kind}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Safe get - returns undefined if controller not found
|
|
73
|
+
*/
|
|
74
|
+
getControllerOrUndefined(kind: string): ControllerInstance | undefined {
|
|
75
|
+
// Return cached instance if available
|
|
76
|
+
if (this.controllersByKind.has(kind)) {
|
|
77
|
+
return this.controllersByKind.get(kind);
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if a controller exists for this kind (definition or directly registered)
|
|
84
|
+
*/
|
|
85
|
+
hasController(kind: string): boolean {
|
|
86
|
+
return this.controllersByKind.has(kind) || this.definitionsByKind.has(kind);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get definition for a kind
|
|
91
|
+
*/
|
|
92
|
+
getDefinition(kind: string): ResourceDefinition | undefined {
|
|
93
|
+
return this.definitionsByKind.get(kind);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get all registered kinds
|
|
98
|
+
*/
|
|
99
|
+
getKinds(): string[] {
|
|
100
|
+
return Array.from(this.definitionsByKind.keys());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getControllerKinds(): string[] {
|
|
104
|
+
return Array.from(this.controllersByKind.keys());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create a resource instance using its controller
|
|
109
|
+
*/
|
|
110
|
+
async create(kind: string, resource: RuntimeResource, ctx: any): Promise<any | null> {
|
|
111
|
+
const controller = this.getController(kind);
|
|
112
|
+
if (!controller || !controller.create) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return controller.create(resource, ctx);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Register a controller for a kind
|
|
120
|
+
*/
|
|
121
|
+
registerController(kind: string, controller: ControllerInstance): void {
|
|
122
|
+
if (!this.definitionsByKind.has(kind)) {
|
|
123
|
+
throw new Error(`Cannot register controller for kind ${kind} without definition`);
|
|
124
|
+
}
|
|
125
|
+
// Ensure controller has schema from definition
|
|
126
|
+
const definition = this.definitionsByKind.get(kind);
|
|
127
|
+
if (definition?.schema && !controller.schema) {
|
|
128
|
+
// Wrap controller to add schema without mutating the original
|
|
129
|
+
const wrappedController: ControllerInstance = {
|
|
130
|
+
...controller,
|
|
131
|
+
schema: definition.schema,
|
|
132
|
+
};
|
|
133
|
+
this.controllersByKind.set(kind, wrappedController);
|
|
134
|
+
} else {
|
|
135
|
+
this.controllersByKind.set(kind, controller);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Private: Register controller loader
|
|
141
|
+
*/
|
|
142
|
+
private registerControllerLoader(
|
|
143
|
+
kind: string,
|
|
144
|
+
definition: ResourceDefinition,
|
|
145
|
+
moduleDir: string,
|
|
146
|
+
): void {
|
|
147
|
+
const controllerDef = definition.controllers?.[0]; // Use first matching controller for now
|
|
148
|
+
if (!controllerDef) return;
|
|
149
|
+
|
|
150
|
+
this.controllerLoaders.set(kind, async () => {
|
|
151
|
+
const modulePath = path.resolve(moduleDir, controllerDef.entry);
|
|
152
|
+
const moduleRuntime = await import(modulePath);
|
|
153
|
+
const exported = moduleRuntime.default || moduleRuntime.Module || moduleRuntime;
|
|
154
|
+
|
|
155
|
+
const registerFn =
|
|
156
|
+
typeof moduleRuntime.register === "function"
|
|
157
|
+
? moduleRuntime.register
|
|
158
|
+
: typeof exported === "function" && !this.isModuleClass(exported)
|
|
159
|
+
? exported
|
|
160
|
+
: null;
|
|
161
|
+
|
|
162
|
+
const createFn =
|
|
163
|
+
typeof moduleRuntime.create === "function"
|
|
164
|
+
? moduleRuntime.create
|
|
165
|
+
: typeof exported?.create === "function"
|
|
166
|
+
? exported.create
|
|
167
|
+
: null;
|
|
168
|
+
|
|
169
|
+
const executeFn =
|
|
170
|
+
typeof moduleRuntime.execute === "function"
|
|
171
|
+
? moduleRuntime.execute
|
|
172
|
+
: typeof exported?.execute === "function"
|
|
173
|
+
? exported.execute
|
|
174
|
+
: null;
|
|
175
|
+
|
|
176
|
+
const compileFn =
|
|
177
|
+
typeof moduleRuntime.compile === "function"
|
|
178
|
+
? moduleRuntime.compile
|
|
179
|
+
: typeof exported?.compile === "function"
|
|
180
|
+
? exported.compile
|
|
181
|
+
: null;
|
|
182
|
+
|
|
183
|
+
if (!registerFn && !executeFn && !createFn && !compileFn) {
|
|
184
|
+
throw new Error(`Controller for "${kind}" exports no usable handlers`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!definition.schema) {
|
|
188
|
+
throw new Error(`Definition for "${kind}" does not have schema`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
register: registerFn ?? undefined,
|
|
193
|
+
create: createFn ?? undefined,
|
|
194
|
+
execute: executeFn ?? undefined,
|
|
195
|
+
compile: compileFn ?? undefined,
|
|
196
|
+
schema: definition.schema,
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private isModuleClass(obj: any): boolean {
|
|
202
|
+
return (
|
|
203
|
+
typeof obj === "function" && (obj.name === "Controller" || obj.toString().includes("class"))
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
2
|
+
|
|
3
|
+
type CapabilityResource = {
|
|
4
|
+
kind: string;
|
|
5
|
+
metadata: {
|
|
6
|
+
name: string;
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
};
|
|
9
|
+
schema?: Record<string, any>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
class Capability implements ResourceInstance {
|
|
13
|
+
constructor(private readonly resource: CapabilityResource) {}
|
|
14
|
+
|
|
15
|
+
async init(ctx: ResourceContext) {
|
|
16
|
+
ctx.registerCapability(this.resource.metadata.name, this.resource.schema);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function create(resource: any, _ctx: ResourceContext): Promise<Capability> {
|
|
21
|
+
return new Capability(resource as CapabilityResource);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const schema = {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
metadata: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
name: { type: "string" },
|
|
31
|
+
},
|
|
32
|
+
required: ["name"],
|
|
33
|
+
},
|
|
34
|
+
schema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
additionalProperties: true,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ["metadata"],
|
|
40
|
+
additionalProperties: true,
|
|
41
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
2
|
+
import { EvaluationContext } from "@telorun/sdk";
|
|
3
|
+
import { Loader } from "../../loader.js";
|
|
4
|
+
|
|
5
|
+
export async function create(resource: any, ctx: ResourceContext): Promise<ResourceInstance> {
|
|
6
|
+
const alias = resource.metadata.name as string;
|
|
7
|
+
const declaringModule: string = resource.metadata.module ?? "default";
|
|
8
|
+
const loader = new Loader();
|
|
9
|
+
|
|
10
|
+
// Load target module manifests. Inject variables/secrets as compile context so that
|
|
11
|
+
// ${{ variables.x }} / ${{ secrets.y }} templates in the child module resolve correctly.
|
|
12
|
+
// No env — child modules are isolated from host environment.
|
|
13
|
+
const manifests = await loader.loadManifest(resource.source as string, ctx.moduleContext.source, {
|
|
14
|
+
// Potentially not needed
|
|
15
|
+
variables: (resource.variables as Record<string, unknown>) ?? {},
|
|
16
|
+
secrets: (resource.secrets as Record<string, unknown>) ?? {},
|
|
17
|
+
});
|
|
18
|
+
// Find the kind: Module manifest to learn the target module name and contract.
|
|
19
|
+
const moduleManifest = manifests.find((m: any) => m.kind === "Kernel.Module");
|
|
20
|
+
if (!moduleManifest) {
|
|
21
|
+
throw new Error(`No kind: Module manifest found in source "${resource.source as string}"`);
|
|
22
|
+
}
|
|
23
|
+
const targetModule: string = moduleManifest.metadata.name;
|
|
24
|
+
|
|
25
|
+
// Validate that every non-module manifest's metadata.module (when explicitly set)
|
|
26
|
+
// matches the target module name. A mismatch means the author put the wrong module
|
|
27
|
+
// name in a resource's metadata, which would silently give it an empty context and
|
|
28
|
+
// produce a confusing CEL "Identifier not found" error at runtime.
|
|
29
|
+
for (const manifest of manifests) {
|
|
30
|
+
if (manifest.kind === "Kernel.Module") continue;
|
|
31
|
+
const declaredModule: string | undefined = manifest.metadata?.module;
|
|
32
|
+
if (declaredModule && declaredModule !== targetModule) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Resource '${manifest.metadata?.name ?? "(unnamed)"}' (kind: ${manifest.kind}) inside module '${targetModule}' ` +
|
|
35
|
+
`has metadata.module: '${declaredModule}', but the module is named '${targetModule}'. ` +
|
|
36
|
+
`Update metadata.module to '${targetModule}'.`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Validate required inputs before injecting.
|
|
42
|
+
validateRequiredInputs(moduleManifest.variables ?? {}, resource.variables ?? {}, "variables");
|
|
43
|
+
validateRequiredInputs(moduleManifest.secrets ?? {}, resource.secrets ?? {}, "secrets");
|
|
44
|
+
|
|
45
|
+
// Create child context with the imported variables/secrets baked in, so that
|
|
46
|
+
// ${{ variables.x }} / ${{ secrets.y }} templates resolve correctly at runtime.
|
|
47
|
+
const child = ctx.moduleContext.spawnChild(
|
|
48
|
+
new EvaluationContext(
|
|
49
|
+
ctx.moduleContext.source,
|
|
50
|
+
{
|
|
51
|
+
variables: (resource.variables as Record<string, unknown>) ?? {},
|
|
52
|
+
secrets: (resource.secrets as Record<string, unknown>) ?? {},
|
|
53
|
+
resources: {},
|
|
54
|
+
},
|
|
55
|
+
ctx.moduleContext.createInstance,
|
|
56
|
+
ctx.moduleContext.secretValues,
|
|
57
|
+
ctx.moduleContext.emit,
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
for (const manifest of manifests) {
|
|
62
|
+
child.registerManifest(manifest);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Link the target module context as a child of the declaring module context in
|
|
66
|
+
// the lifecycle tree. This enables cascading teardown (parent → child order)
|
|
67
|
+
// and makes the import hierarchy visible at runtime.
|
|
68
|
+
// const declaringCtx: ModuleContext = ctx.getModuleContext(declaringModule);
|
|
69
|
+
// const targetCtx: ModuleContext = (ctx as any).getModuleContext(targetModule);
|
|
70
|
+
// if (!targetCtx.parent) {
|
|
71
|
+
// declaringCtx.spawnChild(targetCtx);
|
|
72
|
+
// }
|
|
73
|
+
|
|
74
|
+
// Try to evaluate the target module's exports.
|
|
75
|
+
// Throws if resources.X is not yet populated — the kernel retry loop catches this and retries.
|
|
76
|
+
// const evaluatedExports: any = child.expand(moduleManifest.exports ?? {});
|
|
77
|
+
|
|
78
|
+
const exportedKinds: string[] = moduleManifest.exports?.kinds ?? [];
|
|
79
|
+
ctx.registerModuleImport(alias, targetModule, exportedKinds);
|
|
80
|
+
// Return a ResourceInstance whose snapshot() surfaces the exported values.
|
|
81
|
+
// The kernel's generic setResource() call stores them under resources.<alias>
|
|
82
|
+
// in the declaring module's evaluation context — no separate imports namespace needed.
|
|
83
|
+
return {
|
|
84
|
+
snapshot: () => ({
|
|
85
|
+
variables: (resource.variables as Record<string, unknown>) ?? {},
|
|
86
|
+
secrets: (resource.secrets as Record<string, unknown>) ?? {},
|
|
87
|
+
}),
|
|
88
|
+
run: async () => {
|
|
89
|
+
// Proxy run to target module
|
|
90
|
+
for (const target of (moduleManifest.targets as string[]) ?? []) {
|
|
91
|
+
await child.run(target);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
invoke: async () => {
|
|
95
|
+
// Proxy run to target module
|
|
96
|
+
// for (const target of (moduleManifest.targets as string[]) ?? []) {
|
|
97
|
+
// child.invoke(target);
|
|
98
|
+
// }
|
|
99
|
+
console.log("invoking");
|
|
100
|
+
},
|
|
101
|
+
init: async () => {
|
|
102
|
+
await child.initializeResources();
|
|
103
|
+
},
|
|
104
|
+
teardown: async () => {
|
|
105
|
+
await child.teardownResources();
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function validateRequiredInputs(
|
|
111
|
+
schemaDefs: Record<string, any>,
|
|
112
|
+
provided: Record<string, unknown>,
|
|
113
|
+
kind: "variables" | "secrets",
|
|
114
|
+
): void {
|
|
115
|
+
for (const [key, def] of Object.entries(schemaDefs)) {
|
|
116
|
+
const isRequired = typeof def === "object" && def !== null && !("default" in def);
|
|
117
|
+
if (isRequired && !(key in provided)) {
|
|
118
|
+
throw new Error(`Required ${kind} input "${key}" not provided for module import`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const schema = {
|
|
124
|
+
type: "object",
|
|
125
|
+
properties: {
|
|
126
|
+
kind: { type: "string" },
|
|
127
|
+
metadata: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
name: { type: "string" },
|
|
131
|
+
source: { type: "string" },
|
|
132
|
+
module: { type: "string" },
|
|
133
|
+
},
|
|
134
|
+
required: ["name"],
|
|
135
|
+
additionalProperties: true,
|
|
136
|
+
},
|
|
137
|
+
source: { type: "string" },
|
|
138
|
+
variables: { type: "object" },
|
|
139
|
+
secrets: { type: "object" },
|
|
140
|
+
},
|
|
141
|
+
required: ["metadata", "source"],
|
|
142
|
+
additionalProperties: false,
|
|
143
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
2
|
+
import { Loader } from "../../loader.js";
|
|
3
|
+
|
|
4
|
+
export async function create(resource: any, ctx: ResourceContext): Promise<ResourceInstance> {
|
|
5
|
+
const moduleName = resource.metadata.name as string;
|
|
6
|
+
const loader = new Loader();
|
|
7
|
+
|
|
8
|
+
for (const includePath of (resource.include as string[]) ?? []) {
|
|
9
|
+
const manifests = await loader.loadManifest(
|
|
10
|
+
includePath,
|
|
11
|
+
resource.metadata.source as string,
|
|
12
|
+
{},
|
|
13
|
+
);
|
|
14
|
+
for (const manifest of manifests) {
|
|
15
|
+
if (manifest.kind === "Kernel.Module") {
|
|
16
|
+
throw new Error(`Included file "${includePath}" must not declare kind: Module`);
|
|
17
|
+
}
|
|
18
|
+
if (!manifest.metadata.module) {
|
|
19
|
+
manifest.metadata.module = moduleName;
|
|
20
|
+
}
|
|
21
|
+
ctx.registerManifest(manifest);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
run: async () => {
|
|
27
|
+
for (const target of (resource.targets as string[]) ?? []) {
|
|
28
|
+
const [kind, name] = target.split(".");
|
|
29
|
+
if (!kind || !name) {
|
|
30
|
+
throw new Error(`Invalid target format: "${target}". Expected "Kind.Name"`);
|
|
31
|
+
}
|
|
32
|
+
await ctx.invoke(kind, name, {});
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const schema = {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
kind: { type: "string" },
|
|
42
|
+
metadata: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
name: { type: "string" },
|
|
46
|
+
version: { type: "string" },
|
|
47
|
+
source: { type: "string" },
|
|
48
|
+
module: { type: "string" },
|
|
49
|
+
},
|
|
50
|
+
required: ["name"],
|
|
51
|
+
additionalProperties: true,
|
|
52
|
+
},
|
|
53
|
+
include: { type: "array", items: { type: "string" } },
|
|
54
|
+
variables: { type: "object" },
|
|
55
|
+
secrets: { type: "object" },
|
|
56
|
+
targets: { type: "array", items: { type: "string" } },
|
|
57
|
+
exports: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
kinds: { type: "array", items: { type: "string" } },
|
|
61
|
+
},
|
|
62
|
+
additionalProperties: true,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
required: ["metadata"],
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"properties": {
|
|
4
|
+
"metadata": {
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"name": {
|
|
8
|
+
"type": "string"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"required": ["name"]
|
|
12
|
+
},
|
|
13
|
+
"imports": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "string"
|
|
17
|
+
},
|
|
18
|
+
"description": "Paths to other modules to import"
|
|
19
|
+
},
|
|
20
|
+
"definitions": {
|
|
21
|
+
"type": "array",
|
|
22
|
+
"items": {
|
|
23
|
+
"type": "string"
|
|
24
|
+
},
|
|
25
|
+
"description": "Paths to ResourceDefinition files"
|
|
26
|
+
},
|
|
27
|
+
"resources": {
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": {
|
|
30
|
+
"oneOf": [
|
|
31
|
+
{
|
|
32
|
+
"type": "string"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"path": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"description": "Paths to resource instance files to load"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"required": ["metadata"]
|
|
48
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ControllerContext,
|
|
3
|
+
ResourceContext,
|
|
4
|
+
ResourceInstance,
|
|
5
|
+
RuntimeResource,
|
|
6
|
+
} from "@telorun/sdk";
|
|
7
|
+
import { ControllerLoader } from "../../controller-loader.js";
|
|
8
|
+
import { formatAjvErrors, validateResourceDefinition } from "../../manifest-schemas.js";
|
|
9
|
+
|
|
10
|
+
type ResourceDefinitionResource = RuntimeResource & {
|
|
11
|
+
kind: "Definition";
|
|
12
|
+
metadata: {
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
name: string;
|
|
15
|
+
module?: string;
|
|
16
|
+
};
|
|
17
|
+
schema: Record<string, any>;
|
|
18
|
+
capabilities: string[];
|
|
19
|
+
events?: string[];
|
|
20
|
+
controllers: Array<string>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* ResourceDefinition resource - acts as metadata holder for resource type definitions
|
|
25
|
+
* Validates incoming definitions against schema and maintains definition metadata
|
|
26
|
+
*/
|
|
27
|
+
class ResourceDefinition implements ResourceInstance {
|
|
28
|
+
readonly kind: "ResourceDefinition" = "ResourceDefinition";
|
|
29
|
+
|
|
30
|
+
constructor(
|
|
31
|
+
readonly resource: ResourceDefinitionResource,
|
|
32
|
+
private controllerLoader: ControllerLoader,
|
|
33
|
+
) {}
|
|
34
|
+
|
|
35
|
+
async init(ctx: ResourceContext) {
|
|
36
|
+
for (const cap of this.resource.capabilities) {
|
|
37
|
+
if (!ctx.isCapabilityRegistered(cap)) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Capability "${cap}" is not registered. Declare it as a Kernel.Capability resource.`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const capSchema = ctx.getCapabilitySchema(cap);
|
|
43
|
+
if (capSchema) {
|
|
44
|
+
ctx.validateSchema(this.resource, capSchema);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
ctx.emit("ControllerLoading", { controllers: this.resource.controllers });
|
|
48
|
+
try {
|
|
49
|
+
const controllerInstance = await this.controllerLoader.load(
|
|
50
|
+
this.resource.controllers,
|
|
51
|
+
this.resource.metadata.source,
|
|
52
|
+
);
|
|
53
|
+
ctx.emit("ControllerLoaded", { schema: controllerInstance.schema });
|
|
54
|
+
ctx.registerDefinition(this.resource);
|
|
55
|
+
await ctx.registerController(
|
|
56
|
+
this.resource.metadata.module,
|
|
57
|
+
this.resource.metadata.name,
|
|
58
|
+
controllerInstance,
|
|
59
|
+
);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
ctx.emit("ControllerLoadFailed", { error: (err as Error).message });
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function register(ctx: ControllerContext): void {
|
|
68
|
+
// ResourceDefinition is a passive resource - no registration needed
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function create(resource: any, ctx: ResourceContext): Promise<ResourceDefinition> {
|
|
72
|
+
// Validate incoming resource definition against schema
|
|
73
|
+
if (!validateResourceDefinition(resource)) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Invalid ResourceDefinition "${resource.metadata.name}": ${formatAjvErrors(validateResourceDefinition.errors)}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Return a fully-formed ResourceDefinition instance
|
|
80
|
+
const definition = resource as ResourceDefinitionResource;
|
|
81
|
+
return new ResourceDefinition(definition, new ControllerLoader());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const schema = {
|
|
85
|
+
type: "object",
|
|
86
|
+
additionalProperties: true,
|
|
87
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"properties": {
|
|
4
|
+
"metadata": {
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"name": {
|
|
8
|
+
"type": "string"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"required": ["name"]
|
|
12
|
+
},
|
|
13
|
+
"schema": {
|
|
14
|
+
"type": "object"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"required": ["metadata", "schema"]
|
|
18
|
+
}
|