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.
- package/README.md +64 -16
- package/dist/api-contract-discovery/header-contract-registry.mjs +2 -0
- package/dist/api-contract-discovery/protocol-body-status.d.mts +2 -0
- package/dist/api-contract-discovery/protocol-body-status.mjs +4 -0
- package/dist/api-contract-discovery/protocol-callback-argument-bindings.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-callback-argument-bindings.mjs +33 -0
- package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-callback-invocation.mjs +103 -0
- package/dist/api-contract-discovery/protocol-callback-mutations.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-callback-mutations.mjs +42 -0
- package/dist/api-contract-discovery/protocol-callback-registration.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-callback-registration.mjs +27 -0
- package/dist/api-contract-discovery/protocol-callback-values.d.mts +17 -0
- package/dist/api-contract-discovery/protocol-callback-values.mjs +114 -0
- package/dist/api-contract-discovery/protocol-context-alias.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-context-alias.mjs +20 -0
- package/dist/api-contract-discovery/protocol-contract-extraction.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-contract-extraction.mjs +87 -0
- package/dist/api-contract-discovery/protocol-contract-keys.d.mts +1 -0
- package/dist/api-contract-discovery/protocol-contract-keys.mjs +8 -0
- package/dist/api-contract-discovery/protocol-contract-registry.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-contract-registry.mjs +133 -0
- package/dist/api-contract-discovery/protocol-executable-path.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-executable-path.mjs +64 -0
- package/dist/api-contract-discovery/protocol-execution-path.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-execution-path.mjs +16 -0
- package/dist/api-contract-discovery/protocol-http-association.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-http-association.mjs +97 -0
- package/dist/api-contract-discovery/protocol-http-body-kinds.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-http-body-kinds.mjs +19 -0
- package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +6 -0
- package/dist/api-contract-discovery/protocol-http-context-callers.mjs +34 -0
- package/dist/api-contract-discovery/protocol-http-context.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-http-context.mjs +108 -0
- package/dist/api-contract-discovery/protocol-http-emission.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-http-emission.mjs +76 -0
- package/dist/api-contract-discovery/protocol-http-implicit-taint.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-http-implicit-taint.mjs +26 -0
- package/dist/api-contract-discovery/protocol-http-registration.d.mts +12 -0
- package/dist/api-contract-discovery/protocol-http-registration.mjs +53 -0
- package/dist/api-contract-discovery/protocol-marker-analysis.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-marker-analysis.mjs +50 -0
- package/dist/api-contract-discovery/protocol-opaque-callback.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-opaque-callback.mjs +107 -0
- package/dist/api-contract-discovery/protocol-platform-callbacks.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-platform-callbacks.mjs +72 -0
- package/dist/api-contract-discovery/protocol-platform-mutation-targets.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-platform-mutation-targets.mjs +89 -0
- package/dist/api-contract-discovery/protocol-platform-writes.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-platform-writes.mjs +92 -0
- package/dist/api-contract-discovery/protocol-requested-keys.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-requested-keys.mjs +45 -0
- package/dist/api-contract-discovery/protocol-sse-callers.d.mts +7 -0
- package/dist/api-contract-discovery/protocol-sse-callers.mjs +67 -0
- package/dist/api-contract-discovery/protocol-sse-emission.d.mts +11 -0
- package/dist/api-contract-discovery/protocol-sse-emission.mjs +61 -0
- package/dist/api-contract-discovery/protocol-sse-feasible-status.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-sse-feasible-status.mjs +24 -0
- package/dist/api-contract-discovery/protocol-sse-raw-writes.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-sse-raw-writes.mjs +70 -0
- package/dist/api-contract-discovery/protocol-sse-status.d.mts +3 -0
- package/dist/api-contract-discovery/protocol-sse-status.mjs +84 -0
- package/dist/api-contract-discovery/protocol-sse-write-access.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-sse-write-access.mjs +42 -0
- package/dist/api-contract-discovery/protocol-sse-write-helpers.d.mts +6 -0
- package/dist/api-contract-discovery/protocol-sse-write-helpers.mjs +66 -0
- package/dist/api-contract-discovery/protocol-sse-write-mutations.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-sse-write-mutations.mjs +36 -0
- package/dist/api-contract-discovery/protocol-sse-write-resolution.d.mts +14 -0
- package/dist/api-contract-discovery/protocol-sse-write-resolution.mjs +79 -0
- package/dist/api-contract-discovery/protocol-status-dominance.d.mts +5 -0
- package/dist/api-contract-discovery/protocol-status-dominance.mjs +35 -0
- package/dist/api-contract-discovery/protocol-write-receiver.d.mts +9 -0
- package/dist/api-contract-discovery/protocol-write-receiver.mjs +79 -0
- package/dist/api-contract-discovery/query-contract-registry.mjs +2 -0
- package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-binding-writes.mjs +30 -0
- package/dist/api-contract-discovery/registered-route-catalog.mjs +11 -7
- package/dist/api-contract-discovery/registered-route-factory-returns.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-factory-returns.mjs +29 -0
- package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +5 -1
- package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +79 -35
- package/dist/api-contract-discovery/registered-route-runtime-parameters.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-runtime-parameters.mjs +5 -0
- package/dist/api-contract-discovery/registered-route-terminal-error.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-terminal-error.mjs +98 -0
- package/dist/api-contract-discovery/request-contract-registry.mjs +2 -0
- package/dist/api-contract-discovery/response-contract-implicit.mjs +28 -10
- package/dist/api-contract-discovery/response-contract-registry.mjs +14 -1
- package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-route-analysis.mjs +22 -4
- package/dist/api-contract-discovery/response-contract-status.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-status.mjs +10 -8
- package/dist/api-contract-discovery/response-contract-types.d.mts +7 -0
- package/dist/cli/index.mjs +0 -3
- package/dist/cli/parse-forwarded-commands.d.mts +0 -3
- package/dist/cli/parse-forwarded-commands.mjs +0 -2
- package/dist/cli/parse.d.mts +0 -3
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +0 -5
- package/dist/contract-schema/contract-schema-bounded-array.d.mts +1 -3
- package/dist/contract-schema/contract-schema-bounded-array.mjs +5 -0
- package/dist/contract-schema/contract-schema-object-tuple.mjs +7 -5
- package/dist/contract-schema/contract-schema-type-extractor.mjs +13 -0
- package/dist/contract-schema/contract-schema-validator.mjs +2 -2
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +0 -1
- package/dist/openapi-document/build-openapi-catalog-sse.d.mts +4 -0
- package/dist/openapi-document/build-openapi-catalog-sse.mjs +27 -0
- package/dist/openapi-document/build-openapi-document.mjs +5 -2
- package/dist/openapi-document/build-openapi-response.d.mts +2 -2
- package/dist/openapi-document/build-openapi-response.mjs +33 -3
- package/dist/openapi-document/build-openapi-sse-events.d.mts +11 -0
- package/dist/openapi-document/build-openapi-sse-events.mjs +46 -0
- package/dist/openapi-document/contract-schema-intersection-merge.mjs +11 -11
- package/dist/openapi-document/contract-schema-to-openapi.mjs +3 -3
- package/dist/openapi-document/openapi-route-helpers.mjs +1 -3
- package/dist/openapi-document/openapi-types.d.mts +3 -0
- package/dist/openapi-document/operation-types.d.mts +7 -0
- package/package.json +3 -8
- package/dist/agent-harness-config/apply.d.mts +0 -4
- package/dist/agent-harness-config/apply.mjs +0 -72
- package/dist/agent-harness-config/cli.d.mts +0 -10
- package/dist/agent-harness-config/cli.mjs +0 -100
- package/dist/agent-harness-config/index.d.mts +0 -4
- package/dist/agent-harness-config/index.mjs +0 -3
- package/dist/agent-harness-config/merge-json.d.mts +0 -5
- package/dist/agent-harness-config/merge-json.mjs +0 -62
- package/dist/agent-harness-config/merge-toml.d.mts +0 -6
- package/dist/agent-harness-config/merge-toml.mjs +0 -132
- package/dist/agent-harness-config/paths.d.mts +0 -3
- package/dist/agent-harness-config/paths.mjs +0 -117
- package/dist/agent-harness-config/policy.d.mts +0 -14
- package/dist/agent-harness-config/policy.mjs +0 -51
- package/dist/agent-harness-config/prerequisites.d.mts +0 -2
- package/dist/agent-harness-config/prerequisites.mjs +0 -67
- package/dist/agent-harness-config/toml-value.d.mts +0 -6
- package/dist/agent-harness-config/toml-value.mjs +0 -69
- package/dist/agent-harness-config/types.d.mts +0 -90
- package/dist/agent-harness-config/types.mjs +0 -2
- package/docs/agent-harness-config.md +0 -134
package/README.md
CHANGED
|
@@ -34,9 +34,6 @@ vouchington runner-port-policy
|
|
|
34
34
|
vouchington runner-port-policy --file ./policy.json
|
|
35
35
|
vouchington runner-port-policy --reserved 2200
|
|
36
36
|
vouchington with-host-lock --name expensive-build --timeout-seconds 60 -- make build
|
|
37
|
-
vouchington agent-harness-config dump
|
|
38
|
-
vouchington agent-harness-config check --global
|
|
39
|
-
vouchington agent-harness-config apply --global --repo /path/to/checkout
|
|
40
37
|
vouchington gha-runtime-audit --pr-workflow CI --push-workflow '/^Main CI \\(.+\\)$/'
|
|
41
38
|
vouchington require-up-to-date --remote origin --branch main
|
|
42
39
|
vouchington gitleaks-directory-scan --config .gitleaks.toml
|
|
@@ -199,11 +196,6 @@ import {
|
|
|
199
196
|
import { initSqlAst, extractCreateTableMetadata } from 'vouchington-tooling/sql-ast'
|
|
200
197
|
import { splitSqlStatements, stripSqlComments } from 'vouchington-tooling/sql-scanner'
|
|
201
198
|
import { auditCiJobRuntime } from 'vouchington-tooling/gha-runtime-audit'
|
|
202
|
-
import {
|
|
203
|
-
applyHarnessConfig,
|
|
204
|
-
checkHarnessConfig,
|
|
205
|
-
dumpHarnessPolicy,
|
|
206
|
-
} from 'vouchington-tooling/agent-harness-config'
|
|
207
199
|
import {
|
|
208
200
|
inspectHarnessEnvironment,
|
|
209
201
|
selectHarnessSession,
|
|
@@ -374,14 +366,8 @@ summaries and quoted attributes; it is not a general HTML validator or sanitizer
|
|
|
374
366
|
the input or infers semantic relevance, cost, schema conformance, or failure causes. The existing
|
|
375
367
|
`markdownSectionBetweenHeadings` API retains its original boundary behavior.
|
|
376
368
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
home-directory configs; `--repo` updates a checkout. It does not copy allowlists, hooks, or plugins.
|
|
380
|
-
Grok uses `permission_mode = "auto"` plus `auto_mode.enabled` and `default_auto_mode` so the
|
|
381
|
-
classifier stays available in plan mode; its defined sandbox profile still requires
|
|
382
|
-
`--sandbox workspace-write` at launch. Cursor uses global `approvalMode = "auto-review"`;
|
|
383
|
-
`unrestricted` disables the sandbox. Repo checks surface user-level trust or mode prerequisites
|
|
384
|
-
instead of claiming that repo files alone activate them.
|
|
369
|
+
Machine-wide agent configuration now belongs to [vouchington-machines](https://github.com/vouchington/vouchington-machines/blob/main/docs/agent-config.md).
|
|
370
|
+
Use its `configure-agents.sh` and `diagnose-agents.sh`; project hooks and MCP configuration remain in their checkouts.
|
|
385
371
|
|
|
386
372
|
`agent-harness-identity` inspects the four harness environment signals without choosing a winner.
|
|
387
373
|
Callers supply their own precedence to `selectHarnessSession`, keeping product-specific identity
|
|
@@ -470,6 +456,68 @@ Version one recognizes literal-key `apiResponse`, `apiNoContent`, `apiOpenApiRaw
|
|
|
470
456
|
`ctx.setStatus`, and static `Content-Type` setters. The input source files and TypeScript checker
|
|
471
457
|
are the source representation; the adapter does not parse or discover a repository on its own.
|
|
472
458
|
|
|
459
|
+
Error-only 405 routes require a terminal throw on the handler's own context. Named handlers and
|
|
460
|
+
imported wrappers are followed through compiler declarations; conditional, caught, recursive,
|
|
461
|
+
fallthrough, and callback-only throws do not prove an error-only route. Empty `never[]` contracts
|
|
462
|
+
render as arrays with no items, and `Record<string, never>` renders as a closed empty object.
|
|
463
|
+
Standalone `never` and `any` remain unsupported contracts.
|
|
464
|
+
|
|
465
|
+
Caller-owned `apiSseFrame(key, { event, data })` markers expose literal event names and concrete
|
|
466
|
+
payloads from the frames actually written by a route, including callbacks passed to a handler
|
|
467
|
+
factory. SSE responses keep a string body schema and describe each payload under the media
|
|
468
|
+
type's `x-sse-events` map. Callback arguments require a concrete helper invocation path; ignored,
|
|
469
|
+
declaration-only, deferred, or rewritten callbacks fail closed. Callback options passed to a callee
|
|
470
|
+
without a concrete implementation also fail closed. Opaque callback escape checks include array and
|
|
471
|
+
tuple containers, nested object properties, and literal array spreads. Factory callbacks must execute
|
|
472
|
+
from the returned request handler; registration-time invocations do not prove a request emission. Compiler-resolved platform timers
|
|
473
|
+
and Promise executors supply invocation paths using the caller Program's actual standard-library
|
|
474
|
+
identities; shadowed, custom, or reassigned platform APIs do not. Mutation checks recognize Node's
|
|
475
|
+
compiler-declared `global` as well as `globalThis`. Fluent route registrations retain their method. Stable named
|
|
476
|
+
handlers and called local helpers are followed within their declaring source file. Ordinary response,
|
|
477
|
+
request, query, and header markers also require callback invocation before route attribution.
|
|
478
|
+
Broad names, unknown root payloads, executable unmarked frames through stream aliases, and
|
|
479
|
+
conditional or mutable-context status changes fail closed. Called SSE helpers inherit the proven
|
|
480
|
+
request context and its status at the invocation; mutually exclusive complete status branches retain
|
|
481
|
+
their separate responses. Explicit SSE statuses must be integers from 100 through 599.
|
|
482
|
+
Unconditional blocks preserve the last proven status setter; conditional or foreign overwrites fail closed.
|
|
483
|
+
Indirect `setStatus.call`, `apply`, and `bind` forms fail closed instead of assuming status 200.
|
|
484
|
+
Ambiguous caller contexts fail closed. A raw stream failure invalidates all
|
|
485
|
+
selected SSE rows, including when only one protocol variant was requested. Object-literal wrapper
|
|
486
|
+
receivers fail closed for marked frames because their properties can mutate; raw wrapper writes
|
|
487
|
+
are matched to the original stream, including proven helper-parameter forwarding, overloaded helper
|
|
488
|
+
implementations, and literal bracket
|
|
489
|
+
methods. Compiler-resolved `Reflect.apply` calls use their actual receiver and payload.
|
|
490
|
+
Unsupported computed methods on that receiver fail closed. Byte-bearing
|
|
491
|
+
stream `end` calls and streams used as pipe destinations are raw emissions; empty terminal cleanup
|
|
492
|
+
is preserved. Replacing a selected stream's `write` implementation invalidates its frame evidence.
|
|
493
|
+
Binding a selected stream's `write` or `end` method fails closed because the bound
|
|
494
|
+
method can escape the proven emission path. A callback that captures
|
|
495
|
+
the route's stream or HTTP context and escapes to a callee without a concrete implementation can
|
|
496
|
+
write undocumented bytes, so the affected response contracts fail closed. Uncalled local callbacks,
|
|
497
|
+
dead branches, and implemented helpers that ignore their callback preserve the proven contracts.
|
|
498
|
+
Cataloged SSE routes with only extracted non-SSE variants retain an unavailable event-stream
|
|
499
|
+
possibility alongside those responses until the extracted variants describe the stream.
|
|
500
|
+
|
|
501
|
+
Caller-owned `apiOpenApiHttpResponse(key, response)` markers bind a constant opaque response
|
|
502
|
+
carrying optional readonly `apiHttpResponseVariants` metadata. Each concrete union variant has
|
|
503
|
+
`status` and `bodyKind`; content variants also have `mediaType` and `body`. Discovery associates
|
|
504
|
+
the marked variable's status and body emissions without reading or replacing response bytes.
|
|
505
|
+
A status setter must dominate each body emission, including separate branches; constant context
|
|
506
|
+
aliases are followed, assigned mutable context aliases and object-literal context wrappers fail closed,
|
|
507
|
+
and helper parameters must resolve to the registered caller's unchanged response context.
|
|
508
|
+
Helper-owned contracts also account for response emissions in their proven caller scopes.
|
|
509
|
+
Indirect response `call`, `apply`, and `bind` forms fail closed, including borrowed context receivers.
|
|
510
|
+
SSE frames and branded content variants reject body-forbidden 1xx, 204, 205, and 304 statuses.
|
|
511
|
+
Literal bracket response methods have the same emission checks as property methods. Unrelated raw emissions
|
|
512
|
+
remain unavailable. Separate branded response branches are checked together, and the nearest
|
|
513
|
+
proven status setter for each body must belong to that response. Observed content and bodyless
|
|
514
|
+
emissions must match the complete metadata union, including an empty response in the `else` branch
|
|
515
|
+
of a positive body check. Exact requested protocol rows are selected before
|
|
516
|
+
other payloads, media types, or statuses are validated. In lenient mode, incomplete marker evidence
|
|
517
|
+
keeps every selected sibling unavailable. Explicit 400 content variants also include the framework's
|
|
518
|
+
JSON error schema independently of their own media type. Explicit bodyless 400 metadata also
|
|
519
|
+
retains that framework JSON possibility and fails closed on the conflicting body kinds.
|
|
520
|
+
|
|
473
521
|
Fork diagnostics use a caller-selected `recordDirectory` in both the worker setup file and the
|
|
474
522
|
main-process reporter. Call `registerForkExitSentinel({ recordDirectory })` once per fork, then
|
|
475
523
|
update `setCurrentForkExitModule` and `setCurrentForkExitProject` around tests. Construct
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { registerPlatformCompilerLibraries } from './protocol-platform-callbacks.mjs';
|
|
2
3
|
import { contractError } from './response-contract-registration.mjs';
|
|
3
4
|
import { parseHeaderContract } from './header-contract-parser.mjs';
|
|
4
5
|
import { collectHandlerBindings, enclosingRouteBinding, visit, } from './response-contract-route-analysis.mjs';
|
|
5
6
|
export function discoverApiHeaderContracts(program, sourceFiles, knownResponseRoutes) {
|
|
7
|
+
registerPlatformCompilerLibraries(program);
|
|
6
8
|
const checker = program.getTypeChecker();
|
|
7
9
|
const bindings = collectHandlerBindings(sourceFiles, checker);
|
|
8
10
|
const contracts = new Map();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { CallbackBindings, createProtocolCallbackValueResolver } from './protocol-callback-values.mts';
|
|
3
|
+
/** Binds literal callback parameters, declining unsupported destructuring shapes. */
|
|
4
|
+
export declare function callbackArgumentBindings(fn: ts.FunctionLikeDeclaration, call: ts.CallExpression | ts.NewExpression, env: CallbackBindings, captured: CallbackBindings, checker: ts.TypeChecker, resolver: ReturnType<typeof createProtocolCallbackValueResolver>): Map<ts.Symbol, {
|
|
5
|
+
node: ts.Node;
|
|
6
|
+
env: CallbackBindings;
|
|
7
|
+
}> | undefined;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
|
|
3
|
+
/** Binds literal callback parameters, declining unsupported destructuring shapes. */
|
|
4
|
+
export function callbackArgumentBindings(fn, call, env, captured, checker, resolver) {
|
|
5
|
+
const next = new Map(captured);
|
|
6
|
+
for (const [index, parameter] of runtimeParameters(fn).entries()) {
|
|
7
|
+
const argument = call.arguments?.[index];
|
|
8
|
+
if (!argument)
|
|
9
|
+
continue;
|
|
10
|
+
if (ts.isSpreadElement(argument))
|
|
11
|
+
return undefined;
|
|
12
|
+
if (ts.isIdentifier(parameter.name)) {
|
|
13
|
+
next.set(checker.getSymbolAtLocation(parameter.name), { node: argument, env });
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
if (!ts.isObjectBindingPattern(parameter.name))
|
|
17
|
+
return undefined;
|
|
18
|
+
const value = resolver.resolve(argument, env);
|
|
19
|
+
for (const element of parameter.name.elements) {
|
|
20
|
+
const key = element.propertyName ?? element.name;
|
|
21
|
+
if (!ts.isIdentifier(element.name) ||
|
|
22
|
+
element.dotDotDotToken ||
|
|
23
|
+
element.initializer ||
|
|
24
|
+
!(ts.isIdentifier(key) || ts.isStringLiteral(key)))
|
|
25
|
+
return undefined;
|
|
26
|
+
const bound = resolver.property(value, key.text, new Set());
|
|
27
|
+
if (bound)
|
|
28
|
+
next.set(checker.getSymbolAtLocation(element.name), bound);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return next;
|
|
33
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
type FunctionNode = ts.FunctionLikeDeclaration;
|
|
3
|
+
/** Proves callback consumption through concrete helper bodies, without executing nested closures. */
|
|
4
|
+
export declare function isSupportedProtocolCallback(fn: FunctionNode, checker: ts.TypeChecker): boolean;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
|
|
3
|
+
import { invokedResult, registeredHandler } from './protocol-callback-registration.mjs';
|
|
4
|
+
import { platformCallbackArgument } from './protocol-platform-callbacks.mjs';
|
|
5
|
+
import { potentiallyExecuted } from './protocol-executable-path.mjs';
|
|
6
|
+
import { callbackBindingReplaced, callbackOptionsEscape } from './protocol-callback-mutations.mjs';
|
|
7
|
+
import { enclosingFunction } from './protocol-marker-analysis.mjs';
|
|
8
|
+
import { createProtocolCallbackValueResolver, isProtocolCallbackFunction as functionNode, protocolCallbackSourceCalls, protocolCallbackHasWrittenBindings, } from './protocol-callback-values.mjs';
|
|
9
|
+
/** Proves callback consumption through concrete helper bodies, without executing nested closures. */
|
|
10
|
+
export function isSupportedProtocolCallback(fn, checker) {
|
|
11
|
+
if (!fn.body || fn.asteriskToken || protocolCallbackHasWrittenBindings(fn, checker))
|
|
12
|
+
return false;
|
|
13
|
+
const resolver = createProtocolCallbackValueResolver(checker);
|
|
14
|
+
const { resolve, symbol } = resolver;
|
|
15
|
+
let invalidated = false;
|
|
16
|
+
function consumed(call, env, active, returned = false) {
|
|
17
|
+
const platform = platformCallbackArgument(call, checker);
|
|
18
|
+
const callee = resolve(platform ?? call.expression, env);
|
|
19
|
+
if (!callee || !functionNode(callee.node) || !callee.node.body || callee.node.asteriskToken) {
|
|
20
|
+
invalidated ||= callbackOptionsEscape(call, env, fn, resolver);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (protocolCallbackHasWrittenBindings(callee.node, checker)) {
|
|
24
|
+
invalidated = true;
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (callee.node === fn)
|
|
28
|
+
return !returned;
|
|
29
|
+
if (active.has(callee.node))
|
|
30
|
+
return false;
|
|
31
|
+
const chain = new Set([...active, callee.node]);
|
|
32
|
+
if (platform)
|
|
33
|
+
return body(callee.node.body, callee.env, chain, false);
|
|
34
|
+
const next = new Map(callee.env);
|
|
35
|
+
runtimeParameters(callee.node).forEach((parameter, index) => {
|
|
36
|
+
const argument = call.arguments[index];
|
|
37
|
+
const target = symbol(parameter.name);
|
|
38
|
+
if (argument && !ts.isSpreadElement(argument))
|
|
39
|
+
next.set(target, { node: argument, env });
|
|
40
|
+
});
|
|
41
|
+
return body(callee.node.body, next, chain, returned || invokedResult(call));
|
|
42
|
+
}
|
|
43
|
+
function body(content, env, active, returned) {
|
|
44
|
+
let found = false;
|
|
45
|
+
let replaced = false;
|
|
46
|
+
function visit(node) {
|
|
47
|
+
if (found || replaced || !potentiallyExecuted(node))
|
|
48
|
+
return;
|
|
49
|
+
if (functionNode(node))
|
|
50
|
+
return;
|
|
51
|
+
if (callbackBindingReplaced(node, env, fn, resolver)) {
|
|
52
|
+
replaced = true;
|
|
53
|
+
invalidated = true;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if ((ts.isCallExpression(node) || ts.isNewExpression(node)) &&
|
|
57
|
+
consumed(node, env, active, returned && ts.isReturnStatement(node.parent)) &&
|
|
58
|
+
(!returned || ts.isReturnStatement(node.parent))) {
|
|
59
|
+
found = true;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (returned && ts.isReturnStatement(node) && node.expression) {
|
|
63
|
+
const value = resolve(node.expression, env);
|
|
64
|
+
if (value &&
|
|
65
|
+
functionNode(value.node) &&
|
|
66
|
+
value.node.body &&
|
|
67
|
+
!value.node.asteriskToken &&
|
|
68
|
+
!active.has(value.node) &&
|
|
69
|
+
body(value.node.body, value.env, new Set([...active, value.node]), false)) {
|
|
70
|
+
found = true;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
ts.forEachChild(node, visit);
|
|
75
|
+
}
|
|
76
|
+
visit(content);
|
|
77
|
+
return found && !invalidated;
|
|
78
|
+
}
|
|
79
|
+
let argument = fn;
|
|
80
|
+
while (ts.isPropertyAssignment(argument.parent) ||
|
|
81
|
+
ts.isObjectLiteralExpression(argument.parent) ||
|
|
82
|
+
ts.isParenthesizedExpression(argument.parent) ||
|
|
83
|
+
ts.isAsExpression(argument.parent) ||
|
|
84
|
+
ts.isNonNullExpression(argument.parent))
|
|
85
|
+
argument = argument.parent;
|
|
86
|
+
const call = argument.parent;
|
|
87
|
+
if (!ts.isCallExpression(call) && !ts.isNewExpression(call)) {
|
|
88
|
+
const owner = enclosingFunction(fn);
|
|
89
|
+
if (owner && isSupportedProtocolCallback(owner, checker))
|
|
90
|
+
return body(owner.body, new Map(), new Set([owner]), false);
|
|
91
|
+
return protocolCallbackSourceCalls(fn).some((candidate) => potentiallyExecuted(candidate) &&
|
|
92
|
+
((registeredHandler(candidate) &&
|
|
93
|
+
candidate.arguments.some((value) => resolve(value, new Map())?.node === fn)) ||
|
|
94
|
+
consumed(candidate, new Map(), new Set())));
|
|
95
|
+
}
|
|
96
|
+
if (!potentiallyExecuted(call))
|
|
97
|
+
return false;
|
|
98
|
+
if (ts.isCallExpression(call) && call.expression === argument)
|
|
99
|
+
return true;
|
|
100
|
+
if (argument === fn && ts.isCallExpression(call) && registeredHandler(call))
|
|
101
|
+
return true;
|
|
102
|
+
return (!!call.arguments?.includes(argument) && consumed(call, new Map(), new Set()));
|
|
103
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { CallbackBindings, createProtocolCallbackValueResolver } from './protocol-callback-values.mts';
|
|
3
|
+
export declare function callbackBindingReplaced(node: ts.Node, env: CallbackBindings, fn: ts.FunctionLikeDeclaration, resolver: ReturnType<typeof createProtocolCallbackValueResolver>): boolean;
|
|
4
|
+
/** An opaque callee may replace a callback stored in a forwarded options object. */
|
|
5
|
+
export declare function callbackOptionsEscape(node: ts.CallExpression | ts.NewExpression, env: CallbackBindings, fn: ts.FunctionLikeDeclaration, resolver: ReturnType<typeof createProtocolCallbackValueResolver>): boolean;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { unwrapExpression } from './protocol-marker-analysis.mjs';
|
|
3
|
+
export function callbackBindingReplaced(node, env, fn, resolver) {
|
|
4
|
+
const { resolve, symbol } = resolver;
|
|
5
|
+
const write = ts.isDeleteExpression(node)
|
|
6
|
+
? node.expression
|
|
7
|
+
: ts.isBinaryExpression(node) &&
|
|
8
|
+
node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
|
|
9
|
+
node.operatorToken.kind <= ts.SyntaxKind.LastAssignment
|
|
10
|
+
? node.left
|
|
11
|
+
: undefined;
|
|
12
|
+
if (!write)
|
|
13
|
+
return false;
|
|
14
|
+
let root = unwrapExpression(write);
|
|
15
|
+
while (ts.isPropertyAccessExpression(root) || ts.isElementAccessExpression(root))
|
|
16
|
+
root = unwrapExpression(root.expression);
|
|
17
|
+
return (ts.isIdentifier(root) && env.has(symbol(root))) || resolve(write, env)?.node === fn;
|
|
18
|
+
}
|
|
19
|
+
/** An opaque callee may replace a callback stored in a forwarded options object. */
|
|
20
|
+
export function callbackOptionsEscape(node, env, fn, resolver) {
|
|
21
|
+
function contains(value, bindings, seen = new Set()) {
|
|
22
|
+
const resolved = resolver.resolve(value, bindings);
|
|
23
|
+
if (!resolved || seen.has(resolved.node))
|
|
24
|
+
return false;
|
|
25
|
+
seen.add(resolved.node);
|
|
26
|
+
if (resolved.node === fn)
|
|
27
|
+
return true;
|
|
28
|
+
if (ts.isArrayLiteralExpression(resolved.node))
|
|
29
|
+
return resolved.node.elements.some((element) => contains(ts.isSpreadElement(element) ? element.expression : element, resolved.env, seen));
|
|
30
|
+
if (!ts.isObjectLiteralExpression(resolved.node))
|
|
31
|
+
return false;
|
|
32
|
+
return resolved.node.properties.some((member) => member === fn ||
|
|
33
|
+
(ts.isPropertyAssignment(member)
|
|
34
|
+
? contains(member.initializer, resolved.env, seen)
|
|
35
|
+
: ts.isShorthandPropertyAssignment(member) &&
|
|
36
|
+
(() => {
|
|
37
|
+
const target = resolver.property(resolved, member.name.text, new Set());
|
|
38
|
+
return !!target && contains(target.node, target.env, seen);
|
|
39
|
+
})()));
|
|
40
|
+
}
|
|
41
|
+
return !!node.arguments?.some((argument) => contains(argument, env));
|
|
42
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { unwrapExpression } from './protocol-marker-analysis.mjs';
|
|
3
|
+
export function registeredHandler(call) {
|
|
4
|
+
const method = unwrapExpression(call.expression);
|
|
5
|
+
if (!ts.isPropertyAccessExpression(method) ||
|
|
6
|
+
!['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(method.name.text))
|
|
7
|
+
return false;
|
|
8
|
+
let receiver = method.expression;
|
|
9
|
+
while (ts.isCallExpression(receiver) && ts.isPropertyAccessExpression(receiver.expression)) {
|
|
10
|
+
if (receiver.expression.name.text === 'route')
|
|
11
|
+
return true;
|
|
12
|
+
receiver = receiver.expression.expression;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
export function invokedResult(call) {
|
|
17
|
+
let node = call;
|
|
18
|
+
while (ts.isParenthesizedExpression(node.parent) ||
|
|
19
|
+
ts.isAsExpression(node.parent) ||
|
|
20
|
+
ts.isNonNullExpression(node.parent) ||
|
|
21
|
+
ts.isAwaitExpression(node.parent))
|
|
22
|
+
node = node.parent;
|
|
23
|
+
const parent = node.parent;
|
|
24
|
+
return (ts.isCallExpression(parent) &&
|
|
25
|
+
(parent.expression === node ||
|
|
26
|
+
(registeredHandler(parent) && parent.arguments.includes(node))));
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
type FunctionNode = ts.FunctionLikeDeclaration;
|
|
3
|
+
export type CallbackValue = {
|
|
4
|
+
node: ts.Node;
|
|
5
|
+
env: CallbackBindings;
|
|
6
|
+
};
|
|
7
|
+
export type CallbackBindings = ReadonlyMap<ts.Symbol, CallbackValue>;
|
|
8
|
+
export declare function isProtocolCallbackFunction(node: ts.Node): node is FunctionNode;
|
|
9
|
+
/** Resolves stable named uses in outer statements and directly registered inline handlers. */
|
|
10
|
+
export declare function protocolCallbackSourceCalls(fn: FunctionNode): ts.CallExpression[];
|
|
11
|
+
export declare function createProtocolCallbackValueResolver(checker: ts.TypeChecker): {
|
|
12
|
+
resolve: (node: ts.Node, env: CallbackBindings, seen?: Set<ts.Node>) => CallbackValue | undefined;
|
|
13
|
+
symbol: (node: ts.Node) => ts.Symbol | undefined;
|
|
14
|
+
property: (value: CallbackValue | undefined, name: string, seen: Set<ts.Node>) => CallbackValue | undefined;
|
|
15
|
+
};
|
|
16
|
+
export declare function protocolCallbackHasWrittenBindings(fn: FunctionNode, checker: ts.TypeChecker): boolean;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { registeredHandler } from './protocol-callback-registration.mjs';
|
|
3
|
+
import { runtimeParameters } from './registered-route-runtime-parameters.mjs';
|
|
4
|
+
import { hasBindingWrite } from './registered-route-binding-writes.mjs';
|
|
5
|
+
import { unwrapExpression } from './protocol-marker-analysis.mjs';
|
|
6
|
+
export function isProtocolCallbackFunction(node) {
|
|
7
|
+
return ts.isFunctionLike(node) && 'body' in node;
|
|
8
|
+
}
|
|
9
|
+
/** Resolves stable named uses in outer statements and directly registered inline handlers. */
|
|
10
|
+
export function protocolCallbackSourceCalls(fn) {
|
|
11
|
+
const declaration = fn.parent;
|
|
12
|
+
if (!ts.isFunctionDeclaration(fn) &&
|
|
13
|
+
(!ts.isVariableDeclaration(declaration) ||
|
|
14
|
+
!ts.isIdentifier(declaration.name) ||
|
|
15
|
+
!ts.isVariableDeclarationList(declaration.parent) ||
|
|
16
|
+
!(declaration.parent.flags & ts.NodeFlags.Const)))
|
|
17
|
+
return [];
|
|
18
|
+
const calls = [];
|
|
19
|
+
function visit(node) {
|
|
20
|
+
if (isProtocolCallbackFunction(node) &&
|
|
21
|
+
!(ts.isCallExpression(node.parent) &&
|
|
22
|
+
registeredHandler(node.parent) &&
|
|
23
|
+
node.parent.arguments.includes(node)))
|
|
24
|
+
return;
|
|
25
|
+
if (ts.isCallExpression(node))
|
|
26
|
+
calls.push(node);
|
|
27
|
+
ts.forEachChild(node, visit);
|
|
28
|
+
}
|
|
29
|
+
visit(fn.getSourceFile());
|
|
30
|
+
return calls;
|
|
31
|
+
}
|
|
32
|
+
function literalName(node) {
|
|
33
|
+
return ts.isIdentifier(node) || ts.isStringLiteral(node) ? node.text : undefined;
|
|
34
|
+
}
|
|
35
|
+
export function createProtocolCallbackValueResolver(checker) {
|
|
36
|
+
function symbol(node) {
|
|
37
|
+
const found = checker.getSymbolAtLocation(node);
|
|
38
|
+
return found?.flags && found.flags & ts.SymbolFlags.Alias
|
|
39
|
+
? checker.getAliasedSymbol(found)
|
|
40
|
+
: found;
|
|
41
|
+
}
|
|
42
|
+
function property(value, name, seen) {
|
|
43
|
+
if (!value ||
|
|
44
|
+
!ts.isObjectLiteralExpression(value.node) ||
|
|
45
|
+
value.node.properties.some(ts.isSpreadAssignment))
|
|
46
|
+
return undefined;
|
|
47
|
+
const member = value.node.properties.find((item) => item.name && literalName(item.name) === name);
|
|
48
|
+
if (member && ts.isPropertyAssignment(member))
|
|
49
|
+
return resolve(member.initializer, value.env, seen);
|
|
50
|
+
if (member && ts.isShorthandPropertyAssignment(member)) {
|
|
51
|
+
const target = checker.getShorthandAssignmentValueSymbol(member);
|
|
52
|
+
return target ? declaration(target, value.env, seen) : undefined;
|
|
53
|
+
}
|
|
54
|
+
return member && isProtocolCallbackFunction(member)
|
|
55
|
+
? { node: member, env: value.env }
|
|
56
|
+
: undefined;
|
|
57
|
+
}
|
|
58
|
+
function declaration(target, env, seen) {
|
|
59
|
+
const bound = env.get(target);
|
|
60
|
+
if (bound)
|
|
61
|
+
return resolve(bound.node, bound.env, seen);
|
|
62
|
+
const implemented = target.declarations?.find((item) => isProtocolCallbackFunction(item) && item.body);
|
|
63
|
+
if (implemented)
|
|
64
|
+
return { node: implemented, env };
|
|
65
|
+
const node = target.valueDeclaration;
|
|
66
|
+
if (!node || seen.has(node))
|
|
67
|
+
return undefined;
|
|
68
|
+
seen.add(node);
|
|
69
|
+
if (ts.isVariableDeclaration(node) &&
|
|
70
|
+
node.initializer &&
|
|
71
|
+
ts.isVariableDeclarationList(node.parent) &&
|
|
72
|
+
node.parent.flags & ts.NodeFlags.Const)
|
|
73
|
+
return resolve(node.initializer, env, seen);
|
|
74
|
+
if (ts.isBindingElement(node) && ts.isObjectBindingPattern(node.parent)) {
|
|
75
|
+
const owner = node.parent.parent;
|
|
76
|
+
const name = literalName(node.propertyName ?? node.name);
|
|
77
|
+
if (name &&
|
|
78
|
+
ts.isVariableDeclaration(owner) &&
|
|
79
|
+
owner.initializer &&
|
|
80
|
+
ts.isVariableDeclarationList(owner.parent) &&
|
|
81
|
+
owner.parent.flags & ts.NodeFlags.Const)
|
|
82
|
+
return property(resolve(owner.initializer, env, seen), name, seen);
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
function resolve(node, env, seen = new Set()) {
|
|
87
|
+
if (ts.isExpression(node))
|
|
88
|
+
node = unwrapExpression(node);
|
|
89
|
+
if (seen.has(node))
|
|
90
|
+
return undefined;
|
|
91
|
+
seen.add(node);
|
|
92
|
+
if (isProtocolCallbackFunction(node) ||
|
|
93
|
+
ts.isObjectLiteralExpression(node) ||
|
|
94
|
+
ts.isArrayLiteralExpression(node))
|
|
95
|
+
return { node, env };
|
|
96
|
+
if (ts.isIdentifier(node)) {
|
|
97
|
+
const target = symbol(node);
|
|
98
|
+
return target ? declaration(target, env, seen) : undefined;
|
|
99
|
+
}
|
|
100
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
101
|
+
const member = property(resolve(node.expression, env, seen), node.name.text, seen);
|
|
102
|
+
if (member)
|
|
103
|
+
return member;
|
|
104
|
+
const target = symbol(node.name);
|
|
105
|
+
return target ? declaration(target, env, seen) : undefined;
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
return { resolve, symbol, property };
|
|
110
|
+
}
|
|
111
|
+
export function protocolCallbackHasWrittenBindings(fn, checker) {
|
|
112
|
+
return ((ts.isFunctionDeclaration(fn) && hasBindingWrite(fn, checker)) ||
|
|
113
|
+
runtimeParameters(fn).some((parameter) => hasBindingWrite(parameter, checker)));
|
|
114
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
export declare function unsupportedContextAlias(node: ts.VariableDeclaration, context: ts.Symbol, checker: ts.TypeChecker): boolean;
|
|
3
|
+
export declare function unsupportedContextAssignment(node: ts.Node, context: ts.Symbol, checker: ts.TypeChecker): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { expressionReceiver } from './protocol-write-receiver.mjs';
|
|
3
|
+
export function unsupportedContextAlias(node, context, checker) {
|
|
4
|
+
if (!node.initializer)
|
|
5
|
+
return false;
|
|
6
|
+
const receiver = expressionReceiver(node.initializer, checker);
|
|
7
|
+
return (receiver?.root === context &&
|
|
8
|
+
(receiver.path.length === 0 ||
|
|
9
|
+
(receiver.path.length === 1 && receiver.path[0] === 'response')) &&
|
|
10
|
+
(!ts.isIdentifier(node.name) ||
|
|
11
|
+
!ts.isVariableDeclarationList(node.parent) ||
|
|
12
|
+
!(node.parent.flags & ts.NodeFlags.Const)));
|
|
13
|
+
}
|
|
14
|
+
export function unsupportedContextAssignment(node, context, checker) {
|
|
15
|
+
if (!ts.isBinaryExpression(node) || node.operatorToken.kind !== ts.SyntaxKind.EqualsToken)
|
|
16
|
+
return false;
|
|
17
|
+
const receiver = expressionReceiver(node.right, checker);
|
|
18
|
+
return (receiver?.root === context &&
|
|
19
|
+
(receiver.path.length === 0 || (receiver.path.length === 1 && receiver.path[0] === 'response')));
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import { type ExtractedResponseContract } from '../contract-schema/index.mts';
|
|
3
|
+
import type { DiscoverApiResponseContractsOptions } from './response-contract-lenient.mts';
|
|
4
|
+
import type { BackendResponseContract } from './response-contract-types.mts';
|
|
5
|
+
export declare function extractSseEvents(type: ts.Type, checker: ts.TypeChecker, source: string, options: DiscoverApiResponseContractsOptions | undefined): {
|
|
6
|
+
eventName: string;
|
|
7
|
+
contract: ExtractedResponseContract;
|
|
8
|
+
}[];
|
|
9
|
+
export declare function extractHttpVariants(type: ts.Type, checker: ts.TypeChecker, source: string, options: DiscoverApiResponseContractsOptions | undefined, selectRow: (index: number) => boolean): Omit<BackendResponseContract, 'method' | 'routeTemplate'>[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
+
import { extractContractSchema } from '../contract-schema/index.mjs';
|
|
3
|
+
import { propertyType, stringLiterals, typeVariants } from './protocol-marker-analysis.mjs';
|
|
4
|
+
import { noContentContract } from './response-contract-registration.mjs';
|
|
5
|
+
import { statusForbidsBody } from './protocol-body-status.mjs';
|
|
6
|
+
export function extractSseEvents(type, checker, source, options) {
|
|
7
|
+
return typeVariants(type).flatMap((variant) => {
|
|
8
|
+
const names = stringLiterals(propertyType(variant, 'event', checker));
|
|
9
|
+
const contract = concretePayload(propertyType(variant, 'data', checker), checker, source, options);
|
|
10
|
+
return names.map((eventName) => ({ eventName, contract }));
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function extractHttpVariants(type, checker, source, options, selectRow) {
|
|
14
|
+
const carrier = propertyType(type, 'apiHttpResponseVariants', checker);
|
|
15
|
+
const variants = typeVariants(carrier).filter((variant) => !(variant.flags & ts.TypeFlags.Undefined));
|
|
16
|
+
if (variants.length === 0)
|
|
17
|
+
throw new Error('HTTP response requires concrete variants');
|
|
18
|
+
let row = 0;
|
|
19
|
+
const selections = variants.map((variant) => {
|
|
20
|
+
const rawMedia = rawPropertyType(variant, 'mediaType', checker);
|
|
21
|
+
const rawKind = rawPropertyType(variant, 'bodyKind', checker);
|
|
22
|
+
const count = rawKind?.isStringLiteral() && rawKind.value === 'none'
|
|
23
|
+
? 1
|
|
24
|
+
: rawMedia?.isUnion()
|
|
25
|
+
? rawMedia.types.length
|
|
26
|
+
: 1;
|
|
27
|
+
const selected = Array.from({ length: count }, () => selectRow(row++));
|
|
28
|
+
return selected;
|
|
29
|
+
});
|
|
30
|
+
return variants.flatMap((variant, index) => {
|
|
31
|
+
const selected = selections[index];
|
|
32
|
+
if (!selected.some(Boolean))
|
|
33
|
+
return [];
|
|
34
|
+
const status = propertyType(variant, 'status', checker);
|
|
35
|
+
if (!(status.flags & ts.TypeFlags.NumberLiteral))
|
|
36
|
+
throw new Error('HTTP response requires a literal status');
|
|
37
|
+
const value = status.value;
|
|
38
|
+
if (!Number.isInteger(value) || value < 100 || value > 599)
|
|
39
|
+
throw new Error('HTTP response status is invalid');
|
|
40
|
+
const kinds = stringLiterals(propertyType(variant, 'bodyKind', checker));
|
|
41
|
+
if (kinds.length !== 1)
|
|
42
|
+
throw new Error('HTTP response requires one concrete body kind per variant');
|
|
43
|
+
const statusCodes = [value];
|
|
44
|
+
if (kinds[0] === 'none')
|
|
45
|
+
return [
|
|
46
|
+
{
|
|
47
|
+
...noContentContract(source),
|
|
48
|
+
statusCodes,
|
|
49
|
+
statusKnowledge: 'explicit',
|
|
50
|
+
bodyKind: 'none',
|
|
51
|
+
mediaTypeKnowledge: 'none',
|
|
52
|
+
...(value === 400 ? { includeDefaultError: true } : {}),
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
if (kinds[0] !== 'content')
|
|
56
|
+
throw new Error('HTTP response body kind is invalid');
|
|
57
|
+
if (statusForbidsBody(value))
|
|
58
|
+
throw new Error(`HTTP status ${value} forbids response content`);
|
|
59
|
+
const contract = concretePayload(propertyType(variant, 'body', checker), checker, source, options);
|
|
60
|
+
return stringLiterals(propertyType(variant, 'mediaType', checker)).flatMap((mediaType, index) => selected[index]
|
|
61
|
+
? [
|
|
62
|
+
{
|
|
63
|
+
...contract,
|
|
64
|
+
statusCodes,
|
|
65
|
+
statusKnowledge: 'explicit',
|
|
66
|
+
bodyKind: 'content',
|
|
67
|
+
mediaType,
|
|
68
|
+
mediaTypeKnowledge: 'known',
|
|
69
|
+
...(value === 400 ? { includeDefaultError: true } : {}),
|
|
70
|
+
},
|
|
71
|
+
]
|
|
72
|
+
: []);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function concretePayload(type, checker, source, options) {
|
|
76
|
+
const contract = extractContractSchema(type, checker, source, options);
|
|
77
|
+
if (contract.schema.root.type === 'unknown')
|
|
78
|
+
throw new Error('Protocol payload must have a concrete root');
|
|
79
|
+
return contract;
|
|
80
|
+
}
|
|
81
|
+
function rawPropertyType(type, name, checker) {
|
|
82
|
+
const property = checker.getPropertyOfType(type, name);
|
|
83
|
+
const declaration = property?.valueDeclaration ?? property?.declarations?.[0];
|
|
84
|
+
return property && declaration
|
|
85
|
+
? checker.getTypeOfSymbolAtLocation(property, declaration)
|
|
86
|
+
: undefined;
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function nextProtocolKey(contracts: ReadonlyMap<string, unknown>, key: string): string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import { type DiscoverApiResponseContractsOptions } from './response-contract-lenient.mts';
|
|
3
|
+
import { type HandlerBindings } from './response-contract-route-analysis.mts';
|
|
4
|
+
import type { BackendResponseContract } from './response-contract-types.mts';
|
|
5
|
+
export declare function discoverProtocolContracts(sourceFiles: readonly ts.SourceFile[], checker: ts.TypeChecker, bindings: HandlerBindings, contracts: Map<string, BackendResponseContract>, options: DiscoverApiResponseContractsOptions | undefined, requestedKeys?: ReadonlySet<string>): Set<ts.CallExpression>;
|