@belte/belte 0.19.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.
Files changed (326) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/LICENSE +21 -0
  3. package/README.md +559 -0
  4. package/bin/belte.ts +183 -0
  5. package/package.json +110 -0
  6. package/src/App.svelte +31 -0
  7. package/src/appEntry.ts +151 -0
  8. package/src/assets/app.html +14 -0
  9. package/src/belteResolverPlugin.ts +858 -0
  10. package/src/build.ts +147 -0
  11. package/src/buildCli.ts +129 -0
  12. package/src/buildDisconnected.ts +122 -0
  13. package/src/bundleApp.ts +149 -0
  14. package/src/bundleDisconnectedEntry.ts +17 -0
  15. package/src/cliEntry.ts +25 -0
  16. package/src/clientBuildPlugins.ts +41 -0
  17. package/src/clientEntry.ts +7 -0
  18. package/src/compile.ts +64 -0
  19. package/src/controlServerWorker.ts +422 -0
  20. package/src/dedupeSveltePlugin.ts +66 -0
  21. package/src/devEntry.ts +169 -0
  22. package/src/discoveryEntry.ts +81 -0
  23. package/src/lib/browser/applyStreamedResolution.ts +33 -0
  24. package/src/lib/browser/cacheEntryFromSnapshot.ts +48 -0
  25. package/src/lib/browser/flushUnresolvedPlaceholders.ts +16 -0
  26. package/src/lib/browser/installStreamingPlaceholders.ts +32 -0
  27. package/src/lib/browser/openResolveStream.ts +42 -0
  28. package/src/lib/browser/page.svelte.ts +258 -0
  29. package/src/lib/browser/pageStreamController.ts +17 -0
  30. package/src/lib/browser/refetchPlaceholder.ts +12 -0
  31. package/src/lib/browser/remoteProxy.ts +37 -0
  32. package/src/lib/browser/socketChannel.ts +192 -0
  33. package/src/lib/browser/socketProxy.ts +57 -0
  34. package/src/lib/browser/startClient.ts +153 -0
  35. package/src/lib/browser/subscribe.ts +131 -0
  36. package/src/lib/browser/types/Errors.ts +9 -0
  37. package/src/lib/browser/types/Layouts.ts +7 -0
  38. package/src/lib/browser/types/Pages.ts +7 -0
  39. package/src/lib/browser/types/StreamingDeferred.ts +9 -0
  40. package/src/lib/bundle/BundleMenu.ts +11 -0
  41. package/src/lib/bundle/BundleMenuItem.ts +24 -0
  42. package/src/lib/bundle/BundleWindow.ts +36 -0
  43. package/src/lib/bundle/WEBVIEW_BUILD_REVISION.ts +9 -0
  44. package/src/lib/bundle/WEBVIEW_VERSION.ts +7 -0
  45. package/src/lib/bundle/bindConnectedFlag.ts +29 -0
  46. package/src/lib/bundle/bindRequestNavigate.ts +31 -0
  47. package/src/lib/bundle/buildWebviewLib.ts +111 -0
  48. package/src/lib/bundle/disconnected.css +9 -0
  49. package/src/lib/bundle/disconnected.svelte +386 -0
  50. package/src/lib/bundle/ensureWebviewLib.ts +20 -0
  51. package/src/lib/bundle/exitWithParent.ts +28 -0
  52. package/src/lib/bundle/infoPlist.ts +46 -0
  53. package/src/lib/bundle/installDownloads.ts +24 -0
  54. package/src/lib/bundle/installMacMenu.ts +39 -0
  55. package/src/lib/bundle/listenLocalControlServer.ts +19 -0
  56. package/src/lib/bundle/native/belteMenu.mm +422 -0
  57. package/src/lib/bundle/native/webview.h +4557 -0
  58. package/src/lib/bundle/onMenu.ts +41 -0
  59. package/src/lib/bundle/openWebview.ts +104 -0
  60. package/src/lib/bundle/pngToIcns.ts +47 -0
  61. package/src/lib/bundle/probeBelteServer.ts +34 -0
  62. package/src/lib/bundle/resolveServerBinary.ts +12 -0
  63. package/src/lib/bundle/resolveWebviewLib.ts +53 -0
  64. package/src/lib/bundle/serverBinaryFilename.ts +8 -0
  65. package/src/lib/bundle/signMacApp.ts +35 -0
  66. package/src/lib/bundle/spawnEmbeddedServer.ts +65 -0
  67. package/src/lib/bundle/stableLocalPort.ts +19 -0
  68. package/src/lib/bundle/waitForServer.ts +23 -0
  69. package/src/lib/bundle/webviewCachePath.ts +23 -0
  70. package/src/lib/bundle/webviewLibName.ts +11 -0
  71. package/src/lib/cli/connectToServer.ts +23 -0
  72. package/src/lib/cli/createClient.ts +170 -0
  73. package/src/lib/cli/dispatchCommand.ts +71 -0
  74. package/src/lib/cli/loadEnvFromBinaryDir.ts +16 -0
  75. package/src/lib/cli/parseArgvForRpc.ts +97 -0
  76. package/src/lib/cli/printHelp.ts +119 -0
  77. package/src/lib/cli/printSessionHelp.ts +27 -0
  78. package/src/lib/cli/printSessionStatus.ts +21 -0
  79. package/src/lib/cli/printTrimmed.ts +8 -0
  80. package/src/lib/cli/printValue.ts +10 -0
  81. package/src/lib/cli/resolveCliTarget.ts +48 -0
  82. package/src/lib/cli/runCli.ts +139 -0
  83. package/src/lib/cli/runSession.ts +105 -0
  84. package/src/lib/cli/startLocalInstance.ts +14 -0
  85. package/src/lib/cli/tokenizeLine.ts +51 -0
  86. package/src/lib/cli/types/CliManifest.ts +9 -0
  87. package/src/lib/cli/types/CliManifestEntry.ts +17 -0
  88. package/src/lib/cli/types/CliTarget.ts +13 -0
  89. package/src/lib/mcp/annotationsForMethod.ts +29 -0
  90. package/src/lib/mcp/createMcpResourceServer.ts +101 -0
  91. package/src/lib/mcp/createMcpServer.ts +42 -0
  92. package/src/lib/mcp/dispatchMcpRequest.ts +146 -0
  93. package/src/lib/mcp/mcpResourceServerSlot.ts +18 -0
  94. package/src/lib/mcp/mcpSurface.ts +265 -0
  95. package/src/lib/mcp/toolResultFromResponse.ts +66 -0
  96. package/src/lib/mcp/types/JsonRpcRequest.ts +12 -0
  97. package/src/lib/mcp/types/JsonRpcResponse.ts +20 -0
  98. package/src/lib/mcp/types/McpResourceContents.ts +10 -0
  99. package/src/lib/mcp/types/McpResourceDescriptor.ts +6 -0
  100. package/src/lib/mcp/types/McpResourceServer.ts +12 -0
  101. package/src/lib/mcp/types/McpServer.ts +9 -0
  102. package/src/lib/mcp/types/McpServerOptions.ts +16 -0
  103. package/src/lib/server/AppModule.ts +33 -0
  104. package/src/lib/server/DELETE.ts +9 -0
  105. package/src/lib/server/GET.ts +9 -0
  106. package/src/lib/server/HEAD.ts +9 -0
  107. package/src/lib/server/PATCH.ts +9 -0
  108. package/src/lib/server/POST.ts +9 -0
  109. package/src/lib/server/PUT.ts +9 -0
  110. package/src/lib/server/agent.ts +76 -0
  111. package/src/lib/server/appDataDir.ts +15 -0
  112. package/src/lib/server/cli/buildEnvContent.ts +19 -0
  113. package/src/lib/server/cli/createTarGz.ts +76 -0
  114. package/src/lib/server/cli/handleCliDownload.ts +153 -0
  115. package/src/lib/server/cli/handleCliInstall.ts +37 -0
  116. package/src/lib/server/cli/installScript.ts +29 -0
  117. package/src/lib/server/cli/maxSourceMtime.ts +26 -0
  118. package/src/lib/server/cookies.ts +29 -0
  119. package/src/lib/server/env.ts +50 -0
  120. package/src/lib/server/error.ts +70 -0
  121. package/src/lib/server/json.ts +28 -0
  122. package/src/lib/server/jsonl.ts +46 -0
  123. package/src/lib/server/prompts/definePrompt.ts +20 -0
  124. package/src/lib/server/prompts/promptRegistry.ts +9 -0
  125. package/src/lib/server/prompts/registerPrompt.ts +6 -0
  126. package/src/lib/server/prompts/renderPromptTemplate.ts +16 -0
  127. package/src/lib/server/prompts/types/Prompt.ts +13 -0
  128. package/src/lib/server/prompts/types/PromptOptions.ts +12 -0
  129. package/src/lib/server/prompts/types/PromptRegistryEntry.ts +13 -0
  130. package/src/lib/server/prompts/types/PromptRoutes.ts +10 -0
  131. package/src/lib/server/redirect.ts +42 -0
  132. package/src/lib/server/request.ts +18 -0
  133. package/src/lib/server/rpc/defineVerb.ts +133 -0
  134. package/src/lib/server/rpc/dispatchVerbInProcess.ts +46 -0
  135. package/src/lib/server/rpc/findVerbByCommandName.ts +18 -0
  136. package/src/lib/server/rpc/parseArgs.ts +95 -0
  137. package/src/lib/server/rpc/registerVerb.ts +6 -0
  138. package/src/lib/server/rpc/types/RemoteHandler.ts +27 -0
  139. package/src/lib/server/rpc/types/RemoteRoutes.ts +13 -0
  140. package/src/lib/server/rpc/types/TypedResponse.ts +18 -0
  141. package/src/lib/server/rpc/types/VerbHelper.ts +68 -0
  142. package/src/lib/server/rpc/types/VerbRegistryEntry.ts +29 -0
  143. package/src/lib/server/rpc/unprocessed.ts +14 -0
  144. package/src/lib/server/rpc/verbRegistry.ts +11 -0
  145. package/src/lib/server/runtime/DEFAULT_PORT.ts +6 -0
  146. package/src/lib/server/runtime/DEV_REBUILD_MESSAGE.ts +4 -0
  147. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +29 -0
  148. package/src/lib/server/runtime/acceptsZstd.ts +8 -0
  149. package/src/lib/server/runtime/buildOpenApiSpec.ts +106 -0
  150. package/src/lib/server/runtime/cacheControlForAsset.ts +22 -0
  151. package/src/lib/server/runtime/containsTraversal.ts +37 -0
  152. package/src/lib/server/runtime/createAssetHeaderCache.ts +35 -0
  153. package/src/lib/server/runtime/createPublicAssetServer.ts +63 -0
  154. package/src/lib/server/runtime/createRouteDispatcher.ts +100 -0
  155. package/src/lib/server/runtime/createServer.ts +692 -0
  156. package/src/lib/server/runtime/devReloadResponse.ts +35 -0
  157. package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +27 -0
  158. package/src/lib/server/runtime/envSchemaStore.ts +15 -0
  159. package/src/lib/server/runtime/findOpenPort.ts +35 -0
  160. package/src/lib/server/runtime/getActiveServer.ts +6 -0
  161. package/src/lib/server/runtime/globToPathSet.ts +29 -0
  162. package/src/lib/server/runtime/inProcessServer.ts +20 -0
  163. package/src/lib/server/runtime/internalErrorResponse.ts +25 -0
  164. package/src/lib/server/runtime/isCrossOriginUpgrade.ts +19 -0
  165. package/src/lib/server/runtime/listenOnOpenPort.ts +36 -0
  166. package/src/lib/server/runtime/logExposedSurfaces.ts +162 -0
  167. package/src/lib/server/runtime/mimeForExtension.ts +20 -0
  168. package/src/lib/server/runtime/parseIdleTimeout.ts +10 -0
  169. package/src/lib/server/runtime/parsePort.ts +11 -0
  170. package/src/lib/server/runtime/registryManifests.ts +66 -0
  171. package/src/lib/server/runtime/requestContext.ts +5 -0
  172. package/src/lib/server/runtime/resolveStreamResponse.ts +29 -0
  173. package/src/lib/server/runtime/runWithRequestScope.ts +57 -0
  174. package/src/lib/server/runtime/safeJsonForScript.ts +17 -0
  175. package/src/lib/server/runtime/serializeCacheSnapshot.ts +45 -0
  176. package/src/lib/server/runtime/serverSlot.ts +13 -0
  177. package/src/lib/server/runtime/setActiveServer.ts +6 -0
  178. package/src/lib/server/runtime/snapshotEntryFromCache.ts +81 -0
  179. package/src/lib/server/runtime/streamCacheResolutions.ts +37 -0
  180. package/src/lib/server/runtime/streamFromIterator.ts +86 -0
  181. package/src/lib/server/runtime/streamStash.ts +64 -0
  182. package/src/lib/server/runtime/types/Assets.ts +1 -0
  183. package/src/lib/server/runtime/types/RequestStore.ts +27 -0
  184. package/src/lib/server/runtime/withResponseDefaults.ts +24 -0
  185. package/src/lib/server/server.ts +32 -0
  186. package/src/lib/server/socket.ts +31 -0
  187. package/src/lib/server/sockets/createSocketDispatcher.ts +311 -0
  188. package/src/lib/server/sockets/defineSocket.ts +167 -0
  189. package/src/lib/server/sockets/lookupSocket.ts +6 -0
  190. package/src/lib/server/sockets/recentHistory.ts +11 -0
  191. package/src/lib/server/sockets/registerSocket.ts +6 -0
  192. package/src/lib/server/sockets/socketOperations.ts +35 -0
  193. package/src/lib/server/sockets/socketRegistry.ts +9 -0
  194. package/src/lib/server/sockets/types/Socket.ts +21 -0
  195. package/src/lib/server/sockets/types/SocketClientFrame.ts +18 -0
  196. package/src/lib/server/sockets/types/SocketOperation.ts +22 -0
  197. package/src/lib/server/sockets/types/SocketOptions.ts +22 -0
  198. package/src/lib/server/sockets/types/SocketRegistryEntry.ts +17 -0
  199. package/src/lib/server/sockets/types/SocketRoutes.ts +10 -0
  200. package/src/lib/server/sockets/types/SocketServerFrame.ts +15 -0
  201. package/src/lib/server/sse.ts +53 -0
  202. package/src/lib/shared/BELTE_PACKAGE_NAME.ts +7 -0
  203. package/src/lib/shared/CACHE_CONTROL_VALUES.ts +16 -0
  204. package/src/lib/shared/HttpError.ts +19 -0
  205. package/src/lib/shared/RESOLVE_STREAM_PATH.ts +7 -0
  206. package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +11 -0
  207. package/src/lib/shared/activeCacheStore.ts +20 -0
  208. package/src/lib/shared/appDataDir.ts +34 -0
  209. package/src/lib/shared/belteImportName.ts +44 -0
  210. package/src/lib/shared/browserClientFlags.ts +10 -0
  211. package/src/lib/shared/buildRpcRequest.ts +70 -0
  212. package/src/lib/shared/bundleLayout.ts +36 -0
  213. package/src/lib/shared/bundled.ts +34 -0
  214. package/src/lib/shared/cache.ts +559 -0
  215. package/src/lib/shared/cacheStoreSlot.ts +16 -0
  216. package/src/lib/shared/canonicalJson.ts +63 -0
  217. package/src/lib/shared/carriesBodyArgs.ts +13 -0
  218. package/src/lib/shared/clearLastConnection.ts +7 -0
  219. package/src/lib/shared/commandNameForUrl.ts +17 -0
  220. package/src/lib/shared/createCacheStore.ts +75 -0
  221. package/src/lib/shared/createPushIterator.ts +93 -0
  222. package/src/lib/shared/createRemoteFunction.ts +99 -0
  223. package/src/lib/shared/decodeResponse.ts +47 -0
  224. package/src/lib/shared/detectTarget.ts +27 -0
  225. package/src/lib/shared/exeSuffix.ts +9 -0
  226. package/src/lib/shared/exitOnBuildFailure.ts +17 -0
  227. package/src/lib/shared/extraForwardHeaders.ts +16 -0
  228. package/src/lib/shared/fileStem.ts +9 -0
  229. package/src/lib/shared/findExportCallSite.ts +479 -0
  230. package/src/lib/shared/forwardHeaders.ts +41 -0
  231. package/src/lib/shared/getRemoteMeta.ts +5 -0
  232. package/src/lib/shared/globalCacheStore.ts +15 -0
  233. package/src/lib/shared/globalCacheStoreSlot.ts +14 -0
  234. package/src/lib/shared/importNamesToStrip.ts +13 -0
  235. package/src/lib/shared/invalidateEvent.ts +11 -0
  236. package/src/lib/shared/isCompileTarget.ts +15 -0
  237. package/src/lib/shared/isDebugEnabled.ts +23 -0
  238. package/src/lib/shared/isModuleNotFound.ts +16 -0
  239. package/src/lib/shared/isReadOnlyMethod.ts +14 -0
  240. package/src/lib/shared/isStreamingResponse.ts +11 -0
  241. package/src/lib/shared/jsonSchemaForPromptArguments.ts +29 -0
  242. package/src/lib/shared/jsonSchemaForSchema.ts +32 -0
  243. package/src/lib/shared/jsonlErrorFrame.ts +24 -0
  244. package/src/lib/shared/keyForRemoteCall.ts +29 -0
  245. package/src/lib/shared/lastConnectionPath.ts +7 -0
  246. package/src/lib/shared/loadEnvFile.ts +17 -0
  247. package/src/lib/shared/loadEnvFromDataDir.ts +15 -0
  248. package/src/lib/shared/loadSvelteConfig.ts +18 -0
  249. package/src/lib/shared/log.ts +104 -0
  250. package/src/lib/shared/manifestModule.ts +39 -0
  251. package/src/lib/shared/memoizeByKey.ts +24 -0
  252. package/src/lib/shared/nearestLayoutPrefix.ts +36 -0
  253. package/src/lib/shared/normalizeTarget.ts +10 -0
  254. package/src/lib/shared/pageUrlForFile.ts +14 -0
  255. package/src/lib/shared/parseBoundedEnvInt.ts +20 -0
  256. package/src/lib/shared/parseEnv.ts +30 -0
  257. package/src/lib/shared/parsePromptMarkdown.ts +34 -0
  258. package/src/lib/shared/parseRouteSegments.ts +22 -0
  259. package/src/lib/shared/prepareRpcModule.ts +59 -0
  260. package/src/lib/shared/prepareSocketModule.ts +49 -0
  261. package/src/lib/shared/programNameForPackage.ts +14 -0
  262. package/src/lib/shared/promptNameForFile.ts +10 -0
  263. package/src/lib/shared/queryStringFromArgs.ts +27 -0
  264. package/src/lib/shared/readEnvFile.ts +15 -0
  265. package/src/lib/shared/readLastConnection.ts +18 -0
  266. package/src/lib/shared/readPackageJson.ts +9 -0
  267. package/src/lib/shared/recordRemoteMeta.ts +5 -0
  268. package/src/lib/shared/remoteMetaStore.ts +16 -0
  269. package/src/lib/shared/resolveClientFlags.ts +20 -0
  270. package/src/lib/shared/responseErrorText.ts +9 -0
  271. package/src/lib/shared/rpcUrlForFile.ts +19 -0
  272. package/src/lib/shared/runningAsStandaloneBinary.ts +13 -0
  273. package/src/lib/shared/serializeEnv.ts +18 -0
  274. package/src/lib/shared/setCacheStoreResolver.ts +6 -0
  275. package/src/lib/shared/setGlobalCacheStoreResolver.ts +6 -0
  276. package/src/lib/shared/socketNameForFile.ts +11 -0
  277. package/src/lib/shared/sseErrorFrame.ts +29 -0
  278. package/src/lib/shared/streamResponse.ts +169 -0
  279. package/src/lib/shared/stripImport.ts +27 -0
  280. package/src/lib/shared/subscribableFromResponse.ts +51 -0
  281. package/src/lib/shared/toBunRoutePattern.ts +28 -0
  282. package/src/lib/shared/types/CacheEntry.ts +63 -0
  283. package/src/lib/shared/types/CacheInvalidation.ts +9 -0
  284. package/src/lib/shared/types/CacheOptions.ts +33 -0
  285. package/src/lib/shared/types/CacheSnapshot.ts +16 -0
  286. package/src/lib/shared/types/CacheSnapshotEntry.ts +15 -0
  287. package/src/lib/shared/types/CacheStore.ts +32 -0
  288. package/src/lib/shared/types/ClientFlags.ts +11 -0
  289. package/src/lib/shared/types/CompileTarget.ts +6 -0
  290. package/src/lib/shared/types/HttpVerb.ts +1 -0
  291. package/src/lib/shared/types/LastConnection.ts +9 -0
  292. package/src/lib/shared/types/PromptArgument.ts +12 -0
  293. package/src/lib/shared/types/RawRemoteFunction.ts +13 -0
  294. package/src/lib/shared/types/RemoteFunction.ts +42 -0
  295. package/src/lib/shared/types/StandardSchemaV1.ts +57 -0
  296. package/src/lib/shared/types/StreamedResolution.ts +10 -0
  297. package/src/lib/shared/types/StreamingPlaceholder.ts +13 -0
  298. package/src/lib/shared/types/Subscribable.ts +15 -0
  299. package/src/lib/shared/types/SvelteConfig.ts +5 -0
  300. package/src/lib/shared/withJsonSchema.ts +20 -0
  301. package/src/lib/shared/writeLastConnection.ts +13 -0
  302. package/src/lib/shared/writeRoutesDts.ts +67 -0
  303. package/src/lib/test/clearVerbRegistry.ts +11 -0
  304. package/src/lib/test/createTestClient.ts +78 -0
  305. package/src/preload.ts +20 -0
  306. package/src/scaffold.ts +92 -0
  307. package/src/serverBuildPlugins.ts +25 -0
  308. package/src/serverEntry.ts +94 -0
  309. package/src/sveltePlugin.ts +58 -0
  310. package/src/tailwindStylePreprocessor.ts +62 -0
  311. package/template/bunfig.toml +4 -0
  312. package/template/package.json +19 -0
  313. package/template/src/app.ts +23 -0
  314. package/template/src/browser/app.css +21 -0
  315. package/template/src/browser/app.html +24 -0
  316. package/template/src/browser/pages/about/page.svelte +5 -0
  317. package/template/src/browser/pages/layout.svelte +26 -0
  318. package/template/src/browser/pages/page.svelte +20 -0
  319. package/template/src/bundle/icon.png +0 -0
  320. package/template/src/cli/banner.txt +3 -0
  321. package/template/src/cli/footer.txt +1 -0
  322. package/template/src/server/config.ts +17 -0
  323. package/template/src/server/rpc/getHello.ts +35 -0
  324. package/template/svelte.config.js +12 -0
  325. package/template/tsconfig.json +18 -0
  326. package/tsconfig.app.json +16 -0
@@ -0,0 +1,311 @@
1
+ import type { ServerWebSocket } from 'bun'
2
+ import { log } from '../../shared/log.ts'
3
+ import { memoizeByKey } from '../../shared/memoizeByKey.ts'
4
+ import { error } from '../error.ts'
5
+ import { json } from '../json.ts'
6
+ import { sse } from '../sse.ts'
7
+ import { lookupSocket } from './lookupSocket.ts'
8
+ import { recentHistory } from './recentHistory.ts'
9
+ import type { SocketClientFrame } from './types/SocketClientFrame.ts'
10
+ import type { SocketRoutes } from './types/SocketRoutes.ts'
11
+ import type { SocketServerFrame } from './types/SocketServerFrame.ts'
12
+
13
+ // Reused across every inbound binary frame rather than allocated per message.
14
+ const textDecoder = new TextDecoder()
15
+
16
+ type SocketDispatcher = {
17
+ open(ws: ServerWebSocket<unknown>): void
18
+ message(ws: ServerWebSocket<unknown>, data: string | Buffer): void
19
+ close(ws: ServerWebSocket<unknown>): void
20
+ rest(req: Request, name: string): Promise<Response>
21
+ }
22
+
23
+ /*
24
+ Per-connection state: which sockets this ws is currently subscribed to
25
+ (at the Bun-topic level), and which `sub` ids map to which socket. One
26
+ ws can hold multiple subs against the same socket (e.g. one with
27
+ history, one without); the Bun-topic subscription is reference-counted
28
+ so we only `ws.unsubscribe` when the last local sub drops.
29
+ */
30
+ type ConnectionState = {
31
+ subToSocket: Map<string, string>
32
+ socketSubs: Map<string, Set<string>>
33
+ }
34
+
35
+ /*
36
+ Bridges the framework's socket registry to a single ws per client. All
37
+ sockets multiplex over `/__belte/sockets`. Steady-state fan-out rides
38
+ Bun's native `server.publish('socket:<name>', frame)` so the dispatcher
39
+ is only on the path for sub/unsub bookkeeping and client-initiated pub
40
+ validation; the published `msg` frames go from publisher to subscribers
41
+ without touching JS per frame.
42
+
43
+ `sub` opens a subscription: history is replayed (unless the client
44
+ passed `tail: true`) directly to this ws, then the ws is added to the
45
+ Bun topic. `unsub` drops the local sub and unsubscribes the ws from
46
+ the Bun topic if no other local subs remain. `pub` validates the
47
+ socket's `allowClientPublish` policy and calls `socket.publish` —
48
+ which fans out to in-process iterators and republishes through Bun
49
+ to other connected clients.
50
+
51
+ Module-level lookups are cached per socket name: loading a socket
52
+ module triggers its `defineSocket` call, which inserts into the
53
+ registry. After that the dispatcher just reads the registry.
54
+ */
55
+ export function createSocketDispatcher(sockets: SocketRoutes): SocketDispatcher {
56
+ const connections = new WeakMap<ServerWebSocket<unknown>, ConnectionState>()
57
+
58
+ const ensureLoaded = memoizeByKey((name): Promise<void> | undefined => {
59
+ const loader = sockets[name]
60
+ return loader ? loader().then(() => undefined) : undefined
61
+ })
62
+
63
+ function send(ws: ServerWebSocket<unknown>, frame: SocketServerFrame): void {
64
+ if (ws.readyState !== WebSocket.OPEN) {
65
+ return
66
+ }
67
+ ws.send(JSON.stringify(frame))
68
+ }
69
+
70
+ function addSub(state: ConnectionState, name: string, sub: string): boolean {
71
+ state.subToSocket.set(sub, name)
72
+ let subs = state.socketSubs.get(name)
73
+ if (!subs) {
74
+ subs = new Set()
75
+ state.socketSubs.set(name, subs)
76
+ }
77
+ const wasEmpty = subs.size === 0
78
+ subs.add(sub)
79
+ return wasEmpty
80
+ }
81
+
82
+ function removeSub(state: ConnectionState, sub: string): string | undefined {
83
+ const name = state.subToSocket.get(sub)
84
+ if (!name) {
85
+ return undefined
86
+ }
87
+ state.subToSocket.delete(sub)
88
+ const subs = state.socketSubs.get(name)
89
+ if (!subs) {
90
+ return undefined
91
+ }
92
+ subs.delete(sub)
93
+ if (subs.size === 0) {
94
+ state.socketSubs.delete(name)
95
+ return name
96
+ }
97
+ return undefined
98
+ }
99
+
100
+ async function handleSub(
101
+ ws: ServerWebSocket<unknown>,
102
+ state: ConnectionState,
103
+ frame: Extract<SocketClientFrame, { type: 'sub' }>,
104
+ ): Promise<void> {
105
+ // Reject this sub: emit the error then the terminal end frame for its id.
106
+ function fail(message: string): void {
107
+ send(ws, { type: 'err', sub: frame.sub, message })
108
+ send(ws, { type: 'end', sub: frame.sub })
109
+ }
110
+ const loader = ensureLoaded(frame.socket)
111
+ if (!loader) {
112
+ return fail(`[belte] no socket registered at ${frame.socket}`)
113
+ }
114
+ try {
115
+ await loader
116
+ } catch (error) {
117
+ log.error(error)
118
+ return fail(error instanceof Error ? error.message : String(error))
119
+ }
120
+ const entry = lookupSocket(frame.socket)
121
+ if (!entry) {
122
+ return fail(`[belte] socket module at ${frame.socket} did not register a Socket export`)
123
+ }
124
+ const isFirstLocalSub = addSub(state, frame.socket, frame.sub)
125
+ if (isFirstLocalSub) {
126
+ ws.subscribe(`socket:${frame.socket}`)
127
+ }
128
+ /*
129
+ Replay history directly to this ws via ws.send (not
130
+ server.publish) so other connected subscribers don't see the
131
+ replay. Live messages published from now on flow through the
132
+ Bun topic the ws just joined; clients may observe live messages
133
+ interleaved with the tail of history, so user payloads should
134
+ carry an id/timestamp when ordering matters.
135
+
136
+ `replay === undefined` means full replay (bare `for await`);
137
+ a number is clamped to the buffer length so the client can ask
138
+ for "as many as available, up to N".
139
+ */
140
+ recentHistory(entry, frame.replay).forEach((message) => {
141
+ send(ws, { type: 'msg', socket: frame.socket, message })
142
+ })
143
+ }
144
+
145
+ function handleUnsub(
146
+ ws: ServerWebSocket<unknown>,
147
+ state: ConnectionState,
148
+ frame: Extract<SocketClientFrame, { type: 'unsub' }>,
149
+ ): void {
150
+ const emptied = removeSub(state, frame.sub)
151
+ if (emptied) {
152
+ ws.unsubscribe(`socket:${emptied}`)
153
+ }
154
+ send(ws, { type: 'end', sub: frame.sub })
155
+ }
156
+
157
+ async function handlePub(
158
+ ws: ServerWebSocket<unknown>,
159
+ frame: Extract<SocketClientFrame, { type: 'pub' }>,
160
+ ): Promise<void> {
161
+ const loader = ensureLoaded(frame.socket)
162
+ if (!loader) {
163
+ return
164
+ }
165
+ try {
166
+ await loader
167
+ } catch (error) {
168
+ log.error(error)
169
+ return
170
+ }
171
+ const entry = lookupSocket(frame.socket)
172
+ if (!entry) {
173
+ return
174
+ }
175
+ if (!entry.allowClientPublish) {
176
+ /*
177
+ Silent drop: the publish is rejected because the topic
178
+ wasn't declared `{ clientPublish: true }`. Surfacing this as
179
+ an error per-publish would tempt apps to attempt-then-handle
180
+ instead of routing through an HTTP route for auth. Log it
181
+ once per process at debug level (out of scope here) if
182
+ visibility is needed.
183
+ */
184
+ return
185
+ }
186
+ /*
187
+ publish() runs the topic's optional Standard Schema synchronously
188
+ and throws on failure (see defineSocket.validateSync). The
189
+ dispatcher invokes us via `void handlePub(...)`, so an unhandled
190
+ throw would surface as an unhandled promise rejection on every
191
+ malformed client frame. Catch + log so a buggy client can't take
192
+ the process down.
193
+ */
194
+ try {
195
+ entry.socket.publish(frame.message)
196
+ } catch (error) {
197
+ log.error(error)
198
+ }
199
+ /*
200
+ ws parameter retained for future per-ws auth context (cookies on
201
+ upgrade) the canPublish hook would consult.
202
+ */
203
+ void ws
204
+ }
205
+
206
+ /*
207
+ HTTP face of the sockets hub at `/__belte/sockets/<name>`, for the CLI
208
+ and MCP (which can't speak the ws multiplex protocol):
209
+
210
+ GET text/event-stream → live SSE stream; `?tail=N` replays the last
211
+ N buffered messages before tailing live (default 0 = live only).
212
+ GET otherwise → JSON array of the recent history buffer
213
+ (`?tail=N` caps it; default all).
214
+ POST → publish the JSON body, gated by the socket's
215
+ clientPublish policy and validated against its schema.
216
+
217
+ Loads the socket module on first hit (same cache the ws path uses) so
218
+ its defineSocket call populates the registry.
219
+ */
220
+ async function rest(req: Request, name: string): Promise<Response> {
221
+ const loader = ensureLoaded(name)
222
+ if (!loader) {
223
+ return error(404)
224
+ }
225
+ try {
226
+ await loader
227
+ } catch (loadError) {
228
+ log.error(loadError)
229
+ return error(500, 'socket failed to load')
230
+ }
231
+ const entry = lookupSocket(name)
232
+ if (!entry) {
233
+ return error(404)
234
+ }
235
+ const tailParam = new URL(req.url).searchParams.get('tail')
236
+ const count = tailParam !== null ? Number(tailParam) : undefined
237
+ if (req.method === 'GET' || req.method === 'HEAD') {
238
+ if ((req.headers.get('accept') ?? '').includes('text/event-stream')) {
239
+ return sse(entry.socket.tail(count ?? 0))
240
+ }
241
+ return json(recentHistory(entry, count))
242
+ }
243
+ if (req.method === 'POST') {
244
+ if (!entry.allowClientPublish) {
245
+ return error(403, 'publishing not allowed')
246
+ }
247
+ let message: unknown
248
+ try {
249
+ message = await req.json()
250
+ } catch {
251
+ return error(400, 'body must be JSON')
252
+ }
253
+ try {
254
+ // publish() validates against the socket schema and throws on a bad payload.
255
+ entry.socket.publish(message)
256
+ } catch (publishError) {
257
+ return error(
258
+ 422,
259
+ publishError instanceof Error ? publishError.message : String(publishError),
260
+ )
261
+ }
262
+ return json({ ok: true })
263
+ }
264
+ return error(405, undefined, { headers: { Allow: 'GET, POST' } })
265
+ }
266
+
267
+ return {
268
+ rest,
269
+
270
+ open(ws) {
271
+ connections.set(ws, { subToSocket: new Map(), socketSubs: new Map() })
272
+ },
273
+
274
+ message(ws, data) {
275
+ const state = connections.get(ws)
276
+ if (!state) {
277
+ return
278
+ }
279
+ const text = typeof data === 'string' ? data : textDecoder.decode(data)
280
+ let frame: SocketClientFrame
281
+ try {
282
+ frame = JSON.parse(text) as SocketClientFrame
283
+ } catch {
284
+ return
285
+ }
286
+ if (frame.type === 'sub') {
287
+ void handleSub(ws, state, frame)
288
+ return
289
+ }
290
+ if (frame.type === 'unsub') {
291
+ handleUnsub(ws, state, frame)
292
+ return
293
+ }
294
+ if (frame.type === 'pub') {
295
+ void handlePub(ws, frame)
296
+ return
297
+ }
298
+ },
299
+
300
+ close(ws) {
301
+ const state = connections.get(ws)
302
+ if (!state) {
303
+ return
304
+ }
305
+ connections.delete(ws)
306
+ for (const name of state.socketSubs.keys()) {
307
+ ws.unsubscribe(`socket:${name}`)
308
+ }
309
+ },
310
+ }
311
+ }
@@ -0,0 +1,167 @@
1
+ import { createPushIterator } from '../../shared/createPushIterator.ts'
2
+ import { resolveClientFlags } from '../../shared/resolveClientFlags.ts'
3
+ import { getActiveServer } from '../runtime/getActiveServer.ts'
4
+ import { registerSocket } from './registerSocket.ts'
5
+ import type { Socket } from './types/Socket.ts'
6
+ import type { SocketOptions } from './types/SocketOptions.ts'
7
+
8
+ /*
9
+ Server-side construction of a Socket. The bundler rewrites every
10
+ `export const NAME = socket(opts)` inside `src/server/sockets/<file>.ts` into
11
+ `__belteDefineSocket__("<name>", opts)` so the file path becomes the
12
+ socket's identity. Each subscriber gets its own queue + notifier, the
13
+ optional history buffer is shared, and outbound fan-out rides Bun's
14
+ native `server.publish` so connected ws clients are notified by the
15
+ runtime in C rather than per-client iteration in JS.
16
+
17
+ The Socket itself is the AsyncIterable: `for await (const m of chat)`
18
+ replays the full history buffer then tails live. `chat.tail(count)`
19
+ opens a subscription that replays the last `count` items (default `0`,
20
+ clamped to the configured `history` max). When `ttl` is set, history
21
+ entries older than `ttl` ms are evicted lazily on every read/append —
22
+ no timer runs in the background. `chat.publish(m)` is isomorphic —
23
+ called server-side it both notifies in-process iterators and broadcasts
24
+ to remote subscribers; called client-side (via socketProxy) it sends a
25
+ `pub` frame the dispatcher validates and forwards.
26
+ */
27
+ export function defineSocket<T>(name: string, opts: SocketOptions = {}): Socket<T> {
28
+ const historySize = opts.history ?? 0
29
+ const ttl = opts.ttl
30
+ const schema = opts.schema
31
+ /*
32
+ A schema makes the socket's payload safe to advertise to non-browser
33
+ surfaces, so it flips mcp/cli on by default — exposing the `tail` read
34
+ tool (and `publish` when clientPublish is set). Explicit `clients` wins.
35
+ */
36
+ const hasSchema = schema !== undefined
37
+ const clients = resolveClientFlags(opts.clients, { mcp: hasSchema, cli: hasSchema })
38
+ type BufferEntry = { value: T; expiresAt: number | undefined }
39
+ const buffer: BufferEntry[] = []
40
+ const subscribers = new Set<(message: T) => void>()
41
+ const topic = `socket:${name}`
42
+
43
+ /*
44
+ History entries are stored with an expiry timestamp. When `ttl` is set,
45
+ every read/append starts by dropping leading entries whose expiry has
46
+ passed — entries are appended in order so the expired prefix is
47
+ contiguous. No timer/setInterval is needed: expiry is lazy.
48
+ */
49
+ function pruneExpired(now: number): void {
50
+ if (ttl === undefined) {
51
+ return
52
+ }
53
+ let drop = 0
54
+ for (const entry of buffer) {
55
+ if (entry.expiresAt !== undefined && entry.expiresAt <= now) {
56
+ drop++
57
+ } else {
58
+ break
59
+ }
60
+ }
61
+ if (drop > 0) {
62
+ buffer.splice(0, drop)
63
+ }
64
+ }
65
+
66
+ /*
67
+ Active server is set once per process during createServer's boot,
68
+ immediately after Bun.serve resolves, and never reassigned. Resolve
69
+ it lazily on the first publish then keep the reference so subsequent
70
+ publishes skip the per-call getter.
71
+ */
72
+ let cachedServer: ReturnType<typeof getActiveServer>
73
+ /*
74
+ When a schema is attached, publish() validates synchronously and
75
+ throws on bad payloads. Standard Schema's validate() is generally
76
+ async — but for the synchronous server-side publish path we treat
77
+ a Promise return as a programming error (publish must be sync to
78
+ preserve in-process notify ordering). Schemas that need async
79
+ refinement should pre-validate at the call site instead.
80
+ */
81
+ function validateSync(message: T): T {
82
+ if (!schema) {
83
+ return message
84
+ }
85
+ const result = schema['~standard'].validate(message)
86
+ if (result instanceof Promise) {
87
+ throw new Error(
88
+ `[belte] socket "${name}" schema returned a Promise — sockets require sync validation`,
89
+ )
90
+ }
91
+ if (result.issues) {
92
+ throw new Error(
93
+ `[belte] socket "${name}" publish payload failed validation: ${JSON.stringify(result.issues)}`,
94
+ )
95
+ }
96
+ return result.value as T
97
+ }
98
+ function publish(message: T): void {
99
+ const validated = validateSync(message)
100
+ if (historySize > 0) {
101
+ const now = Date.now()
102
+ pruneExpired(now)
103
+ buffer.push({ value: validated, expiresAt: ttl === undefined ? undefined : now + ttl })
104
+ if (buffer.length > historySize) {
105
+ buffer.shift()
106
+ }
107
+ }
108
+ for (const notify of subscribers) {
109
+ notify(validated)
110
+ }
111
+ if (cachedServer === undefined) {
112
+ cachedServer = getActiveServer()
113
+ }
114
+ const server = cachedServer
115
+ if (server) {
116
+ server.publish(topic, JSON.stringify({ type: 'msg', socket: name, message: validated }))
117
+ }
118
+ }
119
+
120
+ /*
121
+ replay === 'all' replays the entire buffer (bare `for await`);
122
+ a number replays the last min(count, buffer.length) items.
123
+ */
124
+ function iterate(replay: number | 'all'): AsyncIterable<T> {
125
+ return {
126
+ [Symbol.asyncIterator](): AsyncIterator<T, void, undefined> {
127
+ let subscriber: ((message: T) => void) | undefined
128
+ const iter = createPushIterator<T>(() => {
129
+ if (subscriber) {
130
+ subscribers.delete(subscriber)
131
+ }
132
+ })
133
+ pruneExpired(Date.now())
134
+ const replayCount =
135
+ replay === 'all' ? buffer.length : Math.min(replay, buffer.length)
136
+ if (replayCount > 0) {
137
+ const start = buffer.length - replayCount
138
+ for (let index = start; index < buffer.length; index++) {
139
+ iter.push((buffer[index] as BufferEntry).value)
140
+ }
141
+ }
142
+ subscriber = (message: T) => iter.push(message)
143
+ subscribers.add(subscriber)
144
+ return iter
145
+ },
146
+ }
147
+ }
148
+
149
+ const self: Socket<T> = {
150
+ name,
151
+ clients,
152
+ publish,
153
+ tail: (count = 0) => iterate(count),
154
+ [Symbol.asyncIterator]: () => iterate('all')[Symbol.asyncIterator](),
155
+ }
156
+ registerSocket({
157
+ socket: self as Socket<unknown>,
158
+ allowClientPublish: opts.clientPublish ?? false,
159
+ schema,
160
+ clients,
161
+ snapshotHistory: () => {
162
+ pruneExpired(Date.now())
163
+ return buffer.map((entry) => entry.value)
164
+ },
165
+ })
166
+ return self
167
+ }
@@ -0,0 +1,6 @@
1
+ import { socketRegistry } from './socketRegistry.ts'
2
+ import type { SocketRegistryEntry } from './types/SocketRegistryEntry.ts'
3
+
4
+ export function lookupSocket(name: string): SocketRegistryEntry | undefined {
5
+ return socketRegistry.get(name)
6
+ }
@@ -0,0 +1,11 @@
1
+ import type { SocketRegistryEntry } from './types/SocketRegistryEntry.ts'
2
+
3
+ /*
4
+ Recent slice of a socket's history buffer: the last `count` messages, or
5
+ the whole buffer when `count` is undefined. Shared by the sockets HTTP
6
+ `rest()` face and the MCP `<base>-tail` tool so the two can't drift.
7
+ */
8
+ export function recentHistory(entry: SocketRegistryEntry, count: number | undefined): unknown[] {
9
+ const history = entry.snapshotHistory()
10
+ return count === undefined ? history : history.slice(Math.max(0, history.length - count))
11
+ }
@@ -0,0 +1,6 @@
1
+ import { socketRegistry } from './socketRegistry.ts'
2
+ import type { SocketRegistryEntry } from './types/SocketRegistryEntry.ts'
3
+
4
+ export function registerSocket(entry: SocketRegistryEntry): void {
5
+ socketRegistry.set(entry.socket.name, entry)
6
+ }
@@ -0,0 +1,35 @@
1
+ import { commandNameForUrl } from '../../shared/commandNameForUrl.ts'
2
+ import type { SocketOperation } from './types/SocketOperation.ts'
3
+ import type { SocketRegistryEntry } from './types/SocketRegistryEntry.ts'
4
+
5
+ /*
6
+ Projects a socket registry entry into the operations it exposes to the
7
+ CLI and MCP. Single source for the naming convention (`<base>-tail` /
8
+ `<base>-publish`), the existence rule (tail always; publish only when the
9
+ socket allows client publishing), and each operation's HTTP face — so the
10
+ CLI manifest builder, the MCP tool list, and the MCP tool dispatcher can't
11
+ disagree about which operations a socket has or what they're called.
12
+ */
13
+ export function socketOperations(entry: SocketRegistryEntry): SocketOperation[] {
14
+ const base = commandNameForUrl(entry.socket.name)
15
+ const restUrl = `/__belte/sockets/${entry.socket.name}`
16
+ const operations: SocketOperation[] = [
17
+ {
18
+ kind: 'tail',
19
+ name: `${base}-tail`,
20
+ socketName: entry.socket.name,
21
+ restUrl,
22
+ method: 'GET',
23
+ },
24
+ ]
25
+ if (entry.allowClientPublish) {
26
+ operations.push({
27
+ kind: 'publish',
28
+ name: `${base}-publish`,
29
+ socketName: entry.socket.name,
30
+ restUrl,
31
+ method: 'POST',
32
+ })
33
+ }
34
+ return operations
35
+ }
@@ -0,0 +1,9 @@
1
+ import type { SocketRegistryEntry } from './types/SocketRegistryEntry.ts'
2
+
3
+ /*
4
+ Process-wide registry of every Socket declared in the app. defineSocket
5
+ inserts on first construction; the dispatcher reads on every `sub` /
6
+ `pub` frame so it can find the right Socket by name and check the
7
+ opted-in `allowClientPublish` policy.
8
+ */
9
+ export const socketRegistry = new Map<string, SocketRegistryEntry>()
@@ -0,0 +1,21 @@
1
+ import type { ClientFlags } from '../../../shared/types/ClientFlags.ts'
2
+
3
+ /*
4
+ Bidirectional named broadcast primitive. Declared once with `socket<T>()`
5
+ inside a file under `src/server/sockets/`; the same import resolves to a server-side
6
+ fan-out and a client-side ws proxy by build target. Iterating the socket
7
+ opens a subscription with full history replay if the topic was declared
8
+ with `{ history: n }`. `.tail(count)` opens one that replays the last
9
+ `count` items (default `0`, clamped to the topic's history max) before
10
+ tailing live. `publish` is isomorphic: server code publishes in-process
11
+ and fans out to remote subscribers; client code sends a `pub` frame the
12
+ dispatcher validates against the topic's `clientPublish` flag. `clients`
13
+ exposes which adapter surfaces (browser / mcp / cli) advertise this
14
+ socket.
15
+ */
16
+ export interface Socket<T> extends AsyncIterable<T> {
17
+ readonly name: string
18
+ readonly clients: ClientFlags
19
+ publish(message: T): void
20
+ tail(count?: number): AsyncIterable<T>
21
+ }
@@ -0,0 +1,18 @@
1
+ /*
2
+ Wire frame the browser sends over the multiplexed socket connection.
3
+ `sub` opens a subscription against `socket`. The optional `replay`
4
+ controls history: omitted = full replay (default `for await`); a
5
+ number = at most that many trailing items (clamped server-side to the
6
+ topic's history max). `unsub` closes one. `pub` publishes a message —
7
+ the dispatcher checks the topic's `clientPublish` flag before fanning
8
+ out.
9
+
10
+ `sub` is the per-subscription id minted client-side; the server treats
11
+ it as opaque and routes inbound `msg|err|end` frames back to the same
12
+ id so one ws can multiplex many subscriptions to the same or different
13
+ sockets.
14
+ */
15
+ export type SocketClientFrame =
16
+ | { type: 'sub'; sub: string; socket: string; replay?: number }
17
+ | { type: 'unsub'; sub: string }
18
+ | { type: 'pub'; socket: string; message: unknown }
@@ -0,0 +1,22 @@
1
+ import type { HttpVerb } from '../../../shared/types/HttpVerb.ts'
2
+
3
+ /*
4
+ One operation a socket exposes to the non-browser surfaces. A socket
5
+ always offers a `tail` (read recent / stream live) and, when
6
+ `clientPublish` is set, a `publish` (send a message). This is the shared
7
+ skeleton — name, kind, HTTP face — that the CLI manifest, the MCP tool
8
+ list, and the MCP dispatcher all read instead of re-deriving the naming
9
+ convention and existence rule independently. Each surface dresses it with
10
+ its own presentation (descriptions, input schema, annotations).
11
+ */
12
+ export type SocketOperation = {
13
+ kind: 'tail' | 'publish'
14
+ // Command/tool name: the socket's command-name base plus `-tail` / `-publish`.
15
+ name: string
16
+ // Raw socket name, for the HTTP path and human-facing descriptions.
17
+ socketName: string
18
+ // HTTP face of the operation: `/__belte/sockets/<name>`.
19
+ restUrl: string
20
+ // GET for tail, POST for publish.
21
+ method: HttpVerb
22
+ }
@@ -0,0 +1,22 @@
1
+ import type { ClientFlags } from '../../../shared/types/ClientFlags.ts'
2
+ import type { StandardSchemaV1 } from '../../../shared/types/StandardSchemaV1.ts'
3
+
4
+ /*
5
+ Server-side options passed when declaring a socket via `socket<T>(opts)`.
6
+ History buffer (replayed on first iteration), per-frame TTL (history
7
+ entries older than `ttl` ms are evicted before replay), and the client-
8
+ publish gate (off by default — server-only topics ignore pub frames
9
+ coming over the wire). Optional Standard Schema validates payloads on
10
+ publish and gives MCP / CLI a typed payload to describe (projected via the
11
+ schema's own `toJSONSchema()` — wrap with withJsonSchema if its library lacks
12
+ one). `clients` controls which non-browser surfaces (mcp / cli) expose this
13
+ socket; browser is the historical default. All server-only state the bundler
14
+ strips out of the client stub.
15
+ */
16
+ export type SocketOptions<Schema extends StandardSchemaV1 = StandardSchemaV1> = {
17
+ history?: number
18
+ ttl?: number
19
+ clientPublish?: boolean
20
+ schema?: Schema
21
+ clients?: Partial<ClientFlags>
22
+ }
@@ -0,0 +1,17 @@
1
+ import type { ClientFlags } from '../../../shared/types/ClientFlags.ts'
2
+ import type { StandardSchemaV1 } from '../../../shared/types/StandardSchemaV1.ts'
3
+ import type { Socket } from './Socket.ts'
4
+
5
+ /*
6
+ Per-socket registry record. The Socket itself stays uniform between
7
+ server and client by parking policy state (history snapshot, client
8
+ publish gate, payload schema, client targeting) here instead of leaking
9
+ into the public Socket shape.
10
+ */
11
+ export type SocketRegistryEntry = {
12
+ socket: Socket<unknown>
13
+ allowClientPublish: boolean
14
+ schema: StandardSchemaV1 | undefined
15
+ clients: ClientFlags
16
+ snapshotHistory(): unknown[]
17
+ }
@@ -0,0 +1,10 @@
1
+ import type { Socket } from './Socket.ts'
2
+
3
+ /*
4
+ Manifest of socket-name → module loader. Produced by the resolver
5
+ plugin from each `.ts` under src/server/sockets/. Each module has exactly one
6
+ named export, a Socket whose `.name` was stamped in by the bundler
7
+ rewrite. The dispatcher imports a module on first access and caches the
8
+ resolved Socket against its name.
9
+ */
10
+ export type SocketRoutes = Record<string, () => Promise<Record<string, Socket<unknown>>>>