@telorun/kernel 0.2.7 → 0.2.8
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/README.md +100 -0
- package/dist/controllers/module/import-controller.d.ts.map +1 -1
- package/dist/controllers/module/import-controller.js +18 -9
- package/dist/controllers/module/import-controller.js.map +1 -1
- package/dist/evaluation-context.d.ts +76 -38
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +254 -89
- package/dist/evaluation-context.js.map +1 -1
- package/dist/execution-context.d.ts +1 -1
- package/dist/execution-context.d.ts.map +1 -1
- package/dist/execution-context.js +1 -1
- package/dist/execution-context.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +7 -1
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +54 -11
- package/dist/kernel.js.map +1 -1
- package/dist/manifest-adapters/local-file-adapter.d.ts +1 -1
- package/dist/manifest-adapters/local-file-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/local-file-adapter.js +2 -1
- package/dist/manifest-adapters/local-file-adapter.js.map +1 -1
- package/dist/manifest-adapters/manifest-adapter.d.ts +1 -1
- package/dist/module-context.d.ts +28 -5
- package/dist/module-context.d.ts.map +1 -1
- package/dist/module-context.js +107 -4
- package/dist/module-context.js.map +1 -1
- package/dist/resource-context.d.ts +6 -6
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +5 -4
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-valiator.d.ts +1 -0
- package/dist/schema-valiator.d.ts.map +1 -1
- package/dist/schema-valiator.js +11 -1
- package/dist/schema-valiator.js.map +1 -1
- package/dist/schema-validator.d.ts +15 -0
- package/dist/schema-validator.d.ts.map +1 -0
- package/dist/schema-validator.js +127 -0
- package/dist/schema-validator.js.map +1 -0
- package/package.json +21 -10
- package/src/controllers/module/import-controller.ts +27 -11
- package/src/evaluation-context.ts +490 -0
- package/src/execution-context.ts +21 -0
- package/src/index.ts +3 -0
- package/src/kernel.ts +70 -13
- package/src/manifest-adapters/local-file-adapter.ts +2 -2
- package/src/manifest-adapters/manifest-adapter.ts +1 -1
- package/src/module-context.ts +211 -0
- package/src/resource-context.ts +8 -7
- package/src/{schema-valiator.ts → schema-validator.ts} +13 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { Invocable, ModuleContext as IModuleContext } from "@telorun/sdk";
|
|
2
|
+
import type { EmitEvent, InstanceFactory } from "@telorun/sdk";
|
|
3
|
+
import { EvaluationContext } from "./evaluation-context.js";
|
|
4
|
+
|
|
5
|
+
/** Wraps process.env so that missing keys return null instead of throwing in CEL.
|
|
6
|
+
* cel-js uses Object.hasOwn(obj, key) before accessing obj[key], so we must
|
|
7
|
+
* intercept getOwnPropertyDescriptor to report every string key as "own". */
|
|
8
|
+
function lenientEnv(env: Record<string, string | undefined>): Record<string, string | null> {
|
|
9
|
+
return new Proxy(env as Record<string, string | null>, {
|
|
10
|
+
get(target, key) {
|
|
11
|
+
if (typeof key !== "string") return (target as any)[key];
|
|
12
|
+
return key in target ? (target[key] ?? null) : null;
|
|
13
|
+
},
|
|
14
|
+
has() {
|
|
15
|
+
return true;
|
|
16
|
+
},
|
|
17
|
+
getOwnPropertyDescriptor(target, key) {
|
|
18
|
+
if (typeof key !== "string") return Object.getOwnPropertyDescriptor(target, key);
|
|
19
|
+
const value = key in target ? (target[key] ?? null) : null;
|
|
20
|
+
return { configurable: true, enumerable: true, writable: true, value };
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function collectSecretValues(secrets: Record<string, unknown>): Set<string> {
|
|
26
|
+
const values = new Set<string>();
|
|
27
|
+
for (const value of Object.values(secrets)) {
|
|
28
|
+
if (typeof value === "string" && value.length > 0) {
|
|
29
|
+
values.add(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return values;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Persistent, module-scoped context. Three reserved CEL namespaces:
|
|
37
|
+
* variables, secrets, resources.
|
|
38
|
+
*
|
|
39
|
+
* Unlike the base EvaluationContext, ModuleContext is stateful and mutable:
|
|
40
|
+
* variables/secrets/resources accumulate during multi-pass initialization and
|
|
41
|
+
* the context record is rebuilt on each mutation. Import aliases are tracked
|
|
42
|
+
* here for alias-prefixed kind resolution (e.g. MyImport.Http.Route).
|
|
43
|
+
*
|
|
44
|
+
* Imported modules are surfaced under resources.<alias> alongside local
|
|
45
|
+
* resources — no separate imports namespace needed.
|
|
46
|
+
*/
|
|
47
|
+
export class ModuleContext extends EvaluationContext implements IModuleContext {
|
|
48
|
+
private _variables: Record<string, unknown>;
|
|
49
|
+
private _secrets: Record<string, unknown>;
|
|
50
|
+
private _resources: Record<string, unknown>;
|
|
51
|
+
|
|
52
|
+
/** Maps import alias → real module name for kind resolution. */
|
|
53
|
+
readonly importAliases = new Map<string, string>();
|
|
54
|
+
|
|
55
|
+
/** Maps import alias → allowed kind names. Absent entry = unrestricted (e.g. Kernel). */
|
|
56
|
+
private readonly importedKinds = new Map<string, Set<string>>();
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
source: string,
|
|
60
|
+
variables: Record<string, unknown> = {},
|
|
61
|
+
secrets: Record<string, unknown> = {},
|
|
62
|
+
resources: Record<string, unknown> = {},
|
|
63
|
+
private targets: string[] = [],
|
|
64
|
+
createInstance: InstanceFactory = async () => null,
|
|
65
|
+
emit: EmitEvent,
|
|
66
|
+
private readonly _hostEnv?: Record<string, string | undefined>,
|
|
67
|
+
) {
|
|
68
|
+
super(source, {}, createInstance, new Set(), emit);
|
|
69
|
+
this._variables = variables;
|
|
70
|
+
this._secrets = secrets;
|
|
71
|
+
this._resources = resources;
|
|
72
|
+
this._rebuildContext();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get variables(): Record<string, unknown> {
|
|
76
|
+
return this._variables;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get secrets(): Record<string, unknown> {
|
|
80
|
+
return this._secrets;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get resources(): Record<string, unknown> {
|
|
84
|
+
return this._resources;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setVariables(vars: Record<string, unknown>): void {
|
|
88
|
+
this._variables = vars;
|
|
89
|
+
this._rebuildContext();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setTargets(vars: string[]): void {
|
|
93
|
+
this.targets = vars;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setSecrets(secrets: Record<string, unknown>): void {
|
|
97
|
+
this._secrets = secrets;
|
|
98
|
+
this._rebuildContext();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
setResource(name: string, props: Record<string, unknown>): void {
|
|
102
|
+
this._resources = { ...this._resources, [name]: props };
|
|
103
|
+
this._rebuildContext();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
protected override onResourceSnapshotted(name: string, snap: Record<string, unknown>): void {
|
|
107
|
+
this.setResource(name, snap);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Register an imported module under the given alias, with the list of kind names
|
|
112
|
+
* it exports. An empty kinds array means no restriction (used for built-ins like Kernel).
|
|
113
|
+
*/
|
|
114
|
+
registerImport(alias: string, targetModule: string, kinds: string[]): void {
|
|
115
|
+
this.importAliases.set(alias, targetModule);
|
|
116
|
+
if (kinds.length > 0) {
|
|
117
|
+
this.importedKinds.set(alias, new Set(kinds));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
getInstance(name: string): unknown {
|
|
122
|
+
const entry = this.resourceInstances.get(name);
|
|
123
|
+
if (!entry) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Resource '${name}' not found in module context. Available resources: ${[...this.resourceInstances.keys()].join(", ")}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return entry?.instance;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getInvocable<TInput = Record<string, any>, TOutput = any>(
|
|
132
|
+
name: string,
|
|
133
|
+
): Invocable<TInput, TOutput> {
|
|
134
|
+
const instance = this.getInstance(name);
|
|
135
|
+
|
|
136
|
+
if (typeof (instance as any)?.invoke !== "function") {
|
|
137
|
+
throw new Error(`Resource '${name}' does not have an invoke() method.`);
|
|
138
|
+
}
|
|
139
|
+
return instance as Invocable<TInput, TOutput>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Resolve a fully-qualified kind like "Http.Server" to its real kind "http-server.Server".
|
|
144
|
+
* Splits on the first dot, looks up the prefix in importAliases, validates against
|
|
145
|
+
* importedKinds (if set), and reconstructs the resolved kind.
|
|
146
|
+
* Throws with a clear message if the alias is unknown or the kind is not exported.
|
|
147
|
+
*/
|
|
148
|
+
resolveKind(kind: string): string {
|
|
149
|
+
const dot = kind.indexOf(".");
|
|
150
|
+
if (dot === -1) {
|
|
151
|
+
throw new Error(`Kind '${kind}' must be fully qualified (e.g. 'Module.KindName')`);
|
|
152
|
+
}
|
|
153
|
+
const prefix = kind.slice(0, dot);
|
|
154
|
+
const suffix = kind.slice(dot + 1);
|
|
155
|
+
const realModule = this.importAliases.get(prefix);
|
|
156
|
+
if (!realModule) {
|
|
157
|
+
const known = [...this.importAliases.keys()].join(", ") || "(none)";
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Kind '${kind}': no module imported with alias '${prefix}'. Known aliases: ${known}`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const allowed = this.importedKinds.get(prefix);
|
|
163
|
+
if (allowed !== undefined && !allowed.has(suffix)) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Kind '${suffix}' is not exported by module '${realModule}' (imported as '${prefix}'). ` +
|
|
166
|
+
`Exported kinds: ${[...allowed].join(", ")}`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
return `${realModule}.${suffix}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private _rebuildContext(): void {
|
|
173
|
+
this._context = {
|
|
174
|
+
variables: this._variables,
|
|
175
|
+
secrets: this._secrets,
|
|
176
|
+
resources: this._resources,
|
|
177
|
+
...(this._hostEnv ? { env: lenientEnv(this._hostEnv) } : {}),
|
|
178
|
+
};
|
|
179
|
+
this._secretValues = collectSecretValues(this._secrets);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
override async invoke<TInputs>(kind: string, name: string, inputs: TInputs): Promise<any> {
|
|
183
|
+
const result = await super.invoke(kind, name, inputs);
|
|
184
|
+
const entry = this.resourceInstances.get(name);
|
|
185
|
+
if (entry && typeof (entry.instance as any).snapshot === "function") {
|
|
186
|
+
const snap = await Promise.resolve((entry.instance as any).snapshot());
|
|
187
|
+
this.setResource(name, snap as Record<string, unknown>);
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async run(name: string) {
|
|
193
|
+
const resource = this.resourceInstances.get(name);
|
|
194
|
+
if (!resource) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Target resource ${name} not found in module context. Available resources: ${[...this.resourceInstances.keys()].join(", ")}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (typeof resource.instance.run === "function") {
|
|
200
|
+
await resource.instance.run();
|
|
201
|
+
} else {
|
|
202
|
+
throw new Error(`Target resource ${name} does not have a run() method.`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async runTargets() {
|
|
207
|
+
for (const target of this.targets) {
|
|
208
|
+
await this.run(target);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
package/src/resource-context.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
-
EvaluationContext,
|
|
3
|
-
ModuleContext,
|
|
4
2
|
NoopValidator,
|
|
5
3
|
ResourceContext,
|
|
6
4
|
RuntimeError,
|
|
7
5
|
RuntimeResource,
|
|
8
6
|
isCompiledValue,
|
|
7
|
+
type EvaluationContext as IEvaluationContext,
|
|
8
|
+
type ModuleContext,
|
|
9
9
|
type ParsedArgs,
|
|
10
10
|
type TypeRule,
|
|
11
11
|
} from "@telorun/sdk";
|
|
12
|
+
import { EvaluationContext } from "./evaluation-context.js";
|
|
12
13
|
import AjvModule from "ajv";
|
|
13
14
|
import addFormats from "ajv-formats";
|
|
14
15
|
import { Kernel } from "./kernel.js";
|
|
15
16
|
import { formatAjvErrors } from "./manifest-schemas.js";
|
|
16
|
-
import { SchemaValidator } from "./schema-
|
|
17
|
+
import { SchemaValidator } from "./schema-validator.js";
|
|
17
18
|
|
|
18
19
|
const Ajv = AjvModule.default ?? AjvModule;
|
|
19
20
|
|
|
@@ -264,10 +265,10 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
264
265
|
|
|
265
266
|
/**
|
|
266
267
|
* Create a child EvaluationContext attached to the current module context.
|
|
267
|
-
*
|
|
268
|
-
* call
|
|
268
|
+
* Register resources on the returned context with registerManifest(), then
|
|
269
|
+
* call initializeResources() to initialize them in isolation.
|
|
269
270
|
*/
|
|
270
|
-
spawnChildContext():
|
|
271
|
+
spawnChildContext(): IEvaluationContext {
|
|
271
272
|
const child = new EvaluationContext(
|
|
272
273
|
this.moduleContext.source,
|
|
273
274
|
this.moduleContext.context,
|
|
@@ -278,7 +279,7 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
278
279
|
return this.moduleContext.spawnChild(child);
|
|
279
280
|
}
|
|
280
281
|
|
|
281
|
-
transientChild(context: Record<string, any>):
|
|
282
|
+
transientChild(context: Record<string, any>): IEvaluationContext {
|
|
282
283
|
return this.moduleContext.transientChild(context);
|
|
283
284
|
}
|
|
284
285
|
|
|
@@ -10,6 +10,7 @@ export class SchemaValidator {
|
|
|
10
10
|
private ajv: InstanceType<typeof Ajv>;
|
|
11
11
|
private typeRules = new Map<string, TypeRule[]>();
|
|
12
12
|
private rawSchemas = new Map<string, object>();
|
|
13
|
+
private compiledValidators = new WeakMap<object, DataValidator>();
|
|
13
14
|
|
|
14
15
|
constructor() {
|
|
15
16
|
this.ajv = new Ajv({
|
|
@@ -53,6 +54,11 @@ export class SchemaValidator {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
compile(schema: any): DataValidator {
|
|
57
|
+
if (schema && typeof schema === "object") {
|
|
58
|
+
const cached = this.compiledValidators.get(schema as object);
|
|
59
|
+
if (cached) return cached;
|
|
60
|
+
}
|
|
61
|
+
|
|
56
62
|
const isFullSchema =
|
|
57
63
|
("type" in schema && typeof schema.type === "string") ||
|
|
58
64
|
"allOf" in schema ||
|
|
@@ -80,7 +86,7 @@ export class SchemaValidator {
|
|
|
80
86
|
: normalized;
|
|
81
87
|
const validate = this.ajv.compile(injected);
|
|
82
88
|
|
|
83
|
-
|
|
89
|
+
const validator = {
|
|
84
90
|
validate: (data: any) => {
|
|
85
91
|
const isValid = validate(data);
|
|
86
92
|
if (!isValid) {
|
|
@@ -94,6 +100,12 @@ export class SchemaValidator {
|
|
|
94
100
|
return validate(data);
|
|
95
101
|
},
|
|
96
102
|
};
|
|
103
|
+
|
|
104
|
+
if (schema && typeof schema === "object") {
|
|
105
|
+
this.compiledValidators.set(schema as object, validator);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return validator;
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
composeWithRules(base: DataValidator, typeName: string, rules: TypeRule[]): DataValidator {
|