@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.
- package/CHANGELOG.md +313 -0
- package/LICENSE +21 -0
- package/README.md +559 -0
- package/bin/belte.ts +183 -0
- package/package.json +110 -0
- package/src/App.svelte +31 -0
- package/src/appEntry.ts +151 -0
- package/src/assets/app.html +14 -0
- package/src/belteResolverPlugin.ts +858 -0
- package/src/build.ts +147 -0
- package/src/buildCli.ts +129 -0
- package/src/buildDisconnected.ts +122 -0
- package/src/bundleApp.ts +149 -0
- package/src/bundleDisconnectedEntry.ts +17 -0
- package/src/cliEntry.ts +25 -0
- package/src/clientBuildPlugins.ts +41 -0
- package/src/clientEntry.ts +7 -0
- package/src/compile.ts +64 -0
- package/src/controlServerWorker.ts +422 -0
- package/src/dedupeSveltePlugin.ts +66 -0
- package/src/devEntry.ts +169 -0
- package/src/discoveryEntry.ts +81 -0
- package/src/lib/browser/applyStreamedResolution.ts +33 -0
- package/src/lib/browser/cacheEntryFromSnapshot.ts +48 -0
- package/src/lib/browser/flushUnresolvedPlaceholders.ts +16 -0
- package/src/lib/browser/installStreamingPlaceholders.ts +32 -0
- package/src/lib/browser/openResolveStream.ts +42 -0
- package/src/lib/browser/page.svelte.ts +258 -0
- package/src/lib/browser/pageStreamController.ts +17 -0
- package/src/lib/browser/refetchPlaceholder.ts +12 -0
- package/src/lib/browser/remoteProxy.ts +37 -0
- package/src/lib/browser/socketChannel.ts +192 -0
- package/src/lib/browser/socketProxy.ts +57 -0
- package/src/lib/browser/startClient.ts +153 -0
- package/src/lib/browser/subscribe.ts +131 -0
- package/src/lib/browser/types/Errors.ts +9 -0
- package/src/lib/browser/types/Layouts.ts +7 -0
- package/src/lib/browser/types/Pages.ts +7 -0
- package/src/lib/browser/types/StreamingDeferred.ts +9 -0
- package/src/lib/bundle/BundleMenu.ts +11 -0
- package/src/lib/bundle/BundleMenuItem.ts +24 -0
- package/src/lib/bundle/BundleWindow.ts +36 -0
- package/src/lib/bundle/WEBVIEW_BUILD_REVISION.ts +9 -0
- package/src/lib/bundle/WEBVIEW_VERSION.ts +7 -0
- package/src/lib/bundle/bindConnectedFlag.ts +29 -0
- package/src/lib/bundle/bindRequestNavigate.ts +31 -0
- package/src/lib/bundle/buildWebviewLib.ts +111 -0
- package/src/lib/bundle/disconnected.css +9 -0
- package/src/lib/bundle/disconnected.svelte +386 -0
- package/src/lib/bundle/ensureWebviewLib.ts +20 -0
- package/src/lib/bundle/exitWithParent.ts +28 -0
- package/src/lib/bundle/infoPlist.ts +46 -0
- package/src/lib/bundle/installDownloads.ts +24 -0
- package/src/lib/bundle/installMacMenu.ts +39 -0
- package/src/lib/bundle/listenLocalControlServer.ts +19 -0
- package/src/lib/bundle/native/belteMenu.mm +422 -0
- package/src/lib/bundle/native/webview.h +4557 -0
- package/src/lib/bundle/onMenu.ts +41 -0
- package/src/lib/bundle/openWebview.ts +104 -0
- package/src/lib/bundle/pngToIcns.ts +47 -0
- package/src/lib/bundle/probeBelteServer.ts +34 -0
- package/src/lib/bundle/resolveServerBinary.ts +12 -0
- package/src/lib/bundle/resolveWebviewLib.ts +53 -0
- package/src/lib/bundle/serverBinaryFilename.ts +8 -0
- package/src/lib/bundle/signMacApp.ts +35 -0
- package/src/lib/bundle/spawnEmbeddedServer.ts +65 -0
- package/src/lib/bundle/stableLocalPort.ts +19 -0
- package/src/lib/bundle/waitForServer.ts +23 -0
- package/src/lib/bundle/webviewCachePath.ts +23 -0
- package/src/lib/bundle/webviewLibName.ts +11 -0
- package/src/lib/cli/connectToServer.ts +23 -0
- package/src/lib/cli/createClient.ts +170 -0
- package/src/lib/cli/dispatchCommand.ts +71 -0
- package/src/lib/cli/loadEnvFromBinaryDir.ts +16 -0
- package/src/lib/cli/parseArgvForRpc.ts +97 -0
- package/src/lib/cli/printHelp.ts +119 -0
- package/src/lib/cli/printSessionHelp.ts +27 -0
- package/src/lib/cli/printSessionStatus.ts +21 -0
- package/src/lib/cli/printTrimmed.ts +8 -0
- package/src/lib/cli/printValue.ts +10 -0
- package/src/lib/cli/resolveCliTarget.ts +48 -0
- package/src/lib/cli/runCli.ts +139 -0
- package/src/lib/cli/runSession.ts +105 -0
- package/src/lib/cli/startLocalInstance.ts +14 -0
- package/src/lib/cli/tokenizeLine.ts +51 -0
- package/src/lib/cli/types/CliManifest.ts +9 -0
- package/src/lib/cli/types/CliManifestEntry.ts +17 -0
- package/src/lib/cli/types/CliTarget.ts +13 -0
- package/src/lib/mcp/annotationsForMethod.ts +29 -0
- package/src/lib/mcp/createMcpResourceServer.ts +101 -0
- package/src/lib/mcp/createMcpServer.ts +42 -0
- package/src/lib/mcp/dispatchMcpRequest.ts +146 -0
- package/src/lib/mcp/mcpResourceServerSlot.ts +18 -0
- package/src/lib/mcp/mcpSurface.ts +265 -0
- package/src/lib/mcp/toolResultFromResponse.ts +66 -0
- package/src/lib/mcp/types/JsonRpcRequest.ts +12 -0
- package/src/lib/mcp/types/JsonRpcResponse.ts +20 -0
- package/src/lib/mcp/types/McpResourceContents.ts +10 -0
- package/src/lib/mcp/types/McpResourceDescriptor.ts +6 -0
- package/src/lib/mcp/types/McpResourceServer.ts +12 -0
- package/src/lib/mcp/types/McpServer.ts +9 -0
- package/src/lib/mcp/types/McpServerOptions.ts +16 -0
- package/src/lib/server/AppModule.ts +33 -0
- package/src/lib/server/DELETE.ts +9 -0
- package/src/lib/server/GET.ts +9 -0
- package/src/lib/server/HEAD.ts +9 -0
- package/src/lib/server/PATCH.ts +9 -0
- package/src/lib/server/POST.ts +9 -0
- package/src/lib/server/PUT.ts +9 -0
- package/src/lib/server/agent.ts +76 -0
- package/src/lib/server/appDataDir.ts +15 -0
- package/src/lib/server/cli/buildEnvContent.ts +19 -0
- package/src/lib/server/cli/createTarGz.ts +76 -0
- package/src/lib/server/cli/handleCliDownload.ts +153 -0
- package/src/lib/server/cli/handleCliInstall.ts +37 -0
- package/src/lib/server/cli/installScript.ts +29 -0
- package/src/lib/server/cli/maxSourceMtime.ts +26 -0
- package/src/lib/server/cookies.ts +29 -0
- package/src/lib/server/env.ts +50 -0
- package/src/lib/server/error.ts +70 -0
- package/src/lib/server/json.ts +28 -0
- package/src/lib/server/jsonl.ts +46 -0
- package/src/lib/server/prompts/definePrompt.ts +20 -0
- package/src/lib/server/prompts/promptRegistry.ts +9 -0
- package/src/lib/server/prompts/registerPrompt.ts +6 -0
- package/src/lib/server/prompts/renderPromptTemplate.ts +16 -0
- package/src/lib/server/prompts/types/Prompt.ts +13 -0
- package/src/lib/server/prompts/types/PromptOptions.ts +12 -0
- package/src/lib/server/prompts/types/PromptRegistryEntry.ts +13 -0
- package/src/lib/server/prompts/types/PromptRoutes.ts +10 -0
- package/src/lib/server/redirect.ts +42 -0
- package/src/lib/server/request.ts +18 -0
- package/src/lib/server/rpc/defineVerb.ts +133 -0
- package/src/lib/server/rpc/dispatchVerbInProcess.ts +46 -0
- package/src/lib/server/rpc/findVerbByCommandName.ts +18 -0
- package/src/lib/server/rpc/parseArgs.ts +95 -0
- package/src/lib/server/rpc/registerVerb.ts +6 -0
- package/src/lib/server/rpc/types/RemoteHandler.ts +27 -0
- package/src/lib/server/rpc/types/RemoteRoutes.ts +13 -0
- package/src/lib/server/rpc/types/TypedResponse.ts +18 -0
- package/src/lib/server/rpc/types/VerbHelper.ts +68 -0
- package/src/lib/server/rpc/types/VerbRegistryEntry.ts +29 -0
- package/src/lib/server/rpc/unprocessed.ts +14 -0
- package/src/lib/server/rpc/verbRegistry.ts +11 -0
- package/src/lib/server/runtime/DEFAULT_PORT.ts +6 -0
- package/src/lib/server/runtime/DEV_REBUILD_MESSAGE.ts +4 -0
- package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +29 -0
- package/src/lib/server/runtime/acceptsZstd.ts +8 -0
- package/src/lib/server/runtime/buildOpenApiSpec.ts +106 -0
- package/src/lib/server/runtime/cacheControlForAsset.ts +22 -0
- package/src/lib/server/runtime/containsTraversal.ts +37 -0
- package/src/lib/server/runtime/createAssetHeaderCache.ts +35 -0
- package/src/lib/server/runtime/createPublicAssetServer.ts +63 -0
- package/src/lib/server/runtime/createRouteDispatcher.ts +100 -0
- package/src/lib/server/runtime/createServer.ts +692 -0
- package/src/lib/server/runtime/devReloadResponse.ts +35 -0
- package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +27 -0
- package/src/lib/server/runtime/envSchemaStore.ts +15 -0
- package/src/lib/server/runtime/findOpenPort.ts +35 -0
- package/src/lib/server/runtime/getActiveServer.ts +6 -0
- package/src/lib/server/runtime/globToPathSet.ts +29 -0
- package/src/lib/server/runtime/inProcessServer.ts +20 -0
- package/src/lib/server/runtime/internalErrorResponse.ts +25 -0
- package/src/lib/server/runtime/isCrossOriginUpgrade.ts +19 -0
- package/src/lib/server/runtime/listenOnOpenPort.ts +36 -0
- package/src/lib/server/runtime/logExposedSurfaces.ts +162 -0
- package/src/lib/server/runtime/mimeForExtension.ts +20 -0
- package/src/lib/server/runtime/parseIdleTimeout.ts +10 -0
- package/src/lib/server/runtime/parsePort.ts +11 -0
- package/src/lib/server/runtime/registryManifests.ts +66 -0
- package/src/lib/server/runtime/requestContext.ts +5 -0
- package/src/lib/server/runtime/resolveStreamResponse.ts +29 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +57 -0
- package/src/lib/server/runtime/safeJsonForScript.ts +17 -0
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +45 -0
- package/src/lib/server/runtime/serverSlot.ts +13 -0
- package/src/lib/server/runtime/setActiveServer.ts +6 -0
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +81 -0
- package/src/lib/server/runtime/streamCacheResolutions.ts +37 -0
- package/src/lib/server/runtime/streamFromIterator.ts +86 -0
- package/src/lib/server/runtime/streamStash.ts +64 -0
- package/src/lib/server/runtime/types/Assets.ts +1 -0
- package/src/lib/server/runtime/types/RequestStore.ts +27 -0
- package/src/lib/server/runtime/withResponseDefaults.ts +24 -0
- package/src/lib/server/server.ts +32 -0
- package/src/lib/server/socket.ts +31 -0
- package/src/lib/server/sockets/createSocketDispatcher.ts +311 -0
- package/src/lib/server/sockets/defineSocket.ts +167 -0
- package/src/lib/server/sockets/lookupSocket.ts +6 -0
- package/src/lib/server/sockets/recentHistory.ts +11 -0
- package/src/lib/server/sockets/registerSocket.ts +6 -0
- package/src/lib/server/sockets/socketOperations.ts +35 -0
- package/src/lib/server/sockets/socketRegistry.ts +9 -0
- package/src/lib/server/sockets/types/Socket.ts +21 -0
- package/src/lib/server/sockets/types/SocketClientFrame.ts +18 -0
- package/src/lib/server/sockets/types/SocketOperation.ts +22 -0
- package/src/lib/server/sockets/types/SocketOptions.ts +22 -0
- package/src/lib/server/sockets/types/SocketRegistryEntry.ts +17 -0
- package/src/lib/server/sockets/types/SocketRoutes.ts +10 -0
- package/src/lib/server/sockets/types/SocketServerFrame.ts +15 -0
- package/src/lib/server/sse.ts +53 -0
- package/src/lib/shared/BELTE_PACKAGE_NAME.ts +7 -0
- package/src/lib/shared/CACHE_CONTROL_VALUES.ts +16 -0
- package/src/lib/shared/HttpError.ts +19 -0
- package/src/lib/shared/RESOLVE_STREAM_PATH.ts +7 -0
- package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +11 -0
- package/src/lib/shared/activeCacheStore.ts +20 -0
- package/src/lib/shared/appDataDir.ts +34 -0
- package/src/lib/shared/belteImportName.ts +44 -0
- package/src/lib/shared/browserClientFlags.ts +10 -0
- package/src/lib/shared/buildRpcRequest.ts +70 -0
- package/src/lib/shared/bundleLayout.ts +36 -0
- package/src/lib/shared/bundled.ts +34 -0
- package/src/lib/shared/cache.ts +559 -0
- package/src/lib/shared/cacheStoreSlot.ts +16 -0
- package/src/lib/shared/canonicalJson.ts +63 -0
- package/src/lib/shared/carriesBodyArgs.ts +13 -0
- package/src/lib/shared/clearLastConnection.ts +7 -0
- package/src/lib/shared/commandNameForUrl.ts +17 -0
- package/src/lib/shared/createCacheStore.ts +75 -0
- package/src/lib/shared/createPushIterator.ts +93 -0
- package/src/lib/shared/createRemoteFunction.ts +99 -0
- package/src/lib/shared/decodeResponse.ts +47 -0
- package/src/lib/shared/detectTarget.ts +27 -0
- package/src/lib/shared/exeSuffix.ts +9 -0
- package/src/lib/shared/exitOnBuildFailure.ts +17 -0
- package/src/lib/shared/extraForwardHeaders.ts +16 -0
- package/src/lib/shared/fileStem.ts +9 -0
- package/src/lib/shared/findExportCallSite.ts +479 -0
- package/src/lib/shared/forwardHeaders.ts +41 -0
- package/src/lib/shared/getRemoteMeta.ts +5 -0
- package/src/lib/shared/globalCacheStore.ts +15 -0
- package/src/lib/shared/globalCacheStoreSlot.ts +14 -0
- package/src/lib/shared/importNamesToStrip.ts +13 -0
- package/src/lib/shared/invalidateEvent.ts +11 -0
- package/src/lib/shared/isCompileTarget.ts +15 -0
- package/src/lib/shared/isDebugEnabled.ts +23 -0
- package/src/lib/shared/isModuleNotFound.ts +16 -0
- package/src/lib/shared/isReadOnlyMethod.ts +14 -0
- package/src/lib/shared/isStreamingResponse.ts +11 -0
- package/src/lib/shared/jsonSchemaForPromptArguments.ts +29 -0
- package/src/lib/shared/jsonSchemaForSchema.ts +32 -0
- package/src/lib/shared/jsonlErrorFrame.ts +24 -0
- package/src/lib/shared/keyForRemoteCall.ts +29 -0
- package/src/lib/shared/lastConnectionPath.ts +7 -0
- package/src/lib/shared/loadEnvFile.ts +17 -0
- package/src/lib/shared/loadEnvFromDataDir.ts +15 -0
- package/src/lib/shared/loadSvelteConfig.ts +18 -0
- package/src/lib/shared/log.ts +104 -0
- package/src/lib/shared/manifestModule.ts +39 -0
- package/src/lib/shared/memoizeByKey.ts +24 -0
- package/src/lib/shared/nearestLayoutPrefix.ts +36 -0
- package/src/lib/shared/normalizeTarget.ts +10 -0
- package/src/lib/shared/pageUrlForFile.ts +14 -0
- package/src/lib/shared/parseBoundedEnvInt.ts +20 -0
- package/src/lib/shared/parseEnv.ts +30 -0
- package/src/lib/shared/parsePromptMarkdown.ts +34 -0
- package/src/lib/shared/parseRouteSegments.ts +22 -0
- package/src/lib/shared/prepareRpcModule.ts +59 -0
- package/src/lib/shared/prepareSocketModule.ts +49 -0
- package/src/lib/shared/programNameForPackage.ts +14 -0
- package/src/lib/shared/promptNameForFile.ts +10 -0
- package/src/lib/shared/queryStringFromArgs.ts +27 -0
- package/src/lib/shared/readEnvFile.ts +15 -0
- package/src/lib/shared/readLastConnection.ts +18 -0
- package/src/lib/shared/readPackageJson.ts +9 -0
- package/src/lib/shared/recordRemoteMeta.ts +5 -0
- package/src/lib/shared/remoteMetaStore.ts +16 -0
- package/src/lib/shared/resolveClientFlags.ts +20 -0
- package/src/lib/shared/responseErrorText.ts +9 -0
- package/src/lib/shared/rpcUrlForFile.ts +19 -0
- package/src/lib/shared/runningAsStandaloneBinary.ts +13 -0
- package/src/lib/shared/serializeEnv.ts +18 -0
- package/src/lib/shared/setCacheStoreResolver.ts +6 -0
- package/src/lib/shared/setGlobalCacheStoreResolver.ts +6 -0
- package/src/lib/shared/socketNameForFile.ts +11 -0
- package/src/lib/shared/sseErrorFrame.ts +29 -0
- package/src/lib/shared/streamResponse.ts +169 -0
- package/src/lib/shared/stripImport.ts +27 -0
- package/src/lib/shared/subscribableFromResponse.ts +51 -0
- package/src/lib/shared/toBunRoutePattern.ts +28 -0
- package/src/lib/shared/types/CacheEntry.ts +63 -0
- package/src/lib/shared/types/CacheInvalidation.ts +9 -0
- package/src/lib/shared/types/CacheOptions.ts +33 -0
- package/src/lib/shared/types/CacheSnapshot.ts +16 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +15 -0
- package/src/lib/shared/types/CacheStore.ts +32 -0
- package/src/lib/shared/types/ClientFlags.ts +11 -0
- package/src/lib/shared/types/CompileTarget.ts +6 -0
- package/src/lib/shared/types/HttpVerb.ts +1 -0
- package/src/lib/shared/types/LastConnection.ts +9 -0
- package/src/lib/shared/types/PromptArgument.ts +12 -0
- package/src/lib/shared/types/RawRemoteFunction.ts +13 -0
- package/src/lib/shared/types/RemoteFunction.ts +42 -0
- package/src/lib/shared/types/StandardSchemaV1.ts +57 -0
- package/src/lib/shared/types/StreamedResolution.ts +10 -0
- package/src/lib/shared/types/StreamingPlaceholder.ts +13 -0
- package/src/lib/shared/types/Subscribable.ts +15 -0
- package/src/lib/shared/types/SvelteConfig.ts +5 -0
- package/src/lib/shared/withJsonSchema.ts +20 -0
- package/src/lib/shared/writeLastConnection.ts +13 -0
- package/src/lib/shared/writeRoutesDts.ts +67 -0
- package/src/lib/test/clearVerbRegistry.ts +11 -0
- package/src/lib/test/createTestClient.ts +78 -0
- package/src/preload.ts +20 -0
- package/src/scaffold.ts +92 -0
- package/src/serverBuildPlugins.ts +25 -0
- package/src/serverEntry.ts +94 -0
- package/src/sveltePlugin.ts +58 -0
- package/src/tailwindStylePreprocessor.ts +62 -0
- package/template/bunfig.toml +4 -0
- package/template/package.json +19 -0
- package/template/src/app.ts +23 -0
- package/template/src/browser/app.css +21 -0
- package/template/src/browser/app.html +24 -0
- package/template/src/browser/pages/about/page.svelte +5 -0
- package/template/src/browser/pages/layout.svelte +26 -0
- package/template/src/browser/pages/page.svelte +20 -0
- package/template/src/bundle/icon.png +0 -0
- package/template/src/cli/banner.txt +3 -0
- package/template/src/cli/footer.txt +1 -0
- package/template/src/server/config.ts +17 -0
- package/template/src/server/rpc/getHello.ts +35 -0
- package/template/svelte.config.js +12 -0
- package/template/tsconfig.json +18 -0
- package/tsconfig.app.json +16 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Redirect Response with belte-friendly ergonomics — accepts relative
|
|
3
|
+
URLs (the platform's `Response.redirect` throws on them), defaults to
|
|
4
|
+
302, and matches the helper-style call site of `json`/`error` for
|
|
5
|
+
visual consistency inside a handler.
|
|
6
|
+
|
|
7
|
+
return redirect('/login') // 302 to /login
|
|
8
|
+
return redirect('/articles/1', 301) // permanent
|
|
9
|
+
return redirect(externalUrl, 307) // preserve method (POST stays POST)
|
|
10
|
+
|
|
11
|
+
Status guidance:
|
|
12
|
+
- 301 — moved permanently (cacheable; browsers may swap method to GET)
|
|
13
|
+
- 302 — found / temporary (default; browsers may swap method to GET)
|
|
14
|
+
- 303 — "after a POST, GET this" (forces GET on the follow-up)
|
|
15
|
+
- 307 — temporary, preserve method
|
|
16
|
+
- 308 — permanent, preserve method
|
|
17
|
+
|
|
18
|
+
A final `ResponseInit` adds headers (e.g. a `Set-Cookie` on the redirect);
|
|
19
|
+
the positional `status` always wins over any `init.status`.
|
|
20
|
+
*/
|
|
21
|
+
import { NO_STORE } from '../shared/CACHE_CONTROL_VALUES.ts'
|
|
22
|
+
import type { TypedResponse } from './rpc/types/TypedResponse.ts'
|
|
23
|
+
import { withResponseDefaults } from './runtime/withResponseDefaults.ts'
|
|
24
|
+
|
|
25
|
+
type RedirectStatus = 301 | 302 | 303 | 307 | 308
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
Return type is `TypedResponse<never>` for the same reason `error()` is —
|
|
29
|
+
the wire response is a 3xx with no body the caller resolves to, so it
|
|
30
|
+
must not pollute the inferred `Return` of a route that conditionally
|
|
31
|
+
redirects vs returns json.
|
|
32
|
+
*/
|
|
33
|
+
export function redirect(
|
|
34
|
+
url: string,
|
|
35
|
+
status: RedirectStatus = 302,
|
|
36
|
+
init?: ResponseInit,
|
|
37
|
+
): TypedResponse<never> {
|
|
38
|
+
return new Response(
|
|
39
|
+
null,
|
|
40
|
+
withResponseDefaults(init, { Location: url, 'Cache-Control': NO_STORE }, status),
|
|
41
|
+
) as TypedResponse<never>
|
|
42
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { requestContext } from './runtime/requestContext.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Returns the inbound Request for the current SSR/RPC pass. Implemented as an
|
|
5
|
+
AsyncLocalStorage lookup over the per-request store the server installs at
|
|
6
|
+
the fetch boundary. Throws if called outside a request scope (e.g. from
|
|
7
|
+
top-level module code or from app.ts init) — silent undefined would mask
|
|
8
|
+
the misuse.
|
|
9
|
+
*/
|
|
10
|
+
export function request(): Request {
|
|
11
|
+
const store = requestContext.getStore()
|
|
12
|
+
if (!store) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
'[belte] request() called outside a request scope — it only resolves while an SSR render or rpc handler is in flight',
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
return store.req
|
|
18
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { buildRpcRequest } from '../../shared/buildRpcRequest.ts'
|
|
2
|
+
import { createRemoteFunction } from '../../shared/createRemoteFunction.ts'
|
|
3
|
+
import { forwardHeaders } from '../../shared/forwardHeaders.ts'
|
|
4
|
+
import { isReadOnlyMethod } from '../../shared/isReadOnlyMethod.ts'
|
|
5
|
+
import { resolveClientFlags } from '../../shared/resolveClientFlags.ts'
|
|
6
|
+
import type { ClientFlags } from '../../shared/types/ClientFlags.ts'
|
|
7
|
+
import type { HttpVerb } from '../../shared/types/HttpVerb.ts'
|
|
8
|
+
import type { RemoteFunction } from '../../shared/types/RemoteFunction.ts'
|
|
9
|
+
import type { StandardSchemaV1 } from '../../shared/types/StandardSchemaV1.ts'
|
|
10
|
+
import { json } from '../json.ts'
|
|
11
|
+
import { requestContext } from '../runtime/requestContext.ts'
|
|
12
|
+
import { parseArgs } from './parseArgs.ts'
|
|
13
|
+
import { registerVerb } from './registerVerb.ts'
|
|
14
|
+
import type { RemoteHandler } from './types/RemoteHandler.ts'
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
Builds a RemoteFunction from an HTTP verb + RPC URL + handler. The bundler
|
|
18
|
+
rewrites every `export const VERB = handler(fn)` inside an `$rpc/**` module
|
|
19
|
+
so the verb (from the export name) and the URL (from the file path under
|
|
20
|
+
`src/server/rpc/`, with `/rpc/` prefix) are threaded into defineVerb.
|
|
21
|
+
|
|
22
|
+
The plain call (`fn(args)`) resolves to the Content-Type-decoded body;
|
|
23
|
+
non-2xx responses throw HttpError. `.raw(args)` returns the underlying
|
|
24
|
+
Response for callers that need status/headers/body streaming.
|
|
25
|
+
`.fetch(req)` is the dispatch hook the framework's router uses to
|
|
26
|
+
invoke the handler from an incoming HTTP request (with args parsed off
|
|
27
|
+
the Request via parseArgs).
|
|
28
|
+
|
|
29
|
+
Every raw invocation records the synthesized Request against the returned
|
|
30
|
+
promise so cache() can stash it on the entry without re-building.
|
|
31
|
+
*/
|
|
32
|
+
export function defineVerb<Args, Return>(
|
|
33
|
+
method: HttpVerb,
|
|
34
|
+
url: string,
|
|
35
|
+
handler: RemoteHandler<Args, Return>,
|
|
36
|
+
opts?: {
|
|
37
|
+
inputSchema?: StandardSchemaV1
|
|
38
|
+
outputSchema?: StandardSchemaV1
|
|
39
|
+
filesSchema?: StandardSchemaV1
|
|
40
|
+
clients?: Partial<ClientFlags>
|
|
41
|
+
},
|
|
42
|
+
): RemoteFunction<Args, Return> {
|
|
43
|
+
const inputSchema = opts?.inputSchema
|
|
44
|
+
const outputSchema = opts?.outputSchema
|
|
45
|
+
const filesSchema = opts?.filesSchema
|
|
46
|
+
/*
|
|
47
|
+
An input schema makes the handler safe to advertise to non-browser
|
|
48
|
+
surfaces. CLI flips on for any verb with one (a human/script invokes it
|
|
49
|
+
deliberately). MCP only auto-exposes read-only verbs (GET/HEAD) — a
|
|
50
|
+
model shouldn't be able to mutate/delete just because the handler
|
|
51
|
+
carries a schema, so mutating verbs require an explicit clients.mcp.
|
|
52
|
+
Explicit `clients` always wins.
|
|
53
|
+
*/
|
|
54
|
+
const hasSchema = inputSchema !== undefined
|
|
55
|
+
const clients = resolveClientFlags(opts?.clients, {
|
|
56
|
+
mcp: hasSchema && isReadOnlyMethod(method),
|
|
57
|
+
cli: hasSchema,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
function buildRequest(args: Args | undefined): Request {
|
|
61
|
+
const store = requestContext.getStore()
|
|
62
|
+
const baseUrl = store ? store.url.href : 'http://localhost/'
|
|
63
|
+
const headers = store ? forwardHeaders(store.req.headers) : new Headers()
|
|
64
|
+
return buildRpcRequest({ method, url, args, baseUrl, headers })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
Handler bodies may throw synchronously (e.g. an `assert(...)` at the
|
|
69
|
+
top of the function). The `async function` wrapper coerces both sync
|
|
70
|
+
throws and returned non-promises into the Promise<Response> shape
|
|
71
|
+
callers expect, so an SSR caller's `await` always sees the rejection
|
|
72
|
+
through the cache layer's snapshot boundary instead of the error
|
|
73
|
+
escaping the request scope.
|
|
74
|
+
*/
|
|
75
|
+
async function runHandler(args: Args | undefined): Promise<Response> {
|
|
76
|
+
return handler(args as Args) as unknown as Response
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
Validates the parsed args against inputSchema (text fields), then — when the
|
|
81
|
+
verb declares filesSchema — validates the File parts parseArgs split onto
|
|
82
|
+
the request store and merges them into the args bag the handler receives.
|
|
83
|
+
Either schema's issues become a 422. Files stay out of inputSchema so its
|
|
84
|
+
JSON-Schema projection (OpenAPI/MCP/CLI) never has to model a binary.
|
|
85
|
+
*/
|
|
86
|
+
async function validateThenHandle(args: Args | undefined): Promise<Response> {
|
|
87
|
+
let value: unknown = args
|
|
88
|
+
if (inputSchema) {
|
|
89
|
+
const result = await inputSchema['~standard'].validate(value)
|
|
90
|
+
if (result.issues) {
|
|
91
|
+
return json({ issues: result.issues }, { status: 422 })
|
|
92
|
+
}
|
|
93
|
+
value = result.value
|
|
94
|
+
}
|
|
95
|
+
if (filesSchema) {
|
|
96
|
+
const files = requestContext.getStore()?.files ?? {}
|
|
97
|
+
const result = await filesSchema['~standard'].validate(files)
|
|
98
|
+
if (result.issues) {
|
|
99
|
+
return json({ issues: result.issues }, { status: 422 })
|
|
100
|
+
}
|
|
101
|
+
value = { ...(value as object), ...(result.value as object) }
|
|
102
|
+
}
|
|
103
|
+
return runHandler(value as Args)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/*
|
|
107
|
+
`getRequest` is unused on the server path — handlers receive parsed
|
|
108
|
+
`args` directly and reach the inbound Request via `request()`.
|
|
109
|
+
createRemoteFunction passes a thunk so the client side can lazily
|
|
110
|
+
synthesize its Request without forcing the server to allocate one per
|
|
111
|
+
SSR call.
|
|
112
|
+
*/
|
|
113
|
+
function invoke(args: Args | undefined): Promise<Response> {
|
|
114
|
+
return inputSchema || filesSchema ? validateThenHandle(args) : runHandler(args)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const remote = createRemoteFunction<Args, Return>({
|
|
118
|
+
method,
|
|
119
|
+
url,
|
|
120
|
+
clients,
|
|
121
|
+
buildRequest,
|
|
122
|
+
invoke,
|
|
123
|
+
parseArgsForFetch: (request) => parseArgs(method, request) as Promise<Args | undefined>,
|
|
124
|
+
})
|
|
125
|
+
registerVerb({
|
|
126
|
+
remote: remote as RemoteFunction<unknown, unknown>,
|
|
127
|
+
inputSchema,
|
|
128
|
+
outputSchema,
|
|
129
|
+
filesSchema,
|
|
130
|
+
clients,
|
|
131
|
+
})
|
|
132
|
+
return remote
|
|
133
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { buildRpcRequest } from '../../shared/buildRpcRequest.ts'
|
|
2
|
+
import type { AppModule } from '../AppModule.ts'
|
|
3
|
+
import { runWithRequestScope } from '../runtime/runWithRequestScope.ts'
|
|
4
|
+
import type { VerbRegistryEntry } from './types/VerbRegistryEntry.ts'
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Runs a registered verb in-process: synthesizes the rpc Request from the
|
|
8
|
+
entry's own method + url and pipes it through entry.remote.fetch — the
|
|
9
|
+
same handler/validation/error path the HTTP router uses, no network hop.
|
|
10
|
+
The single in-process dispatch every registry-backed consumer surface (the
|
|
11
|
+
CLI client, the MCP tool dispatcher, and the test client) routes through, so
|
|
12
|
+
they can't drift on how a verb is invoked. `baseUrl` gives the synthetic
|
|
13
|
+
Request its origin (handlers reading request.url see the caller's host);
|
|
14
|
+
`headers` carries forwarded auth/identity context.
|
|
15
|
+
|
|
16
|
+
Runs inside the runWithRequestScope seam createServer crosses for real
|
|
17
|
+
requests, so a handler sees an identical scope to a live HTTP request: a fresh
|
|
18
|
+
per-request cache, the cookie jar with Set-Cookie flush, request()/server()
|
|
19
|
+
resolution, and the app's handleError (or the 500 fallback) on a throw. The
|
|
20
|
+
synthesized Request is shared between the scope store and the handler fetch so
|
|
21
|
+
request() returns the same Request parseArgs read from.
|
|
22
|
+
*/
|
|
23
|
+
export function dispatchVerbInProcess({
|
|
24
|
+
entry,
|
|
25
|
+
args,
|
|
26
|
+
baseUrl,
|
|
27
|
+
headers,
|
|
28
|
+
app,
|
|
29
|
+
}: {
|
|
30
|
+
entry: VerbRegistryEntry
|
|
31
|
+
args: unknown
|
|
32
|
+
baseUrl: string
|
|
33
|
+
headers?: Headers
|
|
34
|
+
app?: AppModule
|
|
35
|
+
}): Promise<Response> {
|
|
36
|
+
const request = buildRpcRequest({
|
|
37
|
+
method: entry.remote.method,
|
|
38
|
+
url: entry.remote.url,
|
|
39
|
+
args,
|
|
40
|
+
baseUrl,
|
|
41
|
+
headers,
|
|
42
|
+
})
|
|
43
|
+
return runWithRequestScope(request, { app, logRequests: false }, () =>
|
|
44
|
+
entry.remote.fetch(request),
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { commandNameForUrl } from '../../shared/commandNameForUrl.ts'
|
|
2
|
+
import type { VerbRegistryEntry } from './types/VerbRegistryEntry.ts'
|
|
3
|
+
import { verbRegistry } from './verbRegistry.ts'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Finds the registered verb whose URL maps to a given command name (folder
|
|
7
|
+
segments joined with `-`, per commandNameForUrl). The CLI client proxy and
|
|
8
|
+
the MCP tool dispatcher both key off this name, so the scan lives here once
|
|
9
|
+
rather than being re-implemented — and reused — at each call site.
|
|
10
|
+
*/
|
|
11
|
+
export function findVerbByCommandName(name: string): VerbRegistryEntry | undefined {
|
|
12
|
+
for (const entry of verbRegistry.values()) {
|
|
13
|
+
if (commandNameForUrl(entry.remote.url) === name) {
|
|
14
|
+
return entry
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return undefined
|
|
18
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { carriesBodyArgs } from '../../shared/carriesBodyArgs.ts'
|
|
2
|
+
import type { HttpVerb } from '../../shared/types/HttpVerb.ts'
|
|
3
|
+
import { requestContext } from '../runtime/requestContext.ts'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Splits a parsed FormData into the text fields that become args and the File
|
|
7
|
+
parts that don't. Repeated text keys collapse into an array (an HTML form posts
|
|
8
|
+
multiple same-named inputs); File parts group by field name and stash on the
|
|
9
|
+
request store for files() to read — they never enter args, so the input schema
|
|
10
|
+
keeps validating a plain object with no binary in it.
|
|
11
|
+
*/
|
|
12
|
+
function splitFormData(form: FormData): Record<string, unknown> {
|
|
13
|
+
const fileMap: Record<string, File[]> = {}
|
|
14
|
+
const fields: Record<string, unknown> = {}
|
|
15
|
+
for (const [key, value] of form) {
|
|
16
|
+
if (value instanceof File) {
|
|
17
|
+
fileMap[key] ??= []
|
|
18
|
+
fileMap[key].push(value)
|
|
19
|
+
continue
|
|
20
|
+
}
|
|
21
|
+
const existing = fields[key]
|
|
22
|
+
if (!(key in fields)) {
|
|
23
|
+
fields[key] = value
|
|
24
|
+
} else if (Array.isArray(existing)) {
|
|
25
|
+
existing.push(value)
|
|
26
|
+
} else {
|
|
27
|
+
fields[key] = [existing, value]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const store = requestContext.getStore()
|
|
31
|
+
if (store && Object.keys(fileMap).length > 0) {
|
|
32
|
+
store.files = fileMap
|
|
33
|
+
}
|
|
34
|
+
return fields
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
Parses + merges every source of args available for a verb-defined handler:
|
|
39
|
+
- body (json or form-encoded, ignored for GET/DELETE/HEAD)
|
|
40
|
+
- url query string
|
|
41
|
+
|
|
42
|
+
When both are present and the body is a plain object, the merge layers the
|
|
43
|
+
body on top of the query so the typed body wins on collision — the query
|
|
44
|
+
supplies defaults a body field can override, and a URL param can't silently
|
|
45
|
+
shadow a validated body value. A non-object body (array, primitive, null)
|
|
46
|
+
skips the merge entirely and is returned as-is — there's no key on the body
|
|
47
|
+
to layer the query into, and the framework's args type is a single bag rather
|
|
48
|
+
than a `{body, query}` envelope. Returns undefined when no source contributes
|
|
49
|
+
any key.
|
|
50
|
+
*/
|
|
51
|
+
export async function parseArgs(method: HttpVerb, request: Request): Promise<unknown> {
|
|
52
|
+
/*
|
|
53
|
+
Skip the URL parse entirely when the raw request URL has no query —
|
|
54
|
+
typical POST/PUT/PATCH calls land here with a flat rpc URL and no
|
|
55
|
+
`?…`, so the `new URL(...)` constructor cost (which dwarfs the
|
|
56
|
+
indexOf check) is wasted work.
|
|
57
|
+
*/
|
|
58
|
+
const queryStart = request.url.indexOf('?')
|
|
59
|
+
const hasQuery = queryStart !== -1
|
|
60
|
+
const url = hasQuery ? new URL(request.url) : undefined
|
|
61
|
+
|
|
62
|
+
let body: unknown
|
|
63
|
+
if (carriesBodyArgs(method)) {
|
|
64
|
+
const contentType = (request.headers.get('content-type') ?? '').toLowerCase()
|
|
65
|
+
if (contentType.includes('application/json')) {
|
|
66
|
+
const text = await request.text()
|
|
67
|
+
if (text !== '') {
|
|
68
|
+
body = JSON.parse(text)
|
|
69
|
+
}
|
|
70
|
+
} else if (
|
|
71
|
+
contentType.includes('application/x-www-form-urlencoded') ||
|
|
72
|
+
contentType.includes('multipart/form-data')
|
|
73
|
+
) {
|
|
74
|
+
body = splitFormData(await request.formData())
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (body !== undefined && (typeof body !== 'object' || body === null || Array.isArray(body))) {
|
|
79
|
+
return body
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!url) {
|
|
83
|
+
if (body === undefined) {
|
|
84
|
+
return undefined
|
|
85
|
+
}
|
|
86
|
+
return body
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const bodyObject = (body ?? {}) as Record<string, unknown>
|
|
90
|
+
const merged = { ...Object.fromEntries(url.searchParams), ...bodyObject }
|
|
91
|
+
if (Object.keys(merged).length === 0) {
|
|
92
|
+
return undefined
|
|
93
|
+
}
|
|
94
|
+
return merged
|
|
95
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { TypedResponse } from './TypedResponse.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Handler signature for verb-defined remote functions. Args is `undefined` for
|
|
5
|
+
GETs/DELETEs with no query, JSON-shaped objects for json bodies, and
|
|
6
|
+
form-shaped objects for form-encoded bodies. For a multipart upload it's the
|
|
7
|
+
text fields (`inputSchema`) intersected with the validated File parts
|
|
8
|
+
(`filesSchema`), merged into one bag. For a raw binary body Args is `undefined`
|
|
9
|
+
— read the stream via `request()` from `belte/server` instead.
|
|
10
|
+
|
|
11
|
+
Return is the value type the call site sees after Content-Type-driven
|
|
12
|
+
decoding (a parsed object for JSON, a string for text/*, a Blob otherwise,
|
|
13
|
+
`undefined` for 204). The handler must return a Response at runtime; the
|
|
14
|
+
`TypedResponse<Return>` brand on `json`/`error`/`redirect`/`jsonl`/`sse`
|
|
15
|
+
carries the body shape through the function's inferred return type so the
|
|
16
|
+
verb helper can infer `Return` automatically — no need to annotate
|
|
17
|
+
`GET<Args, Return>` when the handler returns one of the respond helpers.
|
|
18
|
+
A bare `new Response(...)` is still acceptable: the brand is optional, so
|
|
19
|
+
untagged Responses fall back to `Return = unknown`.
|
|
20
|
+
|
|
21
|
+
Handlers that need the inbound Request (headers, `request.signal`, …) read
|
|
22
|
+
it via `request()` from `belte/server` rather than a handler parameter, so
|
|
23
|
+
the signature stays a single parsed-`args` bag.
|
|
24
|
+
*/
|
|
25
|
+
export type RemoteHandler<Args, Return> = (
|
|
26
|
+
args: Args,
|
|
27
|
+
) => TypedResponse<Return> | Promise<TypedResponse<Return>>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RemoteFunction } from '../../../shared/types/RemoteFunction.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Manifest of RPC URL → module loader. Produced by the resolver plugin from
|
|
5
|
+
every `.ts` under src/server/rpc — each file maps to one URL (derived from its
|
|
6
|
+
path under `$rpc`, prefixed with `/rpc/`). Each module has exactly one
|
|
7
|
+
named export, a RemoteFunction whose `.method` and `.url` were stamped in
|
|
8
|
+
by the bundler rewrite.
|
|
9
|
+
*/
|
|
10
|
+
export type RemoteRoutes = Record<
|
|
11
|
+
string,
|
|
12
|
+
() => Promise<Record<string, RemoteFunction<unknown, unknown>>>
|
|
13
|
+
>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
A `Response` tagged with the body type the framework will hand back to
|
|
3
|
+
callers after Content-Type-driven decoding. The tag is phantom — it
|
|
4
|
+
adds no runtime field, only a type-level slot so the verb helpers can
|
|
5
|
+
infer `Return` from the handler's return type instead of forcing every
|
|
6
|
+
route to annotate it via `GET<Args, Return>`.
|
|
7
|
+
|
|
8
|
+
The respond helpers (`json<T>`, `error`, `redirect`, `jsonl<F>`,
|
|
9
|
+
`sse<F>`) all return a `TypedResponse<T>`, so a handler ending in
|
|
10
|
+
`return json({ user })` exposes `{ user: ... }` as its body type; the
|
|
11
|
+
verb overload picks it up via `RemoteHandler<Args, Return>`.
|
|
12
|
+
|
|
13
|
+
`T` is optional on the brand so a plain `new Response(...)` (untagged)
|
|
14
|
+
remains assignable to `TypedResponse<unknown>`; in that case `Return`
|
|
15
|
+
just falls back to its `unknown` default, matching pre-existing
|
|
16
|
+
behaviour for handlers that build Responses by hand.
|
|
17
|
+
*/
|
|
18
|
+
export type TypedResponse<T> = Response & { readonly __body?: T }
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ClientFlags } from '../../../shared/types/ClientFlags.ts'
|
|
2
|
+
import type { RemoteFunction } from '../../../shared/types/RemoteFunction.ts'
|
|
3
|
+
import type { StandardSchemaV1 } from '../../../shared/types/StandardSchemaV1.ts'
|
|
4
|
+
import type { RemoteHandler } from './RemoteHandler.ts'
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Shared signature for every verb helper (GET / POST / …). Three overloads:
|
|
8
|
+
|
|
9
|
+
- `Verb(fn, { inputSchema, outputSchema?, clients? })` — `Args` infers
|
|
10
|
+
from `InferInput<InputSchema>`, the handler receives
|
|
11
|
+
`InferOutput<InputSchema>`. Generic order is `<Return, InputSchema>` so
|
|
12
|
+
users can override `Return` while letting `InputSchema` infer from
|
|
13
|
+
`opts.inputSchema`. `outputSchema` is an optional Standard Schema for
|
|
14
|
+
the success body — it feeds the OpenAPI 200 response and the MCP tool
|
|
15
|
+
`outputSchema`. JSON Schema is projected from each schema's own
|
|
16
|
+
`toJSONSchema()` (wrap with withJsonSchema if the library lacks one).
|
|
17
|
+
`clients` controls which surfaces (browser / mcp / cli) expose this verb.
|
|
18
|
+
- `Verb(fn, { clients })` — schemaless but with explicit client
|
|
19
|
+
targeting (e.g. server-internal RPC with `clients: { browser: false }`).
|
|
20
|
+
- `Verb(fn)` — bare handler. `Args` and `Return` come from the handler
|
|
21
|
+
type; `Return` is usually inferred via the `TypedResponse<T>` brand on
|
|
22
|
+
`json`/`error`/`redirect`/`jsonl`/`sse`.
|
|
23
|
+
*/
|
|
24
|
+
export type VerbHelper = {
|
|
25
|
+
/*
|
|
26
|
+
`Verb(fn, { inputSchema, filesSchema, … })` — multipart upload. The
|
|
27
|
+
handler receives the text fields (`InferOutput<InputSchema>`) intersected
|
|
28
|
+
with the validated File parts (`InferOutput<FilesSchema>`); both are merged
|
|
29
|
+
into one args bag. The call site sends a FormData (RemoteFunction's call
|
|
30
|
+
accepts it), since a File can't ride a JSON body. filesSchema stays off the
|
|
31
|
+
JSON-Schema projection — a File has no honest conversion (see
|
|
32
|
+
jsonSchemaForSchema) — so only inputSchema feeds MCP/CLI/OpenAPI.
|
|
33
|
+
*/
|
|
34
|
+
<
|
|
35
|
+
Return = unknown,
|
|
36
|
+
InputSchema extends StandardSchemaV1 = StandardSchemaV1,
|
|
37
|
+
FilesSchema extends StandardSchemaV1 = StandardSchemaV1,
|
|
38
|
+
>(
|
|
39
|
+
fn: RemoteHandler<
|
|
40
|
+
StandardSchemaV1.InferOutput<InputSchema> & StandardSchemaV1.InferOutput<FilesSchema>,
|
|
41
|
+
Return
|
|
42
|
+
>,
|
|
43
|
+
opts: {
|
|
44
|
+
inputSchema: InputSchema
|
|
45
|
+
filesSchema: FilesSchema
|
|
46
|
+
outputSchema?: StandardSchemaV1
|
|
47
|
+
clients?: Partial<ClientFlags>
|
|
48
|
+
},
|
|
49
|
+
): RemoteFunction<StandardSchemaV1.InferInput<InputSchema>, Return>
|
|
50
|
+
<Return = unknown, InputSchema extends StandardSchemaV1 = StandardSchemaV1>(
|
|
51
|
+
fn: RemoteHandler<StandardSchemaV1.InferOutput<InputSchema>, Return>,
|
|
52
|
+
opts: {
|
|
53
|
+
inputSchema: InputSchema
|
|
54
|
+
outputSchema?: StandardSchemaV1
|
|
55
|
+
clients?: Partial<ClientFlags>
|
|
56
|
+
},
|
|
57
|
+
): RemoteFunction<StandardSchemaV1.InferInput<InputSchema>, Return>
|
|
58
|
+
<Args = undefined, Return = unknown>(
|
|
59
|
+
fn: RemoteHandler<Args, Return>,
|
|
60
|
+
opts: {
|
|
61
|
+
outputSchema?: StandardSchemaV1
|
|
62
|
+
clients: Partial<ClientFlags>
|
|
63
|
+
},
|
|
64
|
+
): RemoteFunction<Args, Return>
|
|
65
|
+
<Args = undefined, Return = unknown>(
|
|
66
|
+
fn: RemoteHandler<Args, Return>,
|
|
67
|
+
): RemoteFunction<Args, Return>
|
|
68
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ClientFlags } from '../../../shared/types/ClientFlags.ts'
|
|
2
|
+
import type { RemoteFunction } from '../../../shared/types/RemoteFunction.ts'
|
|
3
|
+
import type { StandardSchemaV1 } from '../../../shared/types/StandardSchemaV1.ts'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Per-verb registry record on the server side. MCP and CLI enumerate this
|
|
7
|
+
to discover which RPCs are advertised (clients flags) and what shapes
|
|
8
|
+
they expect/return. The schemas and resolved clients stay off the public
|
|
9
|
+
RemoteFunction shape so the browser-side proxy doesn't need to carry
|
|
10
|
+
server-only state.
|
|
11
|
+
|
|
12
|
+
`inputSchema` validates the argument bag and feeds the MCP tool
|
|
13
|
+
`inputSchema` / OpenAPI parameters; `outputSchema` describes the success
|
|
14
|
+
body and feeds the OpenAPI 200 response + MCP tool `outputSchema`. Each
|
|
15
|
+
projects to JSON Schema via its own `toJSONSchema()` (jsonSchemaForSchema) —
|
|
16
|
+
schemas whose library lacks one are wrapped with withJsonSchema.
|
|
17
|
+
|
|
18
|
+
`filesSchema` validates the File parts of a multipart body, kept separate
|
|
19
|
+
from `inputSchema` because a File has no honest JSON-Schema conversion — it
|
|
20
|
+
stays out of the MCP/CLI projection that `inputSchema` feeds, and the OpenAPI
|
|
21
|
+
multipart body advertises the file parts generically as binary.
|
|
22
|
+
*/
|
|
23
|
+
export type VerbRegistryEntry = {
|
|
24
|
+
remote: RemoteFunction<unknown, unknown>
|
|
25
|
+
inputSchema: StandardSchemaV1 | undefined
|
|
26
|
+
outputSchema: StandardSchemaV1 | undefined
|
|
27
|
+
filesSchema: StandardSchemaV1 | undefined
|
|
28
|
+
clients: ClientFlags
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RemoteFunction } from '../../shared/types/RemoteFunction.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Verb helpers (GET / POST / …) are placeholders — the bundler rewrites every
|
|
5
|
+
`export const x = GET(fn)` inside `src/server/rpc/<file>.ts` into a call to
|
|
6
|
+
defineVerb (server target) or remoteProxy (client target). If a call slips
|
|
7
|
+
through, the bundler plugin didn't process the file; throwing here surfaces
|
|
8
|
+
that cleanly instead of silently returning undefined.
|
|
9
|
+
*/
|
|
10
|
+
export function unprocessed<Args, Return>(verb: string): RemoteFunction<Args, Return> {
|
|
11
|
+
throw new Error(
|
|
12
|
+
`[belte] \`${verb}\` was called outside an $rpc module — verb helpers are only valid as the value of \`export const <filename> = ...\` inside a file under src/server/rpc/`,
|
|
13
|
+
)
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { VerbRegistryEntry } from './types/VerbRegistryEntry.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Process-wide registry of every verb-bound RPC declared in the app.
|
|
5
|
+
defineVerb inserts on first construction (which happens at module-load
|
|
6
|
+
time inside the rpc dispatcher cache or eagerly when MCP / CLI walks the
|
|
7
|
+
rpc manifest). MCP server reads this to build its tools list; the CLI
|
|
8
|
+
binary reads it to generate subcommands. The browser path never touches
|
|
9
|
+
this — the client stub has no schema or clients metadata to register.
|
|
10
|
+
*/
|
|
11
|
+
export const verbRegistry = new Map<string, VerbRegistryEntry>()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Dev-only live-reload client, injected into the served shell when the server
|
|
3
|
+
runs under `belte dev`. It opens an EventSource to /__belte/dev and reloads on
|
|
4
|
+
*reconnect*: the channel only drops when the dev orchestrator restarts the
|
|
5
|
+
server after a rebuild, so re-establishing the connection is the signal that
|
|
6
|
+
fresh code is being served. The first open is the initial page load (no
|
|
7
|
+
reload); every open after that is a restart. Self-managed retry keeps the gap
|
|
8
|
+
short instead of relying on EventSource's multi-second default backoff.
|
|
9
|
+
*/
|
|
10
|
+
export const DEV_RELOAD_CLIENT_SCRIPT = `<script>
|
|
11
|
+
;(() => {
|
|
12
|
+
let opened = false
|
|
13
|
+
function connect() {
|
|
14
|
+
const source = new EventSource('/__belte/dev')
|
|
15
|
+
source.onopen = () => {
|
|
16
|
+
if (opened) {
|
|
17
|
+
location.reload()
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
opened = true
|
|
21
|
+
}
|
|
22
|
+
source.onerror = () => {
|
|
23
|
+
source.close()
|
|
24
|
+
setTimeout(connect, 250)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
connect()
|
|
28
|
+
})()
|
|
29
|
+
</script>`
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Whether the client advertised zstd in Accept-Encoding. Both static-asset
|
|
3
|
+
servers (the `/_app/` chunk server and the public/ server) gate their
|
|
4
|
+
pre-compressed responses on this, so the check lives in one place.
|
|
5
|
+
*/
|
|
6
|
+
export function acceptsZstd(req: Request): boolean {
|
|
7
|
+
return (req.headers.get('accept-encoding') ?? '').toLowerCase().includes('zstd')
|
|
8
|
+
}
|