@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.
Files changed (137) hide show
  1. package/dist/base-definition.d.ts +14 -0
  2. package/dist/base-definition.d.ts.map +1 -0
  3. package/dist/base-definition.js +17 -0
  4. package/dist/base-definition.js.map +1 -0
  5. package/dist/capabilities/component.d.ts +3 -0
  6. package/dist/capabilities/component.d.ts.map +1 -0
  7. package/dist/capabilities/component.js +4 -0
  8. package/dist/capabilities/component.js.map +1 -0
  9. package/dist/capabilities/executable.d.ts +3 -0
  10. package/dist/capabilities/executable.d.ts.map +1 -0
  11. package/dist/capabilities/executable.js +5 -0
  12. package/dist/capabilities/executable.js.map +1 -0
  13. package/dist/capabilities/handler.d.ts +3 -0
  14. package/dist/capabilities/handler.d.ts.map +1 -0
  15. package/dist/capabilities/handler.js +4 -0
  16. package/dist/capabilities/handler.js.map +1 -0
  17. package/dist/capabilities/invokable.d.ts +3 -0
  18. package/dist/capabilities/invokable.d.ts.map +1 -0
  19. package/dist/capabilities/invokable.js +5 -0
  20. package/dist/capabilities/invokable.js.map +1 -0
  21. package/dist/capabilities/listener.d.ts +3 -0
  22. package/dist/capabilities/listener.d.ts.map +1 -0
  23. package/dist/capabilities/listener.js +5 -0
  24. package/dist/capabilities/listener.js.map +1 -0
  25. package/dist/capabilities/mount.d.ts +3 -0
  26. package/dist/capabilities/mount.d.ts.map +1 -0
  27. package/dist/capabilities/mount.js +5 -0
  28. package/dist/capabilities/mount.js.map +1 -0
  29. package/dist/capabilities/provider.d.ts +3 -0
  30. package/dist/capabilities/provider.d.ts.map +1 -0
  31. package/dist/capabilities/provider.js +8 -0
  32. package/dist/capabilities/provider.js.map +1 -0
  33. package/dist/capabilities/runnable.d.ts +3 -0
  34. package/dist/capabilities/runnable.d.ts.map +1 -0
  35. package/dist/capabilities/runnable.js +5 -0
  36. package/dist/capabilities/runnable.js.map +1 -0
  37. package/dist/capabilities/service.d.ts +3 -0
  38. package/dist/capabilities/service.d.ts.map +1 -0
  39. package/dist/capabilities/service.js +5 -0
  40. package/dist/capabilities/service.js.map +1 -0
  41. package/dist/capabilities/template.d.ts +3 -0
  42. package/dist/capabilities/template.d.ts.map +1 -0
  43. package/dist/capabilities/template.js +5 -0
  44. package/dist/capabilities/template.js.map +1 -0
  45. package/dist/capabilities/type.d.ts +3 -0
  46. package/dist/capabilities/type.d.ts.map +1 -0
  47. package/dist/capabilities/type.js +5 -0
  48. package/dist/capabilities/type.js.map +1 -0
  49. package/dist/controller-loader.d.ts.map +1 -1
  50. package/dist/controller-loader.js +2 -1
  51. package/dist/controller-loader.js.map +1 -1
  52. package/dist/controller-registry.d.ts +0 -4
  53. package/dist/controller-registry.d.ts.map +1 -1
  54. package/dist/controller-registry.js +11 -22
  55. package/dist/controller-registry.js.map +1 -1
  56. package/dist/controllers/capability/capability-controller.d.ts +0 -5
  57. package/dist/controllers/capability/capability-controller.d.ts.map +1 -1
  58. package/dist/controllers/capability/capability-controller.js +1 -5
  59. package/dist/controllers/capability/capability-controller.js.map +1 -1
  60. package/dist/controllers/module/import-controller.d.ts +0 -3
  61. package/dist/controllers/module/import-controller.d.ts.map +1 -1
  62. package/dist/controllers/module/import-controller.js +21 -27
  63. package/dist/controllers/module/import-controller.js.map +1 -1
  64. package/dist/controllers/module/module-controller.d.ts +0 -59
  65. package/dist/controllers/module/module-controller.d.ts.map +1 -1
  66. package/dist/controllers/module/module-controller.js +0 -45
  67. package/dist/controllers/module/module-controller.js.map +1 -1
  68. package/dist/controllers/resource-definition/resource-definition-controller.d.ts +3 -4
  69. package/dist/controllers/resource-definition/resource-definition-controller.d.ts.map +1 -1
  70. package/dist/controllers/resource-definition/resource-definition-controller.js +6 -8
  71. package/dist/controllers/resource-definition/resource-definition-controller.js.map +1 -1
  72. package/dist/controllers/resource-definition/resource-template-controller.d.ts +12 -0
  73. package/dist/controllers/resource-definition/resource-template-controller.d.ts.map +1 -0
  74. package/dist/controllers/resource-definition/resource-template-controller.js +112 -0
  75. package/dist/controllers/resource-definition/resource-template-controller.js.map +1 -0
  76. package/dist/index.d.ts +1 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +0 -1
  79. package/dist/index.js.map +1 -1
  80. package/dist/kernel.d.ts +21 -24
  81. package/dist/kernel.d.ts.map +1 -1
  82. package/dist/kernel.js +254 -189
  83. package/dist/kernel.js.map +1 -1
  84. package/dist/loader.d.ts +9 -8
  85. package/dist/loader.d.ts.map +1 -1
  86. package/dist/loader.js +50 -132
  87. package/dist/loader.js.map +1 -1
  88. package/dist/manifest-adapters/http-adapter.d.ts.map +1 -1
  89. package/dist/manifest-adapters/http-adapter.js +3 -1
  90. package/dist/manifest-adapters/http-adapter.js.map +1 -1
  91. package/dist/manifest-adapters/local-file-adapter.d.ts +7 -5
  92. package/dist/manifest-adapters/local-file-adapter.d.ts.map +1 -1
  93. package/dist/manifest-adapters/local-file-adapter.js +21 -26
  94. package/dist/manifest-adapters/local-file-adapter.js.map +1 -1
  95. package/dist/manifest-adapters/manifest-adapter.d.ts +2 -0
  96. package/dist/manifest-adapters/manifest-adapter.d.ts.map +1 -1
  97. package/dist/manifest-adapters/registry-adapter.d.ts.map +1 -1
  98. package/dist/manifest-adapters/registry-adapter.js +3 -1
  99. package/dist/manifest-adapters/registry-adapter.js.map +1 -1
  100. package/dist/manifest-schemas.d.ts +61 -25
  101. package/dist/manifest-schemas.d.ts.map +1 -1
  102. package/dist/manifest-schemas.js +56 -16
  103. package/dist/manifest-schemas.js.map +1 -1
  104. package/dist/resource-context.d.ts +2 -5
  105. package/dist/resource-context.d.ts.map +1 -1
  106. package/dist/resource-context.js +31 -21
  107. package/dist/resource-context.js.map +1 -1
  108. package/dist/schema-valiator.d.ts.map +1 -1
  109. package/dist/schema-valiator.js +13 -2
  110. package/dist/schema-valiator.js.map +1 -1
  111. package/package.json +6 -7
  112. package/src/controller-loader.ts +2 -1
  113. package/src/controller-registry.ts +11 -26
  114. package/src/controllers/module/import-controller.ts +30 -30
  115. package/src/controllers/module/module-controller.ts +0 -51
  116. package/src/controllers/resource-definition/resource-definition-controller.ts +14 -15
  117. package/src/controllers/resource-definition/resource-template-controller.ts +138 -0
  118. package/src/index.ts +1 -1
  119. package/src/kernel.ts +313 -224
  120. package/src/loader.ts +54 -165
  121. package/src/manifest-adapters/local-file-adapter.ts +24 -31
  122. package/src/manifest-adapters/manifest-adapter.ts +2 -0
  123. package/src/manifest-schemas.ts +60 -24
  124. package/src/resource-context.ts +31 -30
  125. package/src/schema-valiator.ts +14 -3
  126. package/src/capabilities/component.yaml +0 -4
  127. package/src/capabilities/executable.yaml +0 -8
  128. package/src/capabilities/handler.yaml +0 -4
  129. package/src/capabilities/listener.yaml +0 -4
  130. package/src/capabilities/provider.yaml +0 -4
  131. package/src/capabilities/template.yaml +0 -4
  132. package/src/capabilities/type.yaml +0 -4
  133. package/src/controllers/capability/capability-controller.ts +0 -41
  134. package/src/controllers/module/module.json +0 -48
  135. package/src/controllers/module/module.yaml +0 -32
  136. package/src/manifest-adapters/http-adapter.ts +0 -35
  137. 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
- private readonly adapters: ManifestAdapter[] = [
17
- new HttpAdapter(),
18
- new RegistryAdapter(),
19
- new LocalFileAdapter(),
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.getAdapter(base).resolveRelative(base, relative);
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 files = await this.getAdapter(pathOrUrl).readAll(pathOrUrl);
42
- Loader.ensureProjectRoot(files[0]?.baseDir ?? process.cwd());
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 file of files) {
45
- await this.processFile(file, resources, { env: process.env });
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(file.baseDir);
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 += 1) {
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
- list.push(i);
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 += 1) {
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 += 1) {
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 += 1) {
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<ManifestSourceData> {
16
+ async read(pathOrUrl: string): Promise<{ text: string; source: string }> {
17
17
  const normalizedPath = pathOrUrl.startsWith("file://")
18
- ? pathOrUrl.replace("file://", "")
18
+ ? new URL(pathOrUrl).pathname
19
19
  : pathOrUrl;
20
- const stat = await fs.stat(normalizedPath);
21
- const filePath = stat.isDirectory() ? path.join(normalizedPath, "module.yaml") : normalizedPath;
22
- return this.readFile(filePath);
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<ManifestSourceData[]> {
27
+ async readAll(pathOrUrl: string): Promise<string[]> {
26
28
  const normalizedPath = pathOrUrl.startsWith("file://")
27
- ? pathOrUrl.replace("file://", "")
29
+ ? new URL(pathOrUrl).pathname
28
30
  : pathOrUrl;
29
- const stat = await fs.stat(normalizedPath);
31
+ const resolvedPath = path.resolve(normalizedPath);
32
+ const stat = await fs.stat(resolvedPath);
30
33
  if (stat.isDirectory()) {
31
- const results: ManifestSourceData[] = [];
32
- await this.collectYamlFiles(normalizedPath, results);
33
- return results;
34
+ return this.collectYamlSources(resolvedPath);
34
35
  }
35
- return [await this.readFile(normalizedPath)];
36
+ return [`file://${resolvedPath}`];
36
37
  }
37
38
 
38
39
  resolveRelative(base: string, relative: string): string {
39
- const basePath = base.startsWith("file://") ? base.slice("file://".length) : base;
40
- return `file://${path.resolve(basePath, relative)}`;
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 readFile(filePath: string): Promise<ManifestSourceData> {
44
- const content = await fs.readFile(filePath, "utf-8");
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.collectYamlFiles(fullPath, results);
51
+ sources.push(...(await this.collectYamlSources(fullPath)));
60
52
  } else if (entry.isFile() && this.isYamlFile(entry.name)) {
61
- results.push(await this.readFile(fullPath));
53
+ sources.push(`file://${fullPath}`);
62
54
  }
63
55
  }
56
+ return sources;
64
57
  }
65
58
 
66
59
  private isYamlFile(filename: string): boolean {
@@ -1,4 +1,6 @@
1
1
  export interface ManifestSourceData {
2
+ /** Raw YAML text */
3
+ text: string;
2
4
  /** Parsed YAML documents (result of yaml.loadAll) */
3
5
  documents: any[];
4
6
  /** Stored as metadata.source (file path or URL) */
@@ -1,5 +1,5 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import AjvModule, { ErrorObject } from "ajv";
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
- export const ResourceDefinitionSchema = Type.Object(
15
- {
16
- kind: Type.Literal("Kernel.Definition"),
17
- metadata: Type.Object(
18
- {
19
- name: Type.String(),
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
- { additionalProperties: true },
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: ErrorObject[] | null | undefined): string {
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 path = err.instancePath && err.instancePath.length > 0 ? err.instancePath : "/";
45
- const message = err.message || "is invalid";
46
- return `${path} ${message}`;
81
+ const p = err.instancePath || "/";
82
+ return `${p} ${err.message ?? "is invalid"}`;
47
83
  })
48
84
  .join("; ");
49
85
  }
@@ -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 passed: ${JSON.stringify(value)}. Error: ${formatAjvErrors(validate.errors)}`,
67
+ `[${this.metadata.name}] Invalid value. Error: ${formatAjvErrors(validate.errors)}`,
62
68
  );
63
69
  }
64
70
  }
65
71
 
66
- invoke(kind: string, name: string, ...args: any[]): Promise<any> {
67
- return this.moduleContext.invoke(kind, name, ...args);
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.merge(context).expand(value);
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 this.kernel.teardownContext(child);
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
+ }
@@ -28,7 +28,7 @@ export class SchemaValidator {
28
28
  }
29
29
 
30
30
  compile(schema: any): DataValidator {
31
- const validate = this.ajv.compile(
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) => {
@@ -1,4 +0,0 @@
1
- kind: Kernel.Capability
2
- metadata:
3
- module: Kernel
4
- name: component
@@ -1,8 +0,0 @@
1
- kind: Kernel.Capability
2
- metadata:
3
- module: Kernel
4
- name: executable
5
- schema:
6
- type: object
7
- properties:
8
- foo: { type: string }
@@ -1,4 +0,0 @@
1
- kind: Kernel.Capability
2
- metadata:
3
- module: Kernel
4
- name: handler
@@ -1,4 +0,0 @@
1
- kind: Kernel.Capability
2
- metadata:
3
- module: Kernel
4
- name: listener
@@ -1,4 +0,0 @@
1
- kind: Kernel.Capability
2
- metadata:
3
- module: Kernel
4
- name: provider
@@ -1,4 +0,0 @@
1
- kind: Kernel.Capability
2
- metadata:
3
- module: Kernel
4
- name: template