@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.
Files changed (85) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/abide.ts +80 -173
  3. package/docs/REGISTRY.md +17 -17
  4. package/docs/RULEBOOK.md +50 -12
  5. package/docs/abide.md +9 -9
  6. package/package.json +1 -1
  7. package/src/compiler/Lowering.ts +15 -5
  8. package/src/compiler/Refusals.ts +57 -0
  9. package/src/compiler/TypeSession.ts +45 -86
  10. package/src/compiler/appLayout.ts +447 -0
  11. package/src/compiler/appMap.ts +21 -20
  12. package/src/compiler/checkApp.ts +61 -97
  13. package/src/compiler/checkedShadow.ts +11 -9
  14. package/src/compiler/classifyRead.ts +3 -6
  15. package/src/compiler/compileApp.ts +180 -512
  16. package/src/compiler/componentGraph.ts +2 -71
  17. package/src/compiler/declaredExports.ts +79 -60
  18. package/src/compiler/emitClient.ts +11 -13
  19. package/src/compiler/emitComponent.ts +21 -6
  20. package/src/compiler/emitSource.ts +20 -20
  21. package/src/compiler/handlersUnder.ts +46 -36
  22. package/src/compiler/hookCalls.ts +152 -0
  23. package/src/compiler/mountsOf.ts +31 -54
  24. package/src/compiler/readSources.ts +27 -42
  25. package/src/compiler/readsIn.ts +5 -16
  26. package/src/compiler/resolverIn.ts +77 -0
  27. package/src/server/abideModules.ts +92 -123
  28. package/src/server/answerRequest.ts +18 -4
  29. package/src/server/answerSocket.ts +136 -76
  30. package/src/server/answerValue.ts +28 -15
  31. package/src/server/appTree.ts +49 -71
  32. package/src/server/claudeApi.ts +2 -5
  33. package/src/server/clientBundle.ts +198 -0
  34. package/src/server/coerceArgs.ts +1 -1
  35. package/src/server/commands.ts +93 -57
  36. package/src/server/createApp.ts +90 -16
  37. package/src/server/deferredBuilds.ts +175 -0
  38. package/src/server/devWorker.ts +107 -319
  39. package/src/server/faceOf.ts +120 -0
  40. package/src/server/fenceServer.ts +10 -18
  41. package/src/server/index.ts +3 -0
  42. package/src/server/placement.ts +167 -0
  43. package/src/server/remote.ts +14 -42
  44. package/src/server/render.ts +25 -17
  45. package/src/server/renderComponent.ts +9 -5
  46. package/src/server/requestScope.ts +10 -4
  47. package/src/server/responseRelay.ts +389 -0
  48. package/src/server/runCompiled.ts +9 -12
  49. package/src/server/runDesktop.ts +6 -9
  50. package/src/server/serveBuild.ts +26 -73
  51. package/src/server/serveDev.ts +19 -176
  52. package/src/server/streamEvent.ts +29 -0
  53. package/src/server/withAbideSources.ts +2 -1
  54. package/src/shared/HandlerRoute.ts +68 -0
  55. package/src/shared/REFUSAL_KEY.ts +5 -0
  56. package/src/shared/answerOf.ts +105 -78
  57. package/src/shared/filledPath.ts +1 -9
  58. package/src/shared/framing.ts +127 -128
  59. package/src/shared/queryText.ts +15 -7
  60. package/src/shared/reactive/Cursor.ts +4 -2
  61. package/src/shared/reactive/Reactive.ts +285 -197
  62. package/src/shared/reactive/index.ts +11 -8
  63. package/src/shared/reactive/pairReactive.ts +1 -1
  64. package/src/shared/reactive/scoped.ts +4 -3
  65. package/src/shared/reactive/share.ts +11 -1
  66. package/src/shared/routePattern.ts +0 -123
  67. package/src/shared/rpc.ts +17 -24
  68. package/src/shared/rpcRequest.ts +18 -47
  69. package/src/shared/takeEarlyCall.ts +12 -37
  70. package/src/shared/url.ts +1 -1
  71. package/src/ui/Level.ts +276 -0
  72. package/src/ui/List.ts +17 -17
  73. package/src/ui/Router.ts +20 -87
  74. package/src/ui/applyHead.ts +92 -79
  75. package/src/ui/index.ts +4 -2
  76. package/src/ui/leave.ts +5 -0
  77. package/src/ui/reconcile.ts +23 -104
  78. package/src/ui/regions.ts +27 -2
  79. package/src/ui/rows.ts +123 -2
  80. package/src/ui/wire.ts +12 -27
  81. package/src/compiler/hooksCalled.ts +0 -35
  82. package/src/server/clientBuildOptions.ts +0 -54
  83. package/src/server/refuseAddressCollision.ts +0 -37
  84. package/src/shared/reactive/chunksOfStream.ts +0 -33
  85. package/src/ui/MOUNTING_LEVEL.ts +0 -18
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @abide/abide
2
2
 
3
+ ## 0.105.1
4
+
5
+ ### Patch Changes
6
+
7
+ - fdfe14c: an emptied list clears its region in one write ([`b190cd1`](https://github.com/briancray/abide/commit/b190cd19cd5cf0bda5797a6308eddb5ada1af13b))
8
+
9
+ ## 0.105.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 98e55a3: the app is laid out once per compile, hooks are read by what a module imports, and abide lsp reports the tree's refusals (37.1, 38.34) ([`3b3c6c2`](https://github.com/briancray/abide/commit/3b3c6c277f9cc65a9b08a0d3b4115e7ff8e7c3c5))
14
+ - 98e55a3: a child whose setup made a call is built after the turn, as a page is (41.28) ([`6ae9acd`](https://github.com/briancray/abide/commit/6ae9acd75855b446de1680e4413deb47e0a223d9))
15
+ - 98e55a3: a component's <head> follows the component, and an unkeyed contribution replaces in place and leaves with it (39.13-39.16) ([`8dc5e9e`](https://github.com/briancray/abide/commit/8dc5e9e22054a13bb7fa648ef7df11a243f19639))
16
+
17
+ ### Patch Changes
18
+
19
+ - 98e55a3: streamed answers end with their run, carry X-Accel-Buffering, and sse is read by its fields (16.75, 16.76, 16.77, 18.27) ([`696c5cb`](https://github.com/briancray/abide/commit/696c5cb61cbeb0d83382d742924ff8a35cd14339))
20
+ - 98e55a3: a command means one app, reports its first build on stderr, and abide openapi prints to stdout (38.25, 38.110, 38.111) ([`7ca9f4b`](https://github.com/briancray/abide/commit/7ca9f4b90294ca34f55c0ce3a725a336f8c1f11b))
21
+ - 98e55a3: an unkeyed reactive with a store built outside a request reads it lazily (11.71) ([`acf9440`](https://github.com/briancray/abide/commit/acf94407f779617d63cdc7ed5f371870d58e151e))
22
+ - 98e55a3: a query record and an rpc's URL args share one spelling (30.28) ([`b8c3c48`](https://github.com/briancray/abide/commit/b8c3c48b1c3b056d7172f7aad4b2f1a184b39689))
23
+ - 98e55a3: a keyed list's row record lives with its nodes in rows.ts ([`e37bbb1`](https://github.com/briancray/abide/commit/e37bbb170685cb2714584287d22946c229050781))
24
+
3
25
  ## 0.104.0
4
26
 
5
27
  ### Minor Changes
package/bin/abide.ts CHANGED
@@ -6,13 +6,13 @@ import { existsSync, realpathSync } from 'node:fs' // bun exposes no sync existe
6
6
  import { mkdir, rename, rm } from 'node:fs/promises' // bun exposes no directory creation, rename or recursive directory removal of its own
7
7
  import { dirname, resolve } from 'node:path' // bun exposes no path helpers of its own
8
8
  import { brotliCompress, constants } from 'node:zlib' // bun's brotli is `CompressionStream`, which compresses on the main thread — 1390 ms against this pool's 300 ms over the docs bundle
9
+ import { paints } from '#compiler/appLayout.ts'
9
10
  import { appMap, mapText } from '#compiler/appMap.ts'
10
11
  import { checkApp } from '#compiler/checkApp.ts'
11
12
  import {
12
13
  type CompiledApp,
13
14
  compilationOf,
14
15
  compileApp,
15
- paints,
16
16
  } from '#compiler/compileApp.ts'
17
17
  import { compiledEntry } from '#compiler/compiledEntry.ts'
18
18
  import { formatDiagnosis } from '#compiler/formatDiagnosis.ts'
@@ -22,10 +22,8 @@ import { serveLanguage } from '#compiler/serveLanguage.ts'
22
22
  import {
23
23
  abideModules,
24
24
  type Compilation,
25
- servedTable,
26
25
  serverMapsOf,
27
26
  } from '#server/abideModules.ts'
28
- import { appPlugins } from '#server/appPlugins.ts'
29
27
  import { banner } from '#server/banner.ts'
30
28
  import { buildDirectory } from '#server/buildDirectory.ts'
31
29
  import {
@@ -35,26 +33,15 @@ import {
35
33
  MANIFEST,
36
34
  PUBLIC_DIRECTORY,
37
35
  } from '#server/CLIENT_BUILD.ts'
38
- import {
39
- clientBuildOptions,
40
- stylesheetUnderMount,
41
- } from '#server/clientBuildOptions.ts'
36
+ import { type ClientBundle, clientBundle } from '#server/clientBundle.ts'
42
37
  import { type Given, type Host, runCommands } from '#server/commands.ts'
43
38
  import { type AppPackage, packageUnder } from '#server/config.ts'
44
- import { appRoot, createApp, originUnder } from '#server/createApp.ts'
39
+ import { appOnDisk, appRoot, noAppHere } from '#server/createApp.ts'
45
40
  import { executableName } from '#server/executableName.ts'
46
- import { fenceServer } from '#server/fenceServer.ts'
47
41
  import { iconAccent } from '#server/iconAccent.ts'
48
- import { manifestOf, NO_MANIFEST } from '#server/manifestOf.ts'
49
42
  import { namesUnder } from '#server/namesUnder.ts'
50
- import { refuseAddressCollision } from '#server/refuseAddressCollision.ts'
51
43
  import { DEFAULT_DOCUMENT, intoHead } from '#server/render.ts'
52
- import {
53
- loadBuild,
54
- pathsUnder,
55
- serveApp,
56
- serveBuild,
57
- } from '#server/serveBuild.ts'
44
+ import { pathsUnder, serveBuild } from '#server/serveBuild.ts'
58
45
  import { serveDev } from '#server/serveDev.ts'
59
46
  import { escapeText } from '#shared/escapeText.ts'
60
47
  import { ICON_ADDRESSES, LAUNCHER_PAGE } from '#shared/LAUNCHER_FILES.ts'
@@ -68,30 +55,16 @@ type AppDirectories = { root: string; out: string }
68
55
  */
69
56
  const DISK: Host = {
70
57
  name: 'abide',
71
- // A BUILD ALREADY THERE, NEVER ONE MADE FOR THE ASKING: `call` and the shell are aimed at a
72
- // running app (38.7), which this tree may not even be.
73
- handlers: async ({ values }) => {
74
- const app = directoriesOf(values)
75
- if (
76
- app === undefined ||
77
- !(await serveBuild(app.root, app.out, async () => undefined))
78
- )
79
- return []
80
- return (await servedTable(app.root)).handlers
81
- },
82
- appPackage: () => packageUnder(appRoot(undefined) ?? process.cwd()),
83
- boot: createApp,
84
- serve: inApp(async ({ root, out }, { values: { port } }) => {
85
- if (!(await serveBuild(root, out, buildInProcess))) {
86
- console.error(`abide start: nothing under ${root} to serve.`)
87
- return 1
88
- }
89
- return serveApp(
90
- originUnder(root),
91
- () => loadBuild({ root, out }),
92
- port === undefined ? undefined : Number(port),
93
- )
94
- }),
58
+ // 38.100 — built in this process rather than a child, where nobody built it, and said on
59
+ // stderr: the command's stdout is its own — `openapi`'s document, `mcp`'s messages.
60
+ app: ({ values }) =>
61
+ appOnDisk(directoriesOf(values), async (root, out) =>
62
+ (await serveBuild(root, out, (root, out) =>
63
+ writeBuild({ root, out }, console.error),
64
+ ))
65
+ ? out
66
+ : undefined,
67
+ ),
95
68
  source: {
96
69
  scaffold: scaffoldCommand,
97
70
  dev: inApp(dev),
@@ -104,22 +77,20 @@ const DISK: Host = {
104
77
  },
105
78
  }
106
79
 
107
- // 38.100 — what `serveBuild` builds an app nobody built with, in this process rather than a child.
108
- function buildInProcess(root: string, out: string): Promise<unknown> {
109
- return writeBuild({ root, out })
110
- }
111
-
112
80
  /** `abide build`: compiles the app, bundles its client and copies its public files into `out`. */
113
81
  async function build(directories: AppDirectories): Promise<number> {
114
82
  await writeBuild(directories)
115
83
  return 0
116
84
  }
117
85
 
118
- /** `build`'s work; answers the app it built, or `undefined` where there was nothing under `root`. */
119
- async function writeBuild({
120
- root,
121
- out,
122
- }: AppDirectories): Promise<CompiledApp | undefined> {
86
+ /**
87
+ * `build`'s work; answers the app it built, or `undefined` where there was nothing under `root`.
88
+ * `say` is where its summary goes.
89
+ */
90
+ async function writeBuild(
91
+ { root, out }: AppDirectories,
92
+ say: (line: string) => void = console.log,
93
+ ): Promise<CompiledApp | undefined> {
123
94
  process.env.NODE_ENV ??= 'production'
124
95
  // 38.66 — what this build may remove is only what was written before it began.
125
96
  const began = Date.now()
@@ -136,7 +107,7 @@ async function writeBuild({
136
107
  Object.keys(app.files).length === 0 &&
137
108
  Object.keys(app.handlers).length === 0
138
109
  ) {
139
- console.log(`abide build: no .abide sources or handlers under ${root}`)
110
+ say(`abide build: no .abide sources or handlers under ${root}`)
140
111
  return undefined
141
112
  }
142
113
  // 38.6 — THE COMPILATION, KEPT WHOLE: `abide start` serves every `.abide` out of it at the
@@ -144,28 +115,16 @@ async function writeBuild({
144
115
  const compilation = compilationOf(app)
145
116
  // 36.11 — the directories above `public` and the path below it ARE the address.
146
117
  // `src/public/favicon.ico` answers at `/favicon.ico`, and nothing declares that anywhere.
147
- const publicAddresses = [...app.published.values()]
148
- // 36.13 IS CHECKED BEFORE A BYTE IS BUNDLED OR COPIED, through `refuseAddressCollision` — which
149
- // `abide dev` runs too. Checking it mid-copy left a partial output behind on the throw, and it
150
- // left the dev lane unchecked entirely.
151
- refuseAddressCollision(app.routes, app.mounts, app.published, 'abide build')
152
- await bundleClient(
153
- root,
118
+ await writeClient(
154
119
  out,
155
- [
156
- fenceServer(root, compilation.mounted),
157
- abideModules(root, compilation),
158
- ...(await appPlugins(root)),
159
- ],
160
- publicAddresses,
161
- app.routes.length > 0,
120
+ await clientBundle(root, compilation, app.published, 'build'),
162
121
  began,
163
122
  )
164
123
  // An app's static files are its own whether or not it has a page yet.
165
124
  const copied = await copyPublic(root, out, app.published, began)
166
125
  // LAST, because its presence is what `serveBuild` reads as a finished build (38.100).
167
126
  await writeWhole(marker, JSON.stringify(compilation))
168
- console.log(
127
+ say(
169
128
  `abide build: ${app.sources} source(s), ${copied} public file(s) -> ${out}`,
170
129
  )
171
130
  return app
@@ -559,69 +518,28 @@ async function copyPublic(
559
518
  }
560
519
 
561
520
  /**
562
- * Bundles the client into `out`, with its manifest and precompressed copies.
521
+ * Writes the client bundle (`clientBundle`) into `out`, with its manifest and precompressed copies.
563
522
  *
564
- * THE BUNDLE. Which module boots the client is the compiler's knowledge — it writes the entry
565
- * into the output tree beside the route table — and this is where that entry becomes a browser
566
- * bundle.
567
- *
568
- * `--splitting`, so a destination's markup and bindings stay in the chunk its own route loads
569
- * (30.11) rather than landing in the entry every page pays for. 38.5 IS THIS FUNCTION: hashed and
570
- * minified by the bundler, the manifest off its metafile (the entry's name, and the chunks each
571
- * route module needs for 38.28), and the precompressed copies (`precompressOne`). What the manifest
572
- * does NOT carry yet is 38.18's list of public files.
523
+ * 38.5 — the manifest beside the bundle, and every file in each encoding (`precompressed`). AN APP
524
+ * OF HANDLERS ALONE is written no client, and its manifest anyway, so `abide start` reads one shape
525
+ * whatever the app holds.
573
526
  */
574
- async function bundleClient(
575
- root: string,
527
+ async function writeClient(
576
528
  out: string,
577
- plugins: Bun.BunPlugin[],
578
- publicAddresses: string[],
579
- paged: boolean,
529
+ bundle: ClientBundle,
580
530
  began: number,
581
531
  ): Promise<void> {
582
532
  const client = `${out}/${CLIENT_DIRECTORY}`
583
- // AN APP OF HANDLERS ALONE has no page to boot, and is served with no client — the manifest
584
- // written anyway, so `abide start` reads one shape whatever the app holds.
585
- if (!paged) {
586
- await pruneStale(client, new Set(), began)
587
- await writeWhole(`${out}/${MANIFEST}`, JSON.stringify(NO_MANIFEST))
588
- return
589
- }
590
- // 38.66 — STAGED, THEN MOVED IN FILE BY FILE. `Bun.build` writes in place, so a second build
591
- // into the same `out` — or `abide start` serving it — read a chunk half written, and emptying
592
- // `client/` first took the one it was serving away. Every name but the manifest's is a hash of
593
- // what it holds, so two builds of one tree move in the same bytes under the same names.
533
+ // 38.66 — STAGED, THEN MOVED IN FILE BY FILE. A second build into the same `out` — or `abide
534
+ // start` serving it — read a chunk half written, and emptying `client/` first took the one it
535
+ // was serving away. Every name but the manifest's is a hash of what it holds, so two builds of
536
+ // one tree move in the same bytes under the same names.
594
537
  const staging = `${out}/.staging-${crypto.randomUUID()}`
595
538
  try {
596
- const built = await Bun.build({
597
- ...clientBuildOptions(root, plugins, publicAddresses),
598
- outdir: staging,
599
- // 38.5 — MINIFIED, and EVERY NAME HASHED, the entry's included: chunks and assets are
600
- // already `[name]-[hash]` by default and the entry is not, which left the one file every
601
- // page loads served for a year under a name the next deploy writes different bytes to.
602
- // The manifest is how the document learns what the entry was called this time.
603
- //
604
- // NAMES KEPT, because a class's name is what an error reads to say what it was handed
605
- // (`canonicalKey`), and a production error naming `a e` is one nobody can act on. It is
606
- // free where it is paid: the JS the docs app's `/values` sends is 25.6 KB brotli with
607
- // the names and without, against 31.9 unminified.
608
- minify: {
609
- whitespace: true,
610
- syntax: true,
611
- identifiers: true,
612
- keepNames: true,
613
- },
614
- naming: { entry: '[name]-[hash].[ext]' },
615
- })
616
- // THROWN RATHER THAN LOGGED. A build that emitted server modules and no bundle is an app that
617
- // serves every page dead — the markup is right and nothing is wired — which is the one failure
618
- // a reader cannot see, so it stops the command instead.
619
- if (!built.success)
620
- throw new AggregateError(
621
- built.logs,
622
- 'abide build: the client bundle failed',
623
- )
624
- await Promise.all(built.outputs.map(precompressOne))
539
+ const written: Promise<void>[] = []
540
+ for (const [name, { bytes }] of bundle.files)
541
+ written.push(precompressed(`${staging}/${name}`, bytes))
542
+ await Promise.all(written)
625
543
  const produced = new Set<string>()
626
544
  const moves: Promise<void>[] = []
627
545
  for (const name of namesUnder(staging)) {
@@ -634,17 +552,50 @@ async function bundleClient(
634
552
  }
635
553
  await Promise.all(moves)
636
554
  await pruneStale(client, produced, began)
637
- await writeWhole(
638
- `${out}/${MANIFEST}`,
639
- JSON.stringify(
640
- manifestOf(built.metafile as Bun.BuildMetafile, root),
641
- ),
642
- )
555
+ await writeWhole(`${out}/${MANIFEST}`, JSON.stringify(bundle.manifest))
643
556
  } finally {
644
557
  await rm(staging, { recursive: true, force: true })
645
558
  }
646
559
  }
647
560
 
561
+ /**
562
+ * Writes `bytes` to `path`, and brotli and gzip copies of them beside it.
563
+ *
564
+ * 38.5 — EVERY FILE THE BUNDLE WROTE, BESIDE ITSELF IN EACH OF `ENCODING_SUFFIX`'s ENCODINGS, at
565
+ * the highest level each has. The price is paid here once, where a server compressing per request
566
+ * would pay it on every response or settle for a worse level to keep the response fast.
567
+ *
568
+ * ALL AT ONCE. The async brotli runs off the main thread, so the files compress side by side
569
+ * rather than one after another — brotli at 11 is most of what this step costs.
570
+ */
571
+ async function precompressed(
572
+ path: string,
573
+ bytes: Uint8Array<ArrayBuffer>,
574
+ ): Promise<void> {
575
+ const brotli = await new Promise<Uint8Array>((compressed, failed) =>
576
+ brotliCompress(
577
+ bytes,
578
+ {
579
+ params: {
580
+ [constants.BROTLI_PARAM_QUALITY]:
581
+ constants.BROTLI_MAX_QUALITY,
582
+ [constants.BROTLI_PARAM_SIZE_HINT]: bytes.length,
583
+ },
584
+ },
585
+ (thrown, result) =>
586
+ thrown === null ? compressed(result) : failed(thrown),
587
+ ),
588
+ )
589
+ await Promise.all([
590
+ Bun.write(path, bytes),
591
+ Bun.write(path + ENCODING_SUFFIX.br, brotli),
592
+ Bun.write(
593
+ path + ENCODING_SUFFIX.gzip,
594
+ Bun.gzipSync(bytes, { level: 9 }),
595
+ ),
596
+ ])
597
+ }
598
+
648
599
  /**
649
600
  * Removes from `directory` what this build did not produce.
650
601
  *
@@ -680,50 +631,6 @@ async function writeWhole(path: string, content: string | Blob): Promise<void> {
680
631
  await rename(partial, path)
681
632
  }
682
633
 
683
- /**
684
- * Writes brotli and gzip copies of one bundle output beside it — a stylesheet's addresses written
685
- * relative to the bundle first (`stylesheetUnderMount`).
686
- *
687
- * 38.5 — EVERY FILE THE BUNDLE WROTE, BESIDE ITSELF IN EACH OF `ENCODING_SUFFIX`'s ENCODINGS, at
688
- * the highest level each has. The price is paid here once, where a server compressing per request
689
- * would pay it on every response or settle for a worse level to keep the response fast.
690
- *
691
- * ALL AT ONCE. The async brotli runs off the main thread, so the files compress side by side
692
- * rather than one after another — brotli at 11 is most of what this step costs.
693
- */
694
- async function precompressOne(output: Bun.BuildArtifact): Promise<void> {
695
- // `arrayBuffer` AND NOT `bytes`: the artifact's type declares both and it answers only the one.
696
- let bytes = new Uint8Array(await output.arrayBuffer())
697
- // A stylesheet is rewritten on disk before it is compressed, so all three copies carry it.
698
- if (output.path.endsWith('.css')) {
699
- bytes = new TextEncoder().encode(
700
- stylesheetUnderMount(new TextDecoder().decode(bytes)),
701
- )
702
- await Bun.write(output.path, bytes)
703
- }
704
- const brotli = await new Promise<Uint8Array>((compressed, failed) =>
705
- brotliCompress(
706
- bytes,
707
- {
708
- params: {
709
- [constants.BROTLI_PARAM_QUALITY]:
710
- constants.BROTLI_MAX_QUALITY,
711
- [constants.BROTLI_PARAM_SIZE_HINT]: bytes.length,
712
- },
713
- },
714
- (thrown, result) =>
715
- thrown === null ? compressed(result) : failed(thrown),
716
- ),
717
- )
718
- await Promise.all([
719
- Bun.write(output.path + ENCODING_SUFFIX.br, brotli),
720
- Bun.write(
721
- output.path + ENCODING_SUFFIX.gzip,
722
- Bun.gzipSync(bytes, { level: 9 }),
723
- ),
724
- ])
725
- }
726
-
727
634
  /**
728
635
  * Wraps a command body with the app's absolute root and build directory, or fails with no app.
729
636
  *
@@ -739,7 +646,7 @@ function inApp<Answer>(
739
646
  const app = directoriesOf(given.values)
740
647
  if (app === undefined) {
741
648
  console.error(
742
- `abide ${given.positionals[0]}: no app here. Looked for a .abide under ${resolve('src')}, or under ${process.cwd()} where it has no src. Point --root at the directory your pages sit under.`,
649
+ `abide ${given.positionals[0]}: ${noAppHere()} Point --root at the directory your pages sit under.`,
743
650
  )
744
651
  return 1
745
652
  }
package/docs/REGISTRY.md CHANGED
@@ -89,7 +89,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
89
89
  | --- | --- | --- | --- |
90
90
  | `schema` | `Schema<Accepted>` | The gate on the way in. | 1.3, 4.3, 4.4, 4.5, 11.46, 11.47 |
91
91
  | `transform` | `(value: Accepted) => Shaped` | The shaping on the way to storage. | 4.6, 4.7, 4.8, 4.9, 11.14 |
92
- | `store` | `Store<Stored> \| ((args: Args) => Store<Stored>)` | Where the value lives when the process does not. | 8.1, 8.2, 8.3, 8.14, 9.5, 11.42 |
92
+ | `store` | `Store<Stored> \| ((args: Args) => Store<Stored>)` | Where the value lives when the process does not. | 8.1, 8.2, 8.3, 8.14, 9.5, 11.42, 11.71 |
93
93
  | `identity` | `(next: Stored, previous: Stored) => unknown` | What makes it the same value. | 5.1, 5.2, 5.3, 5.4, 5.5, 9.3, 11.17, 18.4 |
94
94
  | `structural` | `(next: unknown, previous: unknown) => boolean` | The by-value comparator an app declares on `identity`. | 5.4, 5.5, 18.4 |
95
95
  | `by` | `(keyof Element & string) \| ((element: Element) => unknown)`, over the `Element` a `Stored` array holds | What tells one element of the value from another. | 5.22, 18.4, 18.15, 32.32 |
@@ -214,10 +214,10 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
214
214
  | `Produced` | `unknown` | What it yields, per the unit rule. | 1.2, 16.3 |
215
215
  | `Args` | `Record<string, JsonValue> \| undefined` | The arguments, in the body on a mutation and in the URL on a `GET` or `DELETE`. | 11.24, 16.18, 19.15 |
216
216
  | `Middleware` | `<Ctx, Result>(next: (ctx?: Ctx) => Promise<Result>, ctx: Ctx) => Result \| Promise<Result>` | One rung of `middleware`. | 16.14, 16.15, 16.16, 16.21 |
217
- | `GET` | `<Value, Args, Failures, RungFailures, Produced = Value>(handler: Reactive<Value, Value, Failures, Produced> \| Keyed<Value, Args, Value, Failures, Produced>, options?: RpcOptions<Args, RungFailures>) => Rpc<Value, Args, Failures \| RungFailures, Produced>` | Declares a read any surface may call. | 16.1, 16.2, 16.5, 16.6, 16.30, 16.31, 16.32, 16.68, 16.34, 16.35, 16.36, 16.37, 16.39, 16.44, 16.45, 16.46, 16.47, 16.50, 16.51, 16.52, 16.64, 18.1, 18.3, 18.6, 18.8, 18.10, 18.12, 18.21, 18.24 |
217
+ | `GET` | `<Value, Args, Failures, RungFailures, Produced = Value>(handler: Reactive<Value, Value, Failures, Produced> \| Keyed<Value, Args, Value, Failures, Produced>, options?: RpcOptions<Args, RungFailures>) => Rpc<Value, Args, Failures \| RungFailures, Produced>` | Declares a read any surface may call. | 16.1, 16.2, 16.5, 16.6, 16.30, 16.31, 16.32, 16.68, 16.76, 16.34, 16.35, 16.36, 16.37, 16.39, 16.44, 16.45, 16.46, 16.47, 16.75, 16.50, 16.51, 16.52, 16.64, 18.1, 18.3, 18.6, 18.8, 18.10, 18.12, 18.21, 18.24, 18.27 |
218
218
  | `GET` | `<Value, Args, Failures, RungFailures, Produced = Value>(handler: (args?: Args) => Value, options?: RpcOptions<Args, RungFailures> & ReactiveOptions<Value, Value, Failures, Args>) => Rpc<Value, Args, Failures \| RungFailures, Produced>` | The plain-function overload, whose options build the value it becomes beside its address. | 16.1, 16.6, 16.7 |
219
219
  | `GET` | `<Value, Failures, RungFailures>(handler: Value \| PromiseLike<Value>, options?: RpcOptions<undefined, RungFailures> & ReactiveOptions<Value, Value, Failures>) => Rpc<Value, undefined, Failures \| RungFailures>` | The value overload, for what the handler owns outright. | 16.1, 16.6, 16.7 |
220
- | `POST` / `PUT` / `PATCH` / `DELETE` | same as `GET` | Declares a mutation. | 16.2, 16.7, 16.32, 16.68, 16.38, 16.53 |
220
+ | `POST` / `PUT` / `PATCH` / `DELETE` | same as `GET` | Declares a mutation. | 16.2, 16.7, 16.32, 16.68, 16.76, 16.38, 16.53 |
221
221
  | `rpc.busy` | `(pattern?: Partial<Args>) => boolean` | `busy` narrowed to the handler's matching entries. | 13.2, 13.3, 13.12 |
222
222
  | `rpc.ready` | `(pattern?: Partial<Args>) => boolean` | `ready` narrowed to the handler's matching entries. | 13.11, 13.12 |
223
223
  | `rpc.invalidate` | `(pattern?: Partial<Args>) => void` | `invalidate` narrowed to the handler's matching entries. | 7.2, 7.3, 13.4, 13.12 |
@@ -225,7 +225,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
225
225
  | `rpc.isError` | `<Name extends Failures['name']>(error: unknown, name: Name) => error is Extract<Failures, { name: Name }>` | Whether a caught failure is the one named. | 16.8 |
226
226
  | `rpc.raw` | `(args: Args, init?: RequestInit) => Promise<Response>` | The same call handed back as the raw response. | 16.11, 16.42 |
227
227
  | `rpc.method` | `string` | The HTTP method. | 16.2 |
228
- | `rpc.url` | `(args?: Args) => string` | The address, resolved against the mount. | 16.9, 16.10 |
228
+ | `rpc.url` | `(args?: Args) => string` | The address, resolved against the mount. | 16.9, 16.10, 30.28 |
229
229
  | `rpc.description` | `string \| undefined` | The human description. | 16.28 |
230
230
  | `PatchOp` | `{ op: 'add' \| 'replace' \| 'test'; path: string; value: unknown } \| { op: 'remove'; path: string } \| { op: 'move' \| 'copy'; from: string; path: string }` | One JSON Patch operation (RFC 6902), its paths JSON Pointers, as a followed `GET` is sent what an `s.patch` wrote. | 18.11, 18.13 |
231
231
 
@@ -235,7 +235,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
235
235
  | --- | --- | --- | --- |
236
236
  | `description` | `string` | The human description carried onto every generated surface. | 16.28 |
237
237
  | `middleware` | `Middleware<{ request: Request; args: () => Args }, Value \| Failures>[]` | The rungs run per call. | 16.11, 16.12, 16.16, 16.17, 16.18, 16.19, 16.20 |
238
- | `timeout` | `number` | How long the call may stay open, and the floor under the request's idle timeout. | 16.22, 16.23, 16.56, 16.57, 16.58, 16.59, 16.60, 16.61 |
238
+ | `timeout` | `number` | How long the call may stay open, and the floor under the request's idle timeout. | 16.22, 16.23, 16.56, 16.57, 16.58, 16.59, 16.60, 16.61, 16.77 |
239
239
  | `clients` | `Clients` | Which surfaces carry this handler. | 16.37, 20.3 |
240
240
  | `crossOrigin` | `boolean \| string[]` | Whether other origins may call it, or follow it over the mux. | 16.24, 18.2, 21.19 |
241
241
  | `maxBodySize` | `number` | The largest request body a mutation will accept, in bytes. | 16.13, 16.54, 16.55 |
@@ -245,12 +245,12 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
245
245
  | Name | Signature | Meaning | Rules |
246
246
  | --- | --- | --- | --- |
247
247
  | `Values<T>` | `Iterable<T> \| AsyncIterable<T> \| ReadableStream<T>` | What every body helper takes. | 17.1 |
248
- | `page` | `<Body extends string \| Uint8Array \| Values<Uint8Array \| string>>(body: Body) => Body` | A rendered document as `text/html`. | 17.2 |
248
+ | `page` | `<Body extends string \| Uint8Array \| Values<Uint8Array \| string>>(body: Body) => Body` | A rendered document as `text/html`. | 16.75, 17.2 |
249
249
  | `json` | `<T>(value: T) => T` | A value as JSON, fixed, with `undefined` sent as `null`. | 17.7, 17.9, 17.10 |
250
250
  | `redirect` | `(to: string, status?: RedirectStatus) => undefined` | A navigation. | 16.52, 17.3 |
251
- | `jsonl` | `<T>(values: Values<T>) => Values<T>` | One JSON value per line, fixed. | 16.44, 17.6, 17.7 |
252
- | `sse` | `<T>(values: Values<T>) => Values<T>` | The same machine framed as server-sent events, fixed. | 16.45, 17.6, 17.7 |
253
- | `bytes` | `(values: Values<Uint8Array>) => Values<Uint8Array>` | A stream of binary chunks, fixed. | 16.34, 17.6, 17.7 |
251
+ | `jsonl` | `<T>(values: Values<T>) => Values<T>` | One JSON value per line, fixed. | 16.44, 16.75, 17.6, 17.7 |
252
+ | `sse` | `<T>(values: Values<T>) => Values<T>` | The same machine framed as server-sent events, fixed. | 16.45, 16.75, 17.6, 17.7 |
253
+ | `bytes` | `(values: Values<Uint8Array>) => Values<Uint8Array>` | A stream of binary chunks, fixed. | 16.34, 16.75, 17.6, 17.7 |
254
254
  | `RedirectStatus` | `301 \| 302 \| 303 \| 307 \| 308` | The statuses a `redirect` may carry. | 17.3 |
255
255
  | `refuse` | `(status: number, message?: string) => Failed<'HttpError', undefined>` | An undeclared refusal at a status. | 15.5, 15.9, 15.10, 15.16, 15.19, 15.20, 16.40, 17.4, 17.5, 17.8 |
256
256
 
@@ -490,7 +490,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
490
490
  | `RequiredNames<P>` | `string` | The required segment names. | 30.3 |
491
491
  | `OptionalNames<P>` | `string` | The optional segment names. | 30.4 |
492
492
  | `RestNames<P>` | `string` | The rest segment names. | 30.5 |
493
- | `Query` | `Record<string, unknown> \| URLSearchParams` | The query half. | 30.6 |
493
+ | `Query` | `Record<string, unknown> \| URLSearchParams` | The query half. | 30.6, 30.28 |
494
494
  | `ParamsOf<P>` | `& { [K in RequiredNames<P>]: string \| number } & { [K in OptionalNames<P>]?: string \| number } & { [K in RestNames<P>]?: string \| number \| (string \| number)[] }` | What a route literal's segments accept. | 30.3, 30.4, 30.5 |
495
495
  | `RouteParams<P>` | `{ [K in RequiredNames<P>]: string } & { [K in OptionalNames<P>]?: string } & { [K in RestNames<P>]: string[] }` | What a page filed under `P` reads its params as. | 23.9 |
496
496
  | `Routes` | `interface Routes {}` | The app's route literals and public file addresses, which `abide check` declares. | 30.26 |
@@ -564,7 +564,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
564
564
  | Spelling | Meaning | Rules |
565
565
  | --- | --- | --- |
566
566
  | `{#component Name(pattern)}` | An inline component. | 32.39, 32.40, 32.41, 32.42, 32.43, 32.44, 32.45, 32.46, 32.47, 32.48, 32.49, 32.50, 32.51, 32.52, 32.53, 32.54, 32.55 |
567
- | `<Name/>` | A component invocation. | 32.17, 32.34, 32.35, 32.36, 33.19, 33.21, 41.24 |
567
+ | `<Name/>` | A component invocation. | 32.17, 32.34, 32.35, 32.36, 33.19, 33.21, 41.24, 41.28 |
568
568
  | `<slot/>` | The position children render into. | 32.18 |
569
569
  | `<slot>fallback</slot>` | Renders children, or a fallback. | 32.18 |
570
570
  | `<Tag>…</Tag>` | Children passed to a component's slot. | 32.18 |
@@ -594,7 +594,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
594
594
 
595
595
  | Path | Meaning | Rules |
596
596
  | --- | --- | --- |
597
- | `<head>` | A page's contribution to the document head. | 39.1, 39.2, 39.3, 39.4, 39.5, 39.6, 39.11, 39.12, 39.13, 39.14 |
597
+ | `<head>` | A page's, layout's or component's contribution to the document head. | 39.1, 39.2, 39.3, 39.4, 39.5, 39.6, 39.11, 39.12, 39.13, 39.14, 39.15, 39.16 |
598
598
  | `view-transition-name` | What turns view transitions on, in the app's own stylesheet. | 39.7, 39.8, 39.9, 39.10 |
599
599
  | `[name]` | A required dynamic segment. | 16.65, 23.5, 30.3 |
600
600
  | `[[name]]` | An optional segment. | 23.5, 30.4 |
@@ -606,7 +606,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
606
606
  | Name | Signature | Meaning | Rules |
607
607
  | --- | --- | --- | --- |
608
608
  | `Component` | `interface Component {}` | A component bound to its props. | 32.17, 34.16, 35.4 |
609
- | `render` | `(component: Component) => Promise<Uint8Array>` | What produces a document. | 29.10, 35.4, 35.5, 35.6, 35.15, 35.16, 41.1, 41.2, 41.3, 41.4, 41.9, 41.13, 41.16, 41.17, 41.18, 41.19, 41.20, 35.11 |
609
+ | `render` | `(component: Component) => Promise<Uint8Array>` | What produces a document. | 29.10, 35.4, 35.5, 35.6, 35.15, 35.16, 41.1, 41.2, 41.3, 41.4, 41.9, 41.13, 41.16, 41.17, 41.18, 41.19, 41.20, 41.28, 35.11 |
610
610
 
611
611
  ## Sinks
612
612
 
@@ -718,11 +718,11 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
718
718
  | `abide routes [--json]` | Every address the app answers, the file answering it, and where each hook is filed. | 38.108 |
719
719
  | `abide dev [--port <n>]` | Watches the project and keeps the app up. | 37.8, 37.9, 38.4, 38.14, 38.15, 38.16, 38.17, 38.19, 38.24, 38.25, 38.26, 38.28, 38.30, 38.35, 38.36, 38.37, 38.38, 38.41, 38.43, 38.58, 38.63, 38.64, 38.65, 38.94, 38.96, 38.97 |
720
720
  | `abide build` | Builds the client into content-hashed chunks and a manifest. | 38.43, 38.5, 38.18, 38.25, 38.26, 38.27, 38.30, 38.66, 38.94 |
721
- | `abide start [--port <n>]` | Boots the built app. | 37.8, 37.10, 37.11, 37.12, 38.6, 38.100, 38.20, 38.21, 38.22, 38.23, 38.25, 38.26, 38.27, 38.28, 38.29, 38.58, 38.67, 38.68, 38.95, 38.97 |
721
+ | `abide start [--port <n>]` | Boots the built app. | 37.8, 37.10, 37.11, 37.12, 38.6, 38.100, 38.20, 38.21, 38.22, 38.23, 38.25, 38.26, 38.27, 38.28, 38.29, 38.58, 38.67, 38.68, 38.95, 38.97, 38.110 |
722
722
  | `abide [--url <origin>] [--port <n>]` | The interactive shell — on the built app it boots, or on the running app `--url` names. | 37.8, 37.10, 37.11, 37.12, 38.6, 38.7, 38.53, 38.54, 38.55, 38.58, 38.59, 38.60, 38.62 |
723
- | `abide call <address> [args]` | One call to one handler. | 38.8, 38.61 |
724
- | `abide openapi [--out <file>] [--url <origin>]` | The OpenAPI document written rather than served. | 38.9 |
725
- | `abide mcp [--url <origin>]` | The app's MCP server over stdio. | 38.9, 38.13 |
723
+ | `abide call <address> [args]` | One call to one handler. | 38.8, 38.61, 38.110 |
724
+ | `abide openapi [--url <origin>]` | The OpenAPI document printed rather than served. | 38.9, 38.25, 38.110, 38.111 |
725
+ | `abide mcp [--url <origin>]` | The app's MCP server over stdio. | 38.9, 38.13, 38.25, 38.110 |
726
726
  | `abide watch <address> [args]` | A live subscription to one of the app's followed `GET`s. | 38.69 |
727
727
  | `abide compile [--target] [--out] [--platforms]` | One standalone executable. | 37.8, 37.10, 37.11, 37.12, 38.10, 38.25, 38.26, 38.44, 38.45, 38.46, 38.47, 38.48, 38.49, 38.50, 38.51, 38.52, 38.56, 38.93, 38.97 |
728
728
  | `abide bundle [--out]` | A desktop app for the host platform, with a window on the app. | 38.10, 38.25, 38.26, 38.70, 38.71, 38.72, 38.73, 38.74, 38.75, 38.76, 38.77, 38.78, 38.79, 38.80, 38.81, 38.82, 38.83, 38.84, 38.85, 38.86, 38.87, 38.88, 38.89, 38.90, 38.91, 38.92, 38.101, 38.102 |