@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,27 @@
1
+ /*
2
+ Canonical query-string encoding for a query-carrying verb's args bag. The
3
+ single definition both the synthesized Request (buildRpcRequest) and its
4
+ cache key (keyForRemoteCall) build their query from, so the request URL and
5
+ the key encode every value identically and can't drift. `undefined` values
6
+ are dropped; each surviving value is coerced with String() and percent-
7
+ encoded with encodeURIComponent on both name and value. Keys come in
8
+ insertion order for the request and sorted for the key (`sort`), so the key
9
+ is stable under arg reordering. Returns the query body without a leading
10
+ `?` (empty string when nothing survives the filter). Concatenated in one
11
+ walk — no entries / filtered / URLSearchParams intermediates — to keep the
12
+ hot GET-cache path allocation-light. Callers validate that `args` is a
13
+ plain object first; this assumes that.
14
+ */
15
+ export function queryStringFromArgs(args: Record<string, unknown>, sort: boolean): string {
16
+ const keys = sort ? Object.keys(args).sort() : Object.keys(args)
17
+ let query = ''
18
+ for (const key of keys) {
19
+ const value = args[key]
20
+ if (value === undefined) {
21
+ continue
22
+ }
23
+ query += query ? '&' : ''
24
+ query += `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`
25
+ }
26
+ return query
27
+ }
@@ -0,0 +1,15 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { parseEnv } from './parseEnv.ts'
3
+
4
+ /*
5
+ Reads a `.env` at `path` into a key→value record, or {} when it doesn't exist.
6
+ The shared read-and-parse primitive: loadEnvFile builds on it to merge into
7
+ process.env, and the bundle launcher uses the record directly to resolve config
8
+ form pre-fills.
9
+ */
10
+ export async function readEnvFile(path: string): Promise<Record<string, string>> {
11
+ if (!existsSync(path)) {
12
+ return {}
13
+ }
14
+ return parseEnv(await Bun.file(path).text())
15
+ }
@@ -0,0 +1,18 @@
1
+ import { lastConnectionPath } from './lastConnectionPath.ts'
2
+ import type { LastConnection } from './types/LastConnection.ts'
3
+
4
+ /*
5
+ Reads the saved connection intent, or undefined when none is recorded or the file
6
+ is unreadable/corrupt — callers treat undefined as "nothing to resume".
7
+ */
8
+ export async function readLastConnection(programName: string): Promise<LastConnection | undefined> {
9
+ const file = Bun.file(lastConnectionPath(programName))
10
+ if (!(await file.exists())) {
11
+ return undefined
12
+ }
13
+ try {
14
+ return (await file.json()) as LastConnection
15
+ } catch {
16
+ return undefined
17
+ }
18
+ }
@@ -0,0 +1,9 @@
1
+ /*
2
+ Reads and parses a project's `package.json`, or undefined when absent. Callers
3
+ apply their own field defaults — this centralizes only the exists-check + parse
4
+ boilerplate. Bun.file().json() so no Node fs.
5
+ */
6
+ export async function readPackageJson(cwd: string): Promise<Record<string, unknown> | undefined> {
7
+ const file = Bun.file(`${cwd}/package.json`)
8
+ return (await file.exists()) ? ((await file.json()) as Record<string, unknown>) : undefined
9
+ }
@@ -0,0 +1,5 @@
1
+ import { remoteMetaStore } from './remoteMetaStore.ts'
2
+
3
+ export function recordRemoteMeta(promise: Promise<unknown>, getRequest: () => Request): void {
4
+ remoteMetaStore.set(promise, getRequest)
5
+ }
@@ -0,0 +1,16 @@
1
+ /*
2
+ WeakMap that records how to obtain the synthesized Request for a verb
3
+ call. The cache layer reads it to populate an entry's stored request
4
+ without rebuilding from scratch.
5
+
6
+ Stored as a thunk rather than the Request itself so SSR pages that fire
7
+ dozens of in-process verb calls without ever reaching cache() don't pay
8
+ the URL + Headers + Request allocation per call. The thunk memoises its
9
+ own first call inside createRemoteFunction, so cache() and any future
10
+ meta reader see the same Request instance.
11
+
12
+ method/url/key are intentionally not stored — they're derivable from
13
+ the RemoteFunction itself, and cache() does that derivation
14
+ independently.
15
+ */
16
+ export const remoteMetaStore = new WeakMap<Promise<unknown>, () => Request>()
@@ -0,0 +1,20 @@
1
+ import type { ClientFlags } from './types/ClientFlags.ts'
2
+
3
+ /*
4
+ Fills in the missing keys of a user-supplied `clients` option. Browser
5
+ always defaults to true (the historical surface). The mcp/cli auto-on
6
+ defaults are decided by the caller and passed in, since the safe default
7
+ differs per declaration: a read-only verb may auto-expose to MCP while a
8
+ mutating one must not, and sockets gate differently again. Explicit
9
+ values in `flags` always win over the computed defaults.
10
+ */
11
+ export function resolveClientFlags(
12
+ flags: Partial<ClientFlags> | undefined,
13
+ defaults: { mcp: boolean; cli: boolean },
14
+ ): ClientFlags {
15
+ return {
16
+ browser: flags?.browser ?? true,
17
+ mcp: flags?.mcp ?? defaults.mcp,
18
+ cli: flags?.cli ?? defaults.cli,
19
+ }
20
+ }
@@ -0,0 +1,9 @@
1
+ /*
2
+ Human-readable message for a non-2xx Response: `status statusText: body`.
3
+ Shared by the CLI error path and the MCP tool result so the two frame a
4
+ failed request identically. Consumes the body, so call only on a response
5
+ you're done with.
6
+ */
7
+ export async function responseErrorText(response: Response): Promise<string> {
8
+ return `${response.status} ${response.statusText}: ${await response.text()}`
9
+ }
@@ -0,0 +1,19 @@
1
+ /*
2
+ Maps an rpc-relative path (under `src/server/rpc/`) to its URL. Each file
3
+ is one endpoint at `/rpc/<file path>`, dropping the `.ts` extension.
4
+ $rpc URLs are flat function-call endpoints (args go in query or body), so
5
+ bracket-style `[name]` / `[...rest]` segments are rejected — those belong
6
+ in `src/browser/pages/` where they map to dynamic path params.
7
+ */
8
+ export function rpcUrlForFile(relPath: string): string {
9
+ const withoutExt = relPath.replace(/\.ts$/, '')
10
+ const segments = withoutExt.split('/').filter(Boolean)
11
+ for (const segment of segments) {
12
+ if (segment.startsWith('[')) {
13
+ throw new Error(
14
+ `[belte] src/server/rpc/${relPath} has a dynamic segment '${segment}' — $rpc URLs are flat; pass identifiers via args, not the path`,
15
+ )
16
+ }
17
+ }
18
+ return `/rpc/${segments.join('/')}`
19
+ }
@@ -0,0 +1,13 @@
1
+ /*
2
+ True when this code runs inside a `bun build --compile` standalone executable
3
+ (the bundle's embedded server, or an install-tarball server binary) rather than
4
+ under the `bun` CLI. Bun mounts a compiled binary's embedded modules under a
5
+ synthetic root — `/$bunfs/…` on posix, `…~BUN…` on Windows — so `Bun.main`
6
+ carries that marker only in a standalone binary; under `bun dev`/`bun start`
7
+ it's a real on-disk path. Used to scope the bundle's data-dir/binary-dir `.env`
8
+ loading to the shipped app, so `bun dev`/`bun start` keep to their project-local
9
+ CWD `.env` alone.
10
+ */
11
+ export function runningAsStandaloneBinary(): boolean {
12
+ return Bun.main.includes('$bunfs') || Bun.main.includes('~BUN')
13
+ }
@@ -0,0 +1,18 @@
1
+ // Quote only values that wouldn't round-trip bare through parseEnv — empties and
2
+ // anything carrying whitespace or a `#` (which would otherwise read as a comment).
3
+ function needsQuoting(value: string): boolean {
4
+ return value === '' || /[\s#]/.test(value)
5
+ }
6
+
7
+ /*
8
+ Serializes a key→value record to `.env` text — the inverse of parseEnv, used by
9
+ the connect-screen config form to persist the user's answers to the data-dir
10
+ `.env`. One `KEY=value` per line; values that need it are wrapped in double
11
+ quotes so parseEnv reads them back unchanged.
12
+ */
13
+ export function serializeEnv(values: Record<string, string>): string {
14
+ const lines = Object.entries(values).map(([key, value]) =>
15
+ needsQuoting(value) ? `${key}="${value}"` : `${key}=${value}`,
16
+ )
17
+ return `${lines.join('\n')}\n`
18
+ }
@@ -0,0 +1,6 @@
1
+ import { cacheStoreSlot } from './cacheStoreSlot.ts'
2
+ import type { CacheStore } from './types/CacheStore.ts'
3
+
4
+ export function setCacheStoreResolver(fn: () => CacheStore | undefined): void {
5
+ cacheStoreSlot.resolver = fn
6
+ }
@@ -0,0 +1,6 @@
1
+ import { globalCacheStoreSlot } from './globalCacheStoreSlot.ts'
2
+ import type { CacheStore } from './types/CacheStore.ts'
3
+
4
+ export function setGlobalCacheStoreResolver(fn: () => CacheStore | undefined): void {
5
+ globalCacheStoreSlot.resolver = fn
6
+ }
@@ -0,0 +1,11 @@
1
+ /*
2
+ Translates a socket file path under `src/server/sockets/` into the socket's
3
+ identity used on the wire. The name is the file path minus `.ts` so
4
+ nested paths (e.g. `orders/new.ts`) become `orders/new`. Sockets don't
5
+ need a URL prefix the way rpc routes do — they multiplex over the
6
+ framework-owned `/__belte/sockets` ws and are dispatched by name in the
7
+ registry, not by HTTP path.
8
+ */
9
+ export function socketNameForFile(relativePath: string): string {
10
+ return relativePath.replace(/\.ts$/, '')
11
+ }
@@ -0,0 +1,29 @@
1
+ /*
2
+ The in-band error sentinel for an SSE stream: when a handler's generator
3
+ throws, `sse()` emits an `event: error` frame whose data is `{ message }`,
4
+ and `streamResponse` re-throws it on the consumer side. Encoder and decoder
5
+ live together here so the sentinel (event name + payload shape) has one
6
+ definition and can't drift between the two ends of the wire.
7
+ */
8
+ export const sseErrorFrame = {
9
+ // Full `event: error` frame for a thrown message, including the SSE delimiters.
10
+ encode(message: string): string {
11
+ return `event: error\ndata: ${JSON.stringify({ message })}\n\n`
12
+ },
13
+ /*
14
+ The message carried by an error frame, or undefined when `event` isn't
15
+ the error sentinel. Falls back to the raw data when it isn't the JSON
16
+ the encoder produces.
17
+ */
18
+ decode(event: string, data: string): string | undefined {
19
+ if (event !== 'error') {
20
+ return undefined
21
+ }
22
+ try {
23
+ const decoded = JSON.parse(data) as { message?: string }
24
+ return decoded?.message ?? 'sse stream error'
25
+ } catch {
26
+ return data || 'sse stream error'
27
+ }
28
+ },
29
+ }
@@ -0,0 +1,169 @@
1
+ import { decodeResponse } from './decodeResponse.ts'
2
+ import { HttpError } from './HttpError.ts'
3
+ import { jsonlErrorFrame } from './jsonlErrorFrame.ts'
4
+ import { STREAMING_CONTENT_TYPES } from './STREAMING_CONTENT_TYPES.ts'
5
+ import { sseErrorFrame } from './sseErrorFrame.ts'
6
+
7
+ /*
8
+ Turns a Response into an AsyncIterable of frames, regardless of the
9
+ handler's chosen body format. Shared by `fn.stream(args)` (via
10
+ subscribableFromResponse), the CLI's streaming print path, and the MCP
11
+ tool dispatcher's stream drain — so every surface consumes sse/jsonl
12
+ identically. Three shapes are handled:
13
+
14
+ - text/event-stream (SSE): emits the JSON-parsed `data:` payload of
15
+ each event. The `event: error\ndata: {message}` frame the `sse()`
16
+ helper emits on generator throws is mapped back to a thrown Error so
17
+ consumers see the failure mid-iteration.
18
+ - application/jsonl + application/x-ndjson: emits one JSON value per
19
+ line. The trailing `{"$error":"..."}` line the `jsonl()` helper
20
+ emits on generator throws is likewise re-thrown.
21
+ - everything else: one-shot — yields the Content-Type-decoded body
22
+ once, then completes. Lets callers iterate uniformly on every rpc
23
+ handler, not just the streaming ones.
24
+
25
+ Non-2xx responses surface as a thrown HttpError on the first pull,
26
+ mirroring the plain `fn(args)` decode path.
27
+ */
28
+ export function streamResponse<T>(response: Response): AsyncIterable<T> {
29
+ if (!response.ok) {
30
+ return errorIterable<T>(new HttpError(response))
31
+ }
32
+ const contentType = (response.headers.get('content-type') ?? '').toLowerCase()
33
+ if (contentType.startsWith('text/event-stream')) {
34
+ return parseSse<T>(response)
35
+ }
36
+ if (STREAMING_CONTENT_TYPES.some((type) => contentType.startsWith(type))) {
37
+ return parseJsonLines<T>(response)
38
+ }
39
+ return oneShot<T>(response)
40
+ }
41
+
42
+ /* Surfaces a non-2xx response (or any pre-stream failure) as a thrown error on the first pull. */
43
+ // biome-ignore lint/correctness/useYield: throws on first pull; the generator shape is intentional so callers iterate it uniformly
44
+ async function* errorIterable<T>(error: Error): AsyncGenerator<T> {
45
+ throw error
46
+ }
47
+
48
+ /*
49
+ One-shot iterator over a non-streaming Response: decodes the body once
50
+ via the same Content-Type sniffing the plain call uses, yields it, then
51
+ completes. Makes streaming symmetrical across streaming and
52
+ non-streaming handlers — callers can pick the iteration shape without
53
+ worrying about which body the handler returned.
54
+ */
55
+ async function* oneShot<T>(response: Response): AsyncGenerator<T> {
56
+ yield (await decodeResponse(response)) as T
57
+ }
58
+
59
+ /*
60
+ Reads a streaming text Response and yields raw frame strings split on
61
+ `delimiter` (`\n\n` for SSE events, `\n` for JSON lines). Owns the whole
62
+ buffering lifecycle: incremental decode, amortised-O(n) compaction, a
63
+ final flush of the trailing partial frame, and reader cancellation when
64
+ the consumer stops iterating (the generator's `finally` runs on
65
+ `return()`). The SSE and jsonl parsers layer their per-frame parsing on
66
+ top of this single machine so the two can't drift.
67
+ */
68
+ async function* frameReader(response: Response, delimiter: string): AsyncGenerator<string> {
69
+ const body = response.body
70
+ if (!body) {
71
+ return
72
+ }
73
+ const reader = body.pipeThrough(new TextDecoderStream()).getReader()
74
+ let buffer = ''
75
+ let bufferStart = 0
76
+ try {
77
+ while (true) {
78
+ const { value, done } = await reader.read()
79
+ if (done) {
80
+ if (bufferStart < buffer.length) {
81
+ yield buffer.slice(bufferStart)
82
+ }
83
+ return
84
+ }
85
+ /*
86
+ Compact only when the unread region is small relative to the
87
+ consumed prefix — keeps amortised work O(n) instead of
88
+ quadratic slicing per frame boundary.
89
+ */
90
+ if (bufferStart > buffer.length / 2) {
91
+ buffer = buffer.slice(bufferStart) + value
92
+ bufferStart = 0
93
+ } else {
94
+ buffer += value
95
+ }
96
+ let boundary = buffer.indexOf(delimiter, bufferStart)
97
+ while (boundary !== -1) {
98
+ yield buffer.slice(bufferStart, boundary)
99
+ bufferStart = boundary + delimiter.length
100
+ boundary = buffer.indexOf(delimiter, bufferStart)
101
+ }
102
+ }
103
+ } finally {
104
+ await reader.cancel().catch(() => undefined)
105
+ }
106
+ }
107
+
108
+ /*
109
+ SSE parser: yields the JSON-parsed `data` payload of each `event:`/`data:`
110
+ frame. The error sentinel (`sseErrorFrame`) the `sse()` helper emits on a
111
+ generator throw is surfaced as a thrown Error so consumer loops can react
112
+ to mid-stream failure rather than silently stopping.
113
+ */
114
+ async function* parseSse<T>(response: Response): AsyncGenerator<T> {
115
+ for await (const raw of frameReader(response, '\n\n')) {
116
+ const frame = parseFrame(raw)
117
+ if (!frame) {
118
+ continue
119
+ }
120
+ const errorMessage = sseErrorFrame.decode(frame.event, frame.data)
121
+ if (errorMessage !== undefined) {
122
+ throw new Error(errorMessage)
123
+ }
124
+ yield JSON.parse(frame.data) as T
125
+ }
126
+ }
127
+
128
+ function parseFrame(raw: string): { event: string; data: string } | undefined {
129
+ const lines = raw.split('\n').filter((line) => line.length > 0 && !line.startsWith(':'))
130
+ if (lines.length === 0) {
131
+ return undefined
132
+ }
133
+ let event = 'message'
134
+ const dataLines: string[] = []
135
+ for (const line of lines) {
136
+ const colon = line.indexOf(':')
137
+ const field = colon === -1 ? line : line.slice(0, colon)
138
+ const value = colon === -1 ? '' : line.slice(colon + 1).replace(/^ /, '')
139
+ if (field === 'event') {
140
+ event = value
141
+ } else if (field === 'data') {
142
+ dataLines.push(value)
143
+ }
144
+ }
145
+ if (dataLines.length === 0) {
146
+ return undefined
147
+ }
148
+ return { event, data: dataLines.join('\n') }
149
+ }
150
+
151
+ /*
152
+ JSONL/NDJSON parser: parses each non-empty line as JSON and yields the
153
+ value. The error sentinel (`jsonlErrorFrame`) the `jsonl()` helper emits as
154
+ a trailing line on a generator throw is surfaced here as a thrown Error so
155
+ consumer loops can react to mid-stream failure.
156
+ */
157
+ async function* parseJsonLines<T>(response: Response): AsyncGenerator<T> {
158
+ for await (const raw of frameReader(response, '\n')) {
159
+ if (raw.length === 0) {
160
+ continue
161
+ }
162
+ const parsed = JSON.parse(raw)
163
+ const errorMessage = jsonlErrorFrame.decode(parsed)
164
+ if (errorMessage !== undefined) {
165
+ throw new Error(errorMessage)
166
+ }
167
+ yield parsed as T
168
+ }
169
+ }
@@ -0,0 +1,27 @@
1
+ /*
2
+ Strips the user's `import { … } from '<moduleName>'` declaration from a
3
+ module source. Used by the $rpc / $sockets rewriters to remove the
4
+ verb / `socket` import after its call site has been replaced by the
5
+ runtime-injected binding (defineVerb / defineSocket). Without this
6
+ strip the dead import would still side-effect-load the verb/socket
7
+ helper module into the server bundle for every $rpc / $sockets file.
8
+
9
+ The braced body is `[^}]*` rather than `[\s\S]*?` so the lazy match
10
+ can't backtrack across a `}` and accidentally swallow a preceding
11
+ import whose `from` clause doesn't match (e.g. stripping
12
+ `import { GET } from 'belte/server/GET'` from a file that also has
13
+ `import { json } from 'belte/server/json'` on the line above). `[^}]`
14
+ includes newlines, so multi-line braced imports like
15
+ import {
16
+ GET,
17
+ } from 'belte/server/GET'
18
+ still match — the body just can't contain another `}` to bound it.
19
+ */
20
+ export function stripImport(source: string, moduleName: string): string {
21
+ const escaped = moduleName.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
22
+ const pattern = new RegExp(
23
+ `^\\s*import\\s*\\{[^}]*\\}\\s*from\\s*['"]${escaped}['"]\\s*;?\\s*$`,
24
+ 'gm',
25
+ )
26
+ return source.replace(pattern, '')
27
+ }
@@ -0,0 +1,51 @@
1
+ import { streamResponse } from './streamResponse.ts'
2
+ import type { Subscribable } from './types/Subscribable.ts'
3
+
4
+ /*
5
+ Builds the Subscribable returned by `fn.stream(args)`. The carried
6
+ `name` is the cache-style key for (method, url, args) so subscribe()
7
+ dedupes multiple subscribers to identical args into one underlying
8
+ fetch. The fetch is deferred until the first iterator pull so
9
+ constructing the Subscribable (which happens on every $derived
10
+ re-evaluation) doesn't open a connection — subscribe()'s registry
11
+ short-circuits the second instance before it iterates.
12
+ */
13
+ export function subscribableFromResponse<T>(
14
+ name: string,
15
+ fetchResponse: () => Promise<Response>,
16
+ ): Subscribable<T> {
17
+ return {
18
+ name,
19
+ [Symbol.asyncIterator]() {
20
+ let inner: AsyncIterator<T, void, undefined> | undefined
21
+ let cancelled = false
22
+ return {
23
+ async next() {
24
+ if (cancelled) {
25
+ return { value: undefined, done: true }
26
+ }
27
+ if (!inner) {
28
+ const response = await fetchResponse()
29
+ inner = streamResponse<T>(response)[Symbol.asyncIterator]()
30
+ /*
31
+ If return() landed while we were awaiting the
32
+ fetch, `inner` was still undefined then so its
33
+ reader was never cancelled — release the body now
34
+ rather than leaving the HTTP stream open.
35
+ */
36
+ if (cancelled) {
37
+ await inner.return?.(undefined)
38
+ return { value: undefined, done: true }
39
+ }
40
+ }
41
+ return inner.next()
42
+ },
43
+ async return() {
44
+ cancelled = true
45
+ await inner?.return?.(undefined)
46
+ return { value: undefined, done: true }
47
+ },
48
+ }
49
+ },
50
+ }
51
+ }
@@ -0,0 +1,28 @@
1
+ import { parseRouteSegments } from './parseRouteSegments.ts'
2
+
3
+ /*
4
+ Translates a belte route URL (`/media/[id]/[...rest]`) into the pattern Bun
5
+ needs (`/media/:id/*`) for `Bun.serve({ routes })`. Returns the catch-all
6
+ segment's original name alongside so the server can rename Bun's `*` param
7
+ back to that name on the way out, keeping page-prop destructuring consistent
8
+ with the route file path.
9
+ */
10
+ export function toBunRoutePattern(routeUrl: string): {
11
+ pattern: string
12
+ catchAllName: string | undefined
13
+ } {
14
+ let catchAllName: string | undefined
15
+ const pattern = parseRouteSegments(routeUrl)
16
+ .map((segment) => {
17
+ if (segment.kind === 'literal') {
18
+ return segment.value
19
+ }
20
+ if (segment.catchAll) {
21
+ catchAllName = segment.name
22
+ return '*'
23
+ }
24
+ return `:${segment.name}`
25
+ })
26
+ .join('/')
27
+ return { pattern, catchAllName }
28
+ }
@@ -0,0 +1,63 @@
1
+ /*
2
+ Stored shape per cache key. The stored promise resolves to the raw Response for
3
+ a remote function (the snapshot reads its status/headers/body and the cache
4
+ layer hands callers a decoded view derived from it) or to the producer's value
5
+ for a plain producer — hence `Promise<unknown>`.
6
+
7
+ `request` is retained for remote entries so SSR snapshot serialization can
8
+ record the URL and method without re-deriving them from the function. Producer
9
+ entries have no wire request, so it is absent — and the snapshot readers skip
10
+ any entry lacking it (a producer value has no rpc identity to rehydrate against).
11
+
12
+ `ttl`/`expiresAt` drive eviction: expiresAt = undefined means "no TTL" (lives
13
+ forever); ttl = 0 means "dedupe only" (entry is pruned as soon as the promise
14
+ settles).
15
+
16
+ `value` is set only for entries hydrated from the SSR snapshot: the
17
+ snapshot body is pre-decoded synchronously so the first client render can
18
+ read it without a microtask hop and byte-match the SSR DOM. Live fetches
19
+ leave it undefined and take the async decode path.
20
+
21
+ `scope` holds the cache() call's scope tags as a Set so
22
+ `cache.invalidate({ scope })` can drop every entry sharing any tag with O(1)
23
+ membership; a re-read merges new tags in rather than replacing them.
24
+
25
+ `settled` flips true once the stored promise resolves. SSR snapshot
26
+ serialization reads it after `render()` returns to partition entries: ones
27
+ settled by then were consumed via `await` (render blocked on them) and inline
28
+ into `__SSR__`; ones still pending were consumed via `{#await}` (render emitted
29
+ the pending branch without blocking) and stream a resolve chunk instead.
30
+
31
+ `refreshing` flips true while this entry is reloading data it already held —
32
+ either a policy stale-while-revalidate refetch (value still visible) or the
33
+ default drop-then-reload (the prior entry was invalidated and dropped, this is
34
+ its replacement read). It backs cache.refreshing, distinguishing a reload from a
35
+ first-ever load; cleared when the read settles.
36
+
37
+ `invalidation` is present only when the cache() call set an `invalidate`
38
+ throttle/debounce policy: it holds the refetch thunk (the original call captured
39
+ with its args) plus the policy and its runtime timer state, so invalidate() can
40
+ rate-limit refetches of this key instead of dropping the entry and refetching on
41
+ every invalidation.
42
+ */
43
+ export type CacheEntry = {
44
+ key: string
45
+ promise: Promise<unknown>
46
+ request?: Request
47
+ ttl: number | undefined
48
+ expiresAt: number | undefined
49
+ value?: unknown
50
+ scope?: Set<string>
51
+ settled?: boolean
52
+ refreshing?: boolean
53
+ invalidation?: InvalidationState
54
+ }
55
+
56
+ /* Per-key invalidate coalescing: the throttle/debounce policy plus the timer/in-flight state. */
57
+ export type InvalidationState = {
58
+ refetch: () => Promise<unknown>
59
+ throttle?: number
60
+ debounce?: number
61
+ lastFiredAt?: number
62
+ timer?: ReturnType<typeof setTimeout>
63
+ }
@@ -0,0 +1,9 @@
1
+ /*
2
+ Detail payload of the cache store's 'invalidate' event — the set of cache
3
+ keys one invalidation touched. A Set so each subscriber's membership check
4
+ is O(1) regardless of how many keys a single invalidate spans. Shared
5
+ between both dispatchers (cache.invalidate + streamed-resolution settling)
6
+ and the cache store's listener, so the event's shape has one definition
7
+ instead of an inline cast repeated at every site.
8
+ */
9
+ export type CacheInvalidation = Set<string>
@@ -0,0 +1,33 @@
1
+ /*
2
+ Options for cache(). The key is always auto-derived (method+url+args for a remote
3
+ function, producer-reference+args for a plain producer): hoist a producer to a
4
+ stable reference to share its entry across calls. `ttl` is the
5
+ milliseconds-past-resolve that the entry stays live: omitted = forever, 0 =
6
+ dedupe only (entry dropped once the promise settles), any other number = TTL.
7
+ `scope` is one or more free-form tags grouping unrelated calls so one
8
+ `cache.invalidate({ scope })` drops every entry sharing any of them — pass an
9
+ array when a call belongs to multiple invalidation groups. A unique tag (e.g. a
10
+ uuid) shared by a set of calls gives them their own private invalidation group.
11
+
12
+ `global` opts the entry into the process-level store instead of the default
13
+ request-scoped one (server) — so a value computed in one request is reused by
14
+ later requests, e.g. memoising an external endpoint the server calls. Omit it
15
+ for per-request data: the default keeps a per-user response from leaking across
16
+ requests. Write only `global: true`; there is no `false` form. On the client
17
+ there is a single tab store, so the flag is a no-op there.
18
+
19
+ `invalidate` controls how a `cache.invalidate` hit on this key is applied, in ms.
20
+ `{ throttle: N }` refetches on the leading edge then at most once per N ms while
21
+ invalidations keep arriving; `{ debounce: N }` refetches only after N ms of
22
+ quiet. Both coalesce a burst of invalidations (e.g. a socket spraying
23
+ `cache.invalidate`) into far fewer calls and keep serving the existing (stale)
24
+ value until the refetch resolves — stale-while-revalidate. They affect only the
25
+ refetch-after-invalidate; the first fetch and arg-change fetches stay immediate.
26
+ Set at most one.
27
+ */
28
+ export type CacheOptions = {
29
+ ttl?: number
30
+ scope?: string | string[]
31
+ global?: boolean
32
+ invalidate?: { throttle?: number; debounce?: number }
33
+ }
@@ -0,0 +1,16 @@
1
+ import type { CacheEntry } from './CacheEntry.ts'
2
+ import type { CacheSnapshotEntry } from './CacheSnapshotEntry.ts'
3
+
4
+ /*
5
+ The SSR partition of a request's cache, read after `render()` returns.
6
+ `inline` are entries settled by then (consumed via `await`, so render blocked
7
+ on them) — they ship in the first chunk's `__SSR__` blob. `pending` are
8
+ GET/DELETE entries still in flight (consumed via `{#await}`, which render emits
9
+ as a pending branch without blocking) — the streamer awaits each and pushes a
10
+ `__belteResolve` chunk as it lands. Non-GET/DELETE pending entries are dropped:
11
+ they can't be snapshotted, so the client re-fetches them live on cache miss.
12
+ */
13
+ export type CacheSnapshot = {
14
+ inline: CacheSnapshotEntry[]
15
+ pending: CacheEntry[]
16
+ }