@abide/abide 0.49.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 +70 -52
- package/CHANGELOG.md +177 -0
- package/README.md +12 -7
- package/package.json +14 -5
- package/src/abideLsp.ts +5 -6
- package/src/abideResolverPlugin.ts +197 -169
- package/src/build.ts +79 -56
- package/src/buildDisconnected.ts +0 -2
- package/src/checkAbide.ts +12 -2
- 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/spawnEmbeddedServer.ts +13 -4
- package/src/lib/bundle/waitForServer.ts +6 -1
- package/src/lib/mcp/mcpTools.ts +15 -6
- 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 +98 -13
- package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
- 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/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 +14 -14
- 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 +4 -1
- 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/changeAffectsClient.ts +12 -7
- package/src/lib/shared/createCacheStore.ts +31 -8
- 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/{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/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/snapshotShippable.ts +8 -7
- 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/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 -8
- 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/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 +51 -12
- package/src/lib/ui/compile/compileShadow.ts +145 -18
- package/src/lib/ui/compile/composeProps.ts +12 -2
- 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 +418 -36
- package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
- package/src/lib/ui/compile/generateBuild.ts +52 -9
- package/src/lib/ui/compile/generateSSR.ts +242 -43
- 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/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 +26 -23
- 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 -1092
- 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 +21 -22
- 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 +43 -94
- 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/disposeRange.ts +2 -1
- package/src/lib/ui/dom/each.ts +26 -4
- package/src/lib/ui/dom/eachAsync.ts +39 -3
- 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/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/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/switchBlock.ts +30 -7
- package/src/lib/ui/dom/tryBlock.ts +230 -66
- 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 +2 -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/remoteProxy.ts +84 -159
- package/src/lib/ui/renderChain.ts +49 -13
- package/src/lib/ui/renderToStream.ts +22 -18
- package/src/lib/ui/resumeSeedScript.ts +1 -1
- package/src/lib/ui/router.ts +63 -36
- package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
- package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
- 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 +1 -1
- 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 +3 -42
- 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 +16 -6
- 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/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/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/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/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/mutateDocArray.ts +0 -38
- 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 -108
- package/src/lib/ui/installHotBridge.ts +0 -95
- 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 -43
- 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
|
@@ -6,32 +6,49 @@ import { HttpError } from '../shared/HttpError.ts'
|
|
|
6
6
|
import { OFFLINE_HEADER } from '../shared/OFFLINE_HEADER.ts'
|
|
7
7
|
import { rpcTimeoutSlot } from '../shared/rpcTimeoutSlot.ts'
|
|
8
8
|
import { trace } from '../shared/trace.ts'
|
|
9
|
+
import type { CachePolicy } from '../shared/types/CachePolicy.ts'
|
|
9
10
|
import type { HttpMethod } from '../shared/types/HttpMethod.ts'
|
|
10
|
-
import type {
|
|
11
|
+
import type { OutputWirePlan } from '../shared/types/OutputWirePlan.ts'
|
|
11
12
|
import type { RemoteFunction } from '../shared/types/RemoteFunction.ts'
|
|
12
13
|
import type { RpcOptions } from '../shared/types/RpcOptions.ts'
|
|
13
|
-
import {
|
|
14
|
+
import type { StandardSchemaV1 } from '../shared/types/StandardSchemaV1.ts'
|
|
15
|
+
import type { StreamPolicy } from '../shared/types/StreamPolicy.ts'
|
|
16
|
+
import { validationHttpError } from '../shared/validationHttpError.ts'
|
|
14
17
|
import { withBase } from '../shared/withBase.ts'
|
|
15
|
-
import { createOutboxQueue, type OutboxQueue } from './rpcOutbox/createOutboxQueue.ts'
|
|
16
|
-
import { outboxRegistry } from './rpcOutbox/outboxRegistry.ts'
|
|
17
18
|
import { currentAbortSignal } from './runtime/currentAbortSignal.ts'
|
|
18
19
|
import { REQUEST_SUPERSEDED } from './runtime/REQUEST_SUPERSEDED.ts'
|
|
19
|
-
import type { PersistenceStore } from './types/PersistenceStore.ts'
|
|
20
20
|
import { watch } from './watch.ts'
|
|
21
21
|
|
|
22
|
-
/* The
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export type DurableOptions = {
|
|
32
|
-
outbox?: boolean
|
|
22
|
+
/* The proxy's third argument. ADR-0022 D2: the client rpc transform passes the endpoint's LIVE
|
|
23
|
+
`opts` object here verbatim, so the type widens to the endpoint opts shape — remoteProxy reads
|
|
24
|
+
only the keys below and IGNORES the rest (`crossOrigin` / `timeout` / `maxBodySize`), which ride
|
|
25
|
+
along harmlessly. `streaming: true` (handler returns jsonl()/sse(), build-injected) makes the
|
|
26
|
+
bare call return the NamedAsyncIterable directly; `cache` / `stream` carry the endpoint's declared
|
|
27
|
+
policy (ADR-0020) so the client honours the ttl (staleness/SWR), the refetch clock
|
|
28
|
+
(throttle/debounce), and tags. `schemas` — its live `input` validator now reaches the stub (ADR-0022
|
|
29
|
+
D2) — drives the ADR-0026 client-side pre-flight. */
|
|
30
|
+
export type RemoteProxyOptions<Args = unknown> = {
|
|
33
31
|
streaming?: boolean
|
|
34
|
-
|
|
32
|
+
/* The client output wire codec plan (ADR-0029) the resolver plugin baked onto the stub — the
|
|
33
|
+
handler's structured success fields, so the proxy revives a `Set`/`Map`/`bigint`/`Date` off a
|
|
34
|
+
decoded response. Absent on the fail-open path (a response array then stays an array). */
|
|
35
|
+
outputWirePlan?: OutputWirePlan
|
|
36
|
+
cache?: CachePolicy<Args>
|
|
37
|
+
stream?: StreamPolicy
|
|
38
|
+
/* The endpoint's live schema group (ADR-0022 D2 forwards it verbatim). `input` is the
|
|
39
|
+
Standard Schema the ADR-0026 pre-flight validates the typed args against. */
|
|
40
|
+
schemas?: {
|
|
41
|
+
input?: StandardSchemaV1
|
|
42
|
+
output?: StandardSchemaV1
|
|
43
|
+
files?: StandardSchemaV1
|
|
44
|
+
}
|
|
45
|
+
/* Ignored endpoint opts keys, present only so the author's live `opts` object type-checks
|
|
46
|
+
when the client transform forwards it verbatim (ADR-0022). `clients` (surface flags) is
|
|
47
|
+
read only on the server. */
|
|
48
|
+
clients?: unknown
|
|
49
|
+
crossOrigin?: unknown
|
|
50
|
+
timeout?: unknown
|
|
51
|
+
maxBodySize?: unknown
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
/*
|
|
@@ -46,40 +63,33 @@ side.
|
|
|
46
63
|
the query string (GET/DELETE/HEAD). Plain `fn(args)` decodes the Response
|
|
47
64
|
by Content-Type and throws HttpError on non-2xx; `.raw(args)` is the
|
|
48
65
|
escape hatch that returns the Response untouched.
|
|
49
|
-
|
|
50
|
-
A durable (`outbox: true`) rpc is still a normal RemoteFunction — it fetches and
|
|
51
|
-
throws exactly the same. The differences: when the server can't be reached
|
|
52
|
-
(a transport failure, or a 502/503/504/52x), the request is `park`ed onto the
|
|
53
|
-
RPC's app-owned outbox as a SIDE-EFFECT and the throw is a `kind: 'queued'`
|
|
54
|
-
HttpError; and once a backlog exists, a fresh call parks straight to the TAIL
|
|
55
|
-
(no fetch) so writes can't land out of order. The parked write waits for
|
|
56
|
-
`rpc.outbox.retry()` (or the global `outbox.retry()`) — there is no auto-drain;
|
|
57
|
-
the app owns when to replay. `rpc.outbox()` exposes the queue.
|
|
58
66
|
*/
|
|
59
67
|
// @documentation plumbing
|
|
60
68
|
export function remoteProxy<Args, Return>(
|
|
61
69
|
method: HttpMethod,
|
|
62
70
|
url: string,
|
|
63
|
-
|
|
64
|
-
): RemoteFunction<Args, Return
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
url: string,
|
|
73
|
-
durable?: DurableOptions,
|
|
74
|
-
): RemoteFunction<Args, Return, Record<never, never>, boolean> {
|
|
75
|
-
/* Assigned after `createRemoteFunction` so the invoke closure (which runs later, per
|
|
76
|
-
call) parks through the shared queue; undefined leaves the plain fetch path. */
|
|
77
|
-
let queue: OutboxQueue<Args> | undefined
|
|
71
|
+
options?: RemoteProxyOptions<Args>,
|
|
72
|
+
): RemoteFunction<Args, Return> {
|
|
73
|
+
/* ADR-0026 client-side pre-flight: validate the typed args against the endpoint's input
|
|
74
|
+
schema (forwarded to the stub, ADR-0022 D2) BEFORE the fetch whenever one is present — always
|
|
75
|
+
on, no opt-in. This is a UX optimization ONLY: the server's unconditional inputSchema validate
|
|
76
|
+
→ 422 (defineRpc.ts `validateThenHandle`) stays the trust boundary, so a client that skips or
|
|
77
|
+
fakes this check is still fully validated on the server. undefined here (no input schema)
|
|
78
|
+
keeps today's behaviour — the client serializes and sends unvalidated. */
|
|
79
|
+
const preflightSchema = options?.schemas?.input
|
|
78
80
|
const fn = createRemoteFunction<Args, Return>({
|
|
79
81
|
method,
|
|
80
82
|
url,
|
|
81
83
|
clients: browserClientFlags,
|
|
82
|
-
streaming:
|
|
84
|
+
streaming: options?.streaming ?? false,
|
|
85
|
+
/* The client revives a decoded response's structured fields through this plan (ADR-0029
|
|
86
|
+
output path); undefined leaves the honest-JSON body untouched. */
|
|
87
|
+
outputWirePlan: options?.outputWirePlan,
|
|
88
|
+
/* Endpoint policy the resolver plugin spliced onto the stub — governs client cache
|
|
89
|
+
behaviour (ttl/staleness, refetch clock, tags). createRemoteFunction stamps it onto
|
|
90
|
+
`fn.cache` / `fn.stream` so readThrough reads it as the bottom policy layer. */
|
|
91
|
+
cache: options?.cache,
|
|
92
|
+
stream: options?.stream,
|
|
83
93
|
/*
|
|
84
94
|
The Request URL carries the mount base so the fetch routes through the
|
|
85
95
|
proxy (/v2/rpc/…); the cache key keeps the bare `url` (keyForRemoteCall
|
|
@@ -96,56 +106,42 @@ export function remoteProxy<Args, Return>(
|
|
|
96
106
|
/*
|
|
97
107
|
Forcing `getRequest()` once builds the Request and seeds the cache meta thunk in
|
|
98
108
|
createRemoteFunction with the same instance, so cache() readers don't reconstruct
|
|
99
|
-
it.
|
|
100
|
-
`queued`-tagged HttpError — `fetch` consumes the original (its body stream is read
|
|
101
|
-
and locked), so parking that same instance would leave the queue a request a resend
|
|
102
|
-
can't reconstruct and a capture can't read. The clone is parked, the original is
|
|
103
|
-
sent. The throw lets the caller branch on `error.kind === 'queued'` (parked, will
|
|
104
|
-
retry) vs. a real server rejection; `error.data` is the parked entry, so a caller
|
|
105
|
-
can `await (error.data as OutboxEntry).settled` for the eventual outcome.
|
|
109
|
+
it.
|
|
106
110
|
*/
|
|
107
111
|
invoke: (args, getRequest, opts) => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
112
|
+
/* ADR-0026 D2/D3: an input schema present → validate the TYPED args (pre-serialization
|
|
113
|
+
— NOT the string-shaped serialized form, which sidesteps parseArgs's query-coercion
|
|
114
|
+
gap) before any fetch. The validate may return a Promise (StandardSchemaV1), so await
|
|
115
|
+
it (D3). On a returned failure throw an HttpError shaped IDENTICALLY to the server's
|
|
116
|
+
422 (validationHttpError) and make NO fetch — saving the round-trip. */
|
|
117
|
+
if (preflightSchema !== undefined) {
|
|
118
|
+
return (
|
|
119
|
+
Promise.resolve()
|
|
120
|
+
.then(() => preflightSchema['~standard'].validate(args))
|
|
121
|
+
.then(
|
|
122
|
+
(result) => {
|
|
123
|
+
/* A returned `issues` result is a definitive "this input is invalid" —
|
|
124
|
+
throw the same 422-shaped HttpError the server would (validationHttpError)
|
|
125
|
+
so it rejects the chain and makes NO fetch, saving the round-trip. */
|
|
126
|
+
if (result.issues) {
|
|
127
|
+
throw validationHttpError(result.issues)
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
() => {
|
|
131
|
+
/* A validate REJECTION (not an `issues` verdict) means the validator itself
|
|
132
|
+
could not run here (a non-portable / async-resource refinement), which is
|
|
133
|
+
NOT a verdict on the input: swallow it and fall through to the fetch, letting
|
|
134
|
+
the server (the authoritative validator) decide. So a schema can never break
|
|
135
|
+
a call merely by failing to run client-side. */
|
|
136
|
+
},
|
|
137
|
+
)
|
|
138
|
+
/* Fetch exactly ONCE, only after validation settles — outside the validate handlers
|
|
139
|
+
so a fetch/timeout rejection propagates to the caller untouched rather than being
|
|
140
|
+
mistaken for a "validator couldn't run" fall-through and resending the request. */
|
|
141
|
+
.then(() => fetchWithTimeout(getRequest(), opts))
|
|
123
142
|
)
|
|
124
143
|
}
|
|
125
|
-
|
|
126
|
-
const parkable = request.clone()
|
|
127
|
-
return fetchWithTimeout(request, opts).then(
|
|
128
|
-
(response) => {
|
|
129
|
-
if (UNREACHABLE_STATUSES.has(response.status)) {
|
|
130
|
-
throw queuedThrow(
|
|
131
|
-
queue,
|
|
132
|
-
args as Args,
|
|
133
|
-
parkable,
|
|
134
|
-
response,
|
|
135
|
-
new HttpError(response.clone()),
|
|
136
|
-
)
|
|
137
|
-
}
|
|
138
|
-
return response
|
|
139
|
-
},
|
|
140
|
-
(error: unknown) => {
|
|
141
|
-
if (shouldParkRejection(error)) {
|
|
142
|
-
const response =
|
|
143
|
-
error instanceof HttpError ? error.response : unreachableResponse()
|
|
144
|
-
throw queuedThrow(queue, args as Args, parkable, response, error)
|
|
145
|
-
}
|
|
146
|
-
throw error
|
|
147
|
-
},
|
|
148
|
-
)
|
|
144
|
+
return fetchWithTimeout(getRequest(), opts)
|
|
149
145
|
},
|
|
150
146
|
})
|
|
151
147
|
/* Overwrite the inert `.watch` the shared attach bound: on the client the real reaction
|
|
@@ -159,80 +155,9 @@ export function remoteProxy<Args, Return>(
|
|
|
159
155
|
handler,
|
|
160
156
|
),
|
|
161
157
|
})
|
|
162
|
-
if (durable?.outbox === true) {
|
|
163
|
-
queue = getOrCreateOutboxQueue<Args, Return>(url, fn, durable)
|
|
164
|
-
Object.assign(fn, { outbox: outboxFace(queue) })
|
|
165
|
-
}
|
|
166
158
|
return fn
|
|
167
159
|
}
|
|
168
160
|
|
|
169
|
-
/* The synthetic "unreachable" Response a park reuses when there is no real one — a
|
|
170
|
-
transport failure (fetch rejected) or a backlog park that never fetched. */
|
|
171
|
-
function unreachableResponse(): Response {
|
|
172
|
-
return new Response('queued', { status: 503, statusText: 'Service Unavailable' })
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/* Park the unreachable request (`cause` becomes the entry's parked reason, `entry.error`)
|
|
176
|
-
and return the `kind: 'queued'` HttpError to throw — its `.data` is the parked entry, so
|
|
177
|
-
a caller can `await (error.data as OutboxEntry).settled` for the eventual delivered
|
|
178
|
-
result or server refusal. */
|
|
179
|
-
function queuedThrow<Args>(
|
|
180
|
-
queue: OutboxQueue<Args> | undefined,
|
|
181
|
-
args: Args,
|
|
182
|
-
request: Request,
|
|
183
|
-
response: Response,
|
|
184
|
-
cause: unknown,
|
|
185
|
-
): HttpError {
|
|
186
|
-
const entry = queue?.park(args, request, cause)
|
|
187
|
-
return new HttpError(response, QUEUED, entry)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/* A fetch REJECTION (no Response) the durable rpc should park: a transport failure or
|
|
191
|
-
the synthesized client-timeout 504. NOT a caller/scope abort — that's a deliberate
|
|
192
|
-
cancel, not the server being unreachable. (HTTP error STATUSES never reject — `fetch`
|
|
193
|
-
resolves with them — so 4xx/500 are classified on the response, not here.) */
|
|
194
|
-
function shouldParkRejection(error: unknown): boolean {
|
|
195
|
-
if (error === REQUEST_SUPERSEDED) {
|
|
196
|
-
return false
|
|
197
|
-
}
|
|
198
|
-
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
199
|
-
return false
|
|
200
|
-
}
|
|
201
|
-
if (error instanceof HttpError) {
|
|
202
|
-
return UNREACHABLE_STATUSES.has(error.response.status)
|
|
203
|
-
}
|
|
204
|
-
return true
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/* The single app-owned queue for a durable RPC url — created + registered on first use
|
|
208
|
-
so every call site (and the global `outbox()`) shares one queue. The send is a plain
|
|
209
|
-
`fetch`; createOutboxQueue rides the entry's abort signal on the resent Request and
|
|
210
|
-
keeps scope-abort + the client timeout out. */
|
|
211
|
-
function getOrCreateOutboxQueue<Args, Return>(
|
|
212
|
-
url: string,
|
|
213
|
-
rpc: RemoteFunction<Args, Return>,
|
|
214
|
-
durable: DurableOptions,
|
|
215
|
-
): OutboxQueue<Args> {
|
|
216
|
-
const existing = outboxRegistry.get(url)
|
|
217
|
-
if (existing !== undefined) {
|
|
218
|
-
return existing as OutboxQueue<Args>
|
|
219
|
-
}
|
|
220
|
-
const queue = createOutboxQueue<Args>({
|
|
221
|
-
url,
|
|
222
|
-
send: (request) => fetch(request),
|
|
223
|
-
store: durable.store,
|
|
224
|
-
})
|
|
225
|
-
outboxRegistry.register(url, queue as OutboxQueue<unknown>, rpc)
|
|
226
|
-
return queue
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/* The `.outbox` face: callable for the live entries, `retry()` to drain on demand. */
|
|
230
|
-
function outboxFace<Args>(queue: OutboxQueue<Args>): Outbox<Args> {
|
|
231
|
-
const face = (() => queue.entries()) as Outbox<Args>
|
|
232
|
-
face.retry = () => queue.retry()
|
|
233
|
-
return face
|
|
234
|
-
}
|
|
235
|
-
|
|
236
161
|
/*
|
|
237
162
|
Fetches under three optional aborts: the reactive scope that fired the call (so a
|
|
238
163
|
superseded/torn-down read cancels its in-flight request — currentAbortSignal), the
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { isolateCellBarrier } from './isolateCellBarrier.ts'
|
|
1
2
|
import { CHILD_PRESENT } from './runtime/CHILD_PRESENT.ts'
|
|
2
3
|
import { OUTLET_CLOSE, OUTLET_OPEN } from './runtime/OUTLET_MARKER.ts'
|
|
3
4
|
import type { RenderContext } from './runtime/types/RenderContext.ts'
|
|
4
5
|
import type { SsrRender } from './runtime/types/SsrRender.ts'
|
|
5
6
|
import type { UiComponent } from './runtime/types/UiComponent.ts'
|
|
6
7
|
import type { UiProps } from './runtime/types/UiProps.ts'
|
|
8
|
+
import { withPath } from './runtime/withPath.ts'
|
|
7
9
|
|
|
8
10
|
const OPEN = `<!--${OUTLET_OPEN}-->`
|
|
9
11
|
const CLOSE = `<!--${OUTLET_CLOSE}-->`
|
|
@@ -13,11 +15,13 @@ const OUTLET_PLACEHOLDER = `${OPEN}${CLOSE}`
|
|
|
13
15
|
/*
|
|
14
16
|
Server-renders a route's layout chain wrapped around its page into one SsrRender.
|
|
15
17
|
`views` is ordered outermost layout → … → page. The whole chain shares ONE request-local
|
|
16
|
-
block-id counter (`$ctx`): each `
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
block-id counter map (`$ctx`): each `await`/`try` block draws a path-namespaced id
|
|
19
|
+
(`${render-path}:${n}`, ADR-0037), so ids stay unique across layers by path rather than by
|
|
20
|
+
a shared sequential draw — keeping the streamed fragments and the resume manifest aligned
|
|
21
|
+
with the client (which composes the same path-keyed ids). The layers render IN PARALLEL
|
|
22
|
+
(`Promise.all`, ADR-0038): each roots a distinct route-key path so their block ids never collide,
|
|
23
|
+
and each runs under `isolateCellBarrier` so their async-cell barriers don't cross-drain — the html
|
|
24
|
+
fold + state/awaits/resume aggregation below run after all settle and are order-independent.
|
|
21
25
|
|
|
22
26
|
The html nests inner-to-outer: each parent layout's empty outlet boundary
|
|
23
27
|
(`<!--abide:outlet--><!--/abide:outlet-->`) is filled with the accumulated child html —
|
|
@@ -30,8 +34,12 @@ A layout missing its `<slot/>` is a build error surfaced here.
|
|
|
30
34
|
export async function renderChain(
|
|
31
35
|
views: UiComponent[],
|
|
32
36
|
params: Record<string, string>,
|
|
37
|
+
/* Each view's route key (layout directory URL / page route pattern), aligned 1:1 with `views`
|
|
38
|
+
and byte-identical to the client router's `chainKeys`/`pageKey`. Each roots its layer's
|
|
39
|
+
render-path (`withPath`) so a cell's scope id matches the client's for the warm-seed key. */
|
|
40
|
+
keys: string[] = [],
|
|
33
41
|
): Promise<SsrRender> {
|
|
34
|
-
const ctx: RenderContext =
|
|
42
|
+
const ctx: RenderContext = new Map()
|
|
35
43
|
const renders: SsrRender[] = []
|
|
36
44
|
/* Route params as thunks (static server-side — only shape parity with the client so
|
|
37
45
|
`props()` reads `$props["id"]?.()` resolve). A layout (every view but the last) also
|
|
@@ -43,13 +51,41 @@ export async function renderChain(
|
|
|
43
51
|
for (const key of Object.keys(params)) {
|
|
44
52
|
paramThunks[key] = () => params[key]
|
|
45
53
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
if (views.length > 1) {
|
|
55
|
+
/* ADR-0038: a route WITH layouts renders its layers IN PARALLEL. Block ids are path-keyed
|
|
56
|
+
(ADR-0037) and each layer roots a DISTINCT route-key path, so their id allocations never
|
|
57
|
+
collide even as the async continuations interleave; the fold + state/awaits/resume
|
|
58
|
+
aggregation below run AFTER all settle and are order-independent (keyed merges /
|
|
59
|
+
index-ordered). Each layer runs under `isolateCellBarrier` so its async-cell barrier drains
|
|
60
|
+
its OWN pending list — without it, two layers registering cells concurrently into the one
|
|
61
|
+
request-scoped list would splice-drain each other (the hazard ADR-0037 fixed for sibling
|
|
62
|
+
children). Scope needs no isolation: the shipped parallel child renders tolerate the
|
|
63
|
+
identical per-request CURRENT_SCOPE clobber (all scope-sensitive construction is
|
|
64
|
+
synchronous in each render's prefix). */
|
|
65
|
+
const collected = await Promise.all(
|
|
66
|
+
views.map((view, index) => {
|
|
67
|
+
const hasChild = index < views.length - 1
|
|
68
|
+
const props: UiProps = hasChild
|
|
69
|
+
? { ...paramThunks, children: () => CHILD_PRESENT }
|
|
70
|
+
: paramThunks
|
|
71
|
+
const key = keys[index]
|
|
72
|
+
const run = () => isolateCellBarrier(() => view.render(props, ctx))
|
|
73
|
+
return key === undefined ? run() : withPath(key, run)
|
|
74
|
+
}),
|
|
75
|
+
)
|
|
76
|
+
renders.push(...collected)
|
|
77
|
+
} else if (views.length === 1) {
|
|
78
|
+
/* A lone page (no layouts) — the common case — renders DIRECTLY: no parallelism to gain, and
|
|
79
|
+
no `Promise.all`/`isolateCellBarrier` wrap, so its bare-read/settle timing is byte-identical
|
|
80
|
+
to the pre-ADR-0038 path (a fast in-process read stays pending → streams, rather than
|
|
81
|
+
slipping settled → inline behind an extra microtask). */
|
|
82
|
+
const view = views[0] as UiComponent
|
|
83
|
+
const key = keys[0]
|
|
84
|
+
renders.push(
|
|
85
|
+
key === undefined
|
|
86
|
+
? await view.render(paramThunks, ctx)
|
|
87
|
+
: await withPath(key, () => view.render(paramThunks, ctx)),
|
|
88
|
+
)
|
|
53
89
|
}
|
|
54
90
|
let html = renders[renders.length - 1]?.html ?? ''
|
|
55
91
|
for (let index = renders.length - 2; index >= 0; index -= 1) {
|
|
@@ -8,8 +8,8 @@ Out-of-order SSR streaming. Yields the shell first (so the browser paints
|
|
|
8
8
|
immediately), then one resolved fragment per STREAMING await block as its promise
|
|
9
9
|
settles — in completion order, not source order, so a slow read never blocks a fast
|
|
10
10
|
one. Each resolved fragment is a `<abide-resolve data-id="ID"><script
|
|
11
|
-
type="application/json">…</script>…</abide-resolve>` that
|
|
12
|
-
the matching `<!--abide:await:ID-->` boundary; the leading script holds the
|
|
11
|
+
type="application/json">…</script>…</abide-resolve>` that the inline swap script swaps
|
|
12
|
+
into the matching `<!--abide:await:ID-->` boundary; the leading script holds the
|
|
13
13
|
JSON-serialized value, registered for hydration so an `await` block adopts the
|
|
14
14
|
resolved branch on resume instead of re-running.
|
|
15
15
|
|
|
@@ -38,14 +38,13 @@ export async function* renderToStream(
|
|
|
38
38
|
alongside each streamed fragment, so the client adopts the nested blocking branch
|
|
39
39
|
instead of refetching. (`resume` is the render body's live object, so late writes
|
|
40
40
|
appear here.) */
|
|
41
|
-
const seededResume = new Set<
|
|
42
|
-
const resumeDelta = (): Record<
|
|
43
|
-
const delta: Record<
|
|
41
|
+
const seededResume = new Set<string>(Object.keys(resume))
|
|
42
|
+
const resumeDelta = (): Record<string, ResumeEntry> => {
|
|
43
|
+
const delta: Record<string, ResumeEntry> = {}
|
|
44
44
|
for (const [key, entry] of Object.entries(resume)) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
delta[id] = entry
|
|
45
|
+
if (!seededResume.has(key)) {
|
|
46
|
+
seededResume.add(key)
|
|
47
|
+
delta[key] = entry
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
return delta
|
|
@@ -55,8 +54,8 @@ export async function* renderToStream(
|
|
|
55
54
|
awaits — its `branchContent` runs `$awaits.push(...)` onto this same `awaits` array
|
|
56
55
|
during `settle`, AFTER the initial scan. So re-scan for newly-appended blocks after
|
|
57
56
|
every settle (tracking which ids are already enqueued), composing to any depth. */
|
|
58
|
-
const inflight = new Map<
|
|
59
|
-
const enqueued = new Set<
|
|
57
|
+
const inflight = new Map<string, Promise<Settled>>()
|
|
58
|
+
const enqueued = new Set<string>()
|
|
60
59
|
const enqueueNew = (): void => {
|
|
61
60
|
for (const block of awaits) {
|
|
62
61
|
if (!enqueued.has(block.id)) {
|
|
@@ -71,11 +70,14 @@ export async function* renderToStream(
|
|
|
71
70
|
inflight.delete(resolved.id)
|
|
72
71
|
enqueueNew()
|
|
73
72
|
/* An unserializable value (e.g. a cyclic media tree) streams its rendered HTML
|
|
74
|
-
with NO seed script:
|
|
73
|
+
with NO seed script: the inline swap script (SSR_SWAP_SCRIPT) skips
|
|
75
74
|
registration when the leading child isn't a parseable script, so hydration
|
|
76
75
|
re-runs this one branch's promise — degrading to a refetch instead of aborting
|
|
77
76
|
the whole stream. */
|
|
78
|
-
const encoded =
|
|
77
|
+
const encoded =
|
|
78
|
+
resolved.resume === undefined
|
|
79
|
+
? undefined
|
|
80
|
+
: encodeStreamResume(resolved.resume, resolved.id)
|
|
79
81
|
yield resumeSeedScript(resumeDelta()) +
|
|
80
82
|
`<abide-resolve data-id="${resolved.id}">` +
|
|
81
83
|
(encoded === undefined ? '' : `<script type="application/json">${encoded}</script>`) +
|
|
@@ -83,7 +85,7 @@ export async function* renderToStream(
|
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
type Settled = { id:
|
|
88
|
+
type Settled = { id: string; html: string; resume: ResumeEntry | undefined }
|
|
87
89
|
|
|
88
90
|
/* Awaits one streaming block's promise and renders the resolved or error branch to
|
|
89
91
|
HTML (the renderers are async so a nested `await` block composes), capturing the
|
|
@@ -95,7 +97,9 @@ function settle(block: SsrAwait): Promise<Settled> {
|
|
|
95
97
|
async (value) => ({
|
|
96
98
|
id: block.id,
|
|
97
99
|
html: await block.then(value),
|
|
98
|
-
|
|
100
|
+
/* A standalone-unit (streamed component) boundary carries no resume value of its own —
|
|
101
|
+
its `then` already merged the child's nested awaits/resume for streaming composition. */
|
|
102
|
+
resume: block.htmlOnly === true ? undefined : { ok: true, value },
|
|
99
103
|
}),
|
|
100
104
|
async (error) => {
|
|
101
105
|
/* No catch branch → surface the rejection (500 before the first flush,
|
|
@@ -117,8 +121,8 @@ function settle(block: SsrAwait): Promise<Settled> {
|
|
|
117
121
|
literal `</script>` from closing the block early — quotes stay raw, and the escape
|
|
118
122
|
survives `decodeRefJson`'s inner JSON.parse since `<` only ever appears inside JSON
|
|
119
123
|
strings. `tryEncodeResume` handles the serialize-or-refetch policy (undefined → no
|
|
120
|
-
script → the swap
|
|
121
|
-
|
|
122
|
-
function encodeStreamResume(resume: ResumeEntry, id:
|
|
124
|
+
script → the inline swap script skips registration → hydration re-runs that one promise).
|
|
125
|
+
The inline swap script stores it via `.textContent`; `awaitBlock` decodes it. */
|
|
126
|
+
function encodeStreamResume(resume: ResumeEntry, id: string): string | undefined {
|
|
123
127
|
return tryEncodeResume(resume, id)?.replace(/</g, '\\u003c')
|
|
124
128
|
}
|
|
@@ -11,7 +11,7 @@ import { tryEncodeResume } from './tryEncodeResume.ts'
|
|
|
11
11
|
early or parse as a line terminator. Shared by the buffered (`createUiPageRenderer`)
|
|
12
12
|
and streaming (`renderToStream`) paths. */
|
|
13
13
|
// @documentation plumbing
|
|
14
|
-
export function resumeSeedScript(resume: Record<
|
|
14
|
+
export function resumeSeedScript(resume: Record<string, ResumeEntry>): string {
|
|
15
15
|
/* ref-json (not JSON) so a value carrying cycles or shared back-references — a
|
|
16
16
|
media tree with parent↔child links — seeds instead of being dropped. `tryEncodeResume`
|
|
17
17
|
drops just an unserializable entry (the client re-runs that one branch's promise),
|
package/src/lib/ui/router.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hydrationWindow } from '../shared/hydrationWindow.ts'
|
|
2
2
|
import { layoutChainForRoute } from '../shared/layoutChainForRoute.ts'
|
|
3
3
|
import { matchRoute } from '../shared/matchRoute.ts'
|
|
4
|
-
import { wakeHydrationPeeks } from '../shared/wakeHydrationPeeks.ts'
|
|
5
4
|
import { fillBoundary } from './dom/fillBoundary.ts'
|
|
6
5
|
import { outlet } from './dom/outlet.ts'
|
|
7
6
|
import { effect } from './effect.ts'
|
|
@@ -20,6 +19,7 @@ import type { Route } from './runtime/types/Route.ts'
|
|
|
20
19
|
import type { RouteLoader } from './runtime/types/RouteLoader.ts'
|
|
21
20
|
import type { UiProps } from './runtime/types/UiProps.ts'
|
|
22
21
|
import { untrack } from './runtime/untrack.ts'
|
|
22
|
+
import { withPath } from './runtime/withPath.ts'
|
|
23
23
|
|
|
24
24
|
/* An outlet boundary — the `<!--abide:outlet-->`…`<!--/abide:outlet-->` marker pair a
|
|
25
25
|
layer's content lives between (a layout's `<slot/>`, or the router's root boundary in
|
|
@@ -220,15 +220,17 @@ export function router(
|
|
|
220
220
|
for (let depth = index; depth < layoutViews.length; depth += 1) {
|
|
221
221
|
const view = layoutViews[depth] as Route
|
|
222
222
|
PENDING_OUTLET.current = undefined
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
223
|
+
/* Root this layer's render-path at its route key (the directory URL — stable
|
|
224
|
+
across reloads and globally unique), so every scope/cell it builds gets a
|
|
225
|
+
serialization-stable id under it (`withPath`). */
|
|
226
|
+
const { dispose } = withPath(chainKeys[depth] as string, () =>
|
|
227
|
+
fillBoundary(
|
|
228
|
+
boundary.open,
|
|
229
|
+
boundary.close,
|
|
230
|
+
view.build,
|
|
231
|
+
/* A layout always has a child below: a deeper layout, or the page. */
|
|
232
|
+
propsBag(depth < layoutViews.length - 1 || pageView !== undefined),
|
|
233
|
+
),
|
|
232
234
|
)
|
|
233
235
|
const slot = PENDING_OUTLET.current
|
|
234
236
|
if (slot === undefined) {
|
|
@@ -241,34 +243,32 @@ export function router(
|
|
|
241
243
|
mountedPageKey = undefined
|
|
242
244
|
return
|
|
243
245
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
246
|
+
/* Root the page's render-path at its route key (stable, unique) — see the layout
|
|
247
|
+
layer above. */
|
|
248
|
+
disposePage = withPath(pageKey, () =>
|
|
249
|
+
fillBoundary(
|
|
250
|
+
boundary.open,
|
|
251
|
+
boundary.close,
|
|
252
|
+
pageView.build,
|
|
253
|
+
/* A page is a leaf — no child layer. */
|
|
254
|
+
propsBag(false),
|
|
255
|
+
),
|
|
252
256
|
).dispose
|
|
253
257
|
mountedPageKey = pageKey
|
|
254
258
|
}
|
|
255
259
|
if (hydrating) {
|
|
256
260
|
const previous = RENDER.hydration
|
|
257
|
-
const previousHydrating = hydratingSlot.active
|
|
258
261
|
RENDER.hydration = { next: new Map() }
|
|
259
|
-
|
|
262
|
+
hydrationWindow.enter()
|
|
260
263
|
enterRenderPass()
|
|
261
264
|
try {
|
|
262
265
|
run()
|
|
263
266
|
} finally {
|
|
264
267
|
exitRenderPass()
|
|
265
268
|
RENDER.hydration = previous
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (!previousHydrating) {
|
|
270
|
-
wakeHydrationPeeks()
|
|
271
|
-
}
|
|
269
|
+
/* Outermost exit clears the window and wakes the peeks this pass withheld
|
|
270
|
+
for SSR congruence, now that the warm value is congruent to show. */
|
|
271
|
+
hydrationWindow.exit()
|
|
272
272
|
}
|
|
273
273
|
return
|
|
274
274
|
}
|
|
@@ -532,15 +532,42 @@ export function router(
|
|
|
532
532
|
disposeFrom(divergence)
|
|
533
533
|
const url = resolveUrl(path)
|
|
534
534
|
clientPage.value = { route: chainRoute, params, url, navigating: false }
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
535
|
+
try {
|
|
536
|
+
buildFrom(
|
|
537
|
+
divergence,
|
|
538
|
+
chainKeys,
|
|
539
|
+
layoutViews,
|
|
540
|
+
pageView,
|
|
541
|
+
key,
|
|
542
|
+
params,
|
|
543
|
+
hydrating,
|
|
544
|
+
)
|
|
545
|
+
} catch (error) {
|
|
546
|
+
/* A cold mount owns no claim cursor, so the SSR-vs-client claim asserts
|
|
547
|
+
(`assertClaimedText`, `claimExpected`) can't fire there — a throw from
|
|
548
|
+
it is a genuine codegen/user-render defect. Rethrow to `commit`, which
|
|
549
|
+
surfaces it and stops (a reload would re-run the same failure). */
|
|
550
|
+
if (!hydrating) {
|
|
551
|
+
throw error
|
|
552
|
+
}
|
|
553
|
+
/* A HYDRATING first paint threw — a server↔client markup divergence (a
|
|
554
|
+
blocking cell whose resolved value the server couldn't serialize into
|
|
555
|
+
`__SSR__.cells`, so the client reads pending where SSR baked a value;
|
|
556
|
+
or any other SSR/client mismatch). A reload re-runs the same SSR, so
|
|
557
|
+
recover CLIENT-side: discard the server markup and mount the page fresh
|
|
558
|
+
(cold — pending reads render their empty state and fill in on settle,
|
|
559
|
+
congruent because nothing is being adopted). Reset to the pristine
|
|
560
|
+
pre-mount state so `run` re-establishes the root boundary and clears the
|
|
561
|
+
host, then rebuild the whole chain cold. */
|
|
562
|
+
console.warn(
|
|
563
|
+
`[abide] hydration mismatch at ${path} — discarding server markup and rendering on the client:`,
|
|
564
|
+
error,
|
|
565
|
+
)
|
|
566
|
+
disposeFrom(0)
|
|
567
|
+
rootBoundary = undefined
|
|
568
|
+
mountedPageKey = undefined
|
|
569
|
+
buildFrom(0, chainKeys, layoutViews, pageView, key, params, false)
|
|
570
|
+
}
|
|
544
571
|
/* Reapply the destination entry's scroll once its DOM exists — a
|
|
545
572
|
back/forward restores its offset, a fresh nav scrolls to the `#hash`
|
|
546
573
|
anchor (now built) or the top. SKIPPED on a hydrating first paint:
|