@telorun/kernel 0.26.1 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cel-handlers.d.ts +14 -0
- package/dist/cel-handlers.d.ts.map +1 -0
- package/dist/cel-handlers.js +22 -0
- package/dist/cel-handlers.js.map +1 -0
- package/dist/dependency-injection.d.ts +10 -0
- package/dist/dependency-injection.d.ts.map +1 -0
- package/dist/dependency-injection.js +95 -0
- package/dist/dependency-injection.js.map +1 -0
- package/dist/eval-paths.d.ts +10 -0
- package/dist/eval-paths.d.ts.map +1 -0
- package/dist/eval-paths.js +35 -0
- package/dist/eval-paths.js.map +1 -0
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +60 -0
- package/dist/evaluation-context.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/invoke-dispatch.d.ts +11 -0
- package/dist/invoke-dispatch.d.ts.map +1 -0
- package/dist/invoke-dispatch.js +37 -0
- package/dist/invoke-dispatch.js.map +1 -0
- package/dist/kernel.d.ts +0 -31
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +7 -291
- package/dist/kernel.js.map +1 -1
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +2 -15
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-compiled-values.d.ts +7 -0
- package/dist/schema-compiled-values.d.ts.map +1 -0
- package/dist/schema-compiled-values.js +72 -0
- package/dist/schema-compiled-values.js.map +1 -0
- package/package.json +1 -1
- package/src/cel-handlers.ts +24 -0
- package/src/dependency-injection.ts +96 -0
- package/src/eval-paths.ts +44 -0
- package/src/evaluation-context.ts +58 -0
- package/src/index.ts +2 -2
- package/src/invoke-dispatch.ts +43 -0
- package/src/kernel.ts +6 -323
- package/src/resource-context.ts +1 -13
- package/src/schema-compiled-values.ts +85 -0
- package/dist/event-stream.d.ts +0 -45
- package/dist/event-stream.d.ts.map +0 -1
- package/dist/event-stream.js +0 -98
- package/dist/event-stream.js.map +0 -1
- package/src/event-stream.ts +0 -121
package/src/kernel.ts
CHANGED
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
ControllerPolicy,
|
|
14
14
|
createCancellationSource,
|
|
15
15
|
Kernel as IKernel,
|
|
16
|
-
isCompiledValue,
|
|
17
16
|
ResourceContext,
|
|
18
17
|
ResourceDefinition,
|
|
19
18
|
ResourceInstance,
|
|
@@ -28,13 +27,16 @@ import {
|
|
|
28
27
|
type LoadOptions,
|
|
29
28
|
type ParsedArgs,
|
|
30
29
|
} from "@telorun/sdk";
|
|
31
|
-
import { createHash, createHmac } from "node:crypto";
|
|
32
30
|
import { parseArgs } from "util";
|
|
33
31
|
import { ControllerRegistry } from "./controller-registry.js";
|
|
34
|
-
import { EventStream } from "./event-stream.js";
|
|
35
32
|
import { EventBus } from "./events.js";
|
|
36
33
|
import { ModuleContext } from "./module-context.js";
|
|
37
34
|
import { ResourceContextImpl } from "./resource-context.js";
|
|
35
|
+
import { nodeCelHandlers } from "./cel-handlers.js";
|
|
36
|
+
import { parseRef, seedInvokeSource } from "./invoke-dispatch.js";
|
|
37
|
+
import { buildEvalPaths } from "./eval-paths.js";
|
|
38
|
+
import { stripCompiledValues } from "./schema-compiled-values.js";
|
|
39
|
+
import { injectAtPath } from "./dependency-injection.js";
|
|
38
40
|
import {
|
|
39
41
|
computeAnalysisSignature,
|
|
40
42
|
readAnalysisStamp,
|
|
@@ -75,43 +77,6 @@ function throwInvalidState(operation: string, reason: string): never {
|
|
|
75
77
|
);
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
/** Translate embedder `InvokeOptions` (external signal / absolute deadline)
|
|
79
|
-
* into a seeded cancellation source, or `undefined` when nothing was requested
|
|
80
|
-
* so the dispatch path stays on its allocation-free sentinel. The caller
|
|
81
|
-
* disposes the returned source once the invoke settles. */
|
|
82
|
-
function seedInvokeSource(opts?: InvokeOptions): CancellationSource | undefined {
|
|
83
|
-
if (!opts?.signal && opts?.deadlineAt === undefined) return undefined;
|
|
84
|
-
const source = createCancellationSource();
|
|
85
|
-
if (opts.deadlineAt !== undefined) source.cancelAt(opts.deadlineAt);
|
|
86
|
-
const signal = opts.signal;
|
|
87
|
-
if (signal && !signal.aborted) {
|
|
88
|
-
const onAbort = () => source.cancel(String(signal.reason ?? "aborted"));
|
|
89
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
90
|
-
// Detach from the (possibly long-lived) external signal on dispose so the
|
|
91
|
-
// listener — which captures the source — doesn't pin it until the signal
|
|
92
|
-
// eventually aborts (or forever if it never does).
|
|
93
|
-
const baseDispose = source.dispose.bind(source);
|
|
94
|
-
source.dispose = () => {
|
|
95
|
-
signal.removeEventListener("abort", onAbort);
|
|
96
|
-
baseDispose();
|
|
97
|
-
};
|
|
98
|
-
} else if (signal?.aborted) {
|
|
99
|
-
source.cancel(String(signal.reason ?? "aborted"));
|
|
100
|
-
}
|
|
101
|
-
return source;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function parseRef(ref: string): { kind: string; name: string } {
|
|
105
|
-
const lastDot = ref.lastIndexOf(".");
|
|
106
|
-
if (lastDot <= 0 || lastDot === ref.length - 1) {
|
|
107
|
-
throw new RuntimeError(
|
|
108
|
-
"ERR_INVALID_VALUE",
|
|
109
|
-
`Invalid resource reference '${ref}': expected '<Kind>.<Name>' (e.g. 'Http.Server.Main') or pass { kind, name } directly.`,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
return { kind: ref.slice(0, lastDot), name: ref.slice(lastDot + 1) };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
80
|
export interface KernelOptions {
|
|
116
81
|
stdin?: NodeJS.ReadableStream;
|
|
117
82
|
stdout?: NodeJS.WritableStream;
|
|
@@ -133,36 +98,12 @@ export interface KernelOptions {
|
|
|
133
98
|
* Kernel: Central orchestrator managing lifecycle and message bus
|
|
134
99
|
* Handles resource loading, initialization, and execution through controllers
|
|
135
100
|
*/
|
|
136
|
-
/** Node implementations of the host-injected CEL functions (`crypto` / `Buffer`).
|
|
137
|
-
* The kernel wires these into the analyzer + loader; the CLI reuses them for
|
|
138
|
-
* `telo cel eval` so its results match a real run. */
|
|
139
|
-
export const nodeCelHandlers = {
|
|
140
|
-
sha256: (s: string) => createHash("sha256").update(s).digest("hex"),
|
|
141
|
-
md5: (s: string) => createHash("md5").update(s).digest("hex"),
|
|
142
|
-
sha1: (s: string) => createHash("sha1").update(s).digest("hex"),
|
|
143
|
-
sha512: (s: string) => createHash("sha512").update(s).digest("hex"),
|
|
144
|
-
hmac: (algorithm: string, key: string, message: string) =>
|
|
145
|
-
createHmac(algorithm, key).update(message).digest("hex"),
|
|
146
|
-
base64Encode: (s: string) => Buffer.from(s, "utf8").toString("base64"),
|
|
147
|
-
base64Decode: (s: string) => Buffer.from(s, "base64").toString("utf8"),
|
|
148
|
-
// cel-js represents int / uint as BigInt — JSON.stringify throws on BigInts,
|
|
149
|
-
// so coerce them down to Number unconditionally. CEL int is i64 and JS Number
|
|
150
|
-
// is f64, so values outside ±2^53 lose precision; that's accepted behaviour
|
|
151
|
-
// for Telo manifests, which never carry > 2^53 integer values in practice.
|
|
152
|
-
// JSON.stringify returns undefined for top-level undefined / function / symbol
|
|
153
|
-
// — the CEL signature is `json(dyn): string`, so coerce that to "null" rather
|
|
154
|
-
// than break the contract. (CEL `null` already serializes to "null".)
|
|
155
|
-
json: (value: unknown) =>
|
|
156
|
-
JSON.stringify(value, (_k, v) => (typeof v === "bigint" ? Number(v) : v)) ?? "null",
|
|
157
|
-
};
|
|
158
|
-
|
|
159
101
|
export class Kernel implements IKernel {
|
|
160
102
|
private readonly loader: Loader;
|
|
161
103
|
private readonly analyzer = new StaticAnalyzer({ celHandlers: nodeCelHandlers });
|
|
162
104
|
private readonly registry = new AnalysisRegistry();
|
|
163
105
|
private controllers: ControllerRegistry = new ControllerRegistry();
|
|
164
106
|
private eventBus: EventBus = new EventBus();
|
|
165
|
-
private eventStream: EventStream = new EventStream();
|
|
166
107
|
|
|
167
108
|
private holdCount = 0;
|
|
168
109
|
private idleResolvers: Array<() => void> = [];
|
|
@@ -204,7 +145,6 @@ export class Kernel implements IKernel {
|
|
|
204
145
|
for (const source of options.sources) {
|
|
205
146
|
this.loader.register(source);
|
|
206
147
|
}
|
|
207
|
-
this.setupEventStreaming();
|
|
208
148
|
}
|
|
209
149
|
|
|
210
150
|
async registerController(
|
|
@@ -864,12 +804,7 @@ export class Kernel implements IKernel {
|
|
|
864
804
|
* ModuleContext ancestor. Falls back to rootContext if none found.
|
|
865
805
|
*/
|
|
866
806
|
private findModuleContext(ctx: IEvaluationContext): IModuleContext {
|
|
867
|
-
|
|
868
|
-
while (current) {
|
|
869
|
-
if (current instanceof ModuleContext) return current;
|
|
870
|
-
current = current.parent;
|
|
871
|
-
}
|
|
872
|
-
return this.rootContext;
|
|
807
|
+
return findEnclosingModule(ctx) ?? this.rootContext;
|
|
873
808
|
}
|
|
874
809
|
|
|
875
810
|
private createResourceContext(
|
|
@@ -1059,256 +994,4 @@ export class Kernel implements IKernel {
|
|
|
1059
994
|
},
|
|
1060
995
|
);
|
|
1061
996
|
}
|
|
1062
|
-
|
|
1063
|
-
/**
|
|
1064
|
-
* Enable event streaming to a file (JSONL format)
|
|
1065
|
-
*/
|
|
1066
|
-
async enableEventStream(filePath: string): Promise<void> {
|
|
1067
|
-
await this.eventStream.enable(filePath);
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
/**
|
|
1071
|
-
* Disable event streaming
|
|
1072
|
-
*/
|
|
1073
|
-
disableEventStream(): void {
|
|
1074
|
-
this.eventStream.disable();
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
/**
|
|
1078
|
-
* Get the event stream for testing and inspection
|
|
1079
|
-
*/
|
|
1080
|
-
getEventStream(): EventStream {
|
|
1081
|
-
return this.eventStream;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
/**
|
|
1085
|
-
* Setup event streaming hook to capture all events
|
|
1086
|
-
*/
|
|
1087
|
-
private setupEventStreaming(): void {
|
|
1088
|
-
const originalEmit = this.eventBus.emit.bind(this.eventBus);
|
|
1089
|
-
this.eventBus.emit = async (event: string, payload?: any) => {
|
|
1090
|
-
if (this.eventStream.isEnabledStream()) {
|
|
1091
|
-
await this.eventStream.log(event, payload);
|
|
1092
|
-
}
|
|
1093
|
-
return originalEmit(event, payload);
|
|
1094
|
-
};
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
/** Returns a schema-appropriate placeholder value for a CompiledValue field. */
|
|
1099
|
-
function placeholderForSchema(schema: Record<string, unknown>): unknown {
|
|
1100
|
-
if (schema.default !== undefined) return schema.default;
|
|
1101
|
-
switch (schema.type) {
|
|
1102
|
-
case "integer":
|
|
1103
|
-
case "number":
|
|
1104
|
-
return (schema.minimum as number | undefined) ?? 0;
|
|
1105
|
-
case "boolean":
|
|
1106
|
-
return false;
|
|
1107
|
-
case "array":
|
|
1108
|
-
return [];
|
|
1109
|
-
case "object":
|
|
1110
|
-
return {};
|
|
1111
|
-
default:
|
|
1112
|
-
return "";
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
/** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
|
|
1117
|
-
function resolveSchemaRef(
|
|
1118
|
-
schema: Record<string, unknown>,
|
|
1119
|
-
root: Record<string, unknown>,
|
|
1120
|
-
): Record<string, unknown> {
|
|
1121
|
-
if (
|
|
1122
|
-
schema.$ref &&
|
|
1123
|
-
typeof schema.$ref === "string" &&
|
|
1124
|
-
(schema.$ref as string).startsWith("#/$defs/")
|
|
1125
|
-
) {
|
|
1126
|
-
const defName = (schema.$ref as string).slice("#/$defs/".length);
|
|
1127
|
-
const defs = root.$defs as Record<string, Record<string, unknown>> | undefined;
|
|
1128
|
-
const resolved = defs?.[defName];
|
|
1129
|
-
if (resolved) return resolved;
|
|
1130
|
-
}
|
|
1131
|
-
return schema;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
/** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
|
|
1135
|
-
function collectSchemaProperties(
|
|
1136
|
-
schema: Record<string, unknown>,
|
|
1137
|
-
): Record<string, Record<string, unknown>> {
|
|
1138
|
-
const props: Record<string, Record<string, unknown>> = {
|
|
1139
|
-
...((schema.properties ?? {}) as Record<string, Record<string, unknown>>),
|
|
1140
|
-
};
|
|
1141
|
-
for (const sub of (schema.oneOf ?? schema.anyOf ?? []) as Record<string, unknown>[]) {
|
|
1142
|
-
if (sub && typeof sub === "object" && sub.properties) {
|
|
1143
|
-
for (const [k, v] of Object.entries(
|
|
1144
|
-
sub.properties as Record<string, Record<string, unknown>>,
|
|
1145
|
-
)) {
|
|
1146
|
-
if (!(k in props)) props[k] = v;
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
return props;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
/** Replaces CompiledValue wrappers with schema-appropriate placeholders for schema validation.
|
|
1154
|
-
* Template strings were compiled from YAML at load time; this restores a shape
|
|
1155
|
-
* that AJV can validate without evaluating expressions. */
|
|
1156
|
-
function stripCompiledValues(
|
|
1157
|
-
v: unknown,
|
|
1158
|
-
schema: Record<string, unknown> = {},
|
|
1159
|
-
rootSchema?: Record<string, unknown>,
|
|
1160
|
-
): unknown {
|
|
1161
|
-
const root = rootSchema ?? schema;
|
|
1162
|
-
const resolved = resolveSchemaRef(schema, root);
|
|
1163
|
-
|
|
1164
|
-
if (isCompiledValue(v)) return placeholderForSchema(resolved);
|
|
1165
|
-
if (Array.isArray(v)) {
|
|
1166
|
-
const itemSchema = resolveSchemaRef((resolved.items ?? {}) as Record<string, unknown>, root);
|
|
1167
|
-
return v.map((item) => stripCompiledValues(item, itemSchema, root));
|
|
1168
|
-
}
|
|
1169
|
-
if (v !== null && typeof v === "object") {
|
|
1170
|
-
const props = collectSchemaProperties(resolved);
|
|
1171
|
-
const out: Record<string, unknown> = {};
|
|
1172
|
-
for (const [k, val] of Object.entries(v as Record<string, unknown>)) {
|
|
1173
|
-
out[k] = stripCompiledValues(val, props[k] ?? {}, root);
|
|
1174
|
-
}
|
|
1175
|
-
return out;
|
|
1176
|
-
}
|
|
1177
|
-
return v;
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
/**
|
|
1181
|
-
* Walks `resource` following `fieldPath` (dot notation, `[]` = array traversal).
|
|
1182
|
-
* For each leaf value that looks like a {kind, name} reference, calls getInstance(name)
|
|
1183
|
-
* and replaces the value in-place with the returned live ResourceInstance.
|
|
1184
|
-
* Values where getInstance returns undefined are left unchanged.
|
|
1185
|
-
*/
|
|
1186
|
-
/**
|
|
1187
|
-
* Traverses a definition schema and collects all paths annotated with `x-telo-eval`.
|
|
1188
|
-
* Root-level `x-telo-eval` produces the `"**"` wildcard (expand all fields).
|
|
1189
|
-
* Property-level annotations produce the dot-notation path to that property.
|
|
1190
|
-
*/
|
|
1191
|
-
function buildEvalPaths(schema: Record<string, any>): { compile: string[]; runtime: string[] } {
|
|
1192
|
-
const compile: string[] = [];
|
|
1193
|
-
const runtime: string[] = [];
|
|
1194
|
-
|
|
1195
|
-
if (schema["x-telo-eval"] === "compile") compile.push("**");
|
|
1196
|
-
else if (schema["x-telo-eval"] === "runtime") runtime.push("**");
|
|
1197
|
-
|
|
1198
|
-
if (schema.properties) {
|
|
1199
|
-
for (const [key, propSchema] of Object.entries(schema.properties as Record<string, any>)) {
|
|
1200
|
-
collectEvalPathsNode(propSchema, key, compile, runtime);
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
return { compile, runtime };
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
function collectEvalPathsNode(
|
|
1208
|
-
node: Record<string, any>,
|
|
1209
|
-
path: string,
|
|
1210
|
-
compile: string[],
|
|
1211
|
-
runtime: string[],
|
|
1212
|
-
): void {
|
|
1213
|
-
if (node["x-telo-eval"] === "compile") {
|
|
1214
|
-
compile.push(path);
|
|
1215
|
-
return;
|
|
1216
|
-
}
|
|
1217
|
-
if (node["x-telo-eval"] === "runtime") {
|
|
1218
|
-
runtime.push(path);
|
|
1219
|
-
return;
|
|
1220
|
-
}
|
|
1221
|
-
if (node.properties) {
|
|
1222
|
-
for (const [key, propSchema] of Object.entries(node.properties as Record<string, any>)) {
|
|
1223
|
-
collectEvalPathsNode(propSchema, `${path}.${key}`, compile, runtime);
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
function injectAtPath(
|
|
1229
|
-
resource: ResourceManifest,
|
|
1230
|
-
fieldPath: string,
|
|
1231
|
-
getInstance: (name: string, alias?: string) => ResourceInstance | undefined,
|
|
1232
|
-
): void {
|
|
1233
|
-
const parts = fieldPath.split(".");
|
|
1234
|
-
|
|
1235
|
-
// Resolve a {kind, name, alias?} reference to its live instance. A non-`Self` alias is a
|
|
1236
|
-
// cross-module reference into an import's published exports; if that import hasn't
|
|
1237
|
-
// finished init() yet the instance is absent, so we throw to defer this resource to a
|
|
1238
|
-
// later pass of the multi-pass init loop (which catches and retries) rather than leaving
|
|
1239
|
-
// the ref unresolved. Local refs (no alias) that miss are left for topo ordering / later
|
|
1240
|
-
// diagnostics, matching prior behaviour.
|
|
1241
|
-
function resolveInto(ref: Record<string, unknown>): ResourceInstance | undefined {
|
|
1242
|
-
const alias = typeof ref.alias === "string" ? ref.alias : undefined;
|
|
1243
|
-
const instance = getInstance(ref.name as string, alias);
|
|
1244
|
-
if (!instance && alias && alias !== "Self") {
|
|
1245
|
-
throw new RuntimeError(
|
|
1246
|
-
"ERR_CROSS_MODULE_REF_PENDING",
|
|
1247
|
-
`Cross-module reference '${alias}.${String(ref.name)}' is not available yet (import not initialized)`,
|
|
1248
|
-
);
|
|
1249
|
-
}
|
|
1250
|
-
return instance;
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
function traverse(obj: unknown, partsLeft: string[]): void {
|
|
1254
|
-
if (!obj || typeof obj !== "object" || partsLeft.length === 0) return;
|
|
1255
|
-
const [head, ...rest] = partsLeft;
|
|
1256
|
-
|
|
1257
|
-
// Map iteration: descend into every value of the current object (used for
|
|
1258
|
-
// schema fields with `additionalProperties` like `content[mime]`).
|
|
1259
|
-
if (head === "{}") {
|
|
1260
|
-
const container = obj as Record<string, unknown>;
|
|
1261
|
-
for (const mapKey of Object.keys(container)) {
|
|
1262
|
-
const elem = container[mapKey];
|
|
1263
|
-
if (!elem || typeof elem !== "object") continue;
|
|
1264
|
-
if (rest.length === 0) {
|
|
1265
|
-
const ref = elem as Record<string, unknown>;
|
|
1266
|
-
if (typeof ref.kind === "string" && typeof ref.name === "string") {
|
|
1267
|
-
const instance = resolveInto(ref);
|
|
1268
|
-
if (instance) container[mapKey] = instance;
|
|
1269
|
-
}
|
|
1270
|
-
} else {
|
|
1271
|
-
traverse(elem, rest);
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
return;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
const isArr = head.endsWith("[]");
|
|
1278
|
-
const key = isArr ? head.slice(0, -2) : head;
|
|
1279
|
-
const container = obj as Record<string, unknown>;
|
|
1280
|
-
const val = container[key];
|
|
1281
|
-
if (val == null) return;
|
|
1282
|
-
|
|
1283
|
-
if (isArr) {
|
|
1284
|
-
if (!Array.isArray(val)) return;
|
|
1285
|
-
for (let i = 0; i < val.length; i++) {
|
|
1286
|
-
const elem = val[i];
|
|
1287
|
-
if (!elem || typeof elem !== "object") continue;
|
|
1288
|
-
if (rest.length === 0) {
|
|
1289
|
-
const ref = elem as Record<string, unknown>;
|
|
1290
|
-
if (typeof ref.kind === "string" && typeof ref.name === "string") {
|
|
1291
|
-
const instance = resolveInto(ref);
|
|
1292
|
-
if (instance) val[i] = instance;
|
|
1293
|
-
}
|
|
1294
|
-
} else {
|
|
1295
|
-
traverse(elem, rest);
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
} else {
|
|
1299
|
-
if (rest.length === 0) {
|
|
1300
|
-
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
1301
|
-
const ref = val as Record<string, unknown>;
|
|
1302
|
-
if (typeof ref.kind === "string" && typeof ref.name === "string") {
|
|
1303
|
-
const instance = resolveInto(ref);
|
|
1304
|
-
if (instance) container[key] = instance;
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
} else {
|
|
1308
|
-
traverse(val, rest);
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
traverse(resource, parts);
|
|
1314
997
|
}
|
package/src/resource-context.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
RuntimeError,
|
|
7
7
|
RuntimeResource,
|
|
8
8
|
createCancellationSource,
|
|
9
|
-
isCompiledValue,
|
|
10
9
|
type CancellationSource,
|
|
11
10
|
type ControllerPolicy,
|
|
12
11
|
type EvaluationContext as IEvaluationContext,
|
|
@@ -17,6 +16,7 @@ import {
|
|
|
17
16
|
type TypeRule,
|
|
18
17
|
} from "@telorun/sdk";
|
|
19
18
|
import { isRefSentinel } from "@telorun/templating";
|
|
19
|
+
import { stripCompiledValues } from "./schema-compiled-values.js";
|
|
20
20
|
import AjvModule from "ajv";
|
|
21
21
|
import addFormats from "ajv-formats";
|
|
22
22
|
import { EvaluationContext } from "./evaluation-context.js";
|
|
@@ -421,15 +421,3 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
421
421
|
}
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
-
function stripCompiledValues(v: unknown): unknown {
|
|
425
|
-
if (isCompiledValue(v)) return "";
|
|
426
|
-
if (Array.isArray(v)) return v.map(stripCompiledValues);
|
|
427
|
-
if (v !== null && typeof v === "object") {
|
|
428
|
-
const out: Record<string, unknown> = {};
|
|
429
|
-
for (const [k, val] of Object.entries(v as Record<string, unknown>)) {
|
|
430
|
-
out[k] = stripCompiledValues(val);
|
|
431
|
-
}
|
|
432
|
-
return out;
|
|
433
|
-
}
|
|
434
|
-
return v;
|
|
435
|
-
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { isCompiledValue } from "@telorun/sdk";
|
|
2
|
+
|
|
3
|
+
/** Returns a schema-appropriate placeholder value for a CompiledValue field. */
|
|
4
|
+
function placeholderForSchema(schema: Record<string, unknown>): unknown {
|
|
5
|
+
if (schema.default !== undefined) return schema.default;
|
|
6
|
+
switch (schema.type) {
|
|
7
|
+
case "integer":
|
|
8
|
+
case "number":
|
|
9
|
+
return (schema.minimum as number | undefined) ?? 0;
|
|
10
|
+
case "boolean":
|
|
11
|
+
return false;
|
|
12
|
+
case "array":
|
|
13
|
+
return [];
|
|
14
|
+
case "object":
|
|
15
|
+
return {};
|
|
16
|
+
default:
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
|
|
22
|
+
function resolveSchemaRef(
|
|
23
|
+
schema: Record<string, unknown>,
|
|
24
|
+
root: Record<string, unknown>,
|
|
25
|
+
): Record<string, unknown> {
|
|
26
|
+
if (
|
|
27
|
+
schema.$ref &&
|
|
28
|
+
typeof schema.$ref === "string" &&
|
|
29
|
+
(schema.$ref as string).startsWith("#/$defs/")
|
|
30
|
+
) {
|
|
31
|
+
const defName = (schema.$ref as string).slice("#/$defs/".length);
|
|
32
|
+
const defs = root.$defs as Record<string, Record<string, unknown>> | undefined;
|
|
33
|
+
const resolved = defs?.[defName];
|
|
34
|
+
if (resolved) return resolved;
|
|
35
|
+
}
|
|
36
|
+
return schema;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
|
|
40
|
+
function collectSchemaProperties(
|
|
41
|
+
schema: Record<string, unknown>,
|
|
42
|
+
): Record<string, Record<string, unknown>> {
|
|
43
|
+
const props: Record<string, Record<string, unknown>> = {
|
|
44
|
+
...((schema.properties ?? {}) as Record<string, Record<string, unknown>>),
|
|
45
|
+
};
|
|
46
|
+
for (const sub of (schema.oneOf ?? schema.anyOf ?? []) as Record<string, unknown>[]) {
|
|
47
|
+
if (sub && typeof sub === "object" && sub.properties) {
|
|
48
|
+
for (const [k, v] of Object.entries(
|
|
49
|
+
sub.properties as Record<string, Record<string, unknown>>,
|
|
50
|
+
)) {
|
|
51
|
+
if (!(k in props)) props[k] = v;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return props;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Replaces CompiledValue wrappers with schema-appropriate placeholders for schema validation.
|
|
59
|
+
* Template strings were compiled from YAML at load time; this restores a shape
|
|
60
|
+
* that AJV can validate without evaluating expressions. When no schema is
|
|
61
|
+
* supplied every compiled value collapses to `""` (the `default` branch of
|
|
62
|
+
* `placeholderForSchema`), matching the schema-unaware strip. */
|
|
63
|
+
export function stripCompiledValues(
|
|
64
|
+
v: unknown,
|
|
65
|
+
schema: Record<string, unknown> = {},
|
|
66
|
+
rootSchema?: Record<string, unknown>,
|
|
67
|
+
): unknown {
|
|
68
|
+
const root = rootSchema ?? schema;
|
|
69
|
+
const resolved = resolveSchemaRef(schema, root);
|
|
70
|
+
|
|
71
|
+
if (isCompiledValue(v)) return placeholderForSchema(resolved);
|
|
72
|
+
if (Array.isArray(v)) {
|
|
73
|
+
const itemSchema = resolveSchemaRef((resolved.items ?? {}) as Record<string, unknown>, root);
|
|
74
|
+
return v.map((item) => stripCompiledValues(item, itemSchema, root));
|
|
75
|
+
}
|
|
76
|
+
if (v !== null && typeof v === "object") {
|
|
77
|
+
const props = collectSchemaProperties(resolved);
|
|
78
|
+
const out: Record<string, unknown> = {};
|
|
79
|
+
for (const [k, val] of Object.entries(v as Record<string, unknown>)) {
|
|
80
|
+
out[k] = stripCompiledValues(val, props[k] ?? {}, root);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
return v;
|
|
85
|
+
}
|
package/dist/event-stream.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Streaming event log for debugging and testing
|
|
3
|
-
* Records all events in JSONL format (one JSON object per line)
|
|
4
|
-
* Useful for observing runtime execution, testing, and debugging
|
|
5
|
-
*/
|
|
6
|
-
export declare class EventStream {
|
|
7
|
-
private filePath;
|
|
8
|
-
private isEnabled;
|
|
9
|
-
constructor(filePath?: string);
|
|
10
|
-
/**
|
|
11
|
-
* Enable event streaming to a file
|
|
12
|
-
*/
|
|
13
|
-
enable(filePath: string): Promise<void>;
|
|
14
|
-
/**
|
|
15
|
-
* Log an event to the stream
|
|
16
|
-
*/
|
|
17
|
-
log(event: string, payload?: any, metadata?: {
|
|
18
|
-
namespace?: string;
|
|
19
|
-
resource?: string;
|
|
20
|
-
kind?: string;
|
|
21
|
-
name?: string;
|
|
22
|
-
}): Promise<void>;
|
|
23
|
-
/**
|
|
24
|
-
* Read all events from the stream as an array
|
|
25
|
-
* Useful for testing
|
|
26
|
-
*/
|
|
27
|
-
readAll(): Promise<Array<Record<string, any>>>;
|
|
28
|
-
/**
|
|
29
|
-
* Filter events by type
|
|
30
|
-
*/
|
|
31
|
-
getEventsByType(eventType: string): Promise<Array<Record<string, any>>>;
|
|
32
|
-
/**
|
|
33
|
-
* Get the file path for the event stream
|
|
34
|
-
*/
|
|
35
|
-
getFilePath(): string;
|
|
36
|
-
/**
|
|
37
|
-
* Check if event streaming is enabled
|
|
38
|
-
*/
|
|
39
|
-
isEnabledStream(): boolean;
|
|
40
|
-
/**
|
|
41
|
-
* Disable event streaming
|
|
42
|
-
*/
|
|
43
|
-
disable(): void;
|
|
44
|
-
}
|
|
45
|
-
//# sourceMappingURL=event-stream.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"event-stream.d.ts","sourceRoot":"","sources":["../src/event-stream.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAkB;gBAEvB,QAAQ,CAAC,EAAE,MAAM;IAK7B;;OAEG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc7C;;OAEG;IACG,GAAG,CACP,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,GAAG,EACb,QAAQ,CAAC,EAAE;QACT,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GACA,OAAO,CAAC,IAAI,CAAC;IAoBhB;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAoBpD;;OAEG;IACG,eAAe,CACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAKtC;;OAEG;IACH,WAAW,IAAI,MAAM;IAIrB;;OAEG;IACH,eAAe,IAAI,OAAO;IAI1B;;OAEG;IACH,OAAO,IAAI,IAAI;CAGhB"}
|
package/dist/event-stream.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs/promises';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
/**
|
|
4
|
-
* Streaming event log for debugging and testing
|
|
5
|
-
* Records all events in JSONL format (one JSON object per line)
|
|
6
|
-
* Useful for observing runtime execution, testing, and debugging
|
|
7
|
-
*/
|
|
8
|
-
export class EventStream {
|
|
9
|
-
constructor(filePath) {
|
|
10
|
-
this.isEnabled = false;
|
|
11
|
-
this.filePath = filePath || '';
|
|
12
|
-
this.isEnabled = !!filePath;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Enable event streaming to a file
|
|
16
|
-
*/
|
|
17
|
-
async enable(filePath) {
|
|
18
|
-
this.filePath = filePath;
|
|
19
|
-
this.isEnabled = true;
|
|
20
|
-
// Ensure directory exists
|
|
21
|
-
const dir = path.dirname(filePath);
|
|
22
|
-
if (dir !== '.' && dir !== '') {
|
|
23
|
-
await fs.mkdir(dir, { recursive: true });
|
|
24
|
-
}
|
|
25
|
-
// Initialize file (truncate if exists)
|
|
26
|
-
await fs.writeFile(filePath, '', 'utf-8');
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Log an event to the stream
|
|
30
|
-
*/
|
|
31
|
-
async log(event, payload, metadata) {
|
|
32
|
-
if (!this.isEnabled) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
const logEntry = {
|
|
36
|
-
timestamp: new Date().toISOString(),
|
|
37
|
-
event,
|
|
38
|
-
payload,
|
|
39
|
-
...metadata,
|
|
40
|
-
};
|
|
41
|
-
try {
|
|
42
|
-
const line = JSON.stringify(logEntry) + '\n';
|
|
43
|
-
await fs.appendFile(this.filePath, line, 'utf-8');
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
console.error('Failed to write event to stream:', error);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Read all events from the stream as an array
|
|
51
|
-
* Useful for testing
|
|
52
|
-
*/
|
|
53
|
-
async readAll() {
|
|
54
|
-
if (!this.isEnabled || !this.filePath) {
|
|
55
|
-
return [];
|
|
56
|
-
}
|
|
57
|
-
try {
|
|
58
|
-
const content = await fs.readFile(this.filePath, 'utf-8');
|
|
59
|
-
if (!content.trim()) {
|
|
60
|
-
return [];
|
|
61
|
-
}
|
|
62
|
-
return content
|
|
63
|
-
.trim()
|
|
64
|
-
.split('\n')
|
|
65
|
-
.map((line) => JSON.parse(line));
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
console.error('Failed to read event stream:', error);
|
|
69
|
-
return [];
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Filter events by type
|
|
74
|
-
*/
|
|
75
|
-
async getEventsByType(eventType) {
|
|
76
|
-
const all = await this.readAll();
|
|
77
|
-
return all.filter((e) => e.event === eventType);
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Get the file path for the event stream
|
|
81
|
-
*/
|
|
82
|
-
getFilePath() {
|
|
83
|
-
return this.filePath;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Check if event streaming is enabled
|
|
87
|
-
*/
|
|
88
|
-
isEnabledStream() {
|
|
89
|
-
return this.isEnabled;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Disable event streaming
|
|
93
|
-
*/
|
|
94
|
-
disable() {
|
|
95
|
-
this.isEnabled = false;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
//# sourceMappingURL=event-stream.js.map
|
package/dist/event-stream.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"event-stream.js","sourceRoot":"","sources":["../src/event-stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B;;;;GAIG;AACH,MAAM,OAAO,WAAW;IAItB,YAAY,QAAiB;QAFrB,cAAS,GAAY,KAAK,CAAC;QAGjC,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,QAAQ,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAM,CAAC,QAAgB;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QAEtB,0BAA0B;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACnC,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;YAC9B,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;QAED,uCAAuC;QACvC,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAAG,CACP,KAAa,EACb,OAAa,EACb,QAKC;QAED,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG;YACf,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,KAAK;YACL,OAAO;YACP,GAAG,QAAQ;SACZ,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;YAC7C,MAAM,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,KAAK,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtC,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC1D,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;gBACpB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,OAAO,OAAO;iBACX,IAAI,EAAE;iBACN,KAAK,CAAC,IAAI,CAAC;iBACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;YACrD,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,eAAe,CACnB,SAAiB;QAEjB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,eAAe;QACb,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED;;OAEG;IACH,OAAO;QACL,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACzB,CAAC;CACF"}
|