carrick 0.3.127 → 0.3.129

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.
@@ -0,0 +1,547 @@
1
+ /**
2
+ * What a route handler's own parameters state about the route's bodies
3
+ * (carrick#2674), read through the members the service's verified
4
+ * server-framework labels name ({@link LabelledMembers}). A member is matched
5
+ * by its declaration, never by its name, so a type the repo declares itself
6
+ * with a member of the same name states nothing here.
7
+ *
8
+ * - The request: the member a receive op's `request.body` path ends on, read
9
+ * at the handler's parameter. A type the source fills is the body. A body
10
+ * the source leaves `never`, or leaves at the library's unfilled `unknown`
11
+ * while it writes a type argument for the route, is "no request body",
12
+ * provided nothing reads it (`bodyIsUnread`).
13
+ * - The reply: the payload a labelled `send` member's slot takes, read at the
14
+ * handler's parameter, where the library declares that payload through a
15
+ * type parameter of the reply type. A union of success and error bodies is
16
+ * cut to the variants a success-status send through the parameter carries.
17
+ */
18
+ import { Node, SyntaxKind, ts, } from 'ts-morph';
19
+ import { declarationKey } from './library-claims.js';
20
+ /** Type flags of a value that cannot hold a request body: a string, number, boolean and the like. */
21
+ const PLAIN_VALUE_FLAGS = ts.TypeFlags.StringLike |
22
+ ts.TypeFlags.NumberLike |
23
+ ts.TypeFlags.BigIntLike |
24
+ ts.TypeFlags.BooleanLike |
25
+ ts.TypeFlags.EnumLike |
26
+ ts.TypeFlags.Undefined |
27
+ ts.TypeFlags.Null |
28
+ ts.TypeFlags.Void;
29
+ /** The expression's type is a plain value, or a union of plain values. */
30
+ function holdsPlainValue(node) {
31
+ let type;
32
+ try {
33
+ type = node.getType();
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ const parts = type.isUnion() ? type.getUnionTypes() : [type];
39
+ return parts.every((part) => (part.getFlags() & PLAIN_VALUE_FLAGS) !== 0);
40
+ }
41
+ /** How many repo functions the request may be handed through before a read is unprovable. */
42
+ const REACH_DEPTH = 2;
43
+ export class StatedParams {
44
+ labels;
45
+ isRepoFile;
46
+ chainStatus;
47
+ constructor(labels,
48
+ /** True for a file the repo declares itself (not a library's or the runtime's). */
49
+ isRepoFile, chainStatus) {
50
+ this.labels = labels;
51
+ this.isRepoFile = isRepoFile;
52
+ this.chainStatus = chainStatus;
53
+ }
54
+ /** No label names a member: every reading answers nothing. */
55
+ get isEmpty() {
56
+ return this.labels.sends.size === 0 && this.labels.bodies.size === 0;
57
+ }
58
+ /** Whether some label names a reply's send member. */
59
+ get readsReplies() {
60
+ return this.labels.sends.size > 0;
61
+ }
62
+ // ===========================================================================
63
+ // Request
64
+ // ===========================================================================
65
+ /**
66
+ * The request body the handler's parameters state, the first parameter
67
+ * whose type has a labelled body member deciding. Undefined when no
68
+ * parameter has one, or the one that does states nothing decidable. A
69
+ * route whose `registration` hands in another function beside the handler
70
+ * (a middleware, which can read the body first) decides no "no body".
71
+ */
72
+ request(handler, registration) {
73
+ if (this.labels.bodies.size === 0)
74
+ return undefined;
75
+ const parameters = handler.getParameters();
76
+ for (const parameter of parameters) {
77
+ const member = this.labelledMember(parameter, (key) => this.labels.bodies.has(key));
78
+ if (!member)
79
+ continue;
80
+ let body;
81
+ try {
82
+ body = member.getTypeAtLocation(parameter);
83
+ }
84
+ catch {
85
+ return undefined;
86
+ }
87
+ if (body.isAny())
88
+ return undefined;
89
+ if (!body.isUnknown() && !body.isNever())
90
+ return { kind: 'body', type: body, at: parameter };
91
+ if (body.isUnknown() && !this.writesTypeArgument(handler, parameter))
92
+ return undefined;
93
+ if (registration && this.handsInAnotherFunction(registration, handler))
94
+ return undefined;
95
+ const declarations = new Set((member.getDeclarations() ?? []).map((d) => declarationKey(d.compilerNode)));
96
+ if (!this.bodyIsUnread(handler, parameters.indexOf(parameter), declarations, 0, new Map())) {
97
+ return undefined;
98
+ }
99
+ return {
100
+ kind: 'no_body',
101
+ at: parameter,
102
+ detail: body.isNever()
103
+ ? "the route's request type states its body as never, and nothing the handler hands the request to reads it"
104
+ : "the route's request type states its parts and no body, and nothing the handler hands the request to reads it",
105
+ };
106
+ }
107
+ return undefined;
108
+ }
109
+ /**
110
+ * The route hands in a function besides the handler: a callable argument
111
+ * (or list of them) beside the handler on any call it is passed through
112
+ * up to the registration, or, where the registration names the handler by
113
+ * reference, a second callable argument or property there. What such a
114
+ * function (a middleware) does with the request is not read here.
115
+ */
116
+ handsInAnotherFunction(registration, handler) {
117
+ const callable = (node) => {
118
+ let type;
119
+ try {
120
+ type = node.getType();
121
+ }
122
+ catch {
123
+ return true;
124
+ }
125
+ const element = type.getArrayElementType() ?? type.getTupleElements()[0];
126
+ return type.getCallSignatures().length > 0 || (element !== undefined && element.getCallSignatures().length > 0);
127
+ };
128
+ let node = handler;
129
+ let parent = node.getParent();
130
+ while (parent && Node.isCallExpression(parent) && parent.getArguments().includes(node)) {
131
+ const passed = node;
132
+ if (parent.getArguments().some((argument) => argument !== passed && callable(argument)))
133
+ return true;
134
+ node = parent;
135
+ parent = node.getParent();
136
+ }
137
+ const holdsHandler = registration === handler || handler.getAncestors().includes(registration);
138
+ if (holdsHandler) {
139
+ if (!Node.isObjectLiteralExpression(registration))
140
+ return false;
141
+ return registration
142
+ .getProperties()
143
+ .some((property) => !handler.getAncestors().includes(property) && callable(property));
144
+ }
145
+ const siblings = Node.isCallExpression(registration)
146
+ ? registration.getArguments()
147
+ : Node.isObjectLiteralExpression(registration)
148
+ ? registration.getProperties()
149
+ : [];
150
+ return siblings.filter(callable).length > 1;
151
+ }
152
+ /**
153
+ * A type argument the source writes for this route: on a call the handler
154
+ * is passed to (the registration, or a wrapper that makes the handler),
155
+ * through any calls enclosing that one as an argument, or on the
156
+ * parameter's own annotation.
157
+ */
158
+ writesTypeArgument(handler, parameter) {
159
+ const annotation = parameter.getTypeNode();
160
+ if (annotation && Node.isTypeReference(annotation) && annotation.getTypeArguments().length > 0) {
161
+ return true;
162
+ }
163
+ let node = handler;
164
+ let parent = node.getParent();
165
+ while (parent && Node.isCallExpression(parent) && parent.getArguments().includes(node)) {
166
+ if (parent.getTypeArguments().length > 0)
167
+ return true;
168
+ node = parent;
169
+ parent = node.getParent();
170
+ }
171
+ return false;
172
+ }
173
+ /**
174
+ * Nothing reads the labelled body member off parameter `index` of
175
+ * `func`, nor off the request in the repo functions it is handed to, up to
176
+ * `REACH_DEPTH` calls deep. Every use of the request must be a member
177
+ * read that is not the body and is not itself called (a framework's own
178
+ * body reader), or a hand-off to a function the repo declares, whole and
179
+ * by position. Anything else (a destructured parameter, a library
180
+ * function, an alias, a spread) is a read this cannot rule out.
181
+ */
182
+ bodyIsUnread(func, index, bodyKeys, depth, seen) {
183
+ const visited = seen.get(func) ?? new Set();
184
+ if (visited.has(index))
185
+ return true;
186
+ visited.add(index);
187
+ seen.set(func, visited);
188
+ const parameter = func.getParameters()[index];
189
+ if (!parameter)
190
+ return true;
191
+ if (parameter.isRestParameter())
192
+ return false;
193
+ const name = parameter.getNameNode();
194
+ if (!Node.isIdentifier(name))
195
+ return false;
196
+ const symbol = name.getSymbol();
197
+ const body = func.getBody();
198
+ if (!symbol || !body)
199
+ return false;
200
+ for (const use of body.getDescendantsOfKind(SyntaxKind.Identifier)) {
201
+ if (use.getSymbol() !== symbol)
202
+ continue;
203
+ const parent = use.getParent();
204
+ if (!parent)
205
+ return false;
206
+ if (Node.isPropertyAccessExpression(parent) && parent.getExpression() === use) {
207
+ const declarations = parent.getNameNode().getSymbol()?.getDeclarations() ?? [];
208
+ if (declarations.some((d) => bodyKeys.has(declarationKey(d.compilerNode))))
209
+ return false;
210
+ if (!this.memberReadStaysPut(parent))
211
+ return false;
212
+ continue;
213
+ }
214
+ if (Node.isCallExpression(parent) && parent.getArguments().includes(use)) {
215
+ const position = parent.getArguments().indexOf(use);
216
+ if (parent.getArguments().slice(0, position).some(Node.isSpreadElement))
217
+ return false;
218
+ if (depth >= REACH_DEPTH)
219
+ return false;
220
+ const callee = this.repoFunction(parent.getExpression());
221
+ if (!callee)
222
+ return false;
223
+ // A helper that takes the request under a type without the labelled
224
+ // body member could read the body under another declaration, which
225
+ // nothing here can match.
226
+ const taken = callee.getParameters()[position];
227
+ if (!taken || !this.labelledMember(taken, (key) => bodyKeys.has(key)))
228
+ return false;
229
+ if (!this.bodyIsUnread(callee, position, bodyKeys, depth + 1, seen))
230
+ return false;
231
+ continue;
232
+ }
233
+ return false;
234
+ }
235
+ return true;
236
+ }
237
+ /**
238
+ * A read of a request member (`req.headers`, `req.headers.host`) whose
239
+ * value goes nowhere a body could be read from. Along its member chain, a
240
+ * method called on anything but a plain value (`req.raw.pipe(...)`, or a
241
+ * framework's own body reader `req.file()`) is a read, and so is a
242
+ * non-plain value handed to a function (`getRawBody(req.raw)`). A plain
243
+ * value (a header string) holds no body, whatever is done with it.
244
+ */
245
+ memberReadStaysPut(read) {
246
+ let node = read;
247
+ for (;;) {
248
+ const outer = node.getParent();
249
+ if (!outer)
250
+ return true;
251
+ if ((Node.isCallExpression(outer) || Node.isNewExpression(outer)) && outer.getExpression() === node) {
252
+ const receiver = Node.isPropertyAccessExpression(node) || Node.isElementAccessExpression(node) ? node.getExpression() : undefined;
253
+ return receiver !== undefined && receiver !== read.getExpression() && holdsPlainValue(receiver);
254
+ }
255
+ if (Node.isCallExpression(outer) || Node.isNewExpression(outer)) {
256
+ return holdsPlainValue(node);
257
+ }
258
+ if ((Node.isPropertyAccessExpression(outer) || Node.isElementAccessExpression(outer)) &&
259
+ outer.getExpression() === node) {
260
+ node = outer;
261
+ continue;
262
+ }
263
+ if (Node.isNonNullExpression(outer) || Node.isParenthesizedExpression(outer)) {
264
+ node = outer;
265
+ continue;
266
+ }
267
+ return true;
268
+ }
269
+ }
270
+ /** The function a callee names, where the repo declares it; undefined otherwise. */
271
+ repoFunction(callee) {
272
+ let symbol = callee.getSymbol();
273
+ if (symbol?.isAlias())
274
+ symbol = symbol.getAliasedSymbol() ?? symbol;
275
+ for (const declaration of symbol?.getDeclarations() ?? []) {
276
+ if (!this.isRepoFile(declaration.getSourceFile()))
277
+ continue;
278
+ if (Node.isFunctionDeclaration(declaration) ||
279
+ Node.isMethodDeclaration(declaration) ||
280
+ Node.isArrowFunction(declaration) ||
281
+ Node.isFunctionExpression(declaration)) {
282
+ return declaration.getBody() ? declaration : undefined;
283
+ }
284
+ if (Node.isVariableDeclaration(declaration) || Node.isPropertyAssignment(declaration)) {
285
+ const initializer = declaration.getInitializer();
286
+ if (initializer && (Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer))) {
287
+ return initializer;
288
+ }
289
+ }
290
+ }
291
+ return undefined;
292
+ }
293
+ // ===========================================================================
294
+ // Reply
295
+ // ===========================================================================
296
+ /**
297
+ * The reply the handler's parameters state, cut to its success variants.
298
+ * Undefined when no parameter has a labelled send member that states a
299
+ * payload, when two such members disagree, when the payload is `any`,
300
+ * `unknown` or `never`, or when a union of bodies has no variant a
301
+ * success send through the parameter carries.
302
+ */
303
+ reply(handler) {
304
+ if (this.labels.sends.size === 0)
305
+ return undefined;
306
+ for (const parameter of handler.getParameters()) {
307
+ const sends = this.sendsThrough(handler, parameter);
308
+ const stated = this.statedPayload(parameter, new Set(sends.flatMap((send) => send.keys)));
309
+ if (stated === 'none')
310
+ continue;
311
+ if (stated === 'abstain')
312
+ return undefined;
313
+ if (stated.isAny() || stated.isUnknown() || stated.isNever())
314
+ return undefined;
315
+ const variants = stated.isUnion() ? stated.getUnionTypes().filter((t) => !t.isNull() && !t.isUndefined()) : [stated];
316
+ const literalUnion = stated.isBoolean() || variants.every((t) => t.isLiteral() || t.isBooleanLiteral());
317
+ if (variants.length <= 1 || literalUnion) {
318
+ // An optional payload slot adds `undefined`: no body sent, not a body.
319
+ const defined = stated.isUnion() ? stated.getUnionTypes().filter((t) => !t.isUndefined()) : [stated];
320
+ return defined.length > 0 ? { variants: defined, at: parameter } : undefined;
321
+ }
322
+ const carried = this.successPayloads(sends);
323
+ const kept = variants.filter((variant) => carried.some((sent) => sent.isAssignableTo(variant)));
324
+ return kept.length > 0 ? { variants: kept, at: parameter } : undefined;
325
+ }
326
+ return undefined;
327
+ }
328
+ /**
329
+ * The payload the labelled send members the handler calls through the
330
+ * parameter state: `none` when it calls none, `abstain` when they state
331
+ * different payloads, else the one payload type. A member the handler never
332
+ * calls (a reply object's status-code send beside its body send) says
333
+ * nothing about this route.
334
+ */
335
+ statedPayload(parameter, called) {
336
+ let type;
337
+ try {
338
+ type = parameter.getType();
339
+ }
340
+ catch {
341
+ return 'none';
342
+ }
343
+ let found = false;
344
+ let payload;
345
+ for (const property of type.getProperties()) {
346
+ const keys = property.getDeclarations().map((d) => declarationKey(d.compilerNode));
347
+ if (!keys.some((key) => called.has(key)))
348
+ continue;
349
+ const slot = this.sendSlotOf(property.getDeclarations().map((d) => d.compilerNode));
350
+ if (!slot)
351
+ continue;
352
+ found = true;
353
+ const through = property
354
+ .getDeclarations()
355
+ .map((d) => this.payloadTypeParameters(d.compilerNode, slot))
356
+ .find((named) => named.length > 0);
357
+ if (!through)
358
+ continue;
359
+ const checker = parameter.getProject().getTypeChecker().compilerObject;
360
+ const defaults = new Set(through.flatMap((named) => (named.default ? [checker.getTypeFromTypeNode(named.default)] : [])));
361
+ let member;
362
+ try {
363
+ member = property.getTypeAtLocation(parameter);
364
+ }
365
+ catch {
366
+ continue;
367
+ }
368
+ for (const signature of member.getCallSignatures()) {
369
+ // A payload the member's own type parameter types is whatever each
370
+ // call passes: the parameter states nothing.
371
+ if (signature.getTypeParameters().length > 0)
372
+ continue;
373
+ const at = this.payloadAt(signature.getParameters(), slot, parameter);
374
+ if (!at)
375
+ continue;
376
+ // The type parameter at its declared default: the route filled
377
+ // nothing there (a status-code send left at `number`).
378
+ if (defaults.has(at.compilerType))
379
+ continue;
380
+ if (payload === undefined)
381
+ payload = at;
382
+ else if (payload.compilerType !== at.compilerType)
383
+ return 'abstain';
384
+ }
385
+ }
386
+ if (!found)
387
+ return 'none';
388
+ return payload ?? 'none';
389
+ }
390
+ /** The payload slot a labelled send member takes, by any of its declarations. */
391
+ sendSlotOf(declarations) {
392
+ for (const declaration of declarations) {
393
+ const slot = this.labels.sends.get(declarationKey(declaration));
394
+ if (slot)
395
+ return slot;
396
+ }
397
+ return undefined;
398
+ }
399
+ /**
400
+ * The type parameters of the type that declares the member that its
401
+ * payload is declared through: a slot each route fills. None for a payload
402
+ * declared with a fixed type (a file path), which says nothing about any
403
+ * one route.
404
+ */
405
+ payloadTypeParameters(declaration, slot) {
406
+ const owner = declaration.parent;
407
+ const ownParameters = new Set(owner && (ts.isInterfaceDeclaration(owner) || ts.isClassDeclaration(owner) || ts.isClassExpression(owner))
408
+ ? (owner.typeParameters ?? [])
409
+ : []);
410
+ if (ownParameters.size === 0)
411
+ return [];
412
+ let typeNode;
413
+ if (ts.isMethodSignature(declaration) || ts.isMethodDeclaration(declaration)) {
414
+ typeNode = declaration.parameters[slot.arg]?.type;
415
+ }
416
+ else if (ts.isPropertySignature(declaration) || ts.isPropertyDeclaration(declaration)) {
417
+ typeNode = declaration.type;
418
+ }
419
+ if (!typeNode)
420
+ return [];
421
+ const mentioned = new Set();
422
+ const visit = (node) => {
423
+ if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName)) {
424
+ const name = node.typeName.text;
425
+ for (const parameter of ownParameters) {
426
+ if (ts.isTypeParameterDeclaration(parameter) && parameter.name.text === name)
427
+ mentioned.add(parameter);
428
+ }
429
+ }
430
+ ts.forEachChild(node, visit);
431
+ };
432
+ visit(typeNode);
433
+ return [...mentioned];
434
+ }
435
+ /** The type the call signature takes at the payload slot, read at `at`. */
436
+ payloadAt(parameters, slot, at) {
437
+ const symbol = parameters[slot.arg];
438
+ if (!symbol)
439
+ return undefined;
440
+ const declaration = symbol.getDeclarations()[0];
441
+ let type = symbol.getTypeAtLocation(at);
442
+ if (declaration && Node.isParameterDeclaration(declaration) && declaration.isRestParameter()) {
443
+ if (!type.isTuple())
444
+ return undefined;
445
+ const first = type.getTupleElements()[0];
446
+ if (!first)
447
+ return undefined;
448
+ type = first;
449
+ }
450
+ if (slot.key !== undefined) {
451
+ const property = type.getProperty(slot.key);
452
+ if (!property)
453
+ return undefined;
454
+ type = property.getTypeAtLocation(at);
455
+ }
456
+ const optional = declaration !== undefined && Node.isParameterDeclaration(declaration) && declaration.isOptional();
457
+ if (optional && type.isUnion()) {
458
+ const defined = type.getUnionTypes().filter((t) => !t.isUndefined());
459
+ if (defined.length === 1)
460
+ return defined[0];
461
+ }
462
+ return type;
463
+ }
464
+ /**
465
+ * The calls of a labelled send member the handler makes on `parameter`, or
466
+ * on a call chain rooted at it (`res.status(201).json(body)`), each with
467
+ * its payload slot and the keys of the member's declarations.
468
+ */
469
+ sendsThrough(handler, parameter) {
470
+ const name = parameter.getNameNode();
471
+ const symbol = Node.isIdentifier(name) ? name.getSymbol() : undefined;
472
+ const body = handler.getBody();
473
+ if (!symbol || !body)
474
+ return [];
475
+ const sends = [];
476
+ for (const call of body.getDescendantsOfKind(SyntaxKind.CallExpression)) {
477
+ const callee = call.getExpression();
478
+ if (!Node.isPropertyAccessExpression(callee))
479
+ continue;
480
+ const declarations = (callee.getNameNode().getSymbol()?.getDeclarations() ?? []).map((d) => d.compilerNode);
481
+ const slot = this.sendSlotOf(declarations);
482
+ if (!slot)
483
+ continue;
484
+ if (this.rootSymbol(callee.getExpression()) !== symbol)
485
+ continue;
486
+ sends.push({ call, slot, keys: declarations.map(declarationKey) });
487
+ }
488
+ return sends;
489
+ }
490
+ /**
491
+ * The payloads the sends carry whose chain states a success status or
492
+ * none. A value typed `any` or `unknown` is assignable to every variant, so
493
+ * it tells no success body from an error body and is left out.
494
+ */
495
+ successPayloads(sends) {
496
+ const sent = [];
497
+ for (const { call, slot } of sends) {
498
+ const status = this.chainStatus(call);
499
+ if (status !== undefined && status !== 'success')
500
+ continue;
501
+ let argument = call.getArguments()[slot.arg];
502
+ if (!argument || Node.isSpreadElement(argument))
503
+ continue;
504
+ if (slot.key !== undefined) {
505
+ if (!Node.isObjectLiteralExpression(argument))
506
+ continue;
507
+ const property = argument.getProperty(slot.key);
508
+ if (!property || !Node.isPropertyAssignment(property))
509
+ continue;
510
+ const initializer = property.getInitializer();
511
+ if (!initializer)
512
+ continue;
513
+ argument = initializer;
514
+ }
515
+ const type = argument.getType();
516
+ if (type.isAny() || type.isUnknown())
517
+ continue;
518
+ sent.push(type);
519
+ }
520
+ return sent;
521
+ }
522
+ /** The symbol at the root of a member or call chain (`res.status(201)` is `res`). */
523
+ rootSymbol(expression) {
524
+ let node = expression;
525
+ for (;;) {
526
+ if (Node.isPropertyAccessExpression(node) || Node.isCallExpression(node) || Node.isParenthesizedExpression(node) || Node.isNonNullExpression(node)) {
527
+ node = node.getExpression();
528
+ continue;
529
+ }
530
+ break;
531
+ }
532
+ return Node.isIdentifier(node) ? node.getSymbol() : undefined;
533
+ }
534
+ /** The member of the parameter's type whose declaration a label names. */
535
+ labelledMember(parameter, labelled) {
536
+ let type;
537
+ try {
538
+ type = parameter.getType();
539
+ }
540
+ catch {
541
+ return undefined;
542
+ }
543
+ return type
544
+ .getProperties()
545
+ .find((property) => property.getDeclarations().some((d) => labelled(declarationKey(d.compilerNode))));
546
+ }
547
+ }
@@ -19,6 +19,7 @@
19
19
  */
20
20
  import { Project, SourceFile, type CallExpression } from 'ts-morph';
21
21
  import type { InferRequestItem, InferResult, ModelSymbol, ExtractionConfig } from './types.js';
22
+ import type { LabelledMembers } from './library-claims.js';
22
23
  /**
23
24
  * Strongly-discriminating member names of HTTP transport machinery — the
24
25
  * fetch/DOM `Response` & `Request`, a Node `http.ServerResponse`, a framework
@@ -123,9 +124,15 @@ export declare class TypeInferrer {
123
124
  * @param onRequestDone - Called once per request, as the batch is done with it
124
125
  * @returns InferResult with inferred types or errors
125
126
  */
126
- infer(requests: InferRequestItem[], extractionConfig?: ExtractionConfig, onRequestDone?: InferRequestDone, modelSymbols?: ModelSymbol[]): InferResult;
127
+ infer(requests: InferRequestItem[], extractionConfig?: ExtractionConfig, onRequestDone?: InferRequestDone, modelSymbols?: ModelSymbol[], labels?: LabelledMembers): InferResult;
127
128
  /** The types the model named, by the alias of the row each is for (carrick#2111). */
128
129
  private modelSymbols;
130
+ /**
131
+ * carrick#2674: what route handlers' parameters state, through the
132
+ * members the batch's verified server-framework labels name. Empty when
133
+ * the batch carries no labels, and then it reads nothing.
134
+ */
135
+ private statedParams;
129
136
  /**
130
137
  * carrick#2111: where the model named a type for the response this request
131
138
  * reads, record on `inferred` how that type relates to the compiler's type
@@ -161,6 +168,10 @@ export declare class TypeInferrer {
161
168
  * really returns beside the published one (`unwidened_type_string`). See
162
169
  * `unwidened.ts` for what is marked and why the reading is sound.
163
170
  *
171
+ * carrick#2011: a request body (`read` is `'request_body'`) is read the same
172
+ * way, the call that sends it in place of the handler, except that a literal
173
+ * handed to any call other than the send is never marked.
174
+ *
164
175
  * One rewrite for the whole batch: every file on any request's path is
165
176
  * rewritten once, type-checked before and after, the requests re-inferred,
166
177
  * and every file restored before returning, so the project other requests
@@ -643,6 +654,72 @@ export declare class TypeInferrer {
643
654
  private resultCarrierArguments;
644
655
  /** The member names every branch holds, each with one and the same type. */
645
656
  private membersCommonToEveryBranch;
657
+ /**
658
+ * What the source narrows a call's value to, where the type stated at the
659
+ * walk's terminal is a placeholder that leaves a position open
660
+ * (carrick#2714, `as { data: Item[] } | { error: unknown }`): the one type
661
+ * stated for the value later in the function, on its success path
662
+ * (`return json as { data: Item[] }`). A closed statement at the terminal
663
+ * keeps the answer it had.
664
+ */
665
+ private narrowedBodyStatement;
666
+ /**
667
+ * The rule that verifies a call's result as a request library's response
668
+ * object, or undefined (carrick#2714). The result is that object when its
669
+ * awaited type is one a rule recognises. It is that object too when the
670
+ * call states no type arguments and its declared result, with its free
671
+ * type parameters at their defaults (`freeResultAtDefaults`), is a type a
672
+ * rule recognises (`get<T, R = Response<T>>(): Promise<R>`).
673
+ * The compiler then takes the parameter from where the value goes
674
+ * (`return client.post(…)` in a function declared `Promise<void>` types the
675
+ * call `Promise<void>`), and that is the caller's claim about the object,
676
+ * not what the call returns.
677
+ */
678
+ private transportRuleOfCall;
679
+ /**
680
+ * The first rule that matches `type` as a wrapper whose body is a member
681
+ * (`payloadPropertyPath`) the type has, whether or not it reads a payload
682
+ * out of it. A
683
+ * wrapper with no such member (the platform's `Response`) is read by its
684
+ * body reads (`res.json()`), which the def-use walk follows.
685
+ */
686
+ private ruleRecognising;
687
+ /**
688
+ * What a call's declared result is when its free type parameters take
689
+ * their defaults, or undefined. A parameter is free when the declaration's
690
+ * result mentions it and none of its parameters do, so only a type
691
+ * argument, the default or the call's return position can fix it. The
692
+ * result is read through `Promise`, a free parameter (its default), and a
693
+ * conditional alias whose check type is a free parameter tested against
694
+ * exactly that parameter's default (`R extends Default ? Response<T> : R`:
695
+ * the true branch). Anything else is the type as written.
696
+ */
697
+ private freeResultAtDefaults;
698
+ /**
699
+ * The call states the body itself: the rules read a payload out of its
700
+ * result (`client.get<Widget>(url)`). An untyped payload is no payload to
701
+ * the rules (they verify the object and read nothing), and a result its
702
+ * return position chose is no response object to them.
703
+ */
704
+ private callStatesTypedPayload;
705
+ /**
706
+ * The body of a call that returns a response object (carrick#2714): the
707
+ * one type the source states, in the function the call is made in, for the
708
+ * reads of the object's payload member (`res.data as Widget`, `const w:
709
+ * Widget = res.data`, `return res.data` from a function that declares its
710
+ * return type), through a binding or in place. Where no read states one,
711
+ * or two state different types, or the object is handed on whole, the
712
+ * site states no response contract, and the abstain is decided so the
713
+ * capture does not re-read the call and publish the object.
714
+ */
715
+ private transportCallBody;
716
+ /**
717
+ * The reads of a response object's payload member (`path`, e.g. `data`)
718
+ * that take it off the call's value: in place (`(await call).data`),
719
+ * through the name the value is bound to (`res.data`), or as a name
720
+ * destructured out of it (`const { data } = await call`).
721
+ */
722
+ private payloadMemberReads;
646
723
  /**
647
724
  * The decided abstain of a call whose result carries transport the
648
725
  * service's wrapper rules verify and read no payload out of (carrick#1841,
@@ -2090,6 +2167,17 @@ export declare class TypeInferrer {
2090
2167
  * own locator authoritative when a route declares nothing.
2091
2168
  */
2092
2169
  private declaredRequestContract;
2170
+ /** The stated-parameter reader over `labels`, judging files and statuses as this inferrer does. */
2171
+ private statedParamsFor;
2172
+ /**
2173
+ * carrick#2674: where today's reading of a route's response is empty
2174
+ * (nothing, `void`, or an `unknown` the inferrer did not decide), the reply
2175
+ * its handler's parameter states, cut to its success variants. A typed
2176
+ * reading, or a decided abstain, stands as it is.
2177
+ */
2178
+ private withStatedReply;
2179
+ /** Nothing read: `void`, or an `unknown` with no decided reason at its root. */
2180
+ private isEmptyResponseReading;
2093
2181
  /**
2094
2182
  * Anchor (a): the request contract declared on the handler's own signature.
2095
2183
  *