@abide/abide 0.123.3 → 0.124.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.
- package/ABIDE.md +8 -5
- package/CHANGELOG.md +38 -0
- package/docs/REGISTRY.md +45 -12
- package/docs/RULEBOOK.md +29 -16
- package/docs/abide.md +999 -459
- package/package.json +1 -1
- package/src/compiler/ARRAY_MUTATORS.ts +20 -0
- package/src/compiler/LoweredFile.ts +609 -0
- package/src/compiler/Lowering.ts +46 -4
- package/src/compiler/SHARED_NAMES.ts +2 -0
- package/src/compiler/ShadowHost.ts +670 -0
- package/src/compiler/ShadowMap.ts +1 -1
- package/src/compiler/TypeSession.ts +71 -1715
- package/src/compiler/appLints.ts +109 -0
- package/src/compiler/checkedShadow.ts +1 -1
- package/src/compiler/classifyRead.ts +16 -1
- package/src/compiler/compileApp.ts +2 -2
- package/src/compiler/declaredExports.ts +44 -17
- package/src/compiler/diagnoseSources.ts +232 -0
- package/src/compiler/formatAbide.ts +5 -6
- package/src/compiler/handlersUnder.ts +16 -35
- package/src/compiler/projectShadow.ts +1 -0
- package/src/compiler/reactiveReads.ts +235 -0
- package/src/compiler/reactiveTypes.ts +13 -0
- package/src/compiler/readsIn.ts +180 -81
- package/src/compiler/routesDeclaration.ts +46 -0
- package/src/compiler/scaffold.ts +7 -2
- package/src/compiler/serveLanguage.ts +26 -20
- package/src/compiler/splitImports.ts +2 -2
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/DesktopSession.ts +213 -0
- package/src/server/Followers.ts +399 -0
- package/src/server/MODULE_QUERIES.ts +6 -2
- package/src/server/PageRender.ts +201 -0
- package/src/server/abideModules.ts +7 -7
- package/src/server/answerFailure.ts +3 -2
- package/src/server/answerRequest.ts +6 -70
- package/src/server/answerRpc.ts +48 -260
- package/src/server/answerSocket.ts +41 -463
- package/src/server/appKit.ts +1 -1
- package/src/server/argsContract.ts +1 -1
- package/src/server/createApp.ts +8 -2
- package/src/server/csp.ts +1 -1
- package/src/server/deferredBuilds.ts +3 -16
- package/src/server/faceOf.ts +1 -1
- package/src/server/generatedSurfaces.ts +2 -1
- package/src/server/handlerFileMount.ts +1 -1
- package/src/server/index.ts +1 -1
- package/src/server/installServer.ts +5 -9
- package/src/server/invokeHandler.ts +230 -0
- package/src/server/mountHandlers.ts +184 -0
- package/src/server/openDesktopWindow.ts +308 -0
- package/src/server/preload.ts +18 -0
- package/src/server/principal.ts +1 -1
- package/src/server/remote.ts +1 -1
- package/src/server/render.ts +31 -59
- package/src/server/requestScope.ts +5 -36
- package/src/server/runDesktop.ts +19 -447
- package/src/server/seedBuffer.ts +11 -11
- package/src/server/servedApp.ts +1 -1
- package/src/server/shell.ts +1 -1
- package/src/server/surfaceOf.ts +1 -1
- package/src/server/versionOf.ts +1 -1
- package/src/shared/RPC_SERVER.ts +27 -31
- package/src/shared/Schema.ts +1 -2
- package/src/shared/index.ts +1 -1
- package/src/shared/{note.ts → issueReport.ts} +22 -0
- package/src/shared/loadCall.ts +66 -0
- package/src/shared/reactive/Keyed.ts +23 -15
- package/src/shared/reactive/Reactive.ts +46 -1002
- package/src/shared/reactive/byOf.ts +2 -5
- package/src/shared/reactive/callLoad.ts +8 -14
- package/src/shared/reactive/cellShape.ts +734 -0
- package/src/shared/reactive/index.ts +12 -24
- package/src/shared/reactive/pairReactive.ts +5 -3
- package/src/shared/reactive/producer.ts +121 -0
- package/src/shared/reactive/scoped.ts +3 -2
- package/src/shared/reactive/selections.ts +209 -0
- package/src/shared/reactive/watch.ts +3 -4
- package/src/shared/rpc.ts +8 -5
- package/src/shared/validateJson.ts +1 -2
- package/src/shared/writePath.ts +29 -0
- package/src/ui/Router.ts +53 -69
- package/src/ui/outboxQueue.ts +2 -8
- package/src/ui/outcomeOf.ts +120 -0
- package/src/ui/socketWire.ts +3 -3
- package/src/ui/wire.ts +9 -10
- package/src/shared/LOAD_OFFLINE.ts +0 -15
- package/src/shared/LOAD_SIGNAL.ts +0 -10
- package/src/shared/issuesOf.ts +0 -18
- package/src/shared/reactive/ENTRY_LIVES.ts +0 -7
|
@@ -16,10 +16,7 @@ import {
|
|
|
16
16
|
} from '#shared/ADDRESSES.ts'
|
|
17
17
|
import { decodedOrKept } from '#shared/decodedOrKept.ts'
|
|
18
18
|
import { isFailed } from '#shared/Failed.ts'
|
|
19
|
-
import { HandlerRoute } from '#shared/HandlerRoute.ts'
|
|
20
19
|
import { health } from '#shared/health.ts'
|
|
21
|
-
import { KEEP_HEADER } from '#shared/KEEP_HEADER.ts'
|
|
22
|
-
import { KEEP_OPENED } from '#shared/KEEP_OPENED.ts'
|
|
23
20
|
import { matchOf, type Params, partsOf } from '#shared/matchRoute.ts'
|
|
24
21
|
import { compose } from '#shared/Middleware.ts'
|
|
25
22
|
import { mounted } from '#shared/mount.ts'
|
|
@@ -27,35 +24,31 @@ import { NAVIGATION_HEADER } from '#shared/NAVIGATION_HEADER.ts'
|
|
|
27
24
|
import { notFound as refusedNotFound, refuse } from '#shared/refuse.ts'
|
|
28
25
|
import { arrive } from '#shared/route.ts'
|
|
29
26
|
import type { Segment } from '#shared/routePattern.ts'
|
|
30
|
-
import type { Method
|
|
31
|
-
import { rpcRequest } from '#shared/rpcRequest.ts'
|
|
27
|
+
import type { Method } from '#shared/rpc.ts'
|
|
32
28
|
|
|
33
29
|
import { answerFailure, laneFailure } from './answerFailure.ts'
|
|
34
|
-
import { answerRpc
|
|
30
|
+
import { answerRpc } from './answerRpc.ts'
|
|
35
31
|
import { answerBlobTagged, answerValue } from './answerValue.ts'
|
|
36
32
|
import { answerBridge } from './bridgeRelay.ts'
|
|
37
|
-
import { callerOrMint } from './callerOf.ts'
|
|
38
33
|
import { compressAnswer } from './compressAnswer.ts'
|
|
39
|
-
import { noteCallInRender } from './deferredBuilds.ts'
|
|
40
34
|
import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
|
|
41
35
|
import { answerMcp, openApi } from './generatedSurfaces.ts'
|
|
42
36
|
import { failureOf, type LaneRung } from './hooks.ts'
|
|
37
|
+
import { type Mounted, mountedOwn } from './mountHandlers.ts'
|
|
43
38
|
import { muxAdmission } from './muxUpgrade.ts'
|
|
44
39
|
import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
|
|
40
|
+
import { beginPageRender } from './PageRender.ts'
|
|
45
41
|
import { plainAnswer } from './plainAnswer.ts'
|
|
46
42
|
import { PRINCIPAL_MOUNT } from './principal.ts'
|
|
47
43
|
import { boundPage, type Component, renderDocument } from './render.ts'
|
|
48
44
|
import {
|
|
49
45
|
adoptRequest,
|
|
50
46
|
type AnsweringApp,
|
|
51
|
-
beginRender,
|
|
52
47
|
currentRequestScope,
|
|
53
|
-
type PageRender,
|
|
54
48
|
type RequestScope,
|
|
55
49
|
within,
|
|
56
50
|
writtenStatus,
|
|
57
51
|
} from './requestScope.ts'
|
|
58
|
-
import { callsOnly, seedFor } from './seedBuffer.ts'
|
|
59
52
|
|
|
60
53
|
// One row of the server's table the compiler writes (`SERVER_TABLE`). The loader is a dynamic import
|
|
61
54
|
// so a page's markup is off the boot path until an address asks for it, which is the same reason
|
|
@@ -146,9 +139,6 @@ export type AppSources = {
|
|
|
146
139
|
explained: ((thrown: unknown) => Response) | undefined
|
|
147
140
|
}
|
|
148
141
|
|
|
149
|
-
// 41.29 — the seed sink of a render no page hydrates: every call is left to run when it is made.
|
|
150
|
-
const SEEDS_NOTHING: PageRender['seed'] = () => false
|
|
151
|
-
|
|
152
142
|
// 35.19 — what fetch calls a navigation request's destinations, and a script's `fetch`.
|
|
153
143
|
const PAGE_DESTINATIONS = new Set([
|
|
154
144
|
'document',
|
|
@@ -336,9 +326,6 @@ async function answerMatched(
|
|
|
336
326
|
// minted for a request that never becomes a page.
|
|
337
327
|
if (request.method === 'HEAD')
|
|
338
328
|
return new Response(null, { status: writtenStatus(), headers })
|
|
339
|
-
// 26.5 — the caller the seed buffer is keyed by, minted where the browser carries none: the same
|
|
340
|
-
// one `principal.caller` reads for the rest of the request.
|
|
341
|
-
const caller = callerOrMint()
|
|
342
329
|
// 23.1 — WHERE THIS PAGE IS, for its setup to read, in the request's own instances (11.32).
|
|
343
330
|
// 16.10 — AT THE ADDRESS IT IS SERVED AT, which is the one the browser's router arrives at: the
|
|
344
331
|
// request reached here with the mount taken off, and a page reading `route.url()` on both sides
|
|
@@ -347,23 +334,8 @@ async function answerMatched(
|
|
|
347
334
|
at.pathname = mounted(at.pathname)
|
|
348
335
|
arrive(at.href, page.pattern, params)
|
|
349
336
|
// 11.32 — the page renders in a scope of its own, so what its values hold is this request's —
|
|
350
|
-
// and 41.13, every call it makes is
|
|
351
|
-
|
|
352
|
-
// next call this caller makes with what the render saw, a sign-in since included — save the one
|
|
353
|
-
// the worker fetched for the navigation it answers, which is the page that hydrates (45.23).
|
|
354
|
-
const keep = request.headers.get(KEEP_HEADER)
|
|
355
|
-
const hydrated =
|
|
356
|
-
keep === KEEP_OPENED ||
|
|
357
|
-
request.headers.get('sec-fetch-dest') !== 'empty'
|
|
358
|
-
// 41.30 — SAVE ONE THE WORKER KEEPS, whose early calls are what a later open of it starts.
|
|
359
|
-
beginRender({
|
|
360
|
-
seed: hydrated
|
|
361
|
-
? seedFor(caller)
|
|
362
|
-
: keep !== null
|
|
363
|
-
? callsOnly()
|
|
364
|
-
: SEEDS_NOTHING,
|
|
365
|
-
calls: [],
|
|
366
|
-
})
|
|
337
|
+
// and 41.13, every call it makes is recorded into its render.
|
|
338
|
+
beginPageRender(request)
|
|
367
339
|
// 41.1, 35.2 — THE DOCUMENT IS THE BUILT PAGE, read whole before there is a `Response` to commit a
|
|
368
340
|
// status to: a refusal the page read, or a throw, is answered by the nearest error page at its own
|
|
369
341
|
// status rather than as a 200 the stream then abandons.
|
|
@@ -411,42 +383,6 @@ const SURFACES = new Map<
|
|
|
411
383
|
[BRIDGE_ADDRESS, (_app, request) => answerBridge(request)],
|
|
412
384
|
])
|
|
413
385
|
|
|
414
|
-
// Each handler's route, compiled once rather than per render that calls it — as its `Rpc` keeps its
|
|
415
|
-
// own; compiled again only where it was mounted at another address since.
|
|
416
|
-
const ROUTE_OF = new WeakMap<object, HandlerRoute>()
|
|
417
|
-
|
|
418
|
-
// What a call made in a page's render reports into: the seed buffer (41.13), and — where the handler
|
|
419
|
-
// is mounted — the request the browser will make for it, for the early-call script (41.20). Once per
|
|
420
|
-
// render, as the browser's `Rpc` makes it once, and the seed buffer is what says which is the once.
|
|
421
|
-
export function reachedInRender(
|
|
422
|
-
rpc: object,
|
|
423
|
-
args: unknown,
|
|
424
|
-
reached: unknown,
|
|
425
|
-
): void {
|
|
426
|
-
const page = currentRequestScope()?.page
|
|
427
|
-
if (page === undefined) return
|
|
428
|
-
noteCallInRender()
|
|
429
|
-
if (!page.seed(rpc, args, reached)) return
|
|
430
|
-
// 16.10 — the address the browser's own call is made at, stamped under the mount by
|
|
431
|
-
// `mountHandlers` — and `undefined` on a handler nothing mounted.
|
|
432
|
-
const { address, method } = rpc as Rpc<unknown, unknown>
|
|
433
|
-
if (address === undefined) return
|
|
434
|
-
let route = ROUTE_OF.get(rpc)
|
|
435
|
-
if (route?.address !== address) {
|
|
436
|
-
route = new HandlerRoute(address)
|
|
437
|
-
ROUTE_OF.set(rpc, route)
|
|
438
|
-
}
|
|
439
|
-
page.calls.push({
|
|
440
|
-
request: rpcRequest(
|
|
441
|
-
method,
|
|
442
|
-
route,
|
|
443
|
-
args as Record<string, unknown> | undefined,
|
|
444
|
-
),
|
|
445
|
-
reached,
|
|
446
|
-
painted: undefined,
|
|
447
|
-
})
|
|
448
|
-
}
|
|
449
|
-
|
|
450
386
|
/**
|
|
451
387
|
* The server's table the compiler writes (`SERVER_TABLE`): the routes and the error pages.
|
|
452
388
|
*
|
package/src/server/answerRpc.ts
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
|
-
// 16.2 — A HANDLER, ANSWERED OVER HTTP. The call is
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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 `
|
|
6
|
-
//
|
|
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
|
|
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 { 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,184 +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 {
|
|
33
|
-
import { callerOf } from './callerOf.ts'
|
|
25
|
+
import { collapsed } from './argsContract.ts'
|
|
34
26
|
import { positiveSetting } from './config.ts'
|
|
35
|
-
import {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
crossingRefusal,
|
|
40
|
-
} from './crossingOrigin.ts'
|
|
41
|
-
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'
|
|
42
31
|
import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
|
|
43
32
|
import { prefers } from './prefers.ts'
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
rungsTook,
|
|
47
|
-
timed,
|
|
48
|
-
unreap,
|
|
49
|
-
writtenStatus,
|
|
50
|
-
} from './requestScope.ts'
|
|
51
|
-
import { type Outcome, outcomeOf, takeSeed } from './seedBuffer.ts'
|
|
33
|
+
import { currentRequestScope, unreap, writtenStatus } from './requestScope.ts'
|
|
34
|
+
import type { Outcome } from './seedBuffer.ts'
|
|
52
35
|
import { versionOf } from './versionOf.ts'
|
|
53
36
|
|
|
54
|
-
/**
|
|
55
|
-
* One row of the handler table the build writes into the server's table (`SERVER_TABLE`): where a
|
|
56
|
-
* handler answers, and its module.
|
|
57
|
-
*
|
|
58
|
-
* The module is relative to the app's root, which both commands that serve already know.
|
|
59
|
-
*/
|
|
60
|
-
export type Mount = {
|
|
61
|
-
address: string
|
|
62
|
-
method: Method
|
|
63
|
-
source: string
|
|
64
|
-
/** The handler file's name, or the private handler's binding. */
|
|
65
|
-
name: string
|
|
66
|
-
/** What the source's module exports it as: `default` for a handler file. */
|
|
67
|
-
export: string
|
|
68
|
-
/** The route its hooks are filed at, which its rungs are read over (37.1, 16.72). */
|
|
69
|
-
scope: string
|
|
70
|
-
/** 23.26 — each matcher its address names, to the anchored source its row is ranked with. */
|
|
71
|
-
matchers: Record<string, string>
|
|
72
|
-
/** Whether it is a private handler, listed nowhere (16.71). */
|
|
73
|
-
private: boolean
|
|
74
|
-
/** Whether a browser is handed a wrapper for it. 20.4. */
|
|
75
|
-
ui: boolean
|
|
76
|
-
/** Whether the command line lists it — the shell's listing and completion, never whether `call` reaches it. 20.3, 38.62. */
|
|
77
|
-
cli: boolean
|
|
78
|
-
/**
|
|
79
|
-
* Whether MCP lists it where its runtime `clients` does not say.
|
|
80
|
-
*
|
|
81
|
-
* 16.37 — false for a handler typed as returning a `Response`, which only the build can tell.
|
|
82
|
-
*/
|
|
83
|
-
mcp: boolean
|
|
84
|
-
/** Its `description`, where the declaration writes one as a literal the build can read. */
|
|
85
|
-
description: string | undefined
|
|
86
|
-
/**
|
|
87
|
-
* What a `GET`'s value declares that the browser's copy behaves by, or `undefined`.
|
|
88
|
-
*
|
|
89
|
-
* 18.4 — set where its declaration names any of it.
|
|
90
|
-
*/
|
|
91
|
-
copy: CopyOptions | undefined
|
|
92
|
-
/**
|
|
93
|
-
* The defaults the handler's parameter writes, for the browser's copy to key by.
|
|
94
|
-
*
|
|
95
|
-
* 11.29 — the server reads its own off the handler.
|
|
96
|
-
*/
|
|
97
|
-
defaults: Defaults | undefined
|
|
98
|
-
/** The JSON Schema the handler's args annotation derives, where it takes a record. 19.9. */
|
|
99
|
-
args: JsonValue | undefined
|
|
100
|
-
/** The JSON Schema of what it answers, where its type says one. 19.21. */
|
|
101
|
-
result: JsonValue | undefined
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* A row of the table the build wrote, with the import of the module its handler is declared in —
|
|
106
|
-
* the handler file itself, or a private handler's `.abide` server module, the one the route table
|
|
107
|
-
* loads too, so a page and a call reach one handler.
|
|
108
|
-
*/
|
|
109
|
-
export type MountRow = Mount & { load: () => Promise<Record<string, unknown>> }
|
|
110
|
-
|
|
111
|
-
/** A handler's row, mounted: the handler imported and its args schema probed. */
|
|
112
|
-
export type Mounted = Mount & {
|
|
113
|
-
/**
|
|
114
|
-
* The rungs filed over `scope`, read once at mount (16.14) — what an http call and a socket
|
|
115
|
-
* subscription to it both pass through (37.2, 18.26).
|
|
116
|
-
*/
|
|
117
|
-
rungs: LaneRung[]
|
|
118
|
-
handler: Rpc<unknown, unknown> & Gated<unknown> & HeldToAnnotation
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* The row of a handler abide mounts itself, keyed as `answerRpc` looks one up.
|
|
123
|
-
*
|
|
124
|
-
* Built on the core's `declare` with no generated surface (20.1) — the app's tree never names it.
|
|
125
|
-
*/
|
|
126
|
-
export function mountedOwn(
|
|
127
|
-
address: string,
|
|
128
|
-
name: string,
|
|
129
|
-
handler: object,
|
|
130
|
-
): [string, Mounted] {
|
|
131
|
-
// 16.10 — READ WHEN IT IS REPORTED, where a mounted handler's is stamped at boot: the mount is
|
|
132
|
-
// the configuration's, and nothing boots this handler.
|
|
133
|
-
Object.defineProperty(handler, 'address', {
|
|
134
|
-
get: () => underMount(address),
|
|
135
|
-
})
|
|
136
|
-
return [
|
|
137
|
-
address,
|
|
138
|
-
{
|
|
139
|
-
address,
|
|
140
|
-
method: 'GET',
|
|
141
|
-
// The root's rungs run before routing (37.3), and nothing is filed below a route
|
|
142
|
-
// of abide's own.
|
|
143
|
-
rungs: [],
|
|
144
|
-
source: '',
|
|
145
|
-
name,
|
|
146
|
-
export: name,
|
|
147
|
-
scope: '/',
|
|
148
|
-
matchers: {},
|
|
149
|
-
private: false,
|
|
150
|
-
ui: false,
|
|
151
|
-
cli: false,
|
|
152
|
-
mcp: false,
|
|
153
|
-
description: undefined,
|
|
154
|
-
copy: undefined,
|
|
155
|
-
defaults: undefined,
|
|
156
|
-
args: undefined,
|
|
157
|
-
result: undefined,
|
|
158
|
-
handler: handler as Mounted['handler'],
|
|
159
|
-
},
|
|
160
|
-
]
|
|
161
|
-
}
|
|
162
|
-
|
|
163
37
|
// 16.58 — the environment's `timeout`, for every call a handler that names none makes.
|
|
164
38
|
export function environmentTimeout(): number | undefined {
|
|
165
39
|
return positiveSetting('ABIDE_RPC_TIMEOUT', undefined)
|
|
166
40
|
}
|
|
167
41
|
|
|
168
|
-
/**
|
|
169
|
-
* The table's handlers, imported and keyed by method and address.
|
|
170
|
-
*
|
|
171
|
-
* Imported through each row's own loader, which the route table shares, so a page rendering on the
|
|
172
|
-
* server and a call over the wire reach one module and so one handler. AT BOOT rather than on the
|
|
173
|
-
* first call, because a page's render reaches a handler through the page's own import and has to
|
|
174
|
-
* know its address before anything has called it over the wire.
|
|
175
|
-
*/
|
|
176
|
-
export async function mountHandlers(
|
|
177
|
-
table: MountRow[],
|
|
178
|
-
): Promise<{ handlers: Map<string, Mounted> }> {
|
|
179
|
-
const modules = await Promise.all(table.map((mount) => mount.load()))
|
|
180
|
-
const handlers = new Map<string, Mounted>()
|
|
181
|
-
for (let at = 0; at < table.length; at += 1) {
|
|
182
|
-
const mount = table[at] as Mount
|
|
183
|
-
const exported = (modules[at] as Record<string, unknown>)[mount.export]
|
|
184
|
-
const handler = exported as Mounted['handler']
|
|
185
|
-
// 16.9, 16.10 — the address `rpc.url` and `rpc.raw` build on, under the mount — and what a
|
|
186
|
-
// page's render turns a call it made into the browser's request by (41.20).
|
|
187
|
-
handler.address = underMount(mount.address)
|
|
188
|
-
// 19.9 — the schema the build derived off its annotation, for a handler whose module did not
|
|
189
|
-
// file it on load: a bundled executable's faces carry no `derivedArgs` call.
|
|
190
|
-
if (mount.args !== undefined) derivedArgs(handler, mount.args)
|
|
191
|
-
handlers.set(handlerKey(mount.method, mount.address), {
|
|
192
|
-
...mount,
|
|
193
|
-
// 37.1, 16.72 — the route its file's hooks are filed at, which the build read off
|
|
194
|
-
// where the file answers (23.19's mount for a package's), never its place on disk.
|
|
195
|
-
// 37.14 — a handler's lane answers a refusal as Problem Details (15.1).
|
|
196
|
-
rungs: rungsOver(mount.scope, answered),
|
|
197
|
-
handler,
|
|
198
|
-
})
|
|
199
|
-
}
|
|
200
|
-
return { handlers }
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/** What a handler is keyed by among the mounted: its method and its route. 19.1. */
|
|
204
|
-
export function handlerKey(method: string, address: string): string {
|
|
205
|
-
return `${method} ${address}`
|
|
206
|
-
}
|
|
207
|
-
|
|
208
42
|
/**
|
|
209
43
|
* The `Response` to a call of `found` made over http, `params` being what its route matched.
|
|
210
44
|
*
|
|
@@ -228,92 +62,46 @@ export async function answerRpc(
|
|
|
228
62
|
return origin !== null && allows(found.handler.crossOrigin, origin)
|
|
229
63
|
? preflight(found, origin, request)
|
|
230
64
|
: answered(notFound({ path: pathname, method: request.method }))
|
|
231
|
-
// 16.50 — a `HEAD` is the `GET`'s answer, which the server sends without its body.
|
|
232
|
-
const head = request.method === 'HEAD'
|
|
233
|
-
const handler = found.handler
|
|
234
65
|
const inUrl = argsInURL(found.method)
|
|
235
66
|
const form = isForm(request)
|
|
236
|
-
const crossing = crossingOrigin(origin, url)
|
|
237
|
-
const closed = closedTo(handler.crossOrigin, crossing)
|
|
238
67
|
// 21.19 — the origin the answer admits, where the handler declared it may read.
|
|
239
|
-
const admitted =
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
+
)
|
|
244
81
|
if (isFailed(raw)) return answered(raw)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
// 41.7 — the call a hydrating page makes for what its render already reached is answered from
|
|
259
|
-
// that, and the handler is not run again (41.13).
|
|
260
|
-
// 16.51 — a `HEAD` leaves the seed for the `GET` it stands in front of.
|
|
261
|
-
const caller = head ? undefined : callerOf()
|
|
262
|
-
const seeded =
|
|
263
|
-
caller === undefined ? undefined : takeSeed(caller, handler, args)
|
|
264
|
-
// The entry key of a `global` value, taken once for every read of it below.
|
|
265
|
-
const global = GLOBAL_GETS.get(handler)
|
|
266
|
-
const key = global?.key(args)
|
|
267
|
-
// 21.15 — A `global` PRODUCTION ALREADY HELD, and not loading, answers without a run: a run
|
|
268
|
-
// this call started moves the generation, and one it joined was loading when it came.
|
|
269
|
-
const held =
|
|
270
|
-
seeded === undefined && key !== undefined ? global?.at(key) : undefined
|
|
271
|
-
const heldAt = held === undefined ? undefined : heldGenerationOf(held)
|
|
272
|
-
const timing = currentRequestScope()?.timing
|
|
273
|
-
if (timing !== undefined) timing.answering = handler
|
|
274
|
-
const started = performance.now()
|
|
275
|
-
const outcome =
|
|
276
|
-
seeded === undefined ? await outcomeOf(handler(args)) : await seeded
|
|
277
|
-
// The entry as the run left it, which the answer reads too.
|
|
278
|
-
const entry = key === undefined ? undefined : global?.at(key)
|
|
279
|
-
const cached =
|
|
280
|
-
heldAt !== undefined &&
|
|
281
|
-
entry === held &&
|
|
282
|
-
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
|
|
283
94
|
// 16.38 — A MUTATION A DOCUMENT MADE — a form posted with no script — is answered as a navigation.
|
|
284
95
|
if (!inUrl && prefers(request, 'text/html', 'application/json'))
|
|
285
96
|
return navigated(outcome, request, pathname, pages)
|
|
286
|
-
const answer = await answerOutcome(
|
|
287
|
-
outcome,
|
|
288
|
-
request,
|
|
289
|
-
found,
|
|
290
|
-
args,
|
|
291
|
-
key,
|
|
292
|
-
entry,
|
|
293
|
-
offline,
|
|
294
|
-
)
|
|
97
|
+
const answer = await answerOutcome(invoked, request, found, offline)
|
|
295
98
|
// A `Response` the handler built is its own — but the origin answer below is laid over it, so it
|
|
296
99
|
// varies on `Origin` too (21.7), which `handlerHeaders` never reached.
|
|
297
100
|
if (outcome.settled && outcome.value instanceof Response) {
|
|
298
101
|
answer.headers.append('vary', 'origin')
|
|
299
102
|
return admitting(answer, admitted)
|
|
300
103
|
}
|
|
301
|
-
|
|
302
|
-
// already names the handler, so the metric names the run. A stream's time is to its first
|
|
303
|
-
// chunk, which the answer's head waited for.
|
|
304
|
-
const took = outcome.duration ?? performance.now() - started
|
|
305
|
-
const run =
|
|
306
|
-
seeded !== undefined
|
|
307
|
-
? 'Seeded'
|
|
308
|
-
: cached
|
|
309
|
-
? 'Cached'
|
|
310
|
-
: outcome.duration === undefined
|
|
311
|
-
? 'Started'
|
|
312
|
-
: 'Direct'
|
|
313
|
-
// 21.20 — what the rungs took is theirs, and the handler's own body is what `Direct` reports.
|
|
314
|
-
const handled = run === 'Direct' ? timing?.handled : undefined
|
|
315
|
-
if (handled !== undefined) rungsTook(took - handled)
|
|
316
|
-
timed(timing, run, handled ?? took)
|
|
104
|
+
invoked.timed()
|
|
317
105
|
return admitting(answer, admitted)
|
|
318
106
|
}
|
|
319
107
|
|
|
@@ -335,15 +123,12 @@ function admitting(answer: Response, admitted: string | null): Response {
|
|
|
335
123
|
}
|
|
336
124
|
|
|
337
125
|
async function answerOutcome(
|
|
338
|
-
|
|
126
|
+
invoked: Invocation,
|
|
339
127
|
request: Request,
|
|
340
128
|
found: Mounted,
|
|
341
|
-
args: unknown,
|
|
342
|
-
key: string | undefined,
|
|
343
|
-
entry: unknown,
|
|
344
129
|
offline: boolean,
|
|
345
130
|
): Promise<Response> {
|
|
346
|
-
const
|
|
131
|
+
const { outcome, entry } = invoked
|
|
347
132
|
if (!outcome.settled) return answered(failureOf(outcome.error))
|
|
348
133
|
const value = outcome.value
|
|
349
134
|
// The `global` value the answer came from, or `undefined` for one built per request. 18.22 — or
|
|
@@ -370,7 +155,8 @@ async function answerOutcome(
|
|
|
370
155
|
const etag = serving !== undefined ? `"${versionOf(serving)}"` : undefined
|
|
371
156
|
if (etag !== undefined) {
|
|
372
157
|
headers.set(VERSION_HEADER, etag)
|
|
373
|
-
|
|
158
|
+
for (const followers of RPC_SERVER.following)
|
|
159
|
+
followers.served(found.handler, serving, invoked.key as string)
|
|
374
160
|
}
|
|
375
161
|
// 21.22 — A `GET` THE DEVICE KEEPS, where the worker is there to keep it: a value, not a
|
|
376
162
|
// refusal (16.40) nor a stream with no end to keep (16.32) — a generator's, or the lines a
|
|
@@ -404,17 +190,19 @@ async function answerOutcome(
|
|
|
404
190
|
// 18.20 — A READER ASKING FOR AN EVENT STREAM IS FOLLOWED, rather than answered once: the value,
|
|
405
191
|
// then each change, from the version its `Last-Event-ID` names. A handler that streams answers
|
|
406
192
|
// its own chunks as it always has (16.45).
|
|
193
|
+
const followers = currentRequestScope()?.app?.followers
|
|
407
194
|
if (
|
|
408
195
|
etag !== undefined &&
|
|
196
|
+
followers !== undefined &&
|
|
409
197
|
!streams(outcome.value) &&
|
|
410
198
|
prefers(request, 'text/event-stream', 'application/json')
|
|
411
199
|
) {
|
|
412
200
|
const events = handlerHeaders()
|
|
413
201
|
sseHeaders(events)
|
|
414
202
|
return new Response(
|
|
415
|
-
|
|
203
|
+
followers.events(
|
|
416
204
|
found,
|
|
417
|
-
args,
|
|
205
|
+
invoked.args,
|
|
418
206
|
versionInEtag(request.headers.get('last-event-id')),
|
|
419
207
|
),
|
|
420
208
|
{ status: 200, headers: events },
|