vouchington-tooling 0.33.0 → 0.34.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 +181 -1
- package/dist/api-contract-discovery/index.d.mts +4 -1
- package/dist/api-contract-discovery/index.mjs +1 -0
- package/dist/api-contract-discovery/protocol-analysis-cache.d.mts +21 -0
- package/dist/api-contract-discovery/protocol-analysis-cache.mjs +7 -0
- package/dist/api-contract-discovery/protocol-callback-invocation.d.mts +2 -1
- package/dist/api-contract-discovery/protocol-callback-invocation.mjs +12 -4
- package/dist/api-contract-discovery/protocol-callback-values.d.mts +2 -1
- package/dist/api-contract-discovery/protocol-callback-values.mjs +3 -3
- package/dist/api-contract-discovery/protocol-execution-path.d.mts +2 -1
- package/dist/api-contract-discovery/protocol-execution-path.mjs +2 -2
- package/dist/api-contract-discovery/protocol-http-context-callers.d.mts +2 -1
- package/dist/api-contract-discovery/protocol-http-context-callers.mjs +13 -7
- package/dist/api-contract-discovery/query-contract-extraction.mjs +20 -3
- package/dist/api-contract-discovery/query-contract-literals.d.mts +5 -0
- package/dist/api-contract-discovery/query-contract-literals.mjs +29 -0
- package/dist/api-contract-discovery/registered-route-binding-writes.d.mts +2 -1
- package/dist/api-contract-discovery/registered-route-binding-writes.mjs +20 -11
- package/dist/api-contract-discovery/registered-route-handler-analysis.d.mts +7 -1
- package/dist/api-contract-discovery/registered-route-handler-analysis.mjs +27 -26
- package/dist/api-contract-discovery/registered-route-handler-helpers.d.mts +3 -0
- package/dist/api-contract-discovery/registered-route-handler-helpers.mjs +16 -0
- package/dist/api-contract-discovery/request-validation-callbacks.d.mts +9 -0
- package/dist/api-contract-discovery/request-validation-callbacks.mjs +27 -0
- package/dist/api-contract-discovery/request-validation-checker.d.mts +7 -0
- package/dist/api-contract-discovery/request-validation-checker.mjs +25 -0
- package/dist/api-contract-discovery/request-validation-conditional.d.mts +6 -0
- package/dist/api-contract-discovery/request-validation-conditional.mjs +31 -0
- package/dist/api-contract-discovery/request-validation-factory-calls.d.mts +11 -0
- package/dist/api-contract-discovery/request-validation-factory-calls.mjs +23 -0
- package/dist/api-contract-discovery/request-validation-facts.d.mts +16 -0
- package/dist/api-contract-discovery/request-validation-facts.mjs +64 -0
- package/dist/api-contract-discovery/request-validation-follow.d.mts +24 -0
- package/dist/api-contract-discovery/request-validation-follow.mjs +38 -0
- package/dist/api-contract-discovery/request-validation-handler-state.d.mts +8 -0
- package/dist/api-contract-discovery/request-validation-handler-state.mjs +35 -0
- package/dist/api-contract-discovery/request-validation-input.d.mts +12 -0
- package/dist/api-contract-discovery/request-validation-input.mjs +43 -0
- package/dist/api-contract-discovery/request-validation-keys.d.mts +25 -0
- package/dist/api-contract-discovery/request-validation-keys.mjs +119 -0
- package/dist/api-contract-discovery/request-validation-match.d.mts +11 -0
- package/dist/api-contract-discovery/request-validation-match.mjs +47 -0
- package/dist/api-contract-discovery/request-validation-mutation.d.mts +7 -0
- package/dist/api-contract-discovery/request-validation-mutation.mjs +80 -0
- package/dist/api-contract-discovery/request-validation-origin.d.mts +20 -0
- package/dist/api-contract-discovery/request-validation-origin.mjs +71 -0
- package/dist/api-contract-discovery/request-validation-reads.d.mts +13 -0
- package/dist/api-contract-discovery/request-validation-reads.mjs +88 -0
- package/dist/api-contract-discovery/request-validation-registration.d.mts +9 -0
- package/dist/api-contract-discovery/request-validation-registration.mjs +32 -0
- package/dist/api-contract-discovery/request-validation-report.d.mts +3 -0
- package/dist/api-contract-discovery/request-validation-report.mjs +21 -0
- package/dist/api-contract-discovery/request-validation-request-origin.d.mts +5 -0
- package/dist/api-contract-discovery/request-validation-request-origin.mjs +13 -0
- package/dist/api-contract-discovery/request-validation-select.d.mts +9 -0
- package/dist/api-contract-discovery/request-validation-select.mjs +36 -0
- package/dist/api-contract-discovery/request-validation-sites.d.mts +9 -0
- package/dist/api-contract-discovery/request-validation-sites.mjs +77 -0
- package/dist/api-contract-discovery/request-validation-state-key.d.mts +13 -0
- package/dist/api-contract-discovery/request-validation-state-key.mjs +30 -0
- package/dist/api-contract-discovery/request-validation-trace-helpers.d.mts +14 -0
- package/dist/api-contract-discovery/request-validation-trace-helpers.mjs +80 -0
- package/dist/api-contract-discovery/request-validation-trace-select.d.mts +23 -0
- package/dist/api-contract-discovery/request-validation-trace-select.mjs +45 -0
- package/dist/api-contract-discovery/request-validation-trace.d.mts +8 -0
- package/dist/api-contract-discovery/request-validation-trace.mjs +130 -0
- package/dist/api-contract-discovery/request-validation-types.d.mts +84 -0
- package/dist/api-contract-discovery/request-validation-types.mjs +1 -0
- package/dist/api-contract-discovery/request-validation-walker.d.mts +26 -0
- package/dist/api-contract-discovery/request-validation-walker.mjs +143 -0
- package/dist/api-contract-discovery/request-validation.test-helpers.d.mts +39 -0
- package/dist/api-contract-discovery/request-validation.test-helpers.mjs +108 -0
- package/dist/api-contract-discovery/response-contract-attribution-facts.d.mts +2 -0
- package/dist/api-contract-discovery/response-contract-attribution-facts.mjs +18 -0
- package/dist/api-contract-discovery/response-contract-attribution.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-attribution.mjs +23 -0
- package/dist/api-contract-discovery/response-contract-call-classification.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-call-classification.mjs +37 -0
- package/dist/api-contract-discovery/response-contract-error-branch.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-error-branch.mjs +109 -6
- package/dist/api-contract-discovery/response-contract-handler-bindings.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-handler-bindings.mjs +126 -0
- package/dist/api-contract-discovery/response-contract-handler-calls.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-handler-calls.mjs +81 -0
- package/dist/api-contract-discovery/response-contract-implicit-variants.d.mts +2 -0
- package/dist/api-contract-discovery/response-contract-implicit-variants.mjs +6 -0
- package/dist/api-contract-discovery/response-contract-implicit.d.mts +2 -1
- package/dist/api-contract-discovery/response-contract-implicit.mjs +9 -17
- package/dist/api-contract-discovery/response-contract-lenient.d.mts +18 -0
- package/dist/api-contract-discovery/response-contract-object-symbols.d.mts +3 -0
- package/dist/api-contract-discovery/response-contract-object-symbols.mjs +80 -0
- package/dist/api-contract-discovery/response-contract-property-mutations.d.mts +5 -0
- package/dist/api-contract-discovery/response-contract-property-mutations.mjs +84 -0
- package/dist/api-contract-discovery/response-contract-registration.d.mts +1 -1
- package/dist/api-contract-discovery/response-contract-registration.mjs +7 -3
- package/dist/api-contract-discovery/response-contract-registry.mjs +43 -8
- package/dist/api-contract-discovery/response-contract-route-analysis.d.mts +3 -4
- package/dist/api-contract-discovery/response-contract-route-analysis.mjs +4 -101
- package/dist/api-contract-discovery/response-contract-route-syntax.d.mts +7 -0
- package/dist/api-contract-discovery/response-contract-route-syntax.mjs +41 -0
- package/dist/api-contract-discovery/response-contract-symbols.d.mts +5 -0
- package/dist/api-contract-discovery/response-contract-symbols.mjs +82 -0
- package/dist/compiler-build/index.d.mts +2 -0
- package/dist/compiler-build/index.mjs +1 -0
- package/dist/compiler-build/program-configuration.d.mts +9 -0
- package/dist/compiler-build/program-configuration.mjs +28 -0
- package/dist/compiler-build/program-types.d.mts +29 -0
- package/dist/compiler-build/program-types.mjs +1 -0
- package/dist/compiler-build/program.d.mts +7 -0
- package/dist/compiler-build/program.mjs +78 -0
- package/dist/contract-schema/index.d.mts +4 -0
- package/dist/contract-schema/index.mjs +2 -0
- package/dist/contract-schema/type-query-api.d.mts +44 -0
- package/dist/contract-schema/type-query-api.mjs +10 -0
- package/dist/contract-schema/type-query-call.d.mts +16 -0
- package/dist/contract-schema/type-query-call.mjs +52 -0
- package/dist/contract-schema/type-query-export.d.mts +10 -0
- package/dist/contract-schema/type-query-export.mjs +91 -0
- package/dist/contract-schema/type-query.d.mts +22 -0
- package/dist/contract-schema/type-query.mjs +39 -0
- package/dist/finite-enum-ripple/ast.d.mts +9 -0
- package/dist/finite-enum-ripple/ast.mjs +94 -0
- package/dist/finite-enum-ripple/check.d.mts +3 -0
- package/dist/finite-enum-ripple/check.mjs +62 -0
- package/dist/finite-enum-ripple/compare.d.mts +20 -0
- package/dist/finite-enum-ripple/compare.mjs +90 -0
- package/dist/finite-enum-ripple/component-paths.d.mts +8 -0
- package/dist/finite-enum-ripple/component-paths.mjs +30 -0
- package/dist/finite-enum-ripple/configured-object-mutations.d.mts +1 -0
- package/dist/finite-enum-ripple/configured-object-mutations.mjs +78 -0
- package/dist/finite-enum-ripple/create-page-jsx.d.mts +3 -0
- package/dist/finite-enum-ripple/create-page-jsx.mjs +28 -0
- package/dist/finite-enum-ripple/create-page-literals.d.mts +2 -0
- package/dist/finite-enum-ripple/create-page-literals.mjs +83 -0
- package/dist/finite-enum-ripple/create-page-object-visitor.d.mts +2 -0
- package/dist/finite-enum-ripple/create-page-object-visitor.mjs +57 -0
- package/dist/finite-enum-ripple/create-page-properties.d.mts +4 -0
- package/dist/finite-enum-ripple/create-page-properties.mjs +35 -0
- package/dist/finite-enum-ripple/index.d.mts +2 -0
- package/dist/finite-enum-ripple/index.mjs +1 -0
- package/dist/finite-enum-ripple/model.d.mts +74 -0
- package/dist/finite-enum-ripple/model.mjs +1 -0
- package/dist/finite-enum-ripple/parser-support.d.mts +11 -0
- package/dist/finite-enum-ripple/parser-support.mjs +86 -0
- package/dist/finite-enum-ripple/parsers.d.mts +32 -0
- package/dist/finite-enum-ripple/parsers.mjs +113 -0
- package/dist/finite-enum-ripple/pattern-captures.d.mts +2 -0
- package/dist/finite-enum-ripple/pattern-captures.mjs +45 -0
- package/dist/finite-enum-ripple/structured-route-check.d.mts +3 -0
- package/dist/finite-enum-ripple/structured-route-check.mjs +94 -0
- package/dist/finite-enum-ripple/types.d.mts +2 -0
- package/dist/finite-enum-ripple/types.mjs +60 -0
- package/dist/finite-enum-ripple/union-factory-check.d.mts +2 -0
- package/dist/finite-enum-ripple/union-factory-check.mjs +34 -0
- package/dist/finite-enum-ripple/union-types.d.mts +2 -0
- package/dist/finite-enum-ripple/union-types.mjs +129 -0
- package/dist/openapi-document/build-openapi-query.mjs +1 -1
- package/dist/openapi-document/openapi-types.d.mts +1 -1
- package/dist/openapi-document/query-types.d.mts +8 -2
- package/dist/post-publication-inventory/index.d.mts +5 -0
- package/dist/post-publication-inventory/index.mjs +2 -0
- package/dist/post-publication-inventory/readers/sql-executors.d.mts +3 -0
- package/dist/post-publication-inventory/readers/sql-executors.mjs +32 -0
- package/dist/post-publication-inventory/readers/sql-source.d.mts +7 -0
- package/dist/post-publication-inventory/readers/sql-source.mjs +87 -0
- package/dist/post-publication-inventory/readers/sql-template.d.mts +12 -0
- package/dist/post-publication-inventory/readers/sql-template.mjs +119 -0
- package/dist/post-publication-inventory/writers/source.d.mts +9 -0
- package/dist/post-publication-inventory/writers/source.mjs +128 -0
- package/dist/post-publication-inventory/writers/types.d.mts +19 -0
- package/dist/post-publication-inventory/writers/types.mjs +1 -0
- package/package.json +13 -2
package/README.md
CHANGED
|
@@ -8,11 +8,16 @@ GitHub Actions policy checks and compiler host freshness with bounded build sett
|
|
|
8
8
|
[`config-inventory`](../../docs/config-inventory.md) collects environment, dynamic-configuration,
|
|
9
9
|
and package-manager evidence using caller-owned file roles and policies.
|
|
10
10
|
|
|
11
|
+
[`finite-enum-ripple`](../../docs/finite-enum-ripple.md) compares finite TypeScript declarations,
|
|
12
|
+
route configuration, routed pages, and factory calls. Consumers supply every tracked-file role,
|
|
13
|
+
declaration name, route label, factory pattern, and exception.
|
|
14
|
+
|
|
11
15
|
```bash
|
|
12
16
|
npm install vouchington-tooling
|
|
13
17
|
# optional, only if you import vouchington-tooling/sql-ast
|
|
14
18
|
npm install @libpg-query/parser
|
|
15
|
-
# optional,
|
|
19
|
+
# optional, if you import vouchington-tooling/contract-schema, finite-enum-ripple,
|
|
20
|
+
# or post-publication-inventory
|
|
16
21
|
# (classic compiler API; typescript@7's package root is version-only)
|
|
17
22
|
npm install @typescript/typescript6
|
|
18
23
|
# optional, only if you use the Vitest reporter export
|
|
@@ -474,6 +479,138 @@ aliases; `options.onRouteError` enables lenient response and request extraction.
|
|
|
474
479
|
does not cache programs or depend on the caller's repository layout. OpenAPI document assembly
|
|
475
480
|
remains a separate `openapi-document` call.
|
|
476
481
|
|
|
482
|
+
`options.onAmbiguousAttribution` optionally emits facts for response calls lexically inside handler
|
|
483
|
+
functions registered to multiple routes, plus inline response calls within those handlers. Facts
|
|
484
|
+
are delivered after discovery succeeds, ordered by normalized source location; each callback gets
|
|
485
|
+
its own copy of the sorted route list. It recognizes direct property handlers on `const`
|
|
486
|
+
object-literal receivers, shorthand properties, immutable aliases, and inline handlers. Mutable
|
|
487
|
+
receivers whose properties may be reassigned, variable receivers without object-literal initializers,
|
|
488
|
+
and non-identifier receiver expressions fail closed for attribution.
|
|
489
|
+
Direct identifier assignment, identifier writes through array/object destructuring or loop targets,
|
|
490
|
+
identifier updates, and dot-property assignment or updates make that binding ineligible for attribution.
|
|
491
|
+
Computed receiver-property writes, `delete`, runtime reflection, and other indirect mutation are not
|
|
492
|
+
analyzed. Named helper calls are not followed transitively: for example, an
|
|
493
|
+
emission inside `emit(ctx)` is not attributed when a registered `shared(ctx)` handler calls
|
|
494
|
+
`emit(ctx)`. The callback does not change the discovered contract result.
|
|
495
|
+
|
|
496
|
+
Query contracts (`apiQuery`) carry requiredness. A top-level parameter descriptor type may include
|
|
497
|
+
`required: true` (inline or through an intersection such as `{ readonly required: true }`); the
|
|
498
|
+
extracted descriptor then has `required: true` and the OpenAPI parameter emits `required: true`,
|
|
499
|
+
otherwise `required: false`. `required` must be the literal `true`; `false`, `boolean`, optional, or
|
|
500
|
+
non-literal values fail as a malformed query parameter, as do `required` together with `default` and
|
|
501
|
+
`required` on `csv-array` `items`.
|
|
502
|
+
|
|
503
|
+
`discoverRequestValidationFacts({ program, sourceFiles, validators, factories, executedCallbacks })`
|
|
504
|
+
reports, per registered route (`METHOD:/template`), the route `kind` and `source`, every configured
|
|
505
|
+
validator call its handlers reach (`validatorSites`), every configured handler-factory call
|
|
506
|
+
(`factorySites`), and the raw request carriers the handlers read (`carrierReads`). It reports
|
|
507
|
+
facts only; which routes must validate and what counts as covered stay with the caller.
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
import { discoverRequestValidationFacts } from 'vouchington-tooling/api-contract-discovery'
|
|
511
|
+
|
|
512
|
+
const facts = discoverRequestValidationFacts({
|
|
513
|
+
program,
|
|
514
|
+
sourceFiles,
|
|
515
|
+
validators: [
|
|
516
|
+
{
|
|
517
|
+
module: 'lib/validation.ts', // resolved declaration path suffix, or a package specifier
|
|
518
|
+
exportName: 'validateInput',
|
|
519
|
+
operationArgument: 1,
|
|
520
|
+
carriers: { kind: 'input-object', argument: 2 }, // or { kind: 'fixed', carriers, optionCarriers }
|
|
521
|
+
},
|
|
522
|
+
],
|
|
523
|
+
factories: [
|
|
524
|
+
{
|
|
525
|
+
module: 'lib/factory.ts',
|
|
526
|
+
exportName: 'createThingHandler',
|
|
527
|
+
optionsArgument: 0,
|
|
528
|
+
operationProperty: 'operation',
|
|
529
|
+
carriers: ['path', 'body'],
|
|
530
|
+
},
|
|
531
|
+
],
|
|
532
|
+
executedCallbacks: [
|
|
533
|
+
{
|
|
534
|
+
module: 'lib/admit.ts',
|
|
535
|
+
exportName: 'admitWork',
|
|
536
|
+
argument: 0,
|
|
537
|
+
properties: ['beforeCapacity'],
|
|
538
|
+
},
|
|
539
|
+
],
|
|
540
|
+
})
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
A call matches only when its callee resolves, through aliases, re-exports and path mappings, to the
|
|
544
|
+
configured export of a declaration file matching `module` (`.d.ts`, `.d.mts` and `.d.cts` suffixes
|
|
545
|
+
are ignored when comparing, and the configured `module` may be written with or without a `.ts`,
|
|
546
|
+
`.mts`, `.cts`, `.js`, `.mjs` or `.cjs` extension, so `lib/validation.js` matches `lib/validation.ts`
|
|
547
|
+
and `lib/validation.d.ts`); import specifier text and same-named local functions never match.
|
|
548
|
+
Operation keys resolve through literals, `const`s (including imported ones), `as const`,
|
|
549
|
+
`satisfies`, helper parameters bound to static arguments, and, for factories, the option property
|
|
550
|
+
including spread `const` objects; anything else, including a name with no symbol, gives
|
|
551
|
+
`operation: null` with `unresolvedReason`. Each validated carrier reports `origins`, the request
|
|
552
|
+
carriers (`path`, `query`, `body`, `header`) its value derives from, traced through calls, followed
|
|
553
|
+
helper returns, branches, logical operands, spreads and earlier assignments; `unresolved` is set
|
|
554
|
+
when part of the value depends on an unbound parameter. An unconditional reassignment of the
|
|
555
|
+
identifier itself earlier in the same function body (not inside a branch, loop or `try`) replaces
|
|
556
|
+
the earlier origins, conditional reassignments merge, and property writes such as `x.limit = n`
|
|
557
|
+
always add; an assignment on a statically dead path (`if (false)`) is ignored. A destructured
|
|
558
|
+
local traces only the property or element it binds (`const { chosen } = pair`), resolved through
|
|
559
|
+
object and array literals, `const`s and followed helper returns (also awaited), and falls back to the
|
|
560
|
+
whole initializer when it cannot be selected (rest, computed key, spread, unknown source).
|
|
561
|
+
A destructured handler context parameter (`({ query, params, request }) => ...`) binds each member
|
|
562
|
+
to its carrier. A helper parameter that is reassigned is not trusted as the bound operation key:
|
|
563
|
+
only a lone unconditional reassignment is used, anything else gives `operation: null`.
|
|
564
|
+
Generator functions are not followed, because calling one does not run its body.
|
|
565
|
+
Fixed-carrier validators report the carrier itself as their origin.
|
|
566
|
+
|
|
567
|
+
An input-object validator's input is resolved through `const` objects and spreads with
|
|
568
|
+
last-write-wins semantics, like the factory option resolver. When it cannot be fully resolved (a
|
|
569
|
+
non-literal input, a spread of something that is not a resolvable `const` object, a cyclic spread,
|
|
570
|
+
or a computed property name), `carriers` lists what was found and the site sets
|
|
571
|
+
`unresolvedCarriers` to the reason, so a consumer must not treat `carriers` as complete. A `const`
|
|
572
|
+
object (input or factory options) with a property write before the use (`o.p = x`, `o[k] = x`,
|
|
573
|
+
`delete o.p`, `o.n++`, `Object.assign(o, ...)`) is unresolvable too, and a factory then reports
|
|
574
|
+
`operation: null`.
|
|
575
|
+
|
|
576
|
+
`conditional` is true under `if`/ternary branches, the right of `&&`/`||`/`??`, `switch` cases,
|
|
577
|
+
loop bodies, `catch` and `finally`. Handlers are walked into function declarations and `const`
|
|
578
|
+
function expressions in `sourceFiles`, and only through bindings that are never reassigned (`let`
|
|
579
|
+
and reassigned bindings are not trusted as handlers). A callback passed to a followed helper is
|
|
580
|
+
followed only when the helper runs it, and is conditional unless it is invoked directly from the
|
|
581
|
+
helper's own body outside any condition. Known limit: the invocations of an inline callback are
|
|
582
|
+
found only in the callback's own source file, so a callback passed to a helper declared in another
|
|
583
|
+
file (`withTx(ctx, async () => { validateInput(...) })`) is not walked. Inline callback properties of an `executedCallbacks` host,
|
|
584
|
+
written as identifiers or string literals, are walked as executed and take only the condition of
|
|
585
|
+
the call to the configured host, never how its implementation invokes them. Configured validator
|
|
586
|
+
and factory implementations are never entered, but their callee and arguments are still walked for
|
|
587
|
+
nested validator sites and reads; only reads that build a validator's input are left out, including
|
|
588
|
+
reads inside a followed helper that only prepares that input. Validators evaluated while a handler is
|
|
589
|
+
built (registration time) are not sites: only the walk of the handler reports validators. A handler a
|
|
590
|
+
helper returns under a runtime branch (`if (flag) return ctx => ...`) reports `conditional: true`. Factory
|
|
591
|
+
sites are reported for factory calls whose returned handler is a registration-time value (a route
|
|
592
|
+
registration argument, a module `const` used as one, or a helper-returned handler); a factory call
|
|
593
|
+
inside a handler body, such as a discarded `createThingHandler(options);`, is not a factory site.
|
|
594
|
+
A factory site is also found through a module `const` initialized by a factory call that a handler
|
|
595
|
+
calls (`const handler = createThingHandler({...})`, then `handler(ctx)`). Like validator sites,
|
|
596
|
+
`FactorySite.conditional` is true when the site is reached only under a condition, and a site
|
|
597
|
+
reached both ways is reported unconditional. A template-literal operation key folds to a string
|
|
598
|
+
when every substitution resolves (literals, `const`s, bound parameters); otherwise it is `null`.
|
|
599
|
+
|
|
600
|
+
Pass the `program` you already built: the function uses it as given and never builds its own.
|
|
601
|
+
Helpers are followed only within `sourceFiles`, so pass every file that declares a followed helper,
|
|
602
|
+
not just the route files; a helper declared elsewhere is not entered, which silently yields empty
|
|
603
|
+
`origins` rather than an error. An omitted optional or defaulted helper argument contributes no
|
|
604
|
+
origin and is not `unresolved`; an omitted required argument is `unresolved`. Per-call caches make
|
|
605
|
+
repeated helpers cheap, so a 700-route program completes in seconds once the program exists.
|
|
606
|
+
|
|
607
|
+
Each `carrierReads` entry has `access`: `key` for a static key (`ctx.query.limit`, destructured
|
|
608
|
+
names, a resolvable `ctx.query[name]`), `computed` for a non-static key (`ctx.query[name]`, a
|
|
609
|
+
computed destructuring key; `key: null`), and `whole` for the carrier used as a value
|
|
610
|
+
(`consume(ctx.query)`, `{ ...ctx.params }`, `return ctx.headers`, a rest element) or a body read
|
|
611
|
+
such as `ctx.request.json()` (`key: null`). A `const` alias of a carrier is not itself a read; its
|
|
612
|
+
uses are.
|
|
613
|
+
|
|
477
614
|
Version one recognizes literal-key `apiResponse`, `apiNoContent`, `apiOpenApiRawResponse`,
|
|
478
615
|
`apiRequest`, `apiRequestContract`, `apiNoRequestBody`, `apiQuery`, and `apiHeaders` markers inside
|
|
479
616
|
`app.route(...).get/post/put/patch/delete(...)` handlers. It also inspects unmarked `ctx.json`,
|
|
@@ -579,6 +716,29 @@ the dedicated process group to exit after the direct child closes so descendants
|
|
|
579
716
|
`isProcessGroupAlive` and `waitForProcessGroupExit` are also available when callers need the same
|
|
580
717
|
process-group probe and bounded-drain semantics outside a browser session.
|
|
581
718
|
|
|
719
|
+
### Contract schema type queries
|
|
720
|
+
|
|
721
|
+
`getExportedTypeFacts` and `getCallRowTypeFacts` use an existing TypeScript `Program` from the
|
|
722
|
+
`contract-schema` subpath. Every request must also pass `typescript`, the same TypeScript compiler
|
|
723
|
+
API instance that created that `Program`. The source-file recognition check is a compatibility
|
|
724
|
+
guard, and callers remain responsible for pairing the same compiler instance; the query uses the
|
|
725
|
+
supplied API for compiler operations. Pass the exact `SourceFile.fileName`, an exported name or exact call
|
|
726
|
+
expression text, and only the property names and exported comparison types the consumer needs.
|
|
727
|
+
Facts include the displayed type, whether it is `any`, requested property displays, and requested
|
|
728
|
+
assignability booleans. `defaultTypeParameterIndex` selects an exported generic default;
|
|
729
|
+
direct defaults and direct references to preceding defaults are supported. Dependent composite
|
|
730
|
+
defaults and uninstantiated generic type declarations used as assignability targets fail with an
|
|
731
|
+
instruction to export and select a named instantiated type. Generic function values remain valid
|
|
732
|
+
compiler-backed comparison targets. Default lookup uses an outer class, interface, or type-alias
|
|
733
|
+
parameter list when present; otherwise it uses the first checker-visible call or construct
|
|
734
|
+
signature, never a hidden implementation or a later overload.
|
|
735
|
+
`rowSource: 'typeArgument'` selects a call's explicit type argument, while
|
|
736
|
+
`rowSource: 'awaitedRows'` selects `Awaited<ReturnType>.rows[number]`.
|
|
737
|
+
Call results are source ordered and include one-based line and column. An absent call returns an
|
|
738
|
+
empty array so the consumer can enforce its own cardinality; a selected call missing its requested
|
|
739
|
+
row type fails with a descriptive error. The package does not choose source files, expected
|
|
740
|
+
properties, or contract policy.
|
|
741
|
+
|
|
582
742
|
## Workflow skills outside plugins
|
|
583
743
|
|
|
584
744
|
The package ships a flat union of canonical workflow, testing, and database skills at
|
|
@@ -717,3 +877,23 @@ block, escaped and length-bounded like CI failures, and other entries are ignore
|
|
|
717
877
|
TypeScript and route rows from Markdown. Callers configure declaration names and
|
|
718
878
|
runbook headings, then compare the returned data using their own route policy.
|
|
719
879
|
See the [API contract](docs/basic-auth-doc-sync.md).
|
|
880
|
+
|
|
881
|
+
## Publication writer inventory facts
|
|
882
|
+
|
|
883
|
+
`vouchington-tooling/post-publication-inventory` exports
|
|
884
|
+
`analyzePostPublicationWriterSource(source, fileName, options)`. It returns five booleans for an
|
|
885
|
+
approved capture-helper call, a capture opt-out, writes through a configured entity table,
|
|
886
|
+
generated relation writes, and eligibility-table writes. Consumers supply every module, symbol,
|
|
887
|
+
property, receiver, and table name through `PostPublicationWriterSourceOptions` and retain their
|
|
888
|
+
inventory schema, tracked files, exceptions, and diagnostics.
|
|
889
|
+
|
|
890
|
+
This preserves source-inventory heuristics, rather than proving runtime execution or SQL safety.
|
|
891
|
+
Capture imports/calls are name-based and order-independent; relation-variable appends are
|
|
892
|
+
order-sensitive. DML recognition examines literal/template text. Static table matching lowercases
|
|
893
|
+
the extracted name; dynamic literal table matching preserves case. Malformed TypeScript uses the
|
|
894
|
+
compiler's recovery tree. An argument-free append returns no relation fact.
|
|
895
|
+
Pass TypeScript source (`.ts` or `.mts`); the parser uses TypeScript mode even if the file name ends
|
|
896
|
+
in `.tsx`. Template expressions join their static fragments for DML matching, while DML prefixes
|
|
897
|
+
for generated-table facts come from templates rather than ordinary quoted strings. Dynamic
|
|
898
|
+
eligibility-table matching expects an unquoted literal immediately after the DML prefix and does
|
|
899
|
+
not recognize `DELETE FROM ONLY`.
|
|
@@ -27,5 +27,8 @@ export type { BackendRequestContract } from './request-contract-types.mts';
|
|
|
27
27
|
export type { BackendQueryContract, BackendQueryContractRegistry } from './query-contract-types.mts';
|
|
28
28
|
export type { HeaderContract, HeaderContractRegistry } from './header-contract-types.mts';
|
|
29
29
|
export type { RegisteredRoute } from './registered-route-catalog.mts';
|
|
30
|
-
export type { RouteDiscoveryError, DiscoverApiResponseContractsOptions, } from './response-contract-lenient.mts';
|
|
30
|
+
export type { AmbiguousAttributionFact, RouteDiscoveryError, DiscoverApiResponseContractsOptions, } from './response-contract-lenient.mts';
|
|
31
31
|
export type { DiscoverApiRequestContractsOptions } from './request-contract-lenient.mts';
|
|
32
|
+
export { discoverRequestValidationFacts } from './request-validation-facts.mts';
|
|
33
|
+
export type { RequestValidationFactsInput } from './request-validation-facts.mts';
|
|
34
|
+
export type { Carrier, CarrierRead, ExecutedCallbackConfig, FactoryConfig, FactorySite, RouteValidationFacts, ValidatorConfig, ValidatorSite, ValidatorSiteCarrier, } from './request-validation-types.mts';
|
|
@@ -24,3 +24,4 @@ export { discoverApiQueryContracts } from './query-contract-registry.mjs';
|
|
|
24
24
|
export { discoverApiHeaderContracts } from './header-contract-registry.mjs';
|
|
25
25
|
export { discoverRegisteredRoutes, routeShape } from './registered-route-catalog.mjs';
|
|
26
26
|
export { responseStatusCodesForContract } from './response-contract-status.mjs';
|
|
27
|
+
export { discoverRequestValidationFacts } from './request-validation-facts.mjs';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
export type Assignment = {
|
|
3
|
+
node: ts.BinaryExpression;
|
|
4
|
+
direct: boolean;
|
|
5
|
+
start: number;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Memoized protocol-proof results for one analysis run. Nodes are immutable, so every entry stays
|
|
9
|
+
* valid for the run; callers create one per run and drop it afterwards.
|
|
10
|
+
*/
|
|
11
|
+
export type ProtocolCache = {
|
|
12
|
+
/** Plain assignments per owning function (or source file), grouped by the symbol they write. */
|
|
13
|
+
assignments: Map<ts.Node, Map<ts.Symbol, Assignment[]>>;
|
|
14
|
+
/** Symbols written (assigned, updated, or looped over) anywhere in a source file. */
|
|
15
|
+
writtenSymbols: Map<ts.SourceFile, Set<ts.Symbol | undefined>>;
|
|
16
|
+
/** Whether a function is a supported protocol callback. */
|
|
17
|
+
supported: Map<ts.Node, boolean>;
|
|
18
|
+
/** Executable call expressions of a source file. */
|
|
19
|
+
calls: Map<ts.SourceFile, ts.CallExpression[]>;
|
|
20
|
+
};
|
|
21
|
+
export declare const createProtocolCache: () => ProtocolCache;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { ProtocolCache } from './protocol-analysis-cache.mts';
|
|
2
3
|
type FunctionNode = ts.FunctionLikeDeclaration;
|
|
3
4
|
/** Proves callback consumption through concrete helper bodies, without executing nested closures. */
|
|
4
|
-
export declare function isSupportedProtocolCallback(fn: FunctionNode, checker: ts.TypeChecker): boolean;
|
|
5
|
+
export declare function isSupportedProtocolCallback(fn: FunctionNode, checker: ts.TypeChecker, cache?: ProtocolCache): boolean;
|
|
5
6
|
export {};
|
|
@@ -7,8 +7,16 @@ import { callbackBindingReplaced, callbackOptionsEscape } from './protocol-callb
|
|
|
7
7
|
import { enclosingFunction } from './protocol-marker-analysis.mjs';
|
|
8
8
|
import { createProtocolCallbackValueResolver, isProtocolCallbackFunction as functionNode, protocolCallbackSourceCalls, protocolCallbackHasWrittenBindings, } from './protocol-callback-values.mjs';
|
|
9
9
|
/** Proves callback consumption through concrete helper bodies, without executing nested closures. */
|
|
10
|
-
export function isSupportedProtocolCallback(fn, checker) {
|
|
11
|
-
|
|
10
|
+
export function isSupportedProtocolCallback(fn, checker, cache) {
|
|
11
|
+
const known = cache?.supported.get(fn);
|
|
12
|
+
if (known !== undefined)
|
|
13
|
+
return known;
|
|
14
|
+
const supported = proveProtocolCallback(fn, checker, cache);
|
|
15
|
+
cache?.supported.set(fn, supported);
|
|
16
|
+
return supported;
|
|
17
|
+
}
|
|
18
|
+
function proveProtocolCallback(fn, checker, cache) {
|
|
19
|
+
if (!fn.body || fn.asteriskToken || protocolCallbackHasWrittenBindings(fn, checker, cache))
|
|
12
20
|
return false;
|
|
13
21
|
const resolver = createProtocolCallbackValueResolver(checker);
|
|
14
22
|
const { resolve, symbol } = resolver;
|
|
@@ -20,7 +28,7 @@ export function isSupportedProtocolCallback(fn, checker) {
|
|
|
20
28
|
invalidated ||= callbackOptionsEscape(call, env, fn, resolver);
|
|
21
29
|
return false;
|
|
22
30
|
}
|
|
23
|
-
if (protocolCallbackHasWrittenBindings(callee.node, checker)) {
|
|
31
|
+
if (protocolCallbackHasWrittenBindings(callee.node, checker, cache)) {
|
|
24
32
|
invalidated = true;
|
|
25
33
|
return false;
|
|
26
34
|
}
|
|
@@ -86,7 +94,7 @@ export function isSupportedProtocolCallback(fn, checker) {
|
|
|
86
94
|
const call = argument.parent;
|
|
87
95
|
if (!ts.isCallExpression(call) && !ts.isNewExpression(call)) {
|
|
88
96
|
const owner = enclosingFunction(fn);
|
|
89
|
-
if (owner && isSupportedProtocolCallback(owner, checker))
|
|
97
|
+
if (owner && isSupportedProtocolCallback(owner, checker, cache))
|
|
90
98
|
return body(owner.body, new Map(), new Set([owner]), false);
|
|
91
99
|
return protocolCallbackSourceCalls(fn).some((candidate) => potentiallyExecuted(candidate) &&
|
|
92
100
|
((registeredHandler(candidate) &&
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { ProtocolCache } from './protocol-analysis-cache.mts';
|
|
2
3
|
type FunctionNode = ts.FunctionLikeDeclaration;
|
|
3
4
|
export type CallbackValue = {
|
|
4
5
|
node: ts.Node;
|
|
@@ -13,5 +14,5 @@ export declare function createProtocolCallbackValueResolver(checker: ts.TypeChec
|
|
|
13
14
|
symbol: (node: ts.Node) => ts.Symbol | undefined;
|
|
14
15
|
property: (value: CallbackValue | undefined, name: string, seen: Set<ts.Node>) => CallbackValue | undefined;
|
|
15
16
|
};
|
|
16
|
-
export declare function protocolCallbackHasWrittenBindings(fn: FunctionNode, checker: ts.TypeChecker): boolean;
|
|
17
|
+
export declare function protocolCallbackHasWrittenBindings(fn: FunctionNode, checker: ts.TypeChecker, cache?: ProtocolCache): boolean;
|
|
17
18
|
export {};
|
|
@@ -108,7 +108,7 @@ export function createProtocolCallbackValueResolver(checker) {
|
|
|
108
108
|
}
|
|
109
109
|
return { resolve, symbol, property };
|
|
110
110
|
}
|
|
111
|
-
export function protocolCallbackHasWrittenBindings(fn, checker) {
|
|
112
|
-
return ((ts.isFunctionDeclaration(fn) && hasBindingWrite(fn, checker)) ||
|
|
113
|
-
runtimeParameters(fn).some((parameter) => hasBindingWrite(parameter, checker)));
|
|
111
|
+
export function protocolCallbackHasWrittenBindings(fn, checker, cache) {
|
|
112
|
+
return ((ts.isFunctionDeclaration(fn) && hasBindingWrite(fn, checker, cache)) ||
|
|
113
|
+
runtimeParameters(fn).some((parameter) => hasBindingWrite(parameter, checker, cache)));
|
|
114
114
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { ProtocolCache } from './protocol-analysis-cache.mts';
|
|
2
3
|
/** Proves execution up to an optional handler whose registered binding is already established. */
|
|
3
|
-
export declare function executableProtocolPath(node: ts.Node, checker: ts.TypeChecker, boundHandler?: ts.Node): boolean;
|
|
4
|
+
export declare function executableProtocolPath(node: ts.Node, checker: ts.TypeChecker, boundHandler?: ts.Node, cache?: ProtocolCache): boolean;
|
|
@@ -3,12 +3,12 @@ import { isSupportedProtocolCallback } from './protocol-callback-invocation.mjs'
|
|
|
3
3
|
import { potentiallyExecuted } from './protocol-executable-path.mjs';
|
|
4
4
|
import { enclosingFunction } from './protocol-marker-analysis.mjs';
|
|
5
5
|
/** Proves execution up to an optional handler whose registered binding is already established. */
|
|
6
|
-
export function executableProtocolPath(node, checker, boundHandler) {
|
|
6
|
+
export function executableProtocolPath(node, checker, boundHandler, cache) {
|
|
7
7
|
if (!potentiallyExecuted(node))
|
|
8
8
|
return false;
|
|
9
9
|
let fn = enclosingFunction(node);
|
|
10
10
|
while (fn && fn !== boundHandler) {
|
|
11
|
-
if (!potentiallyExecuted(fn) || !isSupportedProtocolCallback(fn, checker))
|
|
11
|
+
if (!potentiallyExecuted(fn) || !isSupportedProtocolCallback(fn, checker, cache))
|
|
12
12
|
return false;
|
|
13
13
|
fn = enclosingFunction(fn);
|
|
14
14
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { ProtocolCache } from './protocol-analysis-cache.mts';
|
|
2
3
|
/** Use the same actual invocation sites for context admission and caller emission coverage. */
|
|
3
|
-
export declare function httpContextInvocations(fn: ts.FunctionLikeDeclaration, checker: ts.TypeChecker): {
|
|
4
|
+
export declare function httpContextInvocations(fn: ts.FunctionLikeDeclaration, checker: ts.TypeChecker, cache?: ProtocolCache): {
|
|
4
5
|
registered: boolean;
|
|
5
6
|
callers: ts.CallExpression[];
|
|
6
7
|
};
|
|
@@ -5,15 +5,21 @@ import { registeredHandler } from './protocol-callback-registration.mjs';
|
|
|
5
5
|
import { executableProtocolPath } from './protocol-execution-path.mjs';
|
|
6
6
|
import { enclosingFunction } from './protocol-marker-analysis.mjs';
|
|
7
7
|
/** Use the same actual invocation sites for context admission and caller emission coverage. */
|
|
8
|
-
export function httpContextInvocations(fn, checker) {
|
|
8
|
+
export function httpContextInvocations(fn, checker, cache) {
|
|
9
9
|
const resolver = createProtocolCallbackValueResolver(checker);
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
const sourceFile = fn.getSourceFile();
|
|
11
|
+
let calls = cache?.calls.get(sourceFile);
|
|
12
|
+
if (!calls) {
|
|
13
|
+
const found = [];
|
|
14
|
+
const visit = (node) => {
|
|
15
|
+
if (ts.isCallExpression(node) && executableProtocolPath(node, checker, undefined, cache))
|
|
16
|
+
found.push(node);
|
|
17
|
+
ts.forEachChild(node, visit);
|
|
18
|
+
};
|
|
19
|
+
visit(sourceFile);
|
|
20
|
+
calls = found;
|
|
21
|
+
cache?.calls.set(sourceFile, calls);
|
|
15
22
|
}
|
|
16
|
-
visit(fn.getSourceFile());
|
|
17
23
|
const registered = calls.some((call) => registeredHandler(call) &&
|
|
18
24
|
call.arguments.some((argument) => resolver.resolve(argument, new Map())?.node === fn));
|
|
19
25
|
const callers = calls.filter((call) => {
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
2
|
import { contractError } from './response-contract-registration.mjs';
|
|
3
|
-
import { optionalNumberLiteral, optionalStringLiteral, requiredBooleanLiteral, requiredNumberLiteral, requiredPropertyType, requiredStringLiteral, stringTuple, } from './query-contract-literals.mjs';
|
|
3
|
+
import { optionalNumberLiteral, declaresProperty, optionalTrueLiteral, optionalStringLiteral, requiredBooleanLiteral, requiredNumberLiteral, requiredPropertyType, rejectUnresolvedType, requiredStringLiteral, stringTuple, } from './query-contract-literals.mjs';
|
|
4
4
|
export function extractQueryParameterDescriptor(type, checker, sourceFile, node, parameterName) {
|
|
5
|
-
|
|
5
|
+
rejectUnresolvedType(type, failure(sourceFile, node, parameterName));
|
|
6
|
+
const descriptor = extractDescriptorShape(type, checker, sourceFile, node, parameterName);
|
|
7
|
+
if (!optionalTrueLiteral(type, 'required', checker, failure(sourceFile, node, parameterName))) {
|
|
8
|
+
return descriptor;
|
|
9
|
+
}
|
|
10
|
+
if (declaresProperty(type, 'default')) {
|
|
11
|
+
failure(sourceFile, node, parameterName)('required cannot be combined with default');
|
|
12
|
+
}
|
|
13
|
+
return { ...descriptor, required: true };
|
|
14
|
+
}
|
|
15
|
+
function failure(sourceFile, node, parameterName) {
|
|
16
|
+
return (detail) => {
|
|
6
17
|
throw contractError(sourceFile, node, `Malformed query parameter "${parameterName}": ${detail}`);
|
|
7
18
|
};
|
|
19
|
+
}
|
|
20
|
+
function extractDescriptorShape(type, checker, sourceFile, node, parameterName) {
|
|
21
|
+
const fail = failure(sourceFile, node, parameterName);
|
|
8
22
|
const kind = requiredStringLiteral(type, 'kind', checker, fail);
|
|
9
23
|
const description = optionalStringLiteral(type, 'description', checker, node, fail);
|
|
10
24
|
const options = description === undefined ? {} : { description };
|
|
@@ -55,7 +69,10 @@ export function extractQueryParameterDescriptor(type, checker, sourceFile, node,
|
|
|
55
69
|
if (requiredBooleanLiteral(type, 'explode', checker, fail) !== false)
|
|
56
70
|
fail('explode must be false');
|
|
57
71
|
const itemType = requiredPropertyType(type, 'items', checker, fail);
|
|
58
|
-
|
|
72
|
+
rejectUnresolvedType(itemType, fail);
|
|
73
|
+
if (declaresProperty(itemType, 'required'))
|
|
74
|
+
fail('array items cannot be required');
|
|
75
|
+
const items = extractDescriptorShape(itemType, checker, sourceFile, node, `${parameterName}[]`);
|
|
59
76
|
if (items.kind === 'string' || items.kind === 'enum') {
|
|
60
77
|
return { kind, items, style: 'form', explode: false, ...options };
|
|
61
78
|
}
|
|
@@ -5,4 +5,9 @@ export declare function requiredNumberLiteral(type: ts.Type, name: string, check
|
|
|
5
5
|
export declare function requiredBooleanLiteral(type: ts.Type, name: string, checker: ts.TypeChecker, fail: (detail: string) => never): boolean;
|
|
6
6
|
export declare function optionalStringLiteral(type: ts.Type, name: string, checker: ts.TypeChecker, node: ts.Node, fail: (detail: string) => never): string | undefined;
|
|
7
7
|
export declare function optionalNumberLiteral(type: ts.Type, name: string, checker: ts.TypeChecker, node: ts.Node, fail: (detail: string) => never): number | undefined;
|
|
8
|
+
export declare function optionalTrueLiteral(type: ts.Type, name: string, checker: ts.TypeChecker, fail: (detail: string) => never): boolean;
|
|
9
|
+
/** True when the type, or any member of a union type, declares the property. */
|
|
10
|
+
export declare function declaresProperty(type: ts.Type, name: string): boolean;
|
|
8
11
|
export declare function stringTuple(type: ts.Type, name: string, checker: ts.TypeChecker, fail: (detail: string) => never): readonly string[];
|
|
12
|
+
/** Fails when the type, or any union/intersection constituent, is conditional or generic. */
|
|
13
|
+
export declare function rejectUnresolvedType(type: ts.Type, fail: (detail: string) => never): void;
|
|
@@ -42,6 +42,25 @@ export function optionalNumberLiteral(type, name, checker, node, fail) {
|
|
|
42
42
|
const value = checker.getTypeOfSymbolAtLocation(property, location);
|
|
43
43
|
return value.isNumberLiteral() ? value.value : fail(`requires literal ${name}`);
|
|
44
44
|
}
|
|
45
|
+
export function optionalTrueLiteral(type, name, checker, fail) {
|
|
46
|
+
const property = type.getProperty(name);
|
|
47
|
+
if (!property) {
|
|
48
|
+
if (declaresProperty(type, name))
|
|
49
|
+
return fail(`${name} must be present on every union member`);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (property.flags & ts.SymbolFlags.Optional)
|
|
53
|
+
return fail(`${name} must be literal when present`);
|
|
54
|
+
const value = requiredPropertyType(type, name, checker, fail);
|
|
55
|
+
const isTrue = value.flags & ts.TypeFlags.BooleanLiteral && checker.typeToString(value) === 'true';
|
|
56
|
+
return isTrue ? true : fail(`${name} must be the literal true`);
|
|
57
|
+
}
|
|
58
|
+
/** True when the type, or any member of a union type, declares the property. */
|
|
59
|
+
export function declaresProperty(type, name) {
|
|
60
|
+
if (type.getProperty(name))
|
|
61
|
+
return true;
|
|
62
|
+
return type.isUnion() && type.types.some((member) => member.getProperty(name) !== undefined);
|
|
63
|
+
}
|
|
45
64
|
export function stringTuple(type, name, checker, fail) {
|
|
46
65
|
const value = requiredPropertyType(type, name, checker, fail);
|
|
47
66
|
if (!checker.isTupleType(value))
|
|
@@ -52,3 +71,13 @@ export function stringTuple(type, name, checker, fail) {
|
|
|
52
71
|
}
|
|
53
72
|
return values.map((item) => item.value);
|
|
54
73
|
}
|
|
74
|
+
/** Fails when the type, or any union/intersection constituent, is conditional or generic. */
|
|
75
|
+
export function rejectUnresolvedType(type, fail) {
|
|
76
|
+
if (type.flags & (ts.TypeFlags.Conditional | ts.TypeFlags.Instantiable)) {
|
|
77
|
+
fail('descriptor must not contain unresolved conditional or generic types');
|
|
78
|
+
}
|
|
79
|
+
if (type.isUnionOrIntersection()) {
|
|
80
|
+
for (const member of type.types)
|
|
81
|
+
rejectUnresolvedType(member, fail);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mts';
|
|
2
|
+
import type { ProtocolCache } from './protocol-analysis-cache.mts';
|
|
2
3
|
/** Helper and factory parameter bindings must remain unchanged in their declaring source. */
|
|
3
|
-
export declare function hasBindingWrite(declaration: ts.FunctionDeclaration | ts.ParameterDeclaration, checker: ts.TypeChecker): boolean;
|
|
4
|
+
export declare function hasBindingWrite(declaration: ts.FunctionDeclaration | ts.ParameterDeclaration, checker: ts.TypeChecker, cache?: ProtocolCache): boolean;
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import ts from '../contract-schema/typescript-api.mjs';
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
return false;
|
|
6
|
-
if (!ts.isIdentifier(declaration.name))
|
|
7
|
-
return true;
|
|
8
|
-
const binding = checker.getSymbolAtLocation(declaration.name);
|
|
9
|
-
let written = false;
|
|
2
|
+
/** Every symbol assigned, updated, or looped over in the source file, found in one scan. */
|
|
3
|
+
function writtenSymbols(sourceFile, checker) {
|
|
4
|
+
const written = new Set();
|
|
10
5
|
const referencesBinding = (node) => {
|
|
11
6
|
// Index expressions and computed property names read their keys, not write them.
|
|
12
7
|
// inspect still visits these expressions separately to detect nested side-effect writes.
|
|
@@ -16,8 +11,8 @@ export function hasBindingWrite(declaration, checker) {
|
|
|
16
11
|
referencesBinding(node.expression);
|
|
17
12
|
return;
|
|
18
13
|
}
|
|
19
|
-
if (ts.isIdentifier(node)
|
|
20
|
-
written
|
|
14
|
+
if (ts.isIdentifier(node))
|
|
15
|
+
written.add(checker.getSymbolAtLocation(node));
|
|
21
16
|
node.forEachChild(referencesBinding);
|
|
22
17
|
};
|
|
23
18
|
const inspect = (node) => {
|
|
@@ -33,6 +28,20 @@ export function hasBindingWrite(declaration, checker) {
|
|
|
33
28
|
referencesBinding(node.initializer);
|
|
34
29
|
node.forEachChild(inspect);
|
|
35
30
|
};
|
|
36
|
-
inspect(
|
|
31
|
+
inspect(sourceFile);
|
|
37
32
|
return written;
|
|
38
33
|
}
|
|
34
|
+
/** Helper and factory parameter bindings must remain unchanged in their declaring source. */
|
|
35
|
+
export function hasBindingWrite(declaration, checker, cache) {
|
|
36
|
+
if (!declaration.name)
|
|
37
|
+
return false;
|
|
38
|
+
if (!ts.isIdentifier(declaration.name))
|
|
39
|
+
return true;
|
|
40
|
+
const sourceFile = declaration.getSourceFile();
|
|
41
|
+
let written = cache?.writtenSymbols.get(sourceFile);
|
|
42
|
+
if (!written) {
|
|
43
|
+
written = writtenSymbols(sourceFile, checker);
|
|
44
|
+
cache?.writtenSymbols.set(sourceFile, written);
|
|
45
|
+
}
|
|
46
|
+
return written.has(checker.getSymbolAtLocation(declaration.name));
|
|
47
|
+
}
|
|
@@ -2,5 +2,11 @@ import ts from '../contract-schema/typescript-api.mts';
|
|
|
2
2
|
export type HandlerProof = {
|
|
3
3
|
node: ts.FunctionLikeDeclaration;
|
|
4
4
|
bindings: Map<ts.Symbol, ts.Expression>;
|
|
5
|
+
/** Set under `executableReturns` when a runtime branch selects the handler. */
|
|
6
|
+
conditional?: boolean;
|
|
5
7
|
};
|
|
6
|
-
export declare function handlerNodes(argument: ts.Expression, checker: ts.TypeChecker, parameterBindings?: Map<ts.Symbol, ts.Expression>, active?: Set<ts.Node>, staticProof?: boolean, proofs?: HandlerProof[]
|
|
8
|
+
export declare function handlerNodes(argument: ts.Expression, checker: ts.TypeChecker, parameterBindings?: Map<ts.Symbol, ts.Expression>, active?: Set<ts.Node>, staticProof?: boolean, proofs?: HandlerProof[],
|
|
9
|
+
/** Ignore `let`, `var` and reassigned bindings; always on under `staticProof`. */
|
|
10
|
+
stableBindings?: boolean,
|
|
11
|
+
/** Ignore returned values on statically dead paths, and mark runtime-branch ones conditional. */
|
|
12
|
+
executableReturns?: boolean): ts.Node[];
|