@abide/abide 0.123.2 → 0.123.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 +27 -0
- package/bin/abide.ts +36 -65
- package/package.json +1 -1
- package/src/compiler/Diagnostics.ts +198 -0
- package/src/compiler/KeptChecker.ts +97 -0
- package/src/compiler/Lowering.ts +1 -1
- package/src/compiler/PositionedRefusal.ts +1 -1
- package/src/compiler/TypeSession.ts +62 -85
- package/src/compiler/appLayout.ts +28 -20
- package/src/compiler/argsDefaultsIn.ts +1 -1
- package/src/compiler/checkApp.ts +64 -68
- package/src/compiler/checkedShadow.ts +1 -1
- package/src/compiler/compileApp.ts +101 -70
- package/src/compiler/declaredExports.ts +2 -4
- package/src/compiler/emitClient.ts +1 -1
- package/src/compiler/formatAbide.ts +2 -2
- package/src/compiler/handlersUnder.ts +17 -29
- package/src/compiler/lineOf.ts +0 -19
- package/src/compiler/matchersOf.ts +3 -3
- package/src/compiler/mountsOf.ts +3 -3
- package/src/compiler/readSources.ts +16 -18
- package/src/compiler/readsIn.ts +1 -1
- package/src/compiler/serveLanguage.ts +23 -57
- package/src/compiler/sharedModuleWrites.ts +1 -1
- package/src/compiler/templateWarnings.ts +1 -2
- package/src/compiler/trackingWarnings.ts +1 -1
- package/src/server/CLIENT_BUILD.ts +4 -32
- package/src/server/RESUME_MILLISECONDS.ts +2 -0
- package/src/server/answerRequest.ts +11 -300
- package/src/server/answerRpc.ts +12 -54
- package/src/server/answerSocket.ts +8 -35
- package/src/server/appBuild.ts +377 -0
- package/src/server/appPlugins.ts +15 -17
- package/src/server/appTree.ts +6 -5
- package/src/server/argsContract.ts +209 -0
- package/src/server/bridgeRelay.ts +153 -65
- package/src/server/claudeCode.ts +30 -127
- package/src/server/config.ts +6 -7
- package/src/server/createApp.ts +30 -64
- package/src/server/devWorker.ts +11 -15
- package/src/server/handlerFileMount.ts +40 -0
- package/src/server/index.ts +2 -2
- package/src/server/packageDirectory.ts +14 -0
- package/src/server/preload.ts +3 -2
- package/src/server/principal.ts +4 -3
- package/src/server/remote.ts +1 -1
- package/src/server/runCompiled.ts +1 -1
- package/src/server/runDesktop.ts +1 -1
- package/src/server/seedBuffer.ts +1 -1
- package/src/server/serveDev.ts +24 -58
- package/src/server/servedApp.ts +390 -0
- package/src/server/signInChanged.ts +20 -0
- package/src/server/surfaceOf.ts +2 -1
- package/src/server/versionOf.ts +6 -15
- package/src/shared/applyPatch.ts +1 -1
- package/src/shared/muxFrames.ts +1 -1
- package/src/shared/reactive/Keyed.ts +1 -1
- package/src/shared/reactive/Reactive.ts +21 -8
- package/src/shared/reactive/argsContract.ts +131 -4
- package/src/shared/reactive/index.ts +1 -0
- package/src/shared/reactive/patchLog.ts +79 -0
- package/src/shared/reactive/patchMarks.ts +34 -0
- package/src/shared/reactive/recordWrites.ts +11 -14
- package/src/shared/reactive/share.ts +4 -3
- package/src/shared/rpc.ts +12 -19
- package/src/ui/ArmRegion.ts +10 -4
- package/src/ui/Level.ts +11 -20
- package/src/ui/LevelChain.ts +272 -0
- package/src/ui/Router.ts +54 -245
- package/src/ui/Scope.ts +17 -3
- package/src/ui/TryRegion.ts +22 -23
- package/src/ui/bindSpread.ts +2 -4
- package/src/ui/listenForRoutes.ts +3 -10
- package/src/ui/outboxQueue.ts +21 -3
- package/src/ui/reconcile.ts +4 -6
- package/src/ui/routing.ts +2 -1
- package/src/ui/rows.ts +2 -2
- package/src/ui/setAttribute.ts +12 -3
- package/src/ui/socketWire.ts +26 -11
- package/src/compiler/Finding.ts +0 -2
- package/src/compiler/Refusals.ts +0 -65
- package/src/compiler/formatDiagnosis.ts +0 -20
- package/src/server/appDirectories.ts +0 -9
- package/src/server/argsFor.ts +0 -39
- package/src/server/buildDirectory.ts +0 -10
- package/src/server/coerceArgs.ts +0 -109
- package/src/server/derivedArgs.ts +0 -26
- package/src/server/runBuild.ts +0 -24
- package/src/server/serveBuild.ts +0 -144
- package/src/server/testBuild.ts +0 -220
- package/src/shared/reactive/FOLLOWED.ts +0 -8
- package/src/shared/reactive/PATCHES.ts +0 -11
- package/src/shared/reactive/PATCH_SINKS.ts +0 -26
- package/src/shared/reactive/canonicalKey.ts +0 -95
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import type { Params } from '#shared/matchRoute.ts'
|
|
2
|
+
import type { ShareScope } from '#shared/reactive/share.ts'
|
|
3
|
+
import { route } from '#shared/route.ts'
|
|
4
|
+
import { setUpLevels } from '#shared/setUpLevels.ts'
|
|
5
|
+
|
|
6
|
+
import { LAYER_MOUNT } from './LAYER_NAMES.ts'
|
|
7
|
+
import { Level } from './Level.ts'
|
|
8
|
+
import { beginLayer, endLayer } from './measureLayer.ts'
|
|
9
|
+
import { fillRegion, type Region } from './regions.ts'
|
|
10
|
+
import type { Scope, Stoppable } from './Scope.ts'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A page's client mount, called against the region the router owns.
|
|
14
|
+
*
|
|
15
|
+
* The same default export a page already has — the router is a CALLER of a page exactly as a page
|
|
16
|
+
* is a caller of a component, which is why nothing about the emitted module changes to be routable.
|
|
17
|
+
*/
|
|
18
|
+
export type PageMount = (
|
|
19
|
+
root: Region,
|
|
20
|
+
props?: Record<string, unknown>,
|
|
21
|
+
// 39.6 — A LEVEL'S HEAD IS ITS OWN MOUNT'S TO APPLY, from inside the instance its slots read
|
|
22
|
+
// (`emitClient`), so the router hands the module a region and says it is the router by handing
|
|
23
|
+
// it the level it stands at — a file invoked as a component too is mounted without it, and
|
|
24
|
+
// applies no head a row. 39.14 — the level is where the mount leaves its head for the router
|
|
25
|
+
// to read when it withdraws.
|
|
26
|
+
routed?: Level,
|
|
27
|
+
) => Scope
|
|
28
|
+
|
|
29
|
+
export type PageModule = { default: PageMount }
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The routed chain standing in the document — its layouts outermost first and the page last — and
|
|
33
|
+
* the head they contribute (39.1, 39.14).
|
|
34
|
+
*
|
|
35
|
+
* 41.11 — THE CHAIN HOLDS ITS LEVELS rather than each level owning the one below it, which is what
|
|
36
|
+
* makes a shared layout something a navigation can KEEP: a scope stopped from above takes its
|
|
37
|
+
* children with it, and a layout that survives a navigation must not take the page it was holding.
|
|
38
|
+
*
|
|
39
|
+
* 39.14 — EVERY WAY A LEVEL LEAVES IS BRACKETED HERE: the head reconciled while the leaving levels
|
|
40
|
+
* still read what is on screen, so what the server painted for them is adopted as theirs, and again
|
|
41
|
+
* once they are gone — and a leaving level's contributions are cleared before its scope stops, or
|
|
42
|
+
* each component's `HeadContribution.stop` would withdraw and reconcile on its own.
|
|
43
|
+
*/
|
|
44
|
+
export class LevelChain {
|
|
45
|
+
/** The region the outermost level mounts into. */
|
|
46
|
+
private readonly root: Region
|
|
47
|
+
private readonly levels: Level[]
|
|
48
|
+
/**
|
|
49
|
+
* The modules the address resolved to, one per level.
|
|
50
|
+
*
|
|
51
|
+
* A kept layout's `<slot/>` mounts from it whenever its region opens — read at that moment
|
|
52
|
+
* rather than captured when the layout mounted, or a slot reopened after a navigation put back
|
|
53
|
+
* the page the reader had left.
|
|
54
|
+
*/
|
|
55
|
+
private modules: PageModule[]
|
|
56
|
+
|
|
57
|
+
constructor(root: Region) {
|
|
58
|
+
this.root = root
|
|
59
|
+
this.levels = []
|
|
60
|
+
this.modules = []
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** How many levels stand. */
|
|
64
|
+
get depth(): number {
|
|
65
|
+
return this.levels.length
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The scope of the level at `at` — negative counts from the page — or `undefined`. */
|
|
69
|
+
scopeAt(at: number): Scope | undefined {
|
|
70
|
+
return this.levels.at(at)?.scope
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Reconciles the document head from every level standing (`Level.reconcile`). */
|
|
74
|
+
reconcile(): void {
|
|
75
|
+
Level.reconcile(this.levels)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* How many levels of `arriving` are already standing.
|
|
80
|
+
*
|
|
81
|
+
* Layouts only: a page reached here is another route's, the same route's being followed instead
|
|
82
|
+
* (30.23). EVERY STANDING LEVEL IS COMPARED, the page included: a page is never a layout, so it
|
|
83
|
+
* cannot match one, and a layout whose slot is closed stands with nothing below it.
|
|
84
|
+
*
|
|
85
|
+
* By the MOUNT rather than by the loader or the module record: the table holds an arrow per
|
|
86
|
+
* row, so two routes under one layout name the same file through two different functions, and a
|
|
87
|
+
* table written by hand may answer with a fresh record each call.
|
|
88
|
+
*/
|
|
89
|
+
sharedWith(arriving: PageModule[]): number {
|
|
90
|
+
const layouts = arriving.length - 1
|
|
91
|
+
let shared = 0
|
|
92
|
+
while (
|
|
93
|
+
shared < layouts &&
|
|
94
|
+
shared < this.levels.length &&
|
|
95
|
+
(this.modules[shared] as PageModule).default ===
|
|
96
|
+
(arriving[shared] as PageModule).default
|
|
97
|
+
)
|
|
98
|
+
shared += 1
|
|
99
|
+
return shared
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Mounts `modules` from the top into the root region the server painted. No head is reconciled:
|
|
104
|
+
* a painted level's mount hydrates, and the head is the one the server wrote.
|
|
105
|
+
*/
|
|
106
|
+
hydrate(modules: PageModule[]): void {
|
|
107
|
+
this.modules = modules
|
|
108
|
+
this.mountMeasured(0, this.root)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Keeps the first `kept` levels and mounts `modules` below them, reconciling the head around it.
|
|
113
|
+
*
|
|
114
|
+
* `between` runs once the leaving levels have stopped and their region is emptied, and before
|
|
115
|
+
* the arriving ones are set up — the address written and `route` moved, which a setup reads.
|
|
116
|
+
*
|
|
117
|
+
* The arriving part goes into the root where nothing is kept, and into the deepest kept layout's
|
|
118
|
+
* slot where something is — NOWHERE where that slot's region is closed: the slot mounts it off
|
|
119
|
+
* `modules` when it opens, and not into a region no longer in the document.
|
|
120
|
+
*/
|
|
121
|
+
replace(kept: number, modules: PageModule[], between: () => void): void {
|
|
122
|
+
this.reconcile()
|
|
123
|
+
this.stopFrom(kept)
|
|
124
|
+
const into =
|
|
125
|
+
kept === 0 ? this.root : (this.levels[kept - 1] as Level).slot
|
|
126
|
+
// EMPTIED THROUGH THE PRIMITIVE EVERY OTHER REGION IS, which carries the 41.10 refusal for a
|
|
127
|
+
// region no longer in a document that a hand-written walk swallows.
|
|
128
|
+
if (into !== undefined) fillRegion(into)
|
|
129
|
+
between()
|
|
130
|
+
this.modules = modules
|
|
131
|
+
// 39.6 — each arriving level applies its own head as it mounts into its emptied region; a
|
|
132
|
+
// kept layout is not mounted again, so neither is its head.
|
|
133
|
+
if (into !== undefined) this.mountMeasured(kept, into)
|
|
134
|
+
// 39.14 — what the leaving levels keyed and the arriving ones did not goes back to the
|
|
135
|
+
// nearest kept layout's, else leaves; their keyless nodes leave with them (39.16).
|
|
136
|
+
this.reconcile()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* AT THE TOP OF THE CHAIN, NOT INSIDE `mountFrom`: it recurses one level per layout, and
|
|
141
|
+
* `shares()` SUMS every measure of a name inside the op — so a pair inside the recursion would
|
|
142
|
+
* count the innermost level once per level above it.
|
|
143
|
+
*/
|
|
144
|
+
private mountMeasured(from: number, into: Region): void {
|
|
145
|
+
beginLayer(LAYER_MOUNT)
|
|
146
|
+
try {
|
|
147
|
+
this.mountFrom(from, into)
|
|
148
|
+
} finally {
|
|
149
|
+
endLayer(LAYER_MOUNT)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Stops every level from `at` down, inside out, and drops them.
|
|
155
|
+
*
|
|
156
|
+
* Stopped from the INSIDE OUT, so a layout's disposers run after the page's — the order a
|
|
157
|
+
* scope's own teardown already has, and the one an author reading two `watch` cleanups in
|
|
158
|
+
* two files would expect.
|
|
159
|
+
*/
|
|
160
|
+
private stopFrom(at: number): void {
|
|
161
|
+
for (let index = this.levels.length - 1; index >= at; index -= 1) {
|
|
162
|
+
const level = this.levels[index] as Level
|
|
163
|
+
// OUT OF THE CHAIN BEFORE IT STOPS, so a slot stop it fires on the way finds it gone
|
|
164
|
+
// (`release`) and leaves the head to whoever stopped it.
|
|
165
|
+
this.levels.length = index
|
|
166
|
+
// The level's head leaves whole, so none of its components withdraws its own.
|
|
167
|
+
level.contributed.clear()
|
|
168
|
+
level.scope?.stop()
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Mounts the chain from level `at` into `region`, each level handed the next as `children`.
|
|
174
|
+
*
|
|
175
|
+
* 35.1 — each level is handed the one below it as `children` (32.18), so a layout puts the page
|
|
176
|
+
* in its `<slot/>` with the same mechanism a caller writing markup between two tags uses — and
|
|
177
|
+
* the chain learns where that slot is on the way past, which is what lets it swap the page
|
|
178
|
+
* there later without the layout knowing.
|
|
179
|
+
*
|
|
180
|
+
* THE CHILD'S SCOPE IS THE CHAIN'S, not the layout's, so a kept layout cannot stop the page
|
|
181
|
+
* arriving under it. What `mount` hands the layout is a stop for WHATEVER STANDS IN THE SLOT
|
|
182
|
+
* when the slot's region leaves — a `<slot/>` inside a closed `{#if}` — rather than for the
|
|
183
|
+
* page that happened to be mounted first. And the slot mounts from `modules`, so a slot that
|
|
184
|
+
* opens again mounts the page the address resolves to now.
|
|
185
|
+
*/
|
|
186
|
+
private mountFrom(at: number, region: Region): void {
|
|
187
|
+
setUpLevels(
|
|
188
|
+
at,
|
|
189
|
+
this.modules.length,
|
|
190
|
+
this.levels[at - 1]?.share,
|
|
191
|
+
ROUTE_PROPS,
|
|
192
|
+
(below, props) => this.setUp(below, region, props),
|
|
193
|
+
)
|
|
194
|
+
this.markUp(at, region)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Runs level `at`'s setup over `props`, leaving its markup on the level for `markUp`, and
|
|
199
|
+
* answers the share scope the setup left.
|
|
200
|
+
*/
|
|
201
|
+
private setUp(
|
|
202
|
+
at: number,
|
|
203
|
+
region: Region,
|
|
204
|
+
props: Record<string, (() => unknown) | undefined>,
|
|
205
|
+
): ShareScope | null {
|
|
206
|
+
const module = this.modules[at] as PageModule
|
|
207
|
+
const level = new Level(this)
|
|
208
|
+
this.levels[at] = level
|
|
209
|
+
if (at < this.modules.length - 1)
|
|
210
|
+
props.children = () => ({
|
|
211
|
+
// NO BUILD, because the level below carries its own (32.17): a module a route
|
|
212
|
+
// reaches fills the region it is mounted against when that region is empty, so
|
|
213
|
+
// the build and the mount are one call rather than two.
|
|
214
|
+
build: undefined,
|
|
215
|
+
mount: (slot: Region): Stoppable => {
|
|
216
|
+
// The level below as `mountFrom` set it up, the first time its slot opens; a
|
|
217
|
+
// slot opening again sets it up afresh.
|
|
218
|
+
const pending = this.levels[at + 1]?.markup !== undefined
|
|
219
|
+
if (!pending) this.release(level, at)
|
|
220
|
+
level.slot = slot
|
|
221
|
+
if (pending) this.markUp(at + 1, slot)
|
|
222
|
+
else this.mountFrom(at + 1, slot)
|
|
223
|
+
return { stop: () => this.release(level, at) }
|
|
224
|
+
},
|
|
225
|
+
})
|
|
226
|
+
// The region a level's markup lands in is its slot above, known only once that markup
|
|
227
|
+
// runs; a module mounted by the chain leaves its markup rather than using this one.
|
|
228
|
+
// A deferred level answers the scope its setup made, so the watches that setup opened are
|
|
229
|
+
// stopped with the level even where its markup never runs — a slot that never opens.
|
|
230
|
+
level.scope = level.mount(() => module.default(region, props, level))
|
|
231
|
+
return level.share
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Runs the markup level `at`'s setup left, into `region`. */
|
|
235
|
+
private markUp(at: number, region: Region): void {
|
|
236
|
+
const level = this.levels[at] as Level
|
|
237
|
+
const markup = level.markup
|
|
238
|
+
if (markup === undefined) return
|
|
239
|
+
level.markup = undefined
|
|
240
|
+
level.scope = level.mount(() => markup(region))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Stops what stands below `level` and forgets its slot.
|
|
245
|
+
*
|
|
246
|
+
* A no-op once the level itself has been replaced, which is how a stop handed to a layout that
|
|
247
|
+
* has since left does nothing.
|
|
248
|
+
*/
|
|
249
|
+
private release(level: Level, at: number): void {
|
|
250
|
+
if (this.levels[at] !== level) return
|
|
251
|
+
level.slot = undefined
|
|
252
|
+
// 39.14, 39.16 — THE LEVELS BELOW LEAVE WITH THE SLOT'S REGION, and their head with them.
|
|
253
|
+
this.reconcile()
|
|
254
|
+
this.stopFrom(at + 1)
|
|
255
|
+
this.reconcile()
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* The props a routed page or layout is set up over, `children` aside: `params` as a live accessor.
|
|
261
|
+
*
|
|
262
|
+
* 33.4 — A PROP IS AN ACCESSOR. A page is mounted as any other component is, so its one prop is
|
|
263
|
+
* passed the way `renderComponent` passes a child's and the way `answerRequest` paints one: a
|
|
264
|
+
* function per key. Passed as the plain record it lowered to `params()` on a value that was not
|
|
265
|
+
* one, so every page with a `[param]` in its address hydrated dead — with its server markup
|
|
266
|
+
* already on screen, which is what kept it quiet.
|
|
267
|
+
*
|
|
268
|
+
* 33.5 — AND A LIVE ONE: `params` reads `route.params`, which every navigation writes. A layout
|
|
269
|
+
* kept across `/users/1` -> `/users/2` is not mounted again, so an accessor over the params it was
|
|
270
|
+
* mounted with went on answering user 1.
|
|
271
|
+
*/
|
|
272
|
+
const ROUTE_PROPS = { params: (): Params => route.params() }
|