@abide/abide 0.123.2 → 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 (150) hide show
  1. package/ABIDE.md +8 -5
  2. package/CHANGELOG.md +58 -0
  3. package/bin/abide.ts +36 -65
  4. package/docs/REGISTRY.md +45 -12
  5. package/docs/RULEBOOK.md +24 -14
  6. package/docs/abide.md +999 -459
  7. package/package.json +1 -1
  8. package/src/compiler/ARRAY_MUTATORS.ts +20 -0
  9. package/src/compiler/Diagnostics.ts +198 -0
  10. package/src/compiler/KeptChecker.ts +97 -0
  11. package/src/compiler/LoweredFile.ts +609 -0
  12. package/src/compiler/Lowering.ts +18 -1
  13. package/src/compiler/PositionedRefusal.ts +1 -1
  14. package/src/compiler/SHARED_NAMES.ts +2 -0
  15. package/src/compiler/ShadowHost.ts +670 -0
  16. package/src/compiler/ShadowMap.ts +1 -1
  17. package/src/compiler/TypeSession.ts +76 -1743
  18. package/src/compiler/appLayout.ts +28 -20
  19. package/src/compiler/appLints.ts +109 -0
  20. package/src/compiler/argsDefaultsIn.ts +1 -1
  21. package/src/compiler/checkApp.ts +64 -68
  22. package/src/compiler/checkedShadow.ts +2 -2
  23. package/src/compiler/classifyRead.ts +16 -1
  24. package/src/compiler/compileApp.ts +102 -71
  25. package/src/compiler/declaredExports.ts +46 -21
  26. package/src/compiler/diagnoseSources.ts +232 -0
  27. package/src/compiler/emitClient.ts +1 -1
  28. package/src/compiler/formatAbide.ts +7 -8
  29. package/src/compiler/handlersUnder.ts +32 -63
  30. package/src/compiler/lineOf.ts +0 -19
  31. package/src/compiler/matchersOf.ts +3 -3
  32. package/src/compiler/mountsOf.ts +3 -3
  33. package/src/compiler/reactiveReads.ts +235 -0
  34. package/src/compiler/reactiveTypes.ts +13 -0
  35. package/src/compiler/readSources.ts +16 -18
  36. package/src/compiler/readsIn.ts +181 -82
  37. package/src/compiler/routesDeclaration.ts +46 -0
  38. package/src/compiler/scaffold.ts +7 -2
  39. package/src/compiler/serveLanguage.ts +46 -74
  40. package/src/compiler/sharedModuleWrites.ts +1 -1
  41. package/src/compiler/splitImports.ts +2 -2
  42. package/src/compiler/templateWarnings.ts +1 -2
  43. package/src/compiler/trackingWarnings.ts +1 -1
  44. package/src/server/CLIENT_BUILD.ts +4 -32
  45. package/src/server/DesktopSession.ts +213 -0
  46. package/src/server/Followers.ts +399 -0
  47. package/src/server/MODULE_QUERIES.ts +6 -2
  48. package/src/server/PageRender.ts +201 -0
  49. package/src/server/RESUME_MILLISECONDS.ts +2 -0
  50. package/src/server/abideModules.ts +7 -7
  51. package/src/server/answerFailure.ts +3 -2
  52. package/src/server/answerRequest.ts +16 -369
  53. package/src/server/answerRpc.ts +48 -302
  54. package/src/server/answerSocket.ts +41 -490
  55. package/src/server/appBuild.ts +377 -0
  56. package/src/server/appKit.ts +1 -1
  57. package/src/server/appPlugins.ts +15 -17
  58. package/src/server/appTree.ts +6 -5
  59. package/src/server/argsContract.ts +209 -0
  60. package/src/server/bridgeRelay.ts +153 -65
  61. package/src/server/claudeCode.ts +30 -127
  62. package/src/server/config.ts +6 -7
  63. package/src/server/createApp.ts +38 -66
  64. package/src/server/csp.ts +1 -1
  65. package/src/server/deferredBuilds.ts +3 -16
  66. package/src/server/devWorker.ts +11 -15
  67. package/src/server/faceOf.ts +1 -1
  68. package/src/server/generatedSurfaces.ts +2 -1
  69. package/src/server/handlerFileMount.ts +40 -0
  70. package/src/server/index.ts +3 -3
  71. package/src/server/installServer.ts +5 -9
  72. package/src/server/invokeHandler.ts +230 -0
  73. package/src/server/mountHandlers.ts +184 -0
  74. package/src/server/openDesktopWindow.ts +308 -0
  75. package/src/server/packageDirectory.ts +14 -0
  76. package/src/server/preload.ts +21 -2
  77. package/src/server/principal.ts +5 -4
  78. package/src/server/remote.ts +2 -2
  79. package/src/server/render.ts +31 -59
  80. package/src/server/requestScope.ts +5 -36
  81. package/src/server/runCompiled.ts +1 -1
  82. package/src/server/runDesktop.ts +20 -448
  83. package/src/server/seedBuffer.ts +12 -12
  84. package/src/server/serveDev.ts +24 -58
  85. package/src/server/servedApp.ts +390 -0
  86. package/src/server/shell.ts +1 -1
  87. package/src/server/signInChanged.ts +20 -0
  88. package/src/server/surfaceOf.ts +3 -2
  89. package/src/server/versionOf.ts +7 -16
  90. package/src/shared/RPC_SERVER.ts +27 -31
  91. package/src/shared/Schema.ts +1 -2
  92. package/src/shared/applyPatch.ts +1 -1
  93. package/src/shared/index.ts +1 -1
  94. package/src/shared/{note.ts → issueReport.ts} +22 -0
  95. package/src/shared/loadCall.ts +66 -0
  96. package/src/shared/muxFrames.ts +1 -1
  97. package/src/shared/reactive/Keyed.ts +24 -16
  98. package/src/shared/reactive/Reactive.ts +37 -991
  99. package/src/shared/reactive/argsContract.ts +131 -4
  100. package/src/shared/reactive/byOf.ts +2 -5
  101. package/src/shared/reactive/callLoad.ts +8 -14
  102. package/src/shared/reactive/cellShape.ts +734 -0
  103. package/src/shared/reactive/index.ts +12 -23
  104. package/src/shared/reactive/pairReactive.ts +5 -3
  105. package/src/shared/reactive/patchLog.ts +79 -0
  106. package/src/shared/reactive/patchMarks.ts +34 -0
  107. package/src/shared/reactive/producer.ts +121 -0
  108. package/src/shared/reactive/recordWrites.ts +11 -14
  109. package/src/shared/reactive/scoped.ts +3 -2
  110. package/src/shared/reactive/selections.ts +209 -0
  111. package/src/shared/reactive/share.ts +4 -3
  112. package/src/shared/reactive/watch.ts +3 -4
  113. package/src/shared/rpc.ts +20 -24
  114. package/src/shared/validateJson.ts +1 -2
  115. package/src/shared/writePath.ts +29 -0
  116. package/src/ui/ArmRegion.ts +10 -4
  117. package/src/ui/Level.ts +11 -20
  118. package/src/ui/LevelChain.ts +272 -0
  119. package/src/ui/Router.ts +106 -313
  120. package/src/ui/Scope.ts +17 -3
  121. package/src/ui/TryRegion.ts +22 -23
  122. package/src/ui/bindSpread.ts +2 -4
  123. package/src/ui/listenForRoutes.ts +3 -10
  124. package/src/ui/outboxQueue.ts +23 -11
  125. package/src/ui/outcomeOf.ts +120 -0
  126. package/src/ui/reconcile.ts +4 -6
  127. package/src/ui/routing.ts +2 -1
  128. package/src/ui/rows.ts +2 -2
  129. package/src/ui/setAttribute.ts +12 -3
  130. package/src/ui/socketWire.ts +28 -13
  131. package/src/ui/wire.ts +9 -10
  132. package/src/compiler/Finding.ts +0 -2
  133. package/src/compiler/Refusals.ts +0 -65
  134. package/src/compiler/formatDiagnosis.ts +0 -20
  135. package/src/server/appDirectories.ts +0 -9
  136. package/src/server/argsFor.ts +0 -39
  137. package/src/server/buildDirectory.ts +0 -10
  138. package/src/server/coerceArgs.ts +0 -109
  139. package/src/server/derivedArgs.ts +0 -26
  140. package/src/server/runBuild.ts +0 -24
  141. package/src/server/serveBuild.ts +0 -144
  142. package/src/server/testBuild.ts +0 -220
  143. package/src/shared/LOAD_OFFLINE.ts +0 -15
  144. package/src/shared/LOAD_SIGNAL.ts +0 -10
  145. package/src/shared/issuesOf.ts +0 -18
  146. package/src/shared/reactive/ENTRY_LIVES.ts +0 -7
  147. package/src/shared/reactive/FOLLOWED.ts +0 -8
  148. package/src/shared/reactive/PATCHES.ts +0 -11
  149. package/src/shared/reactive/PATCH_SINKS.ts +0 -26
  150. package/src/shared/reactive/canonicalKey.ts +0 -95
@@ -1,21 +1,15 @@
1
- // 16.2 — A HANDLER, ANSWERED OVER HTTP. The call is the in-process one (`#shared/rpc.ts`), made in a
2
- // request's own scope (11.32), and what this module adds is the transport either side of it: the
3
- // args off the request, and the answer onto a response.
1
+ // 16.2 — A HANDLER, ANSWERED OVER HTTP. The call is `invokeHandler`'s, made in a request's own scope
2
+ // (11.32), and what this module adds is the transport either side of it: the args off the request,
3
+ // and the answer onto a response.
4
4
  //
5
- // WIRE-ONLY, per 16.13: the `Origin` gate on a mutation, `maxBodySize`, the `crossOrigin` preflight,
6
- // and 19.7's coercion of the text a URL carried.
5
+ // WIRE-ONLY, per 16.13: `maxBodySize`, the `crossOrigin` preflight, and the text a URL or a form
6
+ // carried the args as.
7
7
 
8
- import type { CopyOptions } from '#shared/CopyOptions.ts'
9
8
  import { type Failed, isFailed } from '#shared/Failed.ts'
10
- import { GLOBAL_GETS } from '#shared/GLOBAL_GETS.ts'
11
9
  import { isRecord } from '#shared/isRecord.ts'
12
- import type { JsonValue } from '#shared/JsonValue.ts'
13
10
  import { mounted as underMount } from '#shared/mount.ts'
14
- import type { Gated } from '#shared/reactive/argsContract.ts'
15
- import { deadlineOf, heldGenerationOf } from '#shared/reactive/index.ts'
16
- import type { Defaults } from '#shared/reactive/parameterDefaults.ts'
11
+ import { deadlineOf } from '#shared/reactive/producer.ts'
17
12
  import { notFound, refuse } from '#shared/refuse.ts'
18
- import type { HeldToAnnotation, Method, Rpc } from '#shared/rpc.ts'
19
13
  import { RPC_SERVER } from '#shared/RPC_SERVER.ts'
20
14
  import { argsInURL } from '#shared/rpcRequest.ts'
21
15
  import { VERSION_HEADER } from '#shared/VERSION_HEADER.ts'
@@ -27,196 +21,24 @@ import {
27
21
  type ErrorPages,
28
22
  handlerHeaders,
29
23
  } from './answerFailure.ts'
30
- import { followAsEvents } from './answerSocket.ts'
31
24
  import { answerValue, namesEtag, sseHeaders } from './answerValue.ts'
32
- import { argsFor } from './argsFor.ts'
33
- import { callerOf } from './callerOf.ts'
34
- import {
35
- argsDocumentOf,
36
- coerceArgs,
37
- collapsed,
38
- type Document,
39
- } from './coerceArgs.ts'
25
+ import { collapsed } from './argsContract.ts'
40
26
  import { positiveSetting } from './config.ts'
41
- import {
42
- allows,
43
- closedTo,
44
- crossingOrigin,
45
- crossingRefusal,
46
- } from './crossingOrigin.ts'
47
- import { failureOf, type LaneRung, rungsOver } from './hooks.ts'
27
+ import { allows, crossingOrigin } from './crossingOrigin.ts'
28
+ import { failureOf } from './hooks.ts'
29
+ import { type Invocation, invokeHandler, originGate } from './invokeHandler.ts'
30
+ import type { Mounted } from './mountHandlers.ts'
48
31
  import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
49
32
  import { prefers } from './prefers.ts'
50
- import {
51
- currentRequestScope,
52
- rungsTook,
53
- timed,
54
- unreap,
55
- writtenStatus,
56
- } from './requestScope.ts'
57
- import { type Outcome, outcomeOf, takeSeed } from './seedBuffer.ts'
33
+ import { currentRequestScope, unreap, writtenStatus } from './requestScope.ts'
34
+ import type { Outcome } from './seedBuffer.ts'
58
35
  import { versionOf } from './versionOf.ts'
59
36
 
60
- /**
61
- * One row of the handler table the build writes into the server's table (`SERVER_TABLE`): where a
62
- * handler answers, and its module.
63
- *
64
- * The module is relative to the app's root, which both commands that serve already know.
65
- */
66
- export type Mount = {
67
- address: string
68
- method: Method
69
- source: string
70
- /** The handler file's name, or the private handler's binding. */
71
- name: string
72
- /** What the source's module exports it as: `default` for a handler file. */
73
- export: string
74
- /** The route its hooks are filed at, which its rungs are read over (37.1, 16.72). */
75
- scope: string
76
- /** 23.26 — each matcher its address names, to the anchored source its row is ranked with. */
77
- matchers: Record<string, string>
78
- /** Whether it is a private handler, listed nowhere (16.71). */
79
- private: boolean
80
- /** Whether a browser is handed a wrapper for it. 20.4. */
81
- ui: boolean
82
- /** Whether the command line lists it — the shell's listing and completion, never whether `call` reaches it. 20.3, 38.62. */
83
- cli: boolean
84
- /**
85
- * Whether MCP lists it where its runtime `clients` does not say.
86
- *
87
- * 16.37 — false for a handler typed as returning a `Response`, which only the build can tell.
88
- */
89
- mcp: boolean
90
- /** Its `description`, where the declaration writes one as a literal the build can read. */
91
- description: string | undefined
92
- /**
93
- * What a `GET`'s value declares that the browser's copy behaves by, or `undefined`.
94
- *
95
- * 18.4 — set where its declaration names any of it.
96
- */
97
- copy: CopyOptions | undefined
98
- /**
99
- * The defaults the handler's parameter writes, for the browser's copy to key by.
100
- *
101
- * 11.29 — the server reads its own off the handler.
102
- */
103
- defaults: Defaults | undefined
104
- /** The JSON Schema the handler's args annotation derives, where it takes a record. 19.9. */
105
- args: JsonValue | undefined
106
- /** The JSON Schema of what it answers, where its type says one. 19.21. */
107
- result: JsonValue | undefined
108
- }
109
-
110
- /**
111
- * A row of the table the build wrote, with the import of the module its handler is declared in —
112
- * the handler file itself, or a private handler's `.abide` server module, the one the route table
113
- * loads too, so a page and a call reach one handler.
114
- */
115
- export type MountRow = Mount & { load: () => Promise<Record<string, unknown>> }
116
-
117
- /** A handler's row, mounted: the handler imported and its args schema probed. */
118
- export type Mounted = Mount & {
119
- /**
120
- * The rungs filed over `scope`, read once at mount (16.14) — what an http call and a socket
121
- * subscription to it both pass through (37.2, 18.26).
122
- */
123
- rungs: LaneRung[]
124
- handler: Rpc<unknown, unknown> & Gated<unknown> & HeldToAnnotation
125
- /**
126
- * The args' JSON Schema, probed once at mount.
127
- *
128
- * 19.7, 20.11 — a third-party schema converts itself on every probe.
129
- */
130
- argsDocument: Document | undefined
131
- }
132
-
133
- /**
134
- * The row of a handler abide mounts itself, keyed as `answerRpc` looks one up.
135
- *
136
- * Built on the core's `declare` with no generated surface (20.1) — the app's tree never names it.
137
- */
138
- export function mountedOwn(
139
- address: string,
140
- name: string,
141
- handler: object,
142
- ): [string, Mounted] {
143
- // 16.10 — READ WHEN IT IS REPORTED, where a mounted handler's is stamped at boot: the mount is
144
- // the configuration's, and nothing boots this handler.
145
- Object.defineProperty(handler, 'address', {
146
- get: () => underMount(address),
147
- })
148
- return [
149
- address,
150
- {
151
- address,
152
- method: 'GET',
153
- // The root's rungs run before routing (37.3), and nothing is filed below a route
154
- // of abide's own.
155
- rungs: [],
156
- source: '',
157
- name,
158
- export: name,
159
- scope: '/',
160
- matchers: {},
161
- private: false,
162
- ui: false,
163
- cli: false,
164
- mcp: false,
165
- description: undefined,
166
- copy: undefined,
167
- defaults: undefined,
168
- args: undefined,
169
- result: undefined,
170
- handler: handler as Mounted['handler'],
171
- argsDocument: undefined,
172
- },
173
- ]
174
- }
175
-
176
37
  // 16.58 — the environment's `timeout`, for every call a handler that names none makes.
177
38
  export function environmentTimeout(): number | undefined {
178
39
  return positiveSetting('ABIDE_RPC_TIMEOUT', undefined)
179
40
  }
180
41
 
181
- /**
182
- * The table's handlers, imported and keyed by method and address.
183
- *
184
- * Imported through each row's own loader, which the route table shares, so a page rendering on the
185
- * server and a call over the wire reach one module and so one handler. AT BOOT rather than on the
186
- * first call, because a page's render reaches a handler through the page's own import and has to
187
- * know its address before anything has called it over the wire.
188
- */
189
- export async function mountHandlers(
190
- table: MountRow[],
191
- ): Promise<{ handlers: Map<string, Mounted> }> {
192
- const modules = await Promise.all(table.map((mount) => mount.load()))
193
- const handlers = new Map<string, Mounted>()
194
- for (let at = 0; at < table.length; at += 1) {
195
- const mount = table[at] as Mount
196
- const exported = (modules[at] as Record<string, unknown>)[mount.export]
197
- const handler = exported as Mounted['handler']
198
- const declared = argsDocumentOf(handler)
199
- // 16.9, 16.10 — the address `rpc.url` and `rpc.raw` build on, under the mount — and what a
200
- // page's render turns a call it made into the browser's request by (41.20).
201
- handler.address = underMount(mount.address)
202
- handlers.set(handlerKey(mount.method, mount.address), {
203
- ...mount,
204
- // 37.1, 16.72 — the route its file's hooks are filed at, which the build read off
205
- // where the file answers (23.19's mount for a package's), never its place on disk.
206
- // 37.14 — a handler's lane answers a refusal as Problem Details (15.1).
207
- rungs: rungsOver(mount.scope, answered),
208
- handler,
209
- argsDocument: declared ?? (mount.args as Document | undefined),
210
- })
211
- }
212
- return { handlers }
213
- }
214
-
215
- /** What a handler is keyed by among the mounted: its method and its route. 19.1. */
216
- export function handlerKey(method: string, address: string): string {
217
- return `${method} ${address}`
218
- }
219
-
220
42
  /**
221
43
  * The `Response` to a call of `found` made over http, `params` being what its route matched.
222
44
  *
@@ -240,89 +62,46 @@ export async function answerRpc(
240
62
  return origin !== null && allows(found.handler.crossOrigin, origin)
241
63
  ? preflight(found, origin, request)
242
64
  : answered(notFound({ path: pathname, method: request.method }))
243
- // 16.50 — a `HEAD` is the `GET`'s answer, which the server sends without its body.
244
- const head = request.method === 'HEAD'
245
- const handler = found.handler
246
65
  const inUrl = argsInURL(found.method)
247
66
  const form = isForm(request)
248
- const crossing = crossingOrigin(origin, url)
249
- const closed = closedTo(handler.crossOrigin, crossing)
250
67
  // 21.19 — the origin the answer admits, where the handler declared it may read.
251
- const admitted = closed ? null : crossing
252
- // 16.13 — A MUTATION FROM ANOTHER ORIGIN IS REFUSED unless the handler declared it may come.
253
- if (closed && !inUrl)
254
- return answered(crossingRefusal(crossing, found.method))
255
- const raw = await argsOf(request, url, inUrl, form, handler.maxBodySize)
68
+ const admitted = originGate(
69
+ found.handler,
70
+ crossingOrigin(origin, url),
71
+ false,
72
+ )
73
+ if (isFailed(admitted)) return answered(admitted)
74
+ const raw = await argsOf(
75
+ request,
76
+ url,
77
+ inUrl,
78
+ form,
79
+ found.handler.maxBodySize,
80
+ )
256
81
  if (isFailed(raw)) return answered(raw)
257
- const sent = withParameters(raw, params, inUrl || form, found)
258
- if (isFailed(sent)) return answered(sent)
259
- let args: unknown
260
- try {
261
- // A URL's and a form's args are text, and the query tells a bare name from `name=` (19.7).
262
- args = argsFor(found, sent, inUrl ? url.search : form ? '' : undefined)
263
- } catch (refused) {
264
- if (isFailed(refused)) return answered(refused)
265
- throw refused
266
- }
267
- // 41.7 — the call a hydrating page makes for what its render already reached is answered from
268
- // that, and the handler is not run again (41.13).
269
- // 16.51 — a `HEAD` leaves the seed for the `GET` it stands in front of.
270
- const caller = head ? undefined : callerOf()
271
- const seeded =
272
- caller === undefined ? undefined : takeSeed(caller, handler, args)
273
- // The entry key of a `global` value, taken once for every read of it below.
274
- const global = GLOBAL_GETS.get(handler)
275
- const key = global?.key(args)
276
- // 21.15 — A `global` PRODUCTION ALREADY HELD, and not loading, answers without a run: a run
277
- // this call started moves the generation, and one it joined was loading when it came.
278
- const held =
279
- seeded === undefined && key !== undefined ? global?.at(key) : undefined
280
- const heldAt = held === undefined ? undefined : heldGenerationOf(held)
281
- const timing = currentRequestScope()?.timing
282
- if (timing !== undefined) timing.answering = handler
283
- const started = performance.now()
284
- const outcome =
285
- seeded === undefined ? await outcomeOf(handler(args)) : await seeded
286
- // The entry as the run left it, which the answer reads too.
287
- const entry = key === undefined ? undefined : global?.at(key)
288
- const cached =
289
- heldAt !== undefined &&
290
- entry === held &&
291
- heldGenerationOf(held) === heldAt
82
+ // A URL's and a form's args are text, and the query tells a bare name from `name=` (19.7).
83
+ // 16.50 — a `HEAD` is the `GET`'s answer, which the server sends without its body; 16.51 — and
84
+ // it leaves the seed for the `GET` it stands in front of.
85
+ const invoked = await invokeHandler(
86
+ found,
87
+ raw,
88
+ inUrl ? url.search : form ? '' : undefined,
89
+ params,
90
+ request.method !== 'HEAD',
91
+ )
92
+ if (isFailed(invoked)) return answered(invoked)
93
+ const outcome = invoked.outcome
292
94
  // 16.38 — A MUTATION A DOCUMENT MADE — a form posted with no script — is answered as a navigation.
293
95
  if (!inUrl && prefers(request, 'text/html', 'application/json'))
294
96
  return navigated(outcome, request, pathname, pages)
295
- const answer = await answerOutcome(
296
- outcome,
297
- request,
298
- found,
299
- args,
300
- key,
301
- entry,
302
- offline,
303
- )
97
+ const answer = await answerOutcome(invoked, request, found, offline)
304
98
  // A `Response` the handler built is its own — but the origin answer below is laid over it, so it
305
99
  // varies on `Origin` too (21.7), which `handlerHeaders` never reached.
306
100
  if (outcome.settled && outcome.value instanceof Response) {
307
101
  answer.headers.append('vary', 'origin')
308
102
  return admitting(answer, admitted)
309
103
  }
310
- // 21.15 — HOW LONG THE HANDLER TOOK, AND WHICH RUN THIS IS, for the network panel: the row
311
- // already names the handler, so the metric names the run. A stream's time is to its first
312
- // chunk, which the answer's head waited for.
313
- const took = outcome.duration ?? performance.now() - started
314
- const run =
315
- seeded !== undefined
316
- ? 'Seeded'
317
- : cached
318
- ? 'Cached'
319
- : outcome.duration === undefined
320
- ? 'Started'
321
- : 'Direct'
322
- // 21.20 — what the rungs took is theirs, and the handler's own body is what `Direct` reports.
323
- const handled = run === 'Direct' ? timing?.handled : undefined
324
- if (handled !== undefined) rungsTook(took - handled)
325
- timed(timing, run, handled ?? took)
104
+ invoked.timed()
326
105
  return admitting(answer, admitted)
327
106
  }
328
107
 
@@ -344,15 +123,12 @@ function admitting(answer: Response, admitted: string | null): Response {
344
123
  }
345
124
 
346
125
  async function answerOutcome(
347
- outcome: Outcome,
126
+ invoked: Invocation,
348
127
  request: Request,
349
128
  found: Mounted,
350
- args: unknown,
351
- key: string | undefined,
352
- entry: unknown,
353
129
  offline: boolean,
354
130
  ): Promise<Response> {
355
- const handler = found.handler
131
+ const { outcome, entry } = invoked
356
132
  if (!outcome.settled) return answered(failureOf(outcome.error))
357
133
  const value = outcome.value
358
134
  // The `global` value the answer came from, or `undefined` for one built per request. 18.22 — or
@@ -379,7 +155,8 @@ async function answerOutcome(
379
155
  const etag = serving !== undefined ? `"${versionOf(serving)}"` : undefined
380
156
  if (etag !== undefined) {
381
157
  headers.set(VERSION_HEADER, etag)
382
- RPC_SERVER.served?.(handler, serving, key as string)
158
+ for (const followers of RPC_SERVER.following)
159
+ followers.served(found.handler, serving, invoked.key as string)
383
160
  }
384
161
  // 21.22 — A `GET` THE DEVICE KEEPS, where the worker is there to keep it: a value, not a
385
162
  // refusal (16.40) nor a stream with no end to keep (16.32) — a generator's, or the lines a
@@ -413,17 +190,19 @@ async function answerOutcome(
413
190
  // 18.20 — A READER ASKING FOR AN EVENT STREAM IS FOLLOWED, rather than answered once: the value,
414
191
  // then each change, from the version its `Last-Event-ID` names. A handler that streams answers
415
192
  // its own chunks as it always has (16.45).
193
+ const followers = currentRequestScope()?.app?.followers
416
194
  if (
417
195
  etag !== undefined &&
196
+ followers !== undefined &&
418
197
  !streams(outcome.value) &&
419
198
  prefers(request, 'text/event-stream', 'application/json')
420
199
  ) {
421
200
  const events = handlerHeaders()
422
201
  sseHeaders(events)
423
202
  return new Response(
424
- followAsEvents(
203
+ followers.events(
425
204
  found,
426
- args,
205
+ invoked.args,
427
206
  versionInEtag(request.headers.get('last-event-id')),
428
207
  ),
429
208
  { status: 200, headers: events },
@@ -478,39 +257,6 @@ async function argsOf(
478
257
  return parsed
479
258
  }
480
259
 
481
- /**
482
- * The args a request sent, with the parameters its route matched laid in.
483
- *
484
- * 16.65 — the path's, whatever the method. 16.66 — a parameter the query or the body names as
485
- * well is refused rather than one of the two chosen. A JSON body's members are typed already, so the
486
- * path's text is coerced to the declared types before it joins them (19.7); text args are coerced
487
- * with the rest of the text.
488
- */
489
- function withParameters(
490
- sent: unknown,
491
- params: Record<string, string | string[]>,
492
- text: boolean,
493
- found: Mounted,
494
- ): unknown | Failed<'HttpError', undefined> {
495
- let none = true
496
- for (const name in params) {
497
- none = false
498
- if (isRecord(sent) && name in sent)
499
- return refuse(
500
- 400,
501
- `The parameter '${name}' is in the path and in the request as well.`,
502
- )
503
- }
504
- if (none) return sent
505
- const fromPath = text
506
- ? params
507
- : (coerceArgs(params, found.argsDocument, '') as Record<
508
- string,
509
- unknown
510
- >)
511
- return isRecord(sent) ? { ...sent, ...fromPath } : { ...fromPath }
512
- }
513
-
514
260
  /**
515
261
  * The request's body, received whole, under the ceiling `maxBodySize` sets, or
516
262
  * `ABIDE_MAX_REQUEST_BODY_SIZE` where it is unset, or a 413 refusal past it.