@abide/abide 0.123.3 → 0.124.0

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 (89) hide show
  1. package/ABIDE.md +8 -5
  2. package/CHANGELOG.md +31 -0
  3. package/docs/REGISTRY.md +45 -12
  4. package/docs/RULEBOOK.md +24 -14
  5. package/docs/abide.md +999 -459
  6. package/package.json +1 -1
  7. package/src/compiler/ARRAY_MUTATORS.ts +20 -0
  8. package/src/compiler/LoweredFile.ts +609 -0
  9. package/src/compiler/Lowering.ts +17 -0
  10. package/src/compiler/SHARED_NAMES.ts +2 -0
  11. package/src/compiler/ShadowHost.ts +670 -0
  12. package/src/compiler/ShadowMap.ts +1 -1
  13. package/src/compiler/TypeSession.ts +71 -1715
  14. package/src/compiler/appLints.ts +109 -0
  15. package/src/compiler/checkedShadow.ts +1 -1
  16. package/src/compiler/classifyRead.ts +16 -1
  17. package/src/compiler/compileApp.ts +2 -2
  18. package/src/compiler/declaredExports.ts +44 -17
  19. package/src/compiler/diagnoseSources.ts +232 -0
  20. package/src/compiler/formatAbide.ts +5 -6
  21. package/src/compiler/handlersUnder.ts +16 -35
  22. package/src/compiler/reactiveReads.ts +235 -0
  23. package/src/compiler/reactiveTypes.ts +13 -0
  24. package/src/compiler/readsIn.ts +180 -81
  25. package/src/compiler/routesDeclaration.ts +46 -0
  26. package/src/compiler/scaffold.ts +7 -2
  27. package/src/compiler/serveLanguage.ts +26 -20
  28. package/src/compiler/splitImports.ts +2 -2
  29. package/src/server/DesktopSession.ts +213 -0
  30. package/src/server/Followers.ts +399 -0
  31. package/src/server/MODULE_QUERIES.ts +6 -2
  32. package/src/server/PageRender.ts +201 -0
  33. package/src/server/abideModules.ts +7 -7
  34. package/src/server/answerFailure.ts +3 -2
  35. package/src/server/answerRequest.ts +6 -70
  36. package/src/server/answerRpc.ts +48 -260
  37. package/src/server/answerSocket.ts +41 -463
  38. package/src/server/appKit.ts +1 -1
  39. package/src/server/argsContract.ts +1 -1
  40. package/src/server/createApp.ts +8 -2
  41. package/src/server/csp.ts +1 -1
  42. package/src/server/deferredBuilds.ts +3 -16
  43. package/src/server/faceOf.ts +1 -1
  44. package/src/server/generatedSurfaces.ts +2 -1
  45. package/src/server/handlerFileMount.ts +1 -1
  46. package/src/server/index.ts +1 -1
  47. package/src/server/installServer.ts +5 -9
  48. package/src/server/invokeHandler.ts +230 -0
  49. package/src/server/mountHandlers.ts +184 -0
  50. package/src/server/openDesktopWindow.ts +308 -0
  51. package/src/server/preload.ts +18 -0
  52. package/src/server/principal.ts +1 -1
  53. package/src/server/remote.ts +1 -1
  54. package/src/server/render.ts +31 -59
  55. package/src/server/requestScope.ts +5 -36
  56. package/src/server/runDesktop.ts +19 -447
  57. package/src/server/seedBuffer.ts +11 -11
  58. package/src/server/servedApp.ts +1 -1
  59. package/src/server/shell.ts +1 -1
  60. package/src/server/surfaceOf.ts +1 -1
  61. package/src/server/versionOf.ts +1 -1
  62. package/src/shared/RPC_SERVER.ts +27 -31
  63. package/src/shared/Schema.ts +1 -2
  64. package/src/shared/index.ts +1 -1
  65. package/src/shared/{note.ts → issueReport.ts} +22 -0
  66. package/src/shared/loadCall.ts +66 -0
  67. package/src/shared/reactive/Keyed.ts +23 -15
  68. package/src/shared/reactive/Reactive.ts +34 -1001
  69. package/src/shared/reactive/byOf.ts +2 -5
  70. package/src/shared/reactive/callLoad.ts +8 -14
  71. package/src/shared/reactive/cellShape.ts +734 -0
  72. package/src/shared/reactive/index.ts +12 -24
  73. package/src/shared/reactive/pairReactive.ts +5 -3
  74. package/src/shared/reactive/producer.ts +121 -0
  75. package/src/shared/reactive/scoped.ts +3 -2
  76. package/src/shared/reactive/selections.ts +209 -0
  77. package/src/shared/reactive/watch.ts +3 -4
  78. package/src/shared/rpc.ts +8 -5
  79. package/src/shared/validateJson.ts +1 -2
  80. package/src/shared/writePath.ts +29 -0
  81. package/src/ui/Router.ts +53 -69
  82. package/src/ui/outboxQueue.ts +2 -8
  83. package/src/ui/outcomeOf.ts +120 -0
  84. package/src/ui/socketWire.ts +3 -3
  85. package/src/ui/wire.ts +9 -10
  86. package/src/shared/LOAD_OFFLINE.ts +0 -15
  87. package/src/shared/LOAD_SIGNAL.ts +0 -10
  88. package/src/shared/issuesOf.ts +0 -18
  89. package/src/shared/reactive/ENTRY_LIVES.ts +0 -7
package/src/ui/Router.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { CURRENT_ROUTER, type NavigateOptions } from '#shared/CURRENT_ROUTER.ts'
2
2
  import { decodedOrKept } from '#shared/decodedOrKept.ts'
3
3
  import { ERROR_META } from '#shared/ERROR_META.ts'
4
- import { firstMatch, type Params, partsOf } from '#shared/matchRoute.ts'
4
+ import { firstMatch, partsOf } from '#shared/matchRoute.ts'
5
5
  import { unmounted } from '#shared/mount.ts'
6
6
  import { NAVIGATION_HEADER } from '#shared/NAVIGATION_HEADER.ts'
7
7
  import { OFFLINE_SAVED } from '#shared/OFFLINE_CACHES.ts'
@@ -13,6 +13,12 @@ import { paintedCallsLanded } from '#shared/takeEarlyCall.ts'
13
13
  import { LAYER_NAVIGATE } from './LAYER_NAMES.ts'
14
14
  import { LevelChain, type PageModule } from './LevelChain.ts'
15
15
  import { beginLayer, endLayer } from './measureLayer.ts'
16
+ import {
17
+ type Bearing,
18
+ type Found,
19
+ outcomeOf,
20
+ type Settled,
21
+ } from './outcomeOf.ts'
16
22
  import { pageRegion, type Region } from './regions.ts'
17
23
 
18
24
  export type Route = {
@@ -30,8 +36,6 @@ export type Route = {
30
36
 
31
37
  type Compiled = Route & { segments: Segment[] }
32
38
 
33
- type Found = { route: Compiled; params: Params }
34
-
35
39
  /** A window's scroll, `[scrollX, scrollY]`. */
36
40
  type Scroll = readonly [number, number]
37
41
 
@@ -151,7 +155,7 @@ export class Router {
151
155
  * as the address below the app's root. Every way in — a click, an intent, back and forward, a
152
156
  * hydration — is matched here.
153
157
  */
154
- match(pathname: string): Found | undefined {
158
+ match(pathname: string): Found<Compiled> | undefined {
155
159
  const below = unmounted(pathname)
156
160
  const parts = below === undefined ? undefined : partsOf(below)
157
161
  return parts === undefined ? undefined : firstMatch(this.routes, parts)
@@ -209,28 +213,17 @@ export class Router {
209
213
  options: NavigateOptions,
210
214
  step: Step,
211
215
  ): Promise<void> {
212
- const target =
213
- typeof url === 'string' ? new URL(url, location.href) : url
214
- // A CROSS-ORIGIN ADDRESS IS NOT A ROUTE. Handing it to `pushState` throws, and treating
215
- // it as a miss would leave the caller with a navigation that silently did nothing.
216
- if (target.origin !== location.origin || this.errorPage) {
217
- location.href = target.href
218
- return
219
- }
220
- const found = this.match(target.pathname)
221
- if (found === undefined) {
222
- // NOT A ROUTE THIS BUILD KNOWS, so it is the server's to answer — which is a document
223
- // load, and the one case where 30.7 does not apply because there is no route to run.
224
- location.href = target.href
225
- return
226
- }
227
- // 30.21, 30.23, 30.24 — THE ROUTE STANDING, AT ITS OWN PATH: another query, another fragment or
228
- // the same address. The page reads its address through `route`, so it follows rather than
229
- // being rebuilt, and there is no path for `middleware` to decide anything new about.
230
- if (found.route === this.standing && this.mounted === target.pathname) {
231
- this.follow(target, options, found, step)
232
- return
216
+ const bearing: Bearing<Compiled> = {
217
+ target: typeof url === 'string' ? new URL(url, location.href) : url,
218
+ origin: location.origin,
219
+ errorPage: this.errorPage,
220
+ standing: this.standing,
221
+ mounted: this.mounted,
222
+ match: (pathname) => this.match(pathname),
233
223
  }
224
+ const before = outcomeOf(bearing)
225
+ if (before.kind !== 'ask') return this.settle(before, options, step)
226
+ const found = before.found
234
227
  const ticket = this.supersede()
235
228
  route.navigating.set(true)
236
229
  // EVERY WAY OUT CLEARS WHAT THE WAY IN SET: a load that fails, a setup that throws and a
@@ -239,70 +232,61 @@ export class Router {
239
232
  try {
240
233
  // 30.15 — THE SERVER IS ASKED ALONGSIDE THE LOAD, so its `middleware` decides the
241
234
  // navigation for the price of the one round trip the chunks already cost.
235
+ // 30.27 — `loadOrMiss`, because a rejection here was dropped by the click's `void`,
236
+ // and the link did nothing.
242
237
  const [answer, loaded] = await Promise.all([
243
- ask(target, ticket.signal),
238
+ ask(bearing.target, ticket.signal),
244
239
  loadOrMiss(found.route),
245
240
  ])
246
241
  // SUPERSEDED, so this navigation is over. Nothing has been touched yet, which is the
247
242
  // whole reason the ticket is checked here rather than after the mount — and
248
243
  // `navigating` stays the newer navigation's to clear.
249
244
  if (ticket.signal.aborted) return
250
- // 30.32 — OFFLINE, THE DOCUMENT LOAD BELOW CAN ONLY PAINT THE BROWSER'S ERROR PAGE over a
251
- // page that works. A history step has already moved the bar, so it is left to the
252
- // document.
253
- if (
254
- answer === undefined &&
255
- !navigator.onLine &&
256
- step.direction === 'push'
257
- ) {
245
+ const after = outcomeOf(bearing, {
246
+ found,
247
+ answer,
248
+ loaded: loaded !== undefined,
249
+ online: navigator.onLine,
250
+ pushed: step.direction === 'push',
251
+ })
252
+ if (after.kind === 'offline') {
258
253
  // 45.12 — UNLESS THE SERVICE WORKER SAVED THE PAGE, which the document load is
259
254
  // then answered with.
260
- if ((await savedOffline(target)) && !ticket.signal.aborted)
261
- location.href = target.href
262
- return
263
- }
264
- // 30.18 — A REFUSAL, A RESPONSE MIDDLEWARE ANSWERED ITSELF, or no answer at all: the
265
- // document asks again and paints whatever the server says. A fetch that failed —
266
- // offline, or a redirect to another origin that CORS will not let it follow — goes to
267
- // the address it was sent to.
268
- // 30.27 — AND CHUNKS THAT WOULD NOT LOAD, with the server down or a deploy that removed
269
- // them: a rejection here was dropped by the click's `void`, and the link did nothing.
270
- if (answer?.status !== 204 || loaded === undefined) {
271
- location.href = answer?.url ?? target.href
272
- return
273
- }
274
- // 30.17 — REDIRECTED, and the server already said yes to where it landed: the answer is
275
- // the 204 at the end of the chain. That address is mounted in place of the one asked
276
- // for, and a route the client has no row for is the document's (30.10).
277
- const landed = answer.redirected ? new URL(answer.url) : target
278
- const landing = answer.redirected
279
- ? this.match(landed.pathname)
280
- : found
281
- if (landing === undefined) {
282
- location.href = landed.href
283
- return
284
- }
285
- // 30.23 — THE ROUTE STANDING, AT ANOTHER PATH: `middleware` has said yes to the path, and
286
- // the page it matched is the one already mounted, so it follows.
287
- if (landing.route === this.standing) {
288
- this.follow(landed, options, landing, step)
255
+ if (
256
+ (await savedOffline(bearing.target)) &&
257
+ !ticket.signal.aborted
258
+ )
259
+ location.href = after.href
289
260
  return
290
261
  }
262
+ if (after.kind !== 'mount') return this.settle(after, options, step)
291
263
  const arriving =
292
- landing === found ? loaded : await loadOrMiss(landing.route)
264
+ after.found === found
265
+ ? loaded
266
+ : await loadOrMiss(after.found.route)
293
267
  if (ticket.signal.aborted) return
294
268
  if (arriving === undefined) {
295
- location.href = landed.href
269
+ location.href = after.at.href
296
270
  return
297
271
  }
298
272
  await this.write(ticket, step.direction, () =>
299
- this.commit(landed, landing, arriving, options, step),
273
+ this.commit(after.at, after.found, arriving, options, step),
300
274
  )
301
275
  } finally {
302
276
  if (this.navigation === ticket) route.navigating.set(false)
303
277
  }
304
278
  }
305
279
 
280
+ /** Executes an outcome that loads nothing: a document load, or a step within the page. */
281
+ private settle(
282
+ outcome: Settled<Compiled>,
283
+ options: NavigateOptions,
284
+ step: Step,
285
+ ): void {
286
+ if (outcome.kind === 'document') location.href = outcome.href
287
+ else this.follow(outcome.at, options, outcome.found, step)
288
+ }
289
+
306
290
  /**
307
291
  * Moves the address within the standing page, leaving the page mounted.
308
292
  *
@@ -317,7 +301,7 @@ export class Router {
317
301
  follow(
318
302
  target: URL,
319
303
  options: NavigateOptions,
320
- found: Found,
304
+ found: Found<Compiled>,
321
305
  step: Step,
322
306
  ): void {
323
307
  this.supersede()
@@ -447,7 +431,7 @@ export class Router {
447
431
  */
448
432
  private commit(
449
433
  target: URL,
450
- found: Found,
434
+ found: Found<Compiled>,
451
435
  arriving: PageModule[],
452
436
  options: NavigateOptions,
453
437
  step: Step,
@@ -515,7 +499,7 @@ export class Router {
515
499
  *
516
500
  * 23.1 — WRITTEN BEFORE THE ARRIVING PAGE'S SETUP, which reads it.
517
501
  */
518
- private arriveAt(target: URL, found: Found): void {
502
+ private arriveAt(target: URL, found: Found<Compiled>): void {
519
503
  arrive(target.href, found.route.pattern, found.params)
520
504
  this.standing = found.route
521
505
  this.mounted = target.pathname
@@ -19,14 +19,8 @@ import { failedFrom, isFailed, problemOf } from '#shared/Failed.ts'
19
19
  import type { Problem } from '#shared/Failed.ts'
20
20
  import { showLine, type ShownCall, showSending } from '#shared/outbox.ts'
21
21
  import { onPrincipalRotated } from '#shared/principalRotated.ts'
22
- import {
23
- isReactive,
24
- produceInto,
25
- type Reactive,
26
- reactive,
27
- refuseInto,
28
- standIn,
29
- } from '#shared/reactive/index.ts'
22
+ import { isReactive, type Reactive, reactive } from '#shared/reactive/index.ts'
23
+ import { produceInto, refuseInto, standIn } from '#shared/reactive/producer.ts'
30
24
  import { rpcRequest } from '#shared/rpcRequest.ts'
31
25
 
32
26
  const DATABASE = 'abide-outbox'
@@ -0,0 +1,120 @@
1
+ import type { Params } from '#shared/matchRoute.ts'
2
+
3
+ /** A route an address matched, and the params it matched with. */
4
+ export type Found<RouteRow> = { route: RouteRow; params: Params }
5
+
6
+ /**
7
+ * What a navigation knows before it asks anything: where it is going, and what stands.
8
+ *
9
+ * `match` is the router's own, so 16.10's mount and 30.9's ranking are decided where they always
10
+ * were.
11
+ */
12
+ export type Bearing<RouteRow> = {
13
+ target: URL
14
+ /** The document's origin, which an address outside of is no route (30.10). */
15
+ origin: string
16
+ /** 35.18 — the document is an error page's, so leaving it is a document load. */
17
+ errorPage: boolean
18
+ /** The route the standing page was matched by. */
19
+ standing: RouteRow | undefined
20
+ /** The path the standing chain was mounted for. */
21
+ mounted: string | undefined
22
+ match: (pathname: string) => Found<RouteRow> | undefined
23
+ }
24
+
25
+ /** What the ask and the load came back with (30.15). */
26
+ export type Heard<RouteRow> = {
27
+ /** The route the ask was made for — the `ask` outcome's `found`. */
28
+ found: Found<RouteRow>
29
+ /** The server's answer; `undefined` where the fetch failed. */
30
+ answer: { status: number; url: string; redirected: boolean } | undefined
31
+ /** Whether the asked-for route's chunks loaded. */
32
+ loaded: boolean
33
+ online: boolean
34
+ /** Whether the navigation pushes, rather than arriving at a history step the bar already took. */
35
+ pushed: boolean
36
+ }
37
+
38
+ /**
39
+ * How a navigation ends.
40
+ *
41
+ * `document` hands `href` to a document load. `follow` moves the address within the standing page.
42
+ * `ask` is the first call's answer where the server must be asked first. `offline` is 30.32's: the
43
+ * page stays, unless the service worker saved `href` (45.12). `mount` mounts `found` at `at`.
44
+ */
45
+ type Outcome<RouteRow> =
46
+ | Settled<RouteRow>
47
+ | { kind: 'ask'; found: Found<RouteRow> }
48
+ | { kind: 'offline'; href: string }
49
+ | { kind: 'mount'; at: URL; found: Found<RouteRow> }
50
+
51
+ /** An outcome that loads nothing, which either call can answer. */
52
+ export type Settled<RouteRow> =
53
+ | { kind: 'document'; href: string }
54
+ | { kind: 'follow'; at: URL; found: Found<RouteRow> }
55
+
56
+ /**
57
+ * Decides how a navigation ends, from what it knows: called once before the ask, and once more
58
+ * with what was `heard` where the first call answered `ask`.
59
+ *
60
+ * Decides and touches nothing — the router executes the answer. A `mount` whose `found` is not
61
+ * `heard.found` lands on a route whose chunks were not loaded yet, and the router hands that
62
+ * address to the document where they will not load (30.27).
63
+ */
64
+ export function outcomeOf<RouteRow>(
65
+ bearing: Bearing<RouteRow>,
66
+ ): Settled<RouteRow> | Extract<Outcome<RouteRow>, { kind: 'ask' }>
67
+ export function outcomeOf<RouteRow>(
68
+ bearing: Bearing<RouteRow>,
69
+ heard: Heard<RouteRow>,
70
+ ): Settled<RouteRow> | Extract<Outcome<RouteRow>, { kind: 'offline' | 'mount' }>
71
+ export function outcomeOf<RouteRow>(
72
+ bearing: Bearing<RouteRow>,
73
+ heard?: Heard<RouteRow>,
74
+ ): Outcome<RouteRow> {
75
+ const target = bearing.target
76
+ if (heard === undefined) {
77
+ // A CROSS-ORIGIN ADDRESS IS NOT A ROUTE. Handing it to `pushState` throws, and treating
78
+ // it as a miss would leave the caller with a navigation that silently did nothing.
79
+ if (target.origin !== bearing.origin || bearing.errorPage)
80
+ return { kind: 'document', href: target.href }
81
+ const found = bearing.match(target.pathname)
82
+ // 30.10 — NOT A ROUTE THIS BUILD KNOWS, so it is the server's to answer — which is a
83
+ // document load, and the one case where 30.7 does not apply because there is no route.
84
+ if (found === undefined) return { kind: 'document', href: target.href }
85
+ // 30.21, 30.23, 30.24 — THE ROUTE STANDING, AT ITS OWN PATH: another query, another
86
+ // fragment or the same address. The page reads its address through `route`, so it follows
87
+ // rather than being rebuilt, and there is no path for `middleware` to decide anything new
88
+ // about.
89
+ if (
90
+ found.route === bearing.standing &&
91
+ bearing.mounted === target.pathname
92
+ )
93
+ return { kind: 'follow', at: target, found }
94
+ return { kind: 'ask', found }
95
+ }
96
+ const answer = heard.answer
97
+ // 30.32 — OFFLINE, THE DOCUMENT LOAD BELOW CAN ONLY PAINT THE BROWSER'S ERROR PAGE over a page
98
+ // that works. A history step has already moved the bar, so it is left to the document.
99
+ if (answer === undefined && !heard.online && heard.pushed)
100
+ return { kind: 'offline', href: target.href }
101
+ // 30.18 — A REFUSAL, A RESPONSE MIDDLEWARE ANSWERED ITSELF, or no answer at all: the document
102
+ // asks again and paints whatever the server says. A fetch that failed — offline, or a redirect
103
+ // to another origin that CORS will not let it follow — goes to the address it was sent to.
104
+ // 30.27 — AND CHUNKS THAT WOULD NOT LOAD, with the server down or a deploy that removed them.
105
+ if (answer?.status !== 204 || !heard.loaded)
106
+ return { kind: 'document', href: answer?.url ?? target.href }
107
+ // 30.17 — REDIRECTED, and the server already said yes to where it landed: the answer is the
108
+ // 204 at the end of the chain. That address is mounted in place of the one asked for, and a
109
+ // route the client has no row for is the document's (30.10).
110
+ const landed = answer.redirected ? new URL(answer.url) : target
111
+ const landing = answer.redirected
112
+ ? bearing.match(landed.pathname)
113
+ : heard.found
114
+ if (landing === undefined) return { kind: 'document', href: landed.href }
115
+ // 30.23 — THE ROUTE STANDING, AT ANOTHER PATH: `middleware` has said yes to the path, and the
116
+ // page it matched is the one already mounted, so it follows.
117
+ if (landing.route === bearing.standing)
118
+ return { kind: 'follow', at: landed, found: landing }
119
+ return { kind: 'mount', at: landed, found: landing }
120
+ }
@@ -8,15 +8,15 @@ import { mounted } from '#shared/mount.ts'
8
8
  import type { BrowserFrame, ServerFrame } from '#shared/muxFrames.ts'
9
9
  import { onPrincipalRotated } from '#shared/principalRotated.ts'
10
10
  import { canonicalKey } from '#shared/reactive/argsContract.ts'
11
+ import { type Reactive } from '#shared/reactive/index.ts'
12
+ import { patchEpoch, writtenSince } from '#shared/reactive/patchMarks.ts'
11
13
  import {
12
14
  heldGenerationOf,
13
15
  loadOf,
14
16
  patchInto,
15
17
  produceInto,
16
- type Reactive,
17
18
  writtenLocally,
18
- } from '#shared/reactive/index.ts'
19
- import { patchEpoch, writtenSince } from '#shared/reactive/patchMarks.ts'
19
+ } from '#shared/reactive/producer.ts'
20
20
 
21
21
  import { HYDRATE_LOG } from './Mismatch.ts'
22
22
  import { undelivered, whenDelivered } from './outboxQueue.ts'
package/src/ui/wire.ts CHANGED
@@ -29,13 +29,13 @@ import { isFailed } from '#shared/Failed.ts'
29
29
  import { answerChunks } from '#shared/framing.ts'
30
30
  import { HandlerRoute } from '#shared/HandlerRoute.ts'
31
31
  import type { JsonValue } from '#shared/JsonValue.ts'
32
- import { LOAD_OFFLINE } from '#shared/LOAD_OFFLINE.ts'
33
- import { LOAD_SIGNAL } from '#shared/LOAD_SIGNAL.ts'
34
- import { reactive, standIn, structural } from '#shared/reactive/index.ts'
32
+ import { standForLoadCall, startingLoadCall } from '#shared/loadCall.ts'
33
+ import { reactive, structural } from '#shared/reactive/index.ts'
35
34
  import {
36
35
  declareDefaults,
37
36
  type Defaults,
38
37
  } from '#shared/reactive/parameterDefaults.ts'
38
+ import { standIn } from '#shared/reactive/producer.ts'
39
39
  import { declare, type Method } from '#shared/rpc.ts'
40
40
  import { type RpcRequest, rpcRequest } from '#shared/rpcRequest.ts'
41
41
 
@@ -72,11 +72,11 @@ export function wire(
72
72
  // ONE PARAMETER, so the value is keyed by args whatever the handler declared (16.6's arity rule):
73
73
  // an unkeyed handler is reached with no args, which is one key.
74
74
  const body = (args: Record<string, unknown> | undefined) => {
75
- // 16.49, 16.81 — the load's own signal and stand-in, set by the call starting it and read
76
- // before anything awaits.
77
- const signal = LOAD_SIGNAL.current
75
+ // 16.49, 16.81 — the load's own signal and stand-in, as the call starting it carried them.
76
+ const starting = startingLoadCall()
77
+ const signal = starting.signal
78
78
  const request = requestOf(args)
79
- let offline = LOAD_OFFLINE.current
79
+ let offline = starting.offline
80
80
  // A reload no call started — a refresh, or the one `whenOnline` asks — that gets no answer
81
81
  // waits before asking again, where asking at once would get none on every tick.
82
82
  const retryIn = offline === undefined ? UNANSWERED_RETRY_MS : 0
@@ -93,8 +93,7 @@ export function wire(
93
93
  ? queuedCall(request, address, args)
94
94
  : undefined
95
95
  let standing: { value: unknown } | undefined
96
- if (queued !== undefined)
97
- LOAD_OFFLINE.standing = standing = { value: undefined }
96
+ if (queued !== undefined) standing = standForLoadCall()
98
97
  const stoodIn = (failed: Answer): unknown => {
99
98
  const value = (offline as (args: unknown) => unknown)(args)
100
99
  if (queued === undefined) {
@@ -145,7 +144,7 @@ export function wire(
145
144
  ): AsyncGenerator<unknown> {
146
145
  // 16.49 — read on the first line: the core's pump takes the first step inside the reach, so
147
146
  // this runs while the call that started the load still has its signal set.
148
- const signal = LOAD_SIGNAL.current
147
+ const signal = startingLoadCall().signal
149
148
  const request = requestOf(args)
150
149
  const answer = await answerOf(request, true, signal)
151
150
  if (answer.ok) answeredOnce.add(request.key)
@@ -1,15 +0,0 @@
1
- /**
2
- * The `offline` of the call that is starting a load, while it starts.
3
- *
4
- * 16.81 — SET AROUND THE REACH as `LOAD_SIGNAL` is, and read by the browser's wire as its body
5
- * starts: what stands in for an answer the call could not get. A leaf, because `callLoad` sets it
6
- * and the wire reads it.
7
- */
8
- export const LOAD_OFFLINE: {
9
- current: ((args: unknown) => unknown) | undefined
10
- /**
11
- * Where the body that queues the call asks for the value the call reached, which `callLoad`
12
- * fills once the reach returns: what the delivered answer lands in (46.7).
13
- */
14
- standing: { value: unknown } | undefined
15
- } = { current: undefined, standing: undefined }
@@ -1,10 +0,0 @@
1
- /**
2
- * The abort signal of the call that is starting a load, while it starts.
3
- *
4
- * 16.49 — THE SIGNAL OF THE CALL THAT IS STARTING A LOAD, set around the reach and read by a
5
- * transport's body as it starts: the browser's wire hands it to `fetch`, so a load every caller
6
- * aborted ends its request too. A leaf, because `callLoad` sets it and the wire reads it.
7
- */
8
- export const LOAD_SIGNAL: { current: AbortSignal | undefined } = {
9
- current: undefined,
10
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * Issues in the shape `Issues<T>` gives them for the value refused: by path for an object or an
3
- * array, the bare list for anything else.
4
- *
5
- * DECIDED BY THE VALUE, because the value is all the runtime holds — `T` is erased. A scalar has
6
- * no path to key by, so every issue about one stands on `''`, and handing back `{ '': [...] }`
7
- * there is the wrapper `Issues<T>` says a reader never unwraps: `error().data[0]` read `undefined`.
8
- */
9
- export function issuesOf(
10
- found: Record<string, string[]>,
11
- value: unknown,
12
- ): Record<string, string[]> | string[] {
13
- if (typeof value === 'object' && value !== null) return found
14
- const listed: string[] = []
15
- for (const path in found)
16
- for (const said of found[path] as string[]) listed.push(said)
17
- return listed
18
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * When each entry of a `Keyed` declaring `ttl` last produced, and how long it lives, by entry cell.
3
- *
4
- * 11.45. A leaf because `Keyed.ts` writes it and `Reactive.ts` reads it (`deadlineOf`), and the
5
- * second cannot import the first.
6
- */
7
- export const ENTRY_LIVES = new WeakMap<object, { at: number; ms: number }>()