@abide/abide 0.100.0 → 0.102.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 +31 -0
- package/bin/abide.ts +227 -163
- package/docs/REGISTRY.md +726 -0
- package/docs/RULEBOOK.md +2753 -0
- package/docs/abide.md +12878 -0
- package/package.json +10 -3
- package/scaffold/AGENTS.md +31 -0
- package/scaffold/CLAUDE.md +1 -0
- package/scaffold/src/layout.abide +18 -0
- package/src/compiler/Lowering.ts +78 -9
- package/src/compiler/ShadowMap.ts +2 -2
- package/src/compiler/TypeSession.ts +262 -67
- package/src/compiler/argsSchemaOf.ts +1 -1
- package/src/compiler/checkApp.ts +43 -15
- package/src/compiler/checkedShadow.ts +39 -5
- package/src/compiler/classifyRead.ts +6 -4
- package/src/compiler/classifyTemplate.ts +1 -1
- package/src/compiler/compileApp.ts +433 -193
- package/src/compiler/compiledEntry.ts +43 -39
- package/src/compiler/componentGraph.ts +58 -71
- package/src/compiler/declaredExports.ts +24 -7
- package/src/compiler/emitClient.ts +141 -71
- package/src/compiler/emitComponent.ts +37 -8
- package/src/compiler/emitSource.ts +106 -13
- package/src/compiler/handlersUnder.ts +106 -56
- package/src/compiler/index.ts +7 -0
- package/src/compiler/mountsOf.ts +138 -0
- package/src/compiler/nearest.ts +14 -0
- package/src/compiler/parseAbide.ts +157 -20
- package/src/compiler/projectShadow.ts +213 -7
- package/src/compiler/reactiveTypes.ts +20 -7
- package/src/compiler/readSources.ts +15 -2
- package/src/compiler/readsIn.ts +61 -7
- package/src/compiler/renderMarkup.ts +3 -3
- package/src/compiler/scaffold.ts +30 -11
- package/src/compiler/serveLanguage.ts +125 -34
- package/src/compiler/sourceMapOf.ts +193 -0
- package/src/compiler/splitImports.ts +205 -83
- package/src/compiler/syncTypes.ts +32 -0
- package/src/compiler/tokenize.ts +14 -0
- package/src/compiler/writeDefaultProject.ts +1 -1
- package/src/compiler/writeTypes.ts +82 -0
- package/src/server/CLIENT_BUILD.ts +11 -13
- package/src/server/DEFAULT_PORT.ts +1 -1
- package/src/server/LAUNCHER_FILES.ts +9 -0
- package/src/server/MODULE_QUERIES.ts +18 -0
- package/src/server/TABLES.ts +7 -0
- package/src/server/VIRTUAL_DIRECTORY.ts +4 -5
- package/src/server/abideModules.ts +391 -0
- package/src/server/answerFailure.ts +16 -6
- package/src/server/answerRequest.ts +235 -106
- package/src/server/answerRpc.ts +124 -39
- package/src/server/answerSocket.ts +141 -48
- package/src/server/answerValue.ts +22 -9
- package/src/server/appDirectories.ts +1 -1
- package/src/server/appPlugins.ts +1 -1
- package/src/server/appTree.ts +179 -0
- package/src/server/argsFor.ts +2 -2
- package/src/server/assistant.ts +2 -2
- package/src/server/bridgeRelay.ts +6 -8
- package/src/server/claudeCode.ts +0 -6
- package/src/server/clientBuildOptions.ts +8 -3
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +37 -37
- package/src/server/componentModules.d.ts +3 -1
- package/src/server/compressAnswer.ts +48 -14
- package/src/server/config.ts +25 -29
- package/src/server/configFields.ts +31 -25
- package/src/server/createApp.ts +59 -73
- package/src/server/devWorker.ts +144 -183
- package/src/server/executableName.ts +4 -0
- package/src/server/fenceServer.ts +37 -35
- package/src/server/generatedSurfaces.ts +99 -66
- package/src/server/hooks.ts +124 -52
- package/src/server/iconAccent.ts +191 -0
- package/src/server/index.ts +11 -7
- package/src/server/installSourceMaps.ts +88 -0
- package/src/server/launcherOptions.ts +34 -31
- package/src/server/launcherPage.ts +107 -64
- package/src/server/manifestOf.ts +16 -13
- package/src/server/namesUnder.ts +2 -2
- package/src/server/nativeMenu.ts +5 -5
- package/src/server/negotiateEncoding.ts +1 -1
- package/src/server/principal.ts +10 -5
- package/src/server/realOrAsGiven.ts +10 -0
- package/src/server/refuseAddressCollision.ts +21 -14
- package/src/server/remote.ts +49 -52
- package/src/server/render.ts +65 -94
- package/src/server/renderComponent.ts +5 -0
- package/src/server/reportHealth.ts +2 -2
- package/src/server/requestScope.ts +12 -1
- package/src/server/runBuild.ts +24 -0
- package/src/server/runCompiled.ts +5 -5
- package/src/server/runDesktop.ts +52 -27
- package/src/server/serveBuild.ts +43 -52
- package/src/server/serveDev.ts +81 -39
- package/src/server/shell.ts +7 -22
- package/src/server/sourceMappings.ts +139 -0
- package/src/server/surfaceOf.ts +18 -3
- package/src/server/testBuild.ts +1 -19
- package/src/server/withAbideSources.ts +117 -0
- package/src/shared/ADDRESSES.ts +7 -18
- package/src/shared/APP_META.ts +7 -0
- package/src/shared/CopyOptions.ts +1 -1
- package/src/shared/DEBUG_META.ts +1 -1
- package/src/shared/GLOBAL_GETS.ts +9 -3
- package/src/shared/RESERVED_DIRECTORIES.ts +15 -0
- package/src/shared/RPC_SERVER.ts +2 -2
- package/src/shared/cookies.ts +2 -2
- package/src/shared/csp.ts +1 -1
- package/src/shared/escapeText.ts +1 -1
- package/src/shared/filledPath.ts +85 -0
- package/src/shared/framing.ts +79 -6
- package/src/shared/headKeyOf.ts +2 -0
- package/src/shared/index.ts +1 -1
- package/src/shared/log.ts +27 -13
- package/src/shared/matchRoute.ts +23 -4
- package/src/shared/menuItem.ts +3 -3
- package/src/shared/reactive/Cursor.ts +3 -9
- package/src/shared/reactive/Keyed.ts +4 -3
- package/src/shared/reactive/NotLanded.ts +1 -1
- package/src/shared/reactive/Reactive.ts +81 -60
- package/src/shared/reactive/argsContract.ts +4 -4
- package/src/shared/reactive/canonicalKey.ts +4 -4
- package/src/shared/reactive/chunksOfStream.ts +6 -0
- package/src/shared/reactive/index.ts +1 -1
- package/src/shared/reactive/recordWrites.ts +103 -5
- package/src/shared/reactive/structural.ts +1 -1
- package/src/shared/reactive/tracking.ts +155 -21
- package/src/shared/reactive/watch.ts +10 -6
- package/src/shared/refuse.ts +1 -1
- package/src/shared/routePattern.ts +82 -46
- package/src/shared/rpc.ts +47 -13
- package/src/shared/rpcRequest.ts +27 -3
- package/src/shared/url.ts +4 -44
- package/src/shared/writePath.ts +1 -1
- package/src/ui/LAYER_NAMES.ts +1 -1
- package/src/ui/List.ts +3 -3
- package/src/ui/MOUNTING_LEVEL.ts +18 -0
- package/src/ui/Mismatch.ts +1 -1
- package/src/ui/RawRun.ts +1 -1
- package/src/ui/Router.ts +38 -31
- package/src/ui/Scope.ts +1 -1
- package/src/ui/TryRegion.ts +2 -2
- package/src/ui/applyHead.ts +33 -0
- package/src/ui/index.ts +1 -1
- package/src/ui/menuBridge.ts +1 -1
- package/src/ui/moveNode.ts +1 -1
- package/src/ui/reconcile.ts +2 -2
- package/src/ui/reportMenu.ts +2 -2
- package/src/ui/routing.ts +2 -2
- package/src/ui/setAttribute.ts +1 -1
- package/src/ui/setText.ts +1 -1
- package/src/ui/socketWire.ts +9 -7
- package/src/ui/wire.ts +1 -1
- package/scaffold/src/server/app.ts +0 -6
- package/scaffold/src/shared/.gitkeep +0 -0
- package/scaffold/src/ui/app.html +0 -8
- package/src/server/componentImports.ts +0 -56
- /package/scaffold/src/{ui/pages/page.abide → page.abide} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// A ROUTE LITERAL, PARSED. One grammar for two things that would otherwise drift: the
|
|
2
|
-
// page is filed
|
|
1
|
+
// A ROUTE LITERAL, PARSED. One grammar for two things that would otherwise drift: the file a
|
|
2
|
+
// page is filed as (`invoices/[id]/page.abide`) and the literal an author writes
|
|
3
3
|
// (`'/invoices/[id]'`). The bracket spelling is the file system's, so the address is read off the
|
|
4
4
|
// tree rather than declared beside it and the two cannot disagree.
|
|
5
5
|
//
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
// required takes a value, optional may be left out and omits its segment with it, and rest takes
|
|
8
8
|
// an array as well as a scalar.
|
|
9
9
|
//
|
|
10
|
-
// A LEAF:
|
|
11
|
-
// a constant crossing a seam lives where nothing pulls a graph in behind it.
|
|
10
|
+
// A LEAF: nothing imported but another leaf, because the compiler, the client router and a server
|
|
11
|
+
// router all read it and a constant crossing a seam lives where nothing pulls a graph in behind it.
|
|
12
|
+
|
|
13
|
+
import { PUBLIC_DIRECTORY, SERVER_DIRECTORY } from './RESERVED_DIRECTORIES.ts'
|
|
12
14
|
|
|
13
15
|
type SegmentKind = 'static' | 'required' | 'optional' | 'rest'
|
|
14
16
|
|
|
@@ -40,26 +42,23 @@ export function parseRoutePattern(pattern: string): Segment[] {
|
|
|
40
42
|
return out
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
const ROUTES = 'ui/pages/'
|
|
44
|
-
|
|
45
45
|
/** Whether a source is a page, a layout, an error page or a component. */
|
|
46
46
|
export type FileKind = 'page' | 'layout' | 'error' | 'component'
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Whether the `.abide` at `path` is a page, a layout, an error page or a component.
|
|
50
50
|
*
|
|
51
|
-
* AN ADDRESS RATHER THAN A NAME.
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
51
|
+
* AN ADDRESS RATHER THAN A NAME. The file name says which of the three it is, and only a file
|
|
52
|
+
* whose place is an `address` (`placeOf`) is one: a `page.abide` under a `server` directory is a
|
|
53
|
+
* component.
|
|
54
|
+
* One test for every reader: the build's routes, the checker's `Routes` and the `route.params` a
|
|
55
|
+
* page is typed by.
|
|
56
56
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* to the app or the project, or absolute.
|
|
57
|
+
* `path` is RELATIVE TO THE APP'S ROOT: its directories are its route, so an absolute path would
|
|
58
|
+
* read the machine's directories as the app's.
|
|
60
59
|
*/
|
|
61
60
|
export function fileKind(path: string): FileKind {
|
|
62
|
-
if (
|
|
61
|
+
if (placeOf(path) !== 'address') return 'component'
|
|
63
62
|
// The WHOLE basename: `homepage.abide` ends with `page.abide` and is a component.
|
|
64
63
|
const name = path.slice(path.lastIndexOf('/') + 1)
|
|
65
64
|
if (name === 'page.abide') return 'page'
|
|
@@ -67,30 +66,50 @@ export function fileKind(path: string): FileKind {
|
|
|
67
66
|
return name === 'error.abide' ? 'error' : 'component'
|
|
68
67
|
}
|
|
69
68
|
|
|
69
|
+
/** Where a file sits in an app, which is what it is to every reader (`placeOf`). */
|
|
70
|
+
export type Place = 'address' | 'server' | 'public' | 'dependency' | 'skipped'
|
|
71
|
+
|
|
70
72
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* `page.abide` is an address rather than a name (CLAUDE.md), so the directories above it ARE the
|
|
74
|
-
* route and the file itself contributes nothing.
|
|
73
|
+
* Where the file at `path`, relative to the app's root, sits — THE ONE ANSWER every walk, fence and
|
|
74
|
+
* emit reads, so no two of them disagree about a path.
|
|
75
75
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
76
|
+
* - `dependency` — 34.28: a package's, outside the root or under `node_modules`, whose directories
|
|
77
|
+
* are its package's layout and none of the app's;
|
|
78
|
+
* - `skipped` — under a dot directory, which no walk of the app enters;
|
|
79
|
+
* - `server` or `public` — 23.15: under that reserved directory, the nearest to the root deciding
|
|
80
|
+
* (23.16 refuses one inside the other);
|
|
81
|
+
* - `address` — 23.14: everywhere else, its directories the segments of its route.
|
|
79
82
|
*/
|
|
80
|
-
export function
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
for (
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
export function placeOf(path: string): Place {
|
|
84
|
+
if (path.startsWith('../') || path.startsWith('/')) return 'dependency'
|
|
85
|
+
const parts = path.split('/')
|
|
86
|
+
// The last part is the file itself, which is never a directory.
|
|
87
|
+
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
88
|
+
const part = parts[index] as string
|
|
89
|
+
if (part === 'node_modules') return 'dependency'
|
|
90
|
+
if (part.startsWith('.')) return 'skipped'
|
|
91
|
+
if (part === SERVER_DIRECTORY) return 'server'
|
|
92
|
+
if (part === PUBLIC_DIRECTORY) return 'public'
|
|
93
|
+
}
|
|
94
|
+
return 'address'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Whether a walk of the app passes over the entry `name`: a dot file or directory, or `node_modules`. */
|
|
98
|
+
export function skipped(name: string): boolean {
|
|
99
|
+
return name.startsWith('.') || name === 'node_modules'
|
|
87
100
|
}
|
|
88
101
|
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
/**
|
|
103
|
+
* The route a file answers at — its DIRECTORY's below the root, whatever its name.
|
|
104
|
+
*
|
|
105
|
+
* 23.14 — `media/[id]/page.abide` is `/media/[id]`, and so is every other file beside it, which is
|
|
106
|
+
* what lets a directory's handlers sit in as many files as an author likes (16.2). `path` is
|
|
107
|
+
* relative to the app's root, as `fileKind`'s is.
|
|
108
|
+
*/
|
|
109
|
+
export function routeOf(path: string): string {
|
|
110
|
+
const parts = path.split('/')
|
|
111
|
+
parts.pop()
|
|
112
|
+
return `/${parts.filter((part) => part !== '').join('/')}`
|
|
94
113
|
}
|
|
95
114
|
|
|
96
115
|
/**
|
|
@@ -107,27 +126,44 @@ function routesAt(path: string): number {
|
|
|
107
126
|
* themselves, and `/x` ranked BELOW `/[[a]]/[[b]]` for the path `/x`: a static segment beaten by
|
|
108
127
|
* two optionals, which is 30.9 exactly inverted. Both were live on the scalar this replaced.
|
|
109
128
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
129
|
+
* WHERE ONE PATTERN ENDS, ITS END RANKS AS A SEGMENT: below a static or a required one and above an
|
|
130
|
+
* optional or a rest. `/a` admits one address and `/a/[[b]]` admits two, so the shorter is the
|
|
131
|
+
* narrower claim; but `/[[lang]]` and `/[[lang]]/about` both match `/about`, the longer by a static
|
|
132
|
+
* segment where the shorter spends `about` on an optional, so there the longer is.
|
|
114
133
|
*/
|
|
115
134
|
export function compareRoutes(a: Segment[], b: Segment[]): number {
|
|
116
|
-
const
|
|
117
|
-
for (let at = 0; at <
|
|
118
|
-
const kind =
|
|
119
|
-
specificity(b[at] as Segment) - specificity(a[at] as Segment)
|
|
135
|
+
const longest = a.length > b.length ? a.length : b.length
|
|
136
|
+
for (let at = 0; at < longest; at += 1) {
|
|
137
|
+
const kind = specificity(b[at]) - specificity(a[at])
|
|
120
138
|
if (kind !== 0) return kind
|
|
121
139
|
}
|
|
122
|
-
return
|
|
140
|
+
return 0
|
|
123
141
|
}
|
|
124
142
|
|
|
125
|
-
function specificity(segment: Segment): number {
|
|
126
|
-
if (segment
|
|
127
|
-
if (segment.kind === '
|
|
143
|
+
function specificity(segment: Segment | undefined): number {
|
|
144
|
+
if (segment === undefined) return 2
|
|
145
|
+
if (segment.kind === 'static') return 4
|
|
146
|
+
if (segment.kind === 'required') return 3
|
|
128
147
|
return segment.kind === 'optional' ? 1 : 0
|
|
129
148
|
}
|
|
130
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Whether `outer` is the front of `route`: a static segment by name, a parameter by kind.
|
|
152
|
+
*
|
|
153
|
+
* 35.1, 37.2 — what a layout wraps and what a route's hooks run over: the routes at or below its
|
|
154
|
+
* own, a parameter's name aside, so `/media/[id]` covers `/media/[slug]/edit`.
|
|
155
|
+
*/
|
|
156
|
+
export function coversRoute(outer: Segment[], route: Segment[]): boolean {
|
|
157
|
+
if (outer.length > route.length) return false
|
|
158
|
+
for (let at = 0; at < outer.length; at += 1) {
|
|
159
|
+
const one = outer[at] as Segment
|
|
160
|
+
const two = route[at] as Segment
|
|
161
|
+
if (one.kind !== two.kind) return false
|
|
162
|
+
if (one.kind === 'static' && one.name !== two.name) return false
|
|
163
|
+
}
|
|
164
|
+
return true
|
|
165
|
+
}
|
|
166
|
+
|
|
131
167
|
/**
|
|
132
168
|
* A route table compiled for matching: each row with its pattern parsed, most specific first.
|
|
133
169
|
*
|
package/src/shared/rpc.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// A HANDLER IS A `reactive` OR BECOMES ONE (16.1), so an `Rpc` is not a second kind of value: the
|
|
7
7
|
// call hands back the handler's own `Reactive` (16.3), and every probe, trigger and failure on it is
|
|
8
8
|
// the reactive core's — the `Rpc`'s own `busy`, `ready`, `refresh` and `invalidate` are the
|
|
9
|
-
// handler's, passed through. What this file adds is the
|
|
9
|
+
// handler's, passed through. What this file adds is the normalizing — a plain function or a value becomes the
|
|
10
10
|
// `reactive` it would have been (16.6) — and the method.
|
|
11
11
|
//
|
|
12
12
|
// A HANDLER DECLARING `middleware` IS REACHED THROUGH ITS RUNGS (16.11), in process as over the wire:
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// handler's own.
|
|
15
15
|
|
|
16
16
|
import { type Failed, isFailedNamed } from './Failed.ts'
|
|
17
|
+
import { filledSegments } from './filledPath.ts'
|
|
17
18
|
import { GLOBAL_GETS } from './GLOBAL_GETS.ts'
|
|
18
19
|
import { compose, type Middleware } from './Middleware.ts'
|
|
19
20
|
import { principalRotated } from './principalRotated.ts'
|
|
@@ -23,7 +24,7 @@ import {
|
|
|
23
24
|
type Gated,
|
|
24
25
|
gateOf,
|
|
25
26
|
keyOf,
|
|
26
|
-
|
|
27
|
+
normalized,
|
|
27
28
|
} from './reactive/argsContract.ts'
|
|
28
29
|
import { callLoad } from './reactive/callLoad.ts'
|
|
29
30
|
import {
|
|
@@ -48,7 +49,8 @@ import { MUTATION } from './reactive/MUTATION.ts'
|
|
|
48
49
|
import { SELECTION_MEMBERS } from './reactive/SELECTION_MEMBERS.ts'
|
|
49
50
|
import { isForwarder } from './reactive/scoped.ts'
|
|
50
51
|
import { checkArgs } from './refuse.ts'
|
|
51
|
-
import {
|
|
52
|
+
import { parseRoutePattern, type Segment } from './routePattern.ts'
|
|
53
|
+
import { pathRefusal, rpcRequest } from './rpcRequest.ts'
|
|
52
54
|
import type { Schema } from './Schema.ts'
|
|
53
55
|
|
|
54
56
|
export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
|
@@ -131,7 +133,7 @@ export type RpcContext<Args = unknown> = { request: Request; args: () => Args }
|
|
|
131
133
|
* The context key holding the args as sent.
|
|
132
134
|
*
|
|
133
135
|
* 11.26 — THE ARGS AS SENT, carried on the context under a key no rung spells, so the reach at the
|
|
134
|
-
* onion's
|
|
136
|
+
* onion's center keys the value by them rather than by what the gate made of them.
|
|
135
137
|
*/
|
|
136
138
|
const SENT: unique symbol = Symbol('abide.sent')
|
|
137
139
|
|
|
@@ -331,10 +333,10 @@ export function declare(method: Method): Declare {
|
|
|
331
333
|
args: argsOnce(gateOf(contract), args),
|
|
332
334
|
[SENT]: args,
|
|
333
335
|
}) as RpcContext
|
|
334
|
-
// 16.32 BEHIND RUNGS — A STREAM'S CHUNKS ARE FORWARDED, not collected. The
|
|
336
|
+
// 16.32 BEHIND RUNGS — A STREAM'S CHUNKS ARE FORWARDED, not collected. The center of the
|
|
335
337
|
// onion is an async function, and a `Reactive` is thenable (1.4), so handing a streaming value
|
|
336
338
|
// out through it would wait out the whole run and answer the accumulation once it closed. So
|
|
337
|
-
// the
|
|
339
|
+
// the center waits only until the value settles or turns out to stream (9.6), and hands a
|
|
338
340
|
// stream on as its chunks — a generator, which is not thenable. The value the call reaches
|
|
339
341
|
// resolves to it and is a stream of its own from then (9.6), so a second call joining it
|
|
340
342
|
// (1.8) reads the same accumulation. A refusal a rung answers with is the value's refusal.
|
|
@@ -369,8 +371,35 @@ export function declare(method: Method): Declare {
|
|
|
369
371
|
// this handler and these args is then answered from what this one reached, rather than by
|
|
370
372
|
// running the handler again. In a browser nothing fills the slot, so the report is one load
|
|
371
373
|
// of a `null`.
|
|
374
|
+
//
|
|
375
|
+
// 15.8 — AND REFUSED AHEAD OF THE HANDLER where the args leave a parameter of its route no
|
|
376
|
+
// spelling (30.3, 30.19), as the wire refuses them: a render and the browser's call then
|
|
377
|
+
// reach the same refusal, and there is no request for the render to report. The route is
|
|
378
|
+
// parsed once per address, which is stamped after the declaration (16.10).
|
|
379
|
+
let routed: string | undefined
|
|
380
|
+
let routeSegments: Segment[] | null = null
|
|
372
381
|
const call = (given?: unknown, options?: CallOptions): unknown => {
|
|
373
|
-
const args =
|
|
382
|
+
const args = normalized(contract, given)
|
|
383
|
+
const address = rpc.address
|
|
384
|
+
if (address !== routed) {
|
|
385
|
+
routed = address
|
|
386
|
+
routeSegments =
|
|
387
|
+
address?.includes('[') === true
|
|
388
|
+
? parseRoutePattern(address)
|
|
389
|
+
: null
|
|
390
|
+
}
|
|
391
|
+
if (routeSegments !== null) {
|
|
392
|
+
try {
|
|
393
|
+
filledSegments(
|
|
394
|
+
routeSegments,
|
|
395
|
+
(args ?? {}) as Record<string, unknown>,
|
|
396
|
+
routed as string,
|
|
397
|
+
pathRefusal,
|
|
398
|
+
)
|
|
399
|
+
} catch (refused) {
|
|
400
|
+
return reactive(() => refused)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
374
403
|
const reached = callLoad(
|
|
375
404
|
reachThrough,
|
|
376
405
|
args,
|
|
@@ -446,12 +475,17 @@ export function declare(method: Method): Declare {
|
|
|
446
475
|
// value produced, and holds no way back to it. A keyed value is looked up without a reach,
|
|
447
476
|
// so asking starts nothing (11.55).
|
|
448
477
|
if (method === 'GET' && !isForwarder(value))
|
|
449
|
-
GLOBAL_GETS.set(
|
|
478
|
+
GLOBAL_GETS.set(
|
|
479
|
+
rpc,
|
|
450
480
|
keyed
|
|
451
|
-
?
|
|
452
|
-
keyOf(contract, args),
|
|
453
|
-
|
|
454
|
-
|
|
481
|
+
? {
|
|
482
|
+
key: (args) => keyOf(contract, args),
|
|
483
|
+
at: (key) =>
|
|
484
|
+
(
|
|
485
|
+
value as { entries: Map<string, unknown> }
|
|
486
|
+
).entries.get(key),
|
|
487
|
+
}
|
|
488
|
+
: { key: () => '', at: () => value },
|
|
455
489
|
)
|
|
456
490
|
return rpc
|
|
457
491
|
}) as Declare
|
|
@@ -467,7 +501,7 @@ export const DELETE: Declare = declare('DELETE')
|
|
|
467
501
|
function addressOf(rpc: { address: string | undefined }): string {
|
|
468
502
|
if (rpc.address === undefined)
|
|
469
503
|
throw new Error(
|
|
470
|
-
'abide: this handler has no address yet. A handler is given one where it is mounted,
|
|
504
|
+
'abide: this handler has no address yet. A handler is given one where it is mounted, exported from a <script server> (16.2).',
|
|
471
505
|
)
|
|
472
506
|
return rpc.address
|
|
473
507
|
}
|
package/src/shared/rpcRequest.ts
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
// that agreement: method, URL and body, which is what makes two requests the same one.
|
|
5
5
|
//
|
|
6
6
|
// A `GET` or `DELETE` carries its args in the URL, in `queryText`'s spelling, and a mutation in its
|
|
7
|
-
// body; 19.7 is where text becomes the declared type again.
|
|
7
|
+
// body; 19.7 is where text becomes the declared type again. 16.9 — whatever the method, a parameter
|
|
8
|
+
// of the handler's route rides in the path, and the server puts it back among the args (16.65).
|
|
8
9
|
|
|
10
|
+
import { filledPath, parametersOf } from './filledPath.ts'
|
|
9
11
|
import { queryText } from './queryText.ts'
|
|
12
|
+
import { validationError } from './refuse.ts'
|
|
10
13
|
import type { Method } from './rpc.ts'
|
|
11
14
|
|
|
12
15
|
export type RpcRequest = {
|
|
@@ -32,10 +35,26 @@ export function argsInURL(method: Method): boolean {
|
|
|
32
35
|
export function rpcRequest(
|
|
33
36
|
method: Method,
|
|
34
37
|
address: string,
|
|
35
|
-
|
|
38
|
+
given: Record<string, unknown> | undefined,
|
|
36
39
|
): RpcRequest {
|
|
40
|
+
let args = given
|
|
37
41
|
let url = address
|
|
38
42
|
let body: string | undefined
|
|
43
|
+
// A route with parameters: they are written into the path and leave the args, so the query or
|
|
44
|
+
// body never carries one twice (16.66). 15.8 — one no address carries (30.3, 30.19) is refused as
|
|
45
|
+
// args a gate refused, so every caller answers it as the wire answers a 422.
|
|
46
|
+
if (address.includes('[')) {
|
|
47
|
+
const params = given ?? {}
|
|
48
|
+
url = filledPath(address, params, address, pathRefusal)
|
|
49
|
+
const inPath = parametersOf(address)
|
|
50
|
+
let rest: Record<string, unknown> | undefined
|
|
51
|
+
for (const name in params) {
|
|
52
|
+
if (inPath.has(name)) continue
|
|
53
|
+
rest ??= {}
|
|
54
|
+
rest[name] = params[name]
|
|
55
|
+
}
|
|
56
|
+
args = rest
|
|
57
|
+
}
|
|
39
58
|
if (argsInURL(method)) {
|
|
40
59
|
if (args !== undefined) {
|
|
41
60
|
let text = queryText(args)
|
|
@@ -47,7 +66,7 @@ export function rpcRequest(
|
|
|
47
66
|
const bare = bareName(name)
|
|
48
67
|
text = text === '' ? bare : `${text}&${bare}`
|
|
49
68
|
}
|
|
50
|
-
if (text !== '') url = `${
|
|
69
|
+
if (text !== '') url = `${url}?${text}`
|
|
51
70
|
}
|
|
52
71
|
} else if (args !== undefined) body = JSON.stringify(args)
|
|
53
72
|
// 18.14 — A COPY IS ITS OWN CACHE and keeps its own `max-age`. What it asks the server is asked
|
|
@@ -67,6 +86,11 @@ export function rpcRequest(
|
|
|
67
86
|
return { method, url, body, init, key: `${method} ${url} ${body ?? ''}` }
|
|
68
87
|
}
|
|
69
88
|
|
|
89
|
+
/** 15.8 — a parameter no address carries, refused as args a gate refused. */
|
|
90
|
+
export function pathRefusal(name: string, message: string): unknown {
|
|
91
|
+
return validationError<Record<string, unknown>>({ [name]: [message] })
|
|
92
|
+
}
|
|
93
|
+
|
|
70
94
|
/** A query member's name in the encoding `URLSearchParams` gives it, without its `=`. */
|
|
71
95
|
export function bareName(name: string): string {
|
|
72
96
|
return new URLSearchParams([[name, '']]).toString().slice(0, -1)
|
package/src/shared/url.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
//
|
|
5
5
|
// 16.10 — RESOLVED AGAINST THE MOUNT at the call, so the same build serves at the root and below it.
|
|
6
6
|
|
|
7
|
+
import { filledPath } from './filledPath.ts'
|
|
7
8
|
import { mounted } from './mount.ts'
|
|
8
9
|
import { queryText } from './queryText.ts'
|
|
9
|
-
import { parseRoutePattern } from './routePattern.ts'
|
|
10
10
|
|
|
11
11
|
type Parts<P extends string> = P extends `${infer Head}/${infer Tail}`
|
|
12
12
|
? Head | Parts<Tail>
|
|
@@ -86,29 +86,9 @@ export function url<P extends RouteLiteral>(
|
|
|
86
86
|
): string {
|
|
87
87
|
const params = (rest[0] ?? {}) as Record<string, unknown>
|
|
88
88
|
const query = rest[1]
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
address += `/${segment.name}`
|
|
93
|
-
continue
|
|
94
|
-
}
|
|
95
|
-
const value = params[segment.name]
|
|
96
|
-
if (value === undefined || value === null || value === '') {
|
|
97
|
-
// 30.4 — an optional left out omits its segment, and so does an empty rest (30.5).
|
|
98
|
-
if (segment.kind !== 'required') continue
|
|
99
|
-
throw new Error(
|
|
100
|
-
`abide: url('${pattern}') was given no '${segment.name}', and the route requires it. RULEBOOK 30.3.`,
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
if (segment.kind === 'rest' && Array.isArray(value)) {
|
|
104
|
-
for (const each of value)
|
|
105
|
-
address += `/${encodedSegment(pattern, segment.name, each)}`
|
|
106
|
-
continue
|
|
107
|
-
}
|
|
108
|
-
address += `/${encodedSegment(pattern, segment.name, value)}`
|
|
109
|
-
}
|
|
110
|
-
if (address === '') address = '/'
|
|
111
|
-
address = mounted(address)
|
|
89
|
+
const address = mounted(
|
|
90
|
+
filledPath(pattern ?? '/', params, `url('${pattern}')`),
|
|
91
|
+
)
|
|
112
92
|
// 30.6 — the query half, last, in the spelling `rpcRequest` writes.
|
|
113
93
|
const search =
|
|
114
94
|
query === undefined
|
|
@@ -118,23 +98,3 @@ export function url<P extends RouteLiteral>(
|
|
|
118
98
|
: queryText(query)
|
|
119
99
|
return search === '' ? address : `${address}?${search}`
|
|
120
100
|
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* A param value encoded as one path segment; `.` and `..` are refused.
|
|
124
|
-
*
|
|
125
|
-
* 30.19 — A VALUE OF `.` OR `..` HAS NO SPELLING AS A SEGMENT: the URL parser collapses it as a
|
|
126
|
-
* dot-segment, and `%2E` / `%2E%2E` with it, so the address would land on another route. Refused
|
|
127
|
-
* rather than written, as a missing required segment is (30.3).
|
|
128
|
-
*/
|
|
129
|
-
function encodedSegment(
|
|
130
|
-
pattern: string | undefined,
|
|
131
|
-
name: string,
|
|
132
|
-
value: unknown,
|
|
133
|
-
): string {
|
|
134
|
-
const text = String(value)
|
|
135
|
-
if (text === '.' || text === '..')
|
|
136
|
-
throw new Error(
|
|
137
|
-
`abide: url('${pattern}') was given '${text}' for '${name}', and no address carries it as a segment — the URL parser resolves it to another route. RULEBOOK 30.19.`,
|
|
138
|
-
)
|
|
139
|
-
return encodeURIComponent(text)
|
|
140
|
-
}
|
package/src/shared/writePath.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { putOwn } from './putOwn.ts'
|
|
|
4
4
|
* A copy of `value` with `next` written at `path`, each level down the path copied.
|
|
5
5
|
*
|
|
6
6
|
* 31.8 — a write through a member path COPIES DOWN THE PATH AND THEN WRITES, and the copy is not
|
|
7
|
-
*
|
|
7
|
+
* optimized away. The copy is the whole point: a `Reactive` wakes its readers on IDENTITY, so
|
|
8
8
|
* `user.name = 'x'` done as a mutation of the held object writes the right value and wakes nobody
|
|
9
9
|
* — the page renders correctly on load and then never moves again, with nothing about it saying
|
|
10
10
|
* so.
|
package/src/ui/LAYER_NAMES.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// code that EMITS the measure to the spec that reads it, and a string typed twice is a
|
|
3
3
|
// `shares()` call that throws "found no measure named" against a layer that ran.
|
|
4
4
|
//
|
|
5
|
-
// `.` RATHER THAN `:`. `abide:hydrate` and `abide:navigate` are WARNING channels (28.
|
|
5
|
+
// `.` RATHER THAN `:`. `abide:hydrate` and `abide:navigate` are WARNING channels (28.8) and these
|
|
6
6
|
// are performance entries — two namespaces that would otherwise be one grep.
|
|
7
7
|
//
|
|
8
8
|
// ONE NAME PER OP SOMEBODY WAITS ON, never per row. `shares()` sums every measure of a layer
|
package/src/ui/List.ts
CHANGED
|
@@ -18,7 +18,7 @@ export class List {
|
|
|
18
18
|
region: Region
|
|
19
19
|
rows: Row[]
|
|
20
20
|
/**
|
|
21
|
-
* The async iterable last started, or null — so a
|
|
21
|
+
* The async iterable last started, or null — so a rerun with the same iterable is not a new
|
|
22
22
|
* source.
|
|
23
23
|
*/
|
|
24
24
|
pumping: unknown
|
|
@@ -209,8 +209,8 @@ function settleList(
|
|
|
209
209
|
* async subject writes no rows inline and the region arrives empty. The pump fills it.
|
|
210
210
|
*/
|
|
211
211
|
function appendRows(list: List, source: AsyncIterable<unknown>): void {
|
|
212
|
-
// A
|
|
213
|
-
// the expression touches
|
|
212
|
+
// A RERUN IS NOT A NEW SOURCE. The subject is read inside a watch, so any other reactive
|
|
213
|
+
// the expression touches reruns it with the SAME iterable — and emptying the list to pump an
|
|
214
214
|
// async generator that has already run to completion left the region blank for good.
|
|
215
215
|
if (list.pumping === source) return
|
|
216
216
|
// A NEW SOURCE IS A NEW LIST. What the last one appended is stopped and removed, or its
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// 39.1, 39.6 — THE ROUTER'S LEVEL BEING MOUNTED, set by the router around a page's or a layout's
|
|
2
|
+
// setup and markup: a component mounted inside one adds its `<head>` to that level's
|
|
3
|
+
// (`contributeHead`), and one mounted later — a row a list grew by — finds none and leaves the
|
|
4
|
+
// document's head alone. `unapplied` is the head the components built on the client contributed
|
|
5
|
+
// while it was set, which the router applies once it is unset rather than once a component.
|
|
6
|
+
export const MOUNTING_LEVEL: {
|
|
7
|
+
current:
|
|
8
|
+
| {
|
|
9
|
+
head: (() => string) | undefined
|
|
10
|
+
/** Every component head joined to the level's own, in the order they mounted. */
|
|
11
|
+
contributed: (() => string)[] | undefined
|
|
12
|
+
}
|
|
13
|
+
| undefined
|
|
14
|
+
unapplied: string
|
|
15
|
+
} = {
|
|
16
|
+
current: undefined,
|
|
17
|
+
unapplied: '',
|
|
18
|
+
}
|
package/src/ui/Mismatch.ts
CHANGED
package/src/ui/RawRun.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { removeUntil } from './regions.ts'
|
|
|
11
11
|
*
|
|
12
12
|
* `html` IS UNDEFINED FOR A RUN THE SERVER PAINTED. What is between the anchors is what the server
|
|
13
13
|
* parsed from the same value, so the first write keeps it (`setMarkup`) the way a hydrated arm keeps
|
|
14
|
-
* the arm it was painted with — reading markup back out of nodes is a
|
|
14
|
+
* the arm it was painted with — reading markup back out of nodes is a serialization, and not the
|
|
15
15
|
* string the server wrote.
|
|
16
16
|
*/
|
|
17
17
|
export class RawRun {
|