@abide/abide 0.124.1 → 0.125.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ABIDE.md +4 -0
- package/CHANGELOG.md +50 -0
- package/bin/abide.ts +3 -2
- package/docs/REGISTRY.md +67 -40
- package/docs/RULEBOOK.md +206 -18
- package/docs/abide.md +393 -67
- package/package.json +1 -1
- package/src/compiler/appMap.ts +14 -1
- package/src/compiler/compileApp.ts +1 -0
- package/src/compiler/declaredExports.ts +41 -0
- package/src/compiler/handlersUnder.ts +1 -0
- package/src/compiler/hookCalls.ts +1 -0
- package/src/compiler/projectShadow.ts +52 -2
- package/src/compiler/readsIn.ts +39 -34
- package/src/server/PageRender.ts +7 -8
- package/src/server/abideModules.ts +22 -9
- package/src/server/addressed.ts +14 -0
- package/src/server/answerRequest.ts +115 -52
- package/src/server/answerSocket.ts +6 -5
- package/src/server/answerWorker.ts +34 -23
- package/src/server/assistant.ts +141 -35
- package/src/server/banner.ts +9 -5
- package/src/server/bridgeRelay.ts +3 -3
- package/src/server/callerOf.ts +5 -0
- package/src/server/claudeCode.ts +20 -11
- package/src/server/commands.ts +14 -7
- package/src/server/createApp.ts +65 -12
- package/src/server/devWorker.ts +18 -16
- package/src/server/faceOf.ts +3 -1
- package/src/server/generatedSurfaces.ts +6 -5
- package/src/server/handlerFileMount.ts +2 -0
- package/src/server/hooks.ts +54 -8
- package/src/server/index.ts +21 -4
- package/src/server/invokeHandler.ts +1 -1
- package/src/server/mountHandlers.ts +9 -7
- package/src/server/principal.ts +3 -2
- package/src/server/render.ts +11 -2
- package/src/server/requestScope.ts +61 -1
- package/src/server/responseRelay.ts +9 -3
- package/src/server/serveDev.ts +18 -3
- package/src/server/sqliteStore.ts +65 -0
- package/src/server/testMount.ts +1 -0
- package/src/shared/AssistantEvent.ts +40 -0
- package/src/shared/BrowserHooks.ts +73 -0
- package/src/shared/CURRENT_ROUTER.ts +3 -1
- package/src/shared/Issues.ts +11 -3
- package/src/shared/Middleware.ts +14 -7
- package/src/shared/UNEXPECTED.ts +1 -1
- package/src/shared/answerOf.ts +9 -4
- package/src/shared/imagePiece.ts +85 -0
- package/src/shared/index.ts +17 -0
- package/src/shared/isServer.ts +15 -0
- package/src/shared/navigate.ts +10 -3
- package/src/shared/onError.ts +51 -0
- package/src/shared/onRefusal.ts +47 -0
- package/src/shared/online.ts +5 -1
- package/src/shared/reactive/Keyed.ts +12 -1
- package/src/shared/reactive/Reactive.ts +53 -25
- package/src/shared/reactive/cellShape.ts +4 -3
- package/src/shared/reactive/poll.ts +49 -0
- package/src/shared/reactive/scoped.ts +10 -1
- package/src/shared/reactive/scoping.ts +14 -0
- package/src/shared/reactive/watch.ts +19 -8
- package/src/shared/resultsOf.ts +23 -0
- package/src/shared/route.ts +18 -4
- package/src/shared/rpc.ts +125 -30
- package/src/shared/seededDocument.ts +5 -6
- package/src/shared/storedValue.ts +32 -0
- package/src/shared/webStore.ts +54 -0
- package/src/ui/Router.ts +42 -9
- package/src/ui/outboxQueue.ts +1 -1
- package/src/ui/wire.ts +4 -4
- package/src/shared/KEEP_OPENED.ts +0 -7
- /package/src/{server → shared}/IMAGE_TYPES.ts +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AssistantEvent } from './AssistantEvent.ts'
|
|
2
|
+
import type { Reactive } from './reactive/Reactive.ts'
|
|
3
|
+
import type { Rpc } from './rpc.ts'
|
|
4
|
+
|
|
5
|
+
/** What every call to `handler` in `events` answered, in order, a failed call's left out. 44.51. */
|
|
6
|
+
export function resultsOf<Value>(
|
|
7
|
+
events: readonly AssistantEvent[],
|
|
8
|
+
// Any `Rpc`, whatever its `Args`: only the value is read off its call.
|
|
9
|
+
handler: ((...args: never) => Reactive<Value, never, unknown, unknown>) &
|
|
10
|
+
Pick<Rpc<Value, never>, 'method' | 'address'>,
|
|
11
|
+
): Value[] {
|
|
12
|
+
const values: Value[] = []
|
|
13
|
+
for (const event of events)
|
|
14
|
+
if (
|
|
15
|
+
event.type === 'result' &&
|
|
16
|
+
!event.failed &&
|
|
17
|
+
event.handler !== undefined &&
|
|
18
|
+
event.handler.method === handler.method &&
|
|
19
|
+
event.handler.address === handler.address
|
|
20
|
+
)
|
|
21
|
+
values.push(event.value as Value)
|
|
22
|
+
return values
|
|
23
|
+
}
|
package/src/shared/route.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 23.1 — WHERE WE ARE, as
|
|
1
|
+
// 23.1 — WHERE WE ARE, as five values rather than one over a composite, so a reader of `params`
|
|
2
2
|
// does not wake for a hash change and a progress bar reading `navigating` does not wake for either.
|
|
3
3
|
//
|
|
4
4
|
// FILLED BY WHOEVER RESOLVES THE ADDRESS: the client router per navigation, and a server's page lane
|
|
@@ -7,23 +7,37 @@
|
|
|
7
7
|
import type { Params } from './matchRoute.ts'
|
|
8
8
|
import { ambient } from './reactive/index.ts'
|
|
9
9
|
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* The current address: its `url`, `params`, pattern `name`, its `index` in this document's history,
|
|
12
|
+
* and whether it is `navigating`.
|
|
13
|
+
*/
|
|
11
14
|
export const route = {
|
|
12
15
|
url: ambient<URL>(),
|
|
13
16
|
params: ambient<Params>(),
|
|
14
17
|
/** 23.6 — the resolution path: the literal the address resolved to, `/invoices/[id]`. */
|
|
15
18
|
name: ambient<string>(),
|
|
19
|
+
/**
|
|
20
|
+
* 23.28, 23.29 — the entry's place in the history this app's router wrote: 0 where a document
|
|
21
|
+
* load arrived, and one past the entry it was pushed from.
|
|
22
|
+
*/
|
|
23
|
+
index: ambient(0),
|
|
16
24
|
navigating: ambient(false),
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
/**
|
|
20
|
-
* Sets `route`'s url, params and
|
|
28
|
+
* Sets `route`'s url, params, name and index to an address just resolved.
|
|
21
29
|
*
|
|
22
30
|
* 23.2 — ONE `URL` PER NAVIGATION, built here as the route resolves, so a reader holding the last
|
|
23
31
|
* one is not handed a value that moved under it.
|
|
24
32
|
*/
|
|
25
|
-
export function arrive(
|
|
33
|
+
export function arrive(
|
|
34
|
+
at: string,
|
|
35
|
+
pattern: string,
|
|
36
|
+
params: Params,
|
|
37
|
+
index: number,
|
|
38
|
+
): void {
|
|
26
39
|
route.url.set(new URL(at))
|
|
27
40
|
route.params.set(params)
|
|
28
41
|
route.name.set(pattern)
|
|
42
|
+
route.index.set(index)
|
|
29
43
|
}
|
package/src/shared/rpc.ts
CHANGED
|
@@ -122,7 +122,7 @@ export type Rpc<Value, Args, Failures = never, Produced = Value> = ((
|
|
|
122
122
|
url(...args: undefined extends Args ? [args?: Args] : [args: Args]): string
|
|
123
123
|
/** The same call, handed back as the `Response` (16.42). */
|
|
124
124
|
raw(args?: Args, init?: RequestInit): Promise<Response>
|
|
125
|
-
/** Where the handler
|
|
125
|
+
/** Where the handler answers: its route under the mount, once its file has loaded (16.88). */
|
|
126
126
|
address: string | undefined
|
|
127
127
|
keyed: boolean
|
|
128
128
|
timeout: number | undefined
|
|
@@ -183,9 +183,51 @@ export type RpcContext<Args = unknown> = { request: Request; args: () => Args }
|
|
|
183
183
|
*/
|
|
184
184
|
const SENT: unique symbol = Symbol('abide.sent')
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
/**
|
|
187
|
+
* What a rung's `next` answers as a declaration types it: opaque, so what the rung answers in the
|
|
188
|
+
* handler's place is told apart from the answer it passes on.
|
|
189
|
+
*/
|
|
190
|
+
declare const REACHED: unique symbol
|
|
191
|
+
type Reached = { readonly [REACHED]: true }
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The refusals a declaration's rungs answer with, read off what each answers.
|
|
195
|
+
*
|
|
196
|
+
* 16.86 — A REFUSAL A RUNG NAMES IS THE `Rpc`'s, and one it does not name is not: a `Failed` whose
|
|
197
|
+
* name is `string` (a `Middleware` declaring no refusals) would widen every name the union narrows
|
|
198
|
+
* by, and a rung answering `unknown` names none.
|
|
199
|
+
*/
|
|
200
|
+
type RungRefusals<Answers extends readonly unknown[]> =
|
|
201
|
+
Answers[number] extends infer Answer
|
|
202
|
+
? Answer extends Failed<infer Name, unknown>
|
|
203
|
+
? string extends Name
|
|
204
|
+
? never
|
|
205
|
+
: Answer
|
|
206
|
+
: never
|
|
207
|
+
: never
|
|
208
|
+
|
|
209
|
+
/** One rung of a handler's `middleware`, answering `Answer` in the handler's place or passing on. */
|
|
210
|
+
type HandlerRung<Args, Answer> = (
|
|
211
|
+
next: (ctx?: RpcContext<Args>) => Promise<Reached>,
|
|
212
|
+
ctx: RpcContext<Args>,
|
|
213
|
+
) => Answer | Promise<Answer>
|
|
214
|
+
|
|
215
|
+
export type RpcOptions<
|
|
216
|
+
Args = unknown,
|
|
217
|
+
Answers extends readonly unknown[] = unknown[],
|
|
218
|
+
> = {
|
|
187
219
|
description?: string
|
|
188
|
-
|
|
220
|
+
/**
|
|
221
|
+
* 16.86 — one rung per answer in `Answers`, each read in context: a rung's own refusals are
|
|
222
|
+
* inferred where the list is written at the declaration.
|
|
223
|
+
*
|
|
224
|
+
* The plain array arm is the context a list built by a GENERIC helper is typed in: the mapped
|
|
225
|
+
* arm is over `Answers`, still being inferred, so the helper's own `Args` read nothing there and
|
|
226
|
+
* are `unknown`. It adds no refusal of its own — `Answers` is still read off the list.
|
|
227
|
+
*/
|
|
228
|
+
middleware?:
|
|
229
|
+
| { [At in keyof Answers]: HandlerRung<Args, Answers[At]> }
|
|
230
|
+
| readonly HandlerRung<Args, unknown>[]
|
|
189
231
|
timeout?: number
|
|
190
232
|
clients?: Clients
|
|
191
233
|
crossOrigin?: boolean | string[]
|
|
@@ -221,20 +263,34 @@ type Held<Accepted> = Accepted extends
|
|
|
221
263
|
* which is why a value handed in takes the address's alone: it has decided its own.
|
|
222
264
|
*/
|
|
223
265
|
type Declare = {
|
|
224
|
-
<
|
|
266
|
+
<
|
|
267
|
+
Stored,
|
|
268
|
+
Args,
|
|
269
|
+
Accepted,
|
|
270
|
+
Failures,
|
|
271
|
+
Produced,
|
|
272
|
+
Answers extends readonly unknown[] = unknown[],
|
|
273
|
+
>(
|
|
225
274
|
handler: Keyed<Stored, Args, Accepted, Failures, Produced>,
|
|
226
|
-
options?: RpcOptions<Args>,
|
|
227
|
-
): Rpc<Stored, Args, Failures
|
|
228
|
-
<
|
|
275
|
+
options?: RpcOptions<Args, Answers>,
|
|
276
|
+
): Rpc<Stored, Args, Failures | RungRefusals<Answers>, Produced>
|
|
277
|
+
<
|
|
278
|
+
Stored,
|
|
279
|
+
Accepted,
|
|
280
|
+
Failures,
|
|
281
|
+
Produced,
|
|
282
|
+
Answers extends readonly unknown[] = unknown[],
|
|
283
|
+
>(
|
|
229
284
|
handler: Reactive<Stored, Accepted, Failures, Produced>,
|
|
230
|
-
options?: RpcOptions<undefined>,
|
|
231
|
-
): Rpc<Stored, undefined, Failures
|
|
285
|
+
options?: RpcOptions<undefined, Answers>,
|
|
286
|
+
): Rpc<Stored, undefined, Failures | RungRefusals<Answers>, Produced>
|
|
232
287
|
// 11.2 — a stream whose parameter is optional is keyed, for `reactive`'s reason.
|
|
233
288
|
<
|
|
234
289
|
Body extends (args?: never) => unknown,
|
|
235
290
|
Shaped = ChunkOf<Body>[],
|
|
236
291
|
Gate = Schema<ChunkOf<Body>[]>,
|
|
237
292
|
ArgsGate = Schema<Parameters<Body>[0]>,
|
|
293
|
+
Answers extends readonly unknown[] = unknown[],
|
|
238
294
|
>(
|
|
239
295
|
handler: Body & OptionalParameter<Body> & Streams<Body> & Plain,
|
|
240
296
|
options?: Shaping<
|
|
@@ -244,36 +300,49 @@ type Declare = {
|
|
|
244
300
|
Gate,
|
|
245
301
|
ArgsGate
|
|
246
302
|
> &
|
|
247
|
-
RpcOptions<Parameters<Body>[0]>,
|
|
303
|
+
RpcOptions<Parameters<Body>[0], Answers>,
|
|
248
304
|
): Rpc<
|
|
249
305
|
Kept<Shaped>,
|
|
250
306
|
Parameters<Body>[0],
|
|
251
|
-
Refusals<Shaped, Gate, ChunkOf<Body>[], ArgsGate, Parameters<Body>[0]
|
|
307
|
+
| Refusals<Shaped, Gate, ChunkOf<Body>[], ArgsGate, Parameters<Body>[0]>
|
|
308
|
+
| RungRefusals<Answers>,
|
|
252
309
|
ChunkOf<Body>
|
|
253
310
|
>
|
|
254
|
-
<
|
|
311
|
+
<
|
|
312
|
+
Chunk,
|
|
313
|
+
Shaped = Chunk[],
|
|
314
|
+
Gate = Schema<Chunk[]>,
|
|
315
|
+
Answers extends readonly unknown[] = unknown[],
|
|
316
|
+
>(
|
|
255
317
|
handler: (() =>
|
|
256
318
|
AsyncGenerator<Chunk> | Promise<AsyncGenerator<Chunk>>) &
|
|
257
319
|
Plain,
|
|
258
|
-
options?: Shaping<Chunk[], Shaped, never, Gate> &
|
|
259
|
-
|
|
320
|
+
options?: Shaping<Chunk[], Shaped, never, Gate> &
|
|
321
|
+
RpcOptions<undefined, Answers>,
|
|
322
|
+
): Rpc<
|
|
323
|
+
Kept<Shaped>,
|
|
324
|
+
undefined,
|
|
325
|
+
Refusals<Shaped, Gate, Chunk[]> | RungRefusals<Answers>,
|
|
326
|
+
Chunk
|
|
327
|
+
>
|
|
260
328
|
<
|
|
261
329
|
Chunk,
|
|
262
330
|
Args,
|
|
263
331
|
Shaped = Chunk[],
|
|
264
332
|
Gate = Schema<Chunk[]>,
|
|
265
333
|
ArgsGate = Schema<Args>,
|
|
334
|
+
Answers extends readonly unknown[] = unknown[],
|
|
266
335
|
>(
|
|
267
336
|
handler: ((
|
|
268
337
|
args: Args,
|
|
269
338
|
) => AsyncGenerator<Chunk> | Promise<AsyncGenerator<Chunk>>) &
|
|
270
339
|
Plain,
|
|
271
340
|
options?: Shaping<Chunk[], Shaped, Args, Gate, ArgsGate> &
|
|
272
|
-
RpcOptions<Args>,
|
|
341
|
+
RpcOptions<Args, Answers>,
|
|
273
342
|
): Rpc<
|
|
274
343
|
Kept<Shaped>,
|
|
275
344
|
Args,
|
|
276
|
-
Refusals<Shaped, Gate, Chunk[], ArgsGate, Args>,
|
|
345
|
+
Refusals<Shaped, Gate, Chunk[], ArgsGate, Args> | RungRefusals<Answers>,
|
|
277
346
|
Chunk
|
|
278
347
|
>
|
|
279
348
|
// 11.2 — an optional parameter is keyed, and ahead of the arm below for `reactive`'s reason.
|
|
@@ -282,6 +351,7 @@ type Declare = {
|
|
|
282
351
|
Shaped = Awaited<ReturnType<Body>>,
|
|
283
352
|
Gate = Schema<Awaited<ReturnType<Body>>>,
|
|
284
353
|
ArgsGate = Schema<Parameters<Body>[0]>,
|
|
354
|
+
Answers extends readonly unknown[] = unknown[],
|
|
285
355
|
>(
|
|
286
356
|
handler: Body & OptionalParameter<Body> & Plain,
|
|
287
357
|
options?: Shaping<
|
|
@@ -291,7 +361,7 @@ type Declare = {
|
|
|
291
361
|
Gate,
|
|
292
362
|
ArgsGate
|
|
293
363
|
> &
|
|
294
|
-
RpcOptions<Parameters<Body>[0]>,
|
|
364
|
+
RpcOptions<Parameters<Body>[0], Answers>,
|
|
295
365
|
): Rpc<
|
|
296
366
|
Kept<Shaped>,
|
|
297
367
|
Parameters<Body>[0],
|
|
@@ -302,42 +372,63 @@ type Declare = {
|
|
|
302
372
|
Awaited<ReturnType<Body>>,
|
|
303
373
|
ArgsGate,
|
|
304
374
|
Parameters<Body>[0]
|
|
305
|
-
|
|
375
|
+
>
|
|
376
|
+
| RungRefusals<Answers>,
|
|
306
377
|
AdoptedProduced<ReturnType<Body>, Kept<Shaped>>
|
|
307
378
|
>
|
|
308
|
-
<
|
|
379
|
+
<
|
|
380
|
+
Computed,
|
|
381
|
+
Shaped = Awaited<Computed>,
|
|
382
|
+
Gate = Schema<Awaited<Computed>>,
|
|
383
|
+
Answers extends readonly unknown[] = unknown[],
|
|
384
|
+
>(
|
|
309
385
|
handler: (() => Computed) & Plain,
|
|
310
386
|
options?: Shaping<Awaited<Computed>, Shaped, never, Gate> &
|
|
311
|
-
RpcOptions<undefined>,
|
|
387
|
+
RpcOptions<undefined, Answers>,
|
|
312
388
|
): Rpc<
|
|
313
389
|
Kept<Shaped>,
|
|
314
390
|
undefined,
|
|
315
|
-
AdoptedFailures<Computed>
|
|
391
|
+
| AdoptedFailures<Computed>
|
|
392
|
+
| Refusals<Shaped, Gate, Awaited<Computed>>
|
|
393
|
+
| RungRefusals<Answers>,
|
|
316
394
|
AdoptedProduced<Computed, Kept<Shaped>>
|
|
317
395
|
>
|
|
396
|
+
// 16.1 — a value, `reactive`'s value overload given an address. Ahead of the arm below rather
|
|
397
|
+
// than last, so a body refused for its options is reported by that arm, naming them, and not as
|
|
398
|
+
// a value it never was: `Held` already turns every function away.
|
|
399
|
+
<
|
|
400
|
+
Accepted,
|
|
401
|
+
Shaped = Accepted,
|
|
402
|
+
Gate = Schema<Accepted>,
|
|
403
|
+
Answers extends readonly unknown[] = unknown[],
|
|
404
|
+
>(
|
|
405
|
+
handler: Held<Accepted>,
|
|
406
|
+
options?: Shaping<Accepted, Shaped, never, Gate> &
|
|
407
|
+
RpcOptions<undefined, Answers>,
|
|
408
|
+
): Rpc<
|
|
409
|
+
Kept<Shaped>,
|
|
410
|
+
undefined,
|
|
411
|
+
Refusals<Shaped, Gate, Accepted> | RungRefusals<Answers>
|
|
412
|
+
>
|
|
318
413
|
<
|
|
319
414
|
Computed,
|
|
320
415
|
Args,
|
|
321
416
|
Shaped = Awaited<Computed>,
|
|
322
417
|
Gate = Schema<Awaited<Computed>>,
|
|
323
418
|
ArgsGate = Schema<Args>,
|
|
419
|
+
Answers extends readonly unknown[] = unknown[],
|
|
324
420
|
>(
|
|
325
421
|
handler: ((args: Args) => Computed) & Plain,
|
|
326
422
|
options?: Shaping<Awaited<Computed>, Shaped, Args, Gate, ArgsGate> &
|
|
327
|
-
RpcOptions<Args>,
|
|
423
|
+
RpcOptions<Args, Answers>,
|
|
328
424
|
): Rpc<
|
|
329
425
|
Kept<Shaped>,
|
|
330
426
|
Args,
|
|
331
427
|
| AdoptedFailures<Computed>
|
|
332
|
-
| Refusals<Shaped, Gate, Awaited<Computed>, ArgsGate, Args
|
|
428
|
+
| Refusals<Shaped, Gate, Awaited<Computed>, ArgsGate, Args>
|
|
429
|
+
| RungRefusals<Answers>,
|
|
333
430
|
AdoptedProduced<Computed, Kept<Shaped>>
|
|
334
431
|
>
|
|
335
|
-
// 16.1 — a value, `reactive`'s value overload given an address. Last, so a function is a body.
|
|
336
|
-
<Accepted, Shaped = Accepted, Gate = Schema<Accepted>>(
|
|
337
|
-
handler: Held<Accepted>,
|
|
338
|
-
options?: Shaping<Accepted, Shaped, never, Gate> &
|
|
339
|
-
RpcOptions<undefined>,
|
|
340
|
-
): Rpc<Kept<Shaped>, undefined, Refusals<Shaped, Gate, Accepted>>
|
|
341
432
|
}
|
|
342
433
|
|
|
343
434
|
/** What a handler's selection members answer before a call has built its value: no entries' answers. */
|
|
@@ -379,7 +470,11 @@ export function declare(method: Method): Declare {
|
|
|
379
470
|
// 16.11, 16.12 — THE RUNGS, IN REGISTRATION ORDER, around the reach. Built once, at the
|
|
380
471
|
// declaration (16.14); a value keyed by args, so a second call with the same args in one
|
|
381
472
|
// request joins the first (1.8) and a server files one per request (11.32).
|
|
382
|
-
|
|
473
|
+
// `Reached` is the declaration's view of `next`; at run time it answers the handler's value.
|
|
474
|
+
const rungs = (options?.middleware ?? []) as readonly Rung<
|
|
475
|
+
RpcContext,
|
|
476
|
+
unknown
|
|
477
|
+
>[]
|
|
383
478
|
// A rung that handed `next` a context of its own making chose those args.
|
|
384
479
|
const argsOf = (ctx: RpcContext): unknown =>
|
|
385
480
|
SENT in ctx ? (ctx as { [SENT]: unknown })[SENT] : ctx.args()
|
|
@@ -22,13 +22,12 @@ export function seededDocument<Document>(
|
|
|
22
22
|
| Document
|
|
23
23
|
| Failed<string, unknown>
|
|
24
24
|
| Promise<Document | Failed<string, unknown>> {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
false,
|
|
28
|
-
undefined,
|
|
29
|
-
)
|
|
25
|
+
const request = rpcRequest('GET', mounted(address), undefined)
|
|
26
|
+
const answer = answerOf(request, false, undefined)
|
|
30
27
|
return (
|
|
31
|
-
answer instanceof Promise
|
|
28
|
+
answer instanceof Promise
|
|
29
|
+
? answer.then((settled) => answered(settled, request))
|
|
30
|
+
: answered(answer, request)
|
|
32
31
|
) as
|
|
33
32
|
| Document
|
|
34
33
|
| Failed<string, unknown>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isRecord } from './isRecord.ts'
|
|
2
|
+
import { runSchema, type Schema } from './Schema.ts'
|
|
3
|
+
|
|
4
|
+
/** The deadline a value written now with `ttl` runs out at, `null` where it never does (8.26). */
|
|
5
|
+
export function expiresAfter(ttl: number): number | null {
|
|
6
|
+
return Number.isFinite(ttl) ? Date.now() + ttl : null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The value a shipped store read back, `undefined` where its `ttl` has passed.
|
|
11
|
+
*
|
|
12
|
+
* 8.27 — THE `schema` A RESTORE SKIPS (8.5) RUNS HERE, on what was read: a refusal throws, which
|
|
13
|
+
* the value takes as a miss and warns about (8.15) — an entry an older build wrote is one an app
|
|
14
|
+
* wants to hear about rather than serve. Shared by `webStore` and the server's `sqliteStore`.
|
|
15
|
+
*/
|
|
16
|
+
export function storedValue(
|
|
17
|
+
kept: unknown,
|
|
18
|
+
schema: Schema<unknown> | undefined,
|
|
19
|
+
where: string,
|
|
20
|
+
): unknown {
|
|
21
|
+
if (!isRecord(kept) || !('value' in kept))
|
|
22
|
+
throw new Error(`${where} holds something no abide store wrote.`)
|
|
23
|
+
const expires = kept.expires
|
|
24
|
+
if (typeof expires === 'number' && expires <= Date.now()) return undefined
|
|
25
|
+
if (schema === undefined) return kept.value
|
|
26
|
+
const checked = runSchema(schema, kept.value)
|
|
27
|
+
if (checked.issues !== null)
|
|
28
|
+
throw new Error(
|
|
29
|
+
`${where} holds a value its schema refuses: ${JSON.stringify(checked.issues)}`,
|
|
30
|
+
)
|
|
31
|
+
return checked.value
|
|
32
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Store } from './reactive/index.ts'
|
|
2
|
+
import type { Schema } from './Schema.ts'
|
|
3
|
+
import { expiresAfter, storedValue } from './storedValue.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A `Store` kept in this browser's `localStorage`, or its `sessionStorage`, under `key`.
|
|
7
|
+
*
|
|
8
|
+
* 8.23 — WHERE THERE IS NO WEB STORAGE, A SERVER, THE STORE IS INERT: every `get` a miss and every
|
|
9
|
+
* `set` nothing, so the value a server renders is the one it would render with no store (8.3).
|
|
10
|
+
* The storage is looked up on each call rather than held, since a browser that refuses storage
|
|
11
|
+
* throws on the lookup — and a throw there is 8.15's miss and 8.12's warning, not a broken module.
|
|
12
|
+
*/
|
|
13
|
+
export function webStore<Stored>(
|
|
14
|
+
key: string,
|
|
15
|
+
options?: { area?: 'local' | 'session'; schema?: Schema<Stored> },
|
|
16
|
+
): Store<Stored> {
|
|
17
|
+
const name = options?.area === 'session' ? 'sessionStorage' : 'localStorage'
|
|
18
|
+
const schema = options?.schema as Schema<unknown> | undefined
|
|
19
|
+
const where = `${name}[${JSON.stringify(key)}]`
|
|
20
|
+
const area = (): Storage | undefined =>
|
|
21
|
+
(globalThis as unknown as Record<string, Storage | undefined>)[name]
|
|
22
|
+
return {
|
|
23
|
+
get: () => {
|
|
24
|
+
const storage = area()
|
|
25
|
+
if (storage === undefined) return undefined
|
|
26
|
+
const text = storage.getItem(key)
|
|
27
|
+
if (text === null) return undefined
|
|
28
|
+
const value = storedValue(JSON.parse(text), schema, where)
|
|
29
|
+
// 8.26 — an entry past its deadline is gone, not merely skipped.
|
|
30
|
+
if (value === undefined) storage.removeItem(key)
|
|
31
|
+
return value as Stored | undefined
|
|
32
|
+
},
|
|
33
|
+
set: (value, retention) => {
|
|
34
|
+
area()?.setItem(
|
|
35
|
+
key,
|
|
36
|
+
JSON.stringify({ value, expires: expiresAfter(retention.ttl) }),
|
|
37
|
+
)
|
|
38
|
+
},
|
|
39
|
+
// 8.24 — another document's write arrives as a `storage` event; this document's own never does.
|
|
40
|
+
watch: (changed) => {
|
|
41
|
+
if (typeof globalThis.addEventListener !== 'function')
|
|
42
|
+
return () => {}
|
|
43
|
+
const listener = (event: StorageEvent): void => {
|
|
44
|
+
if (
|
|
45
|
+
event.storageArea === area() &&
|
|
46
|
+
(event.key === key || event.key === null)
|
|
47
|
+
)
|
|
48
|
+
changed()
|
|
49
|
+
}
|
|
50
|
+
globalThis.addEventListener('storage', listener)
|
|
51
|
+
return () => globalThis.removeEventListener('storage', listener)
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/ui/Router.ts
CHANGED
|
@@ -105,7 +105,12 @@ export class Router {
|
|
|
105
105
|
* nothing hydrates over it, and leaving it is a document load.
|
|
106
106
|
*/
|
|
107
107
|
private readonly errorPage: boolean
|
|
108
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* The place in this document's history of the entry the address bar stands at (`Entry`).
|
|
110
|
+
*
|
|
111
|
+
* 23.28 — `route.index` IS THIS, moved where `route.url` is (`arrive`) rather than here: a back
|
|
112
|
+
* step moves this before the page it arrives at has loaded.
|
|
113
|
+
*/
|
|
109
114
|
private index: number
|
|
110
115
|
/**
|
|
111
116
|
* 30.29 — the scroll each entry was left at, by its `index`.
|
|
@@ -127,6 +132,14 @@ export class Router {
|
|
|
127
132
|
* Read off the compiled `<style>` blocks. Nothing here looks at the document's stylesheets.
|
|
128
133
|
*/
|
|
129
134
|
private readonly transitions: boolean
|
|
135
|
+
/**
|
|
136
|
+
* 30.38 — what settles each `step` still waiting on the browser to say it has arrived.
|
|
137
|
+
*
|
|
138
|
+
* Settled ALL AT ONCE by the next arrival, rather than one per `popstate`: a browser that folds
|
|
139
|
+
* two queued `history.go`s into one traversal fires one event, and a step waiting for a second
|
|
140
|
+
* would never settle.
|
|
141
|
+
*/
|
|
142
|
+
private readonly stepping: (() => void)[]
|
|
130
143
|
|
|
131
144
|
constructor(region: Region, routes: Compiled[], transitions: boolean) {
|
|
132
145
|
this.region = region
|
|
@@ -146,6 +159,7 @@ export class Router {
|
|
|
146
159
|
this.scrolls = []
|
|
147
160
|
this.prefetched = new Set()
|
|
148
161
|
this.transitions = transitions
|
|
162
|
+
this.stepping = []
|
|
149
163
|
}
|
|
150
164
|
|
|
151
165
|
/**
|
|
@@ -199,12 +213,31 @@ export class Router {
|
|
|
199
213
|
* within the standing page, back over an in-page `#anchor` jump, is `follow`'s: nothing asked,
|
|
200
214
|
* nothing remounted. 30.29 — the scroll is put back once the page it belongs to is mounted.
|
|
201
215
|
*/
|
|
202
|
-
popped(): Promise<void> {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
216
|
+
async popped(): Promise<void> {
|
|
217
|
+
try {
|
|
218
|
+
await this.travel(
|
|
219
|
+
location.href,
|
|
220
|
+
{ replace: true, keepScroll: true },
|
|
221
|
+
this.stepped(),
|
|
222
|
+
)
|
|
223
|
+
} finally {
|
|
224
|
+
for (const arrived of this.stepping.splice(0)) arrived()
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Steps the history by `by` entries, settling once the entry stepped to is arrived at.
|
|
230
|
+
*
|
|
231
|
+
* 30.38 — THE BROWSER MOVES THE BAR LATER and says so only by `popstate`, so the step settles in
|
|
232
|
+
* `popped`, once the arriving page is mounted and its scroll put back. 30.39 — `history.go(0)`
|
|
233
|
+
* reloads the document, which no `navigate` does, so a step of none is over before it starts.
|
|
234
|
+
*/
|
|
235
|
+
step(by: number): Promise<void> {
|
|
236
|
+
if (by === 0) return Promise.resolve()
|
|
237
|
+
return new Promise((arrived) => {
|
|
238
|
+
this.stepping.push(arrived)
|
|
239
|
+
history.go(by)
|
|
240
|
+
})
|
|
208
241
|
}
|
|
209
242
|
|
|
210
243
|
/** `go`, arriving as `step` says: a push, or a history step with the scroll it restores. */
|
|
@@ -313,7 +346,7 @@ export class Router {
|
|
|
313
346
|
options.replace === true || target.href === location.href,
|
|
314
347
|
[scrollX, scrollY],
|
|
315
348
|
)
|
|
316
|
-
arrive(target.href, found.route.pattern, found.params)
|
|
349
|
+
arrive(target.href, found.route.pattern, found.params, this.index)
|
|
317
350
|
this.mounted = target.pathname
|
|
318
351
|
if (step.scroll !== undefined) scrollTo(step.scroll[0], step.scroll[1])
|
|
319
352
|
else if (options.keepScroll !== true && target.hash !== '')
|
|
@@ -500,7 +533,7 @@ export class Router {
|
|
|
500
533
|
* 23.1 — WRITTEN BEFORE THE ARRIVING PAGE'S SETUP, which reads it.
|
|
501
534
|
*/
|
|
502
535
|
private arriveAt(target: URL, found: Found<Compiled>): void {
|
|
503
|
-
arrive(target.href, found.route.pattern, found.params)
|
|
536
|
+
arrive(target.href, found.route.pattern, found.params, this.index)
|
|
504
537
|
this.standing = found.route
|
|
505
538
|
this.mounted = target.pathname
|
|
506
539
|
}
|
package/src/ui/outboxQueue.ts
CHANGED
package/src/ui/wire.ts
CHANGED
|
@@ -101,7 +101,7 @@ export function wire(
|
|
|
101
101
|
return standIn(value)
|
|
102
102
|
}
|
|
103
103
|
return enqueue(queued, standing as { value: unknown }, value).then(
|
|
104
|
-
(kept) => (kept ? standIn(value) : answered(failed)),
|
|
104
|
+
(kept) => (kept ? standIn(value) : answered(failed, request)),
|
|
105
105
|
)
|
|
106
106
|
}
|
|
107
107
|
// 16.83 — BEHIND CALLS NOT YET DELIVERED A CALL WAITS ITS TURN, rather than overtaking them.
|
|
@@ -120,9 +120,9 @@ export function wire(
|
|
|
120
120
|
// asked of the network once there is one.
|
|
121
121
|
if (answer.kept) {
|
|
122
122
|
whenOnline(args, 0)
|
|
123
|
-
return standIn(answered(answer))
|
|
123
|
+
return standIn(answered(answer, request))
|
|
124
124
|
}
|
|
125
|
-
return answered(answer)
|
|
125
|
+
return answered(answer, request)
|
|
126
126
|
}
|
|
127
127
|
const answer = answerOf(request, false, signal)
|
|
128
128
|
return answer instanceof Promise ? answer.then(settle) : settle(answer)
|
|
@@ -153,7 +153,7 @@ export function wire(
|
|
|
153
153
|
yield* answerChunks(answer.body, answer.framing)
|
|
154
154
|
return
|
|
155
155
|
}
|
|
156
|
-
const value = answered(answer)
|
|
156
|
+
const value = answered(answer, request)
|
|
157
157
|
if (isFailed(value)) throw value
|
|
158
158
|
if (Array.isArray(value)) yield* value
|
|
159
159
|
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The value of the offline worker's `abide-keep` header on the page a navigation opens, which then
|
|
3
|
-
* hydrates and so is seeded as one (41.29, 45.23) — a page saved behind the one opened is not.
|
|
4
|
-
*
|
|
5
|
-
* A leaf, because the server writes it into the worker's script and reads it off the request.
|
|
6
|
-
*/
|
|
7
|
-
export const KEEP_OPENED = 'open'
|
|
File without changes
|