@abide/abide 0.104.0 → 0.105.1

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 (85) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/abide.ts +80 -173
  3. package/docs/REGISTRY.md +17 -17
  4. package/docs/RULEBOOK.md +50 -12
  5. package/docs/abide.md +9 -9
  6. package/package.json +1 -1
  7. package/src/compiler/Lowering.ts +15 -5
  8. package/src/compiler/Refusals.ts +57 -0
  9. package/src/compiler/TypeSession.ts +45 -86
  10. package/src/compiler/appLayout.ts +447 -0
  11. package/src/compiler/appMap.ts +21 -20
  12. package/src/compiler/checkApp.ts +61 -97
  13. package/src/compiler/checkedShadow.ts +11 -9
  14. package/src/compiler/classifyRead.ts +3 -6
  15. package/src/compiler/compileApp.ts +180 -512
  16. package/src/compiler/componentGraph.ts +2 -71
  17. package/src/compiler/declaredExports.ts +79 -60
  18. package/src/compiler/emitClient.ts +11 -13
  19. package/src/compiler/emitComponent.ts +21 -6
  20. package/src/compiler/emitSource.ts +20 -20
  21. package/src/compiler/handlersUnder.ts +46 -36
  22. package/src/compiler/hookCalls.ts +152 -0
  23. package/src/compiler/mountsOf.ts +31 -54
  24. package/src/compiler/readSources.ts +27 -42
  25. package/src/compiler/readsIn.ts +5 -16
  26. package/src/compiler/resolverIn.ts +77 -0
  27. package/src/server/abideModules.ts +92 -123
  28. package/src/server/answerRequest.ts +18 -4
  29. package/src/server/answerSocket.ts +136 -76
  30. package/src/server/answerValue.ts +28 -15
  31. package/src/server/appTree.ts +49 -71
  32. package/src/server/claudeApi.ts +2 -5
  33. package/src/server/clientBundle.ts +198 -0
  34. package/src/server/coerceArgs.ts +1 -1
  35. package/src/server/commands.ts +93 -57
  36. package/src/server/createApp.ts +90 -16
  37. package/src/server/deferredBuilds.ts +175 -0
  38. package/src/server/devWorker.ts +107 -319
  39. package/src/server/faceOf.ts +120 -0
  40. package/src/server/fenceServer.ts +10 -18
  41. package/src/server/index.ts +3 -0
  42. package/src/server/placement.ts +167 -0
  43. package/src/server/remote.ts +14 -42
  44. package/src/server/render.ts +25 -17
  45. package/src/server/renderComponent.ts +9 -5
  46. package/src/server/requestScope.ts +10 -4
  47. package/src/server/responseRelay.ts +389 -0
  48. package/src/server/runCompiled.ts +9 -12
  49. package/src/server/runDesktop.ts +6 -9
  50. package/src/server/serveBuild.ts +26 -73
  51. package/src/server/serveDev.ts +19 -176
  52. package/src/server/streamEvent.ts +29 -0
  53. package/src/server/withAbideSources.ts +2 -1
  54. package/src/shared/HandlerRoute.ts +68 -0
  55. package/src/shared/REFUSAL_KEY.ts +5 -0
  56. package/src/shared/answerOf.ts +105 -78
  57. package/src/shared/filledPath.ts +1 -9
  58. package/src/shared/framing.ts +127 -128
  59. package/src/shared/queryText.ts +15 -7
  60. package/src/shared/reactive/Cursor.ts +4 -2
  61. package/src/shared/reactive/Reactive.ts +285 -197
  62. package/src/shared/reactive/index.ts +11 -8
  63. package/src/shared/reactive/pairReactive.ts +1 -1
  64. package/src/shared/reactive/scoped.ts +4 -3
  65. package/src/shared/reactive/share.ts +11 -1
  66. package/src/shared/routePattern.ts +0 -123
  67. package/src/shared/rpc.ts +17 -24
  68. package/src/shared/rpcRequest.ts +18 -47
  69. package/src/shared/takeEarlyCall.ts +12 -37
  70. package/src/shared/url.ts +1 -1
  71. package/src/ui/Level.ts +276 -0
  72. package/src/ui/List.ts +17 -17
  73. package/src/ui/Router.ts +20 -87
  74. package/src/ui/applyHead.ts +92 -79
  75. package/src/ui/index.ts +4 -2
  76. package/src/ui/leave.ts +5 -0
  77. package/src/ui/reconcile.ts +23 -104
  78. package/src/ui/regions.ts +27 -2
  79. package/src/ui/rows.ts +123 -2
  80. package/src/ui/wire.ts +12 -27
  81. package/src/compiler/hooksCalled.ts +0 -35
  82. package/src/server/clientBuildOptions.ts +0 -54
  83. package/src/server/refuseAddressCollision.ts +0 -37
  84. package/src/shared/reactive/chunksOfStream.ts +0 -33
  85. package/src/ui/MOUNTING_LEVEL.ts +0 -18
@@ -33,7 +33,6 @@ import {
33
33
  import { outsideScope, scoped } from './scoped.ts'
34
34
  import { share } from './share.ts'
35
35
 
36
- export { chunksOfStream } from './chunksOfStream.ts'
37
36
  export { isReactive } from './isReactive.ts'
38
37
  export { isKeyed, type Keyed } from './Keyed.ts'
39
38
  export { NotLanded } from './NotLanded.ts'
@@ -42,6 +41,7 @@ export { NotLanded } from './NotLanded.ts'
42
41
  // first that imports the core directly meets `MEMBER_KEYS` before it is initialized.
43
42
  export {
44
43
  abandonLoad,
44
+ chunksOfStream,
45
45
  deadlineOf,
46
46
  failedTransiently,
47
47
  flushSync,
@@ -368,14 +368,17 @@ export function reactive(
368
368
  ): unknown {
369
369
  const keyed = isKeyedBody(given[0])
370
370
  // 11.32 — ON A SERVER, OUTSIDE A REQUEST, THE VALUE IS FILED: a forwarder to one instance per
371
- // request, and to the one built here where there is no request at all. `global` is what keeps
372
- // a value process-wide (11.34).
373
- if (
374
- outsideScope(
375
- (given[1] as { global?: boolean } | undefined)?.global === true,
371
+ // request, and to the one built here where there is no request at all — or, for a value with a
372
+ // `store`, built where it is first reached (11.71). `global` is what keeps a value process-wide
373
+ // (11.34).
374
+ const options = given[1] as
375
+ | { global?: boolean; store?: unknown }
376
+ | undefined
377
+ if (outsideScope(options?.global === true))
378
+ return scoped(
379
+ () => build(given, keyed),
380
+ !keyed && options?.store !== undefined,
376
381
  )
377
- )
378
- return scoped(() => build(given, keyed))
379
382
  return build(given, keyed)
380
383
  }
381
384
 
@@ -4,7 +4,7 @@
4
4
  // the pair's, and nothing stands here — the value moves when what `get` reads does, as an element's
5
5
  // `bind:value={{get, set}}` does.
6
6
  //
7
- // A PROTOTYPE, NOT A BRANCH in the core's `set`, for `brandedPrototype`'s reason: `simpleShape`
7
+ // A PROTOTYPE, NOT A BRANCH in the core's `set`, for `brandedPrototype`'s reason: `seat`
8
8
  // leaves a seat it does not own alone, and the adoption path reaches this `set` through the
9
9
  // callable it adopted.
10
10
 
@@ -24,9 +24,10 @@ const UNBUILT: unique symbol = Symbol('abide.unbuilt')
24
24
  /**
25
25
  * What a forwarder resolves through: the home, and how to build the same value again in a scope.
26
26
  *
27
- * 16.64 — A HANDLER'S HOME IS BUILT WHERE IT IS FIRST REACHED, and every other value's where it is
28
- * declared. Building a value with a body starts the body (1.7), and at import that ran the handler
29
- * — a mutation fired at boot, for a request nobody had made.
27
+ * 11.71 AND 16.64 — A HANDLER'S HOME, AND AN UNKEYED VALUE'S WITH A `store`, IS BUILT WHERE IT IS
28
+ * FIRST REACHED, and every other value's where it is declared. Building a value starts its body
29
+ * (1.7) and reads its store (8.14), and at import that ran a handler for a request nobody had made,
30
+ * and a store reading `cookies()` where there is no request.
30
31
  *
31
32
  * A `global` handler (11.34) is filed only to be built late: every request reaches the home, built
32
33
  * outside each of them, so it is the one value its option says it is.
@@ -16,6 +16,12 @@ import type { Reactive } from './Reactive.ts'
16
16
  export type ShareScope = {
17
17
  parent: ShareScope | null
18
18
  values: Map<string, unknown>
19
+ /**
20
+ * 39.1 — the routed level the instance stands in, opaque here (`Level`). Carried down the chain
21
+ * so a component mounted after its level — an arm opening, a row grown — finds the level its
22
+ * `<head>` joins through the scope it resumes, as it finds a shared key.
23
+ */
24
+ level: unknown
19
25
  }
20
26
 
21
27
  // The instance being rendered or mounted, or `null` outside one. A dynamic scope like
@@ -62,7 +68,11 @@ export function share<Key extends string, Value>(
62
68
  */
63
69
  export function enterShare(): ShareScope | null {
64
70
  const previous = CURRENT
65
- CURRENT = { parent: previous, values: new Map() }
71
+ CURRENT = {
72
+ parent: previous,
73
+ values: new Map(),
74
+ level: previous === null ? undefined : previous.level,
75
+ }
66
76
  return previous
67
77
  }
68
78
 
@@ -10,13 +10,6 @@
10
10
  // A LEAF: nothing imported but another leaf, because the compiler, the client router and a server
11
11
  // router all read it and a constant crossing a seam lives where nothing pulls a graph in behind it.
12
12
 
13
- import { LAUNCHER_PAGE } from './LAUNCHER_FILES.ts'
14
- import {
15
- HOLDS_SERVER,
16
- RESERVED_DIRECTORIES,
17
- SERVER_DIRECTORY,
18
- } from './RESERVED_DIRECTORIES.ts'
19
-
20
13
  type SegmentKind = 'static' | 'required' | 'optional' | 'rest'
21
14
 
22
15
  /** One `/`-separated piece of a route literal, classified by kind. */
@@ -47,122 +40,6 @@ export function parseRoutePattern(pattern: string): Segment[] {
47
40
  return out
48
41
  }
49
42
 
50
- /** Whether a source is a page, a layout, an error page or a component. */
51
- export type FileKind = 'page' | 'layout' | 'error' | 'component'
52
-
53
- /**
54
- * Whether the `.abide` at `path` is a page, a layout, an error page or a component.
55
- *
56
- * 23.21 — WHERE IT SITS, THEN ITS NAME. Every `.abide` directly in an address directory is a page
57
- * but a `layout.abide`, an `error.abide` and the root's `launcher.abide`; one under a fence is a
58
- * component whatever it is called.
59
- * One test for every reader: the build's routes, the checker's `Routes` and the `route.params` a
60
- * page is typed by.
61
- *
62
- * `path` is RELATIVE TO THE APP'S ROOT: its directories are its route, so an absolute path would
63
- * read the machine's directories as the app's.
64
- */
65
- export function fileKind(path: string): FileKind {
66
- if (placeOf(path) !== 'address' || path === LAUNCHER_PAGE)
67
- return 'component'
68
- const name = path.slice(path.lastIndexOf('/') + 1)
69
- if (name === 'layout.abide') return 'layout'
70
- if (name === 'error.abide') return 'error'
71
- return 'page'
72
- }
73
-
74
- /** Where a file sits in an app, which is what it is to every reader (`placeOf`). */
75
- export type Place =
76
- | 'address'
77
- | 'server'
78
- | 'public'
79
- | 'lib'
80
- | 'tests'
81
- | 'dependency'
82
- | 'skipped'
83
-
84
- /**
85
- * Where the file at `path`, relative to the app's root, sits — THE ONE ANSWER every walk, fence and
86
- * emit reads, so no two of them disagree about a path.
87
- *
88
- * - `dependency` — 34.24: a package's, outside the root or under `node_modules`, whose directories
89
- * are its package's layout and none of the app's;
90
- * - `skipped` — under a dot directory, which no walk of the app enters;
91
- * - `server`, `public`, `lib` or `tests` — 23.15: under that fence, the nearest to the root deciding,
92
- * save a `server` under a `lib` or a `tests`, which is `server` — the fence the browser's bundle
93
- * is checked against (16.29) is never hidden by one above it (23.16);
94
- * - `address` — 23.14: everywhere else, its directories the segments of its route.
95
- */
96
- export function placeOf(path: string): Place {
97
- if (path.startsWith('../') || path.startsWith('/')) return 'dependency'
98
- const parts = path.split('/')
99
- // A `lib` or a `tests` met first, which a `server` below it still overrides.
100
- let fence: Place | undefined
101
- // The last part is the file itself, which is never a directory.
102
- for (let index = 0; index < parts.length - 1; index += 1) {
103
- const part = parts[index] as string
104
- if (skipped(part))
105
- return (
106
- fence ?? (part === 'node_modules' ? 'dependency' : 'skipped')
107
- )
108
- if (fence !== undefined) {
109
- if (part === SERVER_DIRECTORY) return 'server'
110
- } else if (RESERVED_DIRECTORIES.has(part)) {
111
- if (!HOLDS_SERVER.has(part)) return part as Place
112
- fence = part as Place
113
- }
114
- }
115
- return fence ?? 'address'
116
- }
117
-
118
- /** Whether a walk of the app passes over the entry `name`: a dot file or directory, or `node_modules`. */
119
- export function skipped(name: string): boolean {
120
- return name.startsWith('.') || name === 'node_modules'
121
- }
122
-
123
- /**
124
- * The route a file answers at — its path below the root without its extension.
125
- *
126
- * 23.14 — `media/[id].abide` and `media/getGrid.ts` are `/media/[id]` and `/media/getGrid`; an
127
- * `index.abide`, a `layout.abide` and an `error.abide` are their directory's. `path` is relative to
128
- * the app's root, as `fileKind`'s is.
129
- */
130
- export function routeOf(path: string): string {
131
- const parts = path.split('/')
132
- const file = parts.pop() as string
133
- // The extension alone: `[...rest].abide` carries dots of its own.
134
- if (!DIRECTORY_FILES.has(file)) parts.push(file.replace(EXTENSION, ''))
135
- return `/${parts.filter((part) => part !== '').join('/')}`
136
- }
137
-
138
- const EXTENSION = /\.(?:abide|ts)$/
139
-
140
- /** 23.14, 23.18 — the names whose route is their directory's, and so a route's wherever they stand. */
141
- export const DIRECTORY_FILES = new Set([
142
- 'index.abide',
143
- 'layout.abide',
144
- 'error.abide',
145
- ])
146
-
147
- /**
148
- * The route the hooks and private handlers of the file at `path` are filed at.
149
- *
150
- * 37.1 — an address's own route; under a `lib` or a `tests` directory, the route of the directory
151
- * holding it, so a component an admin page renders runs under the admin route's rungs; under a
152
- * `server` or a `public` directory, and for a dependency's file, the root's.
153
- */
154
- export function scopeOf(path: string): string {
155
- // 38.82 — the launcher answers at no route, and files what it calls at the root's.
156
- if (path === LAUNCHER_PAGE) return '/'
157
- const place = placeOf(path)
158
- if (place === 'address') return routeOf(path)
159
- if (place !== 'lib' && place !== 'tests') return '/'
160
- const parts = path.split('/')
161
- // The fence `placeOf` named is the first one, and its name is its place.
162
- const fence = parts.indexOf(place)
163
- return `/${parts.slice(0, fence).join('/')}`
164
- }
165
-
166
43
  /**
167
44
  * A `sort` comparator over two parsed patterns: negative where `a` is the more specific.
168
45
  *
package/src/shared/rpc.ts CHANGED
@@ -14,8 +14,8 @@
14
14
  // handler's own.
15
15
 
16
16
  import { type Failed, isFailedNamed } from './Failed.ts'
17
- import { filledSegments } from './filledPath.ts'
18
17
  import { GLOBAL_GETS } from './GLOBAL_GETS.ts'
18
+ import { HandlerRoute } from './HandlerRoute.ts'
19
19
  import { compose, type Middleware } from './Middleware.ts'
20
20
  import { principalRotated } from './principalRotated.ts'
21
21
  import { RPC_SERVER } from './RPC_SERVER.ts'
@@ -49,8 +49,7 @@ import { MUTATION } from './reactive/MUTATION.ts'
49
49
  import { SELECTION_MEMBERS } from './reactive/SELECTION_MEMBERS.ts'
50
50
  import { isForwarder } from './reactive/scoped.ts'
51
51
  import { checkArgs } from './refuse.ts'
52
- import { parseRoutePattern, type Segment } from './routePattern.ts'
53
- import { pathRefusal, rpcRequest } from './rpcRequest.ts'
52
+ import { rpcRequest } from './rpcRequest.ts'
54
53
  import type { Schema } from './Schema.ts'
55
54
 
56
55
  export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
@@ -374,28 +373,22 @@ export function declare(method: Method): Declare {
374
373
  //
375
374
  // 15.8 — AND REFUSED AHEAD OF THE HANDLER where the args leave a parameter of its route no
376
375
  // spelling (30.3, 30.19), as the wire refuses them: a render and the browser's call then
377
- // reach the same refusal, and there is no request for the render to report. The route is
378
- // parsed once per address, which is stamped after the declaration (16.10).
379
- let routed: string | undefined
380
- let routeSegments: Segment[] | null = null
376
+ // reach the same refusal, and there is no request for the render to report.
377
+ //
378
+ // THE ROUTE IS COMPILED ONCE PER ADDRESS, which is stamped after the declaration (16.10),
379
+ // and every call, `rpc.url` and `rpc.raw` read it.
380
+ let route: HandlerRoute | undefined
381
+ const routeAt = (address: string): HandlerRoute => {
382
+ if (route?.address !== address) route = new HandlerRoute(address)
383
+ return route
384
+ }
381
385
  const call = (given?: unknown, options?: CallOptions): unknown => {
382
386
  const args = normalized(contract, given)
383
- const address = rpc.address
384
- if (address !== routed) {
385
- routed = address
386
- routeSegments =
387
- address?.includes('[') === true
388
- ? parseRoutePattern(address)
389
- : null
390
- }
391
- if (routeSegments !== null) {
387
+ const route =
388
+ rpc.address === undefined ? undefined : routeAt(rpc.address)
389
+ if (route?.segments !== undefined) {
392
390
  try {
393
- filledSegments(
394
- routeSegments,
395
- (args ?? {}) as Record<string, unknown>,
396
- routed as string,
397
- pathRefusal,
398
- )
391
+ route.path(args as Record<string, unknown> | undefined)
399
392
  } catch (refused) {
400
393
  return reactive(() => refused)
401
394
  }
@@ -428,13 +421,13 @@ export function declare(method: Method): Declare {
428
421
  guarded: rungs.length > 0,
429
422
  isError: isFailedNamed,
430
423
  url(args?: Record<string, unknown>): string {
431
- return rpcRequest(method, addressOf(rpc), args).url
424
+ return rpcRequest(method, routeAt(addressOf(rpc)), args).url
432
425
  },
433
426
  raw(
434
427
  args?: Record<string, unknown>,
435
428
  init?: RequestInit,
436
429
  ): Promise<Response> {
437
- const asked = rpcRequest(method, addressOf(rpc), args)
430
+ const asked = rpcRequest(method, routeAt(addressOf(rpc)), args)
438
431
  // `Headers`, not a spread — the caller's may be a `Headers` itself, which spreads empty.
439
432
  const headers = new Headers(asked.init.headers)
440
433
  for (const [name, value] of new Headers(init?.headers))
@@ -3,13 +3,13 @@
3
3
  // to the byte, or the browser's call misses the request that was already started for it. `key` is
4
4
  // that agreement: method, URL and body, which is what makes two requests the same one.
5
5
  //
6
- // A `GET` or `DELETE` carries its args in the URL, in `queryText`'s spelling, and a mutation in its
7
- // body; 19.7 is where text becomes the declared type again. 16.9 — whatever the method, a parameter
8
- // of the handler's route rides in the path, and the server puts it back among the args (16.65).
6
+ // A `GET` or `DELETE` carries its args in the URL, in `queryText`'s spelling (30.28), and a mutation
7
+ // in its body; 19.7 is where text becomes the declared type again. 16.9 — whatever the method, a
8
+ // parameter of the handler's route rides in the path, and the server puts it back among the args
9
+ // (16.65).
9
10
 
10
- import { filledPath, parametersOf } from './filledPath.ts'
11
+ import { HandlerRoute } from './HandlerRoute.ts'
11
12
  import { queryText } from './queryText.ts'
12
- import { validationError } from './refuse.ts'
13
13
  import type { Method } from './rpc.ts'
14
14
 
15
15
  export type RpcRequest = {
@@ -31,44 +31,25 @@ export function argsInURL(method: Method): boolean {
31
31
  return method === 'GET' || method === 'DELETE'
32
32
  }
33
33
 
34
- /** The request a call is: its URL, body, `fetch` init and the `key` both ends match it by. */
34
+ /**
35
+ * The request a call is: its URL, body, `fetch` init and the `key` both ends match it by.
36
+ *
37
+ * `route` is the handler's, compiled where the caller holds one; an address is compiled here.
38
+ */
35
39
  export function rpcRequest(
36
40
  method: Method,
37
- address: string,
41
+ route: HandlerRoute | string,
38
42
  given: Record<string, unknown> | undefined,
39
43
  ): RpcRequest {
40
- let args = given
41
- let url = address
44
+ const { path, rest } = (
45
+ typeof route === 'string' ? new HandlerRoute(route) : route
46
+ ).split(given)
47
+ let url = path
42
48
  let body: string | undefined
43
- // A route with parameters: they are written into the path and leave the args, so the query or
44
- // body never carries one twice (16.66). 15.8 — one no address carries (30.3, 30.19) is refused as
45
- // args a gate refused, so every caller answers it as the wire answers a 422.
46
- if (address.includes('[')) {
47
- const params = given ?? {}
48
- url = filledPath(address, params, address, pathRefusal)
49
- const inPath = parametersOf(address)
50
- let rest: Record<string, unknown> | undefined
51
- for (const name in params) {
52
- if (inPath.has(name)) continue
53
- rest ??= {}
54
- rest[name] = params[name]
55
- }
56
- args = rest
57
- }
58
49
  if (argsInURL(method)) {
59
- if (args !== undefined) {
60
- let text = queryText(args)
61
- // AN EMPTY LIST IS ITS NAME WITH NO `=`, which no text value spells — `tags=` is the
62
- // one-member list `['']` — so 19.7 reads it back as `[]` rather than losing the member.
63
- for (const name in args) {
64
- const value = args[name]
65
- if (!Array.isArray(value) || value.length !== 0) continue
66
- const bare = bareName(name)
67
- text = text === '' ? bare : `${text}&${bare}`
68
- }
69
- if (text !== '') url = `${url}?${text}`
70
- }
71
- } else if (args !== undefined) body = JSON.stringify(args)
50
+ const text = rest === undefined ? '' : queryText(rest)
51
+ if (text !== '') url = `${url}?${text}`
52
+ } else if (rest !== undefined) body = JSON.stringify(rest)
72
53
  // 18.14 — A COPY IS ITS OWN CACHE and keeps its own `max-age`. What it asks the server is asked
73
54
  // because it went stale or was invalidated, and the browser's HTTP cache, which hears neither,
74
55
  // would answer it with the value it was dropping. `rpc.raw` is handed the same, under whatever
@@ -85,13 +66,3 @@ export function rpcRequest(
85
66
  }
86
67
  return { method, url, body, init, key: `${method} ${url} ${body ?? ''}` }
87
68
  }
88
-
89
- /** 15.8 — a parameter no address carries, refused as args a gate refused. */
90
- export function pathRefusal(name: string, message: string): unknown {
91
- return validationError<Record<string, unknown>>({ [name]: [message] })
92
- }
93
-
94
- /** A query member's name in the encoding `URLSearchParams` gives it, without its `=`. */
95
- export function bareName(name: string): string {
96
- return new URLSearchParams([[name, '']]).toString().slice(0, -1)
97
- }
@@ -5,44 +5,19 @@
5
5
  import { EARLY_CALLS } from './EARLY_CALLS.ts'
6
6
 
7
7
  /**
8
- * What a call answered: the status and the text — which stays unset where the request itself did
9
- * not complete.
8
+ * What the early-call script leaves per call: the request in flight, and once it has answered, the
9
+ * `Response`. The script decides nothing about it — `answerOf` reads it as it reads any answer.
10
10
  */
11
- export type Answer = {
12
- status: number | undefined
13
- statusText: string | undefined
14
- type: string | null | undefined
15
- /**
16
- * The answer's version, or `null` where it named none (18.5).
17
- *
18
- * A `null` is a value built per request.
19
- */
20
- version: string | null | undefined
21
- /** The answer's `cache-control`, whose `max-age` is how long the copy may serve it (21.8). */
22
- life: string | null | undefined
23
- text: string | undefined
24
- /**
25
- * A binary answer's body read to the end (16.34), set in place of `text` — a body read as text
26
- * is not the bytes it carried.
27
- */
28
- bytes: Uint8Array | undefined
29
- /**
30
- * A streamed answer's body, left unread so its chunks are taken as they land (16.32).
31
- *
32
- * Set in place of `text` for a handler the build marked as streaming, and by the early-call
33
- * script for any framed answer.
34
- */
35
- body: ReadableStream<Uint8Array> | null | undefined
36
- }
37
-
38
- /**
39
- * What the early-call script leaves per call: the request in flight, and once it has answered, what
40
- * it answered.
41
- */
42
- export type EarlyCall = Answer & {
43
- promise: Promise<void>
11
+ export type EarlyCall = {
12
+ promise: Promise<unknown>
44
13
  /** `1` where the server built the page's markup from this call settled (41.25). */
45
14
  painted: 1 | undefined
15
+ /**
16
+ * Set once the request has answered — and, where `painted`, once its body is read to the end into
17
+ * `bytes`, so hydration's wait covers the body too. Unset where the request failed.
18
+ */
19
+ response: Response | undefined
20
+ bytes: ArrayBuffer | undefined
46
21
  }
47
22
 
48
23
  function startedCalls(): Record<string, EarlyCall | undefined> | undefined {
@@ -75,10 +50,10 @@ export function takeEarlyCall(key: string): EarlyCall | undefined {
75
50
  export function paintedCallsLanded(): Promise<unknown> | undefined {
76
51
  const started = startedCalls()
77
52
  if (started === undefined) return undefined
78
- let waiting: Promise<void>[] | undefined
53
+ let waiting: Promise<unknown>[] | undefined
79
54
  for (const key in started) {
80
55
  const early = started[key]
81
- if (early?.painted !== 1 || early.text !== undefined) continue
56
+ if (early?.painted !== 1 || early.response !== undefined) continue
82
57
  if (waiting === undefined) waiting = []
83
58
  waiting.push(early.promise)
84
59
  }
package/src/shared/url.ts CHANGED
@@ -89,7 +89,7 @@ export function url<P extends RouteLiteral>(
89
89
  const address = mounted(
90
90
  filledPath(pattern ?? '/', params, `url('${pattern}')`),
91
91
  )
92
- // 30.6 — the query half, last, in the spelling `rpcRequest` writes.
92
+ // 30.6 — the query half, last; 30.28 — in the spelling an `Rpc`'s args are written in.
93
93
  const search =
94
94
  query === undefined
95
95
  ? ''