@telorun/kernel 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +2 -2
- 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 +38 -24
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +296 -201
- 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 +11 -10
- package/dist/resource-context.d.ts.map +1 -1
- package/dist/resource-context.js +70 -25
- package/dist/resource-context.js.map +1 -1
- package/dist/schema-valiator.d.ts +6 -1
- package/dist/schema-valiator.d.ts.map +1 -1
- package/dist/schema-valiator.js +76 -4
- 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 +2 -2
- package/src/kernel.ts +364 -226
- 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 +90 -35
- package/src/schema-valiator.ts +90 -13
- 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/loader.ts +0 -245
- package/src/manifest-adapters/http-adapter.ts +0 -35
- package/src/manifest-adapters/registry-adapter.ts +0 -56
|
@@ -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,9 @@ import {
|
|
|
5
5
|
ResourceContext,
|
|
6
6
|
RuntimeError,
|
|
7
7
|
RuntimeResource,
|
|
8
|
+
isCompiledValue,
|
|
9
|
+
type ParsedArgs,
|
|
10
|
+
type TypeRule,
|
|
8
11
|
} from "@telorun/sdk";
|
|
9
12
|
import AjvModule from "ajv";
|
|
10
13
|
import addFormats from "ajv-formats";
|
|
@@ -15,16 +18,26 @@ import { SchemaValidator } from "./schema-valiator.js";
|
|
|
15
18
|
const Ajv = AjvModule.default ?? AjvModule;
|
|
16
19
|
|
|
17
20
|
export class ResourceContextImpl implements ResourceContext {
|
|
21
|
+
readonly stdin: NodeJS.ReadableStream;
|
|
22
|
+
readonly stdout: NodeJS.WritableStream;
|
|
23
|
+
readonly stderr: NodeJS.WritableStream;
|
|
24
|
+
readonly args: ParsedArgs;
|
|
25
|
+
|
|
18
26
|
constructor(
|
|
19
27
|
readonly kernel: Kernel,
|
|
20
28
|
readonly moduleContext: ModuleContext,
|
|
21
29
|
private readonly metadata: Record<string, any>,
|
|
22
30
|
private readonly validator: SchemaValidator = new SchemaValidator(),
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
stdin?: NodeJS.ReadableStream,
|
|
32
|
+
stdout?: NodeJS.WritableStream,
|
|
33
|
+
stderr?: NodeJS.WritableStream,
|
|
34
|
+
args?: ParsedArgs,
|
|
35
|
+
) {
|
|
36
|
+
this.stdin = stdin ?? process.stdin;
|
|
37
|
+
this.stdout = stdout ?? process.stdout;
|
|
38
|
+
this.stderr = stderr ?? process.stderr;
|
|
39
|
+
this.args = args ?? { _: [] };
|
|
40
|
+
}
|
|
28
41
|
|
|
29
42
|
createSchemaValidator(schema: any) {
|
|
30
43
|
if (!schema) {
|
|
@@ -41,9 +54,56 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
41
54
|
return this.validator.getSchema(name);
|
|
42
55
|
}
|
|
43
56
|
|
|
57
|
+
registerTypeRules(name: string, rules: TypeRule[]): void {
|
|
58
|
+
this.validator.addTypeRules(name, rules);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
lookupTypeRules(name: string): TypeRule[] | undefined {
|
|
62
|
+
return this.validator.getTypeRules(name);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
createTypeValidator(typeRef: string | Record<string, any> | undefined) {
|
|
66
|
+
if (!typeRef) return new NoopValidator();
|
|
67
|
+
|
|
68
|
+
// String reference: look up registered type schema by name
|
|
69
|
+
if (typeof typeRef === "string") {
|
|
70
|
+
const schema = this.validator.getSchema(typeRef);
|
|
71
|
+
if (!schema) {
|
|
72
|
+
throw new RuntimeError(
|
|
73
|
+
"ERR_TYPE_NOT_FOUND",
|
|
74
|
+
`Type "${typeRef}" not found in schema registry`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const base = this.validator.compile(schema);
|
|
78
|
+
const rules = this.validator.getTypeRules(typeRef);
|
|
79
|
+
if (rules && rules.length > 0) {
|
|
80
|
+
return this.validator.composeWithRules(base, typeRef, rules);
|
|
81
|
+
}
|
|
82
|
+
return base;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Inline schema object: if it has a `schema` property, it's a type resource shape
|
|
86
|
+
if (typeRef.schema && typeof typeRef.schema === "object") {
|
|
87
|
+
const base = this.validator.compile(typeRef.schema);
|
|
88
|
+
const rules = Array.isArray(typeRef.rules) ? typeRef.rules : [];
|
|
89
|
+
if (rules.length > 0) {
|
|
90
|
+
return this.validator.composeWithRules(base, "inline", rules);
|
|
91
|
+
}
|
|
92
|
+
return base;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Raw JSON Schema object (direct schema, not wrapped in type resource)
|
|
96
|
+
return this.validator.compile(typeRef);
|
|
97
|
+
}
|
|
98
|
+
|
|
44
99
|
validateSchema(value: any, schema: any) {
|
|
45
|
-
const ajv = new Ajv(
|
|
100
|
+
const ajv = new Ajv({
|
|
101
|
+
removeAdditional: true,
|
|
102
|
+
});
|
|
46
103
|
addFormats.default(ajv);
|
|
104
|
+
for (const kw of ["x-telo-ref", "x-telo-scope", "x-telo-context", "x-telo-schema-from"]) {
|
|
105
|
+
ajv.addKeyword(kw);
|
|
106
|
+
}
|
|
47
107
|
const validate = ajv.compile(
|
|
48
108
|
"type" in schema && typeof schema.type === "string"
|
|
49
109
|
? schema
|
|
@@ -54,17 +114,17 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
54
114
|
additionalProperties: false,
|
|
55
115
|
},
|
|
56
116
|
);
|
|
57
|
-
const isValid = validate(value);
|
|
117
|
+
const isValid = validate(stripCompiledValues(value));
|
|
58
118
|
if (!isValid) {
|
|
59
119
|
throw new RuntimeError(
|
|
60
120
|
"ERR_INVALID_VALUE",
|
|
61
|
-
`[${this.metadata.name}] Invalid value
|
|
121
|
+
`[${this.metadata.name}] Invalid value. Error: ${formatAjvErrors(validate.errors)}`,
|
|
62
122
|
);
|
|
63
123
|
}
|
|
64
124
|
}
|
|
65
125
|
|
|
66
|
-
invoke(kind: string, name: string,
|
|
67
|
-
return this.moduleContext.invoke(kind, name,
|
|
126
|
+
invoke<TInputs>(kind: string, name: string, inputs: TInputs): Promise<any> {
|
|
127
|
+
return this.moduleContext.invoke(kind, name, inputs);
|
|
68
128
|
}
|
|
69
129
|
|
|
70
130
|
async run(name: string) {
|
|
@@ -112,7 +172,7 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
112
172
|
(k) => k !== "kind" && k !== "name" && k !== "metadata",
|
|
113
173
|
);
|
|
114
174
|
|
|
115
|
-
if (definitionKeys.length > 0) {
|
|
175
|
+
if (definitionKeys.length > 0 && !this.moduleContext.hasManifest(name)) {
|
|
116
176
|
this.registerManifest({
|
|
117
177
|
...resource,
|
|
118
178
|
metadata: {
|
|
@@ -157,18 +217,6 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
157
217
|
this.kernel.registerResourceDefinition(def);
|
|
158
218
|
}
|
|
159
219
|
|
|
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
220
|
on(event: string, handler: (payload?: any) => void | Promise<void>): void {
|
|
173
221
|
this.kernel.on(event, handler);
|
|
174
222
|
}
|
|
@@ -193,18 +241,8 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
193
241
|
this.kernel.requestExit(code);
|
|
194
242
|
}
|
|
195
243
|
|
|
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
244
|
expandValue(value: any, context: Record<string, any>) {
|
|
207
|
-
return this.moduleContext.
|
|
245
|
+
return this.moduleContext.expandWith(value, context);
|
|
208
246
|
}
|
|
209
247
|
|
|
210
248
|
async emitEvent(event: string, payload?: any) {
|
|
@@ -240,6 +278,10 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
240
278
|
return this.moduleContext.spawnChild(child);
|
|
241
279
|
}
|
|
242
280
|
|
|
281
|
+
transientChild(context: Record<string, any>): EvaluationContext {
|
|
282
|
+
return this.moduleContext.transientChild(context);
|
|
283
|
+
}
|
|
284
|
+
|
|
243
285
|
/**
|
|
244
286
|
* Create a temporary child context, queue manifests on it, run a function,
|
|
245
287
|
* then tear down the child context and its resources.
|
|
@@ -259,8 +301,21 @@ export class ResourceContextImpl implements ResourceContext {
|
|
|
259
301
|
await child.initializeResources();
|
|
260
302
|
return await Promise.resolve(fn() as any);
|
|
261
303
|
} finally {
|
|
262
|
-
await
|
|
304
|
+
await child.teardownResources();
|
|
263
305
|
}
|
|
264
306
|
})() as unknown as T;
|
|
265
307
|
}
|
|
266
308
|
}
|
|
309
|
+
|
|
310
|
+
function stripCompiledValues(v: unknown): unknown {
|
|
311
|
+
if (isCompiledValue(v)) return "";
|
|
312
|
+
if (Array.isArray(v)) return v.map(stripCompiledValues);
|
|
313
|
+
if (v !== null && typeof v === "object") {
|
|
314
|
+
const out: Record<string, unknown> = {};
|
|
315
|
+
for (const [k, val] of Object.entries(v as Record<string, unknown>)) {
|
|
316
|
+
out[k] = stripCompiledValues(val);
|
|
317
|
+
}
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
return v;
|
|
321
|
+
}
|
package/src/schema-valiator.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { evaluate } from "@marcbachmann/cel-js";
|
|
2
|
+
import { DataValidator, RuntimeError, TypeRule } from "@telorun/sdk";
|
|
2
3
|
import AjvModule from "ajv";
|
|
3
4
|
import addFormats from "ajv-formats";
|
|
4
5
|
import { formatAjvErrors } from "./manifest-schemas.js";
|
|
@@ -7,6 +8,8 @@ const Ajv = AjvModule.default ?? AjvModule;
|
|
|
7
8
|
|
|
8
9
|
export class SchemaValidator {
|
|
9
10
|
private ajv: InstanceType<typeof Ajv>;
|
|
11
|
+
private typeRules = new Map<string, TypeRule[]>();
|
|
12
|
+
private rawSchemas = new Map<string, object>();
|
|
10
13
|
|
|
11
14
|
constructor() {
|
|
12
15
|
this.ajv = new Ajv({
|
|
@@ -15,36 +18,74 @@ export class SchemaValidator {
|
|
|
15
18
|
useDefaults: true,
|
|
16
19
|
});
|
|
17
20
|
addFormats.default(this.ajv);
|
|
21
|
+
for (const kw of [
|
|
22
|
+
"x-telo-ref",
|
|
23
|
+
"x-telo-eval",
|
|
24
|
+
"x-telo-scope",
|
|
25
|
+
"x-telo-context",
|
|
26
|
+
"x-telo-context-from",
|
|
27
|
+
"x-telo-context-ref-from",
|
|
28
|
+
"x-telo-schema-from",
|
|
29
|
+
"x-telo-topology-role",
|
|
30
|
+
"x-telo-step-context",
|
|
31
|
+
]) {
|
|
32
|
+
this.ajv.addKeyword(kw);
|
|
33
|
+
}
|
|
18
34
|
}
|
|
19
35
|
|
|
20
36
|
addSchema(name: string, schema: object): void {
|
|
21
37
|
if (!this.ajv.getSchema(name)) {
|
|
22
38
|
this.ajv.addSchema(schema, name);
|
|
23
39
|
}
|
|
40
|
+
this.rawSchemas.set(name, schema);
|
|
24
41
|
}
|
|
25
42
|
|
|
26
43
|
getSchema(name: string): object | undefined {
|
|
27
|
-
return this.
|
|
44
|
+
return this.rawSchemas.get(name);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
addTypeRules(name: string, rules: TypeRule[]): void {
|
|
48
|
+
this.typeRules.set(name, rules);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getTypeRules(name: string): TypeRule[] | undefined {
|
|
52
|
+
return this.typeRules.get(name);
|
|
28
53
|
}
|
|
29
54
|
|
|
30
55
|
compile(schema: any): DataValidator {
|
|
31
|
-
const
|
|
32
|
-
"type" in schema && typeof schema.type === "string"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
56
|
+
const isFullSchema =
|
|
57
|
+
("type" in schema && typeof schema.type === "string") ||
|
|
58
|
+
"allOf" in schema ||
|
|
59
|
+
"anyOf" in schema ||
|
|
60
|
+
"oneOf" in schema ||
|
|
61
|
+
"$ref" in schema;
|
|
62
|
+
const normalized = isFullSchema
|
|
63
|
+
? schema
|
|
64
|
+
: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: schema,
|
|
67
|
+
required: Object.keys(schema),
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
};
|
|
70
|
+
const injected =
|
|
71
|
+
normalized.additionalProperties === false
|
|
72
|
+
? {
|
|
73
|
+
...normalized,
|
|
74
|
+
properties: {
|
|
75
|
+
kind: { type: "string" },
|
|
76
|
+
metadata: { type: "object" },
|
|
77
|
+
...normalized.properties,
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
: normalized;
|
|
81
|
+
const validate = this.ajv.compile(injected);
|
|
41
82
|
|
|
42
83
|
return {
|
|
43
84
|
validate: (data: any) => {
|
|
44
85
|
const isValid = validate(data);
|
|
45
86
|
if (!isValid) {
|
|
46
87
|
throw new RuntimeError(
|
|
47
|
-
"
|
|
88
|
+
"ERR_RESOURCE_SCHEMA_VALIDATION_FAILED",
|
|
48
89
|
`Invalid value passed: ${JSON.stringify(data)}. Error: ${formatAjvErrors(validate.errors)}`,
|
|
49
90
|
);
|
|
50
91
|
}
|
|
@@ -54,4 +95,40 @@ export class SchemaValidator {
|
|
|
54
95
|
},
|
|
55
96
|
};
|
|
56
97
|
}
|
|
98
|
+
|
|
99
|
+
composeWithRules(base: DataValidator, typeName: string, rules: TypeRule[]): DataValidator {
|
|
100
|
+
return {
|
|
101
|
+
validate: (data: any) => {
|
|
102
|
+
base.validate(data);
|
|
103
|
+
for (const rule of rules) {
|
|
104
|
+
let result: unknown;
|
|
105
|
+
try {
|
|
106
|
+
result = evaluate(rule.condition, { this: data });
|
|
107
|
+
} catch (err) {
|
|
108
|
+
throw new RuntimeError(
|
|
109
|
+
"ERR_TYPE_VALIDATION_FAILED",
|
|
110
|
+
`Type "${typeName}" rule evaluation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (result !== true) {
|
|
114
|
+
throw new RuntimeError(
|
|
115
|
+
rule.code ?? "ERR_TYPE_VALIDATION_FAILED",
|
|
116
|
+
rule.message ?? `Type "${typeName}" validation failed: rule "${rule.code}" not satisfied`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
isValid: (data: any) => {
|
|
122
|
+
if (!base.isValid(data)) return false;
|
|
123
|
+
for (const rule of rules) {
|
|
124
|
+
try {
|
|
125
|
+
if (evaluate(rule.condition, { this: data }) !== true) return false;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
57
134
|
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
2
|
-
|
|
3
|
-
type CapabilityResource = {
|
|
4
|
-
kind: string;
|
|
5
|
-
metadata: {
|
|
6
|
-
name: string;
|
|
7
|
-
[key: string]: any;
|
|
8
|
-
};
|
|
9
|
-
schema?: Record<string, any>;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
class Capability implements ResourceInstance {
|
|
13
|
-
constructor(private readonly resource: CapabilityResource) {}
|
|
14
|
-
|
|
15
|
-
async init(ctx: ResourceContext) {
|
|
16
|
-
ctx.registerCapability(this.resource.metadata.name, this.resource.schema);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function create(resource: any, _ctx: ResourceContext): Promise<Capability> {
|
|
21
|
-
return new Capability(resource as CapabilityResource);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const schema = {
|
|
25
|
-
type: "object",
|
|
26
|
-
properties: {
|
|
27
|
-
metadata: {
|
|
28
|
-
type: "object",
|
|
29
|
-
properties: {
|
|
30
|
-
name: { type: "string" },
|
|
31
|
-
},
|
|
32
|
-
required: ["name"],
|
|
33
|
-
},
|
|
34
|
-
schema: {
|
|
35
|
-
type: "object",
|
|
36
|
-
additionalProperties: true,
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
required: ["metadata"],
|
|
40
|
-
additionalProperties: true,
|
|
41
|
-
};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"type": "object",
|
|
3
|
-
"properties": {
|
|
4
|
-
"metadata": {
|
|
5
|
-
"type": "object",
|
|
6
|
-
"properties": {
|
|
7
|
-
"name": {
|
|
8
|
-
"type": "string"
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
"required": ["name"]
|
|
12
|
-
},
|
|
13
|
-
"imports": {
|
|
14
|
-
"type": "array",
|
|
15
|
-
"items": {
|
|
16
|
-
"type": "string"
|
|
17
|
-
},
|
|
18
|
-
"description": "Paths to other modules to import"
|
|
19
|
-
},
|
|
20
|
-
"definitions": {
|
|
21
|
-
"type": "array",
|
|
22
|
-
"items": {
|
|
23
|
-
"type": "string"
|
|
24
|
-
},
|
|
25
|
-
"description": "Paths to ResourceDefinition files"
|
|
26
|
-
},
|
|
27
|
-
"resources": {
|
|
28
|
-
"type": "array",
|
|
29
|
-
"items": {
|
|
30
|
-
"oneOf": [
|
|
31
|
-
{
|
|
32
|
-
"type": "string"
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"type": "object",
|
|
36
|
-
"properties": {
|
|
37
|
-
"path": {
|
|
38
|
-
"type": "string"
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
]
|
|
43
|
-
},
|
|
44
|
-
"description": "Paths to resource instance files to load"
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
"required": ["metadata"]
|
|
48
|
-
}
|