@abide/abide 0.124.1 → 0.125.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/ABIDE.md +4 -0
- package/CHANGELOG.md +36 -0
- package/bin/abide.ts +3 -2
- package/docs/REGISTRY.md +66 -40
- package/docs/RULEBOOK.md +202 -18
- package/docs/abide.md +391 -67
- package/package.json +1 -1
- package/src/compiler/appMap.ts +14 -1
- package/src/compiler/declaredExports.ts +41 -0
- package/src/compiler/handlersUnder.ts +1 -0
- package/src/compiler/hookCalls.ts +1 -0
- package/src/compiler/projectShadow.ts +52 -2
- package/src/compiler/readsIn.ts +39 -34
- package/src/server/PageRender.ts +7 -8
- package/src/server/abideModules.ts +6 -0
- package/src/server/answerRequest.ts +115 -52
- package/src/server/answerSocket.ts +6 -5
- package/src/server/answerWorker.ts +34 -23
- package/src/server/assistant.ts +141 -35
- package/src/server/banner.ts +9 -5
- package/src/server/bridgeRelay.ts +3 -3
- package/src/server/callerOf.ts +5 -0
- package/src/server/claudeCode.ts +20 -11
- package/src/server/commands.ts +14 -7
- package/src/server/createApp.ts +65 -12
- package/src/server/devWorker.ts +18 -16
- package/src/server/generatedSurfaces.ts +6 -5
- package/src/server/handlerFileMount.ts +2 -0
- package/src/server/hooks.ts +54 -8
- package/src/server/index.ts +18 -4
- package/src/server/invokeHandler.ts +1 -1
- package/src/server/mountHandlers.ts +6 -0
- package/src/server/principal.ts +3 -2
- package/src/server/render.ts +11 -2
- package/src/server/requestScope.ts +61 -1
- package/src/server/responseRelay.ts +9 -3
- package/src/server/serveDev.ts +18 -3
- package/src/server/sqliteStore.ts +65 -0
- package/src/server/testMount.ts +1 -0
- package/src/shared/AssistantEvent.ts +40 -0
- package/src/shared/BrowserHooks.ts +73 -0
- package/src/shared/CURRENT_ROUTER.ts +3 -1
- package/src/shared/Issues.ts +11 -3
- package/src/shared/Middleware.ts +9 -9
- package/src/shared/UNEXPECTED.ts +1 -1
- package/src/shared/answerOf.ts +9 -4
- package/src/shared/imagePiece.ts +85 -0
- package/src/shared/index.ts +17 -0
- package/src/shared/isServer.ts +15 -0
- package/src/shared/navigate.ts +10 -3
- package/src/shared/onError.ts +51 -0
- package/src/shared/onRefusal.ts +47 -0
- package/src/shared/online.ts +5 -1
- package/src/shared/reactive/Keyed.ts +12 -1
- package/src/shared/reactive/Reactive.ts +53 -25
- package/src/shared/reactive/cellShape.ts +4 -3
- package/src/shared/reactive/poll.ts +49 -0
- package/src/shared/reactive/scoped.ts +10 -1
- package/src/shared/reactive/scoping.ts +14 -0
- package/src/shared/reactive/watch.ts +19 -8
- package/src/shared/resultsOf.ts +21 -0
- package/src/shared/route.ts +18 -4
- package/src/shared/rpc.ts +112 -26
- package/src/shared/seededDocument.ts +5 -6
- package/src/shared/storedValue.ts +32 -0
- package/src/shared/webStore.ts +54 -0
- package/src/ui/Router.ts +42 -9
- package/src/ui/outboxQueue.ts +1 -1
- package/src/ui/wire.ts +4 -4
- package/src/shared/KEEP_OPENED.ts +0 -7
- /package/src/{server → shared}/IMAGE_TYPES.ts +0 -0
package/src/shared/online.ts
CHANGED
|
@@ -7,12 +7,16 @@
|
|
|
7
7
|
// PURE, SO AN APP THAT NEVER NAMES `online` DOES NOT SHIP IT: the listeners are a side effect at
|
|
8
8
|
// load, and a top-level side effect keeps the module in the shared chunk whether anything reads it.
|
|
9
9
|
|
|
10
|
+
// A BODY RATHER THAN A VALUE where there is a window, so each scope's instance asks again: a test
|
|
11
|
+
// process has both a DOM and a server render, and the render's `online` is 23.8's (22.11).
|
|
12
|
+
|
|
13
|
+
import { isServer } from './isServer.ts'
|
|
10
14
|
import { ambient } from './reactive/index.ts'
|
|
11
15
|
|
|
12
16
|
/** Whether the caller can reach the app, as a `Reactive<boolean>`. */
|
|
13
17
|
export const online = /* @__PURE__ */ (() => {
|
|
14
18
|
if (typeof window === 'undefined') return ambient<boolean>(true)
|
|
15
|
-
const value = ambient<boolean>(navigator.onLine)
|
|
19
|
+
const value = ambient<boolean>(() => isServer() || navigator.onLine)
|
|
16
20
|
window.addEventListener('online', () => value.set(true))
|
|
17
21
|
window.addEventListener('offline', () => value.set(false))
|
|
18
22
|
return value
|
|
@@ -23,6 +23,7 @@ import { KEYED_BRAND } from './KEYED_BRAND.ts'
|
|
|
23
23
|
import { matchesPattern } from './matchesPattern.ts'
|
|
24
24
|
import { NotLanded } from './NotLanded.ts'
|
|
25
25
|
import { parameterDefaults } from './parameterDefaults.ts'
|
|
26
|
+
import { armPoll, refreshIdle, refuseBadPoll } from './poll.ts'
|
|
26
27
|
import {
|
|
27
28
|
abandonDeferred,
|
|
28
29
|
createReactive,
|
|
@@ -344,7 +345,8 @@ export function createKeyed<
|
|
|
344
345
|
// 11.45: A KEYED `ttl` IS THE LIFE OF AN ENTRY, not of a production inside one, so it is
|
|
345
346
|
// taken off the options every entry is built from — forwarded, it would build the
|
|
346
347
|
// unkeyed mechanism in every entry and evict nothing from this map.
|
|
347
|
-
|
|
348
|
+
// 7.20 — and `poll`, so one timer walks the entries rather than one per entry.
|
|
349
|
+
const { ttl, args, throttle, debounce, poll, ...rest } = options ?? {}
|
|
348
350
|
const gate = args as Schema<Args> | undefined
|
|
349
351
|
const entryOptions = rest
|
|
350
352
|
// 5.14, at the factory: a keyed value is capped one way or the other.
|
|
@@ -375,6 +377,15 @@ export function createKeyed<
|
|
|
375
377
|
for (const entry of entries.values()) unwatchStore(entry)
|
|
376
378
|
})
|
|
377
379
|
|
|
380
|
+
// 7.20, 7.23 — one timer over the entries held at each tick: an evicted entry is out of the map.
|
|
381
|
+
if (poll !== undefined) {
|
|
382
|
+
refuseBadPoll(`${label}()`, poll)
|
|
383
|
+
const stop = armPoll(poll, () => {
|
|
384
|
+
for (const entry of entries.values()) refreshIdle(entry)
|
|
385
|
+
})
|
|
386
|
+
if (owning()) owned(stop)
|
|
387
|
+
}
|
|
388
|
+
|
|
378
389
|
function askMembership(): void {
|
|
379
390
|
const reader = TRACKING.reader
|
|
380
391
|
if (reader !== null) trackProbe(membership, reader)
|
|
@@ -40,6 +40,7 @@ import { isThenable } from './isThenable.ts'
|
|
|
40
40
|
import { NO_INITIAL } from './NO_INITIAL.ts'
|
|
41
41
|
import { NotLanded } from './NotLanded.ts'
|
|
42
42
|
import { logOf, PatchLog, type PatchOp, sendInvalidate } from './patchLog.ts'
|
|
43
|
+
import { armPoll, refreshIdle, refuseBadPoll } from './poll.ts'
|
|
43
44
|
import { REACTIVE_BRAND } from './REACTIVE_BRAND.ts'
|
|
44
45
|
import { recordWrites } from './recordWrites.ts'
|
|
45
46
|
import { runInScope } from './scoping.ts'
|
|
@@ -55,7 +56,6 @@ import { STREAMING } from './STREAMING.ts'
|
|
|
55
56
|
import {
|
|
56
57
|
CHECK,
|
|
57
58
|
CLEAN,
|
|
58
|
-
detach,
|
|
59
59
|
DIRTY,
|
|
60
60
|
dropProbeReader,
|
|
61
61
|
newSubscriber,
|
|
@@ -147,6 +147,13 @@ export type ReactiveOptions<
|
|
|
147
147
|
* once — see `expiryFor`.
|
|
148
148
|
*/
|
|
149
149
|
ttl?: number
|
|
150
|
+
/**
|
|
151
|
+
* A refresh every this many milliseconds.
|
|
152
|
+
*
|
|
153
|
+
* RULEBOOK 7.20 through 7.25: `s.refresh` on a clock, skipped while the value is busy and
|
|
154
|
+
* stopped with the value. Inert on a value with no producer (7.24).
|
|
155
|
+
*/
|
|
156
|
+
poll?: number
|
|
150
157
|
/**
|
|
151
158
|
* The gate on the way in, run before `transform` on every way in.
|
|
152
159
|
*
|
|
@@ -1012,8 +1019,8 @@ function serve(cell: Cell, value: unknown): void {
|
|
|
1012
1019
|
// servable — `armNext` reads the deadline off a production that has landed, and from
|
|
1013
1020
|
// `produce` it would be reading one that had not.
|
|
1014
1021
|
if (cell.expiry !== null) armNext(cell, cell.expiry)
|
|
1015
|
-
// 8.8: every production that reaches a reader is written
|
|
1016
|
-
// identity gate collapsed never arrives here.
|
|
1022
|
+
// 8.8: every production that reaches a reader is written but the two `persist` withholds, and
|
|
1023
|
+
// nothing else is — a write the identity gate collapsed never arrives here.
|
|
1017
1024
|
if (cell.store !== null) persist(cell, cell.store, value)
|
|
1018
1025
|
}
|
|
1019
1026
|
|
|
@@ -1677,7 +1684,8 @@ function run(cell: Cell): void {
|
|
|
1677
1684
|
// where the cascade would try: `a` reads `b` and `b` reads `a`. Without this the
|
|
1678
1685
|
// recursion is unbounded and the failure is a stack overflow rather than a value.
|
|
1679
1686
|
if (cell.running) return
|
|
1680
|
-
// A released orphan read again (12.14) — see `tombstone`.
|
|
1687
|
+
// A released orphan read again (12.14) — see `tombstone`. A released stream's run is its next
|
|
1688
|
+
// start: the generator its body hands back is promoted onto the stream it already has (9.6).
|
|
1681
1689
|
if (!cell.alive) revive(cell as unknown as Subscriber)
|
|
1682
1690
|
cell.generation += 1
|
|
1683
1691
|
cell.state = CLEAN
|
|
@@ -1847,11 +1855,13 @@ function landOpening(cell: Cell): void {
|
|
|
1847
1855
|
* WHAT STARTS A PRODUCER, for the three callers that start one: construction, `refresh`
|
|
1848
1856
|
* and `invalidate`. A stream is dispatched HERE rather than inside `run`, which a recompute
|
|
1849
1857
|
* walks per node per write — and a stream is never recomputed, having no tracked source to
|
|
1850
|
-
* be marked by (14.13).
|
|
1858
|
+
* be marked by (14.13). A RELEASED stream (12.16) is the exception: it is owed a run, and the
|
|
1859
|
+
* run is what revives it and settles the debt, where a start here would leave the debt for the
|
|
1860
|
+
* next read to start it a second time.
|
|
1851
1861
|
*/
|
|
1852
1862
|
function startProducer(cell: Cell): void {
|
|
1853
1863
|
const stream = ownStream(cell)
|
|
1854
|
-
if (stream === null) run(cell)
|
|
1864
|
+
if (stream === null || !cell.alive) run(cell)
|
|
1855
1865
|
else startStream(cell, stream)
|
|
1856
1866
|
}
|
|
1857
1867
|
|
|
@@ -3203,11 +3213,19 @@ function settleRestore(
|
|
|
3203
3213
|
restore(cell, persisted, value)
|
|
3204
3214
|
return
|
|
3205
3215
|
}
|
|
3206
|
-
// A MISS IS THE VALUE WITHOUT A STORE: the body runs, or the initial value lands (8.3).
|
|
3216
|
+
// A MISS IS THE VALUE WITHOUT A STORE: the body runs, or the initial value lands (8.3). The
|
|
3217
|
+
// body's run is a production the store is filled from; the initial value is the declaration,
|
|
3218
|
+
// and writing it back on every miss is what a server render did to a browser's store (8.8).
|
|
3207
3219
|
if (cell.body !== null) startProducer(cell)
|
|
3208
3220
|
else if (initial === undefined) landOpening(cell)
|
|
3209
|
-
else if (initial !== NO_INITIAL)
|
|
3210
|
-
|
|
3221
|
+
else if (initial !== NO_INITIAL) {
|
|
3222
|
+
persisted.withheld = true
|
|
3223
|
+
try {
|
|
3224
|
+
open(cell, initial)
|
|
3225
|
+
} finally {
|
|
3226
|
+
persisted.withheld = false
|
|
3227
|
+
}
|
|
3228
|
+
} else endLoad(cell, false)
|
|
3211
3229
|
}
|
|
3212
3230
|
|
|
3213
3231
|
/**
|
|
@@ -3218,13 +3236,13 @@ function settleRestore(
|
|
|
3218
3236
|
* it back.
|
|
3219
3237
|
*/
|
|
3220
3238
|
function restore(cell: Cell, persisted: Persisted, value: unknown): void {
|
|
3221
|
-
persisted.
|
|
3239
|
+
persisted.withheld = true
|
|
3222
3240
|
try {
|
|
3223
3241
|
const stream = cell.stream
|
|
3224
3242
|
if (stream === null) produce(cell, value)
|
|
3225
3243
|
else restoreStream(cell, stream, value as unknown[])
|
|
3226
3244
|
} finally {
|
|
3227
|
-
persisted.
|
|
3245
|
+
persisted.withheld = false
|
|
3228
3246
|
}
|
|
3229
3247
|
}
|
|
3230
3248
|
|
|
@@ -3263,7 +3281,7 @@ function restoreStream(cell: Cell, stream: Stream, chunks: unknown[]): void {
|
|
|
3263
3281
|
* asked at all and the written value stands in its place. Asking took the value BACKWARDS and left
|
|
3264
3282
|
* it there: `s.set(99)` over a store still holding 1 leaves a `set` in flight, a `refresh` in the
|
|
3265
3283
|
* same turn read 1 and produced it, and the queued write then landed 99 in the store — the cell
|
|
3266
|
-
* serving 1 and the store holding 99 for the life of the value, with `
|
|
3284
|
+
* serving 1 and the store holding 99 for the life of the value, with `withheld` stopping the
|
|
3267
3285
|
* reverted value from being written back and no probe moving to say so.
|
|
3268
3286
|
*/
|
|
3269
3287
|
function rereadStore(cell: Cell, persisted: Persisted): void {
|
|
@@ -3353,13 +3371,12 @@ function settleMiss(cell: Cell): void {
|
|
|
3353
3371
|
}
|
|
3354
3372
|
|
|
3355
3373
|
/**
|
|
3356
|
-
* Writes a production to the store, unless it
|
|
3374
|
+
* Writes a production to the store, unless 8.8 withholds it or it is a stream's.
|
|
3357
3375
|
*
|
|
3358
|
-
* A
|
|
3359
|
-
* persisted from here.
|
|
3376
|
+
* A stream is written at its close (8.9), not from here.
|
|
3360
3377
|
*/
|
|
3361
3378
|
function persist(cell: Cell, persisted: Persisted, value: unknown): void {
|
|
3362
|
-
if (persisted.
|
|
3379
|
+
if (persisted.withheld || cell.stream !== null) return
|
|
3363
3380
|
writeStore(cell, persisted, value)
|
|
3364
3381
|
}
|
|
3365
3382
|
|
|
@@ -3874,22 +3891,22 @@ function orphan(cell: Cell): void {
|
|
|
3874
3891
|
* Left subscribed, a value a dropped row built stayed on its sources' reader lists for the life of
|
|
3875
3892
|
* the page, and an adopting one's mirror ran on every write to what it adopted. Released, it holds
|
|
3876
3893
|
* what it last served; a read that reaches it later — it escaped its owner — runs the body again
|
|
3877
|
-
* and subscribes afresh, so it is never stale, and from then on it is nobody's to release.
|
|
3878
|
-
* stream is stopped
|
|
3894
|
+
* and subscribes afresh, so it is never stale, and from then on it is nobody's to release. 12.16 — a
|
|
3895
|
+
* stream's run is stopped and it is owed the same run: the body's next start (9.6), which serves
|
|
3896
|
+
* what the stopped one held until it says something (9.7) — a stream nothing pumps is the one value
|
|
3897
|
+
* a later read could never bring current.
|
|
3879
3898
|
*/
|
|
3880
3899
|
function releaseOrphan(cell: Cell): void {
|
|
3881
3900
|
cell.orphaned = false
|
|
3882
|
-
const stream = cell.stream
|
|
3883
3901
|
const own = ownStream(cell)
|
|
3884
3902
|
if (own !== null) stopStream(own)
|
|
3885
3903
|
dropAdoption(cell)
|
|
3886
3904
|
cell.aborted = false
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
} else detach(cell as unknown as Subscriber)
|
|
3905
|
+
// OWED A RUN, so a read that reaches it again runs it — and `run` revives it first. No `body`
|
|
3906
|
+
// test: only a value with one is ever orphaned (`createReactive`).
|
|
3907
|
+
tombstone(cell as unknown as Subscriber)
|
|
3908
|
+
cell.state = DIRTY
|
|
3909
|
+
cell.direct = false
|
|
3893
3910
|
}
|
|
3894
3911
|
|
|
3895
3912
|
RELEASING.unread = (source: Tracked): void => releaseOrphan(source as Cell)
|
|
@@ -3987,5 +4004,16 @@ export function createReactive<
|
|
|
3987
4004
|
else if (initial !== NO_INITIAL) open(cell, initial)
|
|
3988
4005
|
} else if (deferred) cell.loading = true
|
|
3989
4006
|
else startProducer(cell)
|
|
4007
|
+
// 7.20, 7.23, 7.24 — a `Keyed` takes `poll` off its entries' options and polls them itself.
|
|
4008
|
+
const poll = options?.poll
|
|
4009
|
+
if (poll !== undefined) {
|
|
4010
|
+
refuseBadPoll(label, poll)
|
|
4011
|
+
if (reloads(cell)) {
|
|
4012
|
+
const stop = armPoll(poll, () =>
|
|
4013
|
+
refreshIdle(self as unknown as Reactive<unknown>),
|
|
4014
|
+
)
|
|
4015
|
+
if (owning()) owned(stop)
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
3990
4018
|
return self as unknown as Reactive<Stored, Accepted, Failures, Produced>
|
|
3991
4019
|
}
|
|
@@ -102,9 +102,10 @@ export type Persisted = {
|
|
|
102
102
|
/** The value's own `ttl`, taken once, and infinity where it declares none (8.13). */
|
|
103
103
|
ttl: number
|
|
104
104
|
/**
|
|
105
|
-
* Set while a
|
|
105
|
+
* Set while a production 8.8 does not write back is minted: a restore, which is what was just
|
|
106
|
+
* read, and the initial value a miss falls back to, which the store never held.
|
|
106
107
|
*/
|
|
107
|
-
|
|
108
|
+
withheld: boolean
|
|
108
109
|
writing: boolean
|
|
109
110
|
pending: boolean
|
|
110
111
|
/**
|
|
@@ -601,7 +602,7 @@ export class Cell {
|
|
|
601
602
|
? store(args as never)
|
|
602
603
|
: store) as Store<unknown>,
|
|
603
604
|
ttl: options?.ttl ?? Number.POSITIVE_INFINITY,
|
|
604
|
-
|
|
605
|
+
withheld: false,
|
|
605
606
|
writing: false,
|
|
606
607
|
pending: false,
|
|
607
608
|
unconfirmed: undefined,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// A REFRESH ON A CLOCK. RULEBOOK 7.20 through 7.25: `poll` is `s.refresh` (7.4) run every `poll`
|
|
2
|
+
// milliseconds, which is what an app otherwise writes as a `setInterval` in `onStart` and a
|
|
3
|
+
// `clearInterval` in `onStop` — and then has to remember for every value, keyed entry and request.
|
|
4
|
+
//
|
|
5
|
+
// NO FIELD ON THE CELL. The timer's stop is handed to whatever ends the value — its owner, or its
|
|
6
|
+
// `Keyed` — so a value declaring no `poll` carries nothing for it, and the read and write paths
|
|
7
|
+
// never see it. A `Keyed` polls with ONE timer over the entries it holds at each tick, so an entry
|
|
8
|
+
// its `ttl` evicted is out of the walk by being out of the map.
|
|
9
|
+
|
|
10
|
+
import { LONGEST_DELAY } from '../LONGEST_DELAY.ts'
|
|
11
|
+
import type { Disposer } from './watch.ts'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Throws where `poll` is not a positive number of milliseconds (7.25).
|
|
15
|
+
*
|
|
16
|
+
* At construction, as 5.14 is: a zero or negative interval is a refresh per turn, and the app
|
|
17
|
+
* still has a stack for it here.
|
|
18
|
+
*/
|
|
19
|
+
export function refuseBadPoll(label: string, poll: number | undefined): void {
|
|
20
|
+
if (poll !== undefined && !(poll > 0))
|
|
21
|
+
throw new Error(
|
|
22
|
+
`${label} declares poll: ${poll}. A poll is a positive number of milliseconds.`,
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Runs `tick` every `poll` milliseconds, never holding the process open, and answers its stop.
|
|
28
|
+
*
|
|
29
|
+
* 7.22 FOLLOWS 6.4's FEATURE TEST: Bun's `Timer` carries `unref`, a browser's number does not and
|
|
30
|
+
* has no process to hold. `Infinity` arms nothing; a finite interval past what a timer holds runs
|
|
31
|
+
* at the longest one it does.
|
|
32
|
+
*/
|
|
33
|
+
export function armPoll(poll: number, tick: () => void): Disposer {
|
|
34
|
+
if (poll === Number.POSITIVE_INFINITY) return () => {}
|
|
35
|
+
const timer = setInterval(tick, Math.min(poll, LONGEST_DELAY))
|
|
36
|
+
const unref = (timer as { unref?: () => void }).unref
|
|
37
|
+
if (typeof unref === 'function') unref.call(timer)
|
|
38
|
+
return () => clearInterval(timer)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Refreshes `value` unless it is busy (7.21).
|
|
43
|
+
*
|
|
44
|
+
* A refresh over a load in flight supersedes it, so a producer slower than its interval would
|
|
45
|
+
* otherwise never land.
|
|
46
|
+
*/
|
|
47
|
+
export function refreshIdle(value: { busy(): boolean; refresh(): void }): void {
|
|
48
|
+
if (!value.busy()) value.refresh()
|
|
49
|
+
}
|
|
@@ -20,6 +20,7 @@ import { brandedPrototype, MEMBER_KEYS, signalMoved } from './Reactive.ts'
|
|
|
20
20
|
import { currentScope, runInScope, SCOPING } from './scoping.ts'
|
|
21
21
|
import { SELECTION_MEMBERS } from './SELECTION_MEMBERS.ts'
|
|
22
22
|
import { newTracked, type Tracked, TRACKING, trackProbe } from './tracking.ts'
|
|
23
|
+
import { owning, unowned } from './watch.ts'
|
|
23
24
|
|
|
24
25
|
const UNBUILT: unique symbol = Symbol('abide.unbuilt')
|
|
25
26
|
|
|
@@ -38,6 +39,11 @@ class Filed {
|
|
|
38
39
|
home: unknown
|
|
39
40
|
make: () => unknown
|
|
40
41
|
global: boolean
|
|
42
|
+
/**
|
|
43
|
+
* 12.17 — whether the declaration had an owner. A home declared with none — a module's, which is
|
|
44
|
+
* every handler's — is the process's, and is built as one wherever it is first reached.
|
|
45
|
+
*/
|
|
46
|
+
declaredOwned: boolean
|
|
41
47
|
/**
|
|
42
48
|
* 3.3, 13.19 — what a probe asked before the value was built here subscribes to, moved by the
|
|
43
49
|
* build: the home's. A scope files its own in `instances` under this one as its key, and drops
|
|
@@ -49,6 +55,7 @@ class Filed {
|
|
|
49
55
|
this.make = make
|
|
50
56
|
this.home = lazy ? UNBUILT : make()
|
|
51
57
|
this.global = global
|
|
58
|
+
this.declaredOwned = owning()
|
|
52
59
|
this.unbuilt = newTracked()
|
|
53
60
|
}
|
|
54
61
|
}
|
|
@@ -66,7 +73,9 @@ function instanceOf(filed: Filed): Record<PropertyKey, unknown> {
|
|
|
66
73
|
const scope = filed.global ? undefined : currentScope()
|
|
67
74
|
if (scope === undefined) {
|
|
68
75
|
if (filed.home === UNBUILT) {
|
|
69
|
-
filed.home =
|
|
76
|
+
filed.home = filed.declaredOwned
|
|
77
|
+
? runInScope(undefined, filed.make, undefined)
|
|
78
|
+
: unowned(() => runInScope(undefined, filed.make, undefined))
|
|
70
79
|
signalMoved(filed.unbuilt)
|
|
71
80
|
}
|
|
72
81
|
return filed.home as Record<PropertyKey, unknown>
|
|
@@ -25,6 +25,14 @@ export type Scope = {
|
|
|
25
25
|
|
|
26
26
|
export type Scoping = {
|
|
27
27
|
current(): Scope | undefined
|
|
28
|
+
/**
|
|
29
|
+
* The stops of the server work running — a request's scope, or a `render` outside every request —
|
|
30
|
+
* or `undefined` where none is.
|
|
31
|
+
*
|
|
32
|
+
* 12.5 — what a `watch` opened in that work is stopped with. 22.11 — and whether the work running
|
|
33
|
+
* is a server's at all.
|
|
34
|
+
*/
|
|
35
|
+
serverStops(): (() => void)[] | undefined
|
|
28
36
|
/**
|
|
29
37
|
* Runs `body` with `scope` as the current one, and `undefined` as no scope at all.
|
|
30
38
|
*
|
|
@@ -46,6 +54,12 @@ export function currentScope(): Scope | undefined {
|
|
|
46
54
|
return provider === null ? undefined : provider.current()
|
|
47
55
|
}
|
|
48
56
|
|
|
57
|
+
/** What a `watch` opened now is stopped with on a server — 12.5 — or `undefined` in a browser. */
|
|
58
|
+
export function serverStops(): (() => void)[] | undefined {
|
|
59
|
+
const provider = SCOPING.provider
|
|
60
|
+
return provider === null ? undefined : provider.serverStops()
|
|
61
|
+
}
|
|
62
|
+
|
|
49
63
|
/**
|
|
50
64
|
* Runs `body` under `scope` and returns what it returns.
|
|
51
65
|
*
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// imports the narrowed form from here. A second argument is the author saying which form
|
|
12
12
|
// they meant, and it needs nothing from the other module but a type.
|
|
13
13
|
|
|
14
|
+
import { isServer } from '../isServer.ts'
|
|
14
15
|
import { namedLogger } from '../log.ts'
|
|
15
16
|
import { UNEXPECTED } from '../UNEXPECTED.ts'
|
|
16
17
|
import { WORK } from '../WORK.ts'
|
|
@@ -18,7 +19,7 @@ import { CELL } from './CELL.ts'
|
|
|
18
19
|
import type { Callable } from './cellShape.ts'
|
|
19
20
|
import { NotLanded } from './NotLanded.ts'
|
|
20
21
|
import type { Reactive } from './Reactive.ts'
|
|
21
|
-
import { currentScope, runInScope } from './scoping.ts'
|
|
22
|
+
import { currentScope, runInScope, serverStops } from './scoping.ts'
|
|
22
23
|
import {
|
|
23
24
|
detach,
|
|
24
25
|
newSubscriber,
|
|
@@ -49,9 +50,10 @@ function reportThrow(thrown: unknown, message: string): void {
|
|
|
49
50
|
/**
|
|
50
51
|
* Reports an effect's throw, then re-throws it in a browser.
|
|
51
52
|
*
|
|
52
|
-
* 12.7 — A BROWSER
|
|
53
|
-
*
|
|
54
|
-
*
|
|
53
|
+
* 12.7 — A BROWSER'S THROW GOES ON OUT to surface as an uncaught error: to the caller of `watch` on
|
|
54
|
+
* a first run, and on a rerun to the drain, which reports it and runs the rest of its queue
|
|
55
|
+
* (`drainPending`) — and its `onError`, handed it already, is not handed it again (`onError.ts`).
|
|
56
|
+
* A server's has been handed it, and re-thrown there it would
|
|
55
57
|
* fail the handler or the render whose write woke this effect — a second report of one failure,
|
|
56
58
|
* against code that did not fail.
|
|
57
59
|
*/
|
|
@@ -60,7 +62,8 @@ function effectThrew(thrown: unknown): void {
|
|
|
60
62
|
thrown,
|
|
61
63
|
'an effect threw, and its watch is stopped. RULEBOOK 12.6, 12.8.',
|
|
62
64
|
)
|
|
63
|
-
|
|
65
|
+
// 22.11, NOT `typeof document`: a test process's in-process render has one, and re-threw.
|
|
66
|
+
if (!isServer()) throw thrown
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
/**
|
|
@@ -104,16 +107,24 @@ export function collectWatches(
|
|
|
104
107
|
* Hands `dispose` to the setup being collected, if one is, and to the scope it was opened in, if
|
|
105
108
|
* there is one; answers it.
|
|
106
109
|
*
|
|
107
|
-
* 12.5 — the scope is a server's request (`
|
|
110
|
+
* 12.5 — the scope is a server's request, or its render outside every request (`serverStops`),
|
|
111
|
+
* which a browser never has.
|
|
108
112
|
*/
|
|
109
113
|
export function owned(dispose: Disposer): Disposer {
|
|
110
114
|
if (OWNING.free) return dispose
|
|
111
115
|
const stops = OWNING.stops
|
|
112
116
|
if (stops !== undefined) stops.push(dispose)
|
|
113
|
-
|
|
117
|
+
serverStops()?.push(dispose)
|
|
114
118
|
return dispose
|
|
115
119
|
}
|
|
116
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Whether a component's setup is being collected now: a browser's, since a server's never is.
|
|
123
|
+
*/
|
|
124
|
+
export function settingUp(): boolean {
|
|
125
|
+
return OWNING.stops !== undefined
|
|
126
|
+
}
|
|
127
|
+
|
|
117
128
|
/**
|
|
118
129
|
* Whether something opened now has an owner to be handed to — a setup being collected, or a scope.
|
|
119
130
|
*
|
|
@@ -123,7 +134,7 @@ export function owned(dispose: Disposer): Disposer {
|
|
|
123
134
|
export function owning(): boolean {
|
|
124
135
|
return (
|
|
125
136
|
!OWNING.free &&
|
|
126
|
-
(OWNING.stops !== undefined ||
|
|
137
|
+
(OWNING.stops !== undefined || serverStops() !== undefined)
|
|
127
138
|
)
|
|
128
139
|
}
|
|
129
140
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AssistantEvent } from './AssistantEvent.ts'
|
|
2
|
+
import type { Rpc } from './rpc.ts'
|
|
3
|
+
|
|
4
|
+
/** What every call to `handler` in `events` answered, in order, a failed call's left out. 44.51. */
|
|
5
|
+
export function resultsOf<Value>(
|
|
6
|
+
events: readonly AssistantEvent[],
|
|
7
|
+
// Any handler at all: its args, failures and production are no part of what it answered.
|
|
8
|
+
handler: Rpc<Value, never, unknown, unknown>,
|
|
9
|
+
): Value[] {
|
|
10
|
+
const values: Value[] = []
|
|
11
|
+
for (const event of events)
|
|
12
|
+
if (
|
|
13
|
+
event.type === 'result' &&
|
|
14
|
+
!event.failed &&
|
|
15
|
+
event.handler !== undefined &&
|
|
16
|
+
event.handler.method === handler.method &&
|
|
17
|
+
event.handler.address === handler.address
|
|
18
|
+
)
|
|
19
|
+
values.push(event.value as Value)
|
|
20
|
+
return values
|
|
21
|
+
}
|
package/src/shared/route.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 23.1 — WHERE WE ARE, as
|
|
1
|
+
// 23.1 — WHERE WE ARE, as five values rather than one over a composite, so a reader of `params`
|
|
2
2
|
// does not wake for a hash change and a progress bar reading `navigating` does not wake for either.
|
|
3
3
|
//
|
|
4
4
|
// FILLED BY WHOEVER RESOLVES THE ADDRESS: the client router per navigation, and a server's page lane
|
|
@@ -7,23 +7,37 @@
|
|
|
7
7
|
import type { Params } from './matchRoute.ts'
|
|
8
8
|
import { ambient } from './reactive/index.ts'
|
|
9
9
|
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* The current address: its `url`, `params`, pattern `name`, its `index` in this document's history,
|
|
12
|
+
* and whether it is `navigating`.
|
|
13
|
+
*/
|
|
11
14
|
export const route = {
|
|
12
15
|
url: ambient<URL>(),
|
|
13
16
|
params: ambient<Params>(),
|
|
14
17
|
/** 23.6 — the resolution path: the literal the address resolved to, `/invoices/[id]`. */
|
|
15
18
|
name: ambient<string>(),
|
|
19
|
+
/**
|
|
20
|
+
* 23.28, 23.29 — the entry's place in the history this app's router wrote: 0 where a document
|
|
21
|
+
* load arrived, and one past the entry it was pushed from.
|
|
22
|
+
*/
|
|
23
|
+
index: ambient(0),
|
|
16
24
|
navigating: ambient(false),
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
/**
|
|
20
|
-
* Sets `route`'s url, params and
|
|
28
|
+
* Sets `route`'s url, params, name and index to an address just resolved.
|
|
21
29
|
*
|
|
22
30
|
* 23.2 — ONE `URL` PER NAVIGATION, built here as the route resolves, so a reader holding the last
|
|
23
31
|
* one is not handed a value that moved under it.
|
|
24
32
|
*/
|
|
25
|
-
export function arrive(
|
|
33
|
+
export function arrive(
|
|
34
|
+
at: string,
|
|
35
|
+
pattern: string,
|
|
36
|
+
params: Params,
|
|
37
|
+
index: number,
|
|
38
|
+
): void {
|
|
26
39
|
route.url.set(new URL(at))
|
|
27
40
|
route.params.set(params)
|
|
28
41
|
route.name.set(pattern)
|
|
42
|
+
route.index.set(index)
|
|
29
43
|
}
|