carrick 0.3.107 → 0.3.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/sidecar/dist/src/capture/api.d.ts +15 -0
- package/sidecar/dist/src/capture/check-classify.d.ts +9 -0
- package/sidecar/dist/src/capture/check-classify.js +20 -0
- package/sidecar/dist/src/capture/check-fields.d.ts +24 -0
- package/sidecar/dist/src/capture/check-fields.js +54 -36
- package/sidecar/dist/src/capture/check-union.d.ts +40 -0
- package/sidecar/dist/src/capture/check-union.js +92 -0
- package/sidecar/dist/src/capture/check.js +5 -0
- package/sidecar/dist/src/capture/index.js +84 -44
- package/sidecar/dist/src/definition-resolver.d.ts +5 -8
- package/sidecar/dist/src/definition-resolver.js +5 -7
- package/sidecar/dist/src/index.js +33 -4
- package/sidecar/dist/src/infer-timing.d.ts +54 -0
- package/sidecar/dist/src/infer-timing.js +124 -0
- package/sidecar/dist/src/retype.d.ts +4 -2
- package/sidecar/dist/src/retype.js +9 -4
- package/sidecar/dist/src/type-inferrer.d.ts +146 -1
- package/sidecar/dist/src/type-inferrer.js +416 -19
- package/sidecar/dist/src/types.d.ts +70 -0
- package/sidecar/dist/src/validators.d.ts +54 -16
- package/sidecar/dist/src/validators.js +4 -0
- package/templates/skills/carrick-reuse.md +6 -5
|
@@ -26,19 +26,17 @@
|
|
|
26
26
|
* inference path in `type-inferrer.ts`), which walks the resolved `Type` and
|
|
27
27
|
* rebuilds the inlined text.
|
|
28
28
|
*
|
|
29
|
-
* Each resolve call builds its own throwaway
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Each resolve call builds its own throwaway project over the stub tree and
|
|
30
|
+
* reads nothing else: not the service's project, and not whether the sidecar
|
|
31
|
+
* was ever initialised. So the request costs what the stub costs, in a process
|
|
32
|
+
* that has built nothing as in one that has (carrick#1927), and the sidecar's
|
|
33
|
+
* long-lived project never sees stub files.
|
|
32
34
|
*/
|
|
33
35
|
import * as path from 'node:path';
|
|
34
36
|
import * as fs from 'node:fs';
|
|
35
37
|
import { Project, Node } from 'ts-morph';
|
|
36
38
|
import { expandTypeStructural, } from './type-structural-expander.js';
|
|
37
39
|
export class DefinitionResolver {
|
|
38
|
-
project;
|
|
39
|
-
constructor(options) {
|
|
40
|
-
this.project = options.project;
|
|
41
|
-
}
|
|
42
40
|
/**
|
|
43
41
|
* Resolve surface aliases from a capture stub package directory
|
|
44
42
|
* (`<stub_dir>/types/surface.d.ts` + its declaration tree).
|
|
@@ -27,6 +27,7 @@ import { captureStub, findDisqualifyingTopTypes, jsonWireDeclarations, runCheck,
|
|
|
27
27
|
import { Retyper } from './retype.js';
|
|
28
28
|
import { LibraryClaimsVerifier, httpCheck } from './library-claims.js';
|
|
29
29
|
import { PROGRESS_INTERVAL_MS, atMostEvery } from './progress.js';
|
|
30
|
+
import { mergeInferTimings } from './infer-timing.js';
|
|
30
31
|
// ===========================================================================
|
|
31
32
|
// Module-level state
|
|
32
33
|
// ===========================================================================
|
|
@@ -38,6 +39,13 @@ let initTimeMs = null;
|
|
|
38
39
|
* the service's files (carrick#1604).
|
|
39
40
|
*/
|
|
40
41
|
let components = new Map();
|
|
42
|
+
/**
|
|
43
|
+
* Reads a capture stub's own declaration tree and nothing of the init'd
|
|
44
|
+
* project, so it is not one of the project's components: taking it from them
|
|
45
|
+
* would build the service's whole program to answer about the stub
|
|
46
|
+
* (carrick#1927).
|
|
47
|
+
*/
|
|
48
|
+
const definitionResolver = new DefinitionResolver();
|
|
41
49
|
/**
|
|
42
50
|
* Get the project-backed components, building the project if this is the
|
|
43
51
|
* first request that needs it.
|
|
@@ -67,7 +75,6 @@ function projectComponents(key = '') {
|
|
|
67
75
|
built = {
|
|
68
76
|
typeBundler: new TypeBundler({ project, repoRoot }),
|
|
69
77
|
typeInferrer,
|
|
70
|
-
definitionResolver: new DefinitionResolver({ project }),
|
|
71
78
|
// Locates calls exactly as `infer` does, so it rewrites the node the
|
|
72
79
|
// consumer's published type came from (carrick#1491).
|
|
73
80
|
// The walk is typed against the capture bundle's compiler copy and
|
|
@@ -218,6 +225,10 @@ function handleBundle(request) {
|
|
|
218
225
|
function handleCaptureV2(request) {
|
|
219
226
|
try {
|
|
220
227
|
log(`capture_v2 for service '${request.service_name}' (${request.anchors.length} anchor(s))`);
|
|
228
|
+
// A frame for each stage as the capture reaches it, and within a stage
|
|
229
|
+
// (one report per anchor) at most one per interval.
|
|
230
|
+
let stage;
|
|
231
|
+
const withinStage = atMostEvery(PROGRESS_INTERVAL_MS, (phase, message) => writeProgress(request.request_id, phase, message));
|
|
221
232
|
const result = captureStub({
|
|
222
233
|
repoRoot: request.repo_root,
|
|
223
234
|
serviceName: request.service_name,
|
|
@@ -225,6 +236,12 @@ function handleCaptureV2(request) {
|
|
|
225
236
|
outDir: request.out_dir,
|
|
226
237
|
tsconfigPath: request.tsconfig_path,
|
|
227
238
|
scanRoot: request.scan_root,
|
|
239
|
+
onProgress: (phase, message) => {
|
|
240
|
+
if (phase === stage)
|
|
241
|
+
return withinStage(phase, message);
|
|
242
|
+
stage = phase;
|
|
243
|
+
writeProgress(request.request_id, phase, message);
|
|
244
|
+
},
|
|
228
245
|
});
|
|
229
246
|
return {
|
|
230
247
|
request_id: request.request_id,
|
|
@@ -316,6 +333,9 @@ function handleInfer(request) {
|
|
|
316
333
|
request_id: request.request_id,
|
|
317
334
|
status: result.success ? 'success' : 'error',
|
|
318
335
|
inferred_types: result.inferred_types,
|
|
336
|
+
// Beside the answers and never in them: what the requests cost, for
|
|
337
|
+
// the caller's log (carrick#1985).
|
|
338
|
+
infer_timing: mergeInferTimings(results.map((r) => r.timing)),
|
|
319
339
|
errors: result.errors,
|
|
320
340
|
};
|
|
321
341
|
}
|
|
@@ -345,11 +365,19 @@ function handleRetypeCheck(request) {
|
|
|
345
365
|
const groups = byProject(request.items.map((item, index) => ({ item, index })), ({ item }) => item.file_path);
|
|
346
366
|
const deadline = performance.now() + budget;
|
|
347
367
|
const outcomes = new Array(request.items.length);
|
|
368
|
+
// One count for the request, whichever programs its items span, reported
|
|
369
|
+
// as `infer` reports its batch (carrick#1914, carrick#1945).
|
|
370
|
+
let judged = 0;
|
|
371
|
+
const report = atMostEvery(PROGRESS_INTERVAL_MS, () => writeProgress(request.request_id, 'retype', `${judged} of ${request.items.length}`));
|
|
372
|
+
const onJudged = () => {
|
|
373
|
+
judged += 1;
|
|
374
|
+
report();
|
|
375
|
+
};
|
|
348
376
|
for (const [key, entries] of groups) {
|
|
349
377
|
// One budget for the request, whichever programs it spans.
|
|
350
378
|
const remaining = groups.size === 1 ? budget : Math.max(0, deadline - performance.now());
|
|
351
379
|
projectComponents(key)
|
|
352
|
-
.retyper.run(entries.map(({ item }) => item), remaining)
|
|
380
|
+
.retyper.run(entries.map(({ item }) => item), remaining, onJudged)
|
|
353
381
|
.forEach((outcome, position) => {
|
|
354
382
|
outcomes[entries[position].index] = outcome;
|
|
355
383
|
});
|
|
@@ -446,12 +474,13 @@ function handleListLibrarySurface(request) {
|
|
|
446
474
|
}
|
|
447
475
|
/**
|
|
448
476
|
* Handle the 'resolve_definitions' action - resolve surface aliases from a
|
|
449
|
-
* v2 capture stub package's declaration tree.
|
|
477
|
+
* v2 capture stub package's declaration tree. Stateless, as `capture_v2` is:
|
|
478
|
+
* it needs no init and builds no project but the stub's own.
|
|
450
479
|
*/
|
|
451
480
|
function handleResolveDefinitions(request) {
|
|
452
481
|
try {
|
|
453
482
|
log(`Resolving ${request.aliases.length} type alias(es) from ${request.stub_dir}`);
|
|
454
|
-
const results =
|
|
483
|
+
const results = definitionResolver.resolveFromStub(request.stub_dir, request.aliases);
|
|
455
484
|
return {
|
|
456
485
|
request_id: request.request_id,
|
|
457
486
|
status: 'success',
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which requests of an `infer` batch were slow, and at what (carrick#1985).
|
|
3
|
+
*
|
|
4
|
+
* A pass can send thousands of requests and spend most of its time in a few
|
|
5
|
+
* of them. The inferrer times every request; this module turns those timings
|
|
6
|
+
* into what the batch's answer carries: how many there were and what they
|
|
7
|
+
* add up to, the slowest few by name, the request that printed the longest
|
|
8
|
+
* type, and the requests that were the first asked of their file, apart.
|
|
9
|
+
*
|
|
10
|
+
* A request's time is also told apart by what it went on: the compiler call
|
|
11
|
+
* that computes the type, and the print of that type to text. The two have
|
|
12
|
+
* different remedies, and a printed length alone cannot say which one a slow
|
|
13
|
+
* request was: a type of four characters can take seconds to compute, and a
|
|
14
|
+
* type of a million can take half a second to print.
|
|
15
|
+
*
|
|
16
|
+
* It measures and names. It changes no answer and holds no type text.
|
|
17
|
+
*/
|
|
18
|
+
import type { InferSlotTiming, InferTiming } from './types.js';
|
|
19
|
+
/**
|
|
20
|
+
* How many of its slowest requests a batch's answer names.
|
|
21
|
+
*
|
|
22
|
+
* Enough for a reader adding batches up to say what the slowest hundredth of
|
|
23
|
+
* a whole pass took: a pass's slowest requests are among its batches'
|
|
24
|
+
* slowest, and the last one a batch names is the most any it left out can
|
|
25
|
+
* have taken.
|
|
26
|
+
*/
|
|
27
|
+
export declare const SLOWEST_SLOTS = 25;
|
|
28
|
+
/** How long something that started at `startedMs` has taken by `nowMs`. */
|
|
29
|
+
export declare function elapsedMs(startedMs: number, nowMs: number): number;
|
|
30
|
+
/**
|
|
31
|
+
* The parts of a request's time that are told apart: `type` is the compiler
|
|
32
|
+
* call that computes a type, `print` is the print of a type to text.
|
|
33
|
+
*/
|
|
34
|
+
export type TimedPhase = 'type' | 'print';
|
|
35
|
+
/**
|
|
36
|
+
* Run `work`, and count how long it took as `phase`.
|
|
37
|
+
*
|
|
38
|
+
* The count is the process's, not a request's: whoever wants one request's
|
|
39
|
+
* share reads `phaseClock` before the request and after it. One request runs
|
|
40
|
+
* at a time, so the difference is that request's and nobody else's.
|
|
41
|
+
*/
|
|
42
|
+
export declare function timedPhase<T>(phase: TimedPhase, work: () => T): T;
|
|
43
|
+
/** What each phase has come to so far, in milliseconds. */
|
|
44
|
+
export declare function phaseClock(): Readonly<Record<TimedPhase, number>>;
|
|
45
|
+
/**
|
|
46
|
+
* What a batch's answer says of the time its requests took. `slots` holds one
|
|
47
|
+
* timing per request, in the order the batch was done with them.
|
|
48
|
+
*/
|
|
49
|
+
export declare function inferTiming(slots: readonly InferSlotTiming[]): InferTiming;
|
|
50
|
+
/**
|
|
51
|
+
* One timing for a request several projects answered, each with its own:
|
|
52
|
+
* the counts and the times added up, and the slowest of all of them.
|
|
53
|
+
*/
|
|
54
|
+
export declare function mergeInferTimings(parts: readonly InferTiming[]): InferTiming;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which requests of an `infer` batch were slow, and at what (carrick#1985).
|
|
3
|
+
*
|
|
4
|
+
* A pass can send thousands of requests and spend most of its time in a few
|
|
5
|
+
* of them. The inferrer times every request; this module turns those timings
|
|
6
|
+
* into what the batch's answer carries: how many there were and what they
|
|
7
|
+
* add up to, the slowest few by name, the request that printed the longest
|
|
8
|
+
* type, and the requests that were the first asked of their file, apart.
|
|
9
|
+
*
|
|
10
|
+
* A request's time is also told apart by what it went on: the compiler call
|
|
11
|
+
* that computes the type, and the print of that type to text. The two have
|
|
12
|
+
* different remedies, and a printed length alone cannot say which one a slow
|
|
13
|
+
* request was: a type of four characters can take seconds to compute, and a
|
|
14
|
+
* type of a million can take half a second to print.
|
|
15
|
+
*
|
|
16
|
+
* It measures and names. It changes no answer and holds no type text.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* How many of its slowest requests a batch's answer names.
|
|
20
|
+
*
|
|
21
|
+
* Enough for a reader adding batches up to say what the slowest hundredth of
|
|
22
|
+
* a whole pass took: a pass's slowest requests are among its batches'
|
|
23
|
+
* slowest, and the last one a batch names is the most any it left out can
|
|
24
|
+
* have taken.
|
|
25
|
+
*/
|
|
26
|
+
export const SLOWEST_SLOTS = 25;
|
|
27
|
+
/** Milliseconds to the microsecond: sums of these stay readable. */
|
|
28
|
+
const rounded = (ms) => Math.round(ms * 1000) / 1000;
|
|
29
|
+
/** How long something that started at `startedMs` has taken by `nowMs`. */
|
|
30
|
+
export function elapsedMs(startedMs, nowMs) {
|
|
31
|
+
return rounded(nowMs - startedMs);
|
|
32
|
+
}
|
|
33
|
+
/** What this process has spent in each phase since it started, in milliseconds. */
|
|
34
|
+
const spent = { type: 0, print: 0 };
|
|
35
|
+
/**
|
|
36
|
+
* Run `work`, and count how long it took as `phase`.
|
|
37
|
+
*
|
|
38
|
+
* The count is the process's, not a request's: whoever wants one request's
|
|
39
|
+
* share reads `phaseClock` before the request and after it. One request runs
|
|
40
|
+
* at a time, so the difference is that request's and nobody else's.
|
|
41
|
+
*/
|
|
42
|
+
export function timedPhase(phase, work) {
|
|
43
|
+
const started = performance.now();
|
|
44
|
+
try {
|
|
45
|
+
return work();
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
spent[phase] += performance.now() - started;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** What each phase has come to so far, in milliseconds. */
|
|
52
|
+
export function phaseClock() {
|
|
53
|
+
return { ...spent };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The `count` slowest of `slots`, slowest first. Requests that took the same
|
|
57
|
+
* time keep the order they are in.
|
|
58
|
+
*/
|
|
59
|
+
function slowestOf(slots, count) {
|
|
60
|
+
return [...slots].sort((a, b) => b.ms - a.ms).slice(0, count);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The one of `slots` that printed the longest type, the earliest of those
|
|
64
|
+
* that tie; undefined when none printed anything.
|
|
65
|
+
*/
|
|
66
|
+
function longestPrintedOf(slots) {
|
|
67
|
+
let longest;
|
|
68
|
+
for (const slot of slots) {
|
|
69
|
+
if (slot.printed_length > (longest?.printed_length ?? 0))
|
|
70
|
+
longest = slot;
|
|
71
|
+
}
|
|
72
|
+
return longest;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* What a batch's answer says of the time its requests took. `slots` holds one
|
|
76
|
+
* timing per request, in the order the batch was done with them.
|
|
77
|
+
*/
|
|
78
|
+
export function inferTiming(slots) {
|
|
79
|
+
let slotsMs = 0;
|
|
80
|
+
let typeMs = 0;
|
|
81
|
+
let printMs = 0;
|
|
82
|
+
let firstInFile = 0;
|
|
83
|
+
let firstInFileMs = 0;
|
|
84
|
+
for (const slot of slots) {
|
|
85
|
+
slotsMs += slot.ms;
|
|
86
|
+
typeMs += slot.type_ms;
|
|
87
|
+
printMs += slot.print_ms;
|
|
88
|
+
if (slot.first_in_file) {
|
|
89
|
+
firstInFile += 1;
|
|
90
|
+
firstInFileMs += slot.ms;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const longest = longestPrintedOf(slots);
|
|
94
|
+
return {
|
|
95
|
+
slots: slots.length,
|
|
96
|
+
slots_ms: rounded(slotsMs),
|
|
97
|
+
type_ms: rounded(typeMs),
|
|
98
|
+
print_ms: rounded(printMs),
|
|
99
|
+
first_in_file_slots: firstInFile,
|
|
100
|
+
first_in_file_ms: rounded(firstInFileMs),
|
|
101
|
+
slowest: slowestOf(slots, SLOWEST_SLOTS),
|
|
102
|
+
...(longest ? { longest_printed: longest } : {}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* One timing for a request several projects answered, each with its own:
|
|
107
|
+
* the counts and the times added up, and the slowest of all of them.
|
|
108
|
+
*/
|
|
109
|
+
export function mergeInferTimings(parts) {
|
|
110
|
+
if (parts.length === 1)
|
|
111
|
+
return parts[0];
|
|
112
|
+
const sum = (read) => parts.reduce((total, part) => total + read(part), 0);
|
|
113
|
+
const longest = longestPrintedOf(parts.flatMap((part) => part.longest_printed ?? []));
|
|
114
|
+
return {
|
|
115
|
+
slots: sum((part) => part.slots),
|
|
116
|
+
slots_ms: rounded(sum((part) => part.slots_ms)),
|
|
117
|
+
type_ms: rounded(sum((part) => part.type_ms)),
|
|
118
|
+
print_ms: rounded(sum((part) => part.print_ms)),
|
|
119
|
+
first_in_file_slots: sum((part) => part.first_in_file_slots),
|
|
120
|
+
first_in_file_ms: rounded(sum((part) => part.first_in_file_ms)),
|
|
121
|
+
slowest: slowestOf(parts.flatMap((part) => part.slowest), SLOWEST_SLOTS),
|
|
122
|
+
...(longest ? { longest_printed: longest } : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -53,9 +53,11 @@ export declare class Retyper {
|
|
|
53
53
|
* Judge every item, spending at most `budgetMs`. Each item rebuilds the
|
|
54
54
|
* program at least twice, so a consumer with many calls could otherwise
|
|
55
55
|
* outrun the caller's read deadline and lose every answer; the items the
|
|
56
|
-
* budget does not reach abstain and say so.
|
|
56
|
+
* budget does not reach abstain and say so. `onJudged` is called after each
|
|
57
|
+
* item the budget reached, so the caller can report progress while the
|
|
58
|
+
* event loop is blocked (carrick#1945).
|
|
57
59
|
*/
|
|
58
|
-
run(items: RetypeItem[], budgetMs: number): RetypeOutcome[];
|
|
60
|
+
run(items: RetypeItem[], budgetMs: number, onJudged?: () => void): RetypeOutcome[];
|
|
59
61
|
private runOne;
|
|
60
62
|
/**
|
|
61
63
|
* The rewrite that states the producer's UNWIDENED return (carrick#1516),
|
|
@@ -53,9 +53,11 @@ export class Retyper {
|
|
|
53
53
|
* Judge every item, spending at most `budgetMs`. Each item rebuilds the
|
|
54
54
|
* program at least twice, so a consumer with many calls could otherwise
|
|
55
55
|
* outrun the caller's read deadline and lose every answer; the items the
|
|
56
|
-
* budget does not reach abstain and say so.
|
|
56
|
+
* budget does not reach abstain and say so. `onJudged` is called after each
|
|
57
|
+
* item the budget reached, so the caller can report progress while the
|
|
58
|
+
* event loop is blocked (carrick#1945).
|
|
57
59
|
*/
|
|
58
|
-
run(items, budgetMs) {
|
|
60
|
+
run(items, budgetMs, onJudged) {
|
|
59
61
|
const deadline = performance.now() + budgetMs;
|
|
60
62
|
// What each file said before any rewrite. Every rewrite is undone, so it
|
|
61
63
|
// is the same for every item in the file.
|
|
@@ -64,12 +66,15 @@ export class Retyper {
|
|
|
64
66
|
if (performance.now() >= deadline) {
|
|
65
67
|
return abstain(item, `the retype check ran out of its ${budgetMs}ms budget`);
|
|
66
68
|
}
|
|
69
|
+
let outcome;
|
|
67
70
|
try {
|
|
68
|
-
|
|
71
|
+
outcome = this.runOne(item, before);
|
|
69
72
|
}
|
|
70
73
|
catch (err) {
|
|
71
|
-
|
|
74
|
+
outcome = abstain(item, `the retype check failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
72
75
|
}
|
|
76
|
+
onJudged?.();
|
|
77
|
+
return outcome;
|
|
73
78
|
});
|
|
74
79
|
}
|
|
75
80
|
runOne(item, before) {
|
|
@@ -86,6 +86,12 @@ export declare class TypeInferrer {
|
|
|
86
86
|
*/
|
|
87
87
|
private readonly readNodes;
|
|
88
88
|
private readonly unwidenedBudgetMs;
|
|
89
|
+
/**
|
|
90
|
+
* The files a request has named to this inferrer, as the requests named
|
|
91
|
+
* them (carrick#1985). It lives as long as the project does, so the first
|
|
92
|
+
* request of a file is the first of the process, not of a batch.
|
|
93
|
+
*/
|
|
94
|
+
private readonly filesAsked;
|
|
89
95
|
constructor(options: TypeInferrerOptions);
|
|
90
96
|
/**
|
|
91
97
|
* What the structural printer needs to tell the user's own declarations from
|
|
@@ -193,6 +199,61 @@ export declare class TypeInferrer {
|
|
|
193
199
|
*/
|
|
194
200
|
private resolveParamTarget;
|
|
195
201
|
private inferResponseBody;
|
|
202
|
+
/**
|
|
203
|
+
* The handler a module exports on `line`: a function that opens on exactly
|
|
204
|
+
* that line, exported at the top of its module, where no call on the line
|
|
205
|
+
* registers a route (carrick#807).
|
|
206
|
+
*
|
|
207
|
+
* That is the anchor of a route its module's place in the project states:
|
|
208
|
+
* the row's line is the handler's own export, and the module's exports are
|
|
209
|
+
* how the framework finds it. Each condition keeps another kind of row out:
|
|
210
|
+
*
|
|
211
|
+
* - the line has to be the function's first, with no tolerance. A model
|
|
212
|
+
* row that names no site of its own can sit on any line of a handler,
|
|
213
|
+
* and the nearest function to such a line is whatever is declared
|
|
214
|
+
* beside it;
|
|
215
|
+
* - a method of a class is a controller's handler, whose rows are
|
|
216
|
+
* anchored on what a model located before the method's own return;
|
|
217
|
+
* - a line that registers a route has a registration to read.
|
|
218
|
+
*/
|
|
219
|
+
private handlerDeclaredAtLine;
|
|
220
|
+
/**
|
|
221
|
+
* `handlerDeclaredAtLine` without its registration test, for a caller that
|
|
222
|
+
* has already looked for a registration on the line.
|
|
223
|
+
*/
|
|
224
|
+
private exportedFunctionOpeningOn;
|
|
225
|
+
/**
|
|
226
|
+
* The response of a handler the request's line declares, for a request
|
|
227
|
+
* that also located an expression inside it (carrick#807).
|
|
228
|
+
*
|
|
229
|
+
* The handler's own return is read first, exactly as a `function_return`
|
|
230
|
+
* request at the line reads it, and that reading stands: a body it
|
|
231
|
+
* recovered, and a decision it made not to publish one. The located
|
|
232
|
+
* expression is consulted in one case only. Where the handler returns a
|
|
233
|
+
* call nothing resolves (its library is not installed), the return walk
|
|
234
|
+
* reads no argument the source does not annotate, because it cannot tell a
|
|
235
|
+
* response sender from a query. A located payload that is an ARGUMENT of
|
|
236
|
+
* such a returned call says which it is, so the same walk runs again with
|
|
237
|
+
* that callee taken as a sender: every success body the handler returns
|
|
238
|
+
* through it, error branches dropped as before. The located expression's
|
|
239
|
+
* own type is never what is published here.
|
|
240
|
+
*
|
|
241
|
+
* Returns `undefined` when the located expression is something the return
|
|
242
|
+
* walk never looked at, so it is left to the readings below:
|
|
243
|
+
* - the handler returns nothing (`void`): it sends through a parameter;
|
|
244
|
+
* - the handler returns transport the walk read no body out of, and the
|
|
245
|
+
* located expression is not part of what it returns. A body built before
|
|
246
|
+
* the response is returned (`const res = send(body); ...; return res`)
|
|
247
|
+
* is one. Named on a send that states an error or redirect status, it is
|
|
248
|
+
* no success body and the row abstains.
|
|
249
|
+
*/
|
|
250
|
+
private responseOfDeclaredHandler;
|
|
251
|
+
/**
|
|
252
|
+
* The call inside one of `returned` that the located expression is, or is
|
|
253
|
+
* an argument of: the send a located payload was handed to. `undefined`
|
|
254
|
+
* when the located expression is not part of what the handler returns.
|
|
255
|
+
*/
|
|
256
|
+
private returnedSendHolding;
|
|
196
257
|
/**
|
|
197
258
|
* True when a located call's own result is the route's payload, so the
|
|
198
259
|
* transitional drill into its first argument must not run (carrick#1732).
|
|
@@ -297,6 +358,14 @@ export declare class TypeInferrer {
|
|
|
297
358
|
* else, and is not reported.
|
|
298
359
|
*/
|
|
299
360
|
private statedBodyAtRead;
|
|
361
|
+
/**
|
|
362
|
+
* The type node `statedBodyAtRead` reads, for a caller that wants the
|
|
363
|
+
* annotation itself: a consumer's response read reports its root, and a
|
|
364
|
+
* handler's request read prints it (carrick#807). One reading of "stated at
|
|
365
|
+
* the read" for both, so the two cannot disagree about which annotation
|
|
366
|
+
* belongs to a read.
|
|
367
|
+
*/
|
|
368
|
+
private typeNodeStatedAtRead;
|
|
300
369
|
/**
|
|
301
370
|
* A stated type with `any` or `unknown` written anywhere in it (`unknown`,
|
|
302
371
|
* `Record<string, unknown>`, `{ items: any[] }`) leaves a position open: it
|
|
@@ -892,9 +961,22 @@ export declare class TypeInferrer {
|
|
|
892
961
|
*
|
|
893
962
|
* Under `statedOnly` the annotation is the ONLY thing that counts, so an
|
|
894
963
|
* unresolvable callee's arguments never become a contract by accident.
|
|
964
|
+
*
|
|
965
|
+
* `asSent` judges the object in the form it is sent in, which is what an
|
|
966
|
+
* ARGUMENT handed to a sender is asked; see `typeIsObjectShaped`.
|
|
895
967
|
*/
|
|
896
968
|
private nodeCarriesPayloadContract;
|
|
897
|
-
/**
|
|
969
|
+
/**
|
|
970
|
+
* Object, array-of-object, or a union/intersection containing one.
|
|
971
|
+
*
|
|
972
|
+
* With `asSent` the object is judged in the form it is SENT in
|
|
973
|
+
* (carrick#1163): a value that declares `toJSON()` travels as what that
|
|
974
|
+
* returns. A `URL` or a `Date` is therefore the string it serialises to,
|
|
975
|
+
* the bare primitive this rule already refuses, and `redirect(new URL(path,
|
|
976
|
+
* base))` hands over a location exactly as `redirect("/next")` does
|
|
977
|
+
* (carrick#807). A value whose JSON form is itself an object is a body like
|
|
978
|
+
* any other.
|
|
979
|
+
*/
|
|
898
980
|
private typeIsObjectShaped;
|
|
899
981
|
/**
|
|
900
982
|
* The `satisfies X` / `as X` / `<X>` annotation node on an expression, when
|
|
@@ -1275,6 +1357,46 @@ export declare class TypeInferrer {
|
|
|
1275
1357
|
* Returns null (not a spurious type) for a genuinely payload-less handler.
|
|
1276
1358
|
*/
|
|
1277
1359
|
private inferRequestReadFromHandler;
|
|
1360
|
+
/**
|
|
1361
|
+
* The call in a handler that reads the request's body off the platform
|
|
1362
|
+
* request the handler was handed, or `undefined` (carrick#807).
|
|
1363
|
+
*
|
|
1364
|
+
* Read by shape, with no method name consulted:
|
|
1365
|
+
*
|
|
1366
|
+
* - a call that takes nothing, on a member of a value whose type is request
|
|
1367
|
+
* machinery (`typeIsFrameworkMachinery`: declared by the platform or an
|
|
1368
|
+
* installed library, and carrying its body readers);
|
|
1369
|
+
* - that value is rooted at one of the handler's OWN parameters, named or
|
|
1370
|
+
* destructured. A response read off an outbound call inside the handler
|
|
1371
|
+
* (`(await upstream.json()) as Rate`) has the same shape one variable
|
|
1372
|
+
* away, and is the opposite side of a different exchange;
|
|
1373
|
+
* - and the call's result, awaited, is `any` or `unknown`: the platform's
|
|
1374
|
+
* untyped parse. A read that states its own type (`formData()`, a typed
|
|
1375
|
+
* `json<T>()`) is not this shape and is left to the readers that
|
|
1376
|
+
* already handle it.
|
|
1377
|
+
*
|
|
1378
|
+
* The first such call in source order: a body is read once.
|
|
1379
|
+
*/
|
|
1380
|
+
private platformBodyReadIn;
|
|
1381
|
+
/**
|
|
1382
|
+
* The request contract the source states AT a body read, or null when it
|
|
1383
|
+
* states none there (carrick#807).
|
|
1384
|
+
*
|
|
1385
|
+
* Three statements count, each of them on the read itself:
|
|
1386
|
+
*
|
|
1387
|
+
* - a cast on it: `(await request.json()) as NewWidget`;
|
|
1388
|
+
* - the annotation of the binding it initialises:
|
|
1389
|
+
* `const input: NewWidget = await request.json()`;
|
|
1390
|
+
* - a schema the read, or the binding that holds it, is handed to
|
|
1391
|
+
* (`schemaConsumingRead`): the schema's input is what a caller may send.
|
|
1392
|
+
*
|
|
1393
|
+
* The first two are `typeNodeStatedAtRead`, the reading a consumer's
|
|
1394
|
+
* response read already gets. An annotation further out is not one of them:
|
|
1395
|
+
* `const saved: Saved = await save(await request.json())` types what `save`
|
|
1396
|
+
* returned, and reading it as the body would publish the wrong side of the
|
|
1397
|
+
* handler. Nor is a placeholder (`as unknown`, `Record<string, unknown>`).
|
|
1398
|
+
*/
|
|
1399
|
+
private requestStatedAtBodyRead;
|
|
1278
1400
|
/**
|
|
1279
1401
|
* Resolve a type-annotation/type-argument node to fully-structural text,
|
|
1280
1402
|
* dropping any `Promise<…>` wrapper (`c.req.json<T>()` returns `Promise<T>`,
|
|
@@ -1418,8 +1540,31 @@ export declare class TypeInferrer {
|
|
|
1418
1540
|
* request type resolves `body` to `unknown`/`any`, which the useless-type
|
|
1419
1541
|
* guard rejects. So a handler that declares nothing yields null and the next
|
|
1420
1542
|
* anchor runs.
|
|
1543
|
+
*
|
|
1544
|
+
* The member has to be one the route's own annotation can have filled
|
|
1545
|
+
* (carrick#807). The platform `Request` declares `body` too, as the byte
|
|
1546
|
+
* stream every request carries, and so does each library type that extends
|
|
1547
|
+
* it. That is concrete, so it passed the useless-type guard and a handler
|
|
1548
|
+
* taking the platform request published a stream as its request contract,
|
|
1549
|
+
* ahead of the body read inside it. A `body` a library declares with one
|
|
1550
|
+
* fixed type says nothing about this route and is skipped.
|
|
1421
1551
|
*/
|
|
1422
1552
|
private requestBodyFromHandlerParams;
|
|
1553
|
+
/**
|
|
1554
|
+
* True when a member is declared by a library, or by the platform, with a
|
|
1555
|
+
* type that names none of its declaring type's parameters: the same type on
|
|
1556
|
+
* every value, whichever route the value belongs to.
|
|
1557
|
+
*
|
|
1558
|
+
* `body: ReqBody` on a request type generic in its body is a slot, and the
|
|
1559
|
+
* handler's annotation fills it. `readonly body: ReadableStream<Uint8Array>
|
|
1560
|
+
* | null` on the platform request is not. A member the repo declares itself
|
|
1561
|
+
* is never fixed in this sense: writing `body: NewWidget` on the handler's
|
|
1562
|
+
* own request type is the annotation, and one declaration of the repo's
|
|
1563
|
+
* among several (an intersection with the platform type) decides it. A
|
|
1564
|
+
* declaration that states no type at all is left to the guards that read
|
|
1565
|
+
* the type.
|
|
1566
|
+
*/
|
|
1567
|
+
private memberIsFixedByItsLibrary;
|
|
1423
1568
|
/**
|
|
1424
1569
|
* Anchor (b2): the contract declared by a VALIDATOR MIDDLEWARE on the
|
|
1425
1570
|
* registration (carrick#964).
|