@abide/abide 0.100.0 → 0.102.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/CHANGELOG.md +31 -0
- package/bin/abide.ts +227 -163
- package/docs/REGISTRY.md +726 -0
- package/docs/RULEBOOK.md +2753 -0
- package/docs/abide.md +12878 -0
- package/package.json +10 -3
- package/scaffold/AGENTS.md +31 -0
- package/scaffold/CLAUDE.md +1 -0
- package/scaffold/src/layout.abide +18 -0
- package/src/compiler/Lowering.ts +78 -9
- package/src/compiler/ShadowMap.ts +2 -2
- package/src/compiler/TypeSession.ts +262 -67
- package/src/compiler/argsSchemaOf.ts +1 -1
- package/src/compiler/checkApp.ts +43 -15
- package/src/compiler/checkedShadow.ts +39 -5
- package/src/compiler/classifyRead.ts +6 -4
- package/src/compiler/classifyTemplate.ts +1 -1
- package/src/compiler/compileApp.ts +433 -193
- package/src/compiler/compiledEntry.ts +43 -39
- package/src/compiler/componentGraph.ts +58 -71
- package/src/compiler/declaredExports.ts +24 -7
- package/src/compiler/emitClient.ts +141 -71
- package/src/compiler/emitComponent.ts +37 -8
- package/src/compiler/emitSource.ts +106 -13
- package/src/compiler/handlersUnder.ts +106 -56
- package/src/compiler/index.ts +7 -0
- package/src/compiler/mountsOf.ts +138 -0
- package/src/compiler/nearest.ts +14 -0
- package/src/compiler/parseAbide.ts +157 -20
- package/src/compiler/projectShadow.ts +213 -7
- package/src/compiler/reactiveTypes.ts +20 -7
- package/src/compiler/readSources.ts +15 -2
- package/src/compiler/readsIn.ts +61 -7
- package/src/compiler/renderMarkup.ts +3 -3
- package/src/compiler/scaffold.ts +30 -11
- package/src/compiler/serveLanguage.ts +125 -34
- package/src/compiler/sourceMapOf.ts +193 -0
- package/src/compiler/splitImports.ts +205 -83
- package/src/compiler/syncTypes.ts +32 -0
- package/src/compiler/tokenize.ts +14 -0
- package/src/compiler/writeDefaultProject.ts +1 -1
- package/src/compiler/writeTypes.ts +82 -0
- package/src/server/CLIENT_BUILD.ts +11 -13
- package/src/server/DEFAULT_PORT.ts +1 -1
- package/src/server/LAUNCHER_FILES.ts +9 -0
- package/src/server/MODULE_QUERIES.ts +18 -0
- package/src/server/TABLES.ts +7 -0
- package/src/server/VIRTUAL_DIRECTORY.ts +4 -5
- package/src/server/abideModules.ts +391 -0
- package/src/server/answerFailure.ts +16 -6
- package/src/server/answerRequest.ts +235 -106
- package/src/server/answerRpc.ts +124 -39
- package/src/server/answerSocket.ts +141 -48
- package/src/server/answerValue.ts +22 -9
- package/src/server/appDirectories.ts +1 -1
- package/src/server/appPlugins.ts +1 -1
- package/src/server/appTree.ts +179 -0
- package/src/server/argsFor.ts +2 -2
- package/src/server/assistant.ts +2 -2
- package/src/server/bridgeRelay.ts +6 -8
- package/src/server/claudeCode.ts +0 -6
- package/src/server/clientBuildOptions.ts +8 -3
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +37 -37
- package/src/server/componentModules.d.ts +3 -1
- package/src/server/compressAnswer.ts +48 -14
- package/src/server/config.ts +25 -29
- package/src/server/configFields.ts +31 -25
- package/src/server/createApp.ts +59 -73
- package/src/server/devWorker.ts +144 -183
- package/src/server/executableName.ts +4 -0
- package/src/server/fenceServer.ts +37 -35
- package/src/server/generatedSurfaces.ts +99 -66
- package/src/server/hooks.ts +124 -52
- package/src/server/iconAccent.ts +191 -0
- package/src/server/index.ts +11 -7
- package/src/server/installSourceMaps.ts +88 -0
- package/src/server/launcherOptions.ts +34 -31
- package/src/server/launcherPage.ts +107 -64
- package/src/server/manifestOf.ts +16 -13
- package/src/server/namesUnder.ts +2 -2
- package/src/server/nativeMenu.ts +5 -5
- package/src/server/negotiateEncoding.ts +1 -1
- package/src/server/principal.ts +10 -5
- package/src/server/realOrAsGiven.ts +10 -0
- package/src/server/refuseAddressCollision.ts +21 -14
- package/src/server/remote.ts +49 -52
- package/src/server/render.ts +65 -94
- package/src/server/renderComponent.ts +5 -0
- package/src/server/reportHealth.ts +2 -2
- package/src/server/requestScope.ts +12 -1
- package/src/server/runBuild.ts +24 -0
- package/src/server/runCompiled.ts +5 -5
- package/src/server/runDesktop.ts +52 -27
- package/src/server/serveBuild.ts +43 -52
- package/src/server/serveDev.ts +81 -39
- package/src/server/shell.ts +7 -22
- package/src/server/sourceMappings.ts +139 -0
- package/src/server/surfaceOf.ts +18 -3
- package/src/server/testBuild.ts +1 -19
- package/src/server/withAbideSources.ts +117 -0
- package/src/shared/ADDRESSES.ts +7 -18
- package/src/shared/APP_META.ts +7 -0
- package/src/shared/CopyOptions.ts +1 -1
- package/src/shared/DEBUG_META.ts +1 -1
- package/src/shared/GLOBAL_GETS.ts +9 -3
- package/src/shared/RESERVED_DIRECTORIES.ts +15 -0
- package/src/shared/RPC_SERVER.ts +2 -2
- package/src/shared/cookies.ts +2 -2
- package/src/shared/csp.ts +1 -1
- package/src/shared/escapeText.ts +1 -1
- package/src/shared/filledPath.ts +85 -0
- package/src/shared/framing.ts +79 -6
- package/src/shared/headKeyOf.ts +2 -0
- package/src/shared/index.ts +1 -1
- package/src/shared/log.ts +27 -13
- package/src/shared/matchRoute.ts +23 -4
- package/src/shared/menuItem.ts +3 -3
- package/src/shared/reactive/Cursor.ts +3 -9
- package/src/shared/reactive/Keyed.ts +4 -3
- package/src/shared/reactive/NotLanded.ts +1 -1
- package/src/shared/reactive/Reactive.ts +81 -60
- package/src/shared/reactive/argsContract.ts +4 -4
- package/src/shared/reactive/canonicalKey.ts +4 -4
- package/src/shared/reactive/chunksOfStream.ts +6 -0
- package/src/shared/reactive/index.ts +1 -1
- package/src/shared/reactive/recordWrites.ts +103 -5
- package/src/shared/reactive/structural.ts +1 -1
- package/src/shared/reactive/tracking.ts +155 -21
- package/src/shared/reactive/watch.ts +10 -6
- package/src/shared/refuse.ts +1 -1
- package/src/shared/routePattern.ts +82 -46
- package/src/shared/rpc.ts +47 -13
- package/src/shared/rpcRequest.ts +27 -3
- package/src/shared/url.ts +4 -44
- package/src/shared/writePath.ts +1 -1
- package/src/ui/LAYER_NAMES.ts +1 -1
- package/src/ui/List.ts +3 -3
- package/src/ui/MOUNTING_LEVEL.ts +18 -0
- package/src/ui/Mismatch.ts +1 -1
- package/src/ui/RawRun.ts +1 -1
- package/src/ui/Router.ts +38 -31
- package/src/ui/Scope.ts +1 -1
- package/src/ui/TryRegion.ts +2 -2
- package/src/ui/applyHead.ts +33 -0
- package/src/ui/index.ts +1 -1
- package/src/ui/menuBridge.ts +1 -1
- package/src/ui/moveNode.ts +1 -1
- package/src/ui/reconcile.ts +2 -2
- package/src/ui/reportMenu.ts +2 -2
- package/src/ui/routing.ts +2 -2
- package/src/ui/setAttribute.ts +1 -1
- package/src/ui/setText.ts +1 -1
- package/src/ui/socketWire.ts +9 -7
- package/src/ui/wire.ts +1 -1
- package/scaffold/src/server/app.ts +0 -6
- package/scaffold/src/shared/.gitkeep +0 -0
- package/scaffold/src/ui/app.html +0 -8
- package/src/server/componentImports.ts +0 -56
- /package/scaffold/src/{ui/pages/page.abide → page.abide} +0 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Every field `Cell` declares is written in its constructor, in one order, `undefined`
|
|
12
12
|
// included — a shape that grows a field on a later path is two hidden classes and every
|
|
13
|
-
// read downstream of it
|
|
13
|
+
// read downstream of it deoptimizes.
|
|
14
14
|
|
|
15
15
|
import { type Failed, isFailed, isFailedNamed } from '../Failed.ts'
|
|
16
16
|
import { namedLogger } from '../log.ts'
|
|
@@ -19,10 +19,10 @@ import { type Found, runSchema, type Schema } from '../Schema.ts'
|
|
|
19
19
|
import { UNEXPECTED } from '../UNEXPECTED.ts'
|
|
20
20
|
import { WORK } from '../WORK.ts'
|
|
21
21
|
import { CELL } from './CELL.ts'
|
|
22
|
+
import { Cursor, hasCursorBehind, keepPlaces, type Retained } from './Cursor.ts'
|
|
22
23
|
import { ENTRY_LIVES } from './ENTRY_LIVES.ts'
|
|
23
24
|
import { armTimer, type Expiry, expiryFor } from './Expiry.ts'
|
|
24
25
|
import { FOLLOWED } from './FOLLOWED.ts'
|
|
25
|
-
import { Cursor, hasCursorBehind, keepPlaces, type Retained } from './Cursor.ts'
|
|
26
26
|
import { isReactive } from './isReactive.ts'
|
|
27
27
|
import { isThenable } from './isThenable.ts'
|
|
28
28
|
import { MUTATION } from './MUTATION.ts'
|
|
@@ -38,16 +38,19 @@ import {
|
|
|
38
38
|
CLEAN,
|
|
39
39
|
DIRTY,
|
|
40
40
|
detach,
|
|
41
|
-
detachProbes,
|
|
42
41
|
dropProbeReader,
|
|
43
42
|
newSubscriber,
|
|
43
|
+
newTracked,
|
|
44
44
|
RELEASING,
|
|
45
|
+
revive,
|
|
45
46
|
type Subscriber,
|
|
46
47
|
TRACKING,
|
|
47
48
|
type Tracked,
|
|
49
|
+
tombstone,
|
|
48
50
|
track,
|
|
49
51
|
trackProbe,
|
|
50
52
|
truncate,
|
|
53
|
+
truncateProbes,
|
|
51
54
|
untracked,
|
|
52
55
|
} from './tracking.ts'
|
|
53
56
|
import { owned, owning, watchOver } from './watch.ts'
|
|
@@ -136,7 +139,7 @@ export type ReactiveOptions<
|
|
|
136
139
|
/**
|
|
137
140
|
* Shapes a settled value on the way in, or refuses it by returning a `Failed`.
|
|
138
141
|
*
|
|
139
|
-
* RULEBOOK 4.6, 4.7: runs untracked, on the settled value, once per
|
|
142
|
+
* RULEBOOK 4.6, 4.7: runs untracked, on the settled value, once per materialization,
|
|
140
143
|
* and refuses by RETURNING a `Failed` rather than by throwing. A throw here would be
|
|
141
144
|
* indistinguishable from the body having failed, and 4.9 has a refused write mint
|
|
142
145
|
* nothing where a failed producer (2.3) may still have something to say.
|
|
@@ -177,7 +180,7 @@ export type ReactiveOptions<
|
|
|
177
180
|
* What tells one array element from another.
|
|
178
181
|
*
|
|
179
182
|
* RULEBOOK 5.22, 32.32: what tells one element from another. The property-name form is the one
|
|
180
|
-
* that crosses a wire (18.4) — a function has no
|
|
183
|
+
* that crosses a wire (18.4) — a function has no serialization.
|
|
181
184
|
*/
|
|
182
185
|
by?:
|
|
183
186
|
| (keyof ElementOf<Stored> & string)
|
|
@@ -371,7 +374,7 @@ type Persisted = {
|
|
|
371
374
|
* A STREAMING PRODUCER'S OWN STATE (9.1, 9.2, 9.6), and `null` on every value whose body is
|
|
372
375
|
* not an async generator — the shape `cap` and `adoption` already use.
|
|
373
376
|
*
|
|
374
|
-
* THE ACCUMULATION IS PUSHED INTO AND
|
|
377
|
+
* THE ACCUMULATION IS PUSHED INTO AND MATERIALIZED ON THE READ. A chunk is one push and one
|
|
375
378
|
* cascade; the copy a reader is handed is taken by the read that follows, so a hundred
|
|
376
379
|
* chunks between two reads are one copy rather than a hundred (9.2). The copy is FRESH per
|
|
377
380
|
* chunk count and has to be: a reader holding an earlier snapshot still holds what it read,
|
|
@@ -398,13 +401,13 @@ type Stream = {
|
|
|
398
401
|
*
|
|
399
402
|
* A version rather than a chunk count because a patch changes the accumulation without
|
|
400
403
|
* changing its length (9.13), and a write stands at the version it was made at until the
|
|
401
|
-
* accumulation moves past it (9.11). -1 where nothing has been
|
|
404
|
+
* accumulation moves past it (9.11). -1 where nothing has been materialized.
|
|
402
405
|
*/
|
|
403
|
-
|
|
406
|
+
materializedAt: number
|
|
404
407
|
/**
|
|
405
|
-
* Whether `value` is a write rather than a
|
|
408
|
+
* Whether `value` is a write rather than a materialization.
|
|
406
409
|
*
|
|
407
|
-
* WHETHER `value` IS A WRITE rather than a
|
|
410
|
+
* WHETHER `value` IS A WRITE rather than a materialization, while `materializedAt` stands. A value
|
|
408
411
|
* forwarding this stream serves the write the way it would have served a production (11.59).
|
|
409
412
|
*/
|
|
410
413
|
written: boolean
|
|
@@ -413,7 +416,7 @@ type Stream = {
|
|
|
413
416
|
*
|
|
414
417
|
* HOW MANY CHUNKS A READER HAS BEEN TOLD OF. Every chunk as it lands, without a cap; with one,
|
|
415
418
|
* only what the window has released (5.18) — the rest are in `chunks` for a cursor (5.10) and
|
|
416
|
-
* out of what a read
|
|
419
|
+
* out of what a read materializes until the release.
|
|
417
420
|
*/
|
|
418
421
|
visible: number
|
|
419
422
|
/** The run in flight, told to return when a newer run supersedes it. */
|
|
@@ -467,7 +470,7 @@ function newStream(source: Cell | null): Stream {
|
|
|
467
470
|
chunks: EMPTY_RING,
|
|
468
471
|
version: 0,
|
|
469
472
|
visible: 0,
|
|
470
|
-
|
|
473
|
+
materializedAt: -1,
|
|
471
474
|
written: false,
|
|
472
475
|
iterator: null,
|
|
473
476
|
run: 0,
|
|
@@ -495,7 +498,7 @@ function becomeStream(cell: Cell, source: Cell | null): Stream {
|
|
|
495
498
|
* every other producer's. A failed or superseded run never reaches here, so it starts no clock.
|
|
496
499
|
*/
|
|
497
500
|
function closeRun(cell: Cell, stream: Stream): void {
|
|
498
|
-
//
|
|
501
|
+
// Materialized here so 9.8's gates run on the accumulation the run closed with.
|
|
499
502
|
if (heldValue(cell) === undefined) return
|
|
500
503
|
stream.closed = true
|
|
501
504
|
const expiry = cell.expiry
|
|
@@ -556,8 +559,12 @@ export function isStreamBody(body: (args: never) => unknown): boolean {
|
|
|
556
559
|
* under JSC; adding a DEAD boolean back put all three within 0.6%, which is the proof it
|
|
557
560
|
* was layout and not work — that same dead boolean then cost the probe walk 6-7%.
|
|
558
561
|
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
562
|
+
* Above the budget is not free under JSC, though: three fields more (the dead-reader
|
|
563
|
+
* counts and `probeCursor`) moved `construct a value` and the derived case +6-8% there,
|
|
564
|
+
* and three UNUSED fields on the old record moved them the same — layout, not work.
|
|
565
|
+
*
|
|
566
|
+
* So the boundary is worth KNOWING and not worth engineering around. A field costs a few
|
|
567
|
+
* ns of construction; getting under costs a structural change that the next field added here
|
|
561
568
|
* silently undoes, and it re-rolls in a different direction for every row. If
|
|
562
569
|
* construction ever needs that 11%, shed fields — do not build machinery to shed them.
|
|
563
570
|
* And when a V8-only move of this size shows up on a graph row, the first thing to check
|
|
@@ -568,20 +575,24 @@ class Cell {
|
|
|
568
575
|
* Who reads this value, on the value edge.
|
|
569
576
|
*
|
|
570
577
|
* ARRAYS, not sets. The mark walk walks this per write and a set's iterator
|
|
571
|
-
* allocates
|
|
578
|
+
* allocates. A disposed reader is left in place and counted rather than found — see
|
|
579
|
+
* `dropFrom` in `tracking.ts` — so a walk over this skips `!alive`.
|
|
572
580
|
*/
|
|
573
581
|
declare readers: Subscriber[]
|
|
582
|
+
declare deadReaders: number
|
|
574
583
|
/**
|
|
575
584
|
* Who asked about this value's PROBES — a different edge from `readers`, and see
|
|
576
585
|
* `markProbes` for what that buys.
|
|
577
586
|
*/
|
|
578
587
|
declare probeReaders: Subscriber[]
|
|
588
|
+
declare deadProbeReaders: number
|
|
579
589
|
declare sources: Cell[]
|
|
580
590
|
/**
|
|
581
591
|
* The values whose PROBES this body asked about — a `Cell` is a `Subscriber`, so it
|
|
582
592
|
* carries the reader half of the probe edge as well as the source half.
|
|
583
593
|
*/
|
|
584
594
|
declare probeSources: Cell[]
|
|
595
|
+
declare probeCursor: number
|
|
585
596
|
declare cursor: number
|
|
586
597
|
/** See `Subscriber.seen`. */
|
|
587
598
|
declare seen: Set<Cell> | null
|
|
@@ -728,7 +739,7 @@ class Cell {
|
|
|
728
739
|
* Whether a read may ever take the field straight, fixed at construction.
|
|
729
740
|
*
|
|
730
741
|
* WHETHER A READ MAY EVER TAKE THE FIELD STRAIGHT, fixed at construction: no expiry to
|
|
731
|
-
* check and no accumulation to
|
|
742
|
+
* check and no accumulation to materialize. `setDirect` reads this one field where it
|
|
732
743
|
* would otherwise read one per mechanism that needs the read to go a frame in.
|
|
733
744
|
*/
|
|
734
745
|
declare plain: boolean
|
|
@@ -801,9 +812,12 @@ class Cell {
|
|
|
801
812
|
) {
|
|
802
813
|
// Every field, in one order, `undefined` included.
|
|
803
814
|
this.readers = []
|
|
815
|
+
this.deadReaders = 0
|
|
804
816
|
this.probeReaders = []
|
|
817
|
+
this.deadProbeReaders = 0
|
|
805
818
|
this.sources = []
|
|
806
819
|
this.probeSources = []
|
|
820
|
+
this.probeCursor = 0
|
|
807
821
|
this.cursor = 0
|
|
808
822
|
this.seen = null
|
|
809
823
|
this.seenThrough = 0
|
|
@@ -951,7 +965,7 @@ function capFor(
|
|
|
951
965
|
*
|
|
952
966
|
* So the framework takes the cheap, predictable default — `===` is what JavaScript
|
|
953
967
|
* means by "the same value" — and the app names `identity: structural` on the values
|
|
954
|
-
* where re-
|
|
968
|
+
* where re-materialization is the common case. 5.5's "not equal wherever it cannot
|
|
955
969
|
* decide" is unchanged; it is just no longer the default reading.
|
|
956
970
|
*
|
|
957
971
|
* `identity` is discriminated by ARITY (5.3), once, at construction — not per compare.
|
|
@@ -1088,7 +1102,7 @@ export function flushSync(): void {
|
|
|
1088
1102
|
* is never direct, so every read of it goes a frame in to `readIndirect` — which is
|
|
1089
1103
|
* exactly where 6.5 and 6.6 are answered, and is why a value declaring no `ttl`
|
|
1090
1104
|
* never reaches a clock. See `Expiry.ts`. A stream is never direct either, for the same
|
|
1091
|
-
* shape of reason: its read is where the accumulation is
|
|
1105
|
+
* shape of reason: its read is where the accumulation is materialized (9.1).
|
|
1092
1106
|
*/
|
|
1093
1107
|
function setDirect(cell: Cell, when: boolean): void {
|
|
1094
1108
|
cell.direct = when && cell.plain
|
|
@@ -1294,6 +1308,7 @@ function markProbes(cell: Cell, pass: number): void {
|
|
|
1294
1308
|
const readers = cell.readers
|
|
1295
1309
|
for (let index = 0; index < readers.length; index += 1) {
|
|
1296
1310
|
const reader = readers[index] as Subscriber
|
|
1311
|
+
if (!reader.alive) continue
|
|
1297
1312
|
if (!reader.isEffect) markProbes(reader as unknown as Cell, pass)
|
|
1298
1313
|
else if (reader.adopter !== null)
|
|
1299
1314
|
markProbes(reader.adopter as unknown as Cell, pass)
|
|
@@ -1423,7 +1438,7 @@ function drainPending(): void {
|
|
|
1423
1438
|
// derived CELL on 2.13's aborted-body path, and for one of those `state` is not a
|
|
1424
1439
|
// reminder to run — it IS the pull the next read takes (`state !== CLEAN` is what sends
|
|
1425
1440
|
// a read a frame in). Forced back to `CLEAN` it is not deaf, it is settled: the read
|
|
1426
|
-
// path skips `resolve`, the aborted body is never
|
|
1441
|
+
// path skips `resolve`, the aborted body is never rerun and the `ready()` guard that
|
|
1427
1442
|
// queued it answers false for the life of the page — undoing, on any throw anywhere in
|
|
1428
1443
|
// the drain, exactly what that push was added to repair.
|
|
1429
1444
|
for (let index = pendingAt; index < pendingCount; index += 1) {
|
|
@@ -1732,7 +1747,7 @@ function openWindow(cell: Cell, cap: Cap): void {
|
|
|
1732
1747
|
*
|
|
1733
1748
|
* THE WAY IN, AND THE ORDER IS 4.3 THEN 4.4: the schema gates, and what it RETURNS is
|
|
1734
1749
|
* what `transform` receives — not the original value. A transform reading the raw input
|
|
1735
|
-
* where a schema had
|
|
1750
|
+
* where a schema had normalized it is a second answer to what this value holds.
|
|
1736
1751
|
* What it hands back is the ordinal, or the refusal a gate answered with (4.18) — `unknown`
|
|
1737
1752
|
* because the one caller that cares tells the two apart by type.
|
|
1738
1753
|
*/
|
|
@@ -1754,7 +1769,7 @@ function accept(cell: Cell, value: unknown): unknown {
|
|
|
1754
1769
|
if (transform === null) return produce(cell, checked)
|
|
1755
1770
|
// 4.7 — A `transform` RUNS UNTRACKED: a write can be made from inside a tracked body
|
|
1756
1771
|
// (`watch(() => target.set(…))`, a recompute that writes), and run tracked, the transform's
|
|
1757
|
-
// reads were attributed to that reader — which then
|
|
1772
|
+
// reads were attributed to that reader — which then reran on a value it never read, with
|
|
1758
1773
|
// those reads spliced into its cursor-ordered `sources`.
|
|
1759
1774
|
const shaped = untracked(transform, checked, undefined)
|
|
1760
1775
|
// 4.6 AND 4.9: a `transform` that returned a refusal stops the write here — nothing is
|
|
@@ -1829,7 +1844,7 @@ function writeStream(cell: Cell, value: unknown): unknown {
|
|
|
1829
1844
|
function standWrite(cell: Cell, stream: Stream, value: unknown): number {
|
|
1830
1845
|
// 7.9, as `produce` settles it.
|
|
1831
1846
|
if (cell.owes === OWES_RELOAD) cell.owes = OWES_NOTHING
|
|
1832
|
-
// 5.2 against what is served now, which on a stream may not have been
|
|
1847
|
+
// 5.2 against what is served now, which on a stream may not have been materialized yet.
|
|
1833
1848
|
if (cell.servable) {
|
|
1834
1849
|
const previous = heldValue(cell)
|
|
1835
1850
|
if (
|
|
@@ -1840,7 +1855,7 @@ function standWrite(cell: Cell, stream: Stream, value: unknown): number {
|
|
|
1840
1855
|
return cell.ordinal
|
|
1841
1856
|
}
|
|
1842
1857
|
cell.value = value
|
|
1843
|
-
stream.
|
|
1858
|
+
stream.materializedAt = stream.version
|
|
1844
1859
|
stream.written = true
|
|
1845
1860
|
const moved = !cell.servable || cell.failure !== undefined
|
|
1846
1861
|
landChunks(cell)
|
|
@@ -2015,13 +2030,13 @@ function follow(cell: Cell, stream: Stream, inner: Cell): Error | undefined {
|
|
|
2015
2030
|
if (standing !== null) unfollow(cell)
|
|
2016
2031
|
// What `standWrite`'s identity gate compares against is what is served now.
|
|
2017
2032
|
heldValue(cell)
|
|
2018
|
-
stream.
|
|
2033
|
+
stream.materializedAt = stream.version
|
|
2019
2034
|
stream.written = true
|
|
2020
2035
|
const stop = watchOver([inner], cell, (): undefined => {
|
|
2021
2036
|
// The accumulation moved past the write. The drop is `wakeStreams`'s or the read's, not
|
|
2022
2037
|
// this one's: a mirror runs inside the mark walk over `inner.readers`, and detaching here
|
|
2023
2038
|
// would splice the list that walk is iterating.
|
|
2024
|
-
if (!stream.written || stream.
|
|
2039
|
+
if (!stream.written || stream.materializedAt !== stream.version)
|
|
2025
2040
|
return undefined
|
|
2026
2041
|
if (inner.owes !== OWES_NOTHING) settleOwed(inner)
|
|
2027
2042
|
// 11.16: the followed value has nothing to serve, so neither has this.
|
|
@@ -2060,7 +2075,7 @@ function unserve(cell: Cell): void {
|
|
|
2060
2075
|
}
|
|
2061
2076
|
|
|
2062
2077
|
function mirror(cell: Cell, inner: Cell): void {
|
|
2063
|
-
// 7.13 through the adoption: the re-settle a
|
|
2078
|
+
// 7.13 through the adoption: the re-settle a rerun of the adopter makes reaches a reload the
|
|
2064
2079
|
// adopted value owes, rather than finding it unservable and giving up.
|
|
2065
2080
|
if (inner.owes !== OWES_NOTHING) settleOwed(inner)
|
|
2066
2081
|
const adopted = inner.stream
|
|
@@ -2121,7 +2136,7 @@ function dropAdoption(cell: Cell): void {
|
|
|
2121
2136
|
* Makes an adopter forward an adopted stream's buffer, ring and ordinals.
|
|
2122
2137
|
*
|
|
2123
2138
|
* 11.15 FOR A STREAM: the adopter takes the adopted value's buffer, ring and ordinals by
|
|
2124
|
-
* reference and
|
|
2139
|
+
* reference and materializes from them on its own reads, through its own gates (11.14, 9.8).
|
|
2125
2140
|
* Mirroring the value instead copied the accumulation once per wake whether or not anything
|
|
2126
2141
|
* read it, and handed a cursor over the adopter whole accumulations where it wanted chunks.
|
|
2127
2142
|
*/
|
|
@@ -2136,8 +2151,8 @@ function forwardStream(cell: Cell, inner: Cell, adopted: Stream): void {
|
|
|
2136
2151
|
stream.chunks = adopted.chunks
|
|
2137
2152
|
stream.version = adopted.version
|
|
2138
2153
|
stream.visible = adopted.visible
|
|
2139
|
-
if (adopted.written && adopted.
|
|
2140
|
-
stream.
|
|
2154
|
+
if (adopted.written && adopted.materializedAt === adopted.version) {
|
|
2155
|
+
stream.materializedAt = stream.version
|
|
2141
2156
|
stream.written = true
|
|
2142
2157
|
gateInto(cell, inner.value)
|
|
2143
2158
|
}
|
|
@@ -2173,9 +2188,9 @@ function stopForwarding(cell: Cell): void {
|
|
|
2173
2188
|
* THE ABORT. A body that reads a value which has not landed throws `NotLanded`, and
|
|
2174
2189
|
* this catches it and returns — the body is ABANDONED, not retried in place. The read
|
|
2175
2190
|
* that threw has already registered this cell as a reader of it (tracking happens
|
|
2176
|
-
* before the throw), so the source landing is what
|
|
2191
|
+
* before the throw), so the source landing is what reruns the body.
|
|
2177
2192
|
*
|
|
2178
|
-
* "Throw a sentinel and
|
|
2193
|
+
* "Throw a sentinel and rerun" was refused once on three grounds. Two are answered
|
|
2179
2194
|
* by the reach starting the work (1.7) — the loads are in flight before the body
|
|
2180
2195
|
* runs, so the abort costs a re-entry rather than a round trip. The third, that
|
|
2181
2196
|
* nothing on a server wakes an abandoned body, is answered by the sink rather than
|
|
@@ -2188,6 +2203,8 @@ function run(cell: Cell): void {
|
|
|
2188
2203
|
// where the cascade would try: `a` reads `b` and `b` reads `a`. Without this the
|
|
2189
2204
|
// recursion is unbounded and the failure is a stack overflow rather than a value.
|
|
2190
2205
|
if (cell.running) return
|
|
2206
|
+
// A released orphan read again (12.14) — see `tombstone`.
|
|
2207
|
+
if (!cell.alive) revive(cell as unknown as Subscriber)
|
|
2191
2208
|
cell.generation += 1
|
|
2192
2209
|
cell.state = CLEAN
|
|
2193
2210
|
cell.running = true
|
|
@@ -2199,9 +2216,7 @@ function run(cell: Cell): void {
|
|
|
2199
2216
|
// THE RUN'S CURSOR, spelled out rather than behind a call frame on a path walked once
|
|
2200
2217
|
// per node per write. Zero is also "has diverged nowhere yet": see `tracking.ts`.
|
|
2201
2218
|
reader.cursor = 0
|
|
2202
|
-
|
|
2203
|
-
// to stop waking, and a body reads few probes where it reads many values.
|
|
2204
|
-
if (reader.probeSources.length !== 0) detachProbes(reader)
|
|
2219
|
+
reader.probeCursor = 0
|
|
2205
2220
|
let produced: unknown
|
|
2206
2221
|
try {
|
|
2207
2222
|
// 11.32 — UNDER THE VALUE'S OWN SCOPE, not the ambient one. A drain runs every body a
|
|
@@ -2211,6 +2226,8 @@ function run(cell: Cell): void {
|
|
|
2211
2226
|
} catch (thrown) {
|
|
2212
2227
|
TRACKING.reader = previous
|
|
2213
2228
|
if (reader.cursor < reader.sources.length) truncate(reader)
|
|
2229
|
+
if (reader.probeCursor < reader.probeSources.length)
|
|
2230
|
+
truncateProbes(reader)
|
|
2214
2231
|
cell.running = false
|
|
2215
2232
|
if (thrown instanceof NotLanded) {
|
|
2216
2233
|
cell.loading = true
|
|
@@ -2235,6 +2252,8 @@ function run(cell: Cell): void {
|
|
|
2235
2252
|
// that is the only thing the truncation has to do — so the test is here and the call
|
|
2236
2253
|
// is the rare arm.
|
|
2237
2254
|
if (reader.cursor < reader.sources.length) truncate(reader)
|
|
2255
|
+
// And a guard this run did not take stops waking it.
|
|
2256
|
+
if (reader.probeCursor < reader.probeSources.length) truncateProbes(reader)
|
|
2238
2257
|
cell.running = false
|
|
2239
2258
|
// A VALUE THAT WAS ADOPTING AND IS NOT ANY MORE. Behind the `adoption` load so every
|
|
2240
2259
|
// other recompute pays one field test rather than the brand read below it.
|
|
@@ -2513,7 +2532,7 @@ function acceptChunk(cell: Cell, stream: Stream, chunk: unknown): void {
|
|
|
2513
2532
|
/**
|
|
2514
2533
|
* Tells readers of every chunk landed so far, waking them at most once per task.
|
|
2515
2534
|
*
|
|
2516
|
-
* What a reader is told of: every chunk landed so far becomes what a read
|
|
2535
|
+
* What a reader is told of: every chunk landed so far becomes what a read materializes, and the
|
|
2517
2536
|
* wake goes out at most once per task (9.9).
|
|
2518
2537
|
*/
|
|
2519
2538
|
function releaseChunks(cell: Cell, stream: Stream): void {
|
|
@@ -2527,7 +2546,7 @@ function releaseChunks(cell: Cell, stream: Stream): void {
|
|
|
2527
2546
|
* The streams holding a wake, all drained by one task.
|
|
2528
2547
|
*
|
|
2529
2548
|
* THE STREAMS HOLDING A WAKE, and one task drains all of them — never a task per stream.
|
|
2530
|
-
* The pull is untouched: a read taken before the task runs
|
|
2549
|
+
* The pull is untouched: a read taken before the task runs materializes what has landed
|
|
2531
2550
|
* (12.12), so what waits is only the telling.
|
|
2532
2551
|
*/
|
|
2533
2552
|
const WAKING: Cell[] = []
|
|
@@ -2602,18 +2621,18 @@ function landChunks(cell: Cell): void {
|
|
|
2602
2621
|
}
|
|
2603
2622
|
|
|
2604
2623
|
/**
|
|
2605
|
-
*
|
|
2624
|
+
* Materializes a stream's visible chunks into `value`, through both gates.
|
|
2606
2625
|
*
|
|
2607
2626
|
* 9.1 AND 9.8: the read that asks is where the accumulation becomes a `Stored`, through both
|
|
2608
2627
|
* gates, once per chunk count however many reads ask.
|
|
2609
2628
|
*/
|
|
2610
|
-
function
|
|
2611
|
-
if (stream.
|
|
2629
|
+
function materialize(cell: Cell, stream: Stream): void {
|
|
2630
|
+
if (stream.materializedAt === stream.version || !cell.servable) return
|
|
2612
2631
|
// 9.11: the accumulation moved past a write following a `Reactive`, which lets go of it here
|
|
2613
2632
|
// where no chunk's wake did — a patch, or a run that closed empty. Only an own stream follows;
|
|
2614
2633
|
// a forwarding one's `adoption` is what it forwards.
|
|
2615
2634
|
if (cell.adoption !== null && stream.source === null) unfollow(cell)
|
|
2616
|
-
stream.
|
|
2635
|
+
stream.materializedAt = stream.version
|
|
2617
2636
|
stream.written = false
|
|
2618
2637
|
gateInto(cell, stream.chunks.slice(0, stream.visible))
|
|
2619
2638
|
}
|
|
@@ -2658,7 +2677,7 @@ function gateThrough(cell: Cell, raw: unknown): void {
|
|
|
2658
2677
|
*/
|
|
2659
2678
|
function heldValue(cell: Cell): unknown {
|
|
2660
2679
|
const stream = cell.stream
|
|
2661
|
-
if (stream !== null)
|
|
2680
|
+
if (stream !== null) materialize(cell, stream)
|
|
2662
2681
|
return cell.value
|
|
2663
2682
|
}
|
|
2664
2683
|
|
|
@@ -2821,7 +2840,7 @@ function readIndirect(cell: Cell): unknown {
|
|
|
2821
2840
|
// 6.5: THE READ THAT FOLLOWS. Every value carrying an expiry arrives here and no
|
|
2822
2841
|
// other value does, which is the whole of what `setDirect` buys.
|
|
2823
2842
|
if (cell.expiry !== null) expire(cell)
|
|
2824
|
-
if (cell.stream !== null)
|
|
2843
|
+
if (cell.stream !== null) materialize(cell, cell.stream)
|
|
2825
2844
|
if (!cell.servable) {
|
|
2826
2845
|
// 2.3: a failed producer with nothing landed is the one case holding no value
|
|
2827
2846
|
// and a refusal that explains the absence.
|
|
@@ -2847,7 +2866,7 @@ const EMPTY_RING = Object.freeze([]) as unknown as unknown[]
|
|
|
2847
2866
|
* so a shared subgraph is re-walked once per path that reaches it, and a 26-cell DAG
|
|
2848
2867
|
* with 130 edges cost 19,660 edge visits and 4,010 hash operations per probe read.
|
|
2849
2868
|
*
|
|
2850
|
-
* The stamp
|
|
2869
|
+
* The stamp MEMOIZES, and it is sound for one reason worth stating: within a pass,
|
|
2851
2870
|
* "already visited while the walk is still running" IMPLIES "not busy", because a busy
|
|
2852
2871
|
* node short-circuits the whole walk at its first visit. So a second arrival at a
|
|
2853
2872
|
* stamped node answers false without descending.
|
|
@@ -2864,7 +2883,7 @@ let PROBE_PASS = 0
|
|
|
2864
2883
|
* Propagation, derived on the read that asks (11.22) rather than held as a
|
|
2865
2884
|
* subscription per source. 11.20: a value is busy where a source it read is busy —
|
|
2866
2885
|
* and the premise is what `busy` MEANS, not which trigger produced it, so a refresh
|
|
2867
|
-
* over a landed source reports through here even though the body never
|
|
2886
|
+
* over a landed source reports through here even though the body never reran.
|
|
2868
2887
|
*/
|
|
2869
2888
|
function walkBusy(cell: Cell, pass: number): boolean {
|
|
2870
2889
|
if (cell.loading) return true
|
|
@@ -2948,7 +2967,7 @@ function busyNow(cell: Cell): boolean {
|
|
|
2948
2967
|
* serving the figure it had computed before — right by its own lights, and a lie about the
|
|
2949
2968
|
* data. Nothing in the output says so, which is why this is a walk and not a field.
|
|
2950
2969
|
*
|
|
2951
|
-
* The stamp
|
|
2970
|
+
* The stamp MEMOIZES the same way `walkBusy`'s does and is sound for the mirrored reason:
|
|
2952
2971
|
* this walk short-circuits on NOT ready, so a second arrival at a node the walk is still
|
|
2953
2972
|
* inside is a node that has not refused, and answering `true` there is answering what the
|
|
2954
2973
|
* first visit will.
|
|
@@ -3034,7 +3053,7 @@ function settled(cell: Cell, untilStreaming = false): Promise<unknown> {
|
|
|
3034
3053
|
const waiter: Subscriber = newSubscriber([cell], null)
|
|
3035
3054
|
waiter.notify = (): void => {
|
|
3036
3055
|
// THE WAITER PULLS. A value built from a source that has just landed is marked and
|
|
3037
|
-
// not yet
|
|
3056
|
+
// not yet rerun — nothing else reads it — so asking whether it is busy before
|
|
3038
3057
|
// pulling it answers from the run that aborted, and the await never settles.
|
|
3039
3058
|
if (cell.state !== CLEAN) resolve(cell)
|
|
3040
3059
|
if (untilStreaming && cell.stream !== null) {
|
|
@@ -3074,7 +3093,7 @@ function settled(cell: Cell, untilStreaming = false): Promise<unknown> {
|
|
|
3074
3093
|
* than discovered — and one source given is what `s.watch` means. What was here before was
|
|
3075
3094
|
* a second copy of the subscriber, the disposer, the re-entrancy guard and 12.8, which is
|
|
3076
3095
|
* exactly the divergence 12.9 forbids: the copy had no `catch` at all, so a throwing effect
|
|
3077
|
-
*
|
|
3096
|
+
* reran into the same throw on every write where `watch` stops.
|
|
3078
3097
|
*
|
|
3079
3098
|
* The value is handed over rather than read, so the effect never reaches the tracked path
|
|
3080
3099
|
* and never throws `NotLanded` from this source.
|
|
@@ -3290,6 +3309,7 @@ function owedBelow(cell: Cell): void {
|
|
|
3290
3309
|
const readers = cell.readers
|
|
3291
3310
|
for (let index = 0; index < readers.length; index += 1) {
|
|
3292
3311
|
const reader = readers[index] as Subscriber
|
|
3312
|
+
if (!reader.alive) continue
|
|
3293
3313
|
const below = (reader.isEffect
|
|
3294
3314
|
? reader.adopter
|
|
3295
3315
|
: reader) as unknown as Cell | null
|
|
@@ -3701,7 +3721,7 @@ const TAGGED = new Map<string, Set<WeakRef<Cell>>>()
|
|
|
3701
3721
|
* O(1) delete on eviction; here an evicted value drops its `ref` instead, and a handle whose value
|
|
3702
3722
|
* no longer points back at it is dead, the same as one whose value was collected. Because a bare
|
|
3703
3723
|
* sweep may never run, the filing that finds the array past `compactProducersAt` compacts it, and
|
|
3704
|
-
* the bar doubles past what survives, so the walk is
|
|
3724
|
+
* the bar doubles past what survives, so the walk is amortized over the filings between two.
|
|
3705
3725
|
*/
|
|
3706
3726
|
let filedProducers: WeakRef<Cell>[] = []
|
|
3707
3727
|
let compactProducersAt = 1024
|
|
@@ -3713,14 +3733,13 @@ let compactProducersAt = 1024
|
|
|
3713
3733
|
* subscribes to this, never to each entry — the probe edge `trackProbe` makes, from a node that
|
|
3714
3734
|
* is nothing but that edge.
|
|
3715
3735
|
*/
|
|
3716
|
-
|
|
3717
|
-
const
|
|
3718
|
-
const SIGNALS_BY_TAG = new Map<string, Signal[]>()
|
|
3736
|
+
const SIGNALS = new Map<string, Tracked>()
|
|
3737
|
+
const SIGNALS_BY_TAG = new Map<string, Tracked[]>()
|
|
3719
3738
|
/**
|
|
3720
|
-
* The bare selection's one signal. Not a key in `SIGNALS`: `{ tags: [] }`
|
|
3739
|
+
* The bare selection's one signal. Not a key in `SIGNALS`: `{ tags: [] }` canonicalizes to the
|
|
3721
3740
|
* empty set and matches nothing, where the bare selection matches every filed load in reach.
|
|
3722
3741
|
*/
|
|
3723
|
-
const EVERY_PRODUCER
|
|
3742
|
+
const EVERY_PRODUCER = newTracked()
|
|
3724
3743
|
|
|
3725
3744
|
function refOf(cell: Cell): WeakRef<Cell> {
|
|
3726
3745
|
let ref = cell.ref
|
|
@@ -3839,13 +3858,13 @@ function trackSignal(tags: string[] | null): void {
|
|
|
3839
3858
|
const reader = TRACKING.reader
|
|
3840
3859
|
if (reader === null) return
|
|
3841
3860
|
if (tags === null) {
|
|
3842
|
-
trackProbe(EVERY_PRODUCER
|
|
3861
|
+
trackProbe(EVERY_PRODUCER, reader)
|
|
3843
3862
|
return
|
|
3844
3863
|
}
|
|
3845
3864
|
const key = JSON.stringify([...new Set(tags)].sort())
|
|
3846
3865
|
let signal = SIGNALS.get(key)
|
|
3847
3866
|
if (signal === undefined) {
|
|
3848
|
-
signal =
|
|
3867
|
+
signal = newTracked()
|
|
3849
3868
|
SIGNALS.set(key, signal)
|
|
3850
3869
|
for (const tag of tags) {
|
|
3851
3870
|
const signals = SIGNALS_BY_TAG.get(tag)
|
|
@@ -3853,7 +3872,7 @@ function trackSignal(tags: string[] | null): void {
|
|
|
3853
3872
|
else signals.push(signal)
|
|
3854
3873
|
}
|
|
3855
3874
|
}
|
|
3856
|
-
trackProbe(signal
|
|
3875
|
+
trackProbe(signal, reader)
|
|
3857
3876
|
}
|
|
3858
3877
|
|
|
3859
3878
|
function markSignals(tags: string[]): void {
|
|
@@ -3936,6 +3955,7 @@ export function refreshOver(tags: string[] | null): void {
|
|
|
3936
3955
|
function readByAny(readers: Subscriber[]): boolean {
|
|
3937
3956
|
for (let index = 0; index < readers.length; index += 1) {
|
|
3938
3957
|
const reader = readers[index] as Subscriber
|
|
3958
|
+
if (!reader.alive) continue
|
|
3939
3959
|
if (!reader.isMirror || reader.adopter !== null) return true
|
|
3940
3960
|
}
|
|
3941
3961
|
return false
|
|
@@ -4151,7 +4171,7 @@ export function isStreaming(value: unknown): boolean {
|
|
|
4151
4171
|
*
|
|
4152
4172
|
* 16.32 — THE CHUNKS A STREAMING VALUE HAS TOLD ITS READERS OF PAST `from`, for a writer sending each
|
|
4153
4173
|
* once. Ungated they are read out of the accumulation in place, to `visible` rather than its length
|
|
4154
|
-
* (5.18): a read
|
|
4174
|
+
* (5.18): a read materializes a copy of the whole of it (9.1), which for a writer woken per chunk is
|
|
4155
4175
|
* n²/2 elements copied to send n. A schema or a transform has to see the whole accumulation (9.8),
|
|
4156
4176
|
* so a gated value answers from what a read would.
|
|
4157
4177
|
*/
|
|
@@ -4368,12 +4388,13 @@ function releaseOrphan(cell: Cell): void {
|
|
|
4368
4388
|
const stream = cell.stream
|
|
4369
4389
|
if (stream !== null && stream.source === null) stopStream(stream)
|
|
4370
4390
|
dropAdoption(cell)
|
|
4371
|
-
detach(cell as unknown as Subscriber)
|
|
4372
4391
|
cell.aborted = false
|
|
4373
4392
|
if (cell.body !== null && stream === null) {
|
|
4393
|
+
// OWED A RUN, so a read that reaches it again runs it — and `run` revives it first.
|
|
4394
|
+
tombstone(cell as unknown as Subscriber)
|
|
4374
4395
|
cell.state = DIRTY
|
|
4375
4396
|
cell.direct = false
|
|
4376
|
-
}
|
|
4397
|
+
} else detach(cell as unknown as Subscriber)
|
|
4377
4398
|
}
|
|
4378
4399
|
|
|
4379
4400
|
RELEASING.unread = (source: Tracked): void => releaseOrphan(source as Cell)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// through `contractOf` so a transport reads it off whichever face it was handed.
|
|
5
5
|
//
|
|
6
6
|
// DEFAULTS BEFORE THE KEY, IN ONE PLACE. 11.27: a call that left a member to the parameter and one
|
|
7
|
-
// that wrote it are one entry, so every reach, call and lookup takes the key through `
|
|
7
|
+
// that wrote it are one entry, so every reach, call and lookup takes the key through `normalized`
|
|
8
8
|
// — a caller that keyed the args as passed would file the same call twice, right in the value and
|
|
9
9
|
// wrong in the work.
|
|
10
10
|
//
|
|
@@ -54,7 +54,7 @@ export function gateOf(
|
|
|
54
54
|
* `args` itself where the face has no defaults or nothing was missing, so a caller that passed
|
|
55
55
|
* every member allocates nothing and a second application is free.
|
|
56
56
|
*/
|
|
57
|
-
export function
|
|
57
|
+
export function normalized<Args>(
|
|
58
58
|
face: Pick<Gated<unknown>, typeof PARAMETER_DEFAULTS>,
|
|
59
59
|
args: Args,
|
|
60
60
|
): Args {
|
|
@@ -62,12 +62,12 @@ export function normalised<Args>(
|
|
|
62
62
|
return defaults === null ? args : withDefaults(args, defaults)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
/** The entry key a call with `args` is filed under: `
|
|
65
|
+
/** The entry key a call with `args` is filed under: `normalized`, then 11.24's canonical form. */
|
|
66
66
|
export function keyOf(
|
|
67
67
|
face: Pick<Gated<unknown>, typeof PARAMETER_DEFAULTS>,
|
|
68
68
|
args: unknown,
|
|
69
69
|
): string {
|
|
70
|
-
return canonicalKey(
|
|
70
|
+
return canonicalKey(normalized(face, args))
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// ABSENT ARGS ARE `{}`: a wire writes the two alike (an empty query), so read apart they are one
|
|
@@ -6,12 +6,12 @@ import { putOwn } from '../putOwn.ts'
|
|
|
6
6
|
* RULEBOOK 11.24: members sorted by name, `undefined` members dropped, `Date` written ISO.
|
|
7
7
|
*
|
|
8
8
|
* 11.26: the key is taken BEFORE any gate runs, because a browser holds no copy of the gate — a
|
|
9
|
-
* key taken after
|
|
10
|
-
* handler with a
|
|
9
|
+
* key taken after normalizing is computed one way on a server and another in a browser, and every
|
|
10
|
+
* handler with a normalizing schema would miss its seed buffer and re-issue the call on hydration.
|
|
11
11
|
*
|
|
12
12
|
* 11.64: WHAT THE WIRE FORM CANNOT WRITE IS REFUSED AT THE REACH. `JSON.stringify` does not fail
|
|
13
13
|
* on any of it — a `Set`, a `Map` and a class instance each have no own enumerable keys and
|
|
14
|
-
*
|
|
14
|
+
* canonicalize to `{}`, and a non-finite number is written `null` — so two different reaches
|
|
15
15
|
* produced one key and 1.8 joined the second to the first's standing work. The value was right
|
|
16
16
|
* for the first caller and wrong for every other one, with nothing to see.
|
|
17
17
|
*/
|
|
@@ -55,7 +55,7 @@ function canonical(value: unknown, path: Steps): unknown {
|
|
|
55
55
|
// AN ORDINARY OBJECT, AND `putOwn` FOR THE ONE KEY THAT NEEDS IT: `__proto__` is a SETTER on
|
|
56
56
|
// `Object.prototype`, so `out[key] = …` for that key creates no own property and
|
|
57
57
|
// `JSON.stringify` never sees it — `{id: 1, __proto__: {role: 'admin'}}` and `{id: 1}` then
|
|
58
|
-
//
|
|
58
|
+
// canonicalize to one string, and 1.8 joins the second reach to the first's standing work.
|
|
59
59
|
// `Object.create(null)` closed that too, and V8 builds it in DICTIONARY mode: the shipped key
|
|
60
60
|
// took 49-56% of the time without it there, and a standing keyed reach 53%, where JSC reads
|
|
61
61
|
// level to 110% (`harness/scripts/sharedCases.ts`, two runs per engine).
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isFailed } from '../Failed.ts'
|
|
1
2
|
import { chunksFrom } from './Reactive.ts'
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -48,4 +49,9 @@ export async function* chunksOfStream(value: unknown): AsyncGenerator<unknown> {
|
|
|
48
49
|
void cursor.return?.(undefined)
|
|
49
50
|
}
|
|
50
51
|
if (failure !== undefined) throw failure
|
|
52
|
+
// 16.59, 16.60 — A RUN ENDED BY ITS TIMEOUT SETTLES with the chunks it had and holds its refusal
|
|
53
|
+
// rather than rejecting, so the refusal is read off the value and thrown after them: a stream
|
|
54
|
+
// answered over http then ends on it (16.68) rather than as though it had finished.
|
|
55
|
+
const held = (value as { error?: () => unknown }).error?.()
|
|
56
|
+
if (isFailed(held)) throw held
|
|
51
57
|
}
|
|
@@ -39,7 +39,7 @@ export { isKeyed, type Keyed } from './Keyed.ts'
|
|
|
39
39
|
export { NotLanded } from './NotLanded.ts'
|
|
40
40
|
// The server reaches the core through here rather than `./Reactive.ts`: `Reactive.ts` →
|
|
41
41
|
// `log.ts` → this barrel → `scoped.ts` → `Reactive.ts` is a cycle, and a server module loaded
|
|
42
|
-
// first that imports the core directly meets `MEMBER_KEYS` before it is
|
|
42
|
+
// first that imports the core directly meets `MEMBER_KEYS` before it is initialized.
|
|
43
43
|
export {
|
|
44
44
|
abandonLoad,
|
|
45
45
|
deadlineOf,
|