@abide/abide 0.48.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +85 -59
- package/CHANGELOG.md +216 -0
- package/README.md +21 -10
- package/package.json +15 -4
- package/src/abideLsp.ts +5 -6
- package/src/abideResolverPlugin.ts +197 -169
- package/src/build.ts +79 -56
- package/src/buildCli.ts +3 -5
- package/src/buildDisconnected.ts +2 -5
- package/src/bundleApp.ts +2 -3
- package/src/checkAbide.ts +12 -2
- package/src/compile.ts +2 -4
- package/src/devEntry.ts +113 -28
- package/src/discoveryEntry.ts +3 -2
- package/src/lib/bundle/disconnected.abide +69 -73
- package/src/lib/bundle/infoPlist.ts +13 -7
- package/src/lib/bundle/installDownloads.ts +13 -3
- package/src/lib/bundle/installMacMenu.ts +13 -3
- package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
- package/src/lib/bundle/waitForServer.ts +6 -1
- package/src/lib/cli/parseArgvForRpc.ts +36 -9
- package/src/lib/cli/tokenizeArgvFlags.ts +4 -2
- package/src/lib/mcp/createMcpResourceServer.ts +13 -2
- package/src/lib/mcp/mcpTools.ts +15 -6
- package/src/lib/mcp/toolResultFromResponse.ts +40 -12
- package/src/lib/server/error.ts +6 -6
- package/src/lib/server/json.ts +17 -3
- package/src/lib/server/rpc/defineRpc.ts +58 -24
- package/src/lib/server/rpc/parseArgs.ts +113 -14
- package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
- package/src/lib/server/rpc/runWithRpcTimeout.ts +12 -1
- package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
- package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
- package/src/lib/server/rpc/validationError.ts +1 -1
- package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
- package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
- package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
- package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
- package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
- package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
- package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
- package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
- package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
- package/src/lib/server/runtime/createServer.ts +120 -324
- package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
- package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
- package/src/lib/server/runtime/finalizeResponse.ts +11 -1
- package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
- package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
- package/src/lib/server/runtime/pathStore.ts +15 -0
- package/src/lib/server/runtime/registryManifests.ts +1 -1
- package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +24 -16
- package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
- package/src/lib/server/runtime/streamFromIterator.ts +30 -2
- package/src/lib/server/runtime/textResponse.ts +23 -0
- package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
- package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
- package/src/lib/server/runtime/types/RequestStore.ts +27 -0
- package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
- package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
- package/src/lib/server/sockets/defineSocket.ts +5 -4
- package/src/lib/server/sse.ts +5 -1
- package/src/lib/shared/ASYNC_CELL.ts +5 -0
- package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
- package/src/lib/shared/HTTP_METHODS.ts +6 -0
- package/src/lib/shared/HttpError.ts +1 -5
- package/src/lib/shared/MCP_PATH.ts +6 -0
- package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
- package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
- package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
- package/src/lib/shared/activeCacheStore.ts +1 -0
- package/src/lib/shared/activePage.ts +1 -0
- package/src/lib/shared/buildArtifact.ts +20 -0
- package/src/lib/shared/buildRpcRequest.ts +6 -2
- package/src/lib/shared/buildSocketOverChannel.ts +4 -3
- package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
- package/src/lib/shared/cache.ts +156 -131
- package/src/lib/shared/canonicalJson.ts +24 -1
- package/src/lib/shared/changeAffectsClient.ts +12 -7
- package/src/lib/shared/createCacheStore.ts +31 -8
- package/src/lib/shared/createChannelLog.ts +24 -0
- package/src/lib/shared/createLifecycleChannel.ts +7 -1
- package/src/lib/shared/createReachable.ts +47 -78
- package/src/lib/shared/createRemoteFunction.ts +52 -30
- package/src/lib/shared/createRpcServerProgram.ts +820 -0
- package/src/lib/shared/decodeRefJson.ts +4 -4
- package/src/lib/shared/decodeResponse.ts +2 -2
- package/src/lib/shared/decodeWireBody.ts +35 -0
- package/src/lib/shared/detectRpcMethod.ts +8 -1
- package/src/lib/shared/done.ts +8 -2
- package/src/lib/shared/encodeRefJson.ts +4 -4
- package/src/lib/shared/encodeWireBody.ts +18 -0
- package/src/lib/shared/exitOnBuildFailure.ts +4 -3
- package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
- package/src/lib/shared/generateDeclarations.ts +72 -0
- package/src/lib/shared/hasSeedableRequest.ts +25 -0
- package/src/lib/shared/hydrationWindow.ts +53 -0
- package/src/lib/shared/isAsyncCell.ts +11 -0
- package/src/lib/shared/isAsyncIterable.ts +8 -0
- package/src/lib/shared/isSubscribable.ts +3 -3
- package/src/lib/shared/isThenable.ts +9 -0
- package/src/lib/shared/jsonSchemaForType.ts +258 -0
- package/src/lib/shared/lenientDecode.ts +15 -0
- package/src/lib/shared/loadProjectTsConfig.ts +28 -0
- package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
- package/src/lib/shared/matchRoute.ts +4 -14
- package/src/lib/shared/parseEnv.ts +17 -6
- package/src/lib/shared/peek.ts +14 -0
- package/src/lib/shared/pending.ts +9 -6
- package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
- package/src/lib/shared/prepareRpcModule.ts +91 -93
- package/src/lib/shared/prepareSocketModule.ts +3 -2
- package/src/lib/shared/probeRegistries.ts +5 -18
- package/src/lib/shared/reachable.ts +7 -10
- package/src/lib/shared/refresh.ts +12 -2
- package/src/lib/shared/refreshing.ts +8 -2
- package/src/lib/shared/resolvedCellsSlot.ts +13 -0
- package/src/lib/shared/reviveWireField.ts +49 -0
- package/src/lib/shared/reviveWireOutput.ts +36 -0
- package/src/lib/shared/rpcServerForRoot.ts +25 -0
- package/src/lib/shared/scanPages.ts +25 -0
- package/src/lib/shared/serializeEnv.ts +18 -6
- package/src/lib/shared/snapshotShippable.ts +8 -7
- package/src/lib/shared/snippet.ts +11 -6
- package/src/lib/shared/streamedCellsSlot.ts +14 -0
- package/src/lib/shared/subscribableFromResponse.ts +4 -4
- package/src/lib/shared/subscribableProbes.ts +1 -1
- package/src/lib/shared/tailProbeSlot.ts +1 -1
- package/src/lib/shared/types/AsyncComputed.ts +20 -0
- package/src/lib/shared/types/AsyncState.ts +13 -0
- package/src/lib/shared/types/CacheEntry.ts +7 -7
- package/src/lib/shared/types/CacheOptions.ts +23 -37
- package/src/lib/shared/types/CachePolicy.ts +25 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
- package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
- package/src/lib/shared/types/HttpMethod.ts +3 -1
- package/src/lib/shared/types/InputCoercion.ts +17 -0
- package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
- package/src/lib/shared/types/OutputWirePlan.ts +17 -0
- package/src/lib/shared/types/PagesScan.ts +7 -0
- package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
- package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
- package/src/lib/shared/types/RemoteCallable.ts +8 -7
- package/src/lib/shared/types/RemoteFunction.ts +16 -15
- package/src/lib/shared/types/ResolvedCells.ts +11 -0
- package/src/lib/shared/types/ReturnBody.ts +15 -0
- package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
- package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
- package/src/lib/shared/types/Socket.ts +4 -4
- package/src/lib/shared/types/SsrPayload.ts +5 -1
- package/src/lib/shared/types/StreamPolicy.ts +11 -0
- package/src/lib/shared/types/StreamedCells.ts +19 -0
- package/src/lib/shared/types/StreamedResolution.ts +24 -6
- package/src/lib/shared/types/TailHooks.ts +1 -1
- package/src/lib/shared/types/WireKind.ts +11 -0
- package/src/lib/shared/url.ts +5 -0
- package/src/lib/shared/validationHttpError.ts +33 -0
- package/src/lib/shared/warmSeedKey.ts +16 -0
- package/src/lib/shared/wireJsonReplacer.ts +30 -0
- package/src/lib/shared/writeRpcDts.ts +11 -1
- package/src/lib/shared/writeTestSocketsDts.ts +1 -1
- package/src/lib/test/createTestApp.ts +19 -1
- package/src/lib/ui/README.md +1 -1
- package/src/lib/ui/activePendingCells.ts +14 -0
- package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
- package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -7
- package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
- package/src/lib/ui/compile/analyzeComponent.ts +89 -6
- package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
- package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
- package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
- package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
- package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
- package/src/lib/ui/compile/bindListenEvent.ts +5 -6
- package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
- package/src/lib/ui/compile/catchBinding.ts +7 -5
- package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
- package/src/lib/ui/compile/compileComponent.ts +19 -3
- package/src/lib/ui/compile/compileModule.ts +27 -58
- package/src/lib/ui/compile/compileSSR.ts +52 -13
- package/src/lib/ui/compile/compileShadow.ts +265 -42
- package/src/lib/ui/compile/composeProps.ts +15 -4
- package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
- package/src/lib/ui/compile/createShadowProgram.ts +35 -6
- package/src/lib/ui/compile/declaredNames.ts +36 -0
- package/src/lib/ui/compile/desugarSignals.ts +419 -37
- package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
- package/src/lib/ui/compile/generateBuild.ts +74 -17
- package/src/lib/ui/compile/generateSSR.ts +286 -75
- package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
- package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
- package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
- package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
- package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
- package/src/lib/ui/compile/isWhitespaceText.ts +10 -2
- package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
- package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
- package/src/lib/ui/compile/lowerContext.ts +10 -0
- package/src/lib/ui/compile/lowerDocAccess.ts +51 -10
- package/src/lib/ui/compile/lowerScript.ts +40 -4
- package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
- package/src/lib/ui/compile/parseTemplate.ts +18 -1090
- package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
- package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +26 -40
- package/src/lib/ui/compile/resolveReactiveExport.ts +4 -7
- package/src/lib/ui/compile/scopeCss.ts +27 -4
- package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
- package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
- package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
- package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
- package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
- package/src/lib/ui/compile/templateStartOffset.ts +15 -0
- package/src/lib/ui/compile/tryPlan.ts +4 -3
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
- package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
- package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
- package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
- package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
- package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
- package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
- package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
- package/src/lib/ui/computed.ts +28 -1
- package/src/lib/ui/createScope.ts +35 -68
- package/src/lib/ui/dom/anchoredBranch.ts +142 -0
- package/src/lib/ui/dom/appendText.ts +8 -3
- package/src/lib/ui/dom/awaitBlock.ts +62 -75
- package/src/lib/ui/dom/bindProp.ts +22 -0
- package/src/lib/ui/dom/bindableProp.ts +47 -0
- package/src/lib/ui/dom/cellPending.ts +24 -0
- package/src/lib/ui/dom/discardBoundary.ts +24 -6
- package/src/lib/ui/dom/disposeRange.ts +2 -1
- package/src/lib/ui/dom/each.ts +41 -12
- package/src/lib/ui/dom/eachAsync.ts +39 -3
- package/src/lib/ui/dom/fillBefore.ts +6 -7
- package/src/lib/ui/dom/fillBoundary.ts +2 -3
- package/src/lib/ui/dom/fillRange.ts +4 -4
- package/src/lib/ui/dom/hydrate.ts +6 -12
- package/src/lib/ui/dom/isComment.ts +1 -1
- package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
- package/src/lib/ui/dom/mergeProps.ts +1 -1
- package/src/lib/ui/dom/mount.ts +1 -2
- package/src/lib/ui/dom/mountChild.ts +9 -40
- package/src/lib/ui/dom/mountRange.ts +2 -3
- package/src/lib/ui/dom/mountSlot.ts +1 -1
- package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
- package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
- package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
- package/src/lib/ui/dom/on.ts +2 -2
- package/src/lib/ui/dom/readCell.ts +40 -0
- package/src/lib/ui/dom/restProps.ts +2 -2
- package/src/lib/ui/dom/spreadProps.ts +3 -4
- package/src/lib/ui/dom/switchBlock.ts +30 -7
- package/src/lib/ui/dom/tryBlock.ts +233 -70
- package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
- package/src/lib/ui/dom/when.ts +12 -2
- package/src/lib/ui/dom/withScope.ts +9 -2
- package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
- package/src/lib/ui/flight.ts +56 -0
- package/src/lib/ui/html.ts +12 -1
- package/src/lib/ui/isolateCellBarrier.ts +17 -0
- package/src/lib/ui/linked.ts +48 -2
- package/src/lib/ui/props.ts +17 -0
- package/src/lib/ui/remoteProxy.ts +84 -159
- package/src/lib/ui/renderChain.ts +53 -13
- package/src/lib/ui/renderToStream.ts +22 -18
- package/src/lib/ui/resumeSeedScript.ts +1 -1
- package/src/lib/ui/router.ts +86 -53
- package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
- package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
- package/src/lib/ui/runtime/CHILD_PRESENT.ts +2 -2
- package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
- package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
- package/src/lib/ui/runtime/RENDER.ts +10 -7
- package/src/lib/ui/runtime/RESUME.ts +4 -4
- package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
- package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
- package/src/lib/ui/runtime/applyPatchToTree.ts +17 -4
- package/src/lib/ui/runtime/blockId.ts +31 -0
- package/src/lib/ui/runtime/boundaryFor.ts +11 -0
- package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
- package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
- package/src/lib/ui/runtime/createDoc.ts +31 -54
- package/src/lib/ui/runtime/createEffectNode.ts +9 -0
- package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
- package/src/lib/ui/runtime/flushEffects.ts +33 -1
- package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
- package/src/lib/ui/runtime/nextBlockId.ts +8 -7
- package/src/lib/ui/runtime/renderPath.ts +16 -0
- package/src/lib/ui/runtime/scope.ts +11 -0
- package/src/lib/ui/runtime/toTeardown.ts +10 -3
- package/src/lib/ui/runtime/types/Boundary.ts +9 -0
- package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
- package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
- package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
- package/src/lib/ui/runtime/types/UiProps.ts +6 -5
- package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
- package/src/lib/ui/runtime/withPath.ts +16 -0
- package/src/lib/ui/runtime/withPathFrom.ts +20 -0
- package/src/lib/ui/runtime/withoutHydration.ts +22 -0
- package/src/lib/ui/seedStreamedResolution.ts +31 -6
- package/src/lib/ui/settleAsyncCells.ts +24 -0
- package/src/lib/ui/socketProxy.ts +1 -1
- package/src/lib/ui/startClient.ts +16 -31
- package/src/lib/ui/state.ts +9 -3
- package/src/lib/ui/trackedComputed.ts +68 -0
- package/src/lib/ui/types/Scope.ts +8 -24
- package/src/lib/ui/watch.ts +3 -3
- package/src/serverEntry.ts +14 -0
- package/template/src/server/rpc/getHello.ts +15 -13
- package/template/test/app.test.ts +1 -1
- package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
- package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
- package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
- package/src/lib/shared/hasReplayableRequest.ts +0 -17
- package/src/lib/shared/hydratingSlot.ts +0 -12
- package/src/lib/shared/outboxProbeSlot.ts +0 -20
- package/src/lib/shared/types/Outbox.ts +0 -9
- package/src/lib/shared/types/OutboxEntry.ts +0 -27
- package/src/lib/shared/types/SmartReadOptions.ts +0 -36
- package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
- package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
- package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
- package/src/lib/ui/compile/markupTokens.ts +0 -313
- package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
- package/src/lib/ui/dom/applyResolved.ts +0 -87
- package/src/lib/ui/dom/scopeLabel.ts +0 -21
- package/src/lib/ui/dom/text.ts +0 -20
- package/src/lib/ui/history.ts +0 -101
- package/src/lib/ui/installHotBridge.ts +0 -93
- package/src/lib/ui/installInspectorBridge.ts +0 -140
- package/src/lib/ui/outbox.ts +0 -35
- package/src/lib/ui/persist.ts +0 -115
- package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
- package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
- package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
- package/src/lib/ui/runtime/captureModelDoc.ts +0 -28
- package/src/lib/ui/runtime/hotInstances.ts +0 -10
- package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
- package/src/lib/ui/runtime/hotReplace.ts +0 -38
- package/src/lib/ui/runtime/liveScopes.ts +0 -15
- package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
- package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
- package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
- package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
- package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
- package/src/lib/ui/seedResolved.ts +0 -28
- package/src/lib/ui/sync.ts +0 -48
- package/src/lib/ui/types/History.ts +0 -14
- package/src/lib/ui/types/PersistHandle.ts +0 -11
- package/src/lib/ui/types/PersistenceStore.ts +0 -12
- package/src/lib/ui/types/ResolvedFrame.ts +0 -15
- package/src/lib/ui/types/SyncTransport.ts +0 -13
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { ASYNC_CELL } from '../../shared/ASYNC_CELL.ts'
|
|
2
|
+
import { decodeRefJson } from '../../shared/decodeRefJson.ts'
|
|
3
|
+
import { isAsyncIterable } from '../../shared/isAsyncIterable.ts'
|
|
4
|
+
import { isThenable } from '../../shared/isThenable.ts'
|
|
5
|
+
import { resolvedCellsSlot } from '../../shared/resolvedCellsSlot.ts'
|
|
6
|
+
import { streamedCellsSlot } from '../../shared/streamedCellsSlot.ts'
|
|
7
|
+
import type { AsyncComputed } from '../../shared/types/AsyncComputed.ts'
|
|
8
|
+
import type { AsyncState } from '../../shared/types/AsyncState.ts'
|
|
9
|
+
import type { NamedAsyncIterable } from '../../shared/types/NamedAsyncIterable.ts'
|
|
10
|
+
import { warmSeedKey } from '../../shared/warmSeedKey.ts'
|
|
11
|
+
import { activePendingCells } from '../activePendingCells.ts'
|
|
12
|
+
import type { Scope } from '../types/Scope.ts'
|
|
13
|
+
import { CELL_SEED } from './CELL_SEED.ts'
|
|
14
|
+
import { CURRENT_SCOPE } from './CURRENT_SCOPE.ts'
|
|
15
|
+
import { createEffectNode } from './createEffectNode.ts'
|
|
16
|
+
import { createSignalNode } from './createSignalNode.ts'
|
|
17
|
+
import { readNode } from './readNode.ts'
|
|
18
|
+
import { registerStreamedCell } from './STREAMED_CELLS.ts'
|
|
19
|
+
import { writeNode } from './writeNode.ts'
|
|
20
|
+
|
|
21
|
+
/* The seed's transform gate (mirrors `state`/`linked`): coerces every value entering
|
|
22
|
+
the store, whether from a settling promise, a frame, or a `set()`. */
|
|
23
|
+
type Transform = (next: unknown, previous: unknown) => unknown
|
|
24
|
+
|
|
25
|
+
type AsyncCellOptions = {
|
|
26
|
+
writable: boolean
|
|
27
|
+
transform?: Transform
|
|
28
|
+
/* ADR-0032: a STREAMING cell must NOT join the SSR blocking barrier. A no-`await` async
|
|
29
|
+
value/content position lowers to a cell that ships pending-`undefined` in the shell and
|
|
30
|
+
resolves on the client, rather than blocking the first flush inline (that is the `await`
|
|
31
|
+
tier). Absent/false → today's blocking registration (`await` tier / a keyed bare read).
|
|
32
|
+
A stream (`isAsyncIterable`) never registers regardless — this only gates a promise seed. */
|
|
33
|
+
streaming?: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
The source-agnostic async cell — the runtime behind `computed`/`linked` when the seed
|
|
38
|
+
tracks a promise or a NamedAsyncIterable (ADR-0019 D1). It is **eager**: a reactive
|
|
39
|
+
effect runs the seed once at construction (capturing its synchronous dependencies) and
|
|
40
|
+
re-runs — a *reseed* — whenever they change, so independent cells load in parallel and a
|
|
41
|
+
dependent cell re-loads when its dependency settles. The seed's result drives the cell:
|
|
42
|
+
|
|
43
|
+
- a **promise** (an `async () => await …` thunk) → unwrap; resolve → value, reject → error;
|
|
44
|
+
- a **NamedAsyncIterable** (socket / streaming rpc) → `for await` its frames, each frame
|
|
45
|
+
updating the value; the terminal error/close land on `error()` / clear in-flight;
|
|
46
|
+
- a **synchronous value** (a seed that produced one directly) → settle immediately.
|
|
47
|
+
|
|
48
|
+
Read through the probe family only — there is no `.value`. `pending()` is "no value yet",
|
|
49
|
+
`refreshing()` is "a held value while a fresher source is in flight" (a reseed or a
|
|
50
|
+
`refresh()`), `peek()` is the retained value/latest frame (stale-while-revalidate),
|
|
51
|
+
`error()` the last rejection. Every out-of-order settle is guarded by a run id, so a slow
|
|
52
|
+
prior flight never overwrites a newer one. `refresh()` re-invokes the seed keeping the
|
|
53
|
+
value visible; a writable cell adds `set()`, which latches until the next reseed.
|
|
54
|
+
*/
|
|
55
|
+
export function createAsyncCell(
|
|
56
|
+
seed: () => unknown,
|
|
57
|
+
options: AsyncCellOptions,
|
|
58
|
+
): AsyncComputed<unknown> | AsyncState<unknown> {
|
|
59
|
+
const transform = options.transform
|
|
60
|
+
/* Reactive state, each its own signal so a reader subscribes only to the facets it
|
|
61
|
+
touches (peeking the value does not re-run on a pending flip, and vice versa). */
|
|
62
|
+
const valueNode = createSignalNode(undefined)
|
|
63
|
+
const errorNode = createSignalNode(undefined)
|
|
64
|
+
const inFlightNode = createSignalNode(true)
|
|
65
|
+
const hasValueNode = createSignalNode(false)
|
|
66
|
+
|
|
67
|
+
/* This cell's serialization-stable warm-seed key: its scope's render-path id + a per-scope
|
|
68
|
+
index, drawn at construction (in declaration order) so SSR and client agree on it. Minted
|
|
69
|
+
through the shared `warmSeedKey` so BOTH sides of the handoff (this same isomorphic call,
|
|
70
|
+
server-side keying `resolvedCells`/`streamedCells` and client-side reading `CELL_SEED`) form
|
|
71
|
+
the identical string from one definition. Undefined for a detached cell (no scope) — such a
|
|
72
|
+
cell never crosses SSR→client, so never warm-seeds. */
|
|
73
|
+
const scope = CURRENT_SCOPE.current as (Scope & { nextCellIndex: () => number }) | undefined
|
|
74
|
+
const warmKey = scope !== undefined ? warmSeedKey(scope.id, scope.nextCellIndex()) : undefined
|
|
75
|
+
|
|
76
|
+
/* `linked` write latch: a local `set()` holds the cell until the next reseed, so an
|
|
77
|
+
arriving frame / settling promise never clobbers an in-progress edit. Cleared on reseed. */
|
|
78
|
+
let written = false
|
|
79
|
+
/* Supersedes out-of-order settles: only the latest run's promise/stream may write. */
|
|
80
|
+
let runId = 0
|
|
81
|
+
/* The current run's in-flight promise (undefined once settled, or for a stream/sync seed).
|
|
82
|
+
Surfaced through `settled()` for the SSR barrier's structural read; a stream never sets it. */
|
|
83
|
+
let inFlight: Promise<unknown> | undefined
|
|
84
|
+
/* Cancels the active stream subscription (a reseed, refresh, or dispose supersedes it). */
|
|
85
|
+
let cancelStream: (() => void) | undefined
|
|
86
|
+
|
|
87
|
+
/* Store a produced value, honouring the write latch and the transform gate. */
|
|
88
|
+
const acceptValue = (value: unknown): void => {
|
|
89
|
+
if (written) {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
writeNode(valueNode, transform === undefined ? value : transform(value, valueNode.value))
|
|
93
|
+
writeNode(hasValueNode, true)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const settleValue = (myRun: number, value: unknown): void => {
|
|
97
|
+
if (myRun !== runId) {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
inFlight = undefined
|
|
101
|
+
acceptValue(value)
|
|
102
|
+
writeNode(errorNode, undefined)
|
|
103
|
+
writeNode(inFlightNode, false)
|
|
104
|
+
/* Server-only: record the resolved value keyed by this cell's render-path id (the `window`
|
|
105
|
+
guard keeps client settles from ever recording). BLOCKING cells → `resolvedCells`: the
|
|
106
|
+
barrier awaits them before the template peeks, so the value bakes into the SSR HTML and
|
|
107
|
+
the page renderer stamps it into the PRE-mount `__SSR__.cells` warm-seed — a warm client
|
|
108
|
+
value MATCHES it (no flash, no duplication). STREAMING cells → `streamedCells` (ADR-0035):
|
|
109
|
+
they ship pending in the shell, so their value can't ride the pre-mount seed (it would
|
|
110
|
+
diverge from the pending markup); it's streamed AFTER the shell as an `__abideResolve`
|
|
111
|
+
chunk the client adopts POST-hydration. Recording a VALUE (not awaiting a promise) means a
|
|
112
|
+
cell that never settles this request is simply never recorded — no hang. */
|
|
113
|
+
if (warmKey !== undefined && typeof window === 'undefined') {
|
|
114
|
+
if (options.streaming === true) {
|
|
115
|
+
streamedCellsSlot.get()?.entries.push({ key: warmKey, value })
|
|
116
|
+
} else {
|
|
117
|
+
resolvedCellsSlot.get()?.entries.push({ key: warmKey, value })
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* An error retains the value (SWR): a failed background refresh keeps the stale value
|
|
123
|
+
visible and surfaces the rejection through `error()` — the read decides what to do. */
|
|
124
|
+
const settleError = (myRun: number, error: unknown): void => {
|
|
125
|
+
if (myRun !== runId) {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
inFlight = undefined
|
|
129
|
+
writeNode(errorNode, error)
|
|
130
|
+
writeNode(inFlightNode, false)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Drive a NamedAsyncIterable: first frame clears pending, each later frame updates the
|
|
134
|
+
value (unless written), a throw lands in `error()`, and completion clears in-flight. */
|
|
135
|
+
const consumeStream = (myRun: number, source: NamedAsyncIterable<unknown>): void => {
|
|
136
|
+
const iterator = source[Symbol.asyncIterator]()
|
|
137
|
+
let live = true
|
|
138
|
+
cancelStream = (): void => {
|
|
139
|
+
live = false
|
|
140
|
+
void iterator.return?.(undefined)
|
|
141
|
+
}
|
|
142
|
+
const pump = async (): Promise<void> => {
|
|
143
|
+
try {
|
|
144
|
+
while (live && myRun === runId) {
|
|
145
|
+
const step = await iterator.next()
|
|
146
|
+
if (!live || myRun !== runId) {
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
if (step.done === true) {
|
|
150
|
+
writeNode(inFlightNode, false)
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
acceptValue(step.value)
|
|
154
|
+
writeNode(errorNode, undefined)
|
|
155
|
+
writeNode(inFlightNode, false)
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
settleError(myRun, error)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/* The pump never rejects (its body is fully try/caught), so the kickoff is contained
|
|
162
|
+
— no unhandled rejection, never Bun-fatal. */
|
|
163
|
+
void pump()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* Run (or re-run) the seed. `reseed` marks a dependency-driven new source, which clears
|
|
167
|
+
the write latch; a `refresh()` passes false so a held write and value survive. */
|
|
168
|
+
const run = (reseed: boolean): void => {
|
|
169
|
+
runId += 1
|
|
170
|
+
const myRun = runId
|
|
171
|
+
if (cancelStream !== undefined) {
|
|
172
|
+
cancelStream()
|
|
173
|
+
cancelStream = undefined
|
|
174
|
+
}
|
|
175
|
+
if (reseed) {
|
|
176
|
+
written = false
|
|
177
|
+
}
|
|
178
|
+
writeNode(inFlightNode, true)
|
|
179
|
+
|
|
180
|
+
let produced: unknown
|
|
181
|
+
try {
|
|
182
|
+
produced = seed()
|
|
183
|
+
} catch (error) {
|
|
184
|
+
settleError(myRun, error)
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (isAsyncIterable(produced)) {
|
|
189
|
+
consumeStream(myRun, produced as NamedAsyncIterable<unknown>)
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
if (isThenable(produced)) {
|
|
193
|
+
inFlight = produced as Promise<unknown>
|
|
194
|
+
/* Server-only: register the in-flight promise on the request-scoped pending list so
|
|
195
|
+
the SSR barrier (`$$settleAsyncCells`) awaits it before the template peeks the
|
|
196
|
+
cell — baking the resolved value into the first-pass HTML (ADR-0019 Tier-2). A
|
|
197
|
+
stream (the `isAsyncIterable` branch above) never registers: it never settles. The
|
|
198
|
+
`window` guard keeps client construction from ever registering. A `streaming`
|
|
199
|
+
cell (ADR-0032, a no-`await` position) opts OUT: it ships pending and resolves on
|
|
200
|
+
the client instead of blocking the flush. */
|
|
201
|
+
if (typeof window === 'undefined' && options.streaming !== true) {
|
|
202
|
+
activePendingCells()?.promises.push(inFlight)
|
|
203
|
+
}
|
|
204
|
+
/* `.then(onValue, onError)` handles the rejection inline — contained in `error()`,
|
|
205
|
+
never an unhandled rejection. A STREAMING cell's resolved value is recorded in
|
|
206
|
+
`settleValue` (ADR-0035), not here — awaiting the promise could hang if it never
|
|
207
|
+
settles this request. */
|
|
208
|
+
;(produced as PromiseLike<unknown>).then(
|
|
209
|
+
(value) => settleValue(myRun, value),
|
|
210
|
+
(error) => settleError(myRun, error),
|
|
211
|
+
)
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
settleValue(myRun, produced)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Warm hydrate: if the server shipped this cell's resolved value (keyed by its render-path
|
|
218
|
+
id), adopt it NOW — before the eager run — so `hasValue` is already true. The cell then reads
|
|
219
|
+
as REFRESHING (not pending) through the eager run below, so the value shows instantly with no
|
|
220
|
+
flash and matches the SSR-rendered branch. The run still fires (revalidate + subscribe the
|
|
221
|
+
seed's deps for reactivity, SWR), and its settle supersedes via the `runId` guard. Client-
|
|
222
|
+
only in effect (the store is only ever populated by `startClient`, so it's empty on the
|
|
223
|
+
server — no `window` sniff needed); a decode failure falls through to a cold run. */
|
|
224
|
+
if (warmKey !== undefined) {
|
|
225
|
+
const seeded = CELL_SEED[warmKey]
|
|
226
|
+
if (seeded !== undefined) {
|
|
227
|
+
/* One-shot: consume the seed so a LATER fresh mount at the same render-path can't
|
|
228
|
+
warm-adopt this boot-time snapshot. `scope.id` is the route PATTERN (params-
|
|
229
|
+
independent), so an SPA navigation `/products/42`→`/products/99` — or a back-nav
|
|
230
|
+
that rebuilds the SSR page — recomputes the identical warmKey; without deleting,
|
|
231
|
+
the new cell would render `/products/42`'s stale value until revalidation. The seed
|
|
232
|
+
only ever hydrates the initial SSR render, where each cell is constructed once. */
|
|
233
|
+
delete CELL_SEED[warmKey]
|
|
234
|
+
try {
|
|
235
|
+
acceptValue(decodeRefJson(seeded))
|
|
236
|
+
} catch {
|
|
237
|
+
/* Unserializable/corrupt seed → cold run renders it. */
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* The eager first-run + reactive reseed: the effect tracks the seed's synchronous reads,
|
|
243
|
+
so a dependency change re-runs it. Its cleanup cancels any live stream on reseed/dispose. */
|
|
244
|
+
createEffectNode(() => {
|
|
245
|
+
run(true)
|
|
246
|
+
return (): void => {
|
|
247
|
+
if (cancelStream !== undefined) {
|
|
248
|
+
cancelStream()
|
|
249
|
+
cancelStream = undefined
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
/* Client-only, STREAMING cells (ADR-0035): a streaming cell ships pending and its value streams
|
|
255
|
+
in AFTER the shell. Register — AFTER the eager run above, so its `inFlight = true` is already
|
|
256
|
+
set — to receive that value by render-path id; when it lands, adopt it as a reactive update:
|
|
257
|
+
the value shows immediately (no `loading…` flash) and the in-flight cold seed run re-settles
|
|
258
|
+
the same value, superseded by the write latch. The streamed value only ever seeds the initial
|
|
259
|
+
render; a later dep-driven reseed is authoritative and runs the seed as usual. */
|
|
260
|
+
if (warmKey !== undefined && options.streaming === true && typeof window !== 'undefined') {
|
|
261
|
+
registerStreamedCell(warmKey, (value) => {
|
|
262
|
+
acceptValue(value)
|
|
263
|
+
writeNode(errorNode, undefined)
|
|
264
|
+
writeNode(inFlightNode, false)
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* The shared read surface, identical for read-only and writable cells. */
|
|
269
|
+
const readOnly: AsyncComputed<unknown> = {
|
|
270
|
+
[ASYNC_CELL]: true,
|
|
271
|
+
peek: () => readNode(valueNode),
|
|
272
|
+
/* No value yet: in flight and nothing retained. */
|
|
273
|
+
pending: () => readNode(inFlightNode) === true && readNode(hasValueNode) !== true,
|
|
274
|
+
/* A held value being superseded: in flight but a value is retained. */
|
|
275
|
+
refreshing: () => readNode(inFlightNode) === true && readNode(hasValueNode) === true,
|
|
276
|
+
error: () => readNode(errorNode),
|
|
277
|
+
/* Re-invoke the seed keeping the value visible (SWR); not a reseed, so a write holds. */
|
|
278
|
+
refresh: () => run(false),
|
|
279
|
+
/* The current in-flight promise (undefined once settled / not-a-promise) — an internal
|
|
280
|
+
runtime affordance the SSR barrier reads structurally; not on the public cell types. */
|
|
281
|
+
settled: () => inFlight,
|
|
282
|
+
} as AsyncComputed<unknown>
|
|
283
|
+
if (!options.writable) {
|
|
284
|
+
return readOnly
|
|
285
|
+
}
|
|
286
|
+
/* A writable cell adds `set()`: latches until the next reseed. `settled` rides along from
|
|
287
|
+
the `readOnly` spread (a runtime affordance not on the public type). */
|
|
288
|
+
const writable: AsyncState<unknown> = {
|
|
289
|
+
...readOnly,
|
|
290
|
+
set: (value: unknown): void => {
|
|
291
|
+
written = true
|
|
292
|
+
writeNode(
|
|
293
|
+
valueNode,
|
|
294
|
+
transform === undefined ? value : transform(value, valueNode.value),
|
|
295
|
+
)
|
|
296
|
+
writeNode(hasValueNode, true)
|
|
297
|
+
},
|
|
298
|
+
}
|
|
299
|
+
return writable
|
|
300
|
+
}
|
|
@@ -2,7 +2,6 @@ import { applyPatchToTree } from './applyPatchToTree.ts'
|
|
|
2
2
|
import { batch } from './batch.ts'
|
|
3
3
|
import { createComputedNode } from './createComputedNode.ts'
|
|
4
4
|
import { createSignalNode } from './createSignalNode.ts'
|
|
5
|
-
import { PATCH_BUS } from './PATCH_BUS.ts'
|
|
6
5
|
import { pathSegments } from './pathSegments.ts'
|
|
7
6
|
import { readNode } from './readNode.ts'
|
|
8
7
|
import { trigger } from './trigger.ts'
|
|
@@ -59,12 +58,9 @@ export function createDoc(initial: unknown): Doc {
|
|
|
59
58
|
truth. Held apart from `nodes` so the structural wake/eviction never touches
|
|
60
59
|
them; their dirtiness is driven entirely by the deps they read (the signal
|
|
61
60
|
graph), not by tree mutations. They are not in `tree`, so `snapshot` omits
|
|
62
|
-
them, and they never pass through `apply
|
|
63
|
-
|
|
61
|
+
them, and they never pass through `apply` — a recompute is a downstream reaction
|
|
62
|
+
of the signal graph, not a tree change. */
|
|
64
63
|
const computed = new Map<string, ReactiveNode>()
|
|
65
|
-
/* Set to the returned document before any apply runs, so a PATCH_BUS event can
|
|
66
|
-
name the document it came from (reference identity, the undo/persistence key). */
|
|
67
|
-
let self: Doc
|
|
68
64
|
|
|
69
65
|
/* Links `path` into the trie under its parent and continues up the ancestor chain.
|
|
70
66
|
Stops at the first ancestor whose parent set already lists it — that link, and
|
|
@@ -227,10 +223,6 @@ export function createDoc(initial: unknown): Doc {
|
|
|
227
223
|
/* Segments index the tree, so they carry the REAL keys (unescaped); the path
|
|
228
224
|
strings (parentPath, node-map keys) stay escaped, re-walked through walkPath. */
|
|
229
225
|
const segments = patch.path === '' ? [] : pathSegments(patch.path)
|
|
230
|
-
/* Capture the pre-image only when a consumer is listening (the inverse's only
|
|
231
|
-
cost): a replace/remove inverts to the value it overwrote, an add to a
|
|
232
|
-
remove (computed post-apply, below, to resolve an array append's index). */
|
|
233
|
-
const before = PATCH_BUS.active ? walkPath(tree, patch.path) : undefined
|
|
234
226
|
tree = applyPatchToTree(tree, patch, segments)
|
|
235
227
|
const parentPath = parentPathOf(patch.path)
|
|
236
228
|
const parentValue = walkPath(tree, parentPath).value
|
|
@@ -270,40 +262,9 @@ export function createDoc(initial: unknown): Doc {
|
|
|
270
262
|
} else {
|
|
271
263
|
wakeSubtree(parentPath, true, true)
|
|
272
264
|
}
|
|
273
|
-
/* Announce the change before effects flush, so a patch an effect emits in
|
|
274
|
-
reaction lands AFTER this one on the bus — the journal stays chronological.
|
|
275
|
-
Emitting inside the batch keeps it ahead of the depth-0 flush on batch exit. */
|
|
276
|
-
if (PATCH_BUS.active) {
|
|
277
|
-
PATCH_BUS.emit({ doc: self, patch, inverse: inverseOf(patch, before) })
|
|
278
|
-
}
|
|
279
265
|
})
|
|
280
266
|
}
|
|
281
267
|
|
|
282
|
-
/* The patch that undoes `patch`, from the pre-image `before` (a value the change
|
|
283
|
-
overwrote/removed) and the now-mutated tree. An add inverts to removing the slot
|
|
284
|
-
it created — resolving an array append (`-`) to the concrete last index it took.
|
|
285
|
-
A replace/remove of a path that held nothing inverts to remove/nothing. */
|
|
286
|
-
function inverseOf(
|
|
287
|
-
patch: Patch,
|
|
288
|
-
before: ReturnType<typeof walkPath> | undefined,
|
|
289
|
-
): Patch | undefined {
|
|
290
|
-
if (patch.op === 'add') {
|
|
291
|
-
const parentPath = parentPathOf(patch.path)
|
|
292
|
-
const parent = walkPath(tree, parentPath).value
|
|
293
|
-
const resolved =
|
|
294
|
-
Array.isArray(parent) && patch.path.endsWith('/-')
|
|
295
|
-
? `${parentPath}/${parent.length - 1}`
|
|
296
|
-
: patch.path
|
|
297
|
-
return { op: 'remove', path: resolved }
|
|
298
|
-
}
|
|
299
|
-
if (patch.op === 'replace') {
|
|
300
|
-
return before?.exists
|
|
301
|
-
? { op: 'replace', path: patch.path, value: before.value }
|
|
302
|
-
: { op: 'remove', path: patch.path }
|
|
303
|
-
}
|
|
304
|
-
return before?.exists ? { op: 'add', path: patch.path, value: before.value } : undefined
|
|
305
|
-
}
|
|
306
|
-
|
|
307
268
|
/*
|
|
308
269
|
A stable accessor bound to one scalar leaf — what the compiler emits so a hot
|
|
309
270
|
loop has zero string work: the node, the parent container, and the leaf key
|
|
@@ -316,30 +277,46 @@ export function createDoc(initial: unknown): Doc {
|
|
|
316
277
|
const node = nodeFor(path)
|
|
317
278
|
const segments = pathSegments(path)
|
|
318
279
|
const leafKey = segments[segments.length - 1] as string
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
280
|
+
const ancestors = segments.slice(0, -1)
|
|
281
|
+
/* The resolved parent container, cached after the first write. `get` never needs
|
|
282
|
+
it (it reads through `node`), so it is not computed at cell creation. */
|
|
283
|
+
let parent: Record<string, unknown> | undefined
|
|
284
|
+
/* Auto-vivify missing ancestor objects so a `set` on a nested path bound over a
|
|
285
|
+
doc booted shallow (e.g. `state({})`) writes into the LIVE tree (snapshot/persist
|
|
286
|
+
see it) rather than crashing. Done LAZILY on the first write, NOT at cell
|
|
287
|
+
creation: `hoistCells` lifts a cell to component-mount scope, so vivifying eagerly
|
|
288
|
+
would fabricate container structure for a path only ever written behind a branch /
|
|
289
|
+
in a handler that never runs. Mirrors applyPatchToTree's container assumption,
|
|
290
|
+
but the intermediates may not exist yet since this walk is compiler-emitted. */
|
|
291
|
+
const resolveParent = (): Record<string, unknown> => {
|
|
292
|
+
let current = tree as Record<string, unknown>
|
|
293
|
+
for (let index = 0; index < ancestors.length; index += 1) {
|
|
294
|
+
const segment = ancestors[index] as string
|
|
295
|
+
let next = current[segment]
|
|
296
|
+
if (next === null || typeof next !== 'object') {
|
|
297
|
+
/* Create an array when the NEXT segment addresses an array element (a
|
|
298
|
+
numeric index or the `-` push slot), else a plain object — otherwise a
|
|
299
|
+
bound path like `items/0/name` on a shallow doc fabricates `items` as an
|
|
300
|
+
object keyed by "0", which a later `add("items/-", …)` can't push into. */
|
|
301
|
+
const childKey = (ancestors[index + 1] ?? leafKey) as string
|
|
302
|
+
next = childKey === '-' || /^\d+$/.test(childKey) ? [] : {}
|
|
303
|
+
current[segment] = next
|
|
304
|
+
}
|
|
305
|
+
current = next as Record<string, unknown>
|
|
330
306
|
}
|
|
331
|
-
|
|
307
|
+
return current
|
|
332
308
|
}
|
|
333
309
|
return {
|
|
334
310
|
get: () => readNode(node) as T,
|
|
335
311
|
set: (value: T) => {
|
|
312
|
+
parent ??= resolveParent()
|
|
336
313
|
parent[leafKey] = value
|
|
337
314
|
writeNode(node, value)
|
|
338
315
|
},
|
|
339
316
|
}
|
|
340
317
|
}
|
|
341
318
|
|
|
342
|
-
self = {
|
|
319
|
+
const self: Doc = {
|
|
343
320
|
read,
|
|
344
321
|
cell,
|
|
345
322
|
derive,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { boundaryFor } from './boundaryFor.ts'
|
|
2
|
+
import { CURRENT_BOUNDARY } from './CURRENT_BOUNDARY.ts'
|
|
1
3
|
import { CURRENT_SCOPE } from './CURRENT_SCOPE.ts'
|
|
2
4
|
import { inScope } from './inScope.ts'
|
|
3
5
|
import { NODE_STATE } from './NODE_STATE.ts'
|
|
@@ -67,5 +69,12 @@ export function createEffectNode(fn: () => EffectResult): () => void {
|
|
|
67
69
|
if (OWNER.current !== undefined) {
|
|
68
70
|
OWNER.current.push(dispose)
|
|
69
71
|
}
|
|
72
|
+
/* Effects built inside a reactive `{#try}` associate with its boundary so a LATER re-run
|
|
73
|
+
throw (an async cell that rejects post-mount, read via a throwing peek) routes there
|
|
74
|
+
rather than the collect-and-rethrow fallback. Populated only under a boundary — the
|
|
75
|
+
node shape stays untouched, the lookup is cold (only on the throw path). */
|
|
76
|
+
if (CURRENT_BOUNDARY.current !== undefined) {
|
|
77
|
+
boundaryFor.set(node, CURRENT_BOUNDARY.current)
|
|
78
|
+
}
|
|
70
79
|
return dispose
|
|
71
80
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { RENDER } from './RENDER.ts'
|
|
2
2
|
|
|
3
|
-
/* Marks entry into a render/mount. The OUTERMOST one (depth 0)
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/* Marks entry into a render/mount. The OUTERMOST one (depth 0) clears the per-path
|
|
4
|
+
block-id counters so every render pass starts each path at 0 (a re-navigation to the
|
|
5
|
+
same route must not continue a prior pass's counter); a child component's render/mount
|
|
6
|
+
runs at depth > 0 and continues the same map. Pair with `exitRenderPass`. */
|
|
6
7
|
// @documentation plumbing
|
|
7
8
|
export function enterRenderPass(): void {
|
|
8
9
|
if (RENDER.depth === 0) {
|
|
9
|
-
RENDER.
|
|
10
|
+
RENDER.blockCounters.clear()
|
|
10
11
|
}
|
|
11
12
|
RENDER.depth += 1
|
|
12
13
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { boundaryFor } from './boundaryFor.ts'
|
|
2
|
+
import { NODE_STATE } from './NODE_STATE.ts'
|
|
1
3
|
import { REACTIVE_CONTEXT } from './REACTIVE_CONTEXT.ts'
|
|
2
4
|
import type { ReactiveNode } from './types/ReactiveNode.ts'
|
|
3
5
|
import { updateIfNecessary } from './updateIfNecessary.ts'
|
|
@@ -39,13 +41,43 @@ export function flushEffects(): void {
|
|
|
39
41
|
|
|
40
42
|
function drain(): void {
|
|
41
43
|
let spare: ReactiveNode[] = []
|
|
44
|
+
let errors: unknown[] | undefined
|
|
42
45
|
do {
|
|
43
46
|
const batch = REACTIVE_CONTEXT.pendingEffects
|
|
44
47
|
REACTIVE_CONTEXT.pendingEffects = spare
|
|
45
48
|
for (let index = 0; index < batch.length; index += 1) {
|
|
46
|
-
|
|
49
|
+
const node = batch[index] as ReactiveNode
|
|
50
|
+
try {
|
|
51
|
+
updateIfNecessary(node)
|
|
52
|
+
} catch (error) {
|
|
53
|
+
/* A node built inside a reactive `{#try}` routes its throw to that boundary
|
|
54
|
+
(swap the guarded content to the catch branch) instead of the rethrow
|
|
55
|
+
fallback — this is the "later re-run throw" the sync boundary could not
|
|
56
|
+
catch. Reset it CLEAN either way so a later write to its deps can re-queue
|
|
57
|
+
it (otherwise `mark`'s CLEAN→dirty gate leaves it permanently inert). */
|
|
58
|
+
const boundary = boundaryFor.get(node)
|
|
59
|
+
if (boundary !== undefined) {
|
|
60
|
+
boundary.handle(error)
|
|
61
|
+
node.status = NODE_STATE.CLEAN
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
/* No boundary — one effect throwing must not strand the effects queued behind
|
|
65
|
+
it (they live in this same `batch`, unreachable once we swap
|
|
66
|
+
`pendingEffects`); keep draining and surface the error(s) once the graph has
|
|
67
|
+
settled rather than swallowing them. */
|
|
68
|
+
node.status = NODE_STATE.CLEAN
|
|
69
|
+
if (errors === undefined) {
|
|
70
|
+
errors = []
|
|
71
|
+
}
|
|
72
|
+
errors.push(error)
|
|
73
|
+
}
|
|
47
74
|
}
|
|
48
75
|
batch.length = 0
|
|
49
76
|
spare = batch
|
|
50
77
|
} while (REACTIVE_CONTEXT.pendingEffects.length > 0)
|
|
78
|
+
if (errors !== undefined) {
|
|
79
|
+
throw errors.length === 1
|
|
80
|
+
? errors[0]
|
|
81
|
+
: new AggregateError(errors, 'abide: effects threw during flush')
|
|
82
|
+
}
|
|
51
83
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
True for an `async () => …` / `async function () {}`. This is the async cell's
|
|
3
|
+
`await` marker at runtime: the `computed`/`linked` transform lowers `computed(await p)`
|
|
4
|
+
to `computed(async () => await p)`, so an async-function seed means "unwrap the
|
|
5
|
+
promise and track it" — as opposed to a plain thunk returning a promise, which is held
|
|
6
|
+
opaque (`Computed<Promise<T>>`). The tag is the reliable signal (`.constructor.name`
|
|
7
|
+
survives minification less reliably; `Symbol.toStringTag` is spec-stable).
|
|
8
|
+
*/
|
|
9
|
+
export function isAsyncFunction(value: unknown): boolean {
|
|
10
|
+
return (
|
|
11
|
+
typeof value === 'function' &&
|
|
12
|
+
(value as { [Symbol.toStringTag]?: unknown })[Symbol.toStringTag] === 'AsyncFunction'
|
|
13
|
+
)
|
|
14
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { blockId } from './blockId.ts'
|
|
1
2
|
import { RENDER } from './RENDER.ts'
|
|
2
3
|
|
|
3
|
-
/* The next block id in the current render pass
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/* The next block id in the current render pass, namespaced by the ambient render-path
|
|
5
|
+
(ADR-0037). `await`/`try` blocks draw it in document order WITHIN their path; a page
|
|
6
|
+
id and a child component's id can never collide because they carry different paths, so
|
|
7
|
+
ids stay congruent SSR↔client even when the server renders sibling children concurrently.
|
|
8
|
+
Delegates to the shared `blockId` over the client's per-pass `RENDER.blockCounters` map. */
|
|
6
9
|
// @documentation plumbing
|
|
7
|
-
export function nextBlockId():
|
|
8
|
-
|
|
9
|
-
RENDER.blockId += 1
|
|
10
|
-
return id
|
|
10
|
+
export function nextBlockId(): string {
|
|
11
|
+
return blockId(RENDER.blockCounters)
|
|
11
12
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CURRENT_PATH } from './CURRENT_PATH.ts'
|
|
2
|
+
import { withPath } from './withPath.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
The render-path a `<Child/>` mounts under — the boundary id of a STREAMED child component (ADR-0039).
|
|
6
|
+
Composes the child's ordinal segment onto the ambient path exactly as the client's `mountChild` /
|
|
7
|
+
`mountStreamedChild` does (`withPath(ordinal, …)`), so the server-written `abide:await:CHILDPATH`
|
|
8
|
+
boundary and the client adopter compute the SAME id with no counter to drift. It carries `/`
|
|
9
|
+
separators and never a `:`, so a child path (`products/0`) can't collide with an await-block id
|
|
10
|
+
(`products:0`). Server-emit-only (the streaming boundary is server codegen), so it tree-shakes out of
|
|
11
|
+
the client bundle like `$$flight`.
|
|
12
|
+
*/
|
|
13
|
+
// @documentation plumbing
|
|
14
|
+
export function renderPath(segment: string | number): string {
|
|
15
|
+
return withPath(segment, () => CURRENT_PATH.current)
|
|
16
|
+
}
|
|
@@ -23,6 +23,17 @@ export function scope(build: () => void): () => void {
|
|
|
23
23
|
OWNER.current = disposers
|
|
24
24
|
try {
|
|
25
25
|
untrack(build)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
/* A build that throws partway (e.g. a hydration desync deep in a subtree) has
|
|
28
|
+
already created and subscribed some effects/listeners. The caller never
|
|
29
|
+
receives this scope's disposer on the throw path, so tear down what was built —
|
|
30
|
+
in reverse, children before parents — before rethrowing; otherwise those effects
|
|
31
|
+
stay subscribed forever with no owner able to dispose them. */
|
|
32
|
+
for (let index = disposers.length - 1; index >= 0; index -= 1) {
|
|
33
|
+
disposers[index]?.()
|
|
34
|
+
}
|
|
35
|
+
disposers.length = 0
|
|
36
|
+
throw error
|
|
26
37
|
} finally {
|
|
27
38
|
OWNER.current = previous
|
|
28
39
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { abideLog } from '../../shared/abideLog.ts'
|
|
1
2
|
import type { EffectResult } from './types/EffectResult.ts'
|
|
2
3
|
import type { Teardown } from './types/Teardown.ts'
|
|
3
4
|
|
|
@@ -15,15 +16,21 @@ export function toTeardown(result: EffectResult): Teardown | undefined {
|
|
|
15
16
|
}
|
|
16
17
|
if (result instanceof Promise) {
|
|
17
18
|
return () => {
|
|
18
|
-
/* Swallow a rejection: an async body that rejected (e.g. an aborted RPC) must
|
|
19
|
-
not surface as an unhandled rejection when the teardown runs at dispose. */
|
|
20
19
|
result.then(
|
|
21
20
|
(teardown) => {
|
|
22
21
|
if (typeof teardown === 'function') {
|
|
23
22
|
teardown()
|
|
24
23
|
}
|
|
25
24
|
},
|
|
26
|
-
() =>
|
|
25
|
+
(error) => {
|
|
26
|
+
/* A superseded reactive read aborts its in-flight RPC — expected, stays
|
|
27
|
+
quiet. Any OTHER rejection is a real bug in the async effect body;
|
|
28
|
+
surface it (the visibility goal) instead of silently discarding it. */
|
|
29
|
+
const aborted = (error as { name?: string } | undefined)?.name === 'AbortError'
|
|
30
|
+
if (!aborted) {
|
|
31
|
+
abideLog.error(error)
|
|
32
|
+
}
|
|
33
|
+
},
|
|
27
34
|
)
|
|
28
35
|
}
|
|
29
36
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
A reactive error boundary — the runtime handle a `{#try}` installs so a LATER re-run
|
|
3
|
+
throw from a guarded effect (an async cell that rejects after mount, read via a throwing
|
|
4
|
+
peek) routes back to it. `handle(error)` swaps the guarded content to the catch branch;
|
|
5
|
+
the scheduler (`flushEffects.drain`) calls it when a node associated with this boundary
|
|
6
|
+
(`boundaryFor`) throws during a flush. Kept minimal so the boundary can install itself as
|
|
7
|
+
the ambient `CURRENT_BOUNDARY` around a guarded build without dragging DOM types here.
|
|
8
|
+
*/
|
|
9
|
+
export type Boundary = { handle(error: unknown): void }
|