@telorun/kernel 0.2.4 → 0.2.6
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/base-definition.d.ts +14 -0
- package/dist/base-definition.d.ts.map +1 -0
- package/dist/base-definition.js +17 -0
- package/dist/base-definition.js.map +1 -0
- 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.d.ts +3 -0
- package/dist/capabilities/component.d.ts.map +1 -0
- package/dist/capabilities/component.js +4 -0
- package/dist/capabilities/component.js.map +1 -0
- package/dist/capabilities/component.yaml +1 -1
- package/dist/capabilities/executable.d.ts +3 -0
- package/dist/capabilities/executable.d.ts.map +1 -0
- package/dist/capabilities/executable.js +5 -0
- package/dist/capabilities/executable.js.map +1 -0
- package/dist/capabilities/executable.yaml +1 -1
- package/dist/capabilities/handler.d.ts +3 -0
- package/dist/capabilities/handler.d.ts.map +1 -0
- package/dist/capabilities/handler.js +4 -0
- package/dist/capabilities/handler.js.map +1 -0
- package/dist/capabilities/handler.yaml +1 -1
- package/dist/capabilities/invokable.d.ts +3 -0
- package/dist/capabilities/invokable.d.ts.map +1 -0
- package/dist/capabilities/invokable.js +5 -0
- package/dist/capabilities/invokable.js.map +1 -0
- package/dist/capabilities/listener.d.ts +3 -0
- package/dist/capabilities/listener.d.ts.map +1 -0
- package/dist/capabilities/listener.js +5 -0
- package/dist/capabilities/listener.js.map +1 -0
- package/dist/capabilities/listener.yaml +1 -1
- package/dist/capabilities/mount.d.ts +3 -0
- package/dist/capabilities/mount.d.ts.map +1 -0
- package/dist/capabilities/mount.js +5 -0
- package/dist/capabilities/mount.js.map +1 -0
- package/dist/capabilities/provider.d.ts +3 -0
- package/dist/capabilities/provider.d.ts.map +1 -0
- package/dist/capabilities/provider.js +8 -0
- package/dist/capabilities/provider.js.map +1 -0
- package/dist/capabilities/provider.yaml +1 -1
- package/dist/capabilities/runnable.d.ts +3 -0
- package/dist/capabilities/runnable.d.ts.map +1 -0
- package/dist/capabilities/runnable.js +5 -0
- package/dist/capabilities/runnable.js.map +1 -0
- package/dist/capabilities/service.d.ts +3 -0
- package/dist/capabilities/service.d.ts.map +1 -0
- package/dist/capabilities/service.js +5 -0
- package/dist/capabilities/service.js.map +1 -0
- package/dist/capabilities/template.d.ts +3 -0
- package/dist/capabilities/template.d.ts.map +1 -0
- package/dist/capabilities/template.js +5 -0
- package/dist/capabilities/template.js.map +1 -0
- package/dist/capabilities/template.yaml +1 -1
- package/dist/capabilities/type.d.ts +3 -0
- package/dist/capabilities/type.d.ts.map +1 -0
- package/dist/capabilities/type.js +5 -0
- package/dist/capabilities/type.js.map +1 -0
- 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 +6 -3
- package/dist/controller-loader.js.map +1 -1
- package/dist/controller-registry.d.ts +1 -6
- package/dist/controller-registry.d.ts.map +1 -1
- package/dist/controller-registry.js +11 -22
- package/dist/controller-registry.js.map +1 -1
- package/dist/controllers/capability/capability-controller.d.ts +0 -5
- package/dist/controllers/capability/capability-controller.d.ts.map +1 -1
- package/dist/controllers/capability/capability-controller.js +1 -5
- package/dist/controllers/capability/capability-controller.js.map +1 -1
- package/dist/controllers/module/import-controller.d.ts +35 -0
- package/dist/controllers/module/import-controller.d.ts.map +1 -0
- package/dist/controllers/module/import-controller.js +113 -0
- package/dist/controllers/module/import-controller.js.map +1 -0
- package/dist/controllers/module/module-controller.d.ts +2 -15
- package/dist/controllers/module/module-controller.d.ts.map +1 -1
- package/dist/controllers/module/module-controller.js +9 -90
- package/dist/controllers/module/module-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts +3 -4
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.js +17 -11
- package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-template-controller.d.ts +12 -0
- package/dist/controllers/resource-definition/resource-template-controller.d.ts.map +1 -0
- package/dist/controllers/resource-definition/resource-template-controller.js +112 -0
- package/dist/controllers/resource-definition/resource-template-controller.js.map +1 -0
- 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 +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +31 -42
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +315 -371
- package/dist/kernel.js.map +1 -1
- package/dist/loader.d.ts +11 -10
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +50 -112
- package/dist/loader.js.map +1 -1
- package/dist/manifest-adapters/http-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/http-adapter.js +3 -1
- package/dist/manifest-adapters/http-adapter.js.map +1 -1
- package/dist/manifest-adapters/local-file-adapter.d.ts +7 -5
- package/dist/manifest-adapters/local-file-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/local-file-adapter.js +28 -24
- package/dist/manifest-adapters/local-file-adapter.js.map +1 -1
- package/dist/manifest-adapters/manifest-adapter.d.ts +2 -0
- package/dist/manifest-adapters/manifest-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/registry-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/registry-adapter.js +3 -1
- package/dist/manifest-adapters/registry-adapter.js.map +1 -1
- package/dist/manifest-schemas.d.ts +61 -49
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +58 -37
- 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 +27 -10
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +100 -44
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-valiator.d.ts.map +1 -1
- package/dist/schema-valiator.js +16 -3
- 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 +11 -9
- package/src/boot-context-registry.ts +169 -0
- package/src/controller-loader.ts +299 -0
- package/src/controller-registry.ts +191 -0
- package/src/controllers/module/import-controller.ts +143 -0
- package/src/controllers/module/module-controller.ts +16 -0
- package/src/controllers/resource-definition/resource-definition-controller.ts +86 -0
- package/src/controllers/resource-definition/resource-definition.json +18 -0
- package/src/controllers/resource-definition/resource-template-controller.ts +138 -0
- package/src/event-stream.ts +121 -0
- package/src/events.ts +99 -0
- package/src/index.ts +7 -0
- package/src/kernel.ts +647 -0
- package/src/loader.ts +134 -0
- package/src/manifest-adapters/local-file-adapter.ts +62 -0
- package/src/manifest-adapters/manifest-adapter.ts +35 -0
- package/src/manifest-schemas.ts +85 -0
- package/src/registry.ts +137 -0
- package/src/resource-context.ts +267 -0
- package/src/resource-uri.ts +200 -0
- package/src/schema-valiator.ts +68 -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/src/controllers/module/module.yaml +0 -32
- 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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { DiagnosticSeverity, Loader, StaticAnalyzer } from "@telorun/analyzer";
|
|
2
|
+
import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
3
|
+
import { EvaluationContext, RuntimeError } from "@telorun/sdk";
|
|
4
|
+
import { LocalFileAdapter } from "../../manifest-adapters/local-file-adapter.js";
|
|
5
|
+
|
|
6
|
+
export async function create(resource: any, ctx: ResourceContext): Promise<ResourceInstance> {
|
|
7
|
+
const alias = resource.metadata.name as string;
|
|
8
|
+
const loader = new Loader([new LocalFileAdapter()]);
|
|
9
|
+
|
|
10
|
+
const moduleSource: string = resource.module ?? resource.source;
|
|
11
|
+
|
|
12
|
+
// Validate the imported module and all its transitive imports before loading for runtime.
|
|
13
|
+
// loadManifests() follows Kernel.Import chains so definitions from sub-imports are present,
|
|
14
|
+
// preventing false UNDEFINED_KIND errors for kinds that come from the module's own imports.
|
|
15
|
+
const resolvedUrl = new URL(moduleSource, ctx.moduleContext.source).toString();
|
|
16
|
+
const analysisManifests = await loader.loadManifests(resolvedUrl);
|
|
17
|
+
const diagnostics = new StaticAnalyzer().analyze(analysisManifests);
|
|
18
|
+
const errors = diagnostics.filter((d) => d.severity === DiagnosticSeverity.Error);
|
|
19
|
+
if (errors.length > 0) {
|
|
20
|
+
throw new RuntimeError(
|
|
21
|
+
"ERR_MANIFEST_VALIDATION_FAILED",
|
|
22
|
+
errors.map((d) => d.message).join("\n"),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Load target module manifests for runtime. Inject variables/secrets as compile context so
|
|
27
|
+
// that ${{ variables.x }} / ${{ secrets.y }} templates in the child module resolve correctly.
|
|
28
|
+
// No env — child modules are isolated from host environment.
|
|
29
|
+
const manifests = await loader.loadModule(
|
|
30
|
+
new URL(moduleSource, ctx.moduleContext.source).toString(),
|
|
31
|
+
{
|
|
32
|
+
compile: true,
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
// Find the kind: Module manifest to learn the target module name and contract.
|
|
36
|
+
const moduleManifest = manifests.find((m: any) => m.kind === "Kernel.Module");
|
|
37
|
+
if (!moduleManifest) {
|
|
38
|
+
throw new Error(`No kind: Module manifest found in source "${resource.source as string}"`);
|
|
39
|
+
}
|
|
40
|
+
const targetModule: string = moduleManifest.metadata.name;
|
|
41
|
+
|
|
42
|
+
// Validate required inputs before injecting.
|
|
43
|
+
validateRequiredInputs(moduleManifest.variables ?? {}, resource.variables ?? {}, "variables");
|
|
44
|
+
validateRequiredInputs(moduleManifest.secrets ?? {}, resource.secrets ?? {}, "secrets");
|
|
45
|
+
|
|
46
|
+
// Create child context with the imported variables/secrets baked in, so that
|
|
47
|
+
// ${{ variables.x }} / ${{ secrets.y }} templates resolve correctly at runtime.
|
|
48
|
+
const child = ctx.moduleContext.spawnChild(
|
|
49
|
+
new EvaluationContext(
|
|
50
|
+
ctx.moduleContext.source,
|
|
51
|
+
{
|
|
52
|
+
variables: (resource.variables as Record<string, unknown>) ?? {},
|
|
53
|
+
secrets: (resource.secrets as Record<string, unknown>) ?? {},
|
|
54
|
+
resources: {},
|
|
55
|
+
},
|
|
56
|
+
ctx.moduleContext.createInstance,
|
|
57
|
+
ctx.moduleContext.secretValues,
|
|
58
|
+
ctx.moduleContext.emit,
|
|
59
|
+
),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
for (const manifest of manifests) {
|
|
63
|
+
child.registerManifest(manifest);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Link the target module context as a child of the declaring module context in
|
|
67
|
+
// the lifecycle tree. This enables cascading teardown (parent → child order)
|
|
68
|
+
// and makes the import hierarchy visible at runtime.
|
|
69
|
+
// const declaringCtx: ModuleContext = ctx.getModuleContext(declaringModule);
|
|
70
|
+
// const targetCtx: ModuleContext = (ctx as any).getModuleContext(targetModule);
|
|
71
|
+
// if (!targetCtx.parent) {
|
|
72
|
+
// declaringCtx.spawnChild(targetCtx);
|
|
73
|
+
// }
|
|
74
|
+
|
|
75
|
+
// Try to evaluate the target module's exports.
|
|
76
|
+
// Throws if resources.X is not yet populated — the kernel retry loop catches this and retries.
|
|
77
|
+
// const evaluatedExports: any = child.expand(moduleManifest.exports ?? {});
|
|
78
|
+
|
|
79
|
+
const exportedKinds: string[] = moduleManifest.exports?.kinds ?? [];
|
|
80
|
+
ctx.registerModuleImport(alias, targetModule, exportedKinds);
|
|
81
|
+
// Return a ResourceInstance whose snapshot() surfaces the exported values.
|
|
82
|
+
// The kernel's generic setResource() call stores them under resources.<alias>
|
|
83
|
+
// in the declaring module's evaluation context — no separate imports namespace needed.
|
|
84
|
+
return {
|
|
85
|
+
snapshot: () => ({
|
|
86
|
+
variables: ctx.expandValue(resource.variables, {}) ?? {},
|
|
87
|
+
secrets: ctx.expandValue(resource.secrets, {}) ?? {},
|
|
88
|
+
}),
|
|
89
|
+
run: async () => {
|
|
90
|
+
// Proxy run to target module
|
|
91
|
+
for (const target of (moduleManifest.targets as string[]) ?? []) {
|
|
92
|
+
await child.run(target);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
invoke: async () => {
|
|
96
|
+
// Proxy run to target module
|
|
97
|
+
// for (const target of (moduleManifest.targets as string[]) ?? []) {
|
|
98
|
+
// child.invoke(target);
|
|
99
|
+
// }
|
|
100
|
+
console.log("invoking");
|
|
101
|
+
},
|
|
102
|
+
init: async () => {
|
|
103
|
+
await child.initializeResources();
|
|
104
|
+
},
|
|
105
|
+
teardown: async () => {
|
|
106
|
+
await child.teardownResources();
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function validateRequiredInputs(
|
|
112
|
+
schemaDefs: Record<string, any>,
|
|
113
|
+
provided: Record<string, unknown>,
|
|
114
|
+
kind: "variables" | "secrets",
|
|
115
|
+
): void {
|
|
116
|
+
for (const [key, def] of Object.entries(schemaDefs)) {
|
|
117
|
+
const isRequired = typeof def === "object" && def !== null && !("default" in def);
|
|
118
|
+
if (isRequired && !(key in provided)) {
|
|
119
|
+
throw new Error(`Required ${kind} input "${key}" not provided for module import`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const schema = {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
kind: { type: "string" },
|
|
128
|
+
metadata: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
name: { 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,16 @@
|
|
|
1
|
+
import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
2
|
+
|
|
3
|
+
export async function create(resource: any, ctx: ResourceContext): Promise<ResourceInstance> {
|
|
4
|
+
return {
|
|
5
|
+
run: async () => {
|
|
6
|
+
for (const target of (resource.targets as string[]) ?? []) {
|
|
7
|
+
const [kind, name] = target.split(".");
|
|
8
|
+
if (!kind || !name) {
|
|
9
|
+
throw new Error(`Invalid target format: "${target}". Expected "Kind.Name"`);
|
|
10
|
+
}
|
|
11
|
+
await ctx.invoke(kind, name, {});
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
import { createTemplateController } from "./resource-template-controller.js";
|
|
10
|
+
|
|
11
|
+
type ResourceDefinitionResource = RuntimeResource & {
|
|
12
|
+
kind: "Kernel.Definition";
|
|
13
|
+
metadata: {
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
name: string;
|
|
16
|
+
module?: string;
|
|
17
|
+
};
|
|
18
|
+
schema: Record<string, any>;
|
|
19
|
+
capability?: 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
|
+
if (!this.resource.controllers?.length) {
|
|
37
|
+
const controllerInstance = createTemplateController(this.resource as any);
|
|
38
|
+
ctx.registerDefinition(this.resource);
|
|
39
|
+
await ctx.registerController(
|
|
40
|
+
this.resource.metadata.module,
|
|
41
|
+
this.resource.metadata.name,
|
|
42
|
+
controllerInstance,
|
|
43
|
+
);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
ctx.emit("ControllerLoading", { controllers: this.resource.controllers });
|
|
47
|
+
try {
|
|
48
|
+
const controllerInstance = await this.controllerLoader.load(
|
|
49
|
+
this.resource.controllers,
|
|
50
|
+
this.resource.metadata.source,
|
|
51
|
+
);
|
|
52
|
+
ctx.emit("ControllerLoaded", { schema: controllerInstance.schema });
|
|
53
|
+
ctx.registerDefinition(this.resource);
|
|
54
|
+
await ctx.registerController(
|
|
55
|
+
this.resource.metadata.module,
|
|
56
|
+
this.resource.metadata.name,
|
|
57
|
+
controllerInstance,
|
|
58
|
+
);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
ctx.emit("ControllerLoadFailed", { error: (err as Error).message });
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function register(ctx: ControllerContext): void {
|
|
67
|
+
// ResourceDefinition is a passive resource - no registration needed
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function create(resource: any, ctx: ResourceContext): Promise<ResourceDefinition> {
|
|
71
|
+
// Validate incoming resource definition against schema
|
|
72
|
+
if (!validateResourceDefinition(resource)) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Invalid ResourceDefinition "${resource.metadata.name}": ${formatAjvErrors(validateResourceDefinition.errors)}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Return a fully-formed ResourceDefinition instance
|
|
79
|
+
const definition = resource as unknown as ResourceDefinitionResource;
|
|
80
|
+
return new ResourceDefinition(definition, new ControllerLoader());
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const schema = {
|
|
84
|
+
type: "object",
|
|
85
|
+
additionalProperties: true,
|
|
86
|
+
};
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { ControllerInstance, ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
2
|
+
import { isCompiledValue } from "@telorun/sdk";
|
|
3
|
+
|
|
4
|
+
export function createTemplateController(definition: {
|
|
5
|
+
schema: Record<string, any>;
|
|
6
|
+
resources?: any[];
|
|
7
|
+
invoke?: string | { kind?: string; name: string; inputs?: Record<string, any> };
|
|
8
|
+
run?: string;
|
|
9
|
+
}): ControllerInstance {
|
|
10
|
+
return {
|
|
11
|
+
schema: definition.schema ?? { type: "object", additionalProperties: true },
|
|
12
|
+
|
|
13
|
+
create: async (resource: any, ctx: ResourceContext): Promise<ResourceInstance> => {
|
|
14
|
+
const self = { ...resource, name: resource.metadata.name };
|
|
15
|
+
|
|
16
|
+
// Old string form: invoke is a plain string or a CompiledValue (after precompile).
|
|
17
|
+
// New object form: invoke is a plain object (non-CompiledValue) with at least `name`.
|
|
18
|
+
const objectInvoke =
|
|
19
|
+
definition.invoke !== null &&
|
|
20
|
+
typeof definition.invoke === "object" &&
|
|
21
|
+
!isCompiledValue(definition.invoke)
|
|
22
|
+
? (definition.invoke as { kind?: string; name: string; inputs?: Record<string, any> })
|
|
23
|
+
: null;
|
|
24
|
+
const invokeNameTemplate = objectInvoke ? objectInvoke.name : (definition.invoke ?? null);
|
|
25
|
+
const invokeTarget = invokeNameTemplate
|
|
26
|
+
? (ctx.moduleContext.expandWith(invokeNameTemplate, { self }) as string)
|
|
27
|
+
: null;
|
|
28
|
+
const runTarget = definition.run
|
|
29
|
+
? (ctx.moduleContext.expandWith(definition.run, { self }) as string)
|
|
30
|
+
: null;
|
|
31
|
+
|
|
32
|
+
const persistentManifests: any[] = [];
|
|
33
|
+
let ephemeralTemplate: any = null;
|
|
34
|
+
|
|
35
|
+
for (const template of definition.resources ?? []) {
|
|
36
|
+
const expandedName = ctx.moduleContext.expandWith(template.metadata?.name ?? "", {
|
|
37
|
+
self,
|
|
38
|
+
}) as string;
|
|
39
|
+
const isTarget = expandedName === invokeTarget || expandedName === runTarget;
|
|
40
|
+
if (isTarget) {
|
|
41
|
+
ephemeralTemplate = template;
|
|
42
|
+
} else {
|
|
43
|
+
persistentManifests.push(ctx.moduleContext.expandWith(template, { self }));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const childContext = ctx.spawnChildContext();
|
|
48
|
+
|
|
49
|
+
// Registers an ephemeral manifest on ctx.moduleContext so it shares the same
|
|
50
|
+
// resource scope (and can access connections, etc. via getInstance).
|
|
51
|
+
// Tears down and removes the resource after fn() completes.
|
|
52
|
+
const withEphemeral = async (expandedManifest: any, fn: (name: string) => Promise<any>) => {
|
|
53
|
+
const uniqueName = `${expandedManifest.metadata?.name ?? "eph"}__${Math.random().toString(16).slice(2, 8)}`;
|
|
54
|
+
const manifest = {
|
|
55
|
+
...expandedManifest,
|
|
56
|
+
metadata: {
|
|
57
|
+
...expandedManifest.metadata,
|
|
58
|
+
name: uniqueName,
|
|
59
|
+
module: resource.metadata.module,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
ctx.moduleContext.registerManifest(manifest);
|
|
63
|
+
await ctx.moduleContext.initializeResources();
|
|
64
|
+
const entry = ctx.moduleContext.resourceInstances.get(uniqueName);
|
|
65
|
+
try {
|
|
66
|
+
return await fn(uniqueName);
|
|
67
|
+
} finally {
|
|
68
|
+
if (entry?.instance?.teardown) await entry.instance.teardown();
|
|
69
|
+
ctx.moduleContext.resourceInstances.delete(uniqueName);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
init: async () => {
|
|
75
|
+
for (const m of persistentManifests) childContext.registerManifest(m);
|
|
76
|
+
await childContext.initializeResources();
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
...(invokeTarget && {
|
|
80
|
+
invoke: async (inputs: any) => {
|
|
81
|
+
if (!ephemeralTemplate) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Template '${resource.metadata.name}': no ephemeral resource for invoke target '${invokeTarget}'`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
const extraContext = { self, inputs };
|
|
87
|
+
const expanded = ctx.moduleContext.expandWith(
|
|
88
|
+
ctx.moduleContext.expandWith(ephemeralTemplate, extraContext),
|
|
89
|
+
extraContext,
|
|
90
|
+
) as any;
|
|
91
|
+
return withEphemeral(expanded, async (name) => {
|
|
92
|
+
const entry = ctx.moduleContext.resourceInstances.get(name);
|
|
93
|
+
if (!entry?.instance?.invoke) {
|
|
94
|
+
throw new Error(`Ephemeral resource '${name}' is not invocable`);
|
|
95
|
+
}
|
|
96
|
+
// New object form: expand objectInvoke.inputs with invoke context and pass as arg.
|
|
97
|
+
// Old string form: the manifest inputs were computed during template expansion;
|
|
98
|
+
// pass expanded.inputs so Sql.Exec/Query controllers receive { sql, bindings }.
|
|
99
|
+
const invokeInputs = objectInvoke?.inputs != null
|
|
100
|
+
? ctx.moduleContext.expandWith(
|
|
101
|
+
ctx.moduleContext.expandWith(objectInvoke.inputs, extraContext),
|
|
102
|
+
extraContext,
|
|
103
|
+
)
|
|
104
|
+
: expanded.inputs ?? inputs;
|
|
105
|
+
return entry.instance.invoke(invokeInputs);
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
|
|
110
|
+
...(runTarget && {
|
|
111
|
+
run: async () => {
|
|
112
|
+
if (!ephemeralTemplate) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Template '${resource.metadata.name}': no ephemeral resource for run target '${runTarget}'`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const extraContext = { self };
|
|
118
|
+
const expanded = ctx.moduleContext.expandWith(
|
|
119
|
+
ctx.moduleContext.expandWith(ephemeralTemplate, extraContext),
|
|
120
|
+
extraContext,
|
|
121
|
+
);
|
|
122
|
+
return withEphemeral(expanded, async (name) => {
|
|
123
|
+
const entry = ctx.moduleContext.resourceInstances.get(name);
|
|
124
|
+
if (!entry?.instance?.run) {
|
|
125
|
+
throw new Error(`Ephemeral resource '${name}' is not runnable`);
|
|
126
|
+
}
|
|
127
|
+
return entry.instance.run();
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
}),
|
|
131
|
+
|
|
132
|
+
teardown: async () => {
|
|
133
|
+
await childContext.teardownResources();
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Streaming event log for debugging and testing
|
|
6
|
+
* Records all events in JSONL format (one JSON object per line)
|
|
7
|
+
* Useful for observing runtime execution, testing, and debugging
|
|
8
|
+
*/
|
|
9
|
+
export class EventStream {
|
|
10
|
+
private filePath: string;
|
|
11
|
+
private isEnabled: boolean = false;
|
|
12
|
+
|
|
13
|
+
constructor(filePath?: string) {
|
|
14
|
+
this.filePath = filePath || '';
|
|
15
|
+
this.isEnabled = !!filePath;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Enable event streaming to a file
|
|
20
|
+
*/
|
|
21
|
+
async enable(filePath: string): Promise<void> {
|
|
22
|
+
this.filePath = filePath;
|
|
23
|
+
this.isEnabled = true;
|
|
24
|
+
|
|
25
|
+
// Ensure directory exists
|
|
26
|
+
const dir = path.dirname(filePath);
|
|
27
|
+
if (dir !== '.' && dir !== '') {
|
|
28
|
+
await fs.mkdir(dir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Initialize file (truncate if exists)
|
|
32
|
+
await fs.writeFile(filePath, '', 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Log an event to the stream
|
|
37
|
+
*/
|
|
38
|
+
async log(
|
|
39
|
+
event: string,
|
|
40
|
+
payload?: any,
|
|
41
|
+
metadata?: {
|
|
42
|
+
namespace?: string;
|
|
43
|
+
resource?: string;
|
|
44
|
+
kind?: string;
|
|
45
|
+
name?: string;
|
|
46
|
+
},
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
if (!this.isEnabled) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const logEntry = {
|
|
53
|
+
timestamp: new Date().toISOString(),
|
|
54
|
+
event,
|
|
55
|
+
payload,
|
|
56
|
+
...metadata,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const line = JSON.stringify(logEntry) + '\n';
|
|
61
|
+
await fs.appendFile(this.filePath, line, 'utf-8');
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Failed to write event to stream:', error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Read all events from the stream as an array
|
|
69
|
+
* Useful for testing
|
|
70
|
+
*/
|
|
71
|
+
async readAll(): Promise<Array<Record<string, any>>> {
|
|
72
|
+
if (!this.isEnabled || !this.filePath) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const content = await fs.readFile(this.filePath, 'utf-8');
|
|
78
|
+
if (!content.trim()) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
return content
|
|
82
|
+
.trim()
|
|
83
|
+
.split('\n')
|
|
84
|
+
.map((line) => JSON.parse(line));
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('Failed to read event stream:', error);
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Filter events by type
|
|
93
|
+
*/
|
|
94
|
+
async getEventsByType(
|
|
95
|
+
eventType: string,
|
|
96
|
+
): Promise<Array<Record<string, any>>> {
|
|
97
|
+
const all = await this.readAll();
|
|
98
|
+
return all.filter((e) => e.event === eventType);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the file path for the event stream
|
|
103
|
+
*/
|
|
104
|
+
getFilePath(): string {
|
|
105
|
+
return this.filePath;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if event streaming is enabled
|
|
110
|
+
*/
|
|
111
|
+
isEnabledStream(): boolean {
|
|
112
|
+
return this.isEnabled;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Disable event streaming
|
|
117
|
+
*/
|
|
118
|
+
disable(): void {
|
|
119
|
+
this.isEnabled = false;
|
|
120
|
+
}
|
|
121
|
+
}
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { RuntimeEvent } from "@telorun/sdk";
|
|
2
|
+
|
|
3
|
+
type EventHandler = (payload?: any) => void | Promise<void>;
|
|
4
|
+
|
|
5
|
+
export class EventBus {
|
|
6
|
+
private handlers: Map<string, Set<EventHandler>> = new Map();
|
|
7
|
+
|
|
8
|
+
private validateEventName(event: string): void {
|
|
9
|
+
if (
|
|
10
|
+
event === "" ||
|
|
11
|
+
!/^(\*|[A-Za-z_][A-Za-z0-9_]*)(\.(\*|[A-Za-z_][A-Za-z0-9_]*))*$/.test(event)
|
|
12
|
+
) {
|
|
13
|
+
throw new Error(`Invalid event name "${event}". Expected format "Text.Text" with no spaces.`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private matchesPattern(pattern: string, event: string): boolean {
|
|
18
|
+
if (pattern === "*") {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (pattern === event) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (!pattern.includes("*")) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const patternParts = pattern.split(".");
|
|
28
|
+
const eventParts = event.split(".");
|
|
29
|
+
if (patternParts.length !== eventParts.length) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
for (let i = 0; i < patternParts.length; i += 1) {
|
|
33
|
+
const part = patternParts[i];
|
|
34
|
+
if (part === "*") {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (part !== eventParts[i]) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
on(event: string, handler: EventHandler): void {
|
|
45
|
+
this.validateEventName(event);
|
|
46
|
+
const set = this.handlers.get(event) || new Set();
|
|
47
|
+
set.add(handler);
|
|
48
|
+
this.handlers.set(event, set);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
once(event: string, handler: EventHandler): void {
|
|
52
|
+
this.validateEventName(event);
|
|
53
|
+
const wrapper: EventHandler = async (payload?: any) => {
|
|
54
|
+
this.off(event, wrapper);
|
|
55
|
+
await handler(payload);
|
|
56
|
+
};
|
|
57
|
+
this.on(event, wrapper);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
off(event: string, handler: EventHandler): void {
|
|
61
|
+
this.validateEventName(event);
|
|
62
|
+
const set = this.handlers.get(event);
|
|
63
|
+
if (!set) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
set.delete(handler);
|
|
67
|
+
if (set.size === 0) {
|
|
68
|
+
this.handlers.delete(event);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async emit(event: string, payload?: any, metadata?: any): Promise<void> {
|
|
73
|
+
const handlers: EventHandler[] = [];
|
|
74
|
+
for (const [pattern, set] of this.handlers.entries()) {
|
|
75
|
+
if (!this.matchesPattern(pattern, event)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
for (const handler of set) {
|
|
79
|
+
handlers.push(handler);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (handlers.length === 0) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const evt: RuntimeEvent = { name: event, payload, metadata };
|
|
86
|
+
for (const handler of handlers) {
|
|
87
|
+
await handler(evt);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
hasHandlers(event: string): boolean {
|
|
92
|
+
for (const [pattern, set] of this.handlers.entries()) {
|
|
93
|
+
if (set.size > 0 && this.matchesPattern(pattern, event)) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ControllerRegistry } from "./controller-registry.js";
|
|
2
|
+
export { EventStream } from "./event-stream.js";
|
|
3
|
+
export { Kernel } from "./kernel.js";
|
|
4
|
+
export { ManifestRegistry as Registry } from "./registry.js";
|
|
5
|
+
export { ResourceURI } from "./resource-uri.js";
|
|
6
|
+
export type { RuntimeDiagnostic } from "@telorun/sdk";
|
|
7
|
+
|