@webpieces/nx-webpieces-rules 0.3.240 → 0.3.241

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/nx-webpieces-rules",
3
- "version": "0.3.240",
3
+ "version": "0.3.241",
4
4
  "description": "Nx-specific webpieces validation rules and graph tooling. Bundles all @webpieces rule packages with Nx graph validators and an inference plugin.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -18,11 +18,11 @@
18
18
  "README.md"
19
19
  ],
20
20
  "dependencies": {
21
- "@webpieces/ai-hook-rules": "0.3.240",
22
- "@webpieces/code-rules": "0.3.240",
23
- "@webpieces/eslint-rules": "0.3.240",
24
- "@webpieces/pr-gate": "0.3.240",
25
- "@webpieces/rules-config": "0.3.240",
21
+ "@webpieces/ai-hook-rules": "0.3.241",
22
+ "@webpieces/code-rules": "0.3.241",
23
+ "@webpieces/eslint-rules": "0.3.241",
24
+ "@webpieces/pr-gate": "0.3.241",
25
+ "@webpieces/rules-config": "0.3.241",
26
26
  "madge": "8.0.0"
27
27
  },
28
28
  "peerDependencies": {
@@ -90,10 +90,13 @@ async function runExecutor(_options, context) {
90
90
  }
91
91
  const graph = (0, analyzer_1.buildDiGraph)(program, context.root, projectRoot, projectName);
92
92
  writeDesignFiles(projectRootAbs, graph);
93
+ const nodeCount = graph.designs.reduce((sum, d) => sum + d.nodes.length, 0);
94
+ const edgeCount = graph.designs.reduce((sum, d) => sum + d.edges.length, 0);
93
95
  console.log(`✅ Wrote ${projectRoot}/design.json + design.md ` +
94
- `(${graph.roots.length} root(s), ${graph.nodes.length} node(s), ${graph.edges.length} edge(s))`);
95
- if (graph.unresolved.length > 0) {
96
- console.warn(`⚠️ ${graph.unresolved.length} unresolved token(s)/type(s): ${graph.unresolved.join(', ')}`);
96
+ `(${graph.designs.length} design(s), ${nodeCount} node(s), ${edgeCount} edge(s))`);
97
+ const unresolved = [...new Set(graph.designs.flatMap((d) => d.unresolved))];
98
+ if (unresolved.length > 0) {
99
+ console.warn(`⚠️ ${unresolved.length} unresolved token(s)/type(s): ${unresolved.join(', ')}`);
97
100
  }
98
101
  return { success: true };
99
102
  }
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/di-graph-generate/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DI Graph Generate Executor\n *\n * Per-project: statically analyzes the project's Inversify dependency DAG\n * (constructor injection from controllers — or library top-of-DAG classes —\n * down to leaves) and writes two checked-in files at the project root:\n *\n * design.json — machine-readable graph (deterministic, sorted)\n * design.md — Mermaid diagram rendered by GitHub/IDEs in PRs\n *\n * Runs on every build (cache:false; the build gates on\n * validate-di-graph-unchanged which dependsOn this target). Unrecognized DI\n * patterns become \"unresolved\" nodes rather than failing the build.\n *\n * Config (webpieces.config.json, rule key `di-graph`): mode RUN_EVERY_TIME | OFF.\n *\n * Usage: nx run <project>:di-graph-generate\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadAndValidate } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { buildDiGraph } from '../../lib/di-graph/analyzer';\nimport { createProjectProgram } from '../../lib/di-graph/program';\nimport { toDesignJson } from '../../lib/di-graph/serializer';\nimport { toDesignMarkdown } from '../../lib/di-graph/mermaid';\nimport { DiGraph } from '../../lib/di-graph/model';\nimport { toError } from '../../toError';\n\nexport interface DiGraphGenerateOptions {\n // No options here — config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst RULE_NAME = 'di-graph';\n\n// Cheap substring pre-scan: a project whose source never mentions any DI marker\n// gets an empty graph without paying for a ts.Program.\nconst DI_MARKERS = [\n '@Controller(',\n '@provideSingleton',\n '@provideTransient',\n '@injectable',\n 'new ContainerModule',\n '@inject(',\n];\n\nfunction sourceHasDiMarkers(dir: string): boolean {\n if (!fs.existsSync(dir)) return false;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === 'node_modules' || entry.name === 'dist') continue;\n if (sourceHasDiMarkers(full)) return true;\n } else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) {\n const content = fs.readFileSync(full, 'utf-8');\n if (DI_MARKERS.some((marker: string) => content.includes(marker))) return true;\n }\n }\n return false;\n}\n\nfunction writeDesignFiles(projectRootAbs: string, graph: DiGraph): void {\n fs.writeFileSync(path.join(projectRootAbs, 'design.json'), toDesignJson(graph));\n fs.writeFileSync(path.join(projectRootAbs, 'design.md'), toDesignMarkdown(graph));\n}\n\nexport default async function runExecutor(\n _options: DiGraphGenerateOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadAndValidate(context.root).resolved;\n const rule = shared.rules.get(RULE_NAME);\n if (rule && rule.isOff) {\n console.log(`\\n⏭️ Skipping ${RULE_NAME} generation (mode: OFF)\\n`);\n return { success: true };\n }\n\n const projectName = context.projectName ?? 'project';\n const projectConfig = context.projectsConfigurations?.projects[projectName];\n const projectRoot = projectConfig?.root ?? '.';\n const projectRootAbs = path.join(context.root, projectRoot);\n\n console.log(`\\n🧬 Generating DI design graph for ${projectName}\\n`);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- chokepoint: a generator crash must produce an actionable failure, not a stack trace mid-build\n try {\n if (!sourceHasDiMarkers(path.join(projectRootAbs, 'src'))) {\n console.log(' No DI markers found — writing empty design graph');\n writeDesignFiles(projectRootAbs, new DiGraph(projectName));\n return { success: true };\n }\n\n const program = createProjectProgram(projectRootAbs);\n if (!program) {\n console.log(' No usable tsconfig/source — writing empty design graph');\n writeDesignFiles(projectRootAbs, new DiGraph(projectName));\n return { success: true };\n }\n\n const graph = buildDiGraph(program, context.root, projectRoot, projectName);\n writeDesignFiles(projectRootAbs, graph);\n\n console.log(\n `✅ Wrote ${projectRoot}/design.json + design.md ` +\n `(${graph.roots.length} root(s), ${graph.nodes.length} node(s), ${graph.edges.length} edge(s))`,\n );\n if (graph.unresolved.length > 0) {\n console.warn(`⚠️ ${graph.unresolved.length} unresolved token(s)/type(s): ${graph.unresolved.join(', ')}`);\n }\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error(`❌ DI graph generation failed for ${projectName}: ${error.message}`);\n console.error(` To unblock builds, set rules[\"${RULE_NAME}\"].mode=\"OFF\" in webpieces.config.json.`);\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/di-graph-generate/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DI Graph Generate Executor\n *\n * Per-project: statically analyzes the project's Inversify dependency DAG\n * (constructor injection from controllers — or library top-of-DAG classes —\n * down to leaves) and writes two checked-in files at the project root:\n *\n * design.json — machine-readable graph (deterministic, sorted)\n * design.md — Mermaid diagram rendered by GitHub/IDEs in PRs\n *\n * Runs on every build (cache:false; the build gates on\n * validate-di-graph-unchanged which dependsOn this target). Unrecognized DI\n * patterns become \"unresolved\" nodes rather than failing the build.\n *\n * Config (webpieces.config.json, rule key `di-graph`): mode RUN_EVERY_TIME | OFF.\n *\n * Usage: nx run <project>:di-graph-generate\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { loadAndValidate } from '@webpieces/rules-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { buildDiGraph } from '../../lib/di-graph/analyzer';\nimport { createProjectProgram } from '../../lib/di-graph/program';\nimport { toDesignJson } from '../../lib/di-graph/serializer';\nimport { toDesignMarkdown } from '../../lib/di-graph/mermaid';\nimport { DiDesign, DiGraph } from '../../lib/di-graph/model';\nimport { toError } from '../../toError';\n\nexport interface DiGraphGenerateOptions {\n // No options here — config comes from webpieces.config.json at runtime.\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nconst RULE_NAME = 'di-graph';\n\n// Cheap substring pre-scan: a project whose source never mentions any DI marker\n// gets an empty graph without paying for a ts.Program.\nconst DI_MARKERS = [\n '@Controller(',\n '@provideSingleton',\n '@provideTransient',\n '@injectable',\n 'new ContainerModule',\n '@inject(',\n];\n\nfunction sourceHasDiMarkers(dir: string): boolean {\n if (!fs.existsSync(dir)) return false;\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === 'node_modules' || entry.name === 'dist') continue;\n if (sourceHasDiMarkers(full)) return true;\n } else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) {\n const content = fs.readFileSync(full, 'utf-8');\n if (DI_MARKERS.some((marker: string) => content.includes(marker))) return true;\n }\n }\n return false;\n}\n\nfunction writeDesignFiles(projectRootAbs: string, graph: DiGraph): void {\n fs.writeFileSync(path.join(projectRootAbs, 'design.json'), toDesignJson(graph));\n fs.writeFileSync(path.join(projectRootAbs, 'design.md'), toDesignMarkdown(graph));\n}\n\nexport default async function runExecutor(\n _options: DiGraphGenerateOptions,\n context: ExecutorContext,\n): Promise<ExecutorResult> {\n const shared = loadAndValidate(context.root).resolved;\n const rule = shared.rules.get(RULE_NAME);\n if (rule && rule.isOff) {\n console.log(`\\n⏭️ Skipping ${RULE_NAME} generation (mode: OFF)\\n`);\n return { success: true };\n }\n\n const projectName = context.projectName ?? 'project';\n const projectConfig = context.projectsConfigurations?.projects[projectName];\n const projectRoot = projectConfig?.root ?? '.';\n const projectRootAbs = path.join(context.root, projectRoot);\n\n console.log(`\\n🧬 Generating DI design graph for ${projectName}\\n`);\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- chokepoint: a generator crash must produce an actionable failure, not a stack trace mid-build\n try {\n if (!sourceHasDiMarkers(path.join(projectRootAbs, 'src'))) {\n console.log(' No DI markers found — writing empty design graph');\n writeDesignFiles(projectRootAbs, new DiGraph(projectName));\n return { success: true };\n }\n\n const program = createProjectProgram(projectRootAbs);\n if (!program) {\n console.log(' No usable tsconfig/source — writing empty design graph');\n writeDesignFiles(projectRootAbs, new DiGraph(projectName));\n return { success: true };\n }\n\n const graph = buildDiGraph(program, context.root, projectRoot, projectName);\n writeDesignFiles(projectRootAbs, graph);\n\n const nodeCount = graph.designs.reduce((sum: number, d: DiDesign) => sum + d.nodes.length, 0);\n const edgeCount = graph.designs.reduce((sum: number, d: DiDesign) => sum + d.edges.length, 0);\n console.log(\n `✅ Wrote ${projectRoot}/design.json + design.md ` +\n `(${graph.designs.length} design(s), ${nodeCount} node(s), ${edgeCount} edge(s))`,\n );\n const unresolved = [...new Set(graph.designs.flatMap((d: DiDesign) => d.unresolved))];\n if (unresolved.length > 0) {\n console.warn(`⚠️ ${unresolved.length} unresolved token(s)/type(s): ${unresolved.join(', ')}`);\n }\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n console.error(`❌ DI graph generation failed for ${projectName}: ${error.message}`);\n console.error(` To unblock builds, set rules[\"${RULE_NAME}\"].mode=\"OFF\" in webpieces.config.json.`);\n return { success: false };\n }\n}\n"]}
@@ -17,6 +17,8 @@ import * as ts from 'typescript';
17
17
  import { DiGraph } from './model';
18
18
  export declare function isControllerClass(cls: ts.ClassDeclaration): boolean;
19
19
  /**
20
- * Build the full DI graph for one project. `projectRoot` is workspace-relative.
20
+ * Build the full DI graph for one project: one self-contained `DiDesign` per
21
+ * root — every @Controller class, or (when the project has no controllers)
22
+ * every top-of-DAG DI class. `projectRoot` is workspace-relative.
21
23
  */
22
24
  export declare function buildDiGraph(program: ts.Program, workspaceRoot: string, projectRoot: string, projectName: string): DiGraph;
Binary file
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/analyzer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DI Graph Analyzer (pass 2)\n *\n * Walks constructor injection from a project's root classes down to leaves:\n *\n * - @inject(TOKEN) params → token lookup in the binding table → bound impl\n * - @multiInject(TOKEN) → fan-out edge to EVERY binding of that token\n * - bare typed params → checker resolves the type to a class (inject-by-type)\n * - toConstantValue/toDynamicValue bindings → leaf nodes, no recursion\n * - unresolvable tokens/types → kind \"unresolved\" nodes (generation never fails)\n *\n * Roots: @Controller() classes when the project has any; otherwise every\n * DI-registered class in the project that no other project class injects\n * (the tops of the local DAG).\n */\n\nimport * as ts from 'typescript';\nimport * as path from 'path';\nimport { Binding, DiEdge, DiGraph, DiNode, DiNodeKind, DiScope } from './model';\nimport {\n BindingTable,\n classDecorators,\n collectBindings,\n decoratorCall,\n decoratorName,\n resolveClassDeclaration,\n} from './bindings';\nimport { classTokenKey, relativeFile, resolveTokenKey } from './token-resolver';\n\nconst DI_DECORATORS = new Set(['provideSingleton', 'provideTransient', 'provideSingletonAs', 'injectable']);\n\nclass ParamInjection {\n expr: ts.Expression | null;\n multi: boolean;\n optional: boolean;\n unmanaged: boolean;\n\n constructor(expr: ts.Expression | null, multi: boolean, optional: boolean, unmanaged: boolean) {\n this.expr = expr;\n this.multi = multi;\n this.optional = optional;\n this.unmanaged = unmanaged;\n }\n}\n\nfunction readParamDecorators(param: ts.ParameterDeclaration): ParamInjection {\n let expr: ts.Expression | null = null;\n let multi = false;\n let optional = false;\n let unmanaged = false;\n for (const decorator of ts.getDecorators(param) ?? []) {\n const name = decoratorName(decorator);\n const call = decoratorCall(decorator);\n if (name === 'inject' && call?.arguments[0]) {\n expr = call.arguments[0];\n } else if (name === 'multiInject' && call?.arguments[0]) {\n expr = call.arguments[0];\n multi = true;\n } else if (name === 'optional') {\n optional = true;\n } else if (name === 'unmanaged') {\n unmanaged = true;\n }\n }\n return new ParamInjection(expr, multi, optional, unmanaged);\n}\n\nfunction hasDecoratorNamed(cls: ts.ClassDeclaration, names: Set<string>): boolean {\n for (const decorator of classDecorators(cls)) {\n const name = decoratorName(decorator);\n if (name && names.has(name)) return true;\n }\n return false;\n}\n\nexport function isControllerClass(cls: ts.ClassDeclaration): boolean {\n return hasDecoratorNamed(cls, new Set(['Controller']));\n}\n\nfunction isDiRegisteredClass(cls: ts.ClassDeclaration): boolean {\n return hasDecoratorNamed(cls, DI_DECORATORS);\n}\n\nfunction findConstructor(cls: ts.ClassDeclaration): ts.ConstructorDeclaration | null {\n for (const member of cls.members) {\n if (ts.isConstructorDeclaration(member) && member.body) return member;\n if (ts.isConstructorDeclaration(member)) return member;\n }\n return null;\n}\n\n/** All class declarations in files under the project root. */\nfunction projectClasses(\n program: ts.Program,\n workspaceRoot: string,\n projectRoot: string,\n): ts.ClassDeclaration[] {\n const prefix = projectRoot.endsWith('/') ? projectRoot : projectRoot + '/';\n const classes: ts.ClassDeclaration[] = [];\n for (const sourceFile of program.getSourceFiles()) {\n if (sourceFile.isDeclarationFile) continue;\n const rel = relativeFile(workspaceRoot, sourceFile);\n if (!rel.startsWith(prefix)) continue;\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node) && node.name) classes.push(node);\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n }\n return classes;\n}\n\nclass DiGraphBuilder {\n private readonly checker: ts.TypeChecker;\n private readonly table: BindingTable;\n private readonly workspaceRoot: string;\n private readonly graph: DiGraph;\n private readonly classIds = new Map<ts.ClassDeclaration, string>();\n private readonly leafIds = new Map<Binding, string>();\n private readonly unresolvedIds = new Map<string, string>();\n private readonly usedIds = new Set<string>();\n private readonly visited = new Set<ts.ClassDeclaration>();\n\n constructor(checker: ts.TypeChecker, table: BindingTable, workspaceRoot: string, graph: DiGraph) {\n this.checker = checker;\n this.table = table;\n this.workspaceRoot = workspaceRoot;\n this.graph = graph;\n }\n\n addRoot(cls: ts.ClassDeclaration): void {\n const id = this.classNode(cls);\n this.graph.roots.push(id);\n this.walkClass(cls);\n }\n\n /**\n * Register (or fetch) the node for a class, returning its stable id.\n * `scopeHint` carries the scope of the module binding the class was reached\n * through (e.g. bind(TOKEN).to(X).inSingletonScope() where X is not self-bound).\n */\n private classNode(cls: ts.ClassDeclaration, scopeHint: DiScope = 'unknown'): string {\n const existing = this.classIds.get(cls);\n if (existing) return existing;\n\n const className = cls.name ? cls.name.text : '<anonymous>';\n const file = relativeFile(this.workspaceRoot, cls.getSourceFile());\n const id = this.claimId(className, file);\n this.classIds.set(cls, id);\n\n const declaredScope = this.classScope(cls);\n const scope = declaredScope !== 'unknown' ? declaredScope : scopeHint;\n const kind: DiNodeKind = isControllerClass(cls) ? 'controller' : 'class';\n this.graph.nodes.push(new DiNode(id, className, kind, scope, file));\n return id;\n }\n\n private claimId(preferred: string, file: string): string {\n if (!this.usedIds.has(preferred)) {\n this.usedIds.add(preferred);\n return preferred;\n }\n const disambiguated = `${preferred}@${path.posix.dirname(file)}`;\n let id = disambiguated;\n let n = 2;\n while (this.usedIds.has(id)) {\n id = `${disambiguated}~${n}`;\n n++;\n }\n this.usedIds.add(id);\n return id;\n }\n\n /** Scope from the class's own decorator/module binding, if any. */\n private classScope(cls: ts.ClassDeclaration): DiScope {\n const token = classTokenKey(cls, this.workspaceRoot);\n for (const binding of this.table.lookup(token.key)) {\n if (binding.scope !== 'unknown') return binding.scope;\n }\n return 'unknown';\n }\n\n private leafNode(binding: Binding, kind: DiNodeKind): string {\n const existing = this.leafIds.get(binding);\n if (existing) return existing;\n\n // Dynamic values are factory functions — their source text is noise, so label\n // them by the token they satisfy. Constants keep their (short) expression text.\n const className = kind === 'dynamic'\n ? `${binding.tokenDisplay} (dynamic)`\n : binding.valueText !== '' ? binding.valueText : binding.tokenDisplay;\n const id = this.claimId(className, binding.file);\n this.leafIds.set(binding, id);\n this.graph.nodes.push(new DiNode(id, className, kind, binding.scope, binding.file));\n return id;\n }\n\n private unresolvedNode(display: string, file: string): string {\n const key = `${display}\u0000${file}`;\n const existing = this.unresolvedIds.get(key);\n if (existing) return existing;\n\n const id = this.claimId(display, file);\n this.unresolvedIds.set(key, id);\n this.graph.nodes.push(new DiNode(id, display, 'unresolved', 'unknown', file));\n this.graph.unresolved.push(display);\n return id;\n }\n\n walkClass(cls: ts.ClassDeclaration): void {\n if (this.visited.has(cls)) return;\n this.visited.add(cls);\n\n const ctor = findConstructor(cls);\n if (!ctor) return;\n const fromId = this.classNode(cls);\n\n for (const param of ctor.parameters) {\n this.walkParam(fromId, cls, param);\n }\n }\n\n private walkParam(fromId: string, cls: ts.ClassDeclaration, param: ts.ParameterDeclaration): void {\n const injection = readParamDecorators(param);\n if (injection.unmanaged) return;\n\n const paramName = ts.isIdentifier(param.name) ? param.name.text : param.name.getText();\n const paramType = param.type ? param.type.getText() : '';\n\n if (injection.expr) {\n this.walkTokenParam(fromId, cls, injection, paramName, paramType);\n } else {\n this.walkTypeParam(fromId, cls, param, paramName, paramType);\n }\n }\n\n private walkTokenParam(\n fromId: string,\n cls: ts.ClassDeclaration,\n injection: ParamInjection,\n paramName: string,\n paramType: string,\n ): void {\n const expr = injection.expr;\n if (!expr) return;\n const token = resolveTokenKey(expr, this.checker, this.workspaceRoot);\n const kind = injection.multi ? 'multiInject' : 'token';\n const bindings = this.table.lookup(token.key);\n\n if (bindings.length === 0) {\n // @multiInject @optional with zero bindings is legal — no edge, no error.\n if (injection.multi && injection.optional) return;\n const file = relativeFile(this.workspaceRoot, cls.getSourceFile());\n const toId = this.unresolvedNode(token.display, file);\n this.graph.edges.push(new DiEdge(fromId, toId, kind, token.display, token.key, paramName, paramType));\n return;\n }\n\n for (const binding of bindings) {\n const toId = this.bindingTarget(binding);\n this.graph.edges.push(new DiEdge(fromId, toId, kind, token.display, token.key, paramName, paramType));\n }\n }\n\n private bindingTarget(binding: Binding): string {\n if (binding.implClass) {\n const toId = this.classNode(binding.implClass, binding.scope);\n this.walkClass(binding.implClass);\n return toId;\n }\n if (binding.kind === 'toConstantValue') return this.leafNode(binding, 'constant');\n if (binding.kind === 'toDynamicValue') return this.leafNode(binding, 'dynamic');\n // .to(X) where X did not resolve to a class declaration.\n return this.unresolvedNode(binding.valueText !== '' ? binding.valueText : binding.tokenDisplay, binding.file);\n }\n\n private walkTypeParam(\n fromId: string,\n cls: ts.ClassDeclaration,\n param: ts.ParameterDeclaration,\n paramName: string,\n paramType: string,\n ): void {\n const typeName = param.type && ts.isTypeReferenceNode(param.type) ? param.type.typeName : null;\n const target = typeName && ts.isIdentifier(typeName)\n ? resolveClassDeclaration(typeName, this.checker)\n : null;\n\n if (target) {\n const token = classTokenKey(target, this.workspaceRoot);\n const toId = this.classNode(target);\n this.walkClass(target);\n this.graph.edges.push(new DiEdge(fromId, toId, 'type', token.display, token.key, paramName, paramType));\n return;\n }\n\n // Bare param whose type is not a resolvable class (interface, primitive, etc.).\n const file = relativeFile(this.workspaceRoot, cls.getSourceFile());\n const toId = this.unresolvedNode(paramType !== '' ? paramType : paramName, file);\n this.graph.edges.push(new DiEdge(fromId, toId, 'type', paramType, `type:${paramType}`, paramName, paramType));\n }\n}\n\nfunction byClassName(a: ts.ClassDeclaration, b: ts.ClassDeclaration): number {\n const nameA = a.name ? a.name.text : '';\n const nameB = b.name ? b.name.text : '';\n if (nameA !== nameB) return nameA < nameB ? -1 : 1;\n return a.getSourceFile().fileName < b.getSourceFile().fileName ? -1 : 1;\n}\n\n/**\n * Library-project roots: DI-registered classes in the project that no other\n * project class injects (tops of the local DAG).\n */\nfunction findLibraryRoots(\n classes: ts.ClassDeclaration[],\n checker: ts.TypeChecker,\n table: BindingTable,\n workspaceRoot: string,\n): ts.ClassDeclaration[] {\n const diClasses = classes.filter((cls: ts.ClassDeclaration) => isDiRegisteredClass(cls));\n const injected = new Set<ts.ClassDeclaration>();\n\n for (const cls of diClasses) {\n const ctor = findConstructor(cls);\n for (const param of ctor?.parameters ?? []) {\n const injection = readParamDecorators(param);\n if (injection.unmanaged) continue;\n if (injection.expr) {\n const token = resolveTokenKey(injection.expr, checker, workspaceRoot);\n for (const binding of table.lookup(token.key)) {\n if (binding.implClass) injected.add(binding.implClass);\n }\n } else if (param.type && ts.isTypeReferenceNode(param.type) && ts.isIdentifier(param.type.typeName)) {\n const target = resolveClassDeclaration(param.type.typeName, checker);\n if (target) injected.add(target);\n }\n }\n }\n\n return diClasses.filter((cls: ts.ClassDeclaration) => !injected.has(cls));\n}\n\n/**\n * Build the full DI graph for one project. `projectRoot` is workspace-relative.\n */\nexport function buildDiGraph(\n program: ts.Program,\n workspaceRoot: string,\n projectRoot: string,\n projectName: string,\n): DiGraph {\n const checker = program.getTypeChecker();\n const table = collectBindings(program, checker, workspaceRoot);\n const graph = new DiGraph(projectName);\n const builder = new DiGraphBuilder(checker, table, workspaceRoot, graph);\n\n const classes = projectClasses(program, workspaceRoot, projectRoot);\n const controllers = classes.filter((cls: ts.ClassDeclaration) => isControllerClass(cls));\n const roots = controllers.length > 0\n ? controllers\n : findLibraryRoots(classes, checker, table, workspaceRoot);\n\n for (const root of [...roots].sort(byClassName)) {\n builder.addRoot(root);\n }\n\n return graph;\n}\n"]}
1
+ {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/analyzer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DI Graph Analyzer (pass 2)\n *\n * Walks constructor injection from a project's root classes down to leaves:\n *\n * - @inject(TOKEN) params → token lookup in the binding table → bound impl\n * - @multiInject(TOKEN) → fan-out edge to EVERY binding of that token\n * - bare typed params → checker resolves the type to a class (inject-by-type)\n * - toConstantValue/toDynamicValue bindings → leaf nodes, no recursion\n * - unresolvable tokens/types → kind \"unresolved\" nodes (generation never fails)\n *\n * Roots: @Controller() classes when the project has any; otherwise every\n * DI-registered class in the project that no other project class injects\n * (the tops of the local DAG).\n */\n\nimport * as ts from 'typescript';\nimport * as path from 'path';\nimport { Binding, DiDesign, DiEdge, DiGraph, DiNode, DiNodeKind, DiScope } from './model';\nimport {\n BindingTable,\n classDecorators,\n collectBindings,\n decoratorCall,\n decoratorName,\n resolveClassDeclaration,\n} from './bindings';\nimport { classTokenKey, relativeFile, resolveTokenKey } from './token-resolver';\n\nconst DI_DECORATORS = new Set(['provideSingleton', 'provideTransient', 'provideSingletonAs', 'injectable']);\n\nclass ParamInjection {\n expr: ts.Expression | null;\n multi: boolean;\n optional: boolean;\n unmanaged: boolean;\n\n constructor(expr: ts.Expression | null, multi: boolean, optional: boolean, unmanaged: boolean) {\n this.expr = expr;\n this.multi = multi;\n this.optional = optional;\n this.unmanaged = unmanaged;\n }\n}\n\nfunction readParamDecorators(param: ts.ParameterDeclaration): ParamInjection {\n let expr: ts.Expression | null = null;\n let multi = false;\n let optional = false;\n let unmanaged = false;\n for (const decorator of ts.getDecorators(param) ?? []) {\n const name = decoratorName(decorator);\n const call = decoratorCall(decorator);\n if (name === 'inject' && call?.arguments[0]) {\n expr = call.arguments[0];\n } else if (name === 'multiInject' && call?.arguments[0]) {\n expr = call.arguments[0];\n multi = true;\n } else if (name === 'optional') {\n optional = true;\n } else if (name === 'unmanaged') {\n unmanaged = true;\n }\n }\n return new ParamInjection(expr, multi, optional, unmanaged);\n}\n\nfunction hasDecoratorNamed(cls: ts.ClassDeclaration, names: Set<string>): boolean {\n for (const decorator of classDecorators(cls)) {\n const name = decoratorName(decorator);\n if (name && names.has(name)) return true;\n }\n return false;\n}\n\nexport function isControllerClass(cls: ts.ClassDeclaration): boolean {\n return hasDecoratorNamed(cls, new Set(['Controller']));\n}\n\nfunction isDiRegisteredClass(cls: ts.ClassDeclaration): boolean {\n return hasDecoratorNamed(cls, DI_DECORATORS);\n}\n\nfunction findConstructor(cls: ts.ClassDeclaration): ts.ConstructorDeclaration | null {\n for (const member of cls.members) {\n if (ts.isConstructorDeclaration(member) && member.body) return member;\n if (ts.isConstructorDeclaration(member)) return member;\n }\n return null;\n}\n\n/** All class declarations in files under the project root. */\nfunction projectClasses(\n program: ts.Program,\n workspaceRoot: string,\n projectRoot: string,\n): ts.ClassDeclaration[] {\n const prefix = projectRoot.endsWith('/') ? projectRoot : projectRoot + '/';\n const classes: ts.ClassDeclaration[] = [];\n for (const sourceFile of program.getSourceFiles()) {\n if (sourceFile.isDeclarationFile) continue;\n const rel = relativeFile(workspaceRoot, sourceFile);\n if (!rel.startsWith(prefix)) continue;\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node) && node.name) classes.push(node);\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n }\n return classes;\n}\n\n/**\n * Builds ONE self-contained `DiDesign` for a single root. A fresh instance is\n * created per root so its maps (visited/classIds/usedIds/...) are scoped to\n * that root's tree — a dependency shared by two controllers is therefore walked\n * (and duplicated) into each controller's design, not hidden under whichever\n * controller reached it first.\n */\nclass DiDesignBuilder {\n private readonly checker: ts.TypeChecker;\n private readonly table: BindingTable;\n private readonly workspaceRoot: string;\n private readonly design: DiDesign;\n private readonly classIds = new Map<ts.ClassDeclaration, string>();\n private readonly leafIds = new Map<Binding, string>();\n private readonly unresolvedIds = new Map<string, string>();\n private readonly usedIds = new Set<string>();\n private readonly visited = new Set<ts.ClassDeclaration>();\n\n constructor(checker: ts.TypeChecker, table: BindingTable, workspaceRoot: string, design: DiDesign) {\n this.checker = checker;\n this.table = table;\n this.workspaceRoot = workspaceRoot;\n this.design = design;\n }\n\n addRoot(cls: ts.ClassDeclaration): void {\n const id = this.classNode(cls);\n this.design.root = id;\n this.walkClass(cls);\n }\n\n /**\n * Register (or fetch) the node for a class, returning its stable id.\n * `scopeHint` carries the scope of the module binding the class was reached\n * through (e.g. bind(TOKEN).to(X).inSingletonScope() where X is not self-bound).\n */\n private classNode(cls: ts.ClassDeclaration, scopeHint: DiScope = 'unknown'): string {\n const existing = this.classIds.get(cls);\n if (existing) return existing;\n\n const className = cls.name ? cls.name.text : '<anonymous>';\n const file = relativeFile(this.workspaceRoot, cls.getSourceFile());\n const id = this.claimId(className, file);\n this.classIds.set(cls, id);\n\n const declaredScope = this.classScope(cls);\n const scope = declaredScope !== 'unknown' ? declaredScope : scopeHint;\n const kind: DiNodeKind = isControllerClass(cls) ? 'controller' : 'class';\n this.design.nodes.push(new DiNode(id, className, kind, scope, file));\n return id;\n }\n\n private claimId(preferred: string, file: string): string {\n if (!this.usedIds.has(preferred)) {\n this.usedIds.add(preferred);\n return preferred;\n }\n const disambiguated = `${preferred}@${path.posix.dirname(file)}`;\n let id = disambiguated;\n let n = 2;\n while (this.usedIds.has(id)) {\n id = `${disambiguated}~${n}`;\n n++;\n }\n this.usedIds.add(id);\n return id;\n }\n\n /** Scope from the class's own decorator/module binding, if any. */\n private classScope(cls: ts.ClassDeclaration): DiScope {\n const token = classTokenKey(cls, this.workspaceRoot);\n for (const binding of this.table.lookup(token.key)) {\n if (binding.scope !== 'unknown') return binding.scope;\n }\n return 'unknown';\n }\n\n private leafNode(binding: Binding, kind: DiNodeKind): string {\n const existing = this.leafIds.get(binding);\n if (existing) return existing;\n\n // Dynamic values are factory functions — their source text is noise, so label\n // them by the token they satisfy. Constants keep their (short) expression text.\n const className = kind === 'dynamic'\n ? `${binding.tokenDisplay} (dynamic)`\n : binding.valueText !== '' ? binding.valueText : binding.tokenDisplay;\n const id = this.claimId(className, binding.file);\n this.leafIds.set(binding, id);\n this.design.nodes.push(new DiNode(id, className, kind, binding.scope, binding.file));\n return id;\n }\n\n private unresolvedNode(display: string, file: string): string {\n const key = `${display} ${file}`;\n const existing = this.unresolvedIds.get(key);\n if (existing) return existing;\n\n const id = this.claimId(display, file);\n this.unresolvedIds.set(key, id);\n this.design.nodes.push(new DiNode(id, display, 'unresolved', 'unknown', file));\n this.design.unresolved.push(display);\n return id;\n }\n\n walkClass(cls: ts.ClassDeclaration): void {\n if (this.visited.has(cls)) return;\n this.visited.add(cls);\n\n const ctor = findConstructor(cls);\n if (!ctor) return;\n const fromId = this.classNode(cls);\n\n for (const param of ctor.parameters) {\n this.walkParam(fromId, cls, param);\n }\n }\n\n private walkParam(fromId: string, cls: ts.ClassDeclaration, param: ts.ParameterDeclaration): void {\n const injection = readParamDecorators(param);\n if (injection.unmanaged) return;\n\n const paramName = ts.isIdentifier(param.name) ? param.name.text : param.name.getText();\n const paramType = param.type ? param.type.getText() : '';\n\n if (injection.expr) {\n this.walkTokenParam(fromId, cls, injection, paramName, paramType);\n } else {\n this.walkTypeParam(fromId, cls, param, paramName, paramType);\n }\n }\n\n private walkTokenParam(\n fromId: string,\n cls: ts.ClassDeclaration,\n injection: ParamInjection,\n paramName: string,\n paramType: string,\n ): void {\n const expr = injection.expr;\n if (!expr) return;\n const token = resolveTokenKey(expr, this.checker, this.workspaceRoot);\n const kind = injection.multi ? 'multiInject' : 'token';\n const bindings = this.table.lookup(token.key);\n\n if (bindings.length === 0) {\n // @multiInject @optional with zero bindings is legal — no edge, no error.\n if (injection.multi && injection.optional) return;\n const file = relativeFile(this.workspaceRoot, cls.getSourceFile());\n const toId = this.unresolvedNode(token.display, file);\n this.design.edges.push(new DiEdge(fromId, toId, kind, token.display, token.key, paramName, paramType));\n return;\n }\n\n for (const binding of bindings) {\n const toId = this.bindingTarget(binding);\n this.design.edges.push(new DiEdge(fromId, toId, kind, token.display, token.key, paramName, paramType));\n }\n }\n\n private bindingTarget(binding: Binding): string {\n if (binding.implClass) {\n const toId = this.classNode(binding.implClass, binding.scope);\n this.walkClass(binding.implClass);\n return toId;\n }\n if (binding.kind === 'toConstantValue') return this.leafNode(binding, 'constant');\n if (binding.kind === 'toDynamicValue') return this.leafNode(binding, 'dynamic');\n // .to(X) where X did not resolve to a class declaration.\n return this.unresolvedNode(binding.valueText !== '' ? binding.valueText : binding.tokenDisplay, binding.file);\n }\n\n private walkTypeParam(\n fromId: string,\n cls: ts.ClassDeclaration,\n param: ts.ParameterDeclaration,\n paramName: string,\n paramType: string,\n ): void {\n const typeName = param.type && ts.isTypeReferenceNode(param.type) ? param.type.typeName : null;\n const target = typeName && ts.isIdentifier(typeName)\n ? resolveClassDeclaration(typeName, this.checker)\n : null;\n\n if (target) {\n const token = classTokenKey(target, this.workspaceRoot);\n const toId = this.classNode(target);\n this.walkClass(target);\n this.design.edges.push(new DiEdge(fromId, toId, 'type', token.display, token.key, paramName, paramType));\n return;\n }\n\n // Bare param whose type is not a resolvable class (interface, primitive, etc.).\n const file = relativeFile(this.workspaceRoot, cls.getSourceFile());\n const toId = this.unresolvedNode(paramType !== '' ? paramType : paramName, file);\n this.design.edges.push(new DiEdge(fromId, toId, 'type', paramType, `type:${paramType}`, paramName, paramType));\n }\n}\n\nfunction byClassName(a: ts.ClassDeclaration, b: ts.ClassDeclaration): number {\n const nameA = a.name ? a.name.text : '';\n const nameB = b.name ? b.name.text : '';\n if (nameA !== nameB) return nameA < nameB ? -1 : 1;\n return a.getSourceFile().fileName < b.getSourceFile().fileName ? -1 : 1;\n}\n\n/**\n * Library-project roots: DI-registered classes in the project that no other\n * project class injects (tops of the local DAG).\n */\nfunction findLibraryRoots(\n classes: ts.ClassDeclaration[],\n checker: ts.TypeChecker,\n table: BindingTable,\n workspaceRoot: string,\n): ts.ClassDeclaration[] {\n const diClasses = classes.filter((cls: ts.ClassDeclaration) => isDiRegisteredClass(cls));\n const injected = new Set<ts.ClassDeclaration>();\n\n for (const cls of diClasses) {\n const ctor = findConstructor(cls);\n for (const param of ctor?.parameters ?? []) {\n const injection = readParamDecorators(param);\n if (injection.unmanaged) continue;\n if (injection.expr) {\n const token = resolveTokenKey(injection.expr, checker, workspaceRoot);\n for (const binding of table.lookup(token.key)) {\n if (binding.implClass) injected.add(binding.implClass);\n }\n } else if (param.type && ts.isTypeReferenceNode(param.type) && ts.isIdentifier(param.type.typeName)) {\n const target = resolveClassDeclaration(param.type.typeName, checker);\n if (target) injected.add(target);\n }\n }\n }\n\n return diClasses.filter((cls: ts.ClassDeclaration) => !injected.has(cls));\n}\n\n/**\n * Assign each node its BFS shortest-path depth from the design's root (root =\n * level 0, its direct injections = level 1, ...) and record the deepest level.\n * A node reached from multiple parents keeps its SHALLOWEST depth. Cycles are\n * safe: a node is levelled once, the first (shallowest) time BFS reaches it.\n */\nfunction assignLevels(design: DiDesign): void {\n const outgoing = new Map<string, string[]>();\n for (const e of design.edges) {\n const list = outgoing.get(e.from) ?? [];\n list.push(e.to);\n outgoing.set(e.from, list);\n }\n const nodeById = new Map<string, DiNode>(design.nodes.map((n: DiNode) => [n.id, n]));\n const levelById = new Map<string, number>();\n\n let frontier = [design.root];\n let level = 0;\n while (frontier.length > 0) {\n const next: string[] = [];\n for (const id of frontier) {\n if (levelById.has(id)) continue;\n levelById.set(id, level);\n for (const to of outgoing.get(id) ?? []) {\n if (!levelById.has(to)) next.push(to);\n }\n }\n frontier = next;\n level++;\n }\n\n let maxLevel = 0;\n for (const node of design.nodes) {\n node.level = levelById.get(node.id) ?? 0;\n if (node.level > maxLevel) maxLevel = node.level;\n }\n design.maxLevel = maxLevel;\n}\n\n/** Build one self-contained downward design tree for a single root class. */\nfunction buildDesign(\n root: ts.ClassDeclaration,\n checker: ts.TypeChecker,\n table: BindingTable,\n workspaceRoot: string,\n): DiDesign {\n const className = root.name ? root.name.text : '<anonymous>';\n const rootKind: DiNodeKind = isControllerClass(root) ? 'controller' : 'class';\n const file = relativeFile(workspaceRoot, root.getSourceFile());\n const design = new DiDesign(className, rootKind, file);\n const builder = new DiDesignBuilder(checker, table, workspaceRoot, design);\n builder.addRoot(root);\n assignLevels(design);\n return design;\n}\n\n/**\n * Build the full DI graph for one project: one self-contained `DiDesign` per\n * root — every @Controller class, or (when the project has no controllers)\n * every top-of-DAG DI class. `projectRoot` is workspace-relative.\n */\nexport function buildDiGraph(\n program: ts.Program,\n workspaceRoot: string,\n projectRoot: string,\n projectName: string,\n): DiGraph {\n const checker = program.getTypeChecker();\n const table = collectBindings(program, checker, workspaceRoot);\n const graph = new DiGraph(projectName);\n\n const classes = projectClasses(program, workspaceRoot, projectRoot);\n const controllers = classes.filter((cls: ts.ClassDeclaration) => isControllerClass(cls));\n const roots = controllers.length > 0\n ? controllers\n : findLibraryRoots(classes, checker, table, workspaceRoot);\n\n for (const root of [...roots].sort(byClassName)) {\n graph.designs.push(buildDesign(root, checker, table, workspaceRoot));\n }\n\n return graph;\n}\n"]}
@@ -7,6 +7,8 @@
7
7
  */
8
8
  import { DiGraph } from './model';
9
9
  /**
10
- * Render the design.md content for a project's DI graph.
10
+ * Render the design.md content for a project's DI graph — one Mermaid diagram
11
+ * per design (per controller, or per library top-of-DAG root), each rooted at
12
+ * Level 0 with its injections fanning downward.
11
13
  */
12
14
  export declare function toDesignMarkdown(graph: DiGraph): string;
@@ -32,12 +32,12 @@ function nodeStatement(node) {
32
32
  return ` ${id}{{"${text} ?"}}:::unresolved`;
33
33
  return ` ${id}["${text}"]`;
34
34
  }
35
- function graphBody(graph) {
35
+ function graphBody(design) {
36
36
  const lines = ['graph TD'];
37
- for (const node of graph.nodes) {
37
+ for (const node of design.nodes) {
38
38
  lines.push(nodeStatement(node));
39
39
  }
40
- for (const edge of graph.edges) {
40
+ for (const edge of design.edges) {
41
41
  const from = mermaidId(edge.from);
42
42
  const to = mermaidId(edge.to);
43
43
  const token = edge.injection === 'multiInject' ? `multiInject ${edge.token}` : edge.token;
@@ -52,8 +52,16 @@ function graphBody(graph) {
52
52
  lines.push(' classDef unresolved fill:#f0ad4e,color:#000000,stroke:#b8860b,stroke-dasharray: 5 5');
53
53
  return lines;
54
54
  }
55
+ /** One `## Root` section with the design's Mermaid diagram. */
56
+ function designSection(design) {
57
+ const kind = design.rootKind === 'controller' ? 'controller' : 'root';
58
+ const heading = `## ${design.root} — ${kind}, Level 0…${design.maxLevel}`;
59
+ return [heading, '', '```mermaid', ...graphBody(design), '```', ''];
60
+ }
55
61
  /**
56
- * Render the design.md content for a project's DI graph.
62
+ * Render the design.md content for a project's DI graph — one Mermaid diagram
63
+ * per design (per controller, or per library top-of-DAG root), each rooted at
64
+ * Level 0 with its injections fanning downward.
57
65
  */
58
66
  function toDesignMarkdown(graph) {
59
67
  (0, serializer_1.sortGraph)(graph);
@@ -63,11 +71,19 @@ function toDesignMarkdown(graph) {
63
71
  `> GENERATED by \`nx run ${graph.project}:di-graph-generate\` — do not edit by hand.`,
64
72
  `> Machine-readable version: [design.json](./design.json)`,
65
73
  '',
74
+ `Each section below is one root's dependency tree: Level 0 is the root`,
75
+ `(a \`@Controller\` or top-of-DAG class), and constructor injections fan`,
76
+ `downward through Levels 1, 2, … A dependency shared by multiple roots`,
77
+ `appears in each root's tree.`,
78
+ '',
66
79
  ];
67
- if (graph.nodes.length === 0) {
80
+ if (graph.designs.length === 0) {
68
81
  return header.concat(['No DI-registered classes found in this project.', '']).join('\n');
69
82
  }
70
- const body = ['```mermaid', ...graphBody(graph), '```', ''];
83
+ const sections = [];
84
+ for (const design of graph.designs) {
85
+ sections.push(...designSection(design));
86
+ }
71
87
  const legend = [
72
88
  '',
73
89
  'Edges are constructor injections: `-->|TOKEN|` for `@inject`/`@multiInject`,',
@@ -75,6 +91,6 @@ function toDesignMarkdown(graph) {
75
91
  '`toDynamicValue` leaves; dashed nodes are tokens the analyzer could not resolve.',
76
92
  '',
77
93
  ];
78
- return header.concat(body).concat(legend).join('\n');
94
+ return header.concat(sections).concat(legend).join('\n');
79
95
  }
80
96
  //# sourceMappingURL=mermaid.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mermaid.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/mermaid.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Mermaid Emitter\n *\n * Renders the DI graph as design.md — a Mermaid `graph TD` diagram that GitHub\n * and IDEs render inline, so the DI design is reviewable in every PR. Emitted\n * in the same sorted order as design.json so diffs are equally stable.\n */\n\nimport { DiGraph, DiNode } from './model';\nimport { sortGraph } from './serializer';\n\n/** Mermaid node ids must be simple identifiers — map graph ids to safe ids. */\nfunction mermaidId(id: string): string {\n return id.replace(/[^A-Za-z0-9_]/g, '_');\n}\n\n/** Escape text destined for a quoted mermaid label. */\nfunction label(text: string): string {\n return text.replace(/\"/g, '#quot;');\n}\n\n/** Escape an edge label (rendered between pipes). */\nfunction edgeLabel(text: string): string {\n return text.replace(/\\|/g, '/').replace(/\"/g, \"'\");\n}\n\nfunction nodeStatement(node: DiNode): string {\n const id = mermaidId(node.id);\n const text = label(node.className);\n if (node.kind === 'controller') return ` ${id}[\"${text}\"]:::controller`;\n if (node.kind === 'constant' || node.kind === 'dynamic') return ` ${id}([\"${text}\"])`;\n if (node.kind === 'unresolved') return ` ${id}{{\"${text} ?\"}}:::unresolved`;\n return ` ${id}[\"${text}\"]`;\n}\n\nfunction graphBody(graph: DiGraph): string[] {\n const lines: string[] = ['graph TD'];\n for (const node of graph.nodes) {\n lines.push(nodeStatement(node));\n }\n for (const edge of graph.edges) {\n const from = mermaidId(edge.from);\n const to = mermaidId(edge.to);\n const token = edge.injection === 'multiInject' ? `multiInject ${edge.token}` : edge.token;\n if (token !== '') {\n lines.push(` ${from} -->|${edgeLabel(token)}| ${to}`);\n } else {\n lines.push(` ${from} --> ${to}`);\n }\n }\n lines.push(' classDef controller fill:#1f6feb,color:#ffffff,stroke:#0d419d');\n lines.push(' classDef unresolved fill:#f0ad4e,color:#000000,stroke:#b8860b,stroke-dasharray: 5 5');\n return lines;\n}\n\n/**\n * Render the design.md content for a project's DI graph.\n */\nexport function toDesignMarkdown(graph: DiGraph): string {\n sortGraph(graph);\n\n const header = [\n `# DI Design Graph — ${graph.project}`,\n '',\n `> GENERATED by \\`nx run ${graph.project}:di-graph-generate\\` — do not edit by hand.`,\n `> Machine-readable version: [design.json](./design.json)`,\n '',\n ];\n\n if (graph.nodes.length === 0) {\n return header.concat(['No DI-registered classes found in this project.', '']).join('\\n');\n }\n\n const body = ['```mermaid', ...graphBody(graph), '```', ''];\n\n const legend = [\n '',\n 'Edges are constructor injections: `-->|TOKEN|` for `@inject`/`@multiInject`,',\n 'unlabeled arrows for inject-by-type. Rounded nodes are `toConstantValue`/',\n '`toDynamicValue` leaves; dashed nodes are tokens the analyzer could not resolve.',\n '',\n ];\n\n return header.concat(body).concat(legend).join('\\n');\n}\n"]}
1
+ {"version":3,"file":"mermaid.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/mermaid.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Mermaid Emitter\n *\n * Renders the DI graph as design.md — a Mermaid `graph TD` diagram that GitHub\n * and IDEs render inline, so the DI design is reviewable in every PR. Emitted\n * in the same sorted order as design.json so diffs are equally stable.\n */\n\nimport { DiDesign, DiGraph, DiNode } from './model';\nimport { sortGraph } from './serializer';\n\n/** Mermaid node ids must be simple identifiers — map graph ids to safe ids. */\nfunction mermaidId(id: string): string {\n return id.replace(/[^A-Za-z0-9_]/g, '_');\n}\n\n/** Escape text destined for a quoted mermaid label. */\nfunction label(text: string): string {\n return text.replace(/\"/g, '#quot;');\n}\n\n/** Escape an edge label (rendered between pipes). */\nfunction edgeLabel(text: string): string {\n return text.replace(/\\|/g, '/').replace(/\"/g, \"'\");\n}\n\nfunction nodeStatement(node: DiNode): string {\n const id = mermaidId(node.id);\n const text = label(node.className);\n if (node.kind === 'controller') return ` ${id}[\"${text}\"]:::controller`;\n if (node.kind === 'constant' || node.kind === 'dynamic') return ` ${id}([\"${text}\"])`;\n if (node.kind === 'unresolved') return ` ${id}{{\"${text} ?\"}}:::unresolved`;\n return ` ${id}[\"${text}\"]`;\n}\n\nfunction graphBody(design: DiDesign): string[] {\n const lines: string[] = ['graph TD'];\n for (const node of design.nodes) {\n lines.push(nodeStatement(node));\n }\n for (const edge of design.edges) {\n const from = mermaidId(edge.from);\n const to = mermaidId(edge.to);\n const token = edge.injection === 'multiInject' ? `multiInject ${edge.token}` : edge.token;\n if (token !== '') {\n lines.push(` ${from} -->|${edgeLabel(token)}| ${to}`);\n } else {\n lines.push(` ${from} --> ${to}`);\n }\n }\n lines.push(' classDef controller fill:#1f6feb,color:#ffffff,stroke:#0d419d');\n lines.push(' classDef unresolved fill:#f0ad4e,color:#000000,stroke:#b8860b,stroke-dasharray: 5 5');\n return lines;\n}\n\n/** One `## Root` section with the design's Mermaid diagram. */\nfunction designSection(design: DiDesign): string[] {\n const kind = design.rootKind === 'controller' ? 'controller' : 'root';\n const heading = `## ${design.root} — ${kind}, Level 0…${design.maxLevel}`;\n return [heading, '', '```mermaid', ...graphBody(design), '```', ''];\n}\n\n/**\n * Render the design.md content for a project's DI graph — one Mermaid diagram\n * per design (per controller, or per library top-of-DAG root), each rooted at\n * Level 0 with its injections fanning downward.\n */\nexport function toDesignMarkdown(graph: DiGraph): string {\n sortGraph(graph);\n\n const header = [\n `# DI Design Graph — ${graph.project}`,\n '',\n `> GENERATED by \\`nx run ${graph.project}:di-graph-generate\\` — do not edit by hand.`,\n `> Machine-readable version: [design.json](./design.json)`,\n '',\n `Each section below is one root's dependency tree: Level 0 is the root`,\n `(a \\`@Controller\\` or top-of-DAG class), and constructor injections fan`,\n `downward through Levels 1, 2, … A dependency shared by multiple roots`,\n `appears in each root's tree.`,\n '',\n ];\n\n if (graph.designs.length === 0) {\n return header.concat(['No DI-registered classes found in this project.', '']).join('\\n');\n }\n\n const sections: string[] = [];\n for (const design of graph.designs) {\n sections.push(...designSection(design));\n }\n\n const legend = [\n '',\n 'Edges are constructor injections: `-->|TOKEN|` for `@inject`/`@multiInject`,',\n 'unlabeled arrows for inject-by-type. Rounded nodes are `toConstantValue`/',\n '`toDynamicValue` leaves; dashed nodes are tokens the analyzer could not resolve.',\n '',\n ];\n\n return header.concat(sections).concat(legend).join('\\n');\n}\n"]}
@@ -4,6 +4,12 @@
4
4
  * Data classes for the per-project Inversify dependency DAG that is generated
5
5
  * into <projectRoot>/design.json and <projectRoot>/design.md on every build.
6
6
  *
7
+ * A project's design.json is a container (`DiGraph`) holding an ARRAY of
8
+ * per-root designs (`DiDesign`) — one self-contained downward tree per
9
+ * @Controller class (or, for controller-less library projects, per top-of-DAG
10
+ * class). Each node in a design carries its `level` = BFS depth from that
11
+ * design's root (root = level 0, its direct injections = level 1, and so on).
12
+ *
7
13
  * All structures are classes (not interfaces) per the repo convention for
8
14
  * data-only structures.
9
15
  */
@@ -22,7 +28,9 @@ export declare class DiNode {
22
28
  kind: DiNodeKind;
23
29
  scope: DiScope;
24
30
  file: string;
25
- constructor(id: string, className: string, kind: DiNodeKind, scope: DiScope, file: string);
31
+ /** BFS depth from the design's root (root = 0). Filled in after the walk. */
32
+ level: number;
33
+ constructor(id: string, className: string, kind: DiNodeKind, scope: DiScope, file: string, level?: number);
26
34
  }
27
35
  /**
28
36
  * A constructor-injection edge: `from` class injects `to` node.
@@ -38,15 +46,32 @@ export declare class DiEdge {
38
46
  constructor(from: string, to: string, injection: DiInjectionKind, token: string, tokenKey: string, paramName: string, paramType: string);
39
47
  }
40
48
  /**
41
- * The full per-project DI graph, serialized to design.json.
49
+ * One self-contained DI design tree: a single root (a @Controller, or a
50
+ * library top-of-DAG class) and everything it injects "on down". Shared
51
+ * dependencies are duplicated across designs — each design is its own tree so
52
+ * it can be reviewed in isolation.
42
53
  */
43
- export declare class DiGraph {
44
- schemaVersion: number;
45
- project: string;
46
- roots: string[];
54
+ export declare class DiDesign {
55
+ /** Node id of this design's root (level 0). */
56
+ root: string;
57
+ rootKind: DiNodeKind;
58
+ /** Workspace-relative posix path of the root class's file. */
59
+ file: string;
60
+ /** Deepest level reached from the root (0 when the root has no injections). */
61
+ maxLevel: number;
47
62
  nodes: DiNode[];
48
63
  edges: DiEdge[];
49
64
  unresolved: string[];
65
+ constructor(root: string, rootKind: DiNodeKind, file: string);
66
+ }
67
+ /**
68
+ * The full per-project DI graph, serialized to design.json — a container over
69
+ * one `DiDesign` per root (controller or library top-of-DAG class).
70
+ */
71
+ export declare class DiGraph {
72
+ schemaVersion: number;
73
+ project: string;
74
+ designs: DiDesign[];
50
75
  constructor(project: string);
51
76
  }
52
77
  /**
@@ -5,11 +5,17 @@
5
5
  * Data classes for the per-project Inversify dependency DAG that is generated
6
6
  * into <projectRoot>/design.json and <projectRoot>/design.md on every build.
7
7
  *
8
+ * A project's design.json is a container (`DiGraph`) holding an ARRAY of
9
+ * per-root designs (`DiDesign`) — one self-contained downward tree per
10
+ * @Controller class (or, for controller-less library projects, per top-of-DAG
11
+ * class). Each node in a design carries its `level` = BFS depth from that
12
+ * design's root (root = level 0, its direct injections = level 1, and so on).
13
+ *
8
14
  * All structures are classes (not interfaces) per the repo convention for
9
15
  * data-only structures.
10
16
  */
11
17
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.Binding = exports.TokenRef = exports.DiGraph = exports.DiEdge = exports.DiNode = void 0;
18
+ exports.Binding = exports.TokenRef = exports.DiGraph = exports.DiDesign = exports.DiEdge = exports.DiNode = void 0;
13
19
  /**
14
20
  * A node in the DI graph — a class, a constant/dynamic binding leaf, or an
15
21
  * unresolved token placeholder.
@@ -20,12 +26,15 @@ class DiNode {
20
26
  kind;
21
27
  scope;
22
28
  file;
23
- constructor(id, className, kind, scope, file) {
29
+ /** BFS depth from the design's root (root = 0). Filled in after the walk. */
30
+ level;
31
+ constructor(id, className, kind, scope, file, level = 0) {
24
32
  this.id = id;
25
33
  this.className = className;
26
34
  this.kind = kind;
27
35
  this.scope = scope;
28
36
  this.file = file;
37
+ this.level = level;
29
38
  }
30
39
  }
31
40
  exports.DiNode = DiNode;
@@ -52,24 +61,47 @@ class DiEdge {
52
61
  }
53
62
  exports.DiEdge = DiEdge;
54
63
  /**
55
- * The full per-project DI graph, serialized to design.json.
64
+ * One self-contained DI design tree: a single root (a @Controller, or a
65
+ * library top-of-DAG class) and everything it injects "on down". Shared
66
+ * dependencies are duplicated across designs — each design is its own tree so
67
+ * it can be reviewed in isolation.
56
68
  */
57
- class DiGraph {
58
- schemaVersion;
59
- project;
60
- roots;
69
+ class DiDesign {
70
+ /** Node id of this design's root (level 0). */
71
+ root;
72
+ rootKind;
73
+ /** Workspace-relative posix path of the root class's file. */
74
+ file;
75
+ /** Deepest level reached from the root (0 when the root has no injections). */
76
+ maxLevel;
61
77
  nodes;
62
78
  edges;
63
79
  unresolved;
64
- constructor(project) {
65
- this.schemaVersion = 1;
66
- this.project = project;
67
- this.roots = [];
80
+ constructor(root, rootKind, file) {
81
+ this.root = root;
82
+ this.rootKind = rootKind;
83
+ this.file = file;
84
+ this.maxLevel = 0;
68
85
  this.nodes = [];
69
86
  this.edges = [];
70
87
  this.unresolved = [];
71
88
  }
72
89
  }
90
+ exports.DiDesign = DiDesign;
91
+ /**
92
+ * The full per-project DI graph, serialized to design.json — a container over
93
+ * one `DiDesign` per root (controller or library top-of-DAG class).
94
+ */
95
+ class DiGraph {
96
+ schemaVersion;
97
+ project;
98
+ designs;
99
+ constructor(project) {
100
+ this.schemaVersion = 2;
101
+ this.project = project;
102
+ this.designs = [];
103
+ }
104
+ }
73
105
  exports.DiGraph = DiGraph;
74
106
  /**
75
107
  * Canonical identity of a DI token — the key links `Symbol.for('X')` token
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/model.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAYH;;;GAGG;AACH,MAAa,MAAM;IACf,EAAE,CAAS;IACX,SAAS,CAAS;IAClB,IAAI,CAAa;IACjB,KAAK,CAAU;IACf,IAAI,CAAS;IAEb,YAAY,EAAU,EAAE,SAAiB,EAAE,IAAgB,EAAE,KAAc,EAAE,IAAY;QACrF,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACrB,CAAC;CACJ;AAdD,wBAcC;AAED;;GAEG;AACH,MAAa,MAAM;IACf,IAAI,CAAS;IACb,EAAE,CAAS;IACX,SAAS,CAAkB;IAC3B,KAAK,CAAS;IACd,QAAQ,CAAS;IACjB,SAAS,CAAS;IAClB,SAAS,CAAS;IAElB,YACI,IAAY,EACZ,EAAU,EACV,SAA0B,EAC1B,KAAa,EACb,QAAgB,EAChB,SAAiB,EACjB,SAAiB;QAEjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC/B,CAAC;CACJ;AA1BD,wBA0BC;AAED;;GAEG;AACH,MAAa,OAAO;IAChB,aAAa,CAAS;IACtB,OAAO,CAAS;IAChB,KAAK,CAAW;IAChB,KAAK,CAAW;IAChB,KAAK,CAAW;IAChB,UAAU,CAAW;IAErB,YAAY,OAAe;QACvB,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;IACzB,CAAC;CACJ;AAhBD,0BAgBC;AAED;;;;GAIG;AACH,MAAa,QAAQ;IACjB,GAAG,CAAS;IACZ,OAAO,CAAS;IAEhB,YAAY,GAAW,EAAE,OAAe;QACpC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;CACJ;AARD,4BAQC;AAED;;;GAGG;AACH,MAAa,OAAO;IAChB,QAAQ,CAAS;IACjB,YAAY,CAAS;IACrB,IAAI,CAAc;IAClB,KAAK,CAAU;IACf,wFAAwF;IACxF,SAAS,CAA6B;IACtC,qFAAqF;IACrF,SAAS,CAAS;IAClB,wEAAwE;IACxE,IAAI,CAAS;IAEb,YACI,QAAgB,EAChB,YAAoB,EACpB,IAAiB,EACjB,KAAc,EACd,SAAqC,EACrC,SAAiB,EACjB,IAAY;QAEZ,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACrB,CAAC;CACJ;AA7BD,0BA6BC","sourcesContent":["/**\n * DI Graph Model\n *\n * Data classes for the per-project Inversify dependency DAG that is generated\n * into <projectRoot>/design.json and <projectRoot>/design.md on every build.\n *\n * All structures are classes (not interfaces) per the repo convention for\n * data-only structures.\n */\n\nimport type * as ts from 'typescript';\n\nexport type DiNodeKind = 'controller' | 'class' | 'constant' | 'dynamic' | 'unresolved';\n\nexport type DiInjectionKind = 'token' | 'type' | 'multiInject';\n\nexport type DiScope = 'singleton' | 'transient' | 'unknown';\n\nexport type BindingKind = 'to' | 'toSelf' | 'toConstantValue' | 'toDynamicValue' | 'decorator';\n\n/**\n * A node in the DI graph — a class, a constant/dynamic binding leaf, or an\n * unresolved token placeholder.\n */\nexport class DiNode {\n id: string;\n className: string;\n kind: DiNodeKind;\n scope: DiScope;\n file: string;\n\n constructor(id: string, className: string, kind: DiNodeKind, scope: DiScope, file: string) {\n this.id = id;\n this.className = className;\n this.kind = kind;\n this.scope = scope;\n this.file = file;\n }\n}\n\n/**\n * A constructor-injection edge: `from` class injects `to` node.\n */\nexport class DiEdge {\n from: string;\n to: string;\n injection: DiInjectionKind;\n token: string;\n tokenKey: string;\n paramName: string;\n paramType: string;\n\n constructor(\n from: string,\n to: string,\n injection: DiInjectionKind,\n token: string,\n tokenKey: string,\n paramName: string,\n paramType: string,\n ) {\n this.from = from;\n this.to = to;\n this.injection = injection;\n this.token = token;\n this.tokenKey = tokenKey;\n this.paramName = paramName;\n this.paramType = paramType;\n }\n}\n\n/**\n * The full per-project DI graph, serialized to design.json.\n */\nexport class DiGraph {\n schemaVersion: number;\n project: string;\n roots: string[];\n nodes: DiNode[];\n edges: DiEdge[];\n unresolved: string[];\n\n constructor(project: string) {\n this.schemaVersion = 1;\n this.project = project;\n this.roots = [];\n this.nodes = [];\n this.edges = [];\n this.unresolved = [];\n }\n}\n\n/**\n * Canonical identity of a DI token — the key links `Symbol.for('X')` token\n * definitions to their bind() sites even across packages; display is what a\n * human reads in design.md edge labels (e.g. \"TYPES.Counter\").\n */\nexport class TokenRef {\n key: string;\n display: string;\n\n constructor(key: string, display: string) {\n this.key = key;\n this.display = display;\n }\n}\n\n/**\n * One binding discovered in pass 1 — either a ContainerModule bind() call or a\n * @provideSingleton/@provideSingletonAs/@provideTransient decorator.\n */\nexport class Binding {\n tokenKey: string;\n tokenDisplay: string;\n kind: BindingKind;\n scope: DiScope;\n /** Implementation class for to/toSelf/decorator bindings; null for constant/dynamic. */\n implClass: ts.ClassDeclaration | null;\n /** Source text of the bound expression for constant/dynamic leaves; '' otherwise. */\n valueText: string;\n /** Workspace-relative posix path of the file the binding appears in. */\n file: string;\n\n constructor(\n tokenKey: string,\n tokenDisplay: string,\n kind: BindingKind,\n scope: DiScope,\n implClass: ts.ClassDeclaration | null,\n valueText: string,\n file: string,\n ) {\n this.tokenKey = tokenKey;\n this.tokenDisplay = tokenDisplay;\n this.kind = kind;\n this.scope = scope;\n this.implClass = implClass;\n this.valueText = valueText;\n this.file = file;\n }\n}\n"]}
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/model.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DI Graph Model\n *\n * Data classes for the per-project Inversify dependency DAG that is generated\n * into <projectRoot>/design.json and <projectRoot>/design.md on every build.\n *\n * A project's design.json is a container (`DiGraph`) holding an ARRAY of\n * per-root designs (`DiDesign`) — one self-contained downward tree per\n * @Controller class (or, for controller-less library projects, per top-of-DAG\n * class). Each node in a design carries its `level` = BFS depth from that\n * design's root (root = level 0, its direct injections = level 1, and so on).\n *\n * All structures are classes (not interfaces) per the repo convention for\n * data-only structures.\n */\n\nimport type * as ts from 'typescript';\n\nexport type DiNodeKind = 'controller' | 'class' | 'constant' | 'dynamic' | 'unresolved';\n\nexport type DiInjectionKind = 'token' | 'type' | 'multiInject';\n\nexport type DiScope = 'singleton' | 'transient' | 'unknown';\n\nexport type BindingKind = 'to' | 'toSelf' | 'toConstantValue' | 'toDynamicValue' | 'decorator';\n\n/**\n * A node in the DI graph — a class, a constant/dynamic binding leaf, or an\n * unresolved token placeholder.\n */\nexport class DiNode {\n id: string;\n className: string;\n kind: DiNodeKind;\n scope: DiScope;\n file: string;\n /** BFS depth from the design's root (root = 0). Filled in after the walk. */\n level: number;\n\n constructor(id: string, className: string, kind: DiNodeKind, scope: DiScope, file: string, level = 0) {\n this.id = id;\n this.className = className;\n this.kind = kind;\n this.scope = scope;\n this.file = file;\n this.level = level;\n }\n}\n\n/**\n * A constructor-injection edge: `from` class injects `to` node.\n */\nexport class DiEdge {\n from: string;\n to: string;\n injection: DiInjectionKind;\n token: string;\n tokenKey: string;\n paramName: string;\n paramType: string;\n\n constructor(\n from: string,\n to: string,\n injection: DiInjectionKind,\n token: string,\n tokenKey: string,\n paramName: string,\n paramType: string,\n ) {\n this.from = from;\n this.to = to;\n this.injection = injection;\n this.token = token;\n this.tokenKey = tokenKey;\n this.paramName = paramName;\n this.paramType = paramType;\n }\n}\n\n/**\n * One self-contained DI design tree: a single root (a @Controller, or a\n * library top-of-DAG class) and everything it injects \"on down\". Shared\n * dependencies are duplicated across designs — each design is its own tree so\n * it can be reviewed in isolation.\n */\nexport class DiDesign {\n /** Node id of this design's root (level 0). */\n root: string;\n rootKind: DiNodeKind;\n /** Workspace-relative posix path of the root class's file. */\n file: string;\n /** Deepest level reached from the root (0 when the root has no injections). */\n maxLevel: number;\n nodes: DiNode[];\n edges: DiEdge[];\n unresolved: string[];\n\n constructor(root: string, rootKind: DiNodeKind, file: string) {\n this.root = root;\n this.rootKind = rootKind;\n this.file = file;\n this.maxLevel = 0;\n this.nodes = [];\n this.edges = [];\n this.unresolved = [];\n }\n}\n\n/**\n * The full per-project DI graph, serialized to design.json — a container over\n * one `DiDesign` per root (controller or library top-of-DAG class).\n */\nexport class DiGraph {\n schemaVersion: number;\n project: string;\n designs: DiDesign[];\n\n constructor(project: string) {\n this.schemaVersion = 2;\n this.project = project;\n this.designs = [];\n }\n}\n\n/**\n * Canonical identity of a DI token — the key links `Symbol.for('X')` token\n * definitions to their bind() sites even across packages; display is what a\n * human reads in design.md edge labels (e.g. \"TYPES.Counter\").\n */\nexport class TokenRef {\n key: string;\n display: string;\n\n constructor(key: string, display: string) {\n this.key = key;\n this.display = display;\n }\n}\n\n/**\n * One binding discovered in pass 1 — either a ContainerModule bind() call or a\n * @provideSingleton/@provideSingletonAs/@provideTransient decorator.\n */\nexport class Binding {\n tokenKey: string;\n tokenDisplay: string;\n kind: BindingKind;\n scope: DiScope;\n /** Implementation class for to/toSelf/decorator bindings; null for constant/dynamic. */\n implClass: ts.ClassDeclaration | null;\n /** Source text of the bound expression for constant/dynamic leaves; '' otherwise. */\n valueText: string;\n /** Workspace-relative posix path of the file the binding appears in. */\n file: string;\n\n constructor(\n tokenKey: string,\n tokenDisplay: string,\n kind: BindingKind,\n scope: DiScope,\n implClass: ts.ClassDeclaration | null,\n valueText: string,\n file: string,\n ) {\n this.tokenKey = tokenKey;\n this.tokenDisplay = tokenDisplay;\n this.kind = kind;\n this.scope = scope;\n this.implClass = implClass;\n this.valueText = valueText;\n this.file = file;\n }\n}\n"]}
Binary file
@@ -1 +1 @@
1
- {"version":3,"file":"serializer.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/serializer.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAmBH,8BAMC;AAGD,oCAGC;AA3BD,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACtC,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,OAAO,CAAC,IAAY;IACzB,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;AACxE,CAAC;AAED,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACtC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxB,OAAO,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,iEAAiE;AACjE,SAAgB,SAAS,CAAC,KAAc;IACpC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACnB,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC/B,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC/B,KAAK,CAAC,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,kEAAkE;AAClE,SAAgB,YAAY,CAAC,KAAc;IACvC,SAAS,CAAC,KAAK,CAAC,CAAC;IACjB,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AACjD,CAAC","sourcesContent":["/**\n * DI Graph Serializer\n *\n * Deterministic serialization for design.json: every collection sorted, no\n * timestamps, workspace-relative posix paths only — so the committed file is\n * byte-identical across regenerations of an unchanged graph.\n */\n\nimport { DiEdge, DiGraph, DiNode } from './model';\n\nfunction compareNodes(a: DiNode, b: DiNode): number {\n return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;\n}\n\nfunction edgeKey(edge: DiEdge): string {\n return `${edge.from}\u0000${edge.to}\u0000${edge.tokenKey}\u0000${edge.paramName}`;\n}\n\nfunction compareEdges(a: DiEdge, b: DiEdge): number {\n const keyA = edgeKey(a);\n const keyB = edgeKey(b);\n return keyA < keyB ? -1 : keyA > keyB ? 1 : 0;\n}\n\n/** Sort all graph collections in place (stable output order). */\nexport function sortGraph(graph: DiGraph): DiGraph {\n graph.roots.sort();\n graph.nodes.sort(compareNodes);\n graph.edges.sort(compareEdges);\n graph.unresolved = [...new Set(graph.unresolved)].sort();\n return graph;\n}\n\n/** Serialize a (sorted) graph to the design.json file content. */\nexport function toDesignJson(graph: DiGraph): string {\n sortGraph(graph);\n return JSON.stringify(graph, null, 4) + '\\n';\n}\n"]}
1
+ {"version":3,"file":"serializer.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/di-graph/serializer.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AA0BH,8BAMC;AAGD,oCAGC;AAlCD,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACtC,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,OAAO,CAAC,IAAY;IACzB,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;AACxE,CAAC;AAED,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACtC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxB,OAAO,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,8CAA8C;AAC9C,SAAS,UAAU,CAAC,MAAgB;IAChC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAChC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAChC,MAAM,CAAC,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC/D,CAAC;AAED,iEAAiE;AACjE,SAAgB,SAAS,CAAC,KAAc;IACpC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAW,EAAE,CAAW,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnG,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACjC,UAAU,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,kEAAkE;AAClE,SAAgB,YAAY,CAAC,KAAc;IACvC,SAAS,CAAC,KAAK,CAAC,CAAC;IACjB,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AACjD,CAAC","sourcesContent":["/**\n * DI Graph Serializer\n *\n * Deterministic serialization for design.json: every collection sorted, no\n * timestamps, workspace-relative posix paths only — so the committed file is\n * byte-identical across regenerations of an unchanged graph.\n */\n\nimport { DiDesign, DiEdge, DiGraph, DiNode } from './model';\n\nfunction compareNodes(a: DiNode, b: DiNode): number {\n return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;\n}\n\nfunction edgeKey(edge: DiEdge): string {\n return `${edge.from} ${edge.to} ${edge.tokenKey} ${edge.paramName}`;\n}\n\nfunction compareEdges(a: DiEdge, b: DiEdge): number {\n const keyA = edgeKey(a);\n const keyB = edgeKey(b);\n return keyA < keyB ? -1 : keyA > keyB ? 1 : 0;\n}\n\n/** Sort one design's collections in place. */\nfunction sortDesign(design: DiDesign): void {\n design.nodes.sort(compareNodes);\n design.edges.sort(compareEdges);\n design.unresolved = [...new Set(design.unresolved)].sort();\n}\n\n/** Sort all graph collections in place (stable output order). */\nexport function sortGraph(graph: DiGraph): DiGraph {\n graph.designs.sort((a: DiDesign, b: DiDesign) => (a.root < b.root ? -1 : a.root > b.root ? 1 : 0));\n for (const design of graph.designs) {\n sortDesign(design);\n }\n return graph;\n}\n\n/** Serialize a (sorted) graph to the design.json file content. */\nexport function toDesignJson(graph: DiGraph): string {\n sortGraph(graph);\n return JSON.stringify(graph, null, 4) + '\\n';\n}\n"]}