@abide/abide 0.28.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 +607 -0
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/bin/abide.ts +212 -0
- package/package.json +155 -0
- package/src/abideLsp.ts +211 -0
- package/src/abideModules.d.ts +8 -0
- package/src/abideResolverPlugin.ts +923 -0
- package/src/appEntry.ts +151 -0
- package/src/assets/app.html +12 -0
- package/src/build.ts +143 -0
- package/src/buildCli.ts +127 -0
- package/src/buildDisconnected.ts +118 -0
- package/src/bundleApp.ts +147 -0
- package/src/bundleDisconnectedEntry.ts +14 -0
- package/src/checkAbide.ts +77 -0
- package/src/cliEntry.ts +25 -0
- package/src/clientBuildPlugins.ts +33 -0
- package/src/clientEntry.ts +17 -0
- package/src/compile.ts +63 -0
- package/src/controlServerWorker.ts +426 -0
- package/src/devEntry.ts +250 -0
- package/src/discoveryEntry.ts +81 -0
- package/src/lib/bundle/BundleMenu.ts +12 -0
- package/src/lib/bundle/BundleMenuItem.ts +25 -0
- package/src/lib/bundle/BundleWindow.ts +37 -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 +34 -0
- package/src/lib/bundle/buildWebviewLib.ts +111 -0
- package/src/lib/bundle/bundled.ts +35 -0
- package/src/lib/bundle/disconnected.abide +236 -0
- package/src/lib/bundle/disconnected.css +9 -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/abideMenu.mm +422 -0
- package/src/lib/bundle/native/webview.h +4557 -0
- package/src/lib/bundle/onMenu.ts +42 -0
- package/src/lib/bundle/openWebview.ts +104 -0
- package/src/lib/bundle/pngToIcns.ts +47 -0
- package/src/lib/bundle/probeAbideServer.ts +57 -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 +37 -0
- package/src/lib/bundle/spawnEmbeddedServer.ts +64 -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 +108 -0
- package/src/lib/cli/dispatchCommand.ts +71 -0
- package/src/lib/cli/loadEnvFromBinaryDir.ts +15 -0
- package/src/lib/cli/parseArgvForRpc.ts +100 -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 +176 -0
- package/src/lib/cli/runSession.ts +108 -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 +102 -0
- package/src/lib/mcp/createMcpServer.ts +48 -0
- package/src/lib/mcp/dispatchMcpRequest.ts +138 -0
- package/src/lib/mcp/mcpResourceServerSlot.ts +18 -0
- package/src/lib/mcp/mcpSurface.ts +295 -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 +47 -0
- package/src/lib/server/DELETE.ts +10 -0
- package/src/lib/server/GET.ts +10 -0
- package/src/lib/server/HEAD.ts +10 -0
- package/src/lib/server/PATCH.ts +10 -0
- package/src/lib/server/POST.ts +10 -0
- package/src/lib/server/PUT.ts +10 -0
- package/src/lib/server/agent.ts +86 -0
- package/src/lib/server/appDataDir.ts +16 -0
- package/src/lib/server/cli/buildEnvContent.ts +19 -0
- package/src/lib/server/cli/createTarGz.ts +77 -0
- package/src/lib/server/cli/handleCliDownload.ts +150 -0
- package/src/lib/server/cli/handleCliInstall.ts +37 -0
- package/src/lib/server/cli/installScript.ts +31 -0
- package/src/lib/server/cli/maxSourceMtime.ts +26 -0
- package/src/lib/server/cookies.ts +30 -0
- package/src/lib/server/env.ts +51 -0
- package/src/lib/server/error.ts +73 -0
- package/src/lib/server/json.ts +42 -0
- package/src/lib/server/jsonl.ts +47 -0
- package/src/lib/server/prompts/definePrompt.ts +21 -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 +17 -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/reachable.ts +45 -0
- package/src/lib/server/redirect.ts +43 -0
- package/src/lib/server/request.ts +19 -0
- package/src/lib/server/rpc/defineVerb.ts +210 -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 +127 -0
- package/src/lib/server/rpc/readBodyWithinLimit.ts +44 -0
- package/src/lib/server/rpc/registerVerb.ts +6 -0
- package/src/lib/server/rpc/runWithVerbTimeout.ts +49 -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 +87 -0
- package/src/lib/server/rpc/types/VerbRegistryEntry.ts +35 -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_READY_MESSAGE.ts +6 -0
- package/src/lib/server/runtime/DEV_REBUILD_MESSAGE.ts +4 -0
- package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +107 -0
- package/src/lib/server/runtime/SSR_SWAP_SCRIPT.ts +16 -0
- package/src/lib/server/runtime/acceptsGzip.ts +24 -0
- package/src/lib/server/runtime/buildCacheSnapshot.ts +61 -0
- package/src/lib/server/runtime/buildHealthPayload.ts +34 -0
- package/src/lib/server/runtime/buildInspectorSurface.ts +37 -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/createAppAssetServer.ts +76 -0
- package/src/lib/server/runtime/createAssetHeaderCache.ts +31 -0
- package/src/lib/server/runtime/createPublicAssetServer.ts +67 -0
- package/src/lib/server/runtime/createReachable.ts +109 -0
- package/src/lib/server/runtime/createRouteDispatcher.ts +127 -0
- package/src/lib/server/runtime/createServer.ts +674 -0
- package/src/lib/server/runtime/createUiPageRenderer.ts +181 -0
- package/src/lib/server/runtime/crossOriginForbidden.ts +17 -0
- package/src/lib/server/runtime/crossOriginGate.ts +29 -0
- package/src/lib/server/runtime/devClientFingerprint.ts +117 -0
- package/src/lib/server/runtime/devHotModuleResponse.ts +40 -0
- package/src/lib/server/runtime/devReloadResponse.ts +41 -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 +21 -0
- package/src/lib/server/runtime/getActiveServer.ts +6 -0
- package/src/lib/server/runtime/globToPathSet.ts +29 -0
- package/src/lib/server/runtime/gzipResponse.ts +46 -0
- package/src/lib/server/runtime/inProcessServer.ts +20 -0
- package/src/lib/server/runtime/internalErrorResponse.ts +25 -0
- package/src/lib/server/runtime/isCrossOriginRequest.ts +23 -0
- package/src/lib/server/runtime/listenOnOpenPort.ts +36 -0
- package/src/lib/server/runtime/logExposedSurfaces.ts +156 -0
- package/src/lib/server/runtime/maybeMountInspector.ts +97 -0
- package/src/lib/server/runtime/mimeForExtension.ts +14 -0
- package/src/lib/server/runtime/pageUrlFromStore.ts +15 -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/resolvePageSnapshot.ts +25 -0
- package/src/lib/server/runtime/respondWithEmbeddedAsset.ts +18 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +150 -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 +83 -0
- package/src/lib/server/runtime/streamCacheResolutions.ts +37 -0
- package/src/lib/server/runtime/streamFromIterator.ts +86 -0
- package/src/lib/server/runtime/types/Assets.ts +6 -0
- package/src/lib/server/runtime/types/DevReloadStamp.ts +18 -0
- package/src/lib/server/runtime/types/InspectorCacheEntry.ts +24 -0
- package/src/lib/server/runtime/types/InspectorCacheSnapshot.ts +11 -0
- package/src/lib/server/runtime/types/InspectorContext.ts +30 -0
- package/src/lib/server/runtime/types/InspectorSocket.ts +17 -0
- package/src/lib/server/runtime/types/InspectorSurface.ts +13 -0
- package/src/lib/server/runtime/types/InspectorVerb.ts +27 -0
- package/src/lib/server/runtime/types/RequestStore.ts +55 -0
- package/src/lib/server/runtime/warnUnguardedMcp.ts +32 -0
- package/src/lib/server/runtime/withResponseDefaults.ts +24 -0
- package/src/lib/server/server.ts +33 -0
- package/src/lib/server/socket.ts +32 -0
- package/src/lib/server/sockets/createSocketDispatcher.ts +337 -0
- package/src/lib/server/sockets/defineSocket.ts +179 -0
- package/src/lib/server/sockets/lookupSocket.ts +6 -0
- package/src/lib/server/sockets/registerSocket.ts +6 -0
- package/src/lib/server/sockets/socketOperations.ts +36 -0
- package/src/lib/server/sockets/socketRegistry.ts +9 -0
- package/src/lib/server/sockets/types/Socket.ts +23 -0
- package/src/lib/server/sockets/types/SocketClientFrame.ts +19 -0
- package/src/lib/server/sockets/types/SocketOperation.ts +22 -0
- package/src/lib/server/sockets/types/SocketOptions.ts +26 -0
- package/src/lib/server/sockets/types/SocketRegistryEntry.ts +19 -0
- package/src/lib/server/sockets/types/SocketRoutes.ts +10 -0
- package/src/lib/server/sockets/types/SocketServerFrame.ts +24 -0
- package/src/lib/server/sse.ts +54 -0
- package/src/lib/shared/ABIDE_PACKAGE_NAME.ts +7 -0
- package/src/lib/shared/ABIDE_VERSION.ts +9 -0
- package/src/lib/shared/CACHE_CONTROL_VALUES.ts +16 -0
- package/src/lib/shared/CACHE_WRAPPED.ts +8 -0
- package/src/lib/shared/CLI_PATH.ts +7 -0
- package/src/lib/shared/DEV_HOT_PREFIX.ts +7 -0
- package/src/lib/shared/DEV_RELOAD_PATH.ts +6 -0
- package/src/lib/shared/HEALTH_PATH.ts +7 -0
- package/src/lib/shared/HttpError.ts +20 -0
- package/src/lib/shared/IDENTITY_PATH.ts +6 -0
- package/src/lib/shared/INSPECTOR_PATH.ts +7 -0
- package/src/lib/shared/NAV_HEADER.ts +8 -0
- package/src/lib/shared/OFFLINE_HEADER.ts +8 -0
- package/src/lib/shared/REMOTE_FUNCTION.ts +8 -0
- package/src/lib/shared/REPLAYABLE_METHODS.ts +12 -0
- package/src/lib/shared/SOCKETS_PATH.ts +7 -0
- package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +11 -0
- package/src/lib/shared/SocketDisconnectedError.ts +13 -0
- package/src/lib/shared/TEXT_PLAIN.ts +7 -0
- package/src/lib/shared/abideImportName.ts +44 -0
- package/src/lib/shared/abideLog.ts +38 -0
- package/src/lib/shared/activeCacheStore.ts +20 -0
- package/src/lib/shared/activePage.ts +25 -0
- package/src/lib/shared/appDataDir.ts +34 -0
- package/src/lib/shared/appNameSlot.ts +10 -0
- package/src/lib/shared/basePath.ts +10 -0
- package/src/lib/shared/basePathFromAppUrl.ts +20 -0
- package/src/lib/shared/baseSlot.ts +14 -0
- package/src/lib/shared/binaryDirEnvPath.ts +12 -0
- package/src/lib/shared/browserClientFlags.ts +10 -0
- package/src/lib/shared/buildRpcProxy.ts +39 -0
- package/src/lib/shared/buildRpcRequest.ts +70 -0
- package/src/lib/shared/buildSocketOverChannel.ts +58 -0
- package/src/lib/shared/bundleLayout.ts +36 -0
- package/src/lib/shared/cache.ts +951 -0
- package/src/lib/shared/cacheEntryFromSnapshot.ts +59 -0
- package/src/lib/shared/cacheStoreSlot.ts +16 -0
- package/src/lib/shared/cacheStores.ts +10 -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 +104 -0
- package/src/lib/shared/createChannelLog.ts +122 -0
- package/src/lib/shared/createLifecycleChannel.ts +56 -0
- package/src/lib/shared/createLivenessWatch.ts +118 -0
- package/src/lib/shared/createPushIterator.ts +127 -0
- package/src/lib/shared/createRemoteFunction.ts +122 -0
- package/src/lib/shared/createSubscriber.ts +55 -0
- package/src/lib/shared/createTraceContext.ts +21 -0
- package/src/lib/shared/dataDirEnvPath.ts +12 -0
- package/src/lib/shared/decodeResponse.ts +47 -0
- package/src/lib/shared/detectTarget.ts +27 -0
- package/src/lib/shared/detectVerbMethod.ts +17 -0
- package/src/lib/shared/emitLogRecord.ts +190 -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 +476 -0
- package/src/lib/shared/formatTraceparent.ts +6 -0
- package/src/lib/shared/forwardHeaders.ts +44 -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/health.ts +179 -0
- package/src/lib/shared/healthReadSlot.ts +11 -0
- package/src/lib/shared/healthSeedSlot.ts +12 -0
- package/src/lib/shared/html.ts +38 -0
- package/src/lib/shared/importNamesToStrip.ts +13 -0
- package/src/lib/shared/invalidateEvent.ts +11 -0
- package/src/lib/shared/invalidateTripwire.ts +40 -0
- package/src/lib/shared/isAbideHealthPayload.ts +11 -0
- package/src/lib/shared/isCompileTarget.ts +15 -0
- package/src/lib/shared/isDebugEnabled.ts +26 -0
- package/src/lib/shared/isDebugNegated.ts +19 -0
- package/src/lib/shared/isModuleNotFound.ts +16 -0
- package/src/lib/shared/isReadOnlyMethod.ts +14 -0
- package/src/lib/shared/isReplayableMethod.ts +7 -0
- package/src/lib/shared/isStreamingResponse.ts +11 -0
- package/src/lib/shared/isSubscribable.ts +15 -0
- package/src/lib/shared/jsonSchemaForPromptArguments.ts +29 -0
- package/src/lib/shared/jsonSchemaForSchema.ts +39 -0
- package/src/lib/shared/jsonlErrorFrame.ts +24 -0
- package/src/lib/shared/keyForRemoteCall.ts +29 -0
- package/src/lib/shared/keyMatchesPrefix.ts +9 -0
- package/src/lib/shared/lastConnectionPath.ts +7 -0
- package/src/lib/shared/layoutChainForRoute.ts +22 -0
- package/src/lib/shared/loadEnvFile.ts +17 -0
- package/src/lib/shared/loadEnvFromDataDir.ts +14 -0
- package/src/lib/shared/log.ts +24 -0
- package/src/lib/shared/logClosingRecord.ts +28 -0
- package/src/lib/shared/logTapSlot.ts +13 -0
- package/src/lib/shared/manifestModule.ts +39 -0
- package/src/lib/shared/matchesDebugPattern.ts +16 -0
- package/src/lib/shared/memoizeByKey.ts +32 -0
- package/src/lib/shared/normalizeTarget.ts +10 -0
- package/src/lib/shared/online.ts +51 -0
- package/src/lib/shared/page.ts +30 -0
- package/src/lib/shared/pageSlot.ts +17 -0
- package/src/lib/shared/pageUrlForFile.ts +14 -0
- package/src/lib/shared/parseBoundedEnvInt.ts +20 -0
- package/src/lib/shared/parseDebugPatterns.ts +21 -0
- package/src/lib/shared/parseEnv.ts +30 -0
- package/src/lib/shared/parsePromptMarkdown.ts +35 -0
- package/src/lib/shared/parseRouteSegments.ts +22 -0
- package/src/lib/shared/parseTraceparent.ts +26 -0
- package/src/lib/shared/pending.ts +30 -0
- package/src/lib/shared/prepareRpcModule.ts +59 -0
- package/src/lib/shared/prepareSocketModule.ts +49 -0
- package/src/lib/shared/probeRegistries.ts +68 -0
- package/src/lib/shared/producerKey.ts +32 -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/randomHexId.ts +14 -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/refreshing.ts +31 -0
- package/src/lib/shared/remoteMetaStore.ts +16 -0
- package/src/lib/shared/requestScopeSlot.ts +15 -0
- package/src/lib/shared/resolveClientFlags.ts +20 -0
- package/src/lib/shared/responseErrorText.ts +9 -0
- package/src/lib/shared/rpcTimeoutSlot.ts +9 -0
- package/src/lib/shared/rpcUrlForFile.ts +19 -0
- package/src/lib/shared/runningAsStandaloneBinary.ts +13 -0
- package/src/lib/shared/selectorMatcher.ts +68 -0
- package/src/lib/shared/selectorPrefix.ts +39 -0
- package/src/lib/shared/serializeEnv.ts +18 -0
- package/src/lib/shared/setAppName.ts +5 -0
- package/src/lib/shared/setBaseResolver.ts +6 -0
- package/src/lib/shared/setCacheStoreResolver.ts +6 -0
- package/src/lib/shared/setGlobalCacheStoreResolver.ts +6 -0
- package/src/lib/shared/setPageResolver.ts +7 -0
- package/src/lib/shared/setRequestScopeResolver.ts +6 -0
- package/src/lib/shared/snippet.ts +25 -0
- package/src/lib/shared/socketNameForFile.ts +11 -0
- package/src/lib/shared/socketTapSlot.ts +12 -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/tailProbeSlot.ts +16 -0
- package/src/lib/shared/toBunRoutePattern.ts +28 -0
- package/src/lib/shared/toScopeSet.ts +4 -0
- package/src/lib/shared/trace.ts +16 -0
- package/src/lib/shared/types/CacheEntry.ts +84 -0
- package/src/lib/shared/types/CacheInvalidation.ts +9 -0
- package/src/lib/shared/types/CacheOnContext.ts +25 -0
- package/src/lib/shared/types/CacheOptions.ts +39 -0
- package/src/lib/shared/types/CacheSelector.ts +17 -0
- package/src/lib/shared/types/CacheSnapshot.ts +16 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +17 -0
- package/src/lib/shared/types/CacheStats.ts +13 -0
- package/src/lib/shared/types/CacheStore.ts +39 -0
- package/src/lib/shared/types/ChannelLog.ts +13 -0
- package/src/lib/shared/types/ClientFlags.ts +11 -0
- package/src/lib/shared/types/CompileTarget.ts +6 -0
- package/src/lib/shared/types/FrameworkLog.ts +13 -0
- package/src/lib/shared/types/HttpVerb.ts +1 -0
- package/src/lib/shared/types/LastConnection.ts +9 -0
- package/src/lib/shared/types/Log.ts +13 -0
- package/src/lib/shared/types/LogRecord.ts +42 -0
- package/src/lib/shared/types/LogVoice.ts +7 -0
- package/src/lib/shared/types/PageSnapshot.ts +14 -0
- package/src/lib/shared/types/PromptArgument.ts +12 -0
- package/src/lib/shared/types/RawRemoteFunction.ts +14 -0
- package/src/lib/shared/types/RemoteCallable.ts +12 -0
- package/src/lib/shared/types/RemoteFunction.ts +47 -0
- package/src/lib/shared/types/ReplayableMethod.ts +7 -0
- package/src/lib/shared/types/RequestScopeInfo.ts +16 -0
- package/src/lib/shared/types/RpcInvoker.ts +6 -0
- package/src/lib/shared/types/SocketChannel.ts +17 -0
- package/src/lib/shared/types/SocketSubCallbacks.ts +13 -0
- package/src/lib/shared/types/StandardSchemaV1.ts +56 -0
- package/src/lib/shared/types/StreamedResolution.ts +10 -0
- package/src/lib/shared/types/Subscribable.ts +26 -0
- package/src/lib/shared/types/TailHooks.ts +12 -0
- package/src/lib/shared/types/TailOptions.ts +10 -0
- package/src/lib/shared/types/TraceContext.ts +17 -0
- package/src/lib/shared/url.ts +118 -0
- package/src/lib/shared/withBase.ts +11 -0
- package/src/lib/shared/withBaseUrl.ts +17 -0
- package/src/lib/shared/withJsonSchema.ts +21 -0
- package/src/lib/shared/writeDts.ts +12 -0
- package/src/lib/shared/writeHealthDts.ts +36 -0
- package/src/lib/shared/writeLastConnection.ts +13 -0
- package/src/lib/shared/writePublicAssetsDts.ts +31 -0
- package/src/lib/shared/writeRoutesDts.ts +73 -0
- package/src/lib/shared/writeRpcDts.ts +49 -0
- package/src/lib/shared/writeTestRpcDts.ts +45 -0
- package/src/lib/shared/writeTestSocketsDts.ts +34 -0
- package/src/lib/test/assertAgentFrameConformance.ts +73 -0
- package/src/lib/test/createScriptedSurface.ts +45 -0
- package/src/lib/test/createTestApp.ts +203 -0
- package/src/lib/test/createTestSocketChannel.ts +142 -0
- package/src/lib/ui/README.md +86 -0
- package/src/lib/ui/compile/SSR_ESCAPE.ts +25 -0
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +36 -0
- package/src/lib/ui/compile/VOID_TAGS.ts +21 -0
- package/src/lib/ui/compile/abideUiPlugin.ts +65 -0
- package/src/lib/ui/compile/analyzeComponent.ts +117 -0
- package/src/lib/ui/compile/assetModulesFile.ts +32 -0
- package/src/lib/ui/compile/branchElements.ts +50 -0
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +59 -0
- package/src/lib/ui/compile/compileComponent.ts +20 -0
- package/src/lib/ui/compile/compileModule.ts +116 -0
- package/src/lib/ui/compile/compileSSR.ts +36 -0
- package/src/lib/ui/compile/compileShadow.ts +352 -0
- package/src/lib/ui/compile/createShadowLanguageService.ts +197 -0
- package/src/lib/ui/compile/createShadowProgram.ts +96 -0
- package/src/lib/ui/compile/decodeHtmlEntities.ts +49 -0
- package/src/lib/ui/compile/desugarSignals.ts +133 -0
- package/src/lib/ui/compile/escapeHtml.ts +15 -0
- package/src/lib/ui/compile/generateBuild.ts +638 -0
- package/src/lib/ui/compile/generateSSR.ts +380 -0
- package/src/lib/ui/compile/groupBindParts.ts +28 -0
- package/src/lib/ui/compile/hoistCells.ts +120 -0
- package/src/lib/ui/compile/loadShadowTsConfig.ts +31 -0
- package/src/lib/ui/compile/lowerDocAccess.ts +202 -0
- package/src/lib/ui/compile/nearestProjectRoot.ts +16 -0
- package/src/lib/ui/compile/parseTemplate.ts +396 -0
- package/src/lib/ui/compile/partitionSlots.ts +36 -0
- package/src/lib/ui/compile/prepareNestedScript.ts +42 -0
- package/src/lib/ui/compile/remapShadowDiagnostic.ts +30 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +85 -0
- package/src/lib/ui/compile/resolveAbideImports.ts +29 -0
- package/src/lib/ui/compile/scopeCss.ts +115 -0
- package/src/lib/ui/compile/shadowNaming.ts +11 -0
- package/src/lib/ui/compile/sourceToShadowOffset.ts +24 -0
- package/src/lib/ui/compile/staticAttrValue.ts +13 -0
- package/src/lib/ui/compile/stripEffects.ts +32 -0
- package/src/lib/ui/compile/types/AbideDiagnostic.ts +14 -0
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +25 -0
- package/src/lib/ui/compile/types/CompiledShadow.ts +15 -0
- package/src/lib/ui/compile/types/TemplateAttr.ts +16 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +78 -0
- package/src/lib/ui/compile/types/TextPart.ts +8 -0
- package/src/lib/ui/derived.ts +28 -0
- package/src/lib/ui/doc.ts +15 -0
- package/src/lib/ui/dom/appendSnippet.ts +34 -0
- package/src/lib/ui/dom/appendStatic.ts +27 -0
- package/src/lib/ui/dom/appendText.ts +114 -0
- package/src/lib/ui/dom/applyResolved.ts +72 -0
- package/src/lib/ui/dom/attach.ts +20 -0
- package/src/lib/ui/dom/attr.ts +19 -0
- package/src/lib/ui/dom/awaitBlock.ts +224 -0
- package/src/lib/ui/dom/cloneStatic.ts +52 -0
- package/src/lib/ui/dom/each.ts +115 -0
- package/src/lib/ui/dom/eachAsync.ts +153 -0
- package/src/lib/ui/dom/hydrate.ts +35 -0
- package/src/lib/ui/dom/mount.ts +29 -0
- package/src/lib/ui/dom/mountChild.ts +33 -0
- package/src/lib/ui/dom/on.ts +15 -0
- package/src/lib/ui/dom/openChild.ts +22 -0
- package/src/lib/ui/dom/openRoot.ts +20 -0
- package/src/lib/ui/dom/switchBlock.ts +75 -0
- package/src/lib/ui/dom/text.ts +20 -0
- package/src/lib/ui/dom/tryBlock.ts +112 -0
- package/src/lib/ui/dom/types/EachRow.ts +3 -0
- package/src/lib/ui/dom/types/SwitchCase.ts +6 -0
- package/src/lib/ui/dom/when.ts +73 -0
- package/src/lib/ui/effect.ts +16 -0
- package/src/lib/ui/installHotBridge.ts +73 -0
- package/src/lib/ui/matchRoute.ts +89 -0
- package/src/lib/ui/navigate.ts +17 -0
- package/src/lib/ui/probeNavigation.ts +33 -0
- package/src/lib/ui/remoteProxy.ts +97 -0
- package/src/lib/ui/renderChain.ts +50 -0
- package/src/lib/ui/renderToStream.ts +104 -0
- package/src/lib/ui/router.ts +286 -0
- package/src/lib/ui/runtime/OUTLET_TAG.ts +8 -0
- package/src/lib/ui/runtime/OWNER.ts +8 -0
- package/src/lib/ui/runtime/REACTIVE_CONTEXT.ts +14 -0
- package/src/lib/ui/runtime/RENDER.ts +23 -0
- package/src/lib/ui/runtime/RESUME.ts +16 -0
- package/src/lib/ui/runtime/applyPatchToTree.ts +41 -0
- package/src/lib/ui/runtime/claimChild.ts +10 -0
- package/src/lib/ui/runtime/clientPage.ts +16 -0
- package/src/lib/ui/runtime/createComputedNode.ts +16 -0
- package/src/lib/ui/runtime/createDoc.ts +177 -0
- package/src/lib/ui/runtime/createEffectNode.ts +58 -0
- package/src/lib/ui/runtime/createSignalNode.ts +16 -0
- package/src/lib/ui/runtime/detachLink.ts +21 -0
- package/src/lib/ui/runtime/endTracking.ts +24 -0
- package/src/lib/ui/runtime/enterRenderPass.ts +12 -0
- package/src/lib/ui/runtime/exitRenderPass.ts +7 -0
- package/src/lib/ui/runtime/firstOutlet.ts +22 -0
- package/src/lib/ui/runtime/flushEffects.ts +17 -0
- package/src/lib/ui/runtime/hotInstances.ts +10 -0
- package/src/lib/ui/runtime/hotReloadEnabled.ts +8 -0
- package/src/lib/ui/runtime/hotReplace.ts +25 -0
- package/src/lib/ui/runtime/nextBlockId.ts +11 -0
- package/src/lib/ui/runtime/pathExists.ts +23 -0
- package/src/lib/ui/runtime/readNode.ts +17 -0
- package/src/lib/ui/runtime/registerHotInstance.ts +23 -0
- package/src/lib/ui/runtime/runNode.ts +28 -0
- package/src/lib/ui/runtime/runtimePath.ts +9 -0
- package/src/lib/ui/runtime/scope.ts +24 -0
- package/src/lib/ui/runtime/toTeardown.ts +26 -0
- package/src/lib/ui/runtime/track.ts +58 -0
- package/src/lib/ui/runtime/trigger.ts +44 -0
- package/src/lib/ui/runtime/types/Cell.ts +5 -0
- package/src/lib/ui/runtime/types/Derived.ts +3 -0
- package/src/lib/ui/runtime/types/Doc.ts +19 -0
- package/src/lib/ui/runtime/types/EffectResult.ts +10 -0
- package/src/lib/ui/runtime/types/HotInstance.ts +14 -0
- package/src/lib/ui/runtime/types/NavVerdict.ts +9 -0
- package/src/lib/ui/runtime/types/Patch.ts +11 -0
- package/src/lib/ui/runtime/types/ReactiveLink.ts +21 -0
- package/src/lib/ui/runtime/types/ReactiveNode.ts +25 -0
- package/src/lib/ui/runtime/types/Route.ts +8 -0
- package/src/lib/ui/runtime/types/RouteLoader.ts +7 -0
- package/src/lib/ui/runtime/types/SsrRender.ts +22 -0
- package/src/lib/ui/runtime/types/State.ts +3 -0
- package/src/lib/ui/runtime/types/Teardown.ts +5 -0
- package/src/lib/ui/runtime/types/UiComponent.ts +16 -0
- package/src/lib/ui/runtime/types/UiProps.ts +15 -0
- package/src/lib/ui/runtime/unlinkDeps.ts +20 -0
- package/src/lib/ui/runtime/untrack.ts +20 -0
- package/src/lib/ui/runtime/valueAtPath.ts +18 -0
- package/src/lib/ui/runtime/writeNode.ts +16 -0
- package/src/lib/ui/socketChannel.ts +227 -0
- package/src/lib/ui/socketProxy.ts +25 -0
- package/src/lib/ui/startClient.ts +58 -0
- package/src/lib/ui/state.ts +25 -0
- package/src/lib/ui/tail.ts +324 -0
- package/src/lib/ui/types/Layouts.ts +9 -0
- package/src/lib/ui/types/Pages.ts +8 -0
- package/src/preload.ts +19 -0
- package/src/scaffold.ts +153 -0
- package/src/serverBuildPlugins.ts +19 -0
- package/src/serverEntry.ts +95 -0
- package/template/bunfig.toml +4 -0
- package/template/package.json +18 -0
- package/template/src/app.ts +28 -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 +36 -0
- package/template/src/ui/Layout.abide +19 -0
- package/template/src/ui/app.css +21 -0
- package/template/src/ui/app.html +24 -0
- package/template/src/ui/pages/about/page.abide +9 -0
- package/template/src/ui/pages/page.abide +22 -0
- package/template/test/app.test.ts +30 -0
- package/template/tsconfig.json +18 -0
- package/tsconfig.app.json +17 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
An MCP prompt declared by a markdown file under `src/mcp/prompts/`. The
|
|
3
|
+
resolver plugin parses the file's frontmatter + body and generates a call
|
|
4
|
+
to definePrompt, stamping in the `name` from the file path; `render(args)`
|
|
5
|
+
interpolates the body's `{{name}}` placeholders into the single user
|
|
6
|
+
message returned by `prompts/get`. Prompts are MCP-only — there is no
|
|
7
|
+
client-side counterpart, so the shape carries no ClientFlags.
|
|
8
|
+
*/
|
|
9
|
+
export type Prompt = {
|
|
10
|
+
readonly name: string
|
|
11
|
+
readonly description: string | undefined
|
|
12
|
+
render(args: Record<string, string>): string
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Options definePrompt receives for one markdown prompt. The resolver plugin
|
|
3
|
+
generates this object from the file: `description` + `jsonSchema` come from
|
|
4
|
+
the frontmatter (the schema built from the `arguments` list), and `render`
|
|
5
|
+
closes over the parsed body. All of this is server-only — prompts are never
|
|
6
|
+
imported by client code.
|
|
7
|
+
*/
|
|
8
|
+
export type PromptOptions = {
|
|
9
|
+
description?: string
|
|
10
|
+
jsonSchema?: Record<string, unknown>
|
|
11
|
+
render: (args: Record<string, string>) => string
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Prompt } from './Prompt.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Per-prompt registry record. The MCP dispatcher enumerates this to build
|
|
5
|
+
`prompts/list` (description + arguments from the JSON Schema) and to
|
|
6
|
+
dispatch `prompts/get` (render the body with the caller's arguments).
|
|
7
|
+
jsonSchema stays off the public Prompt shape so the render closure isn't
|
|
8
|
+
burdened with metadata it never reads.
|
|
9
|
+
*/
|
|
10
|
+
export type PromptRegistryEntry = {
|
|
11
|
+
prompt: Prompt
|
|
12
|
+
jsonSchema: Record<string, unknown> | undefined
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Prompt } from './Prompt.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Manifest of prompt-name → module loader. Produced by the resolver plugin
|
|
5
|
+
from each `.md` under src/mcp/prompts/. Each markdown file is transformed
|
|
6
|
+
into a module that registers one Prompt (its `.name` stamped in by the
|
|
7
|
+
generated definePrompt call) on import. The registry loader imports every
|
|
8
|
+
module once so the MCP dispatcher can enumerate the full prompt surface.
|
|
9
|
+
*/
|
|
10
|
+
export type PromptRoutes = Record<string, () => Promise<Record<string, Prompt>>>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { parseBoundedEnvInt } from '../shared/parseBoundedEnvInt.ts'
|
|
2
|
+
import { createReachable } from './runtime/createReachable.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Server-only outbound reachability for an external host. `await reachable(host)`
|
|
6
|
+
HEADs the host's origin: the first call awaits a real probe (faithful — a down
|
|
7
|
+
host costs the full timeout, an up host one handshake) and starts a background
|
|
8
|
+
poll that re-probes every TTL, so every later call resolves instantly off the
|
|
9
|
+
warm value, fresh within one TTL. A down host going down is caught within
|
|
10
|
+
~failureLimit polls; recovery flips it back automatically.
|
|
11
|
+
|
|
12
|
+
if (!(await reachable('api.example.com'))) return error(503)
|
|
13
|
+
|
|
14
|
+
A bare host defaults to https; pass an explicit http://… for a non-TLS host.
|
|
15
|
+
Answers "can I connect to this host," NOT "is my endpoint healthy": any
|
|
16
|
+
completed HTTP response (even 4xx/5xx, even a 405 to HEAD) counts as reachable;
|
|
17
|
+
only a connection failure or timeout reads as unreachable. There is no ambient
|
|
18
|
+
server-side connectivity signal, so this is the honest way to fail a doomed
|
|
19
|
+
outbound call fast — see online() for the inbound/client-reported counterpart.
|
|
20
|
+
|
|
21
|
+
ABIDE_REACHABLE_TTL (poll cadence / freshness, ms) and ABIDE_REACHABLE_TIMEOUT
|
|
22
|
+
(per-HEAD bound, ms) tune the defaults; the timeout is deliberately generous so
|
|
23
|
+
a healthy-but-distant host over a slow link is not mis-read as down.
|
|
24
|
+
*/
|
|
25
|
+
const TTL_MS = parseBoundedEnvInt(process.env.ABIDE_REACHABLE_TTL, 1_000, 600_000) ?? 30_000
|
|
26
|
+
const TIMEOUT_MS = parseBoundedEnvInt(process.env.ABIDE_REACHABLE_TIMEOUT, 100, 60_000) ?? 3_000
|
|
27
|
+
/* Stop polling a host nobody has read in a few TTLs; the next read restarts it cold. */
|
|
28
|
+
const IDLE_MS = TTL_MS * 3
|
|
29
|
+
|
|
30
|
+
/* Status-agnostic HEAD: a completed response proves connectivity; reject/timeout does not. */
|
|
31
|
+
async function probeOrigin(origin: string): Promise<boolean> {
|
|
32
|
+
try {
|
|
33
|
+
await fetch(origin, { method: 'HEAD', signal: AbortSignal.timeout(TIMEOUT_MS) })
|
|
34
|
+
return true
|
|
35
|
+
} catch {
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// @readme observability
|
|
41
|
+
export const reachable = createReachable({
|
|
42
|
+
probe: probeOrigin,
|
|
43
|
+
intervalMs: TTL_MS,
|
|
44
|
+
idleMs: IDLE_MS,
|
|
45
|
+
}).reachable
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Redirect Response with abide-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
|
+
// @readme response
|
|
34
|
+
export function redirect(
|
|
35
|
+
url: string,
|
|
36
|
+
status: RedirectStatus = 302,
|
|
37
|
+
init?: ResponseInit,
|
|
38
|
+
): TypedResponse<never> {
|
|
39
|
+
return new Response(
|
|
40
|
+
null,
|
|
41
|
+
withResponseDefaults(init, { Location: url, 'Cache-Control': NO_STORE }, status),
|
|
42
|
+
) as TypedResponse<never>
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
// @readme request-scope
|
|
11
|
+
export function request(): Request {
|
|
12
|
+
const store = requestContext.getStore()
|
|
13
|
+
if (!store) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
'[abide] request() called outside a request scope — it only resolves while an SSR render or rpc handler is in flight',
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
return store.req
|
|
19
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { abideLog } from '../../shared/abideLog.ts'
|
|
2
|
+
import { buildRpcRequest } from '../../shared/buildRpcRequest.ts'
|
|
3
|
+
import { createRemoteFunction } from '../../shared/createRemoteFunction.ts'
|
|
4
|
+
import { forwardHeaders } from '../../shared/forwardHeaders.ts'
|
|
5
|
+
import { isReadOnlyMethod } from '../../shared/isReadOnlyMethod.ts'
|
|
6
|
+
import { resolveClientFlags } from '../../shared/resolveClientFlags.ts'
|
|
7
|
+
import type { ClientFlags } from '../../shared/types/ClientFlags.ts'
|
|
8
|
+
import type { HttpVerb } from '../../shared/types/HttpVerb.ts'
|
|
9
|
+
import type { RemoteFunction } from '../../shared/types/RemoteFunction.ts'
|
|
10
|
+
import type { StandardSchemaV1 } from '../../shared/types/StandardSchemaV1.ts'
|
|
11
|
+
import { json } from '../json.ts'
|
|
12
|
+
import { requestContext } from '../runtime/requestContext.ts'
|
|
13
|
+
import { parseArgs } from './parseArgs.ts'
|
|
14
|
+
import { registerVerb } from './registerVerb.ts'
|
|
15
|
+
import { runWithVerbTimeout } from './runWithVerbTimeout.ts'
|
|
16
|
+
import type { RemoteHandler } from './types/RemoteHandler.ts'
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
Stash for the per-request AbortController a timed verb composes into the
|
|
20
|
+
inbound signal — read back in invoke's deadline callback to fire it. Lives on
|
|
21
|
+
the scope's Request (one verb per .fetch — network or in-process dispatch) so
|
|
22
|
+
an SSR pass's many in-process cache reads, which call invoke() directly and
|
|
23
|
+
never reach parseArgsForFetch, can't cross-cancel.
|
|
24
|
+
*/
|
|
25
|
+
const VERB_TIMEOUT_ABORT = Symbol('abideVerbTimeoutAbort')
|
|
26
|
+
|
|
27
|
+
/* Verb dispatch + validation spans, opt-in via DEBUG=abide:rpc. Reveals an
|
|
28
|
+
in-process RPC→RPC call (same request scope, same trace) as a nested span. */
|
|
29
|
+
const rpcLog = abideLog.channel('abide:rpc')
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
Builds a RemoteFunction from an HTTP verb + RPC URL + handler. The bundler
|
|
33
|
+
rewrites every `export const VERB = handler(fn)` inside an `$rpc/**` module
|
|
34
|
+
so the verb (from the export name) and the URL (from the file path under
|
|
35
|
+
`src/server/rpc/`, with `/rpc/` prefix) are threaded into defineVerb.
|
|
36
|
+
|
|
37
|
+
The plain call (`fn(args)`) resolves to the Content-Type-decoded body;
|
|
38
|
+
non-2xx responses throw HttpError. `.raw(args)` returns the underlying
|
|
39
|
+
Response for callers that need status/headers/body streaming.
|
|
40
|
+
`.fetch(req)` is the dispatch hook the framework's router uses to
|
|
41
|
+
invoke the handler from an incoming HTTP request (with args parsed off
|
|
42
|
+
the Request via parseArgs).
|
|
43
|
+
|
|
44
|
+
Every raw invocation records the synthesized Request against the returned
|
|
45
|
+
promise so cache() can stash it on the entry without re-building.
|
|
46
|
+
*/
|
|
47
|
+
// @readme plumbing
|
|
48
|
+
export function defineVerb<Args, Return>(
|
|
49
|
+
method: HttpVerb,
|
|
50
|
+
url: string,
|
|
51
|
+
handler: RemoteHandler<Args, Return>,
|
|
52
|
+
opts?: {
|
|
53
|
+
inputSchema?: StandardSchemaV1
|
|
54
|
+
outputSchema?: StandardSchemaV1
|
|
55
|
+
filesSchema?: StandardSchemaV1
|
|
56
|
+
clients?: Partial<ClientFlags>
|
|
57
|
+
crossOrigin?: boolean
|
|
58
|
+
/* Per-verb cap on actual received body bytes (413 past it); omitted = Bun's server-wide maxRequestBodySize. */
|
|
59
|
+
maxBodySize?: number
|
|
60
|
+
/* Per-verb handler deadline (ms): a 504 once exceeded, on every surface (SSR/MCP/CLI/network). */
|
|
61
|
+
timeout?: number
|
|
62
|
+
},
|
|
63
|
+
): RemoteFunction<Args, Return> {
|
|
64
|
+
const timeout = opts?.timeout
|
|
65
|
+
const inputSchema = opts?.inputSchema
|
|
66
|
+
const outputSchema = opts?.outputSchema
|
|
67
|
+
const filesSchema = opts?.filesSchema
|
|
68
|
+
/*
|
|
69
|
+
An input schema makes the handler safe to advertise to non-browser
|
|
70
|
+
surfaces. CLI flips on for any verb with one (a human/script invokes it
|
|
71
|
+
deliberately). MCP only auto-exposes read-only verbs (GET/HEAD) — a
|
|
72
|
+
model shouldn't be able to mutate/delete just because the handler
|
|
73
|
+
carries a schema, so mutating verbs require an explicit clients.mcp.
|
|
74
|
+
Explicit `clients` always wins.
|
|
75
|
+
*/
|
|
76
|
+
const hasSchema = inputSchema !== undefined
|
|
77
|
+
const clients = resolveClientFlags(opts?.clients, {
|
|
78
|
+
mcp: hasSchema && isReadOnlyMethod(method),
|
|
79
|
+
cli: hasSchema,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
function buildRequest(args: Args | undefined): Request {
|
|
83
|
+
const store = requestContext.getStore()
|
|
84
|
+
const baseUrl = store ? store.url.href : 'http://localhost/'
|
|
85
|
+
const headers = store ? forwardHeaders(store.req.headers) : new Headers()
|
|
86
|
+
return buildRpcRequest({ method, url, args, baseUrl, headers })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
Handler bodies may throw synchronously (e.g. an `assert(...)` at the
|
|
91
|
+
top of the function). The `async function` wrapper coerces both sync
|
|
92
|
+
throws and returned non-promises into the Promise<Response> shape
|
|
93
|
+
callers expect, so an SSR caller's `await` always sees the rejection
|
|
94
|
+
through the cache layer's snapshot boundary instead of the error
|
|
95
|
+
escaping the request scope.
|
|
96
|
+
*/
|
|
97
|
+
async function runHandler(args: Args | undefined): Promise<Response> {
|
|
98
|
+
return rpcLog.trace(
|
|
99
|
+
`rpc ${method} ${url}`,
|
|
100
|
+
() => handler(args as Args) as unknown as Response,
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/*
|
|
105
|
+
Validates the parsed args against inputSchema (text fields), then — when the
|
|
106
|
+
verb declares filesSchema — validates the File parts parseArgs split onto
|
|
107
|
+
the request store and merges them into the args bag the handler receives.
|
|
108
|
+
Either schema's issues become a 422. Files stay out of inputSchema so its
|
|
109
|
+
JSON-Schema projection (OpenAPI/MCP/CLI) never has to model a binary.
|
|
110
|
+
*/
|
|
111
|
+
async function validateThenHandle(args: Args | undefined): Promise<Response> {
|
|
112
|
+
let value: unknown = args
|
|
113
|
+
if (inputSchema) {
|
|
114
|
+
const result = await rpcLog.trace(`validate ${url}`, () =>
|
|
115
|
+
inputSchema['~standard'].validate(value),
|
|
116
|
+
)
|
|
117
|
+
if (result.issues) {
|
|
118
|
+
return json({ issues: result.issues }, { status: 422 })
|
|
119
|
+
}
|
|
120
|
+
value = result.value
|
|
121
|
+
}
|
|
122
|
+
if (filesSchema) {
|
|
123
|
+
const files = requestContext.getStore()?.files ?? {}
|
|
124
|
+
const result = await filesSchema['~standard'].validate(files)
|
|
125
|
+
if (result.issues) {
|
|
126
|
+
return json({ issues: result.issues }, { status: 422 })
|
|
127
|
+
}
|
|
128
|
+
value = { ...(value as object), ...(result.value as object) }
|
|
129
|
+
}
|
|
130
|
+
return runHandler(value as Args)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/*
|
|
134
|
+
`getRequest` is unused on the server path — handlers receive parsed
|
|
135
|
+
`args` directly and reach the inbound Request via `request()`.
|
|
136
|
+
createRemoteFunction passes a thunk so the client side can lazily
|
|
137
|
+
synthesize its Request without forcing the server to allocate one per
|
|
138
|
+
SSR call.
|
|
139
|
+
*/
|
|
140
|
+
/* Abort the controller parseArgsForFetch stashed on store.req; a no-op when none was stashed (SSR cache reads). */
|
|
141
|
+
function abortVerbTimeout(): void {
|
|
142
|
+
const req = requestContext.getStore()?.req as
|
|
143
|
+
| (Request & { [VERB_TIMEOUT_ABORT]?: AbortController })
|
|
144
|
+
| undefined
|
|
145
|
+
req?.[VERB_TIMEOUT_ABORT]?.abort(new DOMException('handler timeout', 'TimeoutError'))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function invoke(args: Args | undefined): Promise<Response> {
|
|
149
|
+
const work = inputSchema || filesSchema ? validateThenHandle(args) : runHandler(args)
|
|
150
|
+
if (timeout === undefined) {
|
|
151
|
+
return work
|
|
152
|
+
}
|
|
153
|
+
/*
|
|
154
|
+
On the deadline, fire the controller parseArgsForFetch composed into
|
|
155
|
+
request().signal (absent on the SSR cache-read path, so a sibling
|
|
156
|
+
verb's outbound fetch is never cancelled) — then 504.
|
|
157
|
+
*/
|
|
158
|
+
return runWithVerbTimeout(work, timeout, abortVerbTimeout)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const remote = createRemoteFunction<Args, Return>({
|
|
162
|
+
method,
|
|
163
|
+
url,
|
|
164
|
+
clients,
|
|
165
|
+
crossOrigin: opts?.crossOrigin,
|
|
166
|
+
buildRequest,
|
|
167
|
+
invoke,
|
|
168
|
+
parseArgsForFetch: async (request) => {
|
|
169
|
+
const args = await parseArgs(method, request, opts?.maxBodySize)
|
|
170
|
+
/*
|
|
171
|
+
Compose this verb's deadline into request().signal so a handler's
|
|
172
|
+
fetch(ext, { signal: request().signal }) is cancelled when the
|
|
173
|
+
timeout fires — not just abandoned. Applied after parseArgs onto the
|
|
174
|
+
scope's *final* request: a maxBodySize verb swaps store.req for a
|
|
175
|
+
buffered copy (readBodyWithinLimit) and an app.handle hook may
|
|
176
|
+
rewrite it, so composing onto the inbound `request` would leave
|
|
177
|
+
request() — and abortVerbTimeout, which reads store.req — pointed at
|
|
178
|
+
an un-cancellable signal. Only the signal is shadowed; the body
|
|
179
|
+
stays readable. The store always exists here (network + in-process
|
|
180
|
+
dispatch both run inside runWithRequestScope); SSR cache reads call
|
|
181
|
+
invoke() directly, never this path, so a sibling verb is never
|
|
182
|
+
cross-cancelled.
|
|
183
|
+
*/
|
|
184
|
+
if (timeout !== undefined) {
|
|
185
|
+
const req = requestContext.getStore()?.req
|
|
186
|
+
if (req) {
|
|
187
|
+
const controller = new AbortController()
|
|
188
|
+
const composed = AbortSignal.any([req.signal, controller.signal])
|
|
189
|
+
Object.defineProperty(req, 'signal', { value: composed, configurable: true })
|
|
190
|
+
Object.defineProperty(req, VERB_TIMEOUT_ABORT, {
|
|
191
|
+
value: controller,
|
|
192
|
+
configurable: true,
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return args as Args | undefined
|
|
197
|
+
},
|
|
198
|
+
})
|
|
199
|
+
registerVerb({
|
|
200
|
+
remote: remote as RemoteFunction<unknown, unknown>,
|
|
201
|
+
inputSchema,
|
|
202
|
+
outputSchema,
|
|
203
|
+
filesSchema,
|
|
204
|
+
clients,
|
|
205
|
+
timeout,
|
|
206
|
+
maxBodySize: opts?.maxBodySize,
|
|
207
|
+
crossOrigin: opts?.crossOrigin,
|
|
208
|
+
})
|
|
209
|
+
return remote
|
|
210
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { buildRpcRequest } from '../../shared/buildRpcRequest.ts'
|
|
2
|
+
import type { RemoteFunction } from '../../shared/types/RemoteFunction.ts'
|
|
3
|
+
import type { AppModule } from '../AppModule.ts'
|
|
4
|
+
import { runWithRequestScope } from '../runtime/runWithRequestScope.ts'
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Runs a verb in-process: synthesizes the rpc Request from the remote's own
|
|
8
|
+
method + url and pipes it through remote.fetch — the same handler/validation/
|
|
9
|
+
error path the HTTP router uses, no network hop. The single in-process
|
|
10
|
+
dispatch every consumer surface (the CLI client, the MCP tool dispatcher, and
|
|
11
|
+
the test client) routes through, so they can't drift on how a verb is invoked.
|
|
12
|
+
Takes the RemoteFunction directly — invocation never reads the registry
|
|
13
|
+
entry's schemas/clients (validation is closed over inside the remote), so the
|
|
14
|
+
entry is not a dependency here. `baseUrl` gives the synthetic Request its
|
|
15
|
+
origin (handlers reading request.url see the caller's host); `headers` carries
|
|
16
|
+
forwarded auth/identity context.
|
|
17
|
+
|
|
18
|
+
Runs inside the runWithRequestScope seam createServer crosses for real
|
|
19
|
+
requests, so a handler sees an identical scope to a live HTTP request: a fresh
|
|
20
|
+
per-request cache, the cookie jar with Set-Cookie flush, request()/server()
|
|
21
|
+
resolution, and the app's handleError (or the 500 fallback) on a throw. The
|
|
22
|
+
synthesized Request is shared between the scope store and the handler fetch so
|
|
23
|
+
request() returns the same Request parseArgs read from.
|
|
24
|
+
*/
|
|
25
|
+
export function dispatchVerbInProcess({
|
|
26
|
+
remote,
|
|
27
|
+
args,
|
|
28
|
+
baseUrl,
|
|
29
|
+
headers,
|
|
30
|
+
app,
|
|
31
|
+
}: {
|
|
32
|
+
remote: RemoteFunction<unknown, unknown>
|
|
33
|
+
args: unknown
|
|
34
|
+
baseUrl: string
|
|
35
|
+
headers?: Headers
|
|
36
|
+
app?: AppModule
|
|
37
|
+
}): Promise<Response> {
|
|
38
|
+
const request = buildRpcRequest({
|
|
39
|
+
method: remote.method,
|
|
40
|
+
url: remote.url,
|
|
41
|
+
args,
|
|
42
|
+
baseUrl,
|
|
43
|
+
headers,
|
|
44
|
+
})
|
|
45
|
+
return runWithRequestScope(request, { app, logRequests: false }, () => remote.fetch(request))
|
|
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,127 @@
|
|
|
1
|
+
import { carriesBodyArgs } from '../../shared/carriesBodyArgs.ts'
|
|
2
|
+
import { HttpError } from '../../shared/HttpError.ts'
|
|
3
|
+
import type { HttpVerb } from '../../shared/types/HttpVerb.ts'
|
|
4
|
+
import { error } from '../error.ts'
|
|
5
|
+
import { requestContext } from '../runtime/requestContext.ts'
|
|
6
|
+
import { readBodyWithinLimit } from './readBodyWithinLimit.ts'
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
Splits a parsed FormData into the text fields that become args and the File
|
|
10
|
+
parts that don't. Repeated text keys collapse into an array (an HTML form posts
|
|
11
|
+
multiple same-named inputs); File parts group by field name and stash on the
|
|
12
|
+
request store for files() to read — they never enter args, so the input schema
|
|
13
|
+
keeps validating a plain object with no binary in it.
|
|
14
|
+
*/
|
|
15
|
+
function splitFormData(form: FormData): Record<string, unknown> {
|
|
16
|
+
const fileMap: Record<string, File[]> = {}
|
|
17
|
+
const fields: Record<string, unknown> = {}
|
|
18
|
+
for (const [key, value] of form) {
|
|
19
|
+
if (value instanceof File) {
|
|
20
|
+
fileMap[key] ??= []
|
|
21
|
+
fileMap[key].push(value)
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
const existing = fields[key]
|
|
25
|
+
if (!(key in fields)) {
|
|
26
|
+
fields[key] = value
|
|
27
|
+
} else if (Array.isArray(existing)) {
|
|
28
|
+
existing.push(value)
|
|
29
|
+
} else {
|
|
30
|
+
fields[key] = [existing, value]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const store = requestContext.getStore()
|
|
34
|
+
if (store && Object.keys(fileMap).length > 0) {
|
|
35
|
+
store.files = fileMap
|
|
36
|
+
}
|
|
37
|
+
return fields
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
Parses + merges every source of args available for a verb-defined handler:
|
|
42
|
+
- body (json or form-encoded, ignored for GET/DELETE/HEAD)
|
|
43
|
+
- url query string
|
|
44
|
+
|
|
45
|
+
When both are present and the body is a plain object, the merge layers the
|
|
46
|
+
body on top of the query so the typed body wins on collision — the query
|
|
47
|
+
supplies defaults a body field can override, and a URL param can't silently
|
|
48
|
+
shadow a validated body value. A non-object body (array, primitive, null)
|
|
49
|
+
skips the merge entirely and is returned as-is — there's no key on the body
|
|
50
|
+
to layer the query into, and the framework's args type is a single bag rather
|
|
51
|
+
than a `{body, query}` envelope. Returns undefined when no source contributes
|
|
52
|
+
any key.
|
|
53
|
+
|
|
54
|
+
`maxBodySize` (per-verb, opt-in) bounds the body's actual received bytes
|
|
55
|
+
before any parse — see readBodyWithinLimit. Omitted = no abide-level check;
|
|
56
|
+
Bun.serve's server-wide maxRequestBodySize is the ceiling.
|
|
57
|
+
*/
|
|
58
|
+
export async function parseArgs(
|
|
59
|
+
method: HttpVerb,
|
|
60
|
+
request: Request,
|
|
61
|
+
maxBodySize?: number,
|
|
62
|
+
): Promise<unknown> {
|
|
63
|
+
/*
|
|
64
|
+
Skip the URL parse entirely when the raw request URL has no query —
|
|
65
|
+
typical POST/PUT/PATCH calls land here with a flat rpc URL and no
|
|
66
|
+
`?…`, so the `new URL(...)` constructor cost (which dwarfs the
|
|
67
|
+
indexOf check) is wasted work.
|
|
68
|
+
*/
|
|
69
|
+
const queryStart = request.url.indexOf('?')
|
|
70
|
+
const hasQuery = queryStart !== -1
|
|
71
|
+
const url = hasQuery ? new URL(request.url) : undefined
|
|
72
|
+
|
|
73
|
+
let body: unknown
|
|
74
|
+
if (carriesBodyArgs(method)) {
|
|
75
|
+
let bounded = request
|
|
76
|
+
if (maxBodySize !== undefined) {
|
|
77
|
+
bounded = await readBodyWithinLimit(request, maxBodySize)
|
|
78
|
+
/*
|
|
79
|
+
The size check drained the original body, so point the scope's
|
|
80
|
+
request at the readable copy — a handler with a content-type this
|
|
81
|
+
parse skips (raw uploads) reads the body via request() itself, and
|
|
82
|
+
it must see the bytes, not 'Body already used'.
|
|
83
|
+
*/
|
|
84
|
+
const store = requestContext.getStore()
|
|
85
|
+
if (store) {
|
|
86
|
+
store.req = bounded
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const contentType = (bounded.headers.get('content-type') ?? '').toLowerCase()
|
|
90
|
+
try {
|
|
91
|
+
if (contentType.includes('application/json')) {
|
|
92
|
+
const text = await bounded.text()
|
|
93
|
+
if (text !== '') {
|
|
94
|
+
body = JSON.parse(text)
|
|
95
|
+
}
|
|
96
|
+
} else if (
|
|
97
|
+
contentType.includes('application/x-www-form-urlencoded') ||
|
|
98
|
+
contentType.includes('multipart/form-data')
|
|
99
|
+
) {
|
|
100
|
+
body = splitFormData(await bounded.formData())
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
/* A malformed body is the client's fault — surface a 400, not the 500 a
|
|
104
|
+
raw JSON.parse/formData throw would bubble to. createRemoteFunction.fetch
|
|
105
|
+
unwraps the HttpError's response onto the wire (as it does for the 413). */
|
|
106
|
+
throw new HttpError(error(400, 'Malformed request body'))
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (body !== undefined && (typeof body !== 'object' || body === null || Array.isArray(body))) {
|
|
111
|
+
return body
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!url) {
|
|
115
|
+
if (body === undefined) {
|
|
116
|
+
return undefined
|
|
117
|
+
}
|
|
118
|
+
return body
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const bodyObject = (body ?? {}) as Record<string, unknown>
|
|
122
|
+
const merged = { ...Object.fromEntries(url.searchParams), ...bodyObject }
|
|
123
|
+
if (Object.keys(merged).length === 0) {
|
|
124
|
+
return undefined
|
|
125
|
+
}
|
|
126
|
+
return merged
|
|
127
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { HttpError } from '../../shared/HttpError.ts'
|
|
2
|
+
import { error } from '../error.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Enforces a verb's maxBodySize on the actual received bytes, not the
|
|
6
|
+
Content-Length header (absent on chunked bodies, trivially spoofed). A
|
|
7
|
+
declared Content-Length over the limit rejects before reading anything;
|
|
8
|
+
otherwise the body streams into a buffer that throws 413 the moment the
|
|
9
|
+
limit is crossed — aborting the read, so the remaining bytes are never
|
|
10
|
+
consumed. Returns a reassembled Request carrying the buffered body and the
|
|
11
|
+
original headers, so the parse path's .text()/.formData() work unchanged.
|
|
12
|
+
Only called when the verb declares maxBodySize; the default ceiling is
|
|
13
|
+
Bun.serve's server-wide maxRequestBodySize, which Bun enforces natively.
|
|
14
|
+
*/
|
|
15
|
+
export async function readBodyWithinLimit(request: Request, maxBytes: number): Promise<Request> {
|
|
16
|
+
const tooLarge = () =>
|
|
17
|
+
new HttpError(error(413, `request body exceeds maxBodySize (${maxBytes} bytes)`))
|
|
18
|
+
const declared = Number(request.headers.get('content-length') ?? Number.NaN)
|
|
19
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
20
|
+
throw tooLarge()
|
|
21
|
+
}
|
|
22
|
+
if (!request.body) {
|
|
23
|
+
return request
|
|
24
|
+
}
|
|
25
|
+
const chunks: Uint8Array[] = []
|
|
26
|
+
let total = 0
|
|
27
|
+
for await (const chunk of request.body) {
|
|
28
|
+
total += chunk.byteLength
|
|
29
|
+
if (total > maxBytes) {
|
|
30
|
+
throw tooLarge()
|
|
31
|
+
}
|
|
32
|
+
chunks.push(chunk)
|
|
33
|
+
}
|
|
34
|
+
const buffered = new Uint8Array(total)
|
|
35
|
+
chunks.reduce((offset, chunk) => {
|
|
36
|
+
buffered.set(chunk, offset)
|
|
37
|
+
return offset + chunk.byteLength
|
|
38
|
+
}, 0)
|
|
39
|
+
return new Request(request.url, {
|
|
40
|
+
method: request.method,
|
|
41
|
+
headers: request.headers,
|
|
42
|
+
body: buffered,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { error } from '../error.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Bounds a verb handler's execution: races the in-flight `work` against `ms`.
|
|
5
|
+
On the deadline it resolves a 504 — so the caller (an SSR cache read, an
|
|
6
|
+
MCP/CLI invocation, or the network response) is unblocked in time with an
|
|
7
|
+
honest status — and calls `onTimeout` to cancel any
|
|
8
|
+
cooperating outbound work (the network path composes the verb's deadline into
|
|
9
|
+
request().signal; see defineVerb).
|
|
10
|
+
|
|
11
|
+
`work` keeps running after the deadline — JS can't cancel a running async
|
|
12
|
+
function, only stop awaiting it — so its eventual settlement is swallowed to
|
|
13
|
+
avoid an unhandled rejection, and a late-resolved streaming Response has its
|
|
14
|
+
body cancelled to release the underlying source. A handler that wants its own
|
|
15
|
+
outbound I/O torn down should pass request().signal to it.
|
|
16
|
+
*/
|
|
17
|
+
export function runWithVerbTimeout(
|
|
18
|
+
work: Promise<Response>,
|
|
19
|
+
ms: number,
|
|
20
|
+
onTimeout: () => void,
|
|
21
|
+
): Promise<Response> {
|
|
22
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
23
|
+
let timedOut = false
|
|
24
|
+
const deadline = new Promise<Response>((resolve) => {
|
|
25
|
+
timer = setTimeout(() => {
|
|
26
|
+
timedOut = true
|
|
27
|
+
onTimeout()
|
|
28
|
+
resolve(error(504, 'handler timeout'))
|
|
29
|
+
}, ms)
|
|
30
|
+
})
|
|
31
|
+
return (async () => {
|
|
32
|
+
try {
|
|
33
|
+
return await Promise.race([work, deadline])
|
|
34
|
+
} finally {
|
|
35
|
+
clearTimeout(timer)
|
|
36
|
+
if (timedOut) {
|
|
37
|
+
// The race already returned a 504. Swallow a late rejection, and
|
|
38
|
+
// cancel a late-resolved streaming Response's body so its source
|
|
39
|
+
// (DB cursor, file handle) is released rather than leaked.
|
|
40
|
+
void work.then(
|
|
41
|
+
(late) => {
|
|
42
|
+
void late.body?.cancel()
|
|
43
|
+
},
|
|
44
|
+
() => {},
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
})()
|
|
49
|
+
}
|