@webpieces/nx-webpieces-rules 0.4.795 → 0.4.797
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/package.json +6 -6
- package/src/lib/api-usage/api-ast.d.ts +16 -80
- package/src/lib/api-usage/api-ast.js +75 -98
- package/src/lib/api-usage/api-ast.js.map +1 -1
- package/src/lib/api-usage/api-contract-errors.d.ts +8 -3
- package/src/lib/api-usage/api-contract-errors.js +24 -8
- package/src/lib/api-usage/api-contract-errors.js.map +1 -1
- package/src/lib/api-usage/api-relations.d.ts +26 -2
- package/src/lib/api-usage/api-relations.js +23 -1
- package/src/lib/api-usage/api-relations.js.map +1 -1
- package/src/lib/api-usage/api-scanner.d.ts +8 -5
- package/src/lib/api-usage/api-scanner.js +17 -7
- package/src/lib/api-usage/api-scanner.js.map +1 -1
- package/src/lib/runtime-graph-model.d.ts +1 -1
- package/src/lib/runtime-graph-model.js.map +1 -1
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import type { EnhancedGraph } from '../graph-sorter';
|
|
31
31
|
import { ProjectInfo } from '../project-info';
|
|
32
|
-
import { ApiClassInfo, ApiContracts, EmptiedApiContract, NonLiteralDecoratorArg, ProjectApiRelations, UndeclaredExternalCaller, UnresolvedEndpointPath } from './api-relations';
|
|
32
|
+
import { ApiClassInfo, ApiContracts, EmptiedApiContract, NonLiteralDecoratorArg, ProjectApiRelations, UndeclaredExternalCaller, UndeclaredEndpointOperation, UnresolvedEndpointPath } from './api-relations';
|
|
33
33
|
/**
|
|
34
34
|
* An `addRoutes`/`createRpcClient`/`createPubSubClient` first argument that resolved to an
|
|
35
35
|
* abstract class in a DECLARATION file which owns no indexed contract. Unambiguously a broken
|
|
@@ -96,6 +96,8 @@ export interface ApiScanResult {
|
|
|
96
96
|
* graph exists to name that system, and with nothing to name it restates our own contract name.
|
|
97
97
|
*/
|
|
98
98
|
undeclaredExternalCallers: UndeclaredExternalCaller[];
|
|
99
|
+
/** Endpoints lacking the explicit side-effect contract used for retry safety and MCP hints. */
|
|
100
|
+
undeclaredEndpointOperations: UndeclaredEndpointOperation[];
|
|
99
101
|
}
|
|
100
102
|
/** Statically scans every project for its api-lib implements/uses relationships. */
|
|
101
103
|
export declare class ApiUsageScanner {
|
|
@@ -168,14 +170,15 @@ export declare function scanAndAttachApiRelations(workspaceRoot: string, graph:
|
|
|
168
170
|
* would be an empty shell, and its identity is already carried by the `external` refs in
|
|
169
171
|
* apiRelations. Sorted by api name, methods left in declaration order, so the file is deterministic.
|
|
170
172
|
*
|
|
171
|
-
* THROWS on the
|
|
172
|
-
* an `external` method that never said WHO calls it — UndeclaredExternalCallerError):
|
|
173
|
+
* THROWS on the ways an entry can be wrong-but-green, checked root cause first:
|
|
173
174
|
* 1. an `@Endpoint` path the scan could not read (UnresolvedEndpointPathError) — the other half of
|
|
174
175
|
* the URL a consumer computes, and the cause of most emptied contracts;
|
|
175
176
|
* 2. a class that declared endpoints and kept none (EmptiedApiContractError), which would otherwise
|
|
176
177
|
* leave silently through the zero-method skip above;
|
|
177
|
-
* 3. a
|
|
178
|
-
*
|
|
178
|
+
* 3. a method without an explicit operation (UndeclaredEndpointOperationError);
|
|
179
|
+
* 4. an `external` method that never said WHO calls it (UndeclaredExternalCallerError);
|
|
180
|
+
* 5. a routed contract with no basePath (MissingBasePathError).
|
|
181
|
+
* All are worse than an absent entry: a consumer joining `basePath + path` computes a
|
|
179
182
|
* confidently wrong URL with no signal that anything is off, because every other entry is complete.
|
|
180
183
|
* Each error aggregates EVERY offender, so a developer fixing five constants sees five in one run.
|
|
181
184
|
*/
|
|
@@ -195,10 +195,15 @@ class RelationAccumulator {
|
|
|
195
195
|
}
|
|
196
196
|
/** Build the deterministic { owner -> relation } record, owners in sorted order. */
|
|
197
197
|
toRelations() {
|
|
198
|
-
const owners = new Set([
|
|
198
|
+
const owners = new Set([
|
|
199
|
+
...this.implementsByOwner.keys(),
|
|
200
|
+
...this.usesByOwner.keys(),
|
|
201
|
+
]);
|
|
199
202
|
const relations = {};
|
|
200
203
|
for (const owner of [...owners].sort()) {
|
|
201
|
-
const implementsRefs = (0, api_relations_1.sortApiRefs)([
|
|
204
|
+
const implementsRefs = (0, api_relations_1.sortApiRefs)([
|
|
205
|
+
...(this.implementsByOwner.get(owner)?.values() ?? []),
|
|
206
|
+
]);
|
|
202
207
|
const usesRefs = (0, api_relations_1.sortApiRefs)([...(this.usesByOwner.get(owner)?.values() ?? [])]);
|
|
203
208
|
const relation = {
|
|
204
209
|
kind: (0, api_relations_1.deriveApiRelationKind)(implementsRefs, usesRefs),
|
|
@@ -261,6 +266,7 @@ class ApiUsageScanner {
|
|
|
261
266
|
unresolvedEndpointPaths: this.decoratorArgDiagnostics.unresolvedEndpointPaths(),
|
|
262
267
|
emptiedApiContracts: this.decoratorArgDiagnostics.emptiedContracts(),
|
|
263
268
|
undeclaredExternalCallers: this.decoratorArgDiagnostics.undeclaredExternalCallers(),
|
|
269
|
+
undeclaredEndpointOperations: this.decoratorArgDiagnostics.undeclaredEndpointOperations(),
|
|
264
270
|
};
|
|
265
271
|
}
|
|
266
272
|
scanProject(info) {
|
|
@@ -421,14 +427,15 @@ function scanAndAttachApiRelations(workspaceRoot, graph, projectInfos, externalA
|
|
|
421
427
|
* would be an empty shell, and its identity is already carried by the `external` refs in
|
|
422
428
|
* apiRelations. Sorted by api name, methods left in declaration order, so the file is deterministic.
|
|
423
429
|
*
|
|
424
|
-
* THROWS on the
|
|
425
|
-
* an `external` method that never said WHO calls it — UndeclaredExternalCallerError):
|
|
430
|
+
* THROWS on the ways an entry can be wrong-but-green, checked root cause first:
|
|
426
431
|
* 1. an `@Endpoint` path the scan could not read (UnresolvedEndpointPathError) — the other half of
|
|
427
432
|
* the URL a consumer computes, and the cause of most emptied contracts;
|
|
428
433
|
* 2. a class that declared endpoints and kept none (EmptiedApiContractError), which would otherwise
|
|
429
434
|
* leave silently through the zero-method skip above;
|
|
430
|
-
* 3. a
|
|
431
|
-
*
|
|
435
|
+
* 3. a method without an explicit operation (UndeclaredEndpointOperationError);
|
|
436
|
+
* 4. an `external` method that never said WHO calls it (UndeclaredExternalCallerError);
|
|
437
|
+
* 5. a routed contract with no basePath (MissingBasePathError).
|
|
438
|
+
* All are worse than an absent entry: a consumer joining `basePath + path` computes a
|
|
432
439
|
* confidently wrong URL with no signal that anything is off, because every other entry is complete.
|
|
433
440
|
* Each error aggregates EVERY offender, so a developer fixing five constants sees five in one run.
|
|
434
441
|
*/
|
|
@@ -440,6 +447,9 @@ function buildApiContracts(scan) {
|
|
|
440
447
|
throw new api_contract_errors_1.UnresolvedEndpointPathError(scan.unresolvedEndpointPaths);
|
|
441
448
|
if (scan.emptiedApiContracts.length > 0)
|
|
442
449
|
throw new api_contract_errors_1.EmptiedApiContractError(scan.emptiedApiContracts);
|
|
450
|
+
if (scan.undeclaredEndpointOperations.length > 0) {
|
|
451
|
+
throw new api_contract_errors_1.UndeclaredEndpointOperationError(scan.undeclaredEndpointOperations);
|
|
452
|
+
}
|
|
443
453
|
// After the two above: an unreadable path is what empties a contract, and a contract that lost
|
|
444
454
|
// every method has no external endpoint left to complain about.
|
|
445
455
|
if (scan.undeclaredExternalCallers.length > 0) {
|
|
@@ -510,7 +520,7 @@ function describeMismatchedEndpointKinds(contracts) {
|
|
|
510
520
|
for (const method of contract.methods) {
|
|
511
521
|
if (allowed.includes(method.kind))
|
|
512
522
|
continue;
|
|
513
|
-
problems.push(`${api}.${method.name} declares @Endpoint('${method.path}',
|
|
523
|
+
problems.push(`${api}.${method.name} declares @Endpoint(${method.httpMethod ?? 'POST'}, '${method.path}', ${method.operation}, ${method.kind}) but ${api} is ` +
|
|
514
524
|
`@${contract.apiKind === 'pubsub' ? 'PubSub' : 'Rpc'} — allowed kinds are ${allowed.join(' | ')}.`);
|
|
515
525
|
}
|
|
516
526
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-scanner.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-scanner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * API Usage Scanner\n *\n * Derives, by scanning real source (not a declaration file), how every project\n * relates to the api-lib projects it depends on. This is the single source of\n * truth for the `apiRelations` field in architecture/dependencies.json AND for\n * the runtime microservice graph.\n *\n * Signals (all resolved through the TypeScript checker, so re-exports resolve):\n * - IMPLEMENTS: `apiFactory.addRoutes(XxxApi, XxxController)` — the registration\n * that actually SERVES the contract over the wire. We deliberately\n * do NOT use `class Ctrl extends XxxApi`: a class can extend an API\n * as an in-process test double / simulator (e.g. Server2Simulator)\n * without ever serving it — only `addRoutes` proves a served route.\n * - USES: `factory.createRpcClient(XxxApi, ...)` → rpc client\n * `factory.createPubSubClient(XxxApi, ...)` → pubsub (Cloud Tasks) client\n * The config argument (`new ClientConfig('helper-fsdb')`) names WHICH service the\n * client talks to and is kept as `ApiRef.targetService` — see targetServiceOf.\n * An api-lib is DETECTED, not tagged: a project exporting an `abstract class`\n * carrying `@ApiPath` owns that API. Its transport is `@PubSub` → 'pubsub', else 'rpc'.\n *\n * Contracts are indexed from SOURCE in a pre-pass (ApiSourceIndexBuilder) rather than\n * from wherever the checker resolves an import to. A consumer without a tsconfig.base\n * `paths` entry resolves `import { XxxApi } from '@scope/xxx-api'` through node_modules\n * to the package's BUILT `dist/**.d.ts` — and tsc ERASES decorators when emitting\n * declarations, so `@ApiPath` can never be read there. Keying off the resolved\n * declaration therefore dropped whole services from the graph, silently. See\n * `recoverFromDeclaration`.\n */\n\nimport * as ts from 'typescript';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { matchesAnyGlob } from '@webpieces/rules-config';\nimport type { EnhancedGraph } from '../graph-sorter';\nimport { ProjectInfo } from '../project-info';\nimport { findProjectTsconfig } from '../di-graph/program';\nimport { resolveClassDeclaration } from '../di-graph/bindings';\nimport {\n ApiClassInfo,\n ApiContract,\n ApiContracts,\n ApiRef,\n ApiRelation,\n EmptiedApiContract,\n EndpointKind,\n NonLiteralDecoratorArg,\n ProjectApiRelations,\n UndeclaredExternalCaller,\n UnresolvedEndpointPath,\n apiRefKey,\n deriveApiRelationKind,\n sortApiRefs,\n} from './api-relations';\nimport {\n EmptiedApiContractError,\n MissingBasePathError,\n UndeclaredExternalCallerError,\n UnresolvedEndpointPathError,\n} from './api-contract-errors';\nimport {\n DecoratorArgDiagnostics,\n apiClassInfoFrom,\n apiClassInfoFromNode,\n calleeMethodName,\n collectTsFiles,\n constructorParamsOf,\n externalApiInfoFrom,\n implementedTypeNames,\n isAbstractClass,\n isTestFile,\n targetServiceOf,\n typeReferenceName,\n} from './api-ast';\n\nconst RPC_CLIENT_METHOD = 'createRpcClient';\nconst PUBSUB_CLIENT_METHOD = 'createPubSubClient';\nconst ADD_ROUTES_METHOD = 'addRoutes';\n\n/**\n * An `addRoutes`/`createRpcClient`/`createPubSubClient` first argument that resolved to an\n * abstract class in a DECLARATION file which owns no indexed contract. Unambiguously a broken\n * scan (a real api-lib whose source we never indexed), never a \"this isn't an API\" argument —\n * so it is reported loudly instead of collapsing into a silent `return null`.\n */\nexport class UnresolvedApiCall {\n constructor(\n /** The project whose source makes the call. */\n public readonly project: string,\n /** The contract class name as written at the call site. */\n public readonly api: string,\n /** `path/to/file.ts:LINE` of the call site, workspace-relative. */\n public readonly at: string,\n /** The declaration file the checker resolved to (where decorators are erased). */\n public readonly declaredIn: string,\n ) {}\n}\n\n/** The whole-workspace result of a scan. */\nexport interface ApiScanResult {\n /** projectName -> { apiLibProject -> relation }; only projects with ≥1 relation appear. */\n relationsByProject: Map<string, ProjectApiRelations>;\n /** Every project that owns ≥1 API contract class. */\n apiLibProjects: Set<string>;\n /** apiClassName -> where it lives + its transport. */\n apiIndex: Map<string, ApiClassInfo>;\n /**\n * Projects whose production (non-test) source was actually scanned. A project with only test\n * files (e.g. an e2e harness), or one the compiler couldn't load, is ABSENT — callers must not\n * conclude \"no implements/uses\" for it, because its behavior was never observed.\n */\n scannedProjects: Set<string>;\n /**\n * Call sites naming a contract we could not map back to workspace source. Non-empty means the\n * graph is INCOMPLETE — callers must surface these rather than emit a green, wrong graph.\n */\n unresolvedApiCalls: UnresolvedApiCall[];\n /**\n * Decorator arguments that were present but could not be reduced to a string (a cross-module\n * constant, a computed expression). Each one costs the graph a basePath, a method, or — when it\n * takes out every method of a class — the whole contract, so they must be surfaced.\n */\n nonLiteralDecoratorArgs: NonLiteralDecoratorArg[];\n /**\n * The subset of the above that is FATAL: an `@Endpoint` path that could not be read. Every client\n * builds its URL as `basePath + path`, so this is missing routing, not missing metadata —\n * buildApiContracts throws on a non-empty list rather than shipping a contract without it.\n */\n unresolvedEndpointPaths: UnresolvedEndpointPath[];\n /**\n * Contract classes that declared `@Endpoint` methods and kept none — the exact shape that used to\n * slip out through buildApiContracts' zero-method skip, taking a whole service's queues with it.\n */\n emptiedApiContracts: EmptiedApiContract[];\n /**\n * `external` endpoints that did not say WHO calls them. Fatal: the inbound box on the runtime\n * graph exists to name that system, and with nothing to name it restates our own contract name.\n */\n undeclaredExternalCallers: UndeclaredExternalCaller[];\n}\n\n/** Maps an absolute source-file path to the workspace project that owns it (longest-root-prefix). */\nclass ProjectLocator {\n private readonly roots: ProjectRoot[];\n\n constructor(workspaceRoot: string, projectInfos: Map<string, ProjectInfo>) {\n const roots: ProjectRoot[] = [];\n for (const info of projectInfos.values()) {\n if (info.root === '' || info.root === '.') continue;\n roots.push(new ProjectRoot(info.name, path.resolve(workspaceRoot, info.root)));\n }\n // Longest root first so a nested project wins over its parent.\n this.roots = roots.sort((a: ProjectRoot, b: ProjectRoot) => b.abs.length - a.abs.length);\n }\n\n projectOf(absFile: string): string | null {\n const normalized = path.resolve(absFile);\n for (const root of this.roots) {\n if (normalized === root.abs || normalized.startsWith(root.abs + path.sep)) return root.name;\n }\n return null;\n }\n}\n\nclass ProjectRoot {\n constructor(\n public readonly name: string,\n public readonly abs: string,\n ) {}\n}\n\n/**\n * Every API contract in the workspace, keyed by class name, read from SOURCE.\n *\n * Name-keyed because a call site only ever gives us a name once its import has resolved into a\n * decorator-erased declaration. Two api-libs exporting the same class name collide (last wins) —\n * the same collision the published `apiIndex` has always had.\n */\nclass ApiSourceIndex {\n constructor(\n public readonly byName: Map<string, ApiClassInfo>,\n public readonly owners: Set<string>,\n ) {}\n\n lookup(api: string): ApiClassInfo | null {\n return this.byName.get(api) ?? null;\n }\n}\n\n/**\n * Builds the ApiSourceIndex by parsing each project's own `src/**` directly.\n *\n * Deliberately parser-only (no ts.Program, no checker): we need the decorators exactly as\n * written, and a plain parse cannot be diverted to a `.d.ts` by module resolution — which is\n * the entire bug this guards against. It is also cheap enough to run over every project.\n */\nclass ApiSourceIndexBuilder {\n private readonly byName = new Map<string, ApiClassInfo>();\n private readonly owners = new Set<string>();\n\n constructor(\n private readonly workspaceRoot: string,\n private readonly projectInfos: Map<string, ProjectInfo>,\n /** Globs of project roots holding vendor contracts — see ExternalApiIndex. */\n private readonly externalApiPaths: readonly string[],\n /** Sink for decorator arguments this parser-only pass cannot reduce to a string. */\n private readonly diagnostics: DecoratorArgDiagnostics,\n ) {}\n\n build(): ApiSourceIndex {\n for (const info of this.projectInfos.values()) {\n if (info.root === '' || info.root === '.') continue;\n this.indexProject(info);\n }\n return new ApiSourceIndex(this.byName, this.owners);\n }\n\n private indexProject(info: ProjectInfo): void {\n const srcDir = path.join(path.resolve(this.workspaceRoot, info.root), 'src');\n if (!fs.existsSync(srcDir)) return;\n const external = matchesAnyGlob(info.root, this.externalApiPaths);\n for (const file of collectTsFiles(srcDir)) {\n if (isTestFile(file)) continue; // tests are not production topology\n const text = fs.readFileSync(file, 'utf8');\n const sourceFile = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);\n this.indexNode(sourceFile, info.name, external);\n }\n }\n\n private indexNode(node: ts.Node, project: string, external: boolean): void {\n const info = external\n ? externalApiInfoFrom(node, project)\n : apiClassInfoFromNode(node, project, this.diagnostics);\n if (info) {\n this.owners.add(project);\n this.byName.set(info.api, info);\n }\n ts.forEachChild(node, (child: ts.Node) => this.indexNode(child, project, external));\n }\n}\n/** Per-owner accumulator that dedupes API refs while a single project is scanned. */\nclass RelationAccumulator {\n private readonly implementsByOwner = new Map<string, Map<string, ApiRef>>();\n private readonly usesByOwner = new Map<string, Map<string, ApiRef>>();\n\n addImplements(owner: string, ref: ApiRef): void {\n ensureRefMap(this.implementsByOwner, owner).set(apiRefKey(ref), ref);\n }\n\n /**\n * Keyed by api + targetService: one project legitimately binds the SAME contract against two\n * different services (a WarmupApi client per data server), and those are two relations, not one.\n */\n addUses(owner: string, ref: ApiRef): void {\n ensureRefMap(this.usesByOwner, owner).set(apiRefKey(ref), ref);\n }\n\n /** Build the deterministic { owner -> relation } record, owners in sorted order. */\n toRelations(): ProjectApiRelations {\n const owners = new Set<string>([...this.implementsByOwner.keys(), ...this.usesByOwner.keys()]);\n const relations: ProjectApiRelations = {};\n for (const owner of [...owners].sort()) {\n const implementsRefs = sortApiRefs([...(this.implementsByOwner.get(owner)?.values() ?? [])]);\n const usesRefs = sortApiRefs([...(this.usesByOwner.get(owner)?.values() ?? [])]);\n const relation: ApiRelation = {\n kind: deriveApiRelationKind(implementsRefs, usesRefs),\n implements: implementsRefs,\n uses: usesRefs,\n };\n relations[owner] = relation;\n }\n return relations;\n }\n\n isEmpty(): boolean {\n return this.implementsByOwner.size === 0 && this.usesByOwner.size === 0;\n }\n}\n\n// webpieces-disable no-function-outside-class -- tiny map helper, matching the AST-helper style of di-graph/bindings.ts\nfunction ensureRefMap(map: Map<string, Map<string, ApiRef>>, owner: string): Map<string, ApiRef> {\n let inner = map.get(owner);\n if (!inner) {\n inner = new Map<string, ApiRef>();\n map.set(owner, inner);\n }\n return inner;\n}\n\n/** Statically scans every project for its api-lib implements/uses relationships. */\nexport class ApiUsageScanner {\n private readonly locator: ProjectLocator;\n private readonly relationsByProject = new Map<string, ProjectApiRelations>();\n private readonly scannedProjects = new Set<string>();\n private readonly unresolvedApiCalls: UnresolvedApiCall[] = [];\n private readonly decoratorArgDiagnostics: DecoratorArgDiagnostics;\n private sourceIndex = new ApiSourceIndex(new Map<string, ApiClassInfo>(), new Set<string>());\n\n constructor(\n private readonly workspaceRoot: string,\n private readonly projectInfos: Map<string, ProjectInfo>,\n /** Globs of project roots whose exported `*Api` types are contracts for outside systems. */\n private readonly externalApiPaths: readonly string[] = [],\n ) {\n this.locator = new ProjectLocator(workspaceRoot, projectInfos);\n this.decoratorArgDiagnostics = new DecoratorArgDiagnostics(workspaceRoot);\n }\n\n scan(): ApiScanResult {\n // Pre-pass: every contract, from source, BEFORE any call site is resolved — a call site in\n // one project routinely names a contract owned by a project we have not walked yet.\n this.sourceIndex = new ApiSourceIndexBuilder(\n this.workspaceRoot,\n this.projectInfos,\n this.externalApiPaths,\n this.decoratorArgDiagnostics,\n ).build();\n for (const info of this.projectInfos.values()) {\n if (info.root === '' || info.root === '.') continue;\n this.scanProject(info);\n }\n return {\n relationsByProject: this.relationsByProject,\n apiLibProjects: this.sourceIndex.owners,\n apiIndex: this.sourceIndex.byName,\n scannedProjects: this.scannedProjects,\n unresolvedApiCalls: this.unresolvedApiCalls,\n nonLiteralDecoratorArgs: this.decoratorArgDiagnostics.all(),\n unresolvedEndpointPaths: this.decoratorArgDiagnostics.unresolvedEndpointPaths(),\n emptiedApiContracts: this.decoratorArgDiagnostics.emptiedContracts(),\n undeclaredExternalCallers: this.decoratorArgDiagnostics.undeclaredExternalCallers(),\n };\n }\n\n private scanProject(info: ProjectInfo): void {\n const program = createScanProgram(path.resolve(this.workspaceRoot, info.root));\n if (!program) return;\n const checker = program.getTypeChecker();\n const accumulator = new RelationAccumulator();\n let scannedProductionFile = false;\n\n for (const sourceFile of program.getSourceFiles()) {\n if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('/node_modules/')) continue;\n if (isTestFile(sourceFile.fileName)) continue; // tests are not production topology\n // Only this project's OWN files — imported api-lib source is in the program too.\n if (this.locator.projectOf(sourceFile.fileName) !== info.name) continue;\n scannedProductionFile = true;\n this.visit(sourceFile, checker, info.name, accumulator);\n }\n\n // Record coverage only when we actually saw production source — an all-test project (e2e)\n // stays absent so the validator won't wrongly flag its api-lib deps as unused.\n if (scannedProductionFile) this.scannedProjects.add(info.name);\n if (!accumulator.isEmpty()) this.relationsByProject.set(info.name, accumulator.toRelations());\n }\n\n private visit(node: ts.Node, checker: ts.TypeChecker, project: string, acc: RelationAccumulator): void {\n // In-repo contract classes are indexed by the source pre-pass, so only calls matter for them.\n if (ts.isCallExpression(node)) this.recordCall(node, checker, project, acc);\n // A VENDOR contract has no client-factory call site to key off — it arrives by injection —\n // so classes have to be inspected too.\n if (ts.isClassDeclaration(node)) this.recordExternalUses(node, acc);\n ts.forEachChild(node, (child: ts.Node) => this.visit(child, checker, project, acc));\n }\n\n /**\n * Record a `uses` for every vendor contract this class receives by CONSTRUCTOR INJECTION —\n * `constructor(@inject(GMAIL_TYPES.GmailApi) private readonly gmail: GmailApi)`.\n *\n * The parameter TYPE is the signal, not the token: a token is an opaque Symbol whose name we\n * would have to guess at, while the type is written right there and is what the class actually\n * calls. Matching happens by name against the external index, so an import that resolves to a\n * built `.d.ts` works exactly as well as one resolving to source.\n *\n * A class that IMPLEMENTS the contract is skipped — that is the vendor adapter (`GmailClient`)\n * or a test double (`InMemoryFirestore`, `MockTts`), which IS the seam rather than a caller of\n * it. Counting those would draw an edge from every service embedding a fake to a vendor it never\n * actually reaches.\n */\n private recordExternalUses(cls: ts.ClassDeclaration, acc: RelationAccumulator): void {\n const implemented = implementedTypeNames(cls);\n for (const param of constructorParamsOf(cls)) {\n const typeName = typeReferenceName(param.type);\n if (typeName === null || implemented.has(typeName)) continue;\n const info = this.sourceIndex.lookup(typeName);\n if (info === null || info.type !== 'external') continue;\n acc.addUses(info.owner, { api: info.api, type: 'external' });\n }\n }\n\n private recordCall(\n call: ts.CallExpression,\n checker: ts.TypeChecker,\n project: string,\n acc: RelationAccumulator,\n ): void {\n const method = calleeMethodName(call);\n if (method === null || call.arguments.length === 0) return;\n if (method === ADD_ROUTES_METHOD) {\n const info = this.apiInfoFromExpr(call.arguments[0], checker, project);\n if (info) acc.addImplements(info.owner, { api: info.api, type: info.type });\n return;\n }\n if (method === RPC_CLIENT_METHOD || method === PUBSUB_CLIENT_METHOD) {\n const info = this.apiInfoFromExpr(call.arguments[0], checker, project);\n if (!info) return;\n // Argument 2 names WHICH service this client talks to. Keeping it is what lets the\n // runtime graph draw ONE edge instead of one per implementer of the contract.\n const targetService = targetServiceOf(call);\n const ref: ApiRef = { api: info.api, type: info.type };\n if (targetService !== null) ref.targetService = targetService;\n acc.addUses(info.owner, ref);\n }\n }\n\n /** Resolve an expression to the API contract it names, or null if it is not one. */\n private apiInfoFromExpr(expr: ts.Expression, checker: ts.TypeChecker, project: string): ApiClassInfo | null {\n const decl = resolveClassDeclaration(expr, checker);\n if (!decl) return null;\n const fromSource = this.apiClassInfoFor(decl);\n return fromSource ?? this.recoverFromDeclaration(decl, expr, project);\n }\n\n /**\n * The checker landed on a BUILT declaration instead of source — the consumer has no\n * tsconfig.base `paths` entry for the api-lib, so the import went through node_modules to\n * `dist/**.d.ts`. tsc erases decorators when emitting declarations, so `@ApiPath` is simply\n * not there and never will be. Recover the contract by name from the source index; the graph\n * is then correct no matter how the consumer's tsconfig is laid out.\n */\n private recoverFromDeclaration(\n decl: ts.ClassDeclaration,\n expr: ts.Expression,\n project: string,\n ): ApiClassInfo | null {\n // An abstract class is the shape of a contract; a non-abstract argument is genuinely not one.\n if (!decl.getSourceFile().isDeclarationFile || !isAbstractClass(decl) || !decl.name) return null;\n const recovered = this.sourceIndex.lookup(decl.name.text);\n if (recovered) return recovered;\n // Abstract, in a .d.ts, yet no workspace source owns it — the scan is blind here. Say so.\n this.unresolvedApiCalls.push(\n new UnresolvedApiCall(\n project,\n decl.name.text,\n this.relativeLocation(expr),\n this.relativePath(decl.getSourceFile().fileName),\n ),\n );\n return null;\n }\n\n /** `path/to/file.ts:LINE` for `node`, workspace-relative, for a human-readable report. */\n private relativeLocation(node: ts.Node): string {\n const sourceFile = node.getSourceFile();\n const position = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n return `${this.relativePath(sourceFile.fileName)}:${position.line + 1}`;\n }\n\n private relativePath(absFile: string): string {\n return path.relative(this.workspaceRoot, absFile);\n }\n\n /**\n * {api, owner, type, methods} when `cls` is an `abstract class` carrying `@ApiPath` IN SOURCE,\n * else null. Only the OWNER differs from the index pre-pass — here it comes from the file's\n * location rather than from the project being walked — so the contract test itself is delegated\n * to apiClassInfoFrom, keeping one definition of \"this is a contract\".\n */\n private apiClassInfoFor(cls: ts.ClassDeclaration): ApiClassInfo | null {\n const owner = this.locator.projectOf(cls.getSourceFile().fileName);\n if (owner === null) return null;\n return apiClassInfoFrom(cls, owner);\n }\n}\n\n/**\n * Run the scan and attach the derived `apiRelations` onto each graph entry in\n * place. Shared by `architecture:generate` (which then saves) and\n * `architecture:validate-architecture-unchanged` (which regenerates in memory\n * and must attach the SAME field, or it would see a phantom diff). Returns the\n * full scan so callers (validators, runtime graph) can reuse the api index.\n */\n// webpieces-disable no-function-outside-class -- module entry point, mirrors generateReducedGraph/collectBindings\nexport function scanAndAttachApiRelations(\n workspaceRoot: string,\n graph: EnhancedGraph,\n projectInfos: Map<string, ProjectInfo>,\n externalApiPaths: readonly string[] = [],\n): ApiScanResult {\n const result = new ApiUsageScanner(workspaceRoot, projectInfos, externalApiPaths).scan();\n for (const projectName of result.relationsByProject.keys()) {\n const entry = graph[projectName];\n if (entry) entry.apiRelations = result.relationsByProject.get(projectName);\n }\n return result;\n}\n\n/**\n * The committed api contract table (one `architecture/apis/<ApiName>.json` per entry), from a\n * completed scan.\n *\n * Only contracts with ≥1 endpoint are emitted: a vendor seam has no routes, so a table entry for it\n * would be an empty shell, and its identity is already carried by the `external` refs in\n * apiRelations. Sorted by api name, methods left in declaration order, so the file is deterministic.\n *\n * THROWS on the four ways an entry can be wrong-but-green, checked root cause first (the fourth is\n * an `external` method that never said WHO calls it — UndeclaredExternalCallerError):\n * 1. an `@Endpoint` path the scan could not read (UnresolvedEndpointPathError) — the other half of\n * the URL a consumer computes, and the cause of most emptied contracts;\n * 2. a class that declared endpoints and kept none (EmptiedApiContractError), which would otherwise\n * leave silently through the zero-method skip above;\n * 3. a routed contract with no basePath (MissingBasePathError).\n * All three are worse than an absent entry: a consumer joining `basePath + path` computes a\n * confidently wrong URL with no signal that anything is off, because every other entry is complete.\n * Each error aggregates EVERY offender, so a developer fixing five constants sees five in one run.\n */\n// webpieces-disable no-function-outside-class -- module entry point, mirrors scanAndAttachApiRelations\nexport function buildApiContracts(scan: ApiScanResult): ApiContracts {\n // Root cause before symptom: an unreadable path is what empties a contract, so naming the paths\n // is what the author can actually act on.\n if (scan.unresolvedEndpointPaths.length > 0) throw new UnresolvedEndpointPathError(scan.unresolvedEndpointPaths);\n if (scan.emptiedApiContracts.length > 0) throw new EmptiedApiContractError(scan.emptiedApiContracts);\n // After the two above: an unreadable path is what empties a contract, and a contract that lost\n // every method has no external endpoint left to complain about.\n if (scan.undeclaredExternalCallers.length > 0) {\n throw new UndeclaredExternalCallerError(scan.undeclaredExternalCallers);\n }\n const contracts: ApiContracts = {};\n const missing: string[] = [];\n for (const api of [...scan.apiIndex.keys()].sort()) {\n const info = scan.apiIndex.get(api)!;\n if (info.methods.length === 0) continue;\n if (info.basePath === undefined) {\n missing.push(`${api} (owner ${info.owner})`);\n continue;\n }\n const contract: ApiContract = {\n owner: info.owner,\n apiKind: info.type,\n basePath: info.basePath,\n methods: info.methods,\n };\n contracts[api] = contract;\n }\n if (missing.length > 0) throw new MissingBasePathError(missing);\n return contracts;\n}\n\n\n/**\n * Loud, actionable report for decorator arguments the scan could not reduce to a string.\n *\n * Same-module constants resolve, so anything reaching here is genuinely out of reach of a\n * parser-only pass — and every one of them silently shrinks the graph. Empty string when there is\n * nothing to say, so callers can test it without special-casing.\n */\n// webpieces-disable no-function-outside-class -- pure formatter, mirrors describeUnresolvedApiCalls\nexport function describeNonLiteralDecoratorArgs(args: readonly NonLiteralDecoratorArg[]): string {\n if (args.length === 0) return '';\n const lines = [\n `⚠️ ${args.length} decorator argument(s) are not string literals and could not be resolved.`,\n ` Each one drops data from the graph: a missing basePath, a missing method, or a whole contract:`,\n ];\n for (const arg of args) {\n const where = arg.method === null ? arg.api : `${arg.api}.${arg.method}`;\n lines.push(` • @${arg.decorator}(${arg.argument}) on ${where} at ${arg.at}`);\n }\n lines.push(\n ` A constant declared in the SAME module resolves. One imported from another module does not —`,\n ` this scan is parser-only by design (module resolution can land on a decorator-erased .d.ts).`,\n ` Fix by inlining the string literal, or by moving the constant into the contract's own module.`,\n );\n return lines.join('\\n');\n}\n\n/**\n * Every contract method whose declared @Endpoint kind its api kind cannot deliver — an rpc method on\n * a @PubSub contract (nothing calls a queue synchronously), or a cloudtasks/cron method on an @Rpc\n * contract (naming a queue or schedule nothing could deliver to). Mirrors core-util's\n * ENDPOINT_KINDS_BY_API_KIND at BUILD time, where it can name the file instead of throwing at wiring.\n */\n// webpieces-disable no-function-outside-class -- pure formatter, mirrors describeUnresolvedApiCalls\nexport function describeMismatchedEndpointKinds(contracts: ApiContracts): string[] {\n const allowedByKind: Record<string, readonly EndpointKind[]> = {\n rpc: ['rpc', 'external'],\n pubsub: ['cloudtasks', 'cron', 'external'],\n };\n const problems: string[] = [];\n for (const api of Object.keys(contracts)) {\n const contract = contracts[api];\n const allowed = allowedByKind[contract.apiKind];\n if (allowed === undefined) continue;\n for (const method of contract.methods) {\n if (allowed.includes(method.kind)) continue;\n problems.push(\n `${api}.${method.name} declares @Endpoint('${method.path}', '${method.kind}') but ${api} is ` +\n `@${contract.apiKind === 'pubsub' ? 'PubSub' : 'Rpc'} — allowed kinds are ${allowed.join(' | ')}.`,\n );\n }\n }\n return problems;\n}\n\n/**\n * Loud, actionable report for contracts the scan could not map to source. Callers print this\n * instead of emitting a green graph that is quietly missing relations. Not fatal: a contract\n * from a genuinely EXTERNAL (published, non-workspace) api-lib legitimately has no source here.\n */\n// webpieces-disable no-function-outside-class -- pure formatter, mirrors describeUnclassifiedApiDep\nexport function describeUnresolvedApiCalls(calls: UnresolvedApiCall[]): string {\n const lines = [\n `⚠️ ${calls.length} API contract(s) resolved to a declaration file with no matching workspace source.`,\n ` Decorators (@ApiPath) are ERASED in .d.ts output, so these relations are MISSING from the graph:`,\n ];\n for (const call of calls) {\n lines.push(` • ${call.api} at ${call.at} (${call.project}) → resolved to ${call.declaredIn}`);\n }\n lines.push(\n ` If the api-lib IS in this workspace, add a tsconfig.base.json 'paths' entry mapping it to its`,\n ` src/index.ts, or confirm its project root is registered. If it is a published external package,`,\n ` this relation cannot be derived and the graph edge will not appear.`,\n );\n return lines.join('\\n');\n}\n\n/**\n * Build a program for scanning ONE project. Prefers the project's compile tsconfig; but when that\n * is a solution-style tsconfig (only `references`, no `files`/`include` — e.g. legacy-server), it\n * yields zero files, so we fall back to globbing the project's own `src/**` and reuse the resolved\n * compiler options (which carry tsconfig.base `paths` for cross-package @webpieces resolution).\n *\n * `paths` is a PREFERENCE, not a precondition: it lets imports resolve straight to source. Without\n * it they land on a decorator-erased `dist/**.d.ts`, which the source index recovers from — see\n * ApiUsageScanner.recoverFromDeclaration.\n */\n// webpieces-disable no-function-outside-class -- ts Program factory, mirrors di-graph/program.ts\nfunction createScanProgram(projectRootAbs: string): ts.Program | null {\n const configPath = findProjectTsconfig(projectRootAbs);\n if (!configPath) return buildProgramFromSrc(projectRootAbs, {});\n const host = Object.assign({}, ts.sys, {\n onUnRecoverableConfigFileDiagnostic: (): void => undefined,\n }) as ts.ParseConfigFileHost;\n const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, host);\n if (!parsed) return null;\n if (parsed.fileNames.length > 0) return ts.createProgram(parsed.fileNames, parsed.options);\n return buildProgramFromSrc(projectRootAbs, parsed.options);\n}\n\n// webpieces-disable no-function-outside-class -- ts Program factory helper, mirrors di-graph/program.ts\nfunction buildProgramFromSrc(projectRootAbs: string, options: ts.CompilerOptions): ts.Program | null {\n const srcDir = path.join(projectRootAbs, 'src');\n if (!fs.existsSync(srcDir)) return null;\n const files = collectTsFiles(srcDir);\n return files.length > 0 ? ts.createProgram(files, options) : null;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"api-scanner.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-scanner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * API Usage Scanner\n *\n * Derives, by scanning real source (not a declaration file), how every project\n * relates to the api-lib projects it depends on. This is the single source of\n * truth for the `apiRelations` field in architecture/dependencies.json AND for\n * the runtime microservice graph.\n *\n * Signals (all resolved through the TypeScript checker, so re-exports resolve):\n * - IMPLEMENTS: `apiFactory.addRoutes(XxxApi, XxxController)` — the registration\n * that actually SERVES the contract over the wire. We deliberately\n * do NOT use `class Ctrl extends XxxApi`: a class can extend an API\n * as an in-process test double / simulator (e.g. Server2Simulator)\n * without ever serving it — only `addRoutes` proves a served route.\n * - USES: `factory.createRpcClient(XxxApi, ...)` → rpc client\n * `factory.createPubSubClient(XxxApi, ...)` → pubsub (Cloud Tasks) client\n * The config argument (`new ClientConfig('helper-fsdb')`) names WHICH service the\n * client talks to and is kept as `ApiRef.targetService` — see targetServiceOf.\n * An api-lib is DETECTED, not tagged: a project exporting an `abstract class`\n * carrying `@ApiPath` owns that API. Its transport is `@PubSub` → 'pubsub', else 'rpc'.\n *\n * Contracts are indexed from SOURCE in a pre-pass (ApiSourceIndexBuilder) rather than\n * from wherever the checker resolves an import to. A consumer without a tsconfig.base\n * `paths` entry resolves `import { XxxApi } from '@scope/xxx-api'` through node_modules\n * to the package's BUILT `dist/**.d.ts` — and tsc ERASES decorators when emitting\n * declarations, so `@ApiPath` can never be read there. Keying off the resolved\n * declaration therefore dropped whole services from the graph, silently. See\n * `recoverFromDeclaration`.\n */\n\nimport * as ts from 'typescript';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { matchesAnyGlob } from '@webpieces/rules-config';\nimport type { EnhancedGraph } from '../graph-sorter';\nimport { ProjectInfo } from '../project-info';\nimport { findProjectTsconfig } from '../di-graph/program';\nimport { resolveClassDeclaration } from '../di-graph/bindings';\nimport {\n ApiClassInfo,\n ApiContract,\n ApiContracts,\n ApiRef,\n ApiRelation,\n EmptiedApiContract,\n EndpointKind,\n NonLiteralDecoratorArg,\n ProjectApiRelations,\n UndeclaredExternalCaller,\n UndeclaredEndpointOperation,\n UnresolvedEndpointPath,\n apiRefKey,\n deriveApiRelationKind,\n sortApiRefs,\n} from './api-relations';\nimport {\n EmptiedApiContractError,\n MissingBasePathError,\n UndeclaredExternalCallerError,\n UndeclaredEndpointOperationError,\n UnresolvedEndpointPathError,\n} from './api-contract-errors';\nimport {\n DecoratorArgDiagnostics,\n apiClassInfoFrom,\n apiClassInfoFromNode,\n calleeMethodName,\n collectTsFiles,\n constructorParamsOf,\n externalApiInfoFrom,\n implementedTypeNames,\n isAbstractClass,\n isTestFile,\n targetServiceOf,\n typeReferenceName,\n} from './api-ast';\n\nconst RPC_CLIENT_METHOD = 'createRpcClient';\nconst PUBSUB_CLIENT_METHOD = 'createPubSubClient';\nconst ADD_ROUTES_METHOD = 'addRoutes';\n\n/**\n * An `addRoutes`/`createRpcClient`/`createPubSubClient` first argument that resolved to an\n * abstract class in a DECLARATION file which owns no indexed contract. Unambiguously a broken\n * scan (a real api-lib whose source we never indexed), never a \"this isn't an API\" argument —\n * so it is reported loudly instead of collapsing into a silent `return null`.\n */\nexport class UnresolvedApiCall {\n constructor(\n /** The project whose source makes the call. */\n public readonly project: string,\n /** The contract class name as written at the call site. */\n public readonly api: string,\n /** `path/to/file.ts:LINE` of the call site, workspace-relative. */\n public readonly at: string,\n /** The declaration file the checker resolved to (where decorators are erased). */\n public readonly declaredIn: string,\n ) {}\n}\n\n/** The whole-workspace result of a scan. */\nexport interface ApiScanResult {\n /** projectName -> { apiLibProject -> relation }; only projects with ≥1 relation appear. */\n relationsByProject: Map<string, ProjectApiRelations>;\n /** Every project that owns ≥1 API contract class. */\n apiLibProjects: Set<string>;\n /** apiClassName -> where it lives + its transport. */\n apiIndex: Map<string, ApiClassInfo>;\n /**\n * Projects whose production (non-test) source was actually scanned. A project with only test\n * files (e.g. an e2e harness), or one the compiler couldn't load, is ABSENT — callers must not\n * conclude \"no implements/uses\" for it, because its behavior was never observed.\n */\n scannedProjects: Set<string>;\n /**\n * Call sites naming a contract we could not map back to workspace source. Non-empty means the\n * graph is INCOMPLETE — callers must surface these rather than emit a green, wrong graph.\n */\n unresolvedApiCalls: UnresolvedApiCall[];\n /**\n * Decorator arguments that were present but could not be reduced to a string (a cross-module\n * constant, a computed expression). Each one costs the graph a basePath, a method, or — when it\n * takes out every method of a class — the whole contract, so they must be surfaced.\n */\n nonLiteralDecoratorArgs: NonLiteralDecoratorArg[];\n /**\n * The subset of the above that is FATAL: an `@Endpoint` path that could not be read. Every client\n * builds its URL as `basePath + path`, so this is missing routing, not missing metadata —\n * buildApiContracts throws on a non-empty list rather than shipping a contract without it.\n */\n unresolvedEndpointPaths: UnresolvedEndpointPath[];\n /**\n * Contract classes that declared `@Endpoint` methods and kept none — the exact shape that used to\n * slip out through buildApiContracts' zero-method skip, taking a whole service's queues with it.\n */\n emptiedApiContracts: EmptiedApiContract[];\n /**\n * `external` endpoints that did not say WHO calls them. Fatal: the inbound box on the runtime\n * graph exists to name that system, and with nothing to name it restates our own contract name.\n */\n undeclaredExternalCallers: UndeclaredExternalCaller[];\n /** Endpoints lacking the explicit side-effect contract used for retry safety and MCP hints. */\n undeclaredEndpointOperations: UndeclaredEndpointOperation[];\n}\n\n/** Maps an absolute source-file path to the workspace project that owns it (longest-root-prefix). */\nclass ProjectLocator {\n private readonly roots: ProjectRoot[];\n\n constructor(workspaceRoot: string, projectInfos: Map<string, ProjectInfo>) {\n const roots: ProjectRoot[] = [];\n for (const info of projectInfos.values()) {\n if (info.root === '' || info.root === '.') continue;\n roots.push(new ProjectRoot(info.name, path.resolve(workspaceRoot, info.root)));\n }\n // Longest root first so a nested project wins over its parent.\n this.roots = roots.sort((a: ProjectRoot, b: ProjectRoot) => b.abs.length - a.abs.length);\n }\n\n projectOf(absFile: string): string | null {\n const normalized = path.resolve(absFile);\n for (const root of this.roots) {\n if (normalized === root.abs || normalized.startsWith(root.abs + path.sep))\n return root.name;\n }\n return null;\n }\n}\n\nclass ProjectRoot {\n constructor(\n public readonly name: string,\n public readonly abs: string,\n ) {}\n}\n\n/**\n * Every API contract in the workspace, keyed by class name, read from SOURCE.\n *\n * Name-keyed because a call site only ever gives us a name once its import has resolved into a\n * decorator-erased declaration. Two api-libs exporting the same class name collide (last wins) —\n * the same collision the published `apiIndex` has always had.\n */\nclass ApiSourceIndex {\n constructor(\n public readonly byName: Map<string, ApiClassInfo>,\n public readonly owners: Set<string>,\n ) {}\n\n lookup(api: string): ApiClassInfo | null {\n return this.byName.get(api) ?? null;\n }\n}\n\n/**\n * Builds the ApiSourceIndex by parsing each project's own `src/**` directly.\n *\n * Deliberately parser-only (no ts.Program, no checker): we need the decorators exactly as\n * written, and a plain parse cannot be diverted to a `.d.ts` by module resolution — which is\n * the entire bug this guards against. It is also cheap enough to run over every project.\n */\nclass ApiSourceIndexBuilder {\n private readonly byName = new Map<string, ApiClassInfo>();\n private readonly owners = new Set<string>();\n\n constructor(\n private readonly workspaceRoot: string,\n private readonly projectInfos: Map<string, ProjectInfo>,\n /** Globs of project roots holding vendor contracts — see ExternalApiIndex. */\n private readonly externalApiPaths: readonly string[],\n /** Sink for decorator arguments this parser-only pass cannot reduce to a string. */\n private readonly diagnostics: DecoratorArgDiagnostics,\n ) {}\n\n build(): ApiSourceIndex {\n for (const info of this.projectInfos.values()) {\n if (info.root === '' || info.root === '.') continue;\n this.indexProject(info);\n }\n return new ApiSourceIndex(this.byName, this.owners);\n }\n\n private indexProject(info: ProjectInfo): void {\n const srcDir = path.join(path.resolve(this.workspaceRoot, info.root), 'src');\n if (!fs.existsSync(srcDir)) return;\n const external = matchesAnyGlob(info.root, this.externalApiPaths);\n for (const file of collectTsFiles(srcDir)) {\n if (isTestFile(file)) continue; // tests are not production topology\n const text = fs.readFileSync(file, 'utf8');\n const sourceFile = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);\n this.indexNode(sourceFile, info.name, external);\n }\n }\n\n private indexNode(node: ts.Node, project: string, external: boolean): void {\n const info = external\n ? externalApiInfoFrom(node, project)\n : apiClassInfoFromNode(node, project, this.diagnostics);\n if (info) {\n this.owners.add(project);\n this.byName.set(info.api, info);\n }\n ts.forEachChild(node, (child: ts.Node) => this.indexNode(child, project, external));\n }\n}\n/** Per-owner accumulator that dedupes API refs while a single project is scanned. */\nclass RelationAccumulator {\n private readonly implementsByOwner = new Map<string, Map<string, ApiRef>>();\n private readonly usesByOwner = new Map<string, Map<string, ApiRef>>();\n\n addImplements(owner: string, ref: ApiRef): void {\n ensureRefMap(this.implementsByOwner, owner).set(apiRefKey(ref), ref);\n }\n\n /**\n * Keyed by api + targetService: one project legitimately binds the SAME contract against two\n * different services (a WarmupApi client per data server), and those are two relations, not one.\n */\n addUses(owner: string, ref: ApiRef): void {\n ensureRefMap(this.usesByOwner, owner).set(apiRefKey(ref), ref);\n }\n\n /** Build the deterministic { owner -> relation } record, owners in sorted order. */\n toRelations(): ProjectApiRelations {\n const owners = new Set<string>([\n ...this.implementsByOwner.keys(),\n ...this.usesByOwner.keys(),\n ]);\n const relations: ProjectApiRelations = {};\n for (const owner of [...owners].sort()) {\n const implementsRefs = sortApiRefs([\n ...(this.implementsByOwner.get(owner)?.values() ?? []),\n ]);\n const usesRefs = sortApiRefs([...(this.usesByOwner.get(owner)?.values() ?? [])]);\n const relation: ApiRelation = {\n kind: deriveApiRelationKind(implementsRefs, usesRefs),\n implements: implementsRefs,\n uses: usesRefs,\n };\n relations[owner] = relation;\n }\n return relations;\n }\n\n isEmpty(): boolean {\n return this.implementsByOwner.size === 0 && this.usesByOwner.size === 0;\n }\n}\n\n// webpieces-disable no-function-outside-class -- tiny map helper, matching the AST-helper style of di-graph/bindings.ts\nfunction ensureRefMap(map: Map<string, Map<string, ApiRef>>, owner: string): Map<string, ApiRef> {\n let inner = map.get(owner);\n if (!inner) {\n inner = new Map<string, ApiRef>();\n map.set(owner, inner);\n }\n return inner;\n}\n\n/** Statically scans every project for its api-lib implements/uses relationships. */\nexport class ApiUsageScanner {\n private readonly locator: ProjectLocator;\n private readonly relationsByProject = new Map<string, ProjectApiRelations>();\n private readonly scannedProjects = new Set<string>();\n private readonly unresolvedApiCalls: UnresolvedApiCall[] = [];\n private readonly decoratorArgDiagnostics: DecoratorArgDiagnostics;\n private sourceIndex = new ApiSourceIndex(new Map<string, ApiClassInfo>(), new Set<string>());\n\n constructor(\n private readonly workspaceRoot: string,\n private readonly projectInfos: Map<string, ProjectInfo>,\n /** Globs of project roots whose exported `*Api` types are contracts for outside systems. */\n private readonly externalApiPaths: readonly string[] = [],\n ) {\n this.locator = new ProjectLocator(workspaceRoot, projectInfos);\n this.decoratorArgDiagnostics = new DecoratorArgDiagnostics(workspaceRoot);\n }\n\n scan(): ApiScanResult {\n // Pre-pass: every contract, from source, BEFORE any call site is resolved — a call site in\n // one project routinely names a contract owned by a project we have not walked yet.\n this.sourceIndex = new ApiSourceIndexBuilder(\n this.workspaceRoot,\n this.projectInfos,\n this.externalApiPaths,\n this.decoratorArgDiagnostics,\n ).build();\n for (const info of this.projectInfos.values()) {\n if (info.root === '' || info.root === '.') continue;\n this.scanProject(info);\n }\n return {\n relationsByProject: this.relationsByProject,\n apiLibProjects: this.sourceIndex.owners,\n apiIndex: this.sourceIndex.byName,\n scannedProjects: this.scannedProjects,\n unresolvedApiCalls: this.unresolvedApiCalls,\n nonLiteralDecoratorArgs: this.decoratorArgDiagnostics.all(),\n unresolvedEndpointPaths: this.decoratorArgDiagnostics.unresolvedEndpointPaths(),\n emptiedApiContracts: this.decoratorArgDiagnostics.emptiedContracts(),\n undeclaredExternalCallers: this.decoratorArgDiagnostics.undeclaredExternalCallers(),\n undeclaredEndpointOperations:\n this.decoratorArgDiagnostics.undeclaredEndpointOperations(),\n };\n }\n\n private scanProject(info: ProjectInfo): void {\n const program = createScanProgram(path.resolve(this.workspaceRoot, info.root));\n if (!program) return;\n const checker = program.getTypeChecker();\n const accumulator = new RelationAccumulator();\n let scannedProductionFile = false;\n\n for (const sourceFile of program.getSourceFiles()) {\n if (sourceFile.isDeclarationFile || sourceFile.fileName.includes('/node_modules/'))\n continue;\n if (isTestFile(sourceFile.fileName)) continue; // tests are not production topology\n // Only this project's OWN files — imported api-lib source is in the program too.\n if (this.locator.projectOf(sourceFile.fileName) !== info.name) continue;\n scannedProductionFile = true;\n this.visit(sourceFile, checker, info.name, accumulator);\n }\n\n // Record coverage only when we actually saw production source — an all-test project (e2e)\n // stays absent so the validator won't wrongly flag its api-lib deps as unused.\n if (scannedProductionFile) this.scannedProjects.add(info.name);\n if (!accumulator.isEmpty())\n this.relationsByProject.set(info.name, accumulator.toRelations());\n }\n\n private visit(\n node: ts.Node,\n checker: ts.TypeChecker,\n project: string,\n acc: RelationAccumulator,\n ): void {\n // In-repo contract classes are indexed by the source pre-pass, so only calls matter for them.\n if (ts.isCallExpression(node)) this.recordCall(node, checker, project, acc);\n // A VENDOR contract has no client-factory call site to key off — it arrives by injection —\n // so classes have to be inspected too.\n if (ts.isClassDeclaration(node)) this.recordExternalUses(node, acc);\n ts.forEachChild(node, (child: ts.Node) => this.visit(child, checker, project, acc));\n }\n\n /**\n * Record a `uses` for every vendor contract this class receives by CONSTRUCTOR INJECTION —\n * `constructor(@inject(GMAIL_TYPES.GmailApi) private readonly gmail: GmailApi)`.\n *\n * The parameter TYPE is the signal, not the token: a token is an opaque Symbol whose name we\n * would have to guess at, while the type is written right there and is what the class actually\n * calls. Matching happens by name against the external index, so an import that resolves to a\n * built `.d.ts` works exactly as well as one resolving to source.\n *\n * A class that IMPLEMENTS the contract is skipped — that is the vendor adapter (`GmailClient`)\n * or a test double (`InMemoryFirestore`, `MockTts`), which IS the seam rather than a caller of\n * it. Counting those would draw an edge from every service embedding a fake to a vendor it never\n * actually reaches.\n */\n private recordExternalUses(cls: ts.ClassDeclaration, acc: RelationAccumulator): void {\n const implemented = implementedTypeNames(cls);\n for (const param of constructorParamsOf(cls)) {\n const typeName = typeReferenceName(param.type);\n if (typeName === null || implemented.has(typeName)) continue;\n const info = this.sourceIndex.lookup(typeName);\n if (info === null || info.type !== 'external') continue;\n acc.addUses(info.owner, { api: info.api, type: 'external' });\n }\n }\n\n private recordCall(\n call: ts.CallExpression,\n checker: ts.TypeChecker,\n project: string,\n acc: RelationAccumulator,\n ): void {\n const method = calleeMethodName(call);\n if (method === null || call.arguments.length === 0) return;\n if (method === ADD_ROUTES_METHOD) {\n const info = this.apiInfoFromExpr(call.arguments[0], checker, project);\n if (info) acc.addImplements(info.owner, { api: info.api, type: info.type });\n return;\n }\n if (method === RPC_CLIENT_METHOD || method === PUBSUB_CLIENT_METHOD) {\n const info = this.apiInfoFromExpr(call.arguments[0], checker, project);\n if (!info) return;\n // Argument 2 names WHICH service this client talks to. Keeping it is what lets the\n // runtime graph draw ONE edge instead of one per implementer of the contract.\n const targetService = targetServiceOf(call);\n const ref: ApiRef = { api: info.api, type: info.type };\n if (targetService !== null) ref.targetService = targetService;\n acc.addUses(info.owner, ref);\n }\n }\n\n /** Resolve an expression to the API contract it names, or null if it is not one. */\n private apiInfoFromExpr(\n expr: ts.Expression,\n checker: ts.TypeChecker,\n project: string,\n ): ApiClassInfo | null {\n const decl = resolveClassDeclaration(expr, checker);\n if (!decl) return null;\n const fromSource = this.apiClassInfoFor(decl);\n return fromSource ?? this.recoverFromDeclaration(decl, expr, project);\n }\n\n /**\n * The checker landed on a BUILT declaration instead of source — the consumer has no\n * tsconfig.base `paths` entry for the api-lib, so the import went through node_modules to\n * `dist/**.d.ts`. tsc erases decorators when emitting declarations, so `@ApiPath` is simply\n * not there and never will be. Recover the contract by name from the source index; the graph\n * is then correct no matter how the consumer's tsconfig is laid out.\n */\n private recoverFromDeclaration(\n decl: ts.ClassDeclaration,\n expr: ts.Expression,\n project: string,\n ): ApiClassInfo | null {\n // An abstract class is the shape of a contract; a non-abstract argument is genuinely not one.\n if (!decl.getSourceFile().isDeclarationFile || !isAbstractClass(decl) || !decl.name)\n return null;\n const recovered = this.sourceIndex.lookup(decl.name.text);\n if (recovered) return recovered;\n // Abstract, in a .d.ts, yet no workspace source owns it — the scan is blind here. Say so.\n this.unresolvedApiCalls.push(\n new UnresolvedApiCall(\n project,\n decl.name.text,\n this.relativeLocation(expr),\n this.relativePath(decl.getSourceFile().fileName),\n ),\n );\n return null;\n }\n\n /** `path/to/file.ts:LINE` for `node`, workspace-relative, for a human-readable report. */\n private relativeLocation(node: ts.Node): string {\n const sourceFile = node.getSourceFile();\n const position = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n return `${this.relativePath(sourceFile.fileName)}:${position.line + 1}`;\n }\n\n private relativePath(absFile: string): string {\n return path.relative(this.workspaceRoot, absFile);\n }\n\n /**\n * {api, owner, type, methods} when `cls` is an `abstract class` carrying `@ApiPath` IN SOURCE,\n * else null. Only the OWNER differs from the index pre-pass — here it comes from the file's\n * location rather than from the project being walked — so the contract test itself is delegated\n * to apiClassInfoFrom, keeping one definition of \"this is a contract\".\n */\n private apiClassInfoFor(cls: ts.ClassDeclaration): ApiClassInfo | null {\n const owner = this.locator.projectOf(cls.getSourceFile().fileName);\n if (owner === null) return null;\n return apiClassInfoFrom(cls, owner);\n }\n}\n\n/**\n * Run the scan and attach the derived `apiRelations` onto each graph entry in\n * place. Shared by `architecture:generate` (which then saves) and\n * `architecture:validate-architecture-unchanged` (which regenerates in memory\n * and must attach the SAME field, or it would see a phantom diff). Returns the\n * full scan so callers (validators, runtime graph) can reuse the api index.\n */\n// webpieces-disable no-function-outside-class -- module entry point, mirrors generateReducedGraph/collectBindings\nexport function scanAndAttachApiRelations(\n workspaceRoot: string,\n graph: EnhancedGraph,\n projectInfos: Map<string, ProjectInfo>,\n externalApiPaths: readonly string[] = [],\n): ApiScanResult {\n const result = new ApiUsageScanner(workspaceRoot, projectInfos, externalApiPaths).scan();\n for (const projectName of result.relationsByProject.keys()) {\n const entry = graph[projectName];\n if (entry) entry.apiRelations = result.relationsByProject.get(projectName);\n }\n return result;\n}\n\n/**\n * The committed api contract table (one `architecture/apis/<ApiName>.json` per entry), from a\n * completed scan.\n *\n * Only contracts with ≥1 endpoint are emitted: a vendor seam has no routes, so a table entry for it\n * would be an empty shell, and its identity is already carried by the `external` refs in\n * apiRelations. Sorted by api name, methods left in declaration order, so the file is deterministic.\n *\n * THROWS on the ways an entry can be wrong-but-green, checked root cause first:\n * 1. an `@Endpoint` path the scan could not read (UnresolvedEndpointPathError) — the other half of\n * the URL a consumer computes, and the cause of most emptied contracts;\n * 2. a class that declared endpoints and kept none (EmptiedApiContractError), which would otherwise\n * leave silently through the zero-method skip above;\n * 3. a method without an explicit operation (UndeclaredEndpointOperationError);\n * 4. an `external` method that never said WHO calls it (UndeclaredExternalCallerError);\n * 5. a routed contract with no basePath (MissingBasePathError).\n * All are worse than an absent entry: a consumer joining `basePath + path` computes a\n * confidently wrong URL with no signal that anything is off, because every other entry is complete.\n * Each error aggregates EVERY offender, so a developer fixing five constants sees five in one run.\n */\n// webpieces-disable no-function-outside-class -- module entry point, mirrors scanAndAttachApiRelations\nexport function buildApiContracts(scan: ApiScanResult): ApiContracts {\n // Root cause before symptom: an unreadable path is what empties a contract, so naming the paths\n // is what the author can actually act on.\n if (scan.unresolvedEndpointPaths.length > 0)\n throw new UnresolvedEndpointPathError(scan.unresolvedEndpointPaths);\n if (scan.emptiedApiContracts.length > 0)\n throw new EmptiedApiContractError(scan.emptiedApiContracts);\n if (scan.undeclaredEndpointOperations.length > 0) {\n throw new UndeclaredEndpointOperationError(scan.undeclaredEndpointOperations);\n }\n // After the two above: an unreadable path is what empties a contract, and a contract that lost\n // every method has no external endpoint left to complain about.\n if (scan.undeclaredExternalCallers.length > 0) {\n throw new UndeclaredExternalCallerError(scan.undeclaredExternalCallers);\n }\n const contracts: ApiContracts = {};\n const missing: string[] = [];\n for (const api of [...scan.apiIndex.keys()].sort()) {\n const info = scan.apiIndex.get(api)!;\n if (info.methods.length === 0) continue;\n if (info.basePath === undefined) {\n missing.push(`${api} (owner ${info.owner})`);\n continue;\n }\n const contract: ApiContract = {\n owner: info.owner,\n apiKind: info.type,\n basePath: info.basePath,\n methods: info.methods,\n };\n contracts[api] = contract;\n }\n if (missing.length > 0) throw new MissingBasePathError(missing);\n return contracts;\n}\n\n/**\n * Loud, actionable report for decorator arguments the scan could not reduce to a string.\n *\n * Same-module constants resolve, so anything reaching here is genuinely out of reach of a\n * parser-only pass — and every one of them silently shrinks the graph. Empty string when there is\n * nothing to say, so callers can test it without special-casing.\n */\n// webpieces-disable no-function-outside-class -- pure formatter, mirrors describeUnresolvedApiCalls\nexport function describeNonLiteralDecoratorArgs(args: readonly NonLiteralDecoratorArg[]): string {\n if (args.length === 0) return '';\n const lines = [\n `⚠️ ${args.length} decorator argument(s) are not string literals and could not be resolved.`,\n ` Each one drops data from the graph: a missing basePath, a missing method, or a whole contract:`,\n ];\n for (const arg of args) {\n const where = arg.method === null ? arg.api : `${arg.api}.${arg.method}`;\n lines.push(` • @${arg.decorator}(${arg.argument}) on ${where} at ${arg.at}`);\n }\n lines.push(\n ` A constant declared in the SAME module resolves. One imported from another module does not —`,\n ` this scan is parser-only by design (module resolution can land on a decorator-erased .d.ts).`,\n ` Fix by inlining the string literal, or by moving the constant into the contract's own module.`,\n );\n return lines.join('\\n');\n}\n\n/**\n * Every contract method whose declared @Endpoint kind its api kind cannot deliver — an rpc method on\n * a @PubSub contract (nothing calls a queue synchronously), or a cloudtasks/cron method on an @Rpc\n * contract (naming a queue or schedule nothing could deliver to). Mirrors core-util's\n * ENDPOINT_KINDS_BY_API_KIND at BUILD time, where it can name the file instead of throwing at wiring.\n */\n// webpieces-disable no-function-outside-class -- pure formatter, mirrors describeUnresolvedApiCalls\nexport function describeMismatchedEndpointKinds(contracts: ApiContracts): string[] {\n const allowedByKind: Record<string, readonly EndpointKind[]> = {\n rpc: ['rpc', 'external'],\n pubsub: ['cloudtasks', 'cron', 'external'],\n };\n const problems: string[] = [];\n for (const api of Object.keys(contracts)) {\n const contract = contracts[api];\n const allowed = allowedByKind[contract.apiKind];\n if (allowed === undefined) continue;\n for (const method of contract.methods) {\n if (allowed.includes(method.kind)) continue;\n problems.push(\n `${api}.${method.name} declares @Endpoint(${method.httpMethod ?? 'POST'}, '${method.path}', ${method.operation}, ${method.kind}) but ${api} is ` +\n `@${contract.apiKind === 'pubsub' ? 'PubSub' : 'Rpc'} — allowed kinds are ${allowed.join(' | ')}.`,\n );\n }\n }\n return problems;\n}\n\n/**\n * Loud, actionable report for contracts the scan could not map to source. Callers print this\n * instead of emitting a green graph that is quietly missing relations. Not fatal: a contract\n * from a genuinely EXTERNAL (published, non-workspace) api-lib legitimately has no source here.\n */\n// webpieces-disable no-function-outside-class -- pure formatter, mirrors describeUnclassifiedApiDep\nexport function describeUnresolvedApiCalls(calls: UnresolvedApiCall[]): string {\n const lines = [\n `⚠️ ${calls.length} API contract(s) resolved to a declaration file with no matching workspace source.`,\n ` Decorators (@ApiPath) are ERASED in .d.ts output, so these relations are MISSING from the graph:`,\n ];\n for (const call of calls) {\n lines.push(\n ` • ${call.api} at ${call.at} (${call.project}) → resolved to ${call.declaredIn}`,\n );\n }\n lines.push(\n ` If the api-lib IS in this workspace, add a tsconfig.base.json 'paths' entry mapping it to its`,\n ` src/index.ts, or confirm its project root is registered. If it is a published external package,`,\n ` this relation cannot be derived and the graph edge will not appear.`,\n );\n return lines.join('\\n');\n}\n\n/**\n * Build a program for scanning ONE project. Prefers the project's compile tsconfig; but when that\n * is a solution-style tsconfig (only `references`, no `files`/`include` — e.g. legacy-server), it\n * yields zero files, so we fall back to globbing the project's own `src/**` and reuse the resolved\n * compiler options (which carry tsconfig.base `paths` for cross-package @webpieces resolution).\n *\n * `paths` is a PREFERENCE, not a precondition: it lets imports resolve straight to source. Without\n * it they land on a decorator-erased `dist/**.d.ts`, which the source index recovers from — see\n * ApiUsageScanner.recoverFromDeclaration.\n */\n// webpieces-disable no-function-outside-class -- ts Program factory, mirrors di-graph/program.ts\nfunction createScanProgram(projectRootAbs: string): ts.Program | null {\n const configPath = findProjectTsconfig(projectRootAbs);\n if (!configPath) return buildProgramFromSrc(projectRootAbs, {});\n const host = Object.assign({}, ts.sys, {\n onUnRecoverableConfigFileDiagnostic: (): void => undefined,\n }) as ts.ParseConfigFileHost;\n const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, host);\n if (!parsed) return null;\n if (parsed.fileNames.length > 0) return ts.createProgram(parsed.fileNames, parsed.options);\n return buildProgramFromSrc(projectRootAbs, parsed.options);\n}\n\n// webpieces-disable no-function-outside-class -- ts Program factory helper, mirrors di-graph/program.ts\nfunction buildProgramFromSrc(\n projectRootAbs: string,\n options: ts.CompilerOptions,\n): ts.Program | null {\n const srcDir = path.join(projectRootAbs, 'src');\n if (!fs.existsSync(srcDir)) return null;\n const files = collectTsFiles(srcDir);\n return files.length > 0 ? ts.createProgram(files, options) : null;\n}\n"]}
|
|
@@ -127,7 +127,7 @@ export interface RuntimeTrigger {
|
|
|
127
127
|
queueName?: string;
|
|
128
128
|
/**
|
|
129
129
|
* Present for 'external': WHO outside this repo posts to it, declared as
|
|
130
|
-
* `@Endpoint(p,
|
|
130
|
+
* `@Endpoint(POST, p, WRITE, EXTERNAL, { calledBy: 'twilio' })`.
|
|
131
131
|
*
|
|
132
132
|
* The SAME `(kind,label)` an OUTBOUND {@link RuntimeExternalSystem} carries, so an inbound
|
|
133
133
|
* `saas twilio` and an outbound `saas twilio` share a node identity and converge on ONE box —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-graph-model.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-graph-model.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG","sourcesContent":["/**\n * Runtime Graph model\n *\n * The serialization DTOs for architecture/runtime-dependencies.json. Split out of runtime-graph.ts,\n * which owns the DERIVATION (and had grown past the file-size limit), so the committed data shape\n * can be read on its own — it is what every consumer of the file, and any future Terraform\n * cross-check, actually programs against.\n *\n * Interfaces rather than classes: these are parsed straight out of JSON with `JSON.parse`, so a\n * class would only ever be a shape assertion over a plain object, never a constructed instance.\n */\n\nimport type {
|
|
1
|
+
{"version":3,"file":"runtime-graph-model.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-graph-model.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG","sourcesContent":["/**\n * Runtime Graph model\n *\n * The serialization DTOs for architecture/runtime-dependencies.json. Split out of runtime-graph.ts,\n * which owns the DERIVATION (and had grown past the file-size limit), so the committed data shape\n * can be read on its own — it is what every consumer of the file, and any future Terraform\n * cross-check, actually programs against.\n *\n * Interfaces rather than classes: these are parsed straight out of JSON with `JSON.parse`, so a\n * class would only ever be a shape assertion over a plain object, never a constructed instance.\n */\n\nimport type {\n ApiTransport,\n ExternalSystemDeclaration,\n ExternalSystemKind,\n} from './api-usage/api-relations';\n\nexport interface RuntimeService {\n level: number;\n /**\n * The project's declared nx role — 'server' or 'client' (a library is never a node). Carried so\n * the viz can LABEL the node with what it actually is: it used to infer the role from\n * `implements.length > 0`, which labelled every relation-less server \"client\". Optional so a\n * runtime-dependencies.json written before this field existed still parses and still renders\n * exactly as it did (the visualizer falls back to the old inference).\n */\n role?: string;\n /**\n * The name clients address this service by (`new ClientConfig('helper-fsdb')`), declared in its\n * project.json. Absent for a service nothing calls by name (e.g. a browser app).\n */\n serviceName?: string;\n /**\n * The service(s) this node's clients call when the call site carries no literal `ClientConfig`,\n * declared in its project.json (metadata.webpieces.callsService). A single name, or an\n * `{ apiClassName: serviceName }` map. Absent when the node declares no target. Mirrors\n * GraphEntry.callsService; it is the CALLING-side counterpart of `serviceName`.\n */\n callsService?: string | Record<string, string>;\n implements: string[];\n /**\n * apiClassName -> the LIBRARY project whose apiRelations declared that implements, for the apis\n * this service serves through an embedded library rather than its own source (e.g. a shared\n * route-registration lib). Answers \"who implements WarmupApi, and where did that come from?\",\n * which previously required walking the dependsOn closure by hand.\n */\n implementsVia?: Record<string, string>;\n uses: string[];\n dependsOn: string[];\n /**\n * When false, this service is hidden from the rendered runtime graph (its\n * node AND every edge touching it are omitted from the HTML/DOT). It stays\n * in runtime-dependencies.json so the data view is complete. Absent means\n * drawn (the default). Mirrors GraphEntry.drawOnGraph from the `drawOnGraph:`\n * nx tag.\n */\n drawOnGraph?: boolean;\n}\n\nexport interface RuntimeApi {\n implementedBy: string[];\n usedBy: string[];\n /** Transport of this API — 'rpc' (direct call) or 'pubsub' (delivered through a queue). */\n type?: ApiTransport;\n /**\n * The api-lib project that OWNS this contract. For a contract nothing in-repo implements, this\n * is the external library the calls leave the repo through (`lib-firestore`, `lib-gmail`), which\n * is what the runtime viz labels its terminal external nodes with.\n */\n owner?: string;\n /**\n * Set when the contract carries an `@externalSystem <kind> [label]` JSDoc tag — the vendor seam\n * declaring WHAT it is a seam to, so the viz can draw firestore as a database rather than as the\n * same grey box as every other external. Absent on everything else.\n */\n externalSystem?: ExternalSystemDeclaration;\n}\n\nexport interface RuntimeEdge {\n from: string;\n to: string;\n via: string[];\n /**\n * Transport of this edge. 'rpc' → a direct call arrow. 'pubsub' → the producer enqueues and the\n * consumer is delivered later, so the runtime viz draws it as producer → QUEUE → consumer.\n * Edges are split by transport, so every edge is a single kind.\n */\n type?: ApiTransport;\n /**\n * `\"ApiClassName.methodName\"` — the queue this edge flows through. Present iff `type` is\n * 'pubsub'. Queues are per METHOD, not per service pair, because that is the unit Cloud Tasks\n * (and Terraform) actually create, so two services exchanging three queued methods are three\n * queues rather than one arrow.\n */\n queue?: string;\n /**\n * True when the CALLING project carries a `cutLegacyCycle:<target>` nx tag naming this edge's\n * target — an admission that this hop closes a REAL cycle that is being tolerated as legacy\n * debt, not a claim that it is harmless. The edge is still drawn (dashed, labelled \"legacy\n * cycle\"); it is only excluded from leveling and cycle detection. Absent on every other edge.\n */\n cutLegacyCycle?: boolean;\n}\n\n/**\n * One Cloud Tasks queue: the async seam between a producer and a consumer, at METHOD granularity.\n *\n * `producedBy` and `consumedBy` are deliberately not symmetric in confidence — see\n * {@link ApiRef.methodsInferred}. The consumer is derived from `addRoutes` plus the contract's\n * method table and is exact; the producer is attributed to every queued method of the contract it\n * built a client for, because which methods it enqueues is not statically recoverable.\n */\nexport interface RuntimeQueue {\n api: string;\n method: string;\n /** `@Queue(...)` override, else `${Api}-${method}` — the name Terraform must match 1:1. */\n queueName: string;\n producedBy: string[];\n consumedBy: string[];\n}\n\n/**\n * An endpoint driven by something that is NOT an in-repo caller — a clock or an outside system.\n * These never appear as runtime EDGES (there is no in-repo `from`), which is exactly why they were\n * invisible until now: a nightly sweep and a GCP push subscription are real runtime entry points\n * with real Terraform behind them, and the graph showed neither.\n */\nexport interface RuntimeTrigger {\n /** 'cron' → a scheduler fires it; 'external' → a system outside this repo posts to it. */\n kind: 'cron' | 'external';\n api: string;\n method: string;\n /** The service that SERVES the endpoint (the arrow's head). */\n service: string;\n /** Present for 'cron': the Cloud Scheduler job / queue name Terraform must match. */\n queueName?: string;\n /**\n * Present for 'external': WHO outside this repo posts to it, declared as\n * `@Endpoint(POST, p, WRITE, EXTERNAL, { calledBy: 'twilio' })`.\n *\n * The SAME `(kind,label)` an OUTBOUND {@link RuntimeExternalSystem} carries, so an inbound\n * `saas twilio` and an outbound `saas twilio` share a node identity and converge on ONE box —\n * `label` is the identity here too, not display text.\n *\n * Optional only for graphs generated BEFORE the caller was required; generation now fails rather\n * than emitting an `external` method without one.\n */\n caller?: ExternalSystemDeclaration;\n}\n\nexport interface RuntimeUnresolved {\n service: string;\n api: string;\n}\n\n/**\n * A system outside this repo that a service TALKS TO, drawn with a shape that says what it is\n * (a database as a cylinder, a bucket as a folder) instead of the one grey box every external\n * used to collapse into.\n *\n * Two declaration sites feed this, because the two real cases differ in whether a contract exists:\n *\n * - **Wrapped** — the repo has a vendor seam (`FirestoreAdminApi`), so the kind is declared with an\n * `@externalSystem <kind> [label]` JSDoc tag on the contract. A decorator cannot go on a TS\n * `interface`, and these seams are interfaces, so JSDoc is the only marker that fits in place.\n * - **Unwrapped** — the service opens the connection itself with no contract to mark (a `pg.Pool`,\n * a TypeORM `DataSource`), so the declaration is an `external:<kind>:<identity>` nx tag on that\n * project's project.json.\n *\n * `label` is the node IDENTITY, not just display text: two services declaring `postgres` converge on\n * ONE node with two arrows into it, rather than drawing a database each.\n */\nexport interface RuntimeExternalSystem {\n kind: ExternalSystemKind;\n /** Display name AND node identity — declarations sharing a label share a node. */\n label: string;\n /** Services with a direct arrow to it. No transitive fan-out: a tag speaks only for its project. */\n usedBy: string[];\n /** Contracts flowing over it. Empty for the unwrapped (tag-declared) case — there are none. */\n apis: string[];\n}\n\nexport interface RuntimeGraph {\n services: Record<string, RuntimeService>;\n apis: Record<string, RuntimeApi>;\n runtimeEdges: RuntimeEdge[];\n unresolvedUses: RuntimeUnresolved[];\n /** `\"Api.method\"` -> the queue between its producers and its consumers. */\n queues: Record<string, RuntimeQueue>;\n /** Clock- and outside-driven entry points, sorted for determinism. */\n triggers: RuntimeTrigger[];\n /**\n * Declared external systems, keyed by identity. Optional so every graph written before this\n * existed still parses — an absent map means \"nothing declared\", which renders exactly as before.\n */\n externalSystems?: Record<string, RuntimeExternalSystem>;\n}\n"]}
|