@abide/abide 0.124.0 → 0.125.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ABIDE.md +4 -0
- package/CHANGELOG.md +43 -0
- package/bin/abide.ts +3 -2
- package/docs/REGISTRY.md +67 -41
- package/docs/RULEBOOK.md +207 -20
- package/docs/abide.md +392 -68
- package/package.json +1 -1
- package/src/compiler/Lowering.ts +29 -4
- package/src/compiler/appMap.ts +14 -1
- 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 +53 -2
- package/src/compiler/readsIn.ts +39 -34
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/PageRender.ts +7 -8
- package/src/server/abideModules.ts +6 -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/generatedSurfaces.ts +6 -5
- package/src/server/handlerFileMount.ts +2 -0
- package/src/server/hooks.ts +54 -8
- package/src/server/index.ts +18 -4
- package/src/server/invokeHandler.ts +1 -1
- package/src/server/mountHandlers.ts +6 -0
- 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 +9 -9
- 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 +65 -26
- 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 +21 -0
- package/src/shared/route.ts +18 -4
- package/src/shared/rpc.ts +112 -26
- 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
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
|
@@ -183,9 +183,44 @@ 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
|
+
export type RpcOptions<
|
|
210
|
+
Args = unknown,
|
|
211
|
+
Answers extends readonly unknown[] = unknown[],
|
|
212
|
+
> = {
|
|
187
213
|
description?: string
|
|
188
|
-
|
|
214
|
+
/**
|
|
215
|
+
* 16.86 — one rung per answer in `Answers`, each read in context: a rung's own refusals are
|
|
216
|
+
* inferred where the list is written at the declaration.
|
|
217
|
+
*/
|
|
218
|
+
middleware?: {
|
|
219
|
+
[At in keyof Answers]: (
|
|
220
|
+
next: (ctx?: RpcContext<Args>) => Promise<Reached>,
|
|
221
|
+
ctx: RpcContext<Args>,
|
|
222
|
+
) => Answers[At] | Promise<Answers[At]>
|
|
223
|
+
}
|
|
189
224
|
timeout?: number
|
|
190
225
|
clients?: Clients
|
|
191
226
|
crossOrigin?: boolean | string[]
|
|
@@ -221,20 +256,34 @@ type Held<Accepted> = Accepted extends
|
|
|
221
256
|
* which is why a value handed in takes the address's alone: it has decided its own.
|
|
222
257
|
*/
|
|
223
258
|
type Declare = {
|
|
224
|
-
<
|
|
259
|
+
<
|
|
260
|
+
Stored,
|
|
261
|
+
Args,
|
|
262
|
+
Accepted,
|
|
263
|
+
Failures,
|
|
264
|
+
Produced,
|
|
265
|
+
Answers extends readonly unknown[] = unknown[],
|
|
266
|
+
>(
|
|
225
267
|
handler: Keyed<Stored, Args, Accepted, Failures, Produced>,
|
|
226
|
-
options?: RpcOptions<Args>,
|
|
227
|
-
): Rpc<Stored, Args, Failures
|
|
228
|
-
<
|
|
268
|
+
options?: RpcOptions<Args, Answers>,
|
|
269
|
+
): Rpc<Stored, Args, Failures | RungRefusals<Answers>, Produced>
|
|
270
|
+
<
|
|
271
|
+
Stored,
|
|
272
|
+
Accepted,
|
|
273
|
+
Failures,
|
|
274
|
+
Produced,
|
|
275
|
+
Answers extends readonly unknown[] = unknown[],
|
|
276
|
+
>(
|
|
229
277
|
handler: Reactive<Stored, Accepted, Failures, Produced>,
|
|
230
|
-
options?: RpcOptions<undefined>,
|
|
231
|
-
): Rpc<Stored, undefined, Failures
|
|
278
|
+
options?: RpcOptions<undefined, Answers>,
|
|
279
|
+
): Rpc<Stored, undefined, Failures | RungRefusals<Answers>, Produced>
|
|
232
280
|
// 11.2 — a stream whose parameter is optional is keyed, for `reactive`'s reason.
|
|
233
281
|
<
|
|
234
282
|
Body extends (args?: never) => unknown,
|
|
235
283
|
Shaped = ChunkOf<Body>[],
|
|
236
284
|
Gate = Schema<ChunkOf<Body>[]>,
|
|
237
285
|
ArgsGate = Schema<Parameters<Body>[0]>,
|
|
286
|
+
Answers extends readonly unknown[] = unknown[],
|
|
238
287
|
>(
|
|
239
288
|
handler: Body & OptionalParameter<Body> & Streams<Body> & Plain,
|
|
240
289
|
options?: Shaping<
|
|
@@ -244,36 +293,49 @@ type Declare = {
|
|
|
244
293
|
Gate,
|
|
245
294
|
ArgsGate
|
|
246
295
|
> &
|
|
247
|
-
RpcOptions<Parameters<Body>[0]>,
|
|
296
|
+
RpcOptions<Parameters<Body>[0], Answers>,
|
|
248
297
|
): Rpc<
|
|
249
298
|
Kept<Shaped>,
|
|
250
299
|
Parameters<Body>[0],
|
|
251
|
-
Refusals<Shaped, Gate, ChunkOf<Body>[], ArgsGate, Parameters<Body>[0]
|
|
300
|
+
| Refusals<Shaped, Gate, ChunkOf<Body>[], ArgsGate, Parameters<Body>[0]>
|
|
301
|
+
| RungRefusals<Answers>,
|
|
252
302
|
ChunkOf<Body>
|
|
253
303
|
>
|
|
254
|
-
<
|
|
304
|
+
<
|
|
305
|
+
Chunk,
|
|
306
|
+
Shaped = Chunk[],
|
|
307
|
+
Gate = Schema<Chunk[]>,
|
|
308
|
+
Answers extends readonly unknown[] = unknown[],
|
|
309
|
+
>(
|
|
255
310
|
handler: (() =>
|
|
256
311
|
AsyncGenerator<Chunk> | Promise<AsyncGenerator<Chunk>>) &
|
|
257
312
|
Plain,
|
|
258
|
-
options?: Shaping<Chunk[], Shaped, never, Gate> &
|
|
259
|
-
|
|
313
|
+
options?: Shaping<Chunk[], Shaped, never, Gate> &
|
|
314
|
+
RpcOptions<undefined, Answers>,
|
|
315
|
+
): Rpc<
|
|
316
|
+
Kept<Shaped>,
|
|
317
|
+
undefined,
|
|
318
|
+
Refusals<Shaped, Gate, Chunk[]> | RungRefusals<Answers>,
|
|
319
|
+
Chunk
|
|
320
|
+
>
|
|
260
321
|
<
|
|
261
322
|
Chunk,
|
|
262
323
|
Args,
|
|
263
324
|
Shaped = Chunk[],
|
|
264
325
|
Gate = Schema<Chunk[]>,
|
|
265
326
|
ArgsGate = Schema<Args>,
|
|
327
|
+
Answers extends readonly unknown[] = unknown[],
|
|
266
328
|
>(
|
|
267
329
|
handler: ((
|
|
268
330
|
args: Args,
|
|
269
331
|
) => AsyncGenerator<Chunk> | Promise<AsyncGenerator<Chunk>>) &
|
|
270
332
|
Plain,
|
|
271
333
|
options?: Shaping<Chunk[], Shaped, Args, Gate, ArgsGate> &
|
|
272
|
-
RpcOptions<Args>,
|
|
334
|
+
RpcOptions<Args, Answers>,
|
|
273
335
|
): Rpc<
|
|
274
336
|
Kept<Shaped>,
|
|
275
337
|
Args,
|
|
276
|
-
Refusals<Shaped, Gate, Chunk[], ArgsGate, Args>,
|
|
338
|
+
Refusals<Shaped, Gate, Chunk[], ArgsGate, Args> | RungRefusals<Answers>,
|
|
277
339
|
Chunk
|
|
278
340
|
>
|
|
279
341
|
// 11.2 — an optional parameter is keyed, and ahead of the arm below for `reactive`'s reason.
|
|
@@ -282,6 +344,7 @@ type Declare = {
|
|
|
282
344
|
Shaped = Awaited<ReturnType<Body>>,
|
|
283
345
|
Gate = Schema<Awaited<ReturnType<Body>>>,
|
|
284
346
|
ArgsGate = Schema<Parameters<Body>[0]>,
|
|
347
|
+
Answers extends readonly unknown[] = unknown[],
|
|
285
348
|
>(
|
|
286
349
|
handler: Body & OptionalParameter<Body> & Plain,
|
|
287
350
|
options?: Shaping<
|
|
@@ -291,7 +354,7 @@ type Declare = {
|
|
|
291
354
|
Gate,
|
|
292
355
|
ArgsGate
|
|
293
356
|
> &
|
|
294
|
-
RpcOptions<Parameters<Body>[0]>,
|
|
357
|
+
RpcOptions<Parameters<Body>[0], Answers>,
|
|
295
358
|
): Rpc<
|
|
296
359
|
Kept<Shaped>,
|
|
297
360
|
Parameters<Body>[0],
|
|
@@ -302,17 +365,25 @@ type Declare = {
|
|
|
302
365
|
Awaited<ReturnType<Body>>,
|
|
303
366
|
ArgsGate,
|
|
304
367
|
Parameters<Body>[0]
|
|
305
|
-
|
|
368
|
+
>
|
|
369
|
+
| RungRefusals<Answers>,
|
|
306
370
|
AdoptedProduced<ReturnType<Body>, Kept<Shaped>>
|
|
307
371
|
>
|
|
308
|
-
<
|
|
372
|
+
<
|
|
373
|
+
Computed,
|
|
374
|
+
Shaped = Awaited<Computed>,
|
|
375
|
+
Gate = Schema<Awaited<Computed>>,
|
|
376
|
+
Answers extends readonly unknown[] = unknown[],
|
|
377
|
+
>(
|
|
309
378
|
handler: (() => Computed) & Plain,
|
|
310
379
|
options?: Shaping<Awaited<Computed>, Shaped, never, Gate> &
|
|
311
|
-
RpcOptions<undefined>,
|
|
380
|
+
RpcOptions<undefined, Answers>,
|
|
312
381
|
): Rpc<
|
|
313
382
|
Kept<Shaped>,
|
|
314
383
|
undefined,
|
|
315
|
-
AdoptedFailures<Computed>
|
|
384
|
+
| AdoptedFailures<Computed>
|
|
385
|
+
| Refusals<Shaped, Gate, Awaited<Computed>>
|
|
386
|
+
| RungRefusals<Answers>,
|
|
316
387
|
AdoptedProduced<Computed, Kept<Shaped>>
|
|
317
388
|
>
|
|
318
389
|
<
|
|
@@ -321,23 +392,34 @@ type Declare = {
|
|
|
321
392
|
Shaped = Awaited<Computed>,
|
|
322
393
|
Gate = Schema<Awaited<Computed>>,
|
|
323
394
|
ArgsGate = Schema<Args>,
|
|
395
|
+
Answers extends readonly unknown[] = unknown[],
|
|
324
396
|
>(
|
|
325
397
|
handler: ((args: Args) => Computed) & Plain,
|
|
326
398
|
options?: Shaping<Awaited<Computed>, Shaped, Args, Gate, ArgsGate> &
|
|
327
|
-
RpcOptions<Args>,
|
|
399
|
+
RpcOptions<Args, Answers>,
|
|
328
400
|
): Rpc<
|
|
329
401
|
Kept<Shaped>,
|
|
330
402
|
Args,
|
|
331
403
|
| AdoptedFailures<Computed>
|
|
332
|
-
| Refusals<Shaped, Gate, Awaited<Computed>, ArgsGate, Args
|
|
404
|
+
| Refusals<Shaped, Gate, Awaited<Computed>, ArgsGate, Args>
|
|
405
|
+
| RungRefusals<Answers>,
|
|
333
406
|
AdoptedProduced<Computed, Kept<Shaped>>
|
|
334
407
|
>
|
|
335
408
|
// 16.1 — a value, `reactive`'s value overload given an address. Last, so a function is a body.
|
|
336
|
-
<
|
|
409
|
+
<
|
|
410
|
+
Accepted,
|
|
411
|
+
Shaped = Accepted,
|
|
412
|
+
Gate = Schema<Accepted>,
|
|
413
|
+
Answers extends readonly unknown[] = unknown[],
|
|
414
|
+
>(
|
|
337
415
|
handler: Held<Accepted>,
|
|
338
416
|
options?: Shaping<Accepted, Shaped, never, Gate> &
|
|
339
|
-
RpcOptions<undefined>,
|
|
340
|
-
): Rpc<
|
|
417
|
+
RpcOptions<undefined, Answers>,
|
|
418
|
+
): Rpc<
|
|
419
|
+
Kept<Shaped>,
|
|
420
|
+
undefined,
|
|
421
|
+
Refusals<Shaped, Gate, Accepted> | RungRefusals<Answers>
|
|
422
|
+
>
|
|
341
423
|
}
|
|
342
424
|
|
|
343
425
|
/** What a handler's selection members answer before a call has built its value: no entries' answers. */
|
|
@@ -379,7 +461,11 @@ export function declare(method: Method): Declare {
|
|
|
379
461
|
// 16.11, 16.12 — THE RUNGS, IN REGISTRATION ORDER, around the reach. Built once, at the
|
|
380
462
|
// declaration (16.14); a value keyed by args, so a second call with the same args in one
|
|
381
463
|
// request joins the first (1.8) and a server files one per request (11.32).
|
|
382
|
-
|
|
464
|
+
// `Reached` is the declaration's view of `next`; at run time it answers the handler's value.
|
|
465
|
+
const rungs = (options?.middleware ?? []) as readonly Rung<
|
|
466
|
+
RpcContext,
|
|
467
|
+
unknown
|
|
468
|
+
>[]
|
|
383
469
|
// A rung that handed `next` a context of its own making chose those args.
|
|
384
470
|
const argsOf = (ctx: RpcContext): unknown =>
|
|
385
471
|
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
|