vouchington-tooling 0.29.0 → 0.30.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 (141) hide show
  1. package/README.md +64 -16
  2. package/dist/api-contract-discovery/header-contract-registry.mjs +2 -0
  3. package/dist/api-contract-discovery/protocol-body-status.d.mts +2 -0
  4. package/dist/api-contract-discovery/protocol-body-status.mjs +4 -0
  5. package/dist/api-contract-discovery/protocol-callback-argument-bindings.d.mts +7 -0
  6. package/dist/api-contract-discovery/protocol-callback-argument-bindings.mjs +33 -0
  7. package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +5 -0
  8. package/dist/api-contract-discovery/protocol-callback-invocation.mjs +103 -0
  9. package/dist/api-contract-discovery/protocol-callback-mutations.d.mts +5 -0
  10. package/dist/api-contract-discovery/protocol-callback-mutations.mjs +42 -0
  11. package/dist/api-contract-discovery/protocol-callback-registration.d.mts +3 -0
  12. package/dist/api-contract-discovery/protocol-callback-registration.mjs +27 -0
  13. package/dist/api-contract-discovery/protocol-callback-values.d.mts +17 -0
  14. package/dist/api-contract-discovery/protocol-callback-values.mjs +114 -0
  15. package/dist/api-contract-discovery/protocol-context-alias.d.mts +3 -0
  16. package/dist/api-contract-discovery/protocol-context-alias.mjs +20 -0
  17. package/dist/api-contract-discovery/protocol-contract-extraction.d.mts +9 -0
  18. package/dist/api-contract-discovery/protocol-contract-extraction.mjs +87 -0
  19. package/dist/api-contract-discovery/protocol-contract-keys.d.mts +1 -0
  20. package/dist/api-contract-discovery/protocol-contract-keys.mjs +8 -0
  21. package/dist/api-contract-discovery/protocol-contract-registry.d.mts +5 -0
  22. package/dist/api-contract-discovery/protocol-contract-registry.mjs +133 -0
  23. package/dist/api-contract-discovery/protocol-executable-path.d.mts +3 -0
  24. package/dist/api-contract-discovery/protocol-executable-path.mjs +64 -0
  25. package/dist/api-contract-discovery/protocol-execution-path.d.mts +3 -0
  26. package/dist/api-contract-discovery/protocol-execution-path.mjs +16 -0
  27. package/dist/api-contract-discovery/protocol-http-association.d.mts +7 -0
  28. package/dist/api-contract-discovery/protocol-http-association.mjs +97 -0
  29. package/dist/api-contract-discovery/protocol-http-body-kinds.d.mts +3 -0
  30. package/dist/api-contract-discovery/protocol-http-body-kinds.mjs +19 -0
  31. package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +6 -0
  32. package/dist/api-contract-discovery/protocol-http-context-callers.mjs +34 -0
  33. package/dist/api-contract-discovery/protocol-http-context.d.mts +9 -0
  34. package/dist/api-contract-discovery/protocol-http-context.mjs +108 -0
  35. package/dist/api-contract-discovery/protocol-http-emission.d.mts +3 -0
  36. package/dist/api-contract-discovery/protocol-http-emission.mjs +76 -0
  37. package/dist/api-contract-discovery/protocol-http-implicit-taint.d.mts +7 -0
  38. package/dist/api-contract-discovery/protocol-http-implicit-taint.mjs +26 -0
  39. package/dist/api-contract-discovery/protocol-http-registration.d.mts +12 -0
  40. package/dist/api-contract-discovery/protocol-http-registration.mjs +53 -0
  41. package/dist/api-contract-discovery/protocol-marker-analysis.d.mts +7 -0
  42. package/dist/api-contract-discovery/protocol-marker-analysis.mjs +50 -0
  43. package/dist/api-contract-discovery/protocol-opaque-callback.d.mts +3 -0
  44. package/dist/api-contract-discovery/protocol-opaque-callback.mjs +107 -0
  45. package/dist/api-contract-discovery/protocol-platform-callbacks.d.mts +5 -0
  46. package/dist/api-contract-discovery/protocol-platform-callbacks.mjs +72 -0
  47. package/dist/api-contract-discovery/protocol-platform-mutation-targets.d.mts +5 -0
  48. package/dist/api-contract-discovery/protocol-platform-mutation-targets.mjs +89 -0
  49. package/dist/api-contract-discovery/protocol-platform-writes.d.mts +3 -0
  50. package/dist/api-contract-discovery/protocol-platform-writes.mjs +92 -0
  51. package/dist/api-contract-discovery/protocol-requested-keys.d.mts +5 -0
  52. package/dist/api-contract-discovery/protocol-requested-keys.mjs +45 -0
  53. package/dist/api-contract-discovery/protocol-sse-callers.d.mts +7 -0
  54. package/dist/api-contract-discovery/protocol-sse-callers.mjs +67 -0
  55. package/dist/api-contract-discovery/protocol-sse-emission.d.mts +11 -0
  56. package/dist/api-contract-discovery/protocol-sse-emission.mjs +61 -0
  57. package/dist/api-contract-discovery/protocol-sse-feasible-status.d.mts +3 -0
  58. package/dist/api-contract-discovery/protocol-sse-feasible-status.mjs +24 -0
  59. package/dist/api-contract-discovery/protocol-sse-raw-writes.d.mts +9 -0
  60. package/dist/api-contract-discovery/protocol-sse-raw-writes.mjs +70 -0
  61. package/dist/api-contract-discovery/protocol-sse-status.d.mts +3 -0
  62. package/dist/api-contract-discovery/protocol-sse-status.mjs +84 -0
  63. package/dist/api-contract-discovery/protocol-sse-write-access.d.mts +9 -0
  64. package/dist/api-contract-discovery/protocol-sse-write-access.mjs +42 -0
  65. package/dist/api-contract-discovery/protocol-sse-write-helpers.d.mts +6 -0
  66. package/dist/api-contract-discovery/protocol-sse-write-helpers.mjs +66 -0
  67. package/dist/api-contract-discovery/protocol-sse-write-mutations.d.mts +9 -0
  68. package/dist/api-contract-discovery/protocol-sse-write-mutations.mjs +36 -0
  69. package/dist/api-contract-discovery/protocol-sse-write-resolution.d.mts +14 -0
  70. package/dist/api-contract-discovery/protocol-sse-write-resolution.mjs +79 -0
  71. package/dist/api-contract-discovery/protocol-status-dominance.d.mts +5 -0
  72. package/dist/api-contract-discovery/protocol-status-dominance.mjs +35 -0
  73. package/dist/api-contract-discovery/protocol-write-receiver.d.mts +9 -0
  74. package/dist/api-contract-discovery/protocol-write-receiver.mjs +79 -0
  75. package/dist/api-contract-discovery/query-contract-registry.mjs +2 -0
  76. package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +3 -0
  77. package/dist/api-contract-discovery/registered-route-binding-writes.mjs +30 -0
  78. package/dist/api-contract-discovery/registered-route-catalog.mjs +11 -7
  79. package/dist/api-contract-discovery/registered-route-factory-returns.d.mts +3 -0
  80. package/dist/api-contract-discovery/registered-route-factory-returns.mjs +29 -0
  81. package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +5 -1
  82. package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +79 -35
  83. package/dist/api-contract-discovery/registered-route-runtime-parameters.d.mts +3 -0
  84. package/dist/api-contract-discovery/registered-route-runtime-parameters.mjs +5 -0
  85. package/dist/api-contract-discovery/registered-route-terminal-error.d.mts +3 -0
  86. package/dist/api-contract-discovery/registered-route-terminal-error.mjs +98 -0
  87. package/dist/api-contract-discovery/request-contract-registry.mjs +2 -0
  88. package/dist/api-contract-discovery/response-contract-implicit.mjs +28 -10
  89. package/dist/api-contract-discovery/response-contract-registry.mjs +14 -1
  90. package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +1 -1
  91. package/dist/api-contract-discovery/response-contract-route-analysis.mjs +22 -4
  92. package/dist/api-contract-discovery/response-contract-status.d.mts +1 -1
  93. package/dist/api-contract-discovery/response-contract-status.mjs +10 -8
  94. package/dist/api-contract-discovery/response-contract-types.d.mts +7 -0
  95. package/dist/cli/index.mjs +0 -3
  96. package/dist/cli/parse-forwarded-commands.d.mts +0 -3
  97. package/dist/cli/parse-forwarded-commands.mjs +0 -2
  98. package/dist/cli/parse.d.mts +0 -3
  99. package/dist/cli/usage.d.mts +1 -1
  100. package/dist/cli/usage.mjs +0 -5
  101. package/dist/contract-schema/contract-schema-bounded-array.d.mts +1 -3
  102. package/dist/contract-schema/contract-schema-bounded-array.mjs +5 -0
  103. package/dist/contract-schema/contract-schema-object-tuple.mjs +7 -5
  104. package/dist/contract-schema/contract-schema-type-extractor.mjs +13 -0
  105. package/dist/contract-schema/contract-schema-validator.mjs +2 -2
  106. package/dist/index.d.mts +0 -2
  107. package/dist/index.mjs +0 -1
  108. package/dist/openapi-document/build-openapi-catalog-sse.d.mts +4 -0
  109. package/dist/openapi-document/build-openapi-catalog-sse.mjs +27 -0
  110. package/dist/openapi-document/build-openapi-document.mjs +5 -2
  111. package/dist/openapi-document/build-openapi-response.d.mts +2 -2
  112. package/dist/openapi-document/build-openapi-response.mjs +33 -3
  113. package/dist/openapi-document/build-openapi-sse-events.d.mts +11 -0
  114. package/dist/openapi-document/build-openapi-sse-events.mjs +46 -0
  115. package/dist/openapi-document/contract-schema-intersection-merge.mjs +11 -11
  116. package/dist/openapi-document/contract-schema-to-openapi.mjs +3 -3
  117. package/dist/openapi-document/openapi-route-helpers.mjs +1 -3
  118. package/dist/openapi-document/openapi-types.d.mts +3 -0
  119. package/dist/openapi-document/operation-types.d.mts +7 -0
  120. package/package.json +3 -8
  121. package/dist/agent-harness-config/apply.d.mts +0 -4
  122. package/dist/agent-harness-config/apply.mjs +0 -72
  123. package/dist/agent-harness-config/cli.d.mts +0 -10
  124. package/dist/agent-harness-config/cli.mjs +0 -100
  125. package/dist/agent-harness-config/index.d.mts +0 -4
  126. package/dist/agent-harness-config/index.mjs +0 -3
  127. package/dist/agent-harness-config/merge-json.d.mts +0 -5
  128. package/dist/agent-harness-config/merge-json.mjs +0 -62
  129. package/dist/agent-harness-config/merge-toml.d.mts +0 -6
  130. package/dist/agent-harness-config/merge-toml.mjs +0 -132
  131. package/dist/agent-harness-config/paths.d.mts +0 -3
  132. package/dist/agent-harness-config/paths.mjs +0 -117
  133. package/dist/agent-harness-config/policy.d.mts +0 -14
  134. package/dist/agent-harness-config/policy.mjs +0 -51
  135. package/dist/agent-harness-config/prerequisites.d.mts +0 -2
  136. package/dist/agent-harness-config/prerequisites.mjs +0 -67
  137. package/dist/agent-harness-config/toml-value.d.mts +0 -6
  138. package/dist/agent-harness-config/toml-value.mjs +0 -69
  139. package/dist/agent-harness-config/types.d.mts +0 -90
  140. package/dist/agent-harness-config/types.mjs +0 -2
  141. package/docs/agent-harness-config.md +0 -134
@@ -0,0 +1,53 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { associateHttpResponse } from './protocol-http-association.mjs';
3
+ import { enclosingFunction } from './protocol-marker-analysis.mjs';
4
+ import { statusDominatesEmission } from './protocol-status-dominance.mjs';
5
+ export function registerHttpProtocols(pending, contracts, checker, reject) {
6
+ const covered = new Set();
7
+ const groups = new Map();
8
+ for (const response of pending) {
9
+ const handler = enclosingFunction(response.call);
10
+ const group = groups.get(handler) ?? [];
11
+ group.push(response);
12
+ groups.set(handler, group);
13
+ }
14
+ for (const group of groups.values()) {
15
+ if (!group.some((response) => response.keys.length))
16
+ continue;
17
+ try {
18
+ const siblings = new Set(group.map((response) => response.symbol));
19
+ const associations = new Map(group.map((response) => [
20
+ response,
21
+ associateHttpResponse(response.call, response.symbol, checker, siblings),
22
+ ]));
23
+ const allStatuses = new Set([...associations.values()].flatMap((emissions) => [...emissions].filter(([, kind]) => kind === 'status').map(([call]) => call)));
24
+ const proven = new Set();
25
+ for (const [response, emissions] of associations) {
26
+ proveHttpEmissions(response, emissions, allStatuses);
27
+ for (const call of emissions.keys())
28
+ proven.add(call);
29
+ }
30
+ // Every sibling must prove its own emissions before any group contract is published.
31
+ for (const response of group)
32
+ response.variants.forEach((variant, index) => contracts.set(response.keys[index], { ...variant, ...response.binding }));
33
+ for (const call of proven)
34
+ covered.add(call);
35
+ }
36
+ catch (error) {
37
+ for (const response of group)
38
+ if (response.keys.length)
39
+ reject(response, error);
40
+ }
41
+ }
42
+ return covered;
43
+ }
44
+ function proveHttpEmissions(response, emissions, allStatuses) {
45
+ const statuses = new Set([...emissions].filter(([, kind]) => kind === 'status').map(([call]) => call));
46
+ const kinds = new Set(emissions.values());
47
+ const declaredBodyKinds = response.declaredBodyKinds;
48
+ if (!kinds.has('status') ||
49
+ [...declaredBodyKinds].some((kind) => !kinds.has(kind)) ||
50
+ [...kinds].some((kind) => kind !== 'status' && !declaredBodyKinds.has(kind)) ||
51
+ [...emissions].some(([call, kind]) => kind !== 'status' && !statusDominatesEmission(call, statuses, allStatuses)))
52
+ throw new Error('HTTP response variants require dominating status and associated body emissions');
53
+ }
@@ -0,0 +1,7 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ export declare function protocolMarker(expression: ts.Expression, checker: ts.TypeChecker): 'apiSseFrame' | 'apiOpenApiHttpResponse' | undefined;
3
+ export declare function propertyType(type: ts.Type, name: string, checker: ts.TypeChecker): ts.Type;
4
+ export declare function typeVariants(type: ts.Type): readonly ts.Type[];
5
+ export declare function stringLiterals(type: ts.Type): string[];
6
+ export declare function unwrapExpression(expression: ts.Expression): ts.Expression;
7
+ export declare function enclosingFunction(node: ts.Node): ts.FunctionLikeDeclaration | undefined;
@@ -0,0 +1,50 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ export function protocolMarker(expression, checker) {
3
+ const symbol = checker.getSymbolAtLocation(expression);
4
+ const resolved = symbol && (symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol);
5
+ const name = resolved?.name;
6
+ return name === 'apiSseFrame' || name === 'apiOpenApiHttpResponse' ? name : undefined;
7
+ }
8
+ export function propertyType(type, name, checker) {
9
+ const property = checker.getPropertyOfType(type, name);
10
+ const declaration = property?.valueDeclaration ?? property?.declarations?.[0];
11
+ if (!property || !declaration)
12
+ throw new Error(`Protocol contract requires ${name}`);
13
+ return checker.getTypeOfSymbolAtLocation(property, declaration);
14
+ }
15
+ export function typeVariants(type) {
16
+ if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never))
17
+ throw new Error('Protocol contract requires concrete variants');
18
+ return type.isUnion() ? type.types : [type];
19
+ }
20
+ export function stringLiterals(type) {
21
+ return typeVariants(type).map((variant) => {
22
+ if (!(variant.flags & ts.TypeFlags.StringLiteral))
23
+ throw new Error('Protocol contract requires literal names and media types');
24
+ const value = variant.value;
25
+ if (!value || /[\r\n]/.test(value))
26
+ throw new Error('Protocol contract requires nonempty single-line names');
27
+ return value;
28
+ });
29
+ }
30
+ export function unwrapExpression(expression) {
31
+ if (ts.isParenthesizedExpression(expression) ||
32
+ ts.isAsExpression(expression) ||
33
+ ts.isTypeAssertionExpression(expression) ||
34
+ ts.isNonNullExpression(expression) ||
35
+ ts.isAwaitExpression(expression))
36
+ return unwrapExpression(expression.expression);
37
+ return expression;
38
+ }
39
+ export function enclosingFunction(node) {
40
+ let current = node.parent;
41
+ while (current) {
42
+ if (ts.isArrowFunction(current) ||
43
+ ts.isFunctionExpression(current) ||
44
+ ts.isFunctionDeclaration(current) ||
45
+ ts.isMethodDeclaration(current))
46
+ return current;
47
+ current = current.parent;
48
+ }
49
+ return undefined;
50
+ }
@@ -0,0 +1,3 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ /** Proves an executable lexical path containing at least one opaque callback escape. */
3
+ export declare function opaqueProtocolCallbackPath(node: ts.Node, checker: ts.TypeChecker): boolean;
@@ -0,0 +1,107 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { callbackOptionsEscape } from './protocol-callback-mutations.mjs';
3
+ import { isSupportedProtocolCallback } from './protocol-callback-invocation.mjs';
4
+ import { potentiallyExecuted } from './protocol-executable-path.mjs';
5
+ import { enclosingFunction } from './protocol-marker-analysis.mjs';
6
+ import { callbackArgumentBindings } from './protocol-callback-argument-bindings.mjs';
7
+ import { createProtocolCallbackValueResolver, isProtocolCallbackFunction as functionNode, } from './protocol-callback-values.mjs';
8
+ function implementedFunction(node) {
9
+ return functionNode(node) && !!node.body;
10
+ }
11
+ /** Proves an executable lexical path containing at least one opaque callback escape. */
12
+ export function opaqueProtocolCallbackPath(node, checker) {
13
+ if (!potentiallyExecuted(node))
14
+ return false;
15
+ const functions = [];
16
+ for (let fn = enclosingFunction(node); fn; fn = enclosingFunction(fn))
17
+ functions.push(fn);
18
+ if (!functions.length ||
19
+ functions.some((fn) => !fn.body || fn.asteriskToken || !potentiallyExecuted(fn)))
20
+ return false;
21
+ let opaque = false;
22
+ for (const fn of functions) {
23
+ if (isSupportedProtocolCallback(fn, checker))
24
+ continue;
25
+ if (!escapesToOpaque(fn, checker))
26
+ return false;
27
+ opaque = true;
28
+ }
29
+ return opaque;
30
+ }
31
+ function escapesToOpaque(callback, checker) {
32
+ const resolver = createProtocolCallbackValueResolver(checker);
33
+ function opaque(call, env) {
34
+ const implementation = resolver.resolve(call.expression, env);
35
+ if (implementation && functionNode(implementation.node) && implementation.node.body)
36
+ return false;
37
+ const declaration = checker.getResolvedSignature(call)?.declaration;
38
+ return !declaration || !('body' in declaration && declaration.body);
39
+ }
40
+ function executableCallsite(call) {
41
+ const callbackAncestors = new Set();
42
+ for (let fn = enclosingFunction(callback); fn; fn = enclosingFunction(fn))
43
+ callbackAncestors.add(fn);
44
+ for (let fn = enclosingFunction(call); fn; fn = enclosingFunction(fn))
45
+ if (!fn.body ||
46
+ fn.asteriskToken ||
47
+ !potentiallyExecuted(fn) ||
48
+ (!callbackAncestors.has(fn) && !isSupportedProtocolCallback(fn, checker)))
49
+ return false;
50
+ return true;
51
+ }
52
+ function scanBody(fn, env, active) {
53
+ const chain = new Set([...active, fn]);
54
+ let escaped = false;
55
+ function visit(node) {
56
+ if (escaped || !potentiallyExecuted(node))
57
+ return;
58
+ if (ts.isFunctionLike(node))
59
+ return;
60
+ if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
61
+ const forwards = callbackOptionsEscape(node, env, callback, resolver);
62
+ if (forwards && opaque(node, env)) {
63
+ escaped = true;
64
+ return;
65
+ }
66
+ const target = forwards ? resolver.resolve(node.expression, env) : undefined;
67
+ if (target &&
68
+ implementedFunction(target.node) &&
69
+ !target.node.asteriskToken &&
70
+ !chain.has(target.node)) {
71
+ const next = callbackArgumentBindings(target.node, node, env, target.env, checker, resolver);
72
+ if (!next || scanBody(target.node, next, chain))
73
+ escaped = true;
74
+ }
75
+ }
76
+ ts.forEachChild(node, visit);
77
+ }
78
+ visit(fn.body);
79
+ return escaped;
80
+ }
81
+ function sourceCalls(source) {
82
+ let escaped = false;
83
+ function visit(node) {
84
+ if (escaped || !potentiallyExecuted(node))
85
+ return;
86
+ if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
87
+ if (!executableCallsite(node))
88
+ return;
89
+ const forwards = callbackOptionsEscape(node, new Map(), callback, resolver);
90
+ if (forwards && opaque(node, new Map())) {
91
+ escaped = true;
92
+ return;
93
+ }
94
+ const target = forwards ? resolver.resolve(node.expression, new Map()) : undefined;
95
+ if (target && implementedFunction(target.node) && !target.node.asteriskToken) {
96
+ const next = callbackArgumentBindings(target.node, node, new Map(), target.env, checker, resolver);
97
+ if (!next || scanBody(target.node, next, new Set()))
98
+ escaped = true;
99
+ }
100
+ }
101
+ ts.forEachChild(node, visit);
102
+ }
103
+ visit(source);
104
+ return escaped;
105
+ }
106
+ return sourceCalls(callback.getSourceFile());
107
+ }
@@ -0,0 +1,5 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ /** Records the actual caller compiler's library identities, including another installation. */
3
+ export declare function registerPlatformCompilerLibraries(program: ts.Program): void;
4
+ /** Recognizes only callback APIs supplied by the compiler's standard libraries or Node typings. */
5
+ export declare function platformCallbackArgument(node: ts.CallExpression | ts.NewExpression, checker: ts.TypeChecker): ts.Expression | undefined;
@@ -0,0 +1,72 @@
1
+ import { basename, dirname, resolve as resolvePath } from 'node:path';
2
+ import ts from '../contract-schema/typescript-api.mjs';
3
+ import { unwrapExpression } from './protocol-marker-analysis.mjs';
4
+ import { writtenPlatformSymbols } from './protocol-platform-writes.mjs';
5
+ const standardLibrary = dirname(resolvePath(ts.getDefaultLibFilePath({})));
6
+ const platformWrites = new WeakMap();
7
+ const compilerLibraries = new WeakMap();
8
+ /** Records the actual caller compiler's library identities, including another installation. */
9
+ export function registerPlatformCompilerLibraries(program) {
10
+ const checker = program.getTypeChecker();
11
+ if (!compilerLibraries.has(checker)) {
12
+ const libraries = new Set(program.getSourceFiles().filter((file) => program.isSourceFileDefaultLibrary(file)));
13
+ compilerLibraries.set(checker, libraries);
14
+ platformWrites.set(checker, writtenPlatformSymbols(program.getSourceFiles(), checker, libraries));
15
+ }
16
+ }
17
+ function standardDeclaration(declaration, timer, checker) {
18
+ const source = declaration.getSourceFile();
19
+ if (!source.isDeclarationFile)
20
+ return false;
21
+ const file = resolvePath(source.fileName);
22
+ const libraries = compilerLibraries.get(checker);
23
+ if (libraries
24
+ ? libraries.has(source)
25
+ : dirname(file) === standardLibrary && /^lib(?:\.[\w.-]+)?\.d\.ts$/.test(basename(file)))
26
+ return true;
27
+ return (timer &&
28
+ /\/node_modules\/@types\/node\/(?:web-globals\/)?timers\.d\.ts$/.test(file.replaceAll('\\', '/')));
29
+ }
30
+ function platformSymbol(expression, checker, seen = new Set()) {
31
+ const value = unwrapExpression(expression);
32
+ if (!ts.isIdentifier(value))
33
+ return undefined;
34
+ const symbol = checker.getSymbolAtLocation(value);
35
+ if (!symbol || seen.has(symbol))
36
+ return undefined;
37
+ seen.add(symbol);
38
+ if (symbol.flags & ts.SymbolFlags.Alias) {
39
+ const target = checker.getAliasedSymbol(symbol);
40
+ if (seen.has(target))
41
+ return undefined;
42
+ return target;
43
+ }
44
+ const declaration = symbol.valueDeclaration;
45
+ if (declaration &&
46
+ ts.isVariableDeclaration(declaration) &&
47
+ declaration.initializer &&
48
+ ts.isVariableDeclarationList(declaration.parent) &&
49
+ declaration.parent.flags & ts.NodeFlags.Const)
50
+ return platformSymbol(declaration.initializer, checker, seen);
51
+ return symbol;
52
+ }
53
+ /** Recognizes only callback APIs supplied by the compiler's standard libraries or Node typings. */
54
+ export function platformCallbackArgument(node, checker) {
55
+ if (ts.isCallExpression(node) && node.questionDotToken)
56
+ return undefined;
57
+ const argument = node.arguments?.[0];
58
+ if (!argument || ts.isSpreadElement(argument))
59
+ return undefined;
60
+ const symbol = platformSymbol(node.expression, checker);
61
+ if (!symbol?.declarations?.length)
62
+ return undefined;
63
+ const timer = symbol.name === 'setInterval' || symbol.name === 'setTimeout';
64
+ if (!(timer ? ts.isCallExpression(node) : symbol.name === 'Promise' && ts.isNewExpression(node)))
65
+ return undefined;
66
+ const writes = platformWrites.get(checker) ?? writtenPlatformSymbols([node.getSourceFile()], checker);
67
+ if (writes.has(symbol))
68
+ return undefined;
69
+ return symbol.declarations.every((declaration) => standardDeclaration(declaration, timer, checker))
70
+ ? argument
71
+ : undefined;
72
+ }
@@ -0,0 +1,5 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ export declare function literalStrings(expression: ts.Expression | undefined, checker: ts.TypeChecker): readonly string[] | undefined;
3
+ export declare function globalTarget(expression: ts.Expression, checker: ts.TypeChecker, seen?: Set<ts.Symbol>): ts.Expression | undefined;
4
+ export declare function mutatorKind(call: ts.CallExpression, checker: ts.TypeChecker, libraries: ReadonlySet<ts.SourceFile> | undefined): 'defineProperty' | 'set' | 'assign' | undefined;
5
+ export declare function sourceKeys(source: ts.Expression, checker: ts.TypeChecker): readonly string[] | undefined;
@@ -0,0 +1,89 @@
1
+ import { basename, dirname, resolve as resolvePath } from 'node:path';
2
+ import ts from '../contract-schema/typescript-api.mjs';
3
+ import { unwrapExpression } from './protocol-marker-analysis.mjs';
4
+ const standardLibrary = dirname(resolvePath(ts.getDefaultLibFilePath({})));
5
+ function isStandardDeclaration(declaration, libraries) {
6
+ const source = declaration.getSourceFile();
7
+ const file = resolvePath(source.fileName);
8
+ return libraries
9
+ ? libraries.has(source)
10
+ : source.isDeclarationFile &&
11
+ dirname(file) === standardLibrary &&
12
+ /^lib(?:\.[\w.-]+)?\.d\.ts$/.test(basename(file));
13
+ }
14
+ export function literalStrings(expression, checker) {
15
+ if (!expression)
16
+ return undefined;
17
+ const strings = new Set();
18
+ function visit(type) {
19
+ if (type.isUnion())
20
+ return type.types.every(visit);
21
+ if (type.flags & ts.TypeFlags.StringLiteral) {
22
+ strings.add(type.value);
23
+ return true;
24
+ }
25
+ if (type.flags &
26
+ (ts.TypeFlags.String | ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.TypeParameter))
27
+ return false;
28
+ return true;
29
+ }
30
+ return visit(checker.getTypeAtLocation(expression)) ? [...strings] : undefined;
31
+ }
32
+ export function globalTarget(expression, checker, seen = new Set()) {
33
+ const value = unwrapExpression(expression);
34
+ if (!ts.isIdentifier(value))
35
+ return undefined;
36
+ const symbol = checker.getSymbolAtLocation(value);
37
+ if (!symbol || seen.has(symbol))
38
+ return undefined;
39
+ seen.add(symbol);
40
+ if (value.text === 'globalThis')
41
+ return symbol.declarations?.length ? undefined : value;
42
+ if (symbol.name === 'global' &&
43
+ symbol.declarations?.some((declaration) => {
44
+ const source = declaration.getSourceFile();
45
+ return (ts.isVariableDeclaration(declaration) &&
46
+ source.isDeclarationFile &&
47
+ resolvePath(source.fileName)
48
+ .replaceAll('\\', '/')
49
+ .endsWith('/node_modules/@types/node/globals.d.ts'));
50
+ }))
51
+ return value;
52
+ const declaration = symbol.valueDeclaration;
53
+ return declaration && ts.isVariableDeclaration(declaration) && declaration.initializer
54
+ ? globalTarget(declaration.initializer, checker, seen)
55
+ : undefined;
56
+ }
57
+ export function mutatorKind(call, checker, libraries) {
58
+ const callee = call.expression;
59
+ if (!ts.isPropertyAccessExpression(callee) || !ts.isIdentifier(callee.expression))
60
+ return undefined;
61
+ const owner = callee.expression.text;
62
+ const method = callee.name.text;
63
+ if (!((owner === 'Object' && ['defineProperty', 'assign'].includes(method)) ||
64
+ (owner === 'Reflect' && method === 'set')))
65
+ return undefined;
66
+ const ownerSymbol = checker.getSymbolAtLocation(callee.expression);
67
+ const methodSymbol = checker.getSymbolAtLocation(callee.name);
68
+ if (!ownerSymbol?.declarations?.length ||
69
+ !methodSymbol?.declarations?.length ||
70
+ !ownerSymbol.declarations.every((declaration) => isStandardDeclaration(declaration, libraries)) ||
71
+ !methodSymbol.declarations.every((declaration) => isStandardDeclaration(declaration, libraries)))
72
+ return undefined;
73
+ return method;
74
+ }
75
+ export function sourceKeys(source, checker) {
76
+ const keys = new Set();
77
+ function visit(type) {
78
+ if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.TypeParameter))
79
+ return false;
80
+ if (type.isUnion())
81
+ return type.types.every(visit);
82
+ if (type.getStringIndexType())
83
+ return false;
84
+ for (const property of type.getProperties())
85
+ keys.add(property.name);
86
+ return true;
87
+ }
88
+ return visit(checker.getTypeAtLocation(unwrapExpression(source))) ? [...keys] : undefined;
89
+ }
@@ -0,0 +1,3 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ /** Records global timer and Promise writes, including calls to standard property mutators. */
3
+ export declare function writtenPlatformSymbols(files: readonly ts.SourceFile[], checker: ts.TypeChecker, libraries?: ReadonlySet<ts.SourceFile>): Set<ts.Symbol>;
@@ -0,0 +1,92 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { unwrapExpression } from './protocol-marker-analysis.mjs';
3
+ import { globalTarget, literalStrings, mutatorKind, sourceKeys, } from './protocol-platform-mutation-targets.mjs';
4
+ const platformNames = ['setTimeout', 'setInterval', 'Promise'];
5
+ /** Records global timer and Promise writes, including calls to standard property mutators. */
6
+ export function writtenPlatformSymbols(files, checker, libraries) {
7
+ const written = new Set();
8
+ function mark(target, keys) {
9
+ const root = globalTarget(target, checker);
10
+ if (!root)
11
+ return;
12
+ const members = keys
13
+ ? keys.map((key) => checker.getPropertyOfType(checker.getTypeAtLocation(root), key))
14
+ : platformNames.map((name) => checker.getPropertyOfType(checker.getTypeAtLocation(root), name));
15
+ for (const member of members)
16
+ if (member && platformNames.includes(member.name))
17
+ written.add(member);
18
+ }
19
+ function target(node) {
20
+ if (ts.isElementAccessExpression(node)) {
21
+ const keys = literalStrings(node.argumentExpression, checker);
22
+ mark(node.expression, keys);
23
+ return;
24
+ }
25
+ if (ts.isPropertyAccessExpression(node)) {
26
+ const symbol = checker.getSymbolAtLocation(node.name);
27
+ if (globalTarget(node.expression, checker) && symbol && platformNames.includes(symbol.name))
28
+ written.add(symbol);
29
+ return;
30
+ }
31
+ const symbol = checker.getSymbolAtLocation(node);
32
+ if (symbol && platformNames.includes(symbol.name))
33
+ written.add(symbol);
34
+ if (ts.isObjectLiteralExpression(node) ||
35
+ ts.isArrayLiteralExpression(node) ||
36
+ ts.isPropertyAssignment(node) ||
37
+ ts.isSpreadAssignment(node) ||
38
+ ts.isSpreadElement(node))
39
+ ts.forEachChild(node, target);
40
+ }
41
+ function visit(node) {
42
+ if (ts.isBinaryExpression(node) &&
43
+ node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
44
+ node.operatorToken.kind <= ts.SyntaxKind.LastAssignment)
45
+ target(node.left);
46
+ if (ts.isDeleteExpression(node))
47
+ target(node.expression);
48
+ if (ts.isCallExpression(node)) {
49
+ const kind = mutatorKind(node, checker, libraries);
50
+ if (kind && node.arguments[0] && ts.isSpreadElement(node.arguments[0]))
51
+ for (const file of files)
52
+ for (const binding of checker.getSymbolsInScope(file, ts.SymbolFlags.Value | ts.SymbolFlags.Alias)) {
53
+ const symbol = binding.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(binding) : binding;
54
+ if (platformNames.includes(symbol.name))
55
+ written.add(symbol);
56
+ }
57
+ if (kind === 'defineProperty' || kind === 'set') {
58
+ const receiver = node.arguments[0];
59
+ if (receiver && !ts.isSpreadElement(receiver))
60
+ mark(receiver, literalStrings(node.arguments[1] && !ts.isSpreadElement(node.arguments[1])
61
+ ? node.arguments[1]
62
+ : undefined, checker));
63
+ }
64
+ else if (kind === 'assign') {
65
+ const receiver = node.arguments[0];
66
+ if (receiver && !ts.isSpreadElement(receiver) && globalTarget(receiver, checker)) {
67
+ for (const source of node.arguments.slice(1)) {
68
+ if (ts.isSpreadElement(source)) {
69
+ const values = unwrapExpression(source.expression);
70
+ if (ts.isArrayLiteralExpression(values)) {
71
+ for (const value of values.elements)
72
+ if (ts.isSpreadElement(value))
73
+ mark(receiver, undefined);
74
+ else
75
+ mark(receiver, sourceKeys(value, checker));
76
+ }
77
+ else
78
+ mark(receiver, undefined);
79
+ }
80
+ else
81
+ mark(receiver, sourceKeys(source, checker));
82
+ }
83
+ }
84
+ }
85
+ }
86
+ ts.forEachChild(node, visit);
87
+ }
88
+ for (const file of files)
89
+ if (!file.isDeclarationFile)
90
+ visit(file);
91
+ return written;
92
+ }
@@ -0,0 +1,5 @@
1
+ import type { RouteBinding } from './response-contract-route-analysis.mts';
2
+ /** Whether requested protocol keys include a route binding, independent of its protocol variants. */
3
+ export declare function protocolBindingRequested(binding: RouteBinding, requestedKeys?: ReadonlySet<string>): boolean;
4
+ /** Maps one generated protocol key to its requested route spelling and exact variant suffix. */
5
+ export declare function requestedProtocolKey(key: string, binding: RouteBinding, requestedKeys?: ReadonlySet<string>): string | undefined;
@@ -0,0 +1,45 @@
1
+ import { routeShape } from './registered-route-catalog.mjs';
2
+ /** Whether requested protocol keys include a route binding, independent of its protocol variants. */
3
+ export function protocolBindingRequested(binding, requestedKeys) {
4
+ if (!requestedKeys)
5
+ return true;
6
+ return [...requestedKeys].some((key) => {
7
+ const requested = parseProtocolKey(key);
8
+ return requested && sameRoute(requested, binding);
9
+ });
10
+ }
11
+ /** Maps one generated protocol key to its requested route spelling and exact variant suffix. */
12
+ export function requestedProtocolKey(key, binding, requestedKeys) {
13
+ if (!requestedKeys)
14
+ return key;
15
+ if (requestedKeys.size === 0)
16
+ return undefined;
17
+ const generated = parseProtocolKey(key);
18
+ if (!generated || !sameRoute(generated, binding))
19
+ return undefined;
20
+ const candidates = [];
21
+ for (const requestedKey of requestedKeys) {
22
+ const parsed = parseProtocolKey(requestedKey);
23
+ if (parsed?.suffix === generated.suffix && sameRoute(parsed, binding))
24
+ candidates.push({ key: requestedKey, parsed });
25
+ }
26
+ const exact = candidates.find((candidate) => candidate.parsed.base === generated.base);
27
+ return (exact ?? candidates.toSorted((left, right) => left.key.localeCompare(right.key))[0])?.key;
28
+ }
29
+ function parseProtocolKey(key) {
30
+ const suffixStart = key.indexOf('#');
31
+ const base = suffixStart < 0 ? key : key.slice(0, suffixStart);
32
+ const separator = base.indexOf(':');
33
+ if (separator <= 0)
34
+ return undefined;
35
+ return {
36
+ method: base.slice(0, separator),
37
+ routeTemplate: base.slice(separator + 1),
38
+ suffix: suffixStart < 0 ? '' : key.slice(suffixStart),
39
+ base,
40
+ };
41
+ }
42
+ function sameRoute(key, binding) {
43
+ return (key.method === binding.method &&
44
+ routeShape(key.routeTemplate) === routeShape(binding.routeTemplate));
45
+ }
@@ -0,0 +1,7 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ import { type HandlerBindings, type RouteBinding } from './response-contract-route-analysis.mts';
3
+ export declare function sseCallerScope(functions: readonly ts.FunctionLikeDeclaration[], calls: readonly ts.CallExpression[], binding: RouteBinding, bindings: HandlerBindings, checker: ts.TypeChecker): {
4
+ paths: (ts.CallExpression | ts.NewExpression)[][];
5
+ contexts: Set<ts.Symbol>;
6
+ functions: ts.FunctionLikeDeclaration[];
7
+ } | undefined;
@@ -0,0 +1,67 @@
1
+ import ts from '../contract-schema/typescript-api.mjs';
2
+ import { createProtocolCallbackValueResolver } from './protocol-callback-values.mjs';
3
+ import { registeredHandler } from './protocol-callback-registration.mjs';
4
+ import { executableProtocolPath } from './protocol-execution-path.mjs';
5
+ import { enclosingFunction } from './protocol-marker-analysis.mjs';
6
+ import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
7
+ import { expressionReceiver } from './protocol-write-receiver.mjs';
8
+ import { enclosingRouteBinding, } from './response-contract-route-analysis.mjs';
9
+ export function sseCallerScope(functions, calls, binding, bindings, checker) {
10
+ const resolver = createProtocolCallbackValueResolver(checker);
11
+ const matches = calls.flatMap((call) => {
12
+ const fn = resolver.resolve(call.expression, new Map())?.node;
13
+ if (!fn ||
14
+ !functions.includes(fn) ||
15
+ !executableProtocolPath(call, checker))
16
+ return [];
17
+ const route = enclosingRouteBinding(call, checker, bindings, false);
18
+ return route?.method === binding.method && route.routeTemplate === binding.routeTemplate
19
+ ? [{ call, fn: fn }]
20
+ : [];
21
+ });
22
+ if (!matches.length)
23
+ return undefined;
24
+ const contexts = new Set();
25
+ const callerFunctions = new Set();
26
+ const paths = matches.map(({ call }) => {
27
+ const anchors = [call];
28
+ let fn = enclosingFunction(call);
29
+ let outer = fn;
30
+ while (fn) {
31
+ callerFunctions.add(fn);
32
+ let value = fn;
33
+ while (ts.isPropertyAssignment(value.parent) || ts.isObjectLiteralExpression(value.parent))
34
+ value = value.parent;
35
+ if (ts.isCallExpression(value.parent) || ts.isNewExpression(value.parent))
36
+ anchors.push(value.parent);
37
+ outer = fn;
38
+ fn = enclosingFunction(fn);
39
+ }
40
+ const name = outer && runtimeParameters(outer)[0]?.name;
41
+ const context = name && ts.isIdentifier(name) ? checker.getSymbolAtLocation(name) : undefined;
42
+ if (!outer ||
43
+ (name && !context) ||
44
+ !calls.some((node) => registeredHandler(node) &&
45
+ node.arguments.some((argument) => resolver.resolve(argument, new Map())?.node === outer)))
46
+ throw new Error('SSE helper must bind a registered caller context');
47
+ if (context)
48
+ contexts.add(context);
49
+ return anchors;
50
+ });
51
+ if (contexts.size > 1)
52
+ throw new Error('SSE helper has ambiguous caller contexts');
53
+ const context = [...contexts][0];
54
+ for (const { call, fn } of matches)
55
+ runtimeParameters(fn).forEach((parameter, index) => {
56
+ const argument = call.arguments[index];
57
+ const receiver = argument && expressionReceiver(argument, checker);
58
+ if (context &&
59
+ receiver?.root === context &&
60
+ receiver.path.length === 0 &&
61
+ !receiver.mutableAlias &&
62
+ ts.isIdentifier(parameter.name)) {
63
+ contexts.add(checker.getSymbolAtLocation(parameter.name));
64
+ }
65
+ });
66
+ return { paths, contexts, functions: [...callerFunctions] };
67
+ }
@@ -0,0 +1,11 @@
1
+ import ts from '../contract-schema/typescript-api.mts';
2
+ import type { HandlerBindings, RouteBinding } from './response-contract-route-analysis.mts';
3
+ export declare function sseEmission(call: ts.CallExpression, checker: ts.TypeChecker, calls: readonly ts.CallExpression[], binding: RouteBinding, bindings: HandlerBindings): {
4
+ write: ts.CallExpression;
5
+ receiver: import("./protocol-write-receiver.mts").WriteReceiver;
6
+ status: {
7
+ statusCodes?: readonly [number, ...number[]];
8
+ statusKnowledge: 'default' | 'explicit' | 'unknown';
9
+ unavailableReason?: string;
10
+ };
11
+ };