@telorun/kernel 0.11.1 → 0.13.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/LICENSE +2 -2
- package/dist/application-env.d.ts +24 -0
- package/dist/application-env.d.ts.map +1 -0
- package/dist/application-env.js +156 -0
- package/dist/application-env.js.map +1 -0
- 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/capabilities/capabilities/component.yaml +4 -0
- package/dist/capabilities/capabilities/executable.yaml +8 -0
- package/dist/capabilities/capabilities/handler.yaml +4 -0
- package/dist/capabilities/capabilities/listener.yaml +4 -0
- package/dist/capabilities/capabilities/provider.yaml +4 -0
- package/dist/capabilities/capabilities/template.yaml +4 -0
- package/dist/capabilities/capabilities/type.yaml +4 -0
- 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 +3 -0
- 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 +7 -0
- 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 +3 -0
- 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 +3 -0
- 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 +3 -0
- 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 +3 -0
- 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 +3 -0
- package/dist/controller-loaders/npm-loader.d.ts +32 -8
- package/dist/controller-loaders/npm-loader.d.ts.map +1 -1
- package/dist/controller-loaders/npm-loader.js +120 -118
- package/dist/controller-loaders/npm-loader.js.map +1 -1
- package/dist/controllers/capability/capability-controller.d.ts +32 -0
- package/dist/controllers/capability/capability-controller.d.ts.map +1 -0
- package/dist/controllers/capability/capability-controller.js +26 -0
- package/dist/controllers/capability/capability-controller.js.map +1 -0
- package/dist/controllers/module/import-controller.d.ts +3 -2
- package/dist/controllers/module/import-controller.d.ts.map +1 -1
- package/dist/controllers/module/import-controller.js +23 -25
- package/dist/controllers/module/import-controller.js.map +1 -1
- package/dist/controllers/module/module.json +48 -0
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts +1 -0
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.js +3 -0
- package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-template-controller.d.ts +6 -1
- package/dist/controllers/resource-definition/resource-template-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-template-controller.js +79 -13
- package/dist/controllers/resource-definition/resource-template-controller.js.map +1 -1
- package/dist/internal-context.d.ts +25 -0
- package/dist/internal-context.d.ts.map +1 -0
- package/dist/internal-context.js +2 -0
- package/dist/internal-context.js.map +1 -0
- package/dist/kernel.d.ts +21 -1
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +109 -5
- package/dist/kernel.js.map +1 -1
- package/dist/loader.d.ts +18 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +127 -0
- package/dist/loader.js.map +1 -0
- package/dist/manifest-adapters/http-adapter.d.ts +8 -0
- package/dist/manifest-adapters/http-adapter.d.ts.map +1 -0
- package/dist/manifest-adapters/http-adapter.js +31 -0
- package/dist/manifest-adapters/http-adapter.js.map +1 -0
- package/dist/manifest-adapters/local-file-adapter.d.ts +15 -0
- package/dist/manifest-adapters/local-file-adapter.d.ts.map +1 -0
- package/dist/manifest-adapters/local-file-adapter.js +95 -0
- package/dist/manifest-adapters/local-file-adapter.js.map +1 -0
- package/dist/manifest-adapters/manifest-adapter.d.ts +35 -0
- package/dist/manifest-adapters/manifest-adapter.d.ts.map +1 -0
- package/dist/manifest-adapters/manifest-adapter.js +2 -0
- package/dist/manifest-adapters/manifest-adapter.js.map +1 -0
- package/dist/manifest-adapters/registry-adapter.d.ts +9 -0
- package/dist/manifest-adapters/registry-adapter.d.ts.map +1 -0
- package/dist/manifest-adapters/registry-adapter.js +48 -0
- package/dist/manifest-adapters/registry-adapter.js.map +1 -0
- package/dist/manifest-schemas.d.ts +7 -23
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +18 -8
- package/dist/manifest-schemas.js.map +1 -1
- package/dist/manifest-sources/analysis-stamp.d.ts +25 -0
- package/dist/manifest-sources/analysis-stamp.d.ts.map +1 -0
- package/dist/manifest-sources/analysis-stamp.js +151 -0
- package/dist/manifest-sources/analysis-stamp.js.map +1 -0
- 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/resource-context.d.ts +2 -0
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +28 -0
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-valiator.d.ts +15 -0
- package/dist/schema-valiator.d.ts.map +1 -0
- package/dist/schema-valiator.js +127 -0
- package/dist/schema-valiator.js.map +1 -0
- package/dist/schema-validator.d.ts +28 -0
- package/dist/schema-validator.d.ts.map +1 -1
- package/dist/schema-validator.js +161 -1
- package/dist/schema-validator.js.map +1 -1
- package/dist/snapshot-serializer.d.ts +62 -0
- package/dist/snapshot-serializer.d.ts.map +1 -0
- package/dist/snapshot-serializer.js +164 -0
- package/dist/snapshot-serializer.js.map +1 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +9 -6
- package/src/application-env.ts +216 -0
- package/src/controller-loaders/npm-loader.ts +133 -118
- package/src/controllers/module/import-controller.ts +33 -36
- package/src/controllers/resource-definition/resource-definition-controller.ts +6 -0
- package/src/controllers/resource-definition/resource-template-controller.ts +110 -16
- package/src/internal-context.ts +25 -0
- package/src/kernel.ts +130 -5
- package/src/manifest-schemas.ts +31 -11
- package/src/manifest-sources/analysis-stamp.ts +169 -0
- package/src/resource-context.ts +34 -0
- package/src/schema-validator.ts +178 -2
- package/dist/generated/runtime-deps.json +0 -6
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as YAML from "js-yaml";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
/**
|
|
5
|
+
* Serializes runtime state into YAML snapshots
|
|
6
|
+
* Captures resource definitions, instances, and custom state
|
|
7
|
+
* Recursively includes nested resources based on generationDepth
|
|
8
|
+
*/
|
|
9
|
+
export class SnapshotSerializer {
|
|
10
|
+
/**
|
|
11
|
+
* Take a snapshot of runtime state
|
|
12
|
+
* @param resources Map of resources organized by kind
|
|
13
|
+
* @param resourceInstances Map of resource instances to include custom snapshots
|
|
14
|
+
* @param filePath Optional file path to write snapshot to
|
|
15
|
+
* @returns Snapshot data object
|
|
16
|
+
*/
|
|
17
|
+
async takeSnapshot(resources, resourceInstances, filePath) {
|
|
18
|
+
const snapshot = {
|
|
19
|
+
timestamp: new Date().toISOString(),
|
|
20
|
+
resources: [],
|
|
21
|
+
};
|
|
22
|
+
// Get all resources organized by generation depth (0 = first level)
|
|
23
|
+
// Start with depth 0 (directly loaded resources) and recursively include nested ones
|
|
24
|
+
const resourcesByDepth = this.groupByGenerationDepth(resources);
|
|
25
|
+
// Process resources starting from depth 0
|
|
26
|
+
for (const depth of Array.from(resourcesByDepth.keys()).sort((a, b) => a - b)) {
|
|
27
|
+
const resourcesAtDepth = resourcesByDepth.get(depth) || [];
|
|
28
|
+
for (const resource of resourcesAtDepth) {
|
|
29
|
+
const resourceEntry = await this.serializeResource(resource, resourceInstances);
|
|
30
|
+
snapshot.resources.push(resourceEntry);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Write to file if path provided
|
|
34
|
+
if (filePath) {
|
|
35
|
+
await this.writeSnapshotToFile(snapshot, filePath);
|
|
36
|
+
}
|
|
37
|
+
return snapshot;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Serialize a single resource
|
|
41
|
+
*/
|
|
42
|
+
async serializeResource(resource, resourceInstances) {
|
|
43
|
+
const { kind, metadata, ...data } = resource;
|
|
44
|
+
const { name } = metadata;
|
|
45
|
+
const resourceEntry = {
|
|
46
|
+
kind,
|
|
47
|
+
name,
|
|
48
|
+
metadata: this.serializeMetadata(metadata),
|
|
49
|
+
data: this.serializeData(data),
|
|
50
|
+
};
|
|
51
|
+
// Include custom snapshot if resource instance has snapshot() method
|
|
52
|
+
if (resourceInstances) {
|
|
53
|
+
const key = this.getResourceKey(kind, name);
|
|
54
|
+
const instanceData = resourceInstances.get(key);
|
|
55
|
+
if (instanceData && instanceData.instance) {
|
|
56
|
+
const snapshotData = await this.getInstanceSnapshot(instanceData.instance);
|
|
57
|
+
if (snapshotData) {
|
|
58
|
+
resourceEntry.snapshot = snapshotData;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return resourceEntry;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get snapshot from resource instance if it implements snapshot() method
|
|
66
|
+
*/
|
|
67
|
+
async getInstanceSnapshot(instance) {
|
|
68
|
+
const instanceAny = instance;
|
|
69
|
+
// Check if snapshot method exists
|
|
70
|
+
if (typeof instanceAny.snapshot === "function") {
|
|
71
|
+
try {
|
|
72
|
+
const result = await Promise.resolve(instanceAny.snapshot(instance));
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error("Error calling snapshot() on resource instance:", error);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Serialize metadata, filtering out circular references and functions
|
|
84
|
+
*/
|
|
85
|
+
serializeMetadata(metadata) {
|
|
86
|
+
const serialized = {};
|
|
87
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
88
|
+
if (typeof value === "function") {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
serialized[key] = JSON.parse(JSON.stringify(value));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Skip non-serializable values
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return serialized;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Serialize resource data, filtering out circular references and functions
|
|
102
|
+
*/
|
|
103
|
+
serializeData(data) {
|
|
104
|
+
const serialized = {};
|
|
105
|
+
for (const [key, value] of Object.entries(data)) {
|
|
106
|
+
if (typeof value === "function") {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
serialized[key] = JSON.parse(JSON.stringify(value));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Skip non-serializable values
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return serialized;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Group resources by generation depth for hierarchical ordering
|
|
120
|
+
*/
|
|
121
|
+
groupByGenerationDepth(resources) {
|
|
122
|
+
const grouped = new Map();
|
|
123
|
+
for (const kindMap of resources.values()) {
|
|
124
|
+
for (const resource of kindMap.values()) {
|
|
125
|
+
const depth = resource.metadata.generationDepth ?? 0;
|
|
126
|
+
if (!grouped.has(depth)) {
|
|
127
|
+
grouped.set(depth, []);
|
|
128
|
+
}
|
|
129
|
+
grouped.get(depth).push(resource);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return grouped;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Write snapshot to YAML file
|
|
136
|
+
*/
|
|
137
|
+
async writeSnapshotToFile(snapshot, filePath) {
|
|
138
|
+
// Ensure directory exists
|
|
139
|
+
const dir = path.dirname(filePath);
|
|
140
|
+
if (dir !== "." && dir !== "") {
|
|
141
|
+
await fs.mkdir(dir, { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
// Convert to YAML
|
|
144
|
+
const yaml = YAML.dump(snapshot, {
|
|
145
|
+
indent: 2,
|
|
146
|
+
lineWidth: 0,
|
|
147
|
+
});
|
|
148
|
+
await fs.writeFile(filePath, yaml, "utf-8");
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Generate resource key for instance lookup
|
|
152
|
+
*/
|
|
153
|
+
getResourceKey(kind, name) {
|
|
154
|
+
return `${kind}:${name}`;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Load snapshot from YAML file
|
|
158
|
+
*/
|
|
159
|
+
async loadSnapshotFromFile(filePath) {
|
|
160
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
161
|
+
return YAML.load(content);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=snapshot-serializer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-serializer.js","sourceRoot":"","sources":["../src/snapshot-serializer.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ControllerContext, ResourceContext, ResourceInstance, ResourceManifest, RuntimeErrorCode, RuntimeResource } from "@telorun/sdk";
|
|
2
|
+
import type { ModuleContext } from "./module-context.js";
|
|
3
|
+
export type { ControllerContext, ResourceContext, ResourceInstance, ResourceManifest } from "@telorun/sdk";
|
|
4
|
+
export interface KernelContext {
|
|
5
|
+
kernel: Kernel;
|
|
6
|
+
}
|
|
7
|
+
export interface ExecContext {
|
|
8
|
+
execute(urn: string, input: any): Promise<any>;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
export type ResourceCapability = string;
|
|
12
|
+
export interface ResourceDefinition {
|
|
13
|
+
kind: string;
|
|
14
|
+
metadata: {
|
|
15
|
+
name: string;
|
|
16
|
+
module: string;
|
|
17
|
+
};
|
|
18
|
+
schema: Record<string, any>;
|
|
19
|
+
capabilities: ResourceCapability[];
|
|
20
|
+
events?: string[];
|
|
21
|
+
controllers?: Array<{
|
|
22
|
+
runtime: string;
|
|
23
|
+
entry: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Controller definition for a resource kind.
|
|
28
|
+
* Maps a fully-qualified resource kind to its controller implementation for a specific runtime.
|
|
29
|
+
*/
|
|
30
|
+
export interface ControllerDefinition {
|
|
31
|
+
kind: string;
|
|
32
|
+
runtime: string;
|
|
33
|
+
entry: string;
|
|
34
|
+
controller?: any;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Controller instance - runtime representation of a controller that handles resource instances.
|
|
38
|
+
*/
|
|
39
|
+
export interface ControllerInstance {
|
|
40
|
+
execute?(name: string, inputs: any, ctx: ExecContext): Promise<any>;
|
|
41
|
+
compile?(resource: ResourceManifest, ctx: ResourceContext): RuntimeResource | Promise<RuntimeResource>;
|
|
42
|
+
register?(ctx: ControllerContext): void | Promise<void>;
|
|
43
|
+
create?(resource: ResourceManifest, ctx: ResourceContext): ResourceInstance | null | Promise<ResourceInstance | null>;
|
|
44
|
+
schema: any;
|
|
45
|
+
}
|
|
46
|
+
export interface Kernel {
|
|
47
|
+
loadFromConfig(runtimeYamlPath: string): Promise<void>;
|
|
48
|
+
start(): Promise<void>;
|
|
49
|
+
acquireHold(reason?: string): () => void;
|
|
50
|
+
waitForIdle(): Promise<void>;
|
|
51
|
+
requestExit(code: number): void;
|
|
52
|
+
readonly exitCode: number;
|
|
53
|
+
teardownResource(module: string, kind: string, name: string): Promise<void>;
|
|
54
|
+
registerChildManifest(parentKey: string, resource: ResourceManifest): void;
|
|
55
|
+
getSourceFiles(): string[];
|
|
56
|
+
reloadSource(sourcePath: string): Promise<void>;
|
|
57
|
+
shutdown(): void;
|
|
58
|
+
registerModuleContext(moduleName: string, variables: Record<string, unknown>, secrets: Record<string, unknown>): void;
|
|
59
|
+
getModuleContext(moduleName: string): ModuleContext;
|
|
60
|
+
}
|
|
61
|
+
export declare class RuntimeError extends Error {
|
|
62
|
+
code: RuntimeErrorCode;
|
|
63
|
+
constructor(code: RuntimeErrorCode, message: string);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA0FA,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,YACS,IAAsB,EAC7B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHR,SAAI,GAAJ,IAAI,CAAkB;QAI7B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/kernel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Telo Runtime - A lightweight, polyglot execution host.",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"
|
|
6
|
+
"telo",
|
|
7
7
|
"runtime",
|
|
8
8
|
"execution",
|
|
9
9
|
"manifest"
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@marcbachmann/cel-js": "^7.5.3",
|
|
49
49
|
"@sinclair/typebox": "^0.34.48",
|
|
50
|
-
"@telorun/analyzer": "0.
|
|
51
|
-
"@telorun/
|
|
50
|
+
"@telorun/analyzer": "0.12.0",
|
|
51
|
+
"@telorun/templating": "0.3.0",
|
|
52
52
|
"ajv": "^8.17.1",
|
|
53
53
|
"ajv-formats": "^3.0.1",
|
|
54
54
|
"minimatch": "^10.2.5",
|
|
@@ -60,11 +60,14 @@
|
|
|
60
60
|
"typescript": "^5.0.0",
|
|
61
61
|
"vitest": "^2.1.8"
|
|
62
62
|
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"@telorun/sdk": "0.12.0"
|
|
65
|
+
},
|
|
63
66
|
"overrides": {
|
|
64
67
|
"@marcbachmann/cel-js": "$@marcbachmann/cel-js",
|
|
65
68
|
"@sinclair/typebox": "$@sinclair/typebox",
|
|
66
69
|
"@telorun/analyzer": "$@telorun/analyzer",
|
|
67
|
-
"@telorun/
|
|
70
|
+
"@telorun/templating": "$@telorun/templating",
|
|
68
71
|
"ajv": "$ajv",
|
|
69
72
|
"ajv-formats": "$ajv-formats",
|
|
70
73
|
"minimatch": "$minimatch",
|
|
@@ -72,7 +75,7 @@
|
|
|
72
75
|
"yaml": "$yaml"
|
|
73
76
|
},
|
|
74
77
|
"scripts": {
|
|
75
|
-
"build": "tsc
|
|
78
|
+
"build": "tsc",
|
|
76
79
|
"dev": "tsc --watch",
|
|
77
80
|
"test": "vitest run",
|
|
78
81
|
"test:watch": "vitest"
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { residualEntrySchema } from "@telorun/analyzer";
|
|
2
|
+
import { RuntimeError } from "@telorun/sdk";
|
|
3
|
+
import { SchemaValidator } from "./schema-validator.js";
|
|
4
|
+
|
|
5
|
+
type EntryType = "string" | "integer" | "number" | "boolean" | "object" | "array";
|
|
6
|
+
|
|
7
|
+
interface EnvEntry {
|
|
8
|
+
env: string;
|
|
9
|
+
type: EntryType;
|
|
10
|
+
default?: unknown;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface EnvResolutionResult {
|
|
15
|
+
variables: Record<string, unknown>;
|
|
16
|
+
secrets: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Populate the root Application's `variables` / `secrets` namespaces from
|
|
21
|
+
* host environment variables, per the per-field `env:` mapping declared on
|
|
22
|
+
* each entry.
|
|
23
|
+
*
|
|
24
|
+
* Implements the polyglot env-resolution spec from
|
|
25
|
+
* kernel/nodejs/plans/application-env-variables.md: read the env var, coerce
|
|
26
|
+
* per `entry.type`, validate the coerced value (or the declared default, when
|
|
27
|
+
* the env var is unset) against the entry's residual schema, and aggregate
|
|
28
|
+
* every failure into a single `ERR_MANIFEST_VALIDATION_FAILED` error so all
|
|
29
|
+
* problems surface before any controller initializes.
|
|
30
|
+
*
|
|
31
|
+
* This must run BEFORE any Telo.Import controller initializes — imports may
|
|
32
|
+
* pass `${{ variables.X }}` as their `variables:` inputs, so the root scope
|
|
33
|
+
* has to be populated by the time the import controller evaluates those
|
|
34
|
+
* expressions.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveApplicationEnv(
|
|
37
|
+
manifest: Record<string, any>,
|
|
38
|
+
env: Record<string, string | undefined>,
|
|
39
|
+
validator: SchemaValidator,
|
|
40
|
+
): EnvResolutionResult {
|
|
41
|
+
const errors: string[] = [];
|
|
42
|
+
const variables = resolveBlock(
|
|
43
|
+
manifest.variables ?? {},
|
|
44
|
+
env,
|
|
45
|
+
validator,
|
|
46
|
+
errors,
|
|
47
|
+
false,
|
|
48
|
+
);
|
|
49
|
+
const secrets = resolveBlock(
|
|
50
|
+
manifest.secrets ?? {},
|
|
51
|
+
env,
|
|
52
|
+
validator,
|
|
53
|
+
errors,
|
|
54
|
+
true,
|
|
55
|
+
);
|
|
56
|
+
if (errors.length > 0) {
|
|
57
|
+
throw new RuntimeError(
|
|
58
|
+
"ERR_MANIFEST_VALIDATION_FAILED",
|
|
59
|
+
`Application environment validation failed:\n` +
|
|
60
|
+
errors.map((e) => ` - ${e}`).join("\n"),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return { variables, secrets };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveBlock(
|
|
67
|
+
block: Record<string, EnvEntry> | unknown,
|
|
68
|
+
env: Record<string, string | undefined>,
|
|
69
|
+
validator: SchemaValidator,
|
|
70
|
+
errors: string[],
|
|
71
|
+
isSecret: boolean,
|
|
72
|
+
): Record<string, unknown> {
|
|
73
|
+
const out: Record<string, unknown> = {};
|
|
74
|
+
if (!block || typeof block !== "object" || Array.isArray(block)) {
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
for (const [name, entry] of Object.entries(block as Record<string, EnvEntry>)) {
|
|
78
|
+
if (!entry || typeof entry !== "object") continue;
|
|
79
|
+
const envKey = entry.env;
|
|
80
|
+
const raw = env[envKey];
|
|
81
|
+
const residual = residualEntrySchema(entry as Record<string, unknown>);
|
|
82
|
+
|
|
83
|
+
if (raw === undefined || raw === null) {
|
|
84
|
+
if (entry.default !== undefined) {
|
|
85
|
+
const validation = validateResidual(entry.default, residual, validator);
|
|
86
|
+
if (validation) {
|
|
87
|
+
errors.push(`${name}: ${validation}`);
|
|
88
|
+
} else {
|
|
89
|
+
out[name] = entry.default;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
errors.push(`${name}: environment variable ${envKey} is not set (no default)`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let coerced: unknown;
|
|
98
|
+
try {
|
|
99
|
+
coerced = coerce(raw, entry.type, envKey, isSecret);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
errors.push(`${name}: ${(e as Error).message}`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const validation = validateResidual(coerced, residual, validator);
|
|
106
|
+
if (validation) {
|
|
107
|
+
errors.push(`${name}: ${validation}`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
out[name] = coerced;
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Render a raw env value for inclusion in an error message. Secret values
|
|
117
|
+
* are masked so coercion / schema diagnostics don't leak secret material
|
|
118
|
+
* into logs (the env-var name and the failure reason still surface). */
|
|
119
|
+
function renderRawForError(raw: string, isSecret: boolean): string {
|
|
120
|
+
return isSecret ? "<redacted>" : `"${raw}"`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function coerce(
|
|
124
|
+
raw: string,
|
|
125
|
+
type: EntryType,
|
|
126
|
+
envKey: string,
|
|
127
|
+
isSecret: boolean,
|
|
128
|
+
): unknown {
|
|
129
|
+
switch (type) {
|
|
130
|
+
case "string":
|
|
131
|
+
return raw;
|
|
132
|
+
case "integer": {
|
|
133
|
+
const trimmed = raw.trim();
|
|
134
|
+
if (!/^-?\d+$/.test(trimmed)) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`environment variable ${envKey}: value ${renderRawForError(raw, isSecret)} is not a valid integer`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return parseInt(trimmed, 10);
|
|
140
|
+
}
|
|
141
|
+
case "number": {
|
|
142
|
+
const n = parseFloat(raw);
|
|
143
|
+
if (Number.isNaN(n)) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`environment variable ${envKey}: value ${renderRawForError(raw, isSecret)} is not a valid number`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return n;
|
|
149
|
+
}
|
|
150
|
+
case "boolean":
|
|
151
|
+
if (raw === "true") return true;
|
|
152
|
+
if (raw === "false") return false;
|
|
153
|
+
throw new Error(
|
|
154
|
+
`environment variable ${envKey}: value ${renderRawForError(raw, isSecret)} is not a valid boolean (expected "true" or "false")`,
|
|
155
|
+
);
|
|
156
|
+
case "object": {
|
|
157
|
+
const parsed = parseJson(raw, envKey, isSecret);
|
|
158
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`environment variable ${envKey}: expected JSON object, got ${describeJsonType(parsed)}`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return parsed;
|
|
164
|
+
}
|
|
165
|
+
case "array": {
|
|
166
|
+
const parsed = parseJson(raw, envKey, isSecret);
|
|
167
|
+
if (!Array.isArray(parsed)) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`environment variable ${envKey}: expected JSON array, got ${describeJsonType(parsed)}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return parsed;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function parseJson(raw: string, envKey: string, isSecret: boolean): unknown {
|
|
178
|
+
try {
|
|
179
|
+
return JSON.parse(raw);
|
|
180
|
+
} catch (e) {
|
|
181
|
+
// Node's JSON.parse error embeds the offending character / position; for
|
|
182
|
+
// secrets, swallow the parser detail and surface only the env var name.
|
|
183
|
+
const detail = isSecret ? "value is not valid JSON" : (e as Error).message;
|
|
184
|
+
throw new Error(`environment variable ${envKey}: ${isSecret ? detail : `value is not valid JSON: ${detail}`}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function describeJsonType(value: unknown): string {
|
|
189
|
+
if (value === null) return "null";
|
|
190
|
+
if (Array.isArray(value)) return "array";
|
|
191
|
+
return typeof value;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function validateResidual(
|
|
195
|
+
value: unknown,
|
|
196
|
+
residual: Record<string, unknown>,
|
|
197
|
+
validator: SchemaValidator,
|
|
198
|
+
): string | null {
|
|
199
|
+
try {
|
|
200
|
+
validator.compile(residual as any).validate(value);
|
|
201
|
+
return null;
|
|
202
|
+
} catch (e) {
|
|
203
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
204
|
+
// Strip SchemaValidator's "Invalid value passed: <JSON>. Error: " prefix
|
|
205
|
+
// so the JSON-stringified value (which can be secret material for entries
|
|
206
|
+
// under `secrets:`) never reaches the caller. The split is anchored on
|
|
207
|
+
// the literal ". Error: " delimiter — a `[^.]*` regex would have leaked
|
|
208
|
+
// any value containing a dot (URLs, versions, paths).
|
|
209
|
+
const sentinel = ". Error: ";
|
|
210
|
+
const idx = msg.indexOf(sentinel);
|
|
211
|
+
if (msg.startsWith("Invalid value passed:") && idx !== -1) {
|
|
212
|
+
return msg.slice(idx + sentinel.length);
|
|
213
|
+
}
|
|
214
|
+
return msg;
|
|
215
|
+
}
|
|
216
|
+
}
|