@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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import ts from 'typescript'
|
|
2
|
+
import { assignmentTargetNames } from './assignmentTargetNames.ts'
|
|
3
|
+
import { isPlainIdentifier } from './isPlainIdentifier.ts'
|
|
4
|
+
import type { TemplateNode } from './types/TemplateNode.ts'
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Every name the TEMPLATE writes — the counterpart to the leading script's own write
|
|
8
|
+
scan. Two ways a prop's name shows up written in markup:
|
|
9
|
+
• forwarded as a `bind:` target — `bind:value={value}` on a child element or component
|
|
10
|
+
passes the name as a two-way target, so it must be a writable cell (a bare identifier
|
|
11
|
+
only; a `{ get, set }` accessor carries its own write path and reads the prop plainly);
|
|
12
|
+
• assigned in a template expression — an event handler like `onclick={() => value += 1}`.
|
|
13
|
+
|
|
14
|
+
Collected across every JS fragment the markup carries (attribute/prop/event/text
|
|
15
|
+
expressions and nested `<script>` bodies), then intersected with the actual prop names by
|
|
16
|
+
the caller. Syntactic and conservative — see `assignmentTargetNames`.
|
|
17
|
+
*/
|
|
18
|
+
export function writtenTemplateNames(nodes: TemplateNode[]): Set<string> {
|
|
19
|
+
const names = new Set<string>()
|
|
20
|
+
/* One synthetic source of every expression fragment, parsed once for its write targets. */
|
|
21
|
+
let fragments = ''
|
|
22
|
+
const addFragment = (code: string | undefined): void => {
|
|
23
|
+
if (code !== undefined && code.trim() !== '') {
|
|
24
|
+
fragments += `${code};\n`
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/* A bare-identifier `bind:` target names a prop forwarded as a two-way channel. */
|
|
28
|
+
const addBindTarget = (code: string): void => {
|
|
29
|
+
if (isPlainIdentifier(code.trim())) {
|
|
30
|
+
names.add(code.trim())
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const walk = (node: TemplateNode): void => {
|
|
34
|
+
if (node.kind === 'text') {
|
|
35
|
+
for (const part of node.parts) {
|
|
36
|
+
if (part.kind === 'expression') {
|
|
37
|
+
addFragment(part.code)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
if (node.kind === 'script') {
|
|
43
|
+
addFragment(node.code)
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
if (node.kind === 'element') {
|
|
47
|
+
for (const attr of node.attrs) {
|
|
48
|
+
if (attr.kind === 'bind') {
|
|
49
|
+
addBindTarget(attr.code)
|
|
50
|
+
addFragment(attr.code)
|
|
51
|
+
} else if (attr.kind === 'interpolated') {
|
|
52
|
+
for (const part of attr.parts) {
|
|
53
|
+
if (part.kind === 'expression') {
|
|
54
|
+
addFragment(part.code)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} else if (attr.kind !== 'static') {
|
|
58
|
+
addFragment(attr.code)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (node.kind === 'component') {
|
|
63
|
+
for (const prop of node.props) {
|
|
64
|
+
if (prop.bind === true) {
|
|
65
|
+
addBindTarget(prop.code)
|
|
66
|
+
}
|
|
67
|
+
addFragment(prop.code)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if ('children' in node) {
|
|
71
|
+
for (const child of node.children) {
|
|
72
|
+
walk(child)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const node of nodes) {
|
|
77
|
+
walk(node)
|
|
78
|
+
}
|
|
79
|
+
if (fragments.trim() !== '') {
|
|
80
|
+
const source = ts.createSourceFile('template.ts', fragments, ts.ScriptTarget.Latest, true)
|
|
81
|
+
assignmentTargetNames(source, names)
|
|
82
|
+
}
|
|
83
|
+
return names
|
|
84
|
+
}
|
package/src/lib/ui/computed.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import type { AsyncComputed } from '../shared/types/AsyncComputed.ts'
|
|
2
|
+
import type { NamedAsyncIterable } from '../shared/types/NamedAsyncIterable.ts'
|
|
3
|
+
import { createAsyncCell } from './runtime/createAsyncCell.ts'
|
|
1
4
|
import { createComputedNode } from './runtime/createComputedNode.ts'
|
|
5
|
+
import { isAsyncFunction } from './runtime/isAsyncFunction.ts'
|
|
2
6
|
import { OWNER } from './runtime/OWNER.ts'
|
|
3
7
|
import { readNode } from './runtime/readNode.ts'
|
|
4
8
|
import type { Computed } from './runtime/types/Computed.ts'
|
|
@@ -13,8 +17,31 @@ would be a write to its *sources*, which is expressed at the binding site instea
|
|
|
13
17
|
(`bind:value={{ get, set }}`), where the write actually originates. Lazy: it
|
|
14
18
|
recomputes on read only when a dependency changed, and never serializes (it is
|
|
15
19
|
re-derived from its inputs on resume). Read via `.value`.
|
|
20
|
+
|
|
21
|
+
When the seed *tracks* an async source it becomes an async cell instead (ADR-0019
|
|
22
|
+
D1): an `async () => await …` thunk (the compiler's `await` lowering) unwraps the
|
|
23
|
+
promise, and a thunk producing a `NamedAsyncIterable` (socket / streaming rpc)
|
|
24
|
+
auto-tracks its frames. Both yield an `AsyncComputed<T>` — no `.value`, read through
|
|
25
|
+
the probe family (`peek`/`pending`/`refreshing`/`error`/`refresh`). A thunk that
|
|
26
|
+
returns a *bare* promise (no `await`) is held opaquely as a normal `Computed<Promise<T>>`
|
|
27
|
+
— render it with `{#await}`.
|
|
16
28
|
*/
|
|
17
|
-
export function computed<T>(compute: () => T):
|
|
29
|
+
export function computed<T>(compute: () => NamedAsyncIterable<T>): AsyncComputed<T>
|
|
30
|
+
export function computed<T>(compute: () => Promise<T>): AsyncComputed<T>
|
|
31
|
+
export function computed<T>(compute: () => T): Computed<T>
|
|
32
|
+
export function computed<T>(
|
|
33
|
+
compute: () => T | Promise<T> | NamedAsyncIterable<T>,
|
|
34
|
+
): Computed<T> | AsyncComputed<T> {
|
|
35
|
+
/* `await` marker: an async-function thunk unwraps its promise into a tracked async cell.
|
|
36
|
+
This is the only classification the read-only primitive can make without running the
|
|
37
|
+
seed — a sync `computed` MUST stay lazy (it does not compute until first read). A stream
|
|
38
|
+
seed (`computed(getStream())`) can only be told from a sync seed by running it, which
|
|
39
|
+
laziness forbids here; the compiler routes that eager path to `createAsyncCell` directly.
|
|
40
|
+
A bare (un-awaited) promise stays a lazy `Computed<Promise<T>>` — render it with `{#await}`. */
|
|
41
|
+
if (isAsyncFunction(compute)) {
|
|
42
|
+
return createAsyncCell(compute as () => unknown, { writable: false }) as AsyncComputed<T>
|
|
43
|
+
}
|
|
44
|
+
/* Sync value or a bare (opaque) promise: the lazy computed, unchanged. */
|
|
18
45
|
const node = createComputedNode(compute as () => unknown)
|
|
19
46
|
/* Tear down with the enclosing scope, the way an effect does. A computed only
|
|
20
47
|
unlinks from its sources when it re-runs (`runNode` re-tracking); one read
|
|
@@ -1,62 +1,68 @@
|
|
|
1
1
|
import { computed } from './computed.ts'
|
|
2
2
|
import { effect } from './effect.ts'
|
|
3
|
-
import { history } from './history.ts'
|
|
4
3
|
import { linked } from './linked.ts'
|
|
5
|
-
import {
|
|
4
|
+
import { CURRENT_PATH } from './runtime/CURRENT_PATH.ts'
|
|
6
5
|
import { createDoc } from './runtime/createDoc.ts'
|
|
7
|
-
import { liveScopes } from './runtime/liveScopes.ts'
|
|
8
6
|
import type { Cell } from './runtime/types/Cell.ts'
|
|
9
7
|
import type { Doc } from './runtime/types/Doc.ts'
|
|
10
8
|
import { state } from './state.ts'
|
|
11
|
-
import {
|
|
12
|
-
import type { History } from './types/History.ts'
|
|
13
|
-
import type { PersistHandle } from './types/PersistHandle.ts'
|
|
9
|
+
import { trackedComputed } from './trackedComputed.ts'
|
|
14
10
|
import type { Scope } from './types/Scope.ts'
|
|
15
11
|
|
|
16
|
-
/*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
/* The counter fallback for a DETACHED scope — one created outside any render (a bare
|
|
13
|
+
`scope()` on first use), where the ambient render-path is empty.
|
|
14
|
+
A rendered scope instead takes the serialization-stable render-path id (`CURRENT_PATH`,
|
|
15
|
+
route + tree position), so an async cell's warm-seed key (`${scope.id}:${index}`) is
|
|
16
|
+
stable across the SSR→client handoff; only the counter path remains merely run-unique. */
|
|
20
17
|
let nextId = 0
|
|
21
18
|
|
|
22
19
|
/*
|
|
23
20
|
Builds a lexical scope. Its data is a document — created eagerly from `initial`,
|
|
24
21
|
or (when `awaiting`) ADOPTED from the first `doc()` a component body creates under
|
|
25
22
|
it, so a scope can wrap the component's own model without changing the data
|
|
26
|
-
lowering. Data methods mirror `Doc` and delegate to that document;
|
|
27
|
-
|
|
28
|
-
`child` nests; `dispose` tears the subtree down children-first.
|
|
23
|
+
lowering. Data methods mirror `Doc` and delegate to that document; `child` nests;
|
|
24
|
+
`dispose` tears the subtree down children-first.
|
|
29
25
|
*/
|
|
30
26
|
export function createScope(
|
|
31
27
|
initial: unknown = {},
|
|
32
28
|
parent: Scope | undefined = undefined,
|
|
33
29
|
awaiting = false,
|
|
34
|
-
label: string | undefined = undefined,
|
|
35
30
|
): Scope {
|
|
36
31
|
/* Eager unless awaiting adoption; `data()` lazily mints an empty doc if a body
|
|
37
32
|
never created one (a stateless component that still reaches for its scope). */
|
|
38
33
|
let document: Doc | undefined = awaiting ? undefined : createDoc(initial)
|
|
39
34
|
const data = (): Doc => (document ??= createDoc({}))
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
/* The scope's serialization-stable id: the ambient render-path when created inside a render
|
|
36
|
+
(route + layout + branch/row + component ordinal, composed by `withPath` at each nesting
|
|
37
|
+
site), else the run-unique counter for a detached scope. Snapshotted at creation, so a
|
|
38
|
+
nested push doesn't mutate an ancestor's id. */
|
|
39
|
+
const renderPath = CURRENT_PATH.current
|
|
40
|
+
const id =
|
|
41
|
+
renderPath !== ''
|
|
42
|
+
? renderPath
|
|
43
|
+
: parent === undefined
|
|
44
|
+
? `scope-${nextId++}`
|
|
45
|
+
: `${parent.id}.${nextId++}`
|
|
42
46
|
/* Adopted build teardowns (the reactivity stopper from the mount core). Disposed
|
|
43
|
-
first and in reverse on teardown
|
|
44
|
-
|
|
47
|
+
first and in reverse on teardown — so the one `dispose` runs the order the call sites
|
|
48
|
+
hand-composed as `stop(); lexical.dispose()`. */
|
|
45
49
|
const owned: Array<() => void> = []
|
|
46
50
|
/* Context values shared down the tree, held apart from the reactive doc (which
|
|
47
51
|
a child does not inherit): keyed by name, read by the closest ancestor walk. */
|
|
48
52
|
const shared = new Map<string, unknown>()
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
/* Per-component monotonic index for the async cells constructed under this scope, in
|
|
54
|
+
declaration order — the local half of a cell's serialization-stable warm-seed key
|
|
55
|
+
(`${scope.id}:${index}`, see `createAsyncCell`). Per-scope (not global) so a client-only
|
|
56
|
+
sibling component can't shift another component's cell keys: divergence stays local. Both
|
|
57
|
+
SSR and client construct a component's cells in the same order, so the indices agree. */
|
|
58
|
+
let cellIndex = 0
|
|
52
59
|
|
|
53
|
-
/* `cell`
|
|
54
|
-
cell-hoisting
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const self: Scope & { cell: <T>(path: string) => Cell<T> } = {
|
|
60
|
+
/* `cell` and `nextCellIndex` are not on the public `Scope` type — `cell` is the compiler-only
|
|
61
|
+
cell-hoisting leaf; `nextCellIndex` is drawn by `createAsyncCell` for its warm-seed key.
|
|
62
|
+
Both stay on the runtime object but off the documented surface. */
|
|
63
|
+
const self: Scope & { cell: <T>(path: string) => Cell<T>; nextCellIndex: () => number } = {
|
|
58
64
|
id,
|
|
59
|
-
|
|
65
|
+
nextCellIndex: () => cellIndex++,
|
|
60
66
|
parent,
|
|
61
67
|
read: (path) => data().read(path),
|
|
62
68
|
replace: (path, value) => data().replace(path, value),
|
|
@@ -73,16 +79,11 @@ export function createScope(
|
|
|
73
79
|
state,
|
|
74
80
|
linked,
|
|
75
81
|
computed,
|
|
82
|
+
trackedComputed,
|
|
76
83
|
effect,
|
|
77
84
|
own: (dispose) => {
|
|
78
85
|
owned.push(dispose)
|
|
79
86
|
},
|
|
80
|
-
child: (childInitial = {}) => {
|
|
81
|
-
const created = createScope(childInitial, self)
|
|
82
|
-
children.push(created)
|
|
83
|
-
return created
|
|
84
|
-
},
|
|
85
|
-
root: () => (parent === undefined ? self : parent.root()),
|
|
86
87
|
/* Reference store — no tracking, so a lookup never subscribes; reactivity comes
|
|
87
88
|
from what is shared (a scope, whose doc is reactive), not from the share. */
|
|
88
89
|
share: (key, value) => {
|
|
@@ -93,49 +94,15 @@ export function createScope(
|
|
|
93
94
|
`undefined` from "not provided"; undefined at the root means no provider. */
|
|
94
95
|
shared: <T>(key: string): T | undefined =>
|
|
95
96
|
shared.has(key) ? (shared.get(key) as T) : parent?.shared<T>(key),
|
|
96
|
-
record: (options) => {
|
|
97
|
-
past ??= history(data(), options)
|
|
98
|
-
},
|
|
99
|
-
persist: (key) => {
|
|
100
|
-
persistence ??= persistDoc(data(), key ?? id)
|
|
101
|
-
},
|
|
102
|
-
broadcast: (transport) => {
|
|
103
|
-
unsync ??= sync(data(), transport)
|
|
104
|
-
},
|
|
105
|
-
undo: () => past?.undo(),
|
|
106
|
-
redo: () => past?.redo(),
|
|
107
|
-
canUndo: () => past?.canUndo() ?? false,
|
|
108
|
-
canRedo: () => past?.canRedo() ?? false,
|
|
109
97
|
dispose: () => {
|
|
110
|
-
/* Stop the build's reactivity
|
|
111
|
-
children and the boundary-crossing capabilities — the order the call sites
|
|
98
|
+
/* Stop the build's reactivity in reverse order — the order the call sites
|
|
112
99
|
hand-composed as `stop(); lexical.dispose()`. */
|
|
113
100
|
for (let index = owned.length - 1; index >= 0; index -= 1) {
|
|
114
101
|
owned[index]?.()
|
|
115
102
|
}
|
|
116
103
|
owned.length = 0
|
|
117
|
-
/* Children reverse too (last created first), so a later child that captured an
|
|
118
|
-
earlier sibling tears down before the sibling it depends on — LIFO like `owned`. */
|
|
119
|
-
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
120
|
-
children[index]?.dispose()
|
|
121
|
-
}
|
|
122
|
-
children.length = 0
|
|
123
104
|
shared.clear()
|
|
124
|
-
past?.dispose()
|
|
125
|
-
past = undefined
|
|
126
|
-
persistence?.dispose()
|
|
127
|
-
persistence = undefined
|
|
128
|
-
unsync?.()
|
|
129
|
-
unsync = undefined
|
|
130
|
-
if (liveScopes.enabled) {
|
|
131
|
-
liveScopes.scopes.delete(self)
|
|
132
|
-
}
|
|
133
105
|
},
|
|
134
106
|
}
|
|
135
|
-
/* Dev-only: register for the inspector's scope-tree view. Gated, so production
|
|
136
|
-
never touches the set. */
|
|
137
|
-
if (liveScopes.enabled) {
|
|
138
|
-
liveScopes.scopes.add(self)
|
|
139
|
-
}
|
|
140
107
|
return self
|
|
141
108
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { scope } from '../runtime/scope.ts'
|
|
2
|
+
import { buildDetachedRange } from './buildDetachedRange.ts'
|
|
3
|
+
import { removeRange } from './removeRange.ts'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
The single-active-branch skeleton shared by `awaitBlock` and `tryBlock`: one
|
|
7
|
+
marker-bounded `[`…`]` range parked before an `anchor`, swapped as a unit when the
|
|
8
|
+
block settles to a different branch. Both blocks previously copy-pasted this skeleton —
|
|
9
|
+
the `active` range record, `detach` (dispose + `removeRange`), the create-path `place`
|
|
10
|
+
(build a detached range and insert it before the anchor), and the hydration `adoptStrand`
|
|
11
|
+
guard — even though their ESSENTIAL orchestration (await's then/catch/finally settlement,
|
|
12
|
+
try's boundary recovery) is genuinely bespoke and stays in each block. This carries only
|
|
13
|
+
the accidental copy-paste, notably its two subtle guards:
|
|
14
|
+
|
|
15
|
+
1. the detached-anchor short-circuit in `place` (drop a late swap whose anchor has been
|
|
16
|
+
pulled from the tree), and
|
|
17
|
+
2. the adopt-strand-dispose guard in `adoptStrand` (a partial hydration build strands a
|
|
18
|
+
live sub-scope, so ALWAYS take its disposer and run it on ANY failure before
|
|
19
|
+
rethrowing, so the caller's cold rebuild starts clean).
|
|
20
|
+
|
|
21
|
+
`anchor` is a mutable field: each block parks its own anchor text node at create /
|
|
22
|
+
hydrate / rebuild time (placement is bespoke), and `place` reads it. The block owns its
|
|
23
|
+
`scopeGroup`; each built branch's disposer is tracked in it so it disposes on owner
|
|
24
|
+
teardown, not only on a swap.
|
|
25
|
+
*/
|
|
26
|
+
export function anchoredBranch(
|
|
27
|
+
parent: Node,
|
|
28
|
+
group: { track: (dispose: () => void) => () => void },
|
|
29
|
+
): {
|
|
30
|
+
anchor: Node | undefined
|
|
31
|
+
detach: () => void
|
|
32
|
+
place: (build: (host: Node) => void, wrapBuild?: (run: () => void) => void) => void
|
|
33
|
+
adoptStrand: (
|
|
34
|
+
build: (host: Node) => void,
|
|
35
|
+
afterBuild: () => { start: Comment; end: Comment },
|
|
36
|
+
) => void
|
|
37
|
+
} {
|
|
38
|
+
let active: { start: Comment; end: Comment; dispose: () => void } | undefined
|
|
39
|
+
let anchor: Node | undefined
|
|
40
|
+
|
|
41
|
+
const detach = (): void => {
|
|
42
|
+
if (active !== undefined) {
|
|
43
|
+
active.dispose()
|
|
44
|
+
/* `removeRange` evicts the markers AND everything between them via the end
|
|
45
|
+
marker's LIVE parent — not the captured `parent`, which (when the block is a
|
|
46
|
+
bare child of a control-flow branch) is the branch's build fragment, emptied
|
|
47
|
+
into the document once the enclosing block placed it. */
|
|
48
|
+
removeRange(active.start, active.end)
|
|
49
|
+
active = undefined
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Replace the current content with a freshly-built branch, before the anchor. The branch
|
|
54
|
+
builds into a detached `[`…`]`-bracketed fragment (so any content — components, text,
|
|
55
|
+
nested blocks — appends freely), the same create primitive the keyed-list runtimes use,
|
|
56
|
+
which lands as a marker-bounded range the next swap detaches with `removeRange`.
|
|
57
|
+
`wrapBuild` (tryBlock only) wraps just the build call so the guarded branch's effects
|
|
58
|
+
build under the ambient error boundary; detach/insert stay outside it. */
|
|
59
|
+
const place = (build: (host: Node) => void, wrapBuild?: (run: () => void) => void): void => {
|
|
60
|
+
/* Backstop for a swap whose anchor has been detached from the tree. The block's own
|
|
61
|
+
generation guard is the PRIMARY defense — it drops a late settle after the owner
|
|
62
|
+
tears down — but a gap (e.g. a nested hydration `adopt` that aborts to `rebuildCold`,
|
|
63
|
+
leaving the inner block's range removed while its guard stays live) can still route a
|
|
64
|
+
late swap here with `anchor` already pulled out of the DOM. Inserting before a node
|
|
65
|
+
that is no longer a child of any parent throws `NotFoundError` from `insertBefore` —
|
|
66
|
+
surfacing as a process-fatal unhandled rejection under Bun. A detached anchor
|
|
67
|
+
unambiguously means the block is gone, so drop the swap. */
|
|
68
|
+
if (anchor !== undefined && anchor.parentNode === null) {
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
detach()
|
|
72
|
+
const namespaceParent = anchor?.parentNode ?? parent
|
|
73
|
+
let built: ReturnType<typeof buildDetachedRange> | undefined
|
|
74
|
+
const run = (): void => {
|
|
75
|
+
built = buildDetachedRange(namespaceParent, build)
|
|
76
|
+
}
|
|
77
|
+
if (wrapBuild !== undefined) {
|
|
78
|
+
wrapBuild(run)
|
|
79
|
+
} else {
|
|
80
|
+
run()
|
|
81
|
+
}
|
|
82
|
+
const range = built as ReturnType<typeof buildDetachedRange>
|
|
83
|
+
const tracked = group.track(range.dispose)
|
|
84
|
+
namespaceParent.insertBefore(range.fragment, anchor ?? null)
|
|
85
|
+
active = { start: range.start, end: range.end, dispose: tracked }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Adopt an SSR branch in place (its content claims the existing nodes). Adoption is
|
|
89
|
+
guarded: a build that can't claim the server markup — a resume value that didn't
|
|
90
|
+
round-trip, a nested-adopt claim desync — throws, and the caller recovers by rebuilding
|
|
91
|
+
cold. But the partial build may have already created a live sub-scope (an inner `await`'s
|
|
92
|
+
effect/guard, a subscription) before it threw; letting the throw escape `scope()` would
|
|
93
|
+
strand that scope's disposer (unreachable → never disposed), leaking the effect AND
|
|
94
|
+
leaving its guard un-bumped so a late settle stays "live". So capture the build's error,
|
|
95
|
+
ALWAYS take the returned disposer, and dispose it on ANY failure before rethrowing — the
|
|
96
|
+
caller's cold rebuild then starts from a clean slate. `afterBuild` (bespoke per block)
|
|
97
|
+
claims/creates the range markers around the adopted nodes and parks the anchor, returning
|
|
98
|
+
the range's start/end; it runs only after a clean build. */
|
|
99
|
+
const adoptStrand = (
|
|
100
|
+
build: (host: Node) => void,
|
|
101
|
+
afterBuild: () => { start: Comment; end: Comment },
|
|
102
|
+
): void => {
|
|
103
|
+
let dispose: (() => void) | undefined
|
|
104
|
+
try {
|
|
105
|
+
let buildFailed = false
|
|
106
|
+
let buildError: unknown
|
|
107
|
+
dispose = group.track(
|
|
108
|
+
scope(() => {
|
|
109
|
+
try {
|
|
110
|
+
build(parent)
|
|
111
|
+
} catch (error) {
|
|
112
|
+
buildFailed = true
|
|
113
|
+
buildError = error
|
|
114
|
+
}
|
|
115
|
+
}),
|
|
116
|
+
)
|
|
117
|
+
if (buildFailed) {
|
|
118
|
+
throw buildError
|
|
119
|
+
}
|
|
120
|
+
const range = afterBuild()
|
|
121
|
+
active = { start: range.start, end: range.end, dispose }
|
|
122
|
+
} catch (error) {
|
|
123
|
+
/* `dispose` (the group-tracked wrapper) is idempotent; running it here tears the
|
|
124
|
+
partial branch scope down and drops it from the group so the cold rebuild doesn't
|
|
125
|
+
inherit a stranded scope, then the caller falls back to a cold build. */
|
|
126
|
+
dispose?.()
|
|
127
|
+
throw error
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
get anchor(): Node | undefined {
|
|
133
|
+
return anchor
|
|
134
|
+
},
|
|
135
|
+
set anchor(node: Node | undefined) {
|
|
136
|
+
anchor = node
|
|
137
|
+
},
|
|
138
|
+
detach,
|
|
139
|
+
place,
|
|
140
|
+
adoptStrand,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -38,7 +38,10 @@ export function appendText(parent: Node, read: () => unknown, splitAlways = fals
|
|
|
38
38
|
const hydration = RENDER.hydration
|
|
39
39
|
if (hydration !== undefined) {
|
|
40
40
|
const claimed = claimChild(hydration, parent)
|
|
41
|
-
|
|
41
|
+
/* Nullish reads render as empty text, never the literal `"undefined"` — so a
|
|
42
|
+
pending async read (undefined-while-pending, ADR-0032 D3) shows nothing. */
|
|
43
|
+
const firstValue = read()
|
|
44
|
+
const value = firstValue == null ? '' : String(firstValue)
|
|
42
45
|
/* A value that first rendered empty produced NO server text node, so the cursor
|
|
43
46
|
points at the following node (an element/comment) or past the end (null) — not a
|
|
44
47
|
text node to claim. Bind to a Text node either way: claim the merged SSR node when
|
|
@@ -74,14 +77,16 @@ export function appendText(parent: Node, read: () => unknown, splitAlways = fals
|
|
|
74
77
|
at the end). */
|
|
75
78
|
hydration.next.set(parent, isText ? node.nextSibling : claimed)
|
|
76
79
|
effect(() => {
|
|
77
|
-
|
|
80
|
+
const next = read()
|
|
81
|
+
node.data = next == null ? '' : String(next)
|
|
78
82
|
})
|
|
79
83
|
return
|
|
80
84
|
}
|
|
81
85
|
const node = document.createTextNode('')
|
|
82
86
|
parent.appendChild(node)
|
|
83
87
|
effect(() => {
|
|
84
|
-
|
|
88
|
+
const next = read()
|
|
89
|
+
node.data = next == null ? '' : String(next)
|
|
85
90
|
})
|
|
86
91
|
}
|
|
87
92
|
|