@webpieces/nx-webpieces-rules 0.4.528 → 0.4.530
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 +29 -1
- package/src/lib/api-usage/api-ast.js +79 -1
- package/src/lib/api-usage/api-ast.js.map +1 -1
- package/src/lib/api-usage/api-contract-errors.d.ts +15 -1
- package/src/lib/api-usage/api-contract-errors.js +30 -1
- package/src/lib/api-usage/api-contract-errors.js.map +1 -1
- package/src/lib/api-usage/api-relations.d.ts +42 -0
- package/src/lib/api-usage/api-relations.js +31 -1
- package/src/lib/api-usage/api-relations.js.map +1 -1
- package/src/lib/api-usage/api-scanner.d.ts +8 -2
- package/src/lib/api-usage/api-scanner.js +8 -1
- package/src/lib/api-usage/api-scanner.js.map +1 -1
- package/src/lib/runtime-graph-model.d.ts +12 -0
- package/src/lib/runtime-graph-model.js.map +1 -1
- package/src/lib/runtime-graph-sorters.d.ts +26 -0
- package/src/lib/runtime-graph-sorters.js +66 -0
- package/src/lib/runtime-graph-sorters.js.map +1 -0
- package/src/lib/runtime-graph.js +12 -51
- package/src/lib/runtime-graph.js.map +1 -1
- package/src/lib/runtime-visualizer.client.js +22 -0
- package/src/lib/runtime-visualizer.d.ts +10 -4
- package/src/lib/runtime-visualizer.js +206 -174
- package/src/lib/runtime-visualizer.js.map +1 -1
- package/src/lib/runtime-viz-theme.d.ts +61 -0
- package/src/lib/runtime-viz-theme.js +137 -0
- package/src/lib/runtime-viz-theme.js.map +1 -0
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* only as binding identifiers, not as member names).
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.EmptiedApiContract = exports.UnresolvedEndpointPath = exports.NonLiteralDecoratorArg = exports.EXTERNAL_SYSTEM_KINDS = void 0;
|
|
18
|
+
exports.EmptiedApiContract = exports.UndeclaredExternalCaller = exports.UnresolvedEndpointPath = exports.NonLiteralDecoratorArg = exports.EXTERNAL_SYSTEM_KINDS = void 0;
|
|
19
19
|
exports.isExternalSystemKind = isExternalSystemKind;
|
|
20
20
|
exports.apiRefKey = apiRefKey;
|
|
21
21
|
exports.deriveApiRelationKind = deriveApiRelationKind;
|
|
@@ -106,6 +106,36 @@ class UnresolvedEndpointPath {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
exports.UnresolvedEndpointPath = UnresolvedEndpointPath;
|
|
109
|
+
/**
|
|
110
|
+
* ONE `external` `@Endpoint` whose CALLER could not be read from the source.
|
|
111
|
+
*
|
|
112
|
+
* Fatal for the same reason {@link UnresolvedEndpointPath} is. The inbound box exists to say who is
|
|
113
|
+
* calling us from outside; with no caller it can only restate our own contract name, which is the
|
|
114
|
+
* exact bug this diagnostic exists to make impossible to reintroduce. `@Endpoint`'s TS overloads
|
|
115
|
+
* already require `calledBy`, so anything reaching here is a JS caller, an `as any`, a cross-module
|
|
116
|
+
* constant the parser-only scan cannot fold, or an unknown `callerKind`.
|
|
117
|
+
*/
|
|
118
|
+
class UndeclaredExternalCaller {
|
|
119
|
+
api;
|
|
120
|
+
method;
|
|
121
|
+
argument;
|
|
122
|
+
at;
|
|
123
|
+
constructor(
|
|
124
|
+
/** The contract class the method is declared on. */
|
|
125
|
+
api,
|
|
126
|
+
/** The method name. */
|
|
127
|
+
method,
|
|
128
|
+
/** What was wrong, as written — `<missing>`, `SOME_CONST`, `callerKind: 'vendor'`. */
|
|
129
|
+
argument,
|
|
130
|
+
/** `path/to/file.ts:LINE`, workspace-relative. */
|
|
131
|
+
at) {
|
|
132
|
+
this.api = api;
|
|
133
|
+
this.method = method;
|
|
134
|
+
this.argument = argument;
|
|
135
|
+
this.at = at;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
exports.UndeclaredExternalCaller = UndeclaredExternalCaller;
|
|
109
139
|
/**
|
|
110
140
|
* A contract class that DECLARED `@Endpoint` methods and kept none of them.
|
|
111
141
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-relations.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-relations.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AA2BH,oDAEC;AAkED,8BAEC;AAuKD,sDAIC;AAOD,kCAKC;AA1QD;;;;;;GAMG;AACU,QAAA,qBAAqB,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAI1G,yEAAyE;AACzE,sHAAsH;AACtH,SAAgB,oBAAoB,CAAC,KAAa;IAC9C,OAAQ,6BAA2C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACxE,CAAC;AA6DD;;;GAGG;AACH,+FAA+F;AAC/F,SAAgB,SAAS,CAAC,GAAW;IACjC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;AACnD,CAAC;AAmGD;;;;;;;;GAQG;AACH,MAAa,sBAAsB;IAGX;IAEA;IAEA;IAEA;IAEA;IAVpB;IACI,sDAAsD;IACtC,GAAW;IAC3B,wCAAwC;IACxB,SAAiB;IACjC,0EAA0E;IAC1D,MAAqB;IACrC,iEAAiE;IACjD,QAAgB;IAChC,kDAAkD;IAClC,EAAU;QARV,QAAG,GAAH,GAAG,CAAQ;QAEX,cAAS,GAAT,SAAS,CAAQ;QAEjB,WAAM,GAAN,MAAM,CAAe;QAErB,aAAQ,GAAR,QAAQ,CAAQ;QAEhB,OAAE,GAAF,EAAE,CAAQ;IAC3B,CAAC;CACP;AAbD,wDAaC;AAED;;;;;;;;;GASG;AACH,MAAa,sBAAsB;IAGX;IAEA;IAEA;IAEA;IARpB;IACI,oDAAoD;IACpC,GAAW;IAC3B,uBAAuB;IACP,MAAc;IAC9B,iEAAiE;IACjD,QAAgB;IAChC,kDAAkD;IAClC,EAAU;QANV,QAAG,GAAH,GAAG,CAAQ;QAEX,WAAM,GAAN,MAAM,CAAQ;QAEd,aAAQ,GAAR,QAAQ,CAAQ;QAEhB,OAAE,GAAF,EAAE,CAAQ;IAC3B,CAAC;CACP;AAXD,wDAWC;AAED;;;;;;;GAOG;AACH,MAAa,kBAAkB;IAGP;IAEA;IAEA;IANpB;IACI,+BAA+B;IACf,GAAW;IAC3B,0DAA0D;IAC1C,QAAgB;IAChC,+DAA+D;IAC/C,EAAU;QAJV,QAAG,GAAH,GAAG,CAAQ;QAEX,aAAQ,GAAR,QAAQ,CAAQ;QAEhB,OAAE,GAAF,EAAE,CAAQ;IAC3B,CAAC;CACP;AATD,gDASC;AAED,oFAAoF;AACpF,+FAA+F;AAC/F,SAAgB,qBAAqB,CAAC,cAAwB,EAAE,QAAkB;IAC9E,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,iBAAiB,CAAC;IAC/E,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,YAAY,CAAC;IACnD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,+FAA+F;AAC/F,SAAgB,WAAW,CAAC,IAAc;IACtC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CACjB,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CACrB,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC,CACjG,CAAC;AACN,CAAC","sourcesContent":["/**\n * API Relations model\n *\n * The typed classification of a compile-time dependency edge P -> apiLib in\n * architecture/dependencies.json. Where the flat `dependsOn` only says \"P depends\n * on apiLib\", `apiRelations[apiLib]` says WHY: which API contracts P IMPLEMENTS\n * (serves, `class Ctrl extends XxxApi`) and which it USES (calls as a client,\n * `factory.createRpcClient(XxxApi, ...)` / `createPubSubClient(...)`), each tagged\n * with its transport.\n *\n * Interfaces + object literals here mirror the sibling runtime-graph.ts model —\n * these are serialization DTOs written verbatim into the committed JSON, and\n * `implements`/`uses` are legal interface property names (they are reserved words\n * only as binding identifiers, not as member names).\n */\n\n/**\n * Transport of an API contract:\n * - `rpc` — synchronous request/response over HTTP\n * - `pubsub` — fire-and-forget, delivered later through a Cloud Tasks queue\n * - `external` — a contract for a system OUTSIDE this repo (firestore, gmail, ...). Nothing in-repo\n * implements it, so it never becomes a service→service edge; it terminates the graph\n * at a dashed vendor node. Detected from `runtime-architecture.externalApiPaths`\n * rather than from a decorator, because a vendor contract is a plain interface bound\n * to a Symbol token, not an `abstract class` carrying @ApiPath.\n */\nexport type ApiTransport = 'rpc' | 'pubsub' | 'external';\n\n/**\n * The kinds an external system can be DECLARED as. Each draws its own shape in the runtime viz, so\n * a datastore stops looking like an HTTP service.\n *\n * Lives here rather than beside the runtime graph model because that model already imports from this\n * file; putting it there and importing back would close a module cycle.\n */\nexport const EXTERNAL_SYSTEM_KINDS = ['database', 'cache', 'queue', 'storage', 'saas', 'system'] as const;\n\nexport type ExternalSystemKind = (typeof EXTERNAL_SYSTEM_KINDS)[number];\n\n/** True for a string that names one of {@link EXTERNAL_SYSTEM_KINDS}. */\n// webpieces-disable no-function-outside-class -- type guard beside the type it guards, matching this file's DTO style\nexport function isExternalSystemKind(value: string): value is ExternalSystemKind {\n return (EXTERNAL_SYSTEM_KINDS as readonly string[]).includes(value);\n}\n\n/**\n * ONE declared external system, keyed in {@link ExternalSystemDecls} by its IDENTITY.\n *\n * Identity, not display text: two projects each tagged `external:database:postgres` name the same\n * `postgres` node and converge on it with one arrow apiece, instead of drawing a database each.\n *\n * The two arrays are the two declaration sites, and a system may legitimately have both — a repo can\n * wrap a datastore behind a contract in one service and open it directly in another.\n */\nexport interface ExternalSystemDecl {\n kind: ExternalSystemKind;\n label: string;\n /** Contracts declaring it with an `@externalSystem` JSDoc tag; every user of one gets an arrow. */\n apis: string[];\n /** Projects declaring it with an `external:<kind>:<identity>` nx tag; each gets its OWN arrow. */\n projects: string[];\n}\n\n/** identity -> its declaration. Serialized as the `externalSystems` key of dependencies.json. */\nexport type ExternalSystemDecls = Record<string, ExternalSystemDecl>;\n\n/**\n * How a project relates to ONE api-lib it depends on:\n * - `implements` — it serves the api (a controller extends it)\n * - `uses` — it calls the api (generates a client)\n * - `uses-implements` — it does BOTH (implements some of the api-lib's contracts,\n * uses others)\n */\nexport type ApiRelationKind = 'implements' | 'uses' | 'uses-implements';\n\n/** One API class a project implements or uses, with its transport. */\nexport interface ApiRef {\n api: string;\n type: ApiTransport;\n /**\n * ONLY on a `uses` ref: the service the call site aims at, read from the client config literal\n * (`createRpcClient(XxxApi, new ClientConfig('helper-fsdb'))` → `helper-fsdb`). It is matched\n * against a project's DECLARED `serviceName` to pick the ONE runtime edge target, instead of\n * fanning the edge out to every implementer of the api — which is catastrophically wrong for a\n * company-wide contract registered in a shared library and therefore implemented by every server.\n *\n * Absent when the config argument is not a `new <Xxx>ClientConfig('<literal>')` (a variable, a\n * computed name, ...). Absent means \"unknown target\", NOT \"no target\" — the runtime graph then\n * falls back to the old fan-out and says so out loud.\n */\n targetService?: string;\n /**\n * ONLY on a `pubsub` uses ref. True means \"this producer was attributed to EVERY cloudtasks\n * method of the contract, not to the methods it actually enqueues\".\n *\n * A producer builds one client for the whole contract (`createPubSubClient(EmailTaskApi, cfg)`)\n * and enqueues through a proxy (`emailTasks.send(req)`) somewhere else entirely — often after\n * the client has been stored in a DI binding — so WHICH methods it enqueues is not statically\n * recoverable. The consumer side IS exact (addRoutes + the contract's method table). Recording\n * the difference keeps a producer-side queue from being read as proof that queue is used.\n */\n methodsInferred?: boolean;\n}\n\n/**\n * Identity of a ref for de-duplication: an api used twice against DIFFERENT services is two distinct\n * relations (two distinct runtime edges), so the api name alone is not the key.\n */\n// webpieces-disable no-function-outside-class -- pure data helper for these serialization DTOs\nexport function apiRefKey(ref: ApiRef): string {\n return `${ref.api} ${ref.targetService ?? ''}`;\n}\n\n/**\n * A project's relationship to ONE api-lib it depends on. Serialized verbatim into\n * architecture/dependencies.json under `apiRelations[apiLibProjectName]`.\n */\nexport interface ApiRelation {\n kind: ApiRelationKind;\n implements: ApiRef[];\n uses: ApiRef[];\n}\n\n/** apiLibProjectName -> relation. Attached to a GraphEntry as `apiRelations`. */\nexport type ProjectApiRelations = Record<string, ApiRelation>;\n\n/**\n * What triggers ONE endpoint, mirroring core-util's `EndpointKind`. Duplicated as a string union\n * rather than imported: nx-webpieces-rules is build tooling and must not take a runtime dependency\n * on the framework it inspects (it reads decorators as TEXT, from projects that may be on a\n * different @webpieces version than the tooling itself).\n */\nexport type EndpointKind = 'rpc' | 'cloudtasks' | 'cron' | 'external';\n\n/**\n * One method on an API contract, as written in source: what triggers it, where it is mounted, and\n * (for a queued method) which Cloud Tasks queue delivers it.\n */\nexport interface ApiMethodMeta {\n name: string;\n /** The @Endpoint path, relative to the class's @ApiPath basePath. */\n path: string;\n kind: EndpointKind;\n /**\n * `@Queue(...)` override, else `${ApiClassName}-${methodName}`.\n *\n * ONLY on a `cloudtasks` or `cron` method — those are the kinds actually delivered through a\n * named queue or schedule, and Terraform matches on this string. A synchronous `rpc` (or an\n * inbound `external`) endpoint has no queue and needs none; emitting a plausible-looking name for\n * one put every synchronous endpoint one naive `methods.map(m => m.queueName)` away from being\n * provisioned as a queue.\n */\n queueName?: string;\n}\n\n/**\n * A discovered API contract class: its name, the api-lib project that owns it, its transport, and\n * its per-method trigger table.\n */\nexport interface ApiClassInfo {\n api: string;\n owner: string;\n type: ApiTransport;\n /** The class's @ApiPath basePath; absent for an external (vendor) contract, which has no route. */\n basePath?: string;\n /**\n * Every @Endpoint method, in declaration order. Empty for an external contract (a vendor\n * interface has no endpoints — it is called through a vendor SDK, not mounted).\n */\n methods: ApiMethodMeta[];\n /**\n * Set when the contract carries an `@externalSystem <kind> [label]` JSDoc tag — a vendor seam\n * declaring WHAT it is a seam to. JSDoc rather than a decorator because these seams are plain TS\n * `interface`s, which cannot carry one.\n */\n externalSystem?: ExternalSystemDeclaration;\n}\n\n/** The `(kind, label)` pair a single declaration resolves to. */\nexport interface ExternalSystemDeclaration {\n kind: ExternalSystemKind;\n label: string;\n}\n\n/**\n * The committed, per-contract view written to `architecture/dependencies.json` under `apiContracts`.\n *\n * The runtime graph is derived SOLELY from dependencies.json so generate and validate can never\n * diverge — which means anything the runtime graph needs must be COMMITTED there, not re-scanned.\n * Per-method trigger kinds and queue names are exactly that: without this table the derivation\n * cannot tell a queued endpoint from a cron sweep, and cannot name the queue between two services.\n */\nexport interface ApiContract {\n owner: string;\n /** 'rpc' | 'pubsub' for an in-repo contract, 'external' for a vendor seam. */\n apiKind: ApiTransport;\n /**\n * REQUIRED. Every routed contract carries `@ApiPath`, so every entry in this table must carry the\n * base path its methods hang off. Optional was worse than absent: a consumer joining\n * `basePath + path` for the ONE entry that lost it computed `/test` where the real route was\n * `/whatsapp/test`, and had no reason to suspect it — every other entry had the field. Generation\n * now FAILS instead of shipping an entry that computes a confidently wrong URL.\n */\n basePath: string;\n methods: ApiMethodMeta[];\n}\n\n/** apiClassName -> its committed contract. Serialized as the `apiContracts` key. */\nexport type ApiContracts = Record<string, ApiContract>;\n\n/**\n * ONE decorator argument the scan saw but could not reduce to a string — `@ApiPath(SOME_CONST)`\n * where SOME_CONST is imported from another module, a computed expression, an enum member, ...\n *\n * Recorded rather than dropped. Before this existed, an unresolvable argument cost the contract its\n * basePath, or a method, or (when EVERY method's path was one) the whole class — with nothing\n * printed anywhere. Same-module constants now resolve, so what remains here is the genuinely\n * unresolvable, which the author can fix by inlining the literal or moving the constant in-module.\n */\nexport class NonLiteralDecoratorArg {\n constructor(\n /** The contract class the argument was written on. */\n public readonly api: string,\n /** `ApiPath` | `Endpoint` | `Queue`. */\n public readonly decorator: string,\n /** The method name for a member decorator, null for a class decorator. */\n public readonly method: string | null,\n /** The argument exactly as written, e.g. `WHATSAPP_API_PATH`. */\n public readonly argument: string,\n /** `path/to/file.ts:LINE`, workspace-relative. */\n public readonly at: string,\n ) {}\n}\n\n/**\n * ONE `@Endpoint(path, kind)` whose PATH argument was present but could not be reduced to a string.\n *\n * Split out of NonLiteralDecoratorArg (which stays a warning, covering @Queue and the rest) because\n * this one is FATAL. Upstream components need the URL: an http client builds its request as\n * `basePath + path`, so an unreadable path is missing ROUTING, not missing metadata — the same\n * reasoning that already makes basePath required. Skipping the method instead used to delete it, and\n * a class whose every path was a constant lost every method and vanished from `apiContracts` with\n * nothing printed anywhere.\n */\nexport class UnresolvedEndpointPath {\n constructor(\n /** The contract class the method is declared on. */\n public readonly api: string,\n /** The method name. */\n public readonly method: string,\n /** The path argument exactly as written, e.g. `PROCESS_PATH`. */\n public readonly argument: string,\n /** `path/to/file.ts:LINE`, workspace-relative. */\n public readonly at: string,\n ) {}\n}\n\n/**\n * A contract class that DECLARED `@Endpoint` methods and kept none of them.\n *\n * The backstop for the mechanism that hid an entire service: `buildApiContracts` skips a zero-method\n * class (correctly — a vendor seam has no routes), so a class gutted by unreadable decorator\n * arguments left through the same door as a legitimately routeless one. A class that declared\n * endpoints and produced none is never legitimate, so it is named instead.\n */\nexport class EmptiedApiContract {\n constructor(\n /** The contract class name. */\n public readonly api: string,\n /** How many `@Endpoint` decorators were written on it. */\n public readonly declared: number,\n /** `path/to/file.ts:LINE` of the class, workspace-relative. */\n public readonly at: string,\n ) {}\n}\n\n/** Derive the relation kind from the (possibly empty) implements/uses ref lists. */\n// webpieces-disable no-function-outside-class -- pure data helper for these serialization DTOs\nexport function deriveApiRelationKind(implementsRefs: ApiRef[], usesRefs: ApiRef[]): ApiRelationKind {\n if (implementsRefs.length > 0 && usesRefs.length > 0) return 'uses-implements';\n if (implementsRefs.length > 0) return 'implements';\n return 'uses';\n}\n\n/**\n * Stable-sort a ref list by api name, then by target service, so the committed JSON is\n * deterministic even when one api is used against two different services.\n */\n// webpieces-disable no-function-outside-class -- pure data helper for these serialization DTOs\nexport function sortApiRefs(refs: ApiRef[]): ApiRef[] {\n return [...refs].sort(\n (a: ApiRef, b: ApiRef) =>\n a.api.localeCompare(b.api) || (a.targetService ?? '').localeCompare(b.targetService ?? ''),\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"api-relations.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-relations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * API Relations model\n *\n * The typed classification of a compile-time dependency edge P -> apiLib in\n * architecture/dependencies.json. Where the flat `dependsOn` only says \"P depends\n * on apiLib\", `apiRelations[apiLib]` says WHY: which API contracts P IMPLEMENTS\n * (serves, `class Ctrl extends XxxApi`) and which it USES (calls as a client,\n * `factory.createRpcClient(XxxApi, ...)` / `createPubSubClient(...)`), each tagged\n * with its transport.\n *\n * Interfaces + object literals here mirror the sibling runtime-graph.ts model —\n * these are serialization DTOs written verbatim into the committed JSON, and\n * `implements`/`uses` are legal interface property names (they are reserved words\n * only as binding identifiers, not as member names).\n */\n\n/**\n * Transport of an API contract:\n * - `rpc` — synchronous request/response over HTTP\n * - `pubsub` — fire-and-forget, delivered later through a Cloud Tasks queue\n * - `external` — a contract for a system OUTSIDE this repo (firestore, gmail, ...). Nothing in-repo\n * implements it, so it never becomes a service→service edge; it terminates the graph\n * at a dashed vendor node. Detected from `runtime-architecture.externalApiPaths`\n * rather than from a decorator, because a vendor contract is a plain interface bound\n * to a Symbol token, not an `abstract class` carrying @ApiPath.\n */\nexport type ApiTransport = 'rpc' | 'pubsub' | 'external';\n\n/**\n * The kinds an external system can be DECLARED as. Each draws its own shape in the runtime viz, so\n * a datastore stops looking like an HTTP service.\n *\n * Lives here rather than beside the runtime graph model because that model already imports from this\n * file; putting it there and importing back would close a module cycle.\n */\nexport const EXTERNAL_SYSTEM_KINDS = ['database', 'cache', 'queue', 'storage', 'saas', 'system'] as const;\n\nexport type ExternalSystemKind = (typeof EXTERNAL_SYSTEM_KINDS)[number];\n\n/** True for a string that names one of {@link EXTERNAL_SYSTEM_KINDS}. */\n// webpieces-disable no-function-outside-class -- type guard beside the type it guards, matching this file's DTO style\nexport function isExternalSystemKind(value: string): value is ExternalSystemKind {\n return (EXTERNAL_SYSTEM_KINDS as readonly string[]).includes(value);\n}\n\n/**\n * ONE declared external system, keyed in {@link ExternalSystemDecls} by its IDENTITY.\n *\n * Identity, not display text: two projects each tagged `external:database:postgres` name the same\n * `postgres` node and converge on it with one arrow apiece, instead of drawing a database each.\n *\n * The two arrays are the two declaration sites, and a system may legitimately have both — a repo can\n * wrap a datastore behind a contract in one service and open it directly in another.\n */\nexport interface ExternalSystemDecl {\n kind: ExternalSystemKind;\n label: string;\n /** Contracts declaring it with an `@externalSystem` JSDoc tag; every user of one gets an arrow. */\n apis: string[];\n /** Projects declaring it with an `external:<kind>:<identity>` nx tag; each gets its OWN arrow. */\n projects: string[];\n}\n\n/** identity -> its declaration. Serialized as the `externalSystems` key of dependencies.json. */\nexport type ExternalSystemDecls = Record<string, ExternalSystemDecl>;\n\n/**\n * How a project relates to ONE api-lib it depends on:\n * - `implements` — it serves the api (a controller extends it)\n * - `uses` — it calls the api (generates a client)\n * - `uses-implements` — it does BOTH (implements some of the api-lib's contracts,\n * uses others)\n */\nexport type ApiRelationKind = 'implements' | 'uses' | 'uses-implements';\n\n/** One API class a project implements or uses, with its transport. */\nexport interface ApiRef {\n api: string;\n type: ApiTransport;\n /**\n * ONLY on a `uses` ref: the service the call site aims at, read from the client config literal\n * (`createRpcClient(XxxApi, new ClientConfig('helper-fsdb'))` → `helper-fsdb`). It is matched\n * against a project's DECLARED `serviceName` to pick the ONE runtime edge target, instead of\n * fanning the edge out to every implementer of the api — which is catastrophically wrong for a\n * company-wide contract registered in a shared library and therefore implemented by every server.\n *\n * Absent when the config argument is not a `new <Xxx>ClientConfig('<literal>')` (a variable, a\n * computed name, ...). Absent means \"unknown target\", NOT \"no target\" — the runtime graph then\n * falls back to the old fan-out and says so out loud.\n */\n targetService?: string;\n /**\n * ONLY on a `pubsub` uses ref. True means \"this producer was attributed to EVERY cloudtasks\n * method of the contract, not to the methods it actually enqueues\".\n *\n * A producer builds one client for the whole contract (`createPubSubClient(EmailTaskApi, cfg)`)\n * and enqueues through a proxy (`emailTasks.send(req)`) somewhere else entirely — often after\n * the client has been stored in a DI binding — so WHICH methods it enqueues is not statically\n * recoverable. The consumer side IS exact (addRoutes + the contract's method table). Recording\n * the difference keeps a producer-side queue from being read as proof that queue is used.\n */\n methodsInferred?: boolean;\n}\n\n/**\n * Identity of a ref for de-duplication: an api used twice against DIFFERENT services is two distinct\n * relations (two distinct runtime edges), so the api name alone is not the key.\n */\n// webpieces-disable no-function-outside-class -- pure data helper for these serialization DTOs\nexport function apiRefKey(ref: ApiRef): string {\n return `${ref.api} ${ref.targetService ?? ''}`;\n}\n\n/**\n * A project's relationship to ONE api-lib it depends on. Serialized verbatim into\n * architecture/dependencies.json under `apiRelations[apiLibProjectName]`.\n */\nexport interface ApiRelation {\n kind: ApiRelationKind;\n implements: ApiRef[];\n uses: ApiRef[];\n}\n\n/** apiLibProjectName -> relation. Attached to a GraphEntry as `apiRelations`. */\nexport type ProjectApiRelations = Record<string, ApiRelation>;\n\n/**\n * What triggers ONE endpoint, mirroring core-util's `EndpointKind`. Duplicated as a string union\n * rather than imported: nx-webpieces-rules is build tooling and must not take a runtime dependency\n * on the framework it inspects (it reads decorators as TEXT, from projects that may be on a\n * different @webpieces version than the tooling itself).\n */\nexport type EndpointKind = 'rpc' | 'cloudtasks' | 'cron' | 'external';\n\n/**\n * One method on an API contract, as written in source: what triggers it, where it is mounted, and\n * (for a queued method) which Cloud Tasks queue delivers it.\n */\nexport interface ApiMethodMeta {\n name: string;\n /** The @Endpoint path, relative to the class's @ApiPath basePath. */\n path: string;\n kind: EndpointKind;\n /**\n * `@Queue(...)` override, else `${ApiClassName}-${methodName}`.\n *\n * ONLY on a `cloudtasks` or `cron` method — those are the kinds actually delivered through a\n * named queue or schedule, and Terraform matches on this string. A synchronous `rpc` (or an\n * inbound `external`) endpoint has no queue and needs none; emitting a plausible-looking name for\n * one put every synchronous endpoint one naive `methods.map(m => m.queueName)` away from being\n * provisioned as a queue.\n */\n queueName?: string;\n /**\n * WHO outside this repo drives this endpoint, from `@Endpoint(p, 'external', { calledBy })`.\n *\n * ONLY on an `external` method, the same way `queueName` is only on the kinds that HAVE a queue.\n *\n * Deliberately the SAME {@link ExternalSystemDeclaration} the OUTBOUND `@externalSystem` tag\n * resolves to, not a parallel inbound-only type: an inbound `saas twilio` and an outbound\n * `saas twilio` are the same vendor, so sharing the type makes them share an IDENTITY and\n * converge on ONE node instead of drawing twilio twice facing opposite directions.\n *\n * Optional in the TYPE only for graphs generated before the caller was required — generation\n * FAILS on an `external` method whose caller cannot be read (UndeclaredExternalCallerError).\n */\n caller?: ExternalSystemDeclaration;\n}\n\n/**\n * A discovered API contract class: its name, the api-lib project that owns it, its transport, and\n * its per-method trigger table.\n */\nexport interface ApiClassInfo {\n api: string;\n owner: string;\n type: ApiTransport;\n /** The class's @ApiPath basePath; absent for an external (vendor) contract, which has no route. */\n basePath?: string;\n /**\n * Every @Endpoint method, in declaration order. Empty for an external contract (a vendor\n * interface has no endpoints — it is called through a vendor SDK, not mounted).\n */\n methods: ApiMethodMeta[];\n /**\n * Set when the contract carries an `@externalSystem <kind> [label]` JSDoc tag — a vendor seam\n * declaring WHAT it is a seam to. JSDoc rather than a decorator because these seams are plain TS\n * `interface`s, which cannot carry one.\n */\n externalSystem?: ExternalSystemDeclaration;\n}\n\n/** The `(kind, label)` pair a single declaration resolves to. */\nexport interface ExternalSystemDeclaration {\n kind: ExternalSystemKind;\n label: string;\n}\n\n/**\n * The committed, per-contract view written to `architecture/dependencies.json` under `apiContracts`.\n *\n * The runtime graph is derived SOLELY from dependencies.json so generate and validate can never\n * diverge — which means anything the runtime graph needs must be COMMITTED there, not re-scanned.\n * Per-method trigger kinds and queue names are exactly that: without this table the derivation\n * cannot tell a queued endpoint from a cron sweep, and cannot name the queue between two services.\n */\nexport interface ApiContract {\n owner: string;\n /** 'rpc' | 'pubsub' for an in-repo contract, 'external' for a vendor seam. */\n apiKind: ApiTransport;\n /**\n * REQUIRED. Every routed contract carries `@ApiPath`, so every entry in this table must carry the\n * base path its methods hang off. Optional was worse than absent: a consumer joining\n * `basePath + path` for the ONE entry that lost it computed `/test` where the real route was\n * `/whatsapp/test`, and had no reason to suspect it — every other entry had the field. Generation\n * now FAILS instead of shipping an entry that computes a confidently wrong URL.\n */\n basePath: string;\n methods: ApiMethodMeta[];\n}\n\n/** apiClassName -> its committed contract. Serialized as the `apiContracts` key. */\nexport type ApiContracts = Record<string, ApiContract>;\n\n/**\n * ONE decorator argument the scan saw but could not reduce to a string — `@ApiPath(SOME_CONST)`\n * where SOME_CONST is imported from another module, a computed expression, an enum member, ...\n *\n * Recorded rather than dropped. Before this existed, an unresolvable argument cost the contract its\n * basePath, or a method, or (when EVERY method's path was one) the whole class — with nothing\n * printed anywhere. Same-module constants now resolve, so what remains here is the genuinely\n * unresolvable, which the author can fix by inlining the literal or moving the constant in-module.\n */\nexport class NonLiteralDecoratorArg {\n constructor(\n /** The contract class the argument was written on. */\n public readonly api: string,\n /** `ApiPath` | `Endpoint` | `Queue`. */\n public readonly decorator: string,\n /** The method name for a member decorator, null for a class decorator. */\n public readonly method: string | null,\n /** The argument exactly as written, e.g. `WHATSAPP_API_PATH`. */\n public readonly argument: string,\n /** `path/to/file.ts:LINE`, workspace-relative. */\n public readonly at: string,\n ) {}\n}\n\n/**\n * ONE `@Endpoint(path, kind)` whose PATH argument was present but could not be reduced to a string.\n *\n * Split out of NonLiteralDecoratorArg (which stays a warning, covering @Queue and the rest) because\n * this one is FATAL. Upstream components need the URL: an http client builds its request as\n * `basePath + path`, so an unreadable path is missing ROUTING, not missing metadata — the same\n * reasoning that already makes basePath required. Skipping the method instead used to delete it, and\n * a class whose every path was a constant lost every method and vanished from `apiContracts` with\n * nothing printed anywhere.\n */\nexport class UnresolvedEndpointPath {\n constructor(\n /** The contract class the method is declared on. */\n public readonly api: string,\n /** The method name. */\n public readonly method: string,\n /** The path argument exactly as written, e.g. `PROCESS_PATH`. */\n public readonly argument: string,\n /** `path/to/file.ts:LINE`, workspace-relative. */\n public readonly at: string,\n ) {}\n}\n\n/**\n * ONE `external` `@Endpoint` whose CALLER could not be read from the source.\n *\n * Fatal for the same reason {@link UnresolvedEndpointPath} is. The inbound box exists to say who is\n * calling us from outside; with no caller it can only restate our own contract name, which is the\n * exact bug this diagnostic exists to make impossible to reintroduce. `@Endpoint`'s TS overloads\n * already require `calledBy`, so anything reaching here is a JS caller, an `as any`, a cross-module\n * constant the parser-only scan cannot fold, or an unknown `callerKind`.\n */\nexport class UndeclaredExternalCaller {\n constructor(\n /** The contract class the method is declared on. */\n public readonly api: string,\n /** The method name. */\n public readonly method: string,\n /** What was wrong, as written — `<missing>`, `SOME_CONST`, `callerKind: 'vendor'`. */\n public readonly argument: string,\n /** `path/to/file.ts:LINE`, workspace-relative. */\n public readonly at: string,\n ) {}\n}\n\n/**\n * A contract class that DECLARED `@Endpoint` methods and kept none of them.\n *\n * The backstop for the mechanism that hid an entire service: `buildApiContracts` skips a zero-method\n * class (correctly — a vendor seam has no routes), so a class gutted by unreadable decorator\n * arguments left through the same door as a legitimately routeless one. A class that declared\n * endpoints and produced none is never legitimate, so it is named instead.\n */\nexport class EmptiedApiContract {\n constructor(\n /** The contract class name. */\n public readonly api: string,\n /** How many `@Endpoint` decorators were written on it. */\n public readonly declared: number,\n /** `path/to/file.ts:LINE` of the class, workspace-relative. */\n public readonly at: string,\n ) {}\n}\n\n/** Derive the relation kind from the (possibly empty) implements/uses ref lists. */\n// webpieces-disable no-function-outside-class -- pure data helper for these serialization DTOs\nexport function deriveApiRelationKind(implementsRefs: ApiRef[], usesRefs: ApiRef[]): ApiRelationKind {\n if (implementsRefs.length > 0 && usesRefs.length > 0) return 'uses-implements';\n if (implementsRefs.length > 0) return 'implements';\n return 'uses';\n}\n\n/**\n * Stable-sort a ref list by api name, then by target service, so the committed JSON is\n * deterministic even when one api is used against two different services.\n */\n// webpieces-disable no-function-outside-class -- pure data helper for these serialization DTOs\nexport function sortApiRefs(refs: ApiRef[]): ApiRef[] {\n return [...refs].sort(\n (a: ApiRef, b: ApiRef) =>\n a.api.localeCompare(b.api) || (a.targetService ?? '').localeCompare(b.targetService ?? ''),\n );\n}\n"]}
|
|
@@ -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, UnresolvedEndpointPath } from './api-relations';
|
|
32
|
+
import { ApiClassInfo, ApiContracts, EmptiedApiContract, NonLiteralDecoratorArg, ProjectApiRelations, UndeclaredExternalCaller, 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
|
|
@@ -91,6 +91,11 @@ export interface ApiScanResult {
|
|
|
91
91
|
* slip out through buildApiContracts' zero-method skip, taking a whole service's queues with it.
|
|
92
92
|
*/
|
|
93
93
|
emptiedApiContracts: EmptiedApiContract[];
|
|
94
|
+
/**
|
|
95
|
+
* `external` endpoints that did not say WHO calls them. Fatal: the inbound box on the runtime
|
|
96
|
+
* graph exists to name that system, and with nothing to name it restates our own contract name.
|
|
97
|
+
*/
|
|
98
|
+
undeclaredExternalCallers: UndeclaredExternalCaller[];
|
|
94
99
|
}
|
|
95
100
|
/** Statically scans every project for its api-lib implements/uses relationships. */
|
|
96
101
|
export declare class ApiUsageScanner {
|
|
@@ -162,7 +167,8 @@ export declare function scanAndAttachApiRelations(workspaceRoot: string, graph:
|
|
|
162
167
|
* would be an empty shell, and its identity is already carried by the `external` refs in
|
|
163
168
|
* apiRelations. Sorted by api name, methods left in declaration order, so the file is deterministic.
|
|
164
169
|
*
|
|
165
|
-
* THROWS on the
|
|
170
|
+
* THROWS on the four ways an entry can be wrong-but-green, checked root cause first (the fourth is
|
|
171
|
+
* an `external` method that never said WHO calls it — UndeclaredExternalCallerError):
|
|
166
172
|
* 1. an `@Endpoint` path the scan could not read (UnresolvedEndpointPathError) — the other half of
|
|
167
173
|
* the URL a consumer computes, and the cause of most emptied contracts;
|
|
168
174
|
* 2. a class that declared endpoints and kept none (EmptiedApiContractError), which would otherwise
|
|
@@ -260,6 +260,7 @@ class ApiUsageScanner {
|
|
|
260
260
|
nonLiteralDecoratorArgs: this.decoratorArgDiagnostics.all(),
|
|
261
261
|
unresolvedEndpointPaths: this.decoratorArgDiagnostics.unresolvedEndpointPaths(),
|
|
262
262
|
emptiedApiContracts: this.decoratorArgDiagnostics.emptiedContracts(),
|
|
263
|
+
undeclaredExternalCallers: this.decoratorArgDiagnostics.undeclaredExternalCallers(),
|
|
263
264
|
};
|
|
264
265
|
}
|
|
265
266
|
scanProject(info) {
|
|
@@ -419,7 +420,8 @@ function scanAndAttachApiRelations(workspaceRoot, graph, projectInfos, externalA
|
|
|
419
420
|
* would be an empty shell, and its identity is already carried by the `external` refs in
|
|
420
421
|
* apiRelations. Sorted by api name, methods left in declaration order, so the file is deterministic.
|
|
421
422
|
*
|
|
422
|
-
* THROWS on the
|
|
423
|
+
* THROWS on the four ways an entry can be wrong-but-green, checked root cause first (the fourth is
|
|
424
|
+
* an `external` method that never said WHO calls it — UndeclaredExternalCallerError):
|
|
423
425
|
* 1. an `@Endpoint` path the scan could not read (UnresolvedEndpointPathError) — the other half of
|
|
424
426
|
* the URL a consumer computes, and the cause of most emptied contracts;
|
|
425
427
|
* 2. a class that declared endpoints and kept none (EmptiedApiContractError), which would otherwise
|
|
@@ -437,6 +439,11 @@ function buildApiContracts(scan) {
|
|
|
437
439
|
throw new api_contract_errors_1.UnresolvedEndpointPathError(scan.unresolvedEndpointPaths);
|
|
438
440
|
if (scan.emptiedApiContracts.length > 0)
|
|
439
441
|
throw new api_contract_errors_1.EmptiedApiContractError(scan.emptiedApiContracts);
|
|
442
|
+
// After the two above: an unreadable path is what empties a contract, and a contract that lost
|
|
443
|
+
// every method has no external endpoint left to complain about.
|
|
444
|
+
if (scan.undeclaredExternalCallers.length > 0) {
|
|
445
|
+
throw new api_contract_errors_1.UndeclaredExternalCallerError(scan.undeclaredExternalCallers);
|
|
446
|
+
}
|
|
440
447
|
const contracts = {};
|
|
441
448
|
const missing = [];
|
|
442
449
|
for (const api of [...scan.apiIndex.keys()].sort()) {
|
|
@@ -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 UnresolvedEndpointPath,\n apiRefKey,\n deriveApiRelationKind,\n sortApiRefs,\n} from './api-relations';\nimport {\n EmptiedApiContractError,\n MissingBasePathError,\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\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 };\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 `apiContracts` table for architecture/dependencies.json, from a 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 three 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 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 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 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 `apiContracts` table for architecture/dependencies.json, from a 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"]}
|
|
@@ -118,6 +118,18 @@ export interface RuntimeTrigger {
|
|
|
118
118
|
service: string;
|
|
119
119
|
/** Present for 'cron': the Cloud Scheduler job / queue name Terraform must match. */
|
|
120
120
|
queueName?: string;
|
|
121
|
+
/**
|
|
122
|
+
* Present for 'external': WHO outside this repo posts to it, declared as
|
|
123
|
+
* `@Endpoint(p, 'external', { calledBy: 'twilio' })`.
|
|
124
|
+
*
|
|
125
|
+
* The SAME `(kind,label)` an OUTBOUND {@link RuntimeExternalSystem} carries, so an inbound
|
|
126
|
+
* `saas twilio` and an outbound `saas twilio` share a node identity and converge on ONE box —
|
|
127
|
+
* `label` is the identity here too, not display text.
|
|
128
|
+
*
|
|
129
|
+
* Optional only for graphs generated BEFORE the caller was required; generation now fails rather
|
|
130
|
+
* than emitting an `external` method without one.
|
|
131
|
+
*/
|
|
132
|
+
caller?: ExternalSystemDeclaration;
|
|
121
133
|
}
|
|
122
134
|
export interface RuntimeUnresolved {
|
|
123
135
|
service: string;
|
|
@@ -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 { ApiTransport, ExternalSystemDeclaration, ExternalSystemKind } 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\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\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"]}
|
|
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 { ApiTransport, ExternalSystemDeclaration, ExternalSystemKind } 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\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(p, '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"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determinism helpers for the runtime graph.
|
|
3
|
+
*
|
|
4
|
+
* `architecture/runtime-dependencies.json` is COMMITTED and compared byte-for-byte by
|
|
5
|
+
* `validate-runtime-architecture`, so every collection that reaches it must come out in a stable
|
|
6
|
+
* order regardless of the order projects happened to be scanned in. These are the pure functions
|
|
7
|
+
* that guarantee that — sorting, and de-duplicating where one node can legitimately absorb the same
|
|
8
|
+
* relation twice.
|
|
9
|
+
*
|
|
10
|
+
* Split out of runtime-graph.ts, which owns the DERIVATION (what the graph means). These own only
|
|
11
|
+
* the SHAPE of the output (what order it is written in). They are leaf functions over the model
|
|
12
|
+
* types with no knowledge of the deriver, which is also why they can be imported without a cycle.
|
|
13
|
+
*/
|
|
14
|
+
import type { ApiRef } from './api-usage/api-relations';
|
|
15
|
+
import type { RuntimeQueue, RuntimeUnresolved } from './runtime-graph-model';
|
|
16
|
+
/** Drop duplicate api refs, keeping the first — needed after a node absorbs the same api from both
|
|
17
|
+
* its own relations and an embedded lib's. Keyed by api AND target service: the same contract aimed
|
|
18
|
+
* at two different services is two distinct relations (two distinct edges), not a duplicate. Input
|
|
19
|
+
* is pre-sorted, so output stays deterministic. */
|
|
20
|
+
export declare function dedupApiRefs(refs: ApiRef[]): ApiRef[];
|
|
21
|
+
/** Queues as a key-sorted object, with each producer/consumer list sorted, for a deterministic file. */
|
|
22
|
+
export declare function sortedQueues(queues: Map<string, RuntimeQueue>): Record<string, RuntimeQueue>;
|
|
23
|
+
/** Sort a Map into a plain object with sorted keys, so the committed JSON is deterministic. */
|
|
24
|
+
export declare function sortedRecord(map: Map<string, string>): Record<string, string>;
|
|
25
|
+
/** Sort AND de-duplicate: one api used against two targets must not be reported unresolved twice. */
|
|
26
|
+
export declare function sortUnresolved(unresolved: RuntimeUnresolved[]): RuntimeUnresolved[];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Determinism helpers for the runtime graph.
|
|
4
|
+
*
|
|
5
|
+
* `architecture/runtime-dependencies.json` is COMMITTED and compared byte-for-byte by
|
|
6
|
+
* `validate-runtime-architecture`, so every collection that reaches it must come out in a stable
|
|
7
|
+
* order regardless of the order projects happened to be scanned in. These are the pure functions
|
|
8
|
+
* that guarantee that — sorting, and de-duplicating where one node can legitimately absorb the same
|
|
9
|
+
* relation twice.
|
|
10
|
+
*
|
|
11
|
+
* Split out of runtime-graph.ts, which owns the DERIVATION (what the graph means). These own only
|
|
12
|
+
* the SHAPE of the output (what order it is written in). They are leaf functions over the model
|
|
13
|
+
* types with no knowledge of the deriver, which is also why they can be imported without a cycle.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.dedupApiRefs = dedupApiRefs;
|
|
17
|
+
exports.sortedQueues = sortedQueues;
|
|
18
|
+
exports.sortedRecord = sortedRecord;
|
|
19
|
+
exports.sortUnresolved = sortUnresolved;
|
|
20
|
+
const api_relations_1 = require("./api-usage/api-relations");
|
|
21
|
+
/** Drop duplicate api refs, keeping the first — needed after a node absorbs the same api from both
|
|
22
|
+
* its own relations and an embedded lib's. Keyed by api AND target service: the same contract aimed
|
|
23
|
+
* at two different services is two distinct relations (two distinct edges), not a duplicate. Input
|
|
24
|
+
* is pre-sorted, so output stays deterministic. */
|
|
25
|
+
// webpieces-disable no-function-outside-class -- pure list helper, matches the sibling helpers in this file
|
|
26
|
+
function dedupApiRefs(refs) {
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const ref of refs) {
|
|
30
|
+
const key = (0, api_relations_1.apiRefKey)(ref);
|
|
31
|
+
if (seen.has(key))
|
|
32
|
+
continue;
|
|
33
|
+
seen.add(key);
|
|
34
|
+
out.push(ref);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
/** Queues as a key-sorted object, with each producer/consumer list sorted, for a deterministic file. */
|
|
39
|
+
// webpieces-disable no-function-outside-class -- pure data helper, matches the sibling helpers in this file
|
|
40
|
+
function sortedQueues(queues) {
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const key of [...queues.keys()].sort()) {
|
|
43
|
+
const queue = queues.get(key);
|
|
44
|
+
queue.producedBy.sort();
|
|
45
|
+
queue.consumedBy.sort();
|
|
46
|
+
out[key] = queue;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** Sort a Map into a plain object with sorted keys, so the committed JSON is deterministic. */
|
|
51
|
+
// webpieces-disable no-function-outside-class -- pure data helper, matches the sibling helpers in this file
|
|
52
|
+
function sortedRecord(map) {
|
|
53
|
+
const out = {};
|
|
54
|
+
for (const key of [...map.keys()].sort())
|
|
55
|
+
out[key] = map.get(key);
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/** Sort AND de-duplicate: one api used against two targets must not be reported unresolved twice. */
|
|
59
|
+
// webpieces-disable no-function-outside-class -- pure sort helper, matches the sibling helpers in this file
|
|
60
|
+
function sortUnresolved(unresolved) {
|
|
61
|
+
const byKey = new Map();
|
|
62
|
+
for (const entry of unresolved)
|
|
63
|
+
byKey.set(`${entry.service} ${entry.api}`, entry);
|
|
64
|
+
return [...byKey.values()].sort((a, b) => a.service.localeCompare(b.service) || a.api.localeCompare(b.api));
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=runtime-graph-sorters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-graph-sorters.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-graph-sorters.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;AAWH,oCAUC;AAID,oCASC;AAID,oCAIC;AAID,wCAOC;AAlDD,6DAAsD;AAGtD;;;mDAGmD;AACnD,4GAA4G;AAC5G,SAAgB,YAAY,CAAC,IAAc;IACvC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,IAAA,yBAAS,EAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,wGAAwG;AACxG,4GAA4G;AAC5G,SAAgB,YAAY,CAAC,MAAiC;IAC1D,MAAM,GAAG,GAAiC,EAAE,CAAC;IAC7C,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;QAC/B,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACxB,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACxB,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACrB,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,+FAA+F;AAC/F,4GAA4G;AAC5G,SAAgB,YAAY,CAAC,GAAwB;IACjD,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QAAE,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;IACnE,OAAO,GAAG,CAAC;AACf,CAAC;AAED,qGAAqG;AACrG,4GAA4G;AAC5G,SAAgB,cAAc,CAAC,UAA+B;IAC1D,MAAM,KAAK,GAAG,IAAI,GAAG,EAA6B,CAAC;IACnD,KAAK,MAAM,KAAK,IAAI,UAAU;QAAE,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAC3B,CAAC,CAAoB,EAAE,CAAoB,EAAE,EAAE,CAC3C,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CACvE,CAAC;AACN,CAAC","sourcesContent":["/**\n * Determinism helpers for the runtime graph.\n *\n * `architecture/runtime-dependencies.json` is COMMITTED and compared byte-for-byte by\n * `validate-runtime-architecture`, so every collection that reaches it must come out in a stable\n * order regardless of the order projects happened to be scanned in. These are the pure functions\n * that guarantee that — sorting, and de-duplicating where one node can legitimately absorb the same\n * relation twice.\n *\n * Split out of runtime-graph.ts, which owns the DERIVATION (what the graph means). These own only\n * the SHAPE of the output (what order it is written in). They are leaf functions over the model\n * types with no knowledge of the deriver, which is also why they can be imported without a cycle.\n */\n\nimport type { ApiRef } from './api-usage/api-relations';\nimport { apiRefKey } from './api-usage/api-relations';\nimport type { RuntimeQueue, RuntimeUnresolved } from './runtime-graph-model';\n\n/** Drop duplicate api refs, keeping the first — needed after a node absorbs the same api from both\n * its own relations and an embedded lib's. Keyed by api AND target service: the same contract aimed\n * at two different services is two distinct relations (two distinct edges), not a duplicate. Input\n * is pre-sorted, so output stays deterministic. */\n// webpieces-disable no-function-outside-class -- pure list helper, matches the sibling helpers in this file\nexport function dedupApiRefs(refs: ApiRef[]): ApiRef[] {\n const seen = new Set<string>();\n const out: ApiRef[] = [];\n for (const ref of refs) {\n const key = apiRefKey(ref);\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(ref);\n }\n return out;\n}\n\n/** Queues as a key-sorted object, with each producer/consumer list sorted, for a deterministic file. */\n// webpieces-disable no-function-outside-class -- pure data helper, matches the sibling helpers in this file\nexport function sortedQueues(queues: Map<string, RuntimeQueue>): Record<string, RuntimeQueue> {\n const out: Record<string, RuntimeQueue> = {};\n for (const key of [...queues.keys()].sort()) {\n const queue = queues.get(key)!;\n queue.producedBy.sort();\n queue.consumedBy.sort();\n out[key] = queue;\n }\n return out;\n}\n\n/** Sort a Map into a plain object with sorted keys, so the committed JSON is deterministic. */\n// webpieces-disable no-function-outside-class -- pure data helper, matches the sibling helpers in this file\nexport function sortedRecord(map: Map<string, string>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const key of [...map.keys()].sort()) out[key] = map.get(key)!;\n return out;\n}\n\n/** Sort AND de-duplicate: one api used against two targets must not be reported unresolved twice. */\n// webpieces-disable no-function-outside-class -- pure sort helper, matches the sibling helpers in this file\nexport function sortUnresolved(unresolved: RuntimeUnresolved[]): RuntimeUnresolved[] {\n const byKey = new Map<string, RuntimeUnresolved>();\n for (const entry of unresolved) byKey.set(`${entry.service} ${entry.api}`, entry);\n return [...byKey.values()].sort(\n (a: RuntimeUnresolved, b: RuntimeUnresolved) =>\n a.service.localeCompare(b.service) || a.api.localeCompare(b.api),\n );\n}\n"]}
|