@telorun/kernel 0.2.4 → 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/boot-context-registry.d.ts.map +1 -1
- package/dist/boot-context-registry.js +6 -6
- package/dist/boot-context-registry.js.map +1 -1
- package/dist/capabilities/capabilities/component.yaml +2 -1
- package/dist/capabilities/capabilities/executable.yaml +2 -1
- package/dist/capabilities/capabilities/handler.yaml +2 -1
- package/dist/capabilities/capabilities/listener.yaml +2 -1
- package/dist/capabilities/capabilities/provider.yaml +2 -1
- package/dist/capabilities/capabilities/template.yaml +2 -1
- package/dist/capabilities/capabilities/type.yaml +2 -1
- 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/component.yaml +1 -1
- 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/executable.yaml +1 -1
- 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/handler.yaml +1 -1
- 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/listener.yaml +1 -1
- 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/provider.yaml +1 -1
- 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/template.yaml +1 -1
- 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/capabilities/type.yaml +1 -1
- package/dist/controller-loader.d.ts +1 -1
- package/dist/controller-loader.d.ts.map +1 -1
- package/dist/controller-loader.js +6 -3
- package/dist/controller-loader.js.map +1 -1
- package/dist/controller-registry.d.ts +1 -6
- 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 +35 -0
- package/dist/controllers/module/import-controller.d.ts.map +1 -0
- package/dist/controllers/module/import-controller.js +113 -0
- package/dist/controllers/module/import-controller.js.map +1 -0
- package/dist/controllers/module/module-controller.d.ts +2 -15
- package/dist/controllers/module/module-controller.d.ts.map +1 -1
- package/dist/controllers/module/module-controller.js +9 -90
- 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 +17 -11
- 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/evaluation-context.d.ts +91 -0
- package/dist/evaluation-context.d.ts.map +1 -0
- package/dist/evaluation-context.js +220 -0
- package/dist/evaluation-context.js.map +1 -0
- package/dist/execution-context.d.ts +13 -0
- package/dist/execution-context.d.ts.map +1 -0
- package/dist/execution-context.js +14 -0
- package/dist/execution-context.js.map +1 -0
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +31 -42
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +315 -371
- package/dist/kernel.js.map +1 -1
- package/dist/loader.d.ts +11 -10
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +50 -112
- 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 +28 -24
- 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 -49
- package/dist/manifest-schemas.d.ts.map +1 -1
- package/dist/manifest-schemas.js +58 -37
- package/dist/manifest-schemas.js.map +1 -1
- package/dist/module-context-registry.d.ts +48 -0
- package/dist/module-context-registry.d.ts.map +1 -0
- package/dist/module-context-registry.js +91 -0
- package/dist/module-context-registry.js.map +1 -0
- package/dist/module-context.d.ts +31 -0
- package/dist/module-context.d.ts.map +1 -0
- package/dist/module-context.js +67 -0
- package/dist/module-context.js.map +1 -0
- package/dist/registry.d.ts +1 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +1 -1
- package/dist/resource-context.d.ts +27 -10
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +100 -44
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-valiator.d.ts.map +1 -1
- package/dist/schema-valiator.js +16 -3
- package/dist/schema-valiator.js.map +1 -1
- package/dist/snapshot-serializer.d.ts +1 -2
- package/dist/snapshot-serializer.d.ts.map +1 -1
- package/dist/snapshot-serializer.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +11 -9
- package/src/boot-context-registry.ts +169 -0
- package/src/controller-loader.ts +299 -0
- package/src/controller-registry.ts +191 -0
- package/src/controllers/module/import-controller.ts +143 -0
- package/src/controllers/module/module-controller.ts +16 -0
- package/src/controllers/resource-definition/resource-definition-controller.ts +86 -0
- package/src/controllers/resource-definition/resource-definition.json +18 -0
- package/src/controllers/resource-definition/resource-template-controller.ts +138 -0
- package/src/event-stream.ts +121 -0
- package/src/events.ts +99 -0
- package/src/index.ts +7 -0
- package/src/kernel.ts +647 -0
- package/src/loader.ts +134 -0
- package/src/manifest-adapters/local-file-adapter.ts +62 -0
- package/src/manifest-adapters/manifest-adapter.ts +35 -0
- package/src/manifest-schemas.ts +85 -0
- package/src/registry.ts +137 -0
- package/src/resource-context.ts +267 -0
- package/src/resource-uri.ts +200 -0
- package/src/schema-valiator.ts +68 -0
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -109
- package/dist/cli.js.map +0 -1
- package/dist/expressions.d.ts +0 -20
- package/dist/expressions.d.ts.map +0 -1
- package/dist/expressions.js +0 -253
- package/dist/expressions.js.map +0 -1
- package/dist/src/controllers/module/module.yaml +0 -32
- package/dist/template-definition.d.ts +0 -38
- package/dist/template-definition.d.ts.map +0 -1
- package/dist/template-definition.js +0 -26
- package/dist/template-definition.js.map +0 -1
- package/dist/template-expander.d.ts +0 -19
- package/dist/template-expander.d.ts.map +0 -1
- package/dist/template-expander.js +0 -425
- package/dist/template-expander.js.map +0 -1
package/src/loader.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Loader as BaseLoader } from "@telorun/analyzer";
|
|
2
|
+
import { ResourceManifest, RuntimeResource } from "@telorun/sdk";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { LocalFileAdapter } from "./manifest-adapters/local-file-adapter.js";
|
|
5
|
+
import { formatAjvErrors, validateRuntimeResource } from "./manifest-schemas.js";
|
|
6
|
+
|
|
7
|
+
export class Loader extends BaseLoader {
|
|
8
|
+
private static projectRoot: string | null = null;
|
|
9
|
+
private readonly localAdapter: LocalFileAdapter;
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
this.localAdapter = new LocalFileAdapter();
|
|
14
|
+
this.register(this.localAdapter);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private static ensureProjectRoot(baseDir: string): void {
|
|
18
|
+
if (!Loader.projectRoot) {
|
|
19
|
+
Loader.projectRoot = path.resolve(baseDir);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
resolvePath(base: string, relative: string): string {
|
|
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;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async loadDirectory(pathOrUrl: string): Promise<ResourceManifest[]> {
|
|
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));
|
|
40
|
+
const resources: RuntimeResource[] = [];
|
|
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
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return this.orderResourcesByKindDependencies(resources);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async loadManifest(
|
|
62
|
+
pathOrUrl: string,
|
|
63
|
+
baseUrl: string,
|
|
64
|
+
compileContext: Record<string, unknown> = {},
|
|
65
|
+
): Promise<ResourceManifest[]> {
|
|
66
|
+
if (!baseUrl) {
|
|
67
|
+
throw new Error("Base URL is required to load target manifest");
|
|
68
|
+
}
|
|
69
|
+
const url = new URL(pathOrUrl, baseUrl).toString();
|
|
70
|
+
if (!Loader.projectRoot) {
|
|
71
|
+
Loader.ensureProjectRoot(path.dirname(new URL(url).pathname));
|
|
72
|
+
}
|
|
73
|
+
return this.loadModule(url, { compile: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private orderResourcesByKindDependencies(resources: RuntimeResource[]): RuntimeResource[] {
|
|
77
|
+
if (resources.length <= 1) return resources;
|
|
78
|
+
|
|
79
|
+
const indicesByName = new Map<string, number[]>();
|
|
80
|
+
for (let i = 0; i < resources.length; i++) {
|
|
81
|
+
const name = resources[i]?.metadata?.name;
|
|
82
|
+
if (!name) continue;
|
|
83
|
+
const list = indicesByName.get(name);
|
|
84
|
+
if (list) list.push(i);
|
|
85
|
+
else indicesByName.set(name, [i]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const edges = new Map<number, Set<number>>();
|
|
89
|
+
const indegree = new Map<number, number>();
|
|
90
|
+
for (let i = 0; i < resources.length; i++) indegree.set(i, 0);
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < resources.length; i++) {
|
|
93
|
+
const kind = resources[i]?.kind;
|
|
94
|
+
if (!kind) continue;
|
|
95
|
+
const definers = indicesByName.get(kind);
|
|
96
|
+
if (!definers) continue;
|
|
97
|
+
for (const definerIndex of definers) {
|
|
98
|
+
if (definerIndex === i) continue;
|
|
99
|
+
let set = edges.get(definerIndex);
|
|
100
|
+
if (!set) {
|
|
101
|
+
set = new Set();
|
|
102
|
+
edges.set(definerIndex, set);
|
|
103
|
+
}
|
|
104
|
+
if (!set.has(i)) {
|
|
105
|
+
set.add(i);
|
|
106
|
+
indegree.set(i, (indegree.get(i) || 0) + 1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const ready: number[] = [];
|
|
112
|
+
for (let i = 0; i < resources.length; i++) {
|
|
113
|
+
if ((indegree.get(i) || 0) === 0) ready.push(i);
|
|
114
|
+
}
|
|
115
|
+
ready.sort((a, b) => a - b);
|
|
116
|
+
|
|
117
|
+
const ordered: RuntimeResource[] = [];
|
|
118
|
+
while (ready.length > 0) {
|
|
119
|
+
const index = ready.shift() as number;
|
|
120
|
+
ordered.push(resources[index]);
|
|
121
|
+
const next = edges.get(index);
|
|
122
|
+
if (!next) continue;
|
|
123
|
+
for (const dependent of next) {
|
|
124
|
+
const count = (indegree.get(dependent) || 0) - 1;
|
|
125
|
+
indegree.set(dependent, count);
|
|
126
|
+
if (count === 0) ready.push(dependent);
|
|
127
|
+
}
|
|
128
|
+
if (ready.length > 1) ready.sort((a, b) => a - b);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (ordered.length !== resources.length) throw new Error("Resource dependency cycle detected");
|
|
132
|
+
return ordered;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ManifestAdapter } from "@telorun/analyzer";
|
|
2
|
+
import * as fs from "fs/promises";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
export class LocalFileAdapter implements ManifestAdapter {
|
|
6
|
+
supports(pathOrUrl: string): boolean {
|
|
7
|
+
return (
|
|
8
|
+
pathOrUrl.startsWith("file://") ||
|
|
9
|
+
pathOrUrl.startsWith("/") ||
|
|
10
|
+
pathOrUrl.startsWith("./") ||
|
|
11
|
+
pathOrUrl.startsWith("../") ||
|
|
12
|
+
(!pathOrUrl.includes("://") && !pathOrUrl.includes("@"))
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async read(pathOrUrl: string): Promise<{ text: string; source: string }> {
|
|
17
|
+
const normalizedPath = pathOrUrl.startsWith("file://")
|
|
18
|
+
? new URL(pathOrUrl).pathname
|
|
19
|
+
: pathOrUrl;
|
|
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}` };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async readAll(pathOrUrl: string): Promise<string[]> {
|
|
28
|
+
const normalizedPath = pathOrUrl.startsWith("file://")
|
|
29
|
+
? new URL(pathOrUrl).pathname
|
|
30
|
+
: pathOrUrl;
|
|
31
|
+
const resolvedPath = path.resolve(normalizedPath);
|
|
32
|
+
const stat = await fs.stat(resolvedPath);
|
|
33
|
+
if (stat.isDirectory()) {
|
|
34
|
+
return this.collectYamlSources(resolvedPath);
|
|
35
|
+
}
|
|
36
|
+
return [`file://${resolvedPath}`];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
resolveRelative(base: string, relative: string): string {
|
|
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)}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async collectYamlSources(dirPath: string): Promise<string[]> {
|
|
46
|
+
const sources: string[] = [];
|
|
47
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
sources.push(...(await this.collectYamlSources(fullPath)));
|
|
52
|
+
} else if (entry.isFile() && this.isYamlFile(entry.name)) {
|
|
53
|
+
sources.push(`file://${fullPath}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return sources;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private isYamlFile(filename: string): boolean {
|
|
60
|
+
return filename.endsWith(".yaml") || filename.endsWith(".yml");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface ManifestSourceData {
|
|
2
|
+
/** Raw YAML text */
|
|
3
|
+
text: string;
|
|
4
|
+
/** Parsed YAML documents (result of yaml.loadAll) */
|
|
5
|
+
documents: any[];
|
|
6
|
+
/** Stored as metadata.source (file path or URL) */
|
|
7
|
+
source: string;
|
|
8
|
+
/** Base directory for resolving relative controller entrypoints */
|
|
9
|
+
baseDir: string;
|
|
10
|
+
/** URI prefix — full resource URI is `${uriBase}#${kind}.${name}` */
|
|
11
|
+
uriBase: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ManifestAdapter {
|
|
15
|
+
/** Returns true if this adapter can handle the given path/URL */
|
|
16
|
+
supports(pathOrUrl: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Read a single manifest entry point.
|
|
19
|
+
* - File path or URL → read that file/URL.
|
|
20
|
+
* - Directory path → find and read `module.yaml` within it.
|
|
21
|
+
*/
|
|
22
|
+
read(pathOrUrl: string): Promise<ManifestSourceData>;
|
|
23
|
+
/**
|
|
24
|
+
* Read all manifest files reachable from the given path/URL.
|
|
25
|
+
* Used for module imports.
|
|
26
|
+
* - Directory path → recursive walk, one entry per .yaml/.yml file found.
|
|
27
|
+
* - File path or URL → single-item array.
|
|
28
|
+
*/
|
|
29
|
+
readAll(pathOrUrl: string): Promise<ManifestSourceData[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a potentially relative path/URL against a base directory/URL.
|
|
32
|
+
* For absolute inputs the base is ignored.
|
|
33
|
+
*/
|
|
34
|
+
resolveRelative(base: string, relative: string): string;
|
|
35
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import AjvModule from "ajv";
|
|
3
|
+
import addFormats from "ajv-formats";
|
|
4
|
+
const Ajv = AjvModule.default ?? AjvModule;
|
|
5
|
+
|
|
6
|
+
export const RuntimeResourceSchema = Type.Object(
|
|
7
|
+
{
|
|
8
|
+
kind: Type.String(),
|
|
9
|
+
metadata: Type.Object({ name: Type.String() }, { additionalProperties: true }),
|
|
10
|
+
},
|
|
11
|
+
{ additionalProperties: true },
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const metadataSchema = {
|
|
15
|
+
type: "object",
|
|
16
|
+
required: ["name"],
|
|
17
|
+
properties: {
|
|
18
|
+
name: { type: "string" },
|
|
19
|
+
module: { type: "string" },
|
|
20
|
+
},
|
|
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
|
+
};
|
|
70
|
+
|
|
71
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
72
|
+
addFormats.default(ajv);
|
|
73
|
+
|
|
74
|
+
export const validateRuntimeResource = ajv.compile(RuntimeResourceSchema);
|
|
75
|
+
export const validateResourceDefinition = ajv.compile(ResourceDefinitionSchema);
|
|
76
|
+
|
|
77
|
+
export function formatAjvErrors(errors: any[] | null | undefined): string {
|
|
78
|
+
if (!errors || errors.length === 0) return "Unknown schema error";
|
|
79
|
+
return errors
|
|
80
|
+
.map((err) => {
|
|
81
|
+
const p = err.instancePath || "/";
|
|
82
|
+
return `${p} ${err.message ?? "is invalid"}`;
|
|
83
|
+
})
|
|
84
|
+
.join("; ");
|
|
85
|
+
}
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { ResourceManifest, RuntimeError, RuntimeResource } from "@telorun/sdk";
|
|
2
|
+
import { ResourceURI } from "./resource-uri.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Registry: Indexes resources by composite key of Kind and Name
|
|
6
|
+
* Maintains URI-based lookup for tracking resource origins and lineage
|
|
7
|
+
*/
|
|
8
|
+
export class ManifestRegistry {
|
|
9
|
+
private resources: Map<string, Map<string, ResourceManifest>> = new Map();
|
|
10
|
+
private kindInheritance: Map<string, string> = new Map(); // derivedKind -> parentKind
|
|
11
|
+
private uriIndex: Map<string, ResourceManifest> = new Map(); // URI -> Resource
|
|
12
|
+
private sourceIndex: Map<string, ResourceManifest[]> = new Map(); // source path -> Resources
|
|
13
|
+
private depthIndex: Map<number, ResourceManifest[]> = new Map(); // generation depth -> Resources
|
|
14
|
+
|
|
15
|
+
register(resource: RuntimeResource): void {
|
|
16
|
+
const { kind, metadata } = resource;
|
|
17
|
+
const { name } = metadata;
|
|
18
|
+
console.log("Registering resource:", kind, name);
|
|
19
|
+
if (!this.resources.has(kind)) {
|
|
20
|
+
this.resources.set(kind, new Map());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const kindMap = this.resources.get(kind)!;
|
|
24
|
+
|
|
25
|
+
if (kindMap.has(name)) {
|
|
26
|
+
throw new RuntimeError("ERR_DUPLICATE_RESOURCE", `Duplicate resource: ${kind}.${name}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
kindMap.set(name, resource);
|
|
30
|
+
|
|
31
|
+
// Index by URI if available
|
|
32
|
+
if (metadata.uri) {
|
|
33
|
+
this.uriIndex.set(metadata.uri, resource);
|
|
34
|
+
|
|
35
|
+
// Index by source file/path
|
|
36
|
+
try {
|
|
37
|
+
const uri = ResourceURI.parse(metadata.uri);
|
|
38
|
+
if (uri.isFileSource()) {
|
|
39
|
+
const sourcePath = uri.path;
|
|
40
|
+
if (!this.sourceIndex.has(sourcePath)) {
|
|
41
|
+
this.sourceIndex.set(sourcePath, []);
|
|
42
|
+
}
|
|
43
|
+
this.sourceIndex.get(sourcePath)!.push(resource);
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// URI parsing failed, skip indexing
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Index by generation depth
|
|
51
|
+
const depth = metadata.generationDepth ?? 0;
|
|
52
|
+
if (!this.depthIndex.has(depth)) {
|
|
53
|
+
this.depthIndex.set(depth, []);
|
|
54
|
+
}
|
|
55
|
+
this.depthIndex.get(depth)!.push(resource);
|
|
56
|
+
|
|
57
|
+
// Check if this is a Kernel.KindDefinition that creates a new kind
|
|
58
|
+
// if (kind === 'Kernel.KindDefinition') {
|
|
59
|
+
// const newKind = name;
|
|
60
|
+
// const parentKind = resource?.extends;
|
|
61
|
+
// if (parentKind) {
|
|
62
|
+
// this.kindInheritance.set(newKind, parentKind);
|
|
63
|
+
// }
|
|
64
|
+
// }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getParentKind(kind: string): string | undefined {
|
|
68
|
+
return this.kindInheritance.get(kind);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
resolveKindChain(kind: string): string[] {
|
|
72
|
+
const chain: string[] = [kind];
|
|
73
|
+
let current = kind;
|
|
74
|
+
while (this.kindInheritance.has(current)) {
|
|
75
|
+
current = this.kindInheritance.get(current)!;
|
|
76
|
+
chain.push(current);
|
|
77
|
+
}
|
|
78
|
+
return chain;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get(kind: string, name: string): ResourceManifest | undefined {
|
|
82
|
+
return this.resources.get(kind)?.get(name);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getByKind(kind: string): ResourceManifest[] {
|
|
86
|
+
const kindMap = this.resources.get(kind);
|
|
87
|
+
return kindMap ? Array.from(kindMap.values()) : [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get resource by its URI
|
|
92
|
+
*/
|
|
93
|
+
getByUri(uri: string): ResourceManifest | undefined {
|
|
94
|
+
return this.uriIndex.get(uri);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get all resources from a specific source file
|
|
99
|
+
*/
|
|
100
|
+
getBySourceFile(sourceFilePath: string): ResourceManifest[] {
|
|
101
|
+
return this.sourceIndex.get(sourceFilePath) ?? [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get all resources at a specific generation depth
|
|
106
|
+
* 0 = directly from files, 1+ = template-generated
|
|
107
|
+
*/
|
|
108
|
+
getByGenerationDepth(depth: number): ResourceManifest[] {
|
|
109
|
+
return this.depthIndex.get(depth) ?? [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get all template-generated resources (depth > 0)
|
|
114
|
+
*/
|
|
115
|
+
getTemplateGenerated(): ResourceManifest[] {
|
|
116
|
+
const results: ResourceManifest[] = [];
|
|
117
|
+
for (const [depth, resources] of this.depthIndex) {
|
|
118
|
+
if (depth > 0) {
|
|
119
|
+
results.push(...resources);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return results;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get all directly-loaded resources (depth = 0)
|
|
127
|
+
*/
|
|
128
|
+
getDirectlyLoaded(): ResourceManifest[] {
|
|
129
|
+
return this.depthIndex.get(0) ?? [];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getAll(): ResourceManifest[] {
|
|
133
|
+
return Array.from(this.resources.values())
|
|
134
|
+
.map((kindMap) => Array.from(kindMap.values()))
|
|
135
|
+
.flat();
|
|
136
|
+
}
|
|
137
|
+
}
|