@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.
- package/LICENSE +17 -0
- package/dist/adapters/http-adapter.d.ts +10 -0
- package/dist/adapters/http-adapter.d.ts.map +1 -0
- package/dist/adapters/http-adapter.js +17 -0
- package/dist/adapters/node-adapter.d.ts +15 -0
- package/dist/adapters/node-adapter.d.ts.map +1 -0
- package/dist/adapters/node-adapter.js +32 -0
- package/dist/adapters/registry-adapter.d.ts +11 -0
- package/dist/adapters/registry-adapter.d.ts.map +1 -0
- package/dist/adapters/registry-adapter.js +33 -0
- package/dist/alias-resolver.d.ts +12 -0
- package/dist/alias-resolver.d.ts.map +1 -0
- package/dist/alias-resolver.js +36 -0
- package/dist/analysis-registry.d.ts +29 -0
- package/dist/analysis-registry.d.ts.map +1 -0
- package/dist/analysis-registry.js +55 -0
- package/dist/analyzer.d.ts +14 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +314 -0
- package/dist/builtins.d.ts +3 -0
- package/dist/builtins.d.ts.map +1 -0
- package/dist/builtins.js +109 -0
- package/dist/cel-environment.d.ts +12 -0
- package/dist/cel-environment.d.ts.map +1 -0
- package/dist/cel-environment.js +59 -0
- package/dist/definition-registry.d.ts +58 -0
- package/dist/definition-registry.d.ts.map +1 -0
- package/dist/definition-registry.js +155 -0
- package/dist/dependency-graph.d.ts +38 -0
- package/dist/dependency-graph.d.ts.map +1 -0
- package/dist/dependency-graph.js +155 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/manifest-loader.d.ts +11 -0
- package/dist/manifest-loader.d.ts.map +1 -0
- package/dist/manifest-loader.js +194 -0
- package/dist/normalize-inline-resources.d.ts +22 -0
- package/dist/normalize-inline-resources.d.ts.map +1 -0
- package/dist/normalize-inline-resources.js +136 -0
- package/dist/precompile.d.ts +9 -0
- package/dist/precompile.d.ts.map +1 -0
- package/dist/precompile.js +51 -0
- package/dist/reference-field-map.d.ts +53 -0
- package/dist/reference-field-map.d.ts.map +1 -0
- package/dist/reference-field-map.js +107 -0
- package/dist/schema-compat.d.ts +42 -0
- package/dist/schema-compat.d.ts.map +1 -0
- package/dist/schema-compat.js +234 -0
- package/dist/scope-resolver.d.ts +5 -0
- package/dist/scope-resolver.d.ts.map +1 -0
- package/dist/scope-resolver.js +13 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/validate-cel-context.d.ts +24 -0
- package/dist/validate-cel-context.d.ts.map +1 -0
- package/dist/validate-cel-context.js +136 -0
- package/dist/validate-references.d.ts +19 -0
- package/dist/validate-references.d.ts.map +1 -0
- package/dist/validate-references.js +275 -0
- package/package.json +34 -0
- package/src/adapters/http-adapter.ts +23 -0
- package/src/adapters/node-adapter.ts +38 -0
- package/src/adapters/registry-adapter.ts +43 -0
- package/src/alias-resolver.ts +37 -0
- package/src/analysis-registry.ts +68 -0
- package/src/analyzer.ts +399 -0
- package/src/builtins.ts +111 -0
- package/src/cel-environment.ts +70 -0
- package/src/definition-registry.ts +170 -0
- package/src/dependency-graph.ts +187 -0
- package/src/index.ts +17 -0
- package/src/manifest-loader.ts +203 -0
- package/src/normalize-inline-resources.ts +170 -0
- package/src/precompile.ts +54 -0
- package/src/reference-field-map.ts +147 -0
- package/src/schema-compat.ts +264 -0
- package/src/scope-resolver.ts +13 -0
- package/src/types.ts +68 -0
- package/src/validate-cel-context.ts +142 -0
- package/src/validate-references.ts +311 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { isMap, isPair, isScalar, isSeq, parseAllDocuments } from "yaml";
|
|
2
|
+
import { HttpAdapter } from "./adapters/http-adapter.js";
|
|
3
|
+
import { RegistryAdapter } from "./adapters/registry-adapter.js";
|
|
4
|
+
import { precompileDoc } from "./precompile.js";
|
|
5
|
+
export class Loader {
|
|
6
|
+
adapters = [new HttpAdapter(), new RegistryAdapter()];
|
|
7
|
+
constructor(extraAdapters = []) {
|
|
8
|
+
this.adapters.unshift(...extraAdapters);
|
|
9
|
+
}
|
|
10
|
+
register(adapter) {
|
|
11
|
+
this.adapters.unshift(adapter);
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
14
|
+
pick(url) {
|
|
15
|
+
const a = this.adapters.find((a) => a.supports(url));
|
|
16
|
+
if (!a)
|
|
17
|
+
throw new Error(`No adapter found for: ${url}`);
|
|
18
|
+
return a;
|
|
19
|
+
}
|
|
20
|
+
async loadModule(url, options) {
|
|
21
|
+
const { text, source } = await this.pick(url).read(url);
|
|
22
|
+
const parsedDocuments = parseAllDocuments(text);
|
|
23
|
+
const rawDocs = parsedDocuments.map((d) => d.toJSON());
|
|
24
|
+
const offsets = documentLineOffsets(text);
|
|
25
|
+
const lineOffsets = buildLineOffsets(text);
|
|
26
|
+
const resolved = [];
|
|
27
|
+
let docIdx = 0;
|
|
28
|
+
for (const rawDoc of rawDocs) {
|
|
29
|
+
const currentDocIdx = docIdx++;
|
|
30
|
+
const sourceLine = offsets[currentDocIdx] ?? 0;
|
|
31
|
+
const positionIndex = buildPositionIndex(parsedDocuments[currentDocIdx], lineOffsets);
|
|
32
|
+
if (rawDoc === null || rawDoc === undefined)
|
|
33
|
+
continue;
|
|
34
|
+
let compiledDocs;
|
|
35
|
+
if (options?.compile) {
|
|
36
|
+
try {
|
|
37
|
+
const result = precompileDoc(rawDoc);
|
|
38
|
+
compiledDocs = Array.isArray(result) ? result : [result];
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new Error(`Failed to compile manifest in ${source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
compiledDocs = [rawDoc];
|
|
46
|
+
}
|
|
47
|
+
for (const doc of compiledDocs) {
|
|
48
|
+
if (doc === null || doc === undefined)
|
|
49
|
+
continue;
|
|
50
|
+
const manifest = doc;
|
|
51
|
+
const metadata = { ...manifest.metadata, source, sourceLine };
|
|
52
|
+
// positionIndex is non-enumerable so it is invisible to spread, JSON.stringify,
|
|
53
|
+
// and schema validation — but still accessible via (m.metadata as any).positionIndex.
|
|
54
|
+
Object.defineProperty(metadata, "positionIndex", {
|
|
55
|
+
value: positionIndex,
|
|
56
|
+
enumerable: false,
|
|
57
|
+
writable: true,
|
|
58
|
+
configurable: true,
|
|
59
|
+
});
|
|
60
|
+
resolved.push({ ...manifest, metadata });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const moduleManifests = resolved.filter((m) => m.kind === "Kernel.Module");
|
|
64
|
+
if (moduleManifests.length > 1) {
|
|
65
|
+
throw new Error(`File '${source}' contains ${moduleManifests.length} Kernel.Module declarations. Maximum one is allowed.`);
|
|
66
|
+
}
|
|
67
|
+
const moduleManifest = moduleManifests[0];
|
|
68
|
+
const moduleName = moduleManifest?.metadata?.name;
|
|
69
|
+
if (moduleName) {
|
|
70
|
+
for (const manifest of resolved) {
|
|
71
|
+
if (manifest.kind !== "Kernel.Module" && !manifest.metadata?.module) {
|
|
72
|
+
manifest.metadata = { ...manifest.metadata, module: moduleName };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return resolved;
|
|
77
|
+
}
|
|
78
|
+
async loadManifests(entryUrl) {
|
|
79
|
+
const visited = new Set([entryUrl]);
|
|
80
|
+
const entry = await this.loadModule(entryUrl);
|
|
81
|
+
const importedDefs = [];
|
|
82
|
+
const queue = [...entry];
|
|
83
|
+
while (queue.length > 0) {
|
|
84
|
+
const m = queue.shift();
|
|
85
|
+
if (m.kind !== "Kernel.Import")
|
|
86
|
+
continue;
|
|
87
|
+
const importSource = m.source;
|
|
88
|
+
if (!importSource)
|
|
89
|
+
continue;
|
|
90
|
+
const base = m.metadata?.source ?? entryUrl;
|
|
91
|
+
const importUrl = this.pick(base).resolveRelative(base, importSource);
|
|
92
|
+
if (visited.has(importUrl))
|
|
93
|
+
continue;
|
|
94
|
+
visited.add(importUrl);
|
|
95
|
+
let imported;
|
|
96
|
+
try {
|
|
97
|
+
imported = await this.loadModule(importUrl);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
101
|
+
e.sourceLine = m.metadata?.sourceLine ?? 0;
|
|
102
|
+
throw e;
|
|
103
|
+
}
|
|
104
|
+
const importedModule = imported.find((im) => im.kind === "Kernel.Module");
|
|
105
|
+
if (importedModule?.metadata?.name) {
|
|
106
|
+
m.metadata = {
|
|
107
|
+
...m.metadata,
|
|
108
|
+
resolvedModuleName: importedModule.metadata.name,
|
|
109
|
+
resolvedNamespace: importedModule.metadata.namespace ?? null,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
for (const im of imported) {
|
|
113
|
+
if (im.kind === "Kernel.Definition")
|
|
114
|
+
importedDefs.push(im);
|
|
115
|
+
if (im.kind === "Kernel.Import")
|
|
116
|
+
queue.push(im);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return [...entry, ...importedDefs];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function documentLineOffsets(text) {
|
|
123
|
+
const offsets = [0];
|
|
124
|
+
const lines = text.split("\n");
|
|
125
|
+
for (let i = 0; i < lines.length; i++) {
|
|
126
|
+
const t = lines[i].trimEnd();
|
|
127
|
+
if (t === "---" || t.startsWith("--- "))
|
|
128
|
+
offsets.push(i + 1);
|
|
129
|
+
}
|
|
130
|
+
return offsets;
|
|
131
|
+
}
|
|
132
|
+
/** Builds a byte-offset-to-line/character lookup table from raw text. */
|
|
133
|
+
function buildLineOffsets(text) {
|
|
134
|
+
const offsets = [0];
|
|
135
|
+
for (let i = 0; i < text.length; i++) {
|
|
136
|
+
if (text[i] === "\n")
|
|
137
|
+
offsets.push(i + 1);
|
|
138
|
+
}
|
|
139
|
+
return offsets;
|
|
140
|
+
}
|
|
141
|
+
function offsetToPosition(offset, lineOffsets) {
|
|
142
|
+
let lo = 0;
|
|
143
|
+
let hi = lineOffsets.length - 1;
|
|
144
|
+
while (lo < hi) {
|
|
145
|
+
const mid = (lo + hi + 1) >> 1;
|
|
146
|
+
if (lineOffsets[mid] <= offset)
|
|
147
|
+
lo = mid;
|
|
148
|
+
else
|
|
149
|
+
hi = mid - 1;
|
|
150
|
+
}
|
|
151
|
+
return { line: lo, character: offset - lineOffsets[lo] };
|
|
152
|
+
}
|
|
153
|
+
/** Walks the YAML AST and records source ranges for every field value, keyed by
|
|
154
|
+
* dotted path (e.g. "kind", "config.handler", "config.routes[0].path"). */
|
|
155
|
+
function buildPositionIndex(doc, lineOffsets) {
|
|
156
|
+
const index = new Map();
|
|
157
|
+
function recordNode(node, path) {
|
|
158
|
+
if (!node || !node.range)
|
|
159
|
+
return;
|
|
160
|
+
const [start, , end] = node.range;
|
|
161
|
+
index.set(path, {
|
|
162
|
+
start: offsetToPosition(start, lineOffsets),
|
|
163
|
+
end: offsetToPosition(end, lineOffsets),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function walk(node, path) {
|
|
167
|
+
if (isMap(node)) {
|
|
168
|
+
for (const pair of node.items) {
|
|
169
|
+
if (!isPair(pair))
|
|
170
|
+
continue;
|
|
171
|
+
const key = isScalar(pair.key) ? String(pair.key.value) : null;
|
|
172
|
+
if (key == null)
|
|
173
|
+
continue;
|
|
174
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
175
|
+
if (pair.value != null) {
|
|
176
|
+
recordNode(pair.value, childPath);
|
|
177
|
+
walk(pair.value, childPath);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else if (isSeq(node)) {
|
|
182
|
+
for (let i = 0; i < node.items.length; i++) {
|
|
183
|
+
const item = node.items[i];
|
|
184
|
+
const childPath = `${path}[${i}]`;
|
|
185
|
+
recordNode(item, childPath);
|
|
186
|
+
walk(item, childPath);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (doc.contents) {
|
|
191
|
+
walk(doc.contents, "");
|
|
192
|
+
}
|
|
193
|
+
return index;
|
|
194
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
|
+
import type { DefinitionRegistry } from "./definition-registry.js";
|
|
3
|
+
import type { AliasResolver } from "./alias-resolver.js";
|
|
4
|
+
/**
|
|
5
|
+
* Phase 2 — Inline resource normalization.
|
|
6
|
+
*
|
|
7
|
+
* After all manifests and definitions are loaded, walks every non-system resource's
|
|
8
|
+
* x-telo-ref slots. For each inline resource value (has keys beyond kind/name/metadata),
|
|
9
|
+
* assigns a deterministic name, extracts it as a first-class manifest, and replaces
|
|
10
|
+
* the inline value in-place with `{kind, name}`. Newly extracted resources are enqueued
|
|
11
|
+
* so nested inlines are resolved in the same pass.
|
|
12
|
+
*
|
|
13
|
+
* Naming scheme:
|
|
14
|
+
* {parentName}_{pathSegment}[_{itemName|index}]_{fieldName}
|
|
15
|
+
* e.g. TestBasicAddition_steps_AddTwoNumbers_invoke
|
|
16
|
+
* TestBasicAddition_steps_0_invoke (when step has no name)
|
|
17
|
+
*
|
|
18
|
+
* Returns a new array containing the original manifests (mutated in-place) plus all
|
|
19
|
+
* extracted manifests. The original array is not mutated.
|
|
20
|
+
*/
|
|
21
|
+
export declare function normalizeInlineResources(resources: ResourceManifest[], registry: DefinitionRegistry, aliases?: AliasResolver): ResourceManifest[];
|
|
22
|
+
//# sourceMappingURL=normalize-inline-resources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-inline-resources.d.ts","sourceRoot":"","sources":["../src/normalize-inline-resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AASzD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,CAAC,EAAE,aAAa,GACtB,gBAAgB,EAAE,CAkDpB"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { isRefEntry, isScopeEntry, isInlineResource } from "./reference-field-map.js";
|
|
2
|
+
const SYSTEM_KINDS = new Set(["Kernel.Definition", "Kernel.Module", "Kernel.Import"]);
|
|
3
|
+
/** Replaces characters outside [a-zA-Z0-9_] with underscores. */
|
|
4
|
+
function sanitizeName(raw) {
|
|
5
|
+
return raw.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Phase 2 — Inline resource normalization.
|
|
9
|
+
*
|
|
10
|
+
* After all manifests and definitions are loaded, walks every non-system resource's
|
|
11
|
+
* x-telo-ref slots. For each inline resource value (has keys beyond kind/name/metadata),
|
|
12
|
+
* assigns a deterministic name, extracts it as a first-class manifest, and replaces
|
|
13
|
+
* the inline value in-place with `{kind, name}`. Newly extracted resources are enqueued
|
|
14
|
+
* so nested inlines are resolved in the same pass.
|
|
15
|
+
*
|
|
16
|
+
* Naming scheme:
|
|
17
|
+
* {parentName}_{pathSegment}[_{itemName|index}]_{fieldName}
|
|
18
|
+
* e.g. TestBasicAddition_steps_AddTwoNumbers_invoke
|
|
19
|
+
* TestBasicAddition_steps_0_invoke (when step has no name)
|
|
20
|
+
*
|
|
21
|
+
* Returns a new array containing the original manifests (mutated in-place) plus all
|
|
22
|
+
* extracted manifests. The original array is not mutated.
|
|
23
|
+
*/
|
|
24
|
+
export function normalizeInlineResources(resources, registry, aliases) {
|
|
25
|
+
const result = [...resources];
|
|
26
|
+
// Queue: all non-system resources with a name. Extracted resources are appended.
|
|
27
|
+
const queue = resources.filter((r) => typeof r.metadata?.name === "string" && !!r.kind && !SYSTEM_KINDS.has(r.kind));
|
|
28
|
+
let i = 0;
|
|
29
|
+
while (i < queue.length) {
|
|
30
|
+
const resource = queue[i++];
|
|
31
|
+
const fieldMap = registry.getFieldMapForKind(resource.kind, aliases);
|
|
32
|
+
if (!fieldMap)
|
|
33
|
+
continue;
|
|
34
|
+
const parentName = resource.metadata.name;
|
|
35
|
+
const parentModule = resource.metadata.module;
|
|
36
|
+
// Collect scope visibility prefixes so we can route extracted resources correctly.
|
|
37
|
+
const scopePrefixes = [];
|
|
38
|
+
for (const [, entry] of fieldMap) {
|
|
39
|
+
if (!isScopeEntry(entry))
|
|
40
|
+
continue;
|
|
41
|
+
const paths = Array.isArray(entry.scope) ? entry.scope : [entry.scope];
|
|
42
|
+
for (const p of paths) {
|
|
43
|
+
scopePrefixes.push(p.replace(/^\//, "").replace(/\//g, "."));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const [fieldPath, entry] of fieldMap) {
|
|
47
|
+
if (!isRefEntry(entry))
|
|
48
|
+
continue;
|
|
49
|
+
const inScope = scopePrefixes.some((prefix) => fieldPath === prefix ||
|
|
50
|
+
fieldPath.startsWith(prefix + ".") ||
|
|
51
|
+
fieldPath.startsWith(prefix + "["));
|
|
52
|
+
const invocationContext = isRefEntry(entry) ? entry.context : undefined;
|
|
53
|
+
const extracted = extractInlinesAtPath(resource, fieldPath, parentName, parentModule, invocationContext);
|
|
54
|
+
for (const manifest of extracted) {
|
|
55
|
+
result.push(manifest);
|
|
56
|
+
queue.push(manifest);
|
|
57
|
+
// TODO Phase 5: when inScope, add to parent's scope array instead of outer set
|
|
58
|
+
void inScope;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Walks `resource` following `fieldPath` (dot notation, `[]` = array traversal).
|
|
66
|
+
* Mutates the resource in-place: replaces each inline value with `{kind, name}`.
|
|
67
|
+
* Returns the extracted manifests.
|
|
68
|
+
*/
|
|
69
|
+
function extractInlinesAtPath(resource, fieldPath, parentName, parentModule, invocationContext) {
|
|
70
|
+
const extracted = [];
|
|
71
|
+
const parts = fieldPath.split(".");
|
|
72
|
+
function traverse(obj, partsLeft, nameParts) {
|
|
73
|
+
if (!obj || typeof obj !== "object" || partsLeft.length === 0)
|
|
74
|
+
return;
|
|
75
|
+
const [head, ...rest] = partsLeft;
|
|
76
|
+
const isArr = head.endsWith("[]");
|
|
77
|
+
const key = isArr ? head.slice(0, -2) : head;
|
|
78
|
+
const container = obj;
|
|
79
|
+
const val = container[key];
|
|
80
|
+
if (val == null)
|
|
81
|
+
return;
|
|
82
|
+
if (isArr) {
|
|
83
|
+
if (!Array.isArray(val))
|
|
84
|
+
return;
|
|
85
|
+
for (let idx = 0; idx < val.length; idx++) {
|
|
86
|
+
const elem = val[idx];
|
|
87
|
+
if (!elem || typeof elem !== "object")
|
|
88
|
+
continue;
|
|
89
|
+
const elemId = typeof elem.name === "string"
|
|
90
|
+
? elem.name
|
|
91
|
+
: String(idx);
|
|
92
|
+
if (rest.length === 0) {
|
|
93
|
+
// Array element itself is the ref slot
|
|
94
|
+
if (isInlineResource(elem)) {
|
|
95
|
+
const name = sanitizeName([parentName, ...nameParts, key, elemId].join("_"));
|
|
96
|
+
extracted.push(buildManifest(elem, name, parentModule, invocationContext));
|
|
97
|
+
val[idx] = { kind: elem.kind, name };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
traverse(elem, rest, [...nameParts, key, elemId]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
if (rest.length === 0) {
|
|
107
|
+
// val is the ref slot
|
|
108
|
+
if (val && typeof val === "object" && !Array.isArray(val) && isInlineResource(val)) {
|
|
109
|
+
const name = sanitizeName([parentName, ...nameParts, key].join("_"));
|
|
110
|
+
extracted.push(buildManifest(val, name, parentModule, invocationContext));
|
|
111
|
+
container[key] = { kind: val.kind, name };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
traverse(val, rest, [...nameParts, key]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
traverse(resource, parts, []);
|
|
120
|
+
return extracted;
|
|
121
|
+
}
|
|
122
|
+
function buildManifest(inline, name, parentModule, invocationContext) {
|
|
123
|
+
const existingMeta = inline.metadata && typeof inline.metadata === "object"
|
|
124
|
+
? inline.metadata
|
|
125
|
+
: {};
|
|
126
|
+
return {
|
|
127
|
+
...inline,
|
|
128
|
+
metadata: {
|
|
129
|
+
...existingMeta,
|
|
130
|
+
name,
|
|
131
|
+
// Inherit parent module only if the inline doesn't already declare one
|
|
132
|
+
...(parentModule && !existingMeta.module ? { module: parentModule } : {}),
|
|
133
|
+
...(invocationContext ? { xTeloInvocationContext: invocationContext } : {}),
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walks a raw YAML document and replaces all "${{ expr }}" strings with
|
|
3
|
+
* CompiledValue wrappers. Throws on CEL syntax errors.
|
|
4
|
+
* Intended to be called once per document at load time.
|
|
5
|
+
* Kernel.Definition documents are returned unchanged — their schema fields
|
|
6
|
+
* are static metadata and must not be treated as CEL templates.
|
|
7
|
+
*/
|
|
8
|
+
export declare function precompileDoc(doc: unknown): unknown;
|
|
9
|
+
//# sourceMappingURL=precompile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"precompile.d.ts","sourceRoot":"","sources":["../src/precompile.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAanD"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { celEnvironment } from "./cel-environment.js";
|
|
2
|
+
const TEMPLATE_REGEX = /\$\{\{\s*([^}]+?)\s*\}\}/g;
|
|
3
|
+
const EXACT_TEMPLATE_REGEX = /^\s*\$\{\{\s*([^}]+?)\s*\}\}\s*$/;
|
|
4
|
+
/**
|
|
5
|
+
* Walks a raw YAML document and replaces all "${{ expr }}" strings with
|
|
6
|
+
* CompiledValue wrappers. Throws on CEL syntax errors.
|
|
7
|
+
* Intended to be called once per document at load time.
|
|
8
|
+
* Kernel.Definition documents are returned unchanged — their schema fields
|
|
9
|
+
* are static metadata and must not be treated as CEL templates.
|
|
10
|
+
*/
|
|
11
|
+
export function precompileDoc(doc) {
|
|
12
|
+
if (typeof doc === "string")
|
|
13
|
+
return compileString(doc);
|
|
14
|
+
if (Array.isArray(doc))
|
|
15
|
+
return doc.map(precompileDoc);
|
|
16
|
+
// Only recurse into plain objects. Class instances (ResourceInstance, ScopeHandle, etc.)
|
|
17
|
+
// are returned as-is — their prototype methods must not be lost by object reconstruction.
|
|
18
|
+
if (doc !== null && typeof doc === "object" && Object.getPrototypeOf(doc) === Object.prototype) {
|
|
19
|
+
const result = {};
|
|
20
|
+
for (const [k, v] of Object.entries(doc)) {
|
|
21
|
+
result[k] = precompileDoc(v);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
return doc;
|
|
26
|
+
}
|
|
27
|
+
function compileString(s) {
|
|
28
|
+
if (!s.includes("${{"))
|
|
29
|
+
return s;
|
|
30
|
+
const exact = s.match(EXACT_TEMPLATE_REGEX);
|
|
31
|
+
if (exact) {
|
|
32
|
+
const fn = celEnvironment.parse(exact[1].trim());
|
|
33
|
+
return { __compiled: true, call: (ctx) => fn(ctx) };
|
|
34
|
+
}
|
|
35
|
+
// Interpolated template — collect literal parts + compiled sub-expressions
|
|
36
|
+
const parts = [];
|
|
37
|
+
let last = 0;
|
|
38
|
+
for (const m of s.matchAll(TEMPLATE_REGEX)) {
|
|
39
|
+
if (m.index > last)
|
|
40
|
+
parts.push(s.slice(last, m.index));
|
|
41
|
+
const fn = celEnvironment.parse(m[1].trim());
|
|
42
|
+
parts.push({ __compiled: true, call: (ctx) => fn(ctx) });
|
|
43
|
+
last = m.index + m[0].length;
|
|
44
|
+
}
|
|
45
|
+
if (last < s.length)
|
|
46
|
+
parts.push(s.slice(last));
|
|
47
|
+
return {
|
|
48
|
+
__compiled: true,
|
|
49
|
+
call: (ctx) => parts.map((p) => (typeof p === "string" ? p : String(p.call(ctx) ?? ""))).join(""),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** An entry for a field that carries one or more x-telo-ref constraints. */
|
|
2
|
+
export interface RefFieldEntry {
|
|
3
|
+
/** One or more canonical ref strings ("namespace/module#TypeName" or "kernel#TypeName").
|
|
4
|
+
* Multiple entries arise from anyOf branches. */
|
|
5
|
+
refs: string[];
|
|
6
|
+
/** True when the field path traversed through at least one array (path contains "[]"). */
|
|
7
|
+
isArray: boolean;
|
|
8
|
+
/** x-telo-context schema declared on this ref slot, if any. Describes the CEL invocation
|
|
9
|
+
* context available to resources placed in this slot. */
|
|
10
|
+
context?: Record<string, any>;
|
|
11
|
+
}
|
|
12
|
+
/** An entry for a field that declares an execution scope (x-telo-scope). */
|
|
13
|
+
export interface ScopeFieldEntry {
|
|
14
|
+
/** JSON Pointer(s) (RFC 6901) declaring where x-telo-ref slots within this field can
|
|
15
|
+
* resolve to the scoped resources. */
|
|
16
|
+
scope: string | string[];
|
|
17
|
+
}
|
|
18
|
+
/** An entry for a field whose schema is resolved dynamically from a referenced resource's
|
|
19
|
+
* definition schema (x-telo-schema-from). */
|
|
20
|
+
export interface SchemaFromFieldEntry {
|
|
21
|
+
/** Full path expression as written in the schema, e.g.:
|
|
22
|
+
* - "backend/$defs/NodeOptions" (relative: sibling x-telo-ref property)
|
|
23
|
+
* - "/backend/$defs/NodeOptions" (absolute: root-level x-telo-ref property) */
|
|
24
|
+
schemaFrom: string;
|
|
25
|
+
}
|
|
26
|
+
export type FieldMapEntry = RefFieldEntry | ScopeFieldEntry | SchemaFromFieldEntry;
|
|
27
|
+
/** Map from field path to its reference or scope metadata.
|
|
28
|
+
* Paths use dot notation; array traversal is denoted by `[]` (e.g. "steps[].invoke"). */
|
|
29
|
+
export type ReferenceFieldMap = Map<string, FieldMapEntry>;
|
|
30
|
+
export declare function isRefEntry(entry: FieldMapEntry): entry is RefFieldEntry;
|
|
31
|
+
export declare function isScopeEntry(entry: FieldMapEntry): entry is ScopeFieldEntry;
|
|
32
|
+
export declare function isSchemaFromEntry(entry: FieldMapEntry): entry is SchemaFromFieldEntry;
|
|
33
|
+
/** Keys that a named reference object may have. Values beyond these indicate an inline resource. */
|
|
34
|
+
export declare const REFERENCE_KEYS: Set<string>;
|
|
35
|
+
/** True when `val` is an inline resource definition rather than a named reference.
|
|
36
|
+
* A named reference (has string `name`) may carry extra keys (e.g. `inputs`) that
|
|
37
|
+
* are runtime call parameters — those are never inline resources. */
|
|
38
|
+
export declare function isInlineResource(val: Record<string, unknown>): boolean;
|
|
39
|
+
/** Resolves all values at a field map path in a resource config.
|
|
40
|
+
* `[]` in a path segment means "iterate array at this key". */
|
|
41
|
+
export declare function resolveFieldValues(obj: unknown, path: string): unknown[];
|
|
42
|
+
/**
|
|
43
|
+
* Traverses a definition's JSON Schema once and returns a field map recording every
|
|
44
|
+
* x-telo-ref slot and every x-telo-scope slot.
|
|
45
|
+
*
|
|
46
|
+
* - A node with `x-telo-ref` → RefFieldEntry with refs: [that value]
|
|
47
|
+
* - A node with `anyOf` whose branches have `x-telo-ref` → RefFieldEntry with all branch refs
|
|
48
|
+
* - A node with `x-telo-scope` → ScopeFieldEntry
|
|
49
|
+
* - A node with `type: array` + `items` → recurse into items with path "fieldName[]"
|
|
50
|
+
* - A node with `properties` → recurse into each property
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildReferenceFieldMap(schema: Record<string, any>): ReferenceFieldMap;
|
|
53
|
+
//# sourceMappingURL=reference-field-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference-field-map.d.ts","sourceRoot":"","sources":["../src/reference-field-map.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B;sDACkD;IAClD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0FAA0F;IAC1F,OAAO,EAAE,OAAO,CAAC;IACjB;8DAC0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B;2CACuC;IACvC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED;8CAC8C;AAC9C,MAAM,WAAW,oBAAoB;IACnC;;qFAEiF;IACjF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAEnF;0FAC0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,aAAa,CAEvE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,eAAe,CAE3E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,IAAI,oBAAoB,CAErF;AAED,oGAAoG;AACpG,eAAO,MAAM,cAAc,aAAwC,CAAC;AAEpE;;sEAEsE;AACtE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAGtE;AAED;gEACgE;AAChE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAiBxE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,iBAAiB,CAQrF"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export function isRefEntry(entry) {
|
|
2
|
+
return "refs" in entry;
|
|
3
|
+
}
|
|
4
|
+
export function isScopeEntry(entry) {
|
|
5
|
+
return "scope" in entry;
|
|
6
|
+
}
|
|
7
|
+
export function isSchemaFromEntry(entry) {
|
|
8
|
+
return "schemaFrom" in entry;
|
|
9
|
+
}
|
|
10
|
+
/** Keys that a named reference object may have. Values beyond these indicate an inline resource. */
|
|
11
|
+
export const REFERENCE_KEYS = new Set(["kind", "name", "metadata"]);
|
|
12
|
+
/** True when `val` is an inline resource definition rather than a named reference.
|
|
13
|
+
* A named reference (has string `name`) may carry extra keys (e.g. `inputs`) that
|
|
14
|
+
* are runtime call parameters — those are never inline resources. */
|
|
15
|
+
export function isInlineResource(val) {
|
|
16
|
+
if (typeof val.name === "string")
|
|
17
|
+
return false;
|
|
18
|
+
return Object.keys(val).some((k) => !REFERENCE_KEYS.has(k));
|
|
19
|
+
}
|
|
20
|
+
/** Resolves all values at a field map path in a resource config.
|
|
21
|
+
* `[]` in a path segment means "iterate array at this key". */
|
|
22
|
+
export function resolveFieldValues(obj, path) {
|
|
23
|
+
const parts = path.split(".");
|
|
24
|
+
let current = [obj];
|
|
25
|
+
for (const part of parts) {
|
|
26
|
+
const isArray = part.endsWith("[]");
|
|
27
|
+
const key = isArray ? part.slice(0, -2) : part;
|
|
28
|
+
const next = [];
|
|
29
|
+
for (const item of current) {
|
|
30
|
+
if (!item || typeof item !== "object")
|
|
31
|
+
continue;
|
|
32
|
+
const val = item[key];
|
|
33
|
+
if (val == null)
|
|
34
|
+
continue;
|
|
35
|
+
if (isArray && Array.isArray(val))
|
|
36
|
+
next.push(...val);
|
|
37
|
+
else if (!isArray)
|
|
38
|
+
next.push(val);
|
|
39
|
+
}
|
|
40
|
+
current = next;
|
|
41
|
+
}
|
|
42
|
+
return current;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Traverses a definition's JSON Schema once and returns a field map recording every
|
|
46
|
+
* x-telo-ref slot and every x-telo-scope slot.
|
|
47
|
+
*
|
|
48
|
+
* - A node with `x-telo-ref` → RefFieldEntry with refs: [that value]
|
|
49
|
+
* - A node with `anyOf` whose branches have `x-telo-ref` → RefFieldEntry with all branch refs
|
|
50
|
+
* - A node with `x-telo-scope` → ScopeFieldEntry
|
|
51
|
+
* - A node with `type: array` + `items` → recurse into items with path "fieldName[]"
|
|
52
|
+
* - A node with `properties` → recurse into each property
|
|
53
|
+
*/
|
|
54
|
+
export function buildReferenceFieldMap(schema) {
|
|
55
|
+
const map = new Map();
|
|
56
|
+
if (schema.properties) {
|
|
57
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
58
|
+
traverseNode(propSchema, key, map);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return map;
|
|
62
|
+
}
|
|
63
|
+
function collectRefs(node) {
|
|
64
|
+
const refs = [];
|
|
65
|
+
if (typeof node["x-telo-ref"] === "string") {
|
|
66
|
+
refs.push(node["x-telo-ref"]);
|
|
67
|
+
}
|
|
68
|
+
if (Array.isArray(node.anyOf)) {
|
|
69
|
+
for (const branch of node.anyOf) {
|
|
70
|
+
if (branch && typeof branch["x-telo-ref"] === "string") {
|
|
71
|
+
refs.push(branch["x-telo-ref"]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return refs;
|
|
76
|
+
}
|
|
77
|
+
function traverseNode(node, path, map) {
|
|
78
|
+
// Scope slot — record and stop; do not recurse into scope contents
|
|
79
|
+
if ("x-telo-scope" in node) {
|
|
80
|
+
map.set(path, { scope: node["x-telo-scope"] });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Schema-from slot — record and stop; no further traversal needed
|
|
84
|
+
if ("x-telo-schema-from" in node) {
|
|
85
|
+
map.set(path, { schemaFrom: node["x-telo-schema-from"] });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Reference slot (direct or via anyOf)
|
|
89
|
+
const refs = collectRefs(node);
|
|
90
|
+
if (refs.length > 0) {
|
|
91
|
+
const entry = { refs, isArray: path.includes("[]") };
|
|
92
|
+
if (node["x-telo-context"])
|
|
93
|
+
entry.context = node["x-telo-context"];
|
|
94
|
+
map.set(path, entry);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Array — recurse into items
|
|
98
|
+
if (node.type === "array" && node.items) {
|
|
99
|
+
traverseNode(node.items, path + "[]", map);
|
|
100
|
+
}
|
|
101
|
+
// Object — recurse into properties
|
|
102
|
+
if (node.properties) {
|
|
103
|
+
for (const [key, propSchema] of Object.entries(node.properties)) {
|
|
104
|
+
traverseNode(propSchema, `${path}.${key}`, map);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
declare const Ajv: any;
|
|
2
|
+
/** Creates a configured AJV instance (allErrors, strict: false, with formats).
|
|
3
|
+
* Called once for the module-level instance and once per DefinitionRegistry instance. */
|
|
4
|
+
export declare function createAjv(): InstanceType<typeof Ajv>;
|
|
5
|
+
export interface CompatibilityResult {
|
|
6
|
+
compatible: boolean;
|
|
7
|
+
issues: string[];
|
|
8
|
+
}
|
|
9
|
+
/** Conservative structural JSON Schema compatibility check.
|
|
10
|
+
* Only flags definite mismatches: missing required fields and primitive type conflicts.
|
|
11
|
+
* Ambiguous cases (anyOf/oneOf/etc.) are treated as compatible. */
|
|
12
|
+
export declare function checkSchemaCompatibility(source: Record<string, any>, target: Record<string, any>): CompatibilityResult;
|
|
13
|
+
export declare function formatSingleError(err: any): string;
|
|
14
|
+
export declare function formatAjvErrors(errors: any[] | null | undefined): string;
|
|
15
|
+
/** A schema validation issue with a dotted-path pointer to the offending field. */
|
|
16
|
+
export interface SchemaIssue {
|
|
17
|
+
message: string;
|
|
18
|
+
/** Dotted path to the field (e.g. "config.handler"). Empty string means root. */
|
|
19
|
+
path: string;
|
|
20
|
+
}
|
|
21
|
+
/** Validate actual data against a JSON Schema. Returns issues with path info, or empty array if valid. */
|
|
22
|
+
export declare function validateAgainstSchema(data: unknown, schema: Record<string, any>): SchemaIssue[];
|
|
23
|
+
/** Resolves a JSON Pointer (RFC 6901, must start with "/") into a schema object.
|
|
24
|
+
* Returns undefined when any segment along the path is missing or not an object. */
|
|
25
|
+
export declare function navigateJsonPointer(schema: unknown, pointer: string): unknown;
|
|
26
|
+
/** Navigate a JSON Schema following a `walkCelExpressions`-style path
|
|
27
|
+
* (e.g. `port`, `routes[0].handler.when`).
|
|
28
|
+
* Dot-separated segments navigate `properties`; `[N]` indices navigate `items`.
|
|
29
|
+
* Stops and returns the current node when a union type (`anyOf`/`oneOf`) is reached.
|
|
30
|
+
* Returns `undefined` if any segment cannot be resolved. */
|
|
31
|
+
export declare function navigateSchemaToExprPath(schema: Record<string, any>, path: string): Record<string, any> | undefined;
|
|
32
|
+
/** Map a JSON Schema type annotation to a CEL type string. */
|
|
33
|
+
export declare function jsonSchemaToCelType(schema: Record<string, any> | undefined): string;
|
|
34
|
+
/** Check whether a CEL return type is compatible with a JSON Schema type constraint. */
|
|
35
|
+
export declare function celTypeSatisfiesJsonSchema(celType: string, schema: Record<string, any>): boolean;
|
|
36
|
+
/** Return a literal placeholder value of the correct schema type for AJV. */
|
|
37
|
+
export declare function celPlaceholderForSchema(schema: Record<string, any>): unknown;
|
|
38
|
+
/** Deep-clone `data`, replacing every pure CEL template string (`${{ expr }}`) with a
|
|
39
|
+
* schema-appropriate placeholder so AJV can validate non-CEL fields without false positives. */
|
|
40
|
+
export declare function substituteCelFields(data: unknown, schema: Record<string, any>): unknown;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=schema-compat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,GAAG,KAA0C,CAAC;AAEpD;0FAC0F;AAC1F,wBAAgB,SAAS,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,CAMpD;AAID,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;oEAEoE;AACpE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,mBAAmB,CAIrB;AAiDD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAGlD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGxE;AAuBD,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,EAAE,CAY/F;AAED;qFACqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7E;AAED;;;;6DAI6D;AAC7D,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAsBjC;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAgBnF;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,OAAO,CAqBT;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAW5E;AAID;iGACiG;AACjG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAqBvF"}
|