@abide/abide 0.46.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 +80 -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/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 +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 +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/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/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 -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
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
|
}
|