@abide/abide 0.103.0 → 0.104.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 +13 -0
- package/docs/REGISTRY.md +10 -10
- package/docs/RULEBOOK.md +18 -12
- package/docs/abide.md +22 -37
- package/package.json +1 -1
- package/src/compiler/TypeSession.ts +4 -6
- package/src/compiler/appMap.ts +1 -6
- package/src/compiler/checkApp.ts +9 -9
- package/src/compiler/checkedShadow.ts +1 -1
- package/src/compiler/compileApp.ts +44 -7
- package/src/compiler/projectShadow.ts +1 -1
- package/src/server/abideModules.ts +80 -43
- package/src/server/appTree.ts +10 -2
- package/src/server/generatedSurfaces.ts +12 -53
- package/src/server/versionOf.ts +1 -2
- package/src/shared/RESERVED_DIRECTORIES.ts +6 -0
- package/src/shared/reactive/Cursor.ts +66 -123
- package/src/shared/reactive/Reactive.ts +74 -28
- package/src/shared/reactive/callLoad.ts +4 -18
- package/src/shared/reactive/chunksOfStream.ts +10 -34
- package/src/shared/routePattern.ts +21 -8
|
@@ -19,7 +19,13 @@ 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 {
|
|
22
|
+
import {
|
|
23
|
+
CAUGHT_UP,
|
|
24
|
+
Cursor,
|
|
25
|
+
hasCursorBehind,
|
|
26
|
+
keepPlaces,
|
|
27
|
+
type Retained,
|
|
28
|
+
} from './Cursor.ts'
|
|
23
29
|
import { ENTRY_LIVES } from './ENTRY_LIVES.ts'
|
|
24
30
|
import { armTimer, type Expiry, expiryFor } from './Expiry.ts'
|
|
25
31
|
import { FOLLOWED } from './FOLLOWED.ts'
|
|
@@ -257,7 +263,7 @@ export type Reactive<
|
|
|
257
263
|
invalidate(): void
|
|
258
264
|
// biome-ignore lint/suspicious/noConfusingVoidType: `void` is the RETURN POSITION of an author's effect, and it is load-bearing. `undefined` there refuses `(v) => { read(v) }` — a block body returning nothing infers `void`, which is not assignable to `undefined` — so the rule's fix would reject the ordinary effect.
|
|
259
265
|
watch(effect: (value: Stored) => void | (() => void)): () => void
|
|
260
|
-
/**
|
|
266
|
+
/** What one run produced: the settled value once, or a stream's chunks to its close (2.5, 2.6). */
|
|
261
267
|
[Symbol.asyncIterator](): AsyncIterator<Produced>
|
|
262
268
|
then<Result>(
|
|
263
269
|
onSettled?: (value: Stored) => Result | PromiseLike<Result>,
|
|
@@ -459,7 +465,7 @@ type Stream = {
|
|
|
459
465
|
* Whether what is served is a run that closed.
|
|
460
466
|
*
|
|
461
467
|
* WHETHER WHAT IS SERVED IS A RUN THAT CLOSED, which is the only accumulation a deadline
|
|
462
|
-
* reaches (6.
|
|
468
|
+
* reaches (6.10). A run in flight has minted nothing, so `expiry.at` says nothing about it —
|
|
463
469
|
* without this the standing arm reads `at` as 0 and drops the value on its first read.
|
|
464
470
|
*/
|
|
465
471
|
closed: boolean
|
|
@@ -494,7 +500,7 @@ function becomeStream(cell: Cell, source: Cell | null): Stream {
|
|
|
494
500
|
}
|
|
495
501
|
|
|
496
502
|
/**
|
|
497
|
-
* Starts a closed run's clock: the close is a streaming producer's mint (6.
|
|
503
|
+
* Starts a closed run's clock: the close is a streaming producer's mint (6.10), as `produce` is
|
|
498
504
|
* every other producer's. A failed or superseded run never reaches here, so it starts no clock.
|
|
499
505
|
*/
|
|
500
506
|
function closeRun(cell: Cell, stream: Stream): void {
|
|
@@ -651,8 +657,8 @@ class Cell {
|
|
|
651
657
|
*/
|
|
652
658
|
declare schema: Schema<unknown> | null
|
|
653
659
|
/**
|
|
654
|
-
* A streaming producer's chunk buffer, for a cursor to walk (2.
|
|
655
|
-
* other value
|
|
660
|
+
* A streaming producer's chunk buffer, for a cursor to walk (2.6); `EMPTY_RING` on every
|
|
661
|
+
* other value.
|
|
656
662
|
*/
|
|
657
663
|
declare ring: unknown[]
|
|
658
664
|
/**
|
|
@@ -1519,9 +1525,7 @@ function produce(cell: Cell, produced: unknown): number {
|
|
|
1519
1525
|
// `ttl` must not pay.
|
|
1520
1526
|
const expiry = cell.expiry
|
|
1521
1527
|
const mintedAt = expiry === null ? 0 : Date.now()
|
|
1522
|
-
|
|
1523
|
-
// message, and not a JSON value either.
|
|
1524
|
-
if (produced !== undefined) cell.ordinal += 1
|
|
1528
|
+
cell.ordinal += 1
|
|
1525
1529
|
if (expiry !== null) {
|
|
1526
1530
|
expiry.at = mintedAt
|
|
1527
1531
|
// A production is what 6.7 released being replaced, so the value is no longer
|
|
@@ -2336,7 +2340,7 @@ function adoptAnswer(cell: Cell, produced: unknown): void {
|
|
|
2336
2340
|
*
|
|
2337
2341
|
* 4.16: AN OPENING `undefined` IS LOADED AND IS NOT A PRODUCTION THROUGH THE PIPELINE. No `schema`
|
|
2338
2342
|
* judges it, no `transform` shapes it, no window holds it and no `store` writes it — it is the value
|
|
2339
|
-
* being empty, and the first real production is what those are for. It mints no ordinal
|
|
2343
|
+
* being empty, and the first real production is what those are for. It mints no ordinal.
|
|
2340
2344
|
*/
|
|
2341
2345
|
function landOpening(cell: Cell): void {
|
|
2342
2346
|
const moved = settleProbes(cell)
|
|
@@ -2495,7 +2499,7 @@ async function pump(
|
|
|
2495
2499
|
cell.loading = false
|
|
2496
2500
|
// 3.7: `busy` held until the close, and this is the close.
|
|
2497
2501
|
if (moved) probesMoved(cell)
|
|
2498
|
-
// 6.
|
|
2502
|
+
// 6.10, AFTER THE CASCADE ABOVE: the entry is what a read would answer with, so it is taken
|
|
2499
2503
|
// once the last chunk has landed and the value is servable.
|
|
2500
2504
|
closeRun(cell, stream)
|
|
2501
2505
|
// 8.9: a stream is written once, at the close, with the accumulation and never a partial.
|
|
@@ -2773,7 +2777,7 @@ function beginLoad(cell: Cell): void {
|
|
|
2773
2777
|
*
|
|
2774
2778
|
* AND WHAT IS SERVED WAS MINTED, which every producer but a streaming one answers yes to the moment
|
|
2775
2779
|
* it is servable. A stream serves the run it is building (9.1), and that run has minted nothing
|
|
2776
|
-
* until it closes (6.
|
|
2780
|
+
* until it closes (6.10) — so a deadline has nothing to measure from and must not drop it.
|
|
2777
2781
|
*/
|
|
2778
2782
|
function standingLive(cell: Cell, expiry: Expiry): boolean {
|
|
2779
2783
|
if (!reloads(cell) || expiry.dropped || !cell.servable) return false
|
|
@@ -2936,6 +2940,53 @@ function failureOf(cell: Cell): unknown {
|
|
|
2936
2940
|
return at.failure
|
|
2937
2941
|
}
|
|
2938
2942
|
|
|
2943
|
+
/**
|
|
2944
|
+
* What one run of `cell` produced, as `for await` walks it.
|
|
2945
|
+
*
|
|
2946
|
+
* 2.5 AND 2.6 — the settling, yielded once, or a stream's chunks from the first to the close. Which
|
|
2947
|
+
* of the two is asked of the settling, since a value may only turn out to stream once its load
|
|
2948
|
+
* resolves (9.6).
|
|
2949
|
+
*
|
|
2950
|
+
* THE CURSOR IS THE WAKE AND THE SETTLING IS THE END (`Cursor.closeOn`).
|
|
2951
|
+
*/
|
|
2952
|
+
async function* producedRun(cell: Cell): AsyncGenerator<unknown> {
|
|
2953
|
+
const first = await settled(cell, true)
|
|
2954
|
+
if (first !== STREAMING) {
|
|
2955
|
+
yield first
|
|
2956
|
+
return
|
|
2957
|
+
}
|
|
2958
|
+
// THE RUN IS ITS RING: a new run's first chunk replaces the array, where a patch mutates it in
|
|
2959
|
+
// place (9.13) and only moves the base. A run with no chunk yet holds `EMPTY_RING`.
|
|
2960
|
+
let ring = cell.ring
|
|
2961
|
+
// 9.15 — a failure already standing is a revalidation's (2.4), and not this run's.
|
|
2962
|
+
const standing = failureOf(cell)
|
|
2963
|
+
const cursor = new Cursor(cell, cell.ringBase)
|
|
2964
|
+
cursor.closeOn(settled(cell))
|
|
2965
|
+
try {
|
|
2966
|
+
for (;;) {
|
|
2967
|
+
if (cell.ring !== ring) {
|
|
2968
|
+
// 9.16.
|
|
2969
|
+
if (ring !== EMPTY_RING) return
|
|
2970
|
+
ring = cell.ring
|
|
2971
|
+
}
|
|
2972
|
+
const chunk = cursor.take()
|
|
2973
|
+
if (chunk !== CAUGHT_UP) {
|
|
2974
|
+
yield chunk
|
|
2975
|
+
continue
|
|
2976
|
+
}
|
|
2977
|
+
if (cursor.closed) break
|
|
2978
|
+
await cursor.turn()
|
|
2979
|
+
}
|
|
2980
|
+
} finally {
|
|
2981
|
+
cursor.stop()
|
|
2982
|
+
}
|
|
2983
|
+
if (cursor.failure !== undefined) throw cursor.failure
|
|
2984
|
+
// 9.15 AND 16.59 — a run that failed after its first chunk, or one its timeout ended, settles
|
|
2985
|
+
// with what it had; the refusal stands beside it.
|
|
2986
|
+
const ended = failureOf(cell)
|
|
2987
|
+
if (ended !== undefined && ended !== standing) throw ended
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2939
2990
|
/**
|
|
2940
2991
|
* `busy` without subscribing.
|
|
2941
2992
|
*
|
|
@@ -4034,20 +4085,7 @@ const MEMBERS = {
|
|
|
4034
4085
|
return watchCell(this[CELL], effect)
|
|
4035
4086
|
},
|
|
4036
4087
|
[Symbol.asyncIterator](this: Callable): AsyncIterator<unknown> {
|
|
4037
|
-
|
|
4038
|
-
// 2.5: "the production in flight, from its start". Starting one ordinal back is
|
|
4039
|
-
// what includes it rather than skipping to whatever comes next — and clamped at
|
|
4040
|
-
// zero, where a value nothing has produced into must WAIT rather than hand over
|
|
4041
|
-
// the `undefined` it is holding.
|
|
4042
|
-
// A STREAM'S PRODUCTION IN FLIGHT IS THE STREAM, so its start is the run's first
|
|
4043
|
-
// chunk rather than the latest one.
|
|
4044
|
-
const from =
|
|
4045
|
-
cell.stream !== null
|
|
4046
|
-
? cell.ringBase
|
|
4047
|
-
: cell.ordinal > 0
|
|
4048
|
-
? cell.ordinal - 1
|
|
4049
|
-
: 0
|
|
4050
|
-
return new Cursor(cell, from)
|
|
4088
|
+
return producedRun(this[CELL])
|
|
4051
4089
|
},
|
|
4052
4090
|
// biome-ignore lint/suspicious/noThenProperty: RULEBOOK 1.4 — a `Reactive` MUST be thenable, which is what this rule warns about. Banning it buys a discrimination rather than a semantics, and 16.4's in-process `Rpc` arm is what that discrimination would have cost. 1.6 is the guard that makes thenability safe — the brand is read before `then`.
|
|
4053
4091
|
then(
|
|
@@ -4166,6 +4204,15 @@ export function isStreaming(value: unknown): boolean {
|
|
|
4166
4204
|
return stream !== null && stream !== undefined
|
|
4167
4205
|
}
|
|
4168
4206
|
|
|
4207
|
+
/**
|
|
4208
|
+
* A cursor over `value` that reads nothing and only wakes when it changes.
|
|
4209
|
+
*
|
|
4210
|
+
* PLACED PAST EVERY CHUNK, so it holds no place a patch has to keep (5.7) and is never behind.
|
|
4211
|
+
*/
|
|
4212
|
+
export function wakeOf(value: unknown): Cursor {
|
|
4213
|
+
return new Cursor((value as Callable)[CELL], Number.POSITIVE_INFINITY)
|
|
4214
|
+
}
|
|
4215
|
+
|
|
4169
4216
|
/**
|
|
4170
4217
|
* The chunks a streaming value has told its readers of, from index `from` on.
|
|
4171
4218
|
*
|
|
@@ -4347,8 +4394,7 @@ const SIMPLE_PROTOTYPE = brandedPrototype({
|
|
|
4347
4394
|
return undefined
|
|
4348
4395
|
}
|
|
4349
4396
|
cell.value = value
|
|
4350
|
-
|
|
4351
|
-
if (value !== undefined) cell.ordinal += 1
|
|
4397
|
+
cell.ordinal += 1
|
|
4352
4398
|
// `setDirect`, with `plain` known true by `simple`.
|
|
4353
4399
|
cell.direct = cell.state === CLEAN
|
|
4354
4400
|
if (cell.readers.length !== 0) markReaders(cell, DIRTY)
|
|
@@ -161,33 +161,19 @@ function timeLoad(attended: Attended, milliseconds: number | undefined): void {
|
|
|
161
161
|
}, milliseconds)
|
|
162
162
|
}
|
|
163
163
|
let settled = false
|
|
164
|
-
let wake = (): void => {}
|
|
165
164
|
const end = (): void => {
|
|
166
165
|
settled = true
|
|
167
166
|
clearTimeout(timer)
|
|
168
|
-
wake()
|
|
169
167
|
}
|
|
170
|
-
// A CHUNK IS PROGRESS
|
|
171
|
-
//
|
|
168
|
+
// A CHUNK IS PROGRESS, and the run's close ends the loop (2.6). What the run failed with is the
|
|
169
|
+
// load's to answer, so the loop only stops on it.
|
|
172
170
|
const followChunks = async (): Promise<void> => {
|
|
173
|
-
const cursor = (reached as AsyncIterable<unknown>)[
|
|
174
|
-
Symbol.asyncIterator
|
|
175
|
-
]()
|
|
176
171
|
try {
|
|
177
|
-
|
|
178
|
-
await new Promise<void>((resolve) => {
|
|
179
|
-
wake = resolve
|
|
180
|
-
cursor.next().then(
|
|
181
|
-
() => resolve(),
|
|
182
|
-
() => resolve(),
|
|
183
|
-
)
|
|
184
|
-
})
|
|
172
|
+
for await (const _ of reached as AsyncIterable<unknown>) {
|
|
185
173
|
if (settled || loadOf(reached) !== load) return
|
|
186
174
|
arm()
|
|
187
175
|
}
|
|
188
|
-
}
|
|
189
|
-
void cursor.return?.(undefined)
|
|
190
|
-
}
|
|
176
|
+
} catch {}
|
|
191
177
|
}
|
|
192
178
|
arm()
|
|
193
179
|
// 9.6 — a value behind rungs only turns out to stream once the onion resolves, so the question
|
|
@@ -1,54 +1,30 @@
|
|
|
1
1
|
import { isFailed } from '../Failed.ts'
|
|
2
|
-
import { chunksFrom } from './Reactive.ts'
|
|
2
|
+
import { chunksFrom, wakeOf } from './Reactive.ts'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The chunks of a streaming value, as they land, ending when its run closes.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the rest.
|
|
7
|
+
* WHAT A READ WOULD SERVE, chunk by chunk (16.32), rather than the ring `for await` walks: a gated
|
|
8
|
+
* value's chunks are its gated accumulation's (9.8). A cursor is the wake and the settling is the
|
|
9
|
+
* end (`Cursor.closeOn`).
|
|
11
10
|
*/
|
|
12
11
|
export async function* chunksOfStream(value: unknown): AsyncGenerator<unknown> {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
let failure: unknown
|
|
16
|
-
// ONE REACTION ON THE CLOSE FOR THE WHOLE STREAM, which wakes whichever wait is current. A
|
|
17
|
-
// `Promise.race` against the close per chunk left one reaction per chunk on it, held until the
|
|
18
|
-
// stream ended.
|
|
19
|
-
let wake: () => void = () => {}
|
|
20
|
-
void Promise.resolve(stream).then(
|
|
21
|
-
() => {
|
|
22
|
-
closed = true
|
|
23
|
-
wake()
|
|
24
|
-
},
|
|
25
|
-
(error: unknown) => {
|
|
26
|
-
closed = true
|
|
27
|
-
failure = error
|
|
28
|
-
wake()
|
|
29
|
-
},
|
|
30
|
-
)
|
|
31
|
-
const cursor = stream[Symbol.asyncIterator]()
|
|
12
|
+
const cursor = wakeOf(value)
|
|
13
|
+
cursor.closeOn(Promise.resolve(value as PromiseLike<unknown>))
|
|
32
14
|
let sent = 0
|
|
33
|
-
let woken = cursor.next()
|
|
34
15
|
try {
|
|
35
16
|
for (;;) {
|
|
36
|
-
if (!closed)
|
|
37
|
-
await new Promise<void>((resolve, reject) => {
|
|
38
|
-
wake = resolve
|
|
39
|
-
woken.then(() => resolve(), reject)
|
|
40
|
-
})
|
|
41
17
|
for (const chunk of chunksFrom(value, sent)) {
|
|
42
18
|
sent += 1
|
|
43
19
|
yield chunk
|
|
44
20
|
}
|
|
45
|
-
if (closed) break
|
|
46
|
-
|
|
21
|
+
if (cursor.closed) break
|
|
22
|
+
await cursor.turn()
|
|
47
23
|
}
|
|
48
24
|
} finally {
|
|
49
|
-
|
|
25
|
+
cursor.stop()
|
|
50
26
|
}
|
|
51
|
-
if (failure !== undefined) throw failure
|
|
27
|
+
if (cursor.failure !== undefined) throw cursor.failure
|
|
52
28
|
// 16.59, 16.60 — A RUN ENDED BY ITS TIMEOUT SETTLES with the chunks it had and holds its refusal
|
|
53
29
|
// rather than rejecting, so the refusal is read off the value and thrown after them: a stream
|
|
54
30
|
// answered over http then ends on it (16.68) rather than as though it had finished.
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
// router all read it and a constant crossing a seam lives where nothing pulls a graph in behind it.
|
|
12
12
|
|
|
13
13
|
import { LAUNCHER_PAGE } from './LAUNCHER_FILES.ts'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
HOLDS_SERVER,
|
|
16
|
+
RESERVED_DIRECTORIES,
|
|
17
|
+
SERVER_DIRECTORY,
|
|
18
|
+
} from './RESERVED_DIRECTORIES.ts'
|
|
15
19
|
|
|
16
20
|
type SegmentKind = 'static' | 'required' | 'optional' | 'rest'
|
|
17
21
|
|
|
@@ -84,22 +88,31 @@ export type Place =
|
|
|
84
88
|
* - `dependency` — 34.24: a package's, outside the root or under `node_modules`, whose directories
|
|
85
89
|
* are its package's layout and none of the app's;
|
|
86
90
|
* - `skipped` — under a dot directory, which no walk of the app enters;
|
|
87
|
-
* - `server`, `public`, `lib` or `tests` — 23.15: under that fence, the nearest to the root deciding
|
|
88
|
-
*
|
|
91
|
+
* - `server`, `public`, `lib` or `tests` — 23.15: under that fence, the nearest to the root deciding,
|
|
92
|
+
* save a `server` under a `lib` or a `tests`, which is `server` — the fence the browser's bundle
|
|
93
|
+
* is checked against (16.29) is never hidden by one above it (23.16);
|
|
89
94
|
* - `address` — 23.14: everywhere else, its directories the segments of its route.
|
|
90
95
|
*/
|
|
91
96
|
export function placeOf(path: string): Place {
|
|
92
97
|
if (path.startsWith('../') || path.startsWith('/')) return 'dependency'
|
|
93
98
|
const parts = path.split('/')
|
|
99
|
+
// A `lib` or a `tests` met first, which a `server` below it still overrides.
|
|
100
|
+
let fence: Place | undefined
|
|
94
101
|
// The last part is the file itself, which is never a directory.
|
|
95
102
|
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
96
103
|
const part = parts[index] as string
|
|
97
|
-
if (part
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
if (skipped(part))
|
|
105
|
+
return (
|
|
106
|
+
fence ?? (part === 'node_modules' ? 'dependency' : 'skipped')
|
|
107
|
+
)
|
|
108
|
+
if (fence !== undefined) {
|
|
109
|
+
if (part === SERVER_DIRECTORY) return 'server'
|
|
110
|
+
} else if (RESERVED_DIRECTORIES.has(part)) {
|
|
111
|
+
if (!HOLDS_SERVER.has(part)) return part as Place
|
|
112
|
+
fence = part as Place
|
|
113
|
+
}
|
|
101
114
|
}
|
|
102
|
-
return 'address'
|
|
115
|
+
return fence ?? 'address'
|
|
103
116
|
}
|
|
104
117
|
|
|
105
118
|
/** Whether a walk of the app passes over the entry `name`: a dot file or directory, or `node_modules`. */
|