@abide/abide 0.104.0 → 0.105.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/bin/abide.ts +80 -173
- package/docs/REGISTRY.md +17 -17
- package/docs/RULEBOOK.md +50 -12
- package/docs/abide.md +9 -9
- package/package.json +1 -1
- package/src/compiler/Lowering.ts +15 -5
- package/src/compiler/Refusals.ts +57 -0
- package/src/compiler/TypeSession.ts +45 -86
- package/src/compiler/appLayout.ts +447 -0
- package/src/compiler/appMap.ts +21 -20
- package/src/compiler/checkApp.ts +61 -97
- package/src/compiler/checkedShadow.ts +11 -9
- package/src/compiler/classifyRead.ts +3 -6
- package/src/compiler/compileApp.ts +180 -512
- package/src/compiler/componentGraph.ts +2 -71
- package/src/compiler/declaredExports.ts +79 -60
- package/src/compiler/emitClient.ts +11 -13
- package/src/compiler/emitComponent.ts +21 -6
- package/src/compiler/emitSource.ts +20 -20
- package/src/compiler/handlersUnder.ts +46 -36
- package/src/compiler/hookCalls.ts +152 -0
- package/src/compiler/mountsOf.ts +31 -54
- package/src/compiler/readSources.ts +27 -42
- package/src/compiler/readsIn.ts +5 -16
- package/src/compiler/resolverIn.ts +77 -0
- package/src/server/abideModules.ts +92 -123
- package/src/server/answerRequest.ts +18 -4
- package/src/server/answerSocket.ts +136 -76
- package/src/server/answerValue.ts +28 -15
- package/src/server/appTree.ts +49 -71
- package/src/server/claudeApi.ts +2 -5
- package/src/server/clientBundle.ts +198 -0
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +93 -57
- package/src/server/createApp.ts +90 -16
- package/src/server/deferredBuilds.ts +175 -0
- package/src/server/devWorker.ts +107 -319
- package/src/server/faceOf.ts +120 -0
- package/src/server/fenceServer.ts +10 -18
- package/src/server/index.ts +3 -0
- package/src/server/placement.ts +167 -0
- package/src/server/remote.ts +14 -42
- package/src/server/render.ts +25 -17
- package/src/server/renderComponent.ts +9 -5
- package/src/server/requestScope.ts +10 -4
- package/src/server/responseRelay.ts +389 -0
- package/src/server/runCompiled.ts +9 -12
- package/src/server/runDesktop.ts +6 -9
- package/src/server/serveBuild.ts +26 -73
- package/src/server/serveDev.ts +19 -176
- package/src/server/streamEvent.ts +29 -0
- package/src/server/withAbideSources.ts +2 -1
- package/src/shared/HandlerRoute.ts +68 -0
- package/src/shared/REFUSAL_KEY.ts +5 -0
- package/src/shared/answerOf.ts +105 -78
- package/src/shared/filledPath.ts +1 -9
- package/src/shared/framing.ts +127 -128
- package/src/shared/queryText.ts +15 -7
- package/src/shared/reactive/Cursor.ts +4 -2
- package/src/shared/reactive/Reactive.ts +285 -197
- package/src/shared/reactive/index.ts +11 -8
- package/src/shared/reactive/pairReactive.ts +1 -1
- package/src/shared/reactive/scoped.ts +4 -3
- package/src/shared/reactive/share.ts +11 -1
- package/src/shared/routePattern.ts +0 -123
- package/src/shared/rpc.ts +17 -24
- package/src/shared/rpcRequest.ts +18 -47
- package/src/shared/takeEarlyCall.ts +12 -37
- package/src/shared/url.ts +1 -1
- package/src/ui/Level.ts +276 -0
- package/src/ui/List.ts +17 -17
- package/src/ui/Router.ts +20 -87
- package/src/ui/applyHead.ts +92 -79
- package/src/ui/index.ts +4 -2
- package/src/ui/leave.ts +5 -0
- package/src/ui/reconcile.ts +23 -104
- package/src/ui/regions.ts +27 -2
- package/src/ui/rows.ts +123 -2
- package/src/ui/wire.ts +12 -27
- package/src/compiler/hooksCalled.ts +0 -35
- package/src/server/clientBuildOptions.ts +0 -54
- package/src/server/refuseAddressCollision.ts +0 -37
- package/src/shared/reactive/chunksOfStream.ts +0 -33
- package/src/ui/MOUNTING_LEVEL.ts +0 -18
|
@@ -21,6 +21,19 @@ import { basename, dirname, resolve } from 'node:path' // bun exposes no path he
|
|
|
21
21
|
import { SERVER_SPECIFIER } from '#shared/SPECIFIERS.ts'
|
|
22
22
|
import type { ServerTable } from './answerRequest.ts'
|
|
23
23
|
import type { MountRow } from './answerRpc.ts'
|
|
24
|
+
import {
|
|
25
|
+
type Asked,
|
|
26
|
+
BOUND_NAMESPACE,
|
|
27
|
+
CLIENT_NAMESPACE,
|
|
28
|
+
componentNamespaceOf,
|
|
29
|
+
type Face,
|
|
30
|
+
faceOf,
|
|
31
|
+
type HandlerFace,
|
|
32
|
+
type Lane,
|
|
33
|
+
realFileOf,
|
|
34
|
+
STYLE_NAMESPACE,
|
|
35
|
+
TEXT_NAMESPACE,
|
|
36
|
+
} from './faceOf.ts'
|
|
24
37
|
import { installSourceMaps, type SourceMaps } from './installSourceMaps.ts'
|
|
25
38
|
import {
|
|
26
39
|
CLIENT_QUERY,
|
|
@@ -68,24 +81,8 @@ export type Compilation = {
|
|
|
68
81
|
mounted: Record<string, string>
|
|
69
82
|
}
|
|
70
83
|
|
|
71
|
-
// A VIRTUAL FACE'S PATH: its file's, with a suffix that is not a query. Both registries key a module
|
|
72
|
-
// by its path whatever its namespace, and the runtime's drops a query from it — so a file's text, its
|
|
73
|
-
// stylesheet or its bound face under `?…` took the place of the module the file compiles to.
|
|
74
|
-
const BOUND = '::bound'
|
|
75
|
-
const CLIENT = '::client'
|
|
76
|
-
const TEXT = '::text'
|
|
77
|
-
const STYLE = '::style'
|
|
78
|
-
|
|
79
|
-
/** The namespace a bundle's component module is answered in, at its file's own path. */
|
|
80
|
-
export const COMPONENT_NAMESPACE = 'abide-component'
|
|
81
|
-
/** The namespaces a file's text and a component's stylesheet are loaded in. */
|
|
82
|
-
export const TEXT_NAMESPACE = 'abide-text'
|
|
83
|
-
export const STYLE_NAMESPACE = 'abide-style'
|
|
84
84
|
const RELATIVE_OR_SUBPATH = /^(?:\.\.?\/|#)/
|
|
85
85
|
|
|
86
|
-
/** One handler file, as `Compilation.handlers` holds it. */
|
|
87
|
-
type HandlerFace = { scope: string; client: string }
|
|
88
|
-
|
|
89
86
|
/** The plugin serving `compilation`'s files and tables, for one bundle. */
|
|
90
87
|
export function abideModules(
|
|
91
88
|
root: string,
|
|
@@ -235,79 +232,59 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
235
232
|
name: 'abide-modules',
|
|
236
233
|
setup(build) {
|
|
237
234
|
// THE LANE IS THE BUILD'S TARGET: a browser bundle gets each file's browser module, and
|
|
238
|
-
// the runtime — which has no config — and a server bundle get its server module.
|
|
239
|
-
// browser module the runtime was asked for (`CLIENT_QUERY`) is the one exception, and
|
|
240
|
-
// what it imports is in its lane.
|
|
235
|
+
// the runtime — which has no config — and a server bundle get its server module.
|
|
241
236
|
const bundling: boolean = 'config' in build
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
browser
|
|
245
|
-
|
|
246
|
-
// `file` path a plugin answers with `..` anywhere in it, which a `[...rest]` directory
|
|
247
|
-
// writes (23.5). The runtime has no such check, and its stack frames name the path.
|
|
248
|
-
const componentNamespace = bundling ? COMPONENT_NAMESPACE : 'file'
|
|
249
|
-
// The module `path` compiles to, in the lane its importer is in.
|
|
250
|
-
const componentFor = (path: string, importer: string) =>
|
|
251
|
-
inBrowser(importer) && !browser
|
|
252
|
-
? { path: `${path}${CLIENT}`, namespace: 'abide-client' }
|
|
253
|
-
: { path, namespace: componentNamespace }
|
|
237
|
+
const lane: Lane = {
|
|
238
|
+
bundling,
|
|
239
|
+
browser: bundling && build.config.target === 'browser',
|
|
240
|
+
}
|
|
254
241
|
const resolved = (specifier: string, importer: string): string =>
|
|
255
242
|
resolvedFile(specifier, dirname(pathOf(importer)))
|
|
243
|
+
// Each hook below only adapts bun's ask to `faceOf`'s: the file the specifier resolved to,
|
|
244
|
+
// its query taken off, and the answer handed back — a refusal thrown.
|
|
245
|
+
const answer = (
|
|
246
|
+
asked: Asked,
|
|
247
|
+
file: string,
|
|
248
|
+
importer: string,
|
|
249
|
+
): Face | undefined => {
|
|
250
|
+
const face = faceOf(
|
|
251
|
+
asked,
|
|
252
|
+
file,
|
|
253
|
+
pathOf(importer),
|
|
254
|
+
lane,
|
|
255
|
+
served.handlers,
|
|
256
|
+
)
|
|
257
|
+
if (face instanceof Error) throw face
|
|
258
|
+
return face
|
|
259
|
+
}
|
|
256
260
|
// A filter off the query's own constant, so renaming one cannot silently stop a match.
|
|
261
|
+
const asking = (asked: Asked, query: string, filter: RegExp) =>
|
|
262
|
+
build.onResolve({ filter }, (args) =>
|
|
263
|
+
resolving
|
|
264
|
+
? undefined
|
|
265
|
+
: answer(
|
|
266
|
+
asked,
|
|
267
|
+
resolved(
|
|
268
|
+
args.path.slice(
|
|
269
|
+
0,
|
|
270
|
+
args.path.length - query.length,
|
|
271
|
+
),
|
|
272
|
+
args.importer,
|
|
273
|
+
),
|
|
274
|
+
args.importer,
|
|
275
|
+
),
|
|
276
|
+
)
|
|
257
277
|
const abideWith = (query: string) =>
|
|
258
278
|
new RegExp(`\\.abide${RegExp.escape(query)}$`)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
(
|
|
263
|
-
if (resolving) return undefined
|
|
264
|
-
const specifier = args.path.slice(0, -TEXT_QUERY.length)
|
|
265
|
-
// A path of its own (`TEXT`): a file is often imported as code and as text at once
|
|
266
|
-
// — an example's Files tab.
|
|
267
|
-
return {
|
|
268
|
-
path: `${resolved(specifier, args.importer)}${TEXT}`,
|
|
269
|
-
namespace: TEXT_NAMESPACE,
|
|
270
|
-
}
|
|
271
|
-
},
|
|
279
|
+
asking(
|
|
280
|
+
'text',
|
|
281
|
+
TEXT_QUERY,
|
|
282
|
+
new RegExp(`${RegExp.escape(TEXT_QUERY)}$`),
|
|
272
283
|
)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
path: `${resolved(specifier, args.importer)}${STYLE}`,
|
|
278
|
-
namespace: STYLE_NAMESPACE,
|
|
279
|
-
}
|
|
280
|
-
})
|
|
281
|
-
build.onResolve({ filter: abideWith(COMPONENT_QUERY) }, (args) => {
|
|
282
|
-
if (resolving) return undefined
|
|
283
|
-
const specifier = args.path.slice(0, -COMPONENT_QUERY.length)
|
|
284
|
-
return componentFor(
|
|
285
|
-
resolved(specifier, args.importer),
|
|
286
|
-
args.importer,
|
|
287
|
-
)
|
|
288
|
-
})
|
|
289
|
-
build.onResolve({ filter: abideWith(CLIENT_QUERY) }, (args) => {
|
|
290
|
-
if (resolving) return undefined
|
|
291
|
-
const path = resolved(
|
|
292
|
-
args.path.slice(0, -CLIENT_QUERY.length),
|
|
293
|
-
args.importer,
|
|
294
|
-
)
|
|
295
|
-
return browser
|
|
296
|
-
? { path, namespace: componentNamespace }
|
|
297
|
-
: { path: `${path}${CLIENT}`, namespace: 'abide-client' }
|
|
298
|
-
})
|
|
299
|
-
build.onResolve({ filter: /\.abide$/ }, (args) => {
|
|
300
|
-
if (resolving) return undefined
|
|
301
|
-
const path = resolved(args.path, args.importer)
|
|
302
|
-
// A bound face's own import of its component, and every browser import, is the
|
|
303
|
-
// component module.
|
|
304
|
-
if (
|
|
305
|
-
inBrowser(args.importer) ||
|
|
306
|
-
pathOf(args.importer) === `${path}${BOUND}`
|
|
307
|
-
)
|
|
308
|
-
return componentFor(path, args.importer)
|
|
309
|
-
return { path: `${path}${BOUND}`, namespace: 'abide-bound' }
|
|
310
|
-
})
|
|
284
|
+
asking('style', STYLE_QUERY, abideWith(STYLE_QUERY))
|
|
285
|
+
asking('component', COMPONENT_QUERY, abideWith(COMPONENT_QUERY))
|
|
286
|
+
asking('client', CLIENT_QUERY, abideWith(CLIENT_QUERY))
|
|
287
|
+
asking('abide', '', /\.abide$/)
|
|
311
288
|
build.onResolve(
|
|
312
289
|
{ filter: new RegExp(`/${VIRTUAL_DIRECTORY}/[^/]+\\.ts$`) },
|
|
313
290
|
(args) => {
|
|
@@ -322,13 +299,15 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
322
299
|
},
|
|
323
300
|
)
|
|
324
301
|
|
|
325
|
-
// A HANDLER FILE IS FOUND BY WHERE ITS IMPORT RESOLVES, however the import spells it
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
// by
|
|
302
|
+
// A HANDLER FILE IS FOUND BY WHERE ITS IMPORT RESOLVES, however the import spells it — the
|
|
303
|
+
// app's own, or a mounted package's own page importing a handler file of its (23.19).
|
|
304
|
+
// THE FILTER IS THE HANDLER FILES' NAMES: a hook matching every relative import costs the
|
|
305
|
+
// bundle abide's own `sideEffects`, and the barrel's rest — the validator, the csp — moved
|
|
306
|
+
// into the chunk every page loads (38.94); and since only an import ending in one of those
|
|
307
|
+
// names is resolved again, a package's other imports cost nothing, so a package's are not
|
|
308
|
+
// passed over by where they lie. ONLY IN A BUNDLE: the runtime finds a handler file by the
|
|
309
|
+
// path it loads at (`handlerFilesPlugin`), and asks no `onResolve` of an extensionless
|
|
310
|
+
// specifier at all.
|
|
332
311
|
// Each handler file's name without `.ts` — what an import of one ends in.
|
|
333
312
|
const stems = new Set<string>()
|
|
334
313
|
if (bundling)
|
|
@@ -344,23 +323,13 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
344
323
|
(args) => {
|
|
345
324
|
if (resolving || !RELATIVE_OR_SUBPATH.test(args.path))
|
|
346
325
|
return undefined
|
|
347
|
-
|
|
348
|
-
if (importer.includes('/node_modules/'))
|
|
349
|
-
return undefined
|
|
350
|
-
let path: string
|
|
326
|
+
let file: string
|
|
351
327
|
try {
|
|
352
|
-
|
|
328
|
+
file = resolved(args.path, args.importer)
|
|
353
329
|
} catch {
|
|
354
330
|
return undefined
|
|
355
331
|
}
|
|
356
|
-
|
|
357
|
-
if (face === undefined) return undefined
|
|
358
|
-
// 20.4 — a handler the browser is handed no wrapper for is no import of its.
|
|
359
|
-
if (inBrowser(args.importer) && face.client === '')
|
|
360
|
-
throw new Error(
|
|
361
|
-
`abide: ${pathOf(args.importer)} imports ${args.path}, whose handler withholds itself from the browser (\`clients: { ui: false }\`). Call it from a <script server>, or let the browser have it. RULEBOOK 20.4.`,
|
|
362
|
-
)
|
|
363
|
-
return { path: `${path}${HANDLER_SUFFIX}` }
|
|
332
|
+
return answer('handler', file, args.importer)
|
|
364
333
|
},
|
|
365
334
|
)
|
|
366
335
|
|
|
@@ -385,11 +354,13 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
385
354
|
}
|
|
386
355
|
|
|
387
356
|
build.onLoad(
|
|
388
|
-
{ filter: /\.abide$/, namespace:
|
|
357
|
+
{ filter: /\.abide$/, namespace: componentNamespaceOf(lane) },
|
|
389
358
|
(args) => {
|
|
390
359
|
const compiled = compiledAt(served, args.path)
|
|
391
360
|
return {
|
|
392
|
-
contents: browser
|
|
361
|
+
contents: lane.browser
|
|
362
|
+
? compiled.client
|
|
363
|
+
: compiled.server,
|
|
393
364
|
loader: 'ts',
|
|
394
365
|
}
|
|
395
366
|
},
|
|
@@ -401,9 +372,9 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
401
372
|
// resolves from its directory as the file's own imports do. A namespace of abide's own
|
|
402
373
|
// had bun resolve a bare one from the working directory: a second abide, run from `/`.
|
|
403
374
|
build.onLoad({ filter: HANDLER_FACE }, async (args) => {
|
|
404
|
-
const path = args.path
|
|
375
|
+
const path = realFileOf(args.path)
|
|
405
376
|
const face = served.handlers.get(path) as HandlerFace
|
|
406
|
-
if (browser) return { contents: face.client, loader: 'js' }
|
|
377
|
+
if (lane.browser) return { contents: face.client, loader: 'js' }
|
|
407
378
|
const text = await Bun.file(path).text()
|
|
408
379
|
return {
|
|
409
380
|
contents: `import { fileRoute as __fileRoute } from '${SERVER_SPECIFIER}';__fileRoute(${JSON.stringify(face.scope)});${text}\n;__fileRoute(undefined)\n`,
|
|
@@ -411,22 +382,22 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
411
382
|
}
|
|
412
383
|
})
|
|
413
384
|
build.onLoad(
|
|
414
|
-
{ filter: /.*/, namespace:
|
|
385
|
+
{ filter: /.*/, namespace: CLIENT_NAMESPACE },
|
|
415
386
|
(args) => ({
|
|
416
|
-
contents: compiledAt(
|
|
417
|
-
served,
|
|
418
|
-
args.path.slice(0, -CLIENT.length),
|
|
419
|
-
).client,
|
|
387
|
+
contents: compiledAt(served, realFileOf(args.path)).client,
|
|
420
388
|
loader: 'ts',
|
|
421
389
|
}),
|
|
422
390
|
)
|
|
423
|
-
build.onLoad(
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
391
|
+
build.onLoad(
|
|
392
|
+
{ filter: /.*/, namespace: BOUND_NAMESPACE },
|
|
393
|
+
(args) => {
|
|
394
|
+
const path = realFileOf(args.path)
|
|
395
|
+
return {
|
|
396
|
+
contents: boundModule(path),
|
|
397
|
+
loader: 'js',
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
)
|
|
430
401
|
// A stylesheet is the bundle's; outside one there is no document to apply it to.
|
|
431
402
|
build.onLoad(
|
|
432
403
|
{ filter: /.*/, namespace: STYLE_NAMESPACE },
|
|
@@ -434,10 +405,8 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
434
405
|
bundling
|
|
435
406
|
? {
|
|
436
407
|
contents:
|
|
437
|
-
compiledAt(
|
|
438
|
-
|
|
439
|
-
args.path.slice(0, -STYLE.length),
|
|
440
|
-
).sheet ?? '',
|
|
408
|
+
compiledAt(served, realFileOf(args.path))
|
|
409
|
+
.sheet ?? '',
|
|
441
410
|
loader: 'css',
|
|
442
411
|
}
|
|
443
412
|
: { contents: '', loader: 'js' },
|
|
@@ -445,7 +414,7 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
445
414
|
build.onLoad(
|
|
446
415
|
{ filter: /.*/, namespace: TEXT_NAMESPACE },
|
|
447
416
|
async (args) => ({
|
|
448
|
-
contents: `export default ${JSON.stringify(await Bun.file(args.path
|
|
417
|
+
contents: `export default ${JSON.stringify(await Bun.file(realFileOf(args.path)).text())}\n`,
|
|
449
418
|
loader: 'js',
|
|
450
419
|
}),
|
|
451
420
|
)
|
|
@@ -20,6 +20,7 @@ import { DEBUG_META } from '#shared/DEBUG_META.ts'
|
|
|
20
20
|
import { decodedOrKept } from '#shared/decodedOrKept.ts'
|
|
21
21
|
import { escapeAttribute } from '#shared/escapeText.ts'
|
|
22
22
|
import { type Failed, isFailed } from '#shared/Failed.ts'
|
|
23
|
+
import { HandlerRoute } from '#shared/HandlerRoute.ts'
|
|
23
24
|
import { health } from '#shared/health.ts'
|
|
24
25
|
import { appName } from '#shared/log.ts'
|
|
25
26
|
import { compose } from '#shared/Middleware.ts'
|
|
@@ -50,6 +51,7 @@ import { CLIENT_ENTRY } from './CLIENT_BUILD.ts'
|
|
|
50
51
|
import { callerOrMint } from './callerOf.ts'
|
|
51
52
|
import { compressAnswer } from './compressAnswer.ts'
|
|
52
53
|
import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
|
|
54
|
+
import { noteCallInRender } from './deferredBuilds.ts'
|
|
53
55
|
import { answerMcp, openApi } from './generatedSurfaces.ts'
|
|
54
56
|
import { type AppRung, failureOf, rungsOver } from './hooks.ts'
|
|
55
57
|
import { chunksOf, type Manifest } from './manifestOf.ts'
|
|
@@ -208,8 +210,8 @@ export async function answerRequest(
|
|
|
208
210
|
}
|
|
209
211
|
|
|
210
212
|
// 36.11 — A PUBLIC FILE IS ITS PATH BELOW THE DIRECTORY, so `/favicon.ico` is answered
|
|
211
|
-
// before any route is tried. The order is not load-bearing: `abide build` and
|
|
212
|
-
//
|
|
213
|
+
// before any route is tried. The order is not load-bearing: the compile `abide build` and
|
|
214
|
+
// `abide dev` share refuses a collision (36.13) over the same two trees before anything is served,
|
|
213
215
|
// so at run time there can never be a collision to resolve. 36.11 — keyed by its name on disk
|
|
214
216
|
// and asked for percent-encoded: `my photo.jpg` is `/my%20photo.jpg`.
|
|
215
217
|
const file = app.published.get(decodedOrKept(asked))
|
|
@@ -383,6 +385,10 @@ const SURFACES = new Map<
|
|
|
383
385
|
[BRIDGE_ADDRESS, (_app, request) => answerBridge(request)],
|
|
384
386
|
])
|
|
385
387
|
|
|
388
|
+
// Each handler's route, compiled once rather than per render that calls it — as its `Rpc` keeps its
|
|
389
|
+
// own; compiled again only where it was mounted at another address since.
|
|
390
|
+
const ROUTE_OF = new WeakMap<object, HandlerRoute>()
|
|
391
|
+
|
|
386
392
|
// What a call made in a page's render reports into: the seed buffer (41.13), and — where the handler
|
|
387
393
|
// is mounted — the request the browser will make for it, for the early-call script (41.20). Once per
|
|
388
394
|
// render, as the browser's `Rpc` makes it once, and the seed buffer is what says which is the once.
|
|
@@ -392,18 +398,26 @@ export function reachedInRender(
|
|
|
392
398
|
reached: unknown,
|
|
393
399
|
): void {
|
|
394
400
|
const page = currentRequestScope()?.page
|
|
395
|
-
if (page === undefined
|
|
401
|
+
if (page === undefined) return
|
|
402
|
+
noteCallInRender()
|
|
403
|
+
if (!page.seed(rpc, args, reached)) return
|
|
396
404
|
// 16.10 — the address the browser's own call is made at, stamped under the mount by
|
|
397
405
|
// `mountHandlers` — and `undefined` on a handler nothing mounted.
|
|
398
406
|
const { address, method } = rpc as Rpc<unknown, unknown>
|
|
399
407
|
if (address === undefined) return
|
|
408
|
+
let route = ROUTE_OF.get(rpc)
|
|
409
|
+
if (route?.address !== address) {
|
|
410
|
+
route = new HandlerRoute(address)
|
|
411
|
+
ROUTE_OF.set(rpc, route)
|
|
412
|
+
}
|
|
400
413
|
page.calls.push({
|
|
401
414
|
request: rpcRequest(
|
|
402
415
|
method,
|
|
403
|
-
|
|
416
|
+
route,
|
|
404
417
|
args as Record<string, unknown> | undefined,
|
|
405
418
|
),
|
|
406
419
|
reached,
|
|
420
|
+
painted: undefined,
|
|
407
421
|
})
|
|
408
422
|
}
|
|
409
423
|
|