@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
package/package.json
CHANGED
package/src/compiler/appMap.ts
CHANGED
|
@@ -16,6 +16,8 @@ type MappedRoute = {
|
|
|
16
16
|
kind: 'page' | 'handler' | 'private'
|
|
17
17
|
/** 23.26 — each matcher its address names, to the anchored pattern it takes a part by. */
|
|
18
18
|
matchers: Record<string, string>
|
|
19
|
+
/** 38.135 — the rungs a handler's `middleware` names, `[]` for none, `null` where unread. */
|
|
20
|
+
middleware: string[] | null
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/** One hook call, the route it is filed at, and the file making it. */
|
|
@@ -42,6 +44,8 @@ export async function appMap(
|
|
|
42
44
|
file: route.source,
|
|
43
45
|
kind: 'page',
|
|
44
46
|
matchers: route.matchers,
|
|
47
|
+
// A page declares no rungs of its own: what runs over it is filed by a hook (37.2).
|
|
48
|
+
middleware: [],
|
|
45
49
|
})
|
|
46
50
|
for (const mount of app.mounts)
|
|
47
51
|
routes.push({
|
|
@@ -50,6 +54,7 @@ export async function appMap(
|
|
|
50
54
|
file: mount.source,
|
|
51
55
|
kind: mount.private ? 'private' : 'handler',
|
|
52
56
|
matchers: mount.matchers,
|
|
57
|
+
middleware: mount.middleware,
|
|
53
58
|
})
|
|
54
59
|
routes.sort((one, two) =>
|
|
55
60
|
one.address === two.address
|
|
@@ -81,7 +86,7 @@ export function mapText(map: Awaited<ReturnType<typeof appMap>>): string {
|
|
|
81
86
|
const addresses = width(map.routes.map((route) => route.address))
|
|
82
87
|
for (const route of map.routes)
|
|
83
88
|
lines.push(
|
|
84
|
-
`${route.method.padEnd(methods)} ${route.address.padEnd(addresses)} ${route.file}${route.kind === 'private' ? ' (private)' : ''}`,
|
|
89
|
+
`${route.method.padEnd(methods)} ${route.address.padEnd(addresses)} ${route.file}${route.kind === 'private' ? ' (private)' : ''}${rungsText(route.middleware)}`,
|
|
85
90
|
)
|
|
86
91
|
// 23.26 — each matcher once, whichever routes name it: what a part must match to be taken.
|
|
87
92
|
const matchers = new Map<string, string>()
|
|
@@ -105,3 +110,11 @@ export function mapText(map: Awaited<ReturnType<typeof appMap>>): string {
|
|
|
105
110
|
}
|
|
106
111
|
return lines.join('\n')
|
|
107
112
|
}
|
|
113
|
+
|
|
114
|
+
/** 38.135 — a handler's rungs as the text map prints them after its file, or nothing for none. */
|
|
115
|
+
function rungsText(middleware: string[] | null): string {
|
|
116
|
+
if (middleware === null) return ' middleware: unread'
|
|
117
|
+
return middleware.length === 0
|
|
118
|
+
? ''
|
|
119
|
+
: ` middleware: ${middleware.join(', ')}`
|
|
120
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
isObjectLiteralExpression,
|
|
11
11
|
isPropertyAccessExpression,
|
|
12
12
|
isPropertyAssignment,
|
|
13
|
+
isSpreadElement,
|
|
13
14
|
isStringLiteral,
|
|
14
15
|
isVariableDeclaration,
|
|
15
16
|
} from 'typescript/unstable/ast/is'
|
|
@@ -179,6 +180,10 @@ async function exportsIn(
|
|
|
179
180
|
cli: declared?.cli ?? true,
|
|
180
181
|
mcp: declared?.mcp ?? true,
|
|
181
182
|
description: declared?.description,
|
|
183
|
+
middleware:
|
|
184
|
+
declared === undefined
|
|
185
|
+
? []
|
|
186
|
+
: declared.middleware,
|
|
182
187
|
copy: declared?.copy,
|
|
183
188
|
defaults: declared?.defaults,
|
|
184
189
|
args:
|
|
@@ -228,6 +233,7 @@ type DeclaredExport = Pick<
|
|
|
228
233
|
| 'args'
|
|
229
234
|
| 'result'
|
|
230
235
|
| 'description'
|
|
236
|
+
| 'middleware'
|
|
231
237
|
> & {
|
|
232
238
|
method: Declared | undefined
|
|
233
239
|
/** 18.15 — what its copy's options declare and the build could not read. */
|
|
@@ -249,6 +255,7 @@ async function declaringMethod(
|
|
|
249
255
|
cli: boolean
|
|
250
256
|
mcp: boolean
|
|
251
257
|
description: string | undefined
|
|
258
|
+
middleware: string[] | null
|
|
252
259
|
copy: CopyOptions | undefined
|
|
253
260
|
unread: Node[]
|
|
254
261
|
defaulted?: string[]
|
|
@@ -321,6 +328,7 @@ async function declaringMethod(
|
|
|
321
328
|
cli: declaredFor(call, 'cli') ?? !answersResponse,
|
|
322
329
|
mcp: declaredFor(call, 'mcp') ?? !answersResponse,
|
|
323
330
|
description,
|
|
331
|
+
middleware: rungsNamed(call),
|
|
324
332
|
...declared,
|
|
325
333
|
defaulted: defaultedMembers(body),
|
|
326
334
|
defaults: literalDefaults(body) ?? undefined,
|
|
@@ -602,6 +610,39 @@ function describedAs(
|
|
|
602
610
|
: undefined
|
|
603
611
|
}
|
|
604
612
|
|
|
613
|
+
/**
|
|
614
|
+
* The rung each entry of a handler call's inline `middleware` names, or `null` where its options are
|
|
615
|
+
* not an object written inline.
|
|
616
|
+
*
|
|
617
|
+
* 38.135 — AS WRITTEN, which is what the build can read without running it: an entry is named by
|
|
618
|
+
* the function it calls or the name it refers to, a spread by what it spreads, and an inline function
|
|
619
|
+
* `<inline>`. A value that is not an array is read as one entry, so `middleware: homeAccess(…)` names
|
|
620
|
+
* `homeAccess`.
|
|
621
|
+
*/
|
|
622
|
+
function rungsNamed(
|
|
623
|
+
call: Node & { arguments?: readonly Node[] },
|
|
624
|
+
): string[] | null {
|
|
625
|
+
const options = call.arguments?.[1]
|
|
626
|
+
if (options === undefined) return []
|
|
627
|
+
if (!isObjectLiteralExpression(options)) return null
|
|
628
|
+
const written = optionNamed(options, 'middleware')
|
|
629
|
+
if (written === undefined) return []
|
|
630
|
+
const entries = isArrayLiteralExpression(written)
|
|
631
|
+
? written.elements
|
|
632
|
+
: [written]
|
|
633
|
+
const names: string[] = []
|
|
634
|
+
for (const entry of entries)
|
|
635
|
+
names.push(rungName(isSpreadElement(entry) ? entry.expression : entry))
|
|
636
|
+
return names
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function rungName(entry: Node): string {
|
|
640
|
+
const named = isCallExpression(entry) ? entry.expression : entry
|
|
641
|
+
return isIdentifier(named) || isPropertyAccessExpression(named)
|
|
642
|
+
? named.getText()
|
|
643
|
+
: '<inline>'
|
|
644
|
+
}
|
|
645
|
+
|
|
605
646
|
/**
|
|
606
647
|
* What a handler call's inline `clients` declares for `surface`, or `undefined` where it does not.
|
|
607
648
|
*
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
SOURCE_SUFFIX,
|
|
21
21
|
} from './splitImports.ts'
|
|
22
22
|
import type {
|
|
23
|
+
Attribute,
|
|
23
24
|
Body,
|
|
24
25
|
Branch,
|
|
25
26
|
ElementNode,
|
|
@@ -489,7 +490,10 @@ function project(file: AbideFile): Projection {
|
|
|
489
490
|
const [kind, open] = attribute.lowerName.startsWith(
|
|
490
491
|
'bind:',
|
|
491
492
|
)
|
|
492
|
-
? ([
|
|
493
|
+
? ([
|
|
494
|
+
'elementBind',
|
|
495
|
+
bindOpenerOf(node, attribute.lowerName),
|
|
496
|
+
] as const)
|
|
493
497
|
: openerOf(node.tag, attribute.lowerName)
|
|
494
498
|
slots.set(
|
|
495
499
|
attribute,
|
|
@@ -636,7 +640,7 @@ function project(file: AbideFile): Projection {
|
|
|
636
640
|
* implicit `any`; an attribute on a component whose tag is no name a call can spell (`invocation`
|
|
637
641
|
* has the rest) is typed by nothing the shadow can see, and says so with `any` rather than with an
|
|
638
642
|
* error the author did not make. A `bind:` operand is not asked about here: the caller opens it
|
|
639
|
-
* with `
|
|
643
|
+
* with `bindOpenerOf`.
|
|
640
644
|
*/
|
|
641
645
|
function openerOf(
|
|
642
646
|
tag: string,
|
|
@@ -648,6 +652,44 @@ function openerOf(
|
|
|
648
652
|
return ['attribute', '(']
|
|
649
653
|
}
|
|
650
654
|
|
|
655
|
+
/**
|
|
656
|
+
* What a `bind:` operand on an element is opened with: the bind's `set` held to what the element
|
|
657
|
+
* writes back (32.78), or `BIND` where the element's write is no type the shadow knows.
|
|
658
|
+
*
|
|
659
|
+
* A `type` or a `multiple` written as an expression, or one a spread may write, is read as each it
|
|
660
|
+
* could be, so the `set` is held to the union of their writes.
|
|
661
|
+
*/
|
|
662
|
+
function bindOpenerOf(node: ElementNode, lowerName: string): string {
|
|
663
|
+
const kind = writesOf(node, lowerName)
|
|
664
|
+
return kind === undefined
|
|
665
|
+
? `${BIND}(`
|
|
666
|
+
: `${BIND_WRITES}(${JSON.stringify(kind)})(`
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/** Which write a `bind:` on an element stands for, as `BIND_WRITES` names its kinds. */
|
|
670
|
+
function writesOf(node: ElementNode, lowerName: string): string | undefined {
|
|
671
|
+
if (lowerName === 'bind:checked' || lowerName === 'bind:open')
|
|
672
|
+
return 'boolean'
|
|
673
|
+
if (lowerName === 'bind:files') return 'files'
|
|
674
|
+
if (lowerName !== 'bind:value') return undefined
|
|
675
|
+
if (node.lowerTag === 'textarea') return 'text'
|
|
676
|
+
if (node.lowerTag !== 'input' && node.lowerTag !== 'select')
|
|
677
|
+
return undefined
|
|
678
|
+
const named = node.lowerTag === 'input' ? 'type' : 'multiple'
|
|
679
|
+
let written: Attribute | undefined
|
|
680
|
+
let spread = false
|
|
681
|
+
for (const attribute of node.attributes) {
|
|
682
|
+
if (attribute.lowerName === named) written = attribute
|
|
683
|
+
else if (attribute.name === '...') spread = true
|
|
684
|
+
}
|
|
685
|
+
if (written?.expression || (written === undefined && spread))
|
|
686
|
+
return node.lowerTag === 'input' ? 'input' : 'select'
|
|
687
|
+
if (node.lowerTag === 'select')
|
|
688
|
+
return written === undefined ? 'option' : 'options'
|
|
689
|
+
const type = written?.value.trim().toLowerCase()
|
|
690
|
+
return type === 'number' || type === 'range' ? 'number' : 'text'
|
|
691
|
+
}
|
|
692
|
+
|
|
651
693
|
const PROP = '__prop'
|
|
652
694
|
// What an inline component's function names the record it is called with, and abide's `props` as
|
|
653
695
|
// its head calls it (32.40) — bound here, since the author imports `props` only where they call it.
|
|
@@ -669,6 +711,8 @@ const ON = '__on'
|
|
|
669
711
|
const ANY = '__any'
|
|
670
712
|
/** 31.3 — what a `bind:` operand is wrapped in on an element and on a component. */
|
|
671
713
|
const BIND = '__bind'
|
|
714
|
+
/** 32.78 — what an element's `bind:` is opened with where the shadow knows what the element writes. */
|
|
715
|
+
const BIND_WRITES = '__bindWrites'
|
|
672
716
|
const BOUND = '__bound'
|
|
673
717
|
|
|
674
718
|
/** 33.6 — the type a written prop's cell is checked as, which is the framework's own. */
|
|
@@ -696,6 +740,12 @@ declare function ${ANY}(value: any): void
|
|
|
696
740
|
declare function ${PROP}<Value>(value: Value): Value
|
|
697
741
|
declare function ${BIND}<Value>(value: { get(): Value; set(value: Value): unknown }): void
|
|
698
742
|
declare function ${BIND}(value: any): void
|
|
743
|
+
type ${BIND_WRITES}Number<Value> = [Extract<Value, number>] extends [never] ? never : number | undefined
|
|
744
|
+
type ${BIND_WRITES}Text<Value> = [Extract<Value, number>] extends [never] ? string : [Exclude<Value, number>] extends [never] ? number | undefined : string | number | undefined
|
|
745
|
+
type ${BIND_WRITES}Option<Value> = [Extract<Value, string>] extends [never] ? ${BIND_WRITES}Text<Value> : Extract<Value, string> | ${BIND_WRITES}Number<Value>
|
|
746
|
+
type ${BIND_WRITES}Options<Value> = Value extends readonly (infer Item)[] ? [Extract<Item, string>] extends [never] ? string[] : Extract<Item, string>[] : string[]
|
|
747
|
+
type ${BIND_WRITES}Of<Kind, Value> = Kind extends 'boolean' ? boolean : Kind extends 'files' ? File[] : Kind extends 'number' ? number | undefined : Kind extends 'input' ? string | number | undefined : Kind extends 'option' ? ${BIND_WRITES}Option<Value> : Kind extends 'options' ? ${BIND_WRITES}Options<Value> : Kind extends 'select' ? ${BIND_WRITES}Option<Value> | ${BIND_WRITES}Options<Value> : ${BIND_WRITES}Text<Value>
|
|
748
|
+
declare function ${BIND_WRITES}<Kind extends string>(kind: Kind): <Value>(value: { (): Value; set: (value: ${BIND_WRITES}Of<Kind, Value>) => unknown } | { get(): Value; set: (value: ${BIND_WRITES}Of<Kind, Value>) => unknown }) => void
|
|
699
749
|
declare function ${BOUND}<Value>(value: ${REACTIVE}<Value, any, any, any>): Value
|
|
700
750
|
declare function ${BOUND}<Value>(value: { get(): Value; set(value: Value): unknown }): Value
|
|
701
751
|
declare function __restValues<Accessors>(record: Accessors): { [Key in keyof Accessors]: NonNullable<Accessors[Key]> extends () => infer Value ? Value : never }
|
package/src/compiler/readsIn.ts
CHANGED
|
@@ -447,6 +447,14 @@ const NULLISH =
|
|
|
447
447
|
TypeFlags.Any |
|
|
448
448
|
TypeFlags.Unknown
|
|
449
449
|
|
|
450
|
+
/** A union's members, or the type alone, with every `NULLISH` one aside. */
|
|
451
|
+
async function presentMembers(type: Type): Promise<Type[]> {
|
|
452
|
+
const present: Type[] = []
|
|
453
|
+
for (const held of await unionMembers(type))
|
|
454
|
+
if ((held.flags & NULLISH) === 0) present.push(held)
|
|
455
|
+
return present
|
|
456
|
+
}
|
|
457
|
+
|
|
450
458
|
async function admitsNullish(type: Type): Promise<boolean> {
|
|
451
459
|
for (const member of await unionMembers(type))
|
|
452
460
|
if ((member.flags & NULLISH) !== 0) return true
|
|
@@ -737,11 +745,11 @@ function valueLeaves(node: Node, leaves: Node[]): void {
|
|
|
737
745
|
}
|
|
738
746
|
}
|
|
739
747
|
|
|
740
|
-
// 23.1 — the
|
|
741
|
-
const ROUTE_MEMBERS = new Set(['url', 'params', 'name', 'navigating'])
|
|
748
|
+
// 23.1 — the five values `route` holds, each a `Reactive` of its own.
|
|
749
|
+
const ROUTE_MEMBERS = new Set(['url', 'params', 'name', 'index', 'navigating'])
|
|
742
750
|
|
|
743
751
|
/**
|
|
744
|
-
* 31.15 — `route`'s
|
|
752
|
+
* 31.15 — `route`'s five values, each read by name as a name bound to a `Reactive` is.
|
|
745
753
|
*
|
|
746
754
|
* `route.params.id` reads `route.params().id`, and the explicit call keeps compiling (31.7). Told
|
|
747
755
|
* by the symbol, as `props` is, so an app's own `route` is not abide's.
|
|
@@ -1224,11 +1232,10 @@ async function ownMembers(
|
|
|
1224
1232
|
asked.push(
|
|
1225
1233
|
valueOf(node).then(async (value) => {
|
|
1226
1234
|
if (value === undefined) return
|
|
1227
|
-
for (const held of await
|
|
1235
|
+
for (const held of await presentMembers(value))
|
|
1228
1236
|
if (
|
|
1229
|
-
(held.flags & NULLISH) === 0 &&
|
|
1230
1237
|
(await checker.getPropertyOfType(held, member)) !==
|
|
1231
|
-
|
|
1238
|
+
undefined
|
|
1232
1239
|
)
|
|
1233
1240
|
return void owned.add(node)
|
|
1234
1241
|
}),
|
|
@@ -1282,7 +1289,9 @@ async function liftedMutators(
|
|
|
1282
1289
|
asked.push(
|
|
1283
1290
|
valueOf(node).then(async (value) => {
|
|
1284
1291
|
if (value === undefined) return
|
|
1285
|
-
|
|
1292
|
+
// `null` and `undefined` aside at the root as at every step down: `drafts?.push(d)`
|
|
1293
|
+
// over a `Reactive<Draft[] | undefined>` is a mutator on an array.
|
|
1294
|
+
let types = await presentMembers(value)
|
|
1286
1295
|
for (const step of steps)
|
|
1287
1296
|
types = await stepTypes(checker, types, step)
|
|
1288
1297
|
if (types.length === 0) return
|
|
@@ -1310,34 +1319,30 @@ async function stepTypes(
|
|
|
1310
1319
|
step: string | undefined,
|
|
1311
1320
|
): Promise<Type[]> {
|
|
1312
1321
|
const next: Type[] = []
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
continue
|
|
1325
|
-
}
|
|
1326
|
-
if (await checker.isArrayType(held)) {
|
|
1327
|
-
const [element] = await checker.getTypeArguments(
|
|
1328
|
-
held as TypeReference,
|
|
1329
|
-
)
|
|
1330
|
-
if (element !== undefined)
|
|
1331
|
-
next.push(...(await unionMembers(element)))
|
|
1332
|
-
continue
|
|
1333
|
-
}
|
|
1334
|
-
for (const index of await checker.getIndexInfosOfType(held))
|
|
1335
|
-
next.push(...(await unionMembers(index.valueType)))
|
|
1322
|
+
// `types` is present members already: the root's, or this walk's own.
|
|
1323
|
+
for (const held of types) {
|
|
1324
|
+
if (step !== undefined) {
|
|
1325
|
+
const member = await checker.getPropertyOfType(held, step)
|
|
1326
|
+
const stepped =
|
|
1327
|
+
member === undefined
|
|
1328
|
+
? undefined
|
|
1329
|
+
: await checker.getTypeOfSymbol(member)
|
|
1330
|
+
if (stepped !== undefined)
|
|
1331
|
+
next.push(...(await presentMembers(stepped)))
|
|
1332
|
+
continue
|
|
1336
1333
|
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1334
|
+
if (await checker.isArrayType(held)) {
|
|
1335
|
+
const [element] = await checker.getTypeArguments(
|
|
1336
|
+
held as TypeReference,
|
|
1337
|
+
)
|
|
1338
|
+
if (element !== undefined)
|
|
1339
|
+
next.push(...(await presentMembers(element)))
|
|
1340
|
+
continue
|
|
1341
|
+
}
|
|
1342
|
+
for (const index of await checker.getIndexInfosOfType(held))
|
|
1343
|
+
next.push(...(await presentMembers(index.valueType)))
|
|
1344
|
+
}
|
|
1345
|
+
return next
|
|
1341
1346
|
}
|
|
1342
1347
|
|
|
1343
1348
|
/**
|
package/src/server/PageRender.ts
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
import { EARLY_CALLS } from '#shared/EARLY_CALLS.ts'
|
|
12
12
|
import { HandlerRoute } from '#shared/HandlerRoute.ts'
|
|
13
13
|
import { KEEP_HEADER } from '#shared/KEEP_HEADER.ts'
|
|
14
|
-
import { KEEP_OPENED } from '#shared/KEEP_OPENED.ts'
|
|
15
14
|
import type { Rpc } from '#shared/rpc.ts'
|
|
16
15
|
import { type RpcRequest, rpcRequest } from '#shared/rpcRequest.ts'
|
|
17
16
|
|
|
@@ -141,18 +140,18 @@ export class PageRender {
|
|
|
141
140
|
* one `principal.caller` reads for the rest of the request. 41.13 — every call the render makes is
|
|
142
141
|
* left for the browser's own call to be answered from. 41.29 — BUT NOT A PAGE A SCRIPT FETCHED, which
|
|
143
142
|
* nothing hydrates: its entries would answer the next call this caller makes with what the render
|
|
144
|
-
* saw, a sign-in since included
|
|
145
|
-
*
|
|
146
|
-
* what a later open of it starts.
|
|
143
|
+
* saw, a sign-in since included. The worker asks for the navigation it answers as that navigation
|
|
144
|
+
* (45.25), so the page that hydrates is never one a script fetched. 41.30 — SAVE ONE THE WORKER
|
|
145
|
+
* KEEPS, whose early calls are what a later open of it starts.
|
|
147
146
|
*/
|
|
148
147
|
export function beginPageRender(request: Request): PageRender {
|
|
149
148
|
const caller = callerOrMint()
|
|
150
149
|
const keep = request.headers.get(KEEP_HEADER)
|
|
151
|
-
const hydrated =
|
|
152
|
-
keep === KEEP_OPENED ||
|
|
153
|
-
request.headers.get('sec-fetch-dest') !== 'empty'
|
|
154
150
|
const page = new PageRender(
|
|
155
|
-
|
|
151
|
+
// A WORKER'S NAVIGATION IS STILL ONE: forwarded unchanged (45.25) it arrives `navigate` with
|
|
152
|
+
// its destination `empty`, and no script's fetch can ask in that mode.
|
|
153
|
+
request.headers.get('sec-fetch-dest') !== 'empty' ||
|
|
154
|
+
request.headers.get('sec-fetch-mode') === 'navigate'
|
|
156
155
|
? seedFor(caller)
|
|
157
156
|
: keep !== null
|
|
158
157
|
? callsOnly()
|
|
@@ -135,6 +135,8 @@ export function serveCompilation(root: string, compilation: Compilation): void {
|
|
|
135
135
|
// plugin per compilation because the filter is fixed at registration, and the runtime's `onLoad`
|
|
136
136
|
// cannot fall through, so the filter names the handler files and nothing else. The face it names
|
|
137
137
|
// is resolved by `modulesPlugin`, registered once.
|
|
138
|
+
// 16.88 — AND GIVES IT ITS ADDRESS, so a test importing the file calls, links and matches it (44.51)
|
|
139
|
+
// with no app booted.
|
|
138
140
|
// 15.8, 19.9 — AND HOLDS ITS HANDLER TO THE SCHEMA ITS ANNOTATION DERIVES, once the face has built it:
|
|
139
141
|
// this module is what every import of the file is answered with — the server's mount, a test's own
|
|
140
142
|
// import, a component's under `mount` — so a call is held to it whoever makes it.
|
|
@@ -153,15 +155,20 @@ function handlerFilesPlugin(
|
|
|
153
155
|
},
|
|
154
156
|
(args) => {
|
|
155
157
|
const face = JSON.stringify(`${args.path}${HANDLER_SUFFIX}`)
|
|
156
|
-
const schema = faces.get(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
158
|
+
const { address, args: schema } = faces.get(
|
|
159
|
+
args.path,
|
|
160
|
+
) as HandlerFace
|
|
161
|
+
const lines = [
|
|
162
|
+
`export { default } from ${face}`,
|
|
163
|
+
`import __handler from ${face}`,
|
|
164
|
+
`import { addressed as __addressed, derivedArgs as __derivedArgs } from '${SERVER_SPECIFIER}'`,
|
|
165
|
+
`__addressed(__handler, ${JSON.stringify(address)})`,
|
|
166
|
+
]
|
|
167
|
+
if (schema !== undefined)
|
|
168
|
+
lines.push(
|
|
169
|
+
`__derivedArgs(__handler, ${JSON.stringify(schema)})`,
|
|
170
|
+
)
|
|
171
|
+
return { contents: `${lines.join('\n')}\n`, loader: 'js' }
|
|
165
172
|
},
|
|
166
173
|
)
|
|
167
174
|
},
|
|
@@ -489,11 +496,17 @@ function compiledAt(served: Held, path: string): CompiledFile {
|
|
|
489
496
|
// what an app's own module calls is a function of plain props answering a `Component` (35.4), which
|
|
490
497
|
// `render` invokes with nothing. The component is the module's one export (34.17).
|
|
491
498
|
// 38.119 — and it carries what `mount` hydrates it with: the same accessors, and its browser module.
|
|
499
|
+
// 38.134 — a prop written as a getter is read through it on every read, as a parent's would be.
|
|
492
500
|
function boundModule(path: string): string {
|
|
493
501
|
return `import component from ${JSON.stringify(path)}
|
|
494
502
|
export default (props = {}) => {
|
|
495
503
|
const accessors = {}
|
|
496
504
|
for (const key in props) {
|
|
505
|
+
const live = Object.getOwnPropertyDescriptor(props, key)?.get
|
|
506
|
+
if (live !== undefined) {
|
|
507
|
+
accessors[key] = () => live.call(props)
|
|
508
|
+
continue
|
|
509
|
+
}
|
|
497
510
|
const value = props[key]
|
|
498
511
|
accessors[key] = () => value
|
|
499
512
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { mounted } from '#shared/mount.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gives a handler its address: `route` under the mount, read when it is reported.
|
|
5
|
+
*
|
|
6
|
+
* 16.88 — ONE WRITER, READ LATE: the mount is the configuration's, so the address a file's module
|
|
7
|
+
* is given as it loads is the one a boot would give it, and a later call only names the route again.
|
|
8
|
+
*/
|
|
9
|
+
export function addressed(handler: object, route: string): void {
|
|
10
|
+
Object.defineProperty(handler, 'address', {
|
|
11
|
+
get: () => mounted(route),
|
|
12
|
+
configurable: true,
|
|
13
|
+
})
|
|
14
|
+
}
|