carrick 0.3.109 → 0.3.111

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/render.d.ts +14 -7
  2. package/dist/render.js +49 -36
  3. package/dist/render.js.map +1 -1
  4. package/package.json +6 -6
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/sidecar/dist/src/bundler.d.ts +10 -0
  7. package/sidecar/dist/src/bundler.js +39 -0
  8. package/sidecar/dist/src/capture/anchors.js +35 -0
  9. package/sidecar/dist/src/capture/api.d.ts +5 -0
  10. package/sidecar/dist/src/capture/check-workspace.d.ts +10 -0
  11. package/sidecar/dist/src/capture/check-workspace.js +68 -6
  12. package/sidecar/dist/src/capture/index.d.ts +1 -0
  13. package/sidecar/dist/src/capture/index.js +18 -3
  14. package/sidecar/dist/src/capture/installed-package-directory.d.ts +22 -0
  15. package/sidecar/dist/src/capture/installed-package-directory.js +30 -0
  16. package/sidecar/dist/src/capture/resolution-edges.d.ts +36 -0
  17. package/sidecar/dist/src/capture/resolution-edges.js +150 -0
  18. package/sidecar/dist/src/capture/status-table.d.ts +62 -0
  19. package/sidecar/dist/src/capture/status-table.js +104 -0
  20. package/sidecar/dist/src/failure-path.d.ts +42 -2
  21. package/sidecar/dist/src/failure-path.js +38 -13
  22. package/sidecar/dist/src/library-claims.js +2 -0
  23. package/sidecar/dist/src/line-index.d.ts +9 -0
  24. package/sidecar/dist/src/line-index.js +24 -0
  25. package/sidecar/dist/src/response-modes.d.ts +77 -0
  26. package/sidecar/dist/src/response-modes.js +459 -0
  27. package/sidecar/dist/src/type-inferrer.d.ts +93 -1
  28. package/sidecar/dist/src/type-inferrer.js +436 -97
  29. package/sidecar/dist/src/types.d.ts +20 -1
  30. package/sidecar/dist/src/validators.d.ts +39 -16
  31. package/sidecar/dist/src/validators.js +4 -0
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Which incoming-message field picks each member of a response union (carrick#2054).
3
+ *
4
+ * A handler that sends `{ x }` when `?mode=a` and `{ y }` otherwise publishes
5
+ * `{ x } | { y }`. A call that states `mode=a` receives only `{ x }`, and a
6
+ * call that states nothing may receive either. The union alone cannot say
7
+ * which, so this reads it from the handler: for each success body the union
8
+ * joined, the tests on its path that compare a value read from the incoming message
9
+ * with a string literal.
10
+ *
11
+ * The reading, in order:
12
+ *
13
+ * - A test counts only when it separates two of the joined bodies: one on
14
+ * each side of it, or, for an earlier `if` that cannot complete, one in
15
+ * its branch and one after it. A validation guard whose branch only sends
16
+ * an error separates nothing the join kept, and is ignored.
17
+ * - A counted test is a message read when a value it tests is read from a
18
+ * parameter of a function enclosing the send (`messageRead`). A test that
19
+ * reads nothing from the incoming message (server state, a local count) is ignored,
20
+ * so a body under it is under no test of the field and stays in every case.
21
+ * - `===`, `==`, `!==` and `!=` against a string literal, with `!` flipping
22
+ * the test, and `switch` labels, are read as values. Any other test of a
23
+ * message read leaves its field read with its values unread.
24
+ * - `reads` lists every message read among the counted tests. `cases` is
25
+ * read only for one placed field whose tests were all read: one case per
26
+ * literal compared, plus "any other value" (`value: null`) when a body
27
+ * admits one, each the union of the bodies it admits.
28
+ * - When every case is the same union, the route has no modes.
29
+ *
30
+ * The published union is not changed by any of this.
31
+ */
32
+ import { Node, type Symbol as TsSymbol } from 'ts-morph';
33
+ /**
34
+ * Where in the incoming message a field is read from. Only the HTTP sources
35
+ * are read so far; another protocol (a socket message, a pub/sub payload)
36
+ * adds its own source here.
37
+ */
38
+ export type MessageSource = 'query' | 'body' | 'unplaced';
39
+ /** One incoming-message field a response union depends on. */
40
+ export interface ResponseModeRead {
41
+ /**
42
+ * `query`: read with `.get(...)` on the default lib's `URLSearchParams`.
43
+ * `body`: a top-level property of the awaited result of a zero-argument
44
+ * `json()` the default lib or an installed library declares. `unplaced`:
45
+ * anything else (a header, a path parameter, the method, a framework's own
46
+ * accessor, a `json()` the repo declares).
47
+ */
48
+ location: MessageSource;
49
+ field: string;
50
+ }
51
+ /** The union a stated value of the field receives. */
52
+ export interface ResponseModeCase {
53
+ /** The value; `null` for any value no other case names. */
54
+ value: string | null;
55
+ type_string: string;
56
+ }
57
+ export interface ResponseModes {
58
+ /** Every message field the union depends on, sorted by location, then field. */
59
+ reads: ResponseModeRead[];
60
+ /**
61
+ * Present only when `reads` is one placed field and every test of it was
62
+ * read. Sorted by value, the `null` case last.
63
+ */
64
+ cases?: ResponseModeCase[];
65
+ }
66
+ /** One success body the union joined, in source order. */
67
+ export interface JoinedBody {
68
+ node: Node;
69
+ type_string: string;
70
+ }
71
+ /**
72
+ * The modes of the union joined from `bodies`, or `undefined` when no message
73
+ * read separates them. `join` builds a union text from member texts with the
74
+ * rules the published union is built with. `isLibOrExternal` says whether a
75
+ * symbol is declared by a TypeScript lib or an installed library.
76
+ */
77
+ export declare function readResponseModes(bodies: JoinedBody[], join: (texts: string[]) => string, isLibOrExternal: (symbol: TsSymbol | undefined) => boolean): ResponseModes | undefined;
@@ -0,0 +1,459 @@
1
+ /**
2
+ * Which incoming-message field picks each member of a response union (carrick#2054).
3
+ *
4
+ * A handler that sends `{ x }` when `?mode=a` and `{ y }` otherwise publishes
5
+ * `{ x } | { y }`. A call that states `mode=a` receives only `{ x }`, and a
6
+ * call that states nothing may receive either. The union alone cannot say
7
+ * which, so this reads it from the handler: for each success body the union
8
+ * joined, the tests on its path that compare a value read from the incoming message
9
+ * with a string literal.
10
+ *
11
+ * The reading, in order:
12
+ *
13
+ * - A test counts only when it separates two of the joined bodies: one on
14
+ * each side of it, or, for an earlier `if` that cannot complete, one in
15
+ * its branch and one after it. A validation guard whose branch only sends
16
+ * an error separates nothing the join kept, and is ignored.
17
+ * - A counted test is a message read when a value it tests is read from a
18
+ * parameter of a function enclosing the send (`messageRead`). A test that
19
+ * reads nothing from the incoming message (server state, a local count) is ignored,
20
+ * so a body under it is under no test of the field and stays in every case.
21
+ * - `===`, `==`, `!==` and `!=` against a string literal, with `!` flipping
22
+ * the test, and `switch` labels, are read as values. Any other test of a
23
+ * message read leaves its field read with its values unread.
24
+ * - `reads` lists every message read among the counted tests. `cases` is
25
+ * read only for one placed field whose tests were all read: one case per
26
+ * literal compared, plus "any other value" (`value: null`) when a body
27
+ * admits one, each the union of the bodies it admits.
28
+ * - When every case is the same union, the route has no modes.
29
+ *
30
+ * The published union is not changed by any of this.
31
+ */
32
+ import { Node, SyntaxKind, VariableDeclarationKind } from 'ts-morph';
33
+ import { cannotComplete, stepsOnPath } from './failure-path.js';
34
+ const MAX_CHAIN = 32;
35
+ /**
36
+ * The modes of the union joined from `bodies`, or `undefined` when no message
37
+ * read separates them. `join` builds a union text from member texts with the
38
+ * rules the published union is built with. `isLibOrExternal` says whether a
39
+ * symbol is declared by a TypeScript lib or an installed library.
40
+ */
41
+ export function readResponseModes(bodies, join, isLibOrExternal) {
42
+ if (bodies.length < 2)
43
+ return undefined;
44
+ const boundary = smallestEnclosingFunction(bodies.map((body) => body.node));
45
+ // Each body's side of each test on its path, keyed by the test.
46
+ // A switch some body follows is read as that, whichever body met it first.
47
+ const sides = new Map();
48
+ bodies.forEach((body, index) => {
49
+ for (const step of stepsOnPath(body.node, boundary)) {
50
+ const entry = sides.get(step.test) ?? { step, byBody: new Map() };
51
+ if (step.kind === 'after-switch')
52
+ entry.step = step;
53
+ entry.byBody.set(index, sideKey(step));
54
+ sides.set(step.test, entry);
55
+ }
56
+ });
57
+ const reads = new Map();
58
+ const unreadFields = new Set();
59
+ const literals = new Map();
60
+ const constraints = bodies.map(() => new Map());
61
+ for (const { step, byBody } of sides.values()) {
62
+ if (new Set(byBody.values()).size < 2)
63
+ continue;
64
+ const reading = readStep(step, isLibOrExternal);
65
+ if (!reading)
66
+ continue;
67
+ if (reading.kind === 'unread') {
68
+ for (const read of reading.reads) {
69
+ reads.set(readKey(read), read);
70
+ unreadFields.add(readKey(read));
71
+ }
72
+ continue;
73
+ }
74
+ const key = readKey(reading.read);
75
+ reads.set(key, reading.read);
76
+ const values = literals.get(key) ?? new Set();
77
+ for (const literal of reading.literals)
78
+ values.add(literal);
79
+ literals.set(key, values);
80
+ for (const [index, side] of byBody) {
81
+ const list = constraints[index].get(key) ?? [];
82
+ list.push(reading.admits(side));
83
+ constraints[index].set(key, list);
84
+ }
85
+ }
86
+ if (reads.size === 0)
87
+ return undefined;
88
+ const sortedReads = [...reads.values()]
89
+ .map(({ location, field }) => ({ location, field }))
90
+ .sort((a, b) => compare(a.location, b.location) || compare(a.field, b.field));
91
+ const readsOnly = { reads: sortedReads };
92
+ if (sortedReads.length !== 1 || sortedReads[0].location === 'unplaced')
93
+ return readsOnly;
94
+ const key = readKey(sortedReads[0]);
95
+ if (unreadFields.has(key))
96
+ return readsOnly;
97
+ const admitted = constraints.map((byField) => intersect(byField.get(key) ?? []));
98
+ const unionFor = (admits) => {
99
+ const texts = bodies.filter((_, index) => admits(admitted[index])).map((body) => body.type_string);
100
+ return texts.length > 0 ? join(texts) : undefined;
101
+ };
102
+ const cases = [];
103
+ for (const value of [...(literals.get(key) ?? [])].sort(compare)) {
104
+ const text = unionFor((body) => admitsValue(body, value));
105
+ // A named value no joined body answers: the reading is not one to narrow on.
106
+ if (text === undefined)
107
+ return readsOnly;
108
+ cases.push({ value, type_string: text });
109
+ }
110
+ const other = unionFor((body) => 'except' in body);
111
+ if (other !== undefined)
112
+ cases.push({ value: null, type_string: other });
113
+ if (new Set(cases.map((c) => c.type_string)).size < 2)
114
+ return undefined;
115
+ return { reads: sortedReads, cases };
116
+ }
117
+ function compare(a, b) {
118
+ return a < b ? -1 : a > b ? 1 : 0;
119
+ }
120
+ function readKey(read) {
121
+ return `${read.location}\u0000${read.field}`;
122
+ }
123
+ /** Which side of its test a step puts a node on. */
124
+ function sideKey(step) {
125
+ switch (step.kind) {
126
+ case 'branch':
127
+ return step.whenTrue ? 'true' : 'false';
128
+ case 'clause':
129
+ return `clause:${step.test.getClauses().indexOf(step.clause)}`;
130
+ case 'after-switch':
131
+ return 'after';
132
+ }
133
+ }
134
+ /** The innermost function that holds every node, `undefined` when none does. */
135
+ function smallestEnclosingFunction(nodes) {
136
+ for (const candidate of nodes[0].getAncestors()) {
137
+ if (!Node.isFunctionLikeDeclaration(candidate))
138
+ continue;
139
+ if (nodes.every((node) => node === candidate || candidate.containsRange(node.getPos(), node.getEnd()))) {
140
+ return candidate;
141
+ }
142
+ }
143
+ return undefined;
144
+ }
145
+ function intersect(list) {
146
+ const every = { except: new Set() };
147
+ let result = every;
148
+ for (const next of list) {
149
+ const current = result;
150
+ if ('only' in next) {
151
+ result = { only: new Set([...next.only].filter((value) => admitsValue(current, value))) };
152
+ }
153
+ else if ('only' in current) {
154
+ result = { only: new Set([...current.only].filter((value) => admitsValue(next, value))) };
155
+ }
156
+ else {
157
+ result = { except: new Set([...current.except, ...next.except]) };
158
+ }
159
+ }
160
+ return result;
161
+ }
162
+ function admitsValue(admits, value) {
163
+ return 'only' in admits ? admits.only.has(value) : !admits.except.has(value);
164
+ }
165
+ /** What `step`'s test reads of the incoming message, `undefined` when nothing. */
166
+ function readStep(step, isLibOrExternal) {
167
+ if (step.kind === 'branch')
168
+ return readCondition(step.condition, isLibOrExternal);
169
+ const subject = step.test.getExpression();
170
+ const read = messageRead(subject, isLibOrExternal);
171
+ if (!read) {
172
+ const inside = readsIn(subject, isLibOrExternal);
173
+ return inside.length > 0 ? { kind: 'unread', reads: inside } : undefined;
174
+ }
175
+ // A body after the switch is reached by the values whose clauses run on
176
+ // past it, which this reading does not follow.
177
+ if (step.kind === 'after-switch')
178
+ return { kind: 'unread', reads: [read] };
179
+ const clauses = step.test.getClauses();
180
+ const labels = [];
181
+ for (const clause of clauses) {
182
+ if (!Node.isCaseClause(clause))
183
+ continue;
184
+ const label = stringLiteral(clause.getExpression());
185
+ if (label === undefined)
186
+ return { kind: 'unread', reads: [read] };
187
+ labels.push(label);
188
+ }
189
+ // A clause is entered by its own label and by every clause before it that
190
+ // runs on into it.
191
+ const entered = clauses.map((clause, index) => {
192
+ const group = [];
193
+ for (let at = index; at >= 0; at--) {
194
+ if (at < index && clauses[at].getStatements().some(cannotComplete))
195
+ break;
196
+ const entry = clauses[at];
197
+ group.push(Node.isCaseClause(entry) ? stringLiteral(entry.getExpression()) : null);
198
+ }
199
+ return group;
200
+ });
201
+ return {
202
+ kind: 'values',
203
+ read,
204
+ literals: labels,
205
+ admits: (side) => {
206
+ const index = Number(side.slice('clause:'.length));
207
+ const group = entered[index] ?? [];
208
+ const named = group.filter((label) => label !== null);
209
+ if (group.includes(null)) {
210
+ return { except: new Set(labels.filter((label) => !named.includes(label))) };
211
+ }
212
+ return { only: new Set(named) };
213
+ },
214
+ };
215
+ }
216
+ /** What an `if` or conditional condition reads of the incoming message. */
217
+ function readCondition(condition, isLibOrExternal) {
218
+ let test = condition;
219
+ let flipped = false;
220
+ for (;;) {
221
+ if (Node.isParenthesizedExpression(test)) {
222
+ test = test.getExpression();
223
+ }
224
+ else if (Node.isPrefixUnaryExpression(test) &&
225
+ test.getOperatorToken() === SyntaxKind.ExclamationToken) {
226
+ flipped = !flipped;
227
+ test = test.getOperand();
228
+ }
229
+ else {
230
+ break;
231
+ }
232
+ }
233
+ if (Node.isBinaryExpression(test)) {
234
+ const operator = test.getOperatorToken().getKind();
235
+ const equal = operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.EqualsEqualsToken;
236
+ const unequal = operator === SyntaxKind.ExclamationEqualsEqualsToken ||
237
+ operator === SyntaxKind.ExclamationEqualsToken;
238
+ if (equal || unequal) {
239
+ const left = test.getLeft();
240
+ const right = test.getRight();
241
+ const rightLiteral = stringLiteral(right);
242
+ const leftLiteral = stringLiteral(left);
243
+ const value = rightLiteral ?? leftLiteral;
244
+ const operand = rightLiteral !== undefined ? left : right;
245
+ const read = value !== undefined ? messageRead(operand, isLibOrExternal) : undefined;
246
+ if (read && value !== undefined) {
247
+ // True side admits the value when the test is an equality.
248
+ const trueAdmitsValue = equal !== flipped;
249
+ return {
250
+ kind: 'values',
251
+ read,
252
+ literals: [value],
253
+ admits: (side) => (side === 'true') === trueAdmitsValue
254
+ ? { only: new Set([value]) }
255
+ : { except: new Set([value]) },
256
+ };
257
+ }
258
+ }
259
+ }
260
+ const inside = readsIn(condition, isLibOrExternal);
261
+ return inside.length > 0 ? { kind: 'unread', reads: inside } : undefined;
262
+ }
263
+ /** The text of a string literal, `undefined` for anything else. */
264
+ function stringLiteral(node) {
265
+ if (!node)
266
+ return undefined;
267
+ let inner = node;
268
+ while (Node.isParenthesizedExpression(inner))
269
+ inner = inner.getExpression();
270
+ if (Node.isStringLiteral(inner) || Node.isNoSubstitutionTemplateLiteral(inner)) {
271
+ return inner.getLiteralValue();
272
+ }
273
+ return undefined;
274
+ }
275
+ /** Every message read inside `node`, outermost reads only. */
276
+ function readsIn(node, isLibOrExternal) {
277
+ const found = [];
278
+ for (const candidate of [node, ...node.getDescendants()]) {
279
+ if (found.some((read) => read.node.containsRange(candidate.getPos(), candidate.getEnd())))
280
+ continue;
281
+ const read = messageRead(candidate, isLibOrExternal);
282
+ if (read)
283
+ found.push(read);
284
+ }
285
+ return found;
286
+ }
287
+ /**
288
+ * The incoming-message field `node` reads, `undefined` when it is not a read of the
289
+ * incoming message: a chain that starts, by symbol, at a parameter of a function
290
+ * enclosing `node`, through `const` bindings, `await`, parentheses, `as`,
291
+ * `!`, property access, element access with a string literal, a method call
292
+ * whose arguments are all string literals, and `new URL(...)` of the global
293
+ * `URL`. A `let`, a binding default, or a call that takes the message as an
294
+ * argument ends the chain, and it is not a read.
295
+ */
296
+ function messageRead(node, isLibOrExternal) {
297
+ const links = chainOf(node, enclosingParameters(node), 0);
298
+ if (!links || links.length === 0)
299
+ return undefined;
300
+ const field = fieldOf(links);
301
+ if (field === undefined)
302
+ return undefined;
303
+ return { location: locationOf(links, isLibOrExternal), field, node };
304
+ }
305
+ function enclosingParameters(node) {
306
+ const parameters = new Set();
307
+ for (const ancestor of node.getAncestors()) {
308
+ if (Node.isFunctionLikeDeclaration(ancestor)) {
309
+ for (const parameter of ancestor.getParameters())
310
+ parameters.add(parameter);
311
+ }
312
+ }
313
+ return parameters;
314
+ }
315
+ function chainOf(node, roots, depth) {
316
+ if (depth > MAX_CHAIN)
317
+ return undefined;
318
+ const next = (inner) => chainOf(inner, roots, depth + 1);
319
+ if (Node.isParenthesizedExpression(node) ||
320
+ Node.isAsExpression(node) ||
321
+ Node.isNonNullExpression(node)) {
322
+ return next(node.getExpression());
323
+ }
324
+ if (Node.isAwaitExpression(node)) {
325
+ const base = next(node.getExpression());
326
+ return base && [...base, { kind: 'await' }];
327
+ }
328
+ if (Node.isPropertyAccessExpression(node)) {
329
+ const base = next(node.getExpression());
330
+ return base && [...base, { kind: 'property', name: node.getName() }];
331
+ }
332
+ if (Node.isElementAccessExpression(node)) {
333
+ const name = stringLiteral(node.getArgumentExpression());
334
+ if (name === undefined)
335
+ return undefined;
336
+ const base = next(node.getExpression());
337
+ return base && [...base, { kind: 'property', name }];
338
+ }
339
+ if (Node.isCallExpression(node)) {
340
+ const callee = node.getExpression();
341
+ if (!Node.isPropertyAccessExpression(callee))
342
+ return undefined;
343
+ const args = node.getArguments().map((arg) => stringLiteral(arg));
344
+ if (args.some((arg) => arg === undefined))
345
+ return undefined;
346
+ const receiver = callee.getExpression();
347
+ const base = next(receiver);
348
+ return (base && [
349
+ ...base,
350
+ { kind: 'call', name: callee.getName(), args: args, call: node, receiver },
351
+ ]);
352
+ }
353
+ if (Node.isNewExpression(node)) {
354
+ const callee = node.getExpression();
355
+ const [input] = node.getArguments();
356
+ if (!Node.isIdentifier(callee) || callee.getText() !== 'URL' || !input)
357
+ return undefined;
358
+ if (!declaredByDefaultLib(callee.getSymbol()))
359
+ return undefined;
360
+ const base = next(input);
361
+ return base && [...base, { kind: 'url' }];
362
+ }
363
+ if (Node.isIdentifier(node)) {
364
+ const declaration = node.getSymbol()?.getDeclarations()[0];
365
+ return declaration ? bindingChain(declaration, roots, depth + 1) : undefined;
366
+ }
367
+ return undefined;
368
+ }
369
+ /** The chain a declaration binds: an incoming-message parameter, or a `const` read of one. */
370
+ function bindingChain(declaration, roots, depth) {
371
+ if (depth > MAX_CHAIN)
372
+ return undefined;
373
+ if (Node.isParameterDeclaration(declaration)) {
374
+ return roots.has(declaration) && !declaration.hasInitializer() ? [] : undefined;
375
+ }
376
+ if (Node.isVariableDeclaration(declaration)) {
377
+ if (!isConst(declaration))
378
+ return undefined;
379
+ const initializer = declaration.getInitializer();
380
+ return initializer ? chainOf(initializer, roots, depth + 1) : undefined;
381
+ }
382
+ if (Node.isBindingElement(declaration)) {
383
+ if (declaration.hasInitializer() || declaration.getDotDotDotToken())
384
+ return undefined;
385
+ const pattern = declaration.getParent();
386
+ if (!Node.isObjectBindingPattern(pattern))
387
+ return undefined;
388
+ const propertyNode = declaration.getPropertyNameNode();
389
+ let name;
390
+ if (!propertyNode) {
391
+ name = declaration.getName();
392
+ }
393
+ else if (Node.isIdentifier(propertyNode)) {
394
+ name = propertyNode.getText();
395
+ }
396
+ else if (Node.isStringLiteral(propertyNode)) {
397
+ name = propertyNode.getLiteralValue();
398
+ }
399
+ if (name === undefined)
400
+ return undefined;
401
+ const holder = pattern.getParent();
402
+ const base = holder ? bindingChain(holder, roots, depth + 1) : undefined;
403
+ return base && [...base, { kind: 'property', name }];
404
+ }
405
+ return undefined;
406
+ }
407
+ function isConst(declaration) {
408
+ const list = declaration.getParent();
409
+ return (!!list &&
410
+ Node.isVariableDeclarationList(list) &&
411
+ list.getDeclarationKind() === VariableDeclarationKind.Const);
412
+ }
413
+ /** The last property name or string argument of the chain. */
414
+ function fieldOf(links) {
415
+ for (let at = links.length - 1; at >= 0; at--) {
416
+ const link = links[at];
417
+ if (link.kind === 'property')
418
+ return link.name;
419
+ if (link.kind === 'call')
420
+ return link.args.length > 0 ? link.args[link.args.length - 1] : link.name;
421
+ }
422
+ return undefined;
423
+ }
424
+ function locationOf(links, isLibOrExternal) {
425
+ const last = links[links.length - 1];
426
+ if (last.kind === 'call' &&
427
+ last.name === 'get' &&
428
+ last.args.length === 1 &&
429
+ declaredByDefaultLib(last.receiver.getType().getSymbol(), 'URLSearchParams')) {
430
+ return 'query';
431
+ }
432
+ if (links.length >= 3 && last.kind === 'property') {
433
+ const awaited = links[links.length - 2];
434
+ const json = links[links.length - 3];
435
+ if (awaited.kind === 'await' &&
436
+ json.kind === 'call' &&
437
+ json.name === 'json' &&
438
+ json.args.length === 0 &&
439
+ Node.isCallExpression(json.call)) {
440
+ const callee = json.call.getExpression();
441
+ const symbol = Node.isPropertyAccessExpression(callee) ? callee.getNameNode().getSymbol() : undefined;
442
+ if (isLibOrExternal(symbol))
443
+ return 'body';
444
+ }
445
+ }
446
+ return 'unplaced';
447
+ }
448
+ /**
449
+ * A default lib file declares `symbol` (named `name`, when given). Another
450
+ * runtime's declarations may merge into the same global, so one is enough.
451
+ */
452
+ function declaredByDefaultLib(symbol, name) {
453
+ if (!symbol || (name !== undefined && symbol.getName() !== name))
454
+ return false;
455
+ return symbol.getDeclarations().some((declaration) => {
456
+ const file = declaration.getSourceFile();
457
+ return file.getProject().getProgram().compilerObject.isSourceFileDefaultLibrary(file.compilerNode);
458
+ });
459
+ }
@@ -273,6 +273,24 @@ export declare class TypeInferrer {
273
273
  * returns the repo's own type (`toInstance(View, plain)`) is the payload.
274
274
  */
275
275
  private callResultIsPayload;
276
+ /**
277
+ * True when a located call evaluates to plain data, so it is the payload
278
+ * whatever its arguments and wherever its type is declared (carrick#2055).
279
+ *
280
+ * `rows.map(toDto)`, `rows.filter(cb)` and `listItems(ownerId)` produce the
281
+ * list a route sends. Read as a send, the first produced the mapper's
282
+ * function type, the second a callback's return, and the third `string`.
283
+ * What a send or a registration hands back is never plain data: a reply
284
+ * builder or a router carries methods, a bare send returns `void`, and a
285
+ * serialiser returns a primitive. So the result alone tells them apart.
286
+ *
287
+ * Plain data, once promise levels are taken off: an object with at least
288
+ * one member, no call or construct signature, and no member that is itself
289
+ * callable; an array of one; or a union whose members other than `null` and
290
+ * `undefined` all are. Tuples and empty objects are left to the callers'
291
+ * own reading.
292
+ */
293
+ private callResultIsPlainData;
276
294
  /**
277
295
  * The wire representation a request's printed type takes. A route response
278
296
  * is serialised as JSON by every sender this layer reads a payload out of,
@@ -297,7 +315,8 @@ export declare class TypeInferrer {
297
315
  *
298
316
  * Returns `undefined` when this does not apply, which leaves the caller's
299
317
  * own reading of the located expression untouched:
300
- * - the row's line is not a route registration;
318
+ * - the row's line is neither a route registration nor a line inside the
319
+ * handler the located send sits in;
301
320
  * - the located expression is neither a send nor the argument of one;
302
321
  * - that send is not one the handler returns;
303
322
  * - it is a success send and the only one that survives, so the join would
@@ -422,14 +441,50 @@ export declare class TypeInferrer {
422
441
  * Where neither decides, the carrier keeps its own answer and the limit is
423
442
  * logged: a coin flip published as a contract is worse than an envelope a
424
443
  * reader can see is an envelope.
444
+ *
445
+ * carrick#1841: an argument that is a RESPONSE TABLE (`{ 200: Item }`, every
446
+ * key a status code) says which side it is by its keys, and that decides
447
+ * first: the one table whose keys are all 2xx is the success side, and what
448
+ * a caller receives is the table's body, the member the branch holds, never
449
+ * the table. A success table whose rows state no body (`{ 204: void }`)
450
+ * states no response contract. A carrier built from tables, or found
451
+ * through the call's arguments, that nothing decides states none either:
452
+ * the union is the client's bookkeeping, not a body, so it is not published
453
+ * as written.
425
454
  */
426
455
  private resultCarrierPayload;
456
+ /**
457
+ * The value of a response table's one success row, for a table a carrier
458
+ * holds as itself rather than through its body. `undefined` where the table
459
+ * has more than one success row: their union has no single type here.
460
+ */
461
+ private singleSuccessValue;
462
+ /**
463
+ * carrick#1841: the type arguments a call's result was built from, for a
464
+ * result that no longer carries them. A client whose result type is a
465
+ * conditional alias (`Result<TData, TError, ThrowOnError>`) resolves to its
466
+ * branch at the call, and the branch is an anonymous union with no
467
+ * arguments of its own. The arguments are still stated in two places the
468
+ * compiler keeps:
469
+ *
470
+ * - the call's own explicit type arguments (`client.get<Table, Errors>(…)`);
471
+ * - the alias the callee's declared return type was written with, where
472
+ * the callee is a function wrapping such a call
473
+ * (`getItem<ThrowOnError>(…)` returning `Result<Table, Errors, ThrowOnError>`).
474
+ *
475
+ * An argument read here names a payload only where a branch of the result
476
+ * holds it (`resultCarrierArguments`), so a type parameter of the callee or
477
+ * a style flag carries nothing.
478
+ */
479
+ private callResultArguments;
427
480
  /**
428
481
  * The shape test of `resultCarrierPayload`: the carrier `type` is, once a
429
482
  * promise-like around it is peeled, and the type arguments a branch of it
430
483
  * holds as a member. `undefined` when `type` is not a carrier.
431
484
  */
432
485
  private resultCarrierArguments;
486
+ /** The member names every branch holds, each with one and the same type. */
487
+ private membersCommonToEveryBranch;
433
488
  /**
434
489
  * The decided abstain of a call whose result carries transport the
435
490
  * service's wrapper rules verify and read no payload out of (carrick#1841,
@@ -869,6 +924,12 @@ export declare class TypeInferrer {
869
924
  * return statements are.
870
925
  */
871
926
  private recoverPayloadFromResponseExpressions;
927
+ /**
928
+ * The request field each member of a joined union answers
929
+ * (`response-modes.ts`). Case texts are joined by the published union's
930
+ * rule. A reading that fails is no reading: it must never cost the union.
931
+ */
932
+ private responseModesOf;
872
933
  /**
873
934
  * The expressions a function returns, excluding returns belonging to nested
874
935
  * functions (a `.map(x => …)` callback returns a row, not the response).
@@ -1025,6 +1086,16 @@ export declare class TypeInferrer {
1025
1086
  * No method or framework name is consulted anywhere.
1026
1087
  */
1027
1088
  private responseSiteStatus;
1089
+ /**
1090
+ * The status an earlier call of a send's own chain states: the receiver
1091
+ * calls `call` is invoked on (`a.status(401).json(body)` has `a.status(401)`),
1092
+ * innermost last. Each receiver call's first argument is read by the rule
1093
+ * an argument past a body is, so a numeric literal or a literal-typed value
1094
+ * counts and a plain `number` is `variable`. `undefined` when no receiver
1095
+ * call states a status. No method name is consulted: a receiver call that
1096
+ * is handed no status-shaped value says nothing.
1097
+ */
1098
+ private chainStatus;
1028
1099
  /**
1029
1100
  * The HTTP status codes an argument states, `'variable'` when it is a
1030
1101
  * status-shaped value the source does not fix, or `undefined` when it says
@@ -1502,6 +1573,27 @@ export declare class TypeInferrer {
1502
1573
  * whose type does not resolve the member, or an opaque config, abstains.
1503
1574
  */
1504
1575
  private requestBodyInCall;
1576
+ /** A string, a string literal, a template literal, or a union of strings. */
1577
+ private isStringLikeType;
1578
+ /**
1579
+ * carrick#1841, request half: a request call that states its URL on its one
1580
+ * object argument (`client.post({ url: '/items', ...options })`, the way a
1581
+ * generated client issues every operation). The method's own parameter
1582
+ * types the body `unknown`, so the object is the only statement of it: its
1583
+ * `body` member, the member `RequestInit` defines, written on the object or
1584
+ * carried in by a spread whose type says what it is.
1585
+ *
1586
+ * - A `body` written after the object's last spread is the body.
1587
+ * - Otherwise the object's own type has the answer. No `body` member, or
1588
+ * one typed `undefined` or `never`, means the call sends no body.
1589
+ * - A member typed `any`, `unknown` or a type parameter, or one only the
1590
+ * compiler's default library declares (a spread `RequestInit` carries the
1591
+ * serialised `BodyInit`, not a payload), states no payload: abstain. So
1592
+ * does an object that carries another body-named member and no `body`.
1593
+ *
1594
+ * An object with no string-typed `url` member is not a request object.
1595
+ */
1596
+ private requestObjectBody;
1505
1597
  /**
1506
1598
  * The body a config-slot argument supplies through `member`: an object
1507
1599
  * literal's own member, or a variable's member read off its type.