@abide/abide 0.104.0 → 0.105.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/CHANGELOG.md +22 -0
- package/bin/abide.ts +80 -173
- package/docs/REGISTRY.md +17 -17
- package/docs/RULEBOOK.md +50 -12
- package/docs/abide.md +9 -9
- package/package.json +1 -1
- package/src/compiler/Lowering.ts +15 -5
- package/src/compiler/Refusals.ts +57 -0
- package/src/compiler/TypeSession.ts +45 -86
- package/src/compiler/appLayout.ts +447 -0
- package/src/compiler/appMap.ts +21 -20
- package/src/compiler/checkApp.ts +61 -97
- package/src/compiler/checkedShadow.ts +11 -9
- package/src/compiler/classifyRead.ts +3 -6
- package/src/compiler/compileApp.ts +180 -512
- package/src/compiler/componentGraph.ts +2 -71
- package/src/compiler/declaredExports.ts +79 -60
- package/src/compiler/emitClient.ts +11 -13
- package/src/compiler/emitComponent.ts +21 -6
- package/src/compiler/emitSource.ts +20 -20
- package/src/compiler/handlersUnder.ts +46 -36
- package/src/compiler/hookCalls.ts +152 -0
- package/src/compiler/mountsOf.ts +31 -54
- package/src/compiler/readSources.ts +27 -42
- package/src/compiler/readsIn.ts +5 -16
- package/src/compiler/resolverIn.ts +77 -0
- package/src/server/abideModules.ts +92 -123
- package/src/server/answerRequest.ts +18 -4
- package/src/server/answerSocket.ts +136 -76
- package/src/server/answerValue.ts +28 -15
- package/src/server/appTree.ts +49 -71
- package/src/server/claudeApi.ts +2 -5
- package/src/server/clientBundle.ts +198 -0
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +93 -57
- package/src/server/createApp.ts +90 -16
- package/src/server/deferredBuilds.ts +175 -0
- package/src/server/devWorker.ts +107 -319
- package/src/server/faceOf.ts +120 -0
- package/src/server/fenceServer.ts +10 -18
- package/src/server/index.ts +3 -0
- package/src/server/placement.ts +167 -0
- package/src/server/remote.ts +14 -42
- package/src/server/render.ts +25 -17
- package/src/server/renderComponent.ts +9 -5
- package/src/server/requestScope.ts +10 -4
- package/src/server/responseRelay.ts +389 -0
- package/src/server/runCompiled.ts +9 -12
- package/src/server/runDesktop.ts +6 -9
- package/src/server/serveBuild.ts +26 -73
- package/src/server/serveDev.ts +19 -176
- package/src/server/streamEvent.ts +29 -0
- package/src/server/withAbideSources.ts +2 -1
- package/src/shared/HandlerRoute.ts +68 -0
- package/src/shared/REFUSAL_KEY.ts +5 -0
- package/src/shared/answerOf.ts +105 -78
- package/src/shared/filledPath.ts +1 -9
- package/src/shared/framing.ts +127 -128
- package/src/shared/queryText.ts +15 -7
- package/src/shared/reactive/Cursor.ts +4 -2
- package/src/shared/reactive/Reactive.ts +285 -197
- package/src/shared/reactive/index.ts +11 -8
- package/src/shared/reactive/pairReactive.ts +1 -1
- package/src/shared/reactive/scoped.ts +4 -3
- package/src/shared/reactive/share.ts +11 -1
- package/src/shared/routePattern.ts +0 -123
- package/src/shared/rpc.ts +17 -24
- package/src/shared/rpcRequest.ts +18 -47
- package/src/shared/takeEarlyCall.ts +12 -37
- package/src/shared/url.ts +1 -1
- package/src/ui/Level.ts +276 -0
- package/src/ui/List.ts +17 -17
- package/src/ui/Router.ts +20 -87
- package/src/ui/applyHead.ts +92 -79
- package/src/ui/index.ts +4 -2
- package/src/ui/leave.ts +5 -0
- package/src/ui/reconcile.ts +23 -104
- package/src/ui/regions.ts +27 -2
- package/src/ui/rows.ts +123 -2
- package/src/ui/wire.ts +12 -27
- package/src/compiler/hooksCalled.ts +0 -35
- package/src/server/clientBuildOptions.ts +0 -54
- package/src/server/refuseAddressCollision.ts +0 -37
- package/src/shared/reactive/chunksOfStream.ts +0 -33
- package/src/ui/MOUNTING_LEVEL.ts +0 -18
package/src/ui/wire.ts
CHANGED
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
// An entry of its own (`@abide/abide/wire`) rather than a name on the runtime barrel: only a generated
|
|
18
18
|
// wrapper imports it, and a page with no handler pays nothing for it.
|
|
19
19
|
|
|
20
|
-
import { answered, answerOf } from '#shared/answerOf.ts'
|
|
20
|
+
import { type Answer, answered, answerOf } from '#shared/answerOf.ts'
|
|
21
21
|
import type { CopyOptions } from '#shared/CopyOptions.ts'
|
|
22
22
|
import { isFailed } from '#shared/Failed.ts'
|
|
23
|
-
import { answerChunks
|
|
23
|
+
import { answerChunks } from '#shared/framing.ts'
|
|
24
|
+
import { HandlerRoute } from '#shared/HandlerRoute.ts'
|
|
24
25
|
import { LOAD_SIGNAL } from '#shared/LOAD_SIGNAL.ts'
|
|
25
26
|
import { reactive, structural } from '#shared/reactive/index.ts'
|
|
26
27
|
import {
|
|
@@ -29,8 +30,6 @@ import {
|
|
|
29
30
|
} from '#shared/reactive/parameterDefaults.ts'
|
|
30
31
|
import { declare, type Method } from '#shared/rpc.ts'
|
|
31
32
|
import { rpcRequest } from '#shared/rpcRequest.ts'
|
|
32
|
-
import type { Answer } from '#shared/takeEarlyCall.ts'
|
|
33
|
-
import { versionInEtag } from '#shared/versionInEtag.ts'
|
|
34
33
|
import { followRpc } from './socketWire.ts'
|
|
35
34
|
|
|
36
35
|
/** The browser's `Rpc` for a handler: the server's declaration over a body that fetches. */
|
|
@@ -42,6 +41,8 @@ export function wire(
|
|
|
42
41
|
// 11.29 — and the defaults its parameter writes, which the copy keys by as the server does.
|
|
43
42
|
defaults?: Defaults,
|
|
44
43
|
): unknown {
|
|
44
|
+
// 16.9 — the handler's route, compiled once for every call through the copy.
|
|
45
|
+
const route = new HandlerRoute(address)
|
|
45
46
|
// ONE PARAMETER, so the value is keyed by args whatever the handler declared (16.6's arity rule):
|
|
46
47
|
// an unkeyed handler is reached with no args, which is one key.
|
|
47
48
|
const body = (args: Record<string, unknown> | undefined) => {
|
|
@@ -51,11 +52,7 @@ export function wire(
|
|
|
51
52
|
follow(args, answer)
|
|
52
53
|
return answered(answer)
|
|
53
54
|
}
|
|
54
|
-
const answer = answerOf(
|
|
55
|
-
rpcRequest(method, address, args),
|
|
56
|
-
false,
|
|
57
|
-
signal,
|
|
58
|
-
)
|
|
55
|
+
const answer = answerOf(rpcRequest(method, route, args), false, signal)
|
|
59
56
|
return answer instanceof Promise ? answer.then(settle) : settle(answer)
|
|
60
57
|
}
|
|
61
58
|
// 16.32 — A STREAMING HANDLER'S COPY IS A STREAM OF ITS OWN, whose chunks land as the answer's
|
|
@@ -68,13 +65,13 @@ export function wire(
|
|
|
68
65
|
// this runs while the call that started the load still has its signal set.
|
|
69
66
|
const signal = LOAD_SIGNAL.current
|
|
70
67
|
const answer = await answerOf(
|
|
71
|
-
rpcRequest(method,
|
|
68
|
+
rpcRequest(method, route, args),
|
|
72
69
|
true,
|
|
73
70
|
signal,
|
|
74
71
|
)
|
|
75
72
|
follow(args, answer)
|
|
76
73
|
if (answer.body !== undefined && answer.body !== null) {
|
|
77
|
-
yield* answerChunks(answer.body, answer.
|
|
74
|
+
yield* answerChunks(answer.body, answer.framing)
|
|
78
75
|
return
|
|
79
76
|
}
|
|
80
77
|
const value = answered(answer)
|
|
@@ -88,22 +85,10 @@ export function wire(
|
|
|
88
85
|
// so a caller signed out since would be fed what this answer just refused it. Nor a binary,
|
|
89
86
|
// which the mux's JSON frames cannot carry.
|
|
90
87
|
const follow = (args: unknown, answer: Answer): void => {
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
answer.status >= 300 ||
|
|
96
|
-
carriesBytes(answer.type)
|
|
97
|
-
)
|
|
98
|
-
return
|
|
99
|
-
const seconds = /max-age=(\d+)/.exec(answer.life ?? '')?.[1]
|
|
100
|
-
followRpc(
|
|
101
|
-
address,
|
|
102
|
-
args,
|
|
103
|
-
versionInEtag(answer.version),
|
|
104
|
-
seconds === undefined ? undefined : Number(seconds) * 1000,
|
|
105
|
-
() => (value as (args: unknown) => unknown)(args),
|
|
106
|
-
)
|
|
88
|
+
if (method === 'GET' && answer.followable)
|
|
89
|
+
followRpc(address, args, answer.version, answer.life, () =>
|
|
90
|
+
(value as (args: unknown) => unknown)(args),
|
|
91
|
+
)
|
|
107
92
|
}
|
|
108
93
|
// A KEYED VALUE OF ITS OWN where the handler's declares what its copy behaves by, so every entry —
|
|
109
94
|
// the copy a page walks — carries it: its key (32.32), its tags (11.43) and its comparator (5.3). A `GET` alone: a mutation's value is written and read once.
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { tokenize } from './tokenize.ts'
|
|
2
|
-
|
|
3
|
-
/** Every hook abide exports, by name. */
|
|
4
|
-
export const HOOK_NAMES: ReadonlySet<string> = new Set([
|
|
5
|
-
'middleware',
|
|
6
|
-
'onStart',
|
|
7
|
-
'onStop',
|
|
8
|
-
'onError',
|
|
9
|
-
'onConfig',
|
|
10
|
-
'onHealth',
|
|
11
|
-
'onPrincipal',
|
|
12
|
-
'onLauncher',
|
|
13
|
-
'onMenu',
|
|
14
|
-
])
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Each of abide's hook names called in `source`, in the order written: the name, then `(`, and not
|
|
18
|
-
* a declaration — a member call included, since `server.onStart(…)` is abide's hook all the same.
|
|
19
|
-
* Read, not run — what `abide routes` lists and the build's refusals of a hook called where it is
|
|
20
|
-
* never read (38.107) go by.
|
|
21
|
-
*/
|
|
22
|
-
export function hooksCalled(source: string): string[] {
|
|
23
|
-
const called: string[] = []
|
|
24
|
-
let previous = ''
|
|
25
|
-
let named: string | undefined
|
|
26
|
-
for (const token of tokenize(source)) {
|
|
27
|
-
if (named !== undefined && token.text === '(') called.push(named)
|
|
28
|
-
named =
|
|
29
|
-
HOOK_NAMES.has(token.text) && previous !== 'function'
|
|
30
|
-
? token.text
|
|
31
|
-
: undefined
|
|
32
|
-
previous = token.text
|
|
33
|
-
}
|
|
34
|
-
return called
|
|
35
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { CLIENT_ADDRESS } from '#shared/ADDRESSES.ts'
|
|
2
|
-
import { tablePath } from './abideModules.ts'
|
|
3
|
-
import { CLIENT_ENTRY } from './CLIENT_BUILD.ts'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The `Bun.build` options for the client bundle both lanes build.
|
|
7
|
-
*
|
|
8
|
-
* `abide build` builds it to disk and `abide dev` in memory. 30.8 says dev resolves an address the
|
|
9
|
-
* way the built app does, so what decides resolution is written once here and each lane adds only
|
|
10
|
-
* where the output goes and how it is named.
|
|
11
|
-
*/
|
|
12
|
-
export function clientBuildOptions(
|
|
13
|
-
root: string,
|
|
14
|
-
plugins: Bun.BunPlugin[],
|
|
15
|
-
publicAddresses: string[],
|
|
16
|
-
) {
|
|
17
|
-
return {
|
|
18
|
-
// The entry is a table the plugin serves (`abideModules`), below the root.
|
|
19
|
-
entrypoints: [tablePath(root, `${CLIENT_ENTRY}.ts`)] as string[],
|
|
20
|
-
// The app's own root, because `Bun.build` opens the entry's directory otherwise, and the
|
|
21
|
-
// tables' is not on disk to open.
|
|
22
|
-
root,
|
|
23
|
-
// 16.28, 16.29 — a `<script server>` is its stand-ins here, and a `server/` module refuses.
|
|
24
|
-
plugins,
|
|
25
|
-
target: 'browser',
|
|
26
|
-
format: 'esm',
|
|
27
|
-
splitting: true,
|
|
28
|
-
metafile: true,
|
|
29
|
-
// 36.15 — a stylesheet's `url('/fonts/…')` is the public file's address, not a path here,
|
|
30
|
-
// and `Bun.build` reads a leading `/` as a path on this disk and fails on it not being
|
|
31
|
-
// there. The list is the files that exist rather than every `/…`: a URL naming nothing the
|
|
32
|
-
// app serves is still worth a build error.
|
|
33
|
-
external: publicAddresses,
|
|
34
|
-
} as const
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// From the bundle's own directory up to the app's root: `../../` below `/__abide/client`.
|
|
38
|
-
const UP_TO_ROOT = '../'.repeat(CLIENT_ADDRESS.split('/').length - 1)
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* A built stylesheet with every root-absolute `url()` written relative to the bundle's directory.
|
|
42
|
-
*
|
|
43
|
-
* 16.10, 36.15 — THE SAME MECHANISM AS THE HANDLER WRAPPERS: the address is written relative to
|
|
44
|
-
* where the bundle is served and the browser resolves it against the sheet, so one build serves
|
|
45
|
-
* at the root and below a mount. What survives the build as `url(/…)` is a public file's address
|
|
46
|
-
* (the `external` list above), and the browser would resolve it against the origin. `//` is
|
|
47
|
-
* another host's.
|
|
48
|
-
*/
|
|
49
|
-
export function stylesheetUnderMount(css: string): string {
|
|
50
|
-
return css.replace(
|
|
51
|
-
/url\((["']?)\/(?!\/)/g,
|
|
52
|
-
(_whole, quote: string) => `url(${quote}${UP_TO_ROOT}`,
|
|
53
|
-
)
|
|
54
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Throws, naming both, when a public file claims a route's address or another public file's.
|
|
3
|
-
*
|
|
4
|
-
* 36.13 — the walks that find routes and public files cannot see each other's collisions from
|
|
5
|
-
* inside either one; this is the one place they meet, and naming BOTH is the clause — naming one
|
|
6
|
-
* leaves the reader to find the other. Two `public` directories can claim one address too, since
|
|
7
|
-
* each mounts at the path above it (36.11).
|
|
8
|
-
*
|
|
9
|
-
* THE BUILD AND DEV, and `abide start` trusts the build: `answerRequest` answers a public file
|
|
10
|
-
* AHEAD of any route on the ground that 36.13 makes a collision impossible at run time, and 30.8
|
|
11
|
-
* says dev must resolve an address the way the built app does.
|
|
12
|
-
*
|
|
13
|
-
* `published` is each public file's source path to its address, as `appTree` reads them.
|
|
14
|
-
*/
|
|
15
|
-
export function refuseAddressCollision(
|
|
16
|
-
pages: readonly { pattern: string }[],
|
|
17
|
-
handlers: readonly { address: string }[],
|
|
18
|
-
published: Map<string, string>,
|
|
19
|
-
command: string,
|
|
20
|
-
): void {
|
|
21
|
-
const patterns = new Set<string>()
|
|
22
|
-
for (const page of pages) patterns.add(page.pattern)
|
|
23
|
-
for (const handler of handlers) patterns.add(handler.address)
|
|
24
|
-
const claimed = new Map<string, string>()
|
|
25
|
-
for (const [source, address] of published) {
|
|
26
|
-
if (patterns.has(address))
|
|
27
|
-
throw new Error(
|
|
28
|
-
`${command}: ${source} and the route ${address} claim the same address. RULEBOOK 36.13.`,
|
|
29
|
-
)
|
|
30
|
-
const other = claimed.get(address)
|
|
31
|
-
if (other !== undefined)
|
|
32
|
-
throw new Error(
|
|
33
|
-
`${command}: ${other} and ${source} are both answered at ${address}. RULEBOOK 36.13.`,
|
|
34
|
-
)
|
|
35
|
-
claimed.set(address, source)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { isFailed } from '../Failed.ts'
|
|
2
|
-
import { chunksFrom, wakeOf } from './Reactive.ts'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The chunks of a streaming value, as they land, ending when its run closes.
|
|
6
|
-
*
|
|
7
|
-
* WHAT A READ WOULD SERVE, chunk by chunk (16.32), rather than the ring `for await` walks: a gated
|
|
8
|
-
* value's chunks are its gated accumulation's (9.8). A cursor is the wake and the settling is the
|
|
9
|
-
* end (`Cursor.closeOn`).
|
|
10
|
-
*/
|
|
11
|
-
export async function* chunksOfStream(value: unknown): AsyncGenerator<unknown> {
|
|
12
|
-
const cursor = wakeOf(value)
|
|
13
|
-
cursor.closeOn(Promise.resolve(value as PromiseLike<unknown>))
|
|
14
|
-
let sent = 0
|
|
15
|
-
try {
|
|
16
|
-
for (;;) {
|
|
17
|
-
for (const chunk of chunksFrom(value, sent)) {
|
|
18
|
-
sent += 1
|
|
19
|
-
yield chunk
|
|
20
|
-
}
|
|
21
|
-
if (cursor.closed) break
|
|
22
|
-
await cursor.turn()
|
|
23
|
-
}
|
|
24
|
-
} finally {
|
|
25
|
-
cursor.stop()
|
|
26
|
-
}
|
|
27
|
-
if (cursor.failure !== undefined) throw cursor.failure
|
|
28
|
-
// 16.59, 16.60 — A RUN ENDED BY ITS TIMEOUT SETTLES with the chunks it had and holds its refusal
|
|
29
|
-
// rather than rejecting, so the refusal is read off the value and thrown after them: a stream
|
|
30
|
-
// answered over http then ends on it (16.68) rather than as though it had finished.
|
|
31
|
-
const held = (value as { error?: () => unknown }).error?.()
|
|
32
|
-
if (isFailed(held)) throw held
|
|
33
|
-
}
|
package/src/ui/MOUNTING_LEVEL.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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
|
-
}
|