@abide/abide 0.46.0 → 0.48.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 +370 -320
- package/CHANGELOG.md +94 -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 +5 -0
- package/src/lib/server/runtime/buildCacheSnapshot.ts +6 -6
- 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/runtime/types/InspectorCacheEntry.ts +1 -1
- package/src/lib/server/runtime/types/InspectorCacheSnapshot.ts +4 -4
- package/src/lib/server/runtime/types/InspectorContext.ts +1 -1
- 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 +522 -117
- package/src/lib/shared/cacheEntryFromSnapshot.ts +3 -22
- package/src/lib/shared/cacheStoreSlot.ts +9 -5
- package/src/lib/shared/cacheStores.ts +3 -3
- 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/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/sharedCacheStore.ts +14 -0
- package/src/lib/shared/sharedCacheStoreSlot.ts +10 -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 +23 -11
- package/src/lib/shared/types/CacheSnapshotEntry.ts +0 -5
- package/src/lib/shared/types/CacheStats.ts +1 -1
- 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 +36 -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 +8 -8
- 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 +48 -16
- 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/parseTemplate.ts +1 -14
- 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/compile/types/TemplateNode.ts +0 -5
- 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 +19 -83
- 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/dom/mountChild.ts +2 -5
- package/src/lib/ui/dom/mountRange.ts +0 -75
- 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 +17 -31
- 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 +13 -11
- 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/DEFER_MIN_ARRAY_LENGTH.ts +0 -14
- 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/globalCacheStore.ts +0 -15
- package/src/lib/shared/globalCacheStoreResolver.ts +0 -12
- package/src/lib/shared/globalCacheStoreSlot.ts +0 -9
- 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 -36
- package/src/lib/ui/dom/firstElementBetween.ts +0 -14
- package/src/lib/ui/matchRoute.ts +0 -106
- package/src/lib/ui/runtime/scheduleWake.ts +0 -28
- package/src/lib/ui/runtime/whenIdle.ts +0 -21
- package/src/lib/ui/runtime/whenVisible.ts +0 -105
- 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,13 +1,14 @@
|
|
|
1
|
+
import { attachSocketSelectorMethods } from '../../shared/attachSocketSelectorMethods.ts'
|
|
1
2
|
import { createPushIterator } from '../../shared/createPushIterator.ts'
|
|
2
3
|
import { encodeRefJson } from '../../shared/encodeRefJson.ts'
|
|
3
4
|
import { resolveClientFlags } from '../../shared/resolveClientFlags.ts'
|
|
4
5
|
import { socketTapSlot } from '../../shared/socketTapSlot.ts'
|
|
6
|
+
import type { Socket } from '../../shared/types/Socket.ts'
|
|
7
|
+
import type { SocketServerFrame } from '../../shared/types/SocketServerFrame.ts'
|
|
5
8
|
import type { TailHooks } from '../../shared/types/TailHooks.ts'
|
|
6
9
|
import { getActiveServer } from '../runtime/getActiveServer.ts'
|
|
7
10
|
import { registerSocket } from './registerSocket.ts'
|
|
8
|
-
import type { Socket } from './types/Socket.ts'
|
|
9
11
|
import type { SocketOptions } from './types/SocketOptions.ts'
|
|
10
|
-
import type { SocketServerFrame } from './types/SocketServerFrame.ts'
|
|
11
12
|
|
|
12
13
|
/*
|
|
13
14
|
Server-side construction of a Socket. The bundler rewrites every
|
|
@@ -23,14 +24,18 @@ is the live stream — no replay. `chat.tail(count)` opens a subscription
|
|
|
23
24
|
seeded with the last `count` retained frames (no-arg = the whole
|
|
24
25
|
retained tail, clamped to the declared `tail` size). When `ttl` is set,
|
|
25
26
|
retained frames older than `ttl` ms are evicted lazily on every
|
|
26
|
-
read/append — no timer runs in the background. `chat.
|
|
27
|
+
read/append — no timer runs in the background. `chat.broadcast(m)` is isomorphic —
|
|
27
28
|
called server-side it both notifies in-process iterators and broadcasts
|
|
28
29
|
to remote subscribers; called client-side (via socketProxy) it sends a
|
|
29
30
|
`pub` frame the dispatcher validates and forwards.
|
|
30
31
|
*/
|
|
31
32
|
// @documentation plumbing
|
|
32
33
|
export function defineSocket<T>(name: string, opts: SocketOptions = {}): Socket<T> {
|
|
33
|
-
|
|
34
|
+
/* Retention defaults to 1: the socket keeps its last frame so a late joiner, a
|
|
35
|
+
reconnect, and `.peek()` all have the current value to seed from. Bare iteration
|
|
36
|
+
still replays nothing (iterate(0) below) — a real-time `watch(socket, …)` reaction
|
|
37
|
+
must see only live frames, never re-process the retained one. `tail: 0` opts out. */
|
|
38
|
+
const retention = opts.tail ?? 1
|
|
34
39
|
const ttl = opts.ttl
|
|
35
40
|
const schema = opts.schema
|
|
36
41
|
/*
|
|
@@ -150,8 +155,10 @@ export function defineSocket<T>(name: string, opts: SocketOptions = {}): Socket<
|
|
|
150
155
|
}
|
|
151
156
|
})
|
|
152
157
|
pruneExpired(Date.now())
|
|
158
|
+
/* Floor: a fractional count (`?tail=1.5` over REST) must not
|
|
159
|
+
produce a fractional buffer index. */
|
|
153
160
|
const replayCount =
|
|
154
|
-
replay === 'all' ? buffer.length : Math.min(replay, buffer.length)
|
|
161
|
+
replay === 'all' ? buffer.length : Math.min(Math.floor(replay), buffer.length)
|
|
155
162
|
if (replayCount > 0) {
|
|
156
163
|
const start = buffer.length - replayCount
|
|
157
164
|
for (let index = start; index < buffer.length; index++) {
|
|
@@ -168,13 +175,25 @@ export function defineSocket<T>(name: string, opts: SocketOptions = {}): Socket<
|
|
|
168
175
|
}
|
|
169
176
|
}
|
|
170
177
|
|
|
171
|
-
const self
|
|
178
|
+
const self = {
|
|
172
179
|
name,
|
|
173
180
|
clients,
|
|
174
|
-
publish
|
|
181
|
+
/* `broadcast` is the public fan-out; `publish` remains the internal function name. */
|
|
182
|
+
broadcast: publish,
|
|
175
183
|
tail: (count?: number, hooks?: TailHooks) => iterate(count ?? 'all', hooks),
|
|
184
|
+
/* The latest retained frame (after ttl pruning), or undefined when none is held. */
|
|
185
|
+
peek: () => {
|
|
186
|
+
pruneExpired(Date.now())
|
|
187
|
+
return buffer.length > 0 ? (buffer[buffer.length - 1] as BufferEntry).value : undefined
|
|
188
|
+
},
|
|
189
|
+
/* No-op on the server: it is the source of truth, so there is nothing to re-pull. */
|
|
190
|
+
refresh: () => undefined,
|
|
191
|
+
/* Inert on the server: reaction is a client-only concern, and an author `socket.watch(…)`
|
|
192
|
+
that survives the SSR effect-strip must be a safe no-op here. Returns a disposer. */
|
|
193
|
+
watch: () => () => {},
|
|
176
194
|
[Symbol.asyncIterator]: () => iterate(0)[Symbol.asyncIterator](),
|
|
177
195
|
}
|
|
196
|
+
attachSocketSelectorMethods(self)
|
|
178
197
|
registerSocket({
|
|
179
198
|
socket: self as Socket<unknown>,
|
|
180
199
|
allowClientPublish: opts.clientPublish ?? false,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ClientFlags } from '../../../shared/types/ClientFlags.ts'
|
|
2
|
+
import type { Socket } from '../../../shared/types/Socket.ts'
|
|
2
3
|
import type { StandardSchemaV1 } from '../../../shared/types/StandardSchemaV1.ts'
|
|
3
|
-
import type { Socket } from './Socket.ts'
|
|
4
4
|
|
|
5
5
|
/*
|
|
6
6
|
Per-socket registry record. The Socket itself stays uniform between
|
package/src/lib/server/sse.ts
CHANGED
|
@@ -35,7 +35,7 @@ const KEEPALIVE_INTERVAL_MS = 15000
|
|
|
35
35
|
export function sse<Frame>(
|
|
36
36
|
iterable: AsyncIterable<Frame>,
|
|
37
37
|
init?: ResponseInit,
|
|
38
|
-
): TypedResponse<Frame
|
|
38
|
+
): TypedResponse<AsyncIterable<Frame>> {
|
|
39
39
|
const body = streamFromIterator(iterable, {
|
|
40
40
|
encodeFrame: (value) => `data: ${JSON.stringify(value)}\n\n`,
|
|
41
41
|
encodeError: (message) => sseErrorFrame.encode(message),
|
|
@@ -50,5 +50,5 @@ export function sse<Frame>(
|
|
|
50
50
|
'X-Content-Type-Options': 'nosniff',
|
|
51
51
|
Connection: 'keep-alive',
|
|
52
52
|
}),
|
|
53
|
-
) as TypedResponse<Frame
|
|
53
|
+
) as TypedResponse<AsyncIterable<Frame>>
|
|
54
54
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The sentinel `bodyValueForKind` returns for the `streaming` and `binary` body kinds —
|
|
3
|
+
those whose handling is side-specific (the live read throws on streaming and blobs on
|
|
4
|
+
binary; the warm read has no synchronous equivalent), so the caller branches on it
|
|
5
|
+
rather than receiving a directly-usable value. A `unique symbol` so a body value can
|
|
6
|
+
never collide with it.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFER: unique symbol = Symbol('defer')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { cacheStoreSlot } from './cacheStoreSlot.ts'
|
|
2
2
|
import type { CacheStore } from './types/CacheStore.ts'
|
|
3
3
|
|
|
4
4
|
/*
|
|
@@ -7,5 +7,5 @@ lazily-created fallback when none is registered (so isolated tests work). The
|
|
|
7
7
|
fallback creator guarantees a value, hence the non-null assertion.
|
|
8
8
|
*/
|
|
9
9
|
export function activeCacheStore(): CacheStore {
|
|
10
|
-
return
|
|
10
|
+
return cacheStoreSlot.get()!
|
|
11
11
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pageSlot } from './pageSlot.ts'
|
|
2
2
|
import type { PageSnapshot } from './types/PageSnapshot.ts'
|
|
3
3
|
|
|
4
4
|
/*
|
|
@@ -7,5 +7,5 @@ single lazily-created empty snapshot when none is registered (so isolated tests
|
|
|
7
7
|
work). The fallback creator guarantees a value, hence the non-null assertion.
|
|
8
8
|
*/
|
|
9
9
|
export function activePage(): PageSnapshot {
|
|
10
|
-
return
|
|
10
|
+
return pageSlot.get()!
|
|
11
11
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { keyForRemoteCall } from './keyForRemoteCall.ts'
|
|
2
|
+
import { keyPrefixForRemote } from './keyPrefixForRemote.ts'
|
|
3
|
+
import { patch } from './patch.ts'
|
|
4
|
+
import { peek } from './peek.ts'
|
|
5
|
+
import { pending } from './pending.ts'
|
|
6
|
+
import { refresh } from './refresh.ts'
|
|
7
|
+
import { refreshing } from './refreshing.ts'
|
|
8
|
+
import { rpcErrorRegistry } from './rpcErrorRegistry.ts'
|
|
9
|
+
import type { RemoteFunction } from './types/RemoteFunction.ts'
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
Attaches the pre-bound selector sugar onto an assembled RemoteFunction:
|
|
13
|
+
`fn.pending(args?)` ≡ `pending(fn, args?)`, likewise refreshing / refresh / peek,
|
|
14
|
+
`fn.patch(args?, updater)` ≡ `patch(fn, args, updater)`, and `fn.error(args?)` — the typed
|
|
15
|
+
last error from the rpc error registry (most-recent across the rpc when args omitted, that
|
|
16
|
+
exact call when given). The cached read is the bare call `fn(args, opts)` itself; refetch is
|
|
17
|
+
`fn.refresh(args?)`. The methods only reference the globals at call time, so the shared import
|
|
18
|
+
edge carries no module-init dependency (safe against any cache ↔ createRemoteFunction cycle).
|
|
19
|
+
Attached in createRemoteFunction so the server (defineRpc) and client (remoteProxy) shapes are
|
|
20
|
+
identical. `patch` is attached uniformly; the type omits it for a streaming rpc (harmless at
|
|
21
|
+
runtime — a stream has no cache entry).
|
|
22
|
+
*/
|
|
23
|
+
export function attachRpcSelectorMethods<Args, Return>(fn: RemoteFunction<Args, Return>): void {
|
|
24
|
+
Object.assign(fn, {
|
|
25
|
+
pending: (args?: Args) => pending(fn, args),
|
|
26
|
+
refreshing: (args?: Args) => refreshing(fn, args),
|
|
27
|
+
refresh: (args?: Args) => refresh(fn, args),
|
|
28
|
+
peek: (args?: Args) => peek(fn, args),
|
|
29
|
+
patch: (argsOrUpdater?: unknown, updater?: unknown) =>
|
|
30
|
+
(patch as (fn: unknown, a?: unknown, b?: unknown) => void)(fn, argsOrUpdater, updater),
|
|
31
|
+
/* Reaction sugar is client-only (`watch` is a ui primitive that must not ride into a
|
|
32
|
+
server bundle), so the shared attach binds an inert no-op — the server (defineRpc)
|
|
33
|
+
shape carries `.watch` too, and an author `fn.watch(…)` surviving the SSR effect-strip
|
|
34
|
+
is a safe no-op. The client proxy (remoteProxy) overwrites this with the real
|
|
35
|
+
`watch(fn, …)`. */
|
|
36
|
+
watch: () => () => {},
|
|
37
|
+
error: (args?: Args) =>
|
|
38
|
+
args === undefined
|
|
39
|
+
? rpcErrorRegistry.readAny(keyPrefixForRemote(fn.method, fn.url))
|
|
40
|
+
: rpcErrorRegistry.read(keyForRemoteCall(fn.method, fn.url, args)),
|
|
41
|
+
})
|
|
42
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { done } from './done.ts'
|
|
2
|
+
import { pending } from './pending.ts'
|
|
3
|
+
import { refreshing } from './refreshing.ts'
|
|
4
|
+
import { tailProbeSlot } from './tailProbeSlot.ts'
|
|
5
|
+
import type { Socket } from './types/Socket.ts'
|
|
6
|
+
|
|
7
|
+
/* The socket surface before the probe sugar is attached — every Socket member except the
|
|
8
|
+
pre-bound probes this module adds. Both construction sites (defineSocket / buildSocketOverChannel)
|
|
9
|
+
build this shape, then the assertion below narrows it to a full Socket. */
|
|
10
|
+
type SocketWithoutProbes<T> = Omit<Socket<T>, 'pending' | 'refreshing' | 'done' | 'error'>
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
Attaches the pre-bound probe sugar onto an assembled Socket, mirroring
|
|
14
|
+
attachRpcSelectorMethods for the rpc side: `socket.pending()` ≡ `pending(socket)`,
|
|
15
|
+
likewise refreshing / done, and `socket.error()` reads the stream's terminal error (the
|
|
16
|
+
tail prober's `error` field — instance-only, no bare global, same as the rpc's `.error`).
|
|
17
|
+
The socket is the pre-bound stream selector. The methods reference the globals at call
|
|
18
|
+
time, so the shared import edge carries no module-init dependency. Called by both
|
|
19
|
+
buildSocketOverChannel (consumer / test) and defineSocket (server) so the two Socket
|
|
20
|
+
shapes stay identical; on the server the tail prober is unregistered, so the probes read
|
|
21
|
+
the same fallbacks the globals give there (pending true until a frame, refreshing/done
|
|
22
|
+
false, error undefined). An assertion return narrows the base object to a full Socket at
|
|
23
|
+
each call site, so neither construction literal needs a cast.
|
|
24
|
+
*/
|
|
25
|
+
export function attachSocketSelectorMethods<T>(
|
|
26
|
+
socket: SocketWithoutProbes<T>,
|
|
27
|
+
): asserts socket is Socket<T> {
|
|
28
|
+
Object.assign(socket, {
|
|
29
|
+
pending: () => pending(socket),
|
|
30
|
+
refreshing: () => refreshing(socket),
|
|
31
|
+
done: () => done(socket),
|
|
32
|
+
error: () => tailProbeSlot.probe?.(socket.name)?.error,
|
|
33
|
+
})
|
|
34
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { baseSlot } from './baseSlot.ts'
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
The current mount base path ('' at root). Resolved per side: the server installs
|
|
@@ -7,5 +7,5 @@ url() reads this to prefix rooted internal paths. Defaults to '' when no resolve
|
|
|
7
7
|
or fallback is set.
|
|
8
8
|
*/
|
|
9
9
|
export function basePath(): string {
|
|
10
|
-
return
|
|
10
|
+
return baseSlot.get() ?? ''
|
|
11
11
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createResolverSlot } from './createResolverSlot.ts'
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
directly
|
|
4
|
+
The mount-base slot. The server entry installs an APP_URL-derived resolver at
|
|
5
|
+
boot; the client entry one reading window.__SSR__.base. No lazy fallback
|
|
6
|
+
creator — `.fallback` is a plain string set directly by isolated tests, and
|
|
7
|
+
basePath() (the public read) supplies the '' default.
|
|
7
8
|
*/
|
|
8
|
-
export const baseSlot =
|
|
9
|
+
export const baseSlot = createResolverSlot<string>()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ContentBodyKind } from './contentBodyKind.ts'
|
|
2
|
+
import { DEFER } from './DEFER.ts'
|
|
2
3
|
|
|
3
4
|
/*
|
|
4
5
|
The single body-kind → value mapping shared by the live read (decodeResponse) and
|
|
@@ -18,8 +19,6 @@ Returns the DEFER sentinel for `streaming` and `binary`: kinds whose handling is
|
|
|
18
19
|
side-specific (the live read throws on streaming and blobs on binary; the warm
|
|
19
20
|
read has no synchronous equivalent for either), so the caller branches on it.
|
|
20
21
|
*/
|
|
21
|
-
export const DEFER: unique symbol = Symbol('defer')
|
|
22
|
-
|
|
23
22
|
export function bodyValueForKind<Value>(
|
|
24
23
|
kind: ContentBodyKind,
|
|
25
24
|
json: () => Value,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { attachSocketSelectorMethods } from './attachSocketSelectorMethods.ts'
|
|
2
2
|
import { browserClientFlags } from './browserClientFlags.ts'
|
|
3
3
|
import { createPushIterator } from './createPushIterator.ts'
|
|
4
|
+
import { SOCKETS_PATH } from './SOCKETS_PATH.ts'
|
|
5
|
+
import type { Socket } from './types/Socket.ts'
|
|
4
6
|
import type { SocketChannel } from './types/SocketChannel.ts'
|
|
5
7
|
import type { TailHooks } from './types/TailHooks.ts'
|
|
8
|
+
import { withBase } from './withBase.ts'
|
|
6
9
|
|
|
7
10
|
/* Per-channel-agnostic sub id counter; uniqueness within a channel is all the
|
|
8
11
|
lifecycle routing needs, so one monotonic source across every socket is fine. */
|
|
@@ -24,6 +27,10 @@ export function buildSocketOverChannel<T>(
|
|
|
24
27
|
name: string,
|
|
25
28
|
resolveChannel: () => SocketChannel,
|
|
26
29
|
): Socket<T> {
|
|
30
|
+
/* The latest frame seen across every iterator of this socket — what peek() returns and
|
|
31
|
+
what refresh() re-seeds from the server tail. A plain slot (not reactive): peek(socket)
|
|
32
|
+
is a synchronous snapshot; live reactivity is the for-await / watch(socket) path. */
|
|
33
|
+
let lastFrame: T | undefined
|
|
27
34
|
function iterate(replay: number | undefined, hooks?: TailHooks): AsyncIterable<T> {
|
|
28
35
|
return {
|
|
29
36
|
[Symbol.asyncIterator](): AsyncIterator<T, void, undefined> {
|
|
@@ -31,9 +38,13 @@ export function buildSocketOverChannel<T>(
|
|
|
31
38
|
const channel = resolveChannel()
|
|
32
39
|
const iterator = createPushIterator<T>(() => channel.unsubscribe(id))
|
|
33
40
|
channel.subscribe(id, name, replay, {
|
|
34
|
-
onMessage: (value) =>
|
|
41
|
+
onMessage: (value) => {
|
|
42
|
+
lastFrame = value as T
|
|
43
|
+
iterator.push(value as T)
|
|
44
|
+
},
|
|
35
45
|
onReplay: (messages) => {
|
|
36
46
|
for (const value of messages) {
|
|
47
|
+
lastFrame = value as T
|
|
37
48
|
iterator.push(value as T)
|
|
38
49
|
}
|
|
39
50
|
if (hooks?.replayed) {
|
|
@@ -48,11 +59,36 @@ export function buildSocketOverChannel<T>(
|
|
|
48
59
|
},
|
|
49
60
|
}
|
|
50
61
|
}
|
|
51
|
-
|
|
62
|
+
const publish = (message: T) => resolveChannel().publish(name, message)
|
|
63
|
+
const socket = {
|
|
52
64
|
name,
|
|
53
65
|
clients: browserClientFlags,
|
|
54
|
-
|
|
66
|
+
/* `broadcast` sends a server-validated `pub` frame (the dispatcher gates it on the
|
|
67
|
+
topic's `clientPublish`); `publish` is the internal channel-send function name. */
|
|
68
|
+
broadcast: publish,
|
|
55
69
|
tail: (count?: number, hooks?: TailHooks) => iterate(count, hooks),
|
|
70
|
+
/* The latest frame this client has seen, synchronously. */
|
|
71
|
+
peek: () => lastFrame,
|
|
72
|
+
/* Re-pull the server's retained tail over the HTTP face (GET /__abide/sockets/<name>
|
|
73
|
+
→ JSON array of retained frames) and reset the local latest — a reconnect / ttl
|
|
74
|
+
resync. Fire-and-forget: the fetch runs in the background and updates what peek()
|
|
75
|
+
returns. A failed pull leaves the current value untouched. */
|
|
76
|
+
refresh: () => {
|
|
77
|
+
void fetch(withBase(`${SOCKETS_PATH}/${name}`))
|
|
78
|
+
.then((response) => (response.ok ? response.json() : undefined))
|
|
79
|
+
.then((frames) => {
|
|
80
|
+
if (Array.isArray(frames) && frames.length > 0) {
|
|
81
|
+
lastFrame = frames[frames.length - 1] as T
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
.catch(() => undefined)
|
|
85
|
+
},
|
|
86
|
+
/* Inert default so the returned object satisfies Socket<T> without importing the ui-only
|
|
87
|
+
`watch` into this shared builder (which the test harness also uses). The client proxy
|
|
88
|
+
(socketProxy) overwrites it with the real `watch(socket, …)`. */
|
|
89
|
+
watch: () => () => {},
|
|
56
90
|
[Symbol.asyncIterator]: () => iterate(0)[Symbol.asyncIterator](),
|
|
57
91
|
}
|
|
92
|
+
attachSocketSelectorMethods(socket)
|
|
93
|
+
return socket
|
|
58
94
|
}
|