@abide/abide 0.100.0 → 0.102.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 (160) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/bin/abide.ts +227 -163
  3. package/docs/REGISTRY.md +726 -0
  4. package/docs/RULEBOOK.md +2753 -0
  5. package/docs/abide.md +12878 -0
  6. package/package.json +10 -3
  7. package/scaffold/AGENTS.md +31 -0
  8. package/scaffold/CLAUDE.md +1 -0
  9. package/scaffold/src/layout.abide +18 -0
  10. package/src/compiler/Lowering.ts +78 -9
  11. package/src/compiler/ShadowMap.ts +2 -2
  12. package/src/compiler/TypeSession.ts +262 -67
  13. package/src/compiler/argsSchemaOf.ts +1 -1
  14. package/src/compiler/checkApp.ts +43 -15
  15. package/src/compiler/checkedShadow.ts +39 -5
  16. package/src/compiler/classifyRead.ts +6 -4
  17. package/src/compiler/classifyTemplate.ts +1 -1
  18. package/src/compiler/compileApp.ts +433 -193
  19. package/src/compiler/compiledEntry.ts +43 -39
  20. package/src/compiler/componentGraph.ts +58 -71
  21. package/src/compiler/declaredExports.ts +24 -7
  22. package/src/compiler/emitClient.ts +141 -71
  23. package/src/compiler/emitComponent.ts +37 -8
  24. package/src/compiler/emitSource.ts +106 -13
  25. package/src/compiler/handlersUnder.ts +106 -56
  26. package/src/compiler/index.ts +7 -0
  27. package/src/compiler/mountsOf.ts +138 -0
  28. package/src/compiler/nearest.ts +14 -0
  29. package/src/compiler/parseAbide.ts +157 -20
  30. package/src/compiler/projectShadow.ts +213 -7
  31. package/src/compiler/reactiveTypes.ts +20 -7
  32. package/src/compiler/readSources.ts +15 -2
  33. package/src/compiler/readsIn.ts +61 -7
  34. package/src/compiler/renderMarkup.ts +3 -3
  35. package/src/compiler/scaffold.ts +30 -11
  36. package/src/compiler/serveLanguage.ts +125 -34
  37. package/src/compiler/sourceMapOf.ts +193 -0
  38. package/src/compiler/splitImports.ts +205 -83
  39. package/src/compiler/syncTypes.ts +32 -0
  40. package/src/compiler/tokenize.ts +14 -0
  41. package/src/compiler/writeDefaultProject.ts +1 -1
  42. package/src/compiler/writeTypes.ts +82 -0
  43. package/src/server/CLIENT_BUILD.ts +11 -13
  44. package/src/server/DEFAULT_PORT.ts +1 -1
  45. package/src/server/LAUNCHER_FILES.ts +9 -0
  46. package/src/server/MODULE_QUERIES.ts +18 -0
  47. package/src/server/TABLES.ts +7 -0
  48. package/src/server/VIRTUAL_DIRECTORY.ts +4 -5
  49. package/src/server/abideModules.ts +391 -0
  50. package/src/server/answerFailure.ts +16 -6
  51. package/src/server/answerRequest.ts +235 -106
  52. package/src/server/answerRpc.ts +124 -39
  53. package/src/server/answerSocket.ts +141 -48
  54. package/src/server/answerValue.ts +22 -9
  55. package/src/server/appDirectories.ts +1 -1
  56. package/src/server/appPlugins.ts +1 -1
  57. package/src/server/appTree.ts +179 -0
  58. package/src/server/argsFor.ts +2 -2
  59. package/src/server/assistant.ts +2 -2
  60. package/src/server/bridgeRelay.ts +6 -8
  61. package/src/server/claudeCode.ts +0 -6
  62. package/src/server/clientBuildOptions.ts +8 -3
  63. package/src/server/coerceArgs.ts +1 -1
  64. package/src/server/commands.ts +37 -37
  65. package/src/server/componentModules.d.ts +3 -1
  66. package/src/server/compressAnswer.ts +48 -14
  67. package/src/server/config.ts +25 -29
  68. package/src/server/configFields.ts +31 -25
  69. package/src/server/createApp.ts +59 -73
  70. package/src/server/devWorker.ts +144 -183
  71. package/src/server/executableName.ts +4 -0
  72. package/src/server/fenceServer.ts +37 -35
  73. package/src/server/generatedSurfaces.ts +99 -66
  74. package/src/server/hooks.ts +124 -52
  75. package/src/server/iconAccent.ts +191 -0
  76. package/src/server/index.ts +11 -7
  77. package/src/server/installSourceMaps.ts +88 -0
  78. package/src/server/launcherOptions.ts +34 -31
  79. package/src/server/launcherPage.ts +107 -64
  80. package/src/server/manifestOf.ts +16 -13
  81. package/src/server/namesUnder.ts +2 -2
  82. package/src/server/nativeMenu.ts +5 -5
  83. package/src/server/negotiateEncoding.ts +1 -1
  84. package/src/server/principal.ts +10 -5
  85. package/src/server/realOrAsGiven.ts +10 -0
  86. package/src/server/refuseAddressCollision.ts +21 -14
  87. package/src/server/remote.ts +49 -52
  88. package/src/server/render.ts +65 -94
  89. package/src/server/renderComponent.ts +5 -0
  90. package/src/server/reportHealth.ts +2 -2
  91. package/src/server/requestScope.ts +12 -1
  92. package/src/server/runBuild.ts +24 -0
  93. package/src/server/runCompiled.ts +5 -5
  94. package/src/server/runDesktop.ts +52 -27
  95. package/src/server/serveBuild.ts +43 -52
  96. package/src/server/serveDev.ts +81 -39
  97. package/src/server/shell.ts +7 -22
  98. package/src/server/sourceMappings.ts +139 -0
  99. package/src/server/surfaceOf.ts +18 -3
  100. package/src/server/testBuild.ts +1 -19
  101. package/src/server/withAbideSources.ts +117 -0
  102. package/src/shared/ADDRESSES.ts +7 -18
  103. package/src/shared/APP_META.ts +7 -0
  104. package/src/shared/CopyOptions.ts +1 -1
  105. package/src/shared/DEBUG_META.ts +1 -1
  106. package/src/shared/GLOBAL_GETS.ts +9 -3
  107. package/src/shared/RESERVED_DIRECTORIES.ts +15 -0
  108. package/src/shared/RPC_SERVER.ts +2 -2
  109. package/src/shared/cookies.ts +2 -2
  110. package/src/shared/csp.ts +1 -1
  111. package/src/shared/escapeText.ts +1 -1
  112. package/src/shared/filledPath.ts +85 -0
  113. package/src/shared/framing.ts +79 -6
  114. package/src/shared/headKeyOf.ts +2 -0
  115. package/src/shared/index.ts +1 -1
  116. package/src/shared/log.ts +27 -13
  117. package/src/shared/matchRoute.ts +23 -4
  118. package/src/shared/menuItem.ts +3 -3
  119. package/src/shared/reactive/Cursor.ts +3 -9
  120. package/src/shared/reactive/Keyed.ts +4 -3
  121. package/src/shared/reactive/NotLanded.ts +1 -1
  122. package/src/shared/reactive/Reactive.ts +81 -60
  123. package/src/shared/reactive/argsContract.ts +4 -4
  124. package/src/shared/reactive/canonicalKey.ts +4 -4
  125. package/src/shared/reactive/chunksOfStream.ts +6 -0
  126. package/src/shared/reactive/index.ts +1 -1
  127. package/src/shared/reactive/recordWrites.ts +103 -5
  128. package/src/shared/reactive/structural.ts +1 -1
  129. package/src/shared/reactive/tracking.ts +155 -21
  130. package/src/shared/reactive/watch.ts +10 -6
  131. package/src/shared/refuse.ts +1 -1
  132. package/src/shared/routePattern.ts +82 -46
  133. package/src/shared/rpc.ts +47 -13
  134. package/src/shared/rpcRequest.ts +27 -3
  135. package/src/shared/url.ts +4 -44
  136. package/src/shared/writePath.ts +1 -1
  137. package/src/ui/LAYER_NAMES.ts +1 -1
  138. package/src/ui/List.ts +3 -3
  139. package/src/ui/MOUNTING_LEVEL.ts +18 -0
  140. package/src/ui/Mismatch.ts +1 -1
  141. package/src/ui/RawRun.ts +1 -1
  142. package/src/ui/Router.ts +38 -31
  143. package/src/ui/Scope.ts +1 -1
  144. package/src/ui/TryRegion.ts +2 -2
  145. package/src/ui/applyHead.ts +33 -0
  146. package/src/ui/index.ts +1 -1
  147. package/src/ui/menuBridge.ts +1 -1
  148. package/src/ui/moveNode.ts +1 -1
  149. package/src/ui/reconcile.ts +2 -2
  150. package/src/ui/reportMenu.ts +2 -2
  151. package/src/ui/routing.ts +2 -2
  152. package/src/ui/setAttribute.ts +1 -1
  153. package/src/ui/setText.ts +1 -1
  154. package/src/ui/socketWire.ts +9 -7
  155. package/src/ui/wire.ts +1 -1
  156. package/scaffold/src/server/app.ts +0 -6
  157. package/scaffold/src/shared/.gitkeep +0 -0
  158. package/scaffold/src/ui/app.html +0 -8
  159. package/src/server/componentImports.ts +0 -56
  160. /package/scaffold/src/{ui/pages/page.abide → page.abide} +0 -0
@@ -11,6 +11,7 @@
11
11
 
12
12
  import { AsyncLocalStorage } from 'node:async_hooks' // bun implements AsyncLocalStorage and has no context-carrying api of its own
13
13
  import { Cookies } from '#shared/cookies.ts'
14
+ import type { Params } from '#shared/matchRoute.ts'
14
15
  import type { Principal } from '#shared/principal.ts'
15
16
  import { SCOPING, type Scope } from '#shared/reactive/scoping.ts'
16
17
  import type { RpcRequest } from '#shared/rpcRequest.ts'
@@ -51,6 +52,14 @@ export type RequestScope = Scope & {
51
52
  trace: { id: string; span: string; flags: string } | undefined
52
53
  /** `undefined` where the scope was opened without one — 42.8's scope, which 22.1 throws in. */
53
54
  request: Request | undefined
55
+ /**
56
+ * The route the request matched, once routing has run: the file route whose hooks answer it.
57
+ *
58
+ * 37.5 — which `onError` hooks an error in this request reaches.
59
+ */
60
+ route: string | undefined
61
+ /** What that route matched in the address, for the answer its rungs reach (16.14). */
62
+ params: Params | undefined
54
63
  /**
55
64
  * The app that opened this scope: the `App.fetch` `RPC_SERVER.raw` answers through, and its MCP
56
65
  * surface.
@@ -158,6 +167,8 @@ export function scopeFor(
158
167
  principal: undefined,
159
168
  applied: false,
160
169
  request: given,
170
+ route: undefined,
171
+ params: undefined,
161
172
  received,
162
173
  app,
163
174
  response: { status: 200, headers: new Headers() },
@@ -376,7 +387,7 @@ export function handlerTook(rpc: object, duration: number): void {
376
387
  /**
377
388
  * Adds `duration` to the time spent in rungs.
378
389
  *
379
- * 21.20 — or takes it back out where it was the centre's.
390
+ * 21.20 — or takes it back out where it was the center's.
380
391
  */
381
392
  export function rungsTook(duration: number): void {
382
393
  const timing = STORAGE.getStore()?.timing
@@ -0,0 +1,24 @@
1
+ // The CLI, spawned rather than imported: the compiler is not reachable from the server seam.
2
+ const ABIDE_BIN = Bun.fileURLToPath(
3
+ new URL('../../bin/abide.ts', import.meta.url),
4
+ )
5
+
6
+ /**
7
+ * `abide build` of the app under `root` into `out`, run as the CLI.
8
+ *
9
+ * SPAWNED RATHER THAN IMPORTED: the compiler is not reachable from the server seam.
10
+ */
11
+ export async function runBuild(root: string, out: string): Promise<void> {
12
+ const child = Bun.spawn(
13
+ [process.execPath, ABIDE_BIN, 'build', '--root', root, '--out', out],
14
+ { stdout: 'ignore', stderr: 'pipe' },
15
+ )
16
+ const [exitCode, said] = await Promise.all([
17
+ child.exited,
18
+ new Response(child.stderr).text(),
19
+ ])
20
+ if (exitCode !== 0)
21
+ throw new Error(
22
+ `abide: could not build the app under ${root}.\n${said}`,
23
+ )
24
+ }
@@ -1,4 +1,4 @@
1
- // 38.51 — A COMPILED APP'S COMMAND LINE: `abide`'s list (`commands.ts`), over the app the executable
1
+ // 38.50 — A COMPILED APP'S COMMAND LINE: `abide`'s list (`commands.ts`), over the app the executable
2
2
  // holds rather than one on disk, and spoken under the name it was started by — a `--platforms` build
3
3
  // is `ledger-linux-x64`, and that is what a reader types.
4
4
 
@@ -9,11 +9,11 @@ import { type BuildParts, builtSources, serveApp } from './serveBuild.ts'
9
9
  export async function runCompiled(
10
10
  origin: AppOrigin,
11
11
  parts: BuildParts,
12
- // 38.57 — every specifier a script `run` may import out of the executable: abide's own entries
13
- // and the app's handler modules, each to the module it holds.
12
+ // 38.56 — every specifier a script `run` may import out of the executable: abide's own entries
13
+ // and the app's `.abide` modules, each to the module it holds.
14
14
  specifiers: Record<string, () => Promise<Record<string, unknown>>>,
15
15
  ): Promise<void> {
16
- // REGISTERED AT START AND LOADED ON FIRST IMPORT: a handler module a script never names never
16
+ // REGISTERED AT START AND LOADED ON FIRST IMPORT: an app module a script never names never
17
17
  // runs its top level, as it would not under `abide run`.
18
18
  Bun.plugin({
19
19
  name: 'abide:compiled-specifiers',
@@ -30,7 +30,7 @@ export async function runCompiled(
30
30
  handlers: async () => parts.handlers,
31
31
  appPackage: () => origin.package,
32
32
  boot: () => bootApp(origin, () => builtSources(parts)),
33
- // 38.50.
33
+ // 38.49.
34
34
  serve: ({ values: { port } }) =>
35
35
  serveApp(
36
36
  origin,
@@ -1,4 +1,4 @@
1
- // 38.71 — WHAT `abide bundle` WRITES, STARTED WITH NO COMMAND: a window on the app. Given a command
1
+ // 38.70 — WHAT `abide bundle` WRITES, STARTED WITH NO COMMAND: a window on the app. Given a command
2
2
  // it is the executable `abide compile` writes (`runCompiled`), so a terminal still reaches every one.
3
3
  //
4
4
  // ONE PROCESS. The app, the launcher's own page and the window share it: AppKit's events are drained
@@ -25,18 +25,19 @@ import {
25
25
  import { APP_PACKAGE, configuredMount, dataDirectory } from './config.ts'
26
26
  import { configFields } from './configFields.ts'
27
27
  import { type App, type AppOrigin, bootApp } from './createApp.ts'
28
+ import { executableName } from './executableName.ts'
28
29
  import { reportUnexpected } from './hooks.ts'
29
30
  import { installServer } from './installServer.ts'
30
- import type { LauncherOptions } from './launcherOptions.ts'
31
+ import { launcherOf } from './launcherOptions.ts'
31
32
  import { type Launch, launcherPage } from './launcherPage.ts'
32
33
  import { installMenu, menuBar, type NativeItem } from './nativeMenu.ts'
33
34
  import { randomToken } from './randomToken.ts'
34
- import { type Component, render } from './render.ts'
35
+ import { type Component, renderDocument } from './render.ts'
35
36
  import { LISTENING } from './requestScope.ts'
36
37
  import { runCompiled } from './runCompiled.ts'
37
38
  import { type BuildParts, builtSources } from './serveBuild.ts'
38
39
 
39
- /** What the launcher did last, and what its forms held, kept across launches. 38.74. */
40
+ /** What the launcher did last, and what its forms held, kept across launches. 38.73. */
40
41
  type Remembered = {
41
42
  last: { start: Record<string, string> } | { connect: string } | null
42
43
  values: Record<string, string>
@@ -50,15 +51,22 @@ type Failure = {
50
51
  issues: Record<string, string[]>
51
52
  }
52
53
 
53
- /** What `abide bundle` read out of `src/bundle/`, carried in the executable. */
54
+ /** What `abide bundle` read of the launcher's files, carried in the executable. */
54
55
  export type Desktop = {
55
- options: LauncherOptions
56
- /** 38.83 — `src/bundle/launch.abide`, where the app has one. */
56
+ /** 38.80 — `src/launcher.abide`, where the app has one: its `launcher`, and its page. */
57
57
  launch:
58
- | (() => Promise<{ default: (props: Launch) => Component }>)
58
+ | (() => Promise<{
59
+ default: (props: Launch) => Component
60
+ launcher?: unknown
61
+ }>)
59
62
  | undefined
60
- /** The document the launch page renders into, its stylesheet already in its head. */
61
- shell: string
63
+ /** 38.82 — whether that file has markup of its own, which then stands in for the built-in page. */
64
+ custom: boolean
65
+ /** The document the launch page renders into, its stylesheet already in its head (38.84). */
66
+ document: string
67
+ /** 38.101 — the app's icon as a data URL, and the accent taken from it. */
68
+ icon: string | undefined
69
+ accent: string | undefined
62
70
  }
63
71
 
64
72
  const LOOPBACK = '127.0.0.1'
@@ -79,14 +87,24 @@ export async function runDesktop(
79
87
  async function launch(
80
88
  origin: AppOrigin,
81
89
  load: () => ReturnType<typeof builtSources>,
82
- { options, launch: launchPage, shell }: Desktop,
90
+ { launch: launchModule, custom, document, icon, accent }: Desktop,
83
91
  ): Promise<void> {
84
92
  installServer()
85
93
  APP_PACKAGE.current = origin.package
86
94
  // 37.1 — imported for what it files, so the start form reads `onConfig`.
87
95
  await origin.hooks?.()
88
- const appName =
89
- options.name ?? (origin.package.name ?? 'app').replace(/^@[^/]+\//, '')
96
+ // 34.32 — the `launcher` export, read off its module as the app launches. 38.85 — one that fails
97
+ // to load leaves the built-in page, with its defaults.
98
+ const launcherModule = await launchModule?.().catch((thrown: unknown) => {
99
+ reportUnexpected(thrown)
100
+ return undefined
101
+ })
102
+ const options = launcherOf(launcherModule?.launcher)
103
+ // 38.81 — a `launcher` the checker would have refused, written where nothing checked it.
104
+ if (typeof options === 'string')
105
+ throw new Error(`abide: ${options}. RULEBOOK 38.81.`)
106
+ const launchPage = custom ? launcherModule : undefined
107
+ const appName = executableName(origin.package.name)
90
108
  const directory = dataDirectory(origin.package.name ?? 'app')
91
109
  const stateFile = `${directory}/desktop.json`
92
110
  const remembered: Remembered = {
@@ -149,21 +167,24 @@ async function launch(
149
167
  )
150
168
  return connected === undefined ? onwards() : page('connect')
151
169
  }
170
+ // The Connect… menu item asks for the page with its connect form open.
152
171
  return page(
153
172
  url.searchParams.get('focus') === 'connect'
154
173
  ? 'connect'
155
- : 'start',
174
+ : undefined,
156
175
  )
157
176
  },
158
177
  })
159
178
  const base = `${launcher.url.origin}/${token}/`
160
- // 38.72 — the answer to a start or a connect that took: the app the window now shows.
179
+ // 38.71 — the answer to a start or a connect that took: the app the window now shows.
161
180
  const onwards = (): Response =>
162
181
  new Response(null, {
163
182
  status: 303,
164
183
  headers: { location: current?.url ?? base },
165
184
  })
166
- const page = async (focus: 'start' | 'connect'): Promise<Response> => {
185
+ const page = async (
186
+ focus: 'start' | 'connect' | undefined,
187
+ ): Promise<Response> => {
167
188
  const shown = failure
168
189
  failure = undefined
169
190
  const props: Launch = {
@@ -173,17 +194,21 @@ async function launch(
173
194
  fields: configFields(derived()),
174
195
  url: remembered.url,
175
196
  failure: shown,
176
- focus: shown?.form ?? (options.start ? focus : 'connect'),
197
+ focus:
198
+ shown?.form ??
199
+ (focus === 'connect' && options.connect ? focus : undefined),
200
+ icon,
201
+ accent,
177
202
  }
178
203
  const headers = { 'content-type': 'text/html; charset=utf-8' }
179
204
  if (launchPage !== undefined)
180
205
  try {
181
206
  return new Response(
182
- await render((await launchPage()).default(props), shell),
207
+ await renderDocument(launchPage.default(props), document),
183
208
  { headers },
184
209
  )
185
210
  } catch (thrown) {
186
- // 38.86 — the built-in page stands in for one that failed.
211
+ // 38.85 — the built-in page stands in for one that failed.
187
212
  reportUnexpected(thrown)
188
213
  }
189
214
  return new Response(launcherPage(props), { headers })
@@ -196,7 +221,7 @@ async function launch(
196
221
  await leaving.app.stop().catch(reportUnexpected)
197
222
  }
198
223
 
199
- // 38.73 — the form's values are the environment the app is booted under, so 27.8 coerces them
224
+ // 38.72 — the form's values are the environment the app is booted under, so 27.8 coerces them
200
225
  // and 27.7's schema refuses them as it would any other.
201
226
  async function start(
202
227
  values: Record<string, string>,
@@ -215,7 +240,7 @@ async function launch(
215
240
  }
216
241
  try {
217
242
  await app
218
- // 38.76.
243
+ // 38.75.
219
244
  .listen({ hostname: LOOPBACK, port: remembered.port })
220
245
  // The port the last launch served on, so the app's origin — and its storage with
221
246
  // it — is the one it had; any free one where that is taken.
@@ -296,7 +321,7 @@ async function launch(
296
321
  types: 'Q@:@',
297
322
  args: [FFIType.ptr],
298
323
  returns: FFIType.u64,
299
- // NSTerminateCancel: 38.77 — the app is stopped first, and the process ends after.
324
+ // NSTerminateCancel: 38.76 — the app is stopped first, and the process ends after.
300
325
  run: () => {
301
326
  quit()
302
327
  return 0
@@ -382,20 +407,20 @@ async function launch(
382
407
  ),
383
408
  null,
384
409
  )
385
- // 38.89 — THE MENU BAR IS THE PAGE'S: the defaults until the page the window loads tells it
410
+ // 38.88 — THE MENU BAR IS THE PAGE'S: the defaults until the page the window loads tells it
386
411
  // what its route exports, and again after every navigation it makes.
387
412
  const showMenu = (told: NativeItem[]): void =>
388
413
  installMenu(menuBar(appName, options.connect, told), (item) => {
389
414
  if (item.role === 'connect')
390
415
  return navigate(`${base}?focus=connect`)
391
- // 38.75.
416
+ // 38.74.
392
417
  if (item.role === 'disconnect') {
393
418
  remembered.last = null
394
419
  void remember()
395
420
  return void disconnect().then(() => navigate(base))
396
421
  }
397
422
  if (item.role === 'reload') return void send(view, 'reload')
398
- // 38.91, 38.92 — the page that numbered the item does what it means.
423
+ // 38.90, 38.91 — the page that numbered the item does what it means.
399
424
  if (item.id !== undefined)
400
425
  send(
401
426
  view,
@@ -433,7 +458,7 @@ async function launch(
433
458
  nsString('abideMenu'),
434
459
  )
435
460
  const navigate = (address: string): void => {
436
- // 38.93.
461
+ // 38.92.
437
462
  showMenu([])
438
463
  send(
439
464
  view,
@@ -485,7 +510,7 @@ async function launch(
485
510
  send(window, 'setTitle:', nsString(title))
486
511
  }, DRAIN_MILLISECONDS)
487
512
 
488
- // 38.74 — WHAT IT DID LAST, done again.
513
+ // 38.73 — WHAT IT DID LAST, done again.
489
514
  const last = remembered.last
490
515
  if (options.start && last !== null && 'start' in last)
491
516
  await start(last.start)
@@ -1,4 +1,4 @@
1
- // 38.6 and 38.11 — `abide start` and a compiled app: the built app, over http. The request path is
1
+ // 38.6 and 38.10 — `abide start` and a compiled app: the built app, over http. The request path is
2
2
  // `answerRequest`'s, shared with `abide dev`; what this module is, is the SUPPLIER — where each part
3
3
  // comes from when the parts are what `abide build` produced.
4
4
  //
@@ -8,19 +8,22 @@
8
8
 
9
9
  import { CLIENT_ADDRESS } from '#shared/ADDRESSES.ts'
10
10
  import {
11
+ type Compilation,
12
+ serveCompilation,
13
+ servedTable,
14
+ } from './abideModules.ts'
15
+ import {
16
+ type AppSources,
11
17
  appSources,
12
- type LoadedSources,
13
- type ServerTable,
14
18
  type SourceParts,
15
19
  } from './answerRequest.ts'
16
- import type { Mount } from './answerRpc.ts'
17
20
  import {
18
21
  CLIENT_DIRECTORY,
22
+ COMPILATION,
19
23
  ENCODING_SUFFIX,
20
24
  MANIFEST,
21
25
  PUBLIC_DIRECTORY,
22
26
  } from './CLIENT_BUILD.ts'
23
- import { componentImports } from './componentImports.ts'
24
27
  import { type App, type AppOrigin, bootApp } from './createApp.ts'
25
28
  import { stopOnSignalOrFault } from './faultPolicy.ts'
26
29
  import type { Manifest } from './manifestOf.ts'
@@ -30,13 +33,13 @@ import {
30
33
  type Encoding,
31
34
  negotiateEncoding,
32
35
  } from './negotiateEncoding.ts'
33
- import { shellUnder } from './render.ts'
36
+ import { runBuild } from './runBuild.ts'
34
37
 
35
38
  /**
36
39
  * What `abide build` produced, as the parts an `App` is booted over.
37
40
  *
38
41
  * Read off disk by `abide start` (`loadBuild`), and out of the executable itself by a compiled app
39
- * (38.11). ONE SHAPE, so the two are answered by one path and differ only in where a byte is read
42
+ * (38.10). ONE SHAPE, so the two are answered by one path and differ only in where a byte is read
40
43
  * from — a file is a PATH in both, and `Bun.file` reads the executable's own the same way it reads
41
44
  * the disk's.
42
45
  */
@@ -53,7 +56,7 @@ export type BuildParts = Omit<SourceParts, 'client' | 'explained'> & {
53
56
  */
54
57
  export async function serveApp(
55
58
  origin: AppOrigin,
56
- load: () => Promise<LoadedSources>,
59
+ load: () => Promise<AppSources>,
57
60
  port: number | undefined,
58
61
  ): Promise<App> {
59
62
  const app = await bootApp(origin, load)
@@ -64,63 +67,51 @@ export async function serveApp(
64
67
  }
65
68
 
66
69
  /**
67
- * A loader for a handler's module, out of the app's source tree.
70
+ * Serves the compilation `abide build` kept in `out` to this process's imports; `false` where there
71
+ * is none, because the app under `root` holds nothing to build.
68
72
  *
69
- * `compiled` is where the app's components were compiled to, which a `.abide` the module imports
70
- * lands on (`componentImports`).
73
+ * 38.6 — what `abide start` boots: no compiler is loaded, and every `.abide` the app imports is
74
+ * answered out of what the build compiled. 38.100 — AN APP NOBODY BUILT IS BUILT, by `build`, rather
75
+ * than refused, so every caller boots the same way whether or not a build came first. The CLI passes
76
+ * its own build, in-process; anything else spawns it.
71
77
  */
72
- export function sourceModuleLoader(
78
+ export async function serveBuild(
73
79
  root: string,
74
- compiled: string,
75
- ): (source: string) => Promise<Record<string, unknown>> {
76
- Bun.plugin(componentImports(root, compiled))
77
- return (source) => import(Bun.pathToFileURL(`${root}/${source}`).href)
78
- }
79
-
80
- /** The handler table a build wrote into `directory`. */
81
- export async function handlersIn(directory: string): Promise<Mount[]> {
82
- const rpc = (await import(
83
- Bun.pathToFileURL(`${directory}/rpc.ts`).href
84
- )) as {
85
- handlers: Mount[]
80
+ out: string,
81
+ build: (root: string, out: string) => Promise<unknown> = runBuild,
82
+ ): Promise<boolean> {
83
+ const path = `${out}/${COMPILATION}`
84
+ if (!(await Bun.file(path).exists())) {
85
+ await build(root, out)
86
+ // A FRESH HANDLE: one taken before the build wrote the file reads it as empty.
87
+ if (!(await Bun.file(path).exists())) return false
86
88
  }
87
- return rpc.handlers
89
+ serveCompilation(root, (await Bun.file(path).json()) as Compilation)
90
+ return true
88
91
  }
89
92
 
90
- /** What `abide build` left on disk, as the sources an `App` is booted over. */
93
+ /**
94
+ * What `abide build` left on disk, as the sources an `App` is booted over — its compilation served
95
+ * already (`serveBuild`).
96
+ */
91
97
  export async function loadBuild(options: {
92
98
  /**
93
- * The app's source root — the directory `ui/` and `server/` sit under, and the one 35.6's
94
- * shell is resolved against.
99
+ * The app's source root — the directory its pages sit under.
95
100
  */
96
101
  root: string
97
102
  /** Where `abide build` put its output. */
98
103
  out: string
99
- }): Promise<LoadedSources> {
100
- if (!(await Bun.file(`${options.out}/server.ts`).exists()))
101
- throw new Error(
102
- `abide: no build at ${options.out}. Run \`abide build\` first.`,
103
- )
104
- // THE BOOT READS OVERLAP: the route table, the handler table, the shell and the manifest are
105
- // independent of each other.
106
- //
107
- // THE SHELL IS READ ONCE, at boot. `render`'s own note prices the per-render read at 98% of the
108
- // op and leaves the caching to whatever server got written; this is that server. The manifest
109
- // names the stylesheet only where the bundler collected one, so an app that imports none links
110
- // none.
111
- const [table, handlers, shell, manifest] = await Promise.all([
112
- import(
113
- Bun.pathToFileURL(`${options.out}/server.ts`).href
114
- ) as Promise<ServerTable>,
115
- handlersIn(options.out),
116
- shellUnder(options.root),
104
+ }): Promise<AppSources> {
105
+ // THE BOOT READS OVERLAP: the tables and the manifest are independent of each other. The
106
+ // manifest names the stylesheet only where the bundler collected one, so an app that imports
107
+ // none links none.
108
+ const [table, manifest] = await Promise.all([
109
+ servedTable(options.root),
117
110
  Bun.file(`${options.out}/${MANIFEST}`).json() as Promise<Manifest>,
118
111
  ])
119
112
  return builtSources({
120
113
  table,
121
- handlers,
122
- handlerModule: sourceModuleLoader(options.root, options.out),
123
- shell,
114
+ handlers: table.handlers,
124
115
  manifest,
125
116
  client: pathsUnder(`${options.out}/${CLIENT_DIRECTORY}`),
126
117
  published: pathsUnder(`${options.out}/${PUBLIC_DIRECTORY}`),
@@ -136,9 +127,9 @@ export function pathsUnder(directory: string): Map<string, string> {
136
127
  }
137
128
 
138
129
  /** The sources an `App` is booted over, out of the parts `abide build` produced. */
139
- export function builtSources(parts: BuildParts): Promise<LoadedSources> {
130
+ export function builtSources(parts: BuildParts): Promise<AppSources> {
140
131
  // THE BUNDLE, ENUMERATED RATHER THAN RESOLVED. A request names a file in this set or it names
141
- // nothing — there is no path to normalise, so `..` is not a case that has to be got right,
132
+ // nothing — there is no path to normalize, so `..` is not a case that has to be got right,
142
133
  // and what is servable is exactly what the build produced.
143
134
  //
144
135
  // THE PRECOMPRESSED COPIES ARE NOT ADDRESSES OF THEIR OWN. They are the same file in another
@@ -173,7 +164,7 @@ export function builtSources(parts: BuildParts): Promise<LoadedSources> {
173
164
  'content-type': type,
174
165
  vary: 'accept-encoding',
175
166
  }
176
- // 38.30 — the precompressed copy where the request takes its encoding.
167
+ // 38.29 — the precompressed copy where the request takes its encoding.
177
168
  const encoding = negotiateEncoding(
178
169
  request.headers.get('accept-encoding'),
179
170
  encodings,