@abide/abide 0.48.0 → 0.50.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/AGENTS.md +85 -59
- package/CHANGELOG.md +216 -0
- package/README.md +21 -10
- package/package.json +15 -4
- package/src/abideLsp.ts +5 -6
- package/src/abideResolverPlugin.ts +197 -169
- package/src/build.ts +79 -56
- package/src/buildCli.ts +3 -5
- package/src/buildDisconnected.ts +2 -5
- package/src/bundleApp.ts +2 -3
- package/src/checkAbide.ts +12 -2
- package/src/compile.ts +2 -4
- package/src/devEntry.ts +113 -28
- package/src/discoveryEntry.ts +3 -2
- package/src/lib/bundle/disconnected.abide +69 -73
- package/src/lib/bundle/infoPlist.ts +13 -7
- package/src/lib/bundle/installDownloads.ts +13 -3
- package/src/lib/bundle/installMacMenu.ts +13 -3
- package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
- package/src/lib/bundle/waitForServer.ts +6 -1
- package/src/lib/cli/parseArgvForRpc.ts +36 -9
- package/src/lib/cli/tokenizeArgvFlags.ts +4 -2
- package/src/lib/mcp/createMcpResourceServer.ts +13 -2
- package/src/lib/mcp/mcpTools.ts +15 -6
- package/src/lib/mcp/toolResultFromResponse.ts +40 -12
- package/src/lib/server/error.ts +6 -6
- package/src/lib/server/json.ts +17 -3
- package/src/lib/server/rpc/defineRpc.ts +58 -24
- package/src/lib/server/rpc/parseArgs.ts +113 -14
- package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
- package/src/lib/server/rpc/runWithRpcTimeout.ts +12 -1
- package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
- package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
- package/src/lib/server/rpc/validationError.ts +1 -1
- package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
- package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
- package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
- package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
- package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
- package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
- package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
- package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
- package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
- package/src/lib/server/runtime/createServer.ts +120 -324
- package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
- package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
- package/src/lib/server/runtime/finalizeResponse.ts +11 -1
- package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
- package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
- package/src/lib/server/runtime/pathStore.ts +15 -0
- package/src/lib/server/runtime/registryManifests.ts +1 -1
- package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +24 -16
- package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
- package/src/lib/server/runtime/streamFromIterator.ts +30 -2
- package/src/lib/server/runtime/textResponse.ts +23 -0
- package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
- package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
- package/src/lib/server/runtime/types/RequestStore.ts +27 -0
- package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
- package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
- package/src/lib/server/sockets/defineSocket.ts +5 -4
- package/src/lib/server/sse.ts +5 -1
- package/src/lib/shared/ASYNC_CELL.ts +5 -0
- package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
- package/src/lib/shared/HTTP_METHODS.ts +6 -0
- package/src/lib/shared/HttpError.ts +1 -5
- package/src/lib/shared/MCP_PATH.ts +6 -0
- package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
- package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
- package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
- package/src/lib/shared/activeCacheStore.ts +1 -0
- package/src/lib/shared/activePage.ts +1 -0
- package/src/lib/shared/buildArtifact.ts +20 -0
- package/src/lib/shared/buildRpcRequest.ts +6 -2
- package/src/lib/shared/buildSocketOverChannel.ts +4 -3
- package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
- package/src/lib/shared/cache.ts +156 -131
- package/src/lib/shared/canonicalJson.ts +24 -1
- package/src/lib/shared/changeAffectsClient.ts +12 -7
- package/src/lib/shared/createCacheStore.ts +31 -8
- package/src/lib/shared/createChannelLog.ts +24 -0
- package/src/lib/shared/createLifecycleChannel.ts +7 -1
- package/src/lib/shared/createReachable.ts +47 -78
- package/src/lib/shared/createRemoteFunction.ts +52 -30
- package/src/lib/shared/createRpcServerProgram.ts +820 -0
- package/src/lib/shared/decodeRefJson.ts +4 -4
- package/src/lib/shared/decodeResponse.ts +2 -2
- package/src/lib/shared/decodeWireBody.ts +35 -0
- package/src/lib/shared/detectRpcMethod.ts +8 -1
- package/src/lib/shared/done.ts +8 -2
- package/src/lib/shared/encodeRefJson.ts +4 -4
- package/src/lib/shared/encodeWireBody.ts +18 -0
- package/src/lib/shared/exitOnBuildFailure.ts +4 -3
- package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
- package/src/lib/shared/generateDeclarations.ts +72 -0
- package/src/lib/shared/hasSeedableRequest.ts +25 -0
- package/src/lib/shared/hydrationWindow.ts +53 -0
- package/src/lib/shared/isAsyncCell.ts +11 -0
- package/src/lib/shared/isAsyncIterable.ts +8 -0
- package/src/lib/shared/isSubscribable.ts +3 -3
- package/src/lib/shared/isThenable.ts +9 -0
- package/src/lib/shared/jsonSchemaForType.ts +258 -0
- package/src/lib/shared/lenientDecode.ts +15 -0
- package/src/lib/shared/loadProjectTsConfig.ts +28 -0
- package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
- package/src/lib/shared/matchRoute.ts +4 -14
- package/src/lib/shared/parseEnv.ts +17 -6
- package/src/lib/shared/peek.ts +14 -0
- package/src/lib/shared/pending.ts +9 -6
- package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
- package/src/lib/shared/prepareRpcModule.ts +91 -93
- package/src/lib/shared/prepareSocketModule.ts +3 -2
- package/src/lib/shared/probeRegistries.ts +5 -18
- package/src/lib/shared/reachable.ts +7 -10
- package/src/lib/shared/refresh.ts +12 -2
- package/src/lib/shared/refreshing.ts +8 -2
- package/src/lib/shared/resolvedCellsSlot.ts +13 -0
- package/src/lib/shared/reviveWireField.ts +49 -0
- package/src/lib/shared/reviveWireOutput.ts +36 -0
- package/src/lib/shared/rpcServerForRoot.ts +25 -0
- package/src/lib/shared/scanPages.ts +25 -0
- package/src/lib/shared/serializeEnv.ts +18 -6
- package/src/lib/shared/snapshotShippable.ts +8 -7
- package/src/lib/shared/snippet.ts +11 -6
- package/src/lib/shared/streamedCellsSlot.ts +14 -0
- package/src/lib/shared/subscribableFromResponse.ts +4 -4
- package/src/lib/shared/subscribableProbes.ts +1 -1
- package/src/lib/shared/tailProbeSlot.ts +1 -1
- package/src/lib/shared/types/AsyncComputed.ts +20 -0
- package/src/lib/shared/types/AsyncState.ts +13 -0
- package/src/lib/shared/types/CacheEntry.ts +7 -7
- package/src/lib/shared/types/CacheOptions.ts +23 -37
- package/src/lib/shared/types/CachePolicy.ts +25 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
- package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
- package/src/lib/shared/types/HttpMethod.ts +3 -1
- package/src/lib/shared/types/InputCoercion.ts +17 -0
- package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
- package/src/lib/shared/types/OutputWirePlan.ts +17 -0
- package/src/lib/shared/types/PagesScan.ts +7 -0
- package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
- package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
- package/src/lib/shared/types/RemoteCallable.ts +8 -7
- package/src/lib/shared/types/RemoteFunction.ts +16 -15
- package/src/lib/shared/types/ResolvedCells.ts +11 -0
- package/src/lib/shared/types/ReturnBody.ts +15 -0
- package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
- package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
- package/src/lib/shared/types/Socket.ts +4 -4
- package/src/lib/shared/types/SsrPayload.ts +5 -1
- package/src/lib/shared/types/StreamPolicy.ts +11 -0
- package/src/lib/shared/types/StreamedCells.ts +19 -0
- package/src/lib/shared/types/StreamedResolution.ts +24 -6
- package/src/lib/shared/types/TailHooks.ts +1 -1
- package/src/lib/shared/types/WireKind.ts +11 -0
- package/src/lib/shared/url.ts +5 -0
- package/src/lib/shared/validationHttpError.ts +33 -0
- package/src/lib/shared/warmSeedKey.ts +16 -0
- package/src/lib/shared/wireJsonReplacer.ts +30 -0
- package/src/lib/shared/writeRpcDts.ts +11 -1
- package/src/lib/shared/writeTestSocketsDts.ts +1 -1
- package/src/lib/test/createTestApp.ts +19 -1
- package/src/lib/ui/README.md +1 -1
- package/src/lib/ui/activePendingCells.ts +14 -0
- package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
- package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -7
- package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
- package/src/lib/ui/compile/analyzeComponent.ts +89 -6
- package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
- package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
- package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
- package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
- package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
- package/src/lib/ui/compile/bindListenEvent.ts +5 -6
- package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
- package/src/lib/ui/compile/catchBinding.ts +7 -5
- package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
- package/src/lib/ui/compile/compileComponent.ts +19 -3
- package/src/lib/ui/compile/compileModule.ts +27 -58
- package/src/lib/ui/compile/compileSSR.ts +52 -13
- package/src/lib/ui/compile/compileShadow.ts +265 -42
- package/src/lib/ui/compile/composeProps.ts +15 -4
- package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
- package/src/lib/ui/compile/createShadowProgram.ts +35 -6
- package/src/lib/ui/compile/declaredNames.ts +36 -0
- package/src/lib/ui/compile/desugarSignals.ts +419 -37
- package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
- package/src/lib/ui/compile/generateBuild.ts +74 -17
- package/src/lib/ui/compile/generateSSR.ts +286 -75
- package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
- package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
- package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
- package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
- package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
- package/src/lib/ui/compile/isWhitespaceText.ts +10 -2
- package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
- package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
- package/src/lib/ui/compile/lowerContext.ts +10 -0
- package/src/lib/ui/compile/lowerDocAccess.ts +51 -10
- package/src/lib/ui/compile/lowerScript.ts +40 -4
- package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
- package/src/lib/ui/compile/parseTemplate.ts +18 -1090
- package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
- package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +26 -40
- package/src/lib/ui/compile/resolveReactiveExport.ts +4 -7
- package/src/lib/ui/compile/scopeCss.ts +27 -4
- package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
- package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
- package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
- package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
- package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
- package/src/lib/ui/compile/templateStartOffset.ts +15 -0
- package/src/lib/ui/compile/tryPlan.ts +4 -3
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
- package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
- package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
- package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
- package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
- package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
- package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
- package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
- package/src/lib/ui/computed.ts +28 -1
- package/src/lib/ui/createScope.ts +35 -68
- package/src/lib/ui/dom/anchoredBranch.ts +142 -0
- package/src/lib/ui/dom/appendText.ts +8 -3
- package/src/lib/ui/dom/awaitBlock.ts +62 -75
- package/src/lib/ui/dom/bindProp.ts +22 -0
- package/src/lib/ui/dom/bindableProp.ts +47 -0
- package/src/lib/ui/dom/cellPending.ts +24 -0
- package/src/lib/ui/dom/discardBoundary.ts +24 -6
- package/src/lib/ui/dom/disposeRange.ts +2 -1
- package/src/lib/ui/dom/each.ts +41 -12
- package/src/lib/ui/dom/eachAsync.ts +39 -3
- package/src/lib/ui/dom/fillBefore.ts +6 -7
- package/src/lib/ui/dom/fillBoundary.ts +2 -3
- package/src/lib/ui/dom/fillRange.ts +4 -4
- package/src/lib/ui/dom/hydrate.ts +6 -12
- package/src/lib/ui/dom/isComment.ts +1 -1
- package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
- package/src/lib/ui/dom/mergeProps.ts +1 -1
- package/src/lib/ui/dom/mount.ts +1 -2
- package/src/lib/ui/dom/mountChild.ts +9 -40
- package/src/lib/ui/dom/mountRange.ts +2 -3
- package/src/lib/ui/dom/mountSlot.ts +1 -1
- package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
- package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
- package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
- package/src/lib/ui/dom/on.ts +2 -2
- package/src/lib/ui/dom/readCell.ts +40 -0
- package/src/lib/ui/dom/restProps.ts +2 -2
- package/src/lib/ui/dom/spreadProps.ts +3 -4
- package/src/lib/ui/dom/switchBlock.ts +30 -7
- package/src/lib/ui/dom/tryBlock.ts +233 -70
- package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
- package/src/lib/ui/dom/when.ts +12 -2
- package/src/lib/ui/dom/withScope.ts +9 -2
- package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
- package/src/lib/ui/flight.ts +56 -0
- package/src/lib/ui/html.ts +12 -1
- package/src/lib/ui/isolateCellBarrier.ts +17 -0
- package/src/lib/ui/linked.ts +48 -2
- package/src/lib/ui/props.ts +17 -0
- package/src/lib/ui/remoteProxy.ts +84 -159
- package/src/lib/ui/renderChain.ts +53 -13
- package/src/lib/ui/renderToStream.ts +22 -18
- package/src/lib/ui/resumeSeedScript.ts +1 -1
- package/src/lib/ui/router.ts +86 -53
- package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
- package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
- package/src/lib/ui/runtime/CHILD_PRESENT.ts +2 -2
- package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
- package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
- package/src/lib/ui/runtime/RENDER.ts +10 -7
- package/src/lib/ui/runtime/RESUME.ts +4 -4
- package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
- package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
- package/src/lib/ui/runtime/applyPatchToTree.ts +17 -4
- package/src/lib/ui/runtime/blockId.ts +31 -0
- package/src/lib/ui/runtime/boundaryFor.ts +11 -0
- package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
- package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
- package/src/lib/ui/runtime/createDoc.ts +31 -54
- package/src/lib/ui/runtime/createEffectNode.ts +9 -0
- package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
- package/src/lib/ui/runtime/flushEffects.ts +33 -1
- package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
- package/src/lib/ui/runtime/nextBlockId.ts +8 -7
- package/src/lib/ui/runtime/renderPath.ts +16 -0
- package/src/lib/ui/runtime/scope.ts +11 -0
- package/src/lib/ui/runtime/toTeardown.ts +10 -3
- package/src/lib/ui/runtime/types/Boundary.ts +9 -0
- package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
- package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
- package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
- package/src/lib/ui/runtime/types/UiProps.ts +6 -5
- package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
- package/src/lib/ui/runtime/withPath.ts +16 -0
- package/src/lib/ui/runtime/withPathFrom.ts +20 -0
- package/src/lib/ui/runtime/withoutHydration.ts +22 -0
- package/src/lib/ui/seedStreamedResolution.ts +31 -6
- package/src/lib/ui/settleAsyncCells.ts +24 -0
- package/src/lib/ui/socketProxy.ts +1 -1
- package/src/lib/ui/startClient.ts +16 -31
- package/src/lib/ui/state.ts +9 -3
- package/src/lib/ui/trackedComputed.ts +68 -0
- package/src/lib/ui/types/Scope.ts +8 -24
- package/src/lib/ui/watch.ts +3 -3
- package/src/serverEntry.ts +14 -0
- package/template/src/server/rpc/getHello.ts +15 -13
- package/template/test/app.test.ts +1 -1
- package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
- package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
- package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
- package/src/lib/shared/hasReplayableRequest.ts +0 -17
- package/src/lib/shared/hydratingSlot.ts +0 -12
- package/src/lib/shared/outboxProbeSlot.ts +0 -20
- package/src/lib/shared/types/Outbox.ts +0 -9
- package/src/lib/shared/types/OutboxEntry.ts +0 -27
- package/src/lib/shared/types/SmartReadOptions.ts +0 -36
- package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
- package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
- package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
- package/src/lib/ui/compile/markupTokens.ts +0 -313
- package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
- package/src/lib/ui/dom/applyResolved.ts +0 -87
- package/src/lib/ui/dom/scopeLabel.ts +0 -21
- package/src/lib/ui/dom/text.ts +0 -20
- package/src/lib/ui/history.ts +0 -101
- package/src/lib/ui/installHotBridge.ts +0 -93
- package/src/lib/ui/installInspectorBridge.ts +0 -140
- package/src/lib/ui/outbox.ts +0 -35
- package/src/lib/ui/persist.ts +0 -115
- package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
- package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
- package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
- package/src/lib/ui/runtime/captureModelDoc.ts +0 -28
- package/src/lib/ui/runtime/hotInstances.ts +0 -10
- package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
- package/src/lib/ui/runtime/hotReplace.ts +0 -38
- package/src/lib/ui/runtime/liveScopes.ts +0 -15
- package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
- package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
- package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
- package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
- package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
- package/src/lib/ui/seedResolved.ts +0 -28
- package/src/lib/ui/sync.ts +0 -48
- package/src/lib/ui/types/History.ts +0 -14
- package/src/lib/ui/types/PersistHandle.ts +0 -11
- package/src/lib/ui/types/PersistenceStore.ts +0 -12
- package/src/lib/ui/types/ResolvedFrame.ts +0 -15
- package/src/lib/ui/types/SyncTransport.ts +0 -13
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { appNameSlot } from '../../shared/appNameSlot.ts'
|
|
2
2
|
import { SSR_CACHE_CONTROL } from '../../shared/CACHE_CONTROL_VALUES.ts'
|
|
3
|
+
import { encodeRefJson } from '../../shared/encodeRefJson.ts'
|
|
3
4
|
import { formatTraceparent } from '../../shared/formatTraceparent.ts'
|
|
5
|
+
import { hasSeedableRequest } from '../../shared/hasSeedableRequest.ts'
|
|
4
6
|
import { layoutChainForRoute } from '../../shared/layoutChainForRoute.ts'
|
|
7
|
+
import { resolvedCellsSlot } from '../../shared/resolvedCellsSlot.ts'
|
|
5
8
|
import { safeJsonForScript } from '../../shared/safeJsonForScript.ts'
|
|
6
|
-
import {
|
|
9
|
+
import type { CacheEntry } from '../../shared/types/CacheEntry.ts'
|
|
7
10
|
import type { CacheSnapshotEntry } from '../../shared/types/CacheSnapshotEntry.ts'
|
|
8
11
|
import type { SsrPayload } from '../../shared/types/SsrPayload.ts'
|
|
9
12
|
import type { StreamedResolution } from '../../shared/types/StreamedResolution.ts'
|
|
@@ -48,16 +51,18 @@ function resolveChunk(resolution: StreamedResolution): string {
|
|
|
48
51
|
The abide-ui SSR document renderer. A matched route + params in, a finished HTML
|
|
49
52
|
Response out.
|
|
50
53
|
|
|
51
|
-
A page with no `await` block
|
|
52
|
-
await blocks
|
|
53
|
-
(
|
|
54
|
-
|
|
54
|
+
A page with no `await` block AND no pending triggered bare read renders synchronously and
|
|
55
|
+
ships buffered. A page with await blocks — or a triggered BARE async read still pending at
|
|
56
|
+
render-return (ADR-0024) — STREAMS: the pending shell flushes first, then each resolved
|
|
57
|
+
fragment (`<abide-resolve>` carrying a JSON `<script>`) as its promise settles, swapped into
|
|
58
|
+
its boundary by the inline SSR_SWAP_SCRIPT — which also registers the value into the resume
|
|
55
59
|
manifest so client hydration adopts it without re-fetching (see abide/ui/awaitBlock).
|
|
56
60
|
Then, once the stream has run every `{#await}` thunk (creating and settling its cache
|
|
57
|
-
entry mid-stream, after the render-return snapshot), each such entry
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
entry mid-stream, after the render-return snapshot), each such entry — plus any bare-read
|
|
62
|
+
entry triggered during the sync render — streams an inline `__abideResolve(...)` chunk: a
|
|
63
|
+
warm snapshot, or a `{ key, miss }` marker for an unshippable body — e.g. a read 504'd by its
|
|
64
|
+
endpoint `timeout` (→ live refetch) — seeding the client store before the deferred bundle
|
|
65
|
+
so the block's / bare read's subscription is warm (no refetch).
|
|
61
66
|
|
|
62
67
|
`__SSR__` carries the route/params, mount base, trace, app name, client timeout,
|
|
63
68
|
and the render-return cache snapshot (top-level `await` reads; the client seeds its tab
|
|
@@ -97,6 +102,29 @@ export function createUiPageRenderer({
|
|
|
97
102
|
stack?: string,
|
|
98
103
|
) => Promise<Response | undefined>
|
|
99
104
|
} {
|
|
105
|
+
/* The `cells` partition of __SSR__: every async cell that RESOLVED during this render (drained
|
|
106
|
+
from the request-scoped `resolvedCellsSlot` populated by `createAsyncCell.settleValue`),
|
|
107
|
+
keyed by its render-path id, each value ref-json-encoded so an in-process graph survives.
|
|
108
|
+
An unserializable value is dropped (a warn) — that one cell falls back to a client re-run
|
|
109
|
+
rather than blanking the payload. Undefined when no cell resolved (the common static page). */
|
|
110
|
+
function resolvedCellCells(): Record<string, string> | undefined {
|
|
111
|
+
const entries = resolvedCellsSlot.get()?.entries ?? []
|
|
112
|
+
if (entries.length === 0) {
|
|
113
|
+
return undefined
|
|
114
|
+
}
|
|
115
|
+
const cells: Record<string, string> = {}
|
|
116
|
+
for (const { key, value } of entries) {
|
|
117
|
+
try {
|
|
118
|
+
cells[key] = encodeRefJson(value)
|
|
119
|
+
} catch {
|
|
120
|
+
console.warn(
|
|
121
|
+
`[abide] async cell "${key}" resolved to an unserializable value — it will re-run on the client instead of hydrating warm.`,
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return Object.keys(cells).length > 0 ? cells : undefined
|
|
126
|
+
}
|
|
127
|
+
|
|
100
128
|
/* Build the __SSR__ <script> the client (startClient) reads on boot. The inline
|
|
101
129
|
(settled) cache partition is computed once by the caller and threaded in, so the
|
|
102
130
|
streaming branch can also drain the pending partition over the same render. */
|
|
@@ -111,6 +139,7 @@ export function createUiPageRenderer({
|
|
|
111
139
|
route: routeUrl,
|
|
112
140
|
params,
|
|
113
141
|
cache: inline,
|
|
142
|
+
cells: resolvedCellCells(),
|
|
114
143
|
base: base || undefined,
|
|
115
144
|
trace: formatTraceparent(store.trace),
|
|
116
145
|
app: appNameSlot.name,
|
|
@@ -183,13 +212,26 @@ export function createUiPageRenderer({
|
|
|
183
212
|
/* Outermost layout → … → page: load every applicable layout chunk plus the
|
|
184
213
|
page, then render the chain as one document (shared block-id pass). */
|
|
185
214
|
const chainKeys = chainKeysForRoute(routeUrl)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
215
|
+
/* Resolve each view PAIRED with its render-path key (layouts then page), then filter the
|
|
216
|
+
pairs together. Zipping in one pass makes the key↔view alignment structural rather than
|
|
217
|
+
an asserted "no view is ever dropped" invariant: were a layout to resolve to no default
|
|
218
|
+
export, dropping it would otherwise shift every later key by one and mis-key the page's
|
|
219
|
+
cells (a silent warm-seed mismatch — the class this render-path id exists to kill). The
|
|
220
|
+
keys stay byte-identical to the client router's `chainKeys`/`pageKey`. */
|
|
221
|
+
const resolved = await Promise.all([
|
|
222
|
+
...chainKeys.map((key) =>
|
|
223
|
+
layouts[key]?.().then((module) => ({ key, view: module.default })),
|
|
224
|
+
),
|
|
225
|
+
loadPage().then((module) => ({ key: routeUrl, view: module.default })),
|
|
189
226
|
])
|
|
227
|
+
const kept = resolved.filter(
|
|
228
|
+
(entry): entry is { key: string; view: UiComponent } =>
|
|
229
|
+
entry !== undefined && entry.view !== undefined,
|
|
230
|
+
)
|
|
190
231
|
const ssr = await renderChain(
|
|
191
|
-
|
|
232
|
+
kept.map((entry) => entry.view),
|
|
192
233
|
params,
|
|
234
|
+
kept.map((entry) => entry.key),
|
|
193
235
|
)
|
|
194
236
|
|
|
195
237
|
/* Snapshot the cache settled by render-return — top-level `await` reads and blocking
|
|
@@ -199,10 +241,24 @@ export function createUiPageRenderer({
|
|
|
199
241
|
const inline = await serializeCacheSnapshot(store.cache)
|
|
200
242
|
const inlinedKeys = new Set(inline.map((entry) => entry.key))
|
|
201
243
|
|
|
202
|
-
/*
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
244
|
+
/* ADR-0024: a bare async read (`{user}`, no `{#await}`) that TRIGGERED its fetch during
|
|
245
|
+
the sync render leaves a still-pending replayable entry in the store at render-return
|
|
246
|
+
— not yet settled, so it missed the inline snapshot above. Detecting one here is what
|
|
247
|
+
opens the streaming gate for a page with no await block, so its value streams in
|
|
248
|
+
instead of shipping `undefined` buffered. `hasSeedableRequest` (the request half of
|
|
249
|
+
shippability — not `snapshotShippable`, which also demands `settled`) is the gate: it
|
|
250
|
+
excludes producers and stream cells (a `NamedAsyncIterable` cell holds no wire
|
|
251
|
+
request), which must stay `peek()`-at-flush and buffered. `!inlinedKeys` drops
|
|
252
|
+
anything already baked into `__SSR__` (a Tier-2 top-level `await`) so a value never
|
|
253
|
+
double-ships. */
|
|
254
|
+
const pendingBareReads = Array.from(store.cache.entries.values()).filter(
|
|
255
|
+
(entry) => hasSeedableRequest(entry) && !inlinedKeys.has(entry.key),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
/* No STREAMING await blocks AND no triggered bare read pending → ship buffered exactly
|
|
259
|
+
as before. Blocking `{#await … then}` blocks rendered inline (their markup is already
|
|
260
|
+
in `ssr.html`); seed their resolved values so hydration adopts them without a refetch. */
|
|
261
|
+
if (ssr.awaits.length === 0 && pendingBareReads.length === 0) {
|
|
206
262
|
const html = injectRoutePreloads(
|
|
207
263
|
shell.replace(SSR_MARKER, (_match, key: string) =>
|
|
208
264
|
key === 'body' ? ssr.html : key === 'state' ? '' : '',
|
|
@@ -232,6 +288,11 @@ export function createUiPageRenderer({
|
|
|
232
288
|
const head =
|
|
233
289
|
`<script>${SSR_SWAP_SCRIPT}${CACHE_RESOLVE_SCRIPT}</script>` +
|
|
234
290
|
`${await stateTag(routeUrl, params, store, inline)}`
|
|
291
|
+
/* ADR-0039: the async-cell keys already baked into the head `__SSR__.cells` snapshot. A
|
|
292
|
+
streamed child's render is deferred to the drain, so its BLOCKING cells resolve AFTER this
|
|
293
|
+
and must ship as post-body `{cellSeed}` chunks instead — the delta below is keys NOT here.
|
|
294
|
+
Read `store` directly (the stream body runs outside the request ALS). */
|
|
295
|
+
const seededCellKeys = new Set(store.resolvedCells.entries.map((entry) => entry.key))
|
|
235
296
|
const filled = injectRoutePreloads(
|
|
236
297
|
shell.replace(HEAD_STATE_MARKER, (_match, key: string) => (key === 'head' ? head : '')),
|
|
237
298
|
routeUrl,
|
|
@@ -256,22 +317,72 @@ export function createUiPageRenderer({
|
|
|
256
317
|
)
|
|
257
318
|
first = false
|
|
258
319
|
}
|
|
259
|
-
/*
|
|
260
|
-
stream — the await expression is a
|
|
261
|
-
so they missed the render-return
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
320
|
+
/* Two kinds of pending entry land here. (1) `{#await}` reads created (and
|
|
321
|
+
settled) their cache entries DURING the stream — the await expression is a
|
|
322
|
+
thunk `renderToStream` ran lazily — so they missed the render-return
|
|
323
|
+
snapshot. (2) A triggered BARE read (ADR-0024) created its entry during the
|
|
324
|
+
sync render but was never awaited, so it may still be in flight now. Drain
|
|
325
|
+
both: each lands an inline `__abideResolve(...)` chunk (a warm snapshot, or a
|
|
326
|
+
`{ key, miss }` marker for an unshippable body — e.g. a read 504'd by its
|
|
327
|
+
endpoint timeout → live refetch) before the deferred bundle, so startClient seeds
|
|
328
|
+
the store and the read's subscription is warm. Skip keys already shipped inline in
|
|
329
|
+
__SSR__. */
|
|
330
|
+
const streamedEntries: CacheEntry[] = Array.from(
|
|
331
|
+
store.cache.entries.values(),
|
|
332
|
+
).filter(
|
|
333
|
+
(entry) => hasSeedableRequest(entry) && !inlinedKeys.has(entry.key),
|
|
268
334
|
)
|
|
335
|
+
/* `hasSeedableRequest`, NOT `snapshotShippable`: a triggered bare read
|
|
336
|
+
(ADR-0024) is drained here still-pending (never awaited by an `{#await}`
|
|
337
|
+
thunk), so gating on `settled` up front would skip it — streamCacheResolutions
|
|
338
|
+
awaits each entry's body itself (see snapshotEntryFromCache). A pending read is
|
|
339
|
+
bounded by its OWN endpoint `timeout` (which 504s the in-process handler during
|
|
340
|
+
SSR), so there is no separate SSR-stream deadline. A late-settling `{#await}`
|
|
341
|
+
entry is already settled by now, so the set is unchanged for the Tier-3 path. */
|
|
269
342
|
for await (const resolution of streamCacheResolutions(
|
|
270
343
|
store.cache,
|
|
271
344
|
streamedEntries,
|
|
272
345
|
)) {
|
|
273
346
|
controller.enqueue(encoder.encode(resolveChunk(resolution)))
|
|
274
347
|
}
|
|
348
|
+
/* ADR-0035: stream each STREAMING cell that SETTLED during this render as an
|
|
349
|
+
`__abideResolve({ cellKey, value })` chunk (keyed by its render-path id),
|
|
350
|
+
so the client adopts it post-hydration instead of only cold-re-running the
|
|
351
|
+
seed — the flash the pre-mount `__SSR__.cells` warm-seed can't fix without
|
|
352
|
+
diverging from the pending shell. These are already-settled VALUES
|
|
353
|
+
(recorded in createAsyncCell.settleValue), never awaited here: a cell that
|
|
354
|
+
stays pending through the request (`{#if getFoo()}` holds) is simply not
|
|
355
|
+
streamed and the client cold-runs it. An unserializable value emits nothing.
|
|
356
|
+
Read from `store` directly since the stream body runs outside the request ALS. */
|
|
357
|
+
for (const { key, value } of store.streamedCells.entries) {
|
|
358
|
+
let encoded: string
|
|
359
|
+
try {
|
|
360
|
+
encoded = encodeRefJson(value)
|
|
361
|
+
} catch {
|
|
362
|
+
continue
|
|
363
|
+
}
|
|
364
|
+
controller.enqueue(
|
|
365
|
+
encoder.encode(resolveChunk({ cellKey: key, value: encoded })),
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
/* ADR-0039: a STREAMED CHILD's BLOCKING async cells resolved during this drain,
|
|
369
|
+
after the head `__SSR__.cells` snapshot — ship the delta (keys not already
|
|
370
|
+
seeded) as `{cellSeed}` chunks so startClient seeds CELL_SEED before the
|
|
371
|
+
child's deferred mount and its cell constructs resolved (no flash/re-run). */
|
|
372
|
+
for (const { key, value } of store.resolvedCells.entries) {
|
|
373
|
+
if (seededCellKeys.has(key)) {
|
|
374
|
+
continue
|
|
375
|
+
}
|
|
376
|
+
let encoded: string
|
|
377
|
+
try {
|
|
378
|
+
encoded = encodeRefJson(value)
|
|
379
|
+
} catch {
|
|
380
|
+
continue
|
|
381
|
+
}
|
|
382
|
+
controller.enqueue(
|
|
383
|
+
encoder.encode(resolveChunk({ cellSeed: key, value: encoded })),
|
|
384
|
+
)
|
|
385
|
+
}
|
|
275
386
|
controller.enqueue(encoder.encode(after ?? ''))
|
|
276
387
|
controller.close()
|
|
277
388
|
} catch (streamError) {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { relative } from 'node:path'
|
|
2
|
-
import { fileName } from '../../shared/fileName.ts'
|
|
3
2
|
import { isLayoutFile } from '../../shared/isLayoutFile.ts'
|
|
4
|
-
import { analyzeComponent } from '../../ui/compile/analyzeComponent.ts'
|
|
5
3
|
import { compileComponent } from '../../ui/compile/compileComponent.ts'
|
|
6
4
|
import { nearestProjectRoot } from '../../ui/compile/nearestProjectRoot.ts'
|
|
7
5
|
import { globToPathSet } from './globToPathSet.ts'
|
|
@@ -12,36 +10,29 @@ const CSS_HREF = /href=(["'])([^"']*\/_app\/[^"']*\.css)\1/
|
|
|
12
10
|
// Every `/_app/<hashed-asset>` ref in the shell (the JS entry, the stylesheet) —
|
|
13
11
|
// content-hashed names that move on any rebuild. Normalised out of `structure` so
|
|
14
12
|
// a component/CSS edit (which re-hashes the bundle) doesn't read as a reload; the
|
|
15
|
-
// source-based signals (
|
|
13
|
+
// source-based signals (component bodies, non-component hashes, cssHref) catch real changes.
|
|
16
14
|
const APP_ASSET = /\/_app\/[^"'\s)]*/g
|
|
17
15
|
// src/.abide is the build's own generated output (route d.ts) — rewritten every
|
|
18
16
|
// rebuild, so it must stay out of the hash or the page would always reload.
|
|
19
17
|
const GENERATED = /(^|\/)\.abide\//
|
|
20
18
|
|
|
21
|
-
// page.abide / layout.abide are router-mounted, not `mountChild`-tracked, so they
|
|
22
|
-
// can't hot-swap — they fold into `structure` (a reload) instead.
|
|
23
|
-
function isPageOrLayout(moduleId: string): boolean {
|
|
24
|
-
return fileName(moduleId) === 'page.abide' || isLayoutFile(moduleId)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
19
|
/*
|
|
28
20
|
The dev live-reload stamp, computed once at worker boot from SOURCE (the dev
|
|
29
21
|
watcher only watches src/, so source is what a rebuild can reflect). Splits the
|
|
30
22
|
edit space so the browser keeps its page where it can:
|
|
31
23
|
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- `
|
|
39
|
-
the
|
|
40
|
-
|
|
41
|
-
- `cssHref` is the entry stylesheet's current URL, read from the shell.
|
|
24
|
+
- `structure` fingerprints every source signal a reload must react to: each
|
|
25
|
+
`.abide`'s client BUILD hash (`compileComponent`, which carries NO CSS — so a
|
|
26
|
+
style-only edit leaves it unchanged), the component-id set (adding/removing a
|
|
27
|
+
component reloads), non-`.abide` source, public assets, and the shell (with the
|
|
28
|
+
content-hashed `/_app/*` asset names normalised out, so a rebuild's re-hash isn't
|
|
29
|
+
itself a change). Any change here reloads.
|
|
30
|
+
- `cssHref` is the entry stylesheet's content-hashed URL; a change here ALONE swaps
|
|
31
|
+
the `<link>` in place — a CSS edit restyles the live page with no reload and no
|
|
32
|
+
state loss.
|
|
42
33
|
|
|
43
|
-
A compile failure (mid-edit syntax error) falls back to a raw-source hash
|
|
44
|
-
|
|
34
|
+
A compile failure (mid-edit syntax error) falls back to a raw-source hash, so the
|
|
35
|
+
worst case is a reload, never a throw at boot.
|
|
45
36
|
*/
|
|
46
37
|
export async function devClientFingerprint({
|
|
47
38
|
srcDir,
|
|
@@ -58,8 +49,7 @@ export async function devClientFingerprint({
|
|
|
58
49
|
.filter((file) => !GENERATED.test(file))
|
|
59
50
|
.sort()
|
|
60
51
|
|
|
61
|
-
const
|
|
62
|
-
const nonHotComponents: string[] = []
|
|
52
|
+
const componentBodies: string[] = []
|
|
63
53
|
const componentIds: string[] = []
|
|
64
54
|
const otherSource: string[] = []
|
|
65
55
|
|
|
@@ -79,19 +69,14 @@ export async function devClientFingerprint({
|
|
|
79
69
|
const moduleId = relative(nearestProjectRoot(full, projectRoot), full)
|
|
80
70
|
componentIds.push(moduleId)
|
|
81
71
|
let bodyHash: string
|
|
82
|
-
let hot = false
|
|
83
72
|
try {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
bodyHash = Bun.hash(
|
|
74
|
+
compileComponent(source, isLayoutFile(moduleId), moduleId),
|
|
75
|
+
).toString(36)
|
|
87
76
|
} catch {
|
|
88
77
|
bodyHash = Bun.hash(source).toString(36)
|
|
89
78
|
}
|
|
90
|
-
|
|
91
|
-
components[moduleId] = bodyHash
|
|
92
|
-
} else {
|
|
93
|
-
nonHotComponents.push(`${moduleId}:${bodyHash}`)
|
|
94
|
-
}
|
|
79
|
+
componentBodies.push(`${moduleId}:${bodyHash}`)
|
|
95
80
|
}),
|
|
96
81
|
)
|
|
97
82
|
|
|
@@ -111,8 +96,8 @@ export async function devClientFingerprint({
|
|
|
111
96
|
publicStamps.join('\n'),
|
|
112
97
|
structureShell,
|
|
113
98
|
componentIds.sort().join(','),
|
|
114
|
-
|
|
99
|
+
componentBodies.sort().join('\n'),
|
|
115
100
|
].join('\0'),
|
|
116
101
|
).toString(36)
|
|
117
|
-
return { structure, cssHref
|
|
102
|
+
return { structure, cssHref }
|
|
118
103
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { responseBodyKind } from '../../shared/responseBodyKind.ts'
|
|
2
2
|
import { gzipResponse } from './gzipResponse.ts'
|
|
3
|
+
import { STREAMED_HTML_HEADER } from './STREAMED_HTML_HEADER.ts'
|
|
3
4
|
import type { RequestStore } from './types/RequestStore.ts'
|
|
4
5
|
|
|
5
6
|
/*
|
|
@@ -24,7 +25,16 @@ export function finalizeResponse(
|
|
|
24
25
|
exemptIdleTimeout: () => void,
|
|
25
26
|
): Response {
|
|
26
27
|
const kind = responseBodyKind(response)
|
|
27
|
-
|
|
28
|
+
/* Close-monitoring (defer the closing record + cache-stats snapshot until the body
|
|
29
|
+
actually drains) must also cover progressively-streamed SSR HTML — a live
|
|
30
|
+
ReadableStream marked by STREAMED_HTML_HEADER that classifies as 'compressible' (it
|
|
31
|
+
still gzips), NOT as 'streaming'. Read the marker before gzipResponse strips it.
|
|
32
|
+
Without this, a page with a slow `{#await}` logs ~1ms elapsed and near-empty cache
|
|
33
|
+
stats at header time instead of the true drain-time values. */
|
|
34
|
+
const streamedHtml = response.headers.has(STREAMED_HTML_HEADER)
|
|
35
|
+
store.responseStreaming = kind === 'streaming' || streamedHtml
|
|
36
|
+
/* Only true frame protocols (SSE/JSONL, socket tail) opt out of the idle-timeout cap;
|
|
37
|
+
streamed HTML pages deliberately rely on it (see STREAMED_HTML_HEADER). */
|
|
28
38
|
if (kind === 'streaming') {
|
|
29
39
|
exemptIdleTimeout()
|
|
30
40
|
}
|
|
@@ -1,19 +1,27 @@
|
|
|
1
|
+
import { ambientPathBacking } from '../../ui/runtime/ambientPathBacking.ts'
|
|
1
2
|
import { ambientScopeBacking } from '../../ui/runtime/ambientScopeBacking.ts'
|
|
3
|
+
import { cellBarrierBacking } from '../../ui/runtime/cellBarrierBacking.ts'
|
|
2
4
|
import type { Scope } from '../../ui/types/Scope.ts'
|
|
5
|
+
import { pathStore } from './pathStore.ts'
|
|
6
|
+
import { renderCellBarrierStore } from './renderCellBarrierStore.ts'
|
|
3
7
|
import { requestContext } from './requestContext.ts'
|
|
4
8
|
|
|
5
|
-
/* Ambient scope when no request is in flight (server boot, a standalone render).
|
|
9
|
+
/* Ambient scope when no request is in flight (server boot, a standalone render). The path needs
|
|
10
|
+
no such fallback: `pathStore.run` establishes its value with or without a request in flight,
|
|
11
|
+
and `getStore()` fails open to `''` outside any push. */
|
|
6
12
|
let outsideRequest: Scope | undefined
|
|
7
13
|
|
|
8
14
|
/*
|
|
9
|
-
Swaps the ambient-scope backing to one keyed off the per-request AsyncLocalStorage
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
right
|
|
14
|
-
another request renders concurrently.
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
Swaps the ambient-scope backing to one keyed off the per-request AsyncLocalStorage store and the
|
|
16
|
+
ambient-PATH backing to the dedicated `pathStore.run` (ADR-0033 D1), so `CURRENT_SCOPE.current` and
|
|
17
|
+
`CURRENT_PATH.current` isolate per request instead of living in one module global (see CURRENT_SCOPE
|
|
18
|
+
/ CURRENT_PATH). SSR is partly async — a render `await`s inline between its `enterScope`/`exitScope`
|
|
19
|
+
bracket — and the async context propagates the right value across those awaits, so a resumed render
|
|
20
|
+
reads ITS OWN scope/path even while another request renders concurrently. For the path, `run`'s value
|
|
21
|
+
is ALSO inherited by the render's own post-await continuation (a mutable slot restored in `finally`
|
|
22
|
+
was not), which is the fix ADR-0033 buys. Called once at server boot (createServer), alongside the
|
|
23
|
+
request-scope resolver install. The scope backing still falls back to a module variable outside any
|
|
24
|
+
request (unchanged behaviour for standalone renders); the path backing needs no such fallback.
|
|
17
25
|
*/
|
|
18
26
|
export function installAmbientScopeStore(): void {
|
|
19
27
|
ambientScopeBacking.active = {
|
|
@@ -30,4 +38,17 @@ export function installAmbientScopeStore(): void {
|
|
|
30
38
|
}
|
|
31
39
|
},
|
|
32
40
|
}
|
|
41
|
+
ambientPathBacking.active = {
|
|
42
|
+
run: (composedPath, build) => pathStore.run(composedPath, build),
|
|
43
|
+
get: () => pathStore.getStore() ?? '',
|
|
44
|
+
}
|
|
45
|
+
/* Per-render async-cell barrier isolation (ADR-0037 Phase 2): a hoisted concurrent child render
|
|
46
|
+
runs under its own pending-cells list via `renderCellBarrierStore.run`, and the barrier
|
|
47
|
+
consumers read it through `cellBarrierBacking.current`. ALS so the fresh list survives the
|
|
48
|
+
child render's own awaits; `undefined` outside any isolated render leaves the page/request
|
|
49
|
+
drain untouched. */
|
|
50
|
+
cellBarrierBacking.active = {
|
|
51
|
+
current: () => renderCellBarrierStore.getStore(),
|
|
52
|
+
run: (list, render) => renderCellBarrierStore.run(list, render),
|
|
53
|
+
}
|
|
33
54
|
}
|
|
@@ -15,9 +15,10 @@ const redden = (text: string): string =>
|
|
|
15
15
|
hasColor ? `${Bun.color('red', 'ansi-256')}${text}\x1b[39m` : text
|
|
16
16
|
|
|
17
17
|
/*
|
|
18
|
-
A declared inputSchema
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
A declared inputSchema — OR a build-projected input-type schema (ADR-0030 input
|
|
19
|
+
side), which advertises the same shape — is what makes mcp/cli safe to advertise
|
|
20
|
+
(see defineRpc / defineSocket), so a declaration with neither gets a red `·` to
|
|
21
|
+
flag that its machine surfaces are gated behind a missing contract.
|
|
21
22
|
*/
|
|
22
23
|
const schemaCell = (hasSchema: boolean): string => (hasSchema ? PRESENT : redden(ABSENT))
|
|
23
24
|
const flag = (on: boolean): string => (on ? PRESENT : ABSENT)
|
|
@@ -133,10 +134,10 @@ export async function logExposedSurfaces(routing: { pages: Pages }): Promise<voi
|
|
|
133
134
|
|
|
134
135
|
const rpcRows = Array.from(rpcRegistry.values(), (entry) => [
|
|
135
136
|
withMethod(entry.remote.method, entry.remote.url),
|
|
136
|
-
schemaCell(
|
|
137
|
-
flag(entry.clients.browser),
|
|
138
|
-
flag(entry.clients.mcp),
|
|
139
|
-
flag(entry.clients.cli),
|
|
137
|
+
schemaCell(entry.inputSchema !== undefined || entry.inputJsonSchema !== undefined),
|
|
138
|
+
flag(entry.remote.clients.browser),
|
|
139
|
+
flag(entry.remote.clients.mcp),
|
|
140
|
+
flag(entry.remote.clients.cli),
|
|
140
141
|
]).sort()
|
|
141
142
|
|
|
142
143
|
logTables([
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// AsyncLocalStorage is canonical via node:async_hooks — Bun has no separate API
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
The server's dedicated render-path store (ADR-0033 D1). Backs `CURRENT_PATH.current` during an
|
|
6
|
+
SSR pass with the COMPOSED path as the store value, pushed by `.run(compose(base, segment), build)`
|
|
7
|
+
at each nesting site (route → layout → row → branch → child ordinal). Unlike a mutable slot restored
|
|
8
|
+
in `finally`, `run`'s value is inherited by every async continuation spawned inside it — so a render
|
|
9
|
+
body resuming after an `await` still reads ITS OWN path, and sibling rows/children (continuations of
|
|
10
|
+
the enclosing render body) read the enclosing render's path. Per-request isolation falls out for free
|
|
11
|
+
(each route render is its own `run` tree), which is why this subsumes the path half of the request
|
|
12
|
+
store. Reads outside any `run` fail open to `''` (a detached scope then takes the counter fallback).
|
|
13
|
+
Server-only, kept out of `lib/ui` so the browser bundle never drags in `node:async_hooks`.
|
|
14
|
+
*/
|
|
15
|
+
export const pathStore = new AsyncLocalStorage<string>()
|
|
@@ -52,7 +52,7 @@ export function ensureRegistriesLoaded(): Promise<void> {
|
|
|
52
52
|
.then(() => undefined)
|
|
53
53
|
/*
|
|
54
54
|
Clear the memo on failure so a transient import error (a
|
|
55
|
-
module that throws at load, fixed by the next
|
|
55
|
+
module that throws at load, fixed by the next rebuild)
|
|
56
56
|
doesn't poison every later enumeration request for the
|
|
57
57
|
process lifetime. The rejection still propagates to this
|
|
58
58
|
caller; the reset only affects subsequent calls.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// AsyncLocalStorage is canonical via node:async_hooks — Bun has no separate API
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
3
|
+
import type { PendingAsyncCells } from '../../shared/types/PendingAsyncCells.ts'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
The server's per-RENDER async-cell barrier store (ADR-0037 Phase 2). Backs `cellBarrierBacking` with
|
|
7
|
+
a fresh pending-cells list pushed by `isolateCellBarrier` around a hoisted concurrent child render.
|
|
8
|
+
Unlike the per-request `pendingAsyncCells` (one list a whole request shares), `run`'s value is
|
|
9
|
+
inherited by the child render's own async continuations — so a child that suspends on its script
|
|
10
|
+
await or its `$$settleAsyncCells` barrier keeps reading ITS list, and two sibling renders in flight
|
|
11
|
+
at once never splice each other's cells. `getStore()` is `undefined` outside any isolated render, so
|
|
12
|
+
the page's own cells fall through to the request list. Server-only, kept out of `lib/ui` so the
|
|
13
|
+
browser bundle never drags in `node:async_hooks`. Mirrors `pathStore` (ADR-0033 D1).
|
|
14
|
+
*/
|
|
15
|
+
export const renderCellBarrierStore = new AsyncLocalStorage<PendingAsyncCells>()
|
|
@@ -31,6 +31,9 @@ export function runWithRequestScope(
|
|
|
31
31
|
url,
|
|
32
32
|
req,
|
|
33
33
|
cache: createCacheStore(),
|
|
34
|
+
pendingAsyncCells: { promises: [] },
|
|
35
|
+
resolvedCells: { entries: [] },
|
|
36
|
+
streamedCells: { entries: [] },
|
|
34
37
|
trace: createTraceContext(req.headers.get('traceparent')),
|
|
35
38
|
start: Bun.nanoseconds(),
|
|
36
39
|
}
|
|
@@ -4,10 +4,12 @@ import type { CacheStore } from '../../shared/types/CacheStore.ts'
|
|
|
4
4
|
import { snapshotEntryFromCache } from './snapshotEntryFromCache.ts'
|
|
5
5
|
|
|
6
6
|
/*
|
|
7
|
-
Snapshots the request-scoped cache for SSR at a single instant: every
|
|
8
|
-
(
|
|
9
|
-
client seeds its store from
|
|
10
|
-
|
|
7
|
+
Snapshots the request-scoped cache for SSR at a single instant: every entry
|
|
8
|
+
(any method) called inline and settled by now, serialized to a wire-safe
|
|
9
|
+
CacheSnapshotEntry the client seeds its store from — so an inline POST hydrates
|
|
10
|
+
warm rather than re-firing after render. Unsettled entries and producers/stream
|
|
11
|
+
cells (no wire request) are skipped; a body that can't ship (binary / streaming /
|
|
12
|
+
rejected) drops out via snapshotEntryFromCache.
|
|
11
13
|
|
|
12
14
|
Snapshots concurrently — the awaits are immediate (entries are already settled), but
|
|
13
15
|
their body reads run in parallel. Never blocks on an unsettled entry.
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { contentBodyKind } from '../../shared/contentBodyKind.ts'
|
|
2
2
|
import { contentTypeOf } from '../../shared/contentTypeOf.ts'
|
|
3
|
-
import {
|
|
3
|
+
import { hasSeedableRequest } from '../../shared/hasSeedableRequest.ts'
|
|
4
4
|
import { isStreamingResponse } from '../../shared/isStreamingResponse.ts'
|
|
5
5
|
import type { CacheEntry } from '../../shared/types/CacheEntry.ts'
|
|
6
6
|
import type { CacheSnapshotEntry } from '../../shared/types/CacheSnapshotEntry.ts'
|
|
7
7
|
import type { CacheStore } from '../../shared/types/CacheStore.ts'
|
|
8
|
-
import type {
|
|
8
|
+
import type { HttpMethod } from '../../shared/types/HttpMethod.ts'
|
|
9
9
|
|
|
10
10
|
/*
|
|
11
11
|
Awaits one cache entry and turns it into a wire-safe snapshot, or undefined
|
|
12
12
|
when it can't ship. Shared by the inline snapshot path (settled entries,
|
|
13
13
|
resolves immediately) and the streaming drain (pending {#await} entries,
|
|
14
|
-
resolves whenever the underlying fetch lands).
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
resolves whenever the underlying fetch lands). Any entry carrying a wire request
|
|
15
|
+
(any method — an inline POST seeds too, so it hydrates warm rather than re-firing
|
|
16
|
+
after render) with a textual Content-Type survives; producers/stream cells (no
|
|
17
|
+
request) and binary bodies (don't round-trip through JSON) drop out. Seeding is
|
|
18
|
+
not replay: the seeded value is read warm once, never auto-re-fired (that stays
|
|
19
|
+
GET-only — see REPLAYABLE_METHODS).
|
|
18
20
|
|
|
19
21
|
Reads the body once and replaces the entry's promise with a string-bodied
|
|
20
22
|
Response so later `shareable()` clones operate on a buffered body instead of
|
|
@@ -27,16 +29,14 @@ export async function snapshotEntryFromCache(
|
|
|
27
29
|
store: CacheStore,
|
|
28
30
|
entry: CacheEntry,
|
|
29
31
|
): Promise<CacheSnapshotEntry | undefined> {
|
|
30
|
-
/* The request half of the shared shippability gate: a
|
|
31
|
-
Producer entries (no request)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!
|
|
32
|
+
/* The request half of the shared shippability gate: a seedable wire request (any method).
|
|
33
|
+
Producer entries and stream cells (no request) never snapshot — the streaming-drain
|
|
34
|
+
caller hands still-pending entries here, so `settled` is NOT gated (the caller's
|
|
35
|
+
snapshotShippable filter already required it for the entries it picks). */
|
|
36
|
+
if (!hasSeedableRequest(entry) || !entry.request) {
|
|
35
37
|
return undefined
|
|
36
38
|
}
|
|
37
|
-
|
|
38
|
-
for the snapshot's typed `method` field rather than re-running the same Set check. */
|
|
39
|
-
const method = entry.request.method.toUpperCase() as ReplayableMethod
|
|
39
|
+
const method = entry.request.method.toUpperCase() as HttpMethod
|
|
40
40
|
const response = await readSettled(entry.promise as Promise<Response>)
|
|
41
41
|
if (!response) {
|
|
42
42
|
return undefined
|
|
@@ -63,8 +63,16 @@ export async function snapshotEntryFromCache(
|
|
|
63
63
|
}
|
|
64
64
|
/* Read a CLONE, not the original: a reader that captured this same `entry.promise`
|
|
65
65
|
before the replacement below still holds `response` and may `.clone()` it — reading
|
|
66
|
-
the original here would lock its body and throw "Body already used" for that reader.
|
|
67
|
-
|
|
66
|
+
the original here would lock its body and throw "Body already used" for that reader.
|
|
67
|
+
The body stream can still error mid-read (reset/truncated upstream) after headers
|
|
68
|
+
resolved — degrade that to undefined (matching the fetch-rejection path) so one flaky
|
|
69
|
+
entry doesn't reject the whole snapshot batch and 500 / abort the page stream. */
|
|
70
|
+
let body: string
|
|
71
|
+
try {
|
|
72
|
+
body = await response.clone().text()
|
|
73
|
+
} catch {
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
68
76
|
entry.promise = Promise.resolve(
|
|
69
77
|
new Response(body, {
|
|
70
78
|
status: response.status,
|