@abide/abide 0.116.3 → 0.117.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/bin/abide.ts +16 -3
- package/docs/REGISTRY.md +19 -19
- package/docs/RULEBOOK.md +26 -2
- package/docs/abide.md +22 -22
- package/package.json +1 -1
- package/src/compiler/TypeSession.ts +7 -2
- package/src/compiler/compileApp.ts +19 -0
- package/src/compiler/emitComponent.ts +60 -12
- package/src/compiler/emitSource.ts +4 -0
- package/src/compiler/scaffold.ts +5 -3
- package/src/server/abideModules.ts +21 -6
- package/src/server/answerRpc.ts +3 -12
- package/src/server/argsFor.ts +5 -8
- package/src/server/deferredBuilds.ts +2 -1
- package/src/server/derivedArgs.ts +25 -0
- package/src/server/faceOf.ts +10 -2
- package/src/server/index.ts +3 -0
- package/src/server/renderComponent.ts +3 -2
- package/src/server/runtimeRefusal.ts +13 -0
- package/src/shared/DERIVED_ARGS.ts +6 -0
- package/src/shared/reactive/scoped.ts +39 -5
- package/src/shared/rpc.ts +19 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @abide/abide
|
|
2
2
|
|
|
3
|
+
## 0.117.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3bb5cc9: `abide dev`, `build`, `start`, `check`, `routes`, `compile`, `bundle`, `openapi`, `mcp` and the shell now refuse to run on a bun outside abide's `engines.bun`, naming the bun running and the range it needs (RULEBOOK 38.123)
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 3bb5cc9: a component whose markup reads a bare `busy()` or `ready()` is now built on the server after the turn, once the siblings after it have started their loads, so it paints the arm the browser chooses at hydration rather than one that is rebuilt (RULEBOOK 41.28, 41.31)
|
|
12
|
+
- 3bb5cc9: the rulebook now states that the effects one turn wakes, a slot's own update among them, run in no guaranteed order (12.15) — a `watch` that post-processes what a sibling `{raw(...)}` slot renders cannot count on the slot having updated first
|
|
13
|
+
- 3bb5cc9: markdown imported `with { type: 'text' }` into an app's `.ts` now checks as a `string` under `abide check`, as it already did in a `.abide` — no more hand-written `declare module '*.md'`
|
|
14
|
+
- 3bb5cc9: the `bunfig.toml` `abide scaffold` writes now sets `[install] peer = false`, so a dependency's peer on `bun` (`bun-plugin-tailwind`'s) cannot install an older bun that shadows the system one inside `bun run` (RULEBOOK 38.122)
|
|
15
|
+
- 3bb5cc9: the rulebook states three things that held but were never written down: a `raw` slot writes its markup unchecked (32.77), a fragment-only step moves `route.url` (30.33), and a `refresh` over `tags` reruns no synchronous derivation that carries none of them (13.20) — and the registry says `route.params` is `Params` outside a page or layout
|
|
16
|
+
|
|
17
|
+
## 0.116.4
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- f4acbfd: a handler whose args schema is derived from its type annotation (no `args` gate) now refuses bad args in process as it does over the wire — a direct call, a call from another handler or `app.run`, and every call under `mount` answer 422 `ValidationError` with the same `Issues` before the body runs, where before the body ran with the bad value
|
|
22
|
+
- be88738: a reader that probed a handler before its first call wakes when the call starts — on a server and under `mount`, `disabled={save.busy()}` read before any call stayed enabled for the whole write, because the unreached answer (0.116.3) subscribed the reader to nothing. It now subscribes the reader to the first call in its scope, and still builds nothing and runs no handler (RULEBOOK 3.3, 13.19)
|
|
23
|
+
|
|
3
24
|
## 0.116.3
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/bin/abide.ts
CHANGED
|
@@ -41,12 +41,17 @@ import { executableName } from '#server/executableName.ts'
|
|
|
41
41
|
import { iconAccent } from '#server/iconAccent.ts'
|
|
42
42
|
import { namesUnder } from '#server/namesUnder.ts'
|
|
43
43
|
import { DEFAULT_DOCUMENT, intoHead } from '#server/render.ts'
|
|
44
|
+
import { runtimeRefusal } from '#server/runtimeRefusal.ts'
|
|
44
45
|
import { pathsUnder, serveBuild } from '#server/serveBuild.ts'
|
|
45
46
|
import { serveDev } from '#server/serveDev.ts'
|
|
46
47
|
import { escapeText } from '#shared/escapeText.ts'
|
|
47
48
|
import { ICON_ADDRESSES, LAUNCHER_PAGE } from '#shared/LAUNCHER_FILES.ts'
|
|
48
49
|
import abidePackage from '../package.json' with { type: 'json' }
|
|
49
50
|
|
|
51
|
+
// 38.123 — the bun this process runs on, held to abide's `engines.bun` before anything is built or
|
|
52
|
+
// booted. A compiled app carries the bun `abide compile` ran on, which this held already.
|
|
53
|
+
const RUNTIME_REFUSAL = runtimeRefusal(Bun.version, abidePackage.engines.bun)
|
|
54
|
+
|
|
50
55
|
/** Where the app a command builds or serves is, and where its build goes — both absolute. */
|
|
51
56
|
type AppDirectories = { root: string; out: string }
|
|
52
57
|
|
|
@@ -59,14 +64,18 @@ const DISK: Host = {
|
|
|
59
64
|
version: abidePackage.version,
|
|
60
65
|
// 38.100 — built in this process rather than a child, where nobody built it, and said on
|
|
61
66
|
// stderr: the command's stdout is its own — `openapi`'s document, `mcp`'s messages.
|
|
62
|
-
app: ({ values }) =>
|
|
63
|
-
appOnDisk(directoriesOf(values), async (root, out) =>
|
|
67
|
+
app: ({ values }) => {
|
|
68
|
+
const handle = appOnDisk(directoriesOf(values), async (root, out) =>
|
|
64
69
|
(await serveBuild(root, out, (root, out) =>
|
|
65
70
|
writeBuild({ root, out }, console.error),
|
|
66
71
|
))
|
|
67
72
|
? out
|
|
68
73
|
: undefined,
|
|
69
|
-
)
|
|
74
|
+
)
|
|
75
|
+
return RUNTIME_REFUSAL === undefined
|
|
76
|
+
? handle
|
|
77
|
+
: { ...handle, boot: async () => RUNTIME_REFUSAL }
|
|
78
|
+
},
|
|
70
79
|
source: {
|
|
71
80
|
scaffold: scaffoldCommand,
|
|
72
81
|
dev: inApp(dev),
|
|
@@ -645,6 +654,10 @@ function inApp<Answer>(
|
|
|
645
654
|
body: (app: AppDirectories, given: Given) => Promise<Answer>,
|
|
646
655
|
): (given: Given) => Promise<Answer | number> {
|
|
647
656
|
return async (given) => {
|
|
657
|
+
if (RUNTIME_REFUSAL !== undefined) {
|
|
658
|
+
console.error(`abide ${given.positionals[0]}: ${RUNTIME_REFUSAL}`)
|
|
659
|
+
return 1
|
|
660
|
+
}
|
|
648
661
|
const app = directoriesOf(given.values)
|
|
649
662
|
if (app === undefined) {
|
|
650
663
|
console.error(
|
package/docs/REGISTRY.md
CHANGED
|
@@ -160,7 +160,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
160
160
|
| --- | --- | --- | --- |
|
|
161
161
|
| `Disposer` | `() => void` | What tears down the previous run. | 12.1 |
|
|
162
162
|
| `Effect` | `() => void \| Disposer` | What runs on change. | 12.2, 12.8, 14.6 |
|
|
163
|
-
| `watch` | `(effect: Effect) => () => void` | Begins a watch over whatever the effect reads. | 12.2, 12.4, 12.5, 12.6, 12.7, 12.10, 12.13 |
|
|
163
|
+
| `watch` | `(effect: Effect) => () => void` | Begins a watch over whatever the effect reads. | 12.2, 12.4, 12.5, 12.6, 12.7, 12.10, 12.13, 12.15 |
|
|
164
164
|
| `watch` | `(sources: Reactive<unknown, never, unknown, unknown> \| Reactive<unknown, never, unknown, unknown>[], effect: Effect) => () => void` | The narrowed form, over named sources. | 12.3 |
|
|
165
165
|
| `flushSync` | `() => void` | Ends the turn: every `Effect` a write woke runs now. | 9.10, 12.11, 12.12 |
|
|
166
166
|
|
|
@@ -169,10 +169,10 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
169
169
|
| Name | Signature | Meaning | Rules |
|
|
170
170
|
| --- | --- | --- | --- |
|
|
171
171
|
| `Selection` | `Reactive<any, any, any, any> \| Keyed<any, any, any, any> \| Rpc<any, any, any, any> \| { tags: string[] }` | What is being asked about. | 13.1, 13.8, 13.9, 13.13, 31.14 |
|
|
172
|
-
| `busy` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether any entry in the selection has work in flight. | 13.2, 13.3, 13.5, 13.12, 46.11 |
|
|
173
|
-
| `ready` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether every entry in the selection has a value to serve. | 13.3, 13.5, 13.11, 13.12 |
|
|
172
|
+
| `busy` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether any entry in the selection has work in flight. | 13.2, 13.3, 13.5, 13.12, 41.28, 41.31, 46.11 |
|
|
173
|
+
| `ready` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether every entry in the selection has a value to serve. | 13.3, 13.5, 13.11, 13.12, 41.28, 41.31 |
|
|
174
174
|
| `synced` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether every entry in the selection holds what its producer made. | 13.12, 13.18 |
|
|
175
|
-
| `refresh` | `(selection?: Selection, pattern?: Partial<Args>) => void` | Stale-while-revalidate over the selection. | 13.4, 13.7, 13.10, 13.12, 13.14, 13.15, 13.16, 13.17 |
|
|
175
|
+
| `refresh` | `(selection?: Selection, pattern?: Partial<Args>) => void` | Stale-while-revalidate over the selection. | 13.4, 13.7, 13.10, 13.12, 13.14, 13.15, 13.16, 13.17, 13.20 |
|
|
176
176
|
| `invalidate` | `(selection?: Selection, pattern?: Partial<Args>) => void` | Marks the selection stale. | 13.4, 13.6, 13.10, 13.12, 13.15, 13.17 |
|
|
177
177
|
|
|
178
178
|
|
|
@@ -380,8 +380,8 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
380
380
|
|
|
381
381
|
| Name | Signature | Meaning | Rules |
|
|
382
382
|
| --- | --- | --- | --- |
|
|
383
|
-
| `route.url` | `Reactive<URL>` | Where we are, fragment included. | 23.1, 23.2, 23.3, 23.4, 30.20, 30.21, 30.23, 19.16, 31.16 |
|
|
384
|
-
| `route.params` | `Reactive<Params>` | The matched route's segments. | 23.1, 23.5, 23.9, 30.23, 31.16 |
|
|
383
|
+
| `route.url` | `Reactive<URL>` | Where we are, fragment included. | 23.1, 23.2, 23.3, 23.4, 30.20, 30.21, 30.23, 30.33, 19.16, 31.16 |
|
|
384
|
+
| `route.params` | `Reactive<Params>` | The matched route's segments — typed by its own route in a page or a layout, and `Params` in any other file. | 23.1, 23.5, 23.9, 30.23, 31.16 |
|
|
385
385
|
| `Params` | `Record<string, string \| string[]>` | What a matched route's segments are. | 23.5 |
|
|
386
386
|
| `route.name` | `Reactive<string>` | The resolution path, the same under any mount path. | 23.1, 23.6, 23.14, 31.16 |
|
|
387
387
|
| `route.navigating` | `Reactive<boolean>` | Whether a navigation is in flight. | 23.1, 31.16 |
|
|
@@ -571,7 +571,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
571
571
|
|
|
572
572
|
| Name | Signature | Meaning | Rules |
|
|
573
573
|
| --- | --- | --- | --- |
|
|
574
|
-
| `raw` | `(value: unknown) => Raw` | The branding a value takes to render as markup rather than as text. | 32.2, 32.24, 32.26, 32.30, 32.31, 32.70 |
|
|
574
|
+
| `raw` | `(value: unknown) => Raw` | The branding a value takes to render as markup rather than as text. | 32.2, 32.24, 32.26, 32.30, 32.31, 32.70, 32.77 |
|
|
575
575
|
| `Raw` | `class Raw { html: string }` | What `raw` hands back. | 32.24 |
|
|
576
576
|
|
|
577
577
|
### Control flow
|
|
@@ -629,7 +629,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
629
629
|
| Name | Signature | Meaning | Rules |
|
|
630
630
|
| --- | --- | --- | --- |
|
|
631
631
|
| `Component` | `interface Component {}` | A component bound to its props. | 32.17, 34.16, 35.4 |
|
|
632
|
-
| `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, 41.29, 41.30 |
|
|
632
|
+
| `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, 41.29, 41.30, 41.31 |
|
|
633
633
|
|
|
634
634
|
## Sinks
|
|
635
635
|
|
|
@@ -849,20 +849,20 @@ rows a runner prints, and an adapter for an abide app.
|
|
|
849
849
|
|
|
850
850
|
| Command | Meaning | Rules |
|
|
851
851
|
| --- | --- | --- |
|
|
852
|
-
| `abide scaffold <name>` | Writes a starter project. | 38.1, 38.42, 38.103, 38.104, 38.106, 38.112 |
|
|
852
|
+
| `abide scaffold <name>` | Writes a starter project. | 38.1, 38.42, 38.103, 38.104, 38.106, 38.112, 38.122 |
|
|
853
853
|
| `abide run <file> [args…]` | Runs a script under the abide runtime. | 38.2, 38.57 |
|
|
854
|
-
| `abide check [dir…]` | The type-checker over a `.abide` tree. | 38.3, 38.31, 38.32, 38.33, 38.40, 38.105 |
|
|
855
|
-
| `abide routes [--json]` | Every address the app answers, the file answering it, and where each hook is filed. | 38.108 |
|
|
856
|
-
| `abide dev [--port <n>]` | Watches the project and keeps the app up. | 16.23, 16.56, 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 |
|
|
857
|
-
| `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 |
|
|
858
|
-
| `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 |
|
|
859
|
-
| `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 |
|
|
854
|
+
| `abide check [dir…]` | The type-checker over a `.abide` tree. | 38.3, 38.31, 38.32, 38.33, 38.40, 38.105, 38.123 |
|
|
855
|
+
| `abide routes [--json]` | Every address the app answers, the file answering it, and where each hook is filed. | 38.108, 38.123 |
|
|
856
|
+
| `abide dev [--port <n>]` | Watches the project and keeps the app up. | 16.23, 16.56, 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, 38.123 |
|
|
857
|
+
| `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, 38.123 |
|
|
858
|
+
| `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, 38.123 |
|
|
859
|
+
| `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, 38.123 |
|
|
860
860
|
| `abide call <address> [args]` | One call to one handler. | 38.8, 38.61, 38.110 |
|
|
861
|
-
| `abide openapi [--url <origin>]` | The OpenAPI document printed rather than served. | 38.9, 38.25, 38.110, 38.111 |
|
|
862
|
-
| `abide mcp [--url <origin>]` | The app's MCP server over stdio. | 38.9, 38.13, 38.25, 38.110 |
|
|
861
|
+
| `abide openapi [--url <origin>]` | The OpenAPI document printed rather than served. | 38.9, 38.25, 38.110, 38.111, 38.123 |
|
|
862
|
+
| `abide mcp [--url <origin>]` | The app's MCP server over stdio. | 38.9, 38.13, 38.25, 38.110, 38.123 |
|
|
863
863
|
| `abide watch <address> [args]` | A live subscription to one of the app's followed `GET`s. | 38.69 |
|
|
864
|
-
| `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 |
|
|
865
|
-
| `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 |
|
|
864
|
+
| `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, 38.123 |
|
|
865
|
+
| `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, 38.123 |
|
|
866
866
|
| `abide lsp` | The `.abide` language server, over stdio. | 38.11, 38.34, 38.39, 38.98, 38.116, 38.117, 38.118 |
|
|
867
867
|
| `--help` · `-h` · `abide help` | Usage, generated from the command list. | 38.12, 38.113, 38.114 |
|
|
868
868
|
| `--version` · `-v` | The version of abide, or of the app a compiled executable holds. | 38.115 |
|
package/docs/RULEBOOK.md
CHANGED
|
@@ -700,6 +700,9 @@ holding that arm or row is removed.
|
|
|
700
700
|
`watch`, MUST stop reading its sources once that owner stops and nothing else reads it, and MUST
|
|
701
701
|
answer a later read with what its body computes then.
|
|
702
702
|
|
|
703
|
+
12.15 The `Effect` values one turn wakes, a slot's own update among them, MAY run in any order,
|
|
704
|
+
save where a clause orders them.
|
|
705
|
+
|
|
703
706
|
# 13. Selections
|
|
704
707
|
|
|
705
708
|
13.1 A `Selection` MUST be one `Reactive`, one `Keyed`, one `Rpc`, or a set of `tags`.
|
|
@@ -754,7 +757,11 @@ mutation's `declare` built while its write is in flight.
|
|
|
754
757
|
where every value it sweeps is.
|
|
755
758
|
|
|
756
759
|
13.19 `busy`, `ready`, `synced`, `refresh` and `invalidate` over an `Rpc` no call has reached in the
|
|
757
|
-
current scope MUST answer as over a `Selection` with no entries
|
|
760
|
+
current scope MUST answer as over a `Selection` with no entries, and reading `busy`, `ready` or
|
|
761
|
+
`synced` there MUST subscribe the reader to the first call that reaches it in that scope.
|
|
762
|
+
|
|
763
|
+
13.20 `refresh` over a set of `tags` MUST NOT rerun a synchronous derivation that carries none of
|
|
764
|
+
them, which MUST recompute from the load it reads.
|
|
758
765
|
|
|
759
766
|
# 14. Tracking
|
|
760
767
|
|
|
@@ -1818,6 +1825,9 @@ to `middleware`.
|
|
|
1818
1825
|
30.32 A `navigate` that is not a history step, whose request gets no answer while `online` is false,
|
|
1819
1826
|
MUST leave the page and the address where they stand, save where 45.12 holds.
|
|
1820
1827
|
|
|
1828
|
+
30.33 A history step under 30.20, and a `navigate` under 30.21, MUST move `route.url` to the address
|
|
1829
|
+
it steps to.
|
|
1830
|
+
|
|
1821
1831
|
# 31. Reading and writing by name
|
|
1822
1832
|
|
|
1823
1833
|
31.1 Inside a `.abide` file a name bound to a `Reactive` MUST be read wherever 31.3 does not hold it.
|
|
@@ -2098,6 +2108,9 @@ element, written or spread, runs for the same event, wherever the two are writte
|
|
|
2098
2108
|
32.76 A `bind:` whose operand is a path into a value that takes an optional step (`?.`) MUST be a
|
|
2099
2109
|
build error.
|
|
2100
2110
|
|
|
2111
|
+
32.77 A slot holding a value branded by `raw` MUST write its markup as given, with no element or
|
|
2112
|
+
attribute in it removed and no URL in it scheme-checked.
|
|
2113
|
+
|
|
2101
2114
|
# 33. Props
|
|
2102
2115
|
|
|
2103
2116
|
33.1 `props` MUST be callable from a `<script>` block only, and calling it from a `<script module>`
|
|
@@ -2765,6 +2778,12 @@ supplies, and `abide start` MUST NOT supply that secret.
|
|
|
2765
2778
|
38.121 Under `mount`, a call the component's browser module makes to a private handler MUST be
|
|
2766
2779
|
answered in the test's process by the handler the component's server module declares.
|
|
2767
2780
|
|
|
2781
|
+
38.122 The project `abide scaffold` writes MUST NOT install a dependency's peer dependencies.
|
|
2782
|
+
|
|
2783
|
+
38.123 A command `abide` builds, checks or boots the app for MUST be refused where the bun running it
|
|
2784
|
+
is outside the range abide's `engines.bun` names, and the refusal MUST name that bun's version and
|
|
2785
|
+
the range.
|
|
2786
|
+
|
|
2768
2787
|
# 39. Head and view transitions
|
|
2769
2788
|
|
|
2770
2789
|
39.1 A `<head>` element in a `.abide` file MUST contribute its children to the document head.
|
|
@@ -3030,13 +3049,18 @@ has answered or failed.
|
|
|
3030
3049
|
adopts the page, however early its answer arrived.
|
|
3031
3050
|
|
|
3032
3051
|
41.28 A `render` MUST let the turn's queued microtasks run between setting up a `<Name/>` whose setup
|
|
3033
|
-
made an `Rpc` call and building that component's
|
|
3052
|
+
made an `Rpc` call, or whose markup reads a bare `busy` or `ready`, and building that component's
|
|
3053
|
+
markup.
|
|
3034
3054
|
|
|
3035
3055
|
41.29 A `render` answering a request whose `sec-fetch-dest` is `empty` MUST NOT fill the seed buffer,
|
|
3036
3056
|
save one whose `abide-keep` header names `open`.
|
|
3037
3057
|
|
|
3038
3058
|
41.30 A `render` answering a request carrying `abide-keep` MUST write 41.20's script.
|
|
3039
3059
|
|
|
3060
|
+
41.31 On a server, a bare `busy` or `ready` MUST answer over the loads set up before its component's
|
|
3061
|
+
markup is built, and a load a component set up later, one only a pending value places, MAY have it
|
|
3062
|
+
choose another arm at hydration under 41.24.
|
|
3063
|
+
|
|
3040
3064
|
# 42. The app object
|
|
3041
3065
|
|
|
3042
3066
|
42.1 `createApp` MUST build the route table and compose every `Middleware` rung, per 16.14.
|
package/docs/abide.md
CHANGED
|
@@ -773,7 +773,7 @@ A hole is `{}` around an expression, and it updates the one thing it names and n
|
|
|
773
773
|
| You write | Where it lands |
|
|
774
774
|
| --- | --- |
|
|
775
775
|
| `{expr}` | a text node, escaped |
|
|
776
|
-
| `{raw(...)}` | markup, unescaped: `raw` brands a string as a `Raw
|
|
776
|
+
| `{raw(...)}` | markup, unescaped and unchecked (32.77): `raw` brands a string as a `Raw`, so hand it markup you wrote, never a model's reply or a fetched page as it came |
|
|
777
777
|
| `name={expr}` | one attribute, whole |
|
|
778
778
|
| `name="…{expr}…"` | one attribute, interpolated |
|
|
779
779
|
| `class:name={cond}` | one class, there while the condition holds |
|
|
@@ -2996,7 +2996,7 @@ test('one file answers for every company, reading its id', async () => {
|
|
|
2996
2996
|
| --- | --- |
|
|
2997
2997
|
| `route.name` | the pattern that matched, `/companies/[id]` for every company, so a log line keys on the page rather than on each address |
|
|
2998
2998
|
| `Params` | what the segments matched; a query string is never in it |
|
|
2999
|
-
| `RouteParams<P>` | the type `route.params` has in a page filed under `P`, so `id` is text and a key the route lacks is an error |
|
|
2999
|
+
| `RouteParams<P>` | the type `route.params` has in a page or layout filed under `P`, so `id` is text and a key the route lacks is an error. A component under `lib/` has no route of its own and reads the loose `Params`: pass it the value as a prop, or narrow it with `RouteParams<'/…'>` |
|
|
3000
3000
|
|
|
3001
3001
|
## A layout holds what the companies pages share
|
|
3002
3002
|
|
|
@@ -3888,7 +3888,7 @@ test('the server paints the density the cookie holds', async () => {
|
|
|
3888
3888
|
|
|
3889
3889
|
`cookies()` is the request's cookies on the server and the document's in the browser, as a `CookieMap`. Reading a cookie by name follows it, so the list changes on the click. The server render reads the same cookie off the request, so a reload paints compact rather than flashing comfortable first.
|
|
3890
3890
|
|
|
3891
|
-
The principal cookie is `HttpOnly`, so
|
|
3891
|
+
`cookies()` reads and writes any cookie the request carries: a density, a theme, or another system's session cookie your server checks. The principal cookie is `HttpOnly`, so in the browser `cookies()` never sees the seal.
|
|
3892
3892
|
|
|
3893
3893
|
## A handler decides which fields a caller may read
|
|
3894
3894
|
|
|
@@ -7112,7 +7112,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
|
|
|
7112
7112
|
|
|
7113
7113
|
| Name | Signature | Meaning | Rules |
|
|
7114
7114
|
| --- | --- | --- | --- |
|
|
7115
|
-
| `watch` | `(effect: Effect) => () => void` | Begins a watch over whatever the effect reads. | [12.2](/rulebook#12.2), [12.4](/rulebook#12.4), [12.5](/rulebook#12.5), [12.6](/rulebook#12.6), [12.7](/rulebook#12.7), [12.10](/rulebook#12.10), [12.13](/rulebook#12.13) |
|
|
7115
|
+
| `watch` | `(effect: Effect) => () => void` | Begins a watch over whatever the effect reads. | [12.2](/rulebook#12.2), [12.4](/rulebook#12.4), [12.5](/rulebook#12.5), [12.6](/rulebook#12.6), [12.7](/rulebook#12.7), [12.10](/rulebook#12.10), [12.13](/rulebook#12.13), [12.15](/rulebook#12.15) |
|
|
7116
7116
|
| `watch` | `(sources: Reactive<unknown, never, unknown, unknown> \| Reactive<unknown, never, unknown, unknown>[], effect: Effect) => () => void` | The narrowed form, over named sources. | [12.3](/rulebook#12.3) |
|
|
7117
7117
|
|
|
7118
7118
|
## Taught in
|
|
@@ -7167,7 +7167,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
|
|
|
7167
7167
|
|
|
7168
7168
|
| Name | Signature | Meaning | Rules |
|
|
7169
7169
|
| --- | --- | --- | --- |
|
|
7170
|
-
| `busy` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether any entry in the selection has work in flight. | [13.2](/rulebook#13.2), [13.3](/rulebook#13.3), [13.5](/rulebook#13.5), [13.12](/rulebook#13.12), [46.11](/rulebook#46.11) |
|
|
7170
|
+
| `busy` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether any entry in the selection has work in flight. | [13.2](/rulebook#13.2), [13.3](/rulebook#13.3), [13.5](/rulebook#13.5), [13.12](/rulebook#13.12), [41.28](/rulebook#41.28), [41.31](/rulebook#41.31), [46.11](/rulebook#46.11) |
|
|
7171
7171
|
|
|
7172
7172
|
## Taught in
|
|
7173
7173
|
|
|
@@ -7185,7 +7185,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
|
|
|
7185
7185
|
|
|
7186
7186
|
| Name | Signature | Meaning | Rules |
|
|
7187
7187
|
| --- | --- | --- | --- |
|
|
7188
|
-
| `ready` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether every entry in the selection has a value to serve. | [13.3](/rulebook#13.3), [13.5](/rulebook#13.5), [13.11](/rulebook#13.11), [13.12](/rulebook#13.12) |
|
|
7188
|
+
| `ready` | `(selection?: Selection, pattern?: Partial<Args>) => boolean` | Whether every entry in the selection has a value to serve. | [13.3](/rulebook#13.3), [13.5](/rulebook#13.5), [13.11](/rulebook#13.11), [13.12](/rulebook#13.12), [41.28](/rulebook#41.28), [41.31](/rulebook#41.31) |
|
|
7189
7189
|
|
|
7190
7190
|
## Taught in
|
|
7191
7191
|
|
|
@@ -7221,7 +7221,7 @@ Arrive here knowing which name you want. Each page is read off the registry: the
|
|
|
7221
7221
|
|
|
7222
7222
|
| Name | Signature | Meaning | Rules |
|
|
7223
7223
|
| --- | --- | --- | --- |
|
|
7224
|
-
| `refresh` | `(selection?: Selection, pattern?: Partial<Args>) => void` | Stale-while-revalidate over the selection. | [13.4](/rulebook#13.4), [13.7](/rulebook#13.7), [13.10](/rulebook#13.10), [13.12](/rulebook#13.12), [13.14](/rulebook#13.14), [13.15](/rulebook#13.15), [13.16](/rulebook#13.16), [13.17](/rulebook#13.17) |
|
|
7224
|
+
| `refresh` | `(selection?: Selection, pattern?: Partial<Args>) => void` | Stale-while-revalidate over the selection. | [13.4](/rulebook#13.4), [13.7](/rulebook#13.7), [13.10](/rulebook#13.10), [13.12](/rulebook#13.12), [13.14](/rulebook#13.14), [13.15](/rulebook#13.15), [13.16](/rulebook#13.16), [13.17](/rulebook#13.17), [13.20](/rulebook#13.20) |
|
|
7225
7225
|
|
|
7226
7226
|
## Taught in
|
|
7227
7227
|
|
|
@@ -8196,8 +8196,8 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8196
8196
|
|
|
8197
8197
|
| Name | Signature | Meaning | Rules |
|
|
8198
8198
|
| --- | --- | --- | --- |
|
|
8199
|
-
| `route.url` | `Reactive<URL>` | Where we are, fragment included. | [23.1](/rulebook#23.1), [23.2](/rulebook#23.2), [23.3](/rulebook#23.3), [23.4](/rulebook#23.4), [30.20](/rulebook#30.20), [30.21](/rulebook#30.21), [30.23](/rulebook#30.23), [19.16](/rulebook#19.16), [31.16](/rulebook#31.16) |
|
|
8200
|
-
| `route.params` | `Reactive<Params>` | The matched route's segments. | [23.1](/rulebook#23.1), [23.5](/rulebook#23.5), [23.9](/rulebook#23.9), [30.23](/rulebook#30.23), [31.16](/rulebook#31.16) |
|
|
8199
|
+
| `route.url` | `Reactive<URL>` | Where we are, fragment included. | [23.1](/rulebook#23.1), [23.2](/rulebook#23.2), [23.3](/rulebook#23.3), [23.4](/rulebook#23.4), [30.20](/rulebook#30.20), [30.21](/rulebook#30.21), [30.23](/rulebook#30.23), [30.33](/rulebook#30.33), [19.16](/rulebook#19.16), [31.16](/rulebook#31.16) |
|
|
8200
|
+
| `route.params` | `Reactive<Params>` | The matched route's segments — typed by its own route in a page or a layout, and `Params` in any other file. | [23.1](/rulebook#23.1), [23.5](/rulebook#23.5), [23.9](/rulebook#23.9), [30.23](/rulebook#30.23), [31.16](/rulebook#31.16) |
|
|
8201
8201
|
| `route.name` | `Reactive<string>` | The resolution path, the same under any mount path. | [23.1](/rulebook#23.1), [23.6](/rulebook#23.6), [23.14](/rulebook#23.14), [31.16](/rulebook#31.16) |
|
|
8202
8202
|
| `route.navigating` | `Reactive<boolean>` | Whether a navigation is in flight. | [23.1](/rulebook#23.1), [31.16](/rulebook#31.16) |
|
|
8203
8203
|
|
|
@@ -8961,7 +8961,7 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8961
8961
|
|
|
8962
8962
|
| Name | Signature | Meaning | Rules |
|
|
8963
8963
|
| --- | --- | --- | --- |
|
|
8964
|
-
| `raw` | `(value: unknown) => Raw` | The branding a value takes to render as markup rather than as text. | [32.2](/rulebook#32.2), [32.24](/rulebook#32.24), [32.26](/rulebook#32.26), [32.30](/rulebook#32.30), [32.31](/rulebook#32.31), [32.70](/rulebook#32.70) |
|
|
8964
|
+
| `raw` | `(value: unknown) => Raw` | The branding a value takes to render as markup rather than as text. | [32.2](/rulebook#32.2), [32.24](/rulebook#32.24), [32.26](/rulebook#32.26), [32.30](/rulebook#32.30), [32.31](/rulebook#32.31), [32.70](/rulebook#32.70), [32.77](/rulebook#32.77) |
|
|
8965
8965
|
|
|
8966
8966
|
## Taught in
|
|
8967
8967
|
|
|
@@ -9084,7 +9084,7 @@ Pages and routing: what is written in place rather than imported.
|
|
|
9084
9084
|
|
|
9085
9085
|
| Name | Signature | Meaning | Rules |
|
|
9086
9086
|
| --- | --- | --- | --- |
|
|
9087
|
-
| `render` | `(component: Component) => Promise<Uint8Array>` | What produces a document. | [29.10](/rulebook#29.10), [35.4](/rulebook#35.4), [35.5](/rulebook#35.5), [35.6](/rulebook#35.6), [35.15](/rulebook#35.15), [35.16](/rulebook#35.16), [41.1](/rulebook#41.1), [41.2](/rulebook#41.2), [41.3](/rulebook#41.3), [41.4](/rulebook#41.4), [41.9](/rulebook#41.9), [41.13](/rulebook#41.13), [41.16](/rulebook#41.16), [41.17](/rulebook#41.17), [41.18](/rulebook#41.18), [41.19](/rulebook#41.19), [41.20](/rulebook#41.20), [41.28](/rulebook#41.28), [35.11](/rulebook#35.11), [41.29](/rulebook#41.29), [41.30](/rulebook#41.30) |
|
|
9087
|
+
| `render` | `(component: Component) => Promise<Uint8Array>` | What produces a document. | [29.10](/rulebook#29.10), [35.4](/rulebook#35.4), [35.5](/rulebook#35.5), [35.6](/rulebook#35.6), [35.15](/rulebook#35.15), [35.16](/rulebook#35.16), [41.1](/rulebook#41.1), [41.2](/rulebook#41.2), [41.3](/rulebook#41.3), [41.4](/rulebook#41.4), [41.9](/rulebook#41.9), [41.13](/rulebook#41.13), [41.16](/rulebook#41.16), [41.17](/rulebook#41.17), [41.18](/rulebook#41.18), [41.19](/rulebook#41.19), [41.20](/rulebook#41.20), [41.28](/rulebook#41.28), [35.11](/rulebook#35.11), [41.29](/rulebook#41.29), [41.30](/rulebook#41.30), [41.31](/rulebook#41.31) |
|
|
9088
9088
|
|
|
9089
9089
|
## Taught in
|
|
9090
9090
|
|
|
@@ -10375,20 +10375,20 @@ CLI: what is written in place rather than imported.
|
|
|
10375
10375
|
|
|
10376
10376
|
| Command | Meaning | Rules |
|
|
10377
10377
|
| --- | --- | --- |
|
|
10378
|
-
| `abide scaffold <name>` | Writes a starter project. | [38.1](/rulebook#38.1), [38.42](/rulebook#38.42), [38.103](/rulebook#38.103), [38.104](/rulebook#38.104), [38.106](/rulebook#38.106), [38.112](/rulebook#38.112) |
|
|
10378
|
+
| `abide scaffold <name>` | Writes a starter project. | [38.1](/rulebook#38.1), [38.42](/rulebook#38.42), [38.103](/rulebook#38.103), [38.104](/rulebook#38.104), [38.106](/rulebook#38.106), [38.112](/rulebook#38.112), [38.122](/rulebook#38.122) |
|
|
10379
10379
|
| `abide run <file> [args…]` | Runs a script under the abide runtime. | [38.2](/rulebook#38.2), [38.57](/rulebook#38.57) |
|
|
10380
|
-
| `abide check [dir…]` | The type-checker over a `.abide` tree. | [38.3](/rulebook#38.3), [38.31](/rulebook#38.31), [38.32](/rulebook#38.32), [38.33](/rulebook#38.33), [38.40](/rulebook#38.40), [38.105](/rulebook#38.105) |
|
|
10381
|
-
| `abide routes [--json]` | Every address the app answers, the file answering it, and where each hook is filed. | [38.108](/rulebook#38.108) |
|
|
10382
|
-
| `abide dev [--port <n>]` | Watches the project and keeps the app up. | [16.23](/rulebook#16.23), [16.56](/rulebook#16.56), [37.8](/rulebook#37.8), [37.9](/rulebook#37.9), [38.4](/rulebook#38.4), [38.14](/rulebook#38.14), [38.15](/rulebook#38.15), [38.16](/rulebook#38.16), [38.17](/rulebook#38.17), [38.19](/rulebook#38.19), [38.24](/rulebook#38.24), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.28](/rulebook#38.28), [38.30](/rulebook#38.30), [38.35](/rulebook#38.35), [38.36](/rulebook#38.36), [38.37](/rulebook#38.37), [38.38](/rulebook#38.38), [38.41](/rulebook#38.41), [38.43](/rulebook#38.43), [38.58](/rulebook#38.58), [38.63](/rulebook#38.63), [38.64](/rulebook#38.64), [38.65](/rulebook#38.65), [38.94](/rulebook#38.94), [38.96](/rulebook#38.96), [38.97](/rulebook#38.97) |
|
|
10383
|
-
| `abide build` | Builds the client into content-hashed chunks and a manifest. | [38.43](/rulebook#38.43), [38.5](/rulebook#38.5), [38.18](/rulebook#38.18), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.27](/rulebook#38.27), [38.30](/rulebook#38.30), [38.66](/rulebook#38.66), [38.94](/rulebook#38.94) |
|
|
10384
|
-
| `abide start [--port <n>]` | Boots the built app. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.6](/rulebook#38.6), [38.100](/rulebook#38.100), [38.20](/rulebook#38.20), [38.21](/rulebook#38.21), [38.22](/rulebook#38.22), [38.23](/rulebook#38.23), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.27](/rulebook#38.27), [38.28](/rulebook#38.28), [38.29](/rulebook#38.29), [38.58](/rulebook#38.58), [38.67](/rulebook#38.67), [38.68](/rulebook#38.68), [38.95](/rulebook#38.95), [38.97](/rulebook#38.97), [38.110](/rulebook#38.110) |
|
|
10385
|
-
| `abide [--url <origin>] [--port <n>]` | The interactive shell — on the built app it boots, or on the running app `--url` names. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.6](/rulebook#38.6), [38.7](/rulebook#38.7), [38.53](/rulebook#38.53), [38.54](/rulebook#38.54), [38.55](/rulebook#38.55), [38.58](/rulebook#38.58), [38.59](/rulebook#38.59), [38.60](/rulebook#38.60), [38.62](/rulebook#38.62) |
|
|
10380
|
+
| `abide check [dir…]` | The type-checker over a `.abide` tree. | [38.3](/rulebook#38.3), [38.31](/rulebook#38.31), [38.32](/rulebook#38.32), [38.33](/rulebook#38.33), [38.40](/rulebook#38.40), [38.105](/rulebook#38.105), [38.123](/rulebook#38.123) |
|
|
10381
|
+
| `abide routes [--json]` | Every address the app answers, the file answering it, and where each hook is filed. | [38.108](/rulebook#38.108), [38.123](/rulebook#38.123) |
|
|
10382
|
+
| `abide dev [--port <n>]` | Watches the project and keeps the app up. | [16.23](/rulebook#16.23), [16.56](/rulebook#16.56), [37.8](/rulebook#37.8), [37.9](/rulebook#37.9), [38.4](/rulebook#38.4), [38.14](/rulebook#38.14), [38.15](/rulebook#38.15), [38.16](/rulebook#38.16), [38.17](/rulebook#38.17), [38.19](/rulebook#38.19), [38.24](/rulebook#38.24), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.28](/rulebook#38.28), [38.30](/rulebook#38.30), [38.35](/rulebook#38.35), [38.36](/rulebook#38.36), [38.37](/rulebook#38.37), [38.38](/rulebook#38.38), [38.41](/rulebook#38.41), [38.43](/rulebook#38.43), [38.58](/rulebook#38.58), [38.63](/rulebook#38.63), [38.64](/rulebook#38.64), [38.65](/rulebook#38.65), [38.94](/rulebook#38.94), [38.96](/rulebook#38.96), [38.97](/rulebook#38.97), [38.123](/rulebook#38.123) |
|
|
10383
|
+
| `abide build` | Builds the client into content-hashed chunks and a manifest. | [38.43](/rulebook#38.43), [38.5](/rulebook#38.5), [38.18](/rulebook#38.18), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.27](/rulebook#38.27), [38.30](/rulebook#38.30), [38.66](/rulebook#38.66), [38.94](/rulebook#38.94), [38.123](/rulebook#38.123) |
|
|
10384
|
+
| `abide start [--port <n>]` | Boots the built app. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.6](/rulebook#38.6), [38.100](/rulebook#38.100), [38.20](/rulebook#38.20), [38.21](/rulebook#38.21), [38.22](/rulebook#38.22), [38.23](/rulebook#38.23), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.27](/rulebook#38.27), [38.28](/rulebook#38.28), [38.29](/rulebook#38.29), [38.58](/rulebook#38.58), [38.67](/rulebook#38.67), [38.68](/rulebook#38.68), [38.95](/rulebook#38.95), [38.97](/rulebook#38.97), [38.110](/rulebook#38.110), [38.123](/rulebook#38.123) |
|
|
10385
|
+
| `abide [--url <origin>] [--port <n>]` | The interactive shell — on the built app it boots, or on the running app `--url` names. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.6](/rulebook#38.6), [38.7](/rulebook#38.7), [38.53](/rulebook#38.53), [38.54](/rulebook#38.54), [38.55](/rulebook#38.55), [38.58](/rulebook#38.58), [38.59](/rulebook#38.59), [38.60](/rulebook#38.60), [38.62](/rulebook#38.62), [38.123](/rulebook#38.123) |
|
|
10386
10386
|
| `abide call <address> [args]` | One call to one handler. | [38.8](/rulebook#38.8), [38.61](/rulebook#38.61), [38.110](/rulebook#38.110) |
|
|
10387
|
-
| `abide openapi [--url <origin>]` | The OpenAPI document printed rather than served. | [38.9](/rulebook#38.9), [38.25](/rulebook#38.25), [38.110](/rulebook#38.110), [38.111](/rulebook#38.111) |
|
|
10388
|
-
| `abide mcp [--url <origin>]` | The app's MCP server over stdio. | [38.9](/rulebook#38.9), [38.13](/rulebook#38.13), [38.25](/rulebook#38.25), [38.110](/rulebook#38.110) |
|
|
10387
|
+
| `abide openapi [--url <origin>]` | The OpenAPI document printed rather than served. | [38.9](/rulebook#38.9), [38.25](/rulebook#38.25), [38.110](/rulebook#38.110), [38.111](/rulebook#38.111), [38.123](/rulebook#38.123) |
|
|
10388
|
+
| `abide mcp [--url <origin>]` | The app's MCP server over stdio. | [38.9](/rulebook#38.9), [38.13](/rulebook#38.13), [38.25](/rulebook#38.25), [38.110](/rulebook#38.110), [38.123](/rulebook#38.123) |
|
|
10389
10389
|
| `abide watch <address> [args]` | A live subscription to one of the app's followed `GET`s. | [38.69](/rulebook#38.69) |
|
|
10390
|
-
| `abide compile [--target] [--out] [--platforms]` | One standalone executable. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.10](/rulebook#38.10), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.44](/rulebook#38.44), [38.45](/rulebook#38.45), [38.46](/rulebook#38.46), [38.47](/rulebook#38.47), [38.48](/rulebook#38.48), [38.49](/rulebook#38.49), [38.50](/rulebook#38.50), [38.51](/rulebook#38.51), [38.52](/rulebook#38.52), [38.56](/rulebook#38.56), [38.93](/rulebook#38.93), [38.97](/rulebook#38.97) |
|
|
10391
|
-
| `abide bundle [--out]` | A desktop app for the host platform, with a window on the app. | [38.10](/rulebook#38.10), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.70](/rulebook#38.70), [38.71](/rulebook#38.71), [38.72](/rulebook#38.72), [38.73](/rulebook#38.73), [38.74](/rulebook#38.74), [38.75](/rulebook#38.75), [38.76](/rulebook#38.76), [38.77](/rulebook#38.77), [38.78](/rulebook#38.78), [38.79](/rulebook#38.79), [38.80](/rulebook#38.80), [38.81](/rulebook#38.81), [38.82](/rulebook#38.82), [38.83](/rulebook#38.83), [38.84](/rulebook#38.84), [38.85](/rulebook#38.85), [38.86](/rulebook#38.86), [38.87](/rulebook#38.87), [38.88](/rulebook#38.88), [38.89](/rulebook#38.89), [38.90](/rulebook#38.90), [38.91](/rulebook#38.91), [38.92](/rulebook#38.92), [38.101](/rulebook#38.101), [38.102](/rulebook#38.102) |
|
|
10390
|
+
| `abide compile [--target] [--out] [--platforms]` | One standalone executable. | [37.8](/rulebook#37.8), [37.10](/rulebook#37.10), [37.11](/rulebook#37.11), [37.12](/rulebook#37.12), [38.10](/rulebook#38.10), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.44](/rulebook#38.44), [38.45](/rulebook#38.45), [38.46](/rulebook#38.46), [38.47](/rulebook#38.47), [38.48](/rulebook#38.48), [38.49](/rulebook#38.49), [38.50](/rulebook#38.50), [38.51](/rulebook#38.51), [38.52](/rulebook#38.52), [38.56](/rulebook#38.56), [38.93](/rulebook#38.93), [38.97](/rulebook#38.97), [38.123](/rulebook#38.123) |
|
|
10391
|
+
| `abide bundle [--out]` | A desktop app for the host platform, with a window on the app. | [38.10](/rulebook#38.10), [38.25](/rulebook#38.25), [38.26](/rulebook#38.26), [38.70](/rulebook#38.70), [38.71](/rulebook#38.71), [38.72](/rulebook#38.72), [38.73](/rulebook#38.73), [38.74](/rulebook#38.74), [38.75](/rulebook#38.75), [38.76](/rulebook#38.76), [38.77](/rulebook#38.77), [38.78](/rulebook#38.78), [38.79](/rulebook#38.79), [38.80](/rulebook#38.80), [38.81](/rulebook#38.81), [38.82](/rulebook#38.82), [38.83](/rulebook#38.83), [38.84](/rulebook#38.84), [38.85](/rulebook#38.85), [38.86](/rulebook#38.86), [38.87](/rulebook#38.87), [38.88](/rulebook#38.88), [38.89](/rulebook#38.89), [38.90](/rulebook#38.90), [38.91](/rulebook#38.91), [38.92](/rulebook#38.92), [38.101](/rulebook#38.101), [38.102](/rulebook#38.102), [38.123](/rulebook#38.123) |
|
|
10392
10392
|
| `abide lsp` | The `.abide` language server, over stdio. | [38.11](/rulebook#38.11), [38.34](/rulebook#38.34), [38.39](/rulebook#38.39), [38.98](/rulebook#38.98), [38.116](/rulebook#38.116), [38.117](/rulebook#38.117), [38.118](/rulebook#38.118) |
|
|
10393
10393
|
| `--help` · `-h` · `abide help` | Usage, generated from the command list. | [38.12](/rulebook#38.12), [38.113](/rulebook#38.113), [38.114](/rulebook#38.114) |
|
|
10394
10394
|
| `--version` · `-v` | The version of abide, or of the app a compiled executable holds. | [38.115](/rulebook#38.115) |
|
package/package.json
CHANGED
|
@@ -915,8 +915,9 @@ export class TypeSession {
|
|
|
915
915
|
checked.push({ source, map, refusals: shadow.refusals })
|
|
916
916
|
}
|
|
917
917
|
// 34.6 — WHAT THE BUNDLER RESOLVES AND THE CHECKER CANNOT: a stylesheet imported for its
|
|
918
|
-
// effect.
|
|
919
|
-
//
|
|
918
|
+
// effect, and (34.21) markdown imported as text into a `.ts`, which bun's own types leave
|
|
919
|
+
// out where they declare `.txt`. Declared in a file of its own at the app's root, which is
|
|
920
|
+
// a script, because a pattern module can only be declared outside a module.
|
|
920
921
|
this.register(
|
|
921
922
|
`${this.canonical(within)}/${AMBIENT}`,
|
|
922
923
|
AMBIENT_DECLARATIONS,
|
|
@@ -1484,6 +1485,10 @@ function routesDeclaration(addresses: readonly string[]): string {
|
|
|
1484
1485
|
return `export {}\ndeclare module ${JSON.stringify(URL_SOURCE)} {\n interface Routes {\n${keys} }\n}\n`
|
|
1485
1486
|
}
|
|
1486
1487
|
const AMBIENT_DECLARATIONS = `declare module '*.css' {}
|
|
1488
|
+
declare module '*.md' {
|
|
1489
|
+
const text: string
|
|
1490
|
+
export default text
|
|
1491
|
+
}
|
|
1487
1492
|
`
|
|
1488
1493
|
|
|
1489
1494
|
/**
|
|
@@ -11,6 +11,7 @@ import { COMPONENT_QUERY } from '#server/MODULE_QUERIES.ts'
|
|
|
11
11
|
import { ROUTES_TABLE, SERVER_TABLE } from '#server/TABLES.ts'
|
|
12
12
|
import { VIRTUAL_DIRECTORY } from '#server/VIRTUAL_DIRECTORY.ts'
|
|
13
13
|
import { CLIENT_ADDRESS } from '#shared/ADDRESSES.ts'
|
|
14
|
+
import type { JsonValue } from '#shared/JsonValue.ts'
|
|
14
15
|
import { ROUTING_SPECIFIER, WIRE_SPECIFIER } from '#shared/SPECIFIERS.ts'
|
|
15
16
|
import {
|
|
16
17
|
type AppLayout,
|
|
@@ -317,6 +318,7 @@ async function emitApp(
|
|
|
317
318
|
refuseSharedRoutes(routes, mounts, refusals)
|
|
318
319
|
refuseAddressCollision(routes, mounts, layout.published, refusals)
|
|
319
320
|
const standIns = standInsOf(mounts)
|
|
321
|
+
const derived = derivedOf(mounts)
|
|
320
322
|
// 37.1 — every module a server block or a handler file runs in, for the boot to import before
|
|
321
323
|
// `onStart`.
|
|
322
324
|
const servers: string[] = []
|
|
@@ -329,6 +331,7 @@ async function emitApp(
|
|
|
329
331
|
entry,
|
|
330
332
|
standIns.get(entry.relative),
|
|
331
333
|
entry.text,
|
|
334
|
+
derived.get(entry.relative),
|
|
332
335
|
)
|
|
333
336
|
} catch (thrown) {
|
|
334
337
|
refusals.at(entry.path, thrown)
|
|
@@ -368,6 +371,7 @@ async function emitApp(
|
|
|
368
371
|
handlers[mount.source] = {
|
|
369
372
|
scope: mount.scope,
|
|
370
373
|
client: standIns.get(mount.source) ?? '',
|
|
374
|
+
args: mount.args,
|
|
371
375
|
}
|
|
372
376
|
if (routes.length > 0) {
|
|
373
377
|
tables[ROUTES_TABLE] = clientTable(routes, transitions)
|
|
@@ -624,6 +628,21 @@ function standInsOf(mounts: Mount[]): Map<string, string> {
|
|
|
624
628
|
return standIns
|
|
625
629
|
}
|
|
626
630
|
|
|
631
|
+
/**
|
|
632
|
+
* 19.9 — the schema each private handler's annotation derives, by its binding, by source: what its
|
|
633
|
+
* server module holds it to (15.8). A handler file's is its face's (`HandlerFace`).
|
|
634
|
+
*/
|
|
635
|
+
function derivedOf(mounts: Mount[]): Map<string, Record<string, JsonValue>> {
|
|
636
|
+
const derived = new Map<string, Record<string, JsonValue>>()
|
|
637
|
+
for (const mount of mounts) {
|
|
638
|
+
if (!mount.private || mount.args === undefined) continue
|
|
639
|
+
const held = derived.get(mount.source) ?? {}
|
|
640
|
+
held[mount.name] = mount.args
|
|
641
|
+
derived.set(mount.source, held)
|
|
642
|
+
}
|
|
643
|
+
return derived
|
|
644
|
+
}
|
|
645
|
+
|
|
627
646
|
/**
|
|
628
647
|
* One handler's wrapper, bound as `binding` writes it.
|
|
629
648
|
*
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { JsonValue } from '#shared/JsonValue.ts'
|
|
2
|
+
import { SERVER_SPECIFIER, SHARED_SPECIFIER } from '#shared/SPECIFIERS.ts'
|
|
2
3
|
import { type CheckerAnswer, UNCHECKED } from './CheckerAnswer.ts'
|
|
3
4
|
import { Cursor, placeTemplate } from './Cursor.ts'
|
|
4
5
|
import { type LoweredChunk, Lowering, PROPS } from './Lowering.ts'
|
|
5
|
-
import type
|
|
6
|
+
import { type AbideFile, scriptBodies } from './parseAbide.ts'
|
|
6
7
|
import { privateExports } from './projectShadow.ts'
|
|
7
8
|
import {
|
|
8
9
|
concat,
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
SERVER_LANE,
|
|
15
16
|
} from './renderMarkup.ts'
|
|
16
17
|
import { insideShare, SETTLED_AT, shareScope } from './shareScope.ts'
|
|
17
|
-
import { splitImports, withoutProps } from './splitImports.ts'
|
|
18
|
+
import { importStatements, splitImports, withoutProps } from './splitImports.ts'
|
|
18
19
|
import type { TemplateNode } from './TemplateNode.ts'
|
|
19
20
|
|
|
20
21
|
// The name the body is bound to while a component's head contributions land (`HEADS`).
|
|
@@ -40,7 +41,8 @@ const CALLS_BEFORE = '__callsBefore'
|
|
|
40
41
|
* `settles` IS A PAGE OR A LAYOUT, which `render` builds a level at a time (41.19): its setup runs
|
|
41
42
|
* at the call and its markup is built on the first read of `head` or `body`, so the calls the
|
|
42
43
|
* setup started are given the turn to answer. Every other component answers the same way only where
|
|
43
|
-
* its setup made a call (41.28), and builds where it is
|
|
44
|
+
* its setup made a call or its markup reads a bare `busy` or `ready` (41.28), and builds where it is
|
|
45
|
+
* called otherwise.
|
|
44
46
|
*/
|
|
45
47
|
export function emitComponent(
|
|
46
48
|
file: AbideFile,
|
|
@@ -50,6 +52,8 @@ export function emitComponent(
|
|
|
50
52
|
route = '/',
|
|
51
53
|
/** 38.97 — where given, every chunk the module is written around (`sourceMapOf`). */
|
|
52
54
|
chunks?: LoweredChunk[],
|
|
55
|
+
/** 19.9 — the schema each private handler's annotation derives, by its binding. */
|
|
56
|
+
derived: Readonly<Record<string, JsonValue>> = {},
|
|
53
57
|
): string {
|
|
54
58
|
// Only what the page actually uses. An emitted module carrying an import nothing calls is a
|
|
55
59
|
// name the app's own tooling reports as unused, in a file nobody wrote.
|
|
@@ -91,7 +95,7 @@ export function emitComponent(
|
|
|
91
95
|
: `${lowering.use('fileRoute')}(${JSON.stringify(route)})
|
|
92
96
|
${serverSetup.body}
|
|
93
97
|
${lowering.use('fileRoute')}(undefined)
|
|
94
|
-
${privateExports(file.servers.join('\n'))}`
|
|
98
|
+
${heldToAnnotations(lowering, derived)}${privateExports(file.servers.join('\n'))}`
|
|
95
99
|
const setup = splitImports(lowering.scripts())
|
|
96
100
|
const componentBody = setup.body
|
|
97
101
|
|
|
@@ -141,16 +145,22 @@ ${privateExports(file.servers.join('\n'))}`
|
|
|
141
145
|
// made a call, and then answers as a page does, for `renderComponent` to build after the turn.
|
|
142
146
|
// THE BUILD IS WRITTEN TWICE rather than always run through `__build`: the closure measured ~4%
|
|
143
147
|
// a row on `bench:component`'s card under JSC, the second copy nothing but server module bytes.
|
|
144
|
-
// One with no setup cannot call, and is written once.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
// One with no setup cannot call, and is written once. One whose markup reads a bare `busy` or
|
|
149
|
+
// `ready` answers as a page does always: built where it is called, it would answer before the
|
|
150
|
+
// siblings after it had started a load, where the browser's answers once every setup has run.
|
|
151
|
+
const probes =
|
|
152
|
+
!settles &&
|
|
153
|
+
readsBareProbe(file, `${head}\n${declared}\n${body.join('\n')}`)
|
|
154
|
+
const waits = !settles && !probes && componentBody.trim() !== ''
|
|
155
|
+
const level =
|
|
156
|
+
settles || probes
|
|
157
|
+
? deferred()
|
|
158
|
+
: waits
|
|
159
|
+
? ` if (${lowering.use('callsInRender')}() !== ${CALLS_BEFORE}) {
|
|
150
160
|
${deferred()}
|
|
151
161
|
}
|
|
152
162
|
${build}`
|
|
153
|
-
|
|
163
|
+
: build
|
|
154
164
|
return `${moduleImports(lowering, () => SERVER_SPECIFIER, [moduleSetup, serverSetup, setup])}
|
|
155
165
|
${moduleSetup.body === '' ? '' : `${moduleSetup.body}\n`}${serverBody}export default function Component(${lowering.takesProps() ? `${PROPS} = {}` : ''}) {
|
|
156
166
|
${waits ? ` const ${CALLS_BEFORE} = ${lowering.use('callsInRender')}()\n` : ''}${opened}${componentBody}
|
|
@@ -159,6 +169,44 @@ ${level}${closed}
|
|
|
159
169
|
`
|
|
160
170
|
}
|
|
161
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Whether `markup`, a component's emitted head and body, calls `busy` or `ready` with no argument,
|
|
174
|
+
* under any name the file's scripts import either by from `@abide/abide`. A member's (`s.busy()`)
|
|
175
|
+
* is not bare.
|
|
176
|
+
*/
|
|
177
|
+
function readsBareProbe(file: AbideFile, markup: string): boolean {
|
|
178
|
+
let names = ''
|
|
179
|
+
for (const script of scriptBodies(file))
|
|
180
|
+
for (const statement of importStatements(script))
|
|
181
|
+
if (statement.specifier === SHARED_SPECIFIER && !statement.typeOnly)
|
|
182
|
+
for (const binding of statement.bindings)
|
|
183
|
+
if (
|
|
184
|
+
!binding.typeOnly &&
|
|
185
|
+
(binding.imported === 'busy' ||
|
|
186
|
+
binding.imported === 'ready')
|
|
187
|
+
)
|
|
188
|
+
names += `${names === '' ? '' : '|'}${binding.local}`
|
|
189
|
+
return (
|
|
190
|
+
names !== '' &&
|
|
191
|
+
new RegExp(`(?<![\\w$.])(?:${names})\\(\\s*\\)`).test(markup)
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 15.8, 19.9 — each private handler held to the schema its annotation derives, once its server block
|
|
197
|
+
* has built it: the module is what the server mounts and what a component under `mount` calls
|
|
198
|
+
* (38.121), so a call is held to it whoever makes it.
|
|
199
|
+
*/
|
|
200
|
+
function heldToAnnotations(
|
|
201
|
+
lowering: Lowering,
|
|
202
|
+
derived: Readonly<Record<string, JsonValue>>,
|
|
203
|
+
): string {
|
|
204
|
+
let out = ''
|
|
205
|
+
for (const name in derived)
|
|
206
|
+
out += `${lowering.use('derivedArgs')}(${name}, ${JSON.stringify(derived[name])})\n`
|
|
207
|
+
return out
|
|
208
|
+
}
|
|
209
|
+
|
|
162
210
|
/**
|
|
163
211
|
* An emitted module's imports: each framework name `lowering` used, from the specifier it ships
|
|
164
212
|
* under, and the author's own from both script blocks, already pointed where they landed
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { STYLE_QUERY } from '#server/MODULE_QUERIES.ts'
|
|
2
|
+
import type { JsonValue } from '#shared/JsonValue.ts'
|
|
2
3
|
import type { Placement } from '#server/placement.ts'
|
|
3
4
|
import type { CheckerAnswer } from './CheckerAnswer.ts'
|
|
4
5
|
import { emitClient } from './emitClient.ts'
|
|
@@ -35,6 +36,8 @@ export function emitSource(
|
|
|
35
36
|
standIns = '',
|
|
36
37
|
/** 38.97 — the `.abide`'s text, where the build wants each module's source map. */
|
|
37
38
|
text?: string,
|
|
39
|
+
/** 19.9 — the schema each private handler it declares derives off its annotation, by name. */
|
|
40
|
+
derived?: Readonly<Record<string, JsonValue>>,
|
|
38
41
|
): {
|
|
39
42
|
server: string
|
|
40
43
|
client: string
|
|
@@ -109,6 +112,7 @@ export function emitSource(
|
|
|
109
112
|
kind !== 'component',
|
|
110
113
|
source.scope,
|
|
111
114
|
serverChunks,
|
|
115
|
+
derived,
|
|
112
116
|
)
|
|
113
117
|
const name = relative.slice(relative.lastIndexOf('/') + 1)
|
|
114
118
|
const browser = withStandIns(
|
package/src/compiler/scaffold.ts
CHANGED
|
@@ -35,15 +35,17 @@ const SOURCE_ALIAS = { '#src/*': ['./src/*', './src/*.ts'] }
|
|
|
35
35
|
* 38.109, 38.119 — the starter's `bunfig.toml`: the harness's abide preload, which is a DOM a test
|
|
36
36
|
* can mount into and measure, then abide's own — the app's `.abide` files imported as modules, with
|
|
37
37
|
* nothing booted first. Its test root is `src`, so a browser test under `e2e` is playwright's alone.
|
|
38
|
+
* 38.122 — and no peer installed: `bun-plugin-tailwind` names `bun` as one, and an installed copy
|
|
39
|
+
* lands first on the PATH inside `bun run`, shadowing the bun that runs everything else.
|
|
38
40
|
*/
|
|
39
|
-
export const
|
|
40
|
-
'# 38.109, 38.119 — a DOM to mount into and measure, then the app\'s `.abide` files as modules.\n[test]\nroot = "src"\npreload = ["@abide/harness/abide/preload"]\n'
|
|
41
|
+
export const BUNFIG =
|
|
42
|
+
'# 38.122 — a peer is not installed: an installed `bun` would shadow this one inside `bun run`.\n[install]\npeer = false\n\n# 38.109, 38.119 — a DOM to mount into and measure, then the app\'s `.abide` files as modules.\n[test]\nroot = "src"\npreload = ["@abide/harness/abide/preload"]\n'
|
|
41
43
|
|
|
42
44
|
// WRITTEN, NOT COPIED, beside the manifests: a packer drops a `bunfig.toml` or a `.gitignore` from
|
|
43
45
|
// the tarball (`bun pm pack` both, `npm pack` the second), so neither survives as a starter file.
|
|
44
46
|
function filesFor(name: string): Record<string, string> {
|
|
45
47
|
return {
|
|
46
|
-
'bunfig.toml':
|
|
48
|
+
'bunfig.toml': BUNFIG,
|
|
47
49
|
'.gitignore': 'node_modules\n.abide\n',
|
|
48
50
|
'package.json': `${JSON.stringify(
|
|
49
51
|
{
|
|
@@ -120,7 +120,8 @@ export function serveCompilation(root: string, compilation: Compilation): void {
|
|
|
120
120
|
const real = realOrAsGiven(root)
|
|
121
121
|
const handlerFiles = addCompilation(real, compilation, RUNTIME)
|
|
122
122
|
installSourceMaps(serverMapsOf(compilation, real), real)
|
|
123
|
-
if (handlerFiles.length !== 0)
|
|
123
|
+
if (handlerFiles.length !== 0)
|
|
124
|
+
Bun.plugin(handlerFilesPlugin(handlerFiles, RUNTIME.handlers))
|
|
124
125
|
if (registered) return
|
|
125
126
|
registered = true
|
|
126
127
|
Bun.plugin(modulesPlugin(RUNTIME))
|
|
@@ -132,7 +133,13 @@ export function serveCompilation(root: string, compilation: Compilation): void {
|
|
|
132
133
|
// plugin per compilation because the filter is fixed at registration, and the runtime's `onLoad`
|
|
133
134
|
// cannot fall through, so the filter names the handler files and nothing else. The face it names
|
|
134
135
|
// is resolved by `modulesPlugin`, registered once.
|
|
135
|
-
|
|
136
|
+
// 15.8, 19.9 — AND HOLDS ITS HANDLER TO THE SCHEMA ITS ANNOTATION DERIVES, once the face has built it:
|
|
137
|
+
// this module is what every import of the file is answered with — the server's mount, a test's own
|
|
138
|
+
// import, a component's under `mount` — so a call is held to it whoever makes it.
|
|
139
|
+
function handlerFilesPlugin(
|
|
140
|
+
paths: string[],
|
|
141
|
+
faces: ReadonlyMap<string, HandlerFace>,
|
|
142
|
+
): Bun.BunPlugin {
|
|
136
143
|
return {
|
|
137
144
|
name: 'abide-handler-files',
|
|
138
145
|
setup(build) {
|
|
@@ -142,10 +149,18 @@ function handlerFilesPlugin(paths: string[]): Bun.BunPlugin {
|
|
|
142
149
|
`^(?:${paths.map(RegExp.escape).join('|')})$`,
|
|
143
150
|
),
|
|
144
151
|
},
|
|
145
|
-
(args) =>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
(args) => {
|
|
153
|
+
const face = JSON.stringify(`${args.path}${HANDLER_SUFFIX}`)
|
|
154
|
+
const schema = faces.get(args.path)?.args
|
|
155
|
+
return {
|
|
156
|
+
contents:
|
|
157
|
+
`export { default } from ${face}\n` +
|
|
158
|
+
(schema === undefined
|
|
159
|
+
? ''
|
|
160
|
+
: `import __handler from ${face}\nimport { derivedArgs as __derivedArgs } from '${SERVER_SPECIFIER}'\n__derivedArgs(__handler, ${JSON.stringify(schema)})\n`),
|
|
161
|
+
loader: 'js',
|
|
162
|
+
}
|
|
163
|
+
},
|
|
149
164
|
)
|
|
150
165
|
},
|
|
151
166
|
}
|
package/src/server/answerRpc.ts
CHANGED
|
@@ -16,7 +16,7 @@ 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 type { Method, Rpc } from '#shared/rpc.ts'
|
|
19
|
+
import type { HeldToAnnotation, Method, Rpc } from '#shared/rpc.ts'
|
|
20
20
|
import { argsInURL } from '#shared/rpcRequest.ts'
|
|
21
21
|
import { VERSION_HEADER } from '#shared/VERSION_HEADER.ts'
|
|
22
22
|
import { versionInEtag } from '#shared/versionInEtag.ts'
|
|
@@ -118,20 +118,13 @@ export type Mounted = Mount & {
|
|
|
118
118
|
* subscription to it both pass through (37.2, 18.26).
|
|
119
119
|
*/
|
|
120
120
|
rungs: AppRung[]
|
|
121
|
-
handler: Rpc<unknown, unknown> & Gated<unknown>
|
|
121
|
+
handler: Rpc<unknown, unknown> & Gated<unknown> & HeldToAnnotation
|
|
122
122
|
/**
|
|
123
123
|
* The args' JSON Schema, probed once at mount.
|
|
124
124
|
*
|
|
125
125
|
* 19.7, 20.11 — a third-party schema converts itself on every probe.
|
|
126
126
|
*/
|
|
127
127
|
argsDocument: Document | undefined
|
|
128
|
-
/**
|
|
129
|
-
* What the wire holds args to where the handler declares no `args` gate of its own.
|
|
130
|
-
*
|
|
131
|
-
* 19.9 — the annotation's schema. An in-process call already holds the type, so the handler
|
|
132
|
-
* never runs it.
|
|
133
|
-
*/
|
|
134
|
-
wireArgs: JsonValue | undefined
|
|
135
128
|
}
|
|
136
129
|
|
|
137
130
|
/**
|
|
@@ -172,7 +165,6 @@ export function mountedOwn(
|
|
|
172
165
|
result: undefined,
|
|
173
166
|
handler: handler as Mounted['handler'],
|
|
174
167
|
argsDocument: undefined,
|
|
175
|
-
wireArgs: undefined,
|
|
176
168
|
},
|
|
177
169
|
]
|
|
178
170
|
}
|
|
@@ -198,7 +190,7 @@ export async function mountHandlers(
|
|
|
198
190
|
for (let at = 0; at < table.length; at += 1) {
|
|
199
191
|
const mount = table[at] as Mount
|
|
200
192
|
const exported = (modules[at] as Record<string, unknown>)[mount.export]
|
|
201
|
-
const handler = exported as
|
|
193
|
+
const handler = exported as Mounted['handler']
|
|
202
194
|
const declared = argsDocumentOf(handler)
|
|
203
195
|
// 16.9, 16.10 — the address `rpc.url` and `rpc.raw` build on, under the mount — and what a
|
|
204
196
|
// page's render turns a call it made into the browser's request by (41.20).
|
|
@@ -210,7 +202,6 @@ export async function mountHandlers(
|
|
|
210
202
|
rungs: rungsOver(mount.scope),
|
|
211
203
|
handler,
|
|
212
204
|
argsDocument: declared ?? (mount.args as Document | undefined),
|
|
213
|
-
wireArgs: declared === undefined ? mount.args : undefined,
|
|
214
205
|
})
|
|
215
206
|
}
|
|
216
207
|
return { handlers }
|
package/src/server/argsFor.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// off the wire: over http (16.2) and a `GET` followed over the socket mux (18.3). Each lane reads the
|
|
3
3
|
// args off its own transport and answers a refusal in its own form.
|
|
4
4
|
|
|
5
|
+
import { DERIVED_ARGS } from '#shared/DERIVED_ARGS.ts'
|
|
5
6
|
import { normalized } from '#shared/reactive/argsContract.ts'
|
|
6
7
|
import { validationError } from '#shared/refuse.ts'
|
|
7
|
-
import { validateJson } from '#shared/validateJson.ts'
|
|
8
8
|
import type { Mounted } from './answerRpc.ts'
|
|
9
9
|
import { coerceArgs } from './coerceArgs.ts'
|
|
10
10
|
|
|
@@ -16,11 +16,10 @@ import { coerceArgs } from './coerceArgs.ts'
|
|
|
16
16
|
* 19.7 — the text as the types the handler's `args` declares. 11.27 — then the defaults, ahead of
|
|
17
17
|
* every key taken after it: the seed a render filed and the `global` entry standing were both filed
|
|
18
18
|
* under them (11.29). 19.9 — THE WIRE HELD TO THE ANNOTATION, where no `args` schema is declared to
|
|
19
|
-
* hold it
|
|
20
|
-
* handler's args are a required record, so a required member missing is refused, not read off nothing.
|
|
19
|
+
* hold it — the check the handler carries (`derivedArgs`), which its in-process call makes too (15.8).
|
|
21
20
|
*/
|
|
22
21
|
export function argsFor(
|
|
23
|
-
found: Pick<Mounted, 'handler' | 'argsDocument'
|
|
22
|
+
found: Pick<Mounted, 'handler' | 'argsDocument'>,
|
|
24
23
|
sent: unknown,
|
|
25
24
|
query: string | undefined,
|
|
26
25
|
): unknown {
|
|
@@ -33,9 +32,7 @@ export function argsFor(
|
|
|
33
32
|
query,
|
|
34
33
|
)
|
|
35
34
|
const args = normalized(found.handler, coerced)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (issues !== null) throw validationError(issues)
|
|
39
|
-
}
|
|
35
|
+
const issues = found.handler[DERIVED_ARGS]?.(args) ?? null
|
|
36
|
+
if (issues !== null) throw validationError(issues)
|
|
40
37
|
return args
|
|
41
38
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// 41.28 — A CHILD WHOSE SETUP MADE A CALL IS BUILT AS A PAGE IS: after the turn
|
|
1
|
+
// 41.28 — A CHILD WHOSE SETUP MADE A CALL IS BUILT AS A PAGE IS: after the turn — and so is one whose
|
|
2
|
+
// markup reads a bare `busy` or `ready`, which the compiler writes to answer as a page does always.
|
|
2
3
|
// A component is set up where its parent's markup invokes it, so its build cannot wait inline; it is
|
|
3
4
|
// left as a marker in the parent's head and body, built once the turn has run, and written in where
|
|
4
5
|
// its marker stands. A child whose setup made no call builds where it stands, as before, and pays a
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DERIVED_ARGS } from '#shared/DERIVED_ARGS.ts'
|
|
2
|
+
import type { JsonValue } from '#shared/JsonValue.ts'
|
|
3
|
+
import type { Gated } from '#shared/reactive/argsContract.ts'
|
|
4
|
+
import type { HeldToAnnotation } from '#shared/rpc.ts'
|
|
5
|
+
import { validateJson } from '#shared/validateJson.ts'
|
|
6
|
+
import { argsDocumentOf } from './coerceArgs.ts'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Holds `handler`'s args to `schema`, the one the build derived off its annotation, for every call
|
|
10
|
+
* of it from now on: in process and over the wire (15.8, 19.9).
|
|
11
|
+
*
|
|
12
|
+
* What the module a handler is loaded through calls once its handler is built — a handler file's
|
|
13
|
+
* face and a `<script server>`'s module — so the schema is there wherever the handler can be
|
|
14
|
+
* called, whether or not an app mounted it. Not where its `args` gate publishes a document of its
|
|
15
|
+
* own, which holds the args itself (19.7). Args that did not come are held as an empty record: a
|
|
16
|
+
* schema is only derived where the handler's args are a required record, so a required member
|
|
17
|
+
* missing is refused, not read off nothing.
|
|
18
|
+
*/
|
|
19
|
+
export function derivedArgs(
|
|
20
|
+
handler: HeldToAnnotation & Gated<unknown>,
|
|
21
|
+
schema: JsonValue,
|
|
22
|
+
): void {
|
|
23
|
+
if (argsDocumentOf(handler) !== undefined) return
|
|
24
|
+
handler[DERIVED_ARGS] = (args) => validateJson(schema, args ?? {})
|
|
25
|
+
}
|
package/src/server/faceOf.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// adapter that hands bun's hooks to it, because bun decides which hook asks (an `onResolve` for a
|
|
4
4
|
// specifier, an `onLoad` for a path), and this decides what the answer is.
|
|
5
5
|
|
|
6
|
+
import type { JsonValue } from '#shared/JsonValue.ts'
|
|
6
7
|
import { HANDLER_SUFFIX, SERVER_QUERY } from './MODULE_QUERIES.ts'
|
|
7
8
|
|
|
8
9
|
// A VIRTUAL FACE'S PATH: its file's, with a suffix that is not a query. Both registries key a module
|
|
@@ -45,8 +46,15 @@ export type Asked =
|
|
|
45
46
|
| 'abide'
|
|
46
47
|
| 'handler'
|
|
47
48
|
|
|
48
|
-
/**
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* One handler file, as `Compilation.handlers` holds it — and 19.9's schema its default's annotation
|
|
51
|
+
* derives, which the module at the file's path holds it to (`derivedArgs`).
|
|
52
|
+
*/
|
|
53
|
+
export type HandlerFace = {
|
|
54
|
+
scope: string
|
|
55
|
+
client: string
|
|
56
|
+
args: JsonValue | undefined
|
|
57
|
+
}
|
|
50
58
|
|
|
51
59
|
/**
|
|
52
60
|
* The face an import of `file` is answered with, asked `asked` from `importer` in `lane` — or
|
package/src/server/index.ts
CHANGED
|
@@ -52,6 +52,9 @@ export { type App, createApp } from './createApp.ts'
|
|
|
52
52
|
// 41.28's run-time half: an emitted component reads it on either side of its setup, and one whose
|
|
53
53
|
// setup made a call answers as a page does. Not an app's name.
|
|
54
54
|
export { callsInRender } from './deferredBuilds.ts'
|
|
55
|
+
// 19.9's run-time half: what the module a handler is loaded through calls to hold it to the schema
|
|
56
|
+
// its annotation derives (15.8). Not an app's name.
|
|
57
|
+
export { derivedArgs } from './derivedArgs.ts'
|
|
55
58
|
// 37.1's run-time half: what a compiled server module calls around its `<script server>`, so the
|
|
56
59
|
// hooks it makes are filed at its route.
|
|
57
60
|
export {
|
|
@@ -30,8 +30,9 @@ export function renderComponent(
|
|
|
30
30
|
const rendered = (
|
|
31
31
|
component as (props: Record<string, () => unknown>) => Rendered
|
|
32
32
|
)(props)
|
|
33
|
-
// 41.28 — A CHILD WHOSE SETUP MADE A CALL
|
|
34
|
-
// left for the first read, and is built after the turn where a
|
|
33
|
+
// 41.28 — A CHILD WHOSE SETUP MADE A CALL, OR WHOSE MARKUP READS A BARE PROBE, ANSWERS AS A PAGE
|
|
34
|
+
// DOES, with a `scope` and its build left for the first read, and is built after the turn where a
|
|
35
|
+
// page is being built.
|
|
35
36
|
if ('scope' in rendered) {
|
|
36
37
|
const marker = deferBuild(rendered, heads, callsFrom)
|
|
37
38
|
if (marker !== undefined) return marker
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why the bun at `version` cannot run abide, which asks for `range` — or `undefined` where it can.
|
|
3
|
+
*
|
|
4
|
+
* 38.123 — named in full, both versions and the way out, since an older bun fails later and
|
|
5
|
+
* somewhere unrelated, if at all.
|
|
6
|
+
*/
|
|
7
|
+
export function runtimeRefusal(
|
|
8
|
+
version: string,
|
|
9
|
+
range: string,
|
|
10
|
+
): string | undefined {
|
|
11
|
+
if (Bun.semver.satisfies(version, range)) return undefined
|
|
12
|
+
return `this is bun ${version}, and abide needs bun ${range}. Run \`bun upgrade\`, or install a bun in that range. RULEBOOK 38.123.`
|
|
13
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The member of an `Rpc` holding its args to the schema its annotation derives (19.9): stamped by the
|
|
3
|
+
* module its handler is loaded through, on the server alone, and read by every call of it — in
|
|
4
|
+
* process and over the wire alike (15.8).
|
|
5
|
+
*/
|
|
6
|
+
export const DERIVED_ARGS: unique symbol = Symbol.for('abide.derivedArgs')
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
import { contractOf, type Gated } from './argsContract.ts'
|
|
16
16
|
import { CELL } from './CELL.ts'
|
|
17
17
|
import { KEYED_BRAND } from './KEYED_BRAND.ts'
|
|
18
|
-
import { brandedPrototype, MEMBER_KEYS } from './Reactive.ts'
|
|
18
|
+
import { brandedPrototype, MEMBER_KEYS, signalMoved } from './Reactive.ts'
|
|
19
19
|
import { SELECTION_MEMBERS } from './SELECTION_MEMBERS.ts'
|
|
20
20
|
import { currentScope, runInScope, SCOPING } from './scoping.ts'
|
|
21
|
+
import { newTracked, TRACKING, type Tracked, trackProbe } from './tracking.ts'
|
|
21
22
|
|
|
22
23
|
const UNBUILT: unique symbol = Symbol('abide.unbuilt')
|
|
23
24
|
|
|
@@ -36,11 +37,18 @@ class Filed {
|
|
|
36
37
|
home: unknown
|
|
37
38
|
make: () => unknown
|
|
38
39
|
global: boolean
|
|
40
|
+
/**
|
|
41
|
+
* 3.3, 13.19 — what a probe asked before the value was built here subscribes to, moved by the
|
|
42
|
+
* build: the home's. A scope files its own in `instances` under this one as its key, and drops
|
|
43
|
+
* it with the scope.
|
|
44
|
+
*/
|
|
45
|
+
unbuilt: Tracked
|
|
39
46
|
|
|
40
47
|
constructor(make: () => unknown, lazy: boolean, global: boolean) {
|
|
41
48
|
this.make = make
|
|
42
49
|
this.home = lazy ? UNBUILT : make()
|
|
43
50
|
this.global = global
|
|
51
|
+
this.unbuilt = newTracked()
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
54
|
|
|
@@ -56,14 +64,21 @@ type Forwarder = { [FILED]: Filed }
|
|
|
56
64
|
function instanceOf(filed: Filed): Record<PropertyKey, unknown> {
|
|
57
65
|
const scope = filed.global ? undefined : currentScope()
|
|
58
66
|
if (scope === undefined) {
|
|
59
|
-
if (filed.home === UNBUILT)
|
|
67
|
+
if (filed.home === UNBUILT) {
|
|
60
68
|
filed.home = runInScope(undefined, () => filed.make(), undefined)
|
|
69
|
+
signalMoved(filed.unbuilt)
|
|
70
|
+
}
|
|
61
71
|
return filed.home as Record<PropertyKey, unknown>
|
|
62
72
|
}
|
|
63
73
|
let instance = scope.instances.get(filed)
|
|
64
74
|
if (instance === undefined) {
|
|
65
75
|
instance = filed.make()
|
|
66
76
|
scope.instances.set(filed, instance)
|
|
77
|
+
const asked = scope.instances.get(filed.unbuilt) as Tracked | undefined
|
|
78
|
+
if (asked !== undefined) {
|
|
79
|
+
scope.instances.delete(filed.unbuilt)
|
|
80
|
+
signalMoved(asked)
|
|
81
|
+
}
|
|
67
82
|
}
|
|
68
83
|
return instance as Record<PropertyKey, unknown>
|
|
69
84
|
}
|
|
@@ -152,14 +167,33 @@ export function scoped(
|
|
|
152
167
|
/**
|
|
153
168
|
* Whether the value `value` forwards to is built for the scope that is current — always, for a
|
|
154
169
|
* value that is no forwarder. Asking builds nothing, where `instanceOf` builds what it finds unbuilt.
|
|
170
|
+
*
|
|
171
|
+
* 3.3 — AND WHERE IT IS NOT, ASKING SUBSCRIBES THE READER TO ITS BUILD: a guard that probed before
|
|
172
|
+
* the first call has no cell to hear the call start from, so `disabled={save.busy()}` stayed
|
|
173
|
+
* enabled through the whole write. One signal per value per scope, never one per entry (13.3).
|
|
155
174
|
*/
|
|
156
175
|
export function builtHere(value: unknown): boolean {
|
|
157
176
|
const filed = (value as Partial<Forwarder> | null)?.[FILED]
|
|
158
177
|
if (filed === undefined) return true
|
|
159
178
|
const scope = filed.global ? undefined : currentScope()
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
179
|
+
if (
|
|
180
|
+
scope === undefined
|
|
181
|
+
? filed.home !== UNBUILT
|
|
182
|
+
: scope.instances.has(filed)
|
|
183
|
+
)
|
|
184
|
+
return true
|
|
185
|
+
const reader = TRACKING.reader
|
|
186
|
+
if (reader === null) return false
|
|
187
|
+
let asked = filed.unbuilt
|
|
188
|
+
if (scope !== undefined) {
|
|
189
|
+
asked = scope.instances.get(filed.unbuilt) as Tracked
|
|
190
|
+
if (asked === undefined) {
|
|
191
|
+
asked = newTracked()
|
|
192
|
+
scope.instances.set(filed.unbuilt, asked)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
trackProbe(asked, reader)
|
|
196
|
+
return false
|
|
163
197
|
}
|
|
164
198
|
|
|
165
199
|
/**
|
package/src/shared/rpc.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// the call hands back a value whose body runs the onion once per args, per request, over the
|
|
14
14
|
// handler's own.
|
|
15
15
|
|
|
16
|
+
import { DERIVED_ARGS } from './DERIVED_ARGS.ts'
|
|
16
17
|
import { type Failed, isFailedNamed } from './Failed.ts'
|
|
17
18
|
import { GLOBAL_GETS } from './GLOBAL_GETS.ts'
|
|
18
19
|
import { HandlerRoute } from './HandlerRoute.ts'
|
|
@@ -52,7 +53,7 @@ import { MUTATION } from './reactive/MUTATION.ts'
|
|
|
52
53
|
import { dropAnswered } from './reactive/Reactive.ts'
|
|
53
54
|
import { SELECTION_MEMBERS } from './reactive/SELECTION_MEMBERS.ts'
|
|
54
55
|
import { builtHere, isForwarder } from './reactive/scoped.ts'
|
|
55
|
-
import { checkArgs } from './refuse.ts'
|
|
56
|
+
import { checkArgs, validationError } from './refuse.ts'
|
|
56
57
|
import { rpcRequest } from './rpcRequest.ts'
|
|
57
58
|
import type { Schema } from './Schema.ts'
|
|
58
59
|
|
|
@@ -162,6 +163,16 @@ type Derived<Member, Whole = Member> = Member extends
|
|
|
162
163
|
: never
|
|
163
164
|
: never
|
|
164
165
|
|
|
166
|
+
/**
|
|
167
|
+
* 19.9 — the issues an `Rpc`'s args leave against the schema its annotation derives, `null` where
|
|
168
|
+
* they hold: `undefined` until the module its handler is loaded through stamps it (`derivedArgs`),
|
|
169
|
+
* and so in a browser always. A function rather than the schema, so this seam never carries the
|
|
170
|
+
* validator (19.13).
|
|
171
|
+
*/
|
|
172
|
+
export type HeldToAnnotation = {
|
|
173
|
+
[DERIVED_ARGS]: ((args: unknown) => Issues<unknown> | null) | undefined
|
|
174
|
+
}
|
|
175
|
+
|
|
165
176
|
/** Which generated surfaces carry a handler (20.1). */
|
|
166
177
|
export type Clients = {
|
|
167
178
|
ui?: boolean
|
|
@@ -440,6 +451,11 @@ export function declare(method: Method): Declare {
|
|
|
440
451
|
}
|
|
441
452
|
const call = (given?: unknown, options?: CallOptions): unknown => {
|
|
442
453
|
const args = normalized(contract, given)
|
|
454
|
+
// 15.8, 19.9 — AND HELD TO ITS ANNOTATION, where the build derived a schema off it: a
|
|
455
|
+
// type an in-process caller wrote holds nothing at run time (`as never`, a value off
|
|
456
|
+
// JSON), so the call is refused as the wire refuses it rather than handed to the body.
|
|
457
|
+
const issues = rpc[DERIVED_ARGS]?.(args) ?? null
|
|
458
|
+
if (issues !== null) return reactive(() => validationError(issues))
|
|
443
459
|
const route =
|
|
444
460
|
rpc.address === undefined ? undefined : routeAt(rpc.address)
|
|
445
461
|
if (route?.segments !== undefined) {
|
|
@@ -469,7 +485,7 @@ export function declare(method: Method): Declare {
|
|
|
469
485
|
// 13.19 — AND NOT YET BUILT, NONE OF THEM BUILDS IT: building an unkeyed value starts its body
|
|
470
486
|
// (1.7), so `save.busy()` over a server's never-called `POST(async () => …)` ran the write and
|
|
471
487
|
// reported it in flight. Until a call reaches it the handler has no entry, and answers as one
|
|
472
|
-
// with no entries does.
|
|
488
|
+
// with no entries does — the asking reader subscribed to the call that builds it (`builtHere`).
|
|
473
489
|
const built = value as Record<
|
|
474
490
|
(typeof SELECTION_MEMBERS)[number],
|
|
475
491
|
(pattern?: unknown) => unknown
|
|
@@ -487,6 +503,7 @@ export function declare(method: Method): Declare {
|
|
|
487
503
|
maxBodySize: options?.maxBodySize,
|
|
488
504
|
clients: options?.clients ?? {},
|
|
489
505
|
guarded: rungs.length > 0,
|
|
506
|
+
[DERIVED_ARGS]: undefined as HeldToAnnotation[typeof DERIVED_ARGS],
|
|
490
507
|
isError: isFailedNamed,
|
|
491
508
|
url(args?: Record<string, unknown>): string {
|
|
492
509
|
return rpcRequest(method, routeAt(addressOf(rpc)), args).url
|