@abide/abide 0.123.3 → 0.124.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 +8 -5
- package/CHANGELOG.md +31 -0
- package/docs/REGISTRY.md +45 -12
- package/docs/RULEBOOK.md +24 -14
- package/docs/abide.md +999 -459
- package/package.json +1 -1
- package/src/compiler/ARRAY_MUTATORS.ts +20 -0
- package/src/compiler/LoweredFile.ts +609 -0
- package/src/compiler/Lowering.ts +17 -0
- package/src/compiler/SHARED_NAMES.ts +2 -0
- package/src/compiler/ShadowHost.ts +670 -0
- package/src/compiler/ShadowMap.ts +1 -1
- package/src/compiler/TypeSession.ts +71 -1715
- package/src/compiler/appLints.ts +109 -0
- package/src/compiler/checkedShadow.ts +1 -1
- package/src/compiler/classifyRead.ts +16 -1
- package/src/compiler/compileApp.ts +2 -2
- package/src/compiler/declaredExports.ts +44 -17
- package/src/compiler/diagnoseSources.ts +232 -0
- package/src/compiler/formatAbide.ts +5 -6
- package/src/compiler/handlersUnder.ts +16 -35
- package/src/compiler/reactiveReads.ts +235 -0
- package/src/compiler/reactiveTypes.ts +13 -0
- package/src/compiler/readsIn.ts +180 -81
- package/src/compiler/routesDeclaration.ts +46 -0
- package/src/compiler/scaffold.ts +7 -2
- package/src/compiler/serveLanguage.ts +26 -20
- package/src/compiler/splitImports.ts +2 -2
- package/src/server/DesktopSession.ts +213 -0
- package/src/server/Followers.ts +399 -0
- package/src/server/MODULE_QUERIES.ts +6 -2
- package/src/server/PageRender.ts +201 -0
- package/src/server/abideModules.ts +7 -7
- package/src/server/answerFailure.ts +3 -2
- package/src/server/answerRequest.ts +6 -70
- package/src/server/answerRpc.ts +48 -260
- package/src/server/answerSocket.ts +41 -463
- package/src/server/appKit.ts +1 -1
- package/src/server/argsContract.ts +1 -1
- package/src/server/createApp.ts +8 -2
- package/src/server/csp.ts +1 -1
- package/src/server/deferredBuilds.ts +3 -16
- package/src/server/faceOf.ts +1 -1
- package/src/server/generatedSurfaces.ts +2 -1
- package/src/server/handlerFileMount.ts +1 -1
- package/src/server/index.ts +1 -1
- package/src/server/installServer.ts +5 -9
- package/src/server/invokeHandler.ts +230 -0
- package/src/server/mountHandlers.ts +184 -0
- package/src/server/openDesktopWindow.ts +308 -0
- package/src/server/preload.ts +18 -0
- package/src/server/principal.ts +1 -1
- package/src/server/remote.ts +1 -1
- package/src/server/render.ts +31 -59
- package/src/server/requestScope.ts +5 -36
- package/src/server/runDesktop.ts +19 -447
- package/src/server/seedBuffer.ts +11 -11
- package/src/server/servedApp.ts +1 -1
- package/src/server/shell.ts +1 -1
- package/src/server/surfaceOf.ts +1 -1
- package/src/server/versionOf.ts +1 -1
- package/src/shared/RPC_SERVER.ts +27 -31
- package/src/shared/Schema.ts +1 -2
- package/src/shared/index.ts +1 -1
- package/src/shared/{note.ts → issueReport.ts} +22 -0
- package/src/shared/loadCall.ts +66 -0
- package/src/shared/reactive/Keyed.ts +23 -15
- package/src/shared/reactive/Reactive.ts +34 -1001
- package/src/shared/reactive/byOf.ts +2 -5
- package/src/shared/reactive/callLoad.ts +8 -14
- package/src/shared/reactive/cellShape.ts +734 -0
- package/src/shared/reactive/index.ts +12 -24
- package/src/shared/reactive/pairReactive.ts +5 -3
- package/src/shared/reactive/producer.ts +121 -0
- package/src/shared/reactive/scoped.ts +3 -2
- package/src/shared/reactive/selections.ts +209 -0
- package/src/shared/reactive/watch.ts +3 -4
- package/src/shared/rpc.ts +8 -5
- package/src/shared/validateJson.ts +1 -2
- package/src/shared/writePath.ts +29 -0
- package/src/ui/Router.ts +53 -69
- package/src/ui/outboxQueue.ts +2 -8
- package/src/ui/outcomeOf.ts +120 -0
- package/src/ui/socketWire.ts +3 -3
- package/src/ui/wire.ts +9 -10
- package/src/shared/LOAD_OFFLINE.ts +0 -15
- package/src/shared/LOAD_SIGNAL.ts +0 -10
- package/src/shared/issuesOf.ts +0 -18
- package/src/shared/reactive/ENTRY_LIVES.ts +0 -7
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// THE ONE FACTORY, and the barrel it is the address of. `state`, `memo` and `channel`
|
|
2
|
-
// collapsed into overloads on one name — a channel being a value
|
|
3
|
-
// refusal behind that split survives rather than being reversed: what it
|
|
4
|
-
// SIGNATURE WITH AN OPTIONAL PARAMETER, not one name, and overloads handing
|
|
5
|
-
// types is the shape its reason forces.
|
|
2
|
+
// collapsed into overloads on one name — a channel being a value over an array its `patch`
|
|
3
|
+
// appends to — and the refusal behind that split survives rather than being reversed: what it
|
|
4
|
+
// refused was ONE SIGNATURE WITH AN OPTIONAL PARAMETER, not one name, and overloads handing
|
|
5
|
+
// back two result types is the shape its reason forces.
|
|
6
6
|
//
|
|
7
7
|
// THE ORDER IS FORCED, and getting it wrong breaks the design silently: `() => C` is
|
|
8
8
|
// assignable to `(args: A) => C` but not the reverse, so keyed-first swallows the
|
|
@@ -38,33 +38,19 @@ import { share } from './share.ts'
|
|
|
38
38
|
export { isReactive } from './isReactive.ts'
|
|
39
39
|
export { isKeyed, type Keyed } from './Keyed.ts'
|
|
40
40
|
export { NotLanded } from './NotLanded.ts'
|
|
41
|
-
// The
|
|
42
|
-
// `log.ts` → this barrel → `scoped.ts` → `Reactive.ts` is a cycle, and a
|
|
43
|
-
//
|
|
41
|
+
// The rest of the framework reaches the core through here rather than `./Reactive.ts`: `Reactive.ts` →
|
|
42
|
+
// `log.ts` → this barrel → `scoped.ts` → `Reactive.ts` is a cycle, and a module loaded first that
|
|
43
|
+
// imports the core directly meets `MEMBER_KEYS` before it is initialized. The verbs a producer
|
|
44
|
+
// outside the core spends a value through are `producer.ts`'s, which comes through here first.
|
|
44
45
|
export {
|
|
45
|
-
abandonLoad,
|
|
46
|
-
chunksOfStream,
|
|
47
|
-
deadlineOf,
|
|
48
|
-
failedTransiently,
|
|
49
46
|
flushSync,
|
|
50
|
-
followServed,
|
|
51
|
-
heldGenerationOf,
|
|
52
47
|
type Identity,
|
|
53
|
-
isStreaming,
|
|
54
|
-
loadOf,
|
|
55
48
|
moveItem,
|
|
56
|
-
patchInto,
|
|
57
|
-
produceInto,
|
|
58
49
|
type Reactive,
|
|
59
50
|
type ReactiveOptions,
|
|
60
|
-
refuseInto,
|
|
61
51
|
rowItem,
|
|
62
52
|
type Store,
|
|
63
|
-
servedCellOf,
|
|
64
|
-
standIn,
|
|
65
|
-
streamingOrSettled,
|
|
66
53
|
type Transformer,
|
|
67
|
-
writtenLocally,
|
|
68
54
|
} from './Reactive.ts'
|
|
69
55
|
export { STREAMING } from './STREAMING.ts'
|
|
70
56
|
export {
|
|
@@ -551,7 +537,8 @@ export function refresh<Chosen extends Selection>(
|
|
|
551
537
|
(selection as { refresh(pattern: unknown): void }).refresh(pattern)
|
|
552
538
|
else {
|
|
553
539
|
refreshOver(sweep)
|
|
554
|
-
RPC_SERVER.
|
|
540
|
+
for (const followers of RPC_SERVER.following)
|
|
541
|
+
followers.sweep('refresh', sweep)
|
|
555
542
|
}
|
|
556
543
|
}
|
|
557
544
|
/** Marks the selection stale: drops the cache behind it, never the value. */
|
|
@@ -566,7 +553,8 @@ export function invalidate<Chosen extends Selection>(
|
|
|
566
553
|
)
|
|
567
554
|
else {
|
|
568
555
|
invalidateOver(sweep)
|
|
569
|
-
RPC_SERVER.
|
|
556
|
+
for (const followers of RPC_SERVER.following)
|
|
557
|
+
followers.sweep('invalidate', sweep)
|
|
570
558
|
}
|
|
571
559
|
}
|
|
572
560
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// callable it adopted.
|
|
10
10
|
|
|
11
11
|
import { CELL } from './CELL.ts'
|
|
12
|
+
import type { Callable } from './cellShape.ts'
|
|
12
13
|
import { reactive } from './index.ts'
|
|
13
14
|
import { brandedPrototype, type Reactive } from './Reactive.ts'
|
|
14
15
|
|
|
@@ -32,9 +33,10 @@ const PAIR_PROTOTYPE = brandedPrototype({
|
|
|
32
33
|
*/
|
|
33
34
|
export function pairReactive(pair: Pair): Reactive<unknown> {
|
|
34
35
|
const over = reactive(() => pair.get())
|
|
35
|
-
const callable = (over as unknown as
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
const callable = (over as unknown as Callable)[CELL]
|
|
37
|
+
.callable as unknown as {
|
|
38
|
+
[PAIR]: Pair
|
|
39
|
+
}
|
|
38
40
|
Object.setPrototypeOf(callable, PAIR_PROTOTYPE)
|
|
39
41
|
callable[PAIR] = pair
|
|
40
42
|
return over as Reactive<unknown>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// THE PRODUCER PORT: every verb a producer OUTSIDE the core spends a value through — the wire and
|
|
2
|
+
// the outbox writing what the server produced into a copy (`produceInto`, `patchInto`,
|
|
3
|
+
// `refuseInto`), a call ending or timing the load it reached (`loadOf`, `abandonLoad`), the server
|
|
4
|
+
// reading what a value serves to stamp, cache and follow it (`servedCellOf`, `deadlineOf`,
|
|
5
|
+
// `followServed`). None of them is a member an app can reach; this module is where the framework's
|
|
6
|
+
// other seams reach them, rather than the core itself or the app-facing barrel.
|
|
7
|
+
//
|
|
8
|
+
// What reads only the cell is here; what has to move the graph — a production, a refusal, a
|
|
9
|
+
// superseded load — is the core's own, and this module hands it on.
|
|
10
|
+
//
|
|
11
|
+
// THROUGH THE BARREL FIRST, for the cycle `index.ts` names: a module reaching this port ahead of
|
|
12
|
+
// the barrel would enter `Reactive.ts` first, and `scoped.ts` would meet `MEMBER_KEYS` before it is
|
|
13
|
+
// initialized. `isReactive` is taken from the barrel, ahead of `Keyed.ts` and `Reactive.ts`, for
|
|
14
|
+
// that reason — `cellShape.ts` has no edge into the cycle, so its place does not matter.
|
|
15
|
+
|
|
16
|
+
import { CELL } from './CELL.ts'
|
|
17
|
+
import { type Callable, type Cell, cellOf } from './cellShape.ts'
|
|
18
|
+
import { isReactive } from './index.ts'
|
|
19
|
+
import { entryDeadline } from './Keyed.ts'
|
|
20
|
+
import { holdLog } from './patchLog.ts'
|
|
21
|
+
import { ownStream, type Reactive } from './Reactive.ts'
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
abandonLoad,
|
|
25
|
+
chunksOfStream,
|
|
26
|
+
dropAnswered,
|
|
27
|
+
failedTransiently,
|
|
28
|
+
patchInto,
|
|
29
|
+
produceInto,
|
|
30
|
+
refuseInto,
|
|
31
|
+
standIn,
|
|
32
|
+
streamingOrSettled,
|
|
33
|
+
} from './Reactive.ts'
|
|
34
|
+
|
|
35
|
+
/** Whether `value` holds what was written here, without subscribing — for a caller that is not a reader. */
|
|
36
|
+
export function writtenLocally(value: Reactive<unknown>): boolean {
|
|
37
|
+
return cellOf(value).local
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The cell `value` serves from, through every adoption: its identity and its ordinal, and nothing
|
|
42
|
+
* else of it the server can reach.
|
|
43
|
+
*
|
|
44
|
+
* 11.12: an adopting value serves the adopted value's production, and an adoption of an adoption
|
|
45
|
+
* goes on down — so a follow's version (18.7) and its feed name the cell that actually produced.
|
|
46
|
+
*/
|
|
47
|
+
export function servedCellOf(value: unknown): { readonly ordinal: number } {
|
|
48
|
+
return adoptedCellOf(value)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Holds a patch log on the cell `value` serves from, with `invalidated` called on its every
|
|
53
|
+
* invalidate; returns what releases both. 18.11, 7.10.
|
|
54
|
+
*/
|
|
55
|
+
export function followServed(
|
|
56
|
+
value: unknown,
|
|
57
|
+
invalidated: () => void,
|
|
58
|
+
): () => void {
|
|
59
|
+
return holdLog(adoptedCellOf(value), invalidated)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* When the production `value` serves stops being servable, as a wall-clock time.
|
|
64
|
+
*
|
|
65
|
+
* 21.8 — WHEN THE PRODUCTION A VALUE IS SERVING STOPS BEING SERVABLE, as a wall-clock time: its
|
|
66
|
+
* own `ttl` from when it was minted (6.5), or its entry's from when the entry last produced
|
|
67
|
+
* (11.45), through whatever it adopts. Infinity where neither bounds it.
|
|
68
|
+
*/
|
|
69
|
+
export function deadlineOf(value: unknown): number {
|
|
70
|
+
const cell = adoptedCellOf(value)
|
|
71
|
+
if (cell.expiry !== null) return cell.expiry.at + cell.expiry.ms
|
|
72
|
+
return entryDeadline(cell)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The generation of the load `value` has in flight, or `undefined`.
|
|
77
|
+
*
|
|
78
|
+
* 16.48 — WHICH LOAD A VALUE HAS IN FLIGHT, as the generation that started it, or `undefined` where
|
|
79
|
+
* none is. Read off the cell rather than through `busy`, which is a probe and would subscribe the
|
|
80
|
+
* caller that asked (3.3).
|
|
81
|
+
*
|
|
82
|
+
* A WINDOW HOLDING A PRODUCTION IS BUSY AND IS NO LOAD (5.15): nothing is in flight for a call to end
|
|
83
|
+
* or time, and the window's close is its own bound. Told apart by the hold, which only a value with
|
|
84
|
+
* neither body nor stream makes — so a write's load taken in under a hold is not one a call times.
|
|
85
|
+
*/
|
|
86
|
+
export function loadOf(value: unknown): number | undefined {
|
|
87
|
+
const cell = cellOf(value)
|
|
88
|
+
if (!cell.loading) return undefined
|
|
89
|
+
if (cell.cap?.holding && cell.stream === null) return undefined
|
|
90
|
+
return cell.generation
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The generation `value`'s past stands at, or `undefined` while a load is in flight.
|
|
95
|
+
*
|
|
96
|
+
* What 21.15's `Cached` compares across a call, read through adoption as `servedCellOf` is.
|
|
97
|
+
*/
|
|
98
|
+
export function heldGenerationOf(value: unknown): number | undefined {
|
|
99
|
+
const cell = adoptedCellOf(value)
|
|
100
|
+
return cell.loading ? undefined : cell.generation
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Whether `value` is a streaming producer's `Reactive`.
|
|
105
|
+
*
|
|
106
|
+
* A streaming producer's own `Reactive` — a body declared as an async generator, or one whose run
|
|
107
|
+
* handed one back (9.6).
|
|
108
|
+
*/
|
|
109
|
+
export function isStreaming(value: unknown): boolean {
|
|
110
|
+
if (!isReactive(value)) return false
|
|
111
|
+
const stream = (value as Partial<Callable>)[CELL]?.stream
|
|
112
|
+
return stream !== null && stream !== undefined
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function adoptedCellOf(value: unknown): Cell {
|
|
116
|
+
let cell = cellOf(value)
|
|
117
|
+
// A stream's own `adoption` is a write it follows (9.11), and its past is its own.
|
|
118
|
+
while (cell.adoption !== null && ownStream(cell) === null)
|
|
119
|
+
cell = cell.adoption.source
|
|
120
|
+
return cell
|
|
121
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { contractOf, type Gated } from './argsContract.ts'
|
|
16
16
|
import { CELL } from './CELL.ts'
|
|
17
|
+
import type { Callable, Cell } from './cellShape.ts'
|
|
17
18
|
import { KEYED_BRAND } from './KEYED_BRAND.ts'
|
|
18
19
|
import { brandedPrototype, MEMBER_KEYS, signalMoved } from './Reactive.ts'
|
|
19
20
|
import { currentScope, runInScope, SCOPING } from './scoping.ts'
|
|
@@ -101,8 +102,8 @@ for (const key of MEMBER_KEYS)
|
|
|
101
102
|
}
|
|
102
103
|
const FORWARDING_PROTOTYPE = brandedPrototype(forwarders)
|
|
103
104
|
Object.defineProperty(FORWARDING_PROTOTYPE, CELL, {
|
|
104
|
-
get(this: Forwarder):
|
|
105
|
-
return instanceOf(this[FILED])[CELL]
|
|
105
|
+
get(this: Forwarder): Cell {
|
|
106
|
+
return (instanceOf(this[FILED]) as unknown as Callable)[CELL]
|
|
106
107
|
},
|
|
107
108
|
})
|
|
108
109
|
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// THE SELECTION INDEX (13.1, 13.3, 13.6): which values carry which tag, which loads a bare sweep
|
|
2
|
+
// reaches, and the one probe signal per selection a guard subscribes to. Over the cell's `tags`,
|
|
3
|
+
// `ref`, `filed`, `scope` and `global` and nothing else of it — what a selection DOES to the values
|
|
4
|
+
// it reaches (`busyOver`, `refreshOver`, …) is the graph's, and stays in `Reactive.ts`.
|
|
5
|
+
|
|
6
|
+
import { type Cell, cellOf } from './cellShape.ts'
|
|
7
|
+
import { currentScope } from './scoping.ts'
|
|
8
|
+
import {
|
|
9
|
+
newTracked,
|
|
10
|
+
type Subscriber,
|
|
11
|
+
type Tracked,
|
|
12
|
+
TRACKING,
|
|
13
|
+
trackProbe,
|
|
14
|
+
} from './tracking.ts'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Each tag to the values carrying it, held weakly.
|
|
18
|
+
*
|
|
19
|
+
* THE INDEX HOLDS VALUES WEAKLY. A tag is a way to find a value, never a reason to keep one, and a
|
|
20
|
+
* strong index would hold every tagged value a page ever made for the life of the process. A
|
|
21
|
+
* collected value is pruned from its sets by the next sweep that finds it gone.
|
|
22
|
+
*/
|
|
23
|
+
const TAGGED = new Map<string, Set<WeakRef<Cell>>>()
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Every value whose producer is a LOAD, held weakly — what a bare selection sweeps.
|
|
27
|
+
*
|
|
28
|
+
* 13.6: A BARE SWEEP REACHES PRODUCERS, and of those it files the ones that LOAD: a value holding
|
|
29
|
+
* a `store`, filed where it is built, and a body the first time it starts a load — a pending
|
|
30
|
+
* answer or a stream (`beginLoad`). A synchronous body is a derivation: reloading it recomputes
|
|
31
|
+
* what its sources already say, and whatever load it reads is filed on its own. And filing is
|
|
32
|
+
* what every filed value pays — a weak handle measured ~230 ns a value under V8 against ~20 under
|
|
33
|
+
* JSC — so a value with nothing to load never pays it: a derivation per cell per row stays at
|
|
34
|
+
* what it cost before, and a load pays it beside the promise it already allocates.
|
|
35
|
+
*
|
|
36
|
+
* Held weakly for `TAGGED`'s reason. AN ARRAY, COMPACTED, RATHER THAN A `Set`: a `Set` of weak
|
|
37
|
+
* handles measured ~120 ns a filing under JSC against ~10 for a push. What a `Set` bought was an
|
|
38
|
+
* O(1) delete on eviction; here an evicted value drops its `ref` instead, and a handle whose value
|
|
39
|
+
* no longer points back at it is dead, the same as one whose value was collected. Because a bare
|
|
40
|
+
* sweep may never run, the filing that finds the array past `compactProducersAt` compacts it, and
|
|
41
|
+
* the bar doubles past what survives, so the walk is amortized over the filings between two.
|
|
42
|
+
*/
|
|
43
|
+
let filedProducers: WeakRef<Cell>[] = []
|
|
44
|
+
let compactProducersAt = 1024
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The probe signal a guard over a selection subscribes to.
|
|
48
|
+
*
|
|
49
|
+
* 13.3 AND 13.9: ONE SIGNAL PER SELECTION, keyed by its canonical tags. A guard over a selection
|
|
50
|
+
* subscribes to this, never to each entry — the probe edge `trackProbe` makes, from a node that
|
|
51
|
+
* is nothing but that edge.
|
|
52
|
+
*/
|
|
53
|
+
const SIGNALS = new Map<string, Tracked>()
|
|
54
|
+
const SIGNALS_BY_TAG = new Map<string, Tracked[]>()
|
|
55
|
+
/**
|
|
56
|
+
* The bare selection's one signal. Not a key in `SIGNALS`: `{ tags: [] }` canonicalizes to the
|
|
57
|
+
* empty set and matches nothing, where the bare selection matches every filed load in reach.
|
|
58
|
+
*/
|
|
59
|
+
export const EVERY_PRODUCER = newTracked()
|
|
60
|
+
|
|
61
|
+
function refOf(cell: Cell): WeakRef<Cell> {
|
|
62
|
+
let ref = cell.ref
|
|
63
|
+
if (ref === null) {
|
|
64
|
+
ref = new WeakRef(cell)
|
|
65
|
+
cell.ref = ref
|
|
66
|
+
}
|
|
67
|
+
return ref
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function tagCell(cell: Cell): void {
|
|
71
|
+
const ref = refOf(cell)
|
|
72
|
+
for (const tag of cell.tags as string[]) {
|
|
73
|
+
let carrying = TAGGED.get(tag)
|
|
74
|
+
if (carrying === undefined) {
|
|
75
|
+
carrying = new Set()
|
|
76
|
+
TAGGED.set(tag, carrying)
|
|
77
|
+
}
|
|
78
|
+
carrying.add(ref)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function fileProducer(cell: Cell): void {
|
|
83
|
+
cell.filed = true
|
|
84
|
+
filedProducers.push(refOf(cell))
|
|
85
|
+
if (filedProducers.length > compactProducersAt) {
|
|
86
|
+
filedProducers = liveProducers()
|
|
87
|
+
compactProducersAt = Math.max(1024, filedProducers.length * 2)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The handles whose value is still standing and still filed — neither collected nor evicted. */
|
|
92
|
+
function liveProducers(): WeakRef<Cell>[] {
|
|
93
|
+
const live: WeakRef<Cell>[] = []
|
|
94
|
+
for (let index = 0; index < filedProducers.length; index += 1) {
|
|
95
|
+
const ref = filedProducers[index] as WeakRef<Cell>
|
|
96
|
+
if (ref.deref()?.ref === ref) live.push(ref)
|
|
97
|
+
}
|
|
98
|
+
return live
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Removes an evicted value from every selection index.
|
|
103
|
+
*
|
|
104
|
+
* What an eviction calls: an entry out of its `Keyed` is out of every selection with it, rather
|
|
105
|
+
* than reachable by a sweep until a collection happens to run.
|
|
106
|
+
*/
|
|
107
|
+
export function leaveSelections(value: unknown): void {
|
|
108
|
+
const cell = cellOf(value)
|
|
109
|
+
const ref = cell.ref
|
|
110
|
+
if (ref === null) return
|
|
111
|
+
// What takes it out of `filedProducers`: a handle its value no longer points back at is dead.
|
|
112
|
+
cell.ref = null
|
|
113
|
+
if (cell.tags === null) return
|
|
114
|
+
for (const tag of cell.tags) {
|
|
115
|
+
const carrying = TAGGED.get(tag)
|
|
116
|
+
if (carrying === undefined) continue
|
|
117
|
+
carrying.delete(ref)
|
|
118
|
+
if (carrying.size === 0) TAGGED.delete(tag)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Takes every value filed so far out of the bare selection, each filed again by its next load.
|
|
124
|
+
*
|
|
125
|
+
* A TEST'S SEAM, with no caller in the framework — `bareSelection.test.ts` calls it before its
|
|
126
|
+
* first case: a page's process ends with the page, and a test process runs every file before this
|
|
127
|
+
* one in it. What those files left held — a module's `global`
|
|
128
|
+
* value, a follow the page keeps for its life — is in reach of a bare sweep (13.13), and a bare
|
|
129
|
+
* `refresh()` reloads it and reads `busy()` true while it asks.
|
|
130
|
+
*/
|
|
131
|
+
export function unfileProducers(): void {
|
|
132
|
+
for (let index = 0; index < filedProducers.length; index += 1) {
|
|
133
|
+
const cell = (filedProducers[index] as WeakRef<Cell>).deref()
|
|
134
|
+
if (cell !== undefined) cell.filed = false
|
|
135
|
+
}
|
|
136
|
+
filedProducers = []
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The values a selection reaches in the current scope, `global` values included: those carrying
|
|
141
|
+
* any of `tags`, or every filed load where `tags` is `null` — the bare selection.
|
|
142
|
+
*
|
|
143
|
+
* 13.8 — A SELECTION IS SCOPE-BOUNDED, and a tag with it, so a selection inside one request never
|
|
144
|
+
* reaches the values another request filed. 13.13 — a `global` value is in every scope, so every
|
|
145
|
+
* selection reaches it, and 13.10 is the bare case of that. In a browser every cell's scope is
|
|
146
|
+
* `undefined`, and so is this.
|
|
147
|
+
*/
|
|
148
|
+
export function inReach(tags: string[] | null): Cell[] {
|
|
149
|
+
const scope = currentScope()
|
|
150
|
+
if (tags === null) {
|
|
151
|
+
filedProducers = liveProducers()
|
|
152
|
+
const found: Cell[] = []
|
|
153
|
+
for (let index = 0; index < filedProducers.length; index += 1) {
|
|
154
|
+
const cell = (
|
|
155
|
+
filedProducers[index] as WeakRef<Cell>
|
|
156
|
+
).deref() as Cell
|
|
157
|
+
if (cell.scope === scope || cell.global) found.push(cell)
|
|
158
|
+
}
|
|
159
|
+
return found
|
|
160
|
+
}
|
|
161
|
+
const found = new Set<Cell>()
|
|
162
|
+
for (const tag of tags) {
|
|
163
|
+
const refs = TAGGED.get(tag)
|
|
164
|
+
if (refs === undefined) continue
|
|
165
|
+
for (const ref of refs) {
|
|
166
|
+
const cell = ref.deref()
|
|
167
|
+
if (cell === undefined) refs.delete(ref)
|
|
168
|
+
else if (cell.scope === scope || cell.global) found.add(cell)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return [...found]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function trackSignal(tags: string[] | null): void {
|
|
175
|
+
const reader = TRACKING.reader
|
|
176
|
+
if (reader === null) return
|
|
177
|
+
if (tags === null) {
|
|
178
|
+
trackProbe(EVERY_PRODUCER, reader)
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
const key = JSON.stringify([...new Set(tags)].sort())
|
|
182
|
+
let signal = SIGNALS.get(key)
|
|
183
|
+
if (signal === undefined) {
|
|
184
|
+
signal = newTracked()
|
|
185
|
+
SIGNALS.set(key, signal)
|
|
186
|
+
for (const tag of tags) {
|
|
187
|
+
const signals = SIGNALS_BY_TAG.get(tag)
|
|
188
|
+
if (signals === undefined) SIGNALS_BY_TAG.set(tag, [signal])
|
|
189
|
+
else signals.push(signal)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
trackProbe(signal, reader)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Hands `mark` the probe readers of every selection signal over any of `tags` (13.3).
|
|
197
|
+
*
|
|
198
|
+
* `mark` is the core's `markAsked`, passed rather than imported so this index stays off the graph.
|
|
199
|
+
*/
|
|
200
|
+
export function markSignals(
|
|
201
|
+
tags: string[],
|
|
202
|
+
mark: (asked: Subscriber[]) => void,
|
|
203
|
+
): void {
|
|
204
|
+
for (const tag of tags) {
|
|
205
|
+
const signals = SIGNALS_BY_TAG.get(tag)
|
|
206
|
+
if (signals === undefined) continue
|
|
207
|
+
for (const signal of signals) mark(signal.probeReaders)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -15,6 +15,7 @@ import { namedLogger } from '../log.ts'
|
|
|
15
15
|
import { UNEXPECTED } from '../UNEXPECTED.ts'
|
|
16
16
|
import { WORK } from '../WORK.ts'
|
|
17
17
|
import { CELL } from './CELL.ts'
|
|
18
|
+
import type { Callable } from './cellShape.ts'
|
|
18
19
|
import { NotLanded } from './NotLanded.ts'
|
|
19
20
|
import type { Reactive } from './Reactive.ts'
|
|
20
21
|
import { currentScope, runInScope } from './scoping.ts'
|
|
@@ -161,12 +162,10 @@ export function watch(first: unknown, second?: Effect): Disposer {
|
|
|
161
162
|
// An app hands over the VALUES; the reader list lives on each one's record. Copied
|
|
162
163
|
// rather than aliased, because the reader keeps this array for its whole life and the
|
|
163
164
|
// app's own array is the app's to mutate.
|
|
164
|
-
const given = (Array.isArray(first) ? first : [first]) as
|
|
165
|
-
[CELL]: Tracked
|
|
166
|
-
}[]
|
|
165
|
+
const given = (Array.isArray(first) ? first : [first]) as Callable[]
|
|
167
166
|
const sources: Tracked[] = []
|
|
168
167
|
for (let index = 0; index < given.length; index += 1)
|
|
169
|
-
sources.push((given[index] as
|
|
168
|
+
sources.push((given[index] as Callable)[CELL])
|
|
170
169
|
return owned(watchOver(sources, null, second))
|
|
171
170
|
}
|
|
172
171
|
|
package/src/shared/rpc.ts
CHANGED
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
type AdoptedFailures,
|
|
36
36
|
type AdoptedProduced,
|
|
37
37
|
type ChunkOf,
|
|
38
|
-
chunksOfStream,
|
|
39
38
|
handlerValue,
|
|
40
39
|
isKeyed,
|
|
41
40
|
type Kept,
|
|
@@ -46,11 +45,14 @@ import {
|
|
|
46
45
|
type Refusals,
|
|
47
46
|
type Shaping,
|
|
48
47
|
STREAMING,
|
|
49
|
-
streamingOrSettled,
|
|
50
48
|
type Streams,
|
|
51
49
|
} from './reactive/index.ts'
|
|
52
50
|
import { MUTATION } from './reactive/MUTATION.ts'
|
|
53
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
chunksOfStream,
|
|
53
|
+
dropAnswered,
|
|
54
|
+
streamingOrSettled,
|
|
55
|
+
} from './reactive/producer.ts'
|
|
54
56
|
import { builtHere, isForwarder } from './reactive/scoped.ts'
|
|
55
57
|
import { SELECTION_MEMBERS } from './reactive/SELECTION_MEMBERS.ts'
|
|
56
58
|
import { checkArgs } from './refuse.ts'
|
|
@@ -532,14 +534,15 @@ export function declare(method: Method): Declare {
|
|
|
532
534
|
}) satisfies Gated<unknown>
|
|
533
535
|
const members = rpc as unknown as Record<string, unknown>
|
|
534
536
|
// 7.10, 7.18 — A TRIGGER ON A SERVER REACHES EVERY BROWSER HOLDING WHAT IT NAMES, through the
|
|
535
|
-
// mux (18.3).
|
|
537
|
+
// mux (18.3). None follow in a browser, where the trigger is the copy's own.
|
|
536
538
|
for (const name of SELECTION_MEMBERS) {
|
|
537
539
|
if (name === 'busy') continue
|
|
538
540
|
members[name] =
|
|
539
541
|
name === 'invalidate' || name === 'refresh'
|
|
540
542
|
? (pattern?: unknown): unknown => {
|
|
541
543
|
const done = faceHere()[name](pattern)
|
|
542
|
-
|
|
544
|
+
for (const followers of RPC_SERVER.following)
|
|
545
|
+
followers.broadcast(rpc, name, pattern)
|
|
543
546
|
return done
|
|
544
547
|
}
|
|
545
548
|
: (pattern?: unknown): unknown => faceHere()[name](pattern)
|
|
@@ -14,10 +14,9 @@
|
|
|
14
14
|
// generated document carries them.
|
|
15
15
|
|
|
16
16
|
import { isRecord } from './isRecord.ts'
|
|
17
|
+
import { issuesOf, note } from './issueReport.ts'
|
|
17
18
|
import type { Issues } from './Issues.ts'
|
|
18
|
-
import { issuesOf } from './issuesOf.ts'
|
|
19
19
|
import type { JsonValue } from './JsonValue.ts'
|
|
20
|
-
import { note } from './note.ts'
|
|
21
20
|
|
|
22
21
|
type Node = { [key: string]: JsonValue }
|
|
23
22
|
|
package/src/shared/writePath.ts
CHANGED
|
@@ -93,3 +93,32 @@ function copyOf(value: unknown, step: string | number): Level {
|
|
|
93
93
|
return (typeof step === 'number' ? [] : {}) as unknown as Level
|
|
94
94
|
return { ...(value as object) }
|
|
95
95
|
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* What `mutate` hands back, called on a copy of the array at `path` that `target` then holds.
|
|
99
|
+
*
|
|
100
|
+
* 31.9 — A MUTATOR IS A WRITE THROUGH ITS RECEIVER'S PATH. `tags.push(t)` run on the held array
|
|
101
|
+
* grows it in place, so the identity a reader wakes on never moves (31.8's failure, spelled as a
|
|
102
|
+
* call). The compiler lifts a call here only where the receiver's TYPE is an array, so a class's
|
|
103
|
+
* own `push` stays a call on the class. A level that is not plain data on the way (31.14), or a
|
|
104
|
+
* receiver that is not an array when the call runs, takes the call in place, waking nobody.
|
|
105
|
+
*/
|
|
106
|
+
export function mutatePath<Result>(
|
|
107
|
+
target: { (): unknown; set(value: unknown): unknown },
|
|
108
|
+
path: readonly (string | number)[],
|
|
109
|
+
mutate: (receiver: never) => Result,
|
|
110
|
+
): Result {
|
|
111
|
+
const value = target()
|
|
112
|
+
let receiver = value
|
|
113
|
+
let plain = true
|
|
114
|
+
for (const step of path) {
|
|
115
|
+
plain &&= isPlain(receiver)
|
|
116
|
+
receiver = (receiver as Level | null | undefined)?.[step]
|
|
117
|
+
}
|
|
118
|
+
if (!plain) return mutate(receiver as never)
|
|
119
|
+
if (!Array.isArray(receiver)) return mutate(receiver as never)
|
|
120
|
+
const copy = receiver.slice()
|
|
121
|
+
const result = mutate(copy as never)
|
|
122
|
+
target.set(writePath(value, path, copy))
|
|
123
|
+
return result
|
|
124
|
+
}
|