@telorun/kernel 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-definition.d.ts +14 -0
- package/dist/base-definition.d.ts.map +1 -0
- package/dist/base-definition.js +17 -0
- package/dist/base-definition.js.map +1 -0
- package/dist/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/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/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/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/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/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/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/controller-loader.d.ts.map +1 -1
- package/dist/controller-loader.js +2 -1
- package/dist/controller-loader.js.map +1 -1
- package/dist/controller-registry.d.ts +0 -4
- package/dist/controller-registry.d.ts.map +1 -1
- package/dist/controller-registry.js +11 -22
- package/dist/controller-registry.js.map +1 -1
- package/dist/controllers/capability/capability-controller.d.ts +0 -5
- package/dist/controllers/capability/capability-controller.d.ts.map +1 -1
- package/dist/controllers/capability/capability-controller.js +1 -5
- package/dist/controllers/capability/capability-controller.js.map +1 -1
- package/dist/controllers/module/import-controller.d.ts +0 -3
- package/dist/controllers/module/import-controller.d.ts.map +1 -1
- package/dist/controllers/module/import-controller.js +21 -27
- package/dist/controllers/module/import-controller.js.map +1 -1
- package/dist/controllers/module/module-controller.d.ts +0 -59
- package/dist/controllers/module/module-controller.d.ts.map +1 -1
- package/dist/controllers/module/module-controller.js +0 -45
- package/dist/controllers/module/module-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts +3 -4
- package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
- package/dist/controllers/resource-definition/resource-definition-controller.js +6 -8
- package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
- package/dist/controllers/resource-definition/resource-template-controller.d.ts +12 -0
- package/dist/controllers/resource-definition/resource-template-controller.d.ts.map +1 -0
- package/dist/controllers/resource-definition/resource-template-controller.js +112 -0
- package/dist/controllers/resource-definition/resource-template-controller.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +21 -24
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +254 -189
- package/dist/kernel.js.map +1 -1
- package/dist/loader.d.ts +9 -8
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +50 -132
- package/dist/loader.js.map +1 -1
- package/dist/manifest-adapters/http-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/http-adapter.js +3 -1
- package/dist/manifest-adapters/http-adapter.js.map +1 -1
- package/dist/manifest-adapters/local-file-adapter.d.ts +7 -5
- package/dist/manifest-adapters/local-file-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/local-file-adapter.js +21 -26
- package/dist/manifest-adapters/local-file-adapter.js.map +1 -1
- package/dist/manifest-adapters/manifest-adapter.d.ts +2 -0
- package/dist/manifest-adapters/manifest-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/registry-adapter.d.ts.map +1 -1
- package/dist/manifest-adapters/registry-adapter.js +3 -1
- package/dist/manifest-adapters/registry-adapter.js.map +1 -1
- package/dist/manifest-schemas.d.ts +61 -25
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +56 -16
- package/dist/manifest-schemas.js.map +1 -1
- package/dist/resource-context.d.ts +2 -5
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +31 -21
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-valiator.d.ts.map +1 -1
- package/dist/schema-valiator.js +13 -2
- package/dist/schema-valiator.js.map +1 -1
- package/package.json +6 -7
- package/src/controller-loader.ts +2 -1
- package/src/controller-registry.ts +11 -26
- package/src/controllers/module/import-controller.ts +30 -30
- package/src/controllers/module/module-controller.ts +0 -51
- package/src/controllers/resource-definition/resource-definition-controller.ts +14 -15
- package/src/controllers/resource-definition/resource-template-controller.ts +138 -0
- package/src/index.ts +1 -1
- package/src/kernel.ts +313 -224
- package/src/loader.ts +54 -165
- package/src/manifest-adapters/local-file-adapter.ts +24 -31
- package/src/manifest-adapters/manifest-adapter.ts +2 -0
- package/src/manifest-schemas.ts +60 -24
- package/src/resource-context.ts +31 -30
- package/src/schema-valiator.ts +14 -3
- package/src/capabilities/component.yaml +0 -4
- package/src/capabilities/executable.yaml +0 -8
- package/src/capabilities/handler.yaml +0 -4
- package/src/capabilities/listener.yaml +0 -4
- package/src/capabilities/provider.yaml +0 -4
- package/src/capabilities/template.yaml +0 -4
- package/src/capabilities/type.yaml +0 -4
- package/src/controllers/capability/capability-controller.ts +0 -41
- package/src/controllers/module/module.json +0 -48
- package/src/controllers/module/module.yaml +0 -32
- package/src/manifest-adapters/http-adapter.ts +0 -35
- package/src/manifest-adapters/registry-adapter.ts +0 -56
package/src/loader.ts
CHANGED
|
@@ -1,30 +1,17 @@
|
|
|
1
|
+
import { Loader as BaseLoader } from "@telorun/analyzer";
|
|
1
2
|
import { ResourceManifest, RuntimeResource } from "@telorun/sdk";
|
|
2
|
-
import { compile } from "@telorun/yaml-cel-templating";
|
|
3
3
|
import * as path from "path";
|
|
4
|
-
import { HttpAdapter } from "./manifest-adapters/http-adapter.js";
|
|
5
4
|
import { LocalFileAdapter } from "./manifest-adapters/local-file-adapter.js";
|
|
6
|
-
import type { ManifestAdapter, ManifestSourceData } from "./manifest-adapters/manifest-adapter.js";
|
|
7
|
-
import { RegistryAdapter } from "./manifest-adapters/registry-adapter.js";
|
|
8
5
|
import { formatAjvErrors, validateRuntimeResource } from "./manifest-schemas.js";
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
* Loader: Ingests resolved YAML manifests from disk or remote URLs into memory
|
|
12
|
-
*/
|
|
13
|
-
export class Loader {
|
|
7
|
+
export class Loader extends BaseLoader {
|
|
14
8
|
private static projectRoot: string | null = null;
|
|
9
|
+
private readonly localAdapter: LocalFileAdapter;
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
new
|
|
19
|
-
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
private getAdapter(pathOrUrl: string): ManifestAdapter {
|
|
23
|
-
const adapter = this.adapters.find((a) => a.supports(pathOrUrl));
|
|
24
|
-
if (!adapter) {
|
|
25
|
-
throw new Error(`No manifest adapter found for: ${pathOrUrl}`);
|
|
26
|
-
}
|
|
27
|
-
return adapter;
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
this.localAdapter = new LocalFileAdapter();
|
|
14
|
+
this.register(this.localAdapter);
|
|
28
15
|
}
|
|
29
16
|
|
|
30
17
|
private static ensureProjectRoot(baseDir: string): void {
|
|
@@ -34,15 +21,39 @@ export class Loader {
|
|
|
34
21
|
}
|
|
35
22
|
|
|
36
23
|
resolvePath(base: string, relative: string): string {
|
|
37
|
-
return this.
|
|
24
|
+
return this.localAdapter.resolveRelative(base, relative);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a path-or-URL to its canonical file source URL.
|
|
29
|
+
* If the path refers to a directory, returns the URL of module.yaml inside it.
|
|
30
|
+
*/
|
|
31
|
+
async resolveEntryPoint(pathOrUrl: string): Promise<string> {
|
|
32
|
+
const { source } = await this.localAdapter.read(pathOrUrl);
|
|
33
|
+
return source;
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
async loadDirectory(pathOrUrl: string): Promise<ResourceManifest[]> {
|
|
41
|
-
const
|
|
42
|
-
|
|
37
|
+
const sources = await this.localAdapter.readAll(pathOrUrl);
|
|
38
|
+
const firstPath = sources[0] ? new URL(sources[0]).pathname : process.cwd();
|
|
39
|
+
Loader.ensureProjectRoot(path.dirname(firstPath));
|
|
43
40
|
const resources: RuntimeResource[] = [];
|
|
44
|
-
for (const
|
|
45
|
-
await this.
|
|
41
|
+
for (const source of sources) {
|
|
42
|
+
const manifests = await this.loadModule(source);
|
|
43
|
+
for (const m of manifests) {
|
|
44
|
+
if (!m.metadata?.name) continue;
|
|
45
|
+
const resource = m as RuntimeResource;
|
|
46
|
+
if (!validateRuntimeResource(resource)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Resource validation failed for ${m.kind}.${m.metadata.name}: ${formatAjvErrors(validateRuntimeResource.errors)}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const filePath = new URL(m.metadata.source as string).pathname;
|
|
52
|
+
const uriBase = `file://localhost${filePath.replace(/\\/g, "/")}`;
|
|
53
|
+
resource.metadata.uri = `${uriBase}#${m.kind}.${m.metadata.name as string}`;
|
|
54
|
+
resource.metadata.generationDepth = 0;
|
|
55
|
+
resources.push(resource);
|
|
56
|
+
}
|
|
46
57
|
}
|
|
47
58
|
return this.orderResourcesByKindDependencies(resources);
|
|
48
59
|
}
|
|
@@ -56,146 +67,35 @@ export class Loader {
|
|
|
56
67
|
throw new Error("Base URL is required to load target manifest");
|
|
57
68
|
}
|
|
58
69
|
const url = new URL(pathOrUrl, baseUrl).toString();
|
|
59
|
-
const file = await this.getAdapter(url).read(url);
|
|
60
70
|
if (!Loader.projectRoot) {
|
|
61
|
-
Loader.ensureProjectRoot(
|
|
71
|
+
Loader.ensureProjectRoot(path.dirname(new URL(url).pathname));
|
|
62
72
|
}
|
|
63
|
-
|
|
64
|
-
const resolved: ResourceManifest[] = [];
|
|
65
|
-
for (const rawDoc of file.documents) {
|
|
66
|
-
let compiled: any;
|
|
67
|
-
try {
|
|
68
|
-
compiled = compile(rawDoc, { context: compileContext });
|
|
69
|
-
} catch (error) {
|
|
70
|
-
throw new Error(
|
|
71
|
-
`Failed to compile manifest in ${file.source}: ${error instanceof Error ? error.message : String(error)}`,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
const compiledDocs = Array.isArray(compiled) ? compiled : [compiled];
|
|
75
|
-
for (const manifest of compiledDocs) {
|
|
76
|
-
if (manifest === null) {
|
|
77
|
-
// Ignore empty documents
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
const resource: ResourceManifest = {
|
|
81
|
-
...manifest,
|
|
82
|
-
metadata: {
|
|
83
|
-
...manifest.metadata,
|
|
84
|
-
source: file.source,
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
resolved.push(resource);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Auto-assign metadata.module from the file's Kernel.Module declaration to
|
|
92
|
-
// any resource that doesn't explicitly declare one. This matches the implicit
|
|
93
|
-
// convention that resources in the same file belong to the declared module.
|
|
94
|
-
const moduleName = resolved.find((m) => m.kind === "Kernel.Module")?.metadata?.name;
|
|
95
|
-
if (moduleName) {
|
|
96
|
-
for (const manifest of resolved) {
|
|
97
|
-
if (manifest.kind !== "Kernel.Module" && !manifest.metadata?.module) {
|
|
98
|
-
manifest.metadata = { ...manifest.metadata, module: moduleName };
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return resolved;
|
|
73
|
+
return this.loadModule(url, { compile: true });
|
|
104
74
|
}
|
|
105
75
|
|
|
106
|
-
private async processFile(
|
|
107
|
-
file: ManifestSourceData,
|
|
108
|
-
resources: RuntimeResource[],
|
|
109
|
-
compileContext: Record<string, unknown> = {},
|
|
110
|
-
): Promise<void> {
|
|
111
|
-
const documents = file.documents;
|
|
112
|
-
|
|
113
|
-
for (const rawDoc of documents) {
|
|
114
|
-
let compiled: any;
|
|
115
|
-
try {
|
|
116
|
-
compiled = compile(rawDoc, { context: compileContext });
|
|
117
|
-
} catch (error) {
|
|
118
|
-
throw new Error(
|
|
119
|
-
`Failed to compile manifest in ${file.source}: ${error instanceof Error ? error.message : String(error)}`,
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
const compiledDocs = Array.isArray(compiled) ? compiled : [compiled];
|
|
123
|
-
for (const doc of compiledDocs) {
|
|
124
|
-
const resource = this.normalizeResource(doc);
|
|
125
|
-
if (!resource) {
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (!validateRuntimeResource(resource)) {
|
|
129
|
-
const kind = (resource as any).kind;
|
|
130
|
-
const name = (resource as any).metadata?.name;
|
|
131
|
-
throw new Error(
|
|
132
|
-
`Resource validation failed for ${kind}.${name}: ${formatAjvErrors(validateRuntimeResource.errors)}`,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const { kind, name } = resource.metadata;
|
|
137
|
-
resource.metadata.source = file.source;
|
|
138
|
-
resource.metadata.uri = `${file.uriBase}#${kind}.${name}`;
|
|
139
|
-
resource.metadata.generationDepth = 0;
|
|
140
|
-
|
|
141
|
-
resources.push(resource);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
private normalizeResource(doc: any): RuntimeResource | null {
|
|
147
|
-
if (!doc || typeof doc !== "object" || typeof doc.kind !== "string") {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Already in correct format
|
|
152
|
-
if (doc.metadata && typeof doc.metadata === "object" && typeof doc.metadata.name === "string") {
|
|
153
|
-
return doc as RuntimeResource;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Validation handled by TypeBox + Ajv schemas.
|
|
160
|
-
|
|
161
76
|
private orderResourcesByKindDependencies(resources: RuntimeResource[]): RuntimeResource[] {
|
|
162
|
-
if (resources.length <= 1)
|
|
163
|
-
return resources;
|
|
164
|
-
}
|
|
77
|
+
if (resources.length <= 1) return resources;
|
|
165
78
|
|
|
166
79
|
const indicesByName = new Map<string, number[]>();
|
|
167
|
-
for (let i = 0; i < resources.length; i
|
|
80
|
+
for (let i = 0; i < resources.length; i++) {
|
|
168
81
|
const name = resources[i]?.metadata?.name;
|
|
169
|
-
if (!name)
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
82
|
+
if (!name) continue;
|
|
172
83
|
const list = indicesByName.get(name);
|
|
173
|
-
if (list)
|
|
174
|
-
|
|
175
|
-
} else {
|
|
176
|
-
indicesByName.set(name, [i]);
|
|
177
|
-
}
|
|
84
|
+
if (list) list.push(i);
|
|
85
|
+
else indicesByName.set(name, [i]);
|
|
178
86
|
}
|
|
179
87
|
|
|
180
88
|
const edges = new Map<number, Set<number>>();
|
|
181
89
|
const indegree = new Map<number, number>();
|
|
182
|
-
for (let i = 0; i < resources.length; i
|
|
183
|
-
indegree.set(i, 0);
|
|
184
|
-
}
|
|
90
|
+
for (let i = 0; i < resources.length; i++) indegree.set(i, 0);
|
|
185
91
|
|
|
186
|
-
for (let i = 0; i < resources.length; i
|
|
92
|
+
for (let i = 0; i < resources.length; i++) {
|
|
187
93
|
const kind = resources[i]?.kind;
|
|
188
|
-
if (!kind)
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
94
|
+
if (!kind) continue;
|
|
191
95
|
const definers = indicesByName.get(kind);
|
|
192
|
-
if (!definers)
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
96
|
+
if (!definers) continue;
|
|
195
97
|
for (const definerIndex of definers) {
|
|
196
|
-
if (definerIndex === i)
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
98
|
+
if (definerIndex === i) continue;
|
|
199
99
|
let set = edges.get(definerIndex);
|
|
200
100
|
if (!set) {
|
|
201
101
|
set = new Set();
|
|
@@ -209,10 +109,8 @@ export class Loader {
|
|
|
209
109
|
}
|
|
210
110
|
|
|
211
111
|
const ready: number[] = [];
|
|
212
|
-
for (let i = 0; i < resources.length; i
|
|
213
|
-
if ((indegree.get(i) || 0) === 0)
|
|
214
|
-
ready.push(i);
|
|
215
|
-
}
|
|
112
|
+
for (let i = 0; i < resources.length; i++) {
|
|
113
|
+
if ((indegree.get(i) || 0) === 0) ready.push(i);
|
|
216
114
|
}
|
|
217
115
|
ready.sort((a, b) => a - b);
|
|
218
116
|
|
|
@@ -221,25 +119,16 @@ export class Loader {
|
|
|
221
119
|
const index = ready.shift() as number;
|
|
222
120
|
ordered.push(resources[index]);
|
|
223
121
|
const next = edges.get(index);
|
|
224
|
-
if (!next)
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
122
|
+
if (!next) continue;
|
|
227
123
|
for (const dependent of next) {
|
|
228
124
|
const count = (indegree.get(dependent) || 0) - 1;
|
|
229
125
|
indegree.set(dependent, count);
|
|
230
|
-
if (count === 0)
|
|
231
|
-
ready.push(dependent);
|
|
232
|
-
}
|
|
126
|
+
if (count === 0) ready.push(dependent);
|
|
233
127
|
}
|
|
234
|
-
if (ready.length > 1)
|
|
235
|
-
ready.sort((a, b) => a - b);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (ordered.length !== resources.length) {
|
|
240
|
-
throw new Error("Resource dependency cycle detected");
|
|
128
|
+
if (ready.length > 1) ready.sort((a, b) => a - b);
|
|
241
129
|
}
|
|
242
130
|
|
|
131
|
+
if (ordered.length !== resources.length) throw new Error("Resource dependency cycle detected");
|
|
243
132
|
return ordered;
|
|
244
133
|
}
|
|
245
134
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import type { ManifestAdapter } from "@telorun/analyzer";
|
|
1
2
|
import * as fs from "fs/promises";
|
|
2
|
-
import * as yaml from "js-yaml";
|
|
3
3
|
import * as path from "path";
|
|
4
|
-
import type { ManifestAdapter, ManifestSourceData } from "./manifest-adapter.js";
|
|
5
4
|
|
|
6
5
|
export class LocalFileAdapter implements ManifestAdapter {
|
|
7
6
|
supports(pathOrUrl: string): boolean {
|
|
@@ -9,58 +8,52 @@ export class LocalFileAdapter implements ManifestAdapter {
|
|
|
9
8
|
pathOrUrl.startsWith("file://") ||
|
|
10
9
|
pathOrUrl.startsWith("/") ||
|
|
11
10
|
pathOrUrl.startsWith("./") ||
|
|
12
|
-
pathOrUrl.startsWith("../")
|
|
11
|
+
pathOrUrl.startsWith("../") ||
|
|
12
|
+
(!pathOrUrl.includes("://") && !pathOrUrl.includes("@"))
|
|
13
13
|
);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
async read(pathOrUrl: string): Promise<
|
|
16
|
+
async read(pathOrUrl: string): Promise<{ text: string; source: string }> {
|
|
17
17
|
const normalizedPath = pathOrUrl.startsWith("file://")
|
|
18
|
-
? pathOrUrl.
|
|
18
|
+
? new URL(pathOrUrl).pathname
|
|
19
19
|
: pathOrUrl;
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
20
|
+
const resolvedPath = path.resolve(normalizedPath);
|
|
21
|
+
const stat = await fs.stat(resolvedPath);
|
|
22
|
+
const filePath = stat.isDirectory() ? path.join(resolvedPath, "module.yaml") : resolvedPath;
|
|
23
|
+
const text = await fs.readFile(filePath, "utf-8");
|
|
24
|
+
return { text, source: `file://${filePath}` };
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
async readAll(pathOrUrl: string): Promise<
|
|
27
|
+
async readAll(pathOrUrl: string): Promise<string[]> {
|
|
26
28
|
const normalizedPath = pathOrUrl.startsWith("file://")
|
|
27
|
-
? pathOrUrl.
|
|
29
|
+
? new URL(pathOrUrl).pathname
|
|
28
30
|
: pathOrUrl;
|
|
29
|
-
const
|
|
31
|
+
const resolvedPath = path.resolve(normalizedPath);
|
|
32
|
+
const stat = await fs.stat(resolvedPath);
|
|
30
33
|
if (stat.isDirectory()) {
|
|
31
|
-
|
|
32
|
-
await this.collectYamlFiles(normalizedPath, results);
|
|
33
|
-
return results;
|
|
34
|
+
return this.collectYamlSources(resolvedPath);
|
|
34
35
|
}
|
|
35
|
-
return [
|
|
36
|
+
return [`file://${resolvedPath}`];
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
resolveRelative(base: string, relative: string): string {
|
|
39
|
-
const basePath = base.startsWith("file://") ? base.
|
|
40
|
-
|
|
40
|
+
const basePath = base.startsWith("file://") ? new URL(base).pathname : base;
|
|
41
|
+
const baseDir = basePath.endsWith("/") ? basePath : path.dirname(basePath);
|
|
42
|
+
return `file://${path.resolve(baseDir, relative)}`;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
private async
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
documents: yaml.loadAll(content),
|
|
48
|
-
source: `file://${filePath}`,
|
|
49
|
-
baseDir: path.dirname(filePath),
|
|
50
|
-
uriBase: `file://localhost${filePath.replace(/\\/g, "/")}`,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
private async collectYamlFiles(dirPath: string, results: ManifestSourceData[]): Promise<void> {
|
|
45
|
+
private async collectYamlSources(dirPath: string): Promise<string[]> {
|
|
46
|
+
const sources: string[] = [];
|
|
55
47
|
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
56
48
|
for (const entry of entries) {
|
|
57
49
|
const fullPath = path.join(dirPath, entry.name);
|
|
58
50
|
if (entry.isDirectory()) {
|
|
59
|
-
await this.
|
|
51
|
+
sources.push(...(await this.collectYamlSources(fullPath)));
|
|
60
52
|
} else if (entry.isFile() && this.isYamlFile(entry.name)) {
|
|
61
|
-
|
|
53
|
+
sources.push(`file://${fullPath}`);
|
|
62
54
|
}
|
|
63
55
|
}
|
|
56
|
+
return sources;
|
|
64
57
|
}
|
|
65
58
|
|
|
66
59
|
private isYamlFile(filename: string): boolean {
|
package/src/manifest-schemas.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import AjvModule
|
|
2
|
+
import AjvModule from "ajv";
|
|
3
3
|
import addFormats from "ajv-formats";
|
|
4
4
|
const Ajv = AjvModule.default ?? AjvModule;
|
|
5
5
|
|
|
@@ -11,23 +11,62 @@ export const RuntimeResourceSchema = Type.Object(
|
|
|
11
11
|
{ additionalProperties: true },
|
|
12
12
|
);
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
module: Type.Optional(Type.String()),
|
|
21
|
-
},
|
|
22
|
-
{ additionalProperties: true },
|
|
23
|
-
),
|
|
24
|
-
schema: Type.Object({}, { additionalProperties: true }),
|
|
25
|
-
capabilities: Type.Array(Type.String(), { minItems: 1 }),
|
|
26
|
-
events: Type.Optional(Type.Array(Type.String())),
|
|
27
|
-
controllers: Type.Optional(Type.Array(Type.String())),
|
|
14
|
+
const metadataSchema = {
|
|
15
|
+
type: "object",
|
|
16
|
+
required: ["name"],
|
|
17
|
+
properties: {
|
|
18
|
+
name: { type: "string" },
|
|
19
|
+
module: { type: "string" },
|
|
28
20
|
},
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
additionalProperties: true,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const baseDefinition = {
|
|
25
|
+
type: "object",
|
|
26
|
+
required: ["kind", "metadata"],
|
|
27
|
+
properties: {
|
|
28
|
+
kind: { const: "Kernel.Definition" },
|
|
29
|
+
metadata: metadataSchema,
|
|
30
|
+
capability: { type: "string" },
|
|
31
|
+
schema: { type: "object", additionalProperties: true },
|
|
32
|
+
controllers: { type: "array", items: { type: "string" } },
|
|
33
|
+
},
|
|
34
|
+
unevaluatedProperties: false,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const KNOWN_CAPABILITIES = [
|
|
38
|
+
"Kernel.Service",
|
|
39
|
+
"Kernel.Runnable",
|
|
40
|
+
"Kernel.Invocable",
|
|
41
|
+
"Kernel.Provider",
|
|
42
|
+
"Kernel.Type",
|
|
43
|
+
"Kernel.Mount",
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
46
|
+
export const ResourceDefinitionSchema = {
|
|
47
|
+
...baseDefinition,
|
|
48
|
+
oneOf: [
|
|
49
|
+
{ required: ["capability"], properties: { capability: { const: "Kernel.Service" } } },
|
|
50
|
+
{ required: ["capability"], properties: { capability: { const: "Kernel.Runnable" } } },
|
|
51
|
+
{ required: ["capability"], properties: { capability: { const: "Kernel.Invocable" } } },
|
|
52
|
+
{ required: ["capability"], properties: { capability: { const: "Kernel.Provider" } } },
|
|
53
|
+
{ required: ["capability"], properties: { capability: { const: "Kernel.Type" } } },
|
|
54
|
+
{
|
|
55
|
+
required: ["capability"],
|
|
56
|
+
properties: {
|
|
57
|
+
capability: { const: "Kernel.Mount" },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
// Unknown/absent capability: open schema for third-party extensibility
|
|
61
|
+
{
|
|
62
|
+
not: {
|
|
63
|
+
required: ["capability"],
|
|
64
|
+
properties: { capability: { enum: KNOWN_CAPABILITIES } },
|
|
65
|
+
},
|
|
66
|
+
unevaluatedProperties: true,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
31
70
|
|
|
32
71
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
33
72
|
addFormats.default(ajv);
|
|
@@ -35,15 +74,12 @@ addFormats.default(ajv);
|
|
|
35
74
|
export const validateRuntimeResource = ajv.compile(RuntimeResourceSchema);
|
|
36
75
|
export const validateResourceDefinition = ajv.compile(ResourceDefinitionSchema);
|
|
37
76
|
|
|
38
|
-
export function formatAjvErrors(errors:
|
|
39
|
-
if (!errors || errors.length === 0)
|
|
40
|
-
return "Unknown schema error";
|
|
41
|
-
}
|
|
77
|
+
export function formatAjvErrors(errors: any[] | null | undefined): string {
|
|
78
|
+
if (!errors || errors.length === 0) return "Unknown schema error";
|
|
42
79
|
return errors
|
|
43
80
|
.map((err) => {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
return `${path} ${message}`;
|
|
81
|
+
const p = err.instancePath || "/";
|
|
82
|
+
return `${p} ${err.message ?? "is invalid"}`;
|
|
47
83
|
})
|
|
48
84
|
.join("; ");
|
|
49
85
|
}
|
package/src/resource-context.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ResourceContext,
|
|
6
6
|
RuntimeError,
|
|
7
7
|
RuntimeResource,
|
|
8
|
+
isCompiledValue,
|
|
8
9
|
} from "@telorun/sdk";
|
|
9
10
|
import AjvModule from "ajv";
|
|
10
11
|
import addFormats from "ajv-formats";
|
|
@@ -42,8 +43,13 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
validateSchema(value: any, schema: any) {
|
|
45
|
-
const ajv = new Ajv(
|
|
46
|
+
const ajv = new Ajv({
|
|
47
|
+
removeAdditional: true,
|
|
48
|
+
});
|
|
46
49
|
addFormats.default(ajv);
|
|
50
|
+
for (const kw of ["x-telo-ref", "x-telo-scope", "x-telo-context", "x-telo-schema-from"]) {
|
|
51
|
+
ajv.addKeyword(kw);
|
|
52
|
+
}
|
|
47
53
|
const validate = ajv.compile(
|
|
48
54
|
"type" in schema && typeof schema.type === "string"
|
|
49
55
|
? schema
|
|
@@ -54,17 +60,17 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
54
60
|
additionalProperties: false,
|
|
55
61
|
},
|
|
56
62
|
);
|
|
57
|
-
const isValid = validate(value);
|
|
63
|
+
const isValid = validate(stripCompiledValues(value));
|
|
58
64
|
if (!isValid) {
|
|
59
65
|
throw new RuntimeError(
|
|
60
66
|
"ERR_INVALID_VALUE",
|
|
61
|
-
`[${this.metadata.name}] Invalid value
|
|
67
|
+
`[${this.metadata.name}] Invalid value. Error: ${formatAjvErrors(validate.errors)}`,
|
|
62
68
|
);
|
|
63
69
|
}
|
|
64
70
|
}
|
|
65
71
|
|
|
66
|
-
invoke(kind: string, name: string,
|
|
67
|
-
return this.moduleContext.invoke(kind, name,
|
|
72
|
+
invoke<TInputs>(kind: string, name: string, inputs: TInputs): Promise<any> {
|
|
73
|
+
return this.moduleContext.invoke(kind, name, inputs);
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
async run(name: string) {
|
|
@@ -112,7 +118,7 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
112
118
|
(k) => k !== "kind" && k !== "name" && k !== "metadata",
|
|
113
119
|
);
|
|
114
120
|
|
|
115
|
-
if (definitionKeys.length > 0) {
|
|
121
|
+
if (definitionKeys.length > 0 && !this.moduleContext.hasManifest(name)) {
|
|
116
122
|
this.registerManifest({
|
|
117
123
|
...resource,
|
|
118
124
|
metadata: {
|
|
@@ -157,18 +163,6 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
157
163
|
this.kernel.registerResourceDefinition(def);
|
|
158
164
|
}
|
|
159
165
|
|
|
160
|
-
registerCapability(name: string, schema?: Record<string, any>): void {
|
|
161
|
-
this.kernel.registerCapability(name, schema);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
isCapabilityRegistered(name: string): boolean {
|
|
165
|
-
return this.kernel.isCapabilityRegistered(name);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
getCapabilitySchema(name: string): Record<string, any> | null | undefined {
|
|
169
|
-
return this.kernel.getCapabilitySchema(name);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
166
|
on(event: string, handler: (payload?: any) => void | Promise<void>): void {
|
|
173
167
|
this.kernel.on(event, handler);
|
|
174
168
|
}
|
|
@@ -193,18 +187,8 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
193
187
|
this.kernel.requestExit(code);
|
|
194
188
|
}
|
|
195
189
|
|
|
196
|
-
evaluateCel(expression: string, context: Record<string, any>): unknown {
|
|
197
|
-
return new EvaluationContext(
|
|
198
|
-
this.moduleContext.source,
|
|
199
|
-
context,
|
|
200
|
-
undefined,
|
|
201
|
-
new Set(),
|
|
202
|
-
this.emit,
|
|
203
|
-
).evaluate(expression);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
190
|
expandValue(value: any, context: Record<string, any>) {
|
|
207
|
-
return this.moduleContext.
|
|
191
|
+
return this.moduleContext.expandWith(value, context);
|
|
208
192
|
}
|
|
209
193
|
|
|
210
194
|
async emitEvent(event: string, payload?: any) {
|
|
@@ -240,6 +224,10 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
240
224
|
return this.moduleContext.spawnChild(child);
|
|
241
225
|
}
|
|
242
226
|
|
|
227
|
+
transientChild(context: Record<string, any>): EvaluationContext {
|
|
228
|
+
return this.moduleContext.transientChild(context);
|
|
229
|
+
}
|
|
230
|
+
|
|
243
231
|
/**
|
|
244
232
|
* Create a temporary child context, queue manifests on it, run a function,
|
|
245
233
|
* then tear down the child context and its resources.
|
|
@@ -259,8 +247,21 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
259
247
|
await child.initializeResources();
|
|
260
248
|
return await Promise.resolve(fn() as any);
|
|
261
249
|
} finally {
|
|
262
|
-
await
|
|
250
|
+
await child.teardownResources();
|
|
263
251
|
}
|
|
264
252
|
})() as unknown as T;
|
|
265
253
|
}
|
|
266
254
|
}
|
|
255
|
+
|
|
256
|
+
function stripCompiledValues(v: unknown): unknown {
|
|
257
|
+
if (isCompiledValue(v)) return "";
|
|
258
|
+
if (Array.isArray(v)) return v.map(stripCompiledValues);
|
|
259
|
+
if (v !== null && typeof v === "object") {
|
|
260
|
+
const out: Record<string, unknown> = {};
|
|
261
|
+
for (const [k, val] of Object.entries(v as Record<string, unknown>)) {
|
|
262
|
+
out[k] = stripCompiledValues(val);
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
return v;
|
|
267
|
+
}
|
package/src/schema-valiator.ts
CHANGED
|
@@ -28,7 +28,7 @@ export class SchemaValidator {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
compile(schema: any): DataValidator {
|
|
31
|
-
const
|
|
31
|
+
const normalized =
|
|
32
32
|
"type" in schema && typeof schema.type === "string"
|
|
33
33
|
? schema
|
|
34
34
|
: {
|
|
@@ -36,8 +36,19 @@ export class SchemaValidator {
|
|
|
36
36
|
properties: schema,
|
|
37
37
|
required: Object.keys(schema),
|
|
38
38
|
additionalProperties: false,
|
|
39
|
-
}
|
|
40
|
-
|
|
39
|
+
};
|
|
40
|
+
const injected =
|
|
41
|
+
normalized.additionalProperties === false
|
|
42
|
+
? {
|
|
43
|
+
...normalized,
|
|
44
|
+
properties: {
|
|
45
|
+
kind: { type: "string" },
|
|
46
|
+
metadata: { type: "object" },
|
|
47
|
+
...normalized.properties,
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
: normalized;
|
|
51
|
+
const validate = this.ajv.compile(injected);
|
|
41
52
|
|
|
42
53
|
return {
|
|
43
54
|
validate: (data: any) => {
|