@abide/abide 0.102.0 → 0.104.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 (73) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/bin/abide.ts +31 -28
  3. package/docs/REGISTRY.md +40 -36
  4. package/docs/RULEBOOK.md +113 -92
  5. package/docs/abide.md +788 -554
  6. package/package.json +2 -1
  7. package/scaffold/AGENTS.md +13 -6
  8. package/src/compiler/TypeSession.ts +108 -35
  9. package/src/compiler/appMap.ts +91 -0
  10. package/src/compiler/argsSchemaOf.ts +1 -1
  11. package/src/compiler/checkApp.ts +35 -18
  12. package/src/compiler/checkedShadow.ts +40 -7
  13. package/src/compiler/compileApp.ts +271 -76
  14. package/src/compiler/compiledEntry.ts +10 -6
  15. package/src/compiler/componentGraph.ts +6 -7
  16. package/src/compiler/declaredExports.ts +3 -3
  17. package/src/compiler/emitClient.ts +3 -3
  18. package/src/compiler/emitComponent.ts +9 -9
  19. package/src/compiler/emitSource.ts +5 -9
  20. package/src/compiler/handlersUnder.ts +114 -73
  21. package/src/compiler/hooksCalled.ts +35 -0
  22. package/src/compiler/mountsOf.ts +30 -18
  23. package/src/compiler/parseAbide.ts +12 -12
  24. package/src/compiler/projectShadow.ts +71 -46
  25. package/src/compiler/readsIn.ts +2 -2
  26. package/src/compiler/scaffold.ts +24 -21
  27. package/src/compiler/serveLanguage.ts +0 -24
  28. package/src/compiler/sharedModuleWrites.ts +2 -2
  29. package/src/compiler/splitImports.ts +4 -4
  30. package/src/compiler/writeDefaultProject.ts +28 -1
  31. package/src/server/MODULE_QUERIES.ts +12 -3
  32. package/src/server/abideModules.ts +156 -53
  33. package/src/server/answerRequest.ts +9 -4
  34. package/src/server/answerRpc.ts +18 -16
  35. package/src/server/appTree.ts +92 -25
  36. package/src/server/commands.ts +5 -4
  37. package/src/server/componentModules.d.ts +1 -2
  38. package/src/server/createApp.ts +23 -11
  39. package/src/server/devWorker.ts +5 -1
  40. package/src/server/fenceServer.ts +36 -13
  41. package/src/server/generatedSurfaces.ts +12 -53
  42. package/src/server/hooks.ts +17 -4
  43. package/src/server/index.ts +1 -0
  44. package/src/server/installSourceMaps.ts +6 -1
  45. package/src/server/launcherOptions.ts +12 -13
  46. package/src/server/launcherPage.ts +2 -2
  47. package/src/server/nativeMenu.ts +3 -3
  48. package/src/server/preload.ts +13 -0
  49. package/src/server/runDesktop.ts +11 -16
  50. package/src/server/serveDev.ts +8 -2
  51. package/src/server/surfaceOf.ts +2 -0
  52. package/src/server/versionOf.ts +1 -2
  53. package/src/server/withAbideSources.ts +1 -1
  54. package/src/shared/ADDRESSES.ts +3 -0
  55. package/src/shared/LAUNCHER_FILES.ts +9 -0
  56. package/src/shared/RESERVED_DIRECTORIES.ts +19 -4
  57. package/src/shared/index.ts +2 -1
  58. package/src/{ui → shared}/menuBridge.ts +1 -1
  59. package/src/shared/menuItem.ts +2 -2
  60. package/src/shared/onMenu.ts +58 -0
  61. package/src/shared/reactive/Cursor.ts +66 -123
  62. package/src/shared/reactive/Reactive.ts +74 -28
  63. package/src/shared/reactive/callLoad.ts +4 -18
  64. package/src/shared/reactive/chunksOfStream.ts +10 -34
  65. package/src/{ui → shared}/reportMenu.ts +7 -9
  66. package/src/shared/routePattern.ts +74 -23
  67. package/src/shared/rpc.ts +1 -1
  68. package/src/ui/Router.ts +0 -6
  69. package/src/compiler/syncTypes.ts +0 -32
  70. package/src/compiler/writeTypes.ts +0 -82
  71. package/src/server/LAUNCHER_FILES.ts +0 -9
  72. /package/scaffold/src/{page.abide → index.abide} +0 -0
  73. /package/src/{compiler → shared}/SPECIFIERS.ts +0 -0
@@ -7,8 +7,8 @@
7
7
  // WHICH FACE AN IMPORT GETS IS SPELLED ON ITS SPECIFIER (`MODULE_QUERIES`), because bun hands a plugin
8
8
  // no import attributes: a compiled module asks for `?component`, a browser module for its own
9
9
  // `?style`, a text import for `?text`. A `.abide` named bare is a module abide did not compile
10
- // asking — on a server that is the bound face 34.16 states, beside every name the module exports,
11
- // and in a browser the component module itself.
10
+ // asking — on a server that is the bound face 34.16 states, and in a browser the component module
11
+ // itself.
12
12
  //
13
13
  // THE TABLES — the route, error and handler tables, and in a browser the router's table and the
14
14
  // entry — are the build's own modules, served from `__abide` below the root, where no app writes.
@@ -17,13 +17,16 @@
17
17
  // that registered one, so a second app booted in the same process — a test's — adds its compilation
18
18
  // to this one rather than registering a plugin nobody asks.
19
19
 
20
- import { dirname, resolve } from 'node:path' // bun exposes no path helpers of its own
20
+ import { basename, dirname, resolve } from 'node:path' // bun exposes no path helpers of its own
21
+ import { SERVER_SPECIFIER } from '#shared/SPECIFIERS.ts'
21
22
  import type { ServerTable } from './answerRequest.ts'
22
23
  import type { MountRow } from './answerRpc.ts'
23
24
  import { installSourceMaps, type SourceMaps } from './installSourceMaps.ts'
24
25
  import {
25
26
  CLIENT_QUERY,
26
27
  COMPONENT_QUERY,
28
+ HANDLER_FACE,
29
+ HANDLER_SUFFIX,
27
30
  STYLE_QUERY,
28
31
  TEXT_QUERY,
29
32
  } from './MODULE_QUERIES.ts'
@@ -51,8 +54,18 @@ export type CompiledFile = {
51
54
  export type Compilation = {
52
55
  /** Every `.abide` compiled, by its path below the root. */
53
56
  files: Record<string, CompiledFile>
57
+ /**
58
+ * Every handler file, by its path below the root: the route its hooks are filed at (37.1), and
59
+ * the module a browser is handed in its place (16.74), `''` where the browser is handed none.
60
+ */
61
+ handlers: Record<string, HandlerFace>
54
62
  /** The build's own modules, by name below `__abide`. */
55
63
  tables: Record<string, string>
64
+ /**
65
+ * 23.19 — every directory a file mounts, by its path below the root, to the position its files
66
+ * take: what the browser's fence reads a mounted file's place by (16.29).
67
+ */
68
+ mounted: Record<string, string>
56
69
  }
57
70
 
58
71
  // A VIRTUAL FACE'S PATH: its file's, with a suffix that is not a query. Both registries key a module
@@ -68,29 +81,40 @@ export const COMPONENT_NAMESPACE = 'abide-component'
68
81
  /** The namespaces a file's text and a component's stylesheet are loaded in. */
69
82
  export const TEXT_NAMESPACE = 'abide-text'
70
83
  export const STYLE_NAMESPACE = 'abide-style'
84
+ const RELATIVE_OR_SUBPATH = /^(?:\.\.?\/|#)/
71
85
 
72
- /** What the plugin answers from: a compiled file or a table by absolute path. */
73
- type Served = {
74
- file: (path: string) => CompiledFile | undefined
75
- table: (path: string) => string | undefined
76
- }
86
+ /** One handler file, as `Compilation.handlers` holds it. */
87
+ type HandlerFace = { scope: string; client: string }
77
88
 
78
89
  /** The plugin serving `compilation`'s files and tables, for one bundle. */
79
90
  export function abideModules(
80
91
  root: string,
81
92
  compilation: Compilation,
82
93
  ): Bun.BunPlugin {
83
- const files = new Map<string, CompiledFile>()
84
- const tables = new Map<string, string>()
85
- addCompilation(realOrAsGiven(root), compilation, files, tables)
86
- return modulesPlugin({
87
- file: (path) => files.get(path),
88
- table: (path) => tables.get(path),
89
- })
94
+ const held: Held = {
95
+ files: new Map(),
96
+ handlers: new Map(),
97
+ tables: new Map(),
98
+ }
99
+ addCompilation(realOrAsGiven(root), compilation, held)
100
+ return modulesPlugin(held)
101
+ }
102
+
103
+ /**
104
+ * A compilation as the plugin keys it, and answers from: a compiled file, a handler file or a table
105
+ * by absolute path.
106
+ */
107
+ type Held = {
108
+ files: Map<string, CompiledFile>
109
+ handlers: Map<string, HandlerFace>
110
+ tables: Map<string, string>
90
111
  }
91
112
 
92
- const RUNTIME_FILES = new Map<string, CompiledFile>()
93
- const RUNTIME_TABLES = new Map<string, string>()
113
+ const RUNTIME: Held = {
114
+ files: new Map(),
115
+ handlers: new Map(),
116
+ tables: new Map(),
117
+ }
94
118
  let registered = false
95
119
 
96
120
  /**
@@ -99,16 +123,37 @@ let registered = false
99
123
  */
100
124
  export function serveCompilation(root: string, compilation: Compilation): void {
101
125
  const real = realOrAsGiven(root)
102
- addCompilation(real, compilation, RUNTIME_FILES, RUNTIME_TABLES)
126
+ const handlerFiles = addCompilation(real, compilation, RUNTIME)
103
127
  installSourceMaps(serverMapsOf(compilation, real), real)
128
+ if (handlerFiles.length !== 0) Bun.plugin(handlerFilesPlugin(handlerFiles))
104
129
  if (registered) return
105
130
  registered = true
106
- Bun.plugin(
107
- modulesPlugin({
108
- file: (path) => RUNTIME_FILES.get(path),
109
- table: (path) => RUNTIME_TABLES.get(path),
110
- }),
111
- )
131
+ Bun.plugin(modulesPlugin(RUNTIME))
132
+ }
133
+
134
+ // 16.2 — A HANDLER FILE LOADED AT ITS OWN PATH RE-EXPORTS ITS FACE, so every spelling of its import
135
+ // is the module the server mounted. The runtime asks `onResolve` only for a specifier with an
136
+ // extension, while `onLoad` is asked by the path the file resolved to, however it was spelled. One
137
+ // plugin per compilation because the filter is fixed at registration, and the runtime's `onLoad`
138
+ // cannot fall through, so the filter names the handler files and nothing else. The face it names
139
+ // is resolved by `modulesPlugin`, registered once.
140
+ function handlerFilesPlugin(paths: string[]): Bun.BunPlugin {
141
+ return {
142
+ name: 'abide-handler-files',
143
+ setup(build) {
144
+ build.onLoad(
145
+ {
146
+ filter: new RegExp(
147
+ `^(?:${paths.map(RegExp.escape).join('|')})$`,
148
+ ),
149
+ },
150
+ (args) => ({
151
+ contents: `export { default } from ${JSON.stringify(`${args.path}${HANDLER_SUFFIX}`)}\n`,
152
+ loader: 'js',
153
+ }),
154
+ )
155
+ },
156
+ }
112
157
  }
113
158
 
114
159
  /** What the server's table exports: its routes and error pages, server blocks and handlers. */
@@ -143,17 +188,24 @@ export function serverMapsOf(
143
188
  return maps
144
189
  }
145
190
 
191
+ /** Adds `compilation` to `held`, and answers its handler files' absolute paths. */
146
192
  function addCompilation(
147
193
  root: string,
148
194
  compilation: Compilation,
149
- files: Map<string, CompiledFile>,
150
- tables: Map<string, string>,
151
- ): void {
195
+ held: Held,
196
+ ): string[] {
152
197
  // RESOLVED, not joined: a dependency's file is keyed above the root (`../node_modules/…`).
153
198
  for (const [relative, compiled] of Object.entries(compilation.files))
154
- files.set(resolve(root, relative), compiled)
199
+ held.files.set(resolve(root, relative), compiled)
200
+ const handlerFiles: string[] = []
201
+ for (const [relative, face] of Object.entries(compilation.handlers)) {
202
+ const path = resolve(root, relative)
203
+ held.handlers.set(path, face)
204
+ handlerFiles.push(path)
205
+ }
155
206
  for (const [name, text] of Object.entries(compilation.tables))
156
- tables.set(`${root}/${VIRTUAL_DIRECTORY}/${name}`, text)
207
+ held.tables.set(`${root}/${VIRTUAL_DIRECTORY}/${name}`, text)
208
+ return handlerFiles
157
209
  }
158
210
 
159
211
  // `Bun.resolveSync` asks EVERY abide plugin's `onResolve` again — this one's, and the runtime's, which
@@ -178,7 +230,7 @@ export function resolvedFile(specifier: string, from: string): string {
178
230
  }
179
231
  }
180
232
 
181
- function modulesPlugin(served: Served): Bun.BunPlugin {
233
+ function modulesPlugin(served: Held): Bun.BunPlugin {
182
234
  return {
183
235
  name: 'abide-modules',
184
236
  setup(build) {
@@ -264,12 +316,54 @@ function modulesPlugin(served: Served): Bun.BunPlugin {
264
316
  dirname(pathOf(args.importer)),
265
317
  args.path,
266
318
  )
267
- return served.table(path) === undefined
319
+ return served.tables.get(path) === undefined
268
320
  ? undefined
269
321
  : { path, namespace: 'abide-table' }
270
322
  },
271
323
  )
272
324
 
325
+ // A HANDLER FILE IS FOUND BY WHERE ITS IMPORT RESOLVES, however the import spells it, and
326
+ // only from the app's own modules: a package's never names one, and asking bun again for
327
+ // each of a package's relative imports is a resolve per module of the bundle. THE FILTER
328
+ // IS THE HANDLER FILES' NAMES: a hook matching every relative import costs the bundle
329
+ // abide's own `sideEffects`, and the barrel's rest — the validator, the csp — moved into
330
+ // the chunk every page loads (38.94). ONLY IN A BUNDLE: the runtime finds a handler file
331
+ // by the path it loads at (`handlerFilesPlugin`), which costs no resolve per import.
332
+ // Each handler file's name without `.ts` — what an import of one ends in.
333
+ const stems = new Set<string>()
334
+ if (bundling)
335
+ for (const path of served.handlers.keys())
336
+ stems.add(basename(path, '.ts'))
337
+ if (stems.size !== 0)
338
+ build.onResolve(
339
+ {
340
+ filter: new RegExp(
341
+ `(?:^|/)(?:${[...stems].map(RegExp.escape).join('|')})(?:\\.ts)?$`,
342
+ ),
343
+ },
344
+ (args) => {
345
+ if (resolving || !RELATIVE_OR_SUBPATH.test(args.path))
346
+ return undefined
347
+ const importer = pathOf(args.importer)
348
+ if (importer.includes('/node_modules/'))
349
+ return undefined
350
+ let path: string
351
+ try {
352
+ path = resolved(args.path, args.importer)
353
+ } catch {
354
+ return undefined
355
+ }
356
+ const face = served.handlers.get(path)
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}` }
364
+ },
365
+ )
366
+
273
367
  // A RELATIVE SPECIFIER A FACE IMPORTS AT RUN TIME is resolved from the file the face
274
368
  // stands for: bun's runtime reads one from an importer in a namespace as no path at
275
369
  // all. A bare one it resolves itself. Registered after every face, so a `.abide` is
@@ -277,12 +371,18 @@ function modulesPlugin(served: Served): Bun.BunPlugin {
277
371
  // and where a hook matching every relative import in a package costs the bundle that
278
372
  // package's `sideEffects`: a two-page app grew by 12 KB, the validator one page never
279
373
  // calls moved into the chunk both pages load.
280
- if (!bundling)
374
+ if (!bundling) {
375
+ // A face `handlerFilesPlugin` re-exports, named by its absolute path, which no file
376
+ // is at.
377
+ build.onResolve({ filter: HANDLER_FACE }, (args) => ({
378
+ path: args.path,
379
+ }))
281
380
  build.onResolve({ filter: /^\.\.?\// }, (args) =>
282
381
  resolving || pathOf(args.importer) === args.importer
283
382
  ? undefined
284
383
  : { path: resolved(args.path, args.importer) },
285
384
  )
385
+ }
286
386
 
287
387
  build.onLoad(
288
388
  { filter: /\.abide$/, namespace: componentNamespace },
@@ -294,6 +394,22 @@ function modulesPlugin(served: Served): Bun.BunPlugin {
294
394
  }
295
395
  },
296
396
  )
397
+ // 16.74, 37.1 — A HANDLER FILE: the browser's is its wrapper, and the server's is the file
398
+ // as written, between the two calls that file its hooks at its route — on its first
399
+ // line, so every line of the file stays where its frames name it. ITS FACE is its path
400
+ // with a suffix, in bun's own namespace — so what it imports, relative, `#` or bare,
401
+ // resolves from its directory as the file's own imports do. A namespace of abide's own
402
+ // had bun resolve a bare one from the working directory: a second abide, run from `/`.
403
+ build.onLoad({ filter: HANDLER_FACE }, async (args) => {
404
+ const path = args.path.slice(0, -HANDLER_SUFFIX.length)
405
+ const face = served.handlers.get(path) as HandlerFace
406
+ if (browser) return { contents: face.client, loader: 'js' }
407
+ const text = await Bun.file(path).text()
408
+ return {
409
+ contents: `import { fileRoute as __fileRoute } from '${SERVER_SPECIFIER}';__fileRoute(${JSON.stringify(face.scope)});${text}\n;__fileRoute(undefined)\n`,
410
+ loader: 'ts',
411
+ }
412
+ })
297
413
  build.onLoad(
298
414
  { filter: /.*/, namespace: 'abide-client' },
299
415
  (args) => ({
@@ -307,10 +423,7 @@ function modulesPlugin(served: Served): Bun.BunPlugin {
307
423
  build.onLoad({ filter: /.*/, namespace: 'abide-bound' }, (args) => {
308
424
  const path = args.path.slice(0, -BOUND.length)
309
425
  return {
310
- contents: boundModule(
311
- path,
312
- compiledAt(served, path).server,
313
- ),
426
+ contents: boundModule(path),
314
427
  loader: 'js',
315
428
  }
316
429
  })
@@ -339,7 +452,7 @@ function modulesPlugin(served: Served): Bun.BunPlugin {
339
452
  build.onLoad(
340
453
  { filter: /.*/, namespace: 'abide-table' },
341
454
  (args) => ({
342
- contents: served.table(args.path) ?? '',
455
+ contents: served.tables.get(args.path) ?? '',
343
456
  loader: 'ts',
344
457
  }),
345
458
  )
@@ -355,8 +468,8 @@ function pathOf(importer: string): string {
355
468
 
356
469
  // THE PATH BUN NAMES A FILE BY HAS ITS LINKS RESOLVED, and so has the root a compilation is served
357
470
  // under (`serveCompilation`).
358
- function compiledAt(served: Served, path: string): CompiledFile {
359
- const compiled = served.file(path)
471
+ function compiledAt(served: Held, path: string): CompiledFile {
472
+ const compiled = served.files.get(path)
360
473
  if (compiled === undefined)
361
474
  throw new Error(
362
475
  `abide: ${path} is imported, and no compile of this app reached it.`,
@@ -364,22 +477,12 @@ function compiledAt(served: Served, path: string): CompiledFile {
364
477
  return compiled
365
478
  }
366
479
 
367
- const SCANNER = new Bun.Transpiler({ loader: 'ts' })
368
-
369
480
  // 34.16 — BOUND, NOT RAW. The compiled module is a function of accessors answering its markup (33.4);
370
481
  // what an app's own module calls is a function of plain props answering a `Component` (35.4), which
371
- // `render` invokes with nothing. Every name the module exports rides beside it: a `<script module>`'s,
372
- // and the handlers a `<script server>` declares (34.23).
373
- //
374
- // NAMED RATHER THAN `export *`: the server blocks load side by side at boot, and a star export
375
- // through a module still loading found none of its names.
376
- function boundModule(path: string, server: string): string {
377
- const specifier = JSON.stringify(path)
378
- const names: string[] = []
379
- for (const name of SCANNER.scan(server).exports)
380
- if (name !== 'default') names.push(name)
381
- return `import component from ${specifier}
382
- ${names.length === 0 ? '' : `export { ${names.join(', ')} } from ${specifier}\n`}export default (props = {}) => {
482
+ // `render` invokes with nothing. The component is the module's one export (34.17).
483
+ function boundModule(path: string): string {
484
+ return `import component from ${JSON.stringify(path)}
485
+ export default (props = {}) => {
383
486
  const accessors = {}
384
487
  for (const key in props) {
385
488
  const value = props[key]
@@ -13,6 +13,7 @@ import {
13
13
  HEALTH_ADDRESS,
14
14
  MCP_ADDRESS,
15
15
  OPENAPI_ADDRESS,
16
+ PRIVATE_HANDLERS_ADDRESS,
16
17
  } from '#shared/ADDRESSES.ts'
17
18
  import { APP_META } from '#shared/APP_META.ts'
18
19
  import { DEBUG_META } from '#shared/DEBUG_META.ts'
@@ -102,7 +103,7 @@ type Ranked = RankedError & {
102
103
  /**
103
104
  * One route of the app: the page at it, and the handler answering each method there.
104
105
  *
105
- * 19.1 — one file per method per route, so a route is a row and a method is a column, and 19.21's
106
+ * 19.1 — one file per method per route, so a route is a row and a method is a column, and 19.20's
106
107
  * "the most specific route answering the method" is a walk down the rows asking each column.
107
108
  */
108
109
  type RouteRow = {
@@ -196,7 +197,11 @@ export async function answerRequest(
196
197
  // 37.3 — THE MUX'S UPGRADE PASSES THE APP LANE as every request does (`muxUpgrade`).
197
198
  const admitted = muxAdmission(asked, request, app)
198
199
  if (admitted !== undefined) return admitted
199
- if (asked.startsWith(`${ABIDE_ADDRESS}/`)) {
200
+ // 16.70 — a private handler is answered by the route table, below abide's own namespace.
201
+ if (
202
+ asked.startsWith(`${ABIDE_ADDRESS}/`) &&
203
+ !asked.startsWith(`${PRIVATE_HANDLERS_ADDRESS}/`)
204
+ ) {
200
205
  const answer = app.framework(asked, request)
201
206
  if (answer === undefined) return notFound()
202
207
  return readsOnly(request) ? answer : methodRefused(app, asked, READS)
@@ -222,7 +227,7 @@ export async function answerRequest(
222
227
  refuse(400, 'The address is not a valid path.'),
223
228
  )
224
229
 
225
- // 19.21 — THE MOST SPECIFIC ROUTE ANSWERING THE METHOD, an `OPTIONS` asking about the method
230
+ // 19.20 — THE MOST SPECIFIC ROUTE ANSWERING THE METHOD, an `OPTIONS` asking about the method
226
231
  // its preflight names (16.24). A route that matches and answers some other method is held, so
227
232
  // an address nothing answers the method at is a 405 naming what is answered there (15.17).
228
233
  const preflight = request.method === 'OPTIONS'
@@ -271,7 +276,7 @@ export async function answerRequest(
271
276
  // above it, the root's having run already, and an error in it reaches that route's `onError`.
272
277
  const scope = currentRequestScope() as RequestScope
273
278
  scope.route =
274
- found.handler === undefined ? found.route.pattern : found.handler.route
279
+ found.handler === undefined ? found.route.pattern : found.handler.scope
275
280
  scope.params = found.params
276
281
  const answer = found.route.answers.get(
277
282
  found.handler ?? (found.route.page as Ranked),
@@ -16,7 +16,6 @@ import type { Gated } from '#shared/reactive/argsContract.ts'
16
16
  import { deadlineOf, heldGenerationOf } from '#shared/reactive/index.ts'
17
17
  import type { Defaults } from '#shared/reactive/parameterDefaults.ts'
18
18
  import { notFound, refuse } from '#shared/refuse.ts'
19
- import { routeOf } from '#shared/routePattern.ts'
20
19
  import type { Method, Rpc } from '#shared/rpc.ts'
21
20
  import { argsInURL } from '#shared/rpcRequest.ts'
22
21
  import { VERSION_HEADER } from '#shared/VERSION_HEADER.ts'
@@ -66,7 +65,14 @@ export type Mount = {
66
65
  address: string
67
66
  method: Method
68
67
  source: string
68
+ /** The handler file's name, or the private handler's binding. */
69
69
  name: string
70
+ /** What the source's module exports it as: `default` for a handler file. */
71
+ export: string
72
+ /** The route its hooks are filed at, which its rungs are read over (37.1, 16.72). */
73
+ scope: string
74
+ /** Whether it is a private handler, listed nowhere (16.71). */
75
+ private: boolean
70
76
  /** Whether a browser is handed a wrapper for it. 20.4. */
71
77
  ui: boolean
72
78
  /** Whether the command line lists it — the shell's listing and completion, never whether `call` reaches it. 20.3, 38.62. */
@@ -97,18 +103,15 @@ export type Mount = {
97
103
 
98
104
  /**
99
105
  * A row of the table the build wrote, with the import of the module its handler is declared in —
100
- * the file's server module, the one the route table loads too, so a page and a call reach one
101
- * handler.
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.
102
108
  */
103
109
  export type MountRow = Mount & { load: () => Promise<Record<string, unknown>> }
104
110
 
105
111
  /** A handler's row, mounted: the handler imported and its args schema probed. */
106
112
  export type Mounted = Mount & {
107
- method: Method
108
- /** The route of the file declaring it, which its hooks are filed at (37.1, 37.2). */
109
- route: string
110
113
  /**
111
- * The rungs filed over `route`, read once at mount (16.14) — what an http call and a socket
114
+ * The rungs filed over `scope`, read once at mount (16.14) — what an http call and a socket
112
115
  * subscription to it both pass through (37.2, 18.26).
113
116
  */
114
117
  rungs: AppRung[]
@@ -148,12 +151,14 @@ export function mountedOwn(
148
151
  {
149
152
  address,
150
153
  method: 'GET',
151
- route: '/',
152
154
  // The root's rungs run before routing (37.3), and nothing is filed below a route
153
155
  // of abide's own.
154
156
  rungs: [],
155
157
  source: '',
156
158
  name,
159
+ export: name,
160
+ scope: '/',
161
+ private: false,
157
162
  ui: false,
158
163
  cli: false,
159
164
  mcp: false,
@@ -188,20 +193,17 @@ export async function mountHandlers(
188
193
  const handlers = new Map<string, Mounted>()
189
194
  for (let at = 0; at < table.length; at += 1) {
190
195
  const mount = table[at] as Mount
191
- const exported = (modules[at] as Record<string, unknown>)[mount.name]
196
+ const exported = (modules[at] as Record<string, unknown>)[mount.export]
192
197
  const handler = exported as Rpc<unknown, unknown> & Gated<unknown>
193
198
  const declared = argsDocumentOf(handler)
194
199
  // 16.9, 16.10 — the address `rpc.url` and `rpc.raw` build on, under the mount — and what a
195
200
  // page's render turns a call it made into the browser's request by (41.20).
196
201
  handler.address = underMount(mount.address)
197
- // 23.19 — the route its address answers below, which is its mount's for a package's file,
198
- // never the file's place on disk.
199
- const route = routeOf(mount.address)
200
202
  handlers.set(handlerKey(mount.method, mount.address), {
201
203
  ...mount,
202
- method: mount.method,
203
- route,
204
- rungs: rungsOver(route),
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
+ rungs: rungsOver(mount.scope),
205
207
  handler,
206
208
  argsDocument: declared ?? (mount.args as Document | undefined),
207
209
  wireArgs: declared === undefined ? mount.args : undefined,
@@ -219,7 +221,7 @@ export function handlerKey(method: string, address: string): string {
219
221
  * The `Response` to a call of `found` made over http, `params` being what its route matched.
220
222
  *
221
223
  * `found` answers the request's method — or, for an `OPTIONS`, the method its preflight asks
222
- * about: which handler a request reaches is the route table's to say (19.21).
224
+ * about: which handler a request reaches is the route table's to say (19.20).
223
225
  */
224
226
  export async function answerRpc(
225
227
  found: Mounted,