@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
package/src/lib/shared/cache.ts
CHANGED
|
@@ -4,13 +4,14 @@ import { cacheStores } from './cacheStores.ts'
|
|
|
4
4
|
import { decodeResponse } from './decodeResponse.ts'
|
|
5
5
|
import { getRemoteMeta } from './getRemoteMeta.ts'
|
|
6
6
|
import { HttpError } from './HttpError.ts'
|
|
7
|
-
import {
|
|
7
|
+
import { hydrationWindow } from './hydrationWindow.ts'
|
|
8
8
|
import { invalidateEvent } from './invalidateEvent.ts'
|
|
9
9
|
import { invalidateTripwire } from './invalidateTripwire.ts'
|
|
10
10
|
import { keyForRemoteCall } from './keyForRemoteCall.ts'
|
|
11
11
|
import { producerKey } from './producerKey.ts'
|
|
12
12
|
import { REMOTE_FUNCTION } from './REMOTE_FUNCTION.ts'
|
|
13
13
|
import { REPLAYABLE_METHODS } from './REPLAYABLE_METHODS.ts'
|
|
14
|
+
import { reviveWireOutput } from './reviveWireOutput.ts'
|
|
14
15
|
import { rpcErrorRegistry } from './rpcErrorRegistry.ts'
|
|
15
16
|
import { SocketDisconnectedError } from './SocketDisconnectedError.ts'
|
|
16
17
|
import { selectorMatcher } from './selectorMatcher.ts'
|
|
@@ -21,11 +22,13 @@ import { toTagSet } from './toTagSet.ts'
|
|
|
21
22
|
import type { CacheEntry } from './types/CacheEntry.ts'
|
|
22
23
|
import type { CacheOnContext } from './types/CacheOnContext.ts'
|
|
23
24
|
import type { CacheOptions } from './types/CacheOptions.ts'
|
|
25
|
+
import type { CachePolicy } from './types/CachePolicy.ts'
|
|
24
26
|
import type { CacheSelector } from './types/CacheSelector.ts'
|
|
25
27
|
import type { CacheStore } from './types/CacheStore.ts'
|
|
28
|
+
import type { NamedAsyncIterable } from './types/NamedAsyncIterable.ts'
|
|
29
|
+
import type { OutputWirePlan } from './types/OutputWirePlan.ts'
|
|
26
30
|
import type { RawRemoteFunction } from './types/RawRemoteFunction.ts'
|
|
27
31
|
import type { RemoteFunction } from './types/RemoteFunction.ts'
|
|
28
|
-
import type { Subscribable } from './types/Subscribable.ts'
|
|
29
32
|
import { withCacheManaged } from './withCacheManaged.ts'
|
|
30
33
|
|
|
31
34
|
type AnyRemote<Args, Return> = RemoteFunction<Args, Return> | RawRemoteFunction<Args>
|
|
@@ -58,28 +61,34 @@ function recordRead(sink: CacheStore, key: string, existing: CacheEntry | undefi
|
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
/*
|
|
61
|
-
Reads a call through a cache store. `cache(fn, args
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
they can't collide with arg shapes. ttl = 0 → dedupe only; ttl > 0 → entry
|
|
66
|
-
expires `ttl` ms after the promise resolves. Omitted ttl → forever for a
|
|
67
|
-
producer and for a remote call on the client; a remote call on the server with
|
|
68
|
-
neither ttl nor swr stated defaults to 0 (coalesce-only, see CacheOptions).
|
|
64
|
+
Reads a call through a cache store. `cache(fn, args?)` checks the store for a
|
|
65
|
+
prior entry and returns a shared promise on hit, or invokes `fn` once and stores
|
|
66
|
+
its promise on miss — a direct read-through call, not a curried invoker. Args
|
|
67
|
+
lead (the common refinement).
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
`
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
A REMOTE rpc carries no call-site options (ADR-0020): its cache policy lives on
|
|
70
|
+
the endpoint definition (`GET(fn, { cache: {...} })`) and readThrough reads it as
|
|
71
|
+
the bottom layer. A plain PRODUCER has no endpoint, so it keeps a trailing
|
|
72
|
+
`options?: CacheOptions` (ttl/tags/throttle/debounce/shared) — its only policy
|
|
73
|
+
home. ttl = 0 → dedupe only; ttl > 0 → entry expires `ttl` ms after the promise
|
|
74
|
+
resolves. Omitted ttl → retained for the store's lifetime: a producer or a remote
|
|
75
|
+
READ is kept as long as its store lives (the request on the server, the tab on the
|
|
76
|
+
client), while a remote WRITE coalesces only (ttl 0 — the mutation idiom). A remote
|
|
77
|
+
read made outside any request scope falls back to the process store and coalesces
|
|
78
|
+
only too, so a scopeless read can't leak forever. See CachePolicy.
|
|
79
|
+
|
|
80
|
+
Coalescing is always on: identical in-flight calls share one flight — double-
|
|
81
|
+
submit coalescing and pending() visibility with nothing retained beyond the
|
|
82
|
+
store's atomic unit (the whole request on the server: one render, one effect; the
|
|
74
83
|
in-flight window in the tab). Caching is the retention `ttl` adds on top.
|
|
75
84
|
|
|
76
85
|
`fn` is either a remote function (a GET/POST/... helper) or a plain producer
|
|
77
86
|
returning a Promise:
|
|
78
87
|
|
|
79
|
-
cache(getPost, { id }) // → Promise<Post> (decoded body)
|
|
88
|
+
cache(getPost, { id }) // → Promise<Post> (decoded body; policy on the endpoint)
|
|
80
89
|
cache(getPost.raw, { id }) // → Promise<Response> (raw escape hatch)
|
|
81
90
|
cache(fetchRates) // → Promise<Rates> (plain producer, no args)
|
|
82
|
-
cache(
|
|
91
|
+
cache(computeReport, input, { ttl: 60_000 }) // producer options trail
|
|
83
92
|
|
|
84
93
|
Remote calls key on fn.method + fn.url + args and store the underlying Response
|
|
85
94
|
(the decoded view is derived on the way out for the non-raw variant; both share
|
|
@@ -89,11 +98,12 @@ new reference every call and never does; a warning fires once per such call
|
|
|
89
98
|
site), and the promise is stored and handed back as-is (no Response, no decode,
|
|
90
99
|
no SSR snapshot).
|
|
91
100
|
|
|
92
|
-
`options.shared` puts the entry in the
|
|
93
|
-
request-scoped one, so a value computed in one
|
|
94
|
-
requests — the memoise-an-external-endpoint case.
|
|
95
|
-
request-scoped on the server, which keeps per-user data from
|
|
96
|
-
requests; on the client there is one tab store either way, so it is
|
|
101
|
+
`options.shared` (producer) / `cache.shared` (endpoint) puts the entry in the
|
|
102
|
+
process-level store instead of the request-scoped one, so a value computed in one
|
|
103
|
+
request is reused by later requests — the memoise-an-external-endpoint case.
|
|
104
|
+
Default (omitted) is request-scoped on the server, which keeps per-user data from
|
|
105
|
+
leaking across requests; on the client there is one tab store either way, so it is
|
|
106
|
+
a no-op.
|
|
97
107
|
|
|
98
108
|
Reactivity is implicit: the read calls `store.subscribe(key)`, which registers
|
|
99
109
|
the surrounding state.computed() / effect() scope. Invalidating the key then
|
|
@@ -132,16 +142,8 @@ export function cache<Args>(
|
|
|
132
142
|
fn: RemoteFunction<Args, AsyncIterable<unknown>>,
|
|
133
143
|
...rest: never[]
|
|
134
144
|
): never
|
|
135
|
-
export function cache<Args, Return>(
|
|
136
|
-
|
|
137
|
-
args?: Args,
|
|
138
|
-
options?: CacheOptions,
|
|
139
|
-
): Promise<Return>
|
|
140
|
-
export function cache<Args>(
|
|
141
|
-
fn: RawRemoteFunction<Args>,
|
|
142
|
-
args?: Args,
|
|
143
|
-
options?: CacheOptions,
|
|
144
|
-
): Promise<Response>
|
|
145
|
+
export function cache<Args, Return>(fn: RemoteFunction<Args, Return>, args?: Args): Promise<Return>
|
|
146
|
+
export function cache<Args>(fn: RawRemoteFunction<Args>, args?: Args): Promise<Response>
|
|
145
147
|
export function cache<Args, Return>(
|
|
146
148
|
fn: Producer<Args, Return>,
|
|
147
149
|
args?: Args,
|
|
@@ -152,7 +154,19 @@ export function cache<Args, Return>(
|
|
|
152
154
|
args?: Args,
|
|
153
155
|
options?: CacheOptions,
|
|
154
156
|
): Promise<Return | Response> {
|
|
155
|
-
|
|
157
|
+
const result = readThrough(fn, args, options, false)
|
|
158
|
+
/* Revive the decoded body's structured fields (Set/Map/Date/bigint) so `cache(fn, args)`
|
|
159
|
+
matches the bare `fn(args)` read (createRemoteFunction.callable revives there) — without
|
|
160
|
+
this the two read APIs disagree for a typed field, and both violate the declared Return.
|
|
161
|
+
A producer / a raw `Response` carry no plan and pass through untouched; the decoded value is
|
|
162
|
+
call-private (fresh decode or cloned warm value), so revive mutates it in place. */
|
|
163
|
+
const plan = (fn as { outputWirePlan?: OutputWirePlan }).outputWirePlan
|
|
164
|
+
if (plan === undefined) {
|
|
165
|
+
return result
|
|
166
|
+
}
|
|
167
|
+
return result.then((value) =>
|
|
168
|
+
value instanceof Response ? value : (reviveWireOutput(value, plan) as Return),
|
|
169
|
+
)
|
|
156
170
|
}
|
|
157
171
|
|
|
158
172
|
/*
|
|
@@ -160,15 +174,12 @@ The smart bare rpc call routes here (createRemoteFunction.callable → cache.rea
|
|
|
160
174
|
identical to cache() except a replayable read gets unconditional SWR retention —
|
|
161
175
|
its value is kept for display regardless of ttl, and ttl marks a staleness
|
|
162
176
|
deadline (the next read past it revalidates in the background) instead of driving
|
|
163
|
-
eviction.
|
|
164
|
-
|
|
177
|
+
eviction. No call-site options (ADR-0020): all policy is read off the endpoint
|
|
178
|
+
(fn.cache). Always a decoded RemoteFunction read (the callable carries .raw), so
|
|
179
|
+
it returns Promise<Return>.
|
|
165
180
|
*/
|
|
166
|
-
function smartRead<Args, Return>(
|
|
167
|
-
fn
|
|
168
|
-
args?: Args,
|
|
169
|
-
options?: CacheOptions,
|
|
170
|
-
): Promise<Return> {
|
|
171
|
-
return readThrough(fn, args, options, true) as Promise<Return>
|
|
181
|
+
function smartRead<Args, Return>(fn: RemoteFunction<Args, Return>, args?: Args): Promise<Return> {
|
|
182
|
+
return readThrough(fn, args, undefined, true) as Promise<Return>
|
|
172
183
|
}
|
|
173
184
|
cache.read = smartRead
|
|
174
185
|
|
|
@@ -177,6 +188,8 @@ The shared read-through core. `smart` marks the smart bare call, which enables
|
|
|
177
188
|
unconditional SWR retention for a replayable read — but only on the client (see
|
|
178
189
|
smartRead / entry.retain and the `retain` computation below); the public cache()
|
|
179
190
|
passes false, keeping its explicit drop-on-ttl / drop-on-invalidate old surface.
|
|
191
|
+
A remote's policy is read off the endpoint (fn.cache — ADR-0020); a producer's
|
|
192
|
+
policy is its explicit cache() `options`.
|
|
180
193
|
*/
|
|
181
194
|
function readThrough<Args, Return>(
|
|
182
195
|
fn: AnyRemote<Args, Return> | Producer<Args, Return>,
|
|
@@ -201,30 +214,42 @@ function readThrough<Args, Return>(
|
|
|
201
214
|
: (fn as RemoteFunction<Args, Return>).raw
|
|
202
215
|
const method = isRemote ? (rawFn as RawRemoteFunction<Args>).method : undefined
|
|
203
216
|
const replayable = method !== undefined && REPLAYABLE_METHODS.has(method.toUpperCase())
|
|
204
|
-
|
|
217
|
+
/* Remote policy is endpoint-declared — the bottom (and only) layer; a producer has no
|
|
218
|
+
endpoint, so its explicit cache() options stand in. */
|
|
219
|
+
const policy = isRemote ? resolveEndpointCache(fn as AnyRemote<Args, Return>, args) : options
|
|
205
220
|
/* SWR retention is a client concern (the tab store lives; revalidation is
|
|
206
221
|
visible). On the server there is no live UI to hold stale, so a smart read
|
|
207
|
-
never retains — it coalesces only.
|
|
208
|
-
|
|
222
|
+
never retains — it coalesces only. Unconditional for a replayable read now
|
|
223
|
+
(no swr toggle to opt out of). */
|
|
224
|
+
const retain = smart && replayable && typeof window !== 'undefined'
|
|
225
|
+
/*
|
|
226
|
+
ttl defaults to Infinity — an entry is retained for as long as its STORE lives, and
|
|
227
|
+
the store's lifetime (not ttl) is what ends a read: the request-scoped store on the
|
|
228
|
+
server (so a non-shared read dies with the request regardless of ttl), the tab store
|
|
229
|
+
on the client (until invalidate/refresh). `shared` selects the process store, where
|
|
230
|
+
the default Infinity memoises across requests and an explicit ttl bounds it — so
|
|
231
|
+
`shared` alone is "memoise", not a store-selector that retains nothing.
|
|
232
|
+
*/
|
|
233
|
+
const store = policy?.shared ? sharedCacheStore() : activeCacheStore()
|
|
209
234
|
/*
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
235
|
+
Two cases force coalesce-only (ttl 0) when the caller stated no ttl — nothing retained
|
|
236
|
+
beyond the in-flight window:
|
|
237
|
+
- a write (non-replayable remote): dropped on settle so a re-submit of the same body
|
|
238
|
+
isn't blocked by a retained entry (the mutation idiom);
|
|
239
|
+
- a read that resolved to the process store WITHOUT `shared` — a server read outside any
|
|
240
|
+
request scope, where activeCacheStore() falls back to the shared store. Nothing bounds
|
|
241
|
+
its lifetime there, so retaining it would leak forever.
|
|
242
|
+
Both apply on either side and however the call is made.
|
|
216
243
|
*/
|
|
217
|
-
const
|
|
244
|
+
const coalesceOnly =
|
|
218
245
|
isRemote &&
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
const effectiveOptions = serverTtlZero || smartWriteTtlZero ? { ...options, ttl: 0 } : options
|
|
246
|
+
policy?.ttl === undefined &&
|
|
247
|
+
(!replayable ||
|
|
248
|
+
(!policy?.shared && typeof window === 'undefined' && store === sharedCacheStore()))
|
|
249
|
+
const effectiveOptions = coalesceOnly ? { ...policy, ttl: 0 } : policy
|
|
224
250
|
if (!isRemote) {
|
|
225
251
|
warnAnonymousProducer(fn as Producer<Args, Return>)
|
|
226
252
|
}
|
|
227
|
-
const store = effectiveOptions?.shared ? sharedCacheStore() : activeCacheStore()
|
|
228
253
|
if (!isRemote) {
|
|
229
254
|
return invokeProducer(store, fn as Producer<Args, Return>, args, effectiveOptions)
|
|
230
255
|
}
|
|
@@ -318,72 +343,48 @@ function cloneWarmValue(value: unknown): unknown {
|
|
|
318
343
|
}
|
|
319
344
|
|
|
320
345
|
/*
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
without an `swr` toggle. Collapsing all of this here lets every downstream site
|
|
327
|
-
treat "is SWR on" as a single defined/undefined check.
|
|
346
|
+
Resolves an rpc endpoint's declared cache policy (ADR-0020) into a plain
|
|
347
|
+
CacheOptions for the read-through machinery, evaluating the `tags` function form
|
|
348
|
+
against this call's args. Both remote variants (decoded + raw) carry `.cache`, so
|
|
349
|
+
either can be passed. Undefined when the endpoint declared no policy (method
|
|
350
|
+
defaults still apply below).
|
|
328
351
|
*/
|
|
329
|
-
function
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
):
|
|
333
|
-
const
|
|
334
|
-
if (
|
|
352
|
+
function resolveEndpointCache<Args, Return>(
|
|
353
|
+
fn: AnyRemote<Args, Return>,
|
|
354
|
+
args: Args | undefined,
|
|
355
|
+
): CacheOptions | undefined {
|
|
356
|
+
const policy = (fn as { cache?: CachePolicy<Args> }).cache
|
|
357
|
+
if (policy === undefined) {
|
|
335
358
|
return undefined
|
|
336
359
|
}
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
360
|
+
const tags = typeof policy.tags === 'function' ? policy.tags(args as Args) : policy.tags
|
|
361
|
+
return {
|
|
362
|
+
ttl: policy.ttl,
|
|
363
|
+
tags,
|
|
364
|
+
shared: policy.shared,
|
|
365
|
+
throttle: policy.throttle,
|
|
366
|
+
debounce: policy.debounce,
|
|
344
367
|
}
|
|
345
|
-
if (swr === true || defaultOn) {
|
|
346
|
-
return root ?? {}
|
|
347
|
-
}
|
|
348
|
-
return undefined
|
|
349
368
|
}
|
|
350
369
|
|
|
351
370
|
/*
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
371
|
+
The keep-stale-and-revalidate refetch window, or undefined when the entry should
|
|
372
|
+
hard-drop on invalidate instead. Present when the smart bare call retains a
|
|
373
|
+
replayable read (`defaultOn`, SWR unconditional — the window is just the endpoint's
|
|
374
|
+
throttle/debounce, or `{}` = fire immediately) OR when a policy explicitly declares
|
|
375
|
+
a throttle/debounce window (producer options / endpoint cache). Collapsing this here
|
|
376
|
+
lets every downstream site treat "is a refetch window armed" as a single
|
|
377
|
+
defined/undefined check.
|
|
359
378
|
*/
|
|
360
|
-
function
|
|
379
|
+
function refetchWindow(
|
|
361
380
|
options: CacheOptions | undefined,
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (!policy) {
|
|
368
|
-
return
|
|
369
|
-
}
|
|
370
|
-
if (policy.throttle !== undefined && policy.debounce !== undefined) {
|
|
371
|
-
throw new Error('[abide] cache(): set throttle or debounce, not both')
|
|
372
|
-
}
|
|
373
|
-
/* An EXPLICIT swr with ttl: 0 is the old error — nothing retained to revalidate.
|
|
374
|
-
The smart read's implicit SWR retains unconditionally, so ttl: 0 is fine there. */
|
|
375
|
-
const explicitSwr =
|
|
376
|
-
options?.swr === true || (typeof options?.swr === 'object' && options?.swr !== null)
|
|
377
|
-
if (explicitSwr && options?.ttl === 0) {
|
|
378
|
-
throw new Error(
|
|
379
|
-
'[abide] cache(): swr requires retention — ttl: 0 keeps nothing to revalidate',
|
|
380
|
-
)
|
|
381
|
-
}
|
|
382
|
-
if (method !== undefined && !replayable) {
|
|
383
|
-
throw new Error(
|
|
384
|
-
`[abide] cache(): swr re-runs the call unprompted — ${method.toUpperCase()} is a write and must not be replayed`,
|
|
385
|
-
)
|
|
381
|
+
defaultOn: boolean,
|
|
382
|
+
): { throttle?: number; debounce?: number } | undefined {
|
|
383
|
+
const hasWindow = options?.throttle !== undefined || options?.debounce !== undefined
|
|
384
|
+
if (defaultOn || hasWindow) {
|
|
385
|
+
return { throttle: options?.throttle, debounce: options?.debounce }
|
|
386
386
|
}
|
|
387
|
+
return undefined
|
|
387
388
|
}
|
|
388
389
|
|
|
389
390
|
/*
|
|
@@ -498,9 +499,9 @@ function registerEntry(
|
|
|
498
499
|
retain: boolean,
|
|
499
500
|
): CacheEntry {
|
|
500
501
|
const ttl = options?.ttl
|
|
501
|
-
/* Capture the refetch thunk + window when
|
|
502
|
-
|
|
503
|
-
const policy =
|
|
502
|
+
/* Capture the refetch thunk + window when a smart retained read (SWR unconditional)
|
|
503
|
+
or an explicit throttle/debounce window is present. */
|
|
504
|
+
const policy = refetchWindow(options, retain)
|
|
504
505
|
const invalidation = policy
|
|
505
506
|
? { refetch, throttle: policy.throttle, debounce: policy.debounce }
|
|
506
507
|
: undefined
|
|
@@ -748,7 +749,7 @@ Invalidates every entry matching the selector (see selectorMatcher) across both
|
|
|
748
749
|
the request/tab store and the process-level store, and notifies readers.
|
|
749
750
|
`args` narrows a fn selector to exactly that call's entry — derived through
|
|
750
751
|
the same encoders the read path uses, so other args variants stay warm. An entry
|
|
751
|
-
with
|
|
752
|
+
with a refetch policy is kept and its refetch coalesced (stale served
|
|
752
753
|
until it resolves); every other match is dropped so the next read refetches —
|
|
753
754
|
its key recorded in pendingRefresh so that read reports as a reload (refreshing())
|
|
754
755
|
rather than a first-ever load. An empty or unmatched selector is a no-op on the
|
|
@@ -821,6 +822,12 @@ blanks. This is the old invalidate reborn as *refetch*: because SWR retains the
|
|
|
821
822
|
value, there is no "invalidate that drops to pending" for the smart call. Follows
|
|
822
823
|
invalidate's exact selector grammar (fn / fn+args / { tags } / bare = all).
|
|
823
824
|
|
|
825
|
+
Only a match with a live reader refetches: refresh keeps the stale value visible
|
|
826
|
+
until the fresh one swaps in, and with no reader on screen there is nothing to
|
|
827
|
+
swap into — so a match whose reactive scope has torn down ("no longer has
|
|
828
|
+
listeners") drops instead of firing a wasted network refetch, and the next read
|
|
829
|
+
reloads it fresh.
|
|
830
|
+
|
|
824
831
|
A smart-read match already carries an invalidation policy, so it routes straight
|
|
825
832
|
through scheduleInvalidationRefetch (throttle/debounce-honouring). A policy-less
|
|
826
833
|
remote match (an explicit cache() entry, or a hydrated one) gets a refetch armed
|
|
@@ -842,6 +849,16 @@ function refresh<Args, Return>(arg?: CacheSelector<Args, Return>, args?: Args):
|
|
|
842
849
|
continue
|
|
843
850
|
}
|
|
844
851
|
matched.push(entry.key)
|
|
852
|
+
/* No live reader is holding this key — its reactive scope has torn down (or
|
|
853
|
+
it was never read reactively and already dropped). A refresh has no
|
|
854
|
+
on-screen value to keep-stale-and-swap, so firing the network now is
|
|
855
|
+
wasted work that may be stale again before anything shows it. Drop the
|
|
856
|
+
entry so the next read reloads fresh instead — the same lazy path a
|
|
857
|
+
reader-less invalidate() takes. */
|
|
858
|
+
if (!store.hasReader(entry.key)) {
|
|
859
|
+
store.entries.delete(entry.key)
|
|
860
|
+
continue
|
|
861
|
+
}
|
|
845
862
|
/* Arm a refetch on the fly for a policy-less remote entry by replaying its
|
|
846
863
|
stored Request — so a refresh always refetches-and-swaps, never blanks. */
|
|
847
864
|
if (entry.invalidation === undefined && entry.request !== undefined) {
|
|
@@ -851,12 +868,11 @@ function refresh<Args, Return>(arg?: CacheSelector<Args, Return>, args?: Args):
|
|
|
851
868
|
if (entry.invalidation !== undefined) {
|
|
852
869
|
scheduleInvalidationRefetch(store, entry)
|
|
853
870
|
} else {
|
|
854
|
-
/* No policy, no request (a producer never cached with
|
|
855
|
-
next read reloads
|
|
871
|
+
/* No policy, no request (a producer never cached with a refetch window): drop so the
|
|
872
|
+
next read reloads. A reader is holding it (checked above), so flag the
|
|
873
|
+
next read a reload (refreshing()). */
|
|
856
874
|
store.entries.delete(entry.key)
|
|
857
|
-
|
|
858
|
-
store.pendingRefresh.add(entry.key)
|
|
859
|
-
}
|
|
875
|
+
store.pendingRefresh.add(entry.key)
|
|
860
876
|
affected.push(entry.key)
|
|
861
877
|
}
|
|
862
878
|
}
|
|
@@ -921,7 +937,8 @@ function peek<Args, Return>(
|
|
|
921
937
|
? (fn as RemoteFunction<Args, Return>).raw
|
|
922
938
|
: (fn as RawRemoteFunction<Args>)
|
|
923
939
|
const key = isRemote
|
|
924
|
-
?
|
|
940
|
+
? // biome-ignore lint/style/noNonNullAssertion: rawFn is undefined only when !isRemote, so this branch has it
|
|
941
|
+
keyForRemoteCall(rawFn!.method, rawFn!.url, args)
|
|
925
942
|
: producerKey(fn as Producer<Args, Return>, args)
|
|
926
943
|
const active = activeCacheStore()
|
|
927
944
|
active.subscribe(key)
|
|
@@ -940,11 +957,19 @@ function peek<Args, Return>(
|
|
|
940
957
|
uniformly undefined and the SSR render shows the fallback. Returning a snapshot-
|
|
941
958
|
seeded warm value here — mid-hydration — would diverge from that server text and
|
|
942
959
|
corrupt the claimed text node. Withhold it until the pass ends; the trackLifecycle
|
|
943
|
-
tap above lets
|
|
944
|
-
if (
|
|
960
|
+
tap above lets hydrationWindow.wake re-run this scope on the congruent value. */
|
|
961
|
+
if (hydrationWindow.active) {
|
|
945
962
|
return undefined
|
|
946
963
|
}
|
|
947
|
-
|
|
964
|
+
/* Revive structured fields after cloning: the clone is the honest-JSON wire form, and revive
|
|
965
|
+
turns a wire array into its declared Set/Map and an ISO string into a Date — so peek matches
|
|
966
|
+
fn(args) / cache(fn, args). A raw or producer read carries no plan → the clone passes through
|
|
967
|
+
untouched (revive is a no-op on an undefined plan). */
|
|
968
|
+
const plan =
|
|
969
|
+
isRemote && 'raw' in fn
|
|
970
|
+
? (fn as { outputWirePlan?: OutputWirePlan }).outputWirePlan
|
|
971
|
+
: undefined
|
|
972
|
+
return reviveWireOutput(cloneWarmValue(entry.value), plan) as Return
|
|
948
973
|
}
|
|
949
974
|
|
|
950
975
|
cache.peek = peek
|
|
@@ -978,7 +1003,7 @@ function applyPatch(
|
|
|
978
1003
|
}
|
|
979
1004
|
|
|
980
1005
|
/*
|
|
981
|
-
Event-driven cache maintenance: subscribes to a
|
|
1006
|
+
Event-driven cache maintenance: subscribes to a NamedAsyncIterable (socket or rpc
|
|
982
1007
|
stream) and runs `handler` once per frame — the declarative home for "this
|
|
983
1008
|
socket event stales that cached data", replacing the hand-rolled $effect +
|
|
984
1009
|
tail() + edge-detection pattern. Bare iteration means live frames only
|
|
@@ -1005,7 +1030,7 @@ already seeded the cache. Dispose aborts `signal`, stops delivery, and
|
|
|
1005
1030
|
closes the subscription.
|
|
1006
1031
|
*/
|
|
1007
1032
|
function on<T>(
|
|
1008
|
-
source:
|
|
1033
|
+
source: NamedAsyncIterable<T>,
|
|
1009
1034
|
handler: (frame: T, context: CacheOnContext) => void | Promise<void>,
|
|
1010
1035
|
): () => void {
|
|
1011
1036
|
if (typeof window === 'undefined') {
|
|
@@ -1074,7 +1099,7 @@ function on<T>(
|
|
|
1074
1099
|
cache.on = on
|
|
1075
1100
|
|
|
1076
1101
|
/*
|
|
1077
|
-
Schedules a coalesced refetch per the entry's
|
|
1102
|
+
Schedules a coalesced refetch per the entry's refetch policy. No window:
|
|
1078
1103
|
fire immediately (throttle defaults to 0, so the leading-edge branch always
|
|
1079
1104
|
takes). debounce: (re)arm a timer that fires after N ms of quiet. throttle: fire
|
|
1080
1105
|
on the leading edge when a full window has elapsed since the last fire, else arm
|
|
@@ -1162,7 +1187,7 @@ function fireRefetch(store: CacheStore, entry: CacheEntry): void {
|
|
|
1162
1187
|
entry keeps its original expiresAt and would read as stale (or be evicted
|
|
1163
1188
|
at the old deadline) despite holding fresh data. Mirrors registerEntry's
|
|
1164
1189
|
settle path: a retained smart read re-stamps the staleness deadline (the
|
|
1165
|
-
next read past it revalidates),
|
|
1190
|
+
next read past it revalidates), a windowed producer entry re-arms the eviction
|
|
1166
1191
|
clock. */
|
|
1167
1192
|
if (entry.ttl !== undefined && entry.ttl !== 0) {
|
|
1168
1193
|
if (entry.retain) {
|
|
@@ -1269,7 +1294,7 @@ function attachPolicy(
|
|
|
1269
1294
|
if (defaultOn) {
|
|
1270
1295
|
entry.retain = true
|
|
1271
1296
|
}
|
|
1272
|
-
const policy =
|
|
1297
|
+
const policy = refetchWindow(options, defaultOn)
|
|
1273
1298
|
if (entry.invalidation || !policy) {
|
|
1274
1299
|
return
|
|
1275
1300
|
}
|
|
@@ -1284,7 +1309,7 @@ fire and the pending()/refreshing() probes re-derive; only the keys in `emitted`
|
|
|
1284
1309
|
had their VISIBLE value change, so the 'invalidate' event fires and the reading
|
|
1285
1310
|
scope re-reads. The two sets diverge by design — a refetch start marks but does
|
|
1286
1311
|
not emit (the stale value is still on screen), an invalidate-drop does both, an
|
|
1287
|
-
|
|
1312
|
+
a keep-stale (refetch-policy) invalidate marks the whole match set while emitting only the dropped subset.
|
|
1288
1313
|
The trailing store-wide mark fires even when nothing matched, so a bare probe
|
|
1289
1314
|
still re-derives (to the same value); marks coalesce per microtask, so a key in
|
|
1290
1315
|
both lists is not double work.
|
|
@@ -8,7 +8,8 @@ POST/PUT/PATCH bodies; the output is a key, not a request body, so it is free to
|
|
|
8
8
|
encode types JSON.stringify would silently flatten (Map/Set → {}), coerce
|
|
9
9
|
(Date → its ISO string, via toJSON before any replacer sees it) or drop
|
|
10
10
|
(undefined). Covers the value types commonly passed as rpc args: primitives,
|
|
11
|
-
arrays, plain objects, Date, Map, Set,
|
|
11
|
+
arrays, plain objects, Date, Map, Set, bigint, and FormData/File/Blob (the
|
|
12
|
+
multipart upload path). Functions and symbols can't
|
|
12
13
|
key anything meaningful but are tagged rather than dropped so a stray one can't
|
|
13
14
|
silently collapse two distinct argument sets onto the same key.
|
|
14
15
|
*/
|
|
@@ -55,6 +56,28 @@ export function canonicalJson(value: unknown): string {
|
|
|
55
56
|
const members = Array.from(value, canonicalJson).sort()
|
|
56
57
|
return `Set{${members.join(',')}}`
|
|
57
58
|
}
|
|
59
|
+
/* FormData is the documented multipart escape hatch for body rpcs. Its fields are
|
|
60
|
+
NOT own enumerable keys, so the generic object branch below would return `{}` for
|
|
61
|
+
every distinct upload — collapsing them onto one cache key and coalescing unrelated
|
|
62
|
+
uploads onto each other. Encode its entries (sorted, so field order doesn't change
|
|
63
|
+
the key) with File/Blob distinguished by identity attributes below. */
|
|
64
|
+
if (typeof FormData !== 'undefined' && value instanceof FormData) {
|
|
65
|
+
const entries: string[] = []
|
|
66
|
+
value.forEach((entryValue, entryKey) => {
|
|
67
|
+
entries.push(`${JSON.stringify(entryKey)}=>${canonicalJson(entryValue)}`)
|
|
68
|
+
})
|
|
69
|
+
entries.sort()
|
|
70
|
+
return `FormData{${entries.join(',')}}`
|
|
71
|
+
}
|
|
72
|
+
/* File extends Blob — check it first so a named upload keys on its name too. Contents
|
|
73
|
+
can't be read synchronously, so identity attributes are the best available key;
|
|
74
|
+
distinct files (name/size/type/mtime) get distinct keys, which is the fix. */
|
|
75
|
+
if (typeof File !== 'undefined' && value instanceof File) {
|
|
76
|
+
return `File(${JSON.stringify(value.name)},${value.size},${JSON.stringify(value.type)},${value.lastModified})`
|
|
77
|
+
}
|
|
78
|
+
if (typeof Blob !== 'undefined' && value instanceof Blob) {
|
|
79
|
+
return `Blob(${value.size},${JSON.stringify(value.type)})`
|
|
80
|
+
}
|
|
58
81
|
const record = value as Record<string, unknown>
|
|
59
82
|
const entries = Object.keys(record)
|
|
60
83
|
.sort()
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PROXIED_SERVER_SUBDIRS } from './PROXIED_SERVER_SUBDIRS.ts'
|
|
2
|
+
|
|
1
3
|
/*
|
|
2
4
|
Classifies a watcher change (a path relative to the project's `src/`, with
|
|
3
5
|
either OS separator) by whether it can alter the client bundle. The dev
|
|
@@ -9,8 +11,8 @@ Server/MCP-only — client unaffected, skip the client rebuild:
|
|
|
9
11
|
path (rpc url) and the `<METHOD>(...)` wrapper, not the
|
|
10
12
|
handler body; the rpc manifest is server-only. A body
|
|
11
13
|
edit produces an identical stub. (Risk: changing the
|
|
12
|
-
method
|
|
13
|
-
|
|
14
|
+
method or the export name does change the stub — those
|
|
15
|
+
need a manual full rebuild.)
|
|
14
16
|
- `server/sockets/**` — name-only `socketProxy` stubs derived from the file
|
|
15
17
|
path; socket opts are server-side. Body edits don't
|
|
16
18
|
change the stub.
|
|
@@ -26,10 +28,13 @@ full rebuild rather than risking a stale client.
|
|
|
26
28
|
*/
|
|
27
29
|
export function changeAffectsClient(relativePath: string): boolean {
|
|
28
30
|
const path = relativePath.split('\\').join('/')
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
/* The proxied server dirs (rpc/sockets) ship path-derived stubs, so a body edit can't change the
|
|
32
|
+
client bundle. Read from the shared PROXIED_SERVER_SUBDIRS authority so this stays in lockstep
|
|
33
|
+
with the resolver's side-classifier. mcp/** and server/config.ts are additional server-only
|
|
34
|
+
skips specific to this classifier (not proxied — never imported by the client at all). */
|
|
35
|
+
const proxiedServerOnly = PROXIED_SERVER_SUBDIRS.some((subdir) =>
|
|
36
|
+
path.startsWith(`server/${subdir}/`),
|
|
37
|
+
)
|
|
38
|
+
const serverOnly = proxiedServerOnly || path.startsWith('mcp/') || path === 'server/config.ts'
|
|
34
39
|
return !serverOnly
|
|
35
40
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { REACTIVE_CONTEXT } from '../ui/runtime/REACTIVE_CONTEXT.ts'
|
|
1
2
|
import { createLifecycleChannel } from './createLifecycleChannel.ts'
|
|
2
3
|
import { createSubscriber } from './createSubscriber.ts'
|
|
3
4
|
import { keyMatchesPrefix } from './keyMatchesPrefix.ts'
|
|
@@ -28,6 +29,16 @@ export function createCacheStore(): CacheStore {
|
|
|
28
29
|
const pendingRefresh = new Set<string>()
|
|
29
30
|
|
|
30
31
|
function subscribe(key: string): void {
|
|
32
|
+
/* An untracked read (an event handler, a setup-time `await getFoo()`, any
|
|
33
|
+
server render) subscribes to nothing: `track` inside createSubscriber no-ops,
|
|
34
|
+
node.subsHead stays empty, and the resource never opens. Skip the bookkeeping
|
|
35
|
+
too — otherwise a bare read would pin an entry in `subscribers` for the tab's
|
|
36
|
+
lifetime, growing the map and leaving hasReader() over-reporting a reader that
|
|
37
|
+
isn't there. Honouring the tracking seam here makes hasReader() mean exactly
|
|
38
|
+
"a live tracking reader exists". */
|
|
39
|
+
if (REACTIVE_CONTEXT.observer === undefined) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
31
42
|
const existing = subscribers.get(key)
|
|
32
43
|
if (existing) {
|
|
33
44
|
existing()
|
|
@@ -63,9 +74,12 @@ export function createCacheStore(): CacheStore {
|
|
|
63
74
|
/*
|
|
64
75
|
Per-prefix channels arm fn-selector probes (pending(fn) / refreshing(fn))
|
|
65
76
|
without waking them on unrelated cache events. Keyed by selector prefix
|
|
66
|
-
(method+url / producer reference id — see selectorPrefix)
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
(method+url / producer reference id — see selectorPrefix). Probes carry the
|
|
78
|
+
FULL call key when args are given (pending(fn, args) / peek(fn, args)), so a
|
|
79
|
+
long session that probes many distinct ids would otherwise accrete one map
|
|
80
|
+
entry per id on the tab-scoped store forever and make markLifecycle's forEach
|
|
81
|
+
O(distinct-keys-ever-probed). Each channel evicts its own entry once its last
|
|
82
|
+
reader tears down (identity-guarded against a concurrent re-track).
|
|
69
83
|
*/
|
|
70
84
|
const prefixLifecycles = new Map<string, ReturnType<typeof createLifecycleChannel>>()
|
|
71
85
|
|
|
@@ -76,7 +90,15 @@ export function createCacheStore(): CacheStore {
|
|
|
76
90
|
}
|
|
77
91
|
let channel = prefixLifecycles.get(keyPrefix)
|
|
78
92
|
if (channel === undefined) {
|
|
79
|
-
|
|
93
|
+
const created = createLifecycleChannel(() => {
|
|
94
|
+
/* Drop the entry only if it's still THIS channel — a concurrent re-track may have
|
|
95
|
+
replaced it, and createSubscriber's own reader-count guard means onIdle fires
|
|
96
|
+
only when genuinely reader-less. */
|
|
97
|
+
if (prefixLifecycles.get(keyPrefix) === created) {
|
|
98
|
+
prefixLifecycles.delete(keyPrefix)
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
channel = created
|
|
80
102
|
prefixLifecycles.set(keyPrefix, channel)
|
|
81
103
|
}
|
|
82
104
|
channel.track()
|
|
@@ -103,10 +125,11 @@ export function createCacheStore(): CacheStore {
|
|
|
103
125
|
entries,
|
|
104
126
|
events,
|
|
105
127
|
subscribe,
|
|
106
|
-
/* True
|
|
107
|
-
its value on screen
|
|
108
|
-
|
|
109
|
-
|
|
128
|
+
/* True iff a live tracking scope is reading this key — i.e. someone is holding
|
|
129
|
+
its value on screen right now. An untracked read never registers (subscribe
|
|
130
|
+
gates on the tracking seam), so a bare handler/setup read does not make this
|
|
131
|
+
true. invalidate()/refresh() gate on this: a key with no live reader has
|
|
132
|
+
nothing to reload into, so the next read is a first-ever load, not a refresh. */
|
|
110
133
|
hasReader: (key: string) => subscribers.has(key),
|
|
111
134
|
trackLifecycle,
|
|
112
135
|
markLifecycle,
|