@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.
- package/ABIDE.md +8 -5
- package/CHANGELOG.md +58 -0
- package/bin/abide.ts +36 -65
- package/docs/REGISTRY.md +45 -12
- package/docs/RULEBOOK.md +24 -14
- package/docs/abide.md +999 -459
- package/package.json +1 -1
- package/src/compiler/ARRAY_MUTATORS.ts +20 -0
- package/src/compiler/Diagnostics.ts +198 -0
- package/src/compiler/KeptChecker.ts +97 -0
- package/src/compiler/LoweredFile.ts +609 -0
- package/src/compiler/Lowering.ts +18 -1
- package/src/compiler/PositionedRefusal.ts +1 -1
- 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 +76 -1743
- package/src/compiler/appLayout.ts +28 -20
- package/src/compiler/appLints.ts +109 -0
- package/src/compiler/argsDefaultsIn.ts +1 -1
- package/src/compiler/checkApp.ts +64 -68
- package/src/compiler/checkedShadow.ts +2 -2
- package/src/compiler/classifyRead.ts +16 -1
- package/src/compiler/compileApp.ts +102 -71
- package/src/compiler/declaredExports.ts +46 -21
- package/src/compiler/diagnoseSources.ts +232 -0
- package/src/compiler/emitClient.ts +1 -1
- package/src/compiler/formatAbide.ts +7 -8
- package/src/compiler/handlersUnder.ts +32 -63
- package/src/compiler/lineOf.ts +0 -19
- package/src/compiler/matchersOf.ts +3 -3
- package/src/compiler/mountsOf.ts +3 -3
- package/src/compiler/reactiveReads.ts +235 -0
- package/src/compiler/reactiveTypes.ts +13 -0
- package/src/compiler/readSources.ts +16 -18
- package/src/compiler/readsIn.ts +181 -82
- package/src/compiler/routesDeclaration.ts +46 -0
- package/src/compiler/scaffold.ts +7 -2
- package/src/compiler/serveLanguage.ts +46 -74
- package/src/compiler/sharedModuleWrites.ts +1 -1
- package/src/compiler/splitImports.ts +2 -2
- package/src/compiler/templateWarnings.ts +1 -2
- package/src/compiler/trackingWarnings.ts +1 -1
- package/src/server/CLIENT_BUILD.ts +4 -32
- 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/RESUME_MILLISECONDS.ts +2 -0
- package/src/server/abideModules.ts +7 -7
- package/src/server/answerFailure.ts +3 -2
- package/src/server/answerRequest.ts +16 -369
- package/src/server/answerRpc.ts +48 -302
- package/src/server/answerSocket.ts +41 -490
- package/src/server/appBuild.ts +377 -0
- package/src/server/appKit.ts +1 -1
- package/src/server/appPlugins.ts +15 -17
- package/src/server/appTree.ts +6 -5
- package/src/server/argsContract.ts +209 -0
- package/src/server/bridgeRelay.ts +153 -65
- package/src/server/claudeCode.ts +30 -127
- package/src/server/config.ts +6 -7
- package/src/server/createApp.ts +38 -66
- package/src/server/csp.ts +1 -1
- package/src/server/deferredBuilds.ts +3 -16
- package/src/server/devWorker.ts +11 -15
- package/src/server/faceOf.ts +1 -1
- package/src/server/generatedSurfaces.ts +2 -1
- package/src/server/handlerFileMount.ts +40 -0
- package/src/server/index.ts +3 -3
- 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/packageDirectory.ts +14 -0
- package/src/server/preload.ts +21 -2
- package/src/server/principal.ts +5 -4
- package/src/server/remote.ts +2 -2
- package/src/server/render.ts +31 -59
- package/src/server/requestScope.ts +5 -36
- package/src/server/runCompiled.ts +1 -1
- package/src/server/runDesktop.ts +20 -448
- package/src/server/seedBuffer.ts +12 -12
- package/src/server/serveDev.ts +24 -58
- package/src/server/servedApp.ts +390 -0
- package/src/server/shell.ts +1 -1
- package/src/server/signInChanged.ts +20 -0
- package/src/server/surfaceOf.ts +3 -2
- package/src/server/versionOf.ts +7 -16
- package/src/shared/RPC_SERVER.ts +27 -31
- package/src/shared/Schema.ts +1 -2
- package/src/shared/applyPatch.ts +1 -1
- 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/muxFrames.ts +1 -1
- package/src/shared/reactive/Keyed.ts +24 -16
- package/src/shared/reactive/Reactive.ts +37 -991
- package/src/shared/reactive/argsContract.ts +131 -4
- 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 -23
- package/src/shared/reactive/pairReactive.ts +5 -3
- package/src/shared/reactive/patchLog.ts +79 -0
- package/src/shared/reactive/patchMarks.ts +34 -0
- package/src/shared/reactive/producer.ts +121 -0
- package/src/shared/reactive/recordWrites.ts +11 -14
- package/src/shared/reactive/scoped.ts +3 -2
- package/src/shared/reactive/selections.ts +209 -0
- package/src/shared/reactive/share.ts +4 -3
- package/src/shared/reactive/watch.ts +3 -4
- package/src/shared/rpc.ts +20 -24
- package/src/shared/validateJson.ts +1 -2
- package/src/shared/writePath.ts +29 -0
- package/src/ui/ArmRegion.ts +10 -4
- package/src/ui/Level.ts +11 -20
- package/src/ui/LevelChain.ts +272 -0
- package/src/ui/Router.ts +106 -313
- package/src/ui/Scope.ts +17 -3
- package/src/ui/TryRegion.ts +22 -23
- package/src/ui/bindSpread.ts +2 -4
- package/src/ui/listenForRoutes.ts +3 -10
- package/src/ui/outboxQueue.ts +23 -11
- package/src/ui/outcomeOf.ts +120 -0
- package/src/ui/reconcile.ts +4 -6
- package/src/ui/routing.ts +2 -1
- package/src/ui/rows.ts +2 -2
- package/src/ui/setAttribute.ts +12 -3
- package/src/ui/socketWire.ts +28 -13
- package/src/ui/wire.ts +9 -10
- package/src/compiler/Finding.ts +0 -2
- package/src/compiler/Refusals.ts +0 -65
- package/src/compiler/formatDiagnosis.ts +0 -20
- package/src/server/appDirectories.ts +0 -9
- package/src/server/argsFor.ts +0 -39
- package/src/server/buildDirectory.ts +0 -10
- package/src/server/coerceArgs.ts +0 -109
- package/src/server/derivedArgs.ts +0 -26
- package/src/server/runBuild.ts +0 -24
- package/src/server/serveBuild.ts +0 -144
- package/src/server/testBuild.ts +0 -220
- 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
- package/src/shared/reactive/FOLLOWED.ts +0 -8
- package/src/shared/reactive/PATCHES.ts +0 -11
- package/src/shared/reactive/PATCH_SINKS.ts +0 -26
- package/src/shared/reactive/canonicalKey.ts +0 -95
|
@@ -8,86 +8,47 @@
|
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
ABIDE_ADDRESS,
|
|
11
|
-
APP_MANIFEST_ADDRESS,
|
|
12
11
|
BRIDGE_ADDRESS,
|
|
13
|
-
CLIENT_ADDRESS,
|
|
14
12
|
HEALTH_ADDRESS,
|
|
15
13
|
MCP_ADDRESS,
|
|
16
14
|
OPENAPI_ADDRESS,
|
|
17
15
|
PRIVATE_HANDLERS_ADDRESS,
|
|
18
|
-
WORKER_ADDRESS,
|
|
19
16
|
} from '#shared/ADDRESSES.ts'
|
|
20
|
-
import { APP_META } from '#shared/APP_META.ts'
|
|
21
|
-
import { DEBUG_META } from '#shared/DEBUG_META.ts'
|
|
22
17
|
import { decodedOrKept } from '#shared/decodedOrKept.ts'
|
|
23
|
-
import { ERROR_META } from '#shared/ERROR_META.ts'
|
|
24
|
-
import { escapeAttribute } from '#shared/escapeText.ts'
|
|
25
18
|
import { isFailed } from '#shared/Failed.ts'
|
|
26
|
-
import { HandlerRoute } from '#shared/HandlerRoute.ts'
|
|
27
19
|
import { health } from '#shared/health.ts'
|
|
28
|
-
import { KEEP_HEADER } from '#shared/KEEP_HEADER.ts'
|
|
29
|
-
import { KEEP_OPENED } from '#shared/KEEP_OPENED.ts'
|
|
30
|
-
import { ICON_ADDRESSES } from '#shared/LAUNCHER_FILES.ts'
|
|
31
|
-
import { appName } from '#shared/log.ts'
|
|
32
20
|
import { matchOf, type Params, partsOf } from '#shared/matchRoute.ts'
|
|
33
21
|
import { compose } from '#shared/Middleware.ts'
|
|
34
|
-
import {
|
|
35
|
-
import { MOUNT_META } from '#shared/MOUNT_META.ts'
|
|
22
|
+
import { mounted } from '#shared/mount.ts'
|
|
36
23
|
import { NAVIGATION_HEADER } from '#shared/NAVIGATION_HEADER.ts'
|
|
37
|
-
import { OFFLINE_META } from '#shared/OFFLINE_META.ts'
|
|
38
24
|
import { notFound as refusedNotFound, refuse } from '#shared/refuse.ts'
|
|
39
25
|
import { arrive } from '#shared/route.ts'
|
|
40
|
-
import {
|
|
41
|
-
|
|
42
|
-
rankRoutes,
|
|
43
|
-
type Segment,
|
|
44
|
-
} from '#shared/routePattern.ts'
|
|
45
|
-
import type { Method, Rpc } from '#shared/rpc.ts'
|
|
46
|
-
import { rpcRequest } from '#shared/rpcRequest.ts'
|
|
26
|
+
import type { Segment } from '#shared/routePattern.ts'
|
|
27
|
+
import type { Method } from '#shared/rpc.ts'
|
|
47
28
|
|
|
48
29
|
import { answerFailure, laneFailure } from './answerFailure.ts'
|
|
49
|
-
import {
|
|
50
|
-
|
|
51
|
-
type Mounted,
|
|
52
|
-
mountedOwn,
|
|
53
|
-
mountHandlers,
|
|
54
|
-
type MountRow,
|
|
55
|
-
} from './answerRpc.ts'
|
|
56
|
-
import { answerBlobTagged, answerValue, contentEtag } from './answerValue.ts'
|
|
57
|
-
import { answerWorker } from './answerWorker.ts'
|
|
58
|
-
import { appManifest } from './appManifest.ts'
|
|
30
|
+
import { answerRpc } from './answerRpc.ts'
|
|
31
|
+
import { answerBlobTagged, answerValue } from './answerValue.ts'
|
|
59
32
|
import { answerBridge } from './bridgeRelay.ts'
|
|
60
|
-
import { callerOrMint } from './callerOf.ts'
|
|
61
|
-
import { CLIENT_ENTRY } from './CLIENT_BUILD.ts'
|
|
62
33
|
import { compressAnswer } from './compressAnswer.ts'
|
|
63
|
-
import { settingOn } from './config.ts'
|
|
64
|
-
import { noteCallInRender } from './deferredBuilds.ts'
|
|
65
34
|
import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
|
|
66
35
|
import { answerMcp, openApi } from './generatedSurfaces.ts'
|
|
67
|
-
import { failureOf, type LaneRung
|
|
68
|
-
import {
|
|
36
|
+
import { failureOf, type LaneRung } from './hooks.ts'
|
|
37
|
+
import { type Mounted, mountedOwn } from './mountHandlers.ts'
|
|
69
38
|
import { muxAdmission } from './muxUpgrade.ts'
|
|
70
39
|
import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
|
|
40
|
+
import { beginPageRender } from './PageRender.ts'
|
|
71
41
|
import { plainAnswer } from './plainAnswer.ts'
|
|
72
42
|
import { PRINCIPAL_MOUNT } from './principal.ts'
|
|
73
|
-
import {
|
|
74
|
-
boundPage,
|
|
75
|
-
type Component,
|
|
76
|
-
DEFAULT_DOCUMENT,
|
|
77
|
-
intoHead,
|
|
78
|
-
renderDocument,
|
|
79
|
-
} from './render.ts'
|
|
43
|
+
import { boundPage, type Component, renderDocument } from './render.ts'
|
|
80
44
|
import {
|
|
81
45
|
adoptRequest,
|
|
82
46
|
type AnsweringApp,
|
|
83
|
-
beginRender,
|
|
84
47
|
currentRequestScope,
|
|
85
|
-
type PageRender,
|
|
86
48
|
type RequestScope,
|
|
87
49
|
within,
|
|
88
50
|
writtenStatus,
|
|
89
51
|
} from './requestScope.ts'
|
|
90
|
-
import { callsOnly, seedFor } from './seedBuffer.ts'
|
|
91
52
|
|
|
92
53
|
// One row of the server's table the compiler writes (`SERVER_TABLE`). The loader is a dynamic import
|
|
93
54
|
// so a page's markup is off the boot path until an address asks for it, which is the same reason
|
|
@@ -107,9 +68,9 @@ type ServerRoute = {
|
|
|
107
68
|
// 35.2 — an error page, and the directory it answers for.
|
|
108
69
|
type ServerError = Omit<ServerRoute, 'clientModules'>
|
|
109
70
|
|
|
110
|
-
type RankedError = ServerError & { segments: Segment[] }
|
|
71
|
+
export type RankedError = ServerError & { segments: Segment[] }
|
|
111
72
|
|
|
112
|
-
type Ranked = RankedError & {
|
|
73
|
+
export type Ranked = RankedError & {
|
|
113
74
|
// The document this route is painted into, with the tags that load the app already in its
|
|
114
75
|
// head (38.22, 38.23) and the preloads for this route's own modules (38.28). One per route and
|
|
115
76
|
// built at boot, so a request pays for none of it.
|
|
@@ -122,7 +83,7 @@ type Ranked = RankedError & {
|
|
|
122
83
|
* 19.1 — one file per method per route, so a route is a row and a method is a column, and 19.20's
|
|
123
84
|
* "the most specific route answering the method" is a walk down the rows asking each column.
|
|
124
85
|
*/
|
|
125
|
-
type RouteRow = {
|
|
86
|
+
export type RouteRow = {
|
|
126
87
|
pattern: string
|
|
127
88
|
matchers: Record<string, string>
|
|
128
89
|
segments: Segment[]
|
|
@@ -141,7 +102,7 @@ type RouteRow = {
|
|
|
141
102
|
}
|
|
142
103
|
|
|
143
104
|
/** A public file, read once at boot: where the bytes are, the etag off them, and its type. */
|
|
144
|
-
type PublicFile = { path: string; etag: string; type: string }
|
|
105
|
+
export type PublicFile = { path: string; etag: string; type: string }
|
|
145
106
|
|
|
146
107
|
/** Everything `answerRequest` answers from: the ranked routes, the lanes and the handlers. */
|
|
147
108
|
export type AppSources = {
|
|
@@ -178,9 +139,6 @@ export type AppSources = {
|
|
|
178
139
|
explained: ((thrown: unknown) => Response) | undefined
|
|
179
140
|
}
|
|
180
141
|
|
|
181
|
-
// 41.29 — the seed sink of a render no page hydrates: every call is left to run when it is made.
|
|
182
|
-
const SEEDS_NOTHING: PageRender['seed'] = () => false
|
|
183
|
-
|
|
184
142
|
// 35.19 — what fetch calls a navigation request's destinations, and a script's `fetch`.
|
|
185
143
|
const PAGE_DESTINATIONS = new Set([
|
|
186
144
|
'document',
|
|
@@ -368,9 +326,6 @@ async function answerMatched(
|
|
|
368
326
|
// minted for a request that never becomes a page.
|
|
369
327
|
if (request.method === 'HEAD')
|
|
370
328
|
return new Response(null, { status: writtenStatus(), headers })
|
|
371
|
-
// 26.5 — the caller the seed buffer is keyed by, minted where the browser carries none: the same
|
|
372
|
-
// one `principal.caller` reads for the rest of the request.
|
|
373
|
-
const caller = callerOrMint()
|
|
374
329
|
// 23.1 — WHERE THIS PAGE IS, for its setup to read, in the request's own instances (11.32).
|
|
375
330
|
// 16.10 — AT THE ADDRESS IT IS SERVED AT, which is the one the browser's router arrives at: the
|
|
376
331
|
// request reached here with the mount taken off, and a page reading `route.url()` on both sides
|
|
@@ -379,23 +334,8 @@ async function answerMatched(
|
|
|
379
334
|
at.pathname = mounted(at.pathname)
|
|
380
335
|
arrive(at.href, page.pattern, params)
|
|
381
336
|
// 11.32 — the page renders in a scope of its own, so what its values hold is this request's —
|
|
382
|
-
// and 41.13, every call it makes is
|
|
383
|
-
|
|
384
|
-
// next call this caller makes with what the render saw, a sign-in since included — save the one
|
|
385
|
-
// the worker fetched for the navigation it answers, which is the page that hydrates (45.23).
|
|
386
|
-
const keep = request.headers.get(KEEP_HEADER)
|
|
387
|
-
const hydrated =
|
|
388
|
-
keep === KEEP_OPENED ||
|
|
389
|
-
request.headers.get('sec-fetch-dest') !== 'empty'
|
|
390
|
-
// 41.30 — SAVE ONE THE WORKER KEEPS, whose early calls are what a later open of it starts.
|
|
391
|
-
beginRender({
|
|
392
|
-
seed: hydrated
|
|
393
|
-
? seedFor(caller)
|
|
394
|
-
: keep !== null
|
|
395
|
-
? callsOnly()
|
|
396
|
-
: SEEDS_NOTHING,
|
|
397
|
-
calls: [],
|
|
398
|
-
})
|
|
337
|
+
// and 41.13, every call it makes is recorded into its render.
|
|
338
|
+
beginPageRender(request)
|
|
399
339
|
// 41.1, 35.2 — THE DOCUMENT IS THE BUILT PAGE, read whole before there is a `Response` to commit a
|
|
400
340
|
// status to: a refusal the page read, or a throw, is answered by the nearest error page at its own
|
|
401
341
|
// status rather than as a 200 the stream then abandons.
|
|
@@ -443,42 +383,6 @@ const SURFACES = new Map<
|
|
|
443
383
|
[BRIDGE_ADDRESS, (_app, request) => answerBridge(request)],
|
|
444
384
|
])
|
|
445
385
|
|
|
446
|
-
// Each handler's route, compiled once rather than per render that calls it — as its `Rpc` keeps its
|
|
447
|
-
// own; compiled again only where it was mounted at another address since.
|
|
448
|
-
const ROUTE_OF = new WeakMap<object, HandlerRoute>()
|
|
449
|
-
|
|
450
|
-
// What a call made in a page's render reports into: the seed buffer (41.13), and — where the handler
|
|
451
|
-
// is mounted — the request the browser will make for it, for the early-call script (41.20). Once per
|
|
452
|
-
// render, as the browser's `Rpc` makes it once, and the seed buffer is what says which is the once.
|
|
453
|
-
export function reachedInRender(
|
|
454
|
-
rpc: object,
|
|
455
|
-
args: unknown,
|
|
456
|
-
reached: unknown,
|
|
457
|
-
): void {
|
|
458
|
-
const page = currentRequestScope()?.page
|
|
459
|
-
if (page === undefined) return
|
|
460
|
-
noteCallInRender()
|
|
461
|
-
if (!page.seed(rpc, args, reached)) return
|
|
462
|
-
// 16.10 — the address the browser's own call is made at, stamped under the mount by
|
|
463
|
-
// `mountHandlers` — and `undefined` on a handler nothing mounted.
|
|
464
|
-
const { address, method } = rpc as Rpc<unknown, unknown>
|
|
465
|
-
if (address === undefined) return
|
|
466
|
-
let route = ROUTE_OF.get(rpc)
|
|
467
|
-
if (route?.address !== address) {
|
|
468
|
-
route = new HandlerRoute(address)
|
|
469
|
-
ROUTE_OF.set(rpc, route)
|
|
470
|
-
}
|
|
471
|
-
page.calls.push({
|
|
472
|
-
request: rpcRequest(
|
|
473
|
-
method,
|
|
474
|
-
route,
|
|
475
|
-
args as Record<string, unknown> | undefined,
|
|
476
|
-
),
|
|
477
|
-
reached,
|
|
478
|
-
painted: undefined,
|
|
479
|
-
})
|
|
480
|
-
}
|
|
481
|
-
|
|
482
386
|
/**
|
|
483
387
|
* The server's table the compiler writes (`SERVER_TABLE`): the routes and the error pages.
|
|
484
388
|
*
|
|
@@ -486,168 +390,8 @@ export function reachedInRender(
|
|
|
486
390
|
*/
|
|
487
391
|
export type ServerTable = { routes: ServerRoute[]; errors: ServerError[] }
|
|
488
392
|
|
|
489
|
-
/**
|
|
490
|
-
* What a command hands `appSources`: the parts it read, built or compiled, and the client lane.
|
|
491
|
-
*
|
|
492
|
-
* ONE SHAPE FOR EVERY SUPPLIER — `abide start` off disk, a compiled app out of itself, `abide dev` out
|
|
493
|
-
* of memory, and a test handing over modules it already holds — so what differs between them is
|
|
494
|
-
* where a part came from and never how the app is assembled from it.
|
|
495
|
-
*/
|
|
496
|
-
export type SourceParts = {
|
|
497
|
-
table: ServerTable
|
|
498
|
-
/** The handler table the build wrote, each row with its module's loader. 16.2. */
|
|
499
|
-
handlers: MountRow[]
|
|
500
|
-
/** Every public file, by its name below the directory to its path. 38.18, 38.19. */
|
|
501
|
-
published: Map<string, string>
|
|
502
|
-
/**
|
|
503
|
-
* The bundle's manifest, its lane, and `head` — `abide dev`'s reload client (38.17), after the
|
|
504
|
-
* app's own tags.
|
|
505
|
-
*
|
|
506
|
-
* A PROMISE WHERE THE BUNDLE IS STILL BUILDING: `abide dev`'s handlers mount and its public files
|
|
507
|
-
* hash while the bundler runs, rather than after it — ~40 ms of a docs-app reload.
|
|
508
|
-
*/
|
|
509
|
-
client: ClientLane | Promise<ClientLane>
|
|
510
|
-
explained: AppSources['explained']
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
type ClientLane = {
|
|
514
|
-
manifest: Manifest
|
|
515
|
-
framework: AppSources['framework']
|
|
516
|
-
head: string
|
|
517
|
-
/**
|
|
518
|
-
* 45.1, 45.9 — whether the worker saves `manifest`'s bundle and the pages register it: `abide
|
|
519
|
-
* build`'s, unless `ABIDE_OFFLINE` is off. Where not, the worker address answers the removing one.
|
|
520
|
-
*/
|
|
521
|
-
offline: boolean
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* The sources an `App` is booted over, off the parts a command supplies.
|
|
526
|
-
*
|
|
527
|
-
* 30.9 — MOST SPECIFIC FIRST, ranked off the table rather than in it. The ordering IS the client
|
|
528
|
-
* router's own: both seams compile through `rankRoutes` and walk a route with `matchOf` — the
|
|
529
|
-
* server past a route that has no answer for the method asked, which the router never is — so
|
|
530
|
-
* "the two cannot resolve one address differently" is a grep rather than a promise. 35.2 — the
|
|
531
|
-
* error pages deepest directory first, so the nearest answers.
|
|
532
|
-
*/
|
|
533
|
-
export async function appSources(parts: SourceParts): Promise<AppSources> {
|
|
534
|
-
const [mounted, published, client] = await Promise.all([
|
|
535
|
-
mountHandlers(parts.handlers),
|
|
536
|
-
publicFilesAt(parts.published),
|
|
537
|
-
parts.client,
|
|
538
|
-
])
|
|
539
|
-
// 35.6 — every page is served in abide's own document; what it carries beyond that, the root
|
|
540
|
-
// `layout.abide` writes (35.15).
|
|
541
|
-
// 45.15 — installable where the pages are kept offline and the app has an icon to install by.
|
|
542
|
-
const icon = published.get(ICON_ADDRESSES.png)
|
|
543
|
-
const install =
|
|
544
|
-
icon !== undefined && settingOn('ABIDE_OFFLINE', true)
|
|
545
|
-
? await appManifest(icon.path)
|
|
546
|
-
: undefined
|
|
547
|
-
const documentOf = (modules: string[]): string =>
|
|
548
|
-
intoHead(
|
|
549
|
-
DEFAULT_DOCUMENT,
|
|
550
|
-
clientTags(client.manifest, modules) +
|
|
551
|
-
(client.offline ? `<meta name="${OFFLINE_META}">` : '') +
|
|
552
|
-
(install?.tags ?? '') +
|
|
553
|
-
client.head,
|
|
554
|
-
)
|
|
555
|
-
const pages: Omit<Ranked, 'segments'>[] = []
|
|
556
|
-
for (const route of parts.table.routes)
|
|
557
|
-
pages.push({
|
|
558
|
-
pattern: route.pattern,
|
|
559
|
-
load: route.load,
|
|
560
|
-
layouts: route.layouts,
|
|
561
|
-
matchers: route.matchers,
|
|
562
|
-
document: documentOf(route.clientModules),
|
|
563
|
-
})
|
|
564
|
-
// 19.1 — the build refused two files at one route and method, so a route is one row however
|
|
565
|
-
// many methods answer at it — and 23.27 one matcher per position, so whichever file opens the
|
|
566
|
-
// row brings what every file at it would.
|
|
567
|
-
const rows = new Map<string, Omit<RouteRow, 'segments'>>()
|
|
568
|
-
const rowAt = (
|
|
569
|
-
pattern: string,
|
|
570
|
-
matchers: Record<string, string>,
|
|
571
|
-
): Omit<RouteRow, 'segments'> => {
|
|
572
|
-
let row = rows.get(pattern)
|
|
573
|
-
if (row === undefined) {
|
|
574
|
-
row = {
|
|
575
|
-
pattern,
|
|
576
|
-
matchers,
|
|
577
|
-
page: undefined,
|
|
578
|
-
handlers: new Map(),
|
|
579
|
-
answers: new Map(),
|
|
580
|
-
}
|
|
581
|
-
rows.set(pattern, row)
|
|
582
|
-
}
|
|
583
|
-
return row
|
|
584
|
-
}
|
|
585
|
-
for (const page of rankRoutes(pages))
|
|
586
|
-
rowAt(page.pattern, page.matchers).page = page
|
|
587
|
-
for (const handler of mounted.handlers.values())
|
|
588
|
-
rowAt(handler.address, handler.matchers).handlers.set(
|
|
589
|
-
handler.method,
|
|
590
|
-
handler,
|
|
591
|
-
)
|
|
592
|
-
const errors: RankedError[] = []
|
|
593
|
-
for (const error of parts.table.errors)
|
|
594
|
-
errors.push({
|
|
595
|
-
...error,
|
|
596
|
-
segments: parseRoutePattern(error.pattern, error.matchers),
|
|
597
|
-
})
|
|
598
|
-
errors.sort((a, b) => b.segments.length - a.segments.length)
|
|
599
|
-
const sources: AppSources = {
|
|
600
|
-
routes: rankRoutes([...rows.values()]),
|
|
601
|
-
errors,
|
|
602
|
-
// 35.18 — marked, so the browser hydrates no page over what an error page painted.
|
|
603
|
-
errorDocument: intoHead(documentOf([]), `<meta name="${ERROR_META}">`),
|
|
604
|
-
framework: (pathname, request) => {
|
|
605
|
-
if (pathname === WORKER_ADDRESS)
|
|
606
|
-
return answerWorker(
|
|
607
|
-
client.offline ? client.manifest : undefined,
|
|
608
|
-
)
|
|
609
|
-
if (pathname === APP_MANIFEST_ADDRESS && install !== undefined)
|
|
610
|
-
return new Response(install.body, {
|
|
611
|
-
headers: {
|
|
612
|
-
'content-type': 'application/manifest+json',
|
|
613
|
-
'cache-control': 'no-cache',
|
|
614
|
-
},
|
|
615
|
-
})
|
|
616
|
-
return client.framework(pathname, request)
|
|
617
|
-
},
|
|
618
|
-
published,
|
|
619
|
-
explained: parts.explained,
|
|
620
|
-
offline: client.offline,
|
|
621
|
-
...mounted,
|
|
622
|
-
}
|
|
623
|
-
for (const row of sources.routes) {
|
|
624
|
-
if (row.page !== undefined)
|
|
625
|
-
row.answers.set(
|
|
626
|
-
row.page,
|
|
627
|
-
answerInside(
|
|
628
|
-
sources,
|
|
629
|
-
row.page,
|
|
630
|
-
// 37.14 — a page's lane paints a refusal as its error page does (35.2).
|
|
631
|
-
rungsOver(row.pattern, (failure, request) =>
|
|
632
|
-
answerFailure(
|
|
633
|
-
sources,
|
|
634
|
-
new URL(request.url).pathname,
|
|
635
|
-
failure,
|
|
636
|
-
),
|
|
637
|
-
),
|
|
638
|
-
),
|
|
639
|
-
)
|
|
640
|
-
for (const handler of row.handlers.values())
|
|
641
|
-
row.answers.set(
|
|
642
|
-
handler,
|
|
643
|
-
answerInside(sources, handler, handler.rungs),
|
|
644
|
-
)
|
|
645
|
-
}
|
|
646
|
-
return sources
|
|
647
|
-
}
|
|
648
|
-
|
|
649
393
|
/** `found`'s answer inside the rungs filed over its route. 16.14, 37.2. */
|
|
650
|
-
function answerInside(
|
|
394
|
+
export function answerInside(
|
|
651
395
|
app: AppSources,
|
|
652
396
|
found: Ranked | Mounted,
|
|
653
397
|
rungs: LaneRung[],
|
|
@@ -655,103 +399,6 @@ function answerInside(
|
|
|
655
399
|
return compose(rungs, ({ request }) => answerMatched(app, request, found))
|
|
656
400
|
}
|
|
657
401
|
|
|
658
|
-
/**
|
|
659
|
-
* The head tags that load the app: its metas, stylesheet, entry script and the route's preloads.
|
|
660
|
-
*
|
|
661
|
-
* 38.22, and 38.23 for the stylesheet beside it. An app does not write either: the bundle's
|
|
662
|
-
* address is the build's, and a hand-written one is a path that goes stale the first time the
|
|
663
|
-
* output layout moves.
|
|
664
|
-
*
|
|
665
|
-
* THE STYLESHEET GOES FIRST, and ahead of the page's own head contributions for the same reason:
|
|
666
|
-
* a document that parses its script before its styles paints once unstyled. It is a plain `<link>`
|
|
667
|
-
* rather than anything the client does, so a page the server painted is styled with no JS at all.
|
|
668
|
-
*
|
|
669
|
-
* ONE STYLESHEET FOR THE APP: the bundler collects every stylesheet reachable from the entry — each
|
|
670
|
-
* component's scoped `<style>` (34.4) and each one a component imports (34.6) — into one file, so a
|
|
671
|
-
* page that renders none of those components is served them too. For a `<style>` block that costs
|
|
672
|
-
* bytes rather than correctness: every rule requires its component's scope token, so on a page the
|
|
673
|
-
* component is not on it matches nothing. An imported stylesheet carries no token and applies as
|
|
674
|
-
* written, wherever it is served.
|
|
675
|
-
*
|
|
676
|
-
* 38.28 — THE ROUTE'S CHUNKS ARE PRELOADED BESIDE THE ENTRY. Left to the client, the page's chunk
|
|
677
|
-
* and the shared runtime it imports are only asked for once the entry has arrived and run the
|
|
678
|
-
* router — a whole round trip after the document, for the largest file in the bundle. Named here,
|
|
679
|
-
* they are requested while the document is still being parsed.
|
|
680
|
-
*
|
|
681
|
-
* EVERY NAME IS THE MANIFEST'S, the entry's included: a build hashes it (38.5). A manifest with no
|
|
682
|
-
* entry is a bundle that failed under `abide dev` (38.17), and gets no tags rather than a
|
|
683
|
-
* `<script>` at a 404.
|
|
684
|
-
*/
|
|
685
|
-
export function clientTags(manifest: Manifest, modules: string[]): string {
|
|
686
|
-
if (manifest.entry === undefined) return ''
|
|
687
|
-
// 28.10 — `DEBUG`, through the document rather than the bundle, where the server has one.
|
|
688
|
-
const debug = process.env.DEBUG
|
|
689
|
-
let tags =
|
|
690
|
-
debug === undefined || debug === ''
|
|
691
|
-
? ''
|
|
692
|
-
: `<meta name="${DEBUG_META}" content="${escapeAttribute(debug)}">`
|
|
693
|
-
// 28.4 — the app's name, so a browser's `log.named` prefixes the one the server's does.
|
|
694
|
-
tags += `<meta name="${APP_META}" content="${escapeAttribute(appName())}">`
|
|
695
|
-
// 16.10 — the mount the browser resolves every address against, the root's `''` included: a
|
|
696
|
-
// lookup that finds no tag has walked the whole document to say so.
|
|
697
|
-
tags += `<meta name="${MOUNT_META}" content="${escapeAttribute(MOUNT.current())}">`
|
|
698
|
-
const client = mounted(CLIENT_ADDRESS)
|
|
699
|
-
tags +=
|
|
700
|
-
manifest.stylesheet === undefined
|
|
701
|
-
? ''
|
|
702
|
-
: `<link rel="stylesheet" href="${client}/${manifest.stylesheet}">`
|
|
703
|
-
tags += `<script type="module" src="${client}/${manifest.entry}"></script>`
|
|
704
|
-
for (const chunk of chunksOf([`${CLIENT_ENTRY}.ts`, ...modules], manifest))
|
|
705
|
-
tags += `<link rel="modulepreload" href="${client}/${chunk}">`
|
|
706
|
-
return tags
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
* The public files by address, off each file's name and where its bytes are.
|
|
711
|
-
*
|
|
712
|
-
* 38.18 and 38.19's directory, as an address per file — or a compiled app's, whose bytes are
|
|
713
|
-
* inside itself. Read at boot rather than per request, and the ETAG IS READ WITH IT: 21.13
|
|
714
|
-
* derives it from the bytes, so there is no cheaper moment than the one read that is happening
|
|
715
|
-
* anyway. The bytes are hashed and dropped — `Bun.file` streams the body when somebody asks, so a
|
|
716
|
-
* large file is not held in memory for the life of the process.
|
|
717
|
-
*/
|
|
718
|
-
async function publicFilesAt(
|
|
719
|
-
paths: Map<string, string>,
|
|
720
|
-
): Promise<Map<string, PublicFile>> {
|
|
721
|
-
const files = new Map<string, PublicFile>()
|
|
722
|
-
// THE READS OVERLAP. They are independent, and `abide dev` runs this again on every
|
|
723
|
-
// reload rather than once at boot — so it is reload latency an author waits on, not just
|
|
724
|
-
// a boot cost. Each is hashed as it lands, so no more than the reads in flight are held.
|
|
725
|
-
const names = [...paths.keys()]
|
|
726
|
-
const etags = await Promise.all(
|
|
727
|
-
names.map(async (name) =>
|
|
728
|
-
contentEtag(await Bun.file(paths.get(name) as string).bytes()),
|
|
729
|
-
),
|
|
730
|
-
)
|
|
731
|
-
for (let index = 0; index < names.length; index += 1) {
|
|
732
|
-
const name = names[index] as string
|
|
733
|
-
files.set(`/${name}`, {
|
|
734
|
-
path: paths.get(name) as string,
|
|
735
|
-
etag: etags[index] as string,
|
|
736
|
-
type: publicType(name),
|
|
737
|
-
})
|
|
738
|
-
}
|
|
739
|
-
return files
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
/**
|
|
743
|
-
* A public file's content type, off its name's extension.
|
|
744
|
-
*
|
|
745
|
-
* 36.14, 36.17 — a text type without a charset is given UTF-8's rather than whatever a browser
|
|
746
|
-
* guesses.
|
|
747
|
-
*/
|
|
748
|
-
function publicType(name: string): string {
|
|
749
|
-
const type = Bun.file(name).type
|
|
750
|
-
return type.startsWith('text/') && !type.includes('charset')
|
|
751
|
-
? `${type}; charset=utf-8`
|
|
752
|
-
: type
|
|
753
|
-
}
|
|
754
|
-
|
|
755
402
|
function notFound(): Response {
|
|
756
403
|
return plainAnswer('not found', 404)
|
|
757
404
|
}
|