@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,73 @@
|
|
|
1
|
+
import { pageUrlForFile } from './pageUrlForFile.ts'
|
|
2
|
+
import { parseRouteSegments } from './parseRouteSegments.ts'
|
|
3
|
+
import { writeDts } from './writeDts.ts'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Walks a `[name]` / `[...rest]` route URL and returns the param shape it
|
|
7
|
+
declares. Catch-all segments map to `string` under their declared name —
|
|
8
|
+
the server's toBunRoutePattern renames Bun's `*` key back to that name
|
|
9
|
+
when dispatching, so the page component sees `params.rest`, not
|
|
10
|
+
`params['*']`.
|
|
11
|
+
*/
|
|
12
|
+
function paramsForRoute(routeUrl: string): Record<string, 'string'> {
|
|
13
|
+
return Object.fromEntries(
|
|
14
|
+
parseRouteSegments(routeUrl)
|
|
15
|
+
.filter((segment) => segment.kind === 'param')
|
|
16
|
+
.map((segment) => [segment.name, 'string'] as const),
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function renderParamsShape(shape: Record<string, 'string'>): string {
|
|
21
|
+
const keys = Object.keys(shape)
|
|
22
|
+
if (keys.length === 0) {
|
|
23
|
+
return 'Record<string, never>'
|
|
24
|
+
}
|
|
25
|
+
return `{ ${keys.map((key) => `${JSON.stringify(key)}: string`).join('; ')} }`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
Emits a `.d.ts` that augments abide's `Routes` interface with one entry per
|
|
30
|
+
page file in the project. Page picks this up as a discriminated union keyed
|
|
31
|
+
on `route`, so `if (page.route === '/media/[id]') page.params.id` is typed
|
|
32
|
+
automatically without consumers writing route types by hand.
|
|
33
|
+
The file is written to `src/.abide/routes.d.ts` so the consumer's existing
|
|
34
|
+
src tsconfig include picks it up with no extra configuration. The augmented
|
|
35
|
+
module is keyed on the name the project imports abide under (`importName`),
|
|
36
|
+
so the augmentation matches the consumer's `page` import whether abide is
|
|
37
|
+
installed directly (`abide`) or behind an alias.
|
|
38
|
+
*/
|
|
39
|
+
export async function writeRoutesDts({
|
|
40
|
+
cwd,
|
|
41
|
+
pageFiles,
|
|
42
|
+
importName,
|
|
43
|
+
}: {
|
|
44
|
+
cwd: string
|
|
45
|
+
pageFiles: string[]
|
|
46
|
+
importName: string
|
|
47
|
+
}): Promise<void> {
|
|
48
|
+
const routes = pageFiles
|
|
49
|
+
.map((file) => ({
|
|
50
|
+
route: pageUrlForFile(file),
|
|
51
|
+
params: paramsForRoute(pageUrlForFile(file)),
|
|
52
|
+
}))
|
|
53
|
+
.toSorted((a, b) => a.route.localeCompare(b.route))
|
|
54
|
+
const entries = routes
|
|
55
|
+
.map(
|
|
56
|
+
({ route, params }) => ` ${JSON.stringify(route)}: ${renderParamsShape(params)}`,
|
|
57
|
+
)
|
|
58
|
+
.join('\n')
|
|
59
|
+
/* Keys-only mirror for url()'s autocomplete (values unused — PathParams derives the shape). */
|
|
60
|
+
const urlKeys = routes.map(({ route }) => ` ${JSON.stringify(route)}: true`).join('\n')
|
|
61
|
+
const body = `declare module '${importName}/shared/page' {
|
|
62
|
+
interface Routes {
|
|
63
|
+
${entries}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
declare module '${importName}/shared/url' {
|
|
68
|
+
interface PageRoutes {
|
|
69
|
+
${urlKeys}
|
|
70
|
+
}
|
|
71
|
+
}`
|
|
72
|
+
await writeDts(cwd, 'routes', body)
|
|
73
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { carriesBodyArgs } from './carriesBodyArgs.ts'
|
|
2
|
+
import { detectVerbMethod } from './detectVerbMethod.ts'
|
|
3
|
+
import { fileStem } from './fileStem.ts'
|
|
4
|
+
import { rpcUrlForFile } from './rpcUrlForFile.ts'
|
|
5
|
+
import { writeDts } from './writeDts.ts'
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
Emits a `.d.ts` that augments abide's `RpcRoutes` interface with one entry per
|
|
9
|
+
query-carrying $rpc verb, so `url('/rpc/search', { q })` types its args against
|
|
10
|
+
the verb's own signature. Only GET/DELETE/HEAD (non-body) verbs are included —
|
|
11
|
+
a url() can carry a query string but not a request body, so a POST verb has no
|
|
12
|
+
URL form. `RpcArgs` lifts the args type out of the verb's RemoteFunction
|
|
13
|
+
(dropping the FormData upload variant); the file path resolves the export by
|
|
14
|
+
its filename, the abide one-export-per-file convention. Written to
|
|
15
|
+
`src/.abide/rpc.d.ts` so the consumer's src tsconfig include picks it up, keyed
|
|
16
|
+
on the project's abide import name like writeRoutesDts.
|
|
17
|
+
*/
|
|
18
|
+
export async function writeRpcDts({
|
|
19
|
+
cwd,
|
|
20
|
+
rpcDir,
|
|
21
|
+
rpcFiles,
|
|
22
|
+
importName,
|
|
23
|
+
}: {
|
|
24
|
+
cwd: string
|
|
25
|
+
rpcDir: string
|
|
26
|
+
rpcFiles: string[]
|
|
27
|
+
importName: string
|
|
28
|
+
}): Promise<void> {
|
|
29
|
+
const lines = await Promise.all(
|
|
30
|
+
rpcFiles.map(async (file) => {
|
|
31
|
+
const method = detectVerbMethod(await Bun.file(`${rpcDir}/${file}`).text())
|
|
32
|
+
// A body verb's args can't ride a URL — leave it out of the url() rpc map.
|
|
33
|
+
if (!method || carriesBodyArgs(method)) {
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
const importPath = `../server/rpc/${file}`
|
|
37
|
+
return ` ${JSON.stringify(rpcUrlForFile(file))}: RpcArgs<typeof import(${JSON.stringify(importPath)}).${fileStem(file)}>`
|
|
38
|
+
}),
|
|
39
|
+
)
|
|
40
|
+
const entries = lines.filter((line) => line !== undefined).toSorted()
|
|
41
|
+
const body = `type RpcArgs<Fn> = Fn extends (args: infer Args) => unknown ? Exclude<Args, FormData> : never
|
|
42
|
+
|
|
43
|
+
declare module '${importName}/shared/url' {
|
|
44
|
+
interface RpcRoutes {
|
|
45
|
+
${entries.join('\n')}
|
|
46
|
+
}
|
|
47
|
+
}`
|
|
48
|
+
await writeDts(cwd, 'rpc', body)
|
|
49
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { commandNameForUrl } from './commandNameForUrl.ts'
|
|
2
|
+
import { fileStem } from './fileStem.ts'
|
|
3
|
+
import { rpcUrlForFile } from './rpcUrlForFile.ts'
|
|
4
|
+
import { writeDts } from './writeDts.ts'
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Emits a `.d.ts` that augments createTestApp's `RpcClient` interface with one
|
|
8
|
+
entry per $rpc verb, keyed by command name (the same key `app.rpc.<name>`
|
|
9
|
+
resolves at runtime). Each entry lifts the verb's args + resolved return out of
|
|
10
|
+
its RemoteFunction so `app.rpc.getProduct({ id })` types against the verb's own
|
|
11
|
+
signature — args in, decoded body out, plus `.raw` for the Response. `RpcArgs`
|
|
12
|
+
drops the FormData upload variant exactly as writeRpcDts does; `RpcReturn`
|
|
13
|
+
reads the resolved body type. Written to `src/.abide/testRpc.d.ts` so the
|
|
14
|
+
consumer's src tsconfig include picks it up, keyed on the project's abide
|
|
15
|
+
import name.
|
|
16
|
+
*/
|
|
17
|
+
export async function writeTestRpcDts({
|
|
18
|
+
cwd,
|
|
19
|
+
rpcFiles,
|
|
20
|
+
importName,
|
|
21
|
+
}: {
|
|
22
|
+
cwd: string
|
|
23
|
+
rpcFiles: string[]
|
|
24
|
+
importName: string
|
|
25
|
+
}): Promise<void> {
|
|
26
|
+
const entries = rpcFiles
|
|
27
|
+
.map((file) => {
|
|
28
|
+
const name = commandNameForUrl(rpcUrlForFile(file))
|
|
29
|
+
const importPath = `../server/rpc/${file}`
|
|
30
|
+
return ` ${JSON.stringify(name)}: RpcInvoker<typeof import(${JSON.stringify(importPath)}).${fileStem(file)}>`
|
|
31
|
+
})
|
|
32
|
+
.toSorted()
|
|
33
|
+
const body = `type RpcArgs<Fn> = Fn extends (args: infer Args) => unknown ? Exclude<Args, FormData> : never
|
|
34
|
+
type RpcReturn<Fn> = Fn extends (...args: never[]) => Promise<infer Return> ? Return : never
|
|
35
|
+
type RpcInvoker<Fn> = ((args?: RpcArgs<Fn>) => Promise<RpcReturn<Fn>>) & {
|
|
36
|
+
raw: (args?: RpcArgs<Fn>) => Promise<Response>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare module '${importName}/test/createTestApp' {
|
|
40
|
+
interface RpcClient {
|
|
41
|
+
${entries.join('\n')}
|
|
42
|
+
}
|
|
43
|
+
}`
|
|
44
|
+
await writeDts(cwd, 'testRpc', body)
|
|
45
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { fileStem } from './fileStem.ts'
|
|
2
|
+
import { socketNameForFile } from './socketNameForFile.ts'
|
|
3
|
+
import { writeDts } from './writeDts.ts'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Emits a `.d.ts` that augments createTestApp's `SocketClient` interface with one
|
|
7
|
+
entry per $socket, keyed by socket name (the same key `app.sockets.<name>`
|
|
8
|
+
resolves at runtime). The `socket(...)` helper statically returns `Socket<T>`,
|
|
9
|
+
so each entry is the export's own type directly — `app.sockets.chat` types as
|
|
10
|
+
`Socket<ChatMessage>`, iterable for the live stream with `.tail`/`.publish`.
|
|
11
|
+
Written to `src/.abide/testSockets.d.ts` like its rpc sibling.
|
|
12
|
+
*/
|
|
13
|
+
export async function writeTestSocketsDts({
|
|
14
|
+
cwd,
|
|
15
|
+
socketFiles,
|
|
16
|
+
importName,
|
|
17
|
+
}: {
|
|
18
|
+
cwd: string
|
|
19
|
+
socketFiles: string[]
|
|
20
|
+
importName: string
|
|
21
|
+
}): Promise<void> {
|
|
22
|
+
const entries = socketFiles
|
|
23
|
+
.map((file) => {
|
|
24
|
+
const importPath = `../server/sockets/${file}`
|
|
25
|
+
return ` ${JSON.stringify(socketNameForFile(file))}: typeof import(${JSON.stringify(importPath)}).${fileStem(file)}`
|
|
26
|
+
})
|
|
27
|
+
.toSorted()
|
|
28
|
+
const body = `declare module '${importName}/test/createTestApp' {
|
|
29
|
+
interface SocketClient {
|
|
30
|
+
${entries.join('\n')}
|
|
31
|
+
}
|
|
32
|
+
}`
|
|
33
|
+
await writeDts(cwd, 'testSockets', body)
|
|
34
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { AgentFrame } from '../server/agent.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Collects an engine's frame stream and asserts the neutral AgentFrame contract
|
|
5
|
+
every provider engine must satisfy, throwing a plain Error (test-framework
|
|
6
|
+
agnostic) on the first violation:
|
|
7
|
+
|
|
8
|
+
- the stream ends with exactly one `done` frame and yields nothing after it
|
|
9
|
+
- every `tool_use` is answered by exactly one `tool_result` carrying the
|
|
10
|
+
same id and name, in use-then-result order
|
|
11
|
+
- no `tool_result` arrives for an unannounced id
|
|
12
|
+
- text deltas are strings
|
|
13
|
+
|
|
14
|
+
Returns the collected frames plus the `done` frame so a scenario can layer
|
|
15
|
+
its provider-specific assertions on top. This is the conformance seam: one
|
|
16
|
+
suite of invariants, N engine adapters run against it.
|
|
17
|
+
*/
|
|
18
|
+
// @readme plumbing
|
|
19
|
+
export async function assertAgentFrameConformance(
|
|
20
|
+
stream: AsyncIterable<AgentFrame>,
|
|
21
|
+
): Promise<{ frames: AgentFrame[]; done: Extract<AgentFrame, { type: 'done' }> }> {
|
|
22
|
+
const frames: AgentFrame[] = []
|
|
23
|
+
for await (const frame of stream) {
|
|
24
|
+
frames.push(frame)
|
|
25
|
+
}
|
|
26
|
+
if (frames.length === 0) {
|
|
27
|
+
throw new Error('conformance: engine yielded no frames — expected at least a done frame')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const doneIndexes = frames.flatMap((frame, index) => (frame.type === 'done' ? [index] : []))
|
|
31
|
+
if (doneIndexes.length !== 1) {
|
|
32
|
+
throw new Error(`conformance: expected exactly one done frame, saw ${doneIndexes.length}`)
|
|
33
|
+
}
|
|
34
|
+
if (doneIndexes[0] !== frames.length - 1) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`conformance: frames continued after done (done at ${doneIndexes[0]} of ${frames.length - 1})`,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const openToolUses = new Map<string, string>()
|
|
41
|
+
for (const [index, frame] of frames.entries()) {
|
|
42
|
+
if (frame.type === 'text' && typeof frame.delta !== 'string') {
|
|
43
|
+
throw new Error(`conformance: text frame at ${index} has a non-string delta`)
|
|
44
|
+
}
|
|
45
|
+
if (frame.type === 'tool_use') {
|
|
46
|
+
if (openToolUses.has(frame.id)) {
|
|
47
|
+
throw new Error(`conformance: duplicate tool_use id ${frame.id} at ${index}`)
|
|
48
|
+
}
|
|
49
|
+
openToolUses.set(frame.id, frame.name)
|
|
50
|
+
}
|
|
51
|
+
if (frame.type === 'tool_result') {
|
|
52
|
+
const expectedName = openToolUses.get(frame.id)
|
|
53
|
+
if (expectedName === undefined) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`conformance: tool_result at ${index} answers unannounced id ${frame.id}`,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
if (frame.name !== expectedName) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`conformance: tool_result at ${index} names ${frame.name}, tool_use said ${expectedName}`,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
openToolUses.delete(frame.id)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (openToolUses.size > 0) {
|
|
67
|
+
const unanswered = Array.from(openToolUses.keys()).join(', ')
|
|
68
|
+
throw new Error(`conformance: tool_use without a tool_result: ${unanswered}`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const done = frames[frames.length - 1] as Extract<AgentFrame, { type: 'done' }>
|
|
72
|
+
return { frames, done }
|
|
73
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ToolResult } from '../mcp/mcpSurface.ts'
|
|
2
|
+
import type { AgentSurface } from '../server/agent.ts'
|
|
3
|
+
|
|
4
|
+
type ScriptedTool = {
|
|
5
|
+
name: string
|
|
6
|
+
description?: string
|
|
7
|
+
inputSchema?: Record<string, unknown>
|
|
8
|
+
/* Result handed back to the engine; throw to simulate a failed dispatch. */
|
|
9
|
+
result: (args: Record<string, unknown> | undefined) => ToolResult
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type RecordedCall = { name: string; args: Record<string, unknown> | undefined }
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
A scripted AgentSurface for engine tests: declarative tool stubs in, an MCP
|
|
16
|
+
surface out, with every `call` recorded so a test can assert exactly which
|
|
17
|
+
tools an engine dispatched and with what arguments. Prompts and resources are
|
|
18
|
+
empty — engines must tolerate an app exposing none.
|
|
19
|
+
*/
|
|
20
|
+
// @readme plumbing
|
|
21
|
+
export function createScriptedSurface(
|
|
22
|
+
tools: ScriptedTool[] = [],
|
|
23
|
+
): AgentSurface & { calls: RecordedCall[] } {
|
|
24
|
+
const calls: RecordedCall[] = []
|
|
25
|
+
return {
|
|
26
|
+
calls,
|
|
27
|
+
tools: tools.map((tool) => ({
|
|
28
|
+
name: tool.name,
|
|
29
|
+
description: tool.description ?? tool.name,
|
|
30
|
+
inputSchema: tool.inputSchema ?? { type: 'object' },
|
|
31
|
+
})),
|
|
32
|
+
async call(name, args) {
|
|
33
|
+
calls.push({ name, args })
|
|
34
|
+
const tool = tools.find((candidate) => candidate.name === name)
|
|
35
|
+
if (!tool) {
|
|
36
|
+
return { content: [{ type: 'text', text: `unknown tool: ${name}` }], isError: true }
|
|
37
|
+
}
|
|
38
|
+
return tool.result(args)
|
|
39
|
+
},
|
|
40
|
+
prompts: [],
|
|
41
|
+
getPrompt: () => [],
|
|
42
|
+
listResources: async () => [],
|
|
43
|
+
readResource: async () => undefined,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { Server } from 'bun'
|
|
2
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
3
|
+
import * as appMod from '../../_virtual/app.ts'
|
|
4
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
5
|
+
import { appInfo } from '../../_virtual/app-info.ts'
|
|
6
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
7
|
+
import { assets } from '../../_virtual/assets.ts'
|
|
8
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
9
|
+
import cliProgramName from '../../_virtual/cli-name.ts'
|
|
10
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
11
|
+
import { layouts } from '../../_virtual/layouts.ts'
|
|
12
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
13
|
+
import mcp from '../../_virtual/mcp.ts'
|
|
14
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
15
|
+
import { mcpResources } from '../../_virtual/mcp-resources.ts'
|
|
16
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
17
|
+
import { pages } from '../../_virtual/pages.ts'
|
|
18
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
19
|
+
import { prompts } from '../../_virtual/prompts.ts'
|
|
20
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
21
|
+
import { publicAssets } from '../../_virtual/public-assets.ts'
|
|
22
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
23
|
+
import { rpc } from '../../_virtual/rpc.ts'
|
|
24
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
25
|
+
import { shell } from '../../_virtual/shell.ts'
|
|
26
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
27
|
+
import { sockets } from '../../_virtual/sockets.ts'
|
|
28
|
+
import { verbRegistry } from '../server/rpc/verbRegistry.ts'
|
|
29
|
+
import { createServer } from '../server/runtime/createServer.ts'
|
|
30
|
+
import { ensureRegistriesLoaded } from '../server/runtime/registryManifests.ts'
|
|
31
|
+
import { requestContext } from '../server/runtime/requestContext.ts'
|
|
32
|
+
import { resolvePageSnapshot } from '../server/runtime/resolvePageSnapshot.ts'
|
|
33
|
+
import { serverSlot } from '../server/runtime/serverSlot.ts'
|
|
34
|
+
import type { Socket } from '../server/sockets/types/Socket.ts'
|
|
35
|
+
import { baseSlot } from '../shared/baseSlot.ts'
|
|
36
|
+
import { buildRpcProxy } from '../shared/buildRpcProxy.ts'
|
|
37
|
+
import { buildRpcRequest } from '../shared/buildRpcRequest.ts'
|
|
38
|
+
import { cacheStoreSlot } from '../shared/cacheStoreSlot.ts'
|
|
39
|
+
import { commandNameForUrl } from '../shared/commandNameForUrl.ts'
|
|
40
|
+
import { createCacheStore } from '../shared/createCacheStore.ts'
|
|
41
|
+
import { globalCacheStoreSlot } from '../shared/globalCacheStoreSlot.ts'
|
|
42
|
+
import { HEALTH_PATH } from '../shared/HEALTH_PATH.ts'
|
|
43
|
+
import { pageSlot } from '../shared/pageSlot.ts'
|
|
44
|
+
import { SOCKETS_PATH } from '../shared/SOCKETS_PATH.ts'
|
|
45
|
+
import type { RemoteFunction } from '../shared/types/RemoteFunction.ts'
|
|
46
|
+
import { createTestSocketChannel } from './createTestSocketChannel.ts'
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
Augmentable verb/socket maps for `app.rpc.<verb>` / `app.sockets.<name>`. The
|
|
50
|
+
build's writeTestRpcDts / writeTestSocketsDts emit one entry per verb/socket
|
|
51
|
+
(into src/.abide/), so the keys + signatures are the project's real surface
|
|
52
|
+
with no imports. Empty here; types arrive once the app has been built. Mirrors
|
|
53
|
+
url's RpcRoutes / health's AppHealthMap.
|
|
54
|
+
*/
|
|
55
|
+
// @readme testing
|
|
56
|
+
// biome-ignore lint/suspicious/noEmptyInterface: augmented by the generated testRpc.d.ts
|
|
57
|
+
export interface RpcClient {}
|
|
58
|
+
// biome-ignore lint/suspicious/noEmptyInterface: augmented by the generated testSockets.d.ts
|
|
59
|
+
export interface SocketClient {}
|
|
60
|
+
|
|
61
|
+
/* The booted app under test. Every named subsystem is reachable as the verb
|
|
62
|
+
you call, the socket you iterate, or a path you fetch — over the real
|
|
63
|
+
server, so the full pipeline (CSRF, cookies, base path) runs. */
|
|
64
|
+
export type TestApp = {
|
|
65
|
+
/* The kernel-assigned origin, e.g. http://localhost:51234. */
|
|
66
|
+
origin: string
|
|
67
|
+
/* fetch against the app: prefixes the kernel-assigned origin, so you pass a
|
|
68
|
+
route-space path (`/products/1`), not the wire URL. The booted server
|
|
69
|
+
matches routes at raw paths — the APP_URL mount base is stripped by an
|
|
70
|
+
external proxy in production, absent here — so paths carry no base. */
|
|
71
|
+
fetch: (path: string, init?: RequestInit) => Promise<Response>
|
|
72
|
+
/* Verb calls over HTTP, keyed by command name: `app.rpc.getProduct({ id })`. */
|
|
73
|
+
rpc: RpcClient
|
|
74
|
+
/* Sockets keyed by name: `app.sockets.ticker` is the Socket — iterate it for
|
|
75
|
+
the live stream, `.tail(n)` to seed, `.publish(m)` to send. */
|
|
76
|
+
sockets: SocketClient
|
|
77
|
+
/* The /__abide/health payload, decoded. */
|
|
78
|
+
health: () => Promise<unknown>
|
|
79
|
+
/* Stops the server, releases the port, restores every touched slot. */
|
|
80
|
+
stop: () => Promise<void>
|
|
81
|
+
/* `await using app = await createTestApp()` — disposal runs stop(), so a
|
|
82
|
+
thrown assertion still releases the port and restores the slots rather
|
|
83
|
+
than leaking the request-scope/cache/page resolvers into the next file. */
|
|
84
|
+
[Symbol.asyncDispose]: () => Promise<void>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/*
|
|
88
|
+
Boots the real app on an ephemeral port — the same wiring serverEntry performs,
|
|
89
|
+
minus the standalone-binary env layers. Imports the framework's virtual
|
|
90
|
+
manifests (resolved by abideResolverPlugin, registered via `abide/preload`
|
|
91
|
+
in the consumer's bunfig), so the routes, verbs, and sockets are the project's
|
|
92
|
+
real surface, not a fixture. Pass nothing: `await createTestApp()` is the app
|
|
93
|
+
exactly as `bun start` would serve it.
|
|
94
|
+
|
|
95
|
+
Slots are saved and restored around the boot so a suite tears down without
|
|
96
|
+
leaking the request-scope/cache/page resolvers into the next test file.
|
|
97
|
+
*/
|
|
98
|
+
export async function createTestApp(): Promise<TestApp> {
|
|
99
|
+
const previous = {
|
|
100
|
+
cacheResolver: cacheStoreSlot.resolver,
|
|
101
|
+
globalResolver: globalCacheStoreSlot.resolver,
|
|
102
|
+
pageResolver: pageSlot.resolver,
|
|
103
|
+
baseResolver: baseSlot.resolver,
|
|
104
|
+
activeServer: serverSlot.active,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
cacheStoreSlot.resolver = () => requestContext.getStore()?.cache
|
|
108
|
+
const globalStore = createCacheStore()
|
|
109
|
+
globalCacheStoreSlot.resolver = () => globalStore
|
|
110
|
+
pageSlot.resolver = resolvePageSnapshot
|
|
111
|
+
|
|
112
|
+
/* Eager env validation, exactly as serverEntry: a top-level env(schema) in
|
|
113
|
+
src/server/config.ts fails the boot loudly here rather than lazily. No-op
|
|
114
|
+
when the file is absent. */
|
|
115
|
+
// @ts-expect-error virtual module resolved by abideResolverPlugin
|
|
116
|
+
await import('../../_virtual/config.ts')
|
|
117
|
+
|
|
118
|
+
const server: Server<unknown> = await createServer({
|
|
119
|
+
pages,
|
|
120
|
+
layouts,
|
|
121
|
+
rpc,
|
|
122
|
+
sockets,
|
|
123
|
+
prompts,
|
|
124
|
+
shell,
|
|
125
|
+
app: appMod,
|
|
126
|
+
assets,
|
|
127
|
+
publicAssets,
|
|
128
|
+
mcpResources,
|
|
129
|
+
mcp,
|
|
130
|
+
cliProgramName,
|
|
131
|
+
appInfo,
|
|
132
|
+
port: 0,
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const origin = `http://localhost:${server.port}`
|
|
136
|
+
|
|
137
|
+
function appFetch(path: string, init?: RequestInit): Promise<Response> {
|
|
138
|
+
return fetch(`${origin}${path}`, init)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Verb modules loaded once so the registry holds every RemoteFunction; the
|
|
142
|
+
proxy maps command name → an HTTP call against the booted server. */
|
|
143
|
+
await ensureRegistriesLoaded()
|
|
144
|
+
const remotes = new Map<string, RemoteFunction<unknown, unknown>>(
|
|
145
|
+
Array.from(verbRegistry.values()).map((entry) => [
|
|
146
|
+
commandNameForUrl(entry.remote.url),
|
|
147
|
+
entry.remote,
|
|
148
|
+
]),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
function send(remote: RemoteFunction<unknown, unknown>, args: unknown): Promise<Response> {
|
|
152
|
+
/* Same-origin Origin header so the CSRF gate admits mutating verbs; the
|
|
153
|
+
server serves verbs at their raw url (no mount base applied here). */
|
|
154
|
+
const request = buildRpcRequest({
|
|
155
|
+
method: remote.method,
|
|
156
|
+
url: remote.url,
|
|
157
|
+
args,
|
|
158
|
+
baseUrl: `${origin}/`,
|
|
159
|
+
headers: new Headers({ origin }),
|
|
160
|
+
})
|
|
161
|
+
return fetch(request)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const rpcClient = buildRpcProxy<RpcClient>((name) => {
|
|
165
|
+
const remote = remotes.get(name)
|
|
166
|
+
return remote ? (args) => send(remote, args) : undefined
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
/* One ws to the booted multiplex, dialed lazily on first socket access so a
|
|
170
|
+
suite touching no socket holds no connection. http→ws, raw path. */
|
|
171
|
+
const wsUrl = `${origin.replace(/^http/, 'ws')}${SOCKETS_PATH}`
|
|
172
|
+
let channel: ReturnType<typeof createTestSocketChannel> | undefined
|
|
173
|
+
const socketNames = new Set(Object.keys(sockets as Record<string, unknown>))
|
|
174
|
+
const socketClient = new Proxy({} as Record<string, Socket<unknown>>, {
|
|
175
|
+
get(_target, prop): Socket<unknown> | undefined {
|
|
176
|
+
if (typeof prop !== 'string' || !socketNames.has(prop)) {
|
|
177
|
+
return undefined
|
|
178
|
+
}
|
|
179
|
+
channel ??= createTestSocketChannel(wsUrl)
|
|
180
|
+
return channel.socket(prop)
|
|
181
|
+
},
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
async function stop(): Promise<void> {
|
|
185
|
+
channel?.close()
|
|
186
|
+
server.stop(true)
|
|
187
|
+
cacheStoreSlot.resolver = previous.cacheResolver
|
|
188
|
+
globalCacheStoreSlot.resolver = previous.globalResolver
|
|
189
|
+
pageSlot.resolver = previous.pageResolver
|
|
190
|
+
baseSlot.resolver = previous.baseResolver
|
|
191
|
+
serverSlot.active = previous.activeServer
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
origin,
|
|
196
|
+
fetch: appFetch,
|
|
197
|
+
rpc: rpcClient,
|
|
198
|
+
sockets: socketClient as unknown as SocketClient,
|
|
199
|
+
health: () => appFetch(HEALTH_PATH).then((response) => response.json()),
|
|
200
|
+
stop,
|
|
201
|
+
[Symbol.asyncDispose]: stop,
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { Socket } from '../server/sockets/types/Socket.ts'
|
|
2
|
+
import type { SocketClientFrame } from '../server/sockets/types/SocketClientFrame.ts'
|
|
3
|
+
import type { SocketServerFrame } from '../server/sockets/types/SocketServerFrame.ts'
|
|
4
|
+
import { buildSocketOverChannel } from '../shared/buildSocketOverChannel.ts'
|
|
5
|
+
import type { SocketChannel } from '../shared/types/SocketChannel.ts'
|
|
6
|
+
import type { SocketSubCallbacks } from '../shared/types/SocketSubCallbacks.ts'
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
Test-side substitute for the browser socketChannel: one ws to the booted
|
|
10
|
+
server's multiplex, speaking the same SocketClientFrame/SocketServerFrame
|
|
11
|
+
protocol. Stripped of the browser channel's reconnect/backoff/visibility
|
|
12
|
+
machinery — a test owns the connection lifecycle through `close()`, so a drop
|
|
13
|
+
is teardown, not something to recover from. Frames sent before the ws opens
|
|
14
|
+
queue and flush on open, the one piece of timing a test can't sidestep.
|
|
15
|
+
|
|
16
|
+
Implements SocketChannel (subscribe/unsubscribe/publish), so `socket(name)`
|
|
17
|
+
hands its sockets to the same buildSocketOverChannel the browser socketProxy
|
|
18
|
+
uses — the Socket<T> surface can't drift between the test path and production.
|
|
19
|
+
*/
|
|
20
|
+
export function createTestSocketChannel(wsUrl: string): {
|
|
21
|
+
socket: <T>(name: string) => Socket<T>
|
|
22
|
+
close: () => void
|
|
23
|
+
/* `using channel = createTestSocketChannel(url)` — disposal closes the ws. */
|
|
24
|
+
[Symbol.dispose]: () => void
|
|
25
|
+
} {
|
|
26
|
+
const subs = new Map<string, { socket: string; callbacks: SocketSubCallbacks }>()
|
|
27
|
+
const subsBySocket = new Map<string, Set<string>>()
|
|
28
|
+
let pendingSends: string[] = []
|
|
29
|
+
|
|
30
|
+
const ws = new WebSocket(wsUrl)
|
|
31
|
+
|
|
32
|
+
function flushPending(): void {
|
|
33
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
for (const message of pendingSends) {
|
|
37
|
+
ws.send(message)
|
|
38
|
+
}
|
|
39
|
+
pendingSends = []
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function send(frame: SocketClientFrame): void {
|
|
43
|
+
const message = JSON.stringify(frame)
|
|
44
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
45
|
+
ws.send(message)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
pendingSends.push(message)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function dropSub(id: string): void {
|
|
52
|
+
const entry = subs.get(id)
|
|
53
|
+
if (!entry) {
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
subs.delete(id)
|
|
57
|
+
const set = subsBySocket.get(entry.socket)
|
|
58
|
+
if (set) {
|
|
59
|
+
set.delete(id)
|
|
60
|
+
if (set.size === 0) {
|
|
61
|
+
subsBySocket.delete(entry.socket)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
ws.addEventListener('open', flushPending)
|
|
67
|
+
ws.addEventListener('message', (event) => {
|
|
68
|
+
let frame: SocketServerFrame
|
|
69
|
+
try {
|
|
70
|
+
frame = JSON.parse(event.data as string) as SocketServerFrame
|
|
71
|
+
} catch {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
if (frame.type === 'msg') {
|
|
75
|
+
const targets = subsBySocket.get(frame.socket)
|
|
76
|
+
if (!targets) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
for (const subId of targets) {
|
|
80
|
+
subs.get(subId)?.callbacks.onMessage(frame.message)
|
|
81
|
+
}
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
if (frame.type === 'replay') {
|
|
85
|
+
subs.get(frame.sub)?.callbacks.onReplay(frame.messages)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
const sub = subs.get(frame.sub)
|
|
89
|
+
if (!sub) {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
dropSub(frame.sub)
|
|
93
|
+
if (frame.type === 'end') {
|
|
94
|
+
sub.callbacks.onEnd()
|
|
95
|
+
} else {
|
|
96
|
+
sub.callbacks.onError(frame.message)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
/* A drop after subs are live is unexpected; surface it so iterators unblock
|
|
100
|
+
instead of awaiting a frame that never comes. Idempotent — the first of
|
|
101
|
+
error/close clears subs, the second finds none. error covers a failed
|
|
102
|
+
handshake (no open, no clean close) that close alone would miss. */
|
|
103
|
+
function disconnectAll(): void {
|
|
104
|
+
const active = [...subs.values()]
|
|
105
|
+
subs.clear()
|
|
106
|
+
subsBySocket.clear()
|
|
107
|
+
for (const sub of active) {
|
|
108
|
+
sub.callbacks.onDisconnect()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
ws.addEventListener('close', disconnectAll)
|
|
112
|
+
ws.addEventListener('error', disconnectAll)
|
|
113
|
+
|
|
114
|
+
const channel: SocketChannel = {
|
|
115
|
+
subscribe(id, socket, replay, callbacks) {
|
|
116
|
+
subs.set(id, { socket, callbacks })
|
|
117
|
+
let set = subsBySocket.get(socket)
|
|
118
|
+
if (!set) {
|
|
119
|
+
set = new Set()
|
|
120
|
+
subsBySocket.set(socket, set)
|
|
121
|
+
}
|
|
122
|
+
set.add(id)
|
|
123
|
+
send({ type: 'sub', sub: id, socket, replay })
|
|
124
|
+
},
|
|
125
|
+
unsubscribe(id) {
|
|
126
|
+
if (!subs.has(id)) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
dropSub(id)
|
|
130
|
+
send({ type: 'unsub', sub: id })
|
|
131
|
+
},
|
|
132
|
+
publish: (socket, message) => send({ type: 'pub', socket, message }),
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Same Socket<T> builder the browser proxy uses, over this test channel. */
|
|
136
|
+
function socket<T>(name: string): Socket<T> {
|
|
137
|
+
return buildSocketOverChannel<T>(name, () => channel)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const close = () => ws.close()
|
|
141
|
+
return { socket, close, [Symbol.dispose]: close }
|
|
142
|
+
}
|