@abide/abide 0.116.2 → 0.116.3
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 +12 -0
- package/docs/REGISTRY.md +17 -17
- package/docs/RULEBOOK.md +18 -3
- package/docs/abide.md +17 -17
- package/package.json +1 -1
- package/src/compiler/Lowering.ts +39 -10
- package/src/compiler/emitClient.ts +6 -7
- package/src/compiler/handlersUnder.ts +1 -1
- package/src/compiler/projectShadow.ts +1 -3
- package/src/server/MODULE_QUERIES.ts +6 -0
- package/src/server/PRIVATE_EXPORT.ts +5 -0
- package/src/server/abideModules.ts +35 -9
- package/src/server/faceOf.ts +12 -3
- package/src/shared/reactive/Reactive.ts +12 -7
- package/src/shared/reactive/scoped.ts +13 -0
- package/src/shared/rpc.ts +59 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @abide/abide
|
|
2
2
|
|
|
3
|
+
## 0.116.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9007822: A `bind:` writes its value before an `on<event>` on the same element runs — `<input type="file" bind:files={picked} onchange={use}>` hands `use` the files just picked, and an `oninput` beside `bind:value` reads the text just typed, wherever the two are written in the tag (RULEBOOK 32.75).
|
|
8
|
+
- ecf1ff0: a `bind:` through a computed key writes back — `bind:value={params[key]}` and `bind:value={params[s.k]}` inside a `{#for}` write the entry the key names when the input fires, and follow the key when it changes, where they were handed the read and never wrote; a `bind:` through an optional step (`params?.a`) is now a build error (RULEBOOK 32.75) rather than a read that writes nothing
|
|
9
|
+
- ee8cce7: a handler with no `args` gate whose parameter is one record now carries the `ValidationError` the wire refuses its args with — `rpc.isError(e, 'ValidationError')` narrows to it and `e.data` is `Issues<Args>`, where it narrowed to `never`. A handler with no args, a scalar, a list, a union of records or `unknown` derives no schema and still carries none
|
|
10
|
+
- fe68a88: a handler no call has reached is not busy, and asking runs nothing — on a server, `save.busy()` over a never-called `POST(async () => …)` ran the write and reported it in flight; it now answers as a handler with no entries (`busy` false, `ready` and `synced` true), for a `GET` with no args as well. A probe over a mutation a `refresh()` invalidated no longer sends the write again (RULEBOOK 3.14, 13.19)
|
|
11
|
+
- 874c919: under `mount` in `bun test`, a dynamic `import('#src/…')` from a component's browser module resolves as the static one does, rather than failing with "Cannot find package '#src'"
|
|
12
|
+
- f60ea39: under `mount` in `bun test`, a component's browser module calling a private handler (one its `<script server>` declares) is answered by that handler in the test's process, rather than fetching an `/abide-client:/…` address and failing with 503 — as a handler file's call already was
|
|
13
|
+
- 3e999ce: a throttled value lets its first change through at once — `reactive(x, { throttle })` then `s.set(v)` reads `v` back in the same turn, where the value it was built with had opened a window that held the first `set` back. RULEBOOK 5.12
|
|
14
|
+
|
|
3
15
|
## 0.116.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/docs/REGISTRY.md
CHANGED
|
@@ -125,10 +125,10 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
125
125
|
|
|
126
126
|
| Name | Signature | Meaning | Rules |
|
|
127
127
|
| --- | --- | --- | --- |
|
|
128
|
-
| `s.busy` | `() => boolean` | Work is in flight for this value. | 3.1, 3.2, 3.3, 3.4, 3.7, 3.9, 5.15, 7.5, 7.8, 11.19, 11.20, 11.21, 11.22, 11.50 |
|
|
129
|
-
| `s.ready` | `() => boolean` | There is a value to serve. | 3.1, 3.2, 3.3, 3.5, 3.6, 3.8, 7.8, 11.20, 11.54, 11.58 |
|
|
130
|
-
| `s.error` | `() => Failures \| undefined` | The standing refusal. | 3.1, 3.2, 3.3, 3.5, 4.10, 4.11, 4.12 |
|
|
131
|
-
| `s.synced` | `() => boolean` | What is held is what the producer made. | 3.1, 3.2, 3.3, 3.10, 3.11, 3.12, 3.13 |
|
|
128
|
+
| `s.busy` | `() => boolean` | Work is in flight for this value. | 3.1, 3.2, 3.3, 3.4, 3.7, 3.9, 3.14, 5.15, 7.5, 7.8, 11.19, 11.20, 11.21, 11.22, 11.50 |
|
|
129
|
+
| `s.ready` | `() => boolean` | There is a value to serve. | 3.1, 3.2, 3.3, 3.5, 3.6, 3.8, 3.14, 7.8, 11.20, 11.54, 11.58 |
|
|
130
|
+
| `s.error` | `() => Failures \| undefined` | The standing refusal. | 3.1, 3.2, 3.3, 3.5, 3.14, 4.10, 4.11, 4.12 |
|
|
131
|
+
| `s.synced` | `() => boolean` | What is held is what the producer made. | 3.1, 3.2, 3.3, 3.10, 3.11, 3.12, 3.13, 3.14 |
|
|
132
132
|
| `s.isError` | `<Name extends Failures['name']>(error: unknown, name: Name) => error is Extract<Failures, { name: Name }>` | Whether a caught failure is the one named. | 3.1 |
|
|
133
133
|
|
|
134
134
|
### Triggers
|
|
@@ -223,11 +223,11 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
223
223
|
| `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 `ReactiveOptions` (`ttl`, `global`, `debounce` and the rest) build the value it becomes beside its address. | 16.1, 16.6, 16.7 |
|
|
224
224
|
| `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, whose `ReactiveOptions` build the value it becomes. | 16.1, 16.6, 16.7 |
|
|
225
225
|
| `POST` / `PUT` / `PATCH` / `DELETE` | same as `GET` | Declares a mutation. | 16.2, 16.7, 16.32, 16.68, 16.76, 16.38, 16.53 |
|
|
226
|
-
| `rpc.busy` | `(pattern?: Partial<Args>) => boolean` | `busy` narrowed to the handler's matching entries. | 13.2, 13.3, 13.12, 46.11 |
|
|
227
|
-
| `rpc.ready` | `(pattern?: Partial<Args>) => boolean` | `ready` narrowed to the handler's matching entries. | 13.11, 13.12 |
|
|
228
|
-
| `rpc.synced` | `(pattern?: Partial<Args>) => boolean` | `synced` narrowed to the handler's matching entries. | 13.12, 13.18 |
|
|
229
|
-
| `rpc.invalidate` | `(pattern?: Partial<Args>) => void` | `invalidate` narrowed to the handler's matching entries. | 7.2, 7.3, 13.4, 13.12 |
|
|
230
|
-
| `rpc.refresh` | `(pattern?: Partial<Args>) => void` | `refresh` narrowed to the handler's matching entries. | 7.4, 7.5, 13.4, 13.12 |
|
|
226
|
+
| `rpc.busy` | `(pattern?: Partial<Args>) => boolean` | `busy` narrowed to the handler's matching entries. | 13.2, 13.3, 13.12, 13.19, 46.11 |
|
|
227
|
+
| `rpc.ready` | `(pattern?: Partial<Args>) => boolean` | `ready` narrowed to the handler's matching entries. | 13.11, 13.12, 13.19 |
|
|
228
|
+
| `rpc.synced` | `(pattern?: Partial<Args>) => boolean` | `synced` narrowed to the handler's matching entries. | 13.12, 13.18, 13.19 |
|
|
229
|
+
| `rpc.invalidate` | `(pattern?: Partial<Args>) => void` | `invalidate` narrowed to the handler's matching entries. | 7.2, 7.3, 13.4, 13.12, 13.19 |
|
|
230
|
+
| `rpc.refresh` | `(pattern?: Partial<Args>) => void` | `refresh` narrowed to the handler's matching entries. | 7.4, 7.5, 13.4, 13.12, 13.19 |
|
|
231
231
|
| `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 |
|
|
232
232
|
| `rpc.raw` | `(args: Args, init?: RequestInit) => Promise<Response>` | The same call handed back as the raw response. | 16.11, 16.42 |
|
|
233
233
|
| `rpc.method` | `string` | The HTTP method. | 16.2 |
|
|
@@ -552,14 +552,14 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
552
552
|
| formatting whitespace | What a run of whitespace renders as. | 33.17, 33.18 |
|
|
553
553
|
| `{raw(...)}` | Raw HTML. | 32.2, 32.25, 32.26, 32.30, 32.31, 32.70 |
|
|
554
554
|
| `name={expr}` | A reactive attribute or property. | 32.3, 32.20, 32.23, 32.37, 32.70, 32.71 |
|
|
555
|
-
| `on<event>={fn}` | A native listener on an element, and an ordinary prop on a component. | 32.4 |
|
|
555
|
+
| `on<event>={fn}` | A native listener on an element, and an ordinary prop on a component. | 32.4, 32.75 |
|
|
556
556
|
| `name="…{expr}…"` | An interpolated quoted value. | 32.3, 32.71 |
|
|
557
|
-
| `bind:value` | Two-way bind on an element. | 5.16, 32.5, 32.58, 32.59, 32.60, 32.61, 32.62, 32.72, 32.74 |
|
|
558
|
-
| `bind:prop={state}` | Hands a component prop the caller's `Reactive`, or a `{get, set}` pair, to write through. | 32.6, 33.6, 33.8, 14.18 |
|
|
559
|
-
| `bind:checked` | A boolean bind on an input. | 32.7, 32.72, 32.74 |
|
|
560
|
-
| `bind:open` | The same, on a `<details>`. | 32.7, 32.72, 32.74 |
|
|
561
|
-
| `bind:group` | Radio or checkbox membership. | 32.8, 32.72, 32.74 |
|
|
562
|
-
| `bind:files` | A file input's files, as a list. | 32.73, 32.72, 32.74 |
|
|
557
|
+
| `bind:value` | Two-way bind on an element. | 5.16, 32.5, 32.58, 32.59, 32.60, 32.61, 32.62, 32.72, 32.74, 32.75, 32.76 |
|
|
558
|
+
| `bind:prop={state}` | Hands a component prop the caller's `Reactive`, or a `{get, set}` pair, to write through. | 32.6, 33.6, 33.8, 14.18, 32.76 |
|
|
559
|
+
| `bind:checked` | A boolean bind on an input. | 32.7, 32.72, 32.74, 32.75, 32.76 |
|
|
560
|
+
| `bind:open` | The same, on a `<details>`. | 32.7, 32.72, 32.74, 32.75, 32.76 |
|
|
561
|
+
| `bind:group` | Radio or checkbox membership. | 32.8, 32.72, 32.74, 32.75, 32.76 |
|
|
562
|
+
| `bind:files` | A file input's files, as a list. | 32.73, 32.72, 32.74, 32.75, 32.76 |
|
|
563
563
|
| `bind:value={{get, set}}` | Two-way bind over an explicit accessor pair, on an element or a component. | 32.5, 32.6 |
|
|
564
564
|
| `bind:element={Reactive<Element> \| ((element: Element) => void \| Disposer)}` | A node reference. | 32.9, 32.38, 32.66, 32.68, 32.69 |
|
|
565
565
|
| `class:name={cond}` | Toggles a class. | 32.10, 32.71 |
|
|
@@ -738,7 +738,7 @@ type Gated<Gate, Accepted> = [Schema<Accepted>] extends [Gate] ? never : Failed<
|
|
|
738
738
|
|
|
739
739
|
| Name | Signature | Meaning | Rules |
|
|
740
740
|
| --- | --- | --- | --- |
|
|
741
|
-
| `mount` | `(component: Component) => Promise<Mounted>` | A bound component, rendered into the document's body and hydrated there. | 38.119 |
|
|
741
|
+
| `mount` | `(component: Component) => Promise<Mounted>` | A bound component, rendered into the document's body and hydrated there. | 38.119, 38.121 |
|
|
742
742
|
| `Mounted` | `{ stop(): void; [Symbol.dispose](): void }` | A component `mount` hydrated, until it is stopped. | 38.119 |
|
|
743
743
|
|
|
744
744
|
# The measurement harness
|
package/docs/RULEBOOK.md
CHANGED
|
@@ -211,6 +211,8 @@ subscribe it to the value.
|
|
|
211
211
|
|
|
212
212
|
3.13 A value MUST report `s.synced` false where a value it read or adopts does.
|
|
213
213
|
|
|
214
|
+
3.14 A probe over a value a mutation's `declare` built MUST NOT run its body.
|
|
215
|
+
|
|
214
216
|
# 4. Writes and gates
|
|
215
217
|
|
|
216
218
|
4.1 `s.set` MUST accept a settled value or a load, and `reactive` MUST accept the same two.
|
|
@@ -751,6 +753,9 @@ mutation's `declare` built while its write is in flight.
|
|
|
751
753
|
13.18 `synced` over a selection MUST be true where every entry in it is synced, and a bare `synced`
|
|
752
754
|
where every value it sweeps is.
|
|
753
755
|
|
|
756
|
+
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.
|
|
758
|
+
|
|
754
759
|
# 14. Tracking
|
|
755
760
|
|
|
756
761
|
14.1 A `Reactive` MUST be tracked only where something downstream will be rebuilt from it.
|
|
@@ -885,8 +890,9 @@ alone.
|
|
|
885
890
|
options declare one.
|
|
886
891
|
|
|
887
892
|
16.8 `Failures` on an `Rpc` MUST be the union of what the handler declared, what its rungs
|
|
888
|
-
declared,
|
|
889
|
-
may answer any call with.
|
|
893
|
+
declared, the undeclared `Failed` named `HttpError` per 17.4, with no data, which the transport
|
|
894
|
+
may answer any call with, and, wherever the `Schema` 19.9 derives from its `Args` is an object's,
|
|
895
|
+
the undeclared `ValidationError` of 15.8 with `Issues` of the `Args` as its data.
|
|
890
896
|
|
|
891
897
|
16.9 `rpc.url` MUST take the `Args`, MUST write each parameter of its route into the path, and MUST
|
|
892
898
|
carry the rest in the query on a `GET` or a `DELETE` alone.
|
|
@@ -1870,7 +1876,7 @@ as a name bound to a `Reactive` is, per 31.1 to 31.7.
|
|
|
1870
1876
|
component.
|
|
1871
1877
|
|
|
1872
1878
|
32.5 `bind:value` MUST read the property and write it back on input or change, through the member
|
|
1873
|
-
path where its operand is a path into a value (31.8).
|
|
1879
|
+
path where its operand is a path into a value (31.8), a computed step's key read at each write.
|
|
1874
1880
|
|
|
1875
1881
|
32.6 `bind:prop` on a component MUST hand the child the caller's `Reactive`, or its `{ get, set }` pair,
|
|
1876
1882
|
as that prop's write path.
|
|
@@ -2086,6 +2092,12 @@ nothing for it.
|
|
|
2086
2092
|
32.74 A `bind:` on an element naming none of `value`, `checked`, `open`, `group`, `files` and
|
|
2087
2093
|
`element` MUST be a build error.
|
|
2088
2094
|
|
|
2095
|
+
32.75 A `bind:` on an element MUST write its operand before any `on<event>` listener on that
|
|
2096
|
+
element, written or spread, runs for the same event, wherever the two are written in the tag.
|
|
2097
|
+
|
|
2098
|
+
32.76 A `bind:` whose operand is a path into a value that takes an optional step (`?.`) MUST be a
|
|
2099
|
+
build error.
|
|
2100
|
+
|
|
2089
2101
|
# 33. Props
|
|
2090
2102
|
|
|
2091
2103
|
33.1 `props` MUST be callable from a `<script>` block only, and calling it from a `<script module>`
|
|
@@ -2750,6 +2762,9 @@ hydration woke before it settles; it MUST throw where the component was not impo
|
|
|
2750
2762
|
`ABIDE_PRINCIPAL_SECRET` MUST seal and verify the principal cookie with a secret the preload
|
|
2751
2763
|
supplies, and `abide start` MUST NOT supply that secret.
|
|
2752
2764
|
|
|
2765
|
+
38.121 Under `mount`, a call the component's browser module makes to a private handler MUST be
|
|
2766
|
+
answered in the test's process by the handler the component's server module declares.
|
|
2767
|
+
|
|
2753
2768
|
# 39. Head and view transitions
|
|
2754
2769
|
|
|
2755
2770
|
39.1 A `<head>` element in a `.abide` file MUST contribute its children to the document head.
|
package/docs/abide.md
CHANGED
|
@@ -6856,10 +6856,10 @@ Arrive here knowing which name you want. Each page is read off the registry: the
|
|
|
6856
6856
|
|
|
6857
6857
|
| Name | Signature | Meaning | Rules |
|
|
6858
6858
|
| --- | --- | --- | --- |
|
|
6859
|
-
| `s.busy` | `() => boolean` | Work is in flight for this value. | [3.1](/rulebook#3.1), [3.2](/rulebook#3.2), [3.3](/rulebook#3.3), [3.4](/rulebook#3.4), [3.7](/rulebook#3.7), [3.9](/rulebook#3.9), [5.15](/rulebook#5.15), [7.5](/rulebook#7.5), [7.8](/rulebook#7.8), [11.19](/rulebook#11.19), [11.20](/rulebook#11.20), [11.21](/rulebook#11.21), [11.22](/rulebook#11.22), [11.50](/rulebook#11.50) |
|
|
6860
|
-
| `s.ready` | `() => boolean` | There is a value to serve. | [3.1](/rulebook#3.1), [3.2](/rulebook#3.2), [3.3](/rulebook#3.3), [3.5](/rulebook#3.5), [3.6](/rulebook#3.6), [3.8](/rulebook#3.8), [7.8](/rulebook#7.8), [11.20](/rulebook#11.20), [11.54](/rulebook#11.54), [11.58](/rulebook#11.58) |
|
|
6861
|
-
| `s.error` | `() => Failures \| undefined` | The standing refusal. | [3.1](/rulebook#3.1), [3.2](/rulebook#3.2), [3.3](/rulebook#3.3), [3.5](/rulebook#3.5), [4.10](/rulebook#4.10), [4.11](/rulebook#4.11), [4.12](/rulebook#4.12) |
|
|
6862
|
-
| `s.synced` | `() => boolean` | What is held is what the producer made. | [3.1](/rulebook#3.1), [3.2](/rulebook#3.2), [3.3](/rulebook#3.3), [3.10](/rulebook#3.10), [3.11](/rulebook#3.11), [3.12](/rulebook#3.12), [3.13](/rulebook#3.13) |
|
|
6859
|
+
| `s.busy` | `() => boolean` | Work is in flight for this value. | [3.1](/rulebook#3.1), [3.2](/rulebook#3.2), [3.3](/rulebook#3.3), [3.4](/rulebook#3.4), [3.7](/rulebook#3.7), [3.9](/rulebook#3.9), [3.14](/rulebook#3.14), [5.15](/rulebook#5.15), [7.5](/rulebook#7.5), [7.8](/rulebook#7.8), [11.19](/rulebook#11.19), [11.20](/rulebook#11.20), [11.21](/rulebook#11.21), [11.22](/rulebook#11.22), [11.50](/rulebook#11.50) |
|
|
6860
|
+
| `s.ready` | `() => boolean` | There is a value to serve. | [3.1](/rulebook#3.1), [3.2](/rulebook#3.2), [3.3](/rulebook#3.3), [3.5](/rulebook#3.5), [3.6](/rulebook#3.6), [3.8](/rulebook#3.8), [3.14](/rulebook#3.14), [7.8](/rulebook#7.8), [11.20](/rulebook#11.20), [11.54](/rulebook#11.54), [11.58](/rulebook#11.58) |
|
|
6861
|
+
| `s.error` | `() => Failures \| undefined` | The standing refusal. | [3.1](/rulebook#3.1), [3.2](/rulebook#3.2), [3.3](/rulebook#3.3), [3.5](/rulebook#3.5), [3.14](/rulebook#3.14), [4.10](/rulebook#4.10), [4.11](/rulebook#4.11), [4.12](/rulebook#4.12) |
|
|
6862
|
+
| `s.synced` | `() => boolean` | What is held is what the producer made. | [3.1](/rulebook#3.1), [3.2](/rulebook#3.2), [3.3](/rulebook#3.3), [3.10](/rulebook#3.10), [3.11](/rulebook#3.11), [3.12](/rulebook#3.12), [3.13](/rulebook#3.13), [3.14](/rulebook#3.14) |
|
|
6863
6863
|
| `s.isError` | `<Name extends Failures['name']>(error: unknown, name: Name) => error is Extract<Failures, { name: Name }>` | Whether a caught failure is the one named. | [3.1](/rulebook#3.1) |
|
|
6864
6864
|
|
|
6865
6865
|
## Triggers
|
|
@@ -7407,11 +7407,11 @@ Reactive primitives: what is written in place rather than imported.
|
|
|
7407
7407
|
| `Value` | `unknown` | What the addressed `Reactive` holds. | [16.3](/rulebook#16.3), [16.30](/rulebook#16.30) |
|
|
7408
7408
|
| `Produced` | `unknown` | What it yields, per the unit rule. | [1.2](/rulebook#1.2), [16.3](/rulebook#16.3) |
|
|
7409
7409
|
| `Args` | `Record<string, JsonValue> \| undefined` | The arguments, in the body on a mutation and in the URL on a `GET` or `DELETE`. | [11.24](/rulebook#11.24), [16.18](/rulebook#16.18), [19.15](/rulebook#19.15) |
|
|
7410
|
-
| `rpc.busy` | `(pattern?: Partial<Args>) => boolean` | `busy` narrowed to the handler's matching entries. | [13.2](/rulebook#13.2), [13.3](/rulebook#13.3), [13.12](/rulebook#13.12), [46.11](/rulebook#46.11) |
|
|
7411
|
-
| `rpc.ready` | `(pattern?: Partial<Args>) => boolean` | `ready` narrowed to the handler's matching entries. | [13.11](/rulebook#13.11), [13.12](/rulebook#13.12) |
|
|
7412
|
-
| `rpc.synced` | `(pattern?: Partial<Args>) => boolean` | `synced` narrowed to the handler's matching entries. | [13.12](/rulebook#13.12), [13.18](/rulebook#13.18) |
|
|
7413
|
-
| `rpc.invalidate` | `(pattern?: Partial<Args>) => void` | `invalidate` narrowed to the handler's matching entries. | [7.2](/rulebook#7.2), [7.3](/rulebook#7.3), [13.4](/rulebook#13.4), [13.12](/rulebook#13.12) |
|
|
7414
|
-
| `rpc.refresh` | `(pattern?: Partial<Args>) => void` | `refresh` narrowed to the handler's matching entries. | [7.4](/rulebook#7.4), [7.5](/rulebook#7.5), [13.4](/rulebook#13.4), [13.12](/rulebook#13.12) |
|
|
7410
|
+
| `rpc.busy` | `(pattern?: Partial<Args>) => boolean` | `busy` narrowed to the handler's matching entries. | [13.2](/rulebook#13.2), [13.3](/rulebook#13.3), [13.12](/rulebook#13.12), [13.19](/rulebook#13.19), [46.11](/rulebook#46.11) |
|
|
7411
|
+
| `rpc.ready` | `(pattern?: Partial<Args>) => boolean` | `ready` narrowed to the handler's matching entries. | [13.11](/rulebook#13.11), [13.12](/rulebook#13.12), [13.19](/rulebook#13.19) |
|
|
7412
|
+
| `rpc.synced` | `(pattern?: Partial<Args>) => boolean` | `synced` narrowed to the handler's matching entries. | [13.12](/rulebook#13.12), [13.18](/rulebook#13.18), [13.19](/rulebook#13.19) |
|
|
7413
|
+
| `rpc.invalidate` | `(pattern?: Partial<Args>) => void` | `invalidate` narrowed to the handler's matching entries. | [7.2](/rulebook#7.2), [7.3](/rulebook#7.3), [13.4](/rulebook#13.4), [13.12](/rulebook#13.12), [13.19](/rulebook#13.19) |
|
|
7414
|
+
| `rpc.refresh` | `(pattern?: Partial<Args>) => void` | `refresh` narrowed to the handler's matching entries. | [7.4](/rulebook#7.4), [7.5](/rulebook#7.5), [13.4](/rulebook#13.4), [13.12](/rulebook#13.12), [13.19](/rulebook#13.19) |
|
|
7415
7415
|
| `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](/rulebook#16.8) |
|
|
7416
7416
|
| `rpc.raw` | `(args: Args, init?: RequestInit) => Promise<Response>` | The same call handed back as the raw response. | [16.11](/rulebook#16.11), [16.42](/rulebook#16.42) |
|
|
7417
7417
|
| `rpc.method` | `string` | The HTTP method. | [16.2](/rulebook#16.2) |
|
|
@@ -8899,14 +8899,14 @@ Generated surfaces: what is written in place rather than imported.
|
|
|
8899
8899
|
| formatting whitespace | What a run of whitespace renders as. | [33.17](/rulebook#33.17), [33.18](/rulebook#33.18) |
|
|
8900
8900
|
| `{raw(...)}` | Raw HTML. | [32.2](/rulebook#32.2), [32.25](/rulebook#32.25), [32.26](/rulebook#32.26), [32.30](/rulebook#32.30), [32.31](/rulebook#32.31), [32.70](/rulebook#32.70) |
|
|
8901
8901
|
| `name={expr}` | A reactive attribute or property. | [32.3](/rulebook#32.3), [32.20](/rulebook#32.20), [32.23](/rulebook#32.23), [32.37](/rulebook#32.37), [32.70](/rulebook#32.70), [32.71](/rulebook#32.71) |
|
|
8902
|
-
| `on<event>={fn}` | A native listener on an element, and an ordinary prop on a component. | [32.4](/rulebook#32.4) |
|
|
8902
|
+
| `on<event>={fn}` | A native listener on an element, and an ordinary prop on a component. | [32.4](/rulebook#32.4), [32.75](/rulebook#32.75) |
|
|
8903
8903
|
| `name="…{expr}…"` | An interpolated quoted value. | [32.3](/rulebook#32.3), [32.71](/rulebook#32.71) |
|
|
8904
|
-
| `bind:value` | Two-way bind on an element. | [5.16](/rulebook#5.16), [32.5](/rulebook#32.5), [32.58](/rulebook#32.58), [32.59](/rulebook#32.59), [32.60](/rulebook#32.60), [32.61](/rulebook#32.61), [32.62](/rulebook#32.62), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74) |
|
|
8905
|
-
| `bind:prop={state}` | Hands a component prop the caller's `Reactive`, or a `{get, set}` pair, to write through. | [32.6](/rulebook#32.6), [33.6](/rulebook#33.6), [33.8](/rulebook#33.8), [14.18](/rulebook#14.18) |
|
|
8906
|
-
| `bind:checked` | A boolean bind on an input. | [32.7](/rulebook#32.7), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74) |
|
|
8907
|
-
| `bind:open` | The same, on a `<details>`. | [32.7](/rulebook#32.7), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74) |
|
|
8908
|
-
| `bind:group` | Radio or checkbox membership. | [32.8](/rulebook#32.8), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74) |
|
|
8909
|
-
| `bind:files` | A file input's files, as a list. | [32.73](/rulebook#32.73), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74) |
|
|
8904
|
+
| `bind:value` | Two-way bind on an element. | [5.16](/rulebook#5.16), [32.5](/rulebook#32.5), [32.58](/rulebook#32.58), [32.59](/rulebook#32.59), [32.60](/rulebook#32.60), [32.61](/rulebook#32.61), [32.62](/rulebook#32.62), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74), [32.75](/rulebook#32.75), [32.76](/rulebook#32.76) |
|
|
8905
|
+
| `bind:prop={state}` | Hands a component prop the caller's `Reactive`, or a `{get, set}` pair, to write through. | [32.6](/rulebook#32.6), [33.6](/rulebook#33.6), [33.8](/rulebook#33.8), [14.18](/rulebook#14.18), [32.76](/rulebook#32.76) |
|
|
8906
|
+
| `bind:checked` | A boolean bind on an input. | [32.7](/rulebook#32.7), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74), [32.75](/rulebook#32.75), [32.76](/rulebook#32.76) |
|
|
8907
|
+
| `bind:open` | The same, on a `<details>`. | [32.7](/rulebook#32.7), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74), [32.75](/rulebook#32.75), [32.76](/rulebook#32.76) |
|
|
8908
|
+
| `bind:group` | Radio or checkbox membership. | [32.8](/rulebook#32.8), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74), [32.75](/rulebook#32.75), [32.76](/rulebook#32.76) |
|
|
8909
|
+
| `bind:files` | A file input's files, as a list. | [32.73](/rulebook#32.73), [32.72](/rulebook#32.72), [32.74](/rulebook#32.74), [32.75](/rulebook#32.75), [32.76](/rulebook#32.76) |
|
|
8910
8910
|
| `bind:value={{get, set}}` | Two-way bind over an explicit accessor pair, on an element or a component. | [32.5](/rulebook#32.5), [32.6](/rulebook#32.6) |
|
|
8911
8911
|
| `bind:element={Reactive<Element> \| ((element: Element) => void \| Disposer)}` | A node reference. | [32.9](/rulebook#32.9), [32.38](/rulebook#32.38), [32.66](/rulebook#32.66), [32.68](/rulebook#32.68), [32.69](/rulebook#32.69) |
|
|
8912
8912
|
| `class:name={cond}` | Toggles a class. | [32.10](/rulebook#32.10), [32.71](/rulebook#32.71) |
|
|
@@ -9469,7 +9469,7 @@ Configuration: what is written in place rather than imported.
|
|
|
9469
9469
|
|
|
9470
9470
|
| Name | Signature | Meaning | Rules |
|
|
9471
9471
|
| --- | --- | --- | --- |
|
|
9472
|
-
| `mount` | `(component: Component) => Promise<Mounted>` | A bound component, rendered into the document's body and hydrated there. | [38.119](/rulebook#38.119) |
|
|
9472
|
+
| `mount` | `(component: Component) => Promise<Mounted>` | A bound component, rendered into the document's body and hydrated there. | [38.119](/rulebook#38.119), [38.121](/rulebook#38.121) |
|
|
9473
9473
|
|
|
9474
9474
|
## Taught in
|
|
9475
9475
|
|
package/package.json
CHANGED
package/src/compiler/Lowering.ts
CHANGED
|
@@ -372,17 +372,17 @@ export class Lowering {
|
|
|
372
372
|
* `contact.address.city = v` to for its set. Lowered as a read, the binding was handed the city
|
|
373
373
|
* as a string and had nothing to write the typed text back into.
|
|
374
374
|
*
|
|
375
|
-
* A
|
|
376
|
-
*
|
|
375
|
+
* A COMPUTED STEP IS EMITTED SOURCE, as 31.8's is: `params[s.k]` writes through `s.k` lowered,
|
|
376
|
+
* so the key is read when the input writes and the get reads it too — the binding follows the
|
|
377
|
+
* key. A `!` is a type and no step. `undefined` for an operand that is not a name followed by
|
|
378
|
+
* steps, which `reference` lowers as it stands — save 32.76's optional step, which is refused.
|
|
377
379
|
*/
|
|
378
380
|
private boundPath(span: Span): string | undefined {
|
|
379
|
-
if (this.checking) return undefined
|
|
380
381
|
const source = this.projection.text
|
|
381
382
|
const text = source.slice(span.start, span.end)
|
|
382
383
|
const from = span.start + (text.length - text.trimStart().length)
|
|
383
384
|
const to = span.start + text.trimEnd().length
|
|
384
|
-
const
|
|
385
|
-
const read = reads[this.answer.firstRead(from)]
|
|
385
|
+
const read = this.answer.reads[this.answer.firstRead(from)]
|
|
386
386
|
if (
|
|
387
387
|
read === undefined ||
|
|
388
388
|
read.from !== from ||
|
|
@@ -390,12 +390,41 @@ export class Lowering {
|
|
|
390
390
|
read.item !== -1
|
|
391
391
|
)
|
|
392
392
|
return undefined
|
|
393
|
-
const
|
|
394
|
-
if (!/^(\s*\.\s*[A-Za-z_$][\w$]*)+$/.test(path)) return undefined
|
|
395
|
-
const name = this.nameOf(read)
|
|
393
|
+
const tokens = tokenize(source.slice(read.to, to))
|
|
396
394
|
const segments: string[] = []
|
|
397
|
-
for (
|
|
398
|
-
|
|
395
|
+
for (let at = 0; at < tokens.length; at += 1) {
|
|
396
|
+
const token = tokens[at] as Token
|
|
397
|
+
if (token.kind === SyntaxKind.ExclamationToken) continue
|
|
398
|
+
if (token.kind === SyntaxKind.QuestionDotToken)
|
|
399
|
+
throw new Error(
|
|
400
|
+
`abide: bind:{${source.slice(from, to)}} writes through an optional step, and nothing can be written through one. Bind the path without ?., or a { get, set } pair. RULEBOOK 32.76.`,
|
|
401
|
+
)
|
|
402
|
+
if (token.kind === SyntaxKind.DotToken) {
|
|
403
|
+
at += 1
|
|
404
|
+
const step = tokens[at]
|
|
405
|
+
if (step === undefined || !/^[A-Za-z_$][\w$]*$/.test(step.text))
|
|
406
|
+
return undefined
|
|
407
|
+
segments.push(JSON.stringify(step.text))
|
|
408
|
+
continue
|
|
409
|
+
}
|
|
410
|
+
if (token.kind !== SyntaxKind.OpenBracketToken) return undefined
|
|
411
|
+
let depth = 1
|
|
412
|
+
let close = at + 1
|
|
413
|
+
for (; close < tokens.length; close += 1) {
|
|
414
|
+
depth += nesting((tokens[close] as Token).kind)
|
|
415
|
+
if (depth === 0) break
|
|
416
|
+
}
|
|
417
|
+
if (depth > 0 || close === at + 1) return undefined
|
|
418
|
+
segments.push(
|
|
419
|
+
this.lower({
|
|
420
|
+
start: read.to + token.end,
|
|
421
|
+
end: read.to + (tokens[close] as Token).start,
|
|
422
|
+
}).trim(),
|
|
423
|
+
)
|
|
424
|
+
at = close
|
|
425
|
+
}
|
|
426
|
+
if (segments.length === 0) return undefined
|
|
427
|
+
const name = this.nameOf(read)
|
|
399
428
|
return `{ get: () => ${this.lowerChunk({ start: from, end: to })}, set: (${STORED}) => ${name}.set(${this.use('writePath')}(${name}(), [${segments.join(', ')}], ${STORED})) }`
|
|
400
429
|
}
|
|
401
430
|
|
|
@@ -184,7 +184,6 @@ export function emitClient(
|
|
|
184
184
|
const classified = classifyAttributes(node, lowering)
|
|
185
185
|
if (!classified.some(isBound)) continue
|
|
186
186
|
const units = elementUnits(element, classified, slot, lowering)
|
|
187
|
-
for (const unit of units.handlers) lines.push(unit)
|
|
188
187
|
for (const unit of units.attributes) lines.push(unit)
|
|
189
188
|
for (const unit of units.elements) lines.push(unit)
|
|
190
189
|
}
|
|
@@ -562,7 +561,7 @@ function emitRegion(sink: Cursor, region: RegionEmission): RegionEmission {
|
|
|
562
561
|
|
|
563
562
|
for (const line of walkTo(positions, live, lowering)) chain.push(line)
|
|
564
563
|
|
|
565
|
-
// THE UNITS — text, listeners,
|
|
564
|
+
// THE UNITS — text, attributes and listeners, the regions, then the node handed over. The walk
|
|
566
565
|
// does not depend on the order.
|
|
567
566
|
// 32.68 — A `bind:element` CALLBACK RUNS LAST, so the node it is handed is whole: a child
|
|
568
567
|
// component, an arm or a list inside it has built, and `form.querySelector('input')` finds an
|
|
@@ -571,7 +570,6 @@ function emitRegion(sink: Cursor, region: RegionEmission): RegionEmission {
|
|
|
571
570
|
// (`Scope.itemWatch`).
|
|
572
571
|
const items: string[] = []
|
|
573
572
|
const texts: string[] = []
|
|
574
|
-
const handlers: string[] = []
|
|
575
573
|
const attributes: string[] = []
|
|
576
574
|
const elements: string[] = []
|
|
577
575
|
const regions: PlacedNode[] = []
|
|
@@ -595,13 +593,11 @@ function emitRegion(sink: Cursor, region: RegionEmission): RegionEmission {
|
|
|
595
593
|
lowering,
|
|
596
594
|
)
|
|
597
595
|
for (const unit of units.items) items.push(unit)
|
|
598
|
-
for (const unit of units.handlers) handlers.push(unit)
|
|
599
596
|
for (const unit of units.attributes) attributes.push(unit)
|
|
600
597
|
for (const unit of units.elements) elements.push(unit)
|
|
601
598
|
}
|
|
602
599
|
for (const unit of items) lines.push(unit)
|
|
603
600
|
for (const unit of texts) lines.push(unit)
|
|
604
|
-
for (const unit of handlers) lines.push(unit)
|
|
605
601
|
for (const unit of attributes) lines.push(unit)
|
|
606
602
|
|
|
607
603
|
// 32.67 — WHAT A ROW'S REGIONS AND COMPONENTS BIND reads the row's item through its cell: the
|
|
@@ -751,7 +747,6 @@ function elementUnits(
|
|
|
751
747
|
lowering: Lowering,
|
|
752
748
|
): {
|
|
753
749
|
items: string[]
|
|
754
|
-
handlers: string[]
|
|
755
750
|
attributes: string[]
|
|
756
751
|
elements: string[]
|
|
757
752
|
} {
|
|
@@ -849,9 +844,13 @@ if (__r${slot} !== undefined) ${SCOPE}.stops.push(__r${slot})`,
|
|
|
849
844
|
items.push(`const __i${slot} = ${element}\n${itemWrites.join('\n')}`)
|
|
850
845
|
if (writes.length > 0)
|
|
851
846
|
attributes.push(`const __a${slot} = ${element}\n${writes.join('\n')}`)
|
|
847
|
+
// 32.75 — A BIND'S LISTENER IS ADDED AHEAD OF THE AUTHOR'S, written or spread, so an
|
|
848
|
+
// `onchange` reading the operand reads what was just picked. After the attribute writes, since
|
|
849
|
+
// a bind reads what they set (`type`, for a number).
|
|
852
850
|
for (const unit of binds) attributes.push(unit)
|
|
851
|
+
for (const unit of handlers) attributes.push(unit)
|
|
853
852
|
for (const unit of spreads) attributes.push(unit)
|
|
854
|
-
return { items,
|
|
853
|
+
return { items, attributes, elements }
|
|
855
854
|
}
|
|
856
855
|
|
|
857
856
|
// A region's bindings go through its SCOPE, so what replaced the region can stop them (see
|
|
@@ -16,7 +16,7 @@ import { PRIVATE_HANDLERS_ADDRESS } from '#shared/ADDRESSES.ts'
|
|
|
16
16
|
import { parseRoutePattern } from '#shared/routePattern.ts'
|
|
17
17
|
import type { PlacedHandler, PlacedSource } from './appLayout.ts'
|
|
18
18
|
import { declaredExports } from './declaredExports.ts'
|
|
19
|
-
import { PRIVATE_EXPORT } from '
|
|
19
|
+
import { PRIVATE_EXPORT } from '#server/PRIVATE_EXPORT.ts'
|
|
20
20
|
import type { Refusals } from './Refusals.ts'
|
|
21
21
|
import type { Diagnosis, TypeSession } from './TypeSession.ts'
|
|
22
22
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SyntaxKind } from 'typescript/unstable/ast'
|
|
2
|
+
import { PRIVATE_EXPORT } from '#server/PRIVATE_EXPORT.ts'
|
|
2
3
|
import { isEventName } from '#shared/isEventName.ts'
|
|
3
4
|
import { isComponentTag, propKeyOf } from './classifyTemplate.ts'
|
|
4
5
|
import { isNamedAlone } from './isNamedAlone.ts'
|
|
@@ -768,9 +769,6 @@ function refuseExport(source: string): void {
|
|
|
768
769
|
}
|
|
769
770
|
}
|
|
770
771
|
|
|
771
|
-
/** The prefix a server block's bindings are re-exported under, for the build alone (16.70). */
|
|
772
|
-
export const PRIVATE_EXPORT = '__handler_'
|
|
773
|
-
|
|
774
772
|
/**
|
|
775
773
|
* 16.70 — the `export { … }` re-exporting each name the server blocks bind under `PRIVATE_EXPORT`,
|
|
776
774
|
* or `''` where they bind none. One spelling for the checker's view and the emitted module, which
|
|
@@ -17,6 +17,12 @@ export const TEXT_QUERY = '?text'
|
|
|
17
17
|
*/
|
|
18
18
|
export const CLIENT_QUERY = '?client'
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* The file's server module, whoever asks — a browser module outside a bundle binding its private
|
|
22
|
+
* handlers (38.121), where `?component` would answer it with itself.
|
|
23
|
+
*/
|
|
24
|
+
export const SERVER_QUERY = '?server'
|
|
25
|
+
|
|
20
26
|
/**
|
|
21
27
|
* What a handler file is imported as: its own path with this after it, so the plugin can serve it
|
|
22
28
|
* between the calls that file its hooks (37.1) and a browser its wrapper (16.74). A stack frame names
|
|
@@ -40,10 +40,12 @@ import {
|
|
|
40
40
|
COMPONENT_QUERY,
|
|
41
41
|
HANDLER_FACE,
|
|
42
42
|
HANDLER_SUFFIX,
|
|
43
|
+
SERVER_QUERY,
|
|
43
44
|
STYLE_QUERY,
|
|
44
45
|
TEXT_QUERY,
|
|
45
46
|
} from './MODULE_QUERIES.ts'
|
|
46
47
|
import { MOUNTABLE } from './MOUNTABLE.ts'
|
|
48
|
+
import { PRIVATE_EXPORT } from './PRIVATE_EXPORT.ts'
|
|
47
49
|
import { realOrAsGiven } from './realOrAsGiven.ts'
|
|
48
50
|
import { SERVER_TABLE } from './TABLES.ts'
|
|
49
51
|
import { VIRTUAL_DIRECTORY } from './VIRTUAL_DIRECTORY.ts'
|
|
@@ -280,6 +282,7 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
280
282
|
asking('style', STYLE_QUERY, abideWith(STYLE_QUERY))
|
|
281
283
|
asking('component', COMPONENT_QUERY, abideWith(COMPONENT_QUERY))
|
|
282
284
|
asking('client', CLIENT_QUERY, abideWith(CLIENT_QUERY))
|
|
285
|
+
asking('server', SERVER_QUERY, abideWith(SERVER_QUERY))
|
|
283
286
|
asking('abide', '', /\.abide$/)
|
|
284
287
|
build.onResolve(
|
|
285
288
|
{ filter: new RegExp(`/${VIRTUAL_DIRECTORY}/[^/]+\\.ts$`) },
|
|
@@ -329,10 +332,11 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
329
332
|
},
|
|
330
333
|
)
|
|
331
334
|
|
|
332
|
-
// A RELATIVE SPECIFIER A FACE IMPORTS AT RUN TIME is resolved from the file the
|
|
333
|
-
// stands for: bun's runtime reads one from an importer in a namespace as no path
|
|
334
|
-
// all
|
|
335
|
-
//
|
|
335
|
+
// A RELATIVE OR `#` SPECIFIER A FACE IMPORTS AT RUN TIME is resolved from the file the
|
|
336
|
+
// face stands for: bun's runtime reads one from an importer in a namespace as no path
|
|
337
|
+
// at all, and a `#` one, asked by `import()`, as a package. A bare one it resolves
|
|
338
|
+
// itself. Registered after every face, so a `.abide` is still answered as one — and
|
|
339
|
+
// NOT IN A BUNDLE, which needs it for no face it serves,
|
|
336
340
|
// and where a hook matching every relative import in a package costs the bundle that
|
|
337
341
|
// package's `sideEffects`: a two-page app grew by 12 KB, the validator one page never
|
|
338
342
|
// calls moved into the chunk both pages load.
|
|
@@ -342,7 +346,7 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
342
346
|
build.onResolve({ filter: HANDLER_FACE }, (args) => ({
|
|
343
347
|
path: args.path,
|
|
344
348
|
}))
|
|
345
|
-
build.onResolve({ filter:
|
|
349
|
+
build.onResolve({ filter: RELATIVE_OR_SUBPATH }, (args) =>
|
|
346
350
|
resolving || pathOf(args.importer) === args.importer
|
|
347
351
|
? undefined
|
|
348
352
|
: { path: resolved(args.path, args.importer) },
|
|
@@ -379,10 +383,16 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
379
383
|
})
|
|
380
384
|
build.onLoad(
|
|
381
385
|
{ filter: /.*/, namespace: CLIENT_NAMESPACE },
|
|
382
|
-
(args) =>
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
+
(args) => {
|
|
387
|
+
const path = realFileOf(args.path)
|
|
388
|
+
return {
|
|
389
|
+
contents: withPrivateHandlers(
|
|
390
|
+
compiledAt(served, path).client,
|
|
391
|
+
path,
|
|
392
|
+
),
|
|
393
|
+
loader: 'ts',
|
|
394
|
+
}
|
|
395
|
+
},
|
|
386
396
|
)
|
|
387
397
|
build.onLoad(
|
|
388
398
|
{ filter: /.*/, namespace: BOUND_NAMESPACE },
|
|
@@ -425,6 +435,22 @@ function modulesPlugin(served: Held): Bun.BunPlugin {
|
|
|
425
435
|
}
|
|
426
436
|
}
|
|
427
437
|
|
|
438
|
+
// 38.121 — A BROWSER MODULE OUTSIDE A BUNDLE HOLDS EACH PRIVATE HANDLER ITSELF, bound from its file's
|
|
439
|
+
// server module, where a bundle's holds the wrapper 16.28 states: no server answers the address that
|
|
440
|
+
// wrapper would fetch, and a handler file's import is already the module the server mounts. Each
|
|
441
|
+
// wrapper is one line of `wireLine`'s, `const <name> = __wire(…)`, and an import is hoisted, so the
|
|
442
|
+
// line is swapped where it stands.
|
|
443
|
+
const PRIVATE_WRAPPER = /^const ([\w$]+) = __wire\(.*$/gm
|
|
444
|
+
|
|
445
|
+
function withPrivateHandlers(client: string, path: string): string {
|
|
446
|
+
const server = JSON.stringify(`${path}${SERVER_QUERY}`)
|
|
447
|
+
return client.replace(
|
|
448
|
+
PRIVATE_WRAPPER,
|
|
449
|
+
(_line, name: string) =>
|
|
450
|
+
`import { ${PRIVATE_EXPORT}${name} as ${name} } from ${server}`,
|
|
451
|
+
)
|
|
452
|
+
}
|
|
453
|
+
|
|
428
454
|
// AN IMPORTER IN ONE OF THIS PLUGIN'S NAMESPACES CARRIES IT in front of its path at run time — with
|
|
429
455
|
// a `/` in front of that again, where the bundler hands the path alone.
|
|
430
456
|
function pathOf(importer: string): string {
|
package/src/server/faceOf.ts
CHANGED
|
@@ -3,7 +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 { HANDLER_SUFFIX } from './MODULE_QUERIES.ts'
|
|
6
|
+
import { HANDLER_SUFFIX, SERVER_QUERY } from './MODULE_QUERIES.ts'
|
|
7
7
|
|
|
8
8
|
// A VIRTUAL FACE'S PATH: its file's, with a suffix that is not a query. Both registries key a module
|
|
9
9
|
// by its path whatever its namespace, and the runtime's drops a query from it — so a file's text, its
|
|
@@ -33,14 +33,15 @@ export type Face = { path: string; namespace: string }
|
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* What an import asks of a file, by its spelling: its text (`?text`), a component's stylesheet
|
|
36
|
-
* (`?style`), the module it compiles to (`?component`), its browser module (`?client`),
|
|
37
|
-
* named bare, or a module that may be a handler file.
|
|
36
|
+
* (`?style`), the module it compiles to (`?component`), its browser module (`?client`), its server
|
|
37
|
+
* module (`?server`), a `.abide` named bare, or a module that may be a handler file.
|
|
38
38
|
*/
|
|
39
39
|
export type Asked =
|
|
40
40
|
| 'text'
|
|
41
41
|
| 'style'
|
|
42
42
|
| 'component'
|
|
43
43
|
| 'client'
|
|
44
|
+
| 'server'
|
|
44
45
|
| 'abide'
|
|
45
46
|
| 'handler'
|
|
46
47
|
|
|
@@ -60,6 +61,8 @@ export type HandlerFace = { scope: string; client: string }
|
|
|
60
61
|
* - 32.17 — the module a `.abide` compiles to: in a browser's lane its browser module, else its
|
|
61
62
|
* server module — in a namespace of its own in a bundle, where bun refuses a `file` path with `..`
|
|
62
63
|
* in it, which a `[...rest]` directory writes (23.5);
|
|
64
|
+
* - 38.121 — its server module, to a browser module outside a bundle binding its private handlers,
|
|
65
|
+
* and never in a browser's lane (16.28);
|
|
63
66
|
* - 34.16 — a `.abide` named bare is a module abide did not compile asking: on a server its bound
|
|
64
67
|
* face, and in a browser the component module itself;
|
|
65
68
|
* - 16.2, 16.74 — a handler file, however its import is spelled and whoever imports it — the app or a
|
|
@@ -89,6 +92,12 @@ export function faceOf(
|
|
|
89
92
|
return lane.browser
|
|
90
93
|
? { path: file, namespace: COMPONENT_NAMESPACE }
|
|
91
94
|
: { path: `${file}${CLIENT}`, namespace: CLIENT_NAMESPACE }
|
|
95
|
+
case 'server':
|
|
96
|
+
return lane.browser
|
|
97
|
+
? new Error(
|
|
98
|
+
`abide: ${realFileOf(importer)} imports ${file}${SERVER_QUERY}, the server module, into the browser. Call its handlers by name. RULEBOOK 16.28.`,
|
|
99
|
+
)
|
|
100
|
+
: { path: file, namespace: componentNamespaceOf(lane) }
|
|
92
101
|
case 'abide':
|
|
93
102
|
return inBrowser || importer === `${file}${BOUND}`
|
|
94
103
|
? component()
|
|
@@ -1706,13 +1706,13 @@ function windowHolds(cell: Cell, cap: Cap): boolean {
|
|
|
1706
1706
|
/**
|
|
1707
1707
|
* Whether an arrival passes the window; the first landing always does.
|
|
1708
1708
|
*
|
|
1709
|
-
* 5.15: the first landing goes through, having nothing to collapse against — and
|
|
1710
|
-
* window
|
|
1709
|
+
* 5.15: the first landing goes through, having nothing to collapse against — and it opens no
|
|
1710
|
+
* window, a throttle's included. 5.12: the first CHANGE is the one a throttle lets through, and a
|
|
1711
|
+
* landing is not a change; a window opened here held back the first `set` after construction, so
|
|
1712
|
+
* `s.set(v); s()` read the value it was built with.
|
|
1711
1713
|
*/
|
|
1712
1714
|
function passes(cell: Cell, cap: Cap): boolean {
|
|
1713
|
-
|
|
1714
|
-
if (cap.leading) openWindow(cell, cap)
|
|
1715
|
-
return true
|
|
1715
|
+
return !cell.landed || !windowHolds(cell, cap)
|
|
1716
1716
|
}
|
|
1717
1717
|
|
|
1718
1718
|
/**
|
|
@@ -3255,11 +3255,16 @@ function bringCurrent(cell: Cell): void {
|
|
|
3255
3255
|
if (cell.state !== CLEAN) resolve(cell)
|
|
3256
3256
|
}
|
|
3257
3257
|
|
|
3258
|
-
/**
|
|
3258
|
+
/**
|
|
3259
|
+
* What every probe does before it answers: subscribes the reader (3.3), then brings `cell` current.
|
|
3260
|
+
*
|
|
3261
|
+
* 3.14 — A MUTATION'S VALUE IS ANSWERED AS IT STANDS: its body is the write, so bringing it current
|
|
3262
|
+
* after a sweep invalidated it (13.16) sent the write again from a `save.busy()`.
|
|
3263
|
+
*/
|
|
3259
3264
|
function probe(cell: Cell): void {
|
|
3260
3265
|
const reader = TRACKING.reader
|
|
3261
3266
|
if (reader !== null) trackProbe(cell, reader)
|
|
3262
|
-
bringCurrent(cell)
|
|
3267
|
+
if (!cell.mutation) bringCurrent(cell)
|
|
3263
3268
|
}
|
|
3264
3269
|
|
|
3265
3270
|
function readyNow(cell: Cell): boolean {
|
|
@@ -149,6 +149,19 @@ export function scoped(
|
|
|
149
149
|
return read
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Whether the value `value` forwards to is built for the scope that is current — always, for a
|
|
154
|
+
* value that is no forwarder. Asking builds nothing, where `instanceOf` builds what it finds unbuilt.
|
|
155
|
+
*/
|
|
156
|
+
export function builtHere(value: unknown): boolean {
|
|
157
|
+
const filed = (value as Partial<Forwarder> | null)?.[FILED]
|
|
158
|
+
if (filed === undefined) return true
|
|
159
|
+
const scope = filed.global ? undefined : currentScope()
|
|
160
|
+
return scope === undefined
|
|
161
|
+
? filed.home !== UNBUILT
|
|
162
|
+
: scope.instances.has(filed)
|
|
163
|
+
}
|
|
164
|
+
|
|
152
165
|
/**
|
|
153
166
|
* Whether `value` is request-local on this server — built without `global`, outside every request.
|
|
154
167
|
*/
|
package/src/shared/rpc.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { type Failed, isFailedNamed } from './Failed.ts'
|
|
17
17
|
import { GLOBAL_GETS } from './GLOBAL_GETS.ts'
|
|
18
18
|
import { HandlerRoute } from './HandlerRoute.ts'
|
|
19
|
+
import type { Issues } from './Issues.ts'
|
|
19
20
|
import { IN_PROCESS_ORIGIN } from './IN_PROCESS_ORIGIN.ts'
|
|
20
21
|
import { compose, type Middleware } from './Middleware.ts'
|
|
21
22
|
import { outbox, type QueuedCall, sendingFor } from './outbox.ts'
|
|
@@ -50,7 +51,7 @@ import {
|
|
|
50
51
|
import { MUTATION } from './reactive/MUTATION.ts'
|
|
51
52
|
import { dropAnswered } from './reactive/Reactive.ts'
|
|
52
53
|
import { SELECTION_MEMBERS } from './reactive/SELECTION_MEMBERS.ts'
|
|
53
|
-
import { isForwarder } from './reactive/scoped.ts'
|
|
54
|
+
import { builtHere, isForwarder } from './reactive/scoped.ts'
|
|
54
55
|
import { checkArgs } from './refuse.ts'
|
|
55
56
|
import { rpcRequest } from './rpcRequest.ts'
|
|
56
57
|
import type { Schema } from './Schema.ts'
|
|
@@ -74,7 +75,9 @@ export type CallOptions<Args = unknown, Value = unknown> = {
|
|
|
74
75
|
*
|
|
75
76
|
* 16.8 — AND ITS FAILURES CARRY THE TRANSPORT'S `HttpError`, which no handler declares and every
|
|
76
77
|
* call can be answered with: a body over `maxBodySize` (413), a closed `crossOrigin` (403), a
|
|
77
|
-
* `timeout` (504), and any failure status whose body was not a `Failed`, rebuilt by `answered`.
|
|
78
|
+
* `timeout` (504), and any failure status whose body was not a `Failed`, rebuilt by `answered`. And
|
|
79
|
+
* the `ValidationError` the wire holds args to the annotation with (19.9), where `Annotated` says it
|
|
80
|
+
* derives a schema at all.
|
|
78
81
|
*/
|
|
79
82
|
export type Rpc<Value, Args, Failures = never, Produced = Value> = ((
|
|
80
83
|
...args: undefined extends Args
|
|
@@ -83,7 +86,7 @@ export type Rpc<Value, Args, Failures = never, Produced = Value> = ((
|
|
|
83
86
|
) => Reactive<
|
|
84
87
|
Value,
|
|
85
88
|
Value,
|
|
86
|
-
Failures | Failed<'HttpError', undefined>,
|
|
89
|
+
Failures | Failed<'HttpError', undefined> | Annotated<Args>,
|
|
87
90
|
Produced
|
|
88
91
|
>) & {
|
|
89
92
|
method: Method
|
|
@@ -104,7 +107,7 @@ export type Rpc<Value, Args, Failures = never, Produced = Value> = ((
|
|
|
104
107
|
error: unknown,
|
|
105
108
|
name: Name,
|
|
106
109
|
): error is Extract<
|
|
107
|
-
Failures | Failed<'HttpError', undefined>,
|
|
110
|
+
Failures | Failed<'HttpError', undefined> | Annotated<Args>,
|
|
108
111
|
{ name: Name }
|
|
109
112
|
>
|
|
110
113
|
/**
|
|
@@ -130,6 +133,35 @@ export type Rpc<Value, Args, Failures = never, Produced = Value> = ((
|
|
|
130
133
|
guarded: boolean
|
|
131
134
|
}
|
|
132
135
|
|
|
136
|
+
/**
|
|
137
|
+
* The `ValidationError` the wire answers args that do not hold to the schema 19.9 derives from
|
|
138
|
+
* `Args`, and `never` where the derived schema is no object's and the wire holds nothing to it.
|
|
139
|
+
*
|
|
140
|
+
* MIRRORS `argsSchemaOf`, which reads the same `Args` off the `Rpc`'s call: an object's schema is
|
|
141
|
+
* derived for ONE record, `undefined` dropped — not a scalar, a list, a callable or a union of
|
|
142
|
+
* records (`anyOf`), and not `any`, `unknown` or `object`, which constrain nothing. `object` is told
|
|
143
|
+
* from `{}`, the one other record with no keys, by `string`, which only `{}` takes.
|
|
144
|
+
*/
|
|
145
|
+
type Annotated<Args> = 0 extends 1 & Args
|
|
146
|
+
? never
|
|
147
|
+
: unknown extends Args
|
|
148
|
+
? never
|
|
149
|
+
: Derived<Exclude<Args, undefined>>
|
|
150
|
+
// `Member` is distributed over and `Whole` is not: a member that is not the whole is one arm of a union.
|
|
151
|
+
type Derived<Member, Whole = Member> = Member extends
|
|
152
|
+
| readonly unknown[]
|
|
153
|
+
| ((...given: never[]) => unknown)
|
|
154
|
+
? never
|
|
155
|
+
: Member extends object
|
|
156
|
+
? [Whole] extends [Member]
|
|
157
|
+
? [keyof Member] extends [never]
|
|
158
|
+
? [string] extends [Member]
|
|
159
|
+
? Failed<'ValidationError', Issues<Whole>>
|
|
160
|
+
: never
|
|
161
|
+
: Failed<'ValidationError', Issues<Whole>>
|
|
162
|
+
: never
|
|
163
|
+
: never
|
|
164
|
+
|
|
133
165
|
/** Which generated surfaces carry a handler (20.1). */
|
|
134
166
|
export type Clients = {
|
|
135
167
|
ui?: boolean
|
|
@@ -307,6 +339,18 @@ type Declare = {
|
|
|
307
339
|
): Rpc<Kept<Shaped>, undefined, Refusals<Shaped, Gate, Accepted>>
|
|
308
340
|
}
|
|
309
341
|
|
|
342
|
+
/** What a handler's selection members answer before a call has built its value: no entries' answers. */
|
|
343
|
+
const UNREACHED: Record<
|
|
344
|
+
(typeof SELECTION_MEMBERS)[number],
|
|
345
|
+
(pattern?: unknown) => unknown
|
|
346
|
+
> = {
|
|
347
|
+
busy: () => false,
|
|
348
|
+
ready: () => true,
|
|
349
|
+
synced: () => true,
|
|
350
|
+
refresh: () => undefined,
|
|
351
|
+
invalidate: () => undefined,
|
|
352
|
+
}
|
|
353
|
+
|
|
310
354
|
/** Builds the handler declarer for one method — what `GET`, `POST` and the rest are. */
|
|
311
355
|
export function declare(method: Method): Declare {
|
|
312
356
|
return ((handler: unknown, options?: RpcOptions): unknown => {
|
|
@@ -421,10 +465,17 @@ export function declare(method: Method): Declare {
|
|
|
421
465
|
}
|
|
422
466
|
// THE HANDLER'S OWN MEMBERS, handed the pattern. A `Keyed` narrows by it and a `Reactive`'s
|
|
423
467
|
// take no argument, so one spelling serves both arms and neither is a second mechanism.
|
|
424
|
-
|
|
468
|
+
//
|
|
469
|
+
// 13.19 — AND NOT YET BUILT, NONE OF THEM BUILDS IT: building an unkeyed value starts its body
|
|
470
|
+
// (1.7), so `save.busy()` over a server's never-called `POST(async () => …)` ran the write and
|
|
471
|
+
// reported it in flight. Until a call reaches it the handler has no entry, and answers as one
|
|
472
|
+
// with no entries does.
|
|
473
|
+
const built = value as Record<
|
|
425
474
|
(typeof SELECTION_MEMBERS)[number],
|
|
426
475
|
(pattern?: unknown) => unknown
|
|
427
476
|
>
|
|
477
|
+
const faceHere = (): typeof built =>
|
|
478
|
+
builtHere(value) ? built : UNREACHED
|
|
428
479
|
const rpc = Object.assign(call, contract, {
|
|
429
480
|
method,
|
|
430
481
|
description: options?.description,
|
|
@@ -473,16 +524,16 @@ export function declare(method: Method): Declare {
|
|
|
473
524
|
members[name] =
|
|
474
525
|
name === 'invalidate' || name === 'refresh'
|
|
475
526
|
? (pattern?: unknown): unknown => {
|
|
476
|
-
const done =
|
|
527
|
+
const done = faceHere()[name](pattern)
|
|
477
528
|
RPC_SERVER.broadcast?.(rpc, name, pattern)
|
|
478
529
|
return done
|
|
479
530
|
}
|
|
480
|
-
: (pattern?: unknown): unknown =>
|
|
531
|
+
: (pattern?: unknown): unknown => faceHere()[name](pattern)
|
|
481
532
|
}
|
|
482
533
|
// 46.11 — BUSY TOO WHILE A CALL THE OUTBOX HOLDS FOR IT IS IN FLIGHT: a queued call sent is work
|
|
483
534
|
// in flight for this handler, as a call made now is. 46.1 — and the calls it holds for it.
|
|
484
535
|
members.busy = (pattern?: unknown): boolean =>
|
|
485
|
-
(
|
|
536
|
+
(faceHere().busy(pattern) as boolean) ||
|
|
486
537
|
sendingFor(rpc, pattern as Record<string, unknown> | undefined)
|
|
487
538
|
members.outbox = (pattern?: unknown): QueuedCall[] =>
|
|
488
539
|
outbox(rpc as never, pattern as Record<string, unknown> | undefined)
|