vouchington-tooling 0.33.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +181 -1
  2. package/dist/api-contract-discovery/index.d.mts +4 -1
  3. package/dist/api-contract-discovery/index.mjs +1 -0
  4. package/dist/api-contract-discovery/protocol-analysis-cache.d.mts +21 -0
  5. package/dist/api-contract-discovery/protocol-analysis-cache.mjs +7 -0
  6. package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +2 -1
  7. package/dist/api-contract-discovery/protocol-callback-invocation.mjs +12 -4
  8. package/dist/api-contract-discovery/protocol-callback-values.d.mts +2 -1
  9. package/dist/api-contract-discovery/protocol-callback-values.mjs +3 -3
  10. package/dist/api-contract-discovery/protocol-execution-path.d.mts +2 -1
  11. package/dist/api-contract-discovery/protocol-execution-path.mjs +2 -2
  12. package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +2 -1
  13. package/dist/api-contract-discovery/protocol-http-context-callers.mjs +13 -7
  14. package/dist/api-contract-discovery/query-contract-extraction.mjs +20 -3
  15. package/dist/api-contract-discovery/query-contract-literals.d.mts +5 -0
  16. package/dist/api-contract-discovery/query-contract-literals.mjs +29 -0
  17. package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +2 -1
  18. package/dist/api-contract-discovery/registered-route-binding-writes.mjs +20 -11
  19. package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +7 -1
  20. package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +27 -26
  21. package/dist/api-contract-discovery/registered-route-handler-helpers.d.mts +3 -0
  22. package/dist/api-contract-discovery/registered-route-handler-helpers.mjs +16 -0
  23. package/dist/api-contract-discovery/request-validation-callbacks.d.mts +9 -0
  24. package/dist/api-contract-discovery/request-validation-callbacks.mjs +27 -0
  25. package/dist/api-contract-discovery/request-validation-checker.d.mts +7 -0
  26. package/dist/api-contract-discovery/request-validation-checker.mjs +25 -0
  27. package/dist/api-contract-discovery/request-validation-conditional.d.mts +6 -0
  28. package/dist/api-contract-discovery/request-validation-conditional.mjs +31 -0
  29. package/dist/api-contract-discovery/request-validation-factory-calls.d.mts +11 -0
  30. package/dist/api-contract-discovery/request-validation-factory-calls.mjs +23 -0
  31. package/dist/api-contract-discovery/request-validation-facts.d.mts +16 -0
  32. package/dist/api-contract-discovery/request-validation-facts.mjs +64 -0
  33. package/dist/api-contract-discovery/request-validation-follow.d.mts +24 -0
  34. package/dist/api-contract-discovery/request-validation-follow.mjs +38 -0
  35. package/dist/api-contract-discovery/request-validation-handler-state.d.mts +8 -0
  36. package/dist/api-contract-discovery/request-validation-handler-state.mjs +35 -0
  37. package/dist/api-contract-discovery/request-validation-input.d.mts +12 -0
  38. package/dist/api-contract-discovery/request-validation-input.mjs +43 -0
  39. package/dist/api-contract-discovery/request-validation-keys.d.mts +25 -0
  40. package/dist/api-contract-discovery/request-validation-keys.mjs +119 -0
  41. package/dist/api-contract-discovery/request-validation-match.d.mts +11 -0
  42. package/dist/api-contract-discovery/request-validation-match.mjs +47 -0
  43. package/dist/api-contract-discovery/request-validation-mutation.d.mts +7 -0
  44. package/dist/api-contract-discovery/request-validation-mutation.mjs +80 -0
  45. package/dist/api-contract-discovery/request-validation-origin.d.mts +20 -0
  46. package/dist/api-contract-discovery/request-validation-origin.mjs +71 -0
  47. package/dist/api-contract-discovery/request-validation-reads.d.mts +13 -0
  48. package/dist/api-contract-discovery/request-validation-reads.mjs +88 -0
  49. package/dist/api-contract-discovery/request-validation-registration.d.mts +9 -0
  50. package/dist/api-contract-discovery/request-validation-registration.mjs +32 -0
  51. package/dist/api-contract-discovery/request-validation-report.d.mts +3 -0
  52. package/dist/api-contract-discovery/request-validation-report.mjs +21 -0
  53. package/dist/api-contract-discovery/request-validation-request-origin.d.mts +5 -0
  54. package/dist/api-contract-discovery/request-validation-request-origin.mjs +13 -0
  55. package/dist/api-contract-discovery/request-validation-select.d.mts +9 -0
  56. package/dist/api-contract-discovery/request-validation-select.mjs +36 -0
  57. package/dist/api-contract-discovery/request-validation-sites.d.mts +9 -0
  58. package/dist/api-contract-discovery/request-validation-sites.mjs +77 -0
  59. package/dist/api-contract-discovery/request-validation-state-key.d.mts +13 -0
  60. package/dist/api-contract-discovery/request-validation-state-key.mjs +30 -0
  61. package/dist/api-contract-discovery/request-validation-trace-helpers.d.mts +14 -0
  62. package/dist/api-contract-discovery/request-validation-trace-helpers.mjs +80 -0
  63. package/dist/api-contract-discovery/request-validation-trace-select.d.mts +23 -0
  64. package/dist/api-contract-discovery/request-validation-trace-select.mjs +45 -0
  65. package/dist/api-contract-discovery/request-validation-trace.d.mts +8 -0
  66. package/dist/api-contract-discovery/request-validation-trace.mjs +130 -0
  67. package/dist/api-contract-discovery/request-validation-types.d.mts +84 -0
  68. package/dist/api-contract-discovery/request-validation-types.mjs +1 -0
  69. package/dist/api-contract-discovery/request-validation-walker.d.mts +26 -0
  70. package/dist/api-contract-discovery/request-validation-walker.mjs +143 -0
  71. package/dist/api-contract-discovery/request-validation.test-helpers.d.mts +39 -0
  72. package/dist/api-contract-discovery/request-validation.test-helpers.mjs +108 -0
  73. package/dist/api-contract-discovery/response-contract-attribution-facts.d.mts +2 -0
  74. package/dist/api-contract-discovery/response-contract-attribution-facts.mjs +18 -0
  75. package/dist/api-contract-discovery/response-contract-attribution.d.mts +3 -0
  76. package/dist/api-contract-discovery/response-contract-attribution.mjs +23 -0
  77. package/dist/api-contract-discovery/response-contract-call-classification.d.mts +3 -0
  78. package/dist/api-contract-discovery/response-contract-call-classification.mjs +37 -0
  79. package/dist/api-contract-discovery/response-contract-error-branch.d.mts +1 -1
  80. package/dist/api-contract-discovery/response-contract-error-branch.mjs +109 -6
  81. package/dist/api-contract-discovery/response-contract-handler-bindings.d.mts +3 -0
  82. package/dist/api-contract-discovery/response-contract-handler-bindings.mjs +126 -0
  83. package/dist/api-contract-discovery/response-contract-handler-calls.d.mts +3 -0
  84. package/dist/api-contract-discovery/response-contract-handler-calls.mjs +81 -0
  85. package/dist/api-contract-discovery/response-contract-implicit-variants.d.mts +2 -0
  86. package/dist/api-contract-discovery/response-contract-implicit-variants.mjs +6 -0
  87. package/dist/api-contract-discovery/response-contract-implicit.d.mts +2 -1
  88. package/dist/api-contract-discovery/response-contract-implicit.mjs +9 -17
  89. package/dist/api-contract-discovery/response-contract-lenient.d.mts +18 -0
  90. package/dist/api-contract-discovery/response-contract-object-symbols.d.mts +3 -0
  91. package/dist/api-contract-discovery/response-contract-object-symbols.mjs +80 -0
  92. package/dist/api-contract-discovery/response-contract-property-mutations.d.mts +5 -0
  93. package/dist/api-contract-discovery/response-contract-property-mutations.mjs +84 -0
  94. package/dist/api-contract-discovery/response-contract-registration.d.mts +1 -1
  95. package/dist/api-contract-discovery/response-contract-registration.mjs +7 -3
  96. package/dist/api-contract-discovery/response-contract-registry.mjs +43 -8
  97. package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +3 -4
  98. package/dist/api-contract-discovery/response-contract-route-analysis.mjs +4 -101
  99. package/dist/api-contract-discovery/response-contract-route-syntax.d.mts +7 -0
  100. package/dist/api-contract-discovery/response-contract-route-syntax.mjs +41 -0
  101. package/dist/api-contract-discovery/response-contract-symbols.d.mts +5 -0
  102. package/dist/api-contract-discovery/response-contract-symbols.mjs +82 -0
  103. package/dist/compiler-build/index.d.mts +2 -0
  104. package/dist/compiler-build/index.mjs +1 -0
  105. package/dist/compiler-build/program-configuration.d.mts +9 -0
  106. package/dist/compiler-build/program-configuration.mjs +28 -0
  107. package/dist/compiler-build/program-types.d.mts +29 -0
  108. package/dist/compiler-build/program-types.mjs +1 -0
  109. package/dist/compiler-build/program.d.mts +7 -0
  110. package/dist/compiler-build/program.mjs +78 -0
  111. package/dist/contract-schema/index.d.mts +4 -0
  112. package/dist/contract-schema/index.mjs +2 -0
  113. package/dist/contract-schema/type-query-api.d.mts +44 -0
  114. package/dist/contract-schema/type-query-api.mjs +10 -0
  115. package/dist/contract-schema/type-query-call.d.mts +16 -0
  116. package/dist/contract-schema/type-query-call.mjs +52 -0
  117. package/dist/contract-schema/type-query-export.d.mts +10 -0
  118. package/dist/contract-schema/type-query-export.mjs +91 -0
  119. package/dist/contract-schema/type-query.d.mts +22 -0
  120. package/dist/contract-schema/type-query.mjs +39 -0
  121. package/dist/finite-enum-ripple/ast.d.mts +9 -0
  122. package/dist/finite-enum-ripple/ast.mjs +94 -0
  123. package/dist/finite-enum-ripple/check.d.mts +3 -0
  124. package/dist/finite-enum-ripple/check.mjs +62 -0
  125. package/dist/finite-enum-ripple/compare.d.mts +20 -0
  126. package/dist/finite-enum-ripple/compare.mjs +88 -0
  127. package/dist/finite-enum-ripple/component-paths.d.mts +8 -0
  128. package/dist/finite-enum-ripple/component-paths.mjs +30 -0
  129. package/dist/finite-enum-ripple/configured-object-mutations.d.mts +1 -0
  130. package/dist/finite-enum-ripple/configured-object-mutations.mjs +78 -0
  131. package/dist/finite-enum-ripple/create-page-jsx.d.mts +3 -0
  132. package/dist/finite-enum-ripple/create-page-jsx.mjs +28 -0
  133. package/dist/finite-enum-ripple/create-page-literals.d.mts +2 -0
  134. package/dist/finite-enum-ripple/create-page-literals.mjs +83 -0
  135. package/dist/finite-enum-ripple/create-page-object-visitor.d.mts +2 -0
  136. package/dist/finite-enum-ripple/create-page-object-visitor.mjs +57 -0
  137. package/dist/finite-enum-ripple/create-page-properties.d.mts +4 -0
  138. package/dist/finite-enum-ripple/create-page-properties.mjs +35 -0
  139. package/dist/finite-enum-ripple/index.d.mts +2 -0
  140. package/dist/finite-enum-ripple/index.mjs +1 -0
  141. package/dist/finite-enum-ripple/model.d.mts +78 -0
  142. package/dist/finite-enum-ripple/model.mjs +1 -0
  143. package/dist/finite-enum-ripple/parser-support.d.mts +11 -0
  144. package/dist/finite-enum-ripple/parser-support.mjs +99 -0
  145. package/dist/finite-enum-ripple/parsers.d.mts +32 -0
  146. package/dist/finite-enum-ripple/parsers.mjs +115 -0
  147. package/dist/finite-enum-ripple/pattern-captures.d.mts +2 -0
  148. package/dist/finite-enum-ripple/pattern-captures.mjs +45 -0
  149. package/dist/finite-enum-ripple/structured-route-check.d.mts +3 -0
  150. package/dist/finite-enum-ripple/structured-route-check.mjs +94 -0
  151. package/dist/finite-enum-ripple/types.d.mts +2 -0
  152. package/dist/finite-enum-ripple/types.mjs +60 -0
  153. package/dist/finite-enum-ripple/union-factory-check.d.mts +2 -0
  154. package/dist/finite-enum-ripple/union-factory-check.mjs +34 -0
  155. package/dist/finite-enum-ripple/union-types.d.mts +2 -0
  156. package/dist/finite-enum-ripple/union-types.mjs +129 -0
  157. package/dist/openapi-document/build-openapi-query.mjs +1 -1
  158. package/dist/openapi-document/openapi-types.d.mts +1 -1
  159. package/dist/openapi-document/query-types.d.mts +8 -2
  160. package/dist/post-publication-inventory/index.d.mts +5 -0
  161. package/dist/post-publication-inventory/index.mjs +2 -0
  162. package/dist/post-publication-inventory/readers/sql-executors.d.mts +3 -0
  163. package/dist/post-publication-inventory/readers/sql-executors.mjs +32 -0
  164. package/dist/post-publication-inventory/readers/sql-source.d.mts +7 -0
  165. package/dist/post-publication-inventory/readers/sql-source.mjs +87 -0
  166. package/dist/post-publication-inventory/readers/sql-template.d.mts +12 -0
  167. package/dist/post-publication-inventory/readers/sql-template.mjs +119 -0
  168. package/dist/post-publication-inventory/writers/source.d.mts +9 -0
  169. package/dist/post-publication-inventory/writers/source.mjs +128 -0
  170. package/dist/post-publication-inventory/writers/types.d.mts +19 -0
  171. package/dist/post-publication-inventory/writers/types.mjs +1 -0
  172. package/dist/workflow-policy/index.d.mts +1 -3
  173. package/dist/workflow-policy/index.mjs +0 -2
  174. package/dist/workflow-policy/types.d.mts +2 -28
  175. package/docs/workflow-and-compiler.md +7 -16
  176. package/package.json +13 -2
  177. package/dist/workflow-policy/concurrency-scope.d.mts +0 -4
  178. package/dist/workflow-policy/concurrency-scope.mjs +0 -66
  179. package/dist/workflow-policy/concurrency.d.mts +0 -10
  180. package/dist/workflow-policy/concurrency.mjs +0 -77
  181. package/dist/workflow-policy/graph.d.mts +0 -2
  182. package/dist/workflow-policy/graph.mjs +0 -138
@@ -0,0 +1,126 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { executableProtocolPath } from './protocol-execution-path.mjs';
3
+ import { potentiallyExecuted } from './protocol-executable-path.mjs';
4
+ import { attributionSymbol, resolveSymbol } from './response-contract-symbols.mjs';
5
+ import { findMutatedBindings, findMutatedProperties, } from './response-contract-property-mutations.mjs';
6
+ import { handlerArgumentSymbols, isMutatedPropertyAccess, } from './response-contract-handler-calls.mjs';
7
+ import { HTTP_METHODS, propertyName, routeTemplateFromExpression, unwrapTransparentExpression, visit, } from './response-contract-route-syntax.mjs';
8
+ import { destructuredPropertyImplementationSymbol, propertyImplementationSymbol, } from './response-contract-object-symbols.mjs';
9
+ export function collectHandlerBindings(sourceFiles, checker, ambiguousBindings) {
10
+ const bindingsBySymbol = new Map();
11
+ const attributionBindingsBySymbol = new Map();
12
+ const mutatedProperties = ambiguousBindings
13
+ ? findMutatedProperties(sourceFiles, checker)
14
+ : undefined;
15
+ const mutatedBindings = ambiguousBindings ? findMutatedBindings(sourceFiles, checker) : undefined;
16
+ for (const sourceFile of sourceFiles) {
17
+ visit(sourceFile, (node) => {
18
+ if (!ts.isCallExpression(node))
19
+ return;
20
+ const method = propertyName(node.expression)?.toUpperCase();
21
+ if (!method || !HTTP_METHODS.has(method))
22
+ return;
23
+ const routeTemplate = routeTemplateFromExpression(node.expression);
24
+ if (!routeTemplate)
25
+ return;
26
+ const binding = { method, routeTemplate };
27
+ for (const symbol of handlerArgumentSymbols(node, checker)) {
28
+ const resolved = resolveSymbol(symbol, checker);
29
+ const candidates = bindingsBySymbol.get(resolved) ?? [];
30
+ candidates.push(binding);
31
+ bindingsBySymbol.set(resolved, candidates);
32
+ }
33
+ if (ambiguousBindings) {
34
+ if (!potentiallyExecuted(node))
35
+ return;
36
+ if (!isExportedRouteBuilder(node) && !executableProtocolPath(node, checker))
37
+ return;
38
+ for (const symbol of handlerArgumentSymbols(node, checker, true, mutatedProperties)) {
39
+ const propertySymbol = attributionSymbol(resolveSymbol(symbol, checker), checker);
40
+ if (mutatedProperties?.has(propertySymbol))
41
+ continue;
42
+ const handlerSymbol = resolveHandlerSymbol(symbol, checker, mutatedProperties);
43
+ const resolvedAttributionSymbol = attributionSymbol(handlerSymbol, checker);
44
+ if (mutatedProperties?.has(resolvedAttributionSymbol))
45
+ continue;
46
+ if (mutatedBindings?.has(resolvedAttributionSymbol))
47
+ continue;
48
+ const attributionCandidates = attributionBindingsBySymbol.get(resolvedAttributionSymbol) ?? [];
49
+ attributionCandidates.push(binding);
50
+ attributionBindingsBySymbol.set(resolvedAttributionSymbol, attributionCandidates);
51
+ }
52
+ }
53
+ });
54
+ }
55
+ const bindings = new Map();
56
+ for (const [symbol, candidates] of bindingsBySymbol) {
57
+ const [first] = candidates;
58
+ const unambiguous = candidates.every((candidate) => candidate.method === first.method && candidate.routeTemplate === first.routeTemplate);
59
+ if (unambiguous) {
60
+ bindings.set(symbol, first);
61
+ }
62
+ }
63
+ if (ambiguousBindings) {
64
+ for (const [symbol, candidates] of attributionBindingsBySymbol) {
65
+ const [first] = candidates;
66
+ const unambiguous = candidates.every((candidate) => candidate.method === first.method && candidate.routeTemplate === first.routeTemplate);
67
+ if (!unambiguous) {
68
+ ambiguousBindings.set(symbol, [
69
+ ...new Set(candidates.map(({ method, routeTemplate }) => `${method}:${routeTemplate}`)),
70
+ ].toSorted());
71
+ }
72
+ }
73
+ }
74
+ return bindings;
75
+ }
76
+ function isExportedRouteBuilder(node) {
77
+ let current = node.parent;
78
+ while (current) {
79
+ if (ts.isFunctionLike(current))
80
+ return (ts.isFunctionDeclaration(current) &&
81
+ !!(ts.getCombinedModifierFlags(current) &
82
+ (ts.ModifierFlags.Export | ts.ModifierFlags.Default)));
83
+ current = current.parent;
84
+ }
85
+ return false;
86
+ }
87
+ function resolveHandlerSymbol(symbol, checker, mutatedProperties) {
88
+ const seen = new Set();
89
+ let current = resolveSymbol(symbol, checker);
90
+ while (!seen.has(current)) {
91
+ seen.add(current);
92
+ const declaration = current.valueDeclaration;
93
+ if (declaration && ts.isBindingElement(declaration)) {
94
+ const value = destructuredPropertyImplementationSymbol(declaration, checker);
95
+ if (!value)
96
+ return current;
97
+ current = resolveSymbol(value, checker);
98
+ continue;
99
+ }
100
+ if (declaration && ts.isShorthandPropertyAssignment(declaration)) {
101
+ const value = checker.getShorthandAssignmentValueSymbol(declaration);
102
+ if (!value)
103
+ return current;
104
+ current = resolveSymbol(value, checker);
105
+ continue;
106
+ }
107
+ if (!declaration || !ts.isVariableDeclaration(declaration) || !declaration.initializer)
108
+ return current;
109
+ if (!ts.isVariableDeclarationList(declaration.parent) ||
110
+ !(declaration.parent.flags & ts.NodeFlags.Const))
111
+ return current;
112
+ const initializer = unwrapTransparentExpression(declaration.initializer);
113
+ if (ts.isPropertyAccessExpression(initializer) &&
114
+ isMutatedPropertyAccess(initializer, checker, mutatedProperties))
115
+ return current;
116
+ const next = ts.isIdentifier(initializer)
117
+ ? checker.getSymbolAtLocation(initializer)
118
+ : ts.isPropertyAccessExpression(initializer)
119
+ ? propertyImplementationSymbol(initializer, checker)
120
+ : undefined;
121
+ if (!next)
122
+ return current;
123
+ current = resolveSymbol(next, checker);
124
+ }
125
+ return current;
126
+ }
@@ -0,0 +1,3 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ export declare function handlerArgumentSymbols(node: ts.CallExpression, checker: ts.TypeChecker, unwrapArguments?: boolean, mutatedProperties?: ReadonlySet<ts.Symbol>): ts.Symbol[];
3
+ export declare function isMutatedPropertyAccess(expression: ts.Expression, checker: ts.TypeChecker, mutatedProperties: ReadonlySet<ts.Symbol> | undefined): boolean;
@@ -0,0 +1,81 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { executableProtocolPath } from './protocol-execution-path.mjs';
3
+ import { attributionSymbol, resolveSymbol } from './response-contract-symbols.mjs';
4
+ import { propertyImplementationSymbol } from './response-contract-object-symbols.mjs';
5
+ import { isInlineRouteHandler, unwrapTransparentExpression, } from './response-contract-route-syntax.mjs';
6
+ export function handlerArgumentSymbols(node, checker, unwrapArguments = false, mutatedProperties) {
7
+ const symbols = [];
8
+ for (const originalArgument of node.arguments) {
9
+ const argument = unwrapArguments
10
+ ? unwrapTransparentExpression(originalArgument)
11
+ : originalArgument;
12
+ if (ts.isIdentifier(argument)) {
13
+ const symbol = checker.getSymbolAtLocation(argument);
14
+ if (symbol)
15
+ symbols.push(symbol);
16
+ continue;
17
+ }
18
+ if (unwrapArguments && ts.isPropertyAccessExpression(argument)) {
19
+ const mutatedSymbol = mutatedPropertyAccessSymbol(argument, checker, mutatedProperties);
20
+ if (mutatedSymbol) {
21
+ symbols.push(mutatedSymbol);
22
+ continue;
23
+ }
24
+ const symbol = propertyImplementationSymbol(argument, checker);
25
+ if (symbol)
26
+ symbols.push(symbol);
27
+ continue;
28
+ }
29
+ if (!isFunctionLike(argument))
30
+ continue;
31
+ visitHandlerCalls(argument, argument, checker, unwrapArguments, (child) => {
32
+ const callee = unwrapArguments
33
+ ? unwrapTransparentExpression(child.expression)
34
+ : child.expression;
35
+ const mutatedSymbol = mutatedPropertyAccessSymbol(callee, checker, mutatedProperties);
36
+ if (mutatedSymbol) {
37
+ symbols.push(mutatedSymbol);
38
+ return;
39
+ }
40
+ const symbol = handlerSymbol(callee, checker, unwrapArguments);
41
+ if (symbol)
42
+ symbols.push(symbol);
43
+ });
44
+ }
45
+ return symbols;
46
+ }
47
+ export function isMutatedPropertyAccess(expression, checker, mutatedProperties) {
48
+ return !!mutatedPropertyAccessSymbol(expression, checker, mutatedProperties);
49
+ }
50
+ function mutatedPropertyAccessSymbol(expression, checker, mutatedProperties) {
51
+ if (!mutatedProperties || !ts.isPropertyAccessExpression(expression))
52
+ return undefined;
53
+ const contextual = checker.getSymbolAtLocation(expression.name);
54
+ return contextual &&
55
+ mutatedProperties.has(attributionSymbol(resolveSymbol(contextual, checker), checker))
56
+ ? contextual
57
+ : undefined;
58
+ }
59
+ function handlerSymbol(expression, checker, resolvePropertyImplementation) {
60
+ if (resolvePropertyImplementation && ts.isPropertyAccessExpression(expression))
61
+ return propertyImplementationSymbol(expression, checker);
62
+ return checker.getSymbolAtLocation(expression);
63
+ }
64
+ function visitHandlerCalls(node, root, checker, attributeNestedCalls, onCall) {
65
+ if (node !== root && isInlineRouteHandler(node))
66
+ return;
67
+ const callee = ts.isCallExpression(node)
68
+ ? attributeNestedCalls
69
+ ? unwrapTransparentExpression(node.expression)
70
+ : node.expression
71
+ : undefined;
72
+ if (ts.isCallExpression(node) &&
73
+ !!callee &&
74
+ (ts.isIdentifier(callee) || (attributeNestedCalls && ts.isPropertyAccessExpression(callee))) &&
75
+ executableProtocolPath(node, checker, attributeNestedCalls ? root : undefined))
76
+ onCall(node);
77
+ ts.forEachChild(node, (child) => visitHandlerCalls(child, root, checker, attributeNestedCalls, onCall));
78
+ }
79
+ function isFunctionLike(node) {
80
+ return ts.isArrowFunction(node) || ts.isFunctionExpression(node);
81
+ }
@@ -0,0 +1,2 @@
1
+ import type { BackendResponseContract } from './response-contract-types.mts';
2
+ export declare function nextImplicitVariantKey(contracts: Map<string, BackendResponseContract>, key: string): string;
@@ -0,0 +1,6 @@
1
+ export function nextImplicitVariantKey(contracts, key) {
2
+ let suffix = 2;
3
+ while (contracts.has(`${key}#implicit-${suffix}`))
4
+ suffix++;
5
+ return `${key}#implicit-${suffix}`;
6
+ }
@@ -1,5 +1,6 @@
1
1
  import ts from '../contract-schema/typescript-api.mts';
2
+ import type { ImplicitResponseCallLabel } from './response-contract-call-classification.mts';
2
3
  import { type DiscoverApiResponseContractsOptions } from './response-contract-lenient.mts';
3
4
  import { type HandlerBindings } from './response-contract-route-analysis.mts';
4
5
  import type { BackendResponseContract } from './response-contract-types.mts';
5
- export declare function discoverImplicitContract(call: ts.CallExpression, checker: ts.TypeChecker, sourceFile: ts.SourceFile, contracts: Map<string, BackendResponseContract>, handlerBindings: HandlerBindings, requestedKeys: ReadonlySet<string> | undefined, options: DiscoverApiResponseContractsOptions | undefined): void;
6
+ export declare function discoverImplicitContract(call: ts.CallExpression, checker: ts.TypeChecker, sourceFile: ts.SourceFile, contracts: Map<string, BackendResponseContract>, handlerBindings: HandlerBindings, callLabel: ImplicitResponseCallLabel | undefined, requestedKeys: ReadonlySet<string> | undefined, options: DiscoverApiResponseContractsOptions | undefined): void;
@@ -1,15 +1,16 @@
1
1
  import ts from '../contract-schema/typescript-api.mjs';
2
2
  import { extractContractSchema } from '../contract-schema/index.mjs';
3
- import { containsResponseMarker, noContentContract, registerContract, responseBodyExpression, sourceLocation, } from './response-contract-registration.mjs';
4
3
  import { isInErrorBranch } from './response-contract-error-branch.mjs';
5
- import { isXmlResponseCall, streamingTextMediaType } from './response-contract-media.mjs';
4
+ import { containsResponseMarker, noContentContract, registerContract, responseBodyExpression, sourceLocation, } from './response-contract-registration.mjs';
5
+ import { streamingTextMediaType } from './response-contract-media.mjs';
6
+ import { nextImplicitVariantKey } from './response-contract-implicit-variants.mjs';
6
7
  import { markBufferedRouteUnavailable, registerRouteContract, } from './response-contract-lenient.mjs';
7
- import { enclosingRouteBinding, isContextMethod, isContextResponseBufferCall, isContextResponseEmptyCall, requestedKeyForBinding, responseMarker, } from './response-contract-route-analysis.mjs';
8
+ import { enclosingRouteBinding, isContextMethod, requestedKeyForBinding, responseMarker, } from './response-contract-route-analysis.mjs';
8
9
  import { resolveEmissionStatus } from './response-contract-status.mjs';
9
10
  import { opaqueHttpResponse, unsupportedContextResponse, supportedResponseContext, } from './protocol-http-association.mjs';
10
11
  import { contextResponseMethod } from './protocol-http-context.mjs';
11
12
  import { taintRouteKeys, markRouteTaint, isSseSetter } from './protocol-http-implicit-taint.mjs';
12
- export function discoverImplicitContract(call, checker, sourceFile, contracts, handlerBindings, requestedKeys, options) {
13
+ export function discoverImplicitContract(call, checker, sourceFile, contracts, handlerBindings, callLabel, requestedKeys, options) {
13
14
  const opaqueResponse = opaqueHttpResponse(call, checker);
14
15
  const binding = enclosingRouteBinding(call, checker, handlerBindings, !opaqueResponse);
15
16
  if (!binding)
@@ -63,12 +64,10 @@ export function discoverImplicitContract(call, checker, sourceFile, contracts, h
63
64
  registerRouteContract(contracts, nextImplicitVariantKey(contracts, key), binding, location, emission, extract, options);
64
65
  return;
65
66
  }
66
- if (isXmlResponseCall(call)) {
67
+ if (callLabel === 'ctx.response.xml()') {
67
68
  if (requestedKeys && contracts.has(key))
68
69
  return;
69
70
  const body = call.arguments[0];
70
- if (!body)
71
- return;
72
71
  const location = sourceLocation(sourceFile, call);
73
72
  registerRouteContract(contracts, contracts.has(key) ? nextImplicitVariantKey(contracts, key) : key, binding, location, {
74
73
  bodyKind: 'content',
@@ -92,8 +91,8 @@ export function discoverImplicitContract(call, checker, sourceFile, contracts, h
92
91
  return;
93
92
  }
94
93
  // Empty branches must not hide any unmarked buffer or pipeline body.
95
- if (isContextResponseBufferCall(call.expression) ||
96
- (isContextMethod(call.expression, 'pipeline') && !body && !containsResponseMarker(call))) {
94
+ if (callLabel === 'ctx.response.buffer()' ||
95
+ (callLabel === 'ctx.pipeline()' && !body && !containsResponseMarker(call))) {
97
96
  markBufferedRouteUnavailable(contracts, key, binding, sourceLocation(sourceFile, call));
98
97
  return;
99
98
  }
@@ -104,7 +103,7 @@ export function discoverImplicitContract(call, checker, sourceFile, contracts, h
104
103
  call.arguments[0] &&
105
104
  ts.isNumericLiteral(call.arguments[0]) &&
106
105
  (call.arguments[0].text === '204' || call.arguments[0].text === '205')) ||
107
- isContextResponseEmptyCall(call.expression);
106
+ callLabel === 'ctx.response.empty()';
108
107
  if (isExplicitNoContentCall) {
109
108
  registerContract(contracts, contracts.has(key) ? nextImplicitVariantKey(contracts, key) : key, {
110
109
  ...noContentContract(sourceLocation(sourceFile, call)),
@@ -119,10 +118,3 @@ export function discoverImplicitContract(call, checker, sourceFile, contracts, h
119
118
  });
120
119
  }
121
120
  }
122
- /** Picks the next deterministic registration key for another implicit response variant. */
123
- function nextImplicitVariantKey(contracts, key) {
124
- let suffix = 2;
125
- while (contracts.has(`${key}#implicit-${suffix}`))
126
- suffix++;
127
- return `${key}#implicit-${suffix}`;
128
- }
@@ -7,8 +7,26 @@ export type RouteDiscoveryError = {
7
7
  source: string;
8
8
  reason: string;
9
9
  };
10
+ /**
11
+ * A response emitter found in a named handler registered for multiple routes. `sourceLocation`
12
+ * is a normalized source path followed by one-based line and column; `routes` contains unique,
13
+ * lexically sorted `METHOD:path` keys. `label` identifies the implicit response form.
14
+ */
15
+ export type AmbiguousAttributionFact = {
16
+ sourceLocation: string;
17
+ label: string;
18
+ routes: readonly string[];
19
+ };
10
20
  export type DiscoverApiResponseContractsOptions = ExtractContractSchemaOptions & {
11
21
  onRouteError?: (error: RouteDiscoveryError) => void;
22
+ /**
23
+ * Opt in to response-attribution facts from the existing discovery pass. Facts are delivered
24
+ * after discovery succeeds, ordered by normalized source location; each callback receives its
25
+ * own copy of the sorted route list. Dynamic error payloads paired with a non-literal status are
26
+ * excluded from these facts; discovery output keeps its existing behavior. Consumers choose
27
+ * whether a fact needs a diagnostic or policy.
28
+ */
29
+ onAmbiguousAttribution?: (fact: AmbiguousAttributionFact) => void;
12
30
  };
13
31
  /** Marks `key` unavailable regardless of AST visit order, for a route that can send a raw buffered body (e.g. a proxied MCP response) alongside a no-content branch. */
14
32
  export declare function markBufferedRouteUnavailable(contracts: Map<string, BackendResponseContract>, key: string, binding: RouteBinding, source: string): void;
@@ -0,0 +1,3 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ export declare function propertyImplementationSymbol(access: ts.PropertyAccessExpression, checker: ts.TypeChecker): ts.Symbol | undefined;
3
+ export declare function destructuredPropertyImplementationSymbol(declaration: ts.BindingElement, checker: ts.TypeChecker): ts.Symbol | undefined;
@@ -0,0 +1,80 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { unwrapTransparentExpression } from './response-contract-route-syntax.mjs';
3
+ export function propertyImplementationSymbol(access, checker) {
4
+ const contextual = checker.getSymbolAtLocation(access.name);
5
+ const receiver = unwrapTransparentExpression(access.expression);
6
+ if (!contextual || !ts.isIdentifier(receiver))
7
+ return undefined;
8
+ const receiverSymbol = checker.getSymbolAtLocation(receiver);
9
+ const declaration = receiverSymbol?.valueDeclaration;
10
+ if (!declaration || !ts.isVariableDeclaration(declaration))
11
+ return contextual;
12
+ if (!declaration.initializer ||
13
+ !ts.isVariableDeclarationList(declaration.parent) ||
14
+ !(declaration.parent.flags & ts.NodeFlags.Const))
15
+ return undefined;
16
+ return constObjectPropertySymbol(receiver, access.name.text, checker);
17
+ }
18
+ export function destructuredPropertyImplementationSymbol(declaration, checker) {
19
+ const pattern = declaration.parent;
20
+ if (!ts.isObjectBindingPattern(pattern) || !ts.isIdentifier(declaration.name))
21
+ return undefined;
22
+ const variable = pattern.parent;
23
+ if (!ts.isVariableDeclaration(variable) ||
24
+ !variable.initializer ||
25
+ !ts.isVariableDeclarationList(variable.parent) ||
26
+ !(variable.parent.flags & ts.NodeFlags.Const))
27
+ return undefined;
28
+ if (declaration.dotDotDotToken || declaration.initializer)
29
+ return undefined;
30
+ const property = declaration.propertyName ?? declaration.name;
31
+ if (!ts.isIdentifier(property) && !ts.isStringLiteral(property))
32
+ return undefined;
33
+ const initializer = unwrapTransparentExpression(variable.initializer);
34
+ return ts.isIdentifier(initializer)
35
+ ? constObjectPropertySymbol(initializer, property.text, checker)
36
+ : undefined;
37
+ }
38
+ function constObjectPropertySymbol(receiver, key, checker, seen = new Set()) {
39
+ const receiverSymbol = checker.getSymbolAtLocation(receiver);
40
+ if (!receiverSymbol || seen.has(receiverSymbol))
41
+ return undefined;
42
+ seen.add(receiverSymbol);
43
+ const declaration = receiverSymbol.valueDeclaration;
44
+ if (!declaration ||
45
+ !ts.isVariableDeclaration(declaration) ||
46
+ !declaration.initializer ||
47
+ !ts.isVariableDeclarationList(declaration.parent) ||
48
+ !(declaration.parent.flags & ts.NodeFlags.Const))
49
+ return undefined;
50
+ const initializer = unwrapTransparentExpression(declaration.initializer);
51
+ if (ts.isIdentifier(initializer))
52
+ return constObjectPropertySymbol(initializer, key, checker, seen);
53
+ if (!ts.isObjectLiteralExpression(initializer))
54
+ return undefined;
55
+ if (initializer.properties.some(ts.isSpreadAssignment))
56
+ return undefined;
57
+ const implementation = initializer.properties.findLast((property) => {
58
+ const name = property.name;
59
+ return objectLiteralPropertyName(name) === key;
60
+ });
61
+ if (implementation && ts.isPropertyAssignment(implementation)) {
62
+ const value = unwrapTransparentExpression(implementation.initializer);
63
+ if (ts.isIdentifier(value))
64
+ return checker.getSymbolAtLocation(value);
65
+ }
66
+ if (implementation &&
67
+ (ts.isGetAccessorDeclaration(implementation) || ts.isSetAccessorDeclaration(implementation)))
68
+ return undefined;
69
+ return implementation?.name && checker.getSymbolAtLocation(implementation.name);
70
+ }
71
+ function objectLiteralPropertyName(name) {
72
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name))
73
+ return name.text;
74
+ if (!ts.isComputedPropertyName(name))
75
+ return undefined;
76
+ const expression = unwrapTransparentExpression(name.expression);
77
+ return ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)
78
+ ? expression.text
79
+ : undefined;
80
+ }
@@ -0,0 +1,5 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ /** Direct syntactic property writes are excluded; indirect mutation is outside this proof. */
3
+ export declare function findMutatedProperties(sourceFiles: readonly ts.SourceFile[], checker: ts.TypeChecker): Set<ts.Symbol>;
4
+ /** Direct identifier and destructuring writes are excluded; indirect writes are unproven. */
5
+ export declare function findMutatedBindings(sourceFiles: readonly ts.SourceFile[], checker: ts.TypeChecker): Set<ts.Symbol>;
@@ -0,0 +1,84 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { attributionSymbol, resolveSymbol } from './response-contract-symbols.mjs';
3
+ import { unwrapTransparentExpression, visit } from './response-contract-route-syntax.mjs';
4
+ /** Direct syntactic property writes are excluded; indirect mutation is outside this proof. */
5
+ export function findMutatedProperties(sourceFiles, checker) {
6
+ const mutated = new Set();
7
+ for (const sourceFile of sourceFiles) {
8
+ visit(sourceFile, (node) => {
9
+ let access;
10
+ if (ts.isBinaryExpression(node) &&
11
+ node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
12
+ node.operatorToken.kind <= ts.SyntaxKind.LastAssignment &&
13
+ ts.isPropertyAccessExpression(unwrapTransparentExpression(node.left)))
14
+ access = unwrapTransparentExpression(node.left);
15
+ else if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
16
+ (node.operator === ts.SyntaxKind.PlusPlusToken ||
17
+ node.operator === ts.SyntaxKind.MinusMinusToken) &&
18
+ ts.isPropertyAccessExpression(node.operand))
19
+ access = node.operand;
20
+ if (!access)
21
+ return;
22
+ const symbol = checker.getSymbolAtLocation(access.name);
23
+ if (symbol)
24
+ mutated.add(attributionSymbol(resolveSymbol(symbol, checker), checker));
25
+ });
26
+ }
27
+ return mutated;
28
+ }
29
+ /** Direct identifier and destructuring writes are excluded; indirect writes are unproven. */
30
+ export function findMutatedBindings(sourceFiles, checker) {
31
+ const mutated = new Set();
32
+ for (const sourceFile of sourceFiles) {
33
+ visit(sourceFile, (node) => {
34
+ let identifiers = [];
35
+ if (ts.isBinaryExpression(node) &&
36
+ node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
37
+ node.operatorToken.kind <= ts.SyntaxKind.LastAssignment &&
38
+ isAssignmentTarget(node.left))
39
+ identifiers = assignmentTargetIdentifiers(node.left);
40
+ else if ((ts.isForInStatement(node) || ts.isForOfStatement(node)) &&
41
+ !ts.isVariableDeclarationList(node.initializer) &&
42
+ isAssignmentTarget(node.initializer))
43
+ identifiers = assignmentTargetIdentifiers(node.initializer);
44
+ else if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
45
+ (node.operator === ts.SyntaxKind.PlusPlusToken ||
46
+ node.operator === ts.SyntaxKind.MinusMinusToken) &&
47
+ ts.isIdentifier(node.operand))
48
+ identifiers = [node.operand];
49
+ for (const identifier of identifiers) {
50
+ const symbol = checker.getSymbolAtLocation(identifier);
51
+ if (symbol)
52
+ mutated.add(attributionSymbol(resolveSymbol(symbol, checker), checker));
53
+ }
54
+ });
55
+ }
56
+ return mutated;
57
+ }
58
+ function isAssignmentTarget(expression) {
59
+ const target = unwrapTransparentExpression(expression);
60
+ return (ts.isIdentifier(target) ||
61
+ ts.isArrayLiteralExpression(target) ||
62
+ ts.isObjectLiteralExpression(target));
63
+ }
64
+ function assignmentTargetIdentifiers(expression) {
65
+ const target = unwrapTransparentExpression(expression);
66
+ if (ts.isIdentifier(target))
67
+ return [target];
68
+ if (ts.isArrayLiteralExpression(target))
69
+ return target.elements.flatMap((element) => ts.isOmittedExpression(element)
70
+ ? []
71
+ : assignmentTargetIdentifiers(ts.isSpreadElement(element) ? element.expression : element));
72
+ if (!ts.isObjectLiteralExpression(target))
73
+ return [];
74
+ const identifiers = [];
75
+ for (const property of target.properties) {
76
+ if (ts.isShorthandPropertyAssignment(property))
77
+ identifiers.push(property.name);
78
+ else if (ts.isPropertyAssignment(property))
79
+ identifiers.push(...assignmentTargetIdentifiers(property.initializer));
80
+ else if (ts.isSpreadAssignment(property))
81
+ identifiers.push(...assignmentTargetIdentifiers(property.expression));
82
+ }
83
+ return identifiers;
84
+ }
@@ -1,7 +1,7 @@
1
1
  import ts from '../contract-schema/typescript-api.mts';
2
2
  import type { ExtractedResponseContract, ExtractContractSchemaOptions } from '../contract-schema/index.mts';
3
3
  import type { BackendResponseContract } from './response-contract-types.mts';
4
- export declare function responseBodyExpression(call: ts.CallExpression): ts.Expression | undefined;
4
+ export declare function responseBodyExpression(call: ts.CallExpression, unwrapPipelineArguments?: boolean): ts.Expression | undefined;
5
5
  export declare function containsResponseMarker(node: ts.Node): boolean;
6
6
  export declare function extractBodyContract(call: ts.CallExpression, checker: ts.TypeChecker, sourceFile: ts.SourceFile, location: string, options?: ExtractContractSchemaOptions): ExtractedResponseContract;
7
7
  export declare function registerContract(contracts: Map<string, BackendResponseContract>, key: string, contract: BackendResponseContract): void;
@@ -1,19 +1,23 @@
1
1
  import ts from '../contract-schema/typescript-api.mjs';
2
2
  import { extractContractSchema, hashContractSchema } from '../contract-schema/index.mjs';
3
3
  import { isContextMethod, responseMarker, visit } from './response-contract-route-analysis.mjs';
4
- export function responseBodyExpression(call) {
4
+ import { unwrapTransparentExpression } from './response-contract-route-syntax.mjs';
5
+ export function responseBodyExpression(call, unwrapPipelineArguments = false) {
5
6
  if (isContextMethod(call.expression, 'json'))
6
7
  return call.arguments[0];
7
8
  if (!isContextMethod(call.expression, 'pipeline'))
8
9
  return undefined;
9
- const pipelineBody = call.arguments[0];
10
+ const originalBody = call.arguments[0];
11
+ const pipelineBody = originalBody &&
12
+ (unwrapPipelineArguments ? unwrapTransparentExpression(originalBody) : originalBody);
10
13
  if (!pipelineBody ||
11
14
  !ts.isCallExpression(pipelineBody) ||
12
15
  !ts.isIdentifier(pipelineBody.expression) ||
13
16
  pipelineBody.expression.text !== 'streamJsonObject') {
14
17
  return undefined;
15
18
  }
16
- return pipelineBody.arguments[0];
19
+ const body = pipelineBody.arguments[0];
20
+ return body && (unwrapPipelineArguments ? unwrapTransparentExpression(body) : body);
17
21
  }
18
22
  export function containsResponseMarker(node) {
19
23
  let found = false;
@@ -1,17 +1,25 @@
1
1
  import ts from '../contract-schema/typescript-api.mjs';
2
2
  import { registerPlatformCompilerLibraries } from './protocol-platform-callbacks.mjs';
3
- import { requestedProtocolKey } from './protocol-requested-keys.mjs';
3
+ import { protocolBindingRequested, requestedProtocolKey } from './protocol-requested-keys.mjs';
4
4
  import { discoverProtocolContracts } from './protocol-contract-registry.mjs';
5
5
  import { discoverImplicitContract } from './response-contract-implicit.mjs';
6
+ import { ambiguousRoutesForCall } from './response-contract-attribution.mjs';
7
+ import { sortAttributionFacts } from './response-contract-attribution-facts.mjs';
8
+ import { implicitResponseCallLabel } from './response-contract-call-classification.mjs';
9
+ import { collectHandlerBindings } from './response-contract-handler-bindings.mjs';
6
10
  import { registerRouteContract, } from './response-contract-lenient.mjs';
7
11
  import { binaryContentContract, contractError, extractBodyContract, noContentContract, sourceLocation, } from './response-contract-registration.mjs';
8
- import { collectHandlerBindings, enclosingRouteBinding, isContextMethod, responseMarker, visit, } from './response-contract-route-analysis.mjs';
12
+ import { enclosingRouteBinding, isContextMethod, responseMarker, visit, } from './response-contract-route-analysis.mjs';
9
13
  import { resolveEmissionStatus } from './response-contract-status.mjs';
10
14
  export function discoverApiResponseContracts(program, sourceFiles, requestedKeys, options) {
11
15
  registerPlatformCompilerLibraries(program);
12
16
  const checker = program.getTypeChecker();
13
17
  const contracts = new Map();
14
- const handlerBindings = collectHandlerBindings(sourceFiles, checker);
18
+ const ambiguousBindings = options?.onAmbiguousAttribution
19
+ ? new Map()
20
+ : undefined;
21
+ const handlerBindings = collectHandlerBindings(sourceFiles, checker, ambiguousBindings);
22
+ const attributionFacts = [];
15
23
  const protocolContracts = new Map();
16
24
  const protocolEmissions = discoverProtocolContracts(sourceFiles, checker, handlerBindings, protocolContracts, options, requestedKeys);
17
25
  for (const [key, contract] of protocolContracts) {
@@ -58,14 +66,41 @@ export function discoverApiResponseContracts(program, sourceFiles, requestedKeys
58
66
  }
59
67
  for (const sourceFile of sourceFiles) {
60
68
  visit(sourceFile, (node) => {
61
- if (!ts.isCallExpression(node) ||
62
- responseMarker(node.expression) ||
63
- protocolEmissions.has(node))
69
+ if (!ts.isCallExpression(node))
70
+ return;
71
+ const callLabel = implicitResponseCallLabel(node);
72
+ if (options?.onAmbiguousAttribution) {
73
+ const attributionLabel = implicitResponseCallLabel(node, true, checker);
74
+ const routes = attributionLabel
75
+ ? ambiguousBindings &&
76
+ ambiguousRoutesForCall(node, checker, handlerBindings, ambiguousBindings)
77
+ : undefined;
78
+ const requestedRoute = !requestedKeys ||
79
+ routes?.some((route) => {
80
+ const separator = route.indexOf(':');
81
+ return (separator > 0 &&
82
+ protocolBindingRequested({ method: route.slice(0, separator), routeTemplate: route.slice(separator + 1) }, requestedKeys));
83
+ });
84
+ if (attributionLabel && routes && requestedRoute) {
85
+ const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
86
+ attributionFacts.push({
87
+ sourceLocation: `${sourceLocation(sourceFile, node)}:${position.line + 1}:${position.character + 1}`,
88
+ label: attributionLabel,
89
+ routes: [...routes],
90
+ });
91
+ }
92
+ }
93
+ if (responseMarker(node.expression) || protocolEmissions.has(node))
64
94
  return;
65
- discoverImplicitContract(node, checker, sourceFile, contracts, handlerBindings, requestedKeys, options);
95
+ discoverImplicitContract(node, checker, sourceFile, contracts, handlerBindings, callLabel, requestedKeys, options);
66
96
  });
67
97
  }
68
- return Object.fromEntries([...contracts.entries()].toSorted(([left], [right]) => left.localeCompare(right)));
98
+ const result = Object.fromEntries([...contracts.entries()].toSorted(([left], [right]) => left.localeCompare(right)));
99
+ if (options?.onAmbiguousAttribution) {
100
+ for (const fact of sortAttributionFacts(attributionFacts))
101
+ options.onAmbiguousAttribution(fact);
102
+ }
103
+ return result;
69
104
  }
70
105
  function rawResponseMediaType(marker, node, sourceFile) {
71
106
  if (marker !== 'apiOpenApiRawResponse')
@@ -1,13 +1,12 @@
1
1
  import ts from '../contract-schema/typescript-api.mts';
2
+ export { collectHandlerBindings } from './response-contract-handler-bindings.mts';
3
+ export { propertyName, routeTemplateFromExpression, visit, } from './response-contract-route-syntax.mts';
2
4
  export { isContextMethod, isContextResponseBufferCall, isContextResponseEmptyCall, responseMarker, } from './response-contract-ctx-analysis.mts';
3
5
  export type RouteBinding = {
4
6
  method: string;
5
7
  routeTemplate: string;
6
8
  };
7
9
  export type HandlerBindings = Map<ts.Symbol, RouteBinding>;
10
+ export type AmbiguousHandlerBindings = Map<ts.Symbol, readonly string[]>;
8
11
  export declare function requestedKeyForBinding(binding: RouteBinding, requestedKeys?: ReadonlySet<string>): string | undefined;
9
12
  export declare function enclosingRouteBinding(node: ts.Node, checker: ts.TypeChecker, handlerBindings: HandlerBindings, proveCallbacks?: boolean): RouteBinding | undefined;
10
- export declare function collectHandlerBindings(sourceFiles: readonly ts.SourceFile[], checker: ts.TypeChecker): HandlerBindings;
11
- export declare function routeTemplateFromExpression(expression: ts.Expression): string | undefined;
12
- export declare function propertyName(expression: ts.Expression): string | undefined;
13
- export declare function visit(node: ts.Node, callback: (node: ts.Node) => void): void;