@telorun/sdk 0.2.5 → 0.2.7
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/capabilities/invokable.d.ts +2 -2
- package/dist/capabilities/invokable.d.ts.map +1 -1
- package/dist/capability-definition.d.ts +8 -0
- package/dist/capability-definition.d.ts.map +1 -0
- package/dist/capability-definition.js +21 -0
- package/dist/cel-environment.d.ts +3 -0
- package/dist/cel-environment.d.ts.map +1 -0
- package/dist/cel-environment.js +13 -0
- package/dist/compiled-value.d.ts +9 -0
- package/dist/compiled-value.d.ts.map +1 -0
- package/dist/compiled-value.js +3 -0
- package/dist/evaluation-context.d.ts +85 -26
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +227 -89
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/module-context.d.ts +6 -4
- package/dist/module-context.d.ts.map +1 -1
- package/dist/module-context.js +31 -4
- package/dist/ref.d.ts +47 -0
- package/dist/ref.d.ts.map +1 -0
- package/dist/ref.js +11 -0
- package/dist/resource-context.d.ts +19 -8
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-instance.d.ts +4 -9
- package/dist/resource-instance.d.ts.map +1 -1
- package/dist/runtime-error.d.ts +8 -1
- package/dist/runtime-error.d.ts.map +1 -1
- package/dist/types.d.ts +24 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -1
- package/package.json +2 -5
- package/src/capabilities/invokable.ts +2 -2
- package/src/compiled-value.ts +11 -0
- package/src/evaluation-context.ts +283 -101
- package/src/index.ts +2 -0
- package/src/module-context.ts +33 -5
- package/src/ref.ts +61 -0
- package/src/resource-context.ts +19 -8
- package/src/resource-instance.ts +11 -14
- package/src/runtime-error.ts +15 -1
- package/src/types.ts +24 -14
|
@@ -1,23 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isCompiledValue } from "./compiled-value.js";
|
|
2
2
|
import { RuntimeError } from "./types.js";
|
|
3
|
-
const TEMPLATE_REGEX = /\$\{\{\s*([^}]+?)\s*\}\}/g;
|
|
4
|
-
const EXACT_TEMPLATE_REGEX = /^\s*\$\{\{\s*([^}]+?)\s*\}\}\s*$/;
|
|
5
3
|
/** Canonical key for a resource instance: "<module>.<kind>.<name>" */
|
|
6
4
|
export function resourceKey(r) {
|
|
7
5
|
return `${r.kind}.${r.metadata.name}`;
|
|
8
6
|
}
|
|
9
|
-
function redactSecrets(message, secretValues) {
|
|
10
|
-
if (secretValues.size === 0)
|
|
11
|
-
return message;
|
|
12
|
-
const sorted = Array.from(secretValues).sort((a, b) => b.length - a.length);
|
|
13
|
-
let result = message;
|
|
14
|
-
for (const secret of sorted) {
|
|
15
|
-
result = result.split(secret).join("[REDACTED]");
|
|
16
|
-
}
|
|
17
|
-
return result;
|
|
18
|
-
}
|
|
19
7
|
/**
|
|
20
|
-
* Base class for all evaluation contexts. Owns
|
|
8
|
+
* Base class for all evaluation contexts. Owns template
|
|
21
9
|
* expansion, secrets redaction, and the generic resource lifecycle tree.
|
|
22
10
|
*
|
|
23
11
|
* Every EvaluationContext node can:
|
|
@@ -41,8 +29,15 @@ export class EvaluationContext {
|
|
|
41
29
|
state = "Pending";
|
|
42
30
|
/** Resource instances owned by this context node, keyed by resourceKey(). */
|
|
43
31
|
resourceInstances = new Map();
|
|
32
|
+
/** Resources that have been created but not yet initialized (between phases). */
|
|
33
|
+
createdInstances = new Map();
|
|
44
34
|
/** Resources queued for initialization on this context node. */
|
|
45
35
|
pendingResources = [];
|
|
36
|
+
/**
|
|
37
|
+
* Optional hook called between create() and init() for each resource.
|
|
38
|
+
* Set by the kernel to inject live instances into reference fields.
|
|
39
|
+
*/
|
|
40
|
+
preInitHook;
|
|
46
41
|
constructor(source, context, createInstance = async () => null, secretValues, emit) {
|
|
47
42
|
this.source = source;
|
|
48
43
|
this._context = context;
|
|
@@ -53,19 +48,44 @@ export class EvaluationContext {
|
|
|
53
48
|
get createInstance() {
|
|
54
49
|
return this._createInstance;
|
|
55
50
|
}
|
|
51
|
+
/** Called after init() when a resource snapshot is available. Overridden by ModuleContext. */
|
|
52
|
+
onResourceSnapshotted(_name, _snap) { }
|
|
56
53
|
get context() {
|
|
57
54
|
return this._context;
|
|
58
55
|
}
|
|
59
56
|
get secretValues() {
|
|
60
57
|
return this._secretValues;
|
|
61
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Reorder pending resources to match the given name sequence (topo order from Phase 4).
|
|
61
|
+
* Resources not present in `names` are left at the end in their original order.
|
|
62
|
+
* Call before initializeResources() so the create/init sub-phases run in dependency order,
|
|
63
|
+
* guaranteeing that Phase 5 injection always finds initialized dependencies.
|
|
64
|
+
*/
|
|
65
|
+
setInitOrder(names) {
|
|
66
|
+
const rank = new Map(names.map((n, i) => [n, i]));
|
|
67
|
+
this.pendingResources.sort((a, b) => {
|
|
68
|
+
const ra = rank.get(a.metadata.name) ?? Infinity;
|
|
69
|
+
const rb = rank.get(b.metadata.name) ?? Infinity;
|
|
70
|
+
return ra - rb;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
62
73
|
/**
|
|
63
74
|
* Queue a resource manifest for initialization on this context.
|
|
64
75
|
*/
|
|
76
|
+
hasManifest(name) {
|
|
77
|
+
return (this.resourceInstances.has(name) ||
|
|
78
|
+
this.createdInstances.has(name) ||
|
|
79
|
+
this.pendingResources.some((r) => r.metadata.name === name));
|
|
80
|
+
}
|
|
65
81
|
registerManifest(resource) {
|
|
66
82
|
if (!resource.metadata) {
|
|
67
83
|
resource.metadata = { name: `__unnamed_${Math.random().toString(16).slice(2, 8)}` };
|
|
68
84
|
}
|
|
85
|
+
const name = resource.metadata.name;
|
|
86
|
+
if (this.hasManifest(name)) {
|
|
87
|
+
throw new RuntimeError("ERR_DUPLICATE_RESOURCE", `Resource '${name}' is already registered`);
|
|
88
|
+
}
|
|
69
89
|
this.pendingResources.push(resource);
|
|
70
90
|
}
|
|
71
91
|
/**
|
|
@@ -75,58 +95,107 @@ export class EvaluationContext {
|
|
|
75
95
|
spawnChild(child) {
|
|
76
96
|
child.parent = this;
|
|
77
97
|
this.children.push(child);
|
|
98
|
+
// Propagate injection hook so all child contexts (module imports, scopes) participate
|
|
99
|
+
// in Phase 5 injection. createScopeHandle overrides this with an extended version.
|
|
100
|
+
if (this.preInitHook && !child.preInitHook) {
|
|
101
|
+
child.preInitHook = this.preInitHook;
|
|
102
|
+
}
|
|
78
103
|
return child;
|
|
79
104
|
}
|
|
80
105
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
106
|
+
* Interleaved create/init loop.
|
|
107
|
+
*
|
|
108
|
+
* Each pass has two sub-phases run back-to-back:
|
|
109
|
+
* 1. Create sub-phase: call controller.create() for each pending resource that
|
|
110
|
+
* hasn't been created yet. Successful results go into createdInstances.
|
|
111
|
+
* 2. Init sub-phase: call instance.init(ctx) for each created-but-not-inited
|
|
112
|
+
* resource. Successful results go into resourceInstances.
|
|
84
113
|
*
|
|
85
|
-
*
|
|
114
|
+
* Interleaving is necessary because some resources' create() depends on effects
|
|
115
|
+
* produced by other resources' init() (e.g. Kernel.Import.init() runs
|
|
116
|
+
* child.initializeResources() which registers controllers needed by sibling
|
|
117
|
+
* resources' create()). Running both sub-phases each pass lets those effects
|
|
118
|
+
* propagate before the next create attempt.
|
|
119
|
+
*
|
|
120
|
+
* Each resource is created at most once and inited at most once.
|
|
121
|
+
* ERR_VISIBILITY_DENIED is fatal and re-thrown immediately.
|
|
86
122
|
* All other errors are tracked and retried until no progress is made.
|
|
87
123
|
*/
|
|
88
124
|
async initializeResources() {
|
|
89
125
|
const MAX_PASSES = 10;
|
|
90
|
-
let pass = 1;
|
|
91
126
|
const errors = new Map();
|
|
127
|
+
let pass = 1;
|
|
92
128
|
do {
|
|
93
|
-
|
|
129
|
+
let progress = false;
|
|
130
|
+
// Create sub-phase
|
|
94
131
|
for (const resource of [...this.pendingResources]) {
|
|
95
|
-
// const rkey = resourceKey(resource);
|
|
96
|
-
// const displayKey = rkey;
|
|
97
132
|
const name = resource.metadata.name;
|
|
98
|
-
if (this.
|
|
133
|
+
if (this.createdInstances.has(name))
|
|
99
134
|
continue;
|
|
100
135
|
try {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
136
|
+
// const expanded = this.expand(resource) as ResourceManifest;
|
|
137
|
+
// FIXME: Cannot expand it for all resources, needs to be selective
|
|
138
|
+
const created = await this._createInstance(this, resource);
|
|
139
|
+
if (created) {
|
|
140
|
+
this.createdInstances.set(name, {
|
|
141
|
+
resource,
|
|
142
|
+
instance: created.instance,
|
|
143
|
+
ctx: created.ctx,
|
|
144
|
+
});
|
|
145
|
+
const idx = this.pendingResources.findIndex((m) => m.metadata.name === name);
|
|
146
|
+
if (idx >= 0)
|
|
147
|
+
this.pendingResources.splice(idx, 1);
|
|
105
148
|
errors.delete(name);
|
|
149
|
+
progress = true;
|
|
106
150
|
}
|
|
107
151
|
}
|
|
108
152
|
catch (error) {
|
|
109
153
|
if (error instanceof RuntimeError && error.code === "ERR_VISIBILITY_DENIED")
|
|
110
154
|
throw error;
|
|
111
|
-
errors.set(name, error instanceof Error ?
|
|
155
|
+
errors.set(name, error instanceof Error ? error.message : String(error));
|
|
112
156
|
}
|
|
113
157
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
158
|
+
// Init sub-phase
|
|
159
|
+
for (const [name, { resource, instance, ctx }] of [...this.createdInstances]) {
|
|
160
|
+
if (this.resourceInstances.has(name))
|
|
161
|
+
continue;
|
|
162
|
+
try {
|
|
163
|
+
if (this.preInitHook) {
|
|
164
|
+
this.preInitHook(resource, (n) => this.resourceInstances.get(n)?.instance);
|
|
165
|
+
}
|
|
166
|
+
if (instance.init)
|
|
167
|
+
await instance.init(ctx);
|
|
168
|
+
if (instance.snapshot) {
|
|
169
|
+
const snap = await Promise.resolve(instance.snapshot()).catch(() => ({}));
|
|
170
|
+
this.onResourceSnapshotted(name, snap ?? {});
|
|
171
|
+
}
|
|
172
|
+
this.resourceInstances.set(name, { resource, instance });
|
|
173
|
+
this.createdInstances.delete(name);
|
|
174
|
+
errors.delete(name);
|
|
175
|
+
progress = true;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error instanceof RuntimeError && error.code === "ERR_VISIBILITY_DENIED")
|
|
179
|
+
throw error;
|
|
180
|
+
errors.set(name, error instanceof Error ? error.message : String(error));
|
|
181
|
+
}
|
|
119
182
|
}
|
|
120
183
|
pass++;
|
|
121
|
-
if (
|
|
184
|
+
if (!progress)
|
|
122
185
|
break;
|
|
123
186
|
} while (pass <= MAX_PASSES);
|
|
124
|
-
if (this.pendingResources.length > 0) {
|
|
125
|
-
const
|
|
126
|
-
.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
187
|
+
if (this.pendingResources.length > 0 || this.createdInstances.size > 0) {
|
|
188
|
+
const diagnostics = [
|
|
189
|
+
...this.pendingResources.map((r) => ({
|
|
190
|
+
resource: r.metadata.name,
|
|
191
|
+
message: errors.get(r.metadata.name) ?? "Unknown error",
|
|
192
|
+
})),
|
|
193
|
+
...[...this.createdInstances.keys()].map((name) => ({
|
|
194
|
+
resource: name,
|
|
195
|
+
message: errors.get(name) ?? "Unknown error",
|
|
196
|
+
})),
|
|
197
|
+
];
|
|
198
|
+
throw new RuntimeError("ERR_RESOURCE_INITIALIZATION_FAILED", "Unable to process resources", diagnostics);
|
|
130
199
|
}
|
|
131
200
|
this.state = "Initialized";
|
|
132
201
|
}
|
|
@@ -145,13 +214,59 @@ export class EvaluationContext {
|
|
|
145
214
|
child.teardownResources();
|
|
146
215
|
}
|
|
147
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Returns a ScopeHandle that initializes `manifests` in a fresh child context each time
|
|
219
|
+
* `run()` is called, executes the callback with a ScopeContext, and tears down when done.
|
|
220
|
+
*
|
|
221
|
+
* The child inherits the parent's preInitHook (if any), extended so that `getInstance`
|
|
222
|
+
* also checks the parent's already-initialized singleton instances. This lets scoped
|
|
223
|
+
* resources hold x-telo-ref slots pointing to outer resources — those deps are already
|
|
224
|
+
* live when the scope opens.
|
|
225
|
+
*/
|
|
226
|
+
createScopeHandle(manifests) {
|
|
227
|
+
const parent = this;
|
|
228
|
+
return {
|
|
229
|
+
async run(fn) {
|
|
230
|
+
const child = parent.spawnChild(new EvaluationContext(parent.source, parent._context, parent._createInstance, parent._secretValues, parent.emit));
|
|
231
|
+
// Propagate injection hook: extend getInstance to also resolve parent singleton instances.
|
|
232
|
+
if (parent.preInitHook) {
|
|
233
|
+
const parentHook = parent.preInitHook;
|
|
234
|
+
child.preInitHook = (resource, childGetInstance) => {
|
|
235
|
+
parentHook(resource, (name) => childGetInstance(name) ?? parent.resourceInstances.get(name)?.instance);
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
for (const manifest of manifests) {
|
|
240
|
+
child.registerManifest(manifest);
|
|
241
|
+
}
|
|
242
|
+
await child.initializeResources();
|
|
243
|
+
const scope = {
|
|
244
|
+
getInstance(name) {
|
|
245
|
+
const childEntry = child.resourceInstances.get(name);
|
|
246
|
+
if (childEntry)
|
|
247
|
+
return childEntry.instance;
|
|
248
|
+
const parentEntry = parent.resourceInstances.get(name);
|
|
249
|
+
if (parentEntry)
|
|
250
|
+
return parentEntry.instance;
|
|
251
|
+
throw new RuntimeError("ERR_SCOPE_RESOURCE_NOT_FOUND", `Resource '${name}' not found in scope or outer context. Available scoped: ${[...child.resourceInstances.keys()].join(", ")}`);
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
return await fn(scope);
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
await child.teardownResources();
|
|
258
|
+
const idx = parent.children.indexOf(child);
|
|
259
|
+
if (idx >= 0)
|
|
260
|
+
parent.children.splice(idx, 1);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
148
265
|
/**
|
|
149
266
|
* Cascade teardown depth-first through the tree:
|
|
150
267
|
* 1. Tear down child contexts in reverse registration order.
|
|
151
|
-
* 2. Tear down own resource instances in reverse registration order
|
|
152
|
-
*
|
|
153
|
-
* Note: Kernel-level events (e.g. Teardown events) are NOT emitted here —
|
|
154
|
-
* they remain the Kernel's responsibility.
|
|
268
|
+
* 2. Tear down own resource instances in reverse registration order,
|
|
269
|
+
* emitting a Teardown event for each via the injected emit callback.
|
|
155
270
|
*/
|
|
156
271
|
async teardownResources() {
|
|
157
272
|
this.state = "Draining";
|
|
@@ -159,24 +274,30 @@ export class EvaluationContext {
|
|
|
159
274
|
await child.teardownResources();
|
|
160
275
|
}
|
|
161
276
|
const entries = [...this.resourceInstances.entries()].reverse();
|
|
162
|
-
for (const [key, { instance }] of entries) {
|
|
277
|
+
for (const [key, { resource, instance }] of entries) {
|
|
163
278
|
if (instance.teardown)
|
|
164
279
|
await instance.teardown();
|
|
280
|
+
await this.emit(`${resource.kind}.${resource.metadata.name}.Teardown`, {
|
|
281
|
+
resource: { kind: resource.kind, name: resource.metadata.name },
|
|
282
|
+
});
|
|
165
283
|
this.resourceInstances.delete(key);
|
|
166
284
|
}
|
|
167
285
|
this.state = "Teardown";
|
|
168
286
|
}
|
|
287
|
+
transientChild(context) {
|
|
288
|
+
return new EvaluationContext(this.source, { ...this.context, ...context }, this._createInstance, this._secretValues, this.emit);
|
|
289
|
+
}
|
|
169
290
|
/**
|
|
170
291
|
* Invoke a resource by kind and name within this context's resourceInstances.
|
|
171
292
|
* Emits a scoped Invoked event via the injected emit callback after invocation.
|
|
172
293
|
*/
|
|
173
|
-
async invoke(kind, name,
|
|
294
|
+
async invoke(kind, name, inputs) {
|
|
174
295
|
const entry = this.resourceInstances.get(name);
|
|
175
296
|
if (entry) {
|
|
176
297
|
if (typeof entry.instance.invoke !== "function") {
|
|
177
298
|
throw new RuntimeError("ERR_RESOURCE_NOT_INVOKABLE", `Resource ${kind}.${name} does not have an invoke method`);
|
|
178
299
|
}
|
|
179
|
-
const outputs = await entry.instance.invoke(
|
|
300
|
+
const outputs = await entry.instance.invoke(inputs);
|
|
180
301
|
await this.emit(`${kind}.${name}.Invoked`, { outputs });
|
|
181
302
|
return outputs;
|
|
182
303
|
}
|
|
@@ -190,29 +311,12 @@ export class EvaluationContext {
|
|
|
190
311
|
throw new RuntimeError("ERR_RESOURCE_NOT_RUNNABLE", `Resource ${name} is not runnable or not found. Available resources: ${[...this.resourceInstances.keys()].join(", ")}`);
|
|
191
312
|
}
|
|
192
313
|
/**
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*/
|
|
196
|
-
evaluate(expression) {
|
|
197
|
-
try {
|
|
198
|
-
return evaluate(expression, this._context);
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
const raw = error instanceof Error ? error.message : String(error);
|
|
202
|
-
const safe = redactSecrets(raw, this._secretValues);
|
|
203
|
-
throw new Error(`CEL evaluation failed: "${expression}": ${safe}`);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Expand a value that may contain ${{ }} templates.
|
|
208
|
-
* Works recursively over strings, arrays, and objects.
|
|
209
|
-
* Templates whose identifiers are not present in the context are left
|
|
210
|
-
* unchanged (deferred) — they will be resolved at execution time when a
|
|
211
|
-
* richer ExecutionContext is available. All other CEL errors are propagated.
|
|
314
|
+
* Expand a value that may contain precompiled ${{ }} templates.
|
|
315
|
+
* Works recursively over CompiledValues, arrays, and objects.
|
|
212
316
|
*/
|
|
213
317
|
expand(value) {
|
|
214
|
-
if (
|
|
215
|
-
return this.
|
|
318
|
+
if (isCompiledValue(value)) {
|
|
319
|
+
return value.call(this._context);
|
|
216
320
|
}
|
|
217
321
|
if (Array.isArray(value)) {
|
|
218
322
|
return value.map((entry) => this.expand(entry));
|
|
@@ -227,30 +331,64 @@ export class EvaluationContext {
|
|
|
227
331
|
return value;
|
|
228
332
|
}
|
|
229
333
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
334
|
+
* Expand a value using this context merged with additional properties.
|
|
335
|
+
* Equivalent to merge(extraContext).expand(value) without allocating a context object.
|
|
232
336
|
*/
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return new EvaluationContext(this.source, merged, this._createInstance, mergedSecrets, this.emit);
|
|
239
|
-
}
|
|
240
|
-
expandString(value) {
|
|
241
|
-
if (!value.includes("${{")) {
|
|
242
|
-
return value;
|
|
337
|
+
expandWith(value, extraContext) {
|
|
338
|
+
const saved = this._context;
|
|
339
|
+
this._context = Object.assign(Object.create(null), saved, extraContext);
|
|
340
|
+
try {
|
|
341
|
+
return this.expand(value);
|
|
243
342
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
return this.evaluate(exact[1]);
|
|
343
|
+
finally {
|
|
344
|
+
this._context = saved;
|
|
247
345
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Expand specific dot-paths within an object. '**' expands the entire object.
|
|
349
|
+
* Paths listed in excludePaths are left untouched (runtime takes precedence).
|
|
350
|
+
* Always throws if an expression cannot be resolved.
|
|
351
|
+
*/
|
|
352
|
+
expandPaths(value, paths, excludePaths = []) {
|
|
353
|
+
if (paths.includes("**")) {
|
|
354
|
+
const result = {};
|
|
355
|
+
for (const [key, v] of Object.entries(value)) {
|
|
356
|
+
result[key] = isExcluded(key, excludePaths) ? v : this.expand(v);
|
|
252
357
|
}
|
|
253
|
-
return
|
|
254
|
-
}
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
const result = { ...value };
|
|
361
|
+
for (const path of paths) {
|
|
362
|
+
if (isExcluded(path, excludePaths))
|
|
363
|
+
continue;
|
|
364
|
+
const parts = path.split(".");
|
|
365
|
+
const current = getNestedValue(result, parts);
|
|
366
|
+
if (current !== undefined) {
|
|
367
|
+
setNestedValue(result, parts, this.expand(current));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function isExcluded(path, excludePaths) {
|
|
374
|
+
return excludePaths.some((ep) => ep === path || ep === "**" || path.startsWith(ep + ".") || ep.startsWith(path + "."));
|
|
375
|
+
}
|
|
376
|
+
function getNestedValue(obj, parts) {
|
|
377
|
+
let current = obj;
|
|
378
|
+
for (const part of parts) {
|
|
379
|
+
if (current === null || typeof current !== "object")
|
|
380
|
+
return undefined;
|
|
381
|
+
current = current[part];
|
|
382
|
+
}
|
|
383
|
+
return current;
|
|
384
|
+
}
|
|
385
|
+
function setNestedValue(obj, parts, value) {
|
|
386
|
+
let current = obj;
|
|
387
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
388
|
+
const next = current[parts[i]];
|
|
389
|
+
if (next === null || typeof next !== "object")
|
|
390
|
+
return;
|
|
391
|
+
current = next;
|
|
255
392
|
}
|
|
393
|
+
current[parts[parts.length - 1]] = value;
|
|
256
394
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,UAAU,CAAC;AACzB,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/module-context.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Invocable } from "./capabilities/invokable.js";
|
|
2
2
|
import { EmitEvent, EvaluationContext, InstanceFactory } from "./evaluation-context.js";
|
|
3
3
|
/**
|
|
4
4
|
* Persistent, module-scoped context. Three reserved CEL namespaces:
|
|
@@ -14,6 +14,7 @@ import { EmitEvent, EvaluationContext, InstanceFactory } from "./evaluation-cont
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class ModuleContext extends EvaluationContext {
|
|
16
16
|
private targets;
|
|
17
|
+
private readonly _hostEnv?;
|
|
17
18
|
private _variables;
|
|
18
19
|
private _secrets;
|
|
19
20
|
private _resources;
|
|
@@ -21,7 +22,7 @@ export declare class ModuleContext extends EvaluationContext {
|
|
|
21
22
|
readonly importAliases: Map<string, string>;
|
|
22
23
|
/** Maps import alias → allowed kind names. Absent entry = unrestricted (e.g. Kernel). */
|
|
23
24
|
private readonly importedKinds;
|
|
24
|
-
constructor(source: string, variables: Record<string, unknown> | undefined, secrets: Record<string, unknown> | undefined, resources: Record<string, unknown> | undefined, targets: string[] | undefined, createInstance: InstanceFactory | undefined, emit: EmitEvent);
|
|
25
|
+
constructor(source: string, variables: Record<string, unknown> | undefined, secrets: Record<string, unknown> | undefined, resources: Record<string, unknown> | undefined, targets: string[] | undefined, createInstance: InstanceFactory | undefined, emit: EmitEvent, _hostEnv?: Record<string, string | undefined> | undefined);
|
|
25
26
|
get variables(): Record<string, unknown>;
|
|
26
27
|
get secrets(): Record<string, unknown>;
|
|
27
28
|
get resources(): Record<string, unknown>;
|
|
@@ -29,13 +30,14 @@ export declare class ModuleContext extends EvaluationContext {
|
|
|
29
30
|
setTargets(vars: string[]): void;
|
|
30
31
|
setSecrets(secrets: Record<string, unknown>): void;
|
|
31
32
|
setResource(name: string, props: Record<string, unknown>): void;
|
|
33
|
+
protected onResourceSnapshotted(name: string, snap: Record<string, unknown>): void;
|
|
32
34
|
/**
|
|
33
35
|
* Register an imported module under the given alias, with the list of kind names
|
|
34
36
|
* it exports. An empty kinds array means no restriction (used for built-ins like Kernel).
|
|
35
37
|
*/
|
|
36
38
|
registerImport(alias: string, targetModule: string, kinds: string[]): void;
|
|
37
39
|
getInstance(name: string): unknown;
|
|
38
|
-
|
|
40
|
+
getInvocable<TInput = Record<string, any>, TOutput = any>(name: string): Invocable<TInput, TOutput>;
|
|
39
41
|
/**
|
|
40
42
|
* Resolve a fully-qualified kind like "Http.Server" to its real kind "http-server.Server".
|
|
41
43
|
* Splits on the first dot, looks up the prefix in importAliases, validates against
|
|
@@ -44,7 +46,7 @@ export declare class ModuleContext extends EvaluationContext {
|
|
|
44
46
|
*/
|
|
45
47
|
resolveKind(kind: string): string;
|
|
46
48
|
private _rebuildContext;
|
|
47
|
-
invoke(kind: string, name: string,
|
|
49
|
+
invoke<TInputs>(kind: string, name: string, inputs: TInputs): Promise<any>;
|
|
48
50
|
run(name: string): Promise<void>;
|
|
49
51
|
runTargets(): Promise<void>;
|
|
50
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-context.d.ts","sourceRoot":"","sources":["../src/module-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"module-context.d.ts","sourceRoot":"","sources":["../src/module-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAgCxF;;;;;;;;;;;GAWG;AACH,qBAAa,aAAc,SAAQ,iBAAiB;IAgBhD,OAAO,CAAC,OAAO;IAGf,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAlB5B,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,UAAU,CAA0B;IAE5C,gEAAgE;IAChE,QAAQ,CAAC,aAAa,sBAA6B;IAEnD,yFAAyF;IACzF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;gBAG9D,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAK,EACvC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAK,EACrC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAK,EAC/B,OAAO,EAAE,MAAM,EAAE,YAAK,EAC9B,cAAc,EAAE,eAAe,YAAmB,EAClD,IAAI,EAAE,SAAS,EACE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,YAAA;IAShE,IAAI,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEvC;IAED,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAErC;IAED,IAAI,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEvC;IAED,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAKjD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAIhC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAKlD,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;cAK5C,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI3F;;;OAGG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAO1E,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAUlC,YAAY,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,GAAG,EACtD,IAAI,EAAE,MAAM,GACX,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC;IAc7B;;;;;OAKG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAwBjC,OAAO,CAAC,eAAe;IAUR,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC;IAUnF,GAAG,CAAC,IAAI,EAAE,MAAM;IAchB,UAAU;CAKjB"}
|
package/dist/module-context.js
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
import { EvaluationContext } from "./evaluation-context.js";
|
|
2
|
+
/** Wraps process.env so that missing keys return null instead of throwing in CEL.
|
|
3
|
+
* cel-js uses Object.hasOwn(obj, key) before accessing obj[key], so we must
|
|
4
|
+
* intercept getOwnPropertyDescriptor to report every string key as "own". */
|
|
5
|
+
function lenientEnv(env) {
|
|
6
|
+
return new Proxy(env, {
|
|
7
|
+
get(target, key) {
|
|
8
|
+
if (typeof key !== "string")
|
|
9
|
+
return target[key];
|
|
10
|
+
return key in target ? (target[key] ?? null) : null;
|
|
11
|
+
},
|
|
12
|
+
has() {
|
|
13
|
+
return true;
|
|
14
|
+
},
|
|
15
|
+
getOwnPropertyDescriptor(target, key) {
|
|
16
|
+
if (typeof key !== "string")
|
|
17
|
+
return Object.getOwnPropertyDescriptor(target, key);
|
|
18
|
+
const value = key in target ? (target[key] ?? null) : null;
|
|
19
|
+
return { configurable: true, enumerable: true, writable: true, value };
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
2
23
|
function collectSecretValues(secrets) {
|
|
3
24
|
const values = new Set();
|
|
4
25
|
for (const value of Object.values(secrets)) {
|
|
@@ -22,6 +43,7 @@ function collectSecretValues(secrets) {
|
|
|
22
43
|
*/
|
|
23
44
|
export class ModuleContext extends EvaluationContext {
|
|
24
45
|
targets;
|
|
46
|
+
_hostEnv;
|
|
25
47
|
_variables;
|
|
26
48
|
_secrets;
|
|
27
49
|
_resources;
|
|
@@ -29,9 +51,10 @@ export class ModuleContext extends EvaluationContext {
|
|
|
29
51
|
importAliases = new Map();
|
|
30
52
|
/** Maps import alias → allowed kind names. Absent entry = unrestricted (e.g. Kernel). */
|
|
31
53
|
importedKinds = new Map();
|
|
32
|
-
constructor(source, variables = {}, secrets = {}, resources = {}, targets = [], createInstance = async () => null, emit) {
|
|
54
|
+
constructor(source, variables = {}, secrets = {}, resources = {}, targets = [], createInstance = async () => null, emit, _hostEnv) {
|
|
33
55
|
super(source, {}, createInstance, new Set(), emit);
|
|
34
56
|
this.targets = targets;
|
|
57
|
+
this._hostEnv = _hostEnv;
|
|
35
58
|
this._variables = variables;
|
|
36
59
|
this._secrets = secrets;
|
|
37
60
|
this._resources = resources;
|
|
@@ -61,6 +84,9 @@ export class ModuleContext extends EvaluationContext {
|
|
|
61
84
|
this._resources = { ...this._resources, [name]: props };
|
|
62
85
|
this._rebuildContext();
|
|
63
86
|
}
|
|
87
|
+
onResourceSnapshotted(name, snap) {
|
|
88
|
+
this.setResource(name, snap);
|
|
89
|
+
}
|
|
64
90
|
/**
|
|
65
91
|
* Register an imported module under the given alias, with the list of kind names
|
|
66
92
|
* it exports. An empty kinds array means no restriction (used for built-ins like Kernel).
|
|
@@ -78,7 +104,7 @@ export class ModuleContext extends EvaluationContext {
|
|
|
78
104
|
}
|
|
79
105
|
return entry?.instance;
|
|
80
106
|
}
|
|
81
|
-
|
|
107
|
+
getInvocable(name) {
|
|
82
108
|
const instance = this.getInstance(name);
|
|
83
109
|
if (instance &&
|
|
84
110
|
typeof instance === "object" &&
|
|
@@ -118,11 +144,12 @@ export class ModuleContext extends EvaluationContext {
|
|
|
118
144
|
variables: this._variables,
|
|
119
145
|
secrets: this._secrets,
|
|
120
146
|
resources: this._resources,
|
|
147
|
+
...(this._hostEnv ? { env: lenientEnv(this._hostEnv) } : {}),
|
|
121
148
|
};
|
|
122
149
|
this._secretValues = collectSecretValues(this._secrets);
|
|
123
150
|
}
|
|
124
|
-
async invoke(kind, name,
|
|
125
|
-
const result = await super.invoke(kind, name,
|
|
151
|
+
async invoke(kind, name, inputs) {
|
|
152
|
+
const result = await super.invoke(kind, name, inputs);
|
|
126
153
|
const entry = this.resourceInstances.get(name);
|
|
127
154
|
if (entry && typeof entry.instance.snapshot === "function") {
|
|
128
155
|
const snap = await Promise.resolve(entry.instance.snapshot());
|
package/dist/ref.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ResourceInstance } from "./resource-instance.js";
|
|
2
|
+
/** Marker type for x-telo-ref fields. Carries the live instance type at the TypeScript level;
|
|
3
|
+
* at runtime this field holds `{ kind, name }` until Phase 5 injection replaces it.
|
|
4
|
+
* T is a phantom type — any capability interface (Invocable, Runnable, …) or ResourceInstance. */
|
|
5
|
+
export interface KindRef<T = ResourceInstance> {
|
|
6
|
+
readonly kind: string;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly __type?: T;
|
|
9
|
+
}
|
|
10
|
+
/** Marker type for x-telo-scope fields. Has no runtime value — used only as a discriminant
|
|
11
|
+
* for Injected<T> to transform the field to ScopeHandle. */
|
|
12
|
+
export interface ScopeRef {
|
|
13
|
+
readonly __scope: true;
|
|
14
|
+
}
|
|
15
|
+
/** Gives a controller access to the resources initialized within a scope. */
|
|
16
|
+
export interface ScopeContext {
|
|
17
|
+
/** Returns the initialized instance for the given name.
|
|
18
|
+
* Throws synchronously if the name was not declared in the scope —
|
|
19
|
+
* this is always a programming error; all scope members are statically
|
|
20
|
+
* validated in Phase 3 before the kernel ever reaches runtime. */
|
|
21
|
+
getInstance(name: string): ResourceInstance;
|
|
22
|
+
}
|
|
23
|
+
/** Returned by Phase 5 injection in place of an x-telo-scope manifest array.
|
|
24
|
+
* The controller calls run() to open the scope, execute work, and tear it down. */
|
|
25
|
+
export interface ScopeHandle {
|
|
26
|
+
run<T>(fn: (scope: ScopeContext) => Promise<T>): Promise<T>;
|
|
27
|
+
}
|
|
28
|
+
/** Transforms the raw config shape into the controller's view:
|
|
29
|
+
* - KindRef<U> → U (live instance, injected by Phase 5)
|
|
30
|
+
* - KindRef<U>[] → U[] (live instances, injected by Phase 5)
|
|
31
|
+
* - ScopeRef → ScopeHandle
|
|
32
|
+
* - everything else is unchanged */
|
|
33
|
+
export type Injected<T> = {
|
|
34
|
+
[K in keyof T]: T[K] extends KindRef<infer U> ? U : T[K] extends KindRef<infer U>[] ? U[] : NonNullable<T[K]> extends ScopeRef ? ScopeHandle | Exclude<T[K], ScopeRef> : T[K];
|
|
35
|
+
};
|
|
36
|
+
/** Returns a schema node that emits `x-telo-ref` for buildReferenceFieldMap and carries
|
|
37
|
+
* KindRef<T> as its TypeScript type. For TypeBox schemas use Type.Unsafe<KindRef<T>>(Ref(...)).
|
|
38
|
+
*
|
|
39
|
+
* @param ref Canonical ref string: "namespace/module-name#TypeName" or "kernel#TypeName" */
|
|
40
|
+
export declare const Ref: <T = ResourceInstance>(ref: string) => KindRef<T>;
|
|
41
|
+
/** Returns a schema node that emits `x-telo-scope` for buildReferenceFieldMap and carries
|
|
42
|
+
* ScopeRef as its TypeScript type. For TypeBox schemas use Type.Unsafe<ScopeRef>(Scope(...)).
|
|
43
|
+
*
|
|
44
|
+
* @param visibilityPath JSON Pointer(s) (RFC 6901) declaring where x-telo-ref slots within
|
|
45
|
+
* this field can resolve to scoped resources. */
|
|
46
|
+
export declare const Scope: (visibilityPath: string | string[]) => ScopeRef;
|
|
47
|
+
//# sourceMappingURL=ref.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ref.d.ts","sourceRoot":"","sources":["../src/ref.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;;mGAEmG;AACnG,MAAM,WAAW,OAAO,CAAC,CAAC,GAAG,gBAAgB;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;CACrB;AAED;6DAC6D;AAC7D,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;CACxB;AAED,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B;;;uEAGmE;IACnE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAAC;CAC7C;AAED;oFACoF;AACpF,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC7D;AAED;;;;qCAIqC;AACrC,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;KACvB,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GACzC,CAAC,GACD,CAAC,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,GAC7B,CAAC,EAAE,GACH,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,QAAQ,GAChC,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,GACrC,CAAC,CAAC,CAAC,CAAC;CACb,CAAC;AAEF;;;6FAG6F;AAC7F,eAAO,MAAM,GAAG,GAAI,CAAC,GAAG,gBAAgB,EAAE,KAAK,MAAM,KAAG,OAAO,CAAC,CAAC,CACf,CAAC;AAEnD;;;;wEAIwE;AACxE,eAAO,MAAM,KAAK,GAAI,gBAAgB,MAAM,GAAG,MAAM,EAAE,KAAG,QACG,CAAC"}
|