@telorun/analyzer 0.70.0 → 0.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis-registry.d.ts.map +1 -1
- package/dist/analysis-registry.js +4 -5
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +92 -10
- package/dist/call-graph.d.ts.map +1 -1
- package/dist/call-graph.js +10 -4
- package/dist/catch-scope.d.ts +72 -0
- package/dist/catch-scope.d.ts.map +1 -0
- package/dist/catch-scope.js +102 -0
- package/dist/cel-scope-query.d.ts +14 -0
- package/dist/cel-scope-query.d.ts.map +1 -1
- package/dist/cel-scope-query.js +36 -6
- package/dist/definition-registry.d.ts.map +1 -1
- package/dist/definition-registry.js +3 -4
- package/dist/deprecation.d.ts +21 -0
- package/dist/deprecation.d.ts.map +1 -0
- package/dist/deprecation.js +26 -0
- package/dist/flatten-for-analyzer.d.ts +2 -2
- package/dist/flatten-for-analyzer.d.ts.map +1 -1
- package/dist/flatten-for-analyzer.js +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/manifest-visitor.d.ts +17 -1
- package/dist/manifest-visitor.d.ts.map +1 -1
- package/dist/manifest-visitor.js +16 -4
- package/dist/migrations/report.d.ts +1 -1
- package/dist/migrations/report.d.ts.map +1 -1
- package/dist/migrations/report.js +5 -0
- package/dist/module-alias-scope.d.ts +65 -0
- package/dist/module-alias-scope.d.ts.map +1 -0
- package/dist/module-alias-scope.js +25 -0
- package/dist/{zone-module-documents.d.ts → module-documents.d.ts} +11 -7
- package/dist/module-documents.d.ts.map +1 -0
- package/dist/ref-sentinel-target.d.ts +38 -0
- package/dist/ref-sentinel-target.d.ts.map +1 -0
- package/dist/ref-sentinel-target.js +13 -0
- package/dist/ref-slot.d.ts +15 -0
- package/dist/ref-slot.d.ts.map +1 -1
- package/dist/ref-slot.js +7 -0
- package/dist/resolve-schema-type-refs.d.ts.map +1 -1
- package/dist/resolve-schema-type-refs.js +2 -1
- package/dist/resolve-throws-union.d.ts +47 -2
- package/dist/resolve-throws-union.d.ts.map +1 -1
- package/dist/resolve-throws-union.js +199 -20
- package/dist/resolve-zone-requirements.d.ts +3 -3
- package/dist/resolve-zone-requirements.d.ts.map +1 -1
- package/dist/resolve-zone-requirements.js +12 -12
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +13 -1
- package/dist/schema-error-report.d.ts.map +1 -1
- package/dist/schema-error-report.js +48 -4
- package/dist/schema-keywords.d.ts.map +1 -1
- package/dist/schema-keywords.js +3 -1
- package/dist/schema-walk.d.ts +27 -0
- package/dist/schema-walk.d.ts.map +1 -1
- package/dist/schema-walk.js +44 -0
- package/dist/telo-version.d.ts +1 -1
- package/dist/telo-version.js +1 -1
- package/dist/template-body.d.ts.map +1 -1
- package/dist/template-body.js +2 -4
- package/dist/types.d.ts +20 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -0
- package/dist/validate-identifier-names.d.ts +2 -2
- package/dist/validate-identifier-names.d.ts.map +1 -1
- package/dist/validate-identifier-names.js +22 -7
- package/dist/validate-invocation-contract.d.ts +5 -0
- package/dist/validate-invocation-contract.d.ts.map +1 -1
- package/dist/validate-invocation-contract.js +114 -3
- package/dist/validate-logging.d.ts.map +1 -1
- package/dist/validate-logging.js +2 -2
- package/dist/validate-ref-slots.d.ts +1 -1
- package/dist/validate-ref-slots.d.ts.map +1 -1
- package/dist/validate-ref-slots.js +34 -0
- package/dist/validate-references.d.ts +16 -5
- package/dist/validate-references.d.ts.map +1 -1
- package/dist/validate-references.js +57 -15
- package/dist/validate-resource-inputs.d.ts +1 -26
- package/dist/validate-resource-inputs.d.ts.map +1 -1
- package/dist/validate-resource-inputs.js +12 -2
- package/dist/validate-schema-type-refs.d.ts.map +1 -1
- package/dist/validate-schema-type-refs.js +2 -1
- package/dist/validate-throws-coverage.d.ts +5 -1
- package/dist/validate-throws-coverage.d.ts.map +1 -1
- package/dist/validate-throws-coverage.js +241 -86
- package/package.json +3 -3
- package/src/analysis-registry.ts +4 -5
- package/src/analyzer.ts +115 -11
- package/src/call-graph.ts +9 -3
- package/src/catch-scope.ts +157 -0
- package/src/cel-scope-query.ts +45 -8
- package/src/definition-registry.ts +3 -6
- package/src/deprecation.ts +36 -0
- package/src/flatten-for-analyzer.ts +3 -3
- package/src/index.ts +10 -3
- package/src/manifest-visitor.ts +30 -5
- package/src/migrations/report.ts +5 -1
- package/src/module-alias-scope.ts +94 -0
- package/src/{zone-module-documents.ts → module-documents.ts} +10 -6
- package/src/ref-sentinel-target.ts +46 -0
- package/src/ref-slot.ts +19 -0
- package/src/resolve-schema-type-refs.ts +2 -1
- package/src/resolve-throws-union.ts +253 -20
- package/src/resolve-zone-requirements.ts +14 -14
- package/src/schema-compat.ts +13 -0
- package/src/schema-error-report.ts +50 -6
- package/src/schema-keywords.ts +4 -1
- package/src/schema-walk.ts +56 -0
- package/src/telo-version.ts +1 -1
- package/src/template-body.ts +2 -5
- package/src/types.ts +21 -3
- package/src/validate-identifier-names.ts +28 -9
- package/src/validate-invocation-contract.ts +128 -2
- package/src/validate-logging.ts +2 -3
- package/src/validate-ref-slots.ts +41 -1
- package/src/validate-references.ts +74 -14
- package/src/validate-resource-inputs.ts +26 -3
- package/src/validate-schema-type-refs.ts +2 -1
- package/src/validate-throws-coverage.ts +344 -92
- package/dist/zone-module-documents.d.ts.map +0 -1
- /package/dist/{zone-module-documents.js → module-documents.js} +0 -0
package/src/manifest-visitor.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type SchemaFromFieldEntry,
|
|
18
18
|
} from "./reference-field-map.js";
|
|
19
19
|
import type { ModuleScopes } from "./alias-resolver.js";
|
|
20
|
+
import { moduleAliasScope } from "./module-alias-scope.js";
|
|
20
21
|
import { templateBodies, withTemplateSelf } from "./template-body.js";
|
|
21
22
|
import { extractContextsFromSchema, pathMatchesScope } from "./validate-cel-context.js";
|
|
22
23
|
|
|
@@ -65,8 +66,21 @@ export interface ScopeBoundaryEvent {
|
|
|
65
66
|
source: ResourceManifest;
|
|
66
67
|
/** Dot-form prefixes of every `x-telo-scope` field on this resource. */
|
|
67
68
|
scopePrefixes: string[];
|
|
68
|
-
/**
|
|
69
|
+
/** VISIBILITY pointer (an `x-telo-scope` entry, e.g. `/steps`) → the manifests
|
|
70
|
+
* visible there. Keyed by where scoped names may be REFERENCED, which is not
|
|
71
|
+
* where they are DECLARED: `Run.Sequence` declares `x-telo-scope:
|
|
72
|
+
* ["/steps", "/targets"]` on its `with:` field, so one declaration appears
|
|
73
|
+
* under two keys and neither names `with`. Use {@link declarations} for the
|
|
74
|
+
* declaration site. */
|
|
69
75
|
manifestsByPointer: Map<string, ResourceManifest[]>;
|
|
76
|
+
/** Every inline declaration inside this resource's scopes, ONCE each, with the
|
|
77
|
+
* concrete path it is written at in the owner's document (`with[0]`).
|
|
78
|
+
*
|
|
79
|
+
* A diagnostic about a scoped resource has to anchor there: the resource is
|
|
80
|
+
* not a top-level document, so position lookup finds the OWNER and then walks
|
|
81
|
+
* this path into it. Deriving one from a visibility pointer instead names a
|
|
82
|
+
* region the declaration is not in (`steps[0]`). */
|
|
83
|
+
declarations: { manifest: ResourceManifest; path: string }[];
|
|
70
84
|
/** Names of every resource declared inside this resource's scopes. Used by
|
|
71
85
|
* the dependency graph to drop boot edges to scoped (on-demand) targets. */
|
|
72
86
|
enclosedNames: Set<string>;
|
|
@@ -237,7 +251,14 @@ export function visitManifest(
|
|
|
237
251
|
if (!r.metadata?.name || !r.kind) continue;
|
|
238
252
|
if (skipKinds?.has(r.kind)) continue;
|
|
239
253
|
|
|
240
|
-
|
|
254
|
+
// A resource's own kind is written in the alias scope of the module that
|
|
255
|
+
// DECLARED it — see `moduleAliasScope`, which is the one place that rule
|
|
256
|
+
// lives. Resolving it through the entry's aliases alone left `definition`
|
|
257
|
+
// undefined for every forwarded resource, so its `x-telo-context` regions
|
|
258
|
+
// contributed nothing and `request` / `result` read as unknown identifiers
|
|
259
|
+
// inside the library's own routes, on a file the consumer cannot fix.
|
|
260
|
+
const moduleScope = moduleAliasScope(r.metadata, aliases, aliasesByModule);
|
|
261
|
+
const resolvedKind = moduleScope?.resolveKind(r.kind);
|
|
241
262
|
const definition =
|
|
242
263
|
registry.resolve(r.kind) ??
|
|
243
264
|
(resolvedKind ? registry.resolve(resolvedKind) : undefined);
|
|
@@ -249,8 +270,8 @@ export function visitManifest(
|
|
|
249
270
|
const emittedRefPaths = wantsNested ? new Set<string>() : null;
|
|
250
271
|
|
|
251
272
|
if (wantsRefs || wantsScope || wantsSchemaFrom) {
|
|
252
|
-
const baseMap =
|
|
253
|
-
? registry.getFieldMapForKind(r.kind,
|
|
273
|
+
const baseMap = moduleScope
|
|
274
|
+
? registry.getFieldMapForKind(r.kind, moduleScope)
|
|
254
275
|
: registry.getFieldMap(r.kind);
|
|
255
276
|
|
|
256
277
|
// Expanded map drives ref/scope sites when requested; schema-from sites
|
|
@@ -263,6 +284,7 @@ export function visitManifest(
|
|
|
263
284
|
if (refScopeMap && (wantsRefs || wantsScope)) {
|
|
264
285
|
const manifestsByPointer = new Map<string, ResourceManifest[]>();
|
|
265
286
|
const scopeRefEntries: { path: string; refName: string }[] = [];
|
|
287
|
+
const declarations: { manifest: ResourceManifest; path: string }[] = [];
|
|
266
288
|
for (const [fieldPath, entry] of refScopeMap) {
|
|
267
289
|
if (!isScopeEntry(entry)) continue;
|
|
268
290
|
const raw: ResourceManifest[] = [];
|
|
@@ -270,6 +292,7 @@ export function visitManifest(
|
|
|
270
292
|
const items = Array.isArray(fe.value) ? fe.value : [fe.value];
|
|
271
293
|
items.forEach((v, i) => {
|
|
272
294
|
if (!v || typeof v !== "object") return;
|
|
295
|
+
const declarationPath = Array.isArray(fe.value) ? `${fe.path}[${i}]` : fe.path;
|
|
273
296
|
// A scope entry must be an inline resource definition; a `!ref`
|
|
274
297
|
// (tagged sentinel or resolved `{kind, name}`) is not — record it
|
|
275
298
|
// so a static diagnostic flags it instead of registering a
|
|
@@ -280,12 +303,13 @@ export function visitManifest(
|
|
|
280
303
|
(typeof rec.kind === "string" && typeof rec.name === "string")
|
|
281
304
|
) {
|
|
282
305
|
scopeRefEntries.push({
|
|
283
|
-
path:
|
|
306
|
+
path: declarationPath,
|
|
284
307
|
refName: isRefSentinel(v) ? v.source : String(rec.name),
|
|
285
308
|
});
|
|
286
309
|
return;
|
|
287
310
|
}
|
|
288
311
|
raw.push(v as ResourceManifest);
|
|
312
|
+
declarations.push({ manifest: v as ResourceManifest, path: declarationPath });
|
|
289
313
|
});
|
|
290
314
|
}
|
|
291
315
|
const pointers = Array.isArray(entry.scope) ? entry.scope : [entry.scope];
|
|
@@ -305,6 +329,7 @@ export function visitManifest(
|
|
|
305
329
|
source: r,
|
|
306
330
|
scopePrefixes,
|
|
307
331
|
manifestsByPointer,
|
|
332
|
+
declarations,
|
|
308
333
|
enclosedNames,
|
|
309
334
|
scopeRefEntries,
|
|
310
335
|
});
|
package/src/migrations/report.ts
CHANGED
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
MigrationPath,
|
|
22
22
|
MigrationRewrite,
|
|
23
23
|
} from "./types.js";
|
|
24
|
-
import type
|
|
24
|
+
import { DiagnosticTag, type AnalysisDiagnostic, type DiagnosticFix } from "../types.js";
|
|
25
25
|
|
|
26
26
|
/** One patch that applied, as the reporting side needs to see it. */
|
|
27
27
|
export interface AppliedPatch {
|
|
@@ -63,6 +63,10 @@ export function toDiagnostic(
|
|
|
63
63
|
severity: applied.entry.severity,
|
|
64
64
|
code: applied.entry.code,
|
|
65
65
|
source: "telo-analyzer",
|
|
66
|
+
// A migration IS a deprecation — a legacy spelling still read, and the one
|
|
67
|
+
// an author is being asked to stop writing — so the range carries the tag
|
|
68
|
+
// whatever severity the entry chose for itself.
|
|
69
|
+
tags: [DiagnosticTag.Deprecated],
|
|
66
70
|
message: `${rewrite.summary}\n${applied.entry.reason}\n${closing}`,
|
|
67
71
|
data: {
|
|
68
72
|
filePath: source,
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { AliasResolver, scopeResolverForModule, type ModuleScopes } from "./alias-resolver.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WHICH ALIAS TABLE A MANIFEST'S OWN NAMES ARE WRITTEN IN.
|
|
5
|
+
*
|
|
6
|
+
* An application analysis is flattened, so an imported library's definitions and
|
|
7
|
+
* exported instances are checked in the CONSUMER's pass. But every alias-qualified
|
|
8
|
+
* name on such a manifest — its `kind:`, its `extends:`, an `x-telo-schema-from`
|
|
9
|
+
* anchor — was written against the alias map of the module that DECLARED it. A
|
|
10
|
+
* library writes `kind: Http.Api` through an import the consumer has no reason to
|
|
11
|
+
* have, so resolving it through the entry's table finds nothing, and everything
|
|
12
|
+
* derived from the definition silently goes missing: the field map (Phase-5
|
|
13
|
+
* injection blind to a forwarded resource's ref slots) and the `x-telo-context`
|
|
14
|
+
* regions (`request` / `result` reported as unknown identifiers on a file the
|
|
15
|
+
* consumer cannot edit).
|
|
16
|
+
*
|
|
17
|
+
* **There are TWO fallbacks, not one, and this file is where they are told
|
|
18
|
+
* apart** — that is the whole reason they live together. They agree on every
|
|
19
|
+
* module that has a table, and differ on a non-root module that has none:
|
|
20
|
+
*
|
|
21
|
+
* - {@link moduleAliasScope} falls back to the GLOBAL table. For a question
|
|
22
|
+
* asked about an arbitrary manifest, where a missing entry means "this is the
|
|
23
|
+
* entry's own", which is what the global table answers correctly.
|
|
24
|
+
* - {@link declaringModuleScope} falls back to an EMPTY table for a non-root
|
|
25
|
+
* module. For canonicalizing what a library DECLARED, where resolving its
|
|
26
|
+
* `Http.Api` through the consumer's imports would silently bind a library's
|
|
27
|
+
* kind to whatever the app happens to import under that alias — a wrong
|
|
28
|
+
* answer, which is worse than none.
|
|
29
|
+
*
|
|
30
|
+
* Both rest on one invariant neither states inline: **a root module is never a
|
|
31
|
+
* key in `aliasesByModule`** — a root's imports are registered into the global
|
|
32
|
+
* table instead (see the `rootModules` guards in `analyze()`) — so for a
|
|
33
|
+
* consumer-owned manifest the two rules coincide and neither fallback is a
|
|
34
|
+
* degradation.
|
|
35
|
+
*
|
|
36
|
+
* Every READ of the rule goes through one of these two. What legitimately does
|
|
37
|
+
* not, and why, so the next reader does not have to re-derive it: the three
|
|
38
|
+
* sites in `analyze()` that POPULATE `aliasesByModule` (a write, not a lookup),
|
|
39
|
+
* and `resolve-ref-sentinels`, whose module is known non-root by construction and
|
|
40
|
+
* whose fallback is the raw kind rather than another table — a different rule
|
|
41
|
+
* that happens to read the same map.
|
|
42
|
+
*/
|
|
43
|
+
export function moduleAliasScope<A extends KindResolver, M extends KindResolver>(
|
|
44
|
+
metadata: { module?: unknown } | undefined,
|
|
45
|
+
aliases: A,
|
|
46
|
+
aliasesByModule: ReadonlyMap<string, M> | undefined,
|
|
47
|
+
): A | M;
|
|
48
|
+
export function moduleAliasScope<A extends KindResolver, M extends KindResolver>(
|
|
49
|
+
metadata: { module?: unknown } | undefined,
|
|
50
|
+
aliases: A | undefined,
|
|
51
|
+
aliasesByModule: ReadonlyMap<string, M> | undefined,
|
|
52
|
+
): A | M | undefined;
|
|
53
|
+
export function moduleAliasScope<A extends KindResolver, M extends KindResolver>(
|
|
54
|
+
metadata: { module?: unknown } | undefined,
|
|
55
|
+
aliases: A | undefined,
|
|
56
|
+
aliasesByModule: ReadonlyMap<string, M> | undefined,
|
|
57
|
+
): A | M | undefined {
|
|
58
|
+
const declaringModule = metadata?.module;
|
|
59
|
+
if (typeof declaringModule !== "string") return aliases;
|
|
60
|
+
return aliasesByModule?.get(declaringModule) ?? aliases;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** All this rule needs of a resolver, and deliberately all it asks for:
|
|
64
|
+
* `ModuleScopes` already types its map this way so a caller can hand over a
|
|
65
|
+
* lighter table, and requiring the full `AliasResolver` here would have made
|
|
66
|
+
* the one site that does (a template body) reach for a cast. */
|
|
67
|
+
interface KindResolver {
|
|
68
|
+
resolveKind(kind: string): string | undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** An empty table, shared: a non-root module with no aliases of its own resolves
|
|
72
|
+
* nothing rather than resolving through the consumer's. One instance because it
|
|
73
|
+
* is immutable in use and allocating one per call put a resolver on a per-schema
|
|
74
|
+
* loop. */
|
|
75
|
+
const NO_ALIASES = new AliasResolver();
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The scope a DECLARING module's own alias-form names are canonicalized in — the
|
|
79
|
+
* root-aware half of the rule above, and the one that must never fall through to
|
|
80
|
+
* the consumer's table. See this file's header for why the two differ.
|
|
81
|
+
*/
|
|
82
|
+
export function declaringModuleScope(
|
|
83
|
+
ownModule: string | undefined,
|
|
84
|
+
aliases: AliasResolver,
|
|
85
|
+
scopes: { aliasesByModule: Map<string, AliasResolver>; rootModules: Set<string> },
|
|
86
|
+
): AliasResolver {
|
|
87
|
+
const own = scopeResolverForModule(ownModule, scopes.rootModules, scopes.aliasesByModule);
|
|
88
|
+
if (own) return own;
|
|
89
|
+
// Root (or unknown-and-therefore-treated-as-root): the global table IS its
|
|
90
|
+
// import map. A non-root module with no table of its own resolves nothing.
|
|
91
|
+
return ownModule && !scopes.rootModules.has(ownModule) ? NO_ALIASES : aliases;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type { ModuleScopes };
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* One imported library's FULL document set
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* One imported library's FULL document set — what the flattened analysis view
|
|
5
|
+
* no longer holds, since it forwards only each library's export surface and
|
|
6
|
+
* never its internal dispatch chain.
|
|
7
|
+
*
|
|
8
|
+
* Named for what it CARRIES rather than for who asked first: the zone stage's
|
|
9
|
+
* per-library export derivation was the original consumer, and the throws walk
|
|
10
|
+
* is the second — a third should not have to import "zone" to ask about
|
|
11
|
+
* something else again.
|
|
8
12
|
*
|
|
9
13
|
* Plain data in a module of its own, deliberately. It is produced by the
|
|
10
|
-
* loading side (`
|
|
14
|
+
* loading side (`collectModuleDocuments`), named in `AnalysisOptions`, and
|
|
11
15
|
* consumed by the projection; putting it in any of the three would make the
|
|
12
16
|
* other two import that one, and `types.ts` ↔ the projection is a genuine
|
|
13
17
|
* cycle. A leaf module with no imports of its own breaks it without an inline
|
|
14
18
|
* `import(...)` type expression standing in for the dependency nobody wanted.
|
|
15
19
|
*/
|
|
16
|
-
export interface
|
|
20
|
+
export interface ModuleDocuments {
|
|
17
21
|
/** The library's module name (its `Telo.Library` doc's `metadata.name`). */
|
|
18
22
|
module: string;
|
|
19
23
|
/** Stable source identity of the library's owner file — the cache key. */
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { isRefSentinel } from "@telorun/templating";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What a `!ref` names, parsed once.
|
|
5
|
+
*
|
|
6
|
+
* THE single reader of the tag's grammar, on the `ref-slot.ts` / `zone-slot.ts`
|
|
7
|
+
* precedent. Three passes had grown their own parse of the same scalar and they
|
|
8
|
+
* disagreed: one took the source verbatim, one split on the first dot and
|
|
9
|
+
* dropped every alias but `Self`, one split on the LAST dot and dropped the
|
|
10
|
+
* alias entirely — so the same `!ref Alias.name` named three different things
|
|
11
|
+
* depending on which pass was asking, and the loosest of the three resolved a
|
|
12
|
+
* bare name against whatever manifest happened to share it.
|
|
13
|
+
*
|
|
14
|
+
* The grammar itself is one line and is not in dispute: `!ref <name>` or
|
|
15
|
+
* `!ref <Alias>.<name>`, split on the FIRST dot, because that is what
|
|
16
|
+
* `resolveRefSentinels` does and a name may not contain one
|
|
17
|
+
* (`INVALID_NAME` rejects it at every declaration site).
|
|
18
|
+
*
|
|
19
|
+
* What this deliberately does NOT do is decide what to look up. A reduction is
|
|
20
|
+
* the caller's, and the three genuinely differ: an edge in the call graph points
|
|
21
|
+
* at a cross-module target and tolerates not resolving it; a zone correlation
|
|
22
|
+
* refuses to bind anything it cannot resolve exactly; a throws walk resolves in
|
|
23
|
+
* the DECLARING library first. Each states its own, over one parse.
|
|
24
|
+
*
|
|
25
|
+
* Browser-safe.
|
|
26
|
+
*/
|
|
27
|
+
export interface RefSentinelTarget {
|
|
28
|
+
/** The scalar as written, for a caller that wants the author's spelling. */
|
|
29
|
+
source: string;
|
|
30
|
+
/** The prefix before the first dot, `Self` included and not normalized away —
|
|
31
|
+
* a caller that treats `Self` as "no alias" says so itself. */
|
|
32
|
+
alias?: string;
|
|
33
|
+
/** The segment after the first dot, or the whole scalar when there is none. */
|
|
34
|
+
name: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Parse a `!ref` sentinel. Returns `undefined` for anything that is not one —
|
|
38
|
+
* including the `{kind, name}` object `resolveRefSentinels` rewrites it into,
|
|
39
|
+
* which is a different shape with a different reader. */
|
|
40
|
+
export function refSentinelTarget(value: unknown): RefSentinelTarget | undefined {
|
|
41
|
+
if (!isRefSentinel(value)) return undefined;
|
|
42
|
+
const source = value.source;
|
|
43
|
+
const dot = source.indexOf(".");
|
|
44
|
+
if (dot <= 0) return { source, name: source };
|
|
45
|
+
return { source, alias: source.slice(0, dot), name: source.slice(dot + 1) };
|
|
46
|
+
}
|
package/src/ref-slot.ts
CHANGED
|
@@ -94,6 +94,21 @@ export interface RefSlot {
|
|
|
94
94
|
* that carries this call's arguments. Replaces
|
|
95
95
|
* `x-telo-topology-role: inputs`. */
|
|
96
96
|
inputs?: string;
|
|
97
|
+
/** Throws raised by the target — or by anything the target drives — surface
|
|
98
|
+
* through the DECLARING resource, so its own throws union includes the
|
|
99
|
+
* target's and its catch scope encloses the target's.
|
|
100
|
+
*
|
|
101
|
+
* It exists because the relation is real where `use` correctly says nothing:
|
|
102
|
+
* `Http.Server.mounts[].mount` is a `dependency` (the server holds the mount
|
|
103
|
+
* and calls a convention method on it — control reaches a route through the
|
|
104
|
+
* MOUNT's own `trigger.inbound` slot, not through this one), yet a throw from
|
|
105
|
+
* that route is exactly what the server renders. Following every `dependency`
|
|
106
|
+
* edge instead would drag a connection's throws into a router's denominator.
|
|
107
|
+
*
|
|
108
|
+
* Declared by the kind that HOLDS, because only it knows the throws surface
|
|
109
|
+
* through it, and it is the same fact its runtime establishes by rethrowing —
|
|
110
|
+
* so the annotation and the behaviour cannot disagree. */
|
|
111
|
+
throwsThrough?: boolean;
|
|
97
112
|
/** `x-telo-inline: true` on the slot or any `anyOf` branch — accepts an inline
|
|
98
113
|
* `{kind, ...config}` definition, not only a `!ref`. */
|
|
99
114
|
inline: boolean;
|
|
@@ -228,6 +243,7 @@ export function readRefSlot(node: Record<string, any> | undefined): RefSlot | un
|
|
|
228
243
|
const uses = new Set<RefUse>();
|
|
229
244
|
let useCases: RefUseCases | undefined;
|
|
230
245
|
let inputs: string | undefined;
|
|
246
|
+
let throwsThrough = false;
|
|
231
247
|
|
|
232
248
|
for (const carrier of nodes) {
|
|
233
249
|
const annotation = carrier["x-telo-ref"];
|
|
@@ -239,6 +255,7 @@ export function readRefSlot(node: Record<string, any> | undefined): RefSlot | un
|
|
|
239
255
|
for (const use of normalizeUses(obj.use)) uses.add(use);
|
|
240
256
|
useCases ??= readUseCases(obj.use);
|
|
241
257
|
if (typeof obj.inputs === "string") inputs ??= obj.inputs;
|
|
258
|
+
if (obj.throwsThrough === true) throwsThrough = true;
|
|
242
259
|
}
|
|
243
260
|
|
|
244
261
|
const slot: RefSlot = {
|
|
@@ -249,6 +266,7 @@ export function readRefSlot(node: Record<string, any> | undefined): RefSlot | un
|
|
|
249
266
|
};
|
|
250
267
|
if (useCases) slot.useCases = useCases;
|
|
251
268
|
if (inputs !== undefined) slot.inputs = inputs;
|
|
269
|
+
if (throwsThrough) slot.throwsThrough = true;
|
|
252
270
|
return slot;
|
|
253
271
|
}
|
|
254
272
|
|
|
@@ -274,6 +292,7 @@ export function refSlotAnnotation(slot: RefSlot): Record<string, unknown> {
|
|
|
274
292
|
else if (slot.uses.length === 1) annotation.use = slot.uses[0];
|
|
275
293
|
else if (slot.uses.length > 1) annotation.use = slot.uses;
|
|
276
294
|
if (slot.inputs !== undefined) annotation.inputs = slot.inputs;
|
|
295
|
+
if (slot.throwsThrough) annotation.throwsThrough = true;
|
|
277
296
|
return annotation;
|
|
278
297
|
}
|
|
279
298
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ResourceManifest } from "@telorun/sdk";
|
|
2
2
|
import { canonicalTypeSchemaId, parseTeloTypeRef } from "@telorun/sdk";
|
|
3
3
|
import type { AliasResolver } from "./alias-resolver.js";
|
|
4
|
+
import { moduleAliasScope } from "./module-alias-scope.js";
|
|
4
5
|
|
|
5
6
|
/** Schema-bearing fields on a Telo.Definition / Telo.Type resource. */
|
|
6
7
|
const SCHEMA_FIELDS = ["schema", "inputType", "outputType"];
|
|
@@ -96,7 +97,7 @@ export function resolveSchemaTypeRefs(
|
|
|
96
97
|
|
|
97
98
|
for (const r of resources) {
|
|
98
99
|
const ownModule = (r.metadata as { module?: string } | undefined)?.module;
|
|
99
|
-
const resolver = (
|
|
100
|
+
const resolver = moduleAliasScope(r.metadata, aliases, aliasesByModule);
|
|
100
101
|
const resolveAuthority = (authority: string): string | undefined =>
|
|
101
102
|
authority === "Self" ? ownModule : resolver?.moduleForAlias(authority);
|
|
102
103
|
for (const field of SCHEMA_FIELDS) {
|