@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.
Files changed (138) 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 +2 -2
  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 +38 -24
  81. package/dist/kernel.d.ts.map +1 -1
  82. package/dist/kernel.js +296 -201
  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 +11 -10
  105. package/dist/resource-context.d.ts.map +1 -1
  106. package/dist/resource-context.js +70 -25
  107. package/dist/resource-context.js.map +1 -1
  108. package/dist/schema-valiator.d.ts +6 -1
  109. package/dist/schema-valiator.d.ts.map +1 -1
  110. package/dist/schema-valiator.js +76 -4
  111. package/dist/schema-valiator.js.map +1 -1
  112. package/package.json +6 -7
  113. package/src/controller-loader.ts +2 -1
  114. package/src/controller-registry.ts +11 -26
  115. package/src/controllers/module/import-controller.ts +30 -30
  116. package/src/controllers/module/module-controller.ts +0 -51
  117. package/src/controllers/resource-definition/resource-definition-controller.ts +14 -15
  118. package/src/controllers/resource-definition/resource-template-controller.ts +138 -0
  119. package/src/index.ts +2 -2
  120. package/src/kernel.ts +364 -226
  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 +90 -35
  125. package/src/schema-valiator.ts +90 -13
  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/loader.ts +0 -245
  137. package/src/manifest-adapters/http-adapter.ts +0 -35
  138. 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<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,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
- stdin: NodeJS.ReadableStream = process.stdin;
26
- stdout: NodeJS.WritableStream = process.stdout;
27
- stderr: NodeJS.WritableStream = process.stderr;
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 passed: ${JSON.stringify(value)}. Error: ${formatAjvErrors(validate.errors)}`,
121
+ `[${this.metadata.name}] Invalid value. Error: ${formatAjvErrors(validate.errors)}`,
62
122
  );
63
123
  }
64
124
  }
65
125
 
66
- invoke(kind: string, name: string, ...args: any[]): Promise<any> {
67
- return this.moduleContext.invoke(kind, name, ...args);
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.merge(context).expand(value);
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 this.kernel.teardownContext(child);
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
+ }
@@ -1,4 +1,5 @@
1
- import { DataValidator, RuntimeError } from "@telorun/sdk";
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.ajv.getSchema(name) as object | undefined;
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 validate = this.ajv.compile(
32
- "type" in schema && typeof schema.type === "string"
33
- ? schema
34
- : {
35
- type: "object",
36
- properties: schema,
37
- required: Object.keys(schema),
38
- additionalProperties: false,
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
- "ERR_RESOURCE_NOT_FOUND",
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,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
@@ -1,4 +0,0 @@
1
- kind: Kernel.Capability
2
- metadata:
3
- module: Kernel
4
- name: type
@@ -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
- }