@abide/abide 0.39.0 → 0.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +318 -146
- package/CHANGELOG.md +89 -0
- package/README.md +77 -79
- package/package.json +1 -1
- package/src/lib/server/rpc/parseArgs.ts +10 -1
- package/src/lib/server/rpc/runWithVerbTimeout.ts +7 -9
- package/src/lib/server/rpc/types/VerbHelper.ts +18 -21
- package/src/lib/server/runtime/SSR_SWAP_SCRIPT.ts +8 -7
- package/src/lib/server/sockets/createSocketDispatcher.ts +12 -3
- package/src/lib/server/sockets/defineSocket.ts +3 -1
- package/src/lib/shared/REF_JSON_HEADER.ts +9 -0
- package/src/lib/shared/REF_JSON_TAGS.ts +31 -0
- package/src/lib/shared/assertExhaustive.ts +14 -0
- package/src/lib/shared/buildRpcRequest.ts +8 -1
- package/src/lib/shared/decodeRefJson.ts +110 -0
- package/src/lib/shared/encodeRefJson.ts +106 -0
- package/src/lib/test/createTestSocketChannel.ts +6 -2
- package/src/lib/ui/compile/TS_PRINTER.ts +7 -0
- package/src/lib/ui/compile/analyzeComponent.ts +15 -16
- package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +66 -0
- package/src/lib/ui/compile/assertTranspiles.ts +19 -0
- package/src/lib/ui/compile/compileModule.ts +56 -34
- package/src/lib/ui/compile/compileSSR.ts +1 -3
- package/src/lib/ui/compile/compileShadow.ts +14 -7
- package/src/lib/ui/compile/desugarSignals.ts +168 -107
- package/src/lib/ui/compile/generateBuild.ts +49 -59
- package/src/lib/ui/compile/generateSSR.ts +30 -21
- package/src/lib/ui/compile/hoistCells.ts +2 -2
- package/src/lib/ui/compile/isWhitespaceText.ts +11 -0
- package/src/lib/ui/compile/lowerContext.ts +23 -10
- package/src/lib/ui/compile/lowerDocAccess.ts +29 -3
- package/src/lib/ui/compile/lowerScript.ts +64 -0
- package/src/lib/ui/compile/parseTemplate.ts +74 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +160 -90
- package/src/lib/ui/compile/resolveBranches.ts +21 -0
- package/src/lib/ui/compile/stripEffects.ts +22 -17
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +5 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +12 -2
- package/src/lib/ui/createScope.ts +14 -0
- package/src/lib/ui/dom/appendText.ts +2 -3
- package/src/lib/ui/dom/appendTextAt.ts +2 -3
- package/src/lib/ui/dom/applyResolved.ts +5 -8
- package/src/lib/ui/dom/awaitBlock.ts +22 -2
- package/src/lib/ui/dom/each.ts +4 -0
- package/src/lib/ui/dom/on.ts +7 -0
- package/src/lib/ui/dom/parseRawNodes.ts +17 -0
- package/src/lib/ui/dom/switchBlock.ts +4 -0
- package/src/lib/ui/dom/when.ts +4 -0
- package/src/lib/ui/installInspectorBridge.ts +3 -1
- package/src/lib/ui/navigate.ts +28 -3
- package/src/lib/ui/renderToStream.ts +17 -9
- package/src/lib/ui/resumeSeedScript.ts +16 -6
- package/src/lib/ui/router.ts +108 -15
- package/src/lib/ui/runtime/NODE_STATE.ts +22 -0
- package/src/lib/ui/runtime/RESUME.ts +13 -6
- package/src/lib/ui/runtime/clientPage.ts +114 -9
- package/src/lib/ui/runtime/createComputedNode.ts +5 -3
- package/src/lib/ui/runtime/createEffectNode.ts +3 -1
- package/src/lib/ui/runtime/createSignalNode.ts +4 -2
- package/src/lib/ui/runtime/flushEffects.ts +8 -5
- package/src/lib/ui/runtime/historyEntries.ts +39 -1
- package/src/lib/ui/runtime/localStoragePersistence.ts +14 -8
- package/src/lib/ui/runtime/readNode.ts +8 -7
- package/src/lib/ui/runtime/runNode.ts +18 -2
- package/src/lib/ui/runtime/scope.ts +12 -1
- package/src/lib/ui/runtime/trigger.ts +40 -24
- package/src/lib/ui/runtime/types/ReactiveNode.ts +4 -1
- package/src/lib/ui/runtime/updateIfNecessary.ts +40 -0
- package/src/lib/ui/socketChannel.ts +5 -2
- package/src/lib/ui/tryEncodeResume.ts +20 -0
- package/src/lib/ui/types/Scope.ts +9 -3
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import { decodeRefJson } from '../../shared/decodeRefJson.ts'
|
|
2
|
+
import { encodeRefJson } from '../../shared/encodeRefJson.ts'
|
|
1
3
|
import type { PersistenceStore } from '../types/PersistenceStore.ts'
|
|
2
4
|
|
|
3
5
|
/*
|
|
4
|
-
The default `persist` backend: `localStorage` keyed by the persistence key,
|
|
5
|
-
|
|
6
|
-
live tree).
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
The default `persist` backend: `localStorage` keyed by the persistence key, using
|
|
7
|
+
the ref-json codec as the wire form (which also clones on decode, so a stored
|
|
8
|
+
snapshot can't alias the live tree). ref-json over plain JSON because a doc
|
|
9
|
+
snapshot can hold the types JSON silently coerces (Date), drops (undefined) or
|
|
10
|
+
throws on (bigint, cycles, shared references) — a throw here is a swallowed save,
|
|
11
|
+
i.e. silent persistence loss. Returns `undefined` where there is no `localStorage`
|
|
12
|
+
— the server, or a browser with storage disabled — which `persist` reads as "stay
|
|
13
|
+
inert". A corrupt or unreadable entry (including one written by an older JSON
|
|
14
|
+
build) loads as `undefined` rather than throwing, so one bad write can't wedge
|
|
15
|
+
boot; the next save rewrites it in ref-json form.
|
|
10
16
|
*/
|
|
11
17
|
export function localStoragePersistence(): PersistenceStore | undefined {
|
|
12
18
|
if (typeof localStorage === 'undefined') {
|
|
@@ -19,7 +25,7 @@ export function localStoragePersistence(): PersistenceStore | undefined {
|
|
|
19
25
|
return undefined
|
|
20
26
|
}
|
|
21
27
|
try {
|
|
22
|
-
return
|
|
28
|
+
return decodeRefJson(raw)
|
|
23
29
|
} catch {
|
|
24
30
|
return undefined
|
|
25
31
|
}
|
|
@@ -29,7 +35,7 @@ export function localStoragePersistence(): PersistenceStore | undefined {
|
|
|
29
35
|
it fires from a debounced flush / pagehide handler with no caller to catch it,
|
|
30
36
|
and a dropped persist must not crash the app. */
|
|
31
37
|
try {
|
|
32
|
-
localStorage.setItem(key,
|
|
38
|
+
localStorage.setItem(key, encodeRefJson(snapshot))
|
|
33
39
|
} catch {
|
|
34
40
|
// best-effort persistence
|
|
35
41
|
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { runNode } from './runNode.ts'
|
|
2
1
|
import { track } from './track.ts'
|
|
3
2
|
import type { ReactiveNode } from './types/ReactiveNode.ts'
|
|
3
|
+
import { updateIfNecessary } from './updateIfNecessary.ts'
|
|
4
4
|
|
|
5
5
|
/*
|
|
6
|
-
Reads a node's current value and subscribes the running observer to it. A
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
Reads a node's current value and subscribes the running observer to it. A computed
|
|
7
|
+
settles first (the lazy pull — `updateIfNecessary` refreshes only the deps that
|
|
8
|
+
changed, recomputing if any did); a signal has no compute and returns its stored
|
|
9
|
+
value directly. Tracking happens after the settle so the reader links to the
|
|
10
|
+
computed itself, not its transitive deps.
|
|
10
11
|
*/
|
|
11
12
|
export function readNode(node: ReactiveNode): unknown {
|
|
12
|
-
if (node.compute !== undefined
|
|
13
|
-
|
|
13
|
+
if (node.compute !== undefined) {
|
|
14
|
+
updateIfNecessary(node)
|
|
14
15
|
}
|
|
15
16
|
track(node)
|
|
16
17
|
return node.value
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { abortNode } from './abortNode.ts'
|
|
2
2
|
import { endTracking } from './endTracking.ts'
|
|
3
|
+
import { NODE_STATE } from './NODE_STATE.ts'
|
|
3
4
|
import { REACTIVE_CONTEXT } from './REACTIVE_CONTEXT.ts'
|
|
4
5
|
import { reactiveAbortState } from './reactiveAbortState.ts'
|
|
5
6
|
import type { ReactiveNode } from './types/ReactiveNode.ts'
|
|
@@ -26,8 +27,23 @@ export function runNode(node: ReactiveNode): unknown {
|
|
|
26
27
|
const previous = REACTIVE_CONTEXT.observer
|
|
27
28
|
REACTIVE_CONTEXT.observer = node
|
|
28
29
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
const next = node.compute?.()
|
|
31
|
+
/* Value memoisation: only a computed whose result actually changed marks its
|
|
32
|
+
subscribers DIRTY, so the in-progress check walk recomputes them. An
|
|
33
|
+
Object.is-equal recompute leaves them at CHECK — they settle back to CLEAN
|
|
34
|
+
on read without re-running, never waking downstream. An effect has no value
|
|
35
|
+
worth comparing and no subscribers, so this is a no-op for it (its body ran
|
|
36
|
+
inside compute above). The subscriber list isn't re-linked here — only its
|
|
37
|
+
members' `status` is bumped — so walking it live is safe. */
|
|
38
|
+
if (!Object.is(node.value, next)) {
|
|
39
|
+
node.value = next
|
|
40
|
+
let link = node.subsHead
|
|
41
|
+
while (link !== undefined) {
|
|
42
|
+
link.sub.status = NODE_STATE.DIRTY
|
|
43
|
+
link = link.nextSub
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
node.status = NODE_STATE.CLEAN
|
|
31
47
|
return node.value
|
|
32
48
|
} finally {
|
|
33
49
|
REACTIVE_CONTEXT.observer = previous
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import { OWNER } from './OWNER.ts'
|
|
2
|
+
import { untrack } from './untrack.ts'
|
|
2
3
|
|
|
3
4
|
/*
|
|
4
5
|
Runs `build` under a fresh ownership scope so every effect and listener created
|
|
5
6
|
inside is collected, and returns a disposer that tears them all down in reverse
|
|
6
7
|
order (children before parents). Save/restore of the previous owner makes scopes
|
|
7
8
|
nest — a list row's scope sits inside its component's scope.
|
|
9
|
+
|
|
10
|
+
The build runs UNTRACKED. A detached subtree is built synchronously, and when that
|
|
11
|
+
build happens inside a swap effect (a control-flow block re-running `each`/`when`/
|
|
12
|
+
`switch`/`await`) its top-level reads would otherwise subscribe THAT effect, so any
|
|
13
|
+
in-content state change would re-run the block and rebuild the whole subtree. The
|
|
14
|
+
content's own interpolations still track normally — each wraps its read in its own
|
|
15
|
+
effect, which re-installs itself as the observer. Untracking here is the framework's
|
|
16
|
+
invariant (a build never subscribes its builder) stated once, so no control-flow
|
|
17
|
+
block has to remember it; it's a harmless no-op for top-level builds (mount/hydrate),
|
|
18
|
+
where there is no surrounding observer to leak into.
|
|
8
19
|
*/
|
|
9
20
|
export function scope(build: () => void): () => void {
|
|
10
21
|
const previous = OWNER.current
|
|
11
22
|
const disposers: Array<() => void> = []
|
|
12
23
|
OWNER.current = disposers
|
|
13
24
|
try {
|
|
14
|
-
build
|
|
25
|
+
untrack(build)
|
|
15
26
|
} finally {
|
|
16
27
|
OWNER.current = previous
|
|
17
28
|
}
|
|
@@ -1,43 +1,59 @@
|
|
|
1
1
|
import { flushEffects } from './flushEffects.ts'
|
|
2
|
+
import { NODE_STATE } from './NODE_STATE.ts'
|
|
2
3
|
import { REACTIVE_CONTEXT } from './REACTIVE_CONTEXT.ts'
|
|
3
4
|
import type { ReactiveNode } from './types/ReactiveNode.ts'
|
|
4
5
|
|
|
5
6
|
/*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
`
|
|
14
|
-
|
|
15
|
-
defends with its own snapshot. `nextSub` is read before recursing, so the walk
|
|
16
|
-
holds no reference a downstream pass could invalidate.
|
|
7
|
+
Raises `node` to at least `status` and propagates the news up its subscriber cone.
|
|
8
|
+
An effect crossing out of CLEAN is queued (whether it became CHECK or DIRTY — the
|
|
9
|
+
flush decides whether it really runs). A computed crossing out of CLEAN propagates
|
|
10
|
+
CHECK to *its* subscribers, so the whole cone learns a dependency *might* have
|
|
11
|
+
changed; it does so exactly once (a later CHECK→DIRTY upgrade re-notifies nobody —
|
|
12
|
+
its subscribers are already CHECK). No compute runs here, so no subscriber list is
|
|
13
|
+
re-linked mid-walk (`track`/`runNode` run inside compute, which only the flush
|
|
14
|
+
reaches). `nextSub` is read before recursing so the walk holds no edge a deeper
|
|
15
|
+
pass could detach.
|
|
17
16
|
*/
|
|
18
|
-
function
|
|
17
|
+
function mark(node: ReactiveNode, status: number): void {
|
|
18
|
+
if (node.status >= status) {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
const wasClean = node.status === NODE_STATE.CLEAN
|
|
22
|
+
node.status = status
|
|
23
|
+
if (node.isEffect) {
|
|
24
|
+
if (wasClean) {
|
|
25
|
+
REACTIVE_CONTEXT.pendingEffects.add(node)
|
|
26
|
+
}
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
/* A computed propagates CHECK to its subscribers only on its first move out of
|
|
30
|
+
CLEAN — they are already CHECK on any later upgrade, so re-walking is wasted. */
|
|
31
|
+
if (!wasClean) {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
19
34
|
let link = node.subsHead
|
|
20
35
|
while (link !== undefined) {
|
|
21
|
-
const observer = link.sub
|
|
22
36
|
const next = link.nextSub
|
|
23
|
-
|
|
24
|
-
REACTIVE_CONTEXT.pendingEffects.add(observer)
|
|
25
|
-
} else if (!observer.dirty) {
|
|
26
|
-
observer.dirty = true
|
|
27
|
-
invalidate(observer)
|
|
28
|
-
}
|
|
37
|
+
mark(link.sub, NODE_STATE.CHECK)
|
|
29
38
|
link = next
|
|
30
39
|
}
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
/*
|
|
34
|
-
Propagates a change forward from a just-written
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
Propagates a change forward from a just-written signal: its direct subscribers read
|
|
44
|
+
a value that actually changed, so they are DIRTY; the rest of their cone is CHECK
|
|
45
|
+
(a transitive dependency *may* have changed — `updateIfNecessary` will verify on
|
|
46
|
+
read). Recompute is lazy. The queued effects flush once, at the outermost trigger
|
|
47
|
+
(or, inside a batch, when the batch owner flushes) — never mid-propagation, so an
|
|
48
|
+
effect never runs against a half-marked graph.
|
|
38
49
|
*/
|
|
39
50
|
export function trigger(node: ReactiveNode): void {
|
|
40
|
-
|
|
51
|
+
let link = node.subsHead
|
|
52
|
+
while (link !== undefined) {
|
|
53
|
+
const next = link.nextSub
|
|
54
|
+
mark(link.sub, NODE_STATE.DIRTY)
|
|
55
|
+
link = next
|
|
56
|
+
}
|
|
41
57
|
if (REACTIVE_CONTEXT.batchDepth === 0) {
|
|
42
58
|
flushEffects()
|
|
43
59
|
}
|
|
@@ -20,6 +20,9 @@ export type ReactiveNode = {
|
|
|
20
20
|
depsTail: ReactiveLink | undefined
|
|
21
21
|
subsHead: ReactiveLink | undefined
|
|
22
22
|
subsTail: ReactiveLink | undefined
|
|
23
|
-
|
|
23
|
+
/* The node's settle-state for push-pull propagation: CLEAN / CHECK / DIRTY (see
|
|
24
|
+
NODE_STATE). A signal is always CLEAN (no compute); a computed is born DIRTY and
|
|
25
|
+
cycles CLEAN→CHECK/DIRTY→CLEAN as deps change and reads settle it. */
|
|
26
|
+
status: number
|
|
24
27
|
isEffect: boolean
|
|
25
28
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { NODE_STATE } from './NODE_STATE.ts'
|
|
2
|
+
import { runNode } from './runNode.ts'
|
|
3
|
+
import type { ReactiveNode } from './types/ReactiveNode.ts'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Settles a node so its value is current before it is read (or, for an effect, before
|
|
7
|
+
it runs at flush). CLEAN: nothing to do. CHECK: a transitive dependency *might* have
|
|
8
|
+
changed — refresh each direct dependency in turn; refreshing one that truly changed
|
|
9
|
+
marks this node DIRTY (`runNode` does the marking), so stop the moment that happens.
|
|
10
|
+
DIRTY (set directly by a write, or by the check walk): recompute.
|
|
11
|
+
|
|
12
|
+
A CHECK node whose dependencies all recompute to equal values ends CLEAN without
|
|
13
|
+
recomputing — the value memoisation that stops an unchanged computed from waking its
|
|
14
|
+
readers. Refreshing deps top-down *before* a recompute reads them is what keeps the
|
|
15
|
+
pull glitch-free: a reader never observes a stale intermediate, because by the time
|
|
16
|
+
it recomputes, every dependency it reads has already settled this pass.
|
|
17
|
+
*/
|
|
18
|
+
export function updateIfNecessary(node: ReactiveNode): void {
|
|
19
|
+
if (node.status === NODE_STATE.CLEAN) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
if (node.status === NODE_STATE.CHECK) {
|
|
23
|
+
let link = node.depsHead
|
|
24
|
+
while (link !== undefined) {
|
|
25
|
+
updateIfNecessary(link.dep)
|
|
26
|
+
/* A dep recomputed to a changed value and marked us DIRTY — no need to
|
|
27
|
+
check the rest, we already know we must recompute. */
|
|
28
|
+
if (node.status === NODE_STATE.DIRTY) {
|
|
29
|
+
break
|
|
30
|
+
}
|
|
31
|
+
link = link.nextDep
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (node.status === NODE_STATE.DIRTY) {
|
|
35
|
+
runNode(node)
|
|
36
|
+
} else {
|
|
37
|
+
/* CHECK with no changed dependency — the cached value still holds. */
|
|
38
|
+
node.status = NODE_STATE.CLEAN
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { SocketClientFrame } from '../server/sockets/types/SocketClientFrame.ts'
|
|
2
2
|
import type { SocketServerFrame } from '../server/sockets/types/SocketServerFrame.ts'
|
|
3
3
|
import { createSocketSubRegistry } from '../shared/createSocketSubRegistry.ts'
|
|
4
|
+
import { decodeRefJson } from '../shared/decodeRefJson.ts'
|
|
5
|
+
import { encodeRefJson } from '../shared/encodeRefJson.ts'
|
|
4
6
|
import { SOCKETS_PATH } from '../shared/SOCKETS_PATH.ts'
|
|
5
7
|
import type { SocketChannel } from '../shared/types/SocketChannel.ts'
|
|
6
8
|
import { withBase } from '../shared/withBase.ts'
|
|
@@ -55,7 +57,8 @@ export function getSocketChannel(): SocketChannel {
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
function send(frame: SocketClientFrame): void {
|
|
58
|
-
|
|
60
|
+
// ref-json frame so a published message graph with cycles/shared refs survives the wire.
|
|
61
|
+
const message = encodeRefJson(frame)
|
|
59
62
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
60
63
|
ws.send(message)
|
|
61
64
|
return
|
|
@@ -90,7 +93,7 @@ export function getSocketChannel(): SocketChannel {
|
|
|
90
93
|
ws.addEventListener('message', (event) => {
|
|
91
94
|
let frame: SocketServerFrame
|
|
92
95
|
try {
|
|
93
|
-
frame =
|
|
96
|
+
frame = decodeRefJson(event.data) as SocketServerFrame
|
|
94
97
|
} catch {
|
|
95
98
|
return
|
|
96
99
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { encodeRefJson } from '../shared/encodeRefJson.ts'
|
|
2
|
+
import type { ResumeEntry } from './runtime/RESUME.ts'
|
|
3
|
+
|
|
4
|
+
/* ref-json-encode an await-resume entry, or `undefined` if it can't be serialized.
|
|
5
|
+
encodeRefJson is total (cycles become back-references, functions fold to undefined),
|
|
6
|
+
but a pathological throw must not blank the surrounding seed/stream — drop just this
|
|
7
|
+
entry and warn, so the client re-runs that one branch's promise while every other
|
|
8
|
+
branch stays seeded. Shared by the streaming (`renderToStream`) and buffered/seed
|
|
9
|
+
(`resumeSeedScript`) paths so the serialize-or-refetch policy lives in one place. */
|
|
10
|
+
export function tryEncodeResume(entry: ResumeEntry, id: number | string): string | undefined {
|
|
11
|
+
try {
|
|
12
|
+
return encodeRefJson(entry)
|
|
13
|
+
} catch (cause) {
|
|
14
|
+
console.warn(
|
|
15
|
+
`[abide] resume for await ${id} is not serializable; client will refetch it`,
|
|
16
|
+
cause,
|
|
17
|
+
)
|
|
18
|
+
return undefined
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -9,9 +9,9 @@ import type { SyncTransport } from './SyncTransport.ts'
|
|
|
9
9
|
A lexical scope: the unit that owns a region's reactive data, its lifetime, and
|
|
10
10
|
the capabilities applied to it. Its data surface MIRRORS `Doc` (read/replace/add/
|
|
11
11
|
remove/cell/derive/apply/snapshot) so the compiler can target a scope as a
|
|
12
|
-
component's data binding directly. It nests (`child`/`root`),
|
|
13
|
-
|
|
14
|
-
`<Child parentScope={scope} />`.
|
|
12
|
+
component's data binding directly. It nests (`child`/`root`), passes values
|
|
13
|
+
down the tree as context (`share`/`shared`), and carries the capability surface as
|
|
14
|
+
methods so a scope is a passable value: `<Child parentScope={scope} />`.
|
|
15
15
|
|
|
16
16
|
Capabilities route where the scope's changes go: `record()` to an undo journal,
|
|
17
17
|
`persist()` to durable storage, `broadcast()` to peers — declared once, then
|
|
@@ -46,6 +46,12 @@ export type Scope = {
|
|
|
46
46
|
/* tree */
|
|
47
47
|
child: (initial?: unknown) => Scope
|
|
48
48
|
root: () => Scope
|
|
49
|
+
/* context — values shared DOWN the tree (not in the reactive doc, which doesn't
|
|
50
|
+
inherit): `share` puts a named value on this scope; `shared` reads the closest
|
|
51
|
+
ancestor (self included) that has the key, undefined if none. The value is held
|
|
52
|
+
by reference, so reactive context = share a `cell`/scope, not a plain object. */
|
|
53
|
+
share: (key: string, value: unknown) => void
|
|
54
|
+
shared: <T>(key: string) => T | undefined
|
|
49
55
|
/* capabilities — enable where the scope's changes go */
|
|
50
56
|
record: (options?: { limit?: number }) => void
|
|
51
57
|
persist: (key?: string) => void
|