@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
|
@@ -13,6 +13,8 @@ import { composeProps } from './composeProps.ts'
|
|
|
13
13
|
import { eachPlan } from './eachPlan.ts'
|
|
14
14
|
import { elementPlan } from './elementPlan.ts'
|
|
15
15
|
import { groupBindParts } from './groupBindParts.ts'
|
|
16
|
+
import { hoistableAwaits } from './hoistableAwaits.ts'
|
|
17
|
+
import { hoistableChildRenders } from './hoistableChildRenders.ts'
|
|
16
18
|
import { ifPlan } from './ifPlan.ts'
|
|
17
19
|
import { interpolatedTemplateLiteral } from './interpolatedTemplateLiteral.ts'
|
|
18
20
|
import { isAnchorPositioned } from './isAnchorPositioned.ts'
|
|
@@ -66,10 +68,16 @@ export function generateSSR(
|
|
|
66
68
|
derivedNames: ReadonlySet<string>,
|
|
67
69
|
computedNames: ReadonlySet<string>,
|
|
68
70
|
isLayout = false,
|
|
69
|
-
)
|
|
71
|
+
/* `linked` / async `computed` names, lowered to `$$readCell(name)` in template exprs. */
|
|
72
|
+
cellReadNames: ReadonlySet<string> = new Set(),
|
|
73
|
+
): { body: string; flightDecls: string; hasStagedChildren: boolean } {
|
|
70
74
|
/* Unique temp var names (child render results); runtime block ids are
|
|
71
75
|
allocated separately at runtime via `$ctx.next++`. */
|
|
72
76
|
const nextVar = makeVarNamer()
|
|
77
|
+
/* A per-body source-order ordinal for each `<Child/>` render site — the render-path segment the
|
|
78
|
+
child roots under, matching the client `mountChild`'s `childOrdinal` (`generateBuild`) drawn
|
|
79
|
+
in the identical document-order walk, so both compose the same cell scope ids. */
|
|
80
|
+
let childOrdinal = 0
|
|
73
81
|
|
|
74
82
|
/* The enclosing `<select bind:value>`s, innermost last: each carries the JS var holding
|
|
75
83
|
its bound value and whether it's a `multiple` (array) select, so an `<option>` rendered
|
|
@@ -84,7 +92,8 @@ export function generateSSR(
|
|
|
84
92
|
withNestedScripts,
|
|
85
93
|
withShadow,
|
|
86
94
|
bindRead,
|
|
87
|
-
|
|
95
|
+
bindWrite,
|
|
96
|
+
} = lowerContext(stateNames, derivedNames, computedNames, cellReadNames)
|
|
88
97
|
|
|
89
98
|
/* SSR has no cells, so every plan `Binding` — `reactive` or `plain` — renders as a `plain`
|
|
90
99
|
shadow (a real JS local / loop var / arrow param, read as the bare identifier). The one
|
|
@@ -150,6 +159,28 @@ export function generateSSR(
|
|
|
150
159
|
through the generic element path exactly as the client clones it. */
|
|
151
160
|
const rootNodes = isLayout ? nodes.map(asOutlet) : nodes
|
|
152
161
|
|
|
162
|
+
/* ADR-0034: the await blocks whose promise-start hoists to the synchronous render prefix so
|
|
163
|
+
independent flights overlap instead of serializing. Server-only — this rewires only the SSR
|
|
164
|
+
promise SOURCE (a blocking `await $flightN`, a streaming `promise: () => $flightN`); the
|
|
165
|
+
block's markers, `$ctx.next++` id, RESUME wire, and the whole client build stay byte-identical.
|
|
166
|
+
The `flightDecls` are emitted by compileSSR after the lowered script and BEFORE the barrier, so
|
|
167
|
+
a hoisted flight is already in-flight while the barrier awaits any unrelated blocking cell. */
|
|
168
|
+
const flightNameByNode = new Map<Extract<TemplateNode, { kind: 'await' }>, string>()
|
|
169
|
+
for (const flight of hoistableAwaits(rootNodes, cellReadNames)) {
|
|
170
|
+
flightNameByNode.set(flight.node, flight.name)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* ADR-0037 Phase 2: the top-level-spine `<Child/>` renders whose start hoists to the prefix so
|
|
174
|
+
sibling renders overlap instead of serializing behind each other's `await`. The component walk
|
|
175
|
+
below emits each hoisted child's flight decl into `childFlightDecls` (with its childOrdinal and
|
|
176
|
+
lowered props computed at the SAME site as the body's `await`, so the two can't drift) and
|
|
177
|
+
awaits the flight const at the structural position. Each hoisted render starts under
|
|
178
|
+
`$$isolateCellBarrier` so its async cells drain in their own list, not a concurrent sibling's
|
|
179
|
+
(the request-scoped barrier is `splice(0)`-drained). */
|
|
180
|
+
const hoistableChildSet = hoistableChildRenders(rootNodes, cellReadNames)
|
|
181
|
+
const childFlightDecls: string[] = []
|
|
182
|
+
let childFlightCounter = 0
|
|
183
|
+
|
|
153
184
|
/* A snippet name (any identifier, `$` included) interpolated into a RegExp must have its
|
|
154
185
|
regex metacharacters escaped, or e.g. a trailing `$` would read as an end-anchor and the
|
|
155
186
|
call site would never match — leaving an un-awaited Promise stringified as `[object
|
|
@@ -184,15 +215,17 @@ export function generateSSR(
|
|
|
184
215
|
})
|
|
185
216
|
|
|
186
217
|
/* Snippet names whose body produces an `await`, so the snippet must be an `async function`
|
|
187
|
-
and its `{name(...)}` call sites awaited: it inlines a child component
|
|
188
|
-
block
|
|
189
|
-
|
|
190
|
-
|
|
218
|
+
and its `{name(...)}` call sites awaited: it inlines a child component, holds an await
|
|
219
|
+
block, or emits a `{children()}` slot fill (all `await $props.$children()` in SSR) — a
|
|
220
|
+
structural scan — OR it text-calls another async snippet. The latter is a dependency
|
|
221
|
+
between snippets, so resolve it to a fixpoint — seed with the structural set, then keep
|
|
222
|
+
adding any snippet that calls an already-async one until nothing changes. */
|
|
191
223
|
const subtreeAwaits = (children: TemplateNode[]): boolean =>
|
|
192
224
|
children.some(
|
|
193
225
|
(child) =>
|
|
194
226
|
child.kind === 'component' ||
|
|
195
227
|
child.kind === 'await' ||
|
|
228
|
+
(child.kind === 'element' && child.tag === 'slot') ||
|
|
196
229
|
('children' in child && subtreeAwaits(child.children)),
|
|
197
230
|
)
|
|
198
231
|
const snippetDefs = new Map<string, TemplateNode[]>()
|
|
@@ -223,14 +256,20 @@ export function generateSSR(
|
|
|
223
256
|
}
|
|
224
257
|
}
|
|
225
258
|
}
|
|
226
|
-
/* A text-part expression
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
259
|
+
/* A text-part expression whose value may be a Promise, so `$text` must `await` it:
|
|
260
|
+
either it calls an async snippet declared HERE, or it CALLS a computed-backed
|
|
261
|
+
binding. The latter covers a snippet handed down as a prop and called by its prop
|
|
262
|
+
name (`{item(label)}`) — that prop lowers to a computed, so it never appears in this
|
|
263
|
+
component's own `asyncSnippets`, yet the parent's snippet body may be async and the
|
|
264
|
+
call returns a Promise (the `[object Promise]` bug). A computed READ (`{full}`) is not
|
|
265
|
+
a call, so plain interpolation stays sync; only `name(...)` on a computed is awaited. */
|
|
266
|
+
const awaitableCallNames = new Set<string>([...asyncSnippets, ...computedNames])
|
|
267
|
+
const callsAwaitable = (code: string): boolean => {
|
|
268
|
+
// The common component with no async snippet and no computed pays zero regex work.
|
|
269
|
+
if (awaitableCallNames.size === 0) {
|
|
231
270
|
return false
|
|
232
271
|
}
|
|
233
|
-
for (const name of
|
|
272
|
+
for (const name of awaitableCallNames) {
|
|
234
273
|
if (callPattern(name).test(code)) {
|
|
235
274
|
return true
|
|
236
275
|
}
|
|
@@ -258,6 +297,26 @@ export function generateSSR(
|
|
|
258
297
|
const openRange = (target: string): string => push(target, RANGE_OPEN)
|
|
259
298
|
const closeRange = (target: string): string => push(target, RANGE_CLOSE)
|
|
260
299
|
|
|
300
|
+
/* Wrap a control-flow branch/row body in the render-path segment the CLIENT pushes for it (the
|
|
301
|
+
`withPathFrom`/`withPath` call the `each`/`when`/`switchBlock` dom runtimes make), so a scope
|
|
302
|
+
created inside — a nested `<Child/>`, always an inline `await` in SSR — composes the SAME
|
|
303
|
+
serialization-stable id on both sides (the warm-seed key). Emitted ONLY when the subtree can
|
|
304
|
+
create such a scope: a component render is the sole scope-creating construct in a branch/row
|
|
305
|
+
and always lowers to an inline `await`, so `subtreeAwaits` is the gate. A purely-synchronous
|
|
306
|
+
branch creates no scope, so its segment is inert — skipping the wrap keeps the static render
|
|
307
|
+
synchronous (no per-row closure/await, matching today's output byte-for-byte). `segment` is a
|
|
308
|
+
JS expression (a literal branch key, or the each row's key/index); `$$withPath` escapes it,
|
|
309
|
+
exactly like the client. The row/branch RANGE markers stay OUTSIDE this call (they create no
|
|
310
|
+
scope), so only the content composes the path — mirroring the client, where the markers are
|
|
311
|
+
built outside `withPathFrom`. */
|
|
312
|
+
const withPathBranch = (segment: string, children: TemplateNode[], target: string): string => {
|
|
313
|
+
const content = branchContent(children, target)
|
|
314
|
+
if (!subtreeAwaits(children)) {
|
|
315
|
+
return content
|
|
316
|
+
}
|
|
317
|
+
return `await $$withPath(${segment}, async () => {\n${content}});\n`
|
|
318
|
+
}
|
|
319
|
+
|
|
261
320
|
/* In a skeleton, a control-flow block or slot is positioned by an `<!--a-->` anchor
|
|
262
321
|
(cloned into the located parent), so it can sit anywhere among static siblings.
|
|
263
322
|
Emitted both sides in document order — the client's anchor scan lines up with it.
|
|
@@ -285,7 +344,7 @@ export function generateSSR(
|
|
|
285
344
|
Plain expressions stay sync, so a component with only interpolation keeps a
|
|
286
345
|
sync render. */
|
|
287
346
|
const lowered = lowerExpression(part.code)
|
|
288
|
-
const value =
|
|
347
|
+
const value = callsAwaitable(part.code)
|
|
289
348
|
? `$text(await (${lowered}))`
|
|
290
349
|
: `$text(${lowered})`
|
|
291
350
|
return markText.get(node)
|
|
@@ -295,31 +354,98 @@ export function generateSSR(
|
|
|
295
354
|
.join('')
|
|
296
355
|
}
|
|
297
356
|
if (node.kind === 'if') {
|
|
298
|
-
/* `case` children are the `elseif`/`else` branches in source order; the rest are
|
|
299
|
-
|
|
300
|
-
|
|
357
|
+
/* `case` children are the `elseif`/`else` branches in source order; the rest are the
|
|
358
|
+
`then` content. The whole `if`/`elseif`/`else` chain desugars to a run of `if` /
|
|
359
|
+
`else if` clauses. A branch whose condition is a bare async subject contributes TWO
|
|
360
|
+
clauses in order — an empty `$$cellPending` guard (renders nothing, and stops the
|
|
361
|
+
chain: a still-loading branch holds, so nothing later renders on its unknown value)
|
|
362
|
+
then the `$$readCell` truthy test — so sync and async branches interleave and each
|
|
363
|
+
resolves at render time, mirroring the client `switchBlock` cond-chain. */
|
|
301
364
|
const plan = ifPlan(node)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
365
|
+
/* The client build lowers a simple `if`/`else` through `when` (branch keys `'then'` /
|
|
366
|
+
`'else'`) and an `if` with any `elseif` through `switchBlock` over `[then, ...branches]`
|
|
367
|
+
(branch key = the case's array index). Mirror whichever the client picks so a nested
|
|
368
|
+
`<Child/>`'s scope id composes identically. */
|
|
369
|
+
const clauses: string[] = []
|
|
370
|
+
const conditional = (
|
|
371
|
+
condition: string,
|
|
372
|
+
asyncSubject: boolean | undefined,
|
|
373
|
+
children: TemplateNode[],
|
|
374
|
+
segment: string,
|
|
375
|
+
): void => {
|
|
376
|
+
if (asyncSubject === true) {
|
|
377
|
+
const cell = condition.trim()
|
|
378
|
+
clauses.push(`($$cellPending(${cell})) {\n}`)
|
|
379
|
+
clauses.push(
|
|
380
|
+
`($$readCell(${cell})) {\n${withPathBranch(segment, children, target)}}`,
|
|
381
|
+
)
|
|
382
|
+
} else {
|
|
383
|
+
clauses.push(
|
|
384
|
+
`(${lowerExpression(condition)}) {\n${withPathBranch(segment, children, target)}}`,
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/* `then` is the `when` `'then'` branch, or the `switchBlock` case at index 0. */
|
|
389
|
+
conditional(
|
|
390
|
+
node.condition,
|
|
391
|
+
node.asyncSubject,
|
|
392
|
+
plan.thenChildren,
|
|
393
|
+
JSON.stringify(plan.hasElseif ? '0' : 'then'),
|
|
394
|
+
)
|
|
395
|
+
for (let index = 0; index < plan.branches.length; index += 1) {
|
|
396
|
+
const branch = plan.branches[index] as Extract<TemplateNode, { kind: 'case' }>
|
|
397
|
+
if (branch.condition !== undefined) {
|
|
398
|
+
/* An `elseif` is the `switchBlock` case at `[then, ...branches]` index `index + 1`
|
|
399
|
+
(a chain with any condition always lowers to `switchBlock`). */
|
|
400
|
+
conditional(
|
|
401
|
+
branch.condition,
|
|
402
|
+
branch.asyncSubject,
|
|
403
|
+
branch.children,
|
|
404
|
+
JSON.stringify(String(index + 1)),
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
let code = clauses
|
|
409
|
+
.map((clause, index) => `${index === 0 ? 'if' : ' else if'} ${clause}`)
|
|
410
|
+
.join('')
|
|
411
|
+
if (plan.elseBranch !== undefined) {
|
|
412
|
+
/* `else` is the `when` `'else'` branch, or (for a cond-chain) the `switchBlock`
|
|
413
|
+
default at its own `[then, ...branches]` index. */
|
|
414
|
+
const elseSegment = plan.hasElseif
|
|
415
|
+
? String(plan.branches.indexOf(plan.elseBranch) + 1)
|
|
416
|
+
: 'else'
|
|
417
|
+
code += ` else {\n${withPathBranch(JSON.stringify(elseSegment), plan.elseBranch.children, target)}}`
|
|
308
418
|
}
|
|
309
419
|
return `${anchor}${openRange(target)}${code}\n${closeRange(target)}`
|
|
310
420
|
}
|
|
311
421
|
if (node.kind === 'switch') {
|
|
312
422
|
const plan = switchPlan(node)
|
|
313
|
-
|
|
423
|
+
/* A bare async subject: read the peek, but only match once the cell has settled —
|
|
424
|
+
a pending subject renders no case (matching the client's `switchBlock`). */
|
|
425
|
+
const subjectExpr =
|
|
426
|
+
node.asyncSubject === true
|
|
427
|
+
? `$$readCell(${node.subject.trim()})`
|
|
428
|
+
: `(${lowerExpression(node.subject)})`
|
|
429
|
+
let code = `{ const $s = ${subjectExpr};\n`
|
|
430
|
+
if (node.asyncSubject === true) {
|
|
431
|
+
code += `if (!$$cellPending(${node.subject.trim()})) {\n`
|
|
432
|
+
}
|
|
314
433
|
let started = false
|
|
315
|
-
|
|
434
|
+
/* The client `switchBlock` keys each branch by its index in `plan.cases` (source order,
|
|
435
|
+
the default at its own position); mirror that index so a nested `<Child/>` matches. */
|
|
436
|
+
for (let index = 0; index < plan.cases.length; index += 1) {
|
|
437
|
+
const branch = plan.cases[index] as Extract<TemplateNode, { kind: 'case' }>
|
|
316
438
|
if (branch.match !== undefined) {
|
|
317
|
-
code += `${started ? 'else ' : ''}if ($s === (${lowerExpression(branch.match)})) {\n${
|
|
439
|
+
code += `${started ? 'else ' : ''}if ($s === (${lowerExpression(branch.match)})) {\n${withPathBranch(JSON.stringify(String(index)), branch.children, target)}}\n`
|
|
318
440
|
started = true
|
|
319
441
|
}
|
|
320
442
|
}
|
|
321
443
|
if (plan.fallback !== undefined) {
|
|
322
|
-
|
|
444
|
+
const fallbackSegment = String(plan.cases.indexOf(plan.fallback))
|
|
445
|
+
code += `${started ? 'else ' : ''}{\n${withPathBranch(JSON.stringify(fallbackSegment), plan.fallback.children, target)}}\n`
|
|
446
|
+
}
|
|
447
|
+
if (node.asyncSubject === true) {
|
|
448
|
+
code += `}\n`
|
|
323
449
|
}
|
|
324
450
|
return `${anchor}${openRange(target)}${code}}\n${closeRange(target)}`
|
|
325
451
|
}
|
|
@@ -361,26 +487,45 @@ export function generateSSR(
|
|
|
361
487
|
if (plan.async) {
|
|
362
488
|
return anchor
|
|
363
489
|
}
|
|
490
|
+
/* The client `each` pushes a render-path segment per row (keyed → the `by` key evaluated
|
|
491
|
+
on the raw item, exactly like its `keyOf`; keyless → the row position), so a nested
|
|
492
|
+
`<Child/>`'s scope id composes identically. Mirror it only when the row can create a
|
|
493
|
+
scope (`subtreeAwaits` — it holds a component); a static row needs no segment and stays
|
|
494
|
+
synchronous. A keyless row that needs its position reuses a bound `index`, else a
|
|
495
|
+
synthesized loop index — the same 0-based `entries()` position the client's row cell
|
|
496
|
+
carries, so the segments agree. */
|
|
497
|
+
const rowSegments = subtreeAwaits(plan.children)
|
|
498
|
+
const keylessIndex =
|
|
499
|
+
plan.key === undefined && plan.index === undefined && rowSegments
|
|
500
|
+
? nextVar('$i')
|
|
501
|
+
: undefined
|
|
502
|
+
const indexBinding = plan.index ?? keylessIndex
|
|
364
503
|
/* The row item (and index) are real `for`-loop locals, so the body must lower
|
|
365
504
|
references to them as the bare identifier — `withBindings` registers the plan's
|
|
366
505
|
row bindings (under `plain`, SSR's only kind) so a row binding that shadows a
|
|
367
506
|
same-named component signal reads the loop value, not the (whole-list) signal it
|
|
368
507
|
shadows. The names come straight from `plan.bindings` (the single source the
|
|
369
508
|
client also reads); the items expression stays outside the shadow. */
|
|
370
|
-
const rowBody = withBindings(
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
509
|
+
const rowBody = withBindings(withShadow, plan.bindings, ssrBindingKind, () => {
|
|
510
|
+
/* The row's render-path segment, lowered inside the row's plain shadow so a keyed
|
|
511
|
+
`by` reads the raw loop item exactly like the client `keyOf` (a keyless row uses
|
|
512
|
+
its position). Unused when `rowSegments` is false — `withPathBranch` then skips it. */
|
|
513
|
+
const segment =
|
|
514
|
+
plan.key === undefined
|
|
515
|
+
? (indexBinding ?? '0')
|
|
516
|
+
: `(${lowerExpression(plan.key)})`
|
|
517
|
+
return `${openRange(target)}${withPathBranch(segment, plan.children, target)}${closeRange(target)}`
|
|
518
|
+
})
|
|
377
519
|
/* `index="i"` binds the row position. SSR reads it as a plain number from
|
|
378
520
|
`entries()` over a materialized array; the client reads the same number from a
|
|
379
521
|
cell, so first paint is congruent. No index → a plain `for…of` over the items. */
|
|
522
|
+
/* An undefined source renders no rows, not a throw — a `{#for x in promise}`
|
|
523
|
+
whose lifted source peeks undefined while pending (ADR-0032 D3). Mirrors the
|
|
524
|
+
client `each`'s undefined-as-empty guard. */
|
|
380
525
|
const header =
|
|
381
|
-
|
|
382
|
-
? `for (const ${plan.as} of (${lowerExpression(plan.items)}))`
|
|
383
|
-
: `for (const [${
|
|
526
|
+
indexBinding === undefined
|
|
527
|
+
? `for (const ${plan.as} of ((${lowerExpression(plan.items)}) ?? []))`
|
|
528
|
+
: `for (const [${indexBinding}, ${plan.as}] of [...((${lowerExpression(plan.items)}) ?? [])].entries())`
|
|
384
529
|
return `${anchor}${header} {\n${rowBody}}\n`
|
|
385
530
|
}
|
|
386
531
|
if (node.kind === 'await') {
|
|
@@ -396,7 +541,7 @@ export function generateSSR(
|
|
|
396
541
|
/* Server-render the child via its `render` and inline the HTML inside the same
|
|
397
542
|
`[ … ]` marker range the client mounts into (`mountRange`) — no wrapper element,
|
|
398
543
|
so SSR and client agree and the child's root lays out as a direct child. Props
|
|
399
|
-
pass as thunks; slot content passes as a string-returning
|
|
544
|
+
pass as thunks; slot content passes as a string-returning `children` the child
|
|
400
545
|
invokes from its <slot>. */
|
|
401
546
|
/* Slot content is a fresh build context — the child's `<slot>` mounts it via
|
|
402
547
|
`mountSlot`, not the parent skeleton clone, and the client builds it through
|
|
@@ -404,33 +549,72 @@ export function generateSSR(
|
|
|
404
549
|
records it reset, so its children emit no enclosing-skeleton anchors the client
|
|
405
550
|
slot builder would lack. */
|
|
406
551
|
const slotCode = generateInto(node.children, '$slot')
|
|
407
|
-
/*
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
552
|
+
/* Slot content rides the `children` prop key as a `Snippet`: a zero-arg callable
|
|
553
|
+
returning an ASYNC builder the child `await`s at its `{children()}` position
|
|
554
|
+
(`generateSlot`), whose resolved value is a `$snip`-branded string — so it renders
|
|
555
|
+
through the same `$text` snippet-marker path as any `{snippet(args)}` and unifies
|
|
556
|
+
with a passed `children={snippet}`. It is NOT pre-resolved: pre-resolving here would
|
|
557
|
+
run the slot's `$ctx.next++` block ids BEFORE the child render's own, but the client
|
|
558
|
+
builds slot content lazily at the `{children()}` site — so a child with an await/try
|
|
559
|
+
before its slot would allocate ids in the opposite order and desync hydration.
|
|
560
|
+
Keeping the slot lazy draws its ids at the `{children()}` site on both sides. The
|
|
561
|
+
builder shares the enclosing render's `$ctx`/`$awaits`/`$resume` (a closure), so
|
|
562
|
+
nested awaits register and number correctly during the child render. A child with a
|
|
563
|
+
slot fill is therefore always an async render. */
|
|
416
564
|
const slotPart =
|
|
417
565
|
slotCode.trim() === ''
|
|
418
566
|
? undefined
|
|
419
|
-
: `"
|
|
567
|
+
: `"children": () => (async () => { const $slot = []; ${slotCode}return $snip($slot.join('')); })`
|
|
420
568
|
/* The same last-wins layering the client build emits (`composeProps`), so SSR
|
|
421
569
|
and hydration read the same prop bag. */
|
|
422
|
-
const propsExpr = composeProps(
|
|
570
|
+
const propsExpr = composeProps(
|
|
571
|
+
node.props,
|
|
572
|
+
lowerExpression,
|
|
573
|
+
slotPart,
|
|
574
|
+
bindRead,
|
|
575
|
+
bindWrite,
|
|
576
|
+
)
|
|
423
577
|
/* Render the child (awaited — render is async) sharing this render's `$ctx`,
|
|
424
578
|
so its `await`/`try` block ids draw from the same depth-first counter,
|
|
425
579
|
unique across page + children, and the streamed fragments resolve into the
|
|
426
580
|
right boundaries. MERGE its streaming awaits into `$awaits` and its inline
|
|
427
581
|
blocking values into `$resume`. ($awaits/$resume are captured from the
|
|
428
582
|
enclosing render body, including from branch closures.) */
|
|
583
|
+
/* The tag lowers like any reference (see generateBuild): a static import is left bare, a
|
|
584
|
+
reactive/loop/await binding derefs — SSR registers such a binding as `plain`, so it
|
|
585
|
+
reads the bare local holding the resolved component, keeping SSR and client congruent.
|
|
586
|
+
Root the child's render-path at this mount site's source-order ordinal — the same
|
|
587
|
+
segment the client's `mountChild` pushes — so the child's cells and block ids (ADR-0037)
|
|
588
|
+
get an id matching the client's. `$$withPath` sets the path across the child's awaits. */
|
|
589
|
+
const ordinal = childOrdinal++
|
|
590
|
+
const renderExpr = `$$withPath(${ordinal}, () => ${lowerExpression(node.name)}.render(${propsExpr}, $ctx))`
|
|
591
|
+
if (hoistableChildSet.has(node)) {
|
|
592
|
+
/* Hoistable (ADR-0037/0039): start the render as an isolated flight in the prefix, and
|
|
593
|
+
RESERVE its output position rather than awaiting inline. After the walk,
|
|
594
|
+
`finalizeStreamedChildren` fills the slot — inline `<!--[-->html<!--]-->` if the
|
|
595
|
+
flight already settled (byte-identical to the old inline await), or a streaming
|
|
596
|
+
`abide:await:CHILDPATH` boundary + html-only await if it is still pending. The
|
|
597
|
+
boundary id is the child's render-path (`$$renderPath(ordinal)`), congruent with the
|
|
598
|
+
client's `mountStreamedChild`. */
|
|
599
|
+
const flightName = `$cf${childFlightCounter++}`
|
|
600
|
+
childFlightDecls.push(
|
|
601
|
+
`const ${flightName} = $$flight(() => $$isolateCellBarrier(() => ${renderExpr}));`,
|
|
602
|
+
)
|
|
603
|
+
const slotVar = nextVar('$slot')
|
|
604
|
+
return (
|
|
605
|
+
anchor +
|
|
606
|
+
`${target}.push('');\n` +
|
|
607
|
+
`const ${slotVar} = ${target}.length - 1;\n` +
|
|
608
|
+
`$childSlots.push({ slot: ${slotVar}, out: ${target}, id: $$renderPath(${ordinal}), flight: ${flightName} });\n`
|
|
609
|
+
)
|
|
610
|
+
}
|
|
611
|
+
/* Non-hoistable child (off the top-level spine): await inline at its structural position,
|
|
612
|
+
sequential, as before — never streams. */
|
|
429
613
|
const result = nextVar('$child')
|
|
430
614
|
return (
|
|
431
615
|
anchor +
|
|
432
616
|
push(target, RANGE_OPEN) +
|
|
433
|
-
`const ${result} = await ${
|
|
617
|
+
`const ${result} = await ${renderExpr};\n` +
|
|
434
618
|
`${target}.push(${result}.html);\n` +
|
|
435
619
|
`for (const $a of ${result}.awaits) { $awaits.push($a); }\n` +
|
|
436
620
|
`Object.assign($resume, ${result}.resume);\n` +
|
|
@@ -572,26 +756,23 @@ export function generateSSR(
|
|
|
572
756
|
return code
|
|
573
757
|
}
|
|
574
758
|
|
|
575
|
-
/* A
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
759
|
+
/* A component `{children()}` fill point: render the `children` prop (a `Snippet`) as a
|
|
760
|
+
snippet text part. `children()()` reads the destructured `children` computed and calls
|
|
761
|
+
it → a Promise of a `$snip`-branded string; `await` it and push through `$text`, which
|
|
762
|
+
wraps the branded string in `<!--abide:snippet-->` markers — the same range the client's
|
|
763
|
+
appendText→appendSnippet emits and claims, so hydration stays congruent. Inside a skeleton
|
|
764
|
+
the slot is positioned by an `<!--a-->` anchor and bounded by a `[ … ]` range (matching the
|
|
765
|
+
client's `mountSlot`); outside one it emits just the snippet markers (matching the client's
|
|
766
|
+
direct `appendText`). The `await` makes a component with a slot an async render (its caller
|
|
767
|
+
already `await`s `render()`). A fallback is now an authored `{#if children}…{:else}…{/if}`,
|
|
768
|
+
so the slot node carries no children. */
|
|
582
769
|
function generateSlot(
|
|
583
770
|
node: Extract<TemplateNode, { kind: 'element' }>,
|
|
584
771
|
target: string,
|
|
585
772
|
anchor: string,
|
|
586
773
|
): string {
|
|
587
774
|
const wrap = inSkeleton.get(node)
|
|
588
|
-
|
|
589
|
-
slot content's block ids allocate AT the slot position — the same order the
|
|
590
|
-
client builds slot content — keeping hydration congruent. The `await` makes a
|
|
591
|
-
component with a slot an async render (its caller already `await`s `render()`).
|
|
592
|
-
A fallback is now an authored `{#if children}…{:else}…{/if}`, so the slot node
|
|
593
|
-
carries no children. */
|
|
594
|
-
const body = `if ($props && $props.$children) { ${target}.push(await $props.$children()); }\n`
|
|
775
|
+
const body = `${target}.push($text(await (${lowerExpression('children()')})));\n`
|
|
595
776
|
if (!wrap) {
|
|
596
777
|
return body
|
|
597
778
|
}
|
|
@@ -632,10 +813,13 @@ export function generateSSR(
|
|
|
632
813
|
`withBindings` shadow models, so a reference to the binding reads the plain
|
|
633
814
|
local rather than the (unresolved) component signal it shadows. */
|
|
634
815
|
const resolved = nextVar('$av')
|
|
635
|
-
|
|
816
|
+
/* ADR-0034: a hoisted block awaits the prefix flight const (already in-flight); a
|
|
817
|
+
non-hoisted block evaluates + awaits its promise inline as before. */
|
|
818
|
+
const hoistedPromise = flightNameByNode.get(node)
|
|
819
|
+
let code = `const ${id} = $$blockId($ctx);\n`
|
|
636
820
|
code += `${target}.push("<!--abide:await:" + ${id} + "-->");\n`
|
|
637
821
|
code += `try {\n`
|
|
638
|
-
code += `const ${resolved} = await (${lowerExpression(node.promise)});\n`
|
|
822
|
+
code += `const ${resolved} = await (${hoistedPromise ?? lowerExpression(node.promise)});\n`
|
|
639
823
|
code += `{\n`
|
|
640
824
|
code += `const ${plan.resolvedAs} = ${resolved};\n`
|
|
641
825
|
code += withBindings(withShadow, plan.resolvedBindings, ssrBindingKind, () =>
|
|
@@ -681,7 +865,7 @@ export function generateSSR(
|
|
|
681
865
|
): string {
|
|
682
866
|
const plan = awaitPlan(node)
|
|
683
867
|
const id = nextVar('$aid')
|
|
684
|
-
let code = `const ${id} = $ctx
|
|
868
|
+
let code = `const ${id} = $$blockId($ctx);\n`
|
|
685
869
|
code += `${target}.push("<!--abide:await:" + ${id} + "-->");\n`
|
|
686
870
|
code += branchContent(plan.pending, target)
|
|
687
871
|
code += `${target}.push("<!--/abide:await:" + ${id} + "-->");\n`
|
|
@@ -701,26 +885,34 @@ export function generateSSR(
|
|
|
701
885
|
const catchProp = plan.surfaceRejection
|
|
702
886
|
? ''
|
|
703
887
|
: `catch: ${settled(plan.catchAs, plan.catchBindings, plan.catchChildren)} `
|
|
888
|
+
/* ADR-0034: a hoisted streaming block's thunk returns the prefix flight const (started
|
|
889
|
+
in the prefix, so it overlaps a preceding blocking await); non-hoisted evaluates lazily
|
|
890
|
+
at drain time as before. */
|
|
891
|
+
const hoistedPromise = flightNameByNode.get(node)
|
|
704
892
|
code +=
|
|
705
893
|
`$awaits.push({ id: ${id}, ` +
|
|
706
|
-
`promise: () => (${lowerExpression(node.promise)}), ` +
|
|
894
|
+
`promise: () => (${hoistedPromise ?? lowerExpression(node.promise)}), ` +
|
|
707
895
|
`then: ${settled(plan.resolvedAs, plan.resolvedBindings, plan.resolvedChildren)}, ` +
|
|
708
896
|
`${catchProp}});\n`
|
|
709
897
|
return code
|
|
710
898
|
}
|
|
711
899
|
|
|
712
|
-
/* A
|
|
713
|
-
try/catch; on a throw, truncate the output back to the
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
900
|
+
/* A reactive error boundary: push the guarded markup (++ finally) inside a real
|
|
901
|
+
try/catch; on a throw, truncate the output back to the range start and push the
|
|
902
|
+
catch markup (++ finally) instead — so even mid-stream a render throw becomes catch
|
|
903
|
+
markup, not a broken response. No catch re-throws (propagates to an enclosing boundary
|
|
904
|
+
/ the 500 / the stream). The `abide:try:N` boundary comments let hydration discard the
|
|
905
|
+
server content if the client adoption fails; the inner `[ … ]` range markers are the
|
|
906
|
+
live range the render-many client (`tryBlock`) tracks — emitted here so a FRESH client
|
|
907
|
+
mount (which brackets its branch) and the SSR markup stay congruent. */
|
|
718
908
|
function generateTry(node: Extract<TemplateNode, { kind: 'try' }>, target: string): string {
|
|
719
909
|
const plan = tryPlan(node)
|
|
720
910
|
const id = nextVar('$tid')
|
|
721
911
|
const mark = nextVar('$trim')
|
|
722
|
-
let code = `const ${id} = $ctx
|
|
912
|
+
let code = `const ${id} = $$blockId($ctx);\n`
|
|
723
913
|
code += `${target}.push("<!--abide:try:" + ${id} + "-->");\n`
|
|
914
|
+
code += openRange(target)
|
|
915
|
+
/* Truncate back to just after the range-open marker on a catch, so `[` survives. */
|
|
724
916
|
code += `const ${mark} = ${target}.length;\n`
|
|
725
917
|
code += `try {\n`
|
|
726
918
|
code += branchContent(plan.guarded, target)
|
|
@@ -735,9 +927,28 @@ export function generateSSR(
|
|
|
735
927
|
code += `throw ${plan.catchAs};\n`
|
|
736
928
|
}
|
|
737
929
|
code += `}\n`
|
|
930
|
+
code += closeRange(target)
|
|
738
931
|
code += `${target}.push("<!--/abide:try:" + ${id} + "-->");\n`
|
|
739
932
|
return code
|
|
740
933
|
}
|
|
741
934
|
|
|
742
|
-
|
|
935
|
+
/* The body walk emits `await $flightN` / `promise: () => $flightN` for hoisted nodes; the
|
|
936
|
+
matching prefix declarations (lowered at component scope — hoisted promises reference only
|
|
937
|
+
component-scope names, never a branch shadow, so this lowering matches the body's) are
|
|
938
|
+
returned for compileSSR to place after the lowered script and before the barrier. */
|
|
939
|
+
const body = generateInto(rootNodes, '$out')
|
|
940
|
+
let flightDecls = ''
|
|
941
|
+
for (const [node, name] of flightNameByNode) {
|
|
942
|
+
flightDecls += `const ${name} = $$flight(() => (${lowerExpression(node.promise)}));\n`
|
|
943
|
+
}
|
|
944
|
+
/* Child-render flights emitted during the body walk above (ADR-0037 Phase 2) — appended after
|
|
945
|
+
the await flights; both are independent prefix promise-starts, so order between them is free. */
|
|
946
|
+
for (const decl of childFlightDecls) {
|
|
947
|
+
flightDecls += `${decl}\n`
|
|
948
|
+
}
|
|
949
|
+
/* `hasStagedChildren` tells compileSSR to declare `$childSlots` and emit the post-walk
|
|
950
|
+
`$$finalizeStreamedChildren` await (ADR-0039); a component with no hoistable child stays
|
|
951
|
+
synchronous. Each hoistable child pushed one `childFlightDecls` entry, so its length is the
|
|
952
|
+
staged-child count. */
|
|
953
|
+
return { body, flightDecls, hasStagedChildren: childFlightDecls.length > 0 }
|
|
743
954
|
}
|