@telorun/analyzer 0.1.1

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 (82) hide show
  1. package/LICENSE +17 -0
  2. package/dist/adapters/http-adapter.d.ts +10 -0
  3. package/dist/adapters/http-adapter.d.ts.map +1 -0
  4. package/dist/adapters/http-adapter.js +17 -0
  5. package/dist/adapters/node-adapter.d.ts +15 -0
  6. package/dist/adapters/node-adapter.d.ts.map +1 -0
  7. package/dist/adapters/node-adapter.js +32 -0
  8. package/dist/adapters/registry-adapter.d.ts +11 -0
  9. package/dist/adapters/registry-adapter.d.ts.map +1 -0
  10. package/dist/adapters/registry-adapter.js +33 -0
  11. package/dist/alias-resolver.d.ts +12 -0
  12. package/dist/alias-resolver.d.ts.map +1 -0
  13. package/dist/alias-resolver.js +36 -0
  14. package/dist/analysis-registry.d.ts +29 -0
  15. package/dist/analysis-registry.d.ts.map +1 -0
  16. package/dist/analysis-registry.js +55 -0
  17. package/dist/analyzer.d.ts +14 -0
  18. package/dist/analyzer.d.ts.map +1 -0
  19. package/dist/analyzer.js +314 -0
  20. package/dist/builtins.d.ts +3 -0
  21. package/dist/builtins.d.ts.map +1 -0
  22. package/dist/builtins.js +109 -0
  23. package/dist/cel-environment.d.ts +12 -0
  24. package/dist/cel-environment.d.ts.map +1 -0
  25. package/dist/cel-environment.js +59 -0
  26. package/dist/definition-registry.d.ts +58 -0
  27. package/dist/definition-registry.d.ts.map +1 -0
  28. package/dist/definition-registry.js +155 -0
  29. package/dist/dependency-graph.d.ts +38 -0
  30. package/dist/dependency-graph.d.ts.map +1 -0
  31. package/dist/dependency-graph.js +155 -0
  32. package/dist/index.d.ts +9 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +7 -0
  35. package/dist/manifest-loader.d.ts +11 -0
  36. package/dist/manifest-loader.d.ts.map +1 -0
  37. package/dist/manifest-loader.js +194 -0
  38. package/dist/normalize-inline-resources.d.ts +22 -0
  39. package/dist/normalize-inline-resources.d.ts.map +1 -0
  40. package/dist/normalize-inline-resources.js +136 -0
  41. package/dist/precompile.d.ts +9 -0
  42. package/dist/precompile.d.ts.map +1 -0
  43. package/dist/precompile.js +51 -0
  44. package/dist/reference-field-map.d.ts +53 -0
  45. package/dist/reference-field-map.d.ts.map +1 -0
  46. package/dist/reference-field-map.js +107 -0
  47. package/dist/schema-compat.d.ts +42 -0
  48. package/dist/schema-compat.d.ts.map +1 -0
  49. package/dist/schema-compat.js +234 -0
  50. package/dist/scope-resolver.d.ts +5 -0
  51. package/dist/scope-resolver.d.ts.map +1 -0
  52. package/dist/scope-resolver.js +13 -0
  53. package/dist/types.d.ts +64 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/types.js +8 -0
  56. package/dist/validate-cel-context.d.ts +24 -0
  57. package/dist/validate-cel-context.d.ts.map +1 -0
  58. package/dist/validate-cel-context.js +136 -0
  59. package/dist/validate-references.d.ts +19 -0
  60. package/dist/validate-references.d.ts.map +1 -0
  61. package/dist/validate-references.js +275 -0
  62. package/package.json +34 -0
  63. package/src/adapters/http-adapter.ts +23 -0
  64. package/src/adapters/node-adapter.ts +38 -0
  65. package/src/adapters/registry-adapter.ts +43 -0
  66. package/src/alias-resolver.ts +37 -0
  67. package/src/analysis-registry.ts +68 -0
  68. package/src/analyzer.ts +399 -0
  69. package/src/builtins.ts +111 -0
  70. package/src/cel-environment.ts +70 -0
  71. package/src/definition-registry.ts +170 -0
  72. package/src/dependency-graph.ts +187 -0
  73. package/src/index.ts +17 -0
  74. package/src/manifest-loader.ts +203 -0
  75. package/src/normalize-inline-resources.ts +170 -0
  76. package/src/precompile.ts +54 -0
  77. package/src/reference-field-map.ts +147 -0
  78. package/src/schema-compat.ts +264 -0
  79. package/src/scope-resolver.ts +13 -0
  80. package/src/types.ts +68 -0
  81. package/src/validate-cel-context.ts +142 -0
  82. package/src/validate-references.ts +311 -0
@@ -0,0 +1,109 @@
1
+ export const KERNEL_BUILTINS = [
2
+ { kind: "Kernel.Abstract", metadata: { name: "Template", module: "Kernel" } },
3
+ { kind: "Kernel.Abstract", metadata: { name: "Runnable", module: "Kernel" } },
4
+ { kind: "Kernel.Abstract", metadata: { name: "Service", module: "Kernel" } },
5
+ { kind: "Kernel.Abstract", metadata: { name: "Invocable", module: "Kernel" } },
6
+ { kind: "Kernel.Abstract", metadata: { name: "Mount", module: "Kernel" } },
7
+ { kind: "Kernel.Abstract", metadata: { name: "Type", module: "Kernel" } },
8
+ {
9
+ kind: "Kernel.Abstract",
10
+ metadata: { name: "Provider", module: "Kernel" },
11
+ schema: { "x-telo-eval": "compile" },
12
+ },
13
+ {
14
+ kind: "Kernel.Definition",
15
+ metadata: { name: "Abstract", module: "Kernel" },
16
+ capability: "Kernel.Template",
17
+ schema: {
18
+ type: "object",
19
+ properties: {
20
+ kind: { type: "string" },
21
+ metadata: {
22
+ type: "object",
23
+ properties: { name: { type: "string" } },
24
+ required: ["name"],
25
+ additionalProperties: true,
26
+ },
27
+ capability: { type: "string" },
28
+ },
29
+ required: ["metadata"],
30
+ additionalProperties: false,
31
+ },
32
+ },
33
+ {
34
+ kind: "Kernel.Definition",
35
+ metadata: { name: "Definition", module: "Kernel" },
36
+ capability: "Kernel.Template",
37
+ schema: { type: "object" },
38
+ },
39
+ {
40
+ kind: "Kernel.Definition",
41
+ metadata: { name: "Import", module: "Kernel" },
42
+ capability: "Kernel.Template",
43
+ schema: {
44
+ type: "object",
45
+ properties: {
46
+ kind: { type: "string" },
47
+ metadata: {
48
+ type: "object",
49
+ properties: { name: { type: "string" } },
50
+ required: ["name"],
51
+ additionalProperties: true,
52
+ },
53
+ source: { type: "string" },
54
+ variables: { type: "object" },
55
+ secrets: { type: "object" },
56
+ },
57
+ required: ["metadata", "source"],
58
+ additionalProperties: false,
59
+ },
60
+ },
61
+ {
62
+ kind: "Kernel.Definition",
63
+ metadata: { name: "Module", module: "Kernel" },
64
+ capability: "Kernel.Template",
65
+ schema: {
66
+ type: "object",
67
+ properties: {
68
+ kind: { type: "string" },
69
+ metadata: {
70
+ type: "object",
71
+ properties: {
72
+ name: { type: "string" },
73
+ version: { type: "string" },
74
+ source: { type: "string" },
75
+ module: { type: "string" },
76
+ },
77
+ required: ["name"],
78
+ additionalProperties: true,
79
+ },
80
+ lifecycle: {
81
+ type: "string",
82
+ enum: ["shared", "isolated"],
83
+ default: "shared",
84
+ },
85
+ keepAlive: { type: "boolean", default: false },
86
+ variables: { type: "object" },
87
+ secrets: { type: "object" },
88
+ targets: {
89
+ type: "array",
90
+ items: {
91
+ anyOf: [
92
+ { type: "string", "x-telo-ref": "kernel#Runnable" },
93
+ { type: "string", "x-telo-ref": "kernel#Service" },
94
+ ],
95
+ },
96
+ },
97
+ exports: {
98
+ type: "object",
99
+ properties: {
100
+ kinds: { type: "array", items: { type: "string" } },
101
+ },
102
+ additionalProperties: true,
103
+ },
104
+ },
105
+ required: ["metadata"],
106
+ additionalProperties: false,
107
+ },
108
+ },
109
+ ];
@@ -0,0 +1,12 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import { Environment } from "@marcbachmann/cel-js";
3
+ export declare const celEnvironment: Environment;
4
+ /** Clone `celEnvironment` and register typed variable declarations so that
5
+ * `env.check(expr)` can infer return types for expressions referencing known variables.
6
+ *
7
+ * - `variables`: typed from the manifest's `variables` field if it is a schema map
8
+ * (only `Kernel.Module` resources carry this); otherwise registered as `map` (dyn).
9
+ * - `secrets`, `resources`, `imports`, `env`: always `map` (dyn — output schemas unknown).
10
+ * - `extraContextSchema`: additional variables from an `x-telo-context` annotation. */
11
+ export declare function buildTypedCelEnvironment(manifest: ResourceManifest, extraContextSchema?: Record<string, any> | null): Environment;
12
+ //# sourceMappingURL=cel-environment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cel-environment.d.ts","sourceRoot":"","sources":["../src/cel-environment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,eAAO,MAAM,cAAc,aAWvB,CAAC;AAEL;;;;;;wFAMwF;AACxF,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,gBAAgB,EAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAC9C,WAAW,CA0Cb"}
@@ -0,0 +1,59 @@
1
+ import { Environment } from "@marcbachmann/cel-js";
2
+ import { jsonSchemaToCelType } from "./schema-compat.js";
3
+ export const celEnvironment = new Environment({ unlistedVariablesAreDyn: true })
4
+ .registerFunction("join(list, string): string", (list, sep) => list.map(String).join(sep))
5
+ .registerFunction("keys(map): list", (map) => {
6
+ if (map instanceof Map)
7
+ return [...map.keys()];
8
+ return Object.keys(map);
9
+ })
10
+ .registerFunction("values(map): list", (map) => {
11
+ if (map instanceof Map)
12
+ return [...map.values()];
13
+ return Object.values(map);
14
+ });
15
+ /** Clone `celEnvironment` and register typed variable declarations so that
16
+ * `env.check(expr)` can infer return types for expressions referencing known variables.
17
+ *
18
+ * - `variables`: typed from the manifest's `variables` field if it is a schema map
19
+ * (only `Kernel.Module` resources carry this); otherwise registered as `map` (dyn).
20
+ * - `secrets`, `resources`, `imports`, `env`: always `map` (dyn — output schemas unknown).
21
+ * - `extraContextSchema`: additional variables from an `x-telo-context` annotation. */
22
+ export function buildTypedCelEnvironment(manifest, extraContextSchema) {
23
+ try {
24
+ const env = celEnvironment.clone();
25
+ // Build typed ObjectSchema from manifest.variables if it looks like a schema map
26
+ const vars = manifest.variables;
27
+ if (vars !== null && typeof vars === "object" && !Array.isArray(vars)) {
28
+ const entries = Object.entries(vars).filter(([, v]) => v !== null && typeof v === "object" && !Array.isArray(v));
29
+ if (entries.length > 0) {
30
+ const schema = {};
31
+ for (const [k, v] of entries) {
32
+ schema[k] = jsonSchemaToCelType(v);
33
+ }
34
+ env.registerVariable({ name: "variables", schema });
35
+ }
36
+ else {
37
+ env.registerVariable("variables", "map");
38
+ }
39
+ }
40
+ else {
41
+ env.registerVariable("variables", "map");
42
+ }
43
+ env.registerVariable("secrets", "map");
44
+ env.registerVariable("resources", "map");
45
+ env.registerVariable("imports", "map");
46
+ env.registerVariable("env", "map");
47
+ if (extraContextSchema?.properties) {
48
+ for (const [name, propSchema] of Object.entries(extraContextSchema.properties)) {
49
+ if (!env.hasVariable(name)) {
50
+ env.registerVariable(name, jsonSchemaToCelType(propSchema));
51
+ }
52
+ }
53
+ }
54
+ return env;
55
+ }
56
+ catch {
57
+ return celEnvironment.clone();
58
+ }
59
+ }
@@ -0,0 +1,58 @@
1
+ import type { ResourceDefinition } from "@telorun/sdk";
2
+ import { type ReferenceFieldMap } from "./reference-field-map.js";
3
+ /** Pure kind → ResourceDefinition map. No controller loading, no lifecycle. */
4
+ export declare class DefinitionRegistry {
5
+ constructor();
6
+ /** Per-instance AJV for cross-module $ref resolution. Isolated so each registry
7
+ * (and thus each AnalysisContext) has its own schema store — no stale schemas
8
+ * across analyze() calls and no unbounded growth across the process lifetime. */
9
+ private readonly ajv;
10
+ private readonly registeredSchemaIds;
11
+ private readonly defs;
12
+ private readonly fieldMaps;
13
+ /** Reverse inheritance index: parent kind → direct child kinds. */
14
+ private readonly extendedBy;
15
+ /** Module identity table: identity string → canonical module name.
16
+ * "kernel" → "Kernel", "std/pipeline" → "pipeline", etc. */
17
+ private readonly identityMap;
18
+ /** Reverse identity table: canonical module name → full identity string.
19
+ * "Kernel" → "kernel", "pipeline" → "std/pipeline", etc.
20
+ * Used to compute definition $id values for the AJV schema store. */
21
+ private readonly reverseIdentityMap;
22
+ register(definition: ResourceDefinition): void;
23
+ /** Register a module identity for x-telo-ref resolution.
24
+ * Call once per Kernel.Module manifest when the manifest is loaded.
25
+ * @param namespace The module's metadata.namespace (e.g. "std"), or null for kernel built-ins.
26
+ * @param moduleName The module's metadata.name (e.g. "pipeline", "http-server"). */
27
+ registerModuleIdentity(namespace: string | null, moduleName: string): void;
28
+ /** Computes the $id for a definition schema: "<identity>/<TypeName>".
29
+ * Returns undefined when the module identity is not yet registered. */
30
+ computeId(moduleName: string, typeName: string): string | undefined;
31
+ /** Validates data against a schema using this registry's AJV instance, which has all
32
+ * registered definition schemas loaded — enabling cross-module $ref resolution. */
33
+ validateWithRefs(data: unknown, schema: Record<string, any>): string[];
34
+ private tryRegisterSchema;
35
+ /** Resolves an x-telo-ref string to a canonical registry kind key.
36
+ * Splits on "#", looks up the left side in the identity table, and returns
37
+ * "<canonicalModule>.<TypeName>".
38
+ *
39
+ * "kernel#Invocable" → "Kernel.Invocable"
40
+ * "std/pipeline#Job" → "pipeline.Job"
41
+ * "std/http-server#Server" → "http-server.Server"
42
+ *
43
+ * Returns undefined when the string is malformed or the identity is not registered. */
44
+ resolveRef(xTeloRef: string): string | undefined;
45
+ resolve(kind: string): ResourceDefinition | undefined;
46
+ /** Returns the cached reference field map for the given kind, built once during register(). */
47
+ getFieldMap(kind: string): ReferenceFieldMap | undefined;
48
+ /** Returns the field map for `kind`, falling back to the alias-resolved kind when not found. */
49
+ getFieldMapForKind(kind: string, aliases?: {
50
+ resolveKind(k: string): string | undefined;
51
+ }): ReferenceFieldMap | undefined;
52
+ /** Returns all definitions that transitively extend the given abstract kind.
53
+ * Follows the capability chain to any depth (equivalent to instanceof in OOP).
54
+ * Definitions are included regardless of registration order. */
55
+ getByExtends(abstractKind: string): ResourceDefinition[];
56
+ kinds(): string[];
57
+ }
58
+ //# sourceMappingURL=definition-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definition-registry.d.ts","sourceRoot":"","sources":["../src/definition-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,EAA0B,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAG1F,+EAA+E;AAC/E,qBAAa,kBAAkB;;IAK7B;;sFAEkF;IAClF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IAEzD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyC;IAC9D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwC;IAClE,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+B;IAC1D;iEAC6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD;;0EAEsE;IACtE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAEhE,QAAQ,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI;IAwB9C;;;yFAGqF;IACrF,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAgB1E;4EACwE;IACxE,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAMnE;wFACoF;IACpF,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE;IAWtE,OAAO,CAAC,iBAAiB;IAczB;;;;;;;;4FAQwF;IACxF,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAUhD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAIrD,+FAA+F;IAC/F,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAIxD,gGAAgG;IAChG,kBAAkB,CAChB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,GACvD,iBAAiB,GAAG,SAAS;IAOhC;;qEAEiE;IACjE,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAgBxD,KAAK,IAAI,MAAM,EAAE;CAGlB"}
@@ -0,0 +1,155 @@
1
+ import { KERNEL_BUILTINS } from "./builtins.js";
2
+ import { buildReferenceFieldMap } from "./reference-field-map.js";
3
+ import { createAjv, formatSingleError } from "./schema-compat.js";
4
+ /** Pure kind → ResourceDefinition map. No controller loading, no lifecycle. */
5
+ export class DefinitionRegistry {
6
+ constructor() {
7
+ for (const def of KERNEL_BUILTINS)
8
+ this.register(def);
9
+ }
10
+ /** Per-instance AJV for cross-module $ref resolution. Isolated so each registry
11
+ * (and thus each AnalysisContext) has its own schema store — no stale schemas
12
+ * across analyze() calls and no unbounded growth across the process lifetime. */
13
+ ajv = createAjv();
14
+ registeredSchemaIds = new Set();
15
+ defs = new Map();
16
+ fieldMaps = new Map();
17
+ /** Reverse inheritance index: parent kind → direct child kinds. */
18
+ extendedBy = new Map();
19
+ /** Module identity table: identity string → canonical module name.
20
+ * "kernel" → "Kernel", "std/pipeline" → "pipeline", etc. */
21
+ identityMap = new Map();
22
+ /** Reverse identity table: canonical module name → full identity string.
23
+ * "Kernel" → "kernel", "pipeline" → "std/pipeline", etc.
24
+ * Used to compute definition $id values for the AJV schema store. */
25
+ reverseIdentityMap = new Map();
26
+ register(definition) {
27
+ const { name, module: mod } = definition.metadata;
28
+ const key = mod ? `${mod}.${name}` : name;
29
+ this.defs.set(key, definition);
30
+ this.fieldMaps.set(key, buildReferenceFieldMap(definition.schema ?? {}));
31
+ if (definition.capability) {
32
+ const children = this.extendedBy.get(definition.capability);
33
+ if (children) {
34
+ children.push(key);
35
+ }
36
+ else {
37
+ this.extendedBy.set(definition.capability, [key]);
38
+ }
39
+ }
40
+ // Auto-register the kernel identity when any Kernel built-in is registered.
41
+ if (definition.kind === "Kernel.Abstract" && mod === "Kernel") {
42
+ this.identityMap.set("kernel", "Kernel");
43
+ this.reverseIdentityMap.set("Kernel", "kernel");
44
+ }
45
+ // If identity is already known, register the schema in AJV immediately.
46
+ if (mod && definition.schema) {
47
+ this.tryRegisterSchema(mod, name, definition.schema);
48
+ }
49
+ }
50
+ /** Register a module identity for x-telo-ref resolution.
51
+ * Call once per Kernel.Module manifest when the manifest is loaded.
52
+ * @param namespace The module's metadata.namespace (e.g. "std"), or null for kernel built-ins.
53
+ * @param moduleName The module's metadata.name (e.g. "pipeline", "http-server"). */
54
+ registerModuleIdentity(namespace, moduleName) {
55
+ const identity = namespace ? `${namespace}/${moduleName}` : "kernel";
56
+ this.identityMap.set(identity, moduleName);
57
+ this.reverseIdentityMap.set(moduleName, identity);
58
+ // Retroactively register AJV schemas for definitions of this module already in the registry.
59
+ for (const def of this.defs.values()) {
60
+ if (def.metadata.module === moduleName && def.schema) {
61
+ this.tryRegisterSchema(moduleName, def.metadata.name, def.schema);
62
+ }
63
+ }
64
+ }
65
+ /** Computes the $id for a definition schema: "<identity>/<TypeName>".
66
+ * Returns undefined when the module identity is not yet registered. */
67
+ computeId(moduleName, typeName) {
68
+ const identity = this.reverseIdentityMap.get(moduleName);
69
+ if (!identity)
70
+ return undefined;
71
+ return `${identity}/${typeName}`;
72
+ }
73
+ /** Validates data against a schema using this registry's AJV instance, which has all
74
+ * registered definition schemas loaded — enabling cross-module $ref resolution. */
75
+ validateWithRefs(data, schema) {
76
+ let validate;
77
+ try {
78
+ validate = this.ajv.compile(schema);
79
+ }
80
+ catch {
81
+ return [];
82
+ }
83
+ if (validate(data))
84
+ return [];
85
+ return (validate.errors ?? []).map(formatSingleError);
86
+ }
87
+ tryRegisterSchema(moduleName, typeName, schema) {
88
+ const id = this.computeId(moduleName, typeName);
89
+ if (!id || this.registeredSchemaIds.has(id))
90
+ return;
91
+ if (this.ajv.getSchema(id)) {
92
+ throw new Error(`Duplicate definition schema $id: "${id}" is already registered`);
93
+ }
94
+ this.ajv.addSchema(schema, id);
95
+ this.registeredSchemaIds.add(id);
96
+ }
97
+ /** Resolves an x-telo-ref string to a canonical registry kind key.
98
+ * Splits on "#", looks up the left side in the identity table, and returns
99
+ * "<canonicalModule>.<TypeName>".
100
+ *
101
+ * "kernel#Invocable" → "Kernel.Invocable"
102
+ * "std/pipeline#Job" → "pipeline.Job"
103
+ * "std/http-server#Server" → "http-server.Server"
104
+ *
105
+ * Returns undefined when the string is malformed or the identity is not registered. */
106
+ resolveRef(xTeloRef) {
107
+ const hash = xTeloRef.indexOf("#");
108
+ if (hash === -1 || hash === xTeloRef.length - 1)
109
+ return undefined;
110
+ const identity = xTeloRef.slice(0, hash);
111
+ const typeName = xTeloRef.slice(hash + 1);
112
+ const moduleName = this.identityMap.get(identity);
113
+ if (!moduleName)
114
+ return undefined;
115
+ return `${moduleName}.${typeName}`;
116
+ }
117
+ resolve(kind) {
118
+ return this.defs.get(kind);
119
+ }
120
+ /** Returns the cached reference field map for the given kind, built once during register(). */
121
+ getFieldMap(kind) {
122
+ return this.fieldMaps.get(kind);
123
+ }
124
+ /** Returns the field map for `kind`, falling back to the alias-resolved kind when not found. */
125
+ getFieldMapForKind(kind, aliases) {
126
+ const fm = this.getFieldMap(kind);
127
+ if (fm)
128
+ return fm;
129
+ const resolved = aliases?.resolveKind(kind);
130
+ return resolved ? this.getFieldMap(resolved) : undefined;
131
+ }
132
+ /** Returns all definitions that transitively extend the given abstract kind.
133
+ * Follows the capability chain to any depth (equivalent to instanceof in OOP).
134
+ * Definitions are included regardless of registration order. */
135
+ getByExtends(abstractKind) {
136
+ const result = [];
137
+ const queue = [abstractKind];
138
+ while (queue.length > 0) {
139
+ const parent = queue.shift();
140
+ const children = this.extendedBy.get(parent);
141
+ if (!children)
142
+ continue;
143
+ for (const child of children) {
144
+ const def = this.defs.get(child);
145
+ if (def)
146
+ result.push(def);
147
+ queue.push(child);
148
+ }
149
+ }
150
+ return result;
151
+ }
152
+ kinds() {
153
+ return Array.from(this.defs.keys());
154
+ }
155
+ }
@@ -0,0 +1,38 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import type { AliasResolver } from "./alias-resolver.js";
3
+ import type { DefinitionRegistry } from "./definition-registry.js";
4
+ export interface ResourceNode {
5
+ kind: string;
6
+ name: string;
7
+ }
8
+ export interface DependencyGraph {
9
+ /** Topological order: each resource appears after all its dependencies (leaves first).
10
+ * Present only when the graph is acyclic. */
11
+ order?: ReadonlyArray<ResourceNode>;
12
+ /** The cycle path when a circular dependency is detected.
13
+ * The first and last elements are the same resource, tracing the full loop. */
14
+ cycle?: ReadonlyArray<ResourceNode>;
15
+ }
16
+ /**
17
+ * Builds a directed acyclic graph (DAG) of runtime resource dependencies and
18
+ * returns either a topological initialization order or the cycle path.
19
+ *
20
+ * Edges represent boot-time dependencies only:
21
+ * - x-telo-ref fields that fall within a scope visibility path are excluded
22
+ * (scoped resources are initialized on demand at runtime, not at boot).
23
+ * - x-telo-scope fields themselves are excluded from the graph.
24
+ *
25
+ * The registry is queried for each resource's field map by kind — callers do
26
+ * not pre-compute or pass field maps separately.
27
+ */
28
+ export declare function buildDependencyGraph(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver): DependencyGraph;
29
+ /**
30
+ * Formats a cycle result into a human-readable error string matching the spec:
31
+ *
32
+ * Circular dependency detected:
33
+ * Run.Sequence "DataSync"
34
+ * → Http.Server "Api"
35
+ * → Run.Sequence "DataSync"
36
+ */
37
+ export declare function formatCycle(cycle: ReadonlyArray<ResourceNode>): string;
38
+ //# sourceMappingURL=dependency-graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency-graph.d.ts","sourceRoot":"","sources":["../src/dependency-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAGnE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B;kDAC8C;IAC9C,KAAK,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACpC;oFACgF;IAChF,KAAK,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACrC;AAOD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,GACtB,eAAe,CAsFjB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,MAAM,CAOtE"}
@@ -0,0 +1,155 @@
1
+ import { isRefEntry, isScopeEntry, resolveFieldValues } from "./reference-field-map.js";
2
+ /** System resource kinds that are not runtime nodes in the dependency graph. */
3
+ const SYSTEM_KINDS = new Set(["Kernel.Definition", "Kernel.Import"]);
4
+ const nodeKey = (kind, name) => `${kind}\0${name}`;
5
+ /**
6
+ * Builds a directed acyclic graph (DAG) of runtime resource dependencies and
7
+ * returns either a topological initialization order or the cycle path.
8
+ *
9
+ * Edges represent boot-time dependencies only:
10
+ * - x-telo-ref fields that fall within a scope visibility path are excluded
11
+ * (scoped resources are initialized on demand at runtime, not at boot).
12
+ * - x-telo-scope fields themselves are excluded from the graph.
13
+ *
14
+ * The registry is queried for each resource's field map by kind — callers do
15
+ * not pre-compute or pass field maps separately.
16
+ */
17
+ export function buildDependencyGraph(resources, registry, aliases) {
18
+ // --- Build node set ---
19
+ const nodes = new Map();
20
+ for (const r of resources) {
21
+ if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind))
22
+ continue;
23
+ const key = nodeKey(r.kind, r.metadata.name);
24
+ nodes.set(key, { kind: r.kind, name: r.metadata.name });
25
+ }
26
+ // --- Build adjacency: from → deps (from depends on dep) ---
27
+ const deps = new Map();
28
+ for (const key of nodes.keys())
29
+ deps.set(key, new Set());
30
+ for (const r of resources) {
31
+ if (!r.metadata?.name || !r.kind || SYSTEM_KINDS.has(r.kind))
32
+ continue;
33
+ const sourceKey = nodeKey(r.kind, r.metadata.name);
34
+ const fieldMap = registry.getFieldMapForKind(r.kind, aliases);
35
+ if (!fieldMap)
36
+ continue;
37
+ // Collect names of resources declared inside scope fields — these are initialized
38
+ // on-demand at runtime, not at boot, so edges pointing to them are excluded from the DAG.
39
+ const scopedNames = new Set();
40
+ for (const [scopeFieldPath, entry] of fieldMap) {
41
+ if (!isScopeEntry(entry))
42
+ continue;
43
+ const scopeVal = r[scopeFieldPath];
44
+ if (!Array.isArray(scopeVal))
45
+ continue;
46
+ for (const item of scopeVal) {
47
+ const name = item?.metadata?.name;
48
+ if (typeof name === "string")
49
+ scopedNames.add(name);
50
+ }
51
+ }
52
+ for (const [fieldPath, entry] of fieldMap) {
53
+ if (!isRefEntry(entry))
54
+ continue;
55
+ for (const val of resolveFieldValues(r, fieldPath)) {
56
+ if (!val || typeof val !== "object")
57
+ continue;
58
+ const ref = val;
59
+ if (!ref.kind || !ref.name)
60
+ continue;
61
+ // Edges to scoped resources are runtime deps, not boot-time deps — exclude from DAG
62
+ if (scopedNames.has(ref.name))
63
+ continue;
64
+ const targetKey = nodeKey(ref.kind, ref.name);
65
+ if (nodes.has(targetKey)) {
66
+ deps.get(sourceKey).add(targetKey);
67
+ }
68
+ }
69
+ }
70
+ }
71
+ // --- Kahn's topological sort ---
72
+ // in-degree[X] = number of X's dependencies (size of deps[X])
73
+ // reverse[dep] = set of nodes that depend on dep (for degree decrement)
74
+ const inDegree = new Map();
75
+ const reverse = new Map();
76
+ for (const key of nodes.keys()) {
77
+ inDegree.set(key, deps.get(key).size);
78
+ reverse.set(key, new Set());
79
+ }
80
+ for (const [from, depSet] of deps) {
81
+ for (const dep of depSet) {
82
+ reverse.get(dep)?.add(from);
83
+ }
84
+ }
85
+ const queue = [];
86
+ for (const [key, deg] of inDegree) {
87
+ if (deg === 0)
88
+ queue.push(key);
89
+ }
90
+ const sorted = [];
91
+ while (queue.length > 0) {
92
+ const key = queue.shift();
93
+ sorted.push(nodes.get(key));
94
+ for (const dependent of reverse.get(key)) {
95
+ const deg = inDegree.get(dependent) - 1;
96
+ inDegree.set(dependent, deg);
97
+ if (deg === 0)
98
+ queue.push(dependent);
99
+ }
100
+ }
101
+ if (sorted.length === nodes.size) {
102
+ return { order: sorted };
103
+ }
104
+ return { cycle: findCycle(nodes, deps) };
105
+ }
106
+ /**
107
+ * Formats a cycle result into a human-readable error string matching the spec:
108
+ *
109
+ * Circular dependency detected:
110
+ * Run.Sequence "DataSync"
111
+ * → Http.Server "Api"
112
+ * → Run.Sequence "DataSync"
113
+ */
114
+ export function formatCycle(cycle) {
115
+ const lines = ["Circular dependency detected:"];
116
+ lines.push(` ${cycle[0].kind} "${cycle[0].name}"`);
117
+ for (const node of cycle.slice(1)) {
118
+ lines.push(` → ${node.kind} "${node.name}"`);
119
+ }
120
+ return lines.join("\n");
121
+ }
122
+ // --- Internals ---
123
+ /** DFS cycle detection — returns the cycle path with the repeated start node appended. */
124
+ function findCycle(nodes, deps) {
125
+ const state = new Map();
126
+ for (const key of nodes.keys())
127
+ state.set(key, "unvisited");
128
+ const stack = [];
129
+ function dfs(key) {
130
+ state.set(key, "visiting");
131
+ stack.push(key);
132
+ for (const dep of deps.get(key) ?? []) {
133
+ if (state.get(dep) === "visiting") {
134
+ const start = stack.indexOf(dep);
135
+ return [...stack.slice(start), dep];
136
+ }
137
+ if (state.get(dep) === "unvisited") {
138
+ const result = dfs(dep);
139
+ if (result)
140
+ return result;
141
+ }
142
+ }
143
+ stack.pop();
144
+ state.set(key, "visited");
145
+ return null;
146
+ }
147
+ for (const key of nodes.keys()) {
148
+ if (state.get(key) === "unvisited") {
149
+ const result = dfs(key);
150
+ if (result)
151
+ return result.map((k) => nodes.get(k));
152
+ }
153
+ }
154
+ return [];
155
+ }
@@ -0,0 +1,9 @@
1
+ export { HttpAdapter } from "./adapters/http-adapter.js";
2
+ export { createNodeAdapter, NodeAdapter } from "./adapters/node-adapter.js";
3
+ export { RegistryAdapter } from "./adapters/registry-adapter.js";
4
+ export { AnalysisRegistry } from "./analysis-registry.js";
5
+ export { StaticAnalyzer } from "./analyzer.js";
6
+ export { Loader } from "./manifest-loader.js";
7
+ export { DiagnosticSeverity } from "./types.js";
8
+ export type { AnalysisDiagnostic, AnalysisOptions, LoadOptions, ManifestAdapter, Position, PositionIndex, Range } from "./types.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,YAAY,EACR,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,eAAe,EACf,QAAQ,EACR,aAAa,EACb,KAAK,EACR,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { HttpAdapter } from "./adapters/http-adapter.js";
2
+ export { createNodeAdapter, NodeAdapter } from "./adapters/node-adapter.js";
3
+ export { RegistryAdapter } from "./adapters/registry-adapter.js";
4
+ export { AnalysisRegistry } from "./analysis-registry.js";
5
+ export { StaticAnalyzer } from "./analyzer.js";
6
+ export { Loader } from "./manifest-loader.js";
7
+ export { DiagnosticSeverity } from "./types.js";
@@ -0,0 +1,11 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+ import type { LoadOptions, ManifestAdapter } from "./types.js";
3
+ export declare class Loader {
4
+ protected adapters: ManifestAdapter[];
5
+ constructor(extraAdapters?: ManifestAdapter[]);
6
+ register(adapter: ManifestAdapter): this;
7
+ private pick;
8
+ loadModule(url: string, options?: LoadOptions): Promise<ResourceManifest[]>;
9
+ loadManifests(entryUrl: string): Promise<ResourceManifest[]>;
10
+ }
11
+ //# sourceMappingURL=manifest-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-loader.d.ts","sourceRoot":"","sources":["../src/manifest-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKrD,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAA2B,MAAM,YAAY,CAAC;AAExF,qBAAa,MAAM;IACjB,SAAS,CAAC,QAAQ,EAAE,eAAe,EAAE,CAA8C;gBAEvE,aAAa,GAAE,eAAe,EAAO;IAIjD,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAKxC,OAAO,CAAC,IAAI;IAMN,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAgE3E,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAwCnE"}