@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,9 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Server } from 'bun'
|
|
2
2
|
import { createMcpResourceServer } from '../../mcp/createMcpResourceServer.ts'
|
|
3
|
-
import {
|
|
3
|
+
import { mcpResourceServerSlot } from '../../mcp/mcpResourceServerSlot.ts'
|
|
4
4
|
import type { McpServer } from '../../mcp/types/McpServer.ts'
|
|
5
5
|
import { abideLog } from '../../shared/abideLog.ts'
|
|
6
6
|
import { basePathFromAppUrl } from '../../shared/basePathFromAppUrl.ts'
|
|
7
|
+
import { baseSlot } from '../../shared/baseSlot.ts'
|
|
7
8
|
import { NO_STORE } from '../../shared/CACHE_CONTROL_VALUES.ts'
|
|
8
9
|
import { CLI_PATH } from '../../shared/CLI_PATH.ts'
|
|
9
10
|
import { DEV_HOT_PREFIX } from '../../shared/DEV_HOT_PREFIX.ts'
|
|
@@ -15,15 +16,15 @@ import { IDENTITY_PATH } from '../../shared/IDENTITY_PATH.ts'
|
|
|
15
16
|
import { INSPECTOR_PATH } from '../../shared/INSPECTOR_PATH.ts'
|
|
16
17
|
import { isDebugNegated } from '../../shared/isDebugNegated.ts'
|
|
17
18
|
import { logClosingRecord } from '../../shared/logClosingRecord.ts'
|
|
19
|
+
import { matchRoute } from '../../shared/matchRoute.ts'
|
|
20
|
+
import { normalizePathname } from '../../shared/normalizePathname.ts'
|
|
18
21
|
import { OFFLINE_HEADER } from '../../shared/OFFLINE_HEADER.ts'
|
|
19
22
|
import { parseBoundedEnvInt } from '../../shared/parseBoundedEnvInt.ts'
|
|
20
|
-
import {
|
|
23
|
+
import { parseRouteSegments } from '../../shared/parseRouteSegments.ts'
|
|
24
|
+
import { requestScopeSlot } from '../../shared/requestScopeSlot.ts'
|
|
21
25
|
import { SOCKETS_PATH } from '../../shared/SOCKETS_PATH.ts'
|
|
22
26
|
import { setAppName } from '../../shared/setAppName.ts'
|
|
23
|
-
import { setBaseResolver } from '../../shared/setBaseResolver.ts'
|
|
24
|
-
import { setRequestScopeResolver } from '../../shared/setRequestScopeResolver.ts'
|
|
25
27
|
import { TEXT_PLAIN } from '../../shared/TEXT_PLAIN.ts'
|
|
26
|
-
import { toBunRoutePattern } from '../../shared/toBunRoutePattern.ts'
|
|
27
28
|
import type { Layouts } from '../../ui/types/Layouts.ts'
|
|
28
29
|
import type { Pages } from '../../ui/types/Pages.ts'
|
|
29
30
|
import type { AppModule } from '../AppModule.ts'
|
|
@@ -49,6 +50,7 @@ import { devClientFingerprint } from './devClientFingerprint.ts'
|
|
|
49
50
|
import { devHotModuleResponse } from './devHotModuleResponse.ts'
|
|
50
51
|
import { devReloadResponse } from './devReloadResponse.ts'
|
|
51
52
|
import { disableIdleTimeoutForStream } from './disableIdleTimeoutForStream.ts'
|
|
53
|
+
import { finalizeResponse } from './finalizeResponse.ts'
|
|
52
54
|
import { gzipResponse } from './gzipResponse.ts'
|
|
53
55
|
import { installAmbientScopeStore } from './installAmbientScopeStore.ts'
|
|
54
56
|
import { internalErrorResponse } from './internalErrorResponse.ts'
|
|
@@ -166,7 +168,7 @@ export async function createServer({
|
|
|
166
168
|
`await`s they suspend on (see installAmbientScopeStore / CURRENT_SCOPE).
|
|
167
169
|
*/
|
|
168
170
|
installAmbientScopeStore()
|
|
169
|
-
|
|
171
|
+
requestScopeSlot.resolver = () => {
|
|
170
172
|
const store = requestContext.getStore()
|
|
171
173
|
if (!store) {
|
|
172
174
|
return undefined
|
|
@@ -179,7 +181,7 @@ export async function createServer({
|
|
|
179
181
|
/* The calling client's reported connectivity — drives server-side online(). Absent header = online. */
|
|
180
182
|
online: !store.req.headers.has(OFFLINE_HEADER),
|
|
181
183
|
}
|
|
182
|
-
}
|
|
184
|
+
}
|
|
183
185
|
/*
|
|
184
186
|
health() during an SSR render marks its request through this slot; the
|
|
185
187
|
renderer stamps the health payload into __SSR__ only for marked requests,
|
|
@@ -206,10 +208,10 @@ export async function createServer({
|
|
|
206
208
|
the server-side resolver so url() prefixes SSR-generated links, and rewrite
|
|
207
209
|
the shell's framework `/_app` entry + css refs to carry the base — relative
|
|
208
210
|
code-split chunks inherit it from the entry's own URL. '' (root mount) is a
|
|
209
|
-
no-op on both. See
|
|
211
|
+
no-op on both. See seedBootState / startClient for the client half.
|
|
210
212
|
*/
|
|
211
213
|
const base = basePathFromAppUrl(process.env.APP_URL)
|
|
212
|
-
|
|
214
|
+
baseSlot.resolver = () => base
|
|
213
215
|
// Rebase the shell's rooted `/_app/` entry refs onto the mount base, matching
|
|
214
216
|
// either quote style so a custom app.html using single quotes still rewrites.
|
|
215
217
|
const activeShell = base
|
|
@@ -237,7 +239,7 @@ export async function createServer({
|
|
|
237
239
|
buildPreloadManifest({ distDir, assets }),
|
|
238
240
|
])
|
|
239
241
|
setRegistryManifests({ rpc, sockets, prompts })
|
|
240
|
-
|
|
242
|
+
mcpResourceServerSlot.server = createMcpResourceServer({ resourcesDir, mcpResources })
|
|
241
243
|
const cliName = cliProgramName ?? 'app'
|
|
242
244
|
/* The app's public identity, shared by the identity probe and the OpenAPI spec. */
|
|
243
245
|
const appName = appInfo?.name ?? cliName
|
|
@@ -309,54 +311,40 @@ export async function createServer({
|
|
|
309
311
|
/*
|
|
310
312
|
Route dispatch — rpc-vs-page-vs-404 resolution and method matching — lives
|
|
311
313
|
behind createRouteDispatcher; renderPage is injected so those decisions stay
|
|
312
|
-
testable without SSR.
|
|
314
|
+
testable without SSR. The fetch handler resolves a request URL to a handler
|
|
315
|
+
through the shared matchRoute — the same matcher the client router runs —
|
|
316
|
+
so params decode and route precedence agree across the sides by
|
|
317
|
+
construction (no Bun routes table with its own pattern semantics).
|
|
313
318
|
*/
|
|
314
319
|
const buildRouteHandler = createRouteDispatcher({ pages, rpc, renderPage })
|
|
315
320
|
|
|
316
321
|
/*
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
slicing the pathname segments after the catch-all's pattern index.
|
|
322
|
-
The reconstructed value is set under the original name (e.g. `rest`)
|
|
323
|
-
so the page component's $props destructure stays consistent with the
|
|
324
|
-
file path. Page URLs and rpc URLs (always `/rpc/...`, flat) are
|
|
325
|
-
disjoint by construction, so a plain object needs no deduplication.
|
|
322
|
+
Handlers pre-bound per registered URL. rpc URLs are flat literals (always
|
|
323
|
+
`/rpc/...`), so they dispatch by direct lookup; page URLs carry `[name]` /
|
|
324
|
+
`[[name]]` / `[...rest]` segments and resolve through matchRoute. Page and
|
|
325
|
+
rpc URLs are disjoint by construction, so a request lands in exactly one.
|
|
326
326
|
*/
|
|
327
|
-
const
|
|
328
|
-
for (const routeUrl of Object.keys(pages)) {
|
|
329
|
-
const handler = buildRouteHandler(routeUrl)
|
|
330
|
-
const { pattern, catchAllName } = toBunRoutePattern(routeUrl)
|
|
331
|
-
const catchAllIndex = catchAllName
|
|
332
|
-
? routeUrl.split('/').findIndex((segment) => segment.startsWith('[...'))
|
|
333
|
-
: -1
|
|
334
|
-
/* Only catch-all routes copy req.params (to write the reconstructed
|
|
335
|
-
segment); plain routes pass it through — it's never mutated downstream. */
|
|
336
|
-
routes[pattern] =
|
|
337
|
-
catchAllName && catchAllIndex !== -1
|
|
338
|
-
? (req) => {
|
|
339
|
-
const pathParams = {
|
|
340
|
-
...((req.params as Record<string, string> | undefined) ?? {}),
|
|
341
|
-
}
|
|
342
|
-
const url = new URL(req.url)
|
|
343
|
-
pathParams[catchAllName] = url.pathname
|
|
344
|
-
.split('/')
|
|
345
|
-
.slice(catchAllIndex)
|
|
346
|
-
.join('/')
|
|
347
|
-
return dispatchRequest(req, pathParams, handler, url)
|
|
348
|
-
}
|
|
349
|
-
: (req) =>
|
|
350
|
-
dispatchRequest(
|
|
351
|
-
req,
|
|
352
|
-
(req.params as Record<string, string> | undefined) ?? {},
|
|
353
|
-
handler,
|
|
354
|
-
)
|
|
355
|
-
}
|
|
327
|
+
const rpcHandlers = new Map<string, ReturnType<typeof buildRouteHandler>>()
|
|
356
328
|
for (const routeUrl of Object.keys(rpc)) {
|
|
357
|
-
|
|
358
|
-
|
|
329
|
+
rpcHandlers.set(routeUrl, buildRouteHandler(routeUrl))
|
|
330
|
+
}
|
|
331
|
+
const pageHandlers = new Map<string, ReturnType<typeof buildRouteHandler>>()
|
|
332
|
+
for (const routeUrl of Object.keys(pages)) {
|
|
333
|
+
/* A `[...rest]` consumes every remaining segment, so segments after it
|
|
334
|
+
can never constrain matching — the route would silently serve paths
|
|
335
|
+
it shouldn't. Fail at boot instead. */
|
|
336
|
+
const segments = parseRouteSegments(routeUrl)
|
|
337
|
+
const catchAllIndex = segments.findIndex(
|
|
338
|
+
(segment) => segment.kind === 'param' && segment.catchAll,
|
|
339
|
+
)
|
|
340
|
+
if (catchAllIndex !== -1 && catchAllIndex !== segments.length - 1) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
`[abide] invalid page route ${routeUrl}: a [...name] catch-all must be the last segment`,
|
|
343
|
+
)
|
|
344
|
+
}
|
|
345
|
+
pageHandlers.set(routeUrl, buildRouteHandler(routeUrl))
|
|
359
346
|
}
|
|
347
|
+
const pageRouteUrls = Object.keys(pages)
|
|
360
348
|
|
|
361
349
|
function dispatchRequest(
|
|
362
350
|
req: Request,
|
|
@@ -366,26 +354,15 @@ export async function createServer({
|
|
|
366
354
|
pathParams: Record<string, string>,
|
|
367
355
|
store: RequestStore,
|
|
368
356
|
) => Promise<Response>,
|
|
369
|
-
|
|
370
|
-
url?: URL,
|
|
357
|
+
url: URL,
|
|
371
358
|
): Promise<Response> {
|
|
372
359
|
return runWithRequestScope(req, { app, logRequests, url }, async (store) => {
|
|
373
360
|
const response = app?.handle
|
|
374
361
|
? await app.handle(req, (next) => handler(next, pathParams, store))
|
|
375
362
|
: await handler(req, pathParams, store)
|
|
376
|
-
/*
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const kind = responseBodyKind(response)
|
|
380
|
-
store.responseStreaming = kind === 'streaming'
|
|
381
|
-
// Streaming bodies (sse/jsonl, socket tail) opt out of the idle timeout.
|
|
382
|
-
if (kind === 'streaming') {
|
|
383
|
-
server.timeout(req, 0)
|
|
384
|
-
}
|
|
385
|
-
/* Gzip compressible dynamic bodies (SSR HTML, rpc/json, 404) when the
|
|
386
|
-
client accepts it; streaming frame protocols and static assets are
|
|
387
|
-
passed through untouched (see gzipResponse). */
|
|
388
|
-
return gzipResponse(req, response, kind)
|
|
363
|
+
/* Wire handling — classify once, mark the stream monitor, exempt
|
|
364
|
+
streams from the idle timeout, gzip — lives in finalizeResponse. */
|
|
365
|
+
return finalizeResponse(req, response, store, () => server.timeout(req, 0))
|
|
389
366
|
})
|
|
390
367
|
}
|
|
391
368
|
|
|
@@ -431,8 +408,6 @@ export async function createServer({
|
|
|
431
408
|
},
|
|
432
409
|
},
|
|
433
410
|
|
|
434
|
-
routes,
|
|
435
|
-
|
|
436
411
|
async fetch(req, bunServer) {
|
|
437
412
|
const url = new URL(req.url)
|
|
438
413
|
/*
|
|
@@ -521,7 +496,7 @@ export async function createServer({
|
|
|
521
496
|
})
|
|
522
497
|
}
|
|
523
498
|
return devHotModuleResponse(
|
|
524
|
-
|
|
499
|
+
decodePathSegment(url.pathname.slice(DEV_HOT_PREFIX.length)),
|
|
525
500
|
)
|
|
526
501
|
}
|
|
527
502
|
/*
|
|
@@ -568,7 +543,7 @@ export async function createServer({
|
|
|
568
543
|
if (publishForbidden) {
|
|
569
544
|
return publishForbidden
|
|
570
545
|
}
|
|
571
|
-
const name =
|
|
546
|
+
const name = decodePathSegment(url.pathname.slice(SOCKETS_REST_PREFIX.length))
|
|
572
547
|
return dispatchRequest(
|
|
573
548
|
req,
|
|
574
549
|
{},
|
|
@@ -596,6 +571,49 @@ export async function createServer({
|
|
|
596
571
|
url,
|
|
597
572
|
)
|
|
598
573
|
}
|
|
574
|
+
/*
|
|
575
|
+
App routes — rpc by flat lookup, pages through the shared
|
|
576
|
+
matcher (the client router runs the same one). Matched AFTER
|
|
577
|
+
the `/__abide/*` plumbing above (a reserved namespace no app
|
|
578
|
+
route occupies) and BEFORE the root-level framework surfaces
|
|
579
|
+
(/openapi.json, /_app/, public/ files), so a page route
|
|
580
|
+
shadows a same-path public file — the precedence the Bun
|
|
581
|
+
routes table used to impose implicitly, now pinned here.
|
|
582
|
+
*/
|
|
583
|
+
const rpcHandler = rpcHandlers.get(url.pathname)
|
|
584
|
+
if (rpcHandler) {
|
|
585
|
+
return dispatchRequest(req, {}, rpcHandler, url)
|
|
586
|
+
}
|
|
587
|
+
/*
|
|
588
|
+
Pages match only in canonical slash form; a non-canonical
|
|
589
|
+
request (`/admin/`, `//admin`) that would match is 308'd to the
|
|
590
|
+
canonical URL instead of served. Serving it directly would hand
|
|
591
|
+
app.handle — the auth seam — a pathname the matcher silently
|
|
592
|
+
normalized away, so an exact-match guard on `/admin` never sees
|
|
593
|
+
the request it's guarding (the old Bun routes table 404'd these
|
|
594
|
+
forms; the redirect keeps the guard sound AND the URL friendly).
|
|
595
|
+
rpc URLs stay exact-match strict, as they always were.
|
|
596
|
+
*/
|
|
597
|
+
const canonicalPathname = normalizePathname(url.pathname)
|
|
598
|
+
if (canonicalPathname !== url.pathname) {
|
|
599
|
+
if (matchRoute(pageRouteUrls, canonicalPathname)) {
|
|
600
|
+
return new Response(null, {
|
|
601
|
+
status: 308,
|
|
602
|
+
headers: {
|
|
603
|
+
Location: `${canonicalPathname}${url.search}`,
|
|
604
|
+
'Cache-Control': NO_STORE,
|
|
605
|
+
},
|
|
606
|
+
})
|
|
607
|
+
}
|
|
608
|
+
} else {
|
|
609
|
+
const matchedPage = matchRoute(pageRouteUrls, url.pathname)
|
|
610
|
+
if (matchedPage) {
|
|
611
|
+
const pageHandler = pageHandlers.get(matchedPage.route)
|
|
612
|
+
if (pageHandler) {
|
|
613
|
+
return dispatchRequest(req, matchedPage.params, pageHandler, url)
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
599
617
|
if (url.pathname === OPENAPI_PATH) {
|
|
600
618
|
return dispatchRequest(
|
|
601
619
|
req,
|
|
@@ -734,3 +752,15 @@ export async function createServer({
|
|
|
734
752
|
}
|
|
735
753
|
return server
|
|
736
754
|
}
|
|
755
|
+
|
|
756
|
+
/* Lenient percent-decode for the internal-route names above (dev hot-module
|
|
757
|
+
paths, socket names) — the same leniency matchRoute applies to page params.
|
|
758
|
+
A malformed escape (`/%`) keeps the raw text so the downstream lookup misses
|
|
759
|
+
naturally instead of a URIError escaping the fetch handler as a 500. */
|
|
760
|
+
function decodePathSegment(segment: string): string {
|
|
761
|
+
try {
|
|
762
|
+
return decodeURIComponent(segment)
|
|
763
|
+
} catch {
|
|
764
|
+
return segment
|
|
765
|
+
}
|
|
766
|
+
}
|
|
@@ -5,6 +5,7 @@ import { layoutChainForRoute } from '../../shared/layoutChainForRoute.ts'
|
|
|
5
5
|
import { safeJsonForScript } from '../../shared/safeJsonForScript.ts'
|
|
6
6
|
import { snapshotShippable } from '../../shared/snapshotShippable.ts'
|
|
7
7
|
import type { CacheSnapshotEntry } from '../../shared/types/CacheSnapshotEntry.ts'
|
|
8
|
+
import type { SsrPayload } from '../../shared/types/SsrPayload.ts'
|
|
8
9
|
import type { StreamedResolution } from '../../shared/types/StreamedResolution.ts'
|
|
9
10
|
import { renderChain } from '../../ui/renderChain.ts'
|
|
10
11
|
import { renderToStream } from '../../ui/renderToStream.ts'
|
|
@@ -115,7 +116,7 @@ export function createUiPageRenderer({
|
|
|
115
116
|
app: appNameSlot.name,
|
|
116
117
|
health,
|
|
117
118
|
clientTimeout,
|
|
118
|
-
})
|
|
119
|
+
} satisfies SsrPayload)
|
|
119
120
|
return `<script>window.__SSR__ = ${payload};</script>`
|
|
120
121
|
}
|
|
121
122
|
|
|
@@ -142,7 +143,22 @@ export function createUiPageRenderer({
|
|
|
142
143
|
build-time injector). A no-op when there are none or the shell carries no </head>. */
|
|
143
144
|
function injectRoutePreloads(html: string, routeUrl: string): string {
|
|
144
145
|
const links = routePreloadLinks(routeUrl)
|
|
145
|
-
return links === '' ? html : html.replace(HEAD_CLOSE_TAG, `${links}</head>`)
|
|
146
|
+
return links === '' ? html : html.replace(HEAD_CLOSE_TAG, () => `${links}</head>`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* The layout chain for a route is a pure function of routeUrl and the fixed `layouts`
|
|
150
|
+
map, so memoise it per route (like `preloadLinkCache`) instead of re-scanning and
|
|
151
|
+
re-sorting every layout key on every request. */
|
|
152
|
+
const layoutKeys = Object.keys(layouts)
|
|
153
|
+
const chainKeyCache = new Map<string, string[]>()
|
|
154
|
+
function chainKeysForRoute(routeUrl: string): string[] {
|
|
155
|
+
const cached = chainKeyCache.get(routeUrl)
|
|
156
|
+
if (cached !== undefined) {
|
|
157
|
+
return cached
|
|
158
|
+
}
|
|
159
|
+
const chain = layoutChainForRoute(routeUrl, layoutKeys)
|
|
160
|
+
chainKeyCache.set(routeUrl, chain)
|
|
161
|
+
return chain
|
|
146
162
|
}
|
|
147
163
|
|
|
148
164
|
async function renderPage(
|
|
@@ -166,7 +182,7 @@ export function createUiPageRenderer({
|
|
|
166
182
|
}
|
|
167
183
|
/* Outermost layout → … → page: load every applicable layout chunk plus the
|
|
168
184
|
page, then render the chain as one document (shared block-id pass). */
|
|
169
|
-
const chainKeys =
|
|
185
|
+
const chainKeys = chainKeysForRoute(routeUrl)
|
|
170
186
|
const views = await Promise.all([
|
|
171
187
|
...chainKeys.map((key) => layouts[key]?.().then((module) => module.default)),
|
|
172
188
|
loadPage().then((module) => module.default),
|
|
@@ -193,9 +209,12 @@ export function createUiPageRenderer({
|
|
|
193
209
|
),
|
|
194
210
|
routeUrl,
|
|
195
211
|
)
|
|
212
|
+
/* Function replacer: the state script carries user cache data, and a string
|
|
213
|
+
replacement would interpret `$&`/`$'`-style patterns inside it. */
|
|
214
|
+
const state = await stateTag(routeUrl, params, store, inline)
|
|
196
215
|
const withState = html.replace(
|
|
197
216
|
'</body>',
|
|
198
|
-
`${resumeSeedScript(ssr.resume)}${
|
|
217
|
+
() => `${resumeSeedScript(ssr.resume)}${state}</body>`,
|
|
199
218
|
)
|
|
200
219
|
return new Response(withState, {
|
|
201
220
|
headers: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { relative } from 'node:path'
|
|
2
2
|
import { fileName } from '../../shared/fileName.ts'
|
|
3
|
+
import { isLayoutFile } from '../../shared/isLayoutFile.ts'
|
|
3
4
|
import { analyzeComponent } from '../../ui/compile/analyzeComponent.ts'
|
|
4
5
|
import { compileComponent } from '../../ui/compile/compileComponent.ts'
|
|
5
6
|
import { nearestProjectRoot } from '../../ui/compile/nearestProjectRoot.ts'
|
|
@@ -20,8 +21,7 @@ const GENERATED = /(^|\/)\.abide\//
|
|
|
20
21
|
// page.abide / layout.abide are router-mounted, not `mountChild`-tracked, so they
|
|
21
22
|
// can't hot-swap — they fold into `structure` (a reload) instead.
|
|
22
23
|
function isPageOrLayout(moduleId: string): boolean {
|
|
23
|
-
|
|
24
|
-
return file === 'page.abide' || file === 'layout.abide'
|
|
24
|
+
return fileName(moduleId) === 'page.abide' || isLayoutFile(moduleId)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/*
|
|
@@ -81,7 +81,7 @@ export async function devClientFingerprint({
|
|
|
81
81
|
let bodyHash: string
|
|
82
82
|
let hot = false
|
|
83
83
|
try {
|
|
84
|
-
const isLayout = moduleId
|
|
84
|
+
const isLayout = isLayoutFile(moduleId)
|
|
85
85
|
bodyHash = Bun.hash(compileComponent(source, isLayout, moduleId)).toString(36)
|
|
86
86
|
hot = analyzeComponent(source).imports === '' && !isPageOrLayout(moduleId)
|
|
87
87
|
} catch {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
2
|
import { NO_STORE } from '../../shared/CACHE_CONTROL_VALUES.ts'
|
|
3
|
+
import { isLayoutFile } from '../../shared/isLayoutFile.ts'
|
|
3
4
|
import { compileModule } from '../../ui/compile/compileModule.ts'
|
|
4
5
|
|
|
5
6
|
// Reused across requests — strips the embedded author TypeScript to browser JS.
|
|
@@ -27,7 +28,7 @@ export async function devHotModuleResponse(moduleId: string): Promise<Response>
|
|
|
27
28
|
return new Response('not found', { status: 404 })
|
|
28
29
|
}
|
|
29
30
|
const { code } = compileModule(source, {
|
|
30
|
-
isLayout: moduleId
|
|
31
|
+
isLayout: isLayoutFile(moduleId),
|
|
31
32
|
moduleId,
|
|
32
33
|
hot: true,
|
|
33
34
|
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { responseBodyKind } from '../../shared/responseBodyKind.ts'
|
|
2
|
+
import { gzipResponse } from './gzipResponse.ts'
|
|
3
|
+
import type { RequestStore } from './types/RequestStore.ts'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
The wire-handling step every dynamic route's response crosses after its
|
|
7
|
+
handler (and app.handle) returns. The body is classified once
|
|
8
|
+
(responseBodyKind) and that one decision is threaded into every consumer, in
|
|
9
|
+
order: the closing-record stream monitor (store.responseStreaming), the idle
|
|
10
|
+
timeout — a streaming body (SSE / JSONL, socket tail) is opted out so a quiet
|
|
11
|
+
stream isn't reaped mid-flight — and gzip, which compresses compressible
|
|
12
|
+
dynamic bodies (SSR HTML, rpc/json, 404) when the client accepts it and
|
|
13
|
+
passes streaming frame protocols and opaque bodies through untouched.
|
|
14
|
+
`exemptIdleTimeout` is the server capability (server.timeout(req, 0))
|
|
15
|
+
injected by createServer, so the sequencing is testable without a live
|
|
16
|
+
socket. Framework plumbing endpoints answered before the request scope
|
|
17
|
+
(inspector / dev-reload SSE) don't cross this seam — they opt out via
|
|
18
|
+
disableIdleTimeoutForStream directly.
|
|
19
|
+
*/
|
|
20
|
+
export function finalizeResponse(
|
|
21
|
+
req: Request,
|
|
22
|
+
response: Response,
|
|
23
|
+
store: RequestStore,
|
|
24
|
+
exemptIdleTimeout: () => void,
|
|
25
|
+
): Response {
|
|
26
|
+
const kind = responseBodyKind(response)
|
|
27
|
+
store.responseStreaming = kind === 'streaming'
|
|
28
|
+
if (kind === 'streaming') {
|
|
29
|
+
exemptIdleTimeout()
|
|
30
|
+
}
|
|
31
|
+
return gzipResponse(req, response, kind)
|
|
32
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { contentBodyKind } from '../../shared/contentBodyKind.ts'
|
|
1
2
|
import { contentTypeOf } from '../../shared/contentTypeOf.ts'
|
|
2
3
|
import { hasReplayableRequest } from '../../shared/hasReplayableRequest.ts'
|
|
3
4
|
import { isStreamingResponse } from '../../shared/isStreamingResponse.ts'
|
|
@@ -51,8 +52,13 @@ export async function snapshotEntryFromCache(
|
|
|
51
52
|
if (isStreamingResponse(response)) {
|
|
52
53
|
return undefined
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
/* Ship only a body the client's WARM read can consume — `json`/`text` per the single
|
|
56
|
+
`contentBodyKind` classification the live decoder uses. Gating on the same table (not a
|
|
57
|
+
private textual list) keeps warm read ≡ live read (ADR-0011): the private list shipped
|
|
58
|
+
`application/xml`, which `contentBodyKind` buckets as `binary` and the warm read defers
|
|
59
|
+
on, so that snapshot could never be consumed warm — a silent divergence. */
|
|
60
|
+
const kind = contentBodyKind(contentTypeOf(response.headers))
|
|
61
|
+
if (kind !== 'json' && kind !== 'text') {
|
|
56
62
|
return undefined
|
|
57
63
|
}
|
|
58
64
|
/* Read a CLONE, not the original: a reader that captured this same `entry.promise`
|
|
@@ -74,8 +80,6 @@ export async function snapshotEntryFromCache(
|
|
|
74
80
|
statusText: response.statusText,
|
|
75
81
|
headers: Array.from(response.headers.entries()),
|
|
76
82
|
body,
|
|
77
|
-
/* Deferred by the SSR resume path → seed the shipped body lazily (no hydration decode). */
|
|
78
|
-
lazy: entry.deferred === true ? true : undefined,
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
|
|
@@ -86,16 +90,3 @@ async function readSettled(promise: Promise<Response>): Promise<Response | undef
|
|
|
86
90
|
return undefined
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
|
-
|
|
90
|
-
function isTextual(contentType: string): boolean {
|
|
91
|
-
if (contentType.startsWith('text/')) {
|
|
92
|
-
return true
|
|
93
|
-
}
|
|
94
|
-
if (contentType.includes('json')) {
|
|
95
|
-
return true
|
|
96
|
-
}
|
|
97
|
-
if (contentType.includes('xml')) {
|
|
98
|
-
return true
|
|
99
|
-
}
|
|
100
|
-
return false
|
|
101
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
One
|
|
2
|
+
One shared cache entry projected for the inspector — the serializable facts the
|
|
3
3
|
Cache tab renders. The stored promise/Request/timer aren't included; what an
|
|
4
4
|
operator wants is the entry's identity, lifecycle state, retention, and a peek
|
|
5
5
|
at the held value.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { InspectorCacheEntry } from './InspectorCacheEntry.ts'
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
|
-
The inspector's view of the cache: the process-level (`
|
|
5
|
-
current entries. That's the persistent store —
|
|
6
|
-
die with their request, so they surface as
|
|
7
|
-
traces rather than here.
|
|
4
|
+
The inspector's view of the cache: the process-level (`shared: true, ttl:
|
|
5
|
+
Infinity`) store's current entries. That's the persistent store —
|
|
6
|
+
request-scoped caches live and die with their request, so they surface as
|
|
7
|
+
per-request tallies in the feed and traces rather than here.
|
|
8
8
|
*/
|
|
9
9
|
export type InspectorCacheSnapshot = {
|
|
10
10
|
entries: InspectorCacheEntry[]
|
|
@@ -17,7 +17,7 @@ export type InspectorContext = {
|
|
|
17
17
|
app: { name: string; version: string }
|
|
18
18
|
/* Eager-loads the registries, then projects the current RPC + socket catalog. */
|
|
19
19
|
loadSurface: () => Promise<InspectorSurface>
|
|
20
|
-
/* Snapshots the persistent (
|
|
20
|
+
/* Snapshots the persistent (shared) cache store — current entries with their
|
|
21
21
|
lifecycle state, retention, tags, and a value preview. */
|
|
22
22
|
cacheSnapshot: () => InspectorCacheSnapshot
|
|
23
23
|
/* Snapshots the requests whose handler is executing right now — the live
|
package/src/lib/server/socket.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { Socket } from '../shared/types/Socket.ts'
|
|
1
2
|
import type { StandardSchemaV1 } from '../shared/types/StandardSchemaV1.ts'
|
|
2
|
-
import type { Socket } from './sockets/types/Socket.ts'
|
|
3
3
|
import type { SocketOptions } from './sockets/types/SocketOptions.ts'
|
|
4
4
|
|
|
5
5
|
/*
|
|
@@ -4,14 +4,14 @@ import { decodeRefJson } from '../../shared/decodeRefJson.ts'
|
|
|
4
4
|
import { encodeRefJson } from '../../shared/encodeRefJson.ts'
|
|
5
5
|
import { memoizeByKey } from '../../shared/memoizeByKey.ts'
|
|
6
6
|
import { messageFromError } from '../../shared/messageFromError.ts'
|
|
7
|
+
import type { SocketClientFrame } from '../../shared/types/SocketClientFrame.ts'
|
|
8
|
+
import type { SocketServerFrame } from '../../shared/types/SocketServerFrame.ts'
|
|
7
9
|
import { error } from '../error.ts'
|
|
8
10
|
import { json } from '../json.ts'
|
|
9
11
|
import { sse } from '../sse.ts'
|
|
10
12
|
import { lookupSocket } from './lookupSocket.ts'
|
|
11
|
-
import type { SocketClientFrame } from './types/SocketClientFrame.ts'
|
|
12
13
|
import type { SocketRegistryEntry } from './types/SocketRegistryEntry.ts'
|
|
13
14
|
import type { SocketRoutes } from './types/SocketRoutes.ts'
|
|
14
|
-
import type { SocketServerFrame } from './types/SocketServerFrame.ts'
|
|
15
15
|
|
|
16
16
|
// Reused across every inbound binary frame rather than allocated per message.
|
|
17
17
|
const textDecoder = new TextDecoder()
|
|
@@ -36,6 +36,12 @@ so we only `ws.unsubscribe` when the last local sub drops.
|
|
|
36
36
|
type ConnectionState = {
|
|
37
37
|
subToSocket: Map<string, string>
|
|
38
38
|
socketSubs: Map<string, Set<string>>
|
|
39
|
+
/* Latest claim token per `sub` id. handleSub awaits the module load before
|
|
40
|
+
registering, so an unsub (delete) or a newer sub (higher token) landing
|
|
41
|
+
during that await must win — the resumed handler re-checks its token and
|
|
42
|
+
bails instead of registering a zombie subscription. */
|
|
43
|
+
subEpochs: Map<string, number>
|
|
44
|
+
epoch: number
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
/*
|
|
@@ -150,8 +156,19 @@ export function createSocketDispatcher(sockets: SocketRoutes): SocketDispatcher
|
|
|
150
156
|
send(ws, { type: 'err', sub: frame.sub, message })
|
|
151
157
|
send(ws, { type: 'end', sub: frame.sub })
|
|
152
158
|
}
|
|
159
|
+
/* Claim the sub id synchronously — the await below yields to other
|
|
160
|
+
frames on this connection. */
|
|
161
|
+
const token = ++state.epoch
|
|
162
|
+
state.subEpochs.set(frame.sub, token)
|
|
153
163
|
const resolution = await resolveEntry(frame.socket)
|
|
164
|
+
if (state.subEpochs.get(frame.sub) !== token) {
|
|
165
|
+
/* An unsub (already answered with `end`) or a newer sub for this id
|
|
166
|
+
won the race while the module loaded — registering now would leak
|
|
167
|
+
a subscription the client can never cancel. */
|
|
168
|
+
return
|
|
169
|
+
}
|
|
154
170
|
if ('failure' in resolution) {
|
|
171
|
+
state.subEpochs.delete(frame.sub)
|
|
155
172
|
return fail(resolution.message)
|
|
156
173
|
}
|
|
157
174
|
const { entry } = resolution
|
|
@@ -194,6 +211,7 @@ export function createSocketDispatcher(sockets: SocketRoutes): SocketDispatcher
|
|
|
194
211
|
state: ConnectionState,
|
|
195
212
|
frame: Extract<SocketClientFrame, { type: 'unsub' }>,
|
|
196
213
|
): void {
|
|
214
|
+
state.subEpochs.delete(frame.sub)
|
|
197
215
|
const emptied = removeSub(state, frame.sub)
|
|
198
216
|
if (emptied) {
|
|
199
217
|
ws.unsubscribe(`socket:${emptied}`)
|
|
@@ -227,7 +245,7 @@ export function createSocketDispatcher(sockets: SocketRoutes): SocketDispatcher
|
|
|
227
245
|
the process down.
|
|
228
246
|
*/
|
|
229
247
|
try {
|
|
230
|
-
entry.socket.
|
|
248
|
+
entry.socket.broadcast(frame.message)
|
|
231
249
|
} catch (publishError) {
|
|
232
250
|
abideLog.error(publishError)
|
|
233
251
|
}
|
|
@@ -291,7 +309,7 @@ export function createSocketDispatcher(sockets: SocketRoutes): SocketDispatcher
|
|
|
291
309
|
}
|
|
292
310
|
try {
|
|
293
311
|
// publish() validates against the socket schema and throws on a bad payload.
|
|
294
|
-
entry.socket.
|
|
312
|
+
entry.socket.broadcast(message)
|
|
295
313
|
} catch (publishError) {
|
|
296
314
|
return error(422, messageFromError(publishError))
|
|
297
315
|
}
|
|
@@ -304,7 +322,12 @@ export function createSocketDispatcher(sockets: SocketRoutes): SocketDispatcher
|
|
|
304
322
|
rest,
|
|
305
323
|
|
|
306
324
|
open(ws) {
|
|
307
|
-
connections.set(ws, {
|
|
325
|
+
connections.set(ws, {
|
|
326
|
+
subToSocket: new Map(),
|
|
327
|
+
socketSubs: new Map(),
|
|
328
|
+
subEpochs: new Map(),
|
|
329
|
+
epoch: 0,
|
|
330
|
+
})
|
|
308
331
|
},
|
|
309
332
|
|
|
310
333
|
message(ws, data) {
|