@abide/abide 0.117.2 → 0.119.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 +22 -0
- package/bin/abide.ts +74 -11
- package/docs/REGISTRY.md +18 -15
- package/docs/RULEBOOK.md +71 -10
- package/docs/abide.md +584 -274
- package/package.json +1 -1
- package/scaffold/AGENTS.md +1 -1
- package/scaffold/src/getGreeting.test.ts +2 -1
- package/scaffold/src/index.abide +5 -2
- package/scaffold/src/index.test.ts +2 -0
- package/scaffold/src/layout.abide +6 -6
- package/src/compiler/Lowering.ts +20 -2
- package/src/compiler/PositionTable.ts +1 -0
- package/src/compiler/Refusals.ts +1 -0
- package/src/compiler/TypeSession.ts +49 -13
- package/src/compiler/aliasedSymbol.ts +1 -1
- package/src/compiler/ambientReadsIn.ts +1 -0
- package/src/compiler/appLayout.ts +55 -3
- package/src/compiler/appMap.ts +15 -0
- package/src/compiler/argsDefaultsIn.ts +1 -0
- package/src/compiler/argsSchemaOf.ts +1 -0
- package/src/compiler/checkApp.ts +3 -1
- package/src/compiler/checkedShadow.ts +2 -1
- package/src/compiler/classifyRead.ts +1 -0
- package/src/compiler/classifyTemplate.ts +1 -0
- package/src/compiler/compileApp.ts +5 -3
- package/src/compiler/compiledEntry.ts +2 -1
- package/src/compiler/componentGraph.ts +1 -0
- package/src/compiler/declaredExports.ts +4 -2
- package/src/compiler/emitClient.ts +19 -14
- package/src/compiler/emitComponent.ts +1 -0
- package/src/compiler/emitSource.ts +2 -1
- package/src/compiler/formatAbide.ts +857 -0
- package/src/compiler/formatDiagnosis.ts +1 -0
- package/src/compiler/handlersUnder.ts +7 -3
- package/src/compiler/hookCalls.ts +1 -0
- package/src/compiler/lineOf.ts +17 -0
- package/src/compiler/matchersOf.ts +157 -0
- package/src/compiler/mountsOf.ts +5 -0
- package/src/compiler/paramFile.ts +125 -0
- package/src/compiler/parseAbide.ts +48 -28
- package/src/compiler/projectShadow.ts +11 -4
- package/src/compiler/reactiveOptions.ts +1 -0
- package/src/compiler/reactiveTypes.ts +2 -1
- package/src/compiler/readsIn.ts +3 -2
- package/src/compiler/renderMarkup.ts +3 -2
- package/src/compiler/resolverIn.ts +2 -0
- package/src/compiler/sameTemplate.ts +150 -0
- package/src/compiler/scaffold.ts +4 -11
- package/src/compiler/serveLanguage.ts +48 -21
- package/src/compiler/sharedModuleWrites.ts +2 -1
- package/src/compiler/sourceMapOf.ts +1 -0
- package/src/compiler/splitImports.ts +2 -0
- package/src/compiler/templateWarnings.ts +2 -2
- package/src/compiler/trackingWarnings.ts +1 -0
- package/src/compiler/writeDefaultProject.ts +5 -1
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/abideModules.ts +2 -0
- package/src/server/answerFailure.ts +1 -0
- package/src/server/answerRequest.ts +57 -19
- package/src/server/answerRpc.ts +6 -2
- package/src/server/answerSocket.ts +2 -1
- package/src/server/answerValue.ts +1 -0
- package/src/server/answerWorker.ts +1 -0
- package/src/server/appTree.ts +18 -9
- package/src/server/argsFor.ts +1 -0
- package/src/server/assistant.ts +2 -1
- package/src/server/banner.ts +2 -0
- package/src/server/bridgeRelay.ts +1 -0
- package/src/server/callerOf.ts +1 -0
- package/src/server/claudeApi.ts +1 -0
- package/src/server/claudeCode.ts +1 -0
- package/src/server/clientBundle.ts +1 -0
- package/src/server/coerceArgs.ts +1 -2
- package/src/server/commands.ts +9 -0
- package/src/server/compressAnswer.ts +2 -1
- package/src/server/config.ts +2 -0
- package/src/server/createApp.ts +4 -2
- package/src/server/crossingOrigin.ts +1 -0
- package/src/server/csp.ts +1 -0
- package/src/server/deferredBuilds.ts +1 -0
- package/src/server/derivedArgs.ts +1 -0
- package/src/server/devWorker.ts +2 -1
- package/src/server/faceOf.ts +2 -7
- package/src/server/generatedSurfaces.ts +79 -7
- package/src/server/hooks.ts +1 -0
- package/src/server/iconAccent.ts +1 -1
- package/src/server/installServer.ts +1 -0
- package/src/server/installSourceMaps.ts +1 -0
- package/src/server/launcherPage.ts +2 -2
- package/src/server/manifestOf.ts +1 -0
- package/src/server/muxUpgrade.ts +1 -0
- package/src/server/nativeMenu.ts +2 -0
- package/src/server/placement.ts +12 -2
- package/src/server/principal.ts +2 -1
- package/src/server/remote.ts +7 -2
- package/src/server/render.ts +3 -3
- package/src/server/renderComponent.ts +1 -0
- package/src/server/reportHealth.ts +1 -0
- package/src/server/requestScope.ts +3 -1
- package/src/server/runDesktop.ts +3 -2
- package/src/server/seedBuffer.ts +1 -0
- package/src/server/serveDev.ts +3 -2
- package/src/server/shell.ts +2 -0
- package/src/server/streamEvent.ts +1 -0
- package/src/server/surfaceOf.ts +6 -1
- package/src/server/testBuild.ts +1 -0
- package/src/server/testMount.ts +1 -0
- package/src/server/trace.ts +2 -0
- package/src/server/withAbideSources.ts +1 -0
- package/src/shared/Attributes.ts +12 -8
- package/src/shared/cookies.ts +1 -1
- package/src/shared/escapeText.ts +1 -1
- package/src/shared/filledPath.ts +16 -10
- package/src/shared/index.ts +1 -5
- package/src/shared/jsonSchema.ts +1 -3
- package/src/shared/matchRoute.ts +16 -4
- package/src/shared/menuBridge.ts +1 -2
- package/src/shared/outbox.ts +1 -1
- package/src/shared/reactive/Keyed.ts +2 -2
- package/src/shared/reactive/Reactive.ts +12 -16
- package/src/shared/reactive/index.ts +3 -4
- package/src/shared/reactive/isReactive.ts +1 -1
- package/src/shared/reactive/scoped.ts +2 -2
- package/src/shared/reactive/share.ts +1 -1
- package/src/shared/reactive/watch.ts +4 -4
- package/src/shared/refuse.ts +1 -1
- package/src/shared/routePattern.ts +66 -14
- package/src/shared/rpc.ts +8 -11
- package/src/shared/url.ts +24 -7
- package/src/shared/validateJson.ts +1 -1
- package/src/ui/ArmRegion.ts +1 -0
- package/src/ui/Level.ts +1 -0
- package/src/ui/List.ts +1 -0
- package/src/ui/RawRun.ts +1 -0
- package/src/ui/Router.ts +1 -0
- package/src/ui/TryRegion.ts +1 -0
- package/src/ui/adoptWalk.ts +2 -1
- package/src/ui/bindElement.ts +1 -0
- package/src/ui/bindFiles.ts +1 -0
- package/src/ui/bindGroup.ts +1 -0
- package/src/ui/bindProperty.ts +1 -0
- package/src/ui/bindSpread.ts +1 -0
- package/src/ui/index.ts +1 -6
- package/src/ui/outboxQueue.ts +1 -1
- package/src/ui/parseMarkup.ts +2 -1
- package/src/ui/reconcile.ts +2 -1
- package/src/ui/reconcileHead.ts +1 -0
- package/src/ui/regions.ts +2 -1
- package/src/ui/registerWorker.ts +1 -0
- package/src/ui/rows.ts +1 -0
- package/src/ui/setMarkup.ts +1 -0
- package/src/ui/setText.ts +1 -0
- package/src/ui/socketWire.ts +4 -3
- package/src/ui/wire.ts +2 -1
- /package/scaffold/src/{getGreeting.ts → getGreeting.handler.ts} +0 -0
package/src/server/remote.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { PatchOp } from '#shared/reactive/PATCH_SINKS.ts'
|
|
|
11
11
|
import { rankRoutes } from '#shared/routePattern.ts'
|
|
12
12
|
import type { Method } from '#shared/rpc.ts'
|
|
13
13
|
import { argsInURL, rpcRequest } from '#shared/rpcRequest.ts'
|
|
14
|
+
|
|
14
15
|
import type { Mount } from './answerRpc.ts'
|
|
15
16
|
import { DEFAULT_PORT } from './DEFAULT_PORT.ts'
|
|
16
17
|
import { withoutTrailingSlash } from './withoutTrailingSlash.ts'
|
|
@@ -115,11 +116,15 @@ function handlerAt(
|
|
|
115
116
|
address: string,
|
|
116
117
|
method: string | undefined,
|
|
117
118
|
): Mount | undefined {
|
|
118
|
-
const rows: {
|
|
119
|
+
const rows: {
|
|
120
|
+
pattern: string
|
|
121
|
+
matchers: Record<string, string>
|
|
122
|
+
mount: Mount
|
|
123
|
+
}[] = []
|
|
119
124
|
for (const mount of handlers) {
|
|
120
125
|
if (method !== undefined && mount.method !== method) continue
|
|
121
126
|
if (mount.address === address) return mount
|
|
122
|
-
rows.push({ pattern: mount.address, mount })
|
|
127
|
+
rows.push({ pattern: mount.address, matchers: mount.matchers, mount })
|
|
123
128
|
}
|
|
124
129
|
return firstMatch(rankRoutes(rows), partsOf(address) ?? [])?.route.mount
|
|
125
130
|
}
|
package/src/server/render.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { headKeyOf } from '#shared/headKeyOf.ts'
|
|
|
9
9
|
import { isThenable } from '#shared/reactive/isThenable.ts'
|
|
10
10
|
import type { ShareScope } from '#shared/reactive/share.ts'
|
|
11
11
|
import { setUpLevels } from '#shared/setUpLevels.ts'
|
|
12
|
+
|
|
12
13
|
import { buildDeferred, markPainted } from './deferredBuilds.ts'
|
|
13
14
|
import { currentRequestScope, type PageCall } from './requestScope.ts'
|
|
14
15
|
|
|
@@ -23,7 +24,7 @@ import { currentRequestScope, type PageCall } from './requestScope.ts'
|
|
|
23
24
|
* satisfy the linter would put the code and the registry row out of step, which is the drift
|
|
24
25
|
* the documents exist to stop.
|
|
25
26
|
*/
|
|
26
|
-
//
|
|
27
|
+
// REGISTRY declares `interface Component {}`.
|
|
27
28
|
export interface Component {}
|
|
28
29
|
|
|
29
30
|
/**
|
|
@@ -364,8 +365,7 @@ export async function renderDocument(
|
|
|
364
365
|
*/
|
|
365
366
|
function earlyCallScript(calls: PageCall[]): string {
|
|
366
367
|
const rows: (
|
|
367
|
-
| [string, string, RequestInit]
|
|
368
|
-
| [string, string, RequestInit, 1]
|
|
368
|
+
[string, string, RequestInit] | [string, string, RequestInit, 1]
|
|
369
369
|
)[] = []
|
|
370
370
|
for (const { request, painted } of calls)
|
|
371
371
|
rows.push(
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { Health } from '#shared/health.ts'
|
|
8
8
|
import { isRecord } from '#shared/isRecord.ts'
|
|
9
9
|
import { namedLogger } from '#shared/log.ts'
|
|
10
|
+
|
|
10
11
|
import abidePackage from '../../package.json' with { type: 'json' }
|
|
11
12
|
import { appIdentity, config } from './config.ts'
|
|
12
13
|
import { HOOKS, inOrder } from './hooks.ts'
|
|
@@ -10,12 +10,14 @@
|
|
|
10
10
|
// values forwarders rather than process-wide.
|
|
11
11
|
|
|
12
12
|
import { AsyncLocalStorage } from 'node:async_hooks' // bun implements AsyncLocalStorage and has no context-carrying api of its own
|
|
13
|
+
|
|
13
14
|
import { Cookies } from '#shared/cookies.ts'
|
|
14
15
|
import type { Framed } from '#shared/framing.ts'
|
|
15
16
|
import type { Params } from '#shared/matchRoute.ts'
|
|
16
17
|
import type { Principal } from '#shared/principal.ts'
|
|
17
|
-
import {
|
|
18
|
+
import { type Scope, SCOPING } from '#shared/reactive/scoping.ts'
|
|
18
19
|
import type { RpcRequest } from '#shared/rpcRequest.ts'
|
|
20
|
+
|
|
19
21
|
import type { McpSurface } from './generatedSurfaces.ts'
|
|
20
22
|
import { IDLE_TIMEOUT_SECONDS } from './IDLE_TIMEOUT_SECONDS.ts'
|
|
21
23
|
|
package/src/server/runDesktop.ts
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
import { FFIType } from 'bun:ffi'
|
|
10
10
|
import { mkdir } from 'node:fs/promises' // bun exposes no directory creation of its own
|
|
11
|
+
|
|
11
12
|
import { isFailed } from '#shared/Failed.ts'
|
|
13
|
+
|
|
12
14
|
import {
|
|
13
15
|
callBlock,
|
|
14
16
|
created,
|
|
@@ -54,8 +56,7 @@ type Failure = {
|
|
|
54
56
|
export type Desktop = {
|
|
55
57
|
/** 38.82 — `src/launcher.abide`'s page, where the app has one that paints. */
|
|
56
58
|
launch:
|
|
57
|
-
|
|
58
|
-
| undefined
|
|
59
|
+
(() => Promise<{ default: (props: Launch) => Component }>) | undefined
|
|
59
60
|
/** The document the launch page renders into, its stylesheet already in its head (38.84). */
|
|
60
61
|
document: string
|
|
61
62
|
/** 38.101 — the app's icon as a data URL, and the accent taken from it. */
|
package/src/server/seedBuffer.ts
CHANGED
package/src/server/serveDev.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import { watch } from 'node:fs' // bun exposes no file watcher of its own
|
|
22
22
|
import { dirname, resolve } from 'node:path' // bun exposes no path helpers of its own
|
|
23
|
+
|
|
23
24
|
import {
|
|
24
25
|
type AppChecker,
|
|
25
26
|
compilationOf,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
} from '#compiler/compileApp.ts'
|
|
29
30
|
import { formatDiagnosis } from '#compiler/formatDiagnosis.ts'
|
|
30
31
|
import { DEV_ADDRESS } from '#shared/ADDRESSES.ts'
|
|
32
|
+
|
|
31
33
|
import type { Compilation } from './abideModules.ts'
|
|
32
34
|
import { DEFAULT_PORT } from './DEFAULT_PORT.ts'
|
|
33
35
|
import type { FromWorker, ToWorker } from './devWorker.ts'
|
|
@@ -66,8 +68,7 @@ const LAST_DEV_PORT = 3099
|
|
|
66
68
|
// An app-mux socket's data: the id the worker knows its connection by, and what `Mux.connect` reads
|
|
67
69
|
// off the upgrade. `undefined` on the reload client's.
|
|
68
70
|
type Carried =
|
|
69
|
-
|
|
70
|
-
| undefined
|
|
71
|
+
{ connection: number; url: string; headers: [string, string][] } | undefined
|
|
71
72
|
|
|
72
73
|
/**
|
|
73
74
|
* `abide dev`'s listener, compiling the app here and serving it from a child process.
|
package/src/server/shell.ts
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
// is the only part that needs a terminal. A call goes over http through `remote.ts`, as any client's.
|
|
5
5
|
|
|
6
6
|
import { createInterface } from 'node:readline' // bun exposes no line editor of its own
|
|
7
|
+
|
|
7
8
|
import { isRecord } from '#shared/isRecord.ts'
|
|
8
9
|
import { useColor } from '#shared/log.ts'
|
|
9
10
|
import { paint as painted } from '#shared/paint.ts'
|
|
11
|
+
|
|
10
12
|
import type { Mount } from './answerRpc.ts'
|
|
11
13
|
import { LogPanel } from './LogPanel.ts'
|
|
12
14
|
import { callHandler, watch } from './remote.ts'
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { Problem } from '#shared/Failed.ts'
|
|
6
6
|
import type { Framed } from '#shared/framing.ts'
|
|
7
7
|
import { REFUSAL_KEY } from '#shared/REFUSAL_KEY.ts'
|
|
8
|
+
|
|
8
9
|
import { HEARTBEAT_MILLISECONDS } from './HEARTBEAT_MILLISECONDS.ts'
|
|
9
10
|
|
|
10
11
|
/** AN EVENT STREAM OPENS ON A COMMENT, so a proxy has bytes to pass before the first event. */
|
package/src/server/surfaceOf.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Method } from '#shared/rpc.ts'
|
|
2
|
+
|
|
2
3
|
import type { Mount, Mounted } from './answerRpc.ts'
|
|
3
4
|
|
|
4
5
|
/** A generated surface a handler may be listed on, besides the browser's (20.4 is the build's). */
|
|
@@ -17,6 +18,8 @@ export type Listed = {
|
|
|
17
18
|
schema: unknown
|
|
18
19
|
/** What it answers, as JSON Schema, where its type says. 19.21. */
|
|
19
20
|
result: unknown
|
|
21
|
+
/** 23.26 — each matcher its address names, to its anchored source. */
|
|
22
|
+
matchers: Record<string, string>
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
/**
|
|
@@ -50,6 +53,7 @@ export function surfaceOf(
|
|
|
50
53
|
live === undefined ? mount.description : live.handler.description,
|
|
51
54
|
schema: live === undefined ? mount.args : live.argsDocument,
|
|
52
55
|
result: mount.result,
|
|
56
|
+
matchers: mount.matchers,
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
59
|
|
|
@@ -60,7 +64,8 @@ export function surfaceOf(
|
|
|
60
64
|
* and dots dropped, so `/media/[id]` reads `media_id` rather than `media__id_`.
|
|
61
65
|
*/
|
|
62
66
|
export function surfaceName(method: Method, route: string): string {
|
|
63
|
-
|
|
67
|
+
// 19.1 — a matcher is no part of what a route is, so none of its name: `=NAME` goes first.
|
|
68
|
+
return `${method.toLowerCase()}${route === '/' ? '' : route.replace(/=[^\]]*/g, '').replace(/[[\].]/g, '')}`.replace(
|
|
64
69
|
/[^a-zA-Z0-9-]/g,
|
|
65
70
|
'_',
|
|
66
71
|
)
|
package/src/server/testBuild.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { mkdir, rename, rm, utimes, writeFile } from 'node:fs/promises' // bun exposes no directory creation, rename, removal, mtime write or exclusive create of its own
|
|
8
8
|
import { dirname } from 'node:path' // bun exposes no path helpers of its own
|
|
9
|
+
|
|
9
10
|
import { namesUnder } from './namesUnder.ts'
|
|
10
11
|
import { runBuild } from './runBuild.ts'
|
|
11
12
|
|
package/src/server/testMount.ts
CHANGED
package/src/server/trace.ts
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
// (24.5) while a sibling running concurrently names its own.
|
|
8
8
|
|
|
9
9
|
import { AsyncLocalStorage } from 'node:async_hooks' // bun implements AsyncLocalStorage and has no context-carrying api of its own
|
|
10
|
+
|
|
10
11
|
import { writeSpan } from '#shared/log.ts'
|
|
12
|
+
|
|
11
13
|
import { randomToken } from './randomToken.ts'
|
|
12
14
|
import {
|
|
13
15
|
currentRequestScope,
|
package/src/shared/Attributes.ts
CHANGED
|
@@ -47,11 +47,13 @@ type Reflected<Element> = {
|
|
|
47
47
|
|
|
48
48
|
/** `Element`'s keys without its index signatures. */
|
|
49
49
|
type Named<Element> = keyof {
|
|
50
|
-
[
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
[
|
|
51
|
+
Key in keyof Element as string extends Key
|
|
52
|
+
? never
|
|
53
|
+
: number extends Key
|
|
54
|
+
? never
|
|
55
|
+
: Key
|
|
56
|
+
]: Element[Key]
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
/** The properties whose attribute is not their name lowercased. */
|
|
@@ -119,9 +121,11 @@ export type Attributes<
|
|
|
119
121
|
|
|
120
122
|
/** An HTML element's attributes, derived from what its DOM type reflects. */
|
|
121
123
|
type HtmlAttributes<Tag extends keyof HTMLElementTagNameMap> = {
|
|
122
|
-
[
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
[
|
|
125
|
+
Key in Reflected<
|
|
126
|
+
HTMLElementTagNameMap[Tag]
|
|
127
|
+
> as Key extends `on${string}` ? never : AttributeName<Key>
|
|
128
|
+
]?: Written<
|
|
125
129
|
AttributeName<Key>,
|
|
126
130
|
NonNullable<HTMLElementTagNameMap[Tag][Key]>
|
|
127
131
|
> | null
|
package/src/shared/cookies.ts
CHANGED
|
@@ -238,7 +238,7 @@ export function browserCookies(): Cookies {
|
|
|
238
238
|
const jar = new Cookies(
|
|
239
239
|
document.cookie,
|
|
240
240
|
(serialized) => {
|
|
241
|
-
//
|
|
241
|
+
// The Cookie Store API is asynchronous and missing from some browsers, and a read of the jar is synchronous.
|
|
242
242
|
document.cookie = serialized
|
|
243
243
|
},
|
|
244
244
|
true,
|
package/src/shared/escapeText.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// `escapeless` in #ui is that path, and the two sitting apart is what keeps them from being
|
|
5
5
|
// confused for one another.
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { Raw, RAW_WRITES, textOf } from './Raw.ts'
|
|
8
8
|
|
|
9
9
|
// The common slot holds nothing to escape, so the scan is the fast path and the rewrite is
|
|
10
10
|
// the fallback: a clean string is returned as it arrived, with no allocation.
|
package/src/shared/filledPath.ts
CHANGED
|
@@ -42,32 +42,38 @@ export function filledSegments(
|
|
|
42
42
|
}
|
|
43
43
|
if (segment.kind === 'rest' && Array.isArray(value)) {
|
|
44
44
|
for (const each of value)
|
|
45
|
-
address += `/${encodedSegment(spelled, segment
|
|
45
|
+
address += `/${encodedSegment(spelled, segment, each, refused)}`
|
|
46
46
|
continue
|
|
47
47
|
}
|
|
48
|
-
address += `/${encodedSegment(spelled, segment
|
|
48
|
+
address += `/${encodedSegment(spelled, segment, value, refused)}`
|
|
49
49
|
}
|
|
50
50
|
return address === '' ? '/' : address
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* A param value encoded as one path segment
|
|
54
|
+
* A param value encoded as one path segment, refused where no address can carry it.
|
|
55
55
|
*
|
|
56
|
-
* 30.19 — A VALUE
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* 30.19 — A VALUE NO SEGMENT TAKES HAS NO SPELLING: one carrying a period, where the segment names no
|
|
57
|
+
* matcher to lift the default (30.34), and `.` or `..` whatever it names (30.36), which the URL
|
|
58
|
+
* parser collapses as a dot-segment, `%2E` / `%2E%2E` with it. Refused rather than written, as a
|
|
59
|
+
* missing required segment is (30.3). A named matcher is not run here: the literal says a matcher
|
|
60
|
+
* is named, and only a route table holds the regex (23.26).
|
|
59
61
|
*/
|
|
60
62
|
function encodedSegment(
|
|
61
63
|
spelled: string,
|
|
62
|
-
|
|
64
|
+
segment: Segment,
|
|
63
65
|
value: unknown,
|
|
64
66
|
refused: (name: string, message: string) => unknown,
|
|
65
67
|
): string {
|
|
66
68
|
const text = String(value)
|
|
67
|
-
|
|
69
|
+
const carried =
|
|
70
|
+
segment.matcher === undefined
|
|
71
|
+
? !text.includes('.')
|
|
72
|
+
: text !== '.' && text !== '..'
|
|
73
|
+
if (!carried)
|
|
68
74
|
throw refused(
|
|
69
|
-
name,
|
|
70
|
-
`abide: ${spelled} was given '${text}' for '${name}', and no address carries
|
|
75
|
+
segment.name,
|
|
76
|
+
`abide: ${spelled} was given '${text}' for '${segment.name}', and no address carries ${segment.matcher === undefined ? 'a period in a param naming no matcher' : 'a dot-segment'}. RULEBOOK 30.19.`,
|
|
71
77
|
)
|
|
72
78
|
return encodeURIComponent(text)
|
|
73
79
|
}
|
package/src/shared/index.ts
CHANGED
|
@@ -23,11 +23,7 @@ export {
|
|
|
23
23
|
} from './boundValue.ts'
|
|
24
24
|
export { type Cookies, cookies } from './cookies.ts'
|
|
25
25
|
export { csp } from './csp.ts'
|
|
26
|
-
export {
|
|
27
|
-
escapeAttribute,
|
|
28
|
-
escapeStyleValue,
|
|
29
|
-
escapeText,
|
|
30
|
-
} from './escapeText.ts'
|
|
26
|
+
export { escapeAttribute, escapeStyleValue, escapeText } from './escapeText.ts'
|
|
31
27
|
export { type Failed, isFailed } from './Failed.ts'
|
|
32
28
|
// 25 — the account of the app.
|
|
33
29
|
export { type Health, health } from './health.ts'
|
package/src/shared/jsonSchema.ts
CHANGED
|
@@ -31,9 +31,7 @@ export function jsonSchema<T = Untyped>(
|
|
|
31
31
|
vendor: 'abide',
|
|
32
32
|
validate(value: unknown) {
|
|
33
33
|
const found = validateJson(document, value) as
|
|
34
|
-
|
|
35
|
-
| string[]
|
|
36
|
-
| null
|
|
34
|
+
Record<string, string[]> | string[] | null
|
|
37
35
|
if (found === null) return { value: value as T }
|
|
38
36
|
const issues: { message: string; path: string[] }[] = []
|
|
39
37
|
// A value that is not an object came back as the bare list, every issue on `''`.
|
package/src/shared/matchRoute.ts
CHANGED
|
@@ -56,9 +56,16 @@ function walk(
|
|
|
56
56
|
return prefix || at === parts.length ? {} : null
|
|
57
57
|
const segment = segments[index] as Segment
|
|
58
58
|
// 30.5 — A REST TAKES THE MOST THE SEGMENTS AFTER IT LEAVE, nothing included, so a handler's
|
|
59
|
-
// name after one (`/docs/[...path]/save`) is reached rather than spent on the rest.
|
|
59
|
+
// name after one (`/docs/[...path]/save`) is reached rather than spent on the rest — and 30.35,
|
|
60
|
+
// never past a part it does not accept.
|
|
60
61
|
if (segment.kind === 'rest') {
|
|
61
|
-
|
|
62
|
+
let furthest = at
|
|
63
|
+
while (
|
|
64
|
+
furthest < parts.length &&
|
|
65
|
+
segment.accepts.test(parts[furthest] as string)
|
|
66
|
+
)
|
|
67
|
+
furthest += 1
|
|
68
|
+
for (let end = furthest; end >= at; end -= 1) {
|
|
62
69
|
const params = walk(segments, index + 1, parts, end, prefix)
|
|
63
70
|
if (params !== null) {
|
|
64
71
|
params[segment.name] = parts.slice(at, end)
|
|
@@ -68,8 +75,12 @@ function walk(
|
|
|
68
75
|
return null
|
|
69
76
|
}
|
|
70
77
|
const part = parts[at]
|
|
78
|
+
// 30.34, 30.35 — A PARAM TAKES A PART ITS SEGMENT ACCEPTS: the matcher it names, or, naming
|
|
79
|
+
// none, no part carrying a period, which is how a file is named — the `/favicon.ico` a browser
|
|
80
|
+
// asks every site for is no `[param]` page's to answer.
|
|
81
|
+
const takeable = part !== undefined && segment.accepts.test(part)
|
|
71
82
|
if (segment.kind === 'optional') {
|
|
72
|
-
if (
|
|
83
|
+
if (takeable) {
|
|
73
84
|
const taken = walk(segments, index + 1, parts, at + 1, prefix)
|
|
74
85
|
if (taken !== null) {
|
|
75
86
|
taken[segment.name] = part
|
|
@@ -79,7 +90,8 @@ function walk(
|
|
|
79
90
|
return walk(segments, index + 1, parts, at, prefix)
|
|
80
91
|
}
|
|
81
92
|
if (part === undefined) return null
|
|
82
|
-
if (segment.kind === 'static'
|
|
93
|
+
if (segment.kind === 'static' ? part !== segment.name : !takeable)
|
|
94
|
+
return null
|
|
83
95
|
const params = walk(segments, index + 1, parts, at + 1, prefix)
|
|
84
96
|
if (params !== null && segment.kind === 'required')
|
|
85
97
|
params[segment.name] = part
|
package/src/shared/menuBridge.ts
CHANGED
package/src/shared/outbox.ts
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import type { Failed } from './Failed.ts'
|
|
11
11
|
import type { JsonValue } from './JsonValue.ts'
|
|
12
|
+
import { OUTBOX_SENDING } from './OUTBOX_SENDING.ts'
|
|
12
13
|
import { ambient, type Reactive, type Selection } from './reactive/index.ts'
|
|
13
14
|
import { matchesPattern } from './reactive/matchesPattern.ts'
|
|
14
|
-
import { OUTBOX_SENDING } from './OUTBOX_SENDING.ts'
|
|
15
15
|
import type { Method } from './rpc.ts'
|
|
16
16
|
|
|
17
17
|
/** One call `outbox` holds: what it sends, when it was queued, how often it was sent, and its value. */
|
|
@@ -15,8 +15,8 @@ import {
|
|
|
15
15
|
normalized,
|
|
16
16
|
PARAMETER_DEFAULTS,
|
|
17
17
|
} from './argsContract.ts'
|
|
18
|
-
import { CELL } from './CELL.ts'
|
|
19
18
|
import { canonicalKey } from './canonicalKey.ts'
|
|
19
|
+
import { CELL } from './CELL.ts'
|
|
20
20
|
import { ENTRY_LIVES } from './ENTRY_LIVES.ts'
|
|
21
21
|
import { armTimer } from './Expiry.ts'
|
|
22
22
|
import { KEYED_BRAND } from './KEYED_BRAND.ts'
|
|
@@ -43,8 +43,8 @@ import {
|
|
|
43
43
|
newSubscriber,
|
|
44
44
|
newTracked,
|
|
45
45
|
type Subscriber,
|
|
46
|
-
TRACKING,
|
|
47
46
|
type Tracked,
|
|
47
|
+
TRACKING,
|
|
48
48
|
trackProbe,
|
|
49
49
|
} from './tracking.ts'
|
|
50
50
|
import { owned, owning } from './watch.ts'
|
|
@@ -31,23 +31,23 @@ import { NotLanded } from './NotLanded.ts'
|
|
|
31
31
|
import { PATCH_SINKS, type PatchOp } from './PATCH_SINKS.ts'
|
|
32
32
|
import { REACTIVE_BRAND } from './REACTIVE_BRAND.ts'
|
|
33
33
|
import { recordWrites } from './recordWrites.ts'
|
|
34
|
-
import { STREAMING } from './STREAMING.ts'
|
|
35
34
|
import { currentScope, runInScope, type Scope } from './scoping.ts'
|
|
35
|
+
import { STREAMING } from './STREAMING.ts'
|
|
36
36
|
import {
|
|
37
37
|
CHECK,
|
|
38
38
|
CLEAN,
|
|
39
|
-
DIRTY,
|
|
40
39
|
detach,
|
|
40
|
+
DIRTY,
|
|
41
41
|
dropProbeReader,
|
|
42
42
|
newSubscriber,
|
|
43
43
|
newTracked,
|
|
44
44
|
RELEASING,
|
|
45
45
|
revive,
|
|
46
46
|
type Subscriber,
|
|
47
|
-
TRACKING,
|
|
48
|
-
type Tracked,
|
|
49
47
|
tombstone,
|
|
50
48
|
track,
|
|
49
|
+
type Tracked,
|
|
50
|
+
TRACKING,
|
|
51
51
|
trackProbe,
|
|
52
52
|
truncate,
|
|
53
53
|
truncateProbes,
|
|
@@ -252,12 +252,12 @@ export type Reactive<
|
|
|
252
252
|
| Accepted
|
|
253
253
|
| PromiseLike<Accepted>
|
|
254
254
|
| Reactive<Accepted, unknown, Failures, unknown>,
|
|
255
|
-
//
|
|
255
|
+
// Same reason as `watch` below — a `set` that returns nothing infers `void`, and 4.18's refusal is the other arm.
|
|
256
256
|
): void | Failures
|
|
257
257
|
patch(mutate: (value: Stored) => void): void
|
|
258
258
|
refresh(): void
|
|
259
259
|
invalidate(): void
|
|
260
|
-
//
|
|
260
|
+
// `void`, not `undefined`: see `Effect` in `watch.ts` — `(v) => { read(v) }` infers `void`.
|
|
261
261
|
watch(effect: (value: Stored) => void | (() => void)): () => void
|
|
262
262
|
/** What one run produced: the settled value once, or a stream's chunks to its close (2.5, 2.6). */
|
|
263
263
|
[Symbol.asyncIterator](): AsyncIterator<Produced>
|
|
@@ -658,8 +658,7 @@ class Cell {
|
|
|
658
658
|
* keyed on `.id` read every number as the same `undefined` and never woke again.
|
|
659
659
|
*/
|
|
660
660
|
declare declaredIdentityFn:
|
|
661
|
-
|
|
662
|
-
| null
|
|
661
|
+
((next: unknown, previous: unknown) => boolean) | null
|
|
663
662
|
declare transform: ((value: unknown) => unknown) | null
|
|
664
663
|
/**
|
|
665
664
|
* `null` on every value that declares no `schema`, which keeps the gate off the write
|
|
@@ -859,8 +858,7 @@ class Cell {
|
|
|
859
858
|
)
|
|
860
859
|
this.declaredIdentityFn = this.identityFn
|
|
861
860
|
this.transform = (options?.transform ?? null) as
|
|
862
|
-
|
|
863
|
-
| null
|
|
861
|
+
((value: unknown) => unknown) | null
|
|
864
862
|
this.schema = (options?.schema ?? null) as Schema<unknown> | null
|
|
865
863
|
this.ring = EMPTY_RING
|
|
866
864
|
this.ringBase = 0
|
|
@@ -906,9 +904,7 @@ class Cell {
|
|
|
906
904
|
unwatch: null,
|
|
907
905
|
}
|
|
908
906
|
this.by = (options?.by ?? null) as
|
|
909
|
-
|
|
|
910
|
-
| ((element: unknown) => unknown)
|
|
911
|
-
| null
|
|
907
|
+
string | ((element: unknown) => unknown) | null
|
|
912
908
|
this.probePass = 0
|
|
913
909
|
this.owes = OWES_NOTHING
|
|
914
910
|
this.isEffect = false
|
|
@@ -3502,7 +3498,7 @@ function settled(cell: Cell, untilStreaming = false): Promise<unknown> {
|
|
|
3502
3498
|
*/
|
|
3503
3499
|
function watchCell(
|
|
3504
3500
|
cell: Cell,
|
|
3505
|
-
//
|
|
3501
|
+
// `void`, not `undefined`: see `Effect` in `watch.ts`.
|
|
3506
3502
|
effect: (value: unknown) => void | (() => void),
|
|
3507
3503
|
): () => void {
|
|
3508
3504
|
return owned(
|
|
@@ -4459,7 +4455,7 @@ const MEMBERS = {
|
|
|
4459
4455
|
},
|
|
4460
4456
|
watch(
|
|
4461
4457
|
this: Callable,
|
|
4462
|
-
//
|
|
4458
|
+
// `void`, not `undefined`: see `Effect` in `watch.ts`.
|
|
4463
4459
|
effect: (value: unknown) => void | (() => void),
|
|
4464
4460
|
): () => void {
|
|
4465
4461
|
return watchCell(this[CELL], effect)
|
|
@@ -4467,7 +4463,7 @@ const MEMBERS = {
|
|
|
4467
4463
|
[Symbol.asyncIterator](this: Callable): AsyncIterator<unknown> {
|
|
4468
4464
|
return producedRun(this[CELL])
|
|
4469
4465
|
},
|
|
4470
|
-
//
|
|
4466
|
+
// RULEBOOK 1.4 — a `Reactive` MUST be thenable. Banning it buys a discrimination rather than a semantics, and 16.4's in-process `Rpc` arm is what that discrimination would have cost. 1.6 is the guard that makes thenability safe — the brand is read before `then`.
|
|
4471
4467
|
then(
|
|
4472
4468
|
this: Callable,
|
|
4473
4469
|
onSettled?: (value: unknown) => unknown,
|
|
@@ -231,7 +231,7 @@ export function reactive<
|
|
|
231
231
|
*/
|
|
232
232
|
export function reactive<
|
|
233
233
|
Body extends (args?: never) => unknown,
|
|
234
|
-
|
|
234
|
+
_Optional extends OptionalParameter<Body>,
|
|
235
235
|
Shaped = ChunkOf<Body>[],
|
|
236
236
|
Gate = Schema<ChunkOf<Body>[]>,
|
|
237
237
|
ArgsGate = Schema<Parameters<Body>[0]>,
|
|
@@ -291,7 +291,7 @@ export function reactive<
|
|
|
291
291
|
*/
|
|
292
292
|
export function reactive<
|
|
293
293
|
Body extends (args?: never) => unknown,
|
|
294
|
-
|
|
294
|
+
_Optional extends OptionalParameter<Body>,
|
|
295
295
|
Shaped = Awaited<ReturnType<Body>>,
|
|
296
296
|
Gate = Schema<Awaited<ReturnType<Body>>>,
|
|
297
297
|
ArgsGate = Schema<Parameters<Body>[0]>,
|
|
@@ -388,8 +388,7 @@ export function reactive(
|
|
|
388
388
|
// `store`, built where it is first reached (11.71). `global` is what keeps a value process-wide
|
|
389
389
|
// (11.34).
|
|
390
390
|
const options = given[1] as
|
|
391
|
-
|
|
392
|
-
| undefined
|
|
391
|
+
{ global?: boolean; store?: unknown } | undefined
|
|
393
392
|
if (outsideScope(options?.global === true))
|
|
394
393
|
return scoped(
|
|
395
394
|
() => build(given, keyed),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Reactive } from './Reactive.ts'
|
|
1
2
|
// 1.6 — THE BRAND, READ ON ITS OWN. A `Reactive` is callable AND thenable, so every guard that
|
|
2
3
|
// can meet one reads this before `typeof value === 'function'` or an `isThenable` probe.
|
|
3
4
|
//
|
|
@@ -9,7 +10,6 @@
|
|
|
9
10
|
// 2,348 bytes without it. The `Reactive` import is TYPE-ONLY and erased, which is what lets the
|
|
10
11
|
// predicate keep its exact shape at no cost.
|
|
11
12
|
import { REACTIVE_BRAND } from './REACTIVE_BRAND.ts'
|
|
12
|
-
import type { Reactive } from './Reactive.ts'
|
|
13
13
|
|
|
14
14
|
export function isReactive(value: unknown): value is Reactive<unknown> {
|
|
15
15
|
return (
|
|
@@ -16,9 +16,9 @@ import { contractOf, type Gated } from './argsContract.ts'
|
|
|
16
16
|
import { CELL } from './CELL.ts'
|
|
17
17
|
import { KEYED_BRAND } from './KEYED_BRAND.ts'
|
|
18
18
|
import { brandedPrototype, MEMBER_KEYS, signalMoved } from './Reactive.ts'
|
|
19
|
-
import { SELECTION_MEMBERS } from './SELECTION_MEMBERS.ts'
|
|
20
19
|
import { currentScope, runInScope, SCOPING } from './scoping.ts'
|
|
21
|
-
import {
|
|
20
|
+
import { SELECTION_MEMBERS } from './SELECTION_MEMBERS.ts'
|
|
21
|
+
import { newTracked, type Tracked, TRACKING, trackProbe } from './tracking.ts'
|
|
22
22
|
|
|
23
23
|
const UNBUILT: unique symbol = Symbol('abide.unbuilt')
|
|
24
24
|
|
|
@@ -35,7 +35,7 @@ let CURRENT: ShareScope | null = null
|
|
|
35
35
|
* Empty here: an app widens it, and a key it declares is checked against it where a key it does
|
|
36
36
|
* not is inferred from the thunk.
|
|
37
37
|
*/
|
|
38
|
-
//
|
|
38
|
+
// 10.3 — an app merges its own keys into this.
|
|
39
39
|
export interface Shared {}
|
|
40
40
|
|
|
41
41
|
/** The value held under `key` by this component or its nearest ancestor, or one created here. */
|