@abide/abide 0.49.0 → 0.50.1
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 +564 -437
- package/CHANGELOG.md +183 -0
- package/README.md +165 -202
- 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
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { decodeRefJson } from '../../shared/decodeRefJson.ts'
|
|
2
2
|
import { effect } from '../effect.ts'
|
|
3
|
+
import { CURRENT_BOUNDARY } from '../runtime/CURRENT_BOUNDARY.ts'
|
|
3
4
|
import { claimExpected } from '../runtime/claimExpected.ts'
|
|
4
5
|
import { generationGuard } from '../runtime/generationGuard.ts'
|
|
5
6
|
import { RANGE_CLOSE, RANGE_OPEN } from '../runtime/RANGE_MARKER.ts'
|
|
6
7
|
import { RENDER } from '../runtime/RENDER.ts'
|
|
7
8
|
import type { ResumeEntry } from '../runtime/RESUME.ts'
|
|
8
9
|
import { RESUME } from '../runtime/RESUME.ts'
|
|
9
|
-
import { scope } from '../runtime/scope.ts'
|
|
10
10
|
import { scopeGroup } from '../runtime/scopeGroup.ts'
|
|
11
11
|
import type { State } from '../runtime/types/State.ts'
|
|
12
12
|
import { withoutHydration } from '../runtime/withoutHydration.ts'
|
|
13
13
|
import { state } from '../state.ts'
|
|
14
|
-
import {
|
|
14
|
+
import { anchoredBranch } from './anchoredBranch.ts'
|
|
15
15
|
import { discardBoundary } from './discardBoundary.ts'
|
|
16
|
-
import { removeRange } from './removeRange.ts'
|
|
17
16
|
|
|
18
17
|
/*
|
|
19
18
|
Async binding — the runtime for `<template await>`. Renders the pending branch,
|
|
@@ -39,12 +38,13 @@ parked just before the close marker.
|
|
|
39
38
|
// @documentation plumbing
|
|
40
39
|
export function awaitBlock(
|
|
41
40
|
parent: Node,
|
|
42
|
-
id:
|
|
41
|
+
id: string,
|
|
43
42
|
promiseThunk: () => unknown,
|
|
44
43
|
renderPending: ((parent: Node) => void) | undefined,
|
|
45
44
|
renderThen: (parent: Node, value: unknown) => void,
|
|
46
|
-
/* Absent when the block has no catch branch — a rejection then
|
|
47
|
-
|
|
45
|
+
/* Absent when the block has no catch branch — a rejection then routes to the enclosing
|
|
46
|
+
{#try} boundary captured at build (if any), else surfaces (re-throws to the
|
|
47
|
+
unhandled-rejection path) instead of rendering an empty branch. */
|
|
48
48
|
renderCatch: ((parent: Node, error: unknown) => void) | undefined,
|
|
49
49
|
/* A static node located by the skeleton: the block's anchor inserts before it on
|
|
50
50
|
create (block before a static suffix). Null appends (tail). insertBefore(x, null)
|
|
@@ -52,11 +52,16 @@ export function awaitBlock(
|
|
|
52
52
|
before: Node | null = null,
|
|
53
53
|
): void {
|
|
54
54
|
const hydration = RENDER.hydration
|
|
55
|
+
/* The enclosing {#try} boundary ambient at BUILD (like createEffectNode captures it). A
|
|
56
|
+
catch-less rejection settles LATER, after CURRENT_BOUNDARY has been restored, so we must
|
|
57
|
+
capture it into this closure now rather than read it at settle time. */
|
|
58
|
+
const capturedBoundary = CURRENT_BOUNDARY.current
|
|
55
59
|
/* The live branch's scope, registered with the owner so it disposes on owner
|
|
56
60
|
teardown — not only when a settle/re-run swaps branches via detach. */
|
|
57
61
|
const group = scopeGroup()
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
/* The single active branch mounted before an anchor — owns `active`/`detach`/`place`/
|
|
63
|
+
adopt-strand guard (shared with `tryBlock`); this block keeps its bespoke settlement. */
|
|
64
|
+
const branch = anchoredBranch(parent, group)
|
|
60
65
|
let first = true
|
|
61
66
|
/* Bumped each run so a prior run's in-flight promise can't clobber a newer one, AND on
|
|
62
67
|
owner teardown so an in-flight promise that settles AFTER the enclosing `{#if}`/
|
|
@@ -73,43 +78,6 @@ export function awaitBlock(
|
|
|
73
78
|
place (then→then) or must build a fresh branch. */
|
|
74
79
|
let activeKind: 'pending' | 'then' | 'catch' | undefined
|
|
75
80
|
|
|
76
|
-
const detach = (): void => {
|
|
77
|
-
if (active !== undefined) {
|
|
78
|
-
active.dispose()
|
|
79
|
-
/* `removeRange` evicts the markers AND everything between them via the end
|
|
80
|
-
marker's LIVE parent — not the captured `parent`, which (when this await is a
|
|
81
|
-
bare child of a control-flow branch) is the branch's build fragment, emptied
|
|
82
|
-
into the document once the enclosing block placed it. */
|
|
83
|
-
removeRange(active.start, active.end)
|
|
84
|
-
active = undefined
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/* Replace the current content with a freshly-built branch, before the anchor. The branch
|
|
89
|
-
builds into a detached `[`…`]`-bracketed fragment (so any content — components, text,
|
|
90
|
-
nested blocks — appends freely), the same create primitive the keyed-list runtimes use,
|
|
91
|
-
which lands as a marker-bounded range the next swap detaches with `removeRange`. */
|
|
92
|
-
const place = (build: (parent: Node) => void): void => {
|
|
93
|
-
/* Backstop for a settle whose anchor has been detached from the tree. The
|
|
94
|
-
generationGuard is the PRIMARY defense — it drops a late settle after the owner
|
|
95
|
-
tears down — but it only covers teardowns that dispose THIS block's scope. A
|
|
96
|
-
gap (e.g. a nested hydration `adopt` that aborts to `rebuildCold`, leaving the
|
|
97
|
-
inner block's range removed while its guard stays live) can still route a late
|
|
98
|
-
settle here with `anchor` already pulled out of the DOM. Inserting before a
|
|
99
|
-
node that is no longer a child of any parent throws a `NotFoundError` from
|
|
100
|
-
`insertBefore` — surfacing as a process-fatal unhandled rejection under Bun. A
|
|
101
|
-
detached anchor unambiguously means the block is gone, so drop the settle. */
|
|
102
|
-
if (anchor !== undefined && anchor.parentNode === null) {
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
detach()
|
|
106
|
-
const namespaceParent = anchor?.parentNode ?? parent
|
|
107
|
-
const { start, end, fragment, dispose } = buildDetachedRange(namespaceParent, build)
|
|
108
|
-
const tracked = group.track(dispose)
|
|
109
|
-
namespaceParent.insertBefore(fragment, anchor ?? null)
|
|
110
|
-
active = { start, end, dispose: tracked }
|
|
111
|
-
}
|
|
112
|
-
|
|
113
81
|
/* Settle to a resolved value. then→then updates the cell in place — the branch and its
|
|
114
82
|
inner each survive (no flash); any other prior kind builds a fresh then-branch around
|
|
115
83
|
a new cell. renderThen receives the CELL (not the raw value), so the branch reads it
|
|
@@ -121,17 +89,23 @@ export function awaitBlock(
|
|
|
121
89
|
}
|
|
122
90
|
const cell = state(value)
|
|
123
91
|
valueCell = cell
|
|
124
|
-
place((host) => renderThen(host, cell))
|
|
92
|
+
branch.place((host) => renderThen(host, cell))
|
|
125
93
|
activeKind = 'then'
|
|
126
94
|
}
|
|
127
95
|
|
|
128
|
-
/* Settle to a rejection:
|
|
96
|
+
/* Settle to a rejection: with a local catch branch, swap to it. With no local catch, route
|
|
97
|
+
to the enclosing {#try} boundary captured at build if one was ambient; otherwise surface
|
|
98
|
+
(unhandled rejection), preserving the prior behaviour when there's no boundary. */
|
|
129
99
|
const settleError = (error: unknown): void => {
|
|
130
100
|
if (renderCatch === undefined) {
|
|
101
|
+
if (capturedBoundary !== undefined) {
|
|
102
|
+
capturedBoundary.handle(error)
|
|
103
|
+
return
|
|
104
|
+
}
|
|
131
105
|
throw error
|
|
132
106
|
}
|
|
133
107
|
valueCell = undefined
|
|
134
|
-
place((host) => renderCatch(host, error))
|
|
108
|
+
branch.place((host) => renderCatch(host, error))
|
|
135
109
|
activeKind = 'catch'
|
|
136
110
|
}
|
|
137
111
|
|
|
@@ -148,10 +122,10 @@ export function awaitBlock(
|
|
|
148
122
|
shows the pending branch (or detaches) while the promise is in flight. */
|
|
149
123
|
if (activeKind !== 'then') {
|
|
150
124
|
if (renderPending !== undefined) {
|
|
151
|
-
place((host) => renderPending(host))
|
|
125
|
+
branch.place((host) => renderPending(host))
|
|
152
126
|
activeKind = 'pending'
|
|
153
127
|
} else {
|
|
154
|
-
detach()
|
|
128
|
+
branch.detach()
|
|
155
129
|
activeKind = undefined
|
|
156
130
|
}
|
|
157
131
|
}
|
|
@@ -174,37 +148,14 @@ export function awaitBlock(
|
|
|
174
148
|
marker for later swaps. The adopted content is everything the build claimed between
|
|
175
149
|
the open and close markers; bracketing it makes the adopted branch a marker-bounded
|
|
176
150
|
range identical to a freshly-`place`d one, so the FIRST swap detaches it with
|
|
177
|
-
`removeRange` like every later swap (no node-array special case).
|
|
151
|
+
`removeRange` like every later swap (no node-array special case). `adoptStrand` carries
|
|
152
|
+
the shared strand-dispose guard; the `afterBuild` closure below is await's bespoke
|
|
153
|
+
bracketing — it runs only after a clean build. */
|
|
178
154
|
const adopt = (open: Node | null, build: (parent: Node) => void): void => {
|
|
179
155
|
const cursor = hydration as NonNullable<typeof hydration>
|
|
180
156
|
const firstAdopted = open?.nextSibling ?? null
|
|
181
157
|
cursor.next.set(parent, firstAdopted)
|
|
182
|
-
|
|
183
|
-
markup — a resume value that didn't round-trip, a nested-adopt claim desync —
|
|
184
|
-
throws, and the caller recovers via `rebuildCold`. But the partial build may have
|
|
185
|
-
already created a live sub-scope (an inner `await`'s effect/guard, a subscription)
|
|
186
|
-
before it threw; letting the throw escape `scope()` would strand that scope's
|
|
187
|
-
disposer (unreachable → never disposed), leaking the effect AND leaving its guard
|
|
188
|
-
un-bumped so a late settle stays "live". So capture the build's error, ALWAYS take
|
|
189
|
-
the returned disposer, and dispose it on ANY failure before rethrowing — `rebuildCold`
|
|
190
|
-
then starts from a clean slate. */
|
|
191
|
-
let dispose: (() => void) | undefined
|
|
192
|
-
try {
|
|
193
|
-
let buildFailed = false
|
|
194
|
-
let buildError: unknown
|
|
195
|
-
dispose = group.track(
|
|
196
|
-
scope(() => {
|
|
197
|
-
try {
|
|
198
|
-
build(parent)
|
|
199
|
-
} catch (error) {
|
|
200
|
-
buildFailed = true
|
|
201
|
-
buildError = error
|
|
202
|
-
}
|
|
203
|
-
}),
|
|
204
|
-
)
|
|
205
|
-
if (buildFailed) {
|
|
206
|
-
throw buildError
|
|
207
|
-
}
|
|
158
|
+
branch.adoptStrand(build, () => {
|
|
208
159
|
/* A guaranteed control-flow marker — claimExpected throws on a desync (caught by
|
|
209
160
|
firstHydrate's adopt try/catch → rebuildCold) instead of silently claiming null
|
|
210
161
|
and over-clearing the parent. */
|
|
@@ -216,22 +167,17 @@ export function awaitBlock(
|
|
|
216
167
|
parent.insertBefore(start, firstAdopted ?? close)
|
|
217
168
|
const end = document.createComment(RANGE_CLOSE)
|
|
218
169
|
parent.insertBefore(end, close)
|
|
219
|
-
|
|
220
|
-
parent.insertBefore(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
partial branch scope down and drops it from the group so `rebuildCold` doesn't
|
|
225
|
-
inherit a stranded scope, then the caller's `catch` falls back to a cold build. */
|
|
226
|
-
dispose?.()
|
|
227
|
-
throw error
|
|
228
|
-
}
|
|
170
|
+
const anchorNode = document.createTextNode('')
|
|
171
|
+
parent.insertBefore(anchorNode, close)
|
|
172
|
+
branch.anchor = anchorNode
|
|
173
|
+
return { start, end }
|
|
174
|
+
})
|
|
229
175
|
}
|
|
230
176
|
|
|
231
177
|
/* Discard the SSR boundary and (re)build the block from the live promise, fresh
|
|
232
178
|
(hydration off) — the recovery path when adoption can't use the server markup. */
|
|
233
179
|
const rebuildCold = (open: Node | null): void => {
|
|
234
|
-
detach()
|
|
180
|
+
branch.detach()
|
|
235
181
|
/* Insert at the node AFTER the discarded boundary (its return) — NOT the captured
|
|
236
182
|
`before`, which for a skeleton-anchored block is the open boundary itself and is
|
|
237
183
|
removed here, so reusing it throws `NotFoundError` in a strict DOM. */
|
|
@@ -241,8 +187,9 @@ export function awaitBlock(
|
|
|
241
187
|
`/abide:await:${id}`,
|
|
242
188
|
hydration as NonNullable<typeof hydration>,
|
|
243
189
|
)
|
|
244
|
-
|
|
245
|
-
|
|
190
|
+
const anchorNode = document.createTextNode('')
|
|
191
|
+
branch.anchor = anchorNode
|
|
192
|
+
parent.insertBefore(anchorNode, after)
|
|
246
193
|
withoutHydration(() => render(promiseThunk()))
|
|
247
194
|
}
|
|
248
195
|
|
|
@@ -324,8 +271,9 @@ export function awaitBlock(
|
|
|
324
271
|
}
|
|
325
272
|
/* Insert at the node after the discarded boundary (see `rebuildCold`). */
|
|
326
273
|
const after = discardBoundary(parent, open, `/abide:await:${id}`, cursor)
|
|
327
|
-
|
|
328
|
-
|
|
274
|
+
const anchorNode = document.createTextNode('')
|
|
275
|
+
branch.anchor = anchorNode
|
|
276
|
+
parent.insertBefore(anchorNode, after)
|
|
329
277
|
/* The boundary's server nodes are gone, so the pending branch builds FRESH — clear
|
|
330
278
|
the claim cursor (see withoutHydration) so its `cloneStatic`/text don't try to
|
|
331
279
|
claim discarded nodes and silently render nothing. */
|
|
@@ -342,8 +290,9 @@ export function awaitBlock(
|
|
|
342
290
|
firstHydrate()
|
|
343
291
|
return
|
|
344
292
|
}
|
|
345
|
-
|
|
346
|
-
|
|
293
|
+
const anchorNode = document.createTextNode('')
|
|
294
|
+
branch.anchor = anchorNode
|
|
295
|
+
parent.insertBefore(anchorNode, before)
|
|
347
296
|
}
|
|
348
297
|
/* Read the promise every subsequent run so an invalidate re-runs the block. ONLY this
|
|
349
298
|
read is tracked (the branch builds untracked via `scope`), so the block re-runs only
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The parent half of a component `bind:prop={target}`: annotates the prop's value
|
|
3
|
+
thunk with a `set` write-back channel, so the same `() => value` entry every prop
|
|
4
|
+
compiles to ALSO carries a setter. A read-only consumer calls the thunk (`$props[key]?.()`)
|
|
5
|
+
and gets the value exactly as for a plain prop — the `set` simply rides along, invisible
|
|
6
|
+
to code that doesn't look for it (`restProps`/`mergeProps`/`spreadProps` pass the thunk
|
|
7
|
+
through untouched). The child upgrades the prop to a writable cell (`bindableProp`) only
|
|
8
|
+
when it writes or forwards it, and reaches this setter through `.set`.
|
|
9
|
+
|
|
10
|
+
`read`/`write` are the caller's lowered bind accessors — an lvalue target reads as
|
|
11
|
+
itself and writes by assignment, a `{ get, set }` accessor reads via `get()` and writes
|
|
12
|
+
via `set(next)` — so a component bind accepts the same targets an element bind does.
|
|
13
|
+
*/
|
|
14
|
+
// @documentation plumbing
|
|
15
|
+
export function bindProp<T>(
|
|
16
|
+
read: () => T,
|
|
17
|
+
write: (next: T) => void,
|
|
18
|
+
): (() => T) & { set: (next: T) => void } {
|
|
19
|
+
const thunk = read as (() => T) & { set: (next: T) => void }
|
|
20
|
+
thunk.set = write
|
|
21
|
+
return thunk
|
|
22
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { linked } from '../linked.ts'
|
|
2
|
+
import type { State } from '../runtime/types/State.ts'
|
|
3
|
+
import type { UiProps } from '../runtime/types/UiProps.ts'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
The child half of a two-way prop: the writable cell a component gets for a prop it
|
|
7
|
+
WRITES or forwards to another `bind:` target. A prop the child only reads stays a
|
|
8
|
+
read-only derive (unchanged, cheap); this upgrade is emitted only for the props the
|
|
9
|
+
compiler sees written, so the common case pays nothing.
|
|
10
|
+
|
|
11
|
+
Whether the prop is actually two-way is decided at construction from what the parent
|
|
12
|
+
passed:
|
|
13
|
+
• Bound (`bind:prop` on the parent) — the value thunk carries a `set` (see `bindProp`),
|
|
14
|
+
so the cell is a pass-through accessor: reads pull the parent's value (tracking its
|
|
15
|
+
reactive source), writes go straight upstream. The parent's target is the single
|
|
16
|
+
source of truth, so no local copy is kept.
|
|
17
|
+
• Unbound (plain `prop={value}`, or the prop was never passed) — no setter exists, so
|
|
18
|
+
the cell degrades to a local `linked` cell seeded from the parent value: it reseeds
|
|
19
|
+
when the parent value changes and holds local writes in between. The component still
|
|
20
|
+
works standalone; its writes just don't flow anywhere.
|
|
21
|
+
|
|
22
|
+
`fallback` supplies the destructure's `= default` when the prop is absent/undefined,
|
|
23
|
+
matching the read-only derive's `?? default`.
|
|
24
|
+
*/
|
|
25
|
+
// @documentation plumbing
|
|
26
|
+
export function bindableProp<T>(props: UiProps, key: string, fallback?: () => T): State<T> {
|
|
27
|
+
const entry = (props as Record<string, unknown>)[key]
|
|
28
|
+
const read = (): T => {
|
|
29
|
+
const value = typeof entry === 'function' ? (entry as () => T)() : (entry as T)
|
|
30
|
+
return (value === undefined ? fallback?.() : value) as T
|
|
31
|
+
}
|
|
32
|
+
const setter =
|
|
33
|
+
typeof entry === 'function' ? (entry as { set?: (next: T) => void }).set : undefined
|
|
34
|
+
if (setter !== undefined) {
|
|
35
|
+
/* Bound: a pure pass-through to the parent's target — no local store. */
|
|
36
|
+
return {
|
|
37
|
+
get value(): T {
|
|
38
|
+
return read()
|
|
39
|
+
},
|
|
40
|
+
set value(next: T) {
|
|
41
|
+
setter(next)
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/* Unbound: a local reseeding cell — writes echo locally, reseed on parent change. */
|
|
46
|
+
return linked(read) as State<T>
|
|
47
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isAsyncCell } from '../../shared/isAsyncCell.ts'
|
|
2
|
+
import type { AsyncComputed } from '../../shared/types/AsyncComputed.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Whether a control-flow subject (`{#if}`/`{#switch}`) is a still-loading async cell — no
|
|
6
|
+
value yet, no error (`AsyncComputed.pending()`). The compiler pairs it with the value read
|
|
7
|
+
for a bare async subject: while `cellPending(cell)` is true the block renders NO branch, so
|
|
8
|
+
"still loading" never collapses into the falsy branch (`{#if getProfile()}` does not flash
|
|
9
|
+
its `{:else}` before the promise settles). A settled falsy value (or a held value being
|
|
10
|
+
refreshed) is NOT pending, so it still routes to its branch. Non-async subjects (a plain
|
|
11
|
+
value, a sync `computed`) are never pending — the block reads them directly.
|
|
12
|
+
|
|
13
|
+
Reading `pending()` subscribes the block's effect to the cell's in-flight/has-value facets,
|
|
14
|
+
so the block re-renders when the value arrives. An errored cell is NOT pending: the paired
|
|
15
|
+
value read (`$$readCell`) then throws its `AsyncCellError`, routing the error to the nearest
|
|
16
|
+
`{#try}` exactly as a bare read does.
|
|
17
|
+
*/
|
|
18
|
+
// @documentation plumbing
|
|
19
|
+
export function cellPending(cell: unknown): boolean {
|
|
20
|
+
if (isAsyncCell(cell)) {
|
|
21
|
+
return (cell as AsyncComputed<unknown>).pending()
|
|
22
|
+
}
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
@@ -5,7 +5,8 @@ import { clearBetween } from './clearBetween.ts'
|
|
|
5
5
|
The standard teardown for a marker-bounded range (component, layout/page boundary,
|
|
6
6
|
slot): dispose its lexical scope (which stops the content's reactivity first, then its
|
|
7
7
|
nested children and capabilities), and clear the nodes between the markers — leaving the
|
|
8
|
-
markers in place so a
|
|
8
|
+
markers in place so a later rebuild (navigation, control-flow re-fill) refills the range.
|
|
9
|
+
Shared by every range mount
|
|
9
10
|
(`fillRange`, `mountRange`, `fillBoundary`) so the one disposer contract lives in a
|
|
10
11
|
single place.
|
|
11
12
|
*/
|
package/src/lib/ui/dom/each.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { effect } from '../effect.ts'
|
|
2
|
+
import { CURRENT_PATH } from '../runtime/CURRENT_PATH.ts'
|
|
2
3
|
import { claimChild } from '../runtime/claimChild.ts'
|
|
3
4
|
import { claimExpected } from '../runtime/claimExpected.ts'
|
|
4
5
|
import { RENDER } from '../runtime/RENDER.ts'
|
|
@@ -6,6 +7,7 @@ import { scope } from '../runtime/scope.ts'
|
|
|
6
7
|
import { scopeGroup } from '../runtime/scopeGroup.ts'
|
|
7
8
|
import type { State } from '../runtime/types/State.ts'
|
|
8
9
|
import { withoutHydration } from '../runtime/withoutHydration.ts'
|
|
10
|
+
import { withPathFrom } from '../runtime/withPathFrom.ts'
|
|
9
11
|
import { state } from '../state.ts'
|
|
10
12
|
import { buildDetachedRange } from './buildDetachedRange.ts'
|
|
11
13
|
import { moveRange } from './moveRange.ts'
|
|
@@ -41,8 +43,17 @@ export function each<T>(
|
|
|
41
43
|
read them; a direct caller reads `item.value` / `index.value`. */
|
|
42
44
|
render: (parent: Node, item: State<T>, index: State<number>) => void,
|
|
43
45
|
before: Node | null = null,
|
|
46
|
+
/* Whether the each has an explicit `by` key. A KEYED row takes its stable key as its
|
|
47
|
+
render-path segment (survives reorders/reloads); a KEYLESS row (no `by`, so `keyOf`
|
|
48
|
+
returns the raw item identity — unstable) takes its POSITION instead, which is at least
|
|
49
|
+
deterministic SSR↔client for a single render. Absent → keyless (a direct caller). */
|
|
50
|
+
keyed = false,
|
|
44
51
|
): void {
|
|
45
52
|
const rows = new Map<string, EachRow>()
|
|
53
|
+
/* This each's render-path ancestry, captured at construction; every row builds under
|
|
54
|
+
`basePath/segment` so a component/cell in a row gets a stable id, re-established on a
|
|
55
|
+
post-mount reconcile (when the ambient path is gone). */
|
|
56
|
+
const basePath = CURRENT_PATH.current
|
|
46
57
|
/* Monotonic reconcile-pass id. Each reconcile stamps every surviving row with the
|
|
47
58
|
current pass, so prune is a `row.gen !== pass` int compare instead of building a
|
|
48
59
|
`Set` of present keys every reconcile. */
|
|
@@ -66,11 +77,19 @@ export function each<T>(
|
|
|
66
77
|
reconcile below) instead of rebuilding it. */
|
|
67
78
|
const cell = state(item) as State<unknown>
|
|
68
79
|
const indexCell = state(position)
|
|
80
|
+
/* Keyed rows key their path on the stable key; keyless on the position. */
|
|
81
|
+
const segment = keyed ? keyOf(item) : position
|
|
69
82
|
const hydration = RENDER.hydration
|
|
70
83
|
if (hydration !== undefined) {
|
|
71
84
|
const start = claimExpected(hydration, parent, 'each row start marker')
|
|
72
85
|
hydration.next.set(parent, start.nextSibling)
|
|
73
|
-
const dispose = group.track(
|
|
86
|
+
const dispose = group.track(
|
|
87
|
+
scope(() =>
|
|
88
|
+
withPathFrom(basePath, segment, () =>
|
|
89
|
+
render(parent, cell as State<T>, indexCell),
|
|
90
|
+
),
|
|
91
|
+
),
|
|
92
|
+
)
|
|
74
93
|
const end = claimExpected(hydration, parent, 'each row end marker')
|
|
75
94
|
hydration.next.set(parent, end.nextSibling)
|
|
76
95
|
return { start, end, dispose, cell, indexCell }
|
|
@@ -79,7 +98,7 @@ export function each<T>(
|
|
|
79
98
|
foreign namespace (so svg/math row children are namespaced, not built as HTML),
|
|
80
99
|
held in `pending` until placement inserts it. */
|
|
81
100
|
const { start, end, fragment, dispose } = buildDetachedRange(parent, (host) =>
|
|
82
|
-
render(host, cell as State<T>, indexCell),
|
|
101
|
+
withPathFrom(basePath, segment, () => render(host, cell as State<T>, indexCell)),
|
|
83
102
|
)
|
|
84
103
|
return { start, end, dispose: group.track(dispose), cell, indexCell, pending: fragment }
|
|
85
104
|
}
|
|
@@ -107,7 +126,9 @@ export function each<T>(
|
|
|
107
126
|
const hydration = RENDER.hydration
|
|
108
127
|
if (hydration !== undefined) {
|
|
109
128
|
let position = 0
|
|
110
|
-
|
|
129
|
+
/* An undefined source renders an empty list, not a throw — a `{#for x in promise}`
|
|
130
|
+
whose lifted source peeks undefined while pending (ADR-0032 D3). */
|
|
131
|
+
for (const item of items() ?? []) {
|
|
111
132
|
const key = keyOf(item)
|
|
112
133
|
const row = buildRow(item, position) // claims the SSR row where it sits
|
|
113
134
|
if (rows.has(key)) {
|
|
@@ -148,7 +169,8 @@ export function each<T>(
|
|
|
148
169
|
freshly keyed row. withoutHydration restores the outer cursor after, so the
|
|
149
170
|
enclosing hydrate pass is untouched (mirrors awaitBlock/tryBlock). */
|
|
150
171
|
withoutHydration(() => {
|
|
151
|
-
|
|
172
|
+
/* Undefined source → empty list (see the hydration loop, ADR-0032 D3). */
|
|
173
|
+
const list = source == null ? [] : Array.isArray(source) ? source : [...source]
|
|
152
174
|
const keys = list.map(keyOf)
|
|
153
175
|
generation += 1
|
|
154
176
|
const pass = generation
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { effect } from '../effect.ts'
|
|
2
|
+
import { CURRENT_BOUNDARY } from '../runtime/CURRENT_BOUNDARY.ts'
|
|
3
|
+
import { CURRENT_PATH } from '../runtime/CURRENT_PATH.ts'
|
|
2
4
|
import { claimChild } from '../runtime/claimChild.ts'
|
|
3
5
|
import { generationGuard } from '../runtime/generationGuard.ts'
|
|
4
6
|
import { RENDER } from '../runtime/RENDER.ts'
|
|
5
7
|
import { scopeGroup } from '../runtime/scopeGroup.ts'
|
|
6
8
|
import type { State } from '../runtime/types/State.ts'
|
|
9
|
+
import { withPathFrom } from '../runtime/withPathFrom.ts'
|
|
7
10
|
import { state } from '../state.ts'
|
|
8
11
|
import { buildDetachedRange } from './buildDetachedRange.ts'
|
|
9
12
|
import { removeRange } from './removeRange.ts'
|
|
@@ -32,11 +35,23 @@ export function eachAsync<T>(
|
|
|
32
35
|
`each`; the streaming runtime rebuilds the row on a re-yield rather than patching, and
|
|
33
36
|
the position is the stream arrival ordinal (a stream only appends, never reorders). */
|
|
34
37
|
render: (parent: Node, item: State<T>, index: State<number>) => void,
|
|
35
|
-
/* Absent → an iterator rejection
|
|
38
|
+
/* Absent → an iterator rejection routes to the enclosing {#try} boundary captured at build
|
|
39
|
+
(if any), else surfaces, instead of rendering a catch branch. */
|
|
36
40
|
renderCatch: ((parent: Node, error: unknown) => void) | undefined,
|
|
37
41
|
before: Node | null = null,
|
|
42
|
+
/* Explicit `by` key → the row keys its render-path segment on the stable key; else on the
|
|
43
|
+
stream arrival ordinal (a stream only appends, so the ordinal is stable). */
|
|
44
|
+
keyed = false,
|
|
38
45
|
): void {
|
|
39
46
|
const rows = new Map<string, EachRow>()
|
|
47
|
+
/* This each's render-path ancestry, captured at build; each row builds under
|
|
48
|
+
`basePath/segment` so a component/cell in a streamed row gets a stable id even though the
|
|
49
|
+
row arrives after the ambient path is gone. */
|
|
50
|
+
const basePath = CURRENT_PATH.current
|
|
51
|
+
/* The enclosing {#try} boundary ambient at BUILD (see `awaitBlock`). A catch-less rejection
|
|
52
|
+
arrives LATER from the async drain, after CURRENT_BOUNDARY has been restored, so capture
|
|
53
|
+
it into this closure now rather than read it at settle time. */
|
|
54
|
+
const capturedBoundary = CURRENT_BOUNDARY.current
|
|
40
55
|
/* Each row's (and the error branch's) scope, registered with the owner so they
|
|
41
56
|
dispose on owner teardown; the block's own teardown only stops the stream. */
|
|
42
57
|
const group = scopeGroup()
|
|
@@ -87,6 +102,17 @@ export function eachAsync<T>(
|
|
|
87
102
|
iterator?.return?.(undefined)?.catch(() => undefined) // close the superseded run's iterator before re-streaming
|
|
88
103
|
iterator = undefined
|
|
89
104
|
clearError() // a fresh run drops a prior error branch
|
|
105
|
+
/* A reseed streams a FRESH source (e.g. `room(newId)`), so the prior run's rows are stale.
|
|
106
|
+
Drop them now rather than relying on the drain's completion prune below — a never-
|
|
107
|
+
completing source (a socket) never reaches that prune, so a key the new stream doesn't
|
|
108
|
+
re-yield would otherwise leak in the DOM and the `rows` map forever (and mix the old
|
|
109
|
+
source's rows in with the new). Keys the new stream does re-yield rebuild fresh (the
|
|
110
|
+
block rebuilds on every re-yield regardless), so nothing reusable is lost. */
|
|
111
|
+
for (const [, row] of rows) {
|
|
112
|
+
row.dispose()
|
|
113
|
+
removeRange(row.start, row.end)
|
|
114
|
+
}
|
|
115
|
+
rows.clear()
|
|
90
116
|
const iterable = items() // read (subscribe) synchronously
|
|
91
117
|
const present = new Set<string>()
|
|
92
118
|
let arrivals = 0 // stream arrival ordinal → each row's index
|
|
@@ -110,9 +136,14 @@ export function eachAsync<T>(
|
|
|
110
136
|
const stale = rows.get(key)
|
|
111
137
|
const cell = state(result.value) as State<unknown>
|
|
112
138
|
const indexCell = state(arrivals)
|
|
139
|
+
const segment = keyed ? key : arrivals
|
|
113
140
|
arrivals += 1
|
|
114
141
|
rows.set(key, {
|
|
115
|
-
...insertRange((host) =>
|
|
142
|
+
...insertRange((host) =>
|
|
143
|
+
withPathFrom(basePath, segment, () =>
|
|
144
|
+
render(host, cell as State<T>, indexCell),
|
|
145
|
+
),
|
|
146
|
+
),
|
|
116
147
|
cell,
|
|
117
148
|
indexCell,
|
|
118
149
|
})
|
|
@@ -133,8 +164,13 @@ export function eachAsync<T>(
|
|
|
133
164
|
if (!guard.live(generationAtStart)) {
|
|
134
165
|
return
|
|
135
166
|
}
|
|
136
|
-
/* No catch branch →
|
|
167
|
+
/* No catch branch → route to the enclosing {#try} boundary captured at build if one
|
|
168
|
+
was ambient; otherwise surface the rejection (mirrors `<template await>`). */
|
|
137
169
|
if (renderCatch === undefined) {
|
|
170
|
+
if (capturedBoundary !== undefined) {
|
|
171
|
+
capturedBoundary.handle(error)
|
|
172
|
+
return
|
|
173
|
+
}
|
|
138
174
|
throw error
|
|
139
175
|
}
|
|
140
176
|
/* Keep the streamed rows; render the catch branch after them, at the anchor. */
|
|
@@ -24,15 +24,14 @@ export function fillBoundary(
|
|
|
24
24
|
close: Comment,
|
|
25
25
|
build: (host: Node, props?: UiProps) => void,
|
|
26
26
|
props: UiProps | undefined,
|
|
27
|
-
label: string | undefined,
|
|
28
27
|
): { dispose: () => void } {
|
|
29
28
|
const hydration = RENDER.hydration
|
|
30
29
|
if (hydration === undefined) {
|
|
31
|
-
return fillRange(open, close, build, props
|
|
30
|
+
return fillRange(open, close, build, props)
|
|
32
31
|
}
|
|
33
32
|
/* Hydrate: adopt the server content between the markers in place. */
|
|
34
33
|
const parent = open.parentNode as Node
|
|
35
34
|
hydration.next.set(parent, open.nextSibling)
|
|
36
|
-
const scoped = withScope(
|
|
35
|
+
const scoped = withScope(() => scope(() => build(parent, props)))
|
|
37
36
|
return { dispose: disposeRange(scoped, open, close) }
|
|
38
37
|
}
|
|
@@ -14,8 +14,9 @@ selector-transparent (a true direct child of its parent, no `<abide-name>` wrapp
|
|
|
14
14
|
Brackets a render pass (a nested child continues the parent's block-id counter) and
|
|
15
15
|
establishes the child's lexical scope in `awaiting` mode so it adopts the model doc
|
|
16
16
|
its first `doc()` creates. The disposer stops the content's reactivity, disposes the
|
|
17
|
-
lexical scope, and clears the range — leaving the markers, so a
|
|
18
|
-
place. Shared by `mountRange` (create
|
|
17
|
+
lexical scope, and clears the range — leaving the markers, so a later rebuild
|
|
18
|
+
(navigation, control-flow re-fill) fills in place. Shared by `mountRange` (create
|
|
19
|
+
branch) and `fillBoundary` (a page/layout outlet boundary).
|
|
19
20
|
*/
|
|
20
21
|
// @documentation plumbing
|
|
21
22
|
export function fillRange(
|
|
@@ -23,8 +24,7 @@ export function fillRange(
|
|
|
23
24
|
end: Comment,
|
|
24
25
|
build: (host: Node, props?: UiProps) => void,
|
|
25
26
|
props: UiProps | undefined,
|
|
26
|
-
label: string | undefined,
|
|
27
27
|
): { start: Comment; end: Comment; dispose: () => void } {
|
|
28
|
-
const scoped = withScope(
|
|
28
|
+
const scoped = withScope(() => fillBefore(end, (fragment) => build(fragment, props)))
|
|
29
29
|
return { start, end, dispose: disposeRange(scoped, start, end) }
|
|
30
30
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { wakeHydrationPeeks } from '../../shared/wakeHydrationPeeks.ts'
|
|
1
|
+
import { hydrationWindow } from '../../shared/hydrationWindow.ts'
|
|
3
2
|
import { RENDER } from '../runtime/RENDER.ts'
|
|
4
3
|
import { scope } from '../runtime/scope.ts'
|
|
5
|
-
import { scopeLabel } from './scopeLabel.ts'
|
|
6
4
|
import { withScope } from './withScope.ts'
|
|
7
5
|
|
|
8
6
|
/*
|
|
@@ -27,21 +25,17 @@ export function hydrate(
|
|
|
27
25
|
props?: unknown,
|
|
28
26
|
): () => void {
|
|
29
27
|
const previous = RENDER.hydration
|
|
30
|
-
const previousHydrating = hydratingSlot.active
|
|
31
28
|
RENDER.hydration = { next: new Map() }
|
|
32
|
-
|
|
29
|
+
hydrationWindow.enter()
|
|
33
30
|
try {
|
|
34
31
|
/* Same shared mount core as `mount` (see `withScope`) — a hydrated component owns a
|
|
35
32
|
scope too, adopting the model its build adopts — run with the claim cursor active. */
|
|
36
|
-
const { lexical } = withScope(
|
|
33
|
+
const { lexical } = withScope(() => scope(() => build(host, props)))
|
|
37
34
|
return () => lexical.dispose()
|
|
38
35
|
} finally {
|
|
39
36
|
RENDER.hydration = previous
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (!previousHydrating) {
|
|
44
|
-
wakeHydrationPeeks()
|
|
45
|
-
}
|
|
37
|
+
/* Outermost exit clears the window and wakes the peeks this pass withheld; a nested
|
|
38
|
+
child hydrate raises/lowers the depth without firing it early. */
|
|
39
|
+
hydrationWindow.exit()
|
|
46
40
|
}
|
|
47
41
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* A comment node carrying exactly `data`. Empty-child check distinguishes the
|
|
2
2
|
`<!--data-->` marker from an element that happens to expose a `data` property.
|
|
3
|
-
Shared by the marker-range scanners in appendText
|
|
3
|
+
Shared by the marker-range scanners in appendText. */
|
|
4
4
|
export function isComment(node: Node, data: string): boolean {
|
|
5
5
|
return (node as { data?: string }).data === data && node.childNodes.length === 0
|
|
6
6
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { commentData } from './commentData.ts'
|
|
2
|
+
import { markerDepthDelta } from './markerDepthDelta.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
The `]` close marker that balances the `[` range opened by `open`, found by a DEPTH-counting
|
|
6
|
+
scan over the following siblings. A branch's content can hold nested `[`…`]` (if/switch/each)
|
|
7
|
+
and `abide:`…`/abide:` (await/try/snippet) ranges — `markerDepthDelta` scores each open `+1`
|
|
8
|
+
and each close `-1`, so an INNER close is skipped and only the one that returns to depth 0 is
|
|
9
|
+
this range's own. Unlike `discardBoundary`'s named-boundary scan (a unique id-suffixed close
|
|
10
|
+
string), the `[`/`]` alphabet is non-unique, so the depth count is what tells nested closes
|
|
11
|
+
apart. Throws on a desync (no balancing close) rather than over-scan to end-of-parent.
|
|
12
|
+
*/
|
|
13
|
+
export function matchingRangeClose(open: Node): Node {
|
|
14
|
+
let depth = 1
|
|
15
|
+
let node = open.nextSibling
|
|
16
|
+
while (node !== null) {
|
|
17
|
+
const data = commentData(node)
|
|
18
|
+
if (data !== undefined) {
|
|
19
|
+
depth += markerDepthDelta(data)
|
|
20
|
+
if (depth === 0) {
|
|
21
|
+
return node
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
node = node.nextSibling
|
|
25
|
+
}
|
|
26
|
+
throw new Error(
|
|
27
|
+
'[abide] hydration desync: a control-flow range open marker "[" has no balancing close "]" — the server DOM is truncated or a block diverged.',
|
|
28
|
+
)
|
|
29
|
+
}
|