@telorun/analyzer 0.1.1 → 0.1.3

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 (52) hide show
  1. package/README.md +233 -0
  2. package/dist/adapters/http-adapter.d.ts +1 -1
  3. package/dist/adapters/http-adapter.d.ts.map +1 -1
  4. package/dist/adapters/http-adapter.js +4 -3
  5. package/dist/adapters/node-adapter.d.ts +1 -1
  6. package/dist/adapters/node-adapter.d.ts.map +1 -1
  7. package/dist/adapters/node-adapter.js +2 -1
  8. package/dist/adapters/registry-adapter.d.ts +5 -1
  9. package/dist/adapters/registry-adapter.d.ts.map +1 -1
  10. package/dist/adapters/registry-adapter.js +27 -7
  11. package/dist/analysis-registry.d.ts +2 -0
  12. package/dist/analysis-registry.d.ts.map +1 -1
  13. package/dist/analysis-registry.js +8 -0
  14. package/dist/analyzer.d.ts.map +1 -1
  15. package/dist/analyzer.js +107 -4
  16. package/dist/cel-environment.d.ts +5 -2
  17. package/dist/cel-environment.d.ts.map +1 -1
  18. package/dist/cel-environment.js +5 -3
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/kernel-globals.d.ts +34 -0
  23. package/dist/kernel-globals.d.ts.map +1 -0
  24. package/dist/kernel-globals.js +94 -0
  25. package/dist/manifest-loader.d.ts +6 -3
  26. package/dist/manifest-loader.d.ts.map +1 -1
  27. package/dist/manifest-loader.js +110 -5
  28. package/dist/schema-compat.d.ts +1 -1
  29. package/dist/schema-compat.d.ts.map +1 -1
  30. package/dist/schema-compat.js +82 -28
  31. package/dist/types.d.ts +12 -0
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/types.js +2 -0
  34. package/dist/validate-cel-context.d.ts +26 -4
  35. package/dist/validate-cel-context.d.ts.map +1 -1
  36. package/dist/validate-cel-context.js +123 -15
  37. package/dist/validate-references.d.ts.map +1 -1
  38. package/dist/validate-references.js +13 -1
  39. package/package.json +21 -2
  40. package/src/adapters/http-adapter.ts +4 -4
  41. package/src/adapters/node-adapter.ts +2 -2
  42. package/src/adapters/registry-adapter.ts +30 -8
  43. package/src/analysis-registry.ts +10 -0
  44. package/src/analyzer.ts +139 -5
  45. package/src/cel-environment.ts +5 -3
  46. package/src/index.ts +2 -4
  47. package/src/kernel-globals.ts +110 -0
  48. package/src/manifest-loader.ts +131 -7
  49. package/src/schema-compat.ts +87 -31
  50. package/src/types.ts +14 -0
  51. package/src/validate-cel-context.ts +150 -15
  52. package/src/validate-references.ts +13 -1
@@ -0,0 +1,110 @@
1
+ import type { ResourceManifest } from "@telorun/sdk";
2
+
3
+ /**
4
+ * Kernel global names available in every CEL evaluation context at runtime.
5
+ * Both `buildKernelGlobalsSchema` (chain-access validation) and
6
+ * `buildTypedCelEnvironment` in cel-environment.ts (CEL type-checking)
7
+ * must stay in sync with this list.
8
+ *
9
+ * Note: `env` is only available in the root module context. Child modules
10
+ * loaded via Kernel.Import do not receive host environment variables.
11
+ * There is no `imports` namespace at runtime — import snapshots are stored
12
+ * under `resources.<alias>`.
13
+ */
14
+ export const KERNEL_GLOBAL_NAMES = ["variables", "secrets", "resources", "env"] as const;
15
+
16
+ const SYSTEM_KINDS = new Set([
17
+ "Kernel.Definition",
18
+ "Kernel.Module",
19
+ "Kernel.Abstract",
20
+ ]);
21
+
22
+ /**
23
+ * Build a typed JSON Schema describing the kernel globals available in the
24
+ * given manifest set. Used to merge into `x-telo-context` schemas so that
25
+ * chain-access validation recognises kernel globals without module authors
26
+ * having to re-declare them.
27
+ *
28
+ * - `variables` / `secrets`: typed from the `Kernel.Module` declaration
29
+ * - `resources`: enumerates all non-system resource names
30
+ * - `env`: dynamic (runtime env vars, root module only)
31
+ */
32
+ export function buildKernelGlobalsSchema(
33
+ manifests: ResourceManifest[],
34
+ ): Record<string, any> {
35
+ const moduleManifest = manifests.find((m) => m.kind === "Kernel.Module") as
36
+ | Record<string, any>
37
+ | undefined;
38
+
39
+ const resourceProps: Record<string, any> = {};
40
+ for (const m of manifests) {
41
+ const name = m.metadata?.name as string | undefined;
42
+ if (!name || !m.kind) continue;
43
+ // Kernel.Import snapshots are stored under resources.<alias> at runtime,
44
+ // so they appear here alongside regular resources.
45
+ if (!SYSTEM_KINDS.has(m.kind)) {
46
+ resourceProps[name] = { type: "object", additionalProperties: true };
47
+ }
48
+ }
49
+
50
+ return {
51
+ type: "object",
52
+ properties: {
53
+ variables: buildSchemaMapSchema(moduleManifest?.variables),
54
+ secrets: buildSchemaMapSchema(moduleManifest?.secrets),
55
+ resources: {
56
+ type: "object",
57
+ properties: resourceProps,
58
+ additionalProperties: false,
59
+ },
60
+ env: { type: "object", additionalProperties: true },
61
+ },
62
+ };
63
+ }
64
+
65
+ /** Wrap a JSON Schema property map (like `Kernel.Module.variables`) into a
66
+ * closed object schema suitable for chain-access validation. Falls back to
67
+ * an open map when the module declares no variables/secrets. */
68
+ function buildSchemaMapSchema(
69
+ schemaMap: Record<string, any> | null | undefined,
70
+ ): Record<string, any> {
71
+ if (!schemaMap || typeof schemaMap !== "object" || Array.isArray(schemaMap)) {
72
+ return { type: "object", additionalProperties: true };
73
+ }
74
+ const props: Record<string, any> = {};
75
+ for (const [key, value] of Object.entries(schemaMap)) {
76
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
77
+ props[key] = value;
78
+ }
79
+ }
80
+ if (Object.keys(props).length === 0) {
81
+ return { type: "object", additionalProperties: true };
82
+ }
83
+ return {
84
+ type: "object",
85
+ properties: props,
86
+ additionalProperties: false,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Merge kernel globals into an `x-telo-context` schema so chain-access
92
+ * validation recognises `variables`, `secrets`, `resources`, `env`
93
+ * without module authors having to re-declare them.
94
+ *
95
+ * Context-specific properties take precedence over globals (spread order).
96
+ * The original `additionalProperties` setting is preserved.
97
+ */
98
+ export function mergeKernelGlobalsIntoContext(
99
+ contextSchema: Record<string, any>,
100
+ globalsSchema: Record<string, any>,
101
+ ): Record<string, any> {
102
+ return {
103
+ ...contextSchema,
104
+ properties: {
105
+ ...globalsSchema.properties,
106
+ ...(contextSchema.properties ?? {}),
107
+ },
108
+ additionalProperties: contextSchema.additionalProperties ?? false,
109
+ };
110
+ }
@@ -1,15 +1,39 @@
1
- import type { ResourceManifest } from "@telorun/sdk";
1
+ import { isCompiledValue, type ResourceManifest } from "@telorun/sdk";
2
2
  import { isMap, isPair, isScalar, isSeq, parseAllDocuments, type Document } from "yaml";
3
3
  import { HttpAdapter } from "./adapters/http-adapter.js";
4
4
  import { RegistryAdapter } from "./adapters/registry-adapter.js";
5
5
  import { precompileDoc } from "./precompile.js";
6
- import type { LoadOptions, ManifestAdapter, Position, PositionIndex } from "./types.js";
6
+ import type {
7
+ LoadOptions,
8
+ LoaderInitOptions,
9
+ ManifestAdapter,
10
+ Position,
11
+ PositionIndex,
12
+ } from "./types.js";
7
13
 
8
14
  export class Loader {
9
- protected adapters: ManifestAdapter[] = [new HttpAdapter(), new RegistryAdapter()];
15
+ private static readonly moduleCache = new Map<
16
+ string,
17
+ { text: string; manifests: ResourceManifest[] }
18
+ >();
10
19
 
11
- constructor(extraAdapters: ManifestAdapter[] = []) {
12
- this.adapters.unshift(...extraAdapters);
20
+ protected adapters: ManifestAdapter[];
21
+
22
+ constructor(extraAdaptersOrOptions: ManifestAdapter[] | LoaderInitOptions = []) {
23
+ const options: LoaderInitOptions = Array.isArray(extraAdaptersOrOptions)
24
+ ? { extraAdapters: extraAdaptersOrOptions }
25
+ : extraAdaptersOrOptions;
26
+
27
+ const includeHttpAdapter = options.includeHttpAdapter ?? true;
28
+ const includeRegistryAdapter = options.includeRegistryAdapter ?? true;
29
+
30
+ this.adapters = [];
31
+ if (includeHttpAdapter) this.adapters.push(new HttpAdapter());
32
+ if (includeRegistryAdapter) this.adapters.push(new RegistryAdapter(options.registryUrl));
33
+
34
+ if (options.extraAdapters?.length) {
35
+ this.adapters.unshift(...options.extraAdapters);
36
+ }
13
37
  }
14
38
 
15
39
  register(adapter: ManifestAdapter): this {
@@ -23,8 +47,19 @@ export class Loader {
23
47
  return a;
24
48
  }
25
49
 
50
+ async resolveEntryPoint(url: string): Promise<string> {
51
+ const { source } = await this.pick(url).read(url);
52
+ return source;
53
+ }
54
+
26
55
  async loadModule(url: string, options?: LoadOptions): Promise<ResourceManifest[]> {
27
56
  const { text, source } = await this.pick(url).read(url);
57
+ const cacheKey = `${options?.compile ? "compiled" : "raw"}:${source}`;
58
+ const cached = Loader.moduleCache.get(cacheKey);
59
+ if (cached && cached.text === text) {
60
+ return cloneManifestArray(cached.manifests);
61
+ }
62
+
28
63
  const parsedDocuments = parseAllDocuments(text);
29
64
  const rawDocs = parsedDocuments.map((d) => d.toJSON());
30
65
  const offsets = documentLineOffsets(text);
@@ -79,12 +114,63 @@ export class Loader {
79
114
  if (moduleName) {
80
115
  for (const manifest of resolved) {
81
116
  if (manifest.kind !== "Kernel.Module" && !manifest.metadata?.module) {
117
+ const pi = (manifest.metadata as any)?.positionIndex;
82
118
  manifest.metadata = { ...manifest.metadata, module: moduleName };
119
+ if (pi) {
120
+ Object.defineProperty(manifest.metadata, "positionIndex", {
121
+ value: pi,
122
+ enumerable: false,
123
+ writable: true,
124
+ configurable: true,
125
+ });
126
+ }
83
127
  }
84
128
  }
85
129
  }
86
130
 
87
- return resolved;
131
+ Loader.moduleCache.set(cacheKey, { text, manifests: resolved });
132
+ return cloneManifestArray(resolved);
133
+ }
134
+
135
+ async loadModuleGraph(
136
+ entryUrl: string,
137
+ onError?: (url: string, error: Error) => void,
138
+ ): Promise<Map<string, ResourceManifest[]>> {
139
+ const visited = new Set<string>([entryUrl]);
140
+ const result = new Map<string, ResourceManifest[]>();
141
+
142
+ const entry = await this.loadModule(entryUrl);
143
+ result.set(entryUrl, entry);
144
+
145
+ const queue: ResourceManifest[] = [...entry];
146
+
147
+ while (queue.length > 0) {
148
+ const m = queue.shift()!;
149
+ if (m.kind !== "Kernel.Import") continue;
150
+ const importSource = (m as any).source as string | undefined;
151
+ if (!importSource) continue;
152
+ const base = (m.metadata as any)?.source ?? entryUrl;
153
+ const importUrl =
154
+ importSource.startsWith(".") || importSource.startsWith("/")
155
+ ? this.pick(base).resolveRelative(base, importSource)
156
+ : importSource;
157
+ if (visited.has(importUrl)) continue;
158
+ visited.add(importUrl);
159
+ let imported: ResourceManifest[];
160
+ try {
161
+ imported = await this.loadModule(importUrl);
162
+ } catch (err) {
163
+ const error = err instanceof Error ? err : new Error(String(err));
164
+ onError?.(importUrl, error);
165
+ continue;
166
+ }
167
+ result.set(importUrl, imported);
168
+ for (const im of imported) {
169
+ if (im.kind === "Kernel.Import") queue.push(im);
170
+ }
171
+ }
172
+
173
+ return result;
88
174
  }
89
175
 
90
176
  async loadManifests(entryUrl: string): Promise<ResourceManifest[]> {
@@ -100,7 +186,10 @@ export class Loader {
100
186
  const importSource = (m as any).source as string | undefined;
101
187
  if (!importSource) continue;
102
188
  const base = (m.metadata as any)?.source ?? entryUrl;
103
- const importUrl = this.pick(base).resolveRelative(base, importSource);
189
+ const importUrl =
190
+ importSource.startsWith(".") || importSource.startsWith("/")
191
+ ? this.pick(base).resolveRelative(base, importSource)
192
+ : importSource;
104
193
  if (visited.has(importUrl)) continue;
105
194
  visited.add(importUrl);
106
195
  let imported: ResourceManifest[];
@@ -113,11 +202,20 @@ export class Loader {
113
202
  }
114
203
  const importedModule = imported.find((im) => im.kind === "Kernel.Module");
115
204
  if (importedModule?.metadata?.name) {
205
+ const pi = (m.metadata as any)?.positionIndex;
116
206
  m.metadata = {
117
207
  ...m.metadata,
118
208
  resolvedModuleName: importedModule.metadata.name as string,
119
209
  resolvedNamespace: (importedModule.metadata as any).namespace ?? null,
120
210
  };
211
+ if (pi) {
212
+ Object.defineProperty(m.metadata, "positionIndex", {
213
+ value: pi,
214
+ enumerable: false,
215
+ writable: true,
216
+ configurable: true,
217
+ });
218
+ }
121
219
  }
122
220
  for (const im of imported) {
123
221
  if (im.kind === "Kernel.Definition") importedDefs.push(im);
@@ -129,6 +227,32 @@ export class Loader {
129
227
  }
130
228
  }
131
229
 
230
+ function cloneManifestArray(manifests: ResourceManifest[]): ResourceManifest[] {
231
+ return manifests.map((manifest) => cloneManifestValue(manifest));
232
+ }
233
+
234
+ function cloneManifestValue<T>(value: T): T {
235
+ if (Array.isArray(value)) {
236
+ return value.map((entry) => cloneManifestValue(entry)) as T;
237
+ }
238
+ if (isCompiledValue(value)) {
239
+ return value;
240
+ }
241
+ if (value !== null && typeof value === "object") {
242
+ const source = value as Record<string, unknown>;
243
+ const clone: Record<string, unknown> = {};
244
+ for (const [key, entry] of Object.entries(source)) {
245
+ clone[key] = cloneManifestValue(entry);
246
+ }
247
+ const positionIndex = Object.getOwnPropertyDescriptor(source, "positionIndex");
248
+ if (positionIndex) {
249
+ Object.defineProperty(clone, "positionIndex", positionIndex);
250
+ }
251
+ return clone as T;
252
+ }
253
+ return value;
254
+ }
255
+
132
256
  function documentLineOffsets(text: string): number[] {
133
257
  const offsets = [0];
134
258
  const lines = text.split("\n");
@@ -14,6 +14,7 @@ export function createAjv(): InstanceType<typeof Ajv> {
14
14
  }
15
15
 
16
16
  const ajv = createAjv();
17
+ const compiledSchemaValidators = new WeakMap<Record<string, any>, ReturnType<typeof ajv.compile>>();
17
18
 
18
19
  export interface CompatibilityResult {
19
20
  compatible: boolean;
@@ -81,7 +82,19 @@ function checkProperty(
81
82
 
82
83
  export function formatSingleError(err: any): string {
83
84
  const p = err.instancePath || "/";
84
- return `${p} ${err.message ?? "is invalid"}`;
85
+ const params = err.params ?? {};
86
+ switch (err.keyword) {
87
+ case "additionalProperties":
88
+ return `${p} must NOT have additional properties ('${params.additionalProperty}' is not allowed)`;
89
+ case "required":
90
+ return `${p} is missing required property '${params.missingProperty}'`;
91
+ case "enum":
92
+ return `${p} ${err.message ?? "is invalid"} (${(params.allowedValues as unknown[])?.join(" | ")})`;
93
+ case "type":
94
+ return `${p} must be ${params.type} (got ${typeof err.data})`;
95
+ default:
96
+ return `${p} ${err.message ?? "is invalid"}`;
97
+ }
85
98
  }
86
99
 
87
100
  export function formatAjvErrors(errors: any[] | null | undefined): string {
@@ -119,11 +132,14 @@ export interface SchemaIssue {
119
132
 
120
133
  /** Validate actual data against a JSON Schema. Returns issues with path info, or empty array if valid. */
121
134
  export function validateAgainstSchema(data: unknown, schema: Record<string, any>): SchemaIssue[] {
122
- let validate: ReturnType<typeof ajv.compile>;
123
- try {
124
- validate = ajv.compile(schema);
125
- } catch {
126
- return [];
135
+ let validate = compiledSchemaValidators.get(schema);
136
+ if (!validate) {
137
+ try {
138
+ validate = ajv.compile(schema);
139
+ compiledSchemaValidators.set(schema, validate);
140
+ } catch {
141
+ return [];
142
+ }
127
143
  }
128
144
  if (validate(data)) return [];
129
145
  return (validate.errors ?? []).map((err: any) => ({
@@ -182,13 +198,20 @@ export function jsonSchemaToCelType(schema: Record<string, any> | undefined): st
182
198
  if (schema.anyOf || schema.oneOf || schema.allOf) return "dyn";
183
199
  if (Array.isArray(schema.type)) return "dyn";
184
200
  switch (schema.type) {
185
- case "integer": return "int";
186
- case "number": return "double";
187
- case "string": return "string";
188
- case "boolean": return "bool";
189
- case "array": return "list";
190
- case "object": return "map";
191
- case "null": return "null_type";
201
+ case "integer":
202
+ return "int";
203
+ case "number":
204
+ return "double";
205
+ case "string":
206
+ return "string";
207
+ case "boolean":
208
+ return "bool";
209
+ case "array":
210
+ return "list";
211
+ case "object":
212
+ return "map";
213
+ case "null":
214
+ return "null_type";
192
215
  }
193
216
  if (schema.properties) return "map";
194
217
  if (schema.items) return "list";
@@ -196,10 +219,7 @@ export function jsonSchemaToCelType(schema: Record<string, any> | undefined): st
196
219
  }
197
220
 
198
221
  /** Check whether a CEL return type is compatible with a JSON Schema type constraint. */
199
- export function celTypeSatisfiesJsonSchema(
200
- celType: string,
201
- schema: Record<string, any>,
202
- ): boolean {
222
+ export function celTypeSatisfiesJsonSchema(celType: string, schema: Record<string, any>): boolean {
203
223
  if (celType === "dyn") return true;
204
224
  if (!schema.type && !schema.anyOf && !schema.oneOf && !schema.allOf) return true;
205
225
  if (schema.anyOf || schema.oneOf || schema.allOf) return true;
@@ -227,36 +247,72 @@ export function celPlaceholderForSchema(schema: Record<string, any>): unknown {
227
247
  if (schema.default !== undefined) return schema.default;
228
248
  switch (schema.type) {
229
249
  case "integer":
230
- case "number": return schema.minimum ?? 0;
231
- case "string": return "";
232
- case "boolean": return false;
233
- case "array": return [];
234
- case "object": return {};
235
- default: return null;
250
+ case "number":
251
+ return schema.minimum ?? 0;
252
+ case "string":
253
+ return "";
254
+ case "boolean":
255
+ return false;
256
+ case "array":
257
+ return [];
258
+ case "object":
259
+ return {};
260
+ default:
261
+ return null;
236
262
  }
237
263
  }
238
264
 
239
265
  const CEL_PURE_RE = /^\s*\$\{\{[^}]*\}\}\s*$/;
240
266
 
267
+ /** Resolve a `$ref` (only `#/$defs/...` form) against the root schema. */
268
+ function resolveRef(schema: Record<string, any>, root: Record<string, any>): Record<string, any> {
269
+ if (schema.$ref && typeof schema.$ref === "string" && schema.$ref.startsWith("#/$defs/")) {
270
+ const defName = schema.$ref.slice("#/$defs/".length);
271
+ const resolved = root.$defs?.[defName];
272
+ if (resolved) return resolved;
273
+ }
274
+ return schema;
275
+ }
276
+
277
+ /** Collect property schemas from top-level `properties` and all `oneOf`/`anyOf` sub-schemas. */
278
+ function collectProperties(schema: Record<string, any>): Record<string, any> {
279
+ const props: Record<string, any> = { ...(schema.properties ?? {}) };
280
+ for (const sub of schema.oneOf ?? schema.anyOf ?? []) {
281
+ if (sub && typeof sub === "object" && sub.properties) {
282
+ for (const [k, v] of Object.entries(sub.properties as Record<string, any>)) {
283
+ if (!(k in props)) props[k] = v;
284
+ }
285
+ }
286
+ }
287
+ return props;
288
+ }
289
+
241
290
  /** Deep-clone `data`, replacing every pure CEL template string (`${{ expr }}`) with a
242
291
  * schema-appropriate placeholder so AJV can validate non-CEL fields without false positives. */
243
- export function substituteCelFields(data: unknown, schema: Record<string, any>): unknown {
292
+ export function substituteCelFields(
293
+ data: unknown,
294
+ schema: Record<string, any>,
295
+ rootSchema?: Record<string, any>,
296
+ ): unknown {
297
+ const root = rootSchema ?? schema;
298
+ const resolved = resolveRef(schema, root);
299
+
244
300
  if (typeof data === "string" && CEL_PURE_RE.test(data)) {
245
- return celPlaceholderForSchema(schema);
301
+ return celPlaceholderForSchema(resolved);
246
302
  }
247
303
  if (Array.isArray(data)) {
248
- const itemSchema = (schema.items ?? {}) as Record<string, any>;
249
- return data.map((item) => substituteCelFields(item, itemSchema));
304
+ const itemSchema = resolveRef((resolved.items ?? {}) as Record<string, any>, root);
305
+ return data.map((item) => substituteCelFields(item, itemSchema, root));
250
306
  }
251
307
  if (data !== null && typeof data === "object") {
252
- const props = (schema.properties ?? {}) as Record<string, any>;
308
+ const props = collectProperties(resolved);
253
309
  const addlProps =
254
- schema.additionalProperties && typeof schema.additionalProperties === "object"
255
- ? (schema.additionalProperties as Record<string, any>)
310
+ resolved.additionalProperties && typeof resolved.additionalProperties === "object"
311
+ ? (resolved.additionalProperties as Record<string, any>)
256
312
  : undefined;
257
313
  const result: Record<string, unknown> = {};
258
314
  for (const [k, v] of Object.entries(data as Record<string, unknown>)) {
259
- result[k] = substituteCelFields(v, (props[k] ?? addlProps ?? {}) as Record<string, any>);
315
+ result[k] = substituteCelFields(v, (props[k] ?? addlProps ?? {}) as Record<string, any>, root);
260
316
  }
261
317
  return result;
262
318
  }
package/src/types.ts CHANGED
@@ -8,6 +8,9 @@ export const DiagnosticSeverity = {
8
8
  } as const;
9
9
  export type DiagnosticSeverity = (typeof DiagnosticSeverity)[keyof typeof DiagnosticSeverity];
10
10
 
11
+ /** Default entry-point filename when a directory is given instead of a file. */
12
+ export const DEFAULT_MANIFEST_FILENAME = "telo.yaml";
13
+
11
14
  export interface Position {
12
15
  /** 0-based line number */
13
16
  line: number;
@@ -52,6 +55,17 @@ export interface LoadOptions {
52
55
  compile?: boolean;
53
56
  }
54
57
 
58
+ export interface LoaderInitOptions {
59
+ /** Adapters inserted with highest priority before built-ins. */
60
+ extraAdapters?: ManifestAdapter[];
61
+ /** Include built-in HttpAdapter. Defaults to true. */
62
+ includeHttpAdapter?: boolean;
63
+ /** Include built-in RegistryAdapter. Defaults to true. */
64
+ includeRegistryAdapter?: boolean;
65
+ /** Base URL used by built-in RegistryAdapter when enabled. */
66
+ registryUrl?: string;
67
+ }
68
+
55
69
  export interface AnalysisOptions {
56
70
  strictContexts?: boolean;
57
71
  }