@abide/abide 0.123.2 → 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 +58 -0
- package/bin/abide.ts +36 -65
- 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/Diagnostics.ts +198 -0
- package/src/compiler/KeptChecker.ts +97 -0
- package/src/compiler/LoweredFile.ts +609 -0
- package/src/compiler/Lowering.ts +18 -1
- package/src/compiler/PositionedRefusal.ts +1 -1
- 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 +76 -1743
- package/src/compiler/appLayout.ts +28 -20
- package/src/compiler/appLints.ts +109 -0
- package/src/compiler/argsDefaultsIn.ts +1 -1
- package/src/compiler/checkApp.ts +64 -68
- package/src/compiler/checkedShadow.ts +2 -2
- package/src/compiler/classifyRead.ts +16 -1
- package/src/compiler/compileApp.ts +102 -71
- package/src/compiler/declaredExports.ts +46 -21
- package/src/compiler/diagnoseSources.ts +232 -0
- package/src/compiler/emitClient.ts +1 -1
- package/src/compiler/formatAbide.ts +7 -8
- package/src/compiler/handlersUnder.ts +32 -63
- package/src/compiler/lineOf.ts +0 -19
- package/src/compiler/matchersOf.ts +3 -3
- package/src/compiler/mountsOf.ts +3 -3
- package/src/compiler/reactiveReads.ts +235 -0
- package/src/compiler/reactiveTypes.ts +13 -0
- package/src/compiler/readSources.ts +16 -18
- package/src/compiler/readsIn.ts +181 -82
- package/src/compiler/routesDeclaration.ts +46 -0
- package/src/compiler/scaffold.ts +7 -2
- package/src/compiler/serveLanguage.ts +46 -74
- package/src/compiler/sharedModuleWrites.ts +1 -1
- package/src/compiler/splitImports.ts +2 -2
- 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/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/RESUME_MILLISECONDS.ts +2 -0
- package/src/server/abideModules.ts +7 -7
- package/src/server/answerFailure.ts +3 -2
- package/src/server/answerRequest.ts +16 -369
- package/src/server/answerRpc.ts +48 -302
- package/src/server/answerSocket.ts +41 -490
- package/src/server/appBuild.ts +377 -0
- package/src/server/appKit.ts +1 -1
- 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 +38 -66
- package/src/server/csp.ts +1 -1
- package/src/server/deferredBuilds.ts +3 -16
- package/src/server/devWorker.ts +11 -15
- package/src/server/faceOf.ts +1 -1
- package/src/server/generatedSurfaces.ts +2 -1
- package/src/server/handlerFileMount.ts +40 -0
- package/src/server/index.ts +3 -3
- 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/packageDirectory.ts +14 -0
- package/src/server/preload.ts +21 -2
- package/src/server/principal.ts +5 -4
- package/src/server/remote.ts +2 -2
- package/src/server/render.ts +31 -59
- package/src/server/requestScope.ts +5 -36
- package/src/server/runCompiled.ts +1 -1
- package/src/server/runDesktop.ts +20 -448
- package/src/server/seedBuffer.ts +12 -12
- package/src/server/serveDev.ts +24 -58
- package/src/server/servedApp.ts +390 -0
- package/src/server/shell.ts +1 -1
- package/src/server/signInChanged.ts +20 -0
- package/src/server/surfaceOf.ts +3 -2
- package/src/server/versionOf.ts +7 -16
- package/src/shared/RPC_SERVER.ts +27 -31
- package/src/shared/Schema.ts +1 -2
- package/src/shared/applyPatch.ts +1 -1
- 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/muxFrames.ts +1 -1
- package/src/shared/reactive/Keyed.ts +24 -16
- package/src/shared/reactive/Reactive.ts +37 -991
- package/src/shared/reactive/argsContract.ts +131 -4
- 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 -23
- package/src/shared/reactive/pairReactive.ts +5 -3
- package/src/shared/reactive/patchLog.ts +79 -0
- package/src/shared/reactive/patchMarks.ts +34 -0
- package/src/shared/reactive/producer.ts +121 -0
- package/src/shared/reactive/recordWrites.ts +11 -14
- package/src/shared/reactive/scoped.ts +3 -2
- package/src/shared/reactive/selections.ts +209 -0
- package/src/shared/reactive/share.ts +4 -3
- package/src/shared/reactive/watch.ts +3 -4
- package/src/shared/rpc.ts +20 -24
- package/src/shared/validateJson.ts +1 -2
- package/src/shared/writePath.ts +29 -0
- 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 +106 -313
- 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 +23 -11
- package/src/ui/outcomeOf.ts +120 -0
- 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 +28 -13
- package/src/ui/wire.ts +9 -10
- 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/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
- 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
|
@@ -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,32 +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
|
-
heldGenerationOf,
|
|
51
47
|
type Identity,
|
|
52
|
-
isStreaming,
|
|
53
|
-
loadOf,
|
|
54
48
|
moveItem,
|
|
55
|
-
patchInto,
|
|
56
|
-
produceInto,
|
|
57
49
|
type Reactive,
|
|
58
50
|
type ReactiveOptions,
|
|
59
|
-
refuseInto,
|
|
60
51
|
rowItem,
|
|
61
52
|
type Store,
|
|
62
|
-
servedCellOf,
|
|
63
|
-
standIn,
|
|
64
|
-
streamingOrSettled,
|
|
65
53
|
type Transformer,
|
|
66
|
-
writtenLocally,
|
|
67
54
|
} from './Reactive.ts'
|
|
68
55
|
export { STREAMING } from './STREAMING.ts'
|
|
69
56
|
export {
|
|
@@ -550,7 +537,8 @@ export function refresh<Chosen extends Selection>(
|
|
|
550
537
|
(selection as { refresh(pattern: unknown): void }).refresh(pattern)
|
|
551
538
|
else {
|
|
552
539
|
refreshOver(sweep)
|
|
553
|
-
RPC_SERVER.
|
|
540
|
+
for (const followers of RPC_SERVER.following)
|
|
541
|
+
followers.sweep('refresh', sweep)
|
|
554
542
|
}
|
|
555
543
|
}
|
|
556
544
|
/** Marks the selection stale: drops the cache behind it, never the value. */
|
|
@@ -565,7 +553,8 @@ export function invalidate<Chosen extends Selection>(
|
|
|
565
553
|
)
|
|
566
554
|
else {
|
|
567
555
|
invalidateOver(sweep)
|
|
568
|
-
RPC_SERVER.
|
|
556
|
+
for (const followers of RPC_SERVER.following)
|
|
557
|
+
followers.sweep('invalidate', sweep)
|
|
569
558
|
}
|
|
570
559
|
}
|
|
571
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,79 @@
|
|
|
1
|
+
// 18.11 — WHAT A PATCH WROTE, KEPT AS DATA for a value the server follows over the mux, by the cell
|
|
2
|
+
// it was written to. Only a followed value has a log, so every other patch records nothing but its
|
|
3
|
+
// marks (32.33). And 7.10 — what an invalidate of that value sends on: the core calls the log's
|
|
4
|
+
// followers for every cell it invalidates, however the trigger was spelled, so every other
|
|
5
|
+
// invalidate is one miss.
|
|
6
|
+
//
|
|
7
|
+
// HELD WHILE ANYONE IS FED THE VALUE, and gone with the last: a log nobody drains grows with every
|
|
8
|
+
// patch.
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* One JSON Patch operation (RFC 6902), its `path` and `from` JSON Pointers (RFC 6901).
|
|
12
|
+
*
|
|
13
|
+
* A patch abide records is `add`, `remove` and `replace` alone; `move`, `copy` and `test` are
|
|
14
|
+
* applied where a client sends them.
|
|
15
|
+
*/
|
|
16
|
+
export type PatchOp =
|
|
17
|
+
| { op: 'add'; path: string; value: unknown }
|
|
18
|
+
| { op: 'remove'; path: string }
|
|
19
|
+
| { op: 'replace'; path: string; value: unknown }
|
|
20
|
+
| { op: 'move'; from: string; path: string }
|
|
21
|
+
| { op: 'copy'; from: string; path: string }
|
|
22
|
+
| { op: 'test'; path: string; value: unknown }
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The writes patches made, as `recordWrites` keeps them. `lost` where one could not be kept as data,
|
|
26
|
+
* and then the change is sent as the value (18.13). `followers` stays empty on a log one patch keeps
|
|
27
|
+
* for itself rather than one the server holds.
|
|
28
|
+
*/
|
|
29
|
+
export class PatchLog {
|
|
30
|
+
ops: PatchOp[] = []
|
|
31
|
+
lost = false
|
|
32
|
+
readonly followers = new Set<() => void>()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const LOGS = new WeakMap<object, PatchLog>()
|
|
36
|
+
|
|
37
|
+
/** The log held on `cell`, or `undefined` where nothing follows it. */
|
|
38
|
+
export function logOf(cell: object): PatchLog | undefined {
|
|
39
|
+
return LOGS.get(cell)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Holds a log on `cell`, with `invalidated` called on its every invalidate; returns what releases
|
|
44
|
+
* both.
|
|
45
|
+
*/
|
|
46
|
+
export function holdLog(cell: object, invalidated: () => void): () => void {
|
|
47
|
+
let log = LOGS.get(cell)
|
|
48
|
+
if (log === undefined) {
|
|
49
|
+
log = new PatchLog()
|
|
50
|
+
LOGS.set(cell, log)
|
|
51
|
+
}
|
|
52
|
+
const held = log
|
|
53
|
+
const followers = held.followers
|
|
54
|
+
followers.add(invalidated)
|
|
55
|
+
// A release run twice, after another follower opened a fresh log, leaves that log standing.
|
|
56
|
+
return () => {
|
|
57
|
+
followers.delete(invalidated)
|
|
58
|
+
if (followers.size === 0 && LOGS.get(cell) === held) LOGS.delete(cell)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The writes kept on `cell` since the last drain, or `null` where there are none or one was lost —
|
|
64
|
+
* and the log starts over either way.
|
|
65
|
+
*/
|
|
66
|
+
export function drainLog(cell: object): PatchOp[] | null {
|
|
67
|
+
const log = LOGS.get(cell)
|
|
68
|
+
if (log === undefined) return null
|
|
69
|
+
const ops = log.lost || log.ops.length === 0 ? null : log.ops
|
|
70
|
+
log.ops = []
|
|
71
|
+
log.lost = false
|
|
72
|
+
return ops
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Calls every follower of `cell`'s log: an invalidate reached it. 7.6. */
|
|
76
|
+
export function sendInvalidate(cell: object): void {
|
|
77
|
+
const log = LOGS.get(cell)
|
|
78
|
+
if (log !== undefined) for (const sendOn of log.followers) sendOn()
|
|
79
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// 32.33 — WHICH OBJECTS AN `s.patch` WROTE TO, AND WHEN. The epoch moves once per patch and each
|
|
2
|
+
// object written holds the epoch it was last written at, so a row that took its item at epoch `e` is
|
|
3
|
+
// stale exactly where its item was written since `e`. No imports, because `reconcile` and the
|
|
4
|
+
// socket wire read it on the client and must not drag the core into the runtime chunk with it.
|
|
5
|
+
|
|
6
|
+
let epoch = 0
|
|
7
|
+
const WRITTEN = new WeakMap<object, number>()
|
|
8
|
+
|
|
9
|
+
/** The epoch the last patch wrote at, 0 before any. */
|
|
10
|
+
export function patchEpoch(): number {
|
|
11
|
+
return epoch
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Opens a patch: moves the epoch, and returns the one its writes are marked at. */
|
|
15
|
+
export function openPatch(): number {
|
|
16
|
+
epoch += 1
|
|
17
|
+
return epoch
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Marks `written` as written by the patch at `at`. */
|
|
21
|
+
export function markWritten(written: object, at: number): void {
|
|
22
|
+
WRITTEN.set(written, at)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The epoch a patch last wrote to `value` at, 0 where none has or it is not an object. */
|
|
26
|
+
export function writtenAt(value: unknown): number {
|
|
27
|
+
if (value === null || typeof value !== 'object') return 0
|
|
28
|
+
return WRITTEN.get(value) ?? 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Whether a patch wrote to `value` after epoch `since`. */
|
|
32
|
+
export function writtenSince(value: unknown, since: number): boolean {
|
|
33
|
+
return writtenAt(value) > since
|
|
34
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
// holds it directly: `list.push(row); row.name = x`. So a placed object is SENT AS IT STANDS WHEN THE
|
|
23
23
|
// CALLBACK RETURNS — every op's value is copied then rather than at its write — and a write made
|
|
24
24
|
// under one is not kept, since that copy already carries it.
|
|
25
|
-
import type { PatchOp } from './
|
|
26
|
-
import {
|
|
25
|
+
import type { PatchLog, PatchOp } from './patchLog.ts'
|
|
26
|
+
import { markWritten, openPatch } from './patchMarks.ts'
|
|
27
27
|
import { RESHAPING } from './RESHAPING.ts'
|
|
28
28
|
|
|
29
29
|
// The reshaping methods JSON Patch has no op for: the array they leave is sent whole instead.
|
|
@@ -39,7 +39,7 @@ const REORDERING: ReadonlySet<string> = new Set([
|
|
|
39
39
|
const TARGET = Symbol('target')
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* Runs `mutate` over a proxy of `root`, marking every object it writes to
|
|
42
|
+
* Runs `mutate` over a proxy of `root`, marking every object it writes to (`patchMarks.ts`).
|
|
43
43
|
*
|
|
44
44
|
* Where a `sink` is handed in, each write is also kept there as a `PatchOp`, or the sink is marked
|
|
45
45
|
* `lost` where it cannot be. A `root` that is not an array or a plain object is handed over as is.
|
|
@@ -47,7 +47,7 @@ const TARGET = Symbol('target')
|
|
|
47
47
|
export function recordWrites<Value>(
|
|
48
48
|
root: Value,
|
|
49
49
|
mutate: (value: Value) => void,
|
|
50
|
-
sink?:
|
|
50
|
+
sink?: PatchLog,
|
|
51
51
|
): void {
|
|
52
52
|
if (!isWrappable(root)) {
|
|
53
53
|
// A root no proxy can stand in for is written as a whole: what the callback reads out of it
|
|
@@ -56,14 +56,12 @@ export function recordWrites<Value>(
|
|
|
56
56
|
mutate(root)
|
|
57
57
|
return
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
mutate(heldView(root,
|
|
59
|
+
const epoch = openPatch()
|
|
60
|
+
markWritten(root, epoch)
|
|
61
|
+
mutate(heldView(root, epoch) as Value)
|
|
62
62
|
return
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
const epoch = PATCHES.epoch
|
|
66
|
-
const written = PATCHES.written
|
|
64
|
+
const epoch = openPatch()
|
|
67
65
|
// EVERY OBJECT THE PATCH REACHED, by what it is: the one proxy it is handed out as, so
|
|
68
66
|
// `list[0] === list[0]` inside the callback, and its parent. ONE ENTRY rather than a map for each,
|
|
69
67
|
// because a row read is the loop this runs per row and each map is a lookup and an insert there.
|
|
@@ -83,7 +81,7 @@ export function recordWrites<Value>(
|
|
|
83
81
|
const entry = reached.get(held) as Reached
|
|
84
82
|
if (entry.marked) return
|
|
85
83
|
entry.marked = true
|
|
86
|
-
|
|
84
|
+
markWritten(held, epoch)
|
|
87
85
|
const above = entry.parent
|
|
88
86
|
if (above instanceof Parents) for (const one of above.all) mark(one)
|
|
89
87
|
else mark(above)
|
|
@@ -428,7 +426,7 @@ class Reached {
|
|
|
428
426
|
proxy: object | null
|
|
429
427
|
parent: object | Parents | null
|
|
430
428
|
/**
|
|
431
|
-
* Written in this patch, directly or below —
|
|
429
|
+
* Written in this patch, directly or below — its mark is this patch's epoch.
|
|
432
430
|
*/
|
|
433
431
|
marked: boolean
|
|
434
432
|
/** What one no proxy can stand in for is handed out as: `heldView` of it. */
|
|
@@ -556,8 +554,7 @@ function* markedEach(
|
|
|
556
554
|
}
|
|
557
555
|
|
|
558
556
|
function markedOut<Value>(value: Value, epoch: number): Value {
|
|
559
|
-
if (typeof value === 'object' && value !== null)
|
|
560
|
-
PATCHES.written.set(value, epoch)
|
|
557
|
+
if (typeof value === 'object' && value !== null) markWritten(value, epoch)
|
|
561
558
|
return value
|
|
562
559
|
}
|
|
563
560
|
|
|
@@ -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
|
+
}
|
|
@@ -64,14 +64,15 @@ export function share<Key extends string, Value>(
|
|
|
64
64
|
* Opens a share scope around an emitted component's body, and returns the previous one.
|
|
65
65
|
*
|
|
66
66
|
* The PREVIOUS scope comes back rather than the new one: restoring is the whole of the close
|
|
67
|
-
* (`leaveShare`), and a component that threw still has to restore it.
|
|
67
|
+
* (`leaveShare`), and a component that threw still has to restore it. `level` is handed in by a
|
|
68
|
+
* routed level opening its own scope (39.1); every other scope carries its parent's down.
|
|
68
69
|
*/
|
|
69
|
-
export function enterShare(): ShareScope | null {
|
|
70
|
+
export function enterShare(level?: unknown): ShareScope | null {
|
|
70
71
|
const previous = CURRENT
|
|
71
72
|
CURRENT = {
|
|
72
73
|
parent: previous,
|
|
73
74
|
values: new Map(),
|
|
74
|
-
level:
|
|
75
|
+
level: level ?? previous?.level,
|
|
75
76
|
}
|
|
76
77
|
return previous
|
|
77
78
|
}
|
|
@@ -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
|
|