@abide/abide 0.45.0 → 0.47.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/AGENTS.md +365 -320
- package/CHANGELOG.md +96 -0
- package/README.md +203 -168
- package/package.json +12 -8
- package/src/abideLsp.ts +11 -12
- package/src/abideResolverPlugin.ts +9 -4
- package/src/lib/bundle/disconnected.abide +3 -0
- package/src/lib/cli/printCommandHelp.ts +43 -0
- package/src/lib/cli/printSessionHelp.ts +2 -1
- package/src/lib/cli/{printHelp.ts → printTopLevelHelp.ts} +3 -40
- package/src/lib/cli/runCli.ts +2 -1
- package/src/lib/mcp/buildPrompts.ts +25 -0
- package/src/lib/mcp/dispatchMcpRequest.ts +8 -6
- package/src/lib/mcp/mcpResourceServerSlot.ts +5 -10
- package/src/lib/mcp/mcpSurface.ts +13 -252
- package/src/lib/mcp/mcpTools.ts +187 -0
- package/src/lib/mcp/renderPrompt.ts +25 -0
- package/src/lib/mcp/types/McpSurface.ts +15 -0
- package/src/lib/mcp/types/PromptDescriptor.ts +5 -0
- package/src/lib/mcp/types/PromptMessage.ts +2 -0
- package/src/lib/mcp/types/ToolDescriptor.ts +7 -0
- package/src/lib/mcp/types/ToolResult.ts +2 -0
- package/src/lib/server/agent.ts +1 -1
- package/src/lib/server/jsonl.ts +2 -2
- package/src/lib/server/rpc/defineRpc.ts +7 -1
- package/src/lib/server/runtime/containsTraversal.ts +21 -17
- package/src/lib/server/runtime/createAppAssetServer.ts +11 -16
- package/src/lib/server/runtime/createServer.ts +101 -71
- package/src/lib/server/runtime/createUiPageRenderer.ts +23 -4
- package/src/lib/server/runtime/devClientFingerprint.ts +3 -3
- package/src/lib/server/runtime/devHotModuleResponse.ts +2 -1
- package/src/lib/server/runtime/finalizeResponse.ts +32 -0
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +8 -17
- package/src/lib/server/socket.ts +1 -1
- package/src/lib/server/sockets/createSocketDispatcher.ts +28 -5
- package/src/lib/server/sockets/defineSocket.ts +26 -7
- package/src/lib/server/sockets/types/SocketRegistryEntry.ts +1 -1
- package/src/lib/server/sockets/types/SocketRoutes.ts +1 -1
- package/src/lib/server/sse.ts +2 -2
- package/src/lib/shared/DEFER.ts +8 -0
- package/src/lib/shared/activeCacheStore.ts +2 -2
- package/src/lib/shared/activePage.ts +2 -2
- package/src/lib/shared/attachRpcSelectorMethods.ts +42 -0
- package/src/lib/shared/attachSocketSelectorMethods.ts +34 -0
- package/src/lib/shared/basePath.ts +2 -2
- package/src/lib/shared/baseSlot.ts +6 -5
- package/src/lib/shared/bodyValueForKind.ts +1 -2
- package/src/lib/shared/buildSocketOverChannel.ts +40 -4
- package/src/lib/shared/cache.ts +477 -110
- package/src/lib/shared/cacheEntryFromSnapshot.ts +3 -22
- package/src/lib/shared/cacheStoreSlot.ts +9 -5
- package/src/lib/{server/runtime → shared}/createReachable.ts +2 -2
- package/src/lib/shared/createRemoteFunction.ts +58 -9
- package/src/lib/shared/createResolverSlot.ts +24 -31
- package/src/lib/shared/createSocketSubRegistry.ts +2 -2
- package/src/lib/shared/decodeRefJson.ts +4 -2
- package/src/lib/shared/decodeResponse.ts +8 -6
- package/src/lib/shared/done.ts +14 -0
- package/src/lib/shared/encodeRefJson.ts +4 -2
- package/src/lib/shared/globalCacheStoreSlot.ts +9 -6
- package/src/lib/shared/hydratingSlot.ts +12 -0
- package/src/lib/shared/isLayoutFile.ts +12 -0
- package/src/lib/shared/keyForRemoteCall.ts +2 -1
- package/src/lib/shared/keyPrefixForRemote.ts +12 -0
- package/src/lib/shared/matchRoute.ts +175 -0
- package/src/lib/shared/normalizePathname.ts +11 -0
- package/src/lib/shared/pageSlot.ts +14 -5
- package/src/lib/shared/pageUrlForFile.ts +3 -3
- package/src/lib/shared/parseRouteSegments.ts +16 -7
- package/src/lib/shared/patch.ts +41 -0
- package/src/lib/shared/peek.ts +35 -0
- package/src/lib/shared/prepareRemoteExport.ts +40 -0
- package/src/lib/shared/prepareRpcModule.ts +98 -16
- package/src/lib/shared/prepareSocketModule.ts +11 -15
- package/src/lib/shared/queryStringFromArgs.ts +11 -0
- package/src/lib/shared/reachable.ts +102 -0
- package/src/lib/shared/refresh.ts +22 -0
- package/src/lib/shared/requestScopeSlot.ts +10 -7
- package/src/lib/shared/routeParamsShape.ts +9 -9
- package/src/lib/shared/rpcErrorRegistry.ts +69 -0
- package/src/lib/shared/selectorPrefix.ts +3 -10
- package/src/lib/shared/setOwnProperty.ts +19 -0
- package/src/lib/shared/snippet.ts +1 -1
- package/src/lib/shared/subscribableProbes.ts +111 -0
- package/src/lib/shared/tailProbeSlot.ts +8 -1
- package/src/lib/shared/types/CacheEntry.ts +9 -15
- package/src/lib/shared/types/CacheOptions.ts +8 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +0 -5
- package/src/lib/shared/types/RemoteCallable.ts +25 -7
- package/src/lib/shared/types/RemoteFunction.ts +48 -13
- package/src/lib/shared/types/ResolverSlot.ts +7 -4
- package/src/lib/shared/types/RpcError.ts +26 -0
- package/src/lib/shared/types/SmartReadOptions.ts +32 -0
- package/src/lib/shared/types/Socket.ts +54 -0
- package/src/lib/shared/types/SsrBootState.ts +27 -0
- package/src/lib/shared/types/SsrPayload.ts +21 -0
- package/src/lib/shared/types/Subscribable.ts +13 -13
- package/src/lib/shared/types/TailHooks.ts +2 -1
- package/src/lib/shared/url.ts +29 -14
- package/src/lib/shared/wakeHydrationPeeks.ts +20 -0
- package/src/lib/shared/writeTestSocketsDts.ts +1 -1
- package/src/lib/test/createScriptedSurface.ts +1 -1
- package/src/lib/test/createTestApp.ts +2 -2
- package/src/lib/test/createTestSocketChannel.ts +3 -3
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +5 -6
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +10 -7
- package/src/lib/ui/compile/abideUiPlugin.ts +2 -2
- package/src/lib/ui/compile/analyzeComponent.ts +28 -6
- package/src/lib/ui/compile/compileModule.ts +137 -11
- package/src/lib/ui/compile/compileShadow.ts +101 -84
- package/src/lib/ui/compile/desugarSignals.ts +120 -107
- package/src/lib/ui/compile/generateBuild.ts +47 -11
- package/src/lib/ui/compile/generateSSR.ts +117 -14
- package/src/lib/ui/compile/identifierReferencePattern.ts +13 -0
- package/src/lib/ui/compile/lowerContext.ts +10 -4
- package/src/lib/ui/compile/lowerScript.ts +72 -5
- package/src/lib/ui/compile/prepareNestedScript.ts +31 -17
- package/src/lib/ui/compile/resolveReactiveExport.ts +95 -0
- package/src/lib/ui/compile/signalCallee.ts +24 -0
- package/src/lib/ui/compile/stripEffects.ts +14 -11
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +5 -0
- package/src/lib/ui/dom/appendStatic.ts +9 -1
- package/src/lib/ui/dom/appendText.ts +15 -3
- package/src/lib/ui/dom/assertClaimedText.ts +20 -0
- package/src/lib/ui/dom/awaitBlock.ts +17 -56
- package/src/lib/ui/dom/bindSelectValue.ts +48 -0
- package/src/lib/ui/dom/each.ts +12 -1
- package/src/lib/ui/dom/hydrate.ts +10 -0
- package/src/lib/ui/effect.ts +4 -3
- package/src/lib/ui/installHotBridge.ts +4 -2
- package/src/lib/ui/remoteProxy.ts +18 -2
- package/src/lib/ui/renderToStream.ts +9 -13
- package/src/lib/ui/resumeSeedScript.ts +2 -2
- package/src/lib/ui/router.ts +21 -2
- package/src/lib/ui/runtime/RESUME.ts +0 -6
- package/src/lib/ui/runtime/ambientScopeBacking.ts +1 -1
- package/src/lib/ui/runtime/types/SsrRender.ts +3 -4
- package/src/lib/ui/scope.ts +6 -3
- package/src/lib/ui/seedBootState.ts +53 -0
- package/src/lib/ui/socketChannel.ts +2 -2
- package/src/lib/ui/socketProxy.ts +9 -3
- package/src/lib/ui/startClient.ts +16 -30
- package/src/lib/ui/state.ts +44 -13
- package/src/lib/ui/sync.ts +11 -5
- package/src/lib/ui/tryEncodeResume.ts +2 -5
- package/src/lib/ui/types/Scope.ts +14 -10
- package/src/lib/ui/watch.ts +140 -0
- package/src/serverEntry.ts +6 -6
- package/template/CLAUDE.md +1 -1
- package/template/package.json +1 -1
- package/template/src/server/rpc/getHello.ts +3 -3
- package/template/src/ui/pages/page.abide +2 -2
- package/template/test/app.test.ts +1 -1
- package/src/lib/server/reachable.ts +0 -45
- package/src/lib/server/sockets/types/Socket.ts +0 -23
- package/src/lib/shared/CACHE_WRAPPED.ts +0 -9
- package/src/lib/shared/baseResolver.ts +0 -10
- package/src/lib/shared/cacheKeyOf.ts +0 -7
- package/src/lib/shared/cacheKeyStore.ts +0 -8
- package/src/lib/shared/cacheStoreResolver.ts +0 -12
- package/src/lib/shared/globalCacheStoreResolver.ts +0 -12
- package/src/lib/shared/pageResolver.ts +0 -16
- package/src/lib/shared/recordCacheKey.ts +0 -6
- package/src/lib/shared/requestScopeResolver.ts +0 -12
- package/src/lib/shared/setBaseResolver.ts +0 -4
- package/src/lib/shared/setCacheStoreResolver.ts +0 -4
- package/src/lib/shared/setGlobalCacheStoreResolver.ts +0 -4
- package/src/lib/shared/setPageResolver.ts +0 -4
- package/src/lib/shared/setRequestScopeResolver.ts +0 -4
- package/src/lib/shared/toBunRoutePattern.ts +0 -28
- package/src/lib/shared/types/TailOptions.ts +0 -10
- package/src/lib/ui/deferResume.ts +0 -29
- package/src/lib/ui/matchRoute.ts +0 -106
- package/src/lib/ui/tail.ts +0 -324
- /package/src/lib/{server/sockets → shared}/types/SocketClientFrame.ts +0 -0
- /package/src/lib/{server/sockets → shared}/types/SocketServerFrame.ts +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { Socket } from '../server/sockets/types/Socket.ts'
|
|
2
1
|
import { buildSocketOverChannel } from '../shared/buildSocketOverChannel.ts'
|
|
2
|
+
import type { Socket } from '../shared/types/Socket.ts'
|
|
3
3
|
import { getSocketChannel } from './socketChannel.ts'
|
|
4
|
+
import { watch } from './watch.ts'
|
|
4
5
|
|
|
5
6
|
/*
|
|
6
7
|
Client-side substitute for a server-declared Socket. The bundler emits
|
|
@@ -10,7 +11,7 @@ over the multiplexed ws channel). Both paths produce identical Socket
|
|
|
10
11
|
shapes so user code reads the same on either side.
|
|
11
12
|
|
|
12
13
|
The Socket surface — bare iteration as the live stream, `.tail(n)` seeded
|
|
13
|
-
from the retained tail, `.
|
|
14
|
+
from the retained tail, `.broadcast` sending a server-validated `pub` frame —
|
|
14
15
|
is built by buildSocketOverChannel over the page's lazily-opened singleton
|
|
15
16
|
channel; this module only binds that builder to the browser channel so the
|
|
16
17
|
test harness can reuse the identical surface over its own channel.
|
|
@@ -21,5 +22,10 @@ socketProxy API, not the wire layer.
|
|
|
21
22
|
*/
|
|
22
23
|
// @documentation plumbing
|
|
23
24
|
export function socketProxy<T>(name: string): Socket<T> {
|
|
24
|
-
|
|
25
|
+
const socket = buildSocketOverChannel<T>(name, getSocketChannel)
|
|
26
|
+
/* Overwrite the shared builder's inert `.watch` with the real reaction sugar
|
|
27
|
+
(`socket.watch(handler)` ≡ `watch(socket, handler)`). Attached here so the ui-only
|
|
28
|
+
`watch` primitive never rides into a server bundle. */
|
|
29
|
+
socket.watch = (handler) => watch(socket, handler)
|
|
30
|
+
return socket
|
|
25
31
|
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
+
import { cacheStoreSlot } from '../shared/cacheStoreSlot.ts'
|
|
1
2
|
import { createCacheStore } from '../shared/createCacheStore.ts'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { setBaseResolver } from '../shared/setBaseResolver.ts'
|
|
6
|
-
import { setCacheStoreResolver } from '../shared/setCacheStoreResolver.ts'
|
|
7
|
-
import { setGlobalCacheStoreResolver } from '../shared/setGlobalCacheStoreResolver.ts'
|
|
8
|
-
import { setPageResolver } from '../shared/setPageResolver.ts'
|
|
9
|
-
import type { CacheSnapshotEntry } from '../shared/types/CacheSnapshotEntry.ts'
|
|
3
|
+
import { globalCacheStoreSlot } from '../shared/globalCacheStoreSlot.ts'
|
|
4
|
+
import { pageSlot } from '../shared/pageSlot.ts'
|
|
5
|
+
import type { SsrPayload } from '../shared/types/SsrPayload.ts'
|
|
10
6
|
import type { StreamedResolution } from '../shared/types/StreamedResolution.ts'
|
|
11
7
|
import { probeNavigation } from './probeNavigation.ts'
|
|
8
|
+
import { seedBootState } from './seedBootState.ts'
|
|
12
9
|
|
|
13
10
|
/* Build-time flag the production client defines false (see build.ts `define`) so the dev-only
|
|
14
11
|
hot bridge — and the entire DOM runtime it statically pulls in for re-builds — is dead-code-
|
|
@@ -21,15 +18,6 @@ import { clientPage } from './runtime/clientPage.ts'
|
|
|
21
18
|
import type { RouteLoader } from './runtime/types/RouteLoader.ts'
|
|
22
19
|
import { seedResolved } from './seedResolved.ts'
|
|
23
20
|
|
|
24
|
-
/* The server's __SSR__ payload this entry consumes. */
|
|
25
|
-
type SsrPayload = {
|
|
26
|
-
cache?: CacheSnapshotEntry[]
|
|
27
|
-
base?: string
|
|
28
|
-
app?: string
|
|
29
|
-
health?: Record<string, unknown>
|
|
30
|
-
clientTimeout?: number
|
|
31
|
-
}
|
|
32
|
-
|
|
33
21
|
/*
|
|
34
22
|
The official abide-ui client entry. Reads the server's `window.__SSR__` payload,
|
|
35
23
|
seeds a tab-scoped cache store from the inline snapshot (so a warm `cache()` read
|
|
@@ -67,23 +55,21 @@ export function startClient(
|
|
|
67
55
|
if ((globalThis as { __abideInspect?: boolean }).__abideInspect) {
|
|
68
56
|
import('./installInspectorBridge.ts').then((module) => module.installInspectorBridge())
|
|
69
57
|
}
|
|
70
|
-
const ssr = (globalThis as { __SSR__?: SsrPayload }).__SSR__ ?? {}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
and unbounded RPC fetches. */
|
|
77
|
-
|
|
78
|
-
healthSeedSlot.payload = ssr.health
|
|
79
|
-
rpcTimeoutSlot.ms = ssr.clientTimeout
|
|
58
|
+
const ssr = (globalThis as { __SSR__?: Partial<SsrPayload> }).__SSR__ ?? {}
|
|
59
|
+
/* Seed the per-page __SSR__ stamps into their shared slots before mount: the mount
|
|
60
|
+
base, app name (default log channel), health payload (so health()'s first probe is
|
|
61
|
+
warm), and the env-configured RPC timeout (ABIDE_CLIENT_TIMEOUT, shipped per
|
|
62
|
+
request). Driven by the exhaustive `seedBootState` map so a new boot field can't be
|
|
63
|
+
stamped server-side and silently dropped here. Without seeding: no mount base,
|
|
64
|
+
channel 'app', a cold first health probe, and unbounded RPC fetches. */
|
|
65
|
+
seedBootState(ssr)
|
|
80
66
|
/* The `page` proxy reads route/params/url off the router-updated snapshot. */
|
|
81
|
-
|
|
67
|
+
pageSlot.resolver = () => clientPage.value
|
|
82
68
|
|
|
83
69
|
const store = createCacheStore()
|
|
84
|
-
|
|
70
|
+
cacheStoreSlot.resolver = () => store
|
|
85
71
|
/* One tab store: cache(fn, { global: true }) shares it, so global is a no-op here. */
|
|
86
|
-
|
|
72
|
+
globalCacheStoreSlot.resolver = () => store
|
|
87
73
|
/* Seed both SSR cache partitions through the one streamed-resolution sink: `ssr.cache`
|
|
88
74
|
(inline — reads settled at render-return, in __SSR__) and `__abideResumeCache` (pending
|
|
89
75
|
{#await} reads whose `__abideResolve(...)` chunks the stream pushed during parse, before
|
package/src/lib/ui/state.ts
CHANGED
|
@@ -1,15 +1,42 @@
|
|
|
1
|
+
import { computed } from './computed.ts'
|
|
2
|
+
import { linked } from './linked.ts'
|
|
1
3
|
import { createSignalNode } from './runtime/createSignalNode.ts'
|
|
2
4
|
import { readNode } from './runtime/readNode.ts'
|
|
3
5
|
import type { State } from './runtime/types/State.ts'
|
|
4
6
|
import { writeNode } from './runtime/writeNode.ts'
|
|
7
|
+
import { scope } from './scope.ts'
|
|
8
|
+
|
|
9
|
+
/* The imported reactive surface: the `state` callable plus its attached members.
|
|
10
|
+
`state`/`state.linked`/`state.computed` are the reactive primitives an author
|
|
11
|
+
imports (`import { state } from '@abide/abide/ui/state'`); the compiler resolves
|
|
12
|
+
the import binding (alias-safe) and lowers each onto the ambient scope. `.share`/
|
|
13
|
+
`.shared` are the context seam — the reference store the runtime scope exposes,
|
|
14
|
+
reached ambiently so a component can pass a named value down its subtree. */
|
|
15
|
+
type StateFn = {
|
|
16
|
+
/* No-arg form for an undefined initial with a declared type: `state<Foo>()` is
|
|
17
|
+
`State<Foo | undefined>`. Without it `state<Foo>(undefined)` is an arity/assign
|
|
18
|
+
error and `state(undefined)` infers `T = undefined` (every `.value` access then
|
|
19
|
+
narrows to `never`). */
|
|
20
|
+
<T>(): State<T | undefined>
|
|
21
|
+
<T>(initial: T, transform?: (next: T, previous: T) => T): State<T>
|
|
22
|
+
/* A writable cell reseeded from a reactive thunk (`state.linked(() => src())`). */
|
|
23
|
+
linked: typeof linked
|
|
24
|
+
/* A read-only cell computed from other cells (`state.computed(() => a() + b())`). */
|
|
25
|
+
computed: typeof computed
|
|
26
|
+
/* Puts a named value on the ambient scope, read down the tree by `state.shared`. */
|
|
27
|
+
share: (key: string, value: unknown) => void
|
|
28
|
+
/* Reads the closest ancestor scope that shared `key`; undefined if none provided. */
|
|
29
|
+
shared: <T>(key: string) => T | undefined
|
|
30
|
+
}
|
|
5
31
|
|
|
6
32
|
/*
|
|
7
33
|
A writable reactive cell — abide's from-scratch reactive primitive, with no
|
|
8
34
|
compiler sigil and no external reactivity-library import. `.value` is a
|
|
9
35
|
plain getter/setter over a signal node, so a read/write shows up as exactly that
|
|
10
|
-
in a stack trace.
|
|
11
|
-
|
|
12
|
-
doc slot, and keeps `state(initial, transform)` as a `.value`
|
|
36
|
+
in a stack trace. Imported and called bare (`let count = state(0)`); the compiler
|
|
37
|
+
resolves the `state` import binding (alias-safe), desugars plain `state(initial)` to
|
|
38
|
+
a serializable `model` doc slot, and keeps `state(initial, transform)` as a `.value`
|
|
39
|
+
cell routed onto the ambient scope; the runtime needs no magic.
|
|
13
40
|
|
|
14
41
|
`transform` is an optional coercion gate on the write path: every `.value =`
|
|
15
42
|
runs it and stores what it returns, with `previous` for clamp-relative writes or
|
|
@@ -18,16 +45,7 @@ mirror of `computed`'s write-through `set` — here the value lives in this cell
|
|
|
18
45
|
the gate *returns* what to store rather than writing an external target. The
|
|
19
46
|
construction `initial` is taken verbatim; the gate runs on writes only.
|
|
20
47
|
*/
|
|
21
|
-
|
|
22
|
-
`State<Foo | undefined>`. Without it `state<Foo>(undefined)` is an arity/assign
|
|
23
|
-
error and `state(undefined)` infers `T = undefined` (every `.value` access then
|
|
24
|
-
narrows to `never`). */
|
|
25
|
-
export function state<T>(): State<T | undefined>
|
|
26
|
-
export function state<T>(initial: T, transform?: (next: T, previous: T) => T): State<T>
|
|
27
|
-
export function state<T>(
|
|
28
|
-
initial?: T,
|
|
29
|
-
transform?: (next: T, previous: T) => T,
|
|
30
|
-
): State<T | undefined> {
|
|
48
|
+
function stateCell<T>(initial?: T, transform?: (next: T, previous: T) => T): State<T | undefined> {
|
|
31
49
|
const node = createSignalNode(initial)
|
|
32
50
|
return {
|
|
33
51
|
get value(): T | undefined {
|
|
@@ -38,3 +56,16 @@ export function state<T>(
|
|
|
38
56
|
},
|
|
39
57
|
}
|
|
40
58
|
}
|
|
59
|
+
|
|
60
|
+
const stateFn = stateCell as StateFn
|
|
61
|
+
stateFn.linked = linked
|
|
62
|
+
stateFn.computed = computed
|
|
63
|
+
/* `.share`/`.shared` route onto the ambient scope, read/written at call time so the
|
|
64
|
+
state ↔ scope module cycle resolves. */
|
|
65
|
+
stateFn.share = (key: string, value: unknown): void => {
|
|
66
|
+
scope().share(key, value)
|
|
67
|
+
}
|
|
68
|
+
stateFn.shared = <T>(key: string): T | undefined => scope().shared<T>(key)
|
|
69
|
+
|
|
70
|
+
// @documentation reactive-state
|
|
71
|
+
export const state = stateFn
|
package/src/lib/ui/sync.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PATCH_BUS } from './runtime/PATCH_BUS.ts'
|
|
2
2
|
import type { Doc } from './runtime/types/Doc.ts'
|
|
3
|
+
import type { Patch } from './runtime/types/Patch.ts'
|
|
3
4
|
import type { SyncTransport } from './types/SyncTransport.ts'
|
|
4
5
|
|
|
5
6
|
/*
|
|
@@ -17,20 +18,25 @@ this core). Returns a disposer.
|
|
|
17
18
|
*/
|
|
18
19
|
// @documentation plumbing
|
|
19
20
|
export function sync(doc: Doc, transport: SyncTransport): () => void {
|
|
20
|
-
/*
|
|
21
|
-
|
|
21
|
+
/* The specific inbound patches currently being applied — their own bus echo must not be
|
|
22
|
+
re-sent. Tracked by IDENTITY (not a blanket `applying` flag): `doc.apply` emits the
|
|
23
|
+
patch on the bus inside its batch, then flushes effects on batch exit while still
|
|
24
|
+
synchronously inside this apply. A local effect that writes the doc in reaction emits
|
|
25
|
+
a DIFFERENT patch during that flush — a genuine new local change peers need — which a
|
|
26
|
+
blanket flag would wrongly suppress, diverging peers permanently. */
|
|
27
|
+
const applyingPatches = new Set<Patch>()
|
|
22
28
|
|
|
23
29
|
const unsubscribeInbound = transport.subscribe((patch) => {
|
|
24
|
-
|
|
30
|
+
applyingPatches.add(patch)
|
|
25
31
|
try {
|
|
26
32
|
doc.apply(patch)
|
|
27
33
|
} finally {
|
|
28
|
-
|
|
34
|
+
applyingPatches.delete(patch)
|
|
29
35
|
}
|
|
30
36
|
})
|
|
31
37
|
|
|
32
38
|
const unsubscribeBus = PATCH_BUS.subscribe((event) => {
|
|
33
|
-
if (event.doc === doc && !
|
|
39
|
+
if (event.doc === doc && !applyingPatches.has(event.patch)) {
|
|
34
40
|
transport.send(event.patch)
|
|
35
41
|
}
|
|
36
42
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { encodeRefJson } from '../shared/encodeRefJson.ts'
|
|
2
|
-
import type {
|
|
2
|
+
import type { ResumeEntry } from './runtime/RESUME.ts'
|
|
3
3
|
|
|
4
4
|
/* ref-json-encode an await-resume entry, or `undefined` if it can't be serialized.
|
|
5
5
|
encodeRefJson is total (cycles become back-references, functions fold to undefined),
|
|
@@ -7,10 +7,7 @@ import type { DeferMarker, ResumeEntry } from './runtime/RESUME.ts'
|
|
|
7
7
|
entry and warn, so the client re-runs that one branch's promise while every other
|
|
8
8
|
branch stays seeded. Shared by the streaming (`renderToStream`) and buffered/seed
|
|
9
9
|
(`resumeSeedScript`) paths so the serialize-or-refetch policy lives in one place. */
|
|
10
|
-
export function tryEncodeResume(
|
|
11
|
-
entry: ResumeEntry | DeferMarker,
|
|
12
|
-
id: number | string,
|
|
13
|
-
): string | undefined {
|
|
10
|
+
export function tryEncodeResume(entry: ResumeEntry, id: number | string): string | undefined {
|
|
14
11
|
try {
|
|
15
12
|
return encodeRefJson(entry)
|
|
16
13
|
} catch (cause) {
|
|
@@ -7,18 +7,22 @@ import type { SyncTransport } from './SyncTransport.ts'
|
|
|
7
7
|
|
|
8
8
|
/*
|
|
9
9
|
A lexical scope: the unit that owns a region's reactive data, its lifetime, and
|
|
10
|
-
the capabilities applied to it.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
the capabilities applied to it. This is the INTERNAL shape — the compiler's lowering
|
|
11
|
+
host and the runtime object; it is no longer the author surface. The author reactive
|
|
12
|
+
surface is the imported `state`/`state.linked`/`state.computed`/`effect` (see
|
|
13
|
+
`state.ts`); the compiler resolves those import bindings and lowers each onto this
|
|
14
|
+
scope (`$$scope().derive`/`.linked`/`.effect`, `state.share`/`.shared` → `share`/
|
|
15
|
+
`shared`). The data surface MIRRORS `Doc` (read/replace/add/remove/derive/apply/
|
|
16
|
+
snapshot) so the compiler can target a scope as a component's data binding directly;
|
|
17
|
+
it nests (`child`/`root`) and passes values down the tree as context (`share`/`shared`).
|
|
15
18
|
|
|
19
|
+
The reactive primitives and capabilities remain on this internal shape because the
|
|
20
|
+
lowered runtime calls them (`$$scope().linked(...)`, generated undo/persist) — they are
|
|
21
|
+
withdrawn from the AUTHOR-facing public surface (docs/examples), not the runtime object.
|
|
16
22
|
Capabilities route where the scope's changes go: `record()` to an undo journal,
|
|
17
|
-
`persist()` to durable storage, `broadcast()` to peers — declared once, then
|
|
18
|
-
`
|
|
19
|
-
|
|
20
|
-
but only for generated binding code and the SSR strip (tagged plumbing). The
|
|
21
|
-
`history`/`persist`/`sync` helpers it composes are internal.
|
|
23
|
+
`persist()` to durable storage, `broadcast()` to peers — declared once, then `undo`/
|
|
24
|
+
`redo` act on a recorded scope. The `history`/`persist`/`sync` helpers it composes are
|
|
25
|
+
internal.
|
|
22
26
|
*/
|
|
23
27
|
export type Scope = {
|
|
24
28
|
readonly id: string
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { cache } from '../shared/cache.ts'
|
|
2
|
+
import { REMOTE_FUNCTION } from '../shared/REMOTE_FUNCTION.ts'
|
|
3
|
+
import type { CacheOnContext } from '../shared/types/CacheOnContext.ts'
|
|
4
|
+
import type { RemoteFunction } from '../shared/types/RemoteFunction.ts'
|
|
5
|
+
import type { Subscribable } from '../shared/types/Subscribable.ts'
|
|
6
|
+
import { effect } from './effect.ts'
|
|
7
|
+
import { generationGuard } from './runtime/generationGuard.ts'
|
|
8
|
+
import type { EffectResult } from './runtime/types/EffectResult.ts'
|
|
9
|
+
import type { State } from './runtime/types/State.ts'
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
The single reaction primitive: `watch(source, handler)` names its trigger and runs
|
|
13
|
+
`handler` with the source's new value whenever it changes. It unifies three
|
|
14
|
+
previously-separate things — the author `effect`, `socket.on`, and `cache.on` —
|
|
15
|
+
and it is also the compiler's binding primitive (emitted as `$$watch(thunk)` for
|
|
16
|
+
`{expr}` / `class:` / `bind:*`). Client-only: SSR-inert here and stripped by the
|
|
17
|
+
compiler. Returns a scope-tied disposer.
|
|
18
|
+
|
|
19
|
+
Instance sugar mirrors the global for the two subscribable/rpc sources (the other
|
|
20
|
+
sources have no instance home): `socket.watch(handler)` ≡ `watch(socket, handler)`, and
|
|
21
|
+
`getUser.watch(handler)` / `getUser.watch(args, handler)` ≡ `watch(getUser, …)`. The
|
|
22
|
+
method is client-attached (socketProxy / remoteProxy) so this ui primitive never rides
|
|
23
|
+
into a server bundle; server-side it is an inert no-op. Unlike bare `watch(…)` — which
|
|
24
|
+
the SSR back-end strips — a `.watch(…)` member call survives to the server and relies on
|
|
25
|
+
that inert stub.
|
|
26
|
+
|
|
27
|
+
Sources (discriminated at runtime, monomorphic per branch):
|
|
28
|
+
watch(thunk) // compiler binding form — auto-tracked, == effect(thunk)
|
|
29
|
+
watch(count, n => …) // a state cell → handler(newValue)
|
|
30
|
+
watch([a, b], vals => …) // multiple cells → fires on any change
|
|
31
|
+
watch(socket, frame => …) // a subscribable → handler per frame (cache.on loop)
|
|
32
|
+
watch(getUser, user => …) // an rpc → runs the smart read, handler(resolved value)
|
|
33
|
+
watch(getUser, args, user => …) // an rpc with args
|
|
34
|
+
*/
|
|
35
|
+
// @documentation reactive-state
|
|
36
|
+
export function watch(thunk: () => EffectResult): () => void
|
|
37
|
+
export function watch<T>(source: State<T>, handler: (value: T) => void): () => void
|
|
38
|
+
export function watch(
|
|
39
|
+
sources: ReadonlyArray<State<unknown>>,
|
|
40
|
+
handler: (values: unknown[]) => void,
|
|
41
|
+
): () => void
|
|
42
|
+
export function watch<Frame>(
|
|
43
|
+
source: Subscribable<Frame>,
|
|
44
|
+
handler: (frame: Frame, context: CacheOnContext) => void | Promise<void>,
|
|
45
|
+
): () => void
|
|
46
|
+
export function watch<Args, Return>(
|
|
47
|
+
fn: RemoteFunction<Args, Return>,
|
|
48
|
+
handler: (value: Return) => void,
|
|
49
|
+
): () => void
|
|
50
|
+
export function watch<Args, Return>(
|
|
51
|
+
fn: RemoteFunction<Args, Return>,
|
|
52
|
+
args: Args,
|
|
53
|
+
handler: (value: Return) => void,
|
|
54
|
+
): () => void
|
|
55
|
+
export function watch(
|
|
56
|
+
source: unknown,
|
|
57
|
+
argsOrHandler?: unknown,
|
|
58
|
+
maybeHandler?: unknown,
|
|
59
|
+
): () => void {
|
|
60
|
+
/* Client lifecycle, exactly like the effect it wraps: no runtime window guard (that would
|
|
61
|
+
also silence the compiler's generated bindings, which build on the client where the test
|
|
62
|
+
DOM sets no global `window`). SSR-safety comes from the compiler stripping author
|
|
63
|
+
`watch(...)` calls, and the subscribable branch's own server guard (cache.on). */
|
|
64
|
+
/* Compiler binding form watch(thunk): a bare auto-tracked effect (== today's $$effect). */
|
|
65
|
+
if (argsOrHandler === undefined) {
|
|
66
|
+
return effect(source as () => EffectResult)
|
|
67
|
+
}
|
|
68
|
+
/* watch(fn, args, handler): an rpc selector with explicit args. */
|
|
69
|
+
if (maybeHandler !== undefined) {
|
|
70
|
+
return reactToRpc(source, argsOrHandler, maybeHandler as (value: unknown) => void)
|
|
71
|
+
}
|
|
72
|
+
const handler = argsOrHandler as (value: unknown, context?: CacheOnContext) => void
|
|
73
|
+
/* A subscribable (socket / stream): per-frame delivery with reconnect-replay — the
|
|
74
|
+
existing cache.on loop is the single implementation this branch delegates to. */
|
|
75
|
+
if (isSubscribable(source)) {
|
|
76
|
+
return cache.on(
|
|
77
|
+
source as Subscribable<unknown>,
|
|
78
|
+
handler as (frame: unknown, context: CacheOnContext) => void | Promise<void>,
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
/* An rpc without args → run the smart read reactively, pipe its value to the handler. A
|
|
82
|
+
RemoteFunction is a callable, so accept `function` too — a state cell is a branded-free
|
|
83
|
+
object and never matches, and a streaming rpc was already caught by isSubscribable above. */
|
|
84
|
+
if (
|
|
85
|
+
source !== null &&
|
|
86
|
+
(typeof source === 'object' || typeof source === 'function') &&
|
|
87
|
+
REMOTE_FUNCTION in source
|
|
88
|
+
) {
|
|
89
|
+
return reactToRpc(source, undefined, handler)
|
|
90
|
+
}
|
|
91
|
+
/* Multiple cells → fire on any change; hand the handler the current values. */
|
|
92
|
+
if (Array.isArray(source)) {
|
|
93
|
+
const cells = source as ReadonlyArray<State<unknown>>
|
|
94
|
+
return effect(() => {
|
|
95
|
+
const values = cells.map((cell) => cell.value)
|
|
96
|
+
handler(values)
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
/* A single state cell → handler(newValue) on change. */
|
|
100
|
+
const cell = source as State<unknown>
|
|
101
|
+
return effect(() => {
|
|
102
|
+
handler(cell.value)
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* True for a socket / rpc stream — anything async-iterable. */
|
|
107
|
+
function isSubscribable(source: unknown): boolean {
|
|
108
|
+
return (
|
|
109
|
+
source !== null &&
|
|
110
|
+
(typeof source === 'object' || typeof source === 'function') &&
|
|
111
|
+
typeof (source as { [Symbol.asyncIterator]?: unknown })[Symbol.asyncIterator] === 'function'
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/*
|
|
116
|
+
Reacts to an rpc's cached value: an effect that runs the smart read (subscribing
|
|
117
|
+
its key synchronously, so the effect re-runs when the value changes — a refresh,
|
|
118
|
+
a patch, an invalidate) and pipes the resolved value to the handler. Triggers the
|
|
119
|
+
read, unlike peek: `watch` observes a live query, so it keeps it flowing.
|
|
120
|
+
*/
|
|
121
|
+
function reactToRpc(fn: unknown, args: unknown, handler: (value: unknown) => void): () => void {
|
|
122
|
+
const call = fn as (args: unknown) => Promise<unknown>
|
|
123
|
+
/* The bare call routes through cache.read (cache-managed flight, not scope-abortable), so
|
|
124
|
+
a slow flight can settle AFTER a faster re-run's flight OR after the owner tears down.
|
|
125
|
+
Guard the handler on the generation so only the current flight's value lands and a
|
|
126
|
+
post-teardown settle is dropped — a re-run renews, teardown bumps (both via the shared
|
|
127
|
+
generationGuard). */
|
|
128
|
+
const guard = generationGuard()
|
|
129
|
+
return effect(() => {
|
|
130
|
+
const generation = guard.renew()
|
|
131
|
+
void Promise.resolve(call(args)).then(
|
|
132
|
+
(value) => {
|
|
133
|
+
if (guard.live(generation)) {
|
|
134
|
+
handler(value)
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
() => undefined,
|
|
138
|
+
)
|
|
139
|
+
})
|
|
140
|
+
}
|
package/src/serverEntry.ts
CHANGED
|
@@ -29,12 +29,12 @@ import { loadEnvFromBinaryDir } from './lib/cli/loadEnvFromBinaryDir.ts'
|
|
|
29
29
|
import { createServer } from './lib/server/runtime/createServer.ts'
|
|
30
30
|
import { requestContext } from './lib/server/runtime/requestContext.ts'
|
|
31
31
|
import { resolvePageSnapshot } from './lib/server/runtime/resolvePageSnapshot.ts'
|
|
32
|
+
import { cacheStoreSlot } from './lib/shared/cacheStoreSlot.ts'
|
|
32
33
|
import { createCacheStore } from './lib/shared/createCacheStore.ts'
|
|
34
|
+
import { globalCacheStoreSlot } from './lib/shared/globalCacheStoreSlot.ts'
|
|
33
35
|
import { loadEnvFromDataDir } from './lib/shared/loadEnvFromDataDir.ts'
|
|
36
|
+
import { pageSlot } from './lib/shared/pageSlot.ts'
|
|
34
37
|
import { runningAsStandaloneBinary } from './lib/shared/runningAsStandaloneBinary.ts'
|
|
35
|
-
import { setCacheStoreResolver } from './lib/shared/setCacheStoreResolver.ts'
|
|
36
|
-
import { setGlobalCacheStoreResolver } from './lib/shared/setGlobalCacheStoreResolver.ts'
|
|
37
|
-
import { setPageResolver } from './lib/shared/setPageResolver.ts'
|
|
38
38
|
|
|
39
39
|
/*
|
|
40
40
|
Resolve config into process.env before anything reads it (createServer reads
|
|
@@ -65,16 +65,16 @@ await import('./_virtual/config.ts')
|
|
|
65
65
|
// In a bundle, tie this server's life to the launcher's (no-op standalone).
|
|
66
66
|
exitWithParent()
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
cacheStoreSlot.resolver = () => requestContext.getStore()?.cache
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
pageSlot.resolver = resolvePageSnapshot
|
|
71
71
|
|
|
72
72
|
/*
|
|
73
73
|
Process-level store for cache(fn, { global: true }) — one per server process,
|
|
74
74
|
outlives every request so memoised external calls are shared across them.
|
|
75
75
|
*/
|
|
76
76
|
const globalCacheStore = createCacheStore()
|
|
77
|
-
|
|
77
|
+
globalCacheStoreSlot.resolver = () => globalCacheStore
|
|
78
78
|
|
|
79
79
|
await createServer({
|
|
80
80
|
pages,
|
package/template/CLAUDE.md
CHANGED
|
@@ -14,7 +14,7 @@ at `node_modules/@abide/abide/README.md`.
|
|
|
14
14
|
## Conventions (see AGENTS.md for the full list)
|
|
15
15
|
|
|
16
16
|
- One export per file, named after the file. No barrels — import each name by its
|
|
17
|
-
own path (`@abide/abide/server/GET`, `@abide/abide/shared/
|
|
17
|
+
own path (`@abide/abide/server/GET`, `@abide/abide/shared/refresh`, …).
|
|
18
18
|
- RPCs live in `src/server/rpc/<name>.ts`; sockets in `src/server/sockets/`;
|
|
19
19
|
pages are `**/page.abide`, layouts `**/layout.abide`.
|
|
20
20
|
- Generated types land in `src/.abide/` — do not hand-edit them; run `abide dev`/
|
package/template/package.json
CHANGED
|
@@ -25,9 +25,9 @@ dedupe). Other helpers are siblings, one per file: `abide/server/error`,
|
|
|
25
25
|
`abide/server/redirect`, `abide/server/sse`, `abide/server/jsonl`.
|
|
26
26
|
|
|
27
27
|
Every rpc value also exposes `.raw(args?)` (returns the underlying
|
|
28
|
-
`Response`)
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
`Response`) for callers that need headers or status. A streaming rpc — a
|
|
29
|
+
jsonl/sse handler — returns a `Subscribable` over the frames from its bare
|
|
30
|
+
call (`fn(args)`); iterate it with `for await` or react to it with `watch(fn(args), frame => …)`.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
import { GET } from '@abide/abide/server/GET'
|
|
@@ -5,15 +5,15 @@ page.abide mounts at that folder's URL; the root layout.abide wraps it.
|
|
|
5
5
|
|
|
6
6
|
The blocking await-block below (the `then` clause sits in the `{#await}` head)
|
|
7
7
|
resolves on the server during SSR and renders inline — no pending placeholder. The
|
|
8
|
+
bare rpc call IS the smart read: cached, coalesced, and reactive by default. The
|
|
8
9
|
decoded body is captured into the per-request cache, serialized into the HTML, and
|
|
9
10
|
replayed on the client during hydration with no second fetch. A `{:then}` *branch*
|
|
10
11
|
instead would stream the resolution in out of order.
|
|
11
12
|
*/
|
|
12
|
-
import { cache } from '@abide/abide/shared/cache'
|
|
13
13
|
import { getHello } from '$server/rpc/getHello.ts'
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
|
-
{#await
|
|
16
|
+
{#await getHello() then hello}
|
|
17
17
|
<h1>{hello.message}</h1>
|
|
18
18
|
{/await}
|
|
19
19
|
<p>Edit <code>src/ui/pages/page.abide</code> and the page hot-reloads.</p>
|
|
@@ -5,7 +5,7 @@ same wiring as `abide start`, no fixtures) and hands back the whole surface:
|
|
|
5
5
|
app.fetch(path) — pages and raw HTTP, origin + mount base resolved
|
|
6
6
|
app.rpc.<name>(args) — RPCs over the real pipeline (CSRF, cookies, base),
|
|
7
7
|
typed and decoded; .raw(args) for the Response
|
|
8
|
-
app.sockets.<name> — a live Socket: iterate it, .
|
|
8
|
+
app.sockets.<name> — a live Socket: iterate it (for await), .peek(), .broadcast(m)
|
|
9
9
|
app.health() — the /__abide/health payload
|
|
10
10
|
|
|
11
11
|
`app.rpc` / `app.sockets` are typed from your own RPCs and sockets — generated
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { parseBoundedEnvInt } from '../shared/parseBoundedEnvInt.ts'
|
|
2
|
-
import { createReachable } from './runtime/createReachable.ts'
|
|
3
|
-
|
|
4
|
-
/*
|
|
5
|
-
Server-only outbound reachability for an external host. `await reachable(host)`
|
|
6
|
-
HEADs the host's origin: the first call awaits a real probe (faithful — a down
|
|
7
|
-
host costs the full timeout, an up host one handshake) and starts a background
|
|
8
|
-
poll that re-probes every TTL, so every later call resolves instantly off the
|
|
9
|
-
warm value, fresh within one TTL. A down host going down is caught within
|
|
10
|
-
~failureLimit polls; recovery flips it back automatically.
|
|
11
|
-
|
|
12
|
-
if (!(await reachable('api.example.com'))) return error(503)
|
|
13
|
-
|
|
14
|
-
A bare host defaults to https; pass an explicit http://… for a non-TLS host.
|
|
15
|
-
Answers "can I connect to this host," NOT "is my endpoint healthy": any
|
|
16
|
-
completed HTTP response (even 4xx/5xx, even a 405 to HEAD) counts as reachable;
|
|
17
|
-
only a connection failure or timeout reads as unreachable. There is no ambient
|
|
18
|
-
server-side connectivity signal, so this is the honest way to fail a doomed
|
|
19
|
-
outbound call fast — see online() for the inbound/client-reported counterpart.
|
|
20
|
-
|
|
21
|
-
ABIDE_REACHABLE_TTL (poll cadence / freshness, ms) and ABIDE_REACHABLE_TIMEOUT
|
|
22
|
-
(per-HEAD bound, ms) tune the defaults; the timeout is deliberately generous so
|
|
23
|
-
a healthy-but-distant host over a slow link is not mis-read as down.
|
|
24
|
-
*/
|
|
25
|
-
const TTL_MS = parseBoundedEnvInt(process.env.ABIDE_REACHABLE_TTL, 1_000, 600_000) ?? 30_000
|
|
26
|
-
const TIMEOUT_MS = parseBoundedEnvInt(process.env.ABIDE_REACHABLE_TIMEOUT, 100, 60_000) ?? 3_000
|
|
27
|
-
/* Stop polling a host nobody has read in a few TTLs; the next read restarts it cold. */
|
|
28
|
-
const IDLE_MS = TTL_MS * 3
|
|
29
|
-
|
|
30
|
-
/* Status-agnostic HEAD: a completed response proves connectivity; reject/timeout does not. */
|
|
31
|
-
async function probeOrigin(origin: string): Promise<boolean> {
|
|
32
|
-
try {
|
|
33
|
-
await fetch(origin, { method: 'HEAD', signal: AbortSignal.timeout(TIMEOUT_MS) })
|
|
34
|
-
return true
|
|
35
|
-
} catch {
|
|
36
|
-
return false
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// @documentation observability
|
|
41
|
-
export const reachable = createReachable({
|
|
42
|
-
probe: probeOrigin,
|
|
43
|
-
intervalMs: TTL_MS,
|
|
44
|
-
idleMs: IDLE_MS,
|
|
45
|
-
}).reachable
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { ClientFlags } from '../../../shared/types/ClientFlags.ts'
|
|
2
|
-
import type { TailHooks } from '../../../shared/types/TailHooks.ts'
|
|
3
|
-
|
|
4
|
-
/*
|
|
5
|
-
Bidirectional named broadcast primitive. Declared once with `socket<T>()`
|
|
6
|
-
inside a file under `src/server/sockets/`; the same import resolves to a server-side
|
|
7
|
-
fan-out and a client-side ws proxy by build target. Iterating the socket
|
|
8
|
-
is the live stream — no replay. `.tail(count)` opens a subscription seeded
|
|
9
|
-
with the last `count` frames of the retained tail (declared via
|
|
10
|
-
`{ tail: n }`; no-arg = the whole retained tail) before going live — the
|
|
11
|
-
optional Subscribable retention capability the reactive `tail()` consumer
|
|
12
|
-
seeds from. `publish` is isomorphic: server code publishes in-process
|
|
13
|
-
and fans out to remote subscribers; client code sends a `pub` frame the
|
|
14
|
-
dispatcher validates against the topic's `clientPublish` flag. `clients`
|
|
15
|
-
exposes which adapter surfaces (browser / mcp / cli) advertise this
|
|
16
|
-
socket.
|
|
17
|
-
*/
|
|
18
|
-
export interface Socket<T> extends AsyncIterable<T> {
|
|
19
|
-
readonly name: string
|
|
20
|
-
readonly clients: ClientFlags
|
|
21
|
-
publish(message: T): void
|
|
22
|
-
tail(count?: number, hooks?: TailHooks): AsyncIterable<T>
|
|
23
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Brand on the invoker cache() returns; its property value is the wrapped fn,
|
|
3
|
-
so inspection shows what a wrapper wraps. Detection is certain (set by us,
|
|
4
|
-
never heuristic), which is why misuse throws instead of warning: a wrapper
|
|
5
|
-
used as a selector matches nothing (no url/method, no producer id), and
|
|
6
|
-
without detection a re-wrapped wrapper would silently downgrade a remote to
|
|
7
|
-
an anonymous producer (no url/method, no shared key, no SSR snapshot).
|
|
8
|
-
*/
|
|
9
|
-
export const CACHE_WRAPPED: unique symbol = Symbol('abide.cacheWrapped')
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { createResolverSlot } from './createResolverSlot.ts'
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
The mount-base slot/resolver/reader bundle. The server entry installs an
|
|
5
|
-
APP_URL-derived resolver at boot; the client entry one reading
|
|
6
|
-
window.__SSR__.base. No lazy fallback creator — `fallback` is a plain string set
|
|
7
|
-
directly by isolated tests, and basePath() supplies the '' default. baseSlot /
|
|
8
|
-
basePath re-export the slot and reader; setBaseResolver the setter.
|
|
9
|
-
*/
|
|
10
|
-
export const baseResolver = createResolverSlot<string>()
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { cacheKeyStore } from './cacheKeyStore.ts'
|
|
2
|
-
|
|
3
|
-
/* The store key behind a cache() read's promise, or undefined if it carries none
|
|
4
|
-
(a producer read, a raw Response read, or a non-cache promise). */
|
|
5
|
-
export function cacheKeyOf(promise: Promise<unknown>): string | undefined {
|
|
6
|
-
return cacheKeyStore.get(promise)
|
|
7
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
WeakMap recording the cache key behind a `cache()` read's returned promise, so a
|
|
3
|
-
consumer holding only the promise can recover its key. Mirrors remoteMetaStore (which
|
|
4
|
-
records the synthesized Request the same way). The SSR resume path uses it to decide
|
|
5
|
-
whether a `{#await cache()}` value is a large cache-backed read it can defer — shipping a
|
|
6
|
-
`{ defer, key }` marker instead of the value. Collected with the promise.
|
|
7
|
-
*/
|
|
8
|
-
export const cacheKeyStore = new WeakMap<Promise<unknown>, string>()
|