carrick 0.3.106 → 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.
Files changed (43) hide show
  1. package/dist/hook/refresh.js +8 -1
  2. package/dist/hook/refresh.js.map +1 -1
  3. package/package.json +6 -6
  4. package/plugin/.claude-plugin/plugin.json +1 -1
  5. package/sidecar/dist/src/capture/anchors.d.ts +15 -2
  6. package/sidecar/dist/src/capture/anchors.js +28 -11
  7. package/sidecar/dist/src/capture/api.d.ts +15 -0
  8. package/sidecar/dist/src/capture/check-classify.d.ts +9 -0
  9. package/sidecar/dist/src/capture/check-classify.js +20 -0
  10. package/sidecar/dist/src/capture/check-fields.d.ts +24 -0
  11. package/sidecar/dist/src/capture/check-fields.js +54 -36
  12. package/sidecar/dist/src/capture/check-poison.js +4 -14
  13. package/sidecar/dist/src/capture/check-union.d.ts +40 -0
  14. package/sidecar/dist/src/capture/check-union.js +92 -0
  15. package/sidecar/dist/src/capture/check.js +5 -0
  16. package/sidecar/dist/src/capture/index.js +138 -80
  17. package/sidecar/dist/src/capture/installed-package.d.ts +2 -0
  18. package/sidecar/dist/src/capture/installed-package.js +2 -1
  19. package/sidecar/dist/src/capture/outside-root.d.ts +19 -0
  20. package/sidecar/dist/src/capture/outside-root.js +39 -2
  21. package/sidecar/dist/src/capture/self-check.js +3 -13
  22. package/sidecar/dist/src/capture/specifiers.d.ts +14 -0
  23. package/sidecar/dist/src/capture/specifiers.js +25 -0
  24. package/sidecar/dist/src/definition-resolver.d.ts +5 -8
  25. package/sidecar/dist/src/definition-resolver.js +5 -7
  26. package/sidecar/dist/src/function-line-index.d.ts +30 -0
  27. package/sidecar/dist/src/function-line-index.js +162 -0
  28. package/sidecar/dist/src/index.d.ts +5 -0
  29. package/sidecar/dist/src/index.js +62 -9
  30. package/sidecar/dist/src/infer-timing.d.ts +54 -0
  31. package/sidecar/dist/src/infer-timing.js +124 -0
  32. package/sidecar/dist/src/line-index.d.ts +9 -0
  33. package/sidecar/dist/src/line-index.js +26 -0
  34. package/sidecar/dist/src/progress.d.ts +22 -0
  35. package/sidecar/dist/src/progress.js +31 -0
  36. package/sidecar/dist/src/retype.d.ts +4 -2
  37. package/sidecar/dist/src/retype.js +10 -23
  38. package/sidecar/dist/src/type-inferrer.d.ts +238 -12
  39. package/sidecar/dist/src/type-inferrer.js +772 -168
  40. package/sidecar/dist/src/types.d.ts +83 -6
  41. package/sidecar/dist/src/validators.d.ts +54 -16
  42. package/sidecar/dist/src/validators.js +4 -0
  43. package/templates/skills/carrick-reuse.md +6 -5
@@ -26,6 +26,7 @@
26
26
  */
27
27
  import { Node, SyntaxKind, ts } from 'ts-morph';
28
28
  import { readsResponseStatus, statusesIn, SUCCEEDED, testsOnPath, } from './failure-path.js';
29
+ import { lineIndex } from './line-index.js';
29
30
  import { fileDiagnostics } from './unwidened.js';
30
31
  /** Names appended to a file that is not ours carry this prefix. */
31
32
  const PREFIX = '__carrick_';
@@ -52,9 +53,11 @@ export class Retyper {
52
53
  * Judge every item, spending at most `budgetMs`. Each item rebuilds the
53
54
  * program at least twice, so a consumer with many calls could otherwise
54
55
  * outrun the caller's read deadline and lose every answer; the items the
55
- * 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).
56
59
  */
57
- run(items, budgetMs) {
60
+ run(items, budgetMs, onJudged) {
58
61
  const deadline = performance.now() + budgetMs;
59
62
  // What each file said before any rewrite. Every rewrite is undone, so it
60
63
  // is the same for every item in the file.
@@ -63,12 +66,15 @@ export class Retyper {
63
66
  if (performance.now() >= deadline) {
64
67
  return abstain(item, `the retype check ran out of its ${budgetMs}ms budget`);
65
68
  }
69
+ let outcome;
66
70
  try {
67
- return this.runOne(item, before);
71
+ outcome = this.runOne(item, before);
68
72
  }
69
73
  catch (err) {
70
- return abstain(item, `the retype check failed: ${err instanceof Error ? err.message : String(err)}`);
74
+ outcome = abstain(item, `the retype check failed: ${err instanceof Error ? err.message : String(err)}`);
71
75
  }
76
+ onJudged?.();
77
+ return outcome;
72
78
  });
73
79
  }
74
80
  runOne(item, before) {
@@ -842,22 +848,3 @@ function mapBack(pos, edits) {
842
848
  }
843
849
  return { kind: 'original', pos: pos - shift };
844
850
  }
845
- /** 1-based line of an original-file position. */
846
- function lineIndex(text) {
847
- const starts = [0];
848
- for (let i = 0; i < text.length; i++)
849
- if (text[i] === '\n')
850
- starts.push(i + 1);
851
- return (pos) => {
852
- let lo = 0;
853
- let hi = starts.length - 1;
854
- while (lo < hi) {
855
- const mid = (lo + hi + 1) >> 1;
856
- if (starts[mid] <= pos)
857
- lo = mid;
858
- else
859
- hi = mid - 1;
860
- }
861
- return lo + 1;
862
- };
863
- }
@@ -63,6 +63,11 @@ export interface TypeInferrerOptions {
63
63
  */
64
64
  unwidenedBudgetMs?: number;
65
65
  }
66
+ /**
67
+ * Called each time an `infer` batch is done with one of its requests,
68
+ * whatever that request answered: skipped, refused, failed or inferred.
69
+ */
70
+ export type InferRequestDone = () => void;
66
71
  /**
67
72
  * TypeInferrer - Extracts types from source code, both explicit and inferred
68
73
  *
@@ -81,6 +86,12 @@ export declare class TypeInferrer {
81
86
  */
82
87
  private readonly readNodes;
83
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;
84
95
  constructor(options: TypeInferrerOptions);
85
96
  /**
86
97
  * What the structural printer needs to tell the user's own declarations from
@@ -94,9 +105,10 @@ export declare class TypeInferrer {
94
105
  *
95
106
  * @param requests - Array of inference requests
96
107
  * @param extractionConfig - Agent-generated extraction config for payload unwrapping
108
+ * @param onRequestDone - Called once per request, as the batch is done with it
97
109
  * @returns InferResult with inferred types or errors
98
110
  */
99
- infer(requests: InferRequestItem[], extractionConfig?: ExtractionConfig): InferResult;
111
+ infer(requests: InferRequestItem[], extractionConfig?: ExtractionConfig, onRequestDone?: InferRequestDone): InferResult;
100
112
  /**
101
113
  * carrick#1836: list on `result` the declarations behind the names its text
102
114
  * prints that the request's file cannot resolve (`PrintedTypes.namesIn`).
@@ -187,6 +199,61 @@ export declare class TypeInferrer {
187
199
  */
188
200
  private resolveParamTarget;
189
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;
190
257
  /**
191
258
  * True when a located call's own result is the route's payload, so the
192
259
  * transitional drill into its first argument must not run (carrick#1732).
@@ -291,6 +358,14 @@ export declare class TypeInferrer {
291
358
  * else, and is not reported.
292
359
  */
293
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;
294
369
  /**
295
370
  * A stated type with `any` or `unknown` written anywhere in it (`unknown`,
296
371
  * `Record<string, unknown>`, `{ items: any[] }`) leaves a position open: it
@@ -350,12 +425,54 @@ export declare class TypeInferrer {
350
425
  */
351
426
  private resultCarrierPayload;
352
427
  /**
353
- * `Future<T>` -> `T` for a promise-like of the source's own making, read off
354
- * the await protocol rather than a name: a `then` whose first parameter is a
355
- * callback, whose own first parameter is the value awaiting it yields.
356
- * `Promise` and `PromiseLike` are peeled by `unwrapPromiseType` before this.
428
+ * The shape test of `resultCarrierPayload`: the carrier `type` is, once a
429
+ * promise-like around it is peeled, and the type arguments a branch of it
430
+ * holds as a member. `undefined` when `type` is not a carrier.
431
+ */
432
+ private resultCarrierArguments;
433
+ /**
434
+ * The decided abstain of a call whose result carries transport the
435
+ * service's wrapper rules verify and read no payload out of (carrick#1841,
436
+ * carrick#1843): `unknown` with `machinery_envelope` at the root and no
437
+ * anchor. The root reason is what keeps the capture's own locator from
438
+ * re-reading the raw call (`inference_decided_no_contract`,
439
+ * engine/type_compat_v2.rs).
440
+ */
441
+ private transportAbstain;
442
+ /**
443
+ * `Future<T>` -> `T` for a thenable, read off the await protocol rather
444
+ * than a name: a `then` whose first parameter is a callback, whose own
445
+ * first parameter is the value awaiting it yields. `Promise` and
446
+ * `PromiseLike` are peeled by `unwrapPromiseType` before this.
447
+ *
448
+ * The callback is read through `null` and `undefined` (carrick#1877). A
449
+ * `then` of the source's own making declares `(value: T) => void`; the
450
+ * platform's declares `onfulfilled?: ((value: T) => ...) | null`, and that
451
+ * is the `then` a subclass of `Promise` inherits. Read as written, an
452
+ * optional, nullable callback has no call signature, and the subclass was
453
+ * not seen as a thenable at all.
454
+ *
455
+ * A `then` that cannot be called, or whose first parameter is no callback,
456
+ * is no protocol, and the type is returned as it is.
457
+ *
458
+ * The compiler's own awaited type arbitrates. This walk reads the first
459
+ * signature of `then`; the language reads all of them. Where awaiting the
460
+ * type and awaiting what this walk found are not the same thing to the
461
+ * compiler (an overloaded `then` whose first signature is not the one
462
+ * `await` takes), the walk did not read the protocol, and the type is
463
+ * returned as it is rather than published as a guess.
357
464
  */
358
465
  private unwrapThenableType;
466
+ /** The value `then`'s first signature hands its callback, read until it stops changing. */
467
+ private firstThenValue;
468
+ /**
469
+ * What `await` yields for `type` where the names do not say: `type` is,
470
+ * once `Promise` and `PromiseLike` are peeled, a thenable by the protocol
471
+ * (a subclass of `Promise`, a class with a `then` of its own). `undefined`
472
+ * where the names say it all or `type` is no thenable, so a caller keeps
473
+ * the reading it had (carrick#1877).
474
+ */
475
+ private awaitedBeyondPromise;
359
476
  /**
360
477
  * The platform's error shape, in full: `name` and `message` strings AND a
361
478
  * `stack`, which is what the `Error` interface declares and every subclass
@@ -456,7 +573,17 @@ export declare class TypeInferrer {
456
573
  */
457
574
  private castsOfUnreadParameter;
458
575
  /**
459
- * The zero-argument whole-body read that takes `identifier` as its receiver,
576
+ * The zero-argument whole-body read taken in place on the value `callExpr`
577
+ * yields, or `undefined` (carrick#1851): `(await fetch(url)).text()`. The
578
+ * receiver is the call itself, through the wrappers that leave a value as
579
+ * it is (parentheses, `await`, `!`), so it is the same read
580
+ * `bodyReadOnReceiver` finds on a binding of that value. A call that is not
581
+ * awaited first is read the same way: a request that is a promise and reads
582
+ * its own body (`send(url).json()`) yields the body from that read too.
583
+ */
584
+ private bodyReadOnCallValue;
585
+ /**
586
+ * The zero-argument whole-body read that takes `receiver` as its receiver,
460
587
  * `res.json()` or `res.text()`, or `undefined`. A text read is a body read
461
588
  * like a json one (carrick#1842): without it, `return res.text()` left the
462
589
  * walk on the response binding and published the transport object.
@@ -518,12 +645,27 @@ export declare class TypeInferrer {
518
645
  * Try to unwrap a type using a single ExtractionRule.
519
646
  */
520
647
  private tryUnwrapWithRule;
648
+ /**
649
+ * The names `type` goes by, own symbol first (carrick#1843).
650
+ *
651
+ * `type Task<A> = __Task<A>` has the class's symbol and arguments, and the
652
+ * alias's beside them. `type Reply<T> = { ... }` has the anonymous `__type`
653
+ * with no arguments of its own. `type Outcome<A, E> = Done<A, E> |
654
+ * Failed<A, E>` has no symbol of its own at all. In each, the alias and its
655
+ * arguments are what a rule naming `Task`, `Reply` or `Outcome` describes.
656
+ */
657
+ private wrapperReadings;
521
658
  /**
522
659
  * Extract the payload type from a matched wrapper. Returns null when the
523
660
  * rule matched the wrapper but no payload is recoverable from generics or
524
661
  * property paths — the caller decides what a payload-less match means
525
662
  * (verified machinery collapses to `unknown` after every rule has run;
526
663
  * a name-only match leaves the type untouched).
664
+ *
665
+ * `payloadGenericIndex` counts the arguments of `reading`, the name the
666
+ * rule matched (carrick#1843): an alias is free to order its parameters
667
+ * differently from the type it stands for, so the same index into the
668
+ * other list is a different argument.
527
669
  */
528
670
  private extractPayloadFromWrapper;
529
671
  /**
@@ -819,9 +961,22 @@ export declare class TypeInferrer {
819
961
  *
820
962
  * Under `statedOnly` the annotation is the ONLY thing that counts, so an
821
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`.
822
967
  */
823
968
  private nodeCarriesPayloadContract;
824
- /** Object, array-of-object, or a union/intersection containing one. */
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
+ */
825
980
  private typeIsObjectShaped;
826
981
  /**
827
982
  * The `satisfies X` / `as X` / `<X>` annotation node on an expression, when
@@ -920,11 +1075,19 @@ export declare class TypeInferrer {
920
1075
  /**
921
1076
  * Declaration file (absolute path) of the anchor symbol
922
1077
  * `primaryTypeSymbol` reports for this type, or `undefined` when the type
923
- * has no user-facing anchor or no source declaration. The scanner's
924
- * pub/sub two-anchor arbitration (carrick#413) uses this to re-aim a
925
- * demoted explicit bundle request: the bundler resolves a `SymbolRequest`
926
- * only against declarations IN its `source_file`, so the request must
927
- * point at the file that actually declares the tsc-witnessed payload type.
1078
+ * has no user-facing anchor or no source declaration. Every path that
1079
+ * reports the symbol reports this beside it (carrick#1819): it is where a
1080
+ * reader finds the type an anchor names. The scanner's pub/sub two-anchor
1081
+ * arbitration (carrick#413) also uses it to re-aim a demoted explicit
1082
+ * bundle request: the bundler resolves a `SymbolRequest` only against
1083
+ * declarations IN its `source_file`, so the request must point at the file
1084
+ * that actually declares the tsc-witnessed payload type.
1085
+ *
1086
+ * The declarations read are those of the type's own symbol, so a name
1087
+ * imported through a barrel reports the file that declares it, and a name
1088
+ * two files declare reports the one this type resolves to. A declaration in
1089
+ * an installed package or a TypeScript lib is reported as it is found, as
1090
+ * an absolute path; what a reader is shown for it is the scanner's call.
928
1091
  *
929
1092
  * Only declaration kinds the bundler's `validateSymbols` can resolve
930
1093
  * (interface, type alias, class, enum, function, variable) count. A
@@ -1194,6 +1357,46 @@ export declare class TypeInferrer {
1194
1357
  * Returns null (not a spurious type) for a genuinely payload-less handler.
1195
1358
  */
1196
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;
1197
1400
  /**
1198
1401
  * Resolve a type-annotation/type-argument node to fully-structural text,
1199
1402
  * dropping any `Promise<…>` wrapper (`c.req.json<T>()` returns `Promise<T>`,
@@ -1337,8 +1540,31 @@ export declare class TypeInferrer {
1337
1540
  * request type resolves `body` to `unknown`/`any`, which the useless-type
1338
1541
  * guard rejects. So a handler that declares nothing yields null and the next
1339
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.
1340
1551
  */
1341
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;
1342
1568
  /**
1343
1569
  * Anchor (b2): the contract declared by a VALIDATOR MIDDLEWARE on the
1344
1570
  * registration (carrick#964).