@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,286 @@
|
|
|
1
|
+
import { layoutChainForRoute } from '../shared/layoutChainForRoute.ts'
|
|
2
|
+
import { effect } from './effect.ts'
|
|
3
|
+
import { matchRoute } from './matchRoute.ts'
|
|
4
|
+
import { navigate } from './navigate.ts'
|
|
5
|
+
import { clientPage } from './runtime/clientPage.ts'
|
|
6
|
+
import { enterRenderPass } from './runtime/enterRenderPass.ts'
|
|
7
|
+
import { exitRenderPass } from './runtime/exitRenderPass.ts'
|
|
8
|
+
import { firstOutlet } from './runtime/firstOutlet.ts'
|
|
9
|
+
import { runtimePath } from './runtime/runtimePath.ts'
|
|
10
|
+
import type { NavVerdict } from './runtime/types/NavVerdict.ts'
|
|
11
|
+
import type { Route } from './runtime/types/Route.ts'
|
|
12
|
+
import type { RouteLoader } from './runtime/types/RouteLoader.ts'
|
|
13
|
+
import { untrack } from './runtime/untrack.ts'
|
|
14
|
+
|
|
15
|
+
/* A layout mounted in the active chain: its route key (the directory URL — its
|
|
16
|
+
identity for the diff), the disposer that stops its reactivity, and the outlet
|
|
17
|
+
element the next layer mounts into. */
|
|
18
|
+
type MountedLayout = { key: string; dispose: () => void; outlet: Element }
|
|
19
|
+
|
|
20
|
+
/* A layout mount that returns no disposer still needs one for the chain teardown. */
|
|
21
|
+
const noop = (): void => {}
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
A minimal client router on the History API. `router` matches the current path
|
|
25
|
+
against the route patterns (literal / `[name]` / `[...rest]`, via matchRoute),
|
|
26
|
+
imports the matching page's chunk — plus every `layout.abide` chunk that wraps it
|
|
27
|
+
(`layoutChainForRoute`) — on demand, and mounts them as a chain: each layout into
|
|
28
|
+
the previous layout's `<slot/>` outlet, the page into the innermost outlet.
|
|
29
|
+
|
|
30
|
+
Navigation re-resolves the chain and DIFFS it against the mounted one: layouts whose
|
|
31
|
+
route key still matches at the same depth stay mounted (their state, effects, scroll,
|
|
32
|
+
and DOM survive); from the first divergent layout down — the leaf layers — everything
|
|
33
|
+
is disposed and rebuilt, and the page (always the leaf) re-mounts every time. The
|
|
34
|
+
reactive `page` proxy means a persisted layout reading route/params updates in place
|
|
35
|
+
without a remount. Each chunk loads only on first visit, cached after.
|
|
36
|
+
|
|
37
|
+
`probe` (when given) runs each post-boot navigation's destination through the
|
|
38
|
+
server's app.handle first, so auth/redirect gating applies to client navigation
|
|
39
|
+
just as it does to a fresh load; its verdict either clears the mount, soft-redirects
|
|
40
|
+
where handle() pointed, or hands off to a full browser load. The first render
|
|
41
|
+
adopts a document handle() already ran on, so it isn't probed. There is no server
|
|
42
|
+
router — the server picks the page by request URL directly; this is the client
|
|
43
|
+
half. `*` is the fallback route.
|
|
44
|
+
*/
|
|
45
|
+
// @readme plumbing
|
|
46
|
+
export function router(
|
|
47
|
+
host: Element,
|
|
48
|
+
loaders: Record<string, RouteLoader>,
|
|
49
|
+
layoutLoaders: Record<string, RouteLoader> = {},
|
|
50
|
+
probe?: (path: string) => Promise<NavVerdict>,
|
|
51
|
+
): () => void {
|
|
52
|
+
/* The mounted layout chain (outermost first) + the page disposer. */
|
|
53
|
+
const mountedLayouts: MountedLayout[] = []
|
|
54
|
+
let disposePage: (() => void) | undefined
|
|
55
|
+
const patterns = Object.keys(loaders).filter((key) => key !== '*')
|
|
56
|
+
const layoutKeys = Object.keys(layoutLoaders)
|
|
57
|
+
|
|
58
|
+
/* A code-split resolver over one loader map: resolved chunks keyed by route
|
|
59
|
+
pattern, so a revisit re-mounts without a second import. */
|
|
60
|
+
const resolver = (
|
|
61
|
+
map: Record<string, RouteLoader>,
|
|
62
|
+
): ((key: string) => Promise<Route | undefined>) => {
|
|
63
|
+
const resolved = new Map<string, Route | undefined>()
|
|
64
|
+
return async (key) => {
|
|
65
|
+
if (resolved.has(key)) {
|
|
66
|
+
return resolved.get(key)
|
|
67
|
+
}
|
|
68
|
+
const loader = map[key]
|
|
69
|
+
const view = loader === undefined ? undefined : (await loader()).default
|
|
70
|
+
resolved.set(key, view)
|
|
71
|
+
return view
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const resolvePage = resolver(loaders)
|
|
75
|
+
const resolveLayout = resolver(layoutLoaders)
|
|
76
|
+
|
|
77
|
+
/* Tear down the page and every layout from `index` inward (innermost first), then
|
|
78
|
+
return the container the rebuild mounts into — the surviving layout's outlet, or
|
|
79
|
+
`host` when the whole chain goes. */
|
|
80
|
+
const disposeFrom = (index: number): Element => {
|
|
81
|
+
disposePage?.()
|
|
82
|
+
disposePage = undefined
|
|
83
|
+
for (let depth = mountedLayouts.length - 1; depth >= index; depth -= 1) {
|
|
84
|
+
mountedLayouts[depth]?.dispose()
|
|
85
|
+
}
|
|
86
|
+
const base = index === 0 ? host : (mountedLayouts[index - 1] as MountedLayout).outlet
|
|
87
|
+
mountedLayouts.length = index
|
|
88
|
+
return base
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Mount (or hydrate) the chain tail — layouts `[index..]` then the page — into
|
|
92
|
+
`base`, threading each layer into the previous one's outlet. Hydration brackets
|
|
93
|
+
ONE render pass across all layers so await/try block ids stay unique and aligned
|
|
94
|
+
with the SSR stream; a fresh mount needs no shared pass. */
|
|
95
|
+
const buildFrom = (
|
|
96
|
+
base: Element,
|
|
97
|
+
index: number,
|
|
98
|
+
chainKeys: string[],
|
|
99
|
+
layoutViews: Route[],
|
|
100
|
+
pageView: Route | undefined,
|
|
101
|
+
params: Record<string, string>,
|
|
102
|
+
hydrating: boolean,
|
|
103
|
+
): void => {
|
|
104
|
+
const run = (): void => {
|
|
105
|
+
let container = base
|
|
106
|
+
for (let depth = index; depth < layoutViews.length; depth += 1) {
|
|
107
|
+
const view = layoutViews[depth] as Route
|
|
108
|
+
const dispose = hydrating
|
|
109
|
+
? (view.hydrate as NonNullable<Route['hydrate']>)(container, params)
|
|
110
|
+
: (view(container, params) ?? noop)
|
|
111
|
+
const outlet = firstOutlet(container)
|
|
112
|
+
if (outlet === undefined) {
|
|
113
|
+
throw new Error('[abide] a layout.abide must contain a <slot/> outlet')
|
|
114
|
+
}
|
|
115
|
+
mountedLayouts.push({ key: chainKeys[depth] as string, dispose, outlet })
|
|
116
|
+
container = outlet
|
|
117
|
+
}
|
|
118
|
+
if (pageView === undefined) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
disposePage = hydrating
|
|
122
|
+
? (pageView.hydrate as NonNullable<Route['hydrate']>)(container, params)
|
|
123
|
+
: (pageView(container, params) ?? undefined)
|
|
124
|
+
}
|
|
125
|
+
if (hydrating) {
|
|
126
|
+
enterRenderPass()
|
|
127
|
+
try {
|
|
128
|
+
run()
|
|
129
|
+
} finally {
|
|
130
|
+
exitRenderPass()
|
|
131
|
+
}
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
run()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const onPopState = (): void => {
|
|
138
|
+
runtimePath.value = location.pathname + location.search + location.hash
|
|
139
|
+
}
|
|
140
|
+
const onClick = (event: MouseEvent): void => {
|
|
141
|
+
/* Let the browser own anything that isn't a plain primary-button click:
|
|
142
|
+
modified clicks (open in a new tab/window), middle/right buttons, and
|
|
143
|
+
already-handled events. */
|
|
144
|
+
if (
|
|
145
|
+
event.defaultPrevented ||
|
|
146
|
+
event.button !== 0 ||
|
|
147
|
+
event.metaKey ||
|
|
148
|
+
event.ctrlKey ||
|
|
149
|
+
event.shiftKey ||
|
|
150
|
+
event.altKey
|
|
151
|
+
) {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
const target = event.target as Element
|
|
155
|
+
const link = target.closest?.('a[href]') as HTMLAnchorElement | null
|
|
156
|
+
if (link === null) {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
/* Defer to the browser for links it should own: a new-tab target, a
|
|
160
|
+
download, or an explicitly external rel. */
|
|
161
|
+
if (
|
|
162
|
+
(link.target !== '' && link.target !== '_self') ||
|
|
163
|
+
link.hasAttribute('download') ||
|
|
164
|
+
link.getAttribute('rel') === 'external'
|
|
165
|
+
) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
const destination = new URL(link.href)
|
|
169
|
+
if (destination.origin !== location.origin) {
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
event.preventDefault()
|
|
173
|
+
/* Carry the query and hash, not just the pathname, so the destination page's
|
|
174
|
+
page.url sees them and an SPA navigation matches a fresh server load. */
|
|
175
|
+
navigate(destination.pathname + destination.search + destination.hash)
|
|
176
|
+
}
|
|
177
|
+
if (typeof window !== 'undefined') {
|
|
178
|
+
window.addEventListener('popstate', onPopState)
|
|
179
|
+
document.addEventListener('click', onClick as EventListener)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* First render adopts the server-rendered DOM (when the matching page is
|
|
183
|
+
hydratable); navigation after that re-mounts fresh. */
|
|
184
|
+
let first = true
|
|
185
|
+
/* Monotonic token: a newer navigation that resolves first wins, so a slow
|
|
186
|
+
chunk landing late never overwrites the page the user has since moved to. */
|
|
187
|
+
let sequence = 0
|
|
188
|
+
/* Latched on teardown so a navigation whose imports/probe were in flight at
|
|
189
|
+
dispose can't run its `.then` and rebuild a chain we just tore down — the
|
|
190
|
+
`token` guard only catches a *newer* navigation, not disposal. */
|
|
191
|
+
let disposed = false
|
|
192
|
+
const stop = effect(() => {
|
|
193
|
+
/* The route is the only dependency the router should re-run on. Everything
|
|
194
|
+
else runs untracked so the page's build-time reads (each interpolation
|
|
195
|
+
reads its value once before wrapping it in its own effect) bind to the
|
|
196
|
+
page's own effects, not this one — otherwise any in-page state change
|
|
197
|
+
would re-run the router and re-mount the page, dropping local state. */
|
|
198
|
+
const path = runtimePath.value
|
|
199
|
+
untrack(() => {
|
|
200
|
+
/* The route matches on the pathname only; the query/hash ride along for
|
|
201
|
+
the probe (so server gating sees them) and for clientPage.url. */
|
|
202
|
+
const pathname = path.split(/[?#]/)[0] ?? path
|
|
203
|
+
const matched = matchRoute(patterns, pathname)
|
|
204
|
+
const key = matched?.route ?? '*'
|
|
205
|
+
const params = matched?.params ?? {}
|
|
206
|
+
/* The layout chain resolves off the literal route key (so a layout at a
|
|
207
|
+
`[id]` directory matches the `[name]` pattern, not the concrete path). */
|
|
208
|
+
const chainRoute = matched?.route ?? pathname
|
|
209
|
+
const chainKeys = layoutChainForRoute(chainRoute, layoutKeys)
|
|
210
|
+
sequence += 1
|
|
211
|
+
const token = sequence
|
|
212
|
+
/* First paint adopts a document the server already ran handle() on;
|
|
213
|
+
only later navigations re-run it through the probe. */
|
|
214
|
+
const verdict: Promise<NavVerdict> =
|
|
215
|
+
first || probe === undefined ? Promise.resolve({ kind: 'mount' }) : probe(path)
|
|
216
|
+
/* Resolve the page chunk, its layout chunks, and the gate in parallel,
|
|
217
|
+
keeping the current chain mounted until all land — no blank frame while
|
|
218
|
+
imports are in flight or the probe is in the air. */
|
|
219
|
+
void Promise.all([
|
|
220
|
+
resolvePage(key),
|
|
221
|
+
Promise.all(chainKeys.map((layoutKey) => resolveLayout(layoutKey))),
|
|
222
|
+
verdict,
|
|
223
|
+
]).then(([pageView, resolvedLayouts, decision]) => {
|
|
224
|
+
if (token !== sequence || disposed) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
/* handle() redirected: go where it pointed, replacing the blocked
|
|
228
|
+
URL so back doesn't trap on it. The router re-probes the target. */
|
|
229
|
+
if (decision.kind === 'redirect') {
|
|
230
|
+
navigate(decision.path, true)
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
/* handle() blocked it / redirected off-origin / the probe failed:
|
|
234
|
+
let the browser load the server's real response. */
|
|
235
|
+
if (decision.kind === 'reload') {
|
|
236
|
+
if (typeof location !== 'undefined') {
|
|
237
|
+
location.href = decision.url
|
|
238
|
+
}
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
/* Publish the active page so the `page` proxy resolves route/params/url. */
|
|
242
|
+
clientPage.value = {
|
|
243
|
+
route: matched?.route ?? pathname,
|
|
244
|
+
params,
|
|
245
|
+
url:
|
|
246
|
+
typeof location === 'undefined'
|
|
247
|
+
? new URL(`http://localhost${path}`)
|
|
248
|
+
: new URL(location.href),
|
|
249
|
+
navigating: false,
|
|
250
|
+
}
|
|
251
|
+
const layoutViews = resolvedLayouts.filter(
|
|
252
|
+
(view): view is Route => view !== undefined,
|
|
253
|
+
)
|
|
254
|
+
/* The shared prefix of layouts (same route key at the same depth) stays
|
|
255
|
+
mounted; the first divergence and everything inward is rebuilt. */
|
|
256
|
+
let divergence = 0
|
|
257
|
+
while (
|
|
258
|
+
divergence < mountedLayouts.length &&
|
|
259
|
+
divergence < chainKeys.length &&
|
|
260
|
+
mountedLayouts[divergence]?.key === chainKeys[divergence]
|
|
261
|
+
) {
|
|
262
|
+
divergence += 1
|
|
263
|
+
}
|
|
264
|
+
const hydrating =
|
|
265
|
+
first && pageView?.hydratable === true && pageView.hydrate !== undefined
|
|
266
|
+
first = false
|
|
267
|
+
const base = disposeFrom(divergence)
|
|
268
|
+
/* A fresh mount clears the torn-down DOM; hydration adopts it in place. */
|
|
269
|
+
if (!hydrating) {
|
|
270
|
+
base.textContent = ''
|
|
271
|
+
}
|
|
272
|
+
buildFrom(base, divergence, chainKeys, layoutViews, pageView, params, hydrating)
|
|
273
|
+
})
|
|
274
|
+
})
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
return () => {
|
|
278
|
+
disposed = true
|
|
279
|
+
if (typeof window !== 'undefined') {
|
|
280
|
+
window.removeEventListener('popstate', onPopState)
|
|
281
|
+
document.removeEventListener('click', onClick as EventListener)
|
|
282
|
+
}
|
|
283
|
+
stop()
|
|
284
|
+
disposeFrom(0)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/* The element a layout's `<slot/>` lowers to: an empty structural container the
|
|
2
|
+
router fills with the next layer of the route's layout chain (the nested layout
|
|
3
|
+
or the page). SSR emits it empty so the renderer can fold the child's html into
|
|
4
|
+
it; on the client the router mounts/hydrates the child into it and finds it by
|
|
5
|
+
tag. Shared by both compiler back-ends, the SSR chain composer, and the router so
|
|
6
|
+
they all agree on the marker. */
|
|
7
|
+
// @readme plumbing
|
|
8
|
+
export const OUTLET_TAG = 'abide-outlet'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The ownership scope currently collecting teardown callbacks. While a component
|
|
3
|
+
(or a list row) builds, `scope()` points this at an array; every effect and event
|
|
4
|
+
listener created during the build pushes its disposer here, so the whole instance
|
|
5
|
+
tears down together. undefined outside any build — effects in plain code own
|
|
6
|
+
their own lifecycle via the disposer they return.
|
|
7
|
+
*/
|
|
8
|
+
export const OWNER: { current: Array<() => void> | undefined } = { current: undefined }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactiveNode } from './types/ReactiveNode.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Mutable singletons shared by every reactive primitive: the observer currently
|
|
5
|
+
running (so a read can register itself as that observer's dependency), the set
|
|
6
|
+
of effects dirtied since the last flush, and the batch depth (writes inside a
|
|
7
|
+
batch queue effects and flush once on exit). Held on one object so signal,
|
|
8
|
+
computed, and effect all reference the same graph state without a barrel.
|
|
9
|
+
*/
|
|
10
|
+
export const REACTIVE_CONTEXT: {
|
|
11
|
+
observer: ReactiveNode | undefined
|
|
12
|
+
pendingEffects: Set<ReactiveNode>
|
|
13
|
+
batchDepth: number
|
|
14
|
+
} = { observer: undefined, pendingEffects: new Set(), batchDepth: 0 }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Render mode shared by the dom helpers. In the default (create) mode `hydration`
|
|
3
|
+
is undefined and helpers build fresh nodes. During `hydrate` it holds, per parent,
|
|
4
|
+
the next server-rendered child node to claim — a node pointer (not an index) so it
|
|
5
|
+
survives nodes a block inserts (anchors) mid-hydration. Helpers claim in build
|
|
6
|
+
order, which matches the SSR order, advancing the pointer to the next sibling.
|
|
7
|
+
|
|
8
|
+
`blockId`/`depth` drive the render-pass block-id counter: every `await`/`try` block
|
|
9
|
+
draws an id from `blockId` in document order, shared across a component and the
|
|
10
|
+
child components it inlines, so ids are globally unique within one render pass (the
|
|
11
|
+
SSR stream and client hydration agree on them — `RESUME` is keyed by id). `depth`
|
|
12
|
+
tracks nesting so the OUTERMOST render/mount resets the counter and a child render/
|
|
13
|
+
mount continues it. See `enterRenderPass`/`nextBlockId`.
|
|
14
|
+
*/
|
|
15
|
+
export const RENDER: {
|
|
16
|
+
hydration: { next: Map<Node, Node | null> } | undefined
|
|
17
|
+
blockId: number
|
|
18
|
+
depth: number
|
|
19
|
+
} = {
|
|
20
|
+
hydration: undefined,
|
|
21
|
+
blockId: 0,
|
|
22
|
+
depth: 0,
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* The await-resume manifest: the resolved value (or error) of each streamed
|
|
2
|
+
`await` block, keyed by its boundary id. The SSR stream serializes each value
|
|
3
|
+
alongside its fragment; the client registers it (via `applyResolved` or the
|
|
4
|
+
inline swap script), and hydration reads it so an `await` block adopts the
|
|
5
|
+
resolved branch with the real value instead of re-running the promise.
|
|
6
|
+
|
|
7
|
+
Backed by `globalThis.__abideResume` so the inline stream-swap script (vanilla,
|
|
8
|
+
running during the stream before the bundle loads) and the framework share one
|
|
9
|
+
store: whoever runs first creates it, the other adopts the same reference. */
|
|
10
|
+
export type ResumeEntry = { ok: true; value: unknown } | { ok: false; error: unknown }
|
|
11
|
+
|
|
12
|
+
const globalScope = globalThis as { __abideResume?: Record<number, ResumeEntry> }
|
|
13
|
+
globalScope.__abideResume ??= {}
|
|
14
|
+
|
|
15
|
+
// @readme plumbing
|
|
16
|
+
export const RESUME: Record<number, ResumeEntry> = globalScope.__abideResume
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Patch } from './types/Patch.ts'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Applies `patch` to `tree` by mutating in place and returns the root (a fresh
|
|
5
|
+
value only when the root itself is replaced). In-place is the deliberate choice:
|
|
6
|
+
the patch path already names exactly what changed, so we never need copy-on-write
|
|
7
|
+
to *detect* a change — and cloning the spine made every leaf write O(width) of
|
|
8
|
+
the widest ancestor (a 5k-item list copied 50k times in the bench). Mutating is
|
|
9
|
+
O(depth). History/undo is served by journalling patches (the change is a value),
|
|
10
|
+
not by retaining old roots — which is cheaper anyway. The wake step in createDoc
|
|
11
|
+
force-notifies ancestor readers from the path, since their container keeps its
|
|
12
|
+
identity now.
|
|
13
|
+
|
|
14
|
+
`segments` is the patch path pre-split by the caller (createDoc also needs it),
|
|
15
|
+
threaded in so the path is split once per patch rather than here and there.
|
|
16
|
+
*/
|
|
17
|
+
export function applyPatchToTree(tree: unknown, patch: Patch, segments: string[]): unknown {
|
|
18
|
+
/* Replacing the root can't mutate in place — hand back the new value. */
|
|
19
|
+
if (segments.length === 0) {
|
|
20
|
+
return patch.op === 'remove' ? undefined : patch.value
|
|
21
|
+
}
|
|
22
|
+
let parent = tree as Record<string, unknown>
|
|
23
|
+
for (const segment of segments.slice(0, -1)) {
|
|
24
|
+
parent = parent[segment] as Record<string, unknown>
|
|
25
|
+
}
|
|
26
|
+
const key = segments[segments.length - 1] as string
|
|
27
|
+
if (patch.op === 'replace') {
|
|
28
|
+
parent[key] = patch.value
|
|
29
|
+
} else if (patch.op === 'add') {
|
|
30
|
+
if (Array.isArray(parent)) {
|
|
31
|
+
parent.splice(key === '-' ? parent.length : Number(key), 0, patch.value)
|
|
32
|
+
} else {
|
|
33
|
+
parent[key] = patch.value
|
|
34
|
+
}
|
|
35
|
+
} else if (Array.isArray(parent)) {
|
|
36
|
+
parent.splice(Number(key), 1)
|
|
37
|
+
} else {
|
|
38
|
+
delete parent[key]
|
|
39
|
+
}
|
|
40
|
+
return tree
|
|
41
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RENDER } from './RENDER.ts'
|
|
2
|
+
|
|
3
|
+
/* The next server-rendered node to claim under `parent` during hydration,
|
|
4
|
+
defaulting to its first child when the pointer hasn't been set yet. */
|
|
5
|
+
export function claimChild(
|
|
6
|
+
hydration: NonNullable<(typeof RENDER)['hydration']>,
|
|
7
|
+
parent: Node,
|
|
8
|
+
): Node | null {
|
|
9
|
+
return hydration.next.has(parent) ? (hydration.next.get(parent) ?? null) : parent.firstChild
|
|
10
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PageSnapshot } from '../../shared/types/PageSnapshot.ts'
|
|
2
|
+
import { state } from '../state.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
The client-side page snapshot the `page` proxy reads (startClient registers
|
|
6
|
+
`() => clientPage.value` as the page resolver). It's a abide-ui signal, so a
|
|
7
|
+
component reading page.url/params/route inside an effect re-runs when the router
|
|
8
|
+
updates it on navigation. Server renders never touch this — there the resolver
|
|
9
|
+
reads the per-request store instead.
|
|
10
|
+
*/
|
|
11
|
+
export const clientPage = state<PageSnapshot>({
|
|
12
|
+
route: '',
|
|
13
|
+
params: {},
|
|
14
|
+
url: typeof location === 'undefined' ? new URL('http://localhost/') : new URL(location.href),
|
|
15
|
+
navigating: false,
|
|
16
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactiveNode } from './types/ReactiveNode.ts'
|
|
2
|
+
|
|
3
|
+
/* Creates a lazy derived node. Born dirty so its first read computes; thereafter
|
|
4
|
+
it recomputes only when a dependency triggers it. */
|
|
5
|
+
export function createComputedNode(compute: () => unknown): ReactiveNode {
|
|
6
|
+
return {
|
|
7
|
+
value: undefined,
|
|
8
|
+
compute,
|
|
9
|
+
depsHead: undefined,
|
|
10
|
+
depsTail: undefined,
|
|
11
|
+
subsHead: undefined,
|
|
12
|
+
subsTail: undefined,
|
|
13
|
+
dirty: true,
|
|
14
|
+
isEffect: false,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { applyPatchToTree } from './applyPatchToTree.ts'
|
|
2
|
+
import { createSignalNode } from './createSignalNode.ts'
|
|
3
|
+
import { flushEffects } from './flushEffects.ts'
|
|
4
|
+
import { pathExists } from './pathExists.ts'
|
|
5
|
+
import { REACTIVE_CONTEXT } from './REACTIVE_CONTEXT.ts'
|
|
6
|
+
import { readNode } from './readNode.ts'
|
|
7
|
+
import { trigger } from './trigger.ts'
|
|
8
|
+
import type { Cell } from './types/Cell.ts'
|
|
9
|
+
import type { Doc } from './types/Doc.ts'
|
|
10
|
+
import type { Patch } from './types/Patch.ts'
|
|
11
|
+
import type { ReactiveNode } from './types/ReactiveNode.ts'
|
|
12
|
+
import { valueAtPath } from './valueAtPath.ts'
|
|
13
|
+
import { writeNode } from './writeNode.ts'
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
Builds a reactive document over `initial`. Each path read for the first time
|
|
17
|
+
mints a signal node; the node is the notification token, the (mutable) tree is
|
|
18
|
+
the source of truth. A patch mutates the tree in place — O(depth), not O(width) —
|
|
19
|
+
then wakes from the patch's **change root** with shape-only reactivity:
|
|
20
|
+
|
|
21
|
+
- a value change at a path (an object key replaced with a new value) wakes only
|
|
22
|
+
that path and, if it became a container, its descendants — NOT its ancestors.
|
|
23
|
+
Reading a container subscribes to its own shape, so a deep field change never
|
|
24
|
+
re-runs a reader of the array/object above it (the reason a single list-item
|
|
25
|
+
edit doesn't reconcile the whole `each`).
|
|
26
|
+
- a structural change (add/remove anywhere, or replacing an array element by
|
|
27
|
+
index) changes the *parent container's* shape, so the parent is the change
|
|
28
|
+
root: it is force-notified (its identity is unchanged after in-place mutation)
|
|
29
|
+
and its descendants are re-read to pick up index shifts.
|
|
30
|
+
|
|
31
|
+
Reads of exactly the changed path are gated by `Object.is`, so an equal write
|
|
32
|
+
wakes nobody. Change is addressed, not diffed, and the address is as shallow as
|
|
33
|
+
the change.
|
|
34
|
+
*/
|
|
35
|
+
export function createDoc(initial: unknown): Doc {
|
|
36
|
+
let tree = initial
|
|
37
|
+
const nodes = new Map<string, ReactiveNode>()
|
|
38
|
+
|
|
39
|
+
function nodeFor(path: string): ReactiveNode {
|
|
40
|
+
let node = nodes.get(path)
|
|
41
|
+
if (node === undefined) {
|
|
42
|
+
node = createSignalNode(valueAtPath(tree, path))
|
|
43
|
+
nodes.set(path, node)
|
|
44
|
+
}
|
|
45
|
+
return node
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function read<T>(path: string): T {
|
|
49
|
+
return readNode(nodeFor(path)) as T
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
Wakes readers from `rootPath`. `force` notifies the root unconditionally (a
|
|
54
|
+
structural change keeps the container's identity, so there is no new value to
|
|
55
|
+
compare); otherwise the root is written through the `Object.is` gate. When
|
|
56
|
+
`descend`, a container root also re-reads its existing descendant nodes (gated)
|
|
57
|
+
to catch nested and index-shifted values. `descend` is skipped for a change
|
|
58
|
+
that leaves every existing descendant path addressing the same value (an
|
|
59
|
+
end-append or an object-key add), so those updates stay O(depth) instead of
|
|
60
|
+
paying a scan over every minted node.
|
|
61
|
+
*/
|
|
62
|
+
function wakeSubtree(rootPath: string, force: boolean, descend: boolean): void {
|
|
63
|
+
const rootValue = valueAtPath(tree, rootPath)
|
|
64
|
+
const rootNode = nodes.get(rootPath)
|
|
65
|
+
if (rootNode !== undefined) {
|
|
66
|
+
if (force) {
|
|
67
|
+
trigger(rootNode)
|
|
68
|
+
} else {
|
|
69
|
+
writeNode(rootNode, rootValue)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!descend || rootValue === null || typeof rootValue !== 'object') {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
const prefix = rootPath === '' ? '' : `${rootPath}/`
|
|
76
|
+
for (const [candidate, node] of nodes) {
|
|
77
|
+
if (candidate !== rootPath && candidate.startsWith(prefix)) {
|
|
78
|
+
/* A descendant whose path the mutation removed — a deleted key, an
|
|
79
|
+
out-of-range index after a shrink — is woken to undefined, then
|
|
80
|
+
dropped from the registry. Without eviction `nodes` grows for the
|
|
81
|
+
life of the session over churning keys (items/<uuid>, message ids),
|
|
82
|
+
and this very descend scan degrades linearly with it. The woken
|
|
83
|
+
reader re-mints a fresh node on its flush if the path ever returns.
|
|
84
|
+
Deleting the current entry mid-iteration is safe on a Map. */
|
|
85
|
+
if (pathExists(tree, candidate)) {
|
|
86
|
+
writeNode(node, valueAtPath(tree, candidate))
|
|
87
|
+
} else {
|
|
88
|
+
writeNode(node, undefined)
|
|
89
|
+
nodes.delete(candidate)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function apply(patch: Patch): void {
|
|
96
|
+
const segments = patch.path === '' ? [] : patch.path.split('/')
|
|
97
|
+
tree = applyPatchToTree(tree, patch, segments)
|
|
98
|
+
const parentPath = segments.slice(0, -1).join('/')
|
|
99
|
+
const parentValue = valueAtPath(tree, parentPath)
|
|
100
|
+
const leafKey = segments[segments.length - 1] as string | undefined
|
|
101
|
+
/* A structural change (add/remove, or an array element replaced by index)
|
|
102
|
+
reshapes the parent; a plain value replace reshapes only its own path. */
|
|
103
|
+
const parentIsArray = Array.isArray(parentValue)
|
|
104
|
+
const structural = patch.op !== 'replace' || parentIsArray
|
|
105
|
+
const arrayLength = parentIsArray ? (parentValue as unknown[]).length : 0
|
|
106
|
+
/* An add that introduces a new path without shifting any existing sibling —
|
|
107
|
+
an object-key add or an array append at the end — changes only the added
|
|
108
|
+
slot's subtree plus (for an array) its `length` node, never the existing
|
|
109
|
+
element nodes. Waking exactly those two avoids re-reading every descendant
|
|
110
|
+
of a large container. Every other structural change (any remove, a
|
|
111
|
+
mid-array insert, an array element replace) shifts indices or replaces a
|
|
112
|
+
subtree, so all descendants must be re-read. */
|
|
113
|
+
const nonShiftingAdd =
|
|
114
|
+
patch.op === 'add' &&
|
|
115
|
+
(!parentIsArray || leafKey === '-' || Number(leafKey) === arrayLength - 1)
|
|
116
|
+
REACTIVE_CONTEXT.batchDepth += 1
|
|
117
|
+
try {
|
|
118
|
+
if (segments.length === 0) {
|
|
119
|
+
wakeSubtree('', true, true)
|
|
120
|
+
} else if (!structural) {
|
|
121
|
+
wakeSubtree(patch.path, false, true)
|
|
122
|
+
} else if (nonShiftingAdd) {
|
|
123
|
+
wakeSubtree(parentPath, true, false)
|
|
124
|
+
/* The appended slot resolves to its real index when keyed by `-`. */
|
|
125
|
+
const addedPath =
|
|
126
|
+
parentIsArray && leafKey === '-'
|
|
127
|
+
? `${parentPath}/${arrayLength - 1}`
|
|
128
|
+
: patch.path
|
|
129
|
+
wakeSubtree(addedPath, false, true)
|
|
130
|
+
const lengthNode = parentIsArray ? nodes.get(`${parentPath}/length`) : undefined
|
|
131
|
+
if (lengthNode !== undefined) {
|
|
132
|
+
writeNode(lengthNode, arrayLength)
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
wakeSubtree(parentPath, true, true)
|
|
136
|
+
}
|
|
137
|
+
} finally {
|
|
138
|
+
REACTIVE_CONTEXT.batchDepth -= 1
|
|
139
|
+
}
|
|
140
|
+
flushEffects()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/*
|
|
144
|
+
A stable accessor bound to one scalar leaf — what the compiler emits so a hot
|
|
145
|
+
loop has zero string work: the node, the parent container, and the leaf key
|
|
146
|
+
are resolved once. `set` mutates the parent directly and writes the node
|
|
147
|
+
through the gate; no ancestor walk, because a scalar leaf change is shape-only
|
|
148
|
+
(it never reshapes its container). Valid while ancestors keep their identity
|
|
149
|
+
(the scalar-field case); a wholesale ancestor replace re-binds the cell.
|
|
150
|
+
*/
|
|
151
|
+
function cell<T>(path: string): Cell<T> {
|
|
152
|
+
const node = nodeFor(path)
|
|
153
|
+
const segments = path.split('/')
|
|
154
|
+
const leafKey = segments[segments.length - 1] as string
|
|
155
|
+
let parent = tree as Record<string, unknown>
|
|
156
|
+
for (const segment of segments.slice(0, -1)) {
|
|
157
|
+
parent = parent[segment] as Record<string, unknown>
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
get: () => readNode(node) as T,
|
|
161
|
+
set: (value: T) => {
|
|
162
|
+
parent[leafKey] = value
|
|
163
|
+
writeNode(node, value)
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
read,
|
|
170
|
+
cell,
|
|
171
|
+
apply,
|
|
172
|
+
replace: (path, value) => apply({ op: 'replace', path, value }),
|
|
173
|
+
add: (path, value) => apply({ op: 'add', path, value }),
|
|
174
|
+
remove: (path) => apply({ op: 'remove', path }),
|
|
175
|
+
snapshot: () => tree,
|
|
176
|
+
}
|
|
177
|
+
}
|