@abide/abide 0.114.1 → 0.116.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 +18 -0
- package/docs/REGISTRY.md +38 -16
- package/docs/RULEBOOK.md +173 -12
- package/docs/abide.md +266 -24
- package/package.json +4 -2
- package/src/compiler/REACTIVE_MEMBERS.ts +1 -0
- package/src/compiler/classifyTemplate.ts +21 -14
- package/src/compiler/emitClient.ts +5 -1
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/IMAGE_TYPES.ts +10 -0
- package/src/server/OFFLINE_STALE.ts +10 -0
- package/src/server/answerRequest.ts +39 -10
- package/src/server/answerRpc.ts +25 -5
- package/src/server/answerWorker.ts +173 -28
- package/src/server/assistant.ts +81 -12
- package/src/server/claudeApi.ts +25 -20
- package/src/server/claudeCode.ts +30 -10
- package/src/server/createApp.ts +8 -2
- package/src/server/generatedSurfaces.ts +48 -14
- package/src/server/index.ts +2 -0
- package/src/server/render.ts +3 -2
- package/src/server/seedBuffer.ts +28 -9
- package/src/shared/KEEP_HEADER.ts +7 -0
- package/src/shared/KEEP_OPENED.ts +7 -0
- package/src/shared/KEPT_HEADER.ts +6 -0
- package/src/shared/LOAD_OFFLINE.ts +15 -0
- package/src/shared/OUTBOX_SENDING.ts +9 -0
- package/src/shared/REACHED_MESSAGE.ts +7 -0
- package/src/shared/answerOf.ts +14 -2
- package/src/shared/framing.ts +10 -2
- package/src/shared/index.ts +2 -0
- package/src/shared/outbox.ts +116 -0
- package/src/shared/reactive/Keyed.ts +9 -0
- package/src/shared/reactive/Reactive.ts +197 -3
- package/src/shared/reactive/SELECTION_MEMBERS.ts +1 -0
- package/src/shared/reactive/callLoad.ts +8 -0
- package/src/shared/reactive/index.ts +23 -1
- package/src/shared/rpc.ts +25 -5
- package/src/shared/rpcRequest.ts +12 -8
- package/src/ui/Router.ts +28 -7
- package/src/ui/bindFiles.ts +42 -0
- package/src/ui/index.ts +1 -0
- package/src/ui/outboxQueue.ts +427 -0
- package/src/ui/socketWire.ts +31 -2
- package/src/ui/wire.ts +111 -10
package/src/shared/index.ts
CHANGED
|
@@ -55,6 +55,7 @@ export {
|
|
|
55
55
|
// modules into the static closure of the entry every page imports.
|
|
56
56
|
export { navigate } from './navigate.ts'
|
|
57
57
|
export { online } from './online.ts'
|
|
58
|
+
export { outbox, type QueuedCall } from './outbox.ts'
|
|
58
59
|
export { onMenu } from './onMenu.ts'
|
|
59
60
|
export { type Principal, principal } from './principal.ts'
|
|
60
61
|
// EMITTED CODE'S: a prop the child writes (33.6, 33.7).
|
|
@@ -93,6 +94,7 @@ export {
|
|
|
93
94
|
type Shared,
|
|
94
95
|
type Store,
|
|
95
96
|
structural,
|
|
97
|
+
synced,
|
|
96
98
|
type Transformer,
|
|
97
99
|
watch,
|
|
98
100
|
} from './reactive/index.ts'
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// 46 — THE CALLS A BROWSER HAS NOT DELIVERED YET, asked of a selection as a probe is: `outbox()` for
|
|
2
|
+
// every one, `outbox(logCall)` and `logCall.outbox(pattern)` for one handler's. What it answers is a
|
|
3
|
+
// plain list, so a page counts, filters and walks it as it would any other, and each call carries
|
|
4
|
+
// what can be done with it.
|
|
5
|
+
//
|
|
6
|
+
// THE FACE, NOT THE QUEUE: the queue is the browser's (`#ui/outboxQueue.ts`), which the wire loads and
|
|
7
|
+
// which shows its line here through `showLine` and `showSending`. On a server nothing shows one, and
|
|
8
|
+
// `outbox` answers no calls (46.15).
|
|
9
|
+
|
|
10
|
+
import type { Failed } from './Failed.ts'
|
|
11
|
+
import type { JsonValue } from './JsonValue.ts'
|
|
12
|
+
import { ambient, type Reactive, type Selection } from './reactive/index.ts'
|
|
13
|
+
import { matchesPattern } from './reactive/matchesPattern.ts'
|
|
14
|
+
import { OUTBOX_SENDING } from './OUTBOX_SENDING.ts'
|
|
15
|
+
import type { Method } from './rpc.ts'
|
|
16
|
+
|
|
17
|
+
/** One call `outbox` holds: what it sends, when it was queued, how often it was sent, and its value. */
|
|
18
|
+
export type QueuedCall<Value = unknown> = {
|
|
19
|
+
/** The call's `idempotency-key`, the same on every send (46.10). */
|
|
20
|
+
id: string
|
|
21
|
+
method: Method
|
|
22
|
+
url: string
|
|
23
|
+
args: Record<string, JsonValue> | undefined
|
|
24
|
+
queuedAt: number
|
|
25
|
+
attempts: number
|
|
26
|
+
/**
|
|
27
|
+
* 46.18 — the value the call settled: its stand-in until the server answers, and its refusal as
|
|
28
|
+
* `error()` where the server refused it.
|
|
29
|
+
*/
|
|
30
|
+
value: Reactive<Value, Value, Failed<string, unknown>>
|
|
31
|
+
/** 46.12 — sends it again, at once. */
|
|
32
|
+
retry(): void
|
|
33
|
+
/** 46.13 — drops it, sent or not. */
|
|
34
|
+
discard(): void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A call as the queue shows it: the handler it is for, which a selection is matched against. */
|
|
38
|
+
export type ShownCall = QueuedCall & { address: string }
|
|
39
|
+
|
|
40
|
+
/** What a handler's calls settle to, read off its own `outbox` member; `unknown` for any other. */
|
|
41
|
+
type ValueOf<Chosen> = Chosen extends {
|
|
42
|
+
outbox(pattern?: never): QueuedCall<infer Value>[]
|
|
43
|
+
}
|
|
44
|
+
? Value
|
|
45
|
+
: unknown
|
|
46
|
+
|
|
47
|
+
/** A handler as a selection names it: its method and the address it is mounted at. */
|
|
48
|
+
type Handler = { method: Method; address: string | undefined }
|
|
49
|
+
|
|
50
|
+
/** A call as a selection is matched against it: its handler, and the args a pattern narrows. */
|
|
51
|
+
type Matched = Pick<ShownCall, 'method' | 'address' | 'args'>
|
|
52
|
+
|
|
53
|
+
const LINE = ambient<ShownCall[]>([])
|
|
54
|
+
/** The call in flight, or `undefined` where none is. */
|
|
55
|
+
const SENDING = ambient<Matched | undefined>(undefined)
|
|
56
|
+
|
|
57
|
+
// 46.11 — a bare `busy` hears a call in flight, which no value holds.
|
|
58
|
+
OUTBOX_SENDING.any = () => SENDING() !== undefined
|
|
59
|
+
|
|
60
|
+
/** 46.1 — shows the queue's line, oldest first. */
|
|
61
|
+
export function showLine(calls: ShownCall[]): void {
|
|
62
|
+
LINE.set(calls)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 46.11 — shows the call being sent, or that none is. */
|
|
66
|
+
export function showSending(call: Matched | undefined): void {
|
|
67
|
+
SENDING.set(call)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The calls waiting to be delivered and the ones refused, oldest first: every one, or the ones a
|
|
72
|
+
* handler names, narrowed by `pattern` as its own members narrow (46.1, 46.16).
|
|
73
|
+
*/
|
|
74
|
+
export function outbox<Chosen extends Selection>(
|
|
75
|
+
selection?: Chosen,
|
|
76
|
+
pattern?: Record<string, unknown>,
|
|
77
|
+
): QueuedCall<ValueOf<Chosen>>[] {
|
|
78
|
+
const line = LINE() as QueuedCall<ValueOf<Chosen>>[]
|
|
79
|
+
if (selection === undefined) return line
|
|
80
|
+
const handler = handlerOf(selection)
|
|
81
|
+
const named: QueuedCall<ValueOf<Chosen>>[] = []
|
|
82
|
+
if (handler === undefined) return named
|
|
83
|
+
for (const call of line)
|
|
84
|
+
if (forHandler(call as ShownCall, handler, pattern)) named.push(call)
|
|
85
|
+
return named
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 46.11 — whether a call `outbox` holds for `handler`, matching `pattern`, is in flight. */
|
|
89
|
+
export function sendingFor(
|
|
90
|
+
handler: Handler,
|
|
91
|
+
pattern: Record<string, unknown> | undefined,
|
|
92
|
+
): boolean {
|
|
93
|
+
const call = SENDING()
|
|
94
|
+
return call !== undefined && forHandler(call, handler, pattern)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function forHandler(
|
|
98
|
+
call: Matched,
|
|
99
|
+
handler: Handler,
|
|
100
|
+
pattern: Record<string, unknown> | undefined,
|
|
101
|
+
): boolean {
|
|
102
|
+
return (
|
|
103
|
+
call.method === handler.method &&
|
|
104
|
+
call.address === handler.address &&
|
|
105
|
+
matchesPattern(call.args, pattern)
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The handler a selection names, or `undefined` where it names none — a value, a key, or tags. */
|
|
110
|
+
function handlerOf(selection: object): Handler | undefined {
|
|
111
|
+
const handler = selection as Partial<Handler>
|
|
112
|
+
return typeof handler.method === 'string' &&
|
|
113
|
+
typeof handler.address === 'string'
|
|
114
|
+
? (handler as Handler)
|
|
115
|
+
: undefined
|
|
116
|
+
}
|
|
@@ -66,6 +66,7 @@ export type Keyed<
|
|
|
66
66
|
) => Reactive<Stored, Accepted, Failures, Produced>) & {
|
|
67
67
|
busy(pattern?: Partial<Args>): boolean
|
|
68
68
|
ready(pattern?: Partial<Args>): boolean
|
|
69
|
+
synced(pattern?: Partial<Args>): boolean
|
|
69
70
|
refresh(pattern?: Partial<Args>): void
|
|
70
71
|
invalidate(pattern?: Partial<Args>): void
|
|
71
72
|
/** The entries reached so far. Not app surface — the statics and the tests read it. */
|
|
@@ -462,6 +463,14 @@ export function createKeyed<
|
|
|
462
463
|
return false
|
|
463
464
|
return true
|
|
464
465
|
},
|
|
466
|
+
// 13.18 — ALL, as `ready` asks: one entry written here is one the server has not seen.
|
|
467
|
+
synced: (pattern?: Partial<Args>): boolean => {
|
|
468
|
+
askMembership()
|
|
469
|
+
for (const entry of entries.values())
|
|
470
|
+
if (matchesPattern(argsOf(entry), pattern) && !entry.synced())
|
|
471
|
+
return false
|
|
472
|
+
return true
|
|
473
|
+
},
|
|
465
474
|
refresh: (pattern?: Partial<Args>): void => {
|
|
466
475
|
for (const entry of entries.values())
|
|
467
476
|
if (matchesPattern(argsOf(entry), pattern)) entry.refresh()
|
|
@@ -225,6 +225,7 @@ export type Reactive<
|
|
|
225
225
|
busy(): boolean
|
|
226
226
|
ready(): boolean
|
|
227
227
|
error(): Failures | undefined
|
|
228
|
+
synced(): boolean
|
|
228
229
|
/**
|
|
229
230
|
* Narrows a caught error to this value's failure of that name.
|
|
230
231
|
*
|
|
@@ -731,6 +732,13 @@ class Cell {
|
|
|
731
732
|
declare tags: string[] | null
|
|
732
733
|
/** Whether `declare` built this value for a mutation, which a sweep invalidates (13.16). */
|
|
733
734
|
declare mutation: boolean
|
|
735
|
+
/**
|
|
736
|
+
* Whether what is held was written here since the producer last produced (3.10).
|
|
737
|
+
*
|
|
738
|
+
* Only ever true on a value with a body: a value with no producer has nothing to be out of
|
|
739
|
+
* step with (3.12). Moved by `writtenHere` and `producedAgain` alone, each owning its probe move.
|
|
740
|
+
*/
|
|
741
|
+
declare local: boolean
|
|
734
742
|
/** `null` on every value declaring no `store`, which is what keeps `serve` from persisting. */
|
|
735
743
|
declare store: Persisted | null
|
|
736
744
|
/**
|
|
@@ -881,6 +889,7 @@ class Cell {
|
|
|
881
889
|
: tags
|
|
882
890
|
this.mutation =
|
|
883
891
|
(options as { [MUTATION]?: true } | undefined)?.[MUTATION] === true
|
|
892
|
+
this.local = false
|
|
884
893
|
const store = options?.store
|
|
885
894
|
this.store =
|
|
886
895
|
store === undefined
|
|
@@ -1518,6 +1527,9 @@ function drainPending(): void {
|
|
|
1518
1527
|
* One production. A write that `identity` deems a repeat mints nothing and wakes nobody (5.2).
|
|
1519
1528
|
*/
|
|
1520
1529
|
function produce(cell: Cell, produced: unknown): number {
|
|
1530
|
+
// 3.11 — A PRODUCTION THE PRODUCER MADE puts the value back in step; one a write here made is
|
|
1531
|
+
// `writtenHere`'s to report, after it lands.
|
|
1532
|
+
if (cell.local && WRITING === 0) producedAgain(cell)
|
|
1521
1533
|
// 7.9: AN ACCEPTED PRODUCTION SETTLES THE RELOAD AN INVALIDATE OWED — ahead of the gate, since
|
|
1522
1534
|
// a write 5.2 collapses was still accepted. Left standing, the next read unserved this value
|
|
1523
1535
|
// and reloaded over it. A look above (`OWES_ABOVE`) is not this value's mark and stays: 11.8
|
|
@@ -2048,6 +2060,13 @@ function absorb(cell: Cell, result: unknown, answered: boolean): void {
|
|
|
2048
2060
|
else if (isAsyncGenerator(settled)) {
|
|
2049
2061
|
promoteToStream(cell, settled)
|
|
2050
2062
|
probesMoved(cell)
|
|
2063
|
+
}
|
|
2064
|
+
// 3.10 — WHAT A WRITE HERE SETTLED TO, and a body's stand-in for an answer it could not
|
|
2065
|
+
// get (16.81), are held as written here rather than produced.
|
|
2066
|
+
else if (!answered) acceptHere(cell, settled)
|
|
2067
|
+
else if (settled instanceof StandIn) {
|
|
2068
|
+
acceptHere(cell, settled.value)
|
|
2069
|
+
writtenHere(cell)
|
|
2051
2070
|
} else accept(cell, settled)
|
|
2052
2071
|
},
|
|
2053
2072
|
(failed) => {
|
|
@@ -2414,6 +2433,13 @@ function run(cell: Cell): void {
|
|
|
2414
2433
|
promoteToStream(cell, produced)
|
|
2415
2434
|
return
|
|
2416
2435
|
}
|
|
2436
|
+
// 16.81 — a stand-in the body could hand back at once: an answer a page started early (41.21)
|
|
2437
|
+
// that the worker answered from a kept copy (16.85).
|
|
2438
|
+
if (!primitive && produced instanceof StandIn) {
|
|
2439
|
+
acceptHere(cell, produced.value)
|
|
2440
|
+
writtenHere(cell)
|
|
2441
|
+
return
|
|
2442
|
+
}
|
|
2417
2443
|
if (
|
|
2418
2444
|
cell.bareWrite &&
|
|
2419
2445
|
typeof (produced as PromiseLike<unknown>).then !== 'function'
|
|
@@ -2654,6 +2680,7 @@ function beginAccumulation(cell: Cell, stream: Stream): void {
|
|
|
2654
2680
|
* whatever `identity` says about its last element.
|
|
2655
2681
|
*/
|
|
2656
2682
|
function acceptChunk(cell: Cell, stream: Stream, chunk: unknown): void {
|
|
2683
|
+
if (cell.local) producedAgain(cell)
|
|
2657
2684
|
stream.chunks.push(chunk)
|
|
2658
2685
|
cell.ordinal += 1
|
|
2659
2686
|
// 3.8: `ready` from the first chunk, and `error` cleared by an accepted production (4.11).
|
|
@@ -3242,6 +3269,147 @@ function readyNow(cell: Cell): boolean {
|
|
|
3242
3269
|
return walkReady(cell, PROBE_PASS)
|
|
3243
3270
|
}
|
|
3244
3271
|
|
|
3272
|
+
/**
|
|
3273
|
+
* Whether this value and everything it reads or adopts holds what its producer made (3.10, 3.13).
|
|
3274
|
+
*
|
|
3275
|
+
* `ready`'s quantifier, and for `ready`'s reason: a figure computed from a value written here is no
|
|
3276
|
+
* more the producer's than that value is.
|
|
3277
|
+
*/
|
|
3278
|
+
function walkSynced(cell: Cell, pass: number): boolean {
|
|
3279
|
+
if (cell.local) return false
|
|
3280
|
+
const adoption = cell.adoption
|
|
3281
|
+
if (cell.sources.length === 0 && adoption === null) return true
|
|
3282
|
+
if (cell.probePass === pass) return true
|
|
3283
|
+
cell.probePass = pass
|
|
3284
|
+
if (adoption !== null && !walkSynced(adoption.source, pass)) return false
|
|
3285
|
+
const sources = cell.sources
|
|
3286
|
+
for (let index = 0; index < sources.length; index += 1)
|
|
3287
|
+
if (!walkSynced(sources[index] as Cell, pass)) return false
|
|
3288
|
+
return true
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
function syncedNow(cell: Cell): boolean {
|
|
3292
|
+
if (cell.local) return false
|
|
3293
|
+
if (cell.sources.length === 0 && cell.adoption === null) return true
|
|
3294
|
+
PROBE_PASS += 1
|
|
3295
|
+
return walkSynced(cell, PROBE_PASS)
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
/**
|
|
3299
|
+
* How many writes made here are on the stack, which `produce` reads to tell one from a production.
|
|
3300
|
+
*
|
|
3301
|
+
* A COUNT RATHER THAN A FLAG, so a write made from inside another's gate — a `transform` writing a
|
|
3302
|
+
* second value — leaves the outer one's mark standing when it returns.
|
|
3303
|
+
*/
|
|
3304
|
+
let WRITING = 0
|
|
3305
|
+
|
|
3306
|
+
/** 3.10 — marks a value with a producer as written here, telling the guards that asked. */
|
|
3307
|
+
function writtenHere(cell: Cell): void {
|
|
3308
|
+
if (cell.body === null || cell.local) return
|
|
3309
|
+
cell.local = true
|
|
3310
|
+
probesMoved(cell)
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
/** 3.11 — puts a value back in step with its producer, telling the guards that asked. */
|
|
3314
|
+
function producedAgain(cell: Cell): void {
|
|
3315
|
+
if (!cell.local) return
|
|
3316
|
+
cell.local = false
|
|
3317
|
+
probesMoved(cell)
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
/** `accept`, as a write made here rather than a production. */
|
|
3321
|
+
function acceptHere(cell: Cell, value: unknown): unknown {
|
|
3322
|
+
WRITING += 1
|
|
3323
|
+
try {
|
|
3324
|
+
return accept(cell, value)
|
|
3325
|
+
} finally {
|
|
3326
|
+
WRITING -= 1
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
/**
|
|
3331
|
+
* A write over a value with a body: held as written here where it moved what is held (3.10).
|
|
3332
|
+
*
|
|
3333
|
+
* A WRITE THE IDENTITY GATE COLLAPSED MOVED NOTHING, so it leaves the value as in step as it was; a
|
|
3334
|
+
* write that settles later is marked now, being a write here whenever it lands.
|
|
3335
|
+
*/
|
|
3336
|
+
function setOverProducer(cell: Cell, value: unknown): unknown {
|
|
3337
|
+
const ordinal = cell.ordinal
|
|
3338
|
+
cell.generation += 1
|
|
3339
|
+
WRITING += 1
|
|
3340
|
+
let answer: unknown
|
|
3341
|
+
try {
|
|
3342
|
+
answer = setThroughGates(cell, value)
|
|
3343
|
+
} finally {
|
|
3344
|
+
WRITING -= 1
|
|
3345
|
+
}
|
|
3346
|
+
if (
|
|
3347
|
+
answer === undefined &&
|
|
3348
|
+
!isReactive(value) &&
|
|
3349
|
+
(cell.ordinal !== ordinal || isThenable(value))
|
|
3350
|
+
)
|
|
3351
|
+
writtenHere(cell)
|
|
3352
|
+
return answer
|
|
3353
|
+
}
|
|
3354
|
+
|
|
3355
|
+
/**
|
|
3356
|
+
* What a body hands back in place of an answer it could not get: held as written here (16.81).
|
|
3357
|
+
*
|
|
3358
|
+
* A CLASS, so the one test that tells it from a value is `instanceof`, made only on the arm that
|
|
3359
|
+
* settles a body's promise.
|
|
3360
|
+
*/
|
|
3361
|
+
class StandIn {
|
|
3362
|
+
value: unknown
|
|
3363
|
+
constructor(value: unknown) {
|
|
3364
|
+
this.value = value
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
/** A body's stand-in for the answer it could not get, which `synced` reads false over (16.81). */
|
|
3369
|
+
export function standIn(value: unknown): unknown {
|
|
3370
|
+
return new StandIn(value)
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
/**
|
|
3374
|
+
* Writes `value` as its producer made it, putting it back in step (3.11).
|
|
3375
|
+
*
|
|
3376
|
+
* WHAT A FOLLOWED COPY IS SENT IS THE PRODUCER'S, whoever carries it in: the server's value
|
|
3377
|
+
* arriving over the mux is a production of the copy, not a write made here (18.10).
|
|
3378
|
+
*/
|
|
3379
|
+
export function produceInto(value: Reactive<unknown>, next: unknown): void {
|
|
3380
|
+
const cell = (value as unknown as Callable)[CELL]
|
|
3381
|
+
// A stream's write stands over its accumulation (9.11), as `STREAM_PROTOTYPE.set` makes it.
|
|
3382
|
+
if (ownStream(cell) !== null) writeStream(cell, next)
|
|
3383
|
+
else {
|
|
3384
|
+
cell.generation += 1
|
|
3385
|
+
setThroughGates(cell, next)
|
|
3386
|
+
}
|
|
3387
|
+
producedAgain(cell)
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
/** `produceInto` for a patch the producer sent (18.11). */
|
|
3391
|
+
export function patchInto(
|
|
3392
|
+
value: Reactive<unknown>,
|
|
3393
|
+
mutate: (value: unknown) => void,
|
|
3394
|
+
): void {
|
|
3395
|
+
const cell = (value as unknown as Callable)[CELL]
|
|
3396
|
+
patchCell(cell, mutate)
|
|
3397
|
+
producedAgain(cell)
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
/**
|
|
3401
|
+
* Refuses `value` as its producer did, leaving what it holds standing (46.7): a call queued under a
|
|
3402
|
+
* stand-in that the server refused once it was delivered.
|
|
3403
|
+
*/
|
|
3404
|
+
export function refuseInto(value: Reactive<unknown>, failure: unknown): void {
|
|
3405
|
+
refused((value as unknown as Callable)[CELL], failure)
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
/** Whether `value` holds what was written here, without subscribing — for a caller that is not a reader. */
|
|
3409
|
+
export function writtenLocally(value: Reactive<unknown>): boolean {
|
|
3410
|
+
return (value as unknown as Callable)[CELL].local
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3245
3413
|
/**
|
|
3246
3414
|
* A promise of the value once it is servable and not busy, or of its failure.
|
|
3247
3415
|
*
|
|
@@ -4140,6 +4308,13 @@ export function readyOver(tags: string[] | null): boolean {
|
|
|
4140
4308
|
return true
|
|
4141
4309
|
}
|
|
4142
4310
|
|
|
4311
|
+
/** Whether every value in the selection is in step with its producer; a reader subscribes to it. */
|
|
4312
|
+
export function syncedOver(tags: string[] | null): boolean {
|
|
4313
|
+
trackSignal(tags)
|
|
4314
|
+
for (const cell of inReach(tags)) if (!syncedNow(cell)) return false
|
|
4315
|
+
return true
|
|
4316
|
+
}
|
|
4317
|
+
|
|
4143
4318
|
/**
|
|
4144
4319
|
* Invalidates every value in the selection, and what each adopts.
|
|
4145
4320
|
*
|
|
@@ -4221,6 +4396,11 @@ const MEMBERS = {
|
|
|
4221
4396
|
ready(this: Callable): boolean {
|
|
4222
4397
|
return isReady(this[CELL])
|
|
4223
4398
|
},
|
|
4399
|
+
synced(this: Callable): boolean {
|
|
4400
|
+
const cell = this[CELL]
|
|
4401
|
+
probe(cell)
|
|
4402
|
+
return syncedNow(cell)
|
|
4403
|
+
},
|
|
4224
4404
|
error(this: Callable): unknown {
|
|
4225
4405
|
const cell = this[CELL]
|
|
4226
4406
|
// 3.3 ON THE THIRD PROBE. `error` is a probe by the rulebook's own term, so a body
|
|
@@ -4239,6 +4419,7 @@ const MEMBERS = {
|
|
|
4239
4419
|
isError: isFailedNamed,
|
|
4240
4420
|
set(this: Callable, value: unknown): unknown {
|
|
4241
4421
|
const cell = this[CELL]
|
|
4422
|
+
if (cell.body !== null) return setOverProducer(cell, value)
|
|
4242
4423
|
cell.generation += 1
|
|
4243
4424
|
// The settled, untransformed write is the overwhelming common shape, and the
|
|
4244
4425
|
// general path is two call frames and a thenable probe behind it. Inlined here
|
|
@@ -4257,7 +4438,9 @@ const MEMBERS = {
|
|
|
4257
4438
|
return setThroughGates(cell, value)
|
|
4258
4439
|
},
|
|
4259
4440
|
patch(this: Callable, mutate: (value: unknown) => void): void {
|
|
4260
|
-
|
|
4441
|
+
const cell = this[CELL]
|
|
4442
|
+
patchCell(cell, mutate)
|
|
4443
|
+
writtenHere(cell)
|
|
4261
4444
|
},
|
|
4262
4445
|
refresh(this: Callable): void {
|
|
4263
4446
|
eachAdopted(this[CELL], refreshOne)
|
|
@@ -4514,7 +4697,15 @@ const STREAM_PROTOTYPE = brandedPrototype({
|
|
|
4514
4697
|
if (cycle !== undefined) throw cycle
|
|
4515
4698
|
return undefined
|
|
4516
4699
|
}
|
|
4517
|
-
|
|
4700
|
+
WRITING += 1
|
|
4701
|
+
let answer: unknown
|
|
4702
|
+
try {
|
|
4703
|
+
answer = writeStream(cell, value)
|
|
4704
|
+
} finally {
|
|
4705
|
+
WRITING -= 1
|
|
4706
|
+
}
|
|
4707
|
+
if (answer === undefined) writtenHere(cell)
|
|
4708
|
+
return answer
|
|
4518
4709
|
},
|
|
4519
4710
|
})
|
|
4520
4711
|
|
|
@@ -4536,7 +4727,7 @@ const ADOPTING_PROTOTYPE = brandedPrototype({
|
|
|
4536
4727
|
},
|
|
4537
4728
|
patch(this: Callable, mutate: (value: unknown) => void): void {
|
|
4538
4729
|
const adoption = this[CELL].adoption
|
|
4539
|
-
if (adoption === null)
|
|
4730
|
+
if (adoption === null) MEMBERS.patch.call(this, mutate)
|
|
4540
4731
|
else (adoption.source.callable as Reactive<unknown>).patch(mutate)
|
|
4541
4732
|
},
|
|
4542
4733
|
})
|
|
@@ -4587,6 +4778,9 @@ const SIMPLE_PROTOTYPE = brandedPrototype({
|
|
|
4587
4778
|
}
|
|
4588
4779
|
cell.value = value
|
|
4589
4780
|
cell.ordinal += 1
|
|
4781
|
+
// 3.10 — a write over a producer is one `synced` hears. Never a production here, so no
|
|
4782
|
+
// `WRITING` count: the short write does not reach `produce`.
|
|
4783
|
+
if (cell.body !== null) writtenHere(cell)
|
|
4590
4784
|
// `setDirect`, with `plain` known true by `simple`.
|
|
4591
4785
|
cell.direct = cell.state === CLEAN
|
|
4592
4786
|
if (cell.readers.length !== 0) markReaders(cell, DIRTY)
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// ONE RECORD PER VALUE, IN A SIDE TABLE, not a field on the cell. Every value would carry the field
|
|
8
8
|
// and only a called one ever fills it, so the table is what keeps a plain `reactive` paying nothing.
|
|
9
9
|
|
|
10
|
+
import { LOAD_OFFLINE } from '../LOAD_OFFLINE.ts'
|
|
10
11
|
import { LOAD_SIGNAL } from '../LOAD_SIGNAL.ts'
|
|
11
12
|
import { aborted, refuse } from '../refuse.ts'
|
|
12
13
|
// Through the barrel, for the cycle it names: rpc reaches this module ahead of the barrel.
|
|
@@ -81,13 +82,18 @@ export function callLoad(
|
|
|
81
82
|
args: unknown,
|
|
82
83
|
signal: AbortSignal | undefined,
|
|
83
84
|
timeout: number | undefined,
|
|
85
|
+
// 16.81 — read by a browser's wire as its body starts, and by nothing on a server (16.84).
|
|
86
|
+
offline?: (args: unknown) => unknown,
|
|
84
87
|
): unknown {
|
|
85
88
|
const ending = signal === undefined ? undefined : new AbortController()
|
|
86
89
|
LOAD_SIGNAL.current = ending?.signal
|
|
90
|
+
LOAD_OFFLINE.current = offline
|
|
87
91
|
let reached: unknown
|
|
88
92
|
let attended: Attended | undefined
|
|
89
93
|
try {
|
|
90
94
|
reached = reach(args)
|
|
95
|
+
const standing = LOAD_OFFLINE.standing
|
|
96
|
+
if (standing !== undefined) standing.value = reached
|
|
91
97
|
attended = ATTENDED.get(reached as object)
|
|
92
98
|
// 16.62 — the callers of an aborted load stopped waiting, and this one has not.
|
|
93
99
|
// 15.19 — and of one whose last answer failed transiently, which runs again here where an
|
|
@@ -101,6 +107,8 @@ export function callLoad(
|
|
|
101
107
|
(reached as { refresh(): void }).refresh()
|
|
102
108
|
} finally {
|
|
103
109
|
LOAD_SIGNAL.current = undefined
|
|
110
|
+
LOAD_OFFLINE.current = undefined
|
|
111
|
+
LOAD_OFFLINE.standing = undefined
|
|
104
112
|
}
|
|
105
113
|
if (!isReactive(reached)) return reached
|
|
106
114
|
const load = loadOf(reached)
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import type { Failed } from '../Failed.ts'
|
|
17
17
|
import type { Issues } from '../Issues.ts'
|
|
18
|
+
import { OUTBOX_SENDING } from '../OUTBOX_SENDING.ts'
|
|
18
19
|
import { RPC_SERVER } from '../RPC_SERVER.ts'
|
|
19
20
|
import type { Schema } from '../Schema.ts'
|
|
20
21
|
import { isReactive } from './isReactive.ts'
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
type ReactiveOptions,
|
|
30
31
|
readyOver,
|
|
31
32
|
refreshOver,
|
|
33
|
+
syncedOver,
|
|
32
34
|
} from './Reactive.ts'
|
|
33
35
|
import { outsideScope, scoped } from './scoped.ts'
|
|
34
36
|
import { share } from './share.ts'
|
|
@@ -50,13 +52,18 @@ export {
|
|
|
50
52
|
isStreaming,
|
|
51
53
|
loadOf,
|
|
52
54
|
moveItem,
|
|
55
|
+
patchInto,
|
|
56
|
+
produceInto,
|
|
53
57
|
type Reactive,
|
|
54
58
|
type ReactiveOptions,
|
|
59
|
+
refuseInto,
|
|
55
60
|
rowItem,
|
|
56
61
|
type Store,
|
|
57
62
|
servedCellOf,
|
|
63
|
+
standIn,
|
|
58
64
|
streamingOrSettled,
|
|
59
65
|
type Transformer,
|
|
66
|
+
writtenLocally,
|
|
60
67
|
} from './Reactive.ts'
|
|
61
68
|
export { STREAMING } from './STREAMING.ts'
|
|
62
69
|
export {
|
|
@@ -82,6 +89,7 @@ export type Selection =
|
|
|
82
89
|
| {
|
|
83
90
|
busy(): boolean
|
|
84
91
|
ready(): boolean
|
|
92
|
+
synced(): boolean
|
|
85
93
|
refresh(): void
|
|
86
94
|
invalidate(): void
|
|
87
95
|
}
|
|
@@ -477,7 +485,7 @@ function build(
|
|
|
477
485
|
// tag arm takes none at all.
|
|
478
486
|
type PatternOf<
|
|
479
487
|
Chosen,
|
|
480
|
-
Member extends 'busy' | 'ready' | 'refresh' | 'invalidate',
|
|
488
|
+
Member extends 'busy' | 'ready' | 'synced' | 'refresh' | 'invalidate',
|
|
481
489
|
> =
|
|
482
490
|
Chosen extends Record<Member, (pattern?: infer Pattern) => unknown>
|
|
483
491
|
? Pattern
|
|
@@ -497,6 +505,9 @@ export function busy<Chosen extends Selection>(
|
|
|
497
505
|
pattern?: PatternOf<Chosen, 'busy'>,
|
|
498
506
|
): boolean {
|
|
499
507
|
const sweep = sweepOf(selection)
|
|
508
|
+
// 46.11 — a bare `busy` hears a call the outbox is sending, which no value holds.
|
|
509
|
+
if (sweep === null)
|
|
510
|
+
return busyOver(sweep) || OUTBOX_SENDING.any?.() === true
|
|
500
511
|
if (sweep !== undefined) return busyOver(sweep)
|
|
501
512
|
return (selection as { busy(pattern: unknown): boolean }).busy(pattern)
|
|
502
513
|
}
|
|
@@ -514,6 +525,17 @@ export function ready<Chosen extends Selection>(
|
|
|
514
525
|
if (sweep !== undefined) return readyOver(sweep)
|
|
515
526
|
return (selection as { ready(pattern: unknown): boolean }).ready(pattern)
|
|
516
527
|
}
|
|
528
|
+
/**
|
|
529
|
+
* Whether every entry in the selection holds what its producer made — ALL, as `ready` asks (13.18).
|
|
530
|
+
*/
|
|
531
|
+
export function synced<Chosen extends Selection>(
|
|
532
|
+
selection?: Chosen,
|
|
533
|
+
pattern?: PatternOf<Chosen, 'synced'>,
|
|
534
|
+
): boolean {
|
|
535
|
+
const sweep = sweepOf(selection)
|
|
536
|
+
if (sweep !== undefined) return syncedOver(sweep)
|
|
537
|
+
return (selection as { synced(pattern: unknown): boolean }).synced(pattern)
|
|
538
|
+
}
|
|
517
539
|
/**
|
|
518
540
|
* Stale-while-revalidate over the selection: reloads it while serving what it holds.
|
|
519
541
|
*
|
package/src/shared/rpc.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { GLOBAL_GETS } from './GLOBAL_GETS.ts'
|
|
|
18
18
|
import { HandlerRoute } from './HandlerRoute.ts'
|
|
19
19
|
import { IN_PROCESS_ORIGIN } from './IN_PROCESS_ORIGIN.ts'
|
|
20
20
|
import { compose, type Middleware } from './Middleware.ts'
|
|
21
|
+
import { outbox, type QueuedCall, sendingFor } from './outbox.ts'
|
|
21
22
|
import { principalRotated } from './principalRotated.ts'
|
|
22
23
|
import { RPC_SERVER } from './RPC_SERVER.ts'
|
|
23
24
|
import {
|
|
@@ -56,8 +57,14 @@ import type { Schema } from './Schema.ts'
|
|
|
56
57
|
|
|
57
58
|
export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
|
58
59
|
|
|
59
|
-
/**
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* What a call is handed beside its args: who may end its load (16.48), and what stands in for an
|
|
62
|
+
* answer a browser could not get (16.81).
|
|
63
|
+
*/
|
|
64
|
+
export type CallOptions<Args = unknown, Value = unknown> = {
|
|
65
|
+
signal?: AbortSignal
|
|
66
|
+
offline?(args: Args): Value
|
|
67
|
+
}
|
|
61
68
|
|
|
62
69
|
/**
|
|
63
70
|
* A declared handler: callable for its `Reactive`, with the handler's own members and its mount.
|
|
@@ -71,8 +78,8 @@ export type CallOptions = { signal?: AbortSignal }
|
|
|
71
78
|
*/
|
|
72
79
|
export type Rpc<Value, Args, Failures = never, Produced = Value> = ((
|
|
73
80
|
...args: undefined extends Args
|
|
74
|
-
? [args?: Args, options?: CallOptions]
|
|
75
|
-
: [args: Args, options?: CallOptions]
|
|
81
|
+
? [args?: Args, options?: CallOptions<Args, NoInfer<Value>>]
|
|
82
|
+
: [args: Args, options?: CallOptions<Args, NoInfer<Value>>]
|
|
76
83
|
) => Reactive<
|
|
77
84
|
Value,
|
|
78
85
|
Value,
|
|
@@ -88,6 +95,9 @@ export type Rpc<Value, Args, Failures = never, Produced = Value> = ((
|
|
|
88
95
|
*/
|
|
89
96
|
busy(pattern?: Partial<Args>): boolean
|
|
90
97
|
ready(pattern?: Partial<Args>): boolean
|
|
98
|
+
synced(pattern?: Partial<Args>): boolean
|
|
99
|
+
/** 46.1 — the calls the outbox holds for this handler, narrowed by a pattern. */
|
|
100
|
+
outbox(pattern?: Partial<Args>): QueuedCall<Value>[]
|
|
91
101
|
refresh(pattern?: Partial<Args>): void
|
|
92
102
|
invalidate(pattern?: Partial<Args>): void
|
|
93
103
|
isError<Name extends string>(
|
|
@@ -404,6 +414,7 @@ export function declare(method: Method): Declare {
|
|
|
404
414
|
args,
|
|
405
415
|
options?.signal,
|
|
406
416
|
rpc.timeout ?? RPC_SERVER.timeout(),
|
|
417
|
+
options?.offline as ((args: unknown) => unknown) | undefined,
|
|
407
418
|
)
|
|
408
419
|
RPC_SERVER.reached?.(rpc, args, reached)
|
|
409
420
|
return reached
|
|
@@ -457,7 +468,8 @@ export function declare(method: Method): Declare {
|
|
|
457
468
|
const members = rpc as unknown as Record<string, unknown>
|
|
458
469
|
// 7.10, 7.18 — A TRIGGER ON A SERVER REACHES EVERY BROWSER HOLDING WHAT IT NAMES, through the
|
|
459
470
|
// mux (18.3). `null` in a browser, where the trigger is the copy's own.
|
|
460
|
-
for (const name of SELECTION_MEMBERS)
|
|
471
|
+
for (const name of SELECTION_MEMBERS) {
|
|
472
|
+
if (name === 'busy') continue
|
|
461
473
|
members[name] =
|
|
462
474
|
name === 'invalidate' || name === 'refresh'
|
|
463
475
|
? (pattern?: unknown): unknown => {
|
|
@@ -466,6 +478,14 @@ export function declare(method: Method): Declare {
|
|
|
466
478
|
return done
|
|
467
479
|
}
|
|
468
480
|
: (pattern?: unknown): unknown => face[name](pattern)
|
|
481
|
+
}
|
|
482
|
+
// 46.11 — BUSY TOO WHILE A CALL THE OUTBOX HOLDS FOR IT IS IN FLIGHT: a queued call sent is work
|
|
483
|
+
// in flight for this handler, as a call made now is. 46.1 — and the calls it holds for it.
|
|
484
|
+
members.busy = (pattern?: unknown): boolean =>
|
|
485
|
+
(face.busy(pattern) as boolean) ||
|
|
486
|
+
sendingFor(rpc, pattern as Record<string, unknown> | undefined)
|
|
487
|
+
members.outbox = (pattern?: unknown): QueuedCall[] =>
|
|
488
|
+
outbox(rpc as never, pattern as Record<string, unknown> | undefined)
|
|
469
489
|
// 21.8, 18.5 — a `GET` over a `global` value, the value it is serving for some args: a cache
|
|
470
490
|
// may hold it for as long as it lives, and the mux may feed it. A request-local one is a
|
|
471
491
|
// forwarder to a value built per request (11.32).
|