@abide/abide 0.57.1 → 0.100.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/CHANGELOG.md +3 -1773
- package/bin/abide.ts +788 -204
- package/package.json +30 -152
- package/scaffold/src/server/app.ts +6 -0
- package/scaffold/src/shared/.gitkeep +0 -0
- package/scaffold/src/ui/app.html +8 -0
- package/scaffold/src/ui/pages/page.abide +13 -0
- package/src/compiler/CHARACTER_REFERENCES.ts +2242 -0
- package/src/compiler/CheckerAnswer.ts +62 -0
- package/src/compiler/Cursor.ts +367 -0
- package/src/compiler/Finding.ts +2 -0
- package/src/compiler/Lowering.ts +648 -0
- package/src/compiler/PositionTable.ts +125 -0
- package/src/compiler/REACTIVE_MEMBERS.ts +36 -0
- package/src/compiler/SHARED_NAMES.ts +45 -0
- package/src/compiler/SPECIFIERS.ts +16 -0
- package/src/compiler/ShadowMap.ts +144 -0
- package/src/compiler/TemplateNode.ts +176 -0
- package/src/compiler/TypeSession.ts +1079 -0
- package/src/compiler/VOID_ELEMENTS.ts +22 -0
- package/src/compiler/aliasedSymbol.ts +15 -0
- package/src/compiler/ambientReadsIn.ts +152 -0
- package/src/compiler/argsDefaultsIn.ts +173 -0
- package/src/compiler/argsSchemaOf.ts +230 -0
- package/src/compiler/checkApp.ts +117 -0
- package/src/compiler/checkedShadow.ts +182 -0
- package/src/compiler/classifyRead.ts +577 -0
- package/src/compiler/classifyTemplate.ts +501 -0
- package/src/compiler/compileApp.ts +559 -0
- package/src/compiler/compiledEntry.ts +141 -0
- package/src/compiler/componentGraph.ts +139 -0
- package/src/compiler/declaredExports.ts +645 -0
- package/src/compiler/decodeCharacterReferences.ts +111 -0
- package/src/compiler/emitClient.ts +1343 -0
- package/src/compiler/emitComponent.ts +162 -0
- package/src/compiler/emitSource.ts +91 -0
- package/src/compiler/formatDiagnosis.ts +19 -0
- package/src/compiler/handlersUnder.ts +109 -0
- package/src/compiler/index.ts +15 -0
- package/src/compiler/invocationProps.ts +67 -0
- package/src/compiler/isNamedAlone.ts +9 -0
- package/src/compiler/lineOf.ts +38 -0
- package/src/compiler/parseAbide.ts +1341 -0
- package/src/compiler/projectShadow.ts +643 -0
- package/src/compiler/reactiveOptions.ts +82 -0
- package/src/compiler/reactiveTypes.ts +222 -0
- package/src/compiler/readSources.ts +100 -0
- package/src/compiler/readsIn.ts +1023 -0
- package/src/compiler/renderMarkup.ts +781 -0
- package/src/compiler/scaffold.ts +105 -0
- package/src/compiler/scopeStyles.ts +309 -0
- package/src/compiler/serveLanguage.ts +405 -0
- package/src/compiler/shareScope.ts +51 -0
- package/src/compiler/sharedModuleWrites.ts +88 -0
- package/src/compiler/splitImports.ts +387 -0
- package/src/compiler/templateWarnings.ts +128 -0
- package/src/compiler/tokenize.ts +317 -0
- package/src/compiler/trackingWarnings.ts +279 -0
- package/src/compiler/writeDefaultProject.ts +42 -0
- package/src/server/BRIDGE_VERSION.ts +3 -0
- package/src/server/CLIENT_BUILD.ts +56 -0
- package/src/server/DEFAULT_PORT.ts +6 -0
- package/src/server/DOCUMENT_HEADERS.ts +11 -0
- package/src/server/Inbox.ts +36 -0
- package/src/server/LogPanel.ts +239 -0
- package/src/server/MCP_PROTOCOL_VERSION.ts +3 -0
- package/src/server/SETTLE_MILLISECONDS.ts +7 -0
- package/src/server/VIRTUAL_DIRECTORY.ts +6 -0
- package/src/server/answerFailure.ts +91 -0
- package/src/server/answerRequest.ts +560 -0
- package/src/server/answerRpc.ts +537 -0
- package/src/server/answerSocket.ts +537 -0
- package/src/server/answerValue.ts +375 -0
- package/src/server/appDirectories.ts +9 -0
- package/src/server/appKit.ts +204 -0
- package/src/server/appPlugins.ts +26 -0
- package/src/server/argsFor.ts +40 -0
- package/src/server/assistant.ts +163 -0
- package/src/server/banner.ts +43 -0
- package/src/server/bridgeRelay.ts +249 -0
- package/src/server/buildDirectory.ts +10 -0
- package/src/server/callerOf.ts +60 -0
- package/src/server/claudeApi.ts +224 -0
- package/src/server/claudeCode.ts +187 -0
- package/src/server/clientBuildOptions.ts +49 -0
- package/src/server/coerceArgs.ts +110 -0
- package/src/server/commands.ts +442 -0
- package/src/server/componentImports.ts +56 -0
- package/src/server/componentModules.d.ts +9 -0
- package/src/server/compressAnswer.ts +66 -0
- package/src/server/config.ts +192 -0
- package/src/server/configFields.ts +92 -0
- package/src/server/createApp.ts +428 -0
- package/src/server/crossingOrigin.ts +49 -0
- package/src/server/csp.ts +77 -0
- package/src/server/devWorker.ts +613 -0
- package/src/server/faultPolicy.ts +59 -0
- package/src/server/fenceServer.ts +48 -0
- package/src/server/generatedSurfaces.ts +430 -0
- package/src/server/hooks.ts +190 -0
- package/src/server/index.ts +87 -0
- package/src/server/installServer.ts +47 -0
- package/src/server/launcherOptions.ts +62 -0
- package/src/server/launcherPage.ts +156 -0
- package/src/server/manifestOf.ts +114 -0
- package/src/server/muxUpgrade.ts +72 -0
- package/src/server/namesUnder.ts +15 -0
- package/src/server/nativeMenu.ts +296 -0
- package/src/server/negotiateEncoding.ts +39 -0
- package/src/server/plainAnswer.ts +19 -0
- package/src/server/prefers.ts +25 -0
- package/src/server/principal.ts +261 -0
- package/src/server/qualitiesOf.ts +22 -0
- package/src/server/randomToken.ts +9 -0
- package/src/server/reachable.ts +31 -0
- package/src/server/refuseAddressCollision.ts +30 -0
- package/src/server/remote.ts +242 -0
- package/src/server/render.ts +464 -0
- package/src/server/renderComponent.ts +33 -0
- package/src/server/reportHealth.ts +46 -0
- package/src/server/requestScope.ts +435 -0
- package/src/server/responseHelpers.ts +75 -0
- package/src/server/runCompiled.ts +44 -0
- package/src/server/runDesktop.ts +572 -0
- package/src/server/seedBuffer.ts +293 -0
- package/src/server/serveBuild.ts +198 -0
- package/src/server/serveDev.ts +499 -0
- package/src/server/shell.ts +506 -0
- package/src/server/surfaceOf.ts +47 -0
- package/src/server/testBuild.ts +237 -0
- package/src/server/trace.ts +151 -0
- package/src/server/versionOf.ts +109 -0
- package/src/server/withoutTrailingSlash.ts +4 -0
- package/src/shared/ADDRESSES.ts +69 -0
- package/src/shared/ANCHORS.ts +54 -0
- package/src/shared/Attributes.ts +339 -0
- package/src/shared/BOOLEAN_ATTRIBUTES.ts +41 -0
- package/src/shared/CHILDREN.ts +11 -0
- package/src/shared/CURRENT_ROUTER.ts +25 -0
- package/src/shared/CopyOptions.ts +19 -0
- package/src/shared/DEBUG_META.ts +6 -0
- package/src/shared/EARLY_CALLS.ts +8 -0
- package/src/shared/FAILURE_STATUS_TEXT.ts +48 -0
- package/src/shared/Failed.ts +146 -0
- package/src/shared/GLOBAL_GETS.ts +7 -0
- package/src/shared/Issues.ts +49 -0
- package/src/shared/JsonValue.ts +18 -0
- package/src/shared/LOAD_SIGNAL.ts +10 -0
- package/src/shared/MOUNT_META.ts +6 -0
- package/src/shared/Middleware.ts +34 -0
- package/src/shared/NAVIGATION_HEADER.ts +7 -0
- package/src/shared/PRINCIPAL_HEADER.ts +6 -0
- package/src/shared/RPC_SERVER.ts +67 -0
- package/src/shared/Raw.ts +90 -0
- package/src/shared/Schema.ts +229 -0
- package/src/shared/UNEXPECTED.ts +11 -0
- package/src/shared/URL_ATTRIBUTES.ts +19 -0
- package/src/shared/VERSION_HEADER.ts +6 -0
- package/src/shared/WORK.ts +37 -0
- package/src/shared/anchorMarkup.ts +15 -0
- package/src/shared/answerOf.ts +141 -0
- package/src/shared/applyPatch.ts +173 -0
- package/src/shared/armAnchor.ts +17 -0
- package/src/shared/attributeText.ts +50 -0
- package/src/shared/boundValue.ts +58 -0
- package/src/shared/cookies.ts +271 -0
- package/src/shared/csp.ts +55 -0
- package/src/shared/decodedOrKept.ts +14 -0
- package/src/shared/escapeText.ts +103 -0
- package/src/shared/framing.ts +131 -0
- package/src/shared/headKeyOf.ts +34 -0
- package/src/shared/health.ts +51 -0
- package/src/shared/index.ts +117 -0
- package/src/shared/isEventName.ts +10 -0
- package/src/shared/isRecord.ts +4 -0
- package/src/shared/issuesOf.ts +18 -0
- package/src/shared/jsonSchema.ts +50 -0
- package/src/shared/listOf.ts +20 -0
- package/src/shared/log.ts +284 -0
- package/src/shared/matchRoute.ts +99 -0
- package/src/shared/menuItem.ts +80 -0
- package/src/shared/mount.ts +45 -0
- package/src/shared/muxFrames.ts +35 -0
- package/src/shared/navigate.ts +26 -0
- package/src/shared/note.ts +24 -0
- package/src/shared/numberFromText.ts +10 -0
- package/src/shared/online.ts +19 -0
- package/src/shared/paint.ts +4 -0
- package/src/shared/principal.ts +145 -0
- package/src/shared/principalRotated.ts +47 -0
- package/src/shared/propCell.ts +99 -0
- package/src/shared/props.ts +24 -0
- package/src/shared/putOwn.ts +24 -0
- package/src/shared/queryText.ts +18 -0
- package/src/shared/reactive/CELL.ts +8 -0
- package/src/shared/reactive/Cursor.ts +223 -0
- package/src/shared/reactive/ENTRY_LIVES.ts +7 -0
- package/src/shared/reactive/Expiry.ts +103 -0
- package/src/shared/reactive/FOLLOWED.ts +8 -0
- package/src/shared/reactive/KEYED_BRAND.ts +8 -0
- package/src/shared/reactive/Keyed.ts +423 -0
- package/src/shared/reactive/MUTATION.ts +5 -0
- package/src/shared/reactive/NO_INITIAL.ts +6 -0
- package/src/shared/reactive/NotLanded.ts +21 -0
- package/src/shared/reactive/PATCHES.ts +11 -0
- package/src/shared/reactive/PATCH_SINKS.ts +26 -0
- package/src/shared/reactive/REACTIVE_BRAND.ts +9 -0
- package/src/shared/reactive/RESHAPING.ts +13 -0
- package/src/shared/reactive/Reactive.ts +4480 -0
- package/src/shared/reactive/SELECTION_MEMBERS.ts +12 -0
- package/src/shared/reactive/STREAMING.ts +5 -0
- package/src/shared/reactive/argsContract.ts +97 -0
- package/src/shared/reactive/byOf.ts +18 -0
- package/src/shared/reactive/callLoad.ts +200 -0
- package/src/shared/reactive/canonicalKey.ts +95 -0
- package/src/shared/reactive/chunksOfStream.ts +51 -0
- package/src/shared/reactive/index.ts +542 -0
- package/src/shared/reactive/isReactive.ts +19 -0
- package/src/shared/reactive/isThenable.ts +14 -0
- package/src/shared/reactive/matchesPattern.ts +16 -0
- package/src/shared/reactive/pairReactive.ts +41 -0
- package/src/shared/reactive/parameterDefaults.ts +356 -0
- package/src/shared/reactive/recordWrites.ts +472 -0
- package/src/shared/reactive/scoped.ts +157 -0
- package/src/shared/reactive/scoping.ts +64 -0
- package/src/shared/reactive/share.ts +91 -0
- package/src/shared/reactive/structural.ts +62 -0
- package/src/shared/reactive/tracking.ts +418 -0
- package/src/shared/reactive/watch.ts +310 -0
- package/src/shared/refuse.ts +224 -0
- package/src/shared/route.ts +29 -0
- package/src/shared/routePattern.ts +146 -0
- package/src/shared/rpc.ts +496 -0
- package/src/shared/rpcRequest.ts +73 -0
- package/src/shared/safeUrl.ts +68 -0
- package/src/shared/seededDocument.ts +36 -0
- package/src/shared/setUpLevels.ts +53 -0
- package/src/shared/slotValue.ts +34 -0
- package/src/shared/spread.ts +79 -0
- package/src/shared/takeEarlyCall.ts +86 -0
- package/src/shared/url.ts +140 -0
- package/src/shared/validateJson.ts +578 -0
- package/src/shared/versionInEtag.ts +14 -0
- package/src/shared/writePath.ts +95 -0
- package/src/ui/ArmRegion.ts +185 -0
- package/src/ui/LAYER_NAMES.ts +30 -0
- package/src/ui/List.ts +273 -0
- package/src/ui/Mismatch.ts +32 -0
- package/src/ui/RawRun.ts +99 -0
- package/src/ui/Router.ts +698 -0
- package/src/ui/Scope.ts +115 -0
- package/src/ui/TryRegion.ts +278 -0
- package/src/ui/adoptWalk.ts +126 -0
- package/src/ui/applyHead.ts +146 -0
- package/src/ui/bindElement.ts +44 -0
- package/src/ui/bindProperty.ts +79 -0
- package/src/ui/bindSpread.ts +44 -0
- package/src/ui/escapeless.ts +14 -0
- package/src/ui/index.ts +64 -0
- package/src/ui/leave.ts +35 -0
- package/src/ui/listenForRoutes.ts +92 -0
- package/src/ui/measureLayer.ts +34 -0
- package/src/ui/menuBridge.ts +19 -0
- package/src/ui/moveNode.ts +34 -0
- package/src/ui/parseMarkup.ts +35 -0
- package/src/ui/reconcile.ts +368 -0
- package/src/ui/regions.ts +137 -0
- package/src/ui/reportMenu.ts +57 -0
- package/src/ui/routing.ts +18 -0
- package/src/ui/rowKeyOf.ts +19 -0
- package/src/ui/rows.ts +99 -0
- package/src/ui/setAttribute.ts +37 -0
- package/src/ui/setClass.ts +27 -0
- package/src/ui/setMarkup.ts +20 -0
- package/src/ui/setStyleProperty.ts +30 -0
- package/src/ui/setText.ts +28 -0
- package/src/ui/socketWire.ts +258 -0
- package/src/ui/wire.ts +141 -0
- package/AGENTS.md +0 -446
- package/README.md +0 -249
- package/src/abideLsp.ts +0 -251
- package/src/abideModules.d.ts +0 -8
- package/src/abideResolverPlugin.ts +0 -1101
- package/src/appEntry.ts +0 -161
- package/src/assets/app.html +0 -12
- package/src/build.ts +0 -189
- package/src/buildCli.ts +0 -125
- package/src/buildDisconnected.ts +0 -117
- package/src/bundleApp.ts +0 -146
- package/src/bundleDisconnectedEntry.ts +0 -14
- package/src/checkAbide.ts +0 -145
- package/src/cliEntry.ts +0 -25
- package/src/clientBuildPlugins.ts +0 -33
- package/src/clientEntry.ts +0 -17
- package/src/compile.ts +0 -61
- package/src/controlServerWorker.ts +0 -433
- package/src/devEntry.ts +0 -381
- package/src/discoveryEntry.ts +0 -82
- package/src/initAgent.ts +0 -50
- package/src/lib/bundle/BundleMenu.ts +0 -12
- package/src/lib/bundle/BundleMenuItem.ts +0 -25
- package/src/lib/bundle/BundleWindow.ts +0 -37
- package/src/lib/bundle/WEBVIEW_BUILD_REVISION.ts +0 -9
- package/src/lib/bundle/WEBVIEW_VERSION.ts +0 -7
- package/src/lib/bundle/bindConnectedFlag.ts +0 -29
- package/src/lib/bundle/bindRequestNavigate.ts +0 -34
- package/src/lib/bundle/buildWebviewLib.ts +0 -111
- package/src/lib/bundle/bundled.ts +0 -35
- package/src/lib/bundle/disconnected.abide +0 -265
- package/src/lib/bundle/disconnected.css +0 -9
- package/src/lib/bundle/ensureWebviewLib.ts +0 -20
- package/src/lib/bundle/exitWithParent.ts +0 -30
- package/src/lib/bundle/infoPlist.ts +0 -52
- package/src/lib/bundle/installDownloads.ts +0 -34
- package/src/lib/bundle/installMacMenu.ts +0 -49
- package/src/lib/bundle/listenLocalControlServer.ts +0 -19
- package/src/lib/bundle/native/abideMenu.mm +0 -422
- package/src/lib/bundle/native/webview.h +0 -4557
- package/src/lib/bundle/onMenu.ts +0 -42
- package/src/lib/bundle/openWebview.ts +0 -104
- package/src/lib/bundle/pngToIcns.ts +0 -47
- package/src/lib/bundle/probeAbideServer.ts +0 -57
- package/src/lib/bundle/resolveServerBinary.ts +0 -12
- package/src/lib/bundle/resolveWebviewLib.ts +0 -53
- package/src/lib/bundle/serverBinaryFilename.ts +0 -8
- package/src/lib/bundle/signMacApp.ts +0 -37
- package/src/lib/bundle/spawnEmbeddedServer.ts +0 -73
- package/src/lib/bundle/stableLocalPort.ts +0 -19
- package/src/lib/bundle/waitForServer.ts +0 -28
- package/src/lib/bundle/webviewCachePath.ts +0 -23
- package/src/lib/bundle/webviewLibName.ts +0 -11
- package/src/lib/cli/completeCli.ts +0 -38
- package/src/lib/cli/connectToServer.ts +0 -23
- package/src/lib/cli/createClient.ts +0 -108
- package/src/lib/cli/dispatchCommand.ts +0 -72
- package/src/lib/cli/loadEnvFromBinaryDir.ts +0 -15
- package/src/lib/cli/parseArgvForRpc.ts +0 -131
- package/src/lib/cli/printCommandHelp.ts +0 -43
- package/src/lib/cli/printSessionHelp.ts +0 -28
- package/src/lib/cli/printSessionStatus.ts +0 -21
- package/src/lib/cli/printTopLevelHelp.ts +0 -83
- package/src/lib/cli/printTrimmed.ts +0 -8
- package/src/lib/cli/printValue.ts +0 -10
- package/src/lib/cli/renderCliCompletions.ts +0 -56
- package/src/lib/cli/resolveCliTarget.ts +0 -47
- package/src/lib/cli/runCli.ts +0 -188
- package/src/lib/cli/runSession.ts +0 -107
- package/src/lib/cli/startLocalInstance.ts +0 -14
- package/src/lib/cli/tokenizeArgvFlags.ts +0 -86
- package/src/lib/cli/tokenizeLine.ts +0 -51
- package/src/lib/cli/types/CliManifest.ts +0 -11
- package/src/lib/cli/types/CliManifestEntry.ts +0 -17
- package/src/lib/cli/types/CliTarget.ts +0 -13
- package/src/lib/mcp/annotationsForMethod.ts +0 -29
- package/src/lib/mcp/buildPrompts.ts +0 -25
- package/src/lib/mcp/createMcpResourceServer.ts +0 -113
- package/src/lib/mcp/createMcpServer.ts +0 -56
- package/src/lib/mcp/dispatchMcpRequest.ts +0 -159
- package/src/lib/mcp/mcpResourceServerSlot.ts +0 -13
- package/src/lib/mcp/mcpSurface.ts +0 -57
- package/src/lib/mcp/mcpTools.ts +0 -196
- package/src/lib/mcp/renderPrompt.ts +0 -25
- package/src/lib/mcp/toolResultFromResponse.ts +0 -100
- package/src/lib/mcp/types/JsonRpcRequest.ts +0 -12
- package/src/lib/mcp/types/JsonRpcResponse.ts +0 -20
- package/src/lib/mcp/types/McpResourceContents.ts +0 -10
- package/src/lib/mcp/types/McpResourceDescriptor.ts +0 -6
- package/src/lib/mcp/types/McpResourceServer.ts +0 -12
- package/src/lib/mcp/types/McpServer.ts +0 -9
- package/src/lib/mcp/types/McpServerOptions.ts +0 -16
- package/src/lib/mcp/types/McpSurface.ts +0 -15
- package/src/lib/mcp/types/PromptDescriptor.ts +0 -5
- package/src/lib/mcp/types/PromptMessage.ts +0 -2
- package/src/lib/mcp/types/ToolDescriptor.ts +0 -7
- package/src/lib/mcp/types/ToolResult.ts +0 -2
- package/src/lib/server/AppModule.ts +0 -47
- package/src/lib/server/DELETE.ts +0 -10
- package/src/lib/server/GET.ts +0 -10
- package/src/lib/server/HEAD.ts +0 -10
- package/src/lib/server/PATCH.ts +0 -10
- package/src/lib/server/POST.ts +0 -10
- package/src/lib/server/PUT.ts +0 -10
- package/src/lib/server/agent.ts +0 -86
- package/src/lib/server/appDataDir.ts +0 -16
- package/src/lib/server/cli/buildEnvContent.ts +0 -19
- package/src/lib/server/cli/createTarGz.ts +0 -77
- package/src/lib/server/cli/handleCliDownload.ts +0 -152
- package/src/lib/server/cli/handleCliInstall.ts +0 -37
- package/src/lib/server/cli/installScript.ts +0 -31
- package/src/lib/server/cli/maxSourceMtime.ts +0 -26
- package/src/lib/server/cookies.ts +0 -30
- package/src/lib/server/env.ts +0 -51
- package/src/lib/server/error.ts +0 -110
- package/src/lib/server/json.ts +0 -57
- package/src/lib/server/jsonl.ts +0 -47
- package/src/lib/server/prompts/definePrompt.ts +0 -21
- package/src/lib/server/prompts/promptRegistry.ts +0 -9
- package/src/lib/server/prompts/registerPrompt.ts +0 -6
- package/src/lib/server/prompts/renderPromptTemplate.ts +0 -17
- package/src/lib/server/prompts/types/Prompt.ts +0 -14
- package/src/lib/server/prompts/types/PromptOptions.ts +0 -12
- package/src/lib/server/prompts/types/PromptRegistryEntry.ts +0 -13
- package/src/lib/server/prompts/types/PromptRoutes.ts +0 -10
- package/src/lib/server/redirect.ts +0 -43
- package/src/lib/server/render.ts +0 -70
- package/src/lib/server/request.ts +0 -19
- package/src/lib/server/rpc/defineRpc.ts +0 -271
- package/src/lib/server/rpc/dispatchRpcInProcess.ts +0 -46
- package/src/lib/server/rpc/findRpcByCommandName.ts +0 -18
- package/src/lib/server/rpc/parseArgs.ts +0 -244
- package/src/lib/server/rpc/readBodyWithinLimit.ts +0 -47
- package/src/lib/server/rpc/registerRpc.ts +0 -6
- package/src/lib/server/rpc/resolveRpcJsonSchema.ts +0 -28
- package/src/lib/server/rpc/rpcRegistry.ts +0 -11
- package/src/lib/server/rpc/runWithRpcTimeout.ts +0 -58
- package/src/lib/server/rpc/types/RemoteHandler.ts +0 -31
- package/src/lib/server/rpc/types/RemoteRoutes.ts +0 -13
- package/src/lib/server/rpc/types/RpcHelper.ts +0 -185
- package/src/lib/server/rpc/types/RpcRegistryEntry.ts +0 -50
- package/src/lib/server/rpc/types/TypedError.ts +0 -18
- package/src/lib/server/rpc/types/TypedResponse.ts +0 -18
- package/src/lib/server/rpc/unprocessed.ts +0 -13
- package/src/lib/server/rpc/validationError.ts +0 -17
- package/src/lib/server/runtime/DEFAULT_PORT.ts +0 -6
- package/src/lib/server/runtime/DEV_READY_MESSAGE.ts +0 -6
- package/src/lib/server/runtime/DEV_REBUILD_MESSAGE.ts +0 -4
- package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -92
- package/src/lib/server/runtime/SSR_SWAP_SCRIPT.ts +0 -19
- package/src/lib/server/runtime/STATUS_TEXT.ts +0 -22
- package/src/lib/server/runtime/STREAMED_HTML_HEADER.ts +0 -13
- package/src/lib/server/runtime/acceptsGzip.ts +0 -33
- package/src/lib/server/runtime/amendBroadcaster.ts +0 -53
- package/src/lib/server/runtime/buildCacheSnapshot.ts +0 -63
- package/src/lib/server/runtime/buildHealthPayload.ts +0 -34
- package/src/lib/server/runtime/buildInFlightSnapshot.ts +0 -35
- package/src/lib/server/runtime/buildInspectorSurface.ts +0 -47
- package/src/lib/server/runtime/buildOpenApiSpec.ts +0 -150
- package/src/lib/server/runtime/buildPreloadManifest.ts +0 -153
- package/src/lib/server/runtime/cacheControlForAsset.ts +0 -22
- package/src/lib/server/runtime/cacheStalenessBroadcaster.ts +0 -33
- package/src/lib/server/runtime/containsTraversal.ts +0 -41
- package/src/lib/server/runtime/createAppAssetServer.ts +0 -100
- package/src/lib/server/runtime/createAppRouteResolver.ts +0 -145
- package/src/lib/server/runtime/createAssetHeaderCache.ts +0 -31
- package/src/lib/server/runtime/createPlumbingRouter.ts +0 -212
- package/src/lib/server/runtime/createPublicAssetServer.ts +0 -76
- package/src/lib/server/runtime/createRouteDispatcher.ts +0 -121
- package/src/lib/server/runtime/createServer.ts +0 -605
- package/src/lib/server/runtime/createUiPageRenderer.ts +0 -489
- package/src/lib/server/runtime/crossOriginForbidden.ts +0 -17
- package/src/lib/server/runtime/crossOriginGate.ts +0 -29
- package/src/lib/server/runtime/devClientFingerprint.ts +0 -103
- package/src/lib/server/runtime/devReloadResponse.ts +0 -41
- package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +0 -27
- package/src/lib/server/runtime/envSchemaStore.ts +0 -15
- package/src/lib/server/runtime/finalizeResponse.ts +0 -42
- package/src/lib/server/runtime/findOpenPort.ts +0 -21
- package/src/lib/server/runtime/flushingGzipStream.ts +0 -62
- package/src/lib/server/runtime/getActiveServer.ts +0 -6
- package/src/lib/server/runtime/globToPathSet.ts +0 -29
- package/src/lib/server/runtime/gzipResponse.ts +0 -57
- package/src/lib/server/runtime/inFlightRequests.ts +0 -13
- package/src/lib/server/runtime/inProcessServer.ts +0 -20
- package/src/lib/server/runtime/installAmbientScopeStore.ts +0 -54
- package/src/lib/server/runtime/internalErrorResponse.ts +0 -28
- package/src/lib/server/runtime/isCrossOriginRequest.ts +0 -23
- package/src/lib/server/runtime/listenOnOpenPort.ts +0 -36
- package/src/lib/server/runtime/logExposedSurfaces.ts +0 -156
- package/src/lib/server/runtime/maybeMountInspector.ts +0 -104
- package/src/lib/server/runtime/mimeForExtension.ts +0 -14
- package/src/lib/server/runtime/pageRenderSlot.ts +0 -15
- package/src/lib/server/runtime/pageUrlFromStore.ts +0 -15
- package/src/lib/server/runtime/parseIdleTimeout.ts +0 -10
- package/src/lib/server/runtime/parsePort.ts +0 -11
- package/src/lib/server/runtime/pathStore.ts +0 -15
- package/src/lib/server/runtime/registryManifests.ts +0 -66
- package/src/lib/server/runtime/renderCellBarrierStore.ts +0 -15
- package/src/lib/server/runtime/requestContext.ts +0 -5
- package/src/lib/server/runtime/resolvePageSnapshot.ts +0 -25
- package/src/lib/server/runtime/respondWithEmbeddedAsset.ts +0 -18
- package/src/lib/server/runtime/runWithRequestScope.ts +0 -165
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +0 -31
- package/src/lib/server/runtime/serverSlot.ts +0 -13
- package/src/lib/server/runtime/setActiveServer.ts +0 -6
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +0 -121
- package/src/lib/server/runtime/streamCacheResolutions.ts +0 -44
- package/src/lib/server/runtime/streamFromIterator.ts +0 -124
- package/src/lib/server/runtime/textResponse.ts +0 -23
- package/src/lib/server/runtime/typedErrorResponse.ts +0 -26
- package/src/lib/server/runtime/types/Assets.ts +0 -6
- package/src/lib/server/runtime/types/DevReloadStamp.ts +0 -14
- package/src/lib/server/runtime/types/InspectorCacheEntry.ts +0 -24
- package/src/lib/server/runtime/types/InspectorCacheSnapshot.ts +0 -11
- package/src/lib/server/runtime/types/InspectorContext.ts +0 -34
- package/src/lib/server/runtime/types/InspectorInFlightRequest.ts +0 -20
- package/src/lib/server/runtime/types/InspectorInFlightSnapshot.ts +0 -10
- package/src/lib/server/runtime/types/InspectorPrompt.ts +0 -15
- package/src/lib/server/runtime/types/InspectorRpc.ts +0 -27
- package/src/lib/server/runtime/types/InspectorSocket.ts +0 -17
- package/src/lib/server/runtime/types/InspectorSurface.ts +0 -16
- package/src/lib/server/runtime/types/RequestStore.ts +0 -125
- package/src/lib/server/runtime/warnUnguardedMcp.ts +0 -43
- package/src/lib/server/runtime/withResponseDefaults.ts +0 -29
- package/src/lib/server/server.ts +0 -33
- package/src/lib/server/socket.ts +0 -32
- package/src/lib/server/sockets/amendFamilyEntry.ts +0 -39
- package/src/lib/server/sockets/createSocketDispatcher.ts +0 -397
- package/src/lib/server/sockets/defineSocket.ts +0 -225
- package/src/lib/server/sockets/lookupSocket.ts +0 -6
- package/src/lib/server/sockets/registerSocket.ts +0 -18
- package/src/lib/server/sockets/socketOperations.ts +0 -36
- package/src/lib/server/sockets/socketRegistry.ts +0 -9
- package/src/lib/server/sockets/types/SocketOperation.ts +0 -22
- package/src/lib/server/sockets/types/SocketOptions.ts +0 -26
- package/src/lib/server/sockets/types/SocketRegistryEntry.ts +0 -19
- package/src/lib/server/sockets/types/SocketRoutes.ts +0 -10
- package/src/lib/server/sse.ts +0 -58
- package/src/lib/shared/ABIDE_PACKAGE_NAME.ts +0 -7
- package/src/lib/shared/ABIDE_VERSION.ts +0 -9
- package/src/lib/shared/AMEND_TOPIC_PREFIX.ts +0 -9
- package/src/lib/shared/ASYNC_CELL.ts +0 -5
- package/src/lib/shared/CACHE_CONTROL_VALUES.ts +0 -16
- package/src/lib/shared/CACHE_STALENESS_SOCKET.ts +0 -8
- package/src/lib/shared/CLI_PATH.ts +0 -7
- package/src/lib/shared/DEFER.ts +0 -8
- package/src/lib/shared/DEV_REBUILD_PATH.ts +0 -6
- package/src/lib/shared/DEV_RELOAD_PATH.ts +0 -6
- package/src/lib/shared/HEALTH_PATH.ts +0 -7
- package/src/lib/shared/HTTP_METHODS.ts +0 -6
- package/src/lib/shared/HttpError.ts +0 -30
- package/src/lib/shared/IDENTITY_PATH.ts +0 -6
- package/src/lib/shared/INSPECTOR_PATH.ts +0 -7
- package/src/lib/shared/MCP_PATH.ts +0 -6
- package/src/lib/shared/NAV_HEADER.ts +0 -8
- package/src/lib/shared/OFFLINE_HEADER.ts +0 -8
- package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +0 -15
- package/src/lib/shared/REF_JSON_HEADER.ts +0 -9
- package/src/lib/shared/REF_JSON_TAGS.ts +0 -43
- package/src/lib/shared/REMOTE_FUNCTION.ts +0 -8
- package/src/lib/shared/REPLAYABLE_METHODS.ts +0 -12
- package/src/lib/shared/RESERVED_SOCKET_PREFIX.ts +0 -8
- package/src/lib/shared/RPC_ARGS_TYPE.ts +0 -7
- package/src/lib/shared/RPC_SHIM_GLOBALS.ts +0 -9
- package/src/lib/shared/SEEDS.ts +0 -33
- package/src/lib/shared/SOCKETS_PATH.ts +0 -7
- package/src/lib/shared/SOCKET_SEED.ts +0 -12
- package/src/lib/shared/SSR_SCRIPT_ID.ts +0 -11
- package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +0 -12
- package/src/lib/shared/SocketDisconnectedError.ts +0 -13
- package/src/lib/shared/TEXT_PLAIN.ts +0 -7
- package/src/lib/shared/abideImportName.ts +0 -44
- package/src/lib/shared/abideLog.ts +0 -38
- package/src/lib/shared/activeCacheStore.ts +0 -12
- package/src/lib/shared/activePage.ts +0 -12
- package/src/lib/shared/amend.ts +0 -48
- package/src/lib/shared/amendBroadcastSlot.ts +0 -19
- package/src/lib/shared/appDataDir.ts +0 -34
- package/src/lib/shared/appNameSlot.ts +0 -10
- package/src/lib/shared/applyAmendLocally.ts +0 -20
- package/src/lib/shared/applyCacheStalenessLocally.ts +0 -18
- package/src/lib/shared/assertExhaustive.ts +0 -14
- package/src/lib/shared/attachRpcSelectorMethods.ts +0 -44
- package/src/lib/shared/attachSocketSelectorMethods.ts +0 -34
- package/src/lib/shared/augmentModule.ts +0 -18
- package/src/lib/shared/basePath.ts +0 -11
- package/src/lib/shared/basePathFromAppUrl.ts +0 -20
- package/src/lib/shared/baseSlot.ts +0 -9
- package/src/lib/shared/binaryDirEnvPath.ts +0 -12
- package/src/lib/shared/bodyValueForKind.ts +0 -34
- package/src/lib/shared/browserClientFlags.ts +0 -10
- package/src/lib/shared/buildArtifact.ts +0 -20
- package/src/lib/shared/buildRpcProxy.ts +0 -39
- package/src/lib/shared/buildRpcRequest.ts +0 -81
- package/src/lib/shared/buildSocketOverChannel.ts +0 -102
- package/src/lib/shared/bundleGraphFromMetafile.ts +0 -68
- package/src/lib/shared/bundleLayout.ts +0 -36
- package/src/lib/shared/cache.ts +0 -1584
- package/src/lib/shared/cacheEntryFromSnapshot.ts +0 -77
- package/src/lib/shared/cacheManagedSlot.ts +0 -10
- package/src/lib/shared/cacheReaderSocketSlot.ts +0 -11
- package/src/lib/shared/cacheStalenessSlot.ts +0 -21
- package/src/lib/shared/cacheStoreSlot.ts +0 -12
- package/src/lib/shared/cacheStores.ts +0 -10
- package/src/lib/shared/canonicalJson.ts +0 -86
- package/src/lib/shared/carriesBodyArgs.ts +0 -13
- package/src/lib/shared/changeAffectsClient.ts +0 -40
- package/src/lib/shared/clearLastConnection.ts +0 -7
- package/src/lib/shared/commandNameForUrl.ts +0 -17
- package/src/lib/shared/contentBodyKind.ts +0 -27
- package/src/lib/shared/contentTypeOf.ts +0 -6
- package/src/lib/shared/createCacheStore.ts +0 -147
- package/src/lib/shared/createChannelLog.ts +0 -146
- package/src/lib/shared/createLifecycleChannel.ts +0 -62
- package/src/lib/shared/createLivenessWatch.ts +0 -118
- package/src/lib/shared/createPushIterator.ts +0 -132
- package/src/lib/shared/createReachable.ts +0 -78
- package/src/lib/shared/createRemoteFunction.ts +0 -210
- package/src/lib/shared/createResolverSlot.ts +0 -30
- package/src/lib/shared/createRpcServerProgram.ts +0 -1007
- package/src/lib/shared/createSocketSubRegistry.ts +0 -112
- package/src/lib/shared/createSubscriber.ts +0 -55
- package/src/lib/shared/createTraceContext.ts +0 -21
- package/src/lib/shared/dataDirEnvPath.ts +0 -12
- package/src/lib/shared/debugGate.ts +0 -90
- package/src/lib/shared/decodeRefJson.ts +0 -112
- package/src/lib/shared/decodeResponse.ts +0 -54
- package/src/lib/shared/decodeWireBody.ts +0 -35
- package/src/lib/shared/detectRpcMethod.ts +0 -24
- package/src/lib/shared/detectTarget.ts +0 -27
- package/src/lib/shared/docSnapshotsSlot.ts +0 -13
- package/src/lib/shared/done.ts +0 -20
- package/src/lib/shared/emitLogRecord.ts +0 -217
- package/src/lib/shared/encodeRefJson.ts +0 -108
- package/src/lib/shared/encodeSeedValue.ts +0 -24
- package/src/lib/shared/encodeWireBody.ts +0 -18
- package/src/lib/shared/escapeHtml.ts +0 -15
- package/src/lib/shared/escapeRegex.ts +0 -9
- package/src/lib/shared/exeSuffix.ts +0 -9
- package/src/lib/shared/exitOnBuildFailure.ts +0 -18
- package/src/lib/shared/extraForwardHeaders.ts +0 -16
- package/src/lib/shared/fieldErrorsFromIssues.ts +0 -27
- package/src/lib/shared/fileName.ts +0 -9
- package/src/lib/shared/fileStem.ts +0 -11
- package/src/lib/shared/findExportCallSite.ts +0 -233
- package/src/lib/shared/formatTraceparent.ts +0 -6
- package/src/lib/shared/forwardHeaders.ts +0 -44
- package/src/lib/shared/generateDeclarations.ts +0 -72
- package/src/lib/shared/getRemoteMeta.ts +0 -5
- package/src/lib/shared/hasSeedableRequest.ts +0 -25
- package/src/lib/shared/health.ts +0 -179
- package/src/lib/shared/healthReadSlot.ts +0 -11
- package/src/lib/shared/healthSeedSlot.ts +0 -12
- package/src/lib/shared/html.ts +0 -19
- package/src/lib/shared/httpErrorFor.ts +0 -26
- package/src/lib/shared/hydrationWindow.ts +0 -53
- package/src/lib/shared/importNamesToStrip.ts +0 -13
- package/src/lib/shared/invalidate.ts +0 -39
- package/src/lib/shared/invalidateEvent.ts +0 -11
- package/src/lib/shared/invalidateTripwire.ts +0 -40
- package/src/lib/shared/isAbideHealthPayload.ts +0 -11
- package/src/lib/shared/isAsciiWhitespace.ts +0 -5
- package/src/lib/shared/isAsyncCell.ts +0 -11
- package/src/lib/shared/isAsyncIterable.ts +0 -8
- package/src/lib/shared/isCompileTarget.ts +0 -21
- package/src/lib/shared/isDebugEnabled.ts +0 -19
- package/src/lib/shared/isDebugNegated.ts +0 -17
- package/src/lib/shared/isIdentPart.ts +0 -9
- package/src/lib/shared/isIdentStart.ts +0 -8
- package/src/lib/shared/isLayoutFile.ts +0 -12
- package/src/lib/shared/isModuleNotFound.ts +0 -18
- package/src/lib/shared/isReadOnlyMethod.ts +0 -14
- package/src/lib/shared/isReplayableMethod.ts +0 -7
- package/src/lib/shared/isStreamingResponse.ts +0 -12
- package/src/lib/shared/isSubscribable.ts +0 -15
- package/src/lib/shared/isThenable.ts +0 -9
- package/src/lib/shared/jsonSchemaForPromptArguments.ts +0 -29
- package/src/lib/shared/jsonSchemaForSchema.ts +0 -39
- package/src/lib/shared/jsonSchemaForType.ts +0 -245
- package/src/lib/shared/jsonlErrorFrame.ts +0 -24
- package/src/lib/shared/keyForRemoteCall.ts +0 -30
- package/src/lib/shared/keyMatchesPrefix.ts +0 -9
- package/src/lib/shared/keyPrefixForRemote.ts +0 -12
- package/src/lib/shared/lastConnectionPath.ts +0 -7
- package/src/lib/shared/layoutChainForRoute.ts +0 -22
- package/src/lib/shared/lenientDecode.ts +0 -15
- package/src/lib/shared/loadEnvFile.ts +0 -17
- package/src/lib/shared/loadEnvFromDataDir.ts +0 -14
- package/src/lib/shared/loadProjectTsConfig.ts +0 -28
- package/src/lib/shared/log.ts +0 -24
- package/src/lib/shared/logClosingRecord.ts +0 -28
- package/src/lib/shared/logTapSlot.ts +0 -13
- package/src/lib/shared/manifestModule.ts +0 -39
- package/src/lib/shared/markFrameworkSourcesIgnored.ts +0 -47
- package/src/lib/shared/matchRoute.ts +0 -165
- package/src/lib/shared/matcherFromEnvelope.ts +0 -31
- package/src/lib/shared/memoizeByKey.ts +0 -32
- package/src/lib/shared/messageFromError.ts +0 -6
- package/src/lib/shared/normalizePathname.ts +0 -11
- package/src/lib/shared/normalizeTarget.ts +0 -10
- package/src/lib/shared/online.ts +0 -51
- package/src/lib/shared/page.ts +0 -30
- package/src/lib/shared/pageSlot.ts +0 -17
- package/src/lib/shared/pageUrlForFile.ts +0 -14
- package/src/lib/shared/parseBoundedEnvInt.ts +0 -20
- package/src/lib/shared/parseEnv.ts +0 -41
- package/src/lib/shared/parsePromptMarkdown.ts +0 -35
- package/src/lib/shared/parseRouteSegments.ts +0 -31
- package/src/lib/shared/parseTraceparent.ts +0 -26
- package/src/lib/shared/peek.ts +0 -46
- package/src/lib/shared/pending.ts +0 -36
- package/src/lib/shared/pendingAsyncCellsSlot.ts +0 -13
- package/src/lib/shared/prepareRemoteExport.ts +0 -40
- package/src/lib/shared/prepareRpcModule.ts +0 -240
- package/src/lib/shared/prepareSocketModule.ts +0 -46
- package/src/lib/shared/probeRegistries.ts +0 -68
- package/src/lib/shared/producerKey.ts +0 -32
- package/src/lib/shared/programNameForPackage.ts +0 -16
- package/src/lib/shared/promptNameForFile.ts +0 -10
- package/src/lib/shared/queryStringFromArgs.ts +0 -38
- package/src/lib/shared/randomHexId.ts +0 -14
- package/src/lib/shared/reachable.ts +0 -99
- package/src/lib/shared/readEnvFile.ts +0 -15
- package/src/lib/shared/readLastConnection.ts +0 -18
- package/src/lib/shared/readPackageJson.ts +0 -9
- package/src/lib/shared/recordRemoteMeta.ts +0 -5
- package/src/lib/shared/refresh.ts +0 -39
- package/src/lib/shared/refreshing.ts +0 -37
- package/src/lib/shared/remoteMetaStore.ts +0 -16
- package/src/lib/shared/requestScopeSlot.ts +0 -13
- package/src/lib/shared/resolveClientFlags.ts +0 -20
- package/src/lib/shared/resolvedCellsSlot.ts +0 -13
- package/src/lib/shared/responseBodyKind.ts +0 -35
- package/src/lib/shared/responseErrorText.ts +0 -9
- package/src/lib/shared/reviveWireField.ts +0 -49
- package/src/lib/shared/reviveWireOutput.ts +0 -36
- package/src/lib/shared/routeParamsShape.ts +0 -20
- package/src/lib/shared/rpcErrorRegistry.ts +0 -69
- package/src/lib/shared/rpcServerForRoot.ts +0 -25
- package/src/lib/shared/rpcTimeoutSlot.ts +0 -9
- package/src/lib/shared/rpcUrlForFile.ts +0 -19
- package/src/lib/shared/runningAsStandaloneBinary.ts +0 -13
- package/src/lib/shared/safeJsonForScript.ts +0 -17
- package/src/lib/shared/scanPages.ts +0 -25
- package/src/lib/shared/selectorMatcher.ts +0 -55
- package/src/lib/shared/selectorPrefix.ts +0 -32
- package/src/lib/shared/serializeEnv.ts +0 -30
- package/src/lib/shared/serializeSelector.ts +0 -69
- package/src/lib/shared/setAppName.ts +0 -5
- package/src/lib/shared/setOwnProperty.ts +0 -19
- package/src/lib/shared/setsIntersect.ts +0 -15
- package/src/lib/shared/sharedCacheStore.ts +0 -14
- package/src/lib/shared/sharedCacheStoreSlot.ts +0 -10
- package/src/lib/shared/skipNonCode.ts +0 -230
- package/src/lib/shared/snapshotShippable.ts +0 -23
- package/src/lib/shared/snippet.ts +0 -30
- package/src/lib/shared/socketNameForFile.ts +0 -11
- package/src/lib/shared/socketTailsSlot.ts +0 -13
- package/src/lib/shared/socketTapSlot.ts +0 -12
- package/src/lib/shared/sseErrorFrame.ts +0 -29
- package/src/lib/shared/streamResponse.ts +0 -180
- package/src/lib/shared/streamedCellsSlot.ts +0 -14
- package/src/lib/shared/stripImport.ts +0 -29
- package/src/lib/shared/subscribableFromResponse.ts +0 -51
- package/src/lib/shared/subscribableProbes.ts +0 -111
- package/src/lib/shared/tailProbeSlot.ts +0 -23
- package/src/lib/shared/toTagSet.ts +0 -4
- package/src/lib/shared/trace.ts +0 -16
- package/src/lib/shared/types/AmendApply.ts +0 -15
- package/src/lib/shared/types/AsyncComputed.ts +0 -20
- package/src/lib/shared/types/AsyncState.ts +0 -13
- package/src/lib/shared/types/CacheEntry.ts +0 -100
- package/src/lib/shared/types/CacheInvalidation.ts +0 -9
- package/src/lib/shared/types/CacheOnContext.ts +0 -15
- package/src/lib/shared/types/CacheOptions.ts +0 -53
- package/src/lib/shared/types/CachePolicy.ts +0 -31
- package/src/lib/shared/types/CacheReaderHook.ts +0 -11
- package/src/lib/shared/types/CacheSelector.ts +0 -17
- package/src/lib/shared/types/CacheSnapshotEntry.ts +0 -32
- package/src/lib/shared/types/CacheStalenessApply.ts +0 -13
- package/src/lib/shared/types/CacheStalenessFrame.ts +0 -24
- package/src/lib/shared/types/CacheStats.ts +0 -13
- package/src/lib/shared/types/CacheStore.ts +0 -45
- package/src/lib/shared/types/ChannelLog.ts +0 -13
- package/src/lib/shared/types/ClientFlags.ts +0 -13
- package/src/lib/shared/types/CompileTarget.ts +0 -6
- package/src/lib/shared/types/DebugGate.ts +0 -10
- package/src/lib/shared/types/DocSnapshots.ts +0 -12
- package/src/lib/shared/types/ErrorJsonSchemas.ts +0 -8
- package/src/lib/shared/types/ErrorSpec.ts +0 -11
- package/src/lib/shared/types/FrameworkLog.ts +0 -13
- package/src/lib/shared/types/HttpMethod.ts +0 -3
- package/src/lib/shared/types/InputCoercion.ts +0 -17
- package/src/lib/shared/types/LastConnection.ts +0 -9
- package/src/lib/shared/types/Log.ts +0 -13
- package/src/lib/shared/types/LogRecord.ts +0 -42
- package/src/lib/shared/types/LogVoice.ts +0 -7
- package/src/lib/shared/types/NamedAsyncIterable.ts +0 -26
- package/src/lib/shared/types/OutputWirePlan.ts +0 -17
- package/src/lib/shared/types/PageSnapshot.ts +0 -14
- package/src/lib/shared/types/PagesScan.ts +0 -7
- package/src/lib/shared/types/PendingAsyncCells.ts +0 -10
- package/src/lib/shared/types/PromptArgument.ts +0 -12
- package/src/lib/shared/types/RawRemoteFunction.ts +0 -20
- package/src/lib/shared/types/RemoteCallable.ts +0 -40
- package/src/lib/shared/types/RemoteFunction.ts +0 -116
- package/src/lib/shared/types/ReplayableMethod.ts +0 -7
- package/src/lib/shared/types/RequestScopeInfo.ts +0 -16
- package/src/lib/shared/types/ResolvedCells.ts +0 -11
- package/src/lib/shared/types/ResolverSlot.ts +0 -13
- package/src/lib/shared/types/ReturnBody.ts +0 -15
- package/src/lib/shared/types/RpcBuildStamps.ts +0 -32
- package/src/lib/shared/types/RpcError.ts +0 -26
- package/src/lib/shared/types/RpcErrorGuard.ts +0 -35
- package/src/lib/shared/types/RpcInvoker.ts +0 -6
- package/src/lib/shared/types/RpcOptions.ts +0 -22
- package/src/lib/shared/types/Socket.ts +0 -54
- package/src/lib/shared/types/SocketChannel.ts +0 -17
- package/src/lib/shared/types/SocketClientFrame.ts +0 -19
- package/src/lib/shared/types/SocketServerFrame.ts +0 -24
- package/src/lib/shared/types/SocketSubCallbacks.ts +0 -13
- package/src/lib/shared/types/SocketTails.ts +0 -13
- package/src/lib/shared/types/SourceMap.ts +0 -14
- package/src/lib/shared/types/SsrBootState.ts +0 -27
- package/src/lib/shared/types/SsrPayload.ts +0 -36
- package/src/lib/shared/types/StandardSchemaV1.ts +0 -56
- package/src/lib/shared/types/StreamPolicy.ts +0 -11
- package/src/lib/shared/types/StreamedCells.ts +0 -19
- package/src/lib/shared/types/StreamedResolution.ts +0 -28
- package/src/lib/shared/types/TailHooks.ts +0 -13
- package/src/lib/shared/types/TraceContext.ts +0 -17
- package/src/lib/shared/types/ValidationErrorData.ts +0 -20
- package/src/lib/shared/types/WireKind.ts +0 -11
- package/src/lib/shared/url.ts +0 -141
- package/src/lib/shared/validationHttpError.ts +0 -33
- package/src/lib/shared/warmSeedKey.ts +0 -16
- package/src/lib/shared/wireJsonReplacer.ts +0 -30
- package/src/lib/shared/withBase.ts +0 -10
- package/src/lib/shared/withBaseUrl.ts +0 -17
- package/src/lib/shared/withCacheManaged.ts +0 -18
- package/src/lib/shared/withJsonSchema.ts +0 -21
- package/src/lib/shared/writeDts.ts +0 -19
- package/src/lib/shared/writeHealthDts.ts +0 -36
- package/src/lib/shared/writeLastConnection.ts +0 -13
- package/src/lib/shared/writePublicAssetsDts.ts +0 -27
- package/src/lib/shared/writeRoutesDts.ts +0 -41
- package/src/lib/shared/writeRpcDts.ts +0 -60
- package/src/lib/shared/writeTestRpcDts.ts +0 -45
- package/src/lib/shared/writeTestSocketsDts.ts +0 -34
- package/src/lib/test/assertAgentFrameConformance.ts +0 -73
- package/src/lib/test/createScriptedSurface.ts +0 -45
- package/src/lib/test/createTestApp.ts +0 -242
- package/src/lib/test/createTestSocketChannel.ts +0 -85
- package/src/lib/ui/README.md +0 -86
- package/src/lib/ui/activePendingCells.ts +0 -14
- package/src/lib/ui/amendReaderHook.ts +0 -125
- package/src/lib/ui/compile/ABIDE_SEMANTIC_TOKENS_LEGEND.ts +0 -79
- package/src/lib/ui/compile/AbideCompileError.ts +0 -16
- package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +0 -21
- package/src/lib/ui/compile/SSR_ESCAPE.ts +0 -40
- package/src/lib/ui/compile/TS_PRINTER.ts +0 -7
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +0 -86
- package/src/lib/ui/compile/VOID_TAGS.ts +0 -22
- package/src/lib/ui/compile/abideUiPlugin.ts +0 -109
- package/src/lib/ui/compile/analyzeComponent.ts +0 -222
- package/src/lib/ui/compile/asOutlet.ts +0 -30
- package/src/lib/ui/compile/assertTranspiles.ts +0 -19
- package/src/lib/ui/compile/assetModulesFile.ts +0 -32
- package/src/lib/ui/compile/assignmentTargetNames.ts +0 -54
- package/src/lib/ui/compile/asyncInterpolationFields.ts +0 -152
- package/src/lib/ui/compile/asyncValuePositionError.ts +0 -21
- package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +0 -64
- package/src/lib/ui/compile/attrLiftPosition.ts +0 -18
- package/src/lib/ui/compile/awaitPlan.ts +0 -60
- package/src/lib/ui/compile/awaitSubjectExpr.ts +0 -23
- package/src/lib/ui/compile/bindListenEvent.ts +0 -18
- package/src/lib/ui/compile/cachedShadowProgram.ts +0 -39
- package/src/lib/ui/compile/cachedSourceFile.ts +0 -40
- package/src/lib/ui/compile/catchBinding.ts +0 -10
- package/src/lib/ui/compile/classStyleMergePlan.ts +0 -108
- package/src/lib/ui/compile/classifyInterpolationType.ts +0 -46
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +0 -166
- package/src/lib/ui/compile/compileComponent.ts +0 -46
- package/src/lib/ui/compile/compileModule.ts +0 -306
- package/src/lib/ui/compile/compileSSR.ts +0 -119
- package/src/lib/ui/compile/compileShadow.ts +0 -1326
- package/src/lib/ui/compile/componentOrdinals.ts +0 -39
- package/src/lib/ui/compile/composeProps.ts +0 -64
- package/src/lib/ui/compile/createShadowLanguageService.ts +0 -348
- package/src/lib/ui/compile/createShadowProgram.ts +0 -127
- package/src/lib/ui/compile/createShadowScope.ts +0 -42
- package/src/lib/ui/compile/declaredNames.ts +0 -36
- package/src/lib/ui/compile/decodeHtmlEntities.ts +0 -53
- package/src/lib/ui/compile/destructureBindingNames.ts +0 -38
- package/src/lib/ui/compile/desugarSignals.ts +0 -1113
- package/src/lib/ui/compile/eachPlan.ts +0 -61
- package/src/lib/ui/compile/elementPlan.ts +0 -55
- package/src/lib/ui/compile/encodeSemanticTokens.ts +0 -60
- package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +0 -19
- package/src/lib/ui/compile/generateBuild.ts +0 -903
- package/src/lib/ui/compile/generateSSR.ts +0 -967
- package/src/lib/ui/compile/groupBindParts.ts +0 -28
- package/src/lib/ui/compile/hasTopLevelAwait.ts +0 -28
- package/src/lib/ui/compile/hoistCells.ts +0 -120
- package/src/lib/ui/compile/hoistableAwaits.ts +0 -183
- package/src/lib/ui/compile/hoistableChildRenders.ts +0 -112
- package/src/lib/ui/compile/identifierReferencePattern.ts +0 -13
- package/src/lib/ui/compile/ifPlan.ts +0 -36
- package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +0 -27
- package/src/lib/ui/compile/interpolationClassifierForRoot.ts +0 -32
- package/src/lib/ui/compile/isAnchorPositioned.ts +0 -19
- package/src/lib/ui/compile/isControlFlow.ts +0 -20
- package/src/lib/ui/compile/isFunctionScopeBoundary.ts +0 -20
- package/src/lib/ui/compile/isPlainIdentifier.ts +0 -7
- package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +0 -215
- package/src/lib/ui/compile/isTextLeaf.ts +0 -16
- package/src/lib/ui/compile/isWhitespaceText.ts +0 -19
- package/src/lib/ui/compile/liftAsyncSubExpressions.ts +0 -166
- package/src/lib/ui/compile/liftScriptSeed.ts +0 -47
- package/src/lib/ui/compile/loadShadowTsConfig.ts +0 -31
- package/src/lib/ui/compile/lowerAsyncInterpolations.ts +0 -92
- package/src/lib/ui/compile/lowerCompoundAssignment.ts +0 -49
- package/src/lib/ui/compile/lowerContext.ts +0 -167
- package/src/lib/ui/compile/lowerDocAccess.ts +0 -302
- package/src/lib/ui/compile/lowerScript.ts +0 -190
- package/src/lib/ui/compile/lowerUpdateExpression.ts +0 -32
- package/src/lib/ui/compile/makeVarNamer.ts +0 -10
- package/src/lib/ui/compile/nearestProjectRoot.ts +0 -16
- package/src/lib/ui/compile/nodeAtShadowOffset.ts +0 -28
- package/src/lib/ui/compile/offsetToLineColumn.ts +0 -16
- package/src/lib/ui/compile/offsetToPosition.ts +0 -9
- package/src/lib/ui/compile/pagePropsType.ts +0 -23
- package/src/lib/ui/compile/parseTemplate.ts +0 -25
- package/src/lib/ui/compile/parseTemplateRecovering.ts +0 -1385
- package/src/lib/ui/compile/prepareNestedScript.ts +0 -62
- package/src/lib/ui/compile/reactiveBinding.ts +0 -47
- package/src/lib/ui/compile/referencedIdentifiers.ts +0 -35
- package/src/lib/ui/compile/remapShadowDiagnostic.ts +0 -30
- package/src/lib/ui/compile/renameSignalRefs.ts +0 -425
- package/src/lib/ui/compile/resolveAbideImports.ts +0 -29
- package/src/lib/ui/compile/resolveBranches.ts +0 -21
- package/src/lib/ui/compile/resolveReactiveExport.ts +0 -92
- package/src/lib/ui/compile/scopeAttr.ts +0 -9
- package/src/lib/ui/compile/scopeCss.ts +0 -138
- package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +0 -60
- package/src/lib/ui/compile/shadowInterpolationClassifier.ts +0 -47
- package/src/lib/ui/compile/shadowNaming.ts +0 -11
- package/src/lib/ui/compile/signalCallee.ts +0 -24
- package/src/lib/ui/compile/skeletonContext.ts +0 -133
- package/src/lib/ui/compile/skeletonable.ts +0 -68
- package/src/lib/ui/compile/snippetPlan.ts +0 -28
- package/src/lib/ui/compile/sourceFileOptionsSignature.ts +0 -20
- package/src/lib/ui/compile/sourceToShadowOffset.ts +0 -24
- package/src/lib/ui/compile/spreadExcludedNames.ts +0 -27
- package/src/lib/ui/compile/staticAttr.ts +0 -11
- package/src/lib/ui/compile/staticAttrValue.ts +0 -13
- package/src/lib/ui/compile/staticTextPart.ts +0 -12
- package/src/lib/ui/compile/stripEffects.ts +0 -48
- package/src/lib/ui/compile/structuralHeadTokens.ts +0 -107
- package/src/lib/ui/compile/switchPlan.ts +0 -28
- package/src/lib/ui/compile/templateAnchorAdapter.ts +0 -61
- package/src/lib/ui/compile/templateElementAdapter.ts +0 -44
- package/src/lib/ui/compile/templateSemanticTokens.ts +0 -21
- package/src/lib/ui/compile/templateStartOffset.ts +0 -15
- package/src/lib/ui/compile/tryPlan.ts +0 -37
- package/src/lib/ui/compile/types/AbideDiagnostic.ts +0 -14
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +0 -42
- package/src/lib/ui/compile/types/AsyncInterpolationField.ts +0 -19
- package/src/lib/ui/compile/types/Binding.ts +0 -24
- package/src/lib/ui/compile/types/CompiledShadow.ts +0 -27
- package/src/lib/ui/compile/types/ElementPlan.ts +0 -43
- package/src/lib/ui/compile/types/InterpolationClassifier.ts +0 -12
- package/src/lib/ui/compile/types/InterpolationKind.ts +0 -7
- package/src/lib/ui/compile/types/ParseDiagnostic.ts +0 -8
- package/src/lib/ui/compile/types/SeedTypeClassifier.ts +0 -15
- package/src/lib/ui/compile/types/SemanticToken.ts +0 -11
- package/src/lib/ui/compile/types/ShadowKind.ts +0 -9
- package/src/lib/ui/compile/types/ShadowScope.ts +0 -19
- package/src/lib/ui/compile/types/SkeletonContext.ts +0 -21
- package/src/lib/ui/compile/types/TemplateAttr.ts +0 -34
- package/src/lib/ui/compile/types/TemplateNode.ts +0 -137
- package/src/lib/ui/compile/types/TextPart.ts +0 -8
- package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +0 -13
- package/src/lib/ui/compile/unwrapParens.ts +0 -10
- package/src/lib/ui/compile/walkAnchorOrder.ts +0 -57
- package/src/lib/ui/compile/walkElementOrder.ts +0 -60
- package/src/lib/ui/compile/withBindings.ts +0 -45
- package/src/lib/ui/compile/wrapReactionCellSources.ts +0 -121
- package/src/lib/ui/compile/wrapReactiveSeeds.ts +0 -44
- package/src/lib/ui/compile/wrapSeed.ts +0 -28
- package/src/lib/ui/compile/writtenTemplateNames.ts +0 -84
- package/src/lib/ui/computed.ts +0 -75
- package/src/lib/ui/createScope.ts +0 -157
- package/src/lib/ui/dom/MATHML_NAMESPACE.ts +0 -6
- package/src/lib/ui/dom/SVG_NAMESPACE.ts +0 -8
- package/src/lib/ui/dom/anchorCursor.ts +0 -25
- package/src/lib/ui/dom/anchoredBranch.ts +0 -153
- package/src/lib/ui/dom/appendSnippet.ts +0 -75
- package/src/lib/ui/dom/appendStatic.ts +0 -26
- package/src/lib/ui/dom/appendText.ts +0 -160
- package/src/lib/ui/dom/appendTextAt.ts +0 -39
- package/src/lib/ui/dom/assertClaimedText.ts +0 -30
- package/src/lib/ui/dom/attach.ts +0 -25
- package/src/lib/ui/dom/attr.ts +0 -57
- package/src/lib/ui/dom/awaitBlock.ts +0 -314
- package/src/lib/ui/dom/awaitSubject.ts +0 -118
- package/src/lib/ui/dom/bindProp.ts +0 -22
- package/src/lib/ui/dom/bindSelectValue.ts +0 -48
- package/src/lib/ui/dom/bindableProp.ts +0 -47
- package/src/lib/ui/dom/buildDetachedRange.ts +0 -31
- package/src/lib/ui/dom/cellPending.ts +0 -24
- package/src/lib/ui/dom/claimText.ts +0 -38
- package/src/lib/ui/dom/clearBetween.ts +0 -16
- package/src/lib/ui/dom/cloneStatic.ts +0 -38
- package/src/lib/ui/dom/commentData.ts +0 -14
- package/src/lib/ui/dom/depthZeroNodes.ts +0 -34
- package/src/lib/ui/dom/discardAndRebuild.ts +0 -23
- package/src/lib/ui/dom/discardBoundary.ts +0 -46
- package/src/lib/ui/dom/disposeRange.ts +0 -18
- package/src/lib/ui/dom/domAnchorAdapter.ts +0 -29
- package/src/lib/ui/dom/domElementAdapter.ts +0 -20
- package/src/lib/ui/dom/each.ts +0 -253
- package/src/lib/ui/dom/eachAsync.ts +0 -188
- package/src/lib/ui/dom/effectiveChildNamespace.ts +0 -13
- package/src/lib/ui/dom/enterNamespace.ts +0 -20
- package/src/lib/ui/dom/fillBefore.ts +0 -42
- package/src/lib/ui/dom/fillBoundary.ts +0 -38
- package/src/lib/ui/dom/fillRange.ts +0 -30
- package/src/lib/ui/dom/foreignWrapperTag.ts +0 -22
- package/src/lib/ui/dom/hydrate.ts +0 -34
- package/src/lib/ui/dom/inheritedNamespace.ts +0 -19
- package/src/lib/ui/dom/isComment.ts +0 -6
- package/src/lib/ui/dom/isElement.ts +0 -6
- package/src/lib/ui/dom/markerDepthDelta.ts +0 -19
- package/src/lib/ui/dom/matchingRangeClose.ts +0 -29
- package/src/lib/ui/dom/mergeProps.ts +0 -32
- package/src/lib/ui/dom/mount.ts +0 -30
- package/src/lib/ui/dom/mountChild.ts +0 -94
- package/src/lib/ui/dom/mountRange.ts +0 -45
- package/src/lib/ui/dom/mountSlot.ts +0 -38
- package/src/lib/ui/dom/mountSwappableRange.ts +0 -122
- package/src/lib/ui/dom/moveRange.ts +0 -19
- package/src/lib/ui/dom/mutateDocContainer.ts +0 -65
- package/src/lib/ui/dom/on.ts +0 -33
- package/src/lib/ui/dom/openMarker.ts +0 -22
- package/src/lib/ui/dom/outlet.ts +0 -63
- package/src/lib/ui/dom/parseRawNodes.ts +0 -17
- package/src/lib/ui/dom/readCall.ts +0 -29
- package/src/lib/ui/dom/readCell.ts +0 -55
- package/src/lib/ui/dom/readTextOrSuspend.ts +0 -21
- package/src/lib/ui/dom/removeRange.ts +0 -18
- package/src/lib/ui/dom/replaceRange.ts +0 -24
- package/src/lib/ui/dom/restProps.ts +0 -32
- package/src/lib/ui/dom/skeleton.ts +0 -153
- package/src/lib/ui/dom/spreadAttrs.ts +0 -95
- package/src/lib/ui/dom/spreadProps.ts +0 -31
- package/src/lib/ui/dom/switchBlock.ts +0 -80
- package/src/lib/ui/dom/templateFor.ts +0 -28
- package/src/lib/ui/dom/tryBlock.ts +0 -263
- package/src/lib/ui/dom/types/EachRow.ts +0 -21
- package/src/lib/ui/dom/types/SkeletonHoles.ts +0 -10
- package/src/lib/ui/dom/types/SwitchCase.ts +0 -11
- package/src/lib/ui/dom/when.ts +0 -58
- package/src/lib/ui/dom/withScope.ts +0 -37
- package/src/lib/ui/dom/withSuspense.ts +0 -22
- package/src/lib/ui/dom/writeCell.ts +0 -22
- package/src/lib/ui/effect.ts +0 -24
- package/src/lib/ui/enterScope.ts +0 -18
- package/src/lib/ui/exitScope.ts +0 -8
- package/src/lib/ui/finalizeStreamedChildren.ts +0 -89
- package/src/lib/ui/flight.ts +0 -56
- package/src/lib/ui/html.ts +0 -39
- package/src/lib/ui/isolateCellBarrier.ts +0 -17
- package/src/lib/ui/linked.ts +0 -125
- package/src/lib/ui/navigate.ts +0 -77
- package/src/lib/ui/probeNavigation.ts +0 -33
- package/src/lib/ui/props.ts +0 -17
- package/src/lib/ui/remoteProxy.ts +0 -256
- package/src/lib/ui/renderChain.ts +0 -113
- package/src/lib/ui/renderToStream.ts +0 -128
- package/src/lib/ui/resumeSeedScript.ts +0 -27
- package/src/lib/ui/router.ts +0 -649
- package/src/lib/ui/runtime/AsyncCellError.ts +0 -21
- package/src/lib/ui/runtime/BRANCH_SEGMENT.ts +0 -24
- package/src/lib/ui/runtime/CELL_SEED.ts +0 -12
- package/src/lib/ui/runtime/CHILD_PRESENT.ts +0 -8
- package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +0 -11
- package/src/lib/ui/runtime/CURRENT_PATH.ts +0 -29
- package/src/lib/ui/runtime/CURRENT_SCOPE.ts +0 -23
- package/src/lib/ui/runtime/DOC_SEED.ts +0 -10
- package/src/lib/ui/runtime/HOLE_ATTRIBUTE.ts +0 -9
- package/src/lib/ui/runtime/NODE_STATE.ts +0 -22
- package/src/lib/ui/runtime/OUTLET_MARKER.ts +0 -10
- package/src/lib/ui/runtime/OUTLET_TAG.ts +0 -7
- package/src/lib/ui/runtime/OWNER.ts +0 -8
- package/src/lib/ui/runtime/PENDING_OUTLET.ts +0 -8
- package/src/lib/ui/runtime/RANGE_MARKER.ts +0 -16
- package/src/lib/ui/runtime/REACTIVE_CONTEXT.ts +0 -20
- package/src/lib/ui/runtime/RENDER.ts +0 -35
- package/src/lib/ui/runtime/REQUEST_SUPERSEDED.ts +0 -8
- package/src/lib/ui/runtime/RESUME.ts +0 -21
- package/src/lib/ui/runtime/SEED_MARKS.ts +0 -14
- package/src/lib/ui/runtime/STREAMED_CELLS.ts +0 -57
- package/src/lib/ui/runtime/SuspenseSignal.ts +0 -24
- package/src/lib/ui/runtime/abortNode.ts +0 -22
- package/src/lib/ui/runtime/ambientPathBacking.ts +0 -32
- package/src/lib/ui/runtime/ambientScopeBacking.ts +0 -26
- package/src/lib/ui/runtime/applyPatchToTree.ts +0 -54
- package/src/lib/ui/runtime/batch.ts +0 -22
- package/src/lib/ui/runtime/blockId.ts +0 -31
- package/src/lib/ui/runtime/boundaryFor.ts +0 -11
- package/src/lib/ui/runtime/cellBarrierBacking.ts +0 -30
- package/src/lib/ui/runtime/claimChild.ts +0 -8
- package/src/lib/ui/runtime/claimExpected.ts +0 -27
- package/src/lib/ui/runtime/claimMarker.ts +0 -13
- package/src/lib/ui/runtime/claimRun.ts +0 -22
- package/src/lib/ui/runtime/clientPage.ts +0 -116
- package/src/lib/ui/runtime/consumeSeed.ts +0 -38
- package/src/lib/ui/runtime/createAsyncCell.ts +0 -358
- package/src/lib/ui/runtime/createComputedNode.ts +0 -19
- package/src/lib/ui/runtime/createDoc.ts +0 -334
- package/src/lib/ui/runtime/createEffectNode.ts +0 -81
- package/src/lib/ui/runtime/createSignalNode.ts +0 -19
- package/src/lib/ui/runtime/currentAbortSignal.ts +0 -26
- package/src/lib/ui/runtime/detachLink.ts +0 -21
- package/src/lib/ui/runtime/endTracking.ts +0 -24
- package/src/lib/ui/runtime/enterRenderPass.ts +0 -13
- package/src/lib/ui/runtime/escapeKey.ts +0 -19
- package/src/lib/ui/runtime/exitRenderPass.ts +0 -7
- package/src/lib/ui/runtime/flushEffects.ts +0 -91
- package/src/lib/ui/runtime/generationGuard.ts +0 -40
- package/src/lib/ui/runtime/historyEntries.ts +0 -151
- package/src/lib/ui/runtime/inScope.ts +0 -19
- package/src/lib/ui/runtime/isAsyncFunction.ts +0 -14
- package/src/lib/ui/runtime/nextBlockId.ts +0 -12
- package/src/lib/ui/runtime/parkCursor.ts +0 -10
- package/src/lib/ui/runtime/pathSegments.ts +0 -10
- package/src/lib/ui/runtime/reactiveAbortState.ts +0 -15
- package/src/lib/ui/runtime/readNode.ts +0 -38
- package/src/lib/ui/runtime/renderPath.ts +0 -16
- package/src/lib/ui/runtime/reportHydrationDivergence.ts +0 -30
- package/src/lib/ui/runtime/routeBareSeed.ts +0 -28
- package/src/lib/ui/runtime/runHydrationPass.ts +0 -69
- package/src/lib/ui/runtime/runNode.ts +0 -75
- package/src/lib/ui/runtime/runtimePath.ts +0 -9
- package/src/lib/ui/runtime/scope.ts +0 -46
- package/src/lib/ui/runtime/scopeGroup.ts +0 -40
- package/src/lib/ui/runtime/toTeardown.ts +0 -38
- package/src/lib/ui/runtime/track.ts +0 -60
- package/src/lib/ui/runtime/trigger.ts +0 -74
- package/src/lib/ui/runtime/types/AbideHistoryState.ts +0 -9
- package/src/lib/ui/runtime/types/Boundary.ts +0 -9
- package/src/lib/ui/runtime/types/Cell.ts +0 -7
- package/src/lib/ui/runtime/types/Computed.ts +0 -3
- package/src/lib/ui/runtime/types/Doc.ts +0 -24
- package/src/lib/ui/runtime/types/EffectResult.ts +0 -10
- package/src/lib/ui/runtime/types/HydrationCursor.ts +0 -8
- package/src/lib/ui/runtime/types/NavVerdict.ts +0 -9
- package/src/lib/ui/runtime/types/Patch.ts +0 -11
- package/src/lib/ui/runtime/types/PathWalk.ts +0 -10
- package/src/lib/ui/runtime/types/ReactiveLink.ts +0 -21
- package/src/lib/ui/runtime/types/ReactiveNode.ts +0 -33
- package/src/lib/ui/runtime/types/RenderContext.ts +0 -10
- package/src/lib/ui/runtime/types/Route.ts +0 -9
- package/src/lib/ui/runtime/types/RouteLoader.ts +0 -7
- package/src/lib/ui/runtime/types/SsrRender.ts +0 -34
- package/src/lib/ui/runtime/types/State.ts +0 -3
- package/src/lib/ui/runtime/types/Teardown.ts +0 -5
- package/src/lib/ui/runtime/types/UiComponent.ts +0 -24
- package/src/lib/ui/runtime/types/UiProps.ts +0 -12
- package/src/lib/ui/runtime/unescapeKey.ts +0 -12
- package/src/lib/ui/runtime/unlinkDeps.ts +0 -28
- package/src/lib/ui/runtime/untrack.ts +0 -20
- package/src/lib/ui/runtime/updateIfNecessary.ts +0 -40
- package/src/lib/ui/runtime/walkPath.ts +0 -41
- package/src/lib/ui/runtime/withOptionalPath.ts +0 -8
- package/src/lib/ui/runtime/withPath.ts +0 -16
- package/src/lib/ui/runtime/withPathFrom.ts +0 -20
- package/src/lib/ui/runtime/withoutHydration.ts +0 -22
- package/src/lib/ui/runtime/writeNode.ts +0 -16
- package/src/lib/ui/scope.ts +0 -20
- package/src/lib/ui/seedBootState.ts +0 -53
- package/src/lib/ui/seedStreamedResolution.ts +0 -56
- package/src/lib/ui/settleAsyncCells.ts +0 -51
- package/src/lib/ui/socketChannel.ts +0 -156
- package/src/lib/ui/socketProxy.ts +0 -47
- package/src/lib/ui/startClient.ts +0 -156
- package/src/lib/ui/state.ts +0 -77
- package/src/lib/ui/subscribeCacheStaleness.ts +0 -85
- package/src/lib/ui/trackedComputed.ts +0 -68
- package/src/lib/ui/tryEncodeResume.ts +0 -11
- package/src/lib/ui/types/Layouts.ts +0 -9
- package/src/lib/ui/types/Pages.ts +0 -8
- package/src/lib/ui/types/Scope.ts +0 -68
- package/src/lib/ui/watch.ts +0 -150
- package/src/preload.ts +0 -19
- package/src/scaffold.ts +0 -153
- package/src/serverBuildPlugins.ts +0 -25
- package/src/serverEntry.ts +0 -138
- package/src/zodCjsPlugin.ts +0 -51
- package/template/.zed/settings.json +0 -10
- package/template/CLAUDE.md +0 -22
- package/template/bunfig.toml +0 -4
- package/template/package.json +0 -21
- package/template/src/app.ts +0 -28
- package/template/src/bundle/icon.png +0 -0
- package/template/src/cli/banner.txt +0 -3
- package/template/src/cli/footer.txt +0 -1
- package/template/src/server/config.ts +0 -17
- package/template/src/server/rpc/getHello.ts +0 -38
- package/template/src/ui/app.css +0 -21
- package/template/src/ui/app.html +0 -24
- package/template/src/ui/pages/about/page.abide +0 -7
- package/template/src/ui/pages/layout.abide +0 -19
- package/template/src/ui/pages/page.abide +0 -19
- package/template/test/app.test.ts +0 -30
- package/template/tsconfig.json +0 -18
- package/tsconfig.app.json +0 -17
|
@@ -0,0 +1,4480 @@
|
|
|
1
|
+
// THE REACTIVE VALUE. RULEBOOK 1.1: every producer in this design hands back one of
|
|
2
|
+
// these, and after the collapse there is exactly one other result type beside it.
|
|
3
|
+
//
|
|
4
|
+
// The value is a FUNCTION capturing one record, `Cell`, that carries its state, and
|
|
5
|
+
// REACHING its members through one shared prototype. CLAUDE.md, hot paths: prefer a class
|
|
6
|
+
// or a plain record to a closure that captures its enclosing scope. A per-instance closure
|
|
7
|
+
// set would be an allocation and a captured scope per member per value, and a page
|
|
8
|
+
// holds one per row — and copying the shared ones onto each instance instead was 87% of
|
|
9
|
+
// what a value cost to construct. See `MEMBER_PROTOTYPE`.
|
|
10
|
+
//
|
|
11
|
+
// Every field `Cell` declares is written in its constructor, in one order, `undefined`
|
|
12
|
+
// included — a shape that grows a field on a later path is two hidden classes and every
|
|
13
|
+
// read downstream of it deoptimises.
|
|
14
|
+
|
|
15
|
+
import { type Failed, isFailed, isFailedNamed } from '../Failed.ts'
|
|
16
|
+
import { namedLogger } from '../log.ts'
|
|
17
|
+
import { validationError } from '../refuse.ts'
|
|
18
|
+
import { type Found, runSchema, type Schema } from '../Schema.ts'
|
|
19
|
+
import { UNEXPECTED } from '../UNEXPECTED.ts'
|
|
20
|
+
import { WORK } from '../WORK.ts'
|
|
21
|
+
import { CELL } from './CELL.ts'
|
|
22
|
+
import { ENTRY_LIVES } from './ENTRY_LIVES.ts'
|
|
23
|
+
import { armTimer, type Expiry, expiryFor } from './Expiry.ts'
|
|
24
|
+
import { FOLLOWED } from './FOLLOWED.ts'
|
|
25
|
+
import { Cursor, hasCursorBehind, keepPlaces, type Retained } from './Cursor.ts'
|
|
26
|
+
import { isReactive } from './isReactive.ts'
|
|
27
|
+
import { isThenable } from './isThenable.ts'
|
|
28
|
+
import { MUTATION } from './MUTATION.ts'
|
|
29
|
+
import { NO_INITIAL } from './NO_INITIAL.ts'
|
|
30
|
+
import { NotLanded } from './NotLanded.ts'
|
|
31
|
+
import { PATCH_SINKS, type PatchOp } from './PATCH_SINKS.ts'
|
|
32
|
+
import { REACTIVE_BRAND } from './REACTIVE_BRAND.ts'
|
|
33
|
+
import { recordWrites } from './recordWrites.ts'
|
|
34
|
+
import { STREAMING } from './STREAMING.ts'
|
|
35
|
+
import { currentScope, runInScope, type Scope } from './scoping.ts'
|
|
36
|
+
import {
|
|
37
|
+
CHECK,
|
|
38
|
+
CLEAN,
|
|
39
|
+
DIRTY,
|
|
40
|
+
detach,
|
|
41
|
+
detachProbes,
|
|
42
|
+
dropProbeReader,
|
|
43
|
+
newSubscriber,
|
|
44
|
+
RELEASING,
|
|
45
|
+
type Subscriber,
|
|
46
|
+
TRACKING,
|
|
47
|
+
type Tracked,
|
|
48
|
+
track,
|
|
49
|
+
trackProbe,
|
|
50
|
+
truncate,
|
|
51
|
+
untracked,
|
|
52
|
+
} from './tracking.ts'
|
|
53
|
+
import { owned, owning, watchOver } from './watch.ts'
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The shape of a `transform`: the accepted value in, the stored value or a refusal out.
|
|
57
|
+
*
|
|
58
|
+
* Named because the registry names it and because 4.6 is written over it: the `Failures` arm of
|
|
59
|
+
* the return is the refusal, not an error channel beside it.
|
|
60
|
+
*/
|
|
61
|
+
export type Transformer<Accepted, Stored, Failures = never> = (
|
|
62
|
+
value: Accepted,
|
|
63
|
+
) => Stored | Failures
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Where a value is persisted: a `get` to restore it and a `set` to write it.
|
|
67
|
+
*
|
|
68
|
+
* Both halves, and no way to spell either alone (8.1). `get` answering `undefined` is a miss
|
|
69
|
+
* (8.17), so the miss is in `get`'s type rather than in `Stored`: a store over a value with a body
|
|
70
|
+
* is typed by what the body produces, and a `Store<T | undefined>` there could not be written.
|
|
71
|
+
*/
|
|
72
|
+
export type Store<Stored> = {
|
|
73
|
+
get: () => Stored | undefined | Promise<Stored | undefined>
|
|
74
|
+
set: (value: Stored, retention: { ttl: number }) => void | Promise<void>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* What `identity` accepts: a one-parameter projection or a two-parameter comparator.
|
|
79
|
+
*
|
|
80
|
+
* ONE SIGNATURE, NOT A UNION OF TWO. A union of signatures with different arities gives an arrow no
|
|
81
|
+
* contextual type, so `identity: (turn) => turn.text` read its parameter as `any`. A projection
|
|
82
|
+
* declares one parameter and is still assignable here; 5.3 tells the two apart by that arity.
|
|
83
|
+
*/
|
|
84
|
+
export type Identity<Stored> = (next: Stored, previous: Stored) => unknown
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The options a `Reactive` is built with: its gates, retention, caps, store and tags.
|
|
88
|
+
*
|
|
89
|
+
* `Failures` IS INFERRED FROM `transform`, which is what makes `s.error` and `s.isError`
|
|
90
|
+
* typed rather than `undefined`. 4.6 has a transform refuse by RETURNING, so its return
|
|
91
|
+
* type is the one place a value's failures are declared — naming them a second time on the
|
|
92
|
+
* factory would be a declaration that can disagree with the code.
|
|
93
|
+
*/
|
|
94
|
+
export type ReactiveOptions<
|
|
95
|
+
Accepted,
|
|
96
|
+
Stored,
|
|
97
|
+
// CONSTRAINED TO `Failed`, and that is the registry's own row — "union of `Failed`" —
|
|
98
|
+
// rather than a convenience. Unconstrained, inference against `Stored | Failures`
|
|
99
|
+
// takes the WHOLE return type into `Failures`, so `s.error()` came back typed
|
|
100
|
+
// `number | Failed<…>` and `.data` did not exist on it. The bound is what tells the
|
|
101
|
+
// two halves of that union apart.
|
|
102
|
+
Failures extends Failed<string, unknown> = never,
|
|
103
|
+
// `never` on every overload but the keyed ones, which is what makes `args` unspellable
|
|
104
|
+
// on the unkeyed form (11.41): its field type collapses to `never` there.
|
|
105
|
+
Args = never,
|
|
106
|
+
> = {
|
|
107
|
+
/**
|
|
108
|
+
* The schema gating a keyed value's args.
|
|
109
|
+
*
|
|
110
|
+
* RULEBOOK 11.40, 11.28, 11.68: the gate on a keyed value's args — what it returns is what the
|
|
111
|
+
* body is handed, and the key was taken before it ran (11.26).
|
|
112
|
+
*/
|
|
113
|
+
args?: [Args] extends [never] ? never : Schema<Args>
|
|
114
|
+
/**
|
|
115
|
+
* What makes a gated write a repeat: a projection or a comparator.
|
|
116
|
+
*
|
|
117
|
+
* RULEBOOK 5.3: discriminated by arity — one parameter is a projection compared by
|
|
118
|
+
* `!==`, two is a comparator answering directly.
|
|
119
|
+
*/
|
|
120
|
+
identity?: Identity<Stored>
|
|
121
|
+
/**
|
|
122
|
+
* The life of a production, in milliseconds.
|
|
123
|
+
*
|
|
124
|
+
* RULEBOOK 6.1 through 6.6. Inert on a value with no producer (6.2), which is decided
|
|
125
|
+
* once — see `expiryFor`.
|
|
126
|
+
*/
|
|
127
|
+
ttl?: number
|
|
128
|
+
/**
|
|
129
|
+
* The gate on the way in, run before `transform` on every way in.
|
|
130
|
+
*
|
|
131
|
+
* RULEBOOK 4.3: THE GATE ON THE WAY IN, and it runs FIRST — before `transform`, on
|
|
132
|
+
* the settled value, on every way in alike: a write, what a body produced (11.46) and
|
|
133
|
+
* what an adoption mirrors (11.47).
|
|
134
|
+
*/
|
|
135
|
+
schema?: Schema<Accepted>
|
|
136
|
+
/**
|
|
137
|
+
* Shapes a settled value on the way in, or refuses it by returning a `Failed`.
|
|
138
|
+
*
|
|
139
|
+
* RULEBOOK 4.6, 4.7: runs untracked, on the settled value, once per materialisation,
|
|
140
|
+
* and refuses by RETURNING a `Failed` rather than by throwing. A throw here would be
|
|
141
|
+
* indistinguishable from the body having failed, and 4.9 has a refused write mint
|
|
142
|
+
* nothing where a failed producer (2.3) may still have something to say.
|
|
143
|
+
*/
|
|
144
|
+
transform?: Transformer<Accepted, Stored, Failures>
|
|
145
|
+
/**
|
|
146
|
+
* Wakes a reader at once, then at most once per window.
|
|
147
|
+
*
|
|
148
|
+
* RULEBOOK 5.8, 5.12: a change reaches a reader immediately and then at most once per
|
|
149
|
+
* window. Declaring this beside `debounce` is refused at construction (5.14).
|
|
150
|
+
*/
|
|
151
|
+
throttle?: number
|
|
152
|
+
/** Wakes a reader once the changes stop (RULEBOOK 5.8, 5.13). */
|
|
153
|
+
debounce?: number
|
|
154
|
+
/**
|
|
155
|
+
* What a `Selection` matches on; the function form is handed the entry's args.
|
|
156
|
+
*
|
|
157
|
+
* RULEBOOK 11.43, 11.44: what a `Selection` matches on, and the one selector that reaches
|
|
158
|
+
* across values. The function form is handed the entry's args, and `undefined` on a value
|
|
159
|
+
* that has none — which is the only value it is ever `undefined` on.
|
|
160
|
+
*/
|
|
161
|
+
tags?: string[] | ((args: ArgsOf<Args>) => string[])
|
|
162
|
+
/**
|
|
163
|
+
* Where the value lives when the process does not.
|
|
164
|
+
*
|
|
165
|
+
* RULEBOOK 8, 9.5, 11.42: where the value lives when the process does not. The function form
|
|
166
|
+
* is handed the entry's args, so each key of a `Keyed` has a store of its own.
|
|
167
|
+
*/
|
|
168
|
+
store?: Store<Stored> | ((args: ArgsOf<Args>) => Store<Stored>)
|
|
169
|
+
/**
|
|
170
|
+
* One value for the whole process rather than one per request.
|
|
171
|
+
*
|
|
172
|
+
* RULEBOOK 11.32, 11.34: one value for the whole process rather than one per request. Inert in a
|
|
173
|
+
* browser, where the process IS the scope.
|
|
174
|
+
*/
|
|
175
|
+
global?: boolean
|
|
176
|
+
/**
|
|
177
|
+
* What tells one array element from another.
|
|
178
|
+
*
|
|
179
|
+
* RULEBOOK 5.22, 32.32: what tells one element from another. The property-name form is the one
|
|
180
|
+
* that crosses a wire (18.4) — a function has no serialisation.
|
|
181
|
+
*/
|
|
182
|
+
by?:
|
|
183
|
+
| (keyof ElementOf<Stored> & string)
|
|
184
|
+
| ((element: ElementOf<Stored>) => unknown)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* What a function-form option is handed: the entry's args, or `undefined` on a value whose body
|
|
189
|
+
* takes none (11.44).
|
|
190
|
+
*/
|
|
191
|
+
type ArgsOf<Args> = [Args] extends [never] ? undefined : Args
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* `unknown` on a value that is not an array, where `by` has no element to key and names no property.
|
|
195
|
+
*/
|
|
196
|
+
type ElementOf<Stored> =
|
|
197
|
+
NonNullable<Stored> extends readonly (infer Element)[] ? Element : unknown
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The reactive value: a callable read, plus its probes, writes, triggers and cursors.
|
|
201
|
+
*
|
|
202
|
+
* `Produced` is the fourth parameter and RULEBOOK 1.2 is what it means: the unit the
|
|
203
|
+
* producer yielded, which is `Stored` for every producer but a stream, and one chunk for a
|
|
204
|
+
* streaming producer (9.1). It defaults to `Stored`, so the parameter is invisible to
|
|
205
|
+
* everything that is not a stream.
|
|
206
|
+
*/
|
|
207
|
+
export type Reactive<
|
|
208
|
+
Stored,
|
|
209
|
+
Accepted = Stored,
|
|
210
|
+
Failures = never,
|
|
211
|
+
Produced = Stored,
|
|
212
|
+
> = {
|
|
213
|
+
/**
|
|
214
|
+
* Reads the value; throws where nothing has landed.
|
|
215
|
+
*
|
|
216
|
+
* RULEBOOK 2.2: throws where nothing has landed. Move 7 is what makes this `Stored`
|
|
217
|
+
* rather than `Stored | undefined` — the absence moved to `peek`.
|
|
218
|
+
*/
|
|
219
|
+
(): Stored
|
|
220
|
+
peek(): Stored | undefined
|
|
221
|
+
busy(): boolean
|
|
222
|
+
ready(): boolean
|
|
223
|
+
error(): Failures | undefined
|
|
224
|
+
/**
|
|
225
|
+
* Narrows a caught error to this value's failure of that name.
|
|
226
|
+
*
|
|
227
|
+
* 3.1: the narrowing, and it takes the error rather than reading one — `s.error` is
|
|
228
|
+
* the probe, this is the type guard over whatever was caught.
|
|
229
|
+
*/
|
|
230
|
+
isError<Name extends string>(
|
|
231
|
+
error: unknown,
|
|
232
|
+
name: Name,
|
|
233
|
+
): error is Extract<Failures, { name: Name }>
|
|
234
|
+
/**
|
|
235
|
+
* Writes a value, gated by `identity`; returns the refusal where a gate refused it.
|
|
236
|
+
*
|
|
237
|
+
* Gated by `identity`, and local. The divergence that once split by PRODUCER
|
|
238
|
+
* splits by MEMBER instead. Hands back the refusal where a gate refused the write (4.18).
|
|
239
|
+
* A `Reactive` IS IN THE UNION because 11.61 adopts one rather than storing it, and it is
|
|
240
|
+
* spelled out rather than left to `PromiseLike`: a `Reactive` satisfies that (1.4), and
|
|
241
|
+
* taking it through that arm is exactly the settle-to-a-snapshot 1.6 refuses. Its failures are
|
|
242
|
+
* this value's, since the adopted value's refusal becomes this one's: an `Rpc` call carries
|
|
243
|
+
* `HttpError` (16.8), so a value read through one is written through another.
|
|
244
|
+
*/
|
|
245
|
+
set(
|
|
246
|
+
value:
|
|
247
|
+
| Accepted
|
|
248
|
+
| PromiseLike<Accepted>
|
|
249
|
+
| Reactive<Accepted, unknown, Failures, unknown>,
|
|
250
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: same reason as `watch` below — a `set` that returns nothing infers `void`, and 4.18's refusal is the other arm.
|
|
251
|
+
): void | Failures
|
|
252
|
+
patch(mutate: (value: Stored) => void): void
|
|
253
|
+
refresh(): void
|
|
254
|
+
invalidate(): void
|
|
255
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: `void` is the RETURN POSITION of an author's effect, and it is load-bearing. `undefined` there refuses `(v) => { read(v) }` — a block body returning nothing infers `void`, which is not assignable to `undefined` — so the rule's fix would reject the ordinary effect.
|
|
256
|
+
watch(effect: (value: Stored) => void | (() => void)): () => void
|
|
257
|
+
/** The production in flight, and then what follows it (2.5). */
|
|
258
|
+
[Symbol.asyncIterator](): AsyncIterator<Produced>
|
|
259
|
+
then<Result>(
|
|
260
|
+
onSettled?: (value: Stored) => Result | PromiseLike<Result>,
|
|
261
|
+
onFailed?: (error: unknown) => Result | PromiseLike<Result>,
|
|
262
|
+
): Promise<Result>
|
|
263
|
+
catch<Result>(
|
|
264
|
+
onFailed: (error: unknown) => Result | PromiseLike<Result>,
|
|
265
|
+
): Promise<Stored | Result>
|
|
266
|
+
finally(onSettled: () => void): Promise<Stored>
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** The standing adoption: what is adopted, and the way to stop mirroring it. */
|
|
270
|
+
type Adoption = { source: Cell; stop: () => void }
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The window a `throttle` or a `debounce` holds open, allocated only where one is declared.
|
|
274
|
+
*
|
|
275
|
+
* THE WINDOW A `throttle` OR A `debounce` HOLDS OPEN (RULEBOOK 5.8). One field on the
|
|
276
|
+
* cell rather than one per window field, allocated only where a value declared one — the
|
|
277
|
+
* shape `adoption` already uses, and for the same reason.
|
|
278
|
+
*
|
|
279
|
+
* WHAT A WINDOW CAPS IS THE WAKE, NEVER THE PRODUCTION (5.9). Every write still mints and
|
|
280
|
+
* the ordinal moves. What waits is the value a READER sees and the cascade that tells it so,
|
|
281
|
+
* which is the whole of 5.8's "how often a value changes for a reader".
|
|
282
|
+
*
|
|
283
|
+
* WHAT IT COSTS EVERY VALUE THAT NEVER CAPS: one field load and one branch on the write
|
|
284
|
+
* path, plus `serve` as a frame rather than five stores inline. Measured at 0.99x -> 1.08x
|
|
285
|
+
* against the hand-written arm on the since-removed `publish into a capped ring` case, consistent across three
|
|
286
|
+
* runs over a 0.7% floor — about 1.3 ns on a 14.7 ns op. Moving the comparison target
|
|
287
|
+
* behind the gate did not recover it, so it is the frame and the branch rather than a
|
|
288
|
+
* wasted load. Inlining `serve` into the uncapped arm would buy it back for a second copy
|
|
289
|
+
* of the reader-visible transition, and this file has already paid for one of those: the
|
|
290
|
+
* trade is stated rather than taken, 1.3 ns being nowhere near perception.
|
|
291
|
+
*/
|
|
292
|
+
type Cap = {
|
|
293
|
+
/**
|
|
294
|
+
* True for a throttle, which lets the leading change through; false for a debounce.
|
|
295
|
+
*
|
|
296
|
+
* 5.12 against 5.13, and the only difference between the two mechanisms: a throttle
|
|
297
|
+
* lets the leading change through and holds what follows, a debounce holds everything
|
|
298
|
+
* until the changes stop.
|
|
299
|
+
*/
|
|
300
|
+
leading: boolean
|
|
301
|
+
ms: number
|
|
302
|
+
timer: ReturnType<typeof setTimeout> | null
|
|
303
|
+
/**
|
|
304
|
+
* The production the window is holding back, while `holding` is set.
|
|
305
|
+
*
|
|
306
|
+
* The production the window is holding back. `holding` is a field rather than a test
|
|
307
|
+
* against `held`, because `undefined` is a value an app can write and so cannot carry
|
|
308
|
+
* the absence.
|
|
309
|
+
*/
|
|
310
|
+
held: unknown
|
|
311
|
+
holding: boolean
|
|
312
|
+
/**
|
|
313
|
+
* The last production minted, which the identity gate compares against.
|
|
314
|
+
*
|
|
315
|
+
* WHAT THE IDENTITY GATE COMPARES AGAINST. On every uncapped value that is `cell.value`
|
|
316
|
+
* and the two are one thing; a window moves the last production MINTED apart from the
|
|
317
|
+
* last one RELEASED, and comparing against the released one would collapse a production
|
|
318
|
+
* 5.9 forbids dropping — A, B, A inside one window is three productions, and only the
|
|
319
|
+
* third's own predecessor makes it a repeat.
|
|
320
|
+
*/
|
|
321
|
+
minted: unknown
|
|
322
|
+
/**
|
|
323
|
+
* Whether the window is holding back a body run.
|
|
324
|
+
*
|
|
325
|
+
* A BODY RUN THE WINDOW IS HOLDING BACK (5.11), against `holding`'s production. The two
|
|
326
|
+
* are separate flags because they can stand together: a stream has a body AND tells its
|
|
327
|
+
* chunks through the window (5.17), so one close can owe a run and a telling at once.
|
|
328
|
+
*/
|
|
329
|
+
owed: boolean
|
|
330
|
+
/**
|
|
331
|
+
* Whether the window is holding back a refresh, started when it closes.
|
|
332
|
+
*
|
|
333
|
+
* 5.24 — A RELOAD THE WINDOW IS HOLDING BACK: a refresh made inside it, started when it closes.
|
|
334
|
+
* Started there rather than owed like a run, because 7.11 reloads whether or not anything reads
|
|
335
|
+
* and an owed run waits for a read to pull it.
|
|
336
|
+
*/
|
|
337
|
+
reloading: boolean
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* A value's store and the state of its writes, allocated only where a `store` was declared.
|
|
342
|
+
*
|
|
343
|
+
* A VALUE'S STORE AND WHAT ITS WRITES ARE DOING, allocated only where a `store` was declared.
|
|
344
|
+
*
|
|
345
|
+
* 8.10: ONE WRITE IN FLIGHT AND ONE PENDING. A production arriving while a write is out replaces
|
|
346
|
+
* the pending value rather than queueing behind it: a slow store handed every intermediate is doing
|
|
347
|
+
* work nobody can observe, and a queue is how it grows without bound behind a fast writer.
|
|
348
|
+
*/
|
|
349
|
+
type Persisted = {
|
|
350
|
+
store: Store<unknown>
|
|
351
|
+
/** The value's own `ttl`, taken once, and infinity where it declares none (8.13). */
|
|
352
|
+
ttl: number
|
|
353
|
+
/**
|
|
354
|
+
* Set while a restore mints its production, so what was just read is not written back (8.8).
|
|
355
|
+
*/
|
|
356
|
+
restoring: boolean
|
|
357
|
+
writing: boolean
|
|
358
|
+
pending: boolean
|
|
359
|
+
/**
|
|
360
|
+
* The newest value handed to the store and not yet confirmed.
|
|
361
|
+
*
|
|
362
|
+
* THE NEWEST VALUE THE APP HAS HANDED THE STORE, standing until the store confirms it. It is
|
|
363
|
+
* what `pending` sends next, and what 8.18 serves a re-read in place of calling `get`.
|
|
364
|
+
*/
|
|
365
|
+
unconfirmed: unknown
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* A streaming producer's own state; `null` on every value whose body is not an async generator.
|
|
370
|
+
*
|
|
371
|
+
* A STREAMING PRODUCER'S OWN STATE (9.1, 9.2, 9.6), and `null` on every value whose body is
|
|
372
|
+
* not an async generator — the shape `cap` and `adoption` already use.
|
|
373
|
+
*
|
|
374
|
+
* THE ACCUMULATION IS PUSHED INTO AND MATERIALISED ON THE READ. A chunk is one push and one
|
|
375
|
+
* cascade; the copy a reader is handed is taken by the read that follows, so a hundred
|
|
376
|
+
* chunks between two reads are one copy rather than a hundred (9.2). The copy is FRESH per
|
|
377
|
+
* chunk count and has to be: a reader holding an earlier snapshot still holds what it read,
|
|
378
|
+
* and a value derived from this one compares by reference — the live buffer handed out would
|
|
379
|
+
* be collapsed at every derived identity gate after the first chunk.
|
|
380
|
+
*
|
|
381
|
+
* WHAT THAT COSTS WHERE A READ FOLLOWS EVERY CHUNK is a copy of the accumulation per read,
|
|
382
|
+
* and a reader woken per chunk made that a copy per chunk: twenty thousand chunks landing in
|
|
383
|
+
* one task took 61 ms against 1.9 ms for the hand-written arm. 9.9 is what bounds it — the
|
|
384
|
+
* wake is held to one per task, which is the unit a browser paints between, so the reads it
|
|
385
|
+
* drops were never visible. A reader that reads per chunk on its own, unwoken, is still a
|
|
386
|
+
* copy per read.
|
|
387
|
+
*/
|
|
388
|
+
type Stream = {
|
|
389
|
+
/**
|
|
390
|
+
* The accumulation of the run being served — and the same array as `ring`, so a cursor
|
|
391
|
+
* walks the chunks this holds rather than a second copy of them.
|
|
392
|
+
*/
|
|
393
|
+
chunks: unknown[]
|
|
394
|
+
/** The accumulation's version: moves on every release, patch and new run. */
|
|
395
|
+
version: number
|
|
396
|
+
/**
|
|
397
|
+
* Which accumulation `value` stands for, as a `version` rather than a chunk count.
|
|
398
|
+
*
|
|
399
|
+
* A version rather than a chunk count because a patch changes the accumulation without
|
|
400
|
+
* changing its length (9.13), and a write stands at the version it was made at until the
|
|
401
|
+
* accumulation moves past it (9.11). -1 where nothing has been materialised.
|
|
402
|
+
*/
|
|
403
|
+
materialisedAt: number
|
|
404
|
+
/**
|
|
405
|
+
* Whether `value` is a write rather than a materialisation.
|
|
406
|
+
*
|
|
407
|
+
* WHETHER `value` IS A WRITE rather than a materialisation, while `materialisedAt` stands. A value
|
|
408
|
+
* forwarding this stream serves the write the way it would have served a production (11.59).
|
|
409
|
+
*/
|
|
410
|
+
written: boolean
|
|
411
|
+
/**
|
|
412
|
+
* How many chunks a reader has been told of.
|
|
413
|
+
*
|
|
414
|
+
* HOW MANY CHUNKS A READER HAS BEEN TOLD OF. Every chunk as it lands, without a cap; with one,
|
|
415
|
+
* only what the window has released (5.18) — the rest are in `chunks` for a cursor (5.10) and
|
|
416
|
+
* out of what a read materialises until the release.
|
|
417
|
+
*/
|
|
418
|
+
visible: number
|
|
419
|
+
/** The run in flight, told to return when a newer run supersedes it. */
|
|
420
|
+
iterator: AsyncIterator<unknown> | null
|
|
421
|
+
/**
|
|
422
|
+
* Which run is current; only a new run moves it.
|
|
423
|
+
*
|
|
424
|
+
* WHICH RUN IS CURRENT, and only a new run moves it. Apart from `generation`, which every
|
|
425
|
+
* write moves so that a load it overtook lands nowhere — a write over a stream stands beside
|
|
426
|
+
* the stream (9.11) rather than superseding it, and sharing the counter stopped the stream.
|
|
427
|
+
*/
|
|
428
|
+
run: number
|
|
429
|
+
/**
|
|
430
|
+
* Whether a wake is already held for this task, so the next chunk only pushes.
|
|
431
|
+
*
|
|
432
|
+
* 9.9: chunks have landed that the readers have not been told about, and the value is
|
|
433
|
+
* already in `WAKING` — so the next chunk in this task pushes and does nothing else.
|
|
434
|
+
*/
|
|
435
|
+
waking: boolean
|
|
436
|
+
/**
|
|
437
|
+
* A probe moved under the held wake — the first chunk making the value ready, or one
|
|
438
|
+
* clearing a standing error — and the wake owes it to the guards as well.
|
|
439
|
+
*/
|
|
440
|
+
probesOwed: boolean
|
|
441
|
+
/**
|
|
442
|
+
* The stream this one forwards, and `null` on a value that produces its own.
|
|
443
|
+
*
|
|
444
|
+
* THE STREAM THIS ONE FORWARDS (11.15), and `null` on a value that produces its own. A
|
|
445
|
+
* value adopting a stream IS that stream to a cursor, a guard and an await, so it holds
|
|
446
|
+
* the adopted value's buffer and ring rather than copies — and this is what stops a
|
|
447
|
+
* trigger on it starting its body as though the body were the generator.
|
|
448
|
+
*/
|
|
449
|
+
source: Cell | null
|
|
450
|
+
/**
|
|
451
|
+
* How far the forwarding value's ordinals run ahead of the adopted value's, fixed when
|
|
452
|
+
* the forwarding begins so the adopter's cursor positions go on from its own.
|
|
453
|
+
*/
|
|
454
|
+
ordinalOffset: number
|
|
455
|
+
/**
|
|
456
|
+
* Whether what is served is a run that closed.
|
|
457
|
+
*
|
|
458
|
+
* WHETHER WHAT IS SERVED IS A RUN THAT CLOSED, which is the only accumulation a deadline
|
|
459
|
+
* reaches (6.11). A run in flight has minted nothing, so `expiry.at` says nothing about it —
|
|
460
|
+
* without this the standing arm reads `at` as 0 and drops the value on its first read.
|
|
461
|
+
*/
|
|
462
|
+
closed: boolean
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function newStream(source: Cell | null): Stream {
|
|
466
|
+
return {
|
|
467
|
+
chunks: EMPTY_RING,
|
|
468
|
+
version: 0,
|
|
469
|
+
visible: 0,
|
|
470
|
+
materialisedAt: -1,
|
|
471
|
+
written: false,
|
|
472
|
+
iterator: null,
|
|
473
|
+
run: 0,
|
|
474
|
+
waking: false,
|
|
475
|
+
probesOwed: false,
|
|
476
|
+
source,
|
|
477
|
+
ordinalOffset: 0,
|
|
478
|
+
closed: false,
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** Turns `cell` into a streaming producer, forwarding `source`'s stream when it has one. */
|
|
483
|
+
function becomeStream(cell: Cell, source: Cell | null): Stream {
|
|
484
|
+
const stream = newStream(source)
|
|
485
|
+
cell.stream = stream
|
|
486
|
+
cell.plain = false
|
|
487
|
+
cell.bareWrite = false
|
|
488
|
+
simpleShape(cell)
|
|
489
|
+
cell.direct = false
|
|
490
|
+
return stream
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Starts a closed run's clock: the close is a streaming producer's mint (6.11), as `produce` is
|
|
495
|
+
* every other producer's. A failed or superseded run never reaches here, so it starts no clock.
|
|
496
|
+
*/
|
|
497
|
+
function closeRun(cell: Cell, stream: Stream): void {
|
|
498
|
+
// Materialised here so 9.8's gates run on the accumulation the run closed with.
|
|
499
|
+
if (heldValue(cell) === undefined) return
|
|
500
|
+
stream.closed = true
|
|
501
|
+
const expiry = cell.expiry
|
|
502
|
+
if (expiry !== null) {
|
|
503
|
+
expiry.at = Date.now()
|
|
504
|
+
// A closed run replaces what 6.7 released, so the value is no longer waiting for one.
|
|
505
|
+
expiry.dropped = false
|
|
506
|
+
armNext(cell, expiry)
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* The prototype every `async function*` has, and so the declaration 9.6 reads at construction.
|
|
512
|
+
* Taken from a literal because the constructor has no global name.
|
|
513
|
+
*/
|
|
514
|
+
const ASYNC_GENERATOR_FUNCTION = Object.getPrototypeOf(async function* () {})
|
|
515
|
+
|
|
516
|
+
/** The prototype of every async generator function's own `prototype`, which its generators have. */
|
|
517
|
+
const ASYNC_GENERATOR = ASYNC_GENERATOR_FUNCTION.prototype
|
|
518
|
+
|
|
519
|
+
/** Whether a run handed back an async generator, which 9.6 makes the run's stream. */
|
|
520
|
+
function isAsyncGenerator(value: unknown): value is AsyncGenerator {
|
|
521
|
+
if (typeof value !== 'object' || value === null) return false
|
|
522
|
+
const own = Object.getPrototypeOf(value)
|
|
523
|
+
return own !== null && Object.getPrototypeOf(own) === ASYNC_GENERATOR
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Whether a body is an async generator function, and so a streaming producer.
|
|
528
|
+
*
|
|
529
|
+
* Exported for what wraps a body and has to keep its kind — `args` in `Keyed.ts`.
|
|
530
|
+
*/
|
|
531
|
+
export function isStreamBody(body: (args: never) => unknown): boolean {
|
|
532
|
+
return Object.getPrototypeOf(body) === ASYNC_GENERATOR_FUNCTION
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* A value's internal state record, apart from the callable an app holds.
|
|
537
|
+
*
|
|
538
|
+
* THE INTERNAL FACE, AND A SECOND OBJECT. With the fields on the callable itself, every
|
|
539
|
+
* one was a property added to a FUNCTION at construction, and that was most of what a
|
|
540
|
+
* value cost to make: moving them here took `construct a value` from 123 ns to 87 under
|
|
541
|
+
* JSC and from 101 to 83 under V8, and a derived value's construction, a chain and a
|
|
542
|
+
* layered DAG 7-13% with it. Nothing measured moved the other way. The callable captures
|
|
543
|
+
* its record, so a read is one context load — the hand-written arm's one captured
|
|
544
|
+
* variable — and a member reaches the record through `CELL`, one load ahead of what it
|
|
545
|
+
* did before.
|
|
546
|
+
*
|
|
547
|
+
* THE FIELD COUNT SITS ON A VM BOUNDARY AND IT IS NOT A LEVER. Both engines give an
|
|
548
|
+
* object a fixed inline slot budget and spill the rest to an out-of-line store, and this
|
|
549
|
+
* record is over it — so the count moves numbers that have nothing to do with the work.
|
|
550
|
+
* Two independent measurements say the same thing from opposite ends. Taking `Cell` to
|
|
551
|
+
* 38 is worth ~11% of `construct a value` and ~6-9% of the derived case, and it does not
|
|
552
|
+
* matter HOW the four go: a consolidation into one record and four fields simply deleted
|
|
553
|
+
* buy the same number, so the consolidation is not what pays. Going the other way,
|
|
554
|
+
* 42 -> 46, is FREE — above the budget the out-of-line store already exists. And dropping
|
|
555
|
+
* ONE field moved chain 20 and both layered DAGs +4.2% under V8 while moving nothing
|
|
556
|
+
* under JSC; adding a DEAD boolean back put all three within 0.6%, which is the proof it
|
|
557
|
+
* was layout and not work — that same dead boolean then cost the probe walk 6-7%.
|
|
558
|
+
*
|
|
559
|
+
* So the boundary is worth KNOWING and not worth engineering around. Adding a field
|
|
560
|
+
* costs nothing; getting under costs a structural change that the next field added here
|
|
561
|
+
* silently undoes, and it re-rolls in a different direction for every row. If
|
|
562
|
+
* construction ever needs that 11%, shed fields — do not build machinery to shed them.
|
|
563
|
+
* And when a V8-only move of this size shows up on a graph row, the first thing to check
|
|
564
|
+
* is whether the field count changed.
|
|
565
|
+
*/
|
|
566
|
+
class Cell {
|
|
567
|
+
/**
|
|
568
|
+
* Who reads this value, on the value edge.
|
|
569
|
+
*
|
|
570
|
+
* ARRAYS, not sets. The mark walk walks this per write and a set's iterator
|
|
571
|
+
* allocates; removal is O(n) against a reader count that is small by construction.
|
|
572
|
+
*/
|
|
573
|
+
declare readers: Subscriber[]
|
|
574
|
+
/**
|
|
575
|
+
* Who asked about this value's PROBES — a different edge from `readers`, and see
|
|
576
|
+
* `markProbes` for what that buys.
|
|
577
|
+
*/
|
|
578
|
+
declare probeReaders: Subscriber[]
|
|
579
|
+
declare sources: Cell[]
|
|
580
|
+
/**
|
|
581
|
+
* The values whose PROBES this body asked about — a `Cell` is a `Subscriber`, so it
|
|
582
|
+
* carries the reader half of the probe edge as well as the source half.
|
|
583
|
+
*/
|
|
584
|
+
declare probeSources: Cell[]
|
|
585
|
+
declare cursor: number
|
|
586
|
+
/** See `Subscriber.seen`. */
|
|
587
|
+
declare seen: Set<Cell> | null
|
|
588
|
+
declare seenThrough: number
|
|
589
|
+
declare scanningFrom: number
|
|
590
|
+
declare label: string
|
|
591
|
+
declare body: ((args: unknown) => unknown) | null
|
|
592
|
+
declare args: unknown
|
|
593
|
+
declare value: unknown
|
|
594
|
+
declare landed: boolean
|
|
595
|
+
/**
|
|
596
|
+
* Whether a read may answer with `value`.
|
|
597
|
+
*
|
|
598
|
+
* WHETHER A READ MAY ANSWER WITH `value`. Implies `landed`, and is false over a landed value
|
|
599
|
+
* a trigger, an abort or an adoption has stopped serving — which is the state `landed`
|
|
600
|
+
* alone cannot say.
|
|
601
|
+
*/
|
|
602
|
+
declare servable: boolean
|
|
603
|
+
/**
|
|
604
|
+
* Whether a read can return `value` straight, without going a frame in.
|
|
605
|
+
*
|
|
606
|
+
* `state === CLEAN && servable` — the whole of "this read can return the field",
|
|
607
|
+
* maintained rather than recomputed. A read is the hottest path in the design and it
|
|
608
|
+
* was three loads and three branches to decide.
|
|
609
|
+
*
|
|
610
|
+
* THE INVARIANT IS ONE-DIRECTIONAL and that is what makes it safe to maintain by
|
|
611
|
+
* hand: `true` is written only where all three are known to hold, and every
|
|
612
|
+
* transition that can break one of them writes `false`. So a site that forgets to
|
|
613
|
+
* set it true costs a frame on the reads that follow and cannot serve a wrong value,
|
|
614
|
+
* which is the opposite of how a maintained flag usually fails.
|
|
615
|
+
*/
|
|
616
|
+
declare direct: boolean
|
|
617
|
+
declare loading: boolean
|
|
618
|
+
declare failure: unknown
|
|
619
|
+
/**
|
|
620
|
+
* The compiled `identity` comparator.
|
|
621
|
+
*
|
|
622
|
+
* `null` IS the default — see `comparatorFor`.
|
|
623
|
+
*/
|
|
624
|
+
declare identityFn: ((next: unknown, previous: unknown) => boolean) | null
|
|
625
|
+
/**
|
|
626
|
+
* The comparator this value DECLARED, which `identityFn` returns to when an adoption ends.
|
|
627
|
+
*
|
|
628
|
+
* 11.17 inherits the adopted value's `identity` only where the outer declared none, and only
|
|
629
|
+
* for as long as it adopts: kept on `identityFn` alone, the inherited comparator outlived the
|
|
630
|
+
* adoption and gated the outer's own productions — `flag() ? inner : count()` with `inner`
|
|
631
|
+
* keyed on `.id` read every number as the same `undefined` and never woke again.
|
|
632
|
+
*/
|
|
633
|
+
declare declaredIdentityFn:
|
|
634
|
+
| ((next: unknown, previous: unknown) => boolean)
|
|
635
|
+
| null
|
|
636
|
+
declare transform: ((value: unknown) => unknown) | null
|
|
637
|
+
/**
|
|
638
|
+
* `null` on every value that declares no `schema`, which keeps the gate off the write
|
|
639
|
+
* path of everything that did not ask for one.
|
|
640
|
+
*/
|
|
641
|
+
declare schema: Schema<unknown> | null
|
|
642
|
+
/**
|
|
643
|
+
* A streaming producer's chunk buffer, for a cursor to walk (2.5); `EMPTY_RING` on every
|
|
644
|
+
* other value, whose cursor answers the standing production from `value`.
|
|
645
|
+
*/
|
|
646
|
+
declare ring: unknown[]
|
|
647
|
+
/**
|
|
648
|
+
* The ordinal before `ring[0]`, so `ring[index]` carries ordinal `ringBase + index + 1` and a
|
|
649
|
+
* cursor finds its place from the ordinal it holds.
|
|
650
|
+
*/
|
|
651
|
+
declare ringBase: number
|
|
652
|
+
/**
|
|
653
|
+
* `null` on every value that declares no `ttl`, and on every value 6.2 makes it inert
|
|
654
|
+
* for. Its presence is also what holds `direct` false — see `serve`.
|
|
655
|
+
*/
|
|
656
|
+
declare expiry: Expiry | null
|
|
657
|
+
declare ordinal: number
|
|
658
|
+
declare generation: number
|
|
659
|
+
declare alive: boolean
|
|
660
|
+
declare isEffect: boolean
|
|
661
|
+
declare isMirror: boolean
|
|
662
|
+
declare adopter: Tracked | null
|
|
663
|
+
/**
|
|
664
|
+
* The mark: `CLEAN`, `CHECK` or `DIRTY`.
|
|
665
|
+
*
|
|
666
|
+
* `CLEAN`, `CHECK` or `DIRTY` — see `tracking.ts`. Stamped on every transitive
|
|
667
|
+
* reader BEFORE anything runs, so a body that reaches a source which has not caught
|
|
668
|
+
* up yet pulls it forward; `running` is what stops a body already on the stack being
|
|
669
|
+
* re-entered.
|
|
670
|
+
*
|
|
671
|
+
* ONE RUN PER NODE PER WRITE is the contract, and the state carries it without a
|
|
672
|
+
* cascade counter: a node that has run is `CLEAN`, and only a source PRODUCING can
|
|
673
|
+
* mark it again — which a source it already read cannot do, because reading it is
|
|
674
|
+
* what resolved it.
|
|
675
|
+
*/
|
|
676
|
+
declare state: number
|
|
677
|
+
declare running: boolean
|
|
678
|
+
/**
|
|
679
|
+
* Whether the last run aborted on a value that had not landed.
|
|
680
|
+
*
|
|
681
|
+
* 2.13: A RUN THAT ABORTED ON A VALUE THAT HAD NOT LANDED, and is owed the rest of itself
|
|
682
|
+
* when that value arrives. Push-pull leaves a marked body for the read that wants it, and
|
|
683
|
+
* that is right for a body which RAN — this one did not, so nothing downstream can pull it:
|
|
684
|
+
* a reader guarding on `ready()` asks the probe edge and never the value, and the guard would
|
|
685
|
+
* answer false for as long as nobody read through it.
|
|
686
|
+
*/
|
|
687
|
+
declare aborted: boolean
|
|
688
|
+
/**
|
|
689
|
+
* The value this one adopted, or `null`.
|
|
690
|
+
*
|
|
691
|
+
* WHAT THIS VALUE'S BODY HANDED BACK, where it handed back another `Reactive`
|
|
692
|
+
* (11.12). `null` on every value that is not adopting, which is nearly all of them —
|
|
693
|
+
* one field rather than two, and the record is allocated only where there is one.
|
|
694
|
+
*/
|
|
695
|
+
declare adoption: Adoption | null
|
|
696
|
+
/**
|
|
697
|
+
* `null` on every value declaring neither `throttle` nor `debounce`, which is nearly
|
|
698
|
+
* all of them.
|
|
699
|
+
*/
|
|
700
|
+
declare cap: Cap | null
|
|
701
|
+
/**
|
|
702
|
+
* `null` on every value declaring no `tags`, which keeps `markProbes` from looking up a
|
|
703
|
+
* selection signal for any of them.
|
|
704
|
+
*/
|
|
705
|
+
declare tags: string[] | null
|
|
706
|
+
/** Whether `declare` built this value for a mutation, which a sweep invalidates (13.16). */
|
|
707
|
+
declare mutation: boolean
|
|
708
|
+
/** `null` on every value declaring no `store`, which is what keeps `serve` from persisting. */
|
|
709
|
+
declare store: Persisted | null
|
|
710
|
+
/**
|
|
711
|
+
* The declared `by`.
|
|
712
|
+
*
|
|
713
|
+
* 5.22 — held as declared rather than as a function, so the property name is still there to send.
|
|
714
|
+
*/
|
|
715
|
+
declare by: string | ((element: unknown) => unknown) | null
|
|
716
|
+
/** The probe pass this cell was last visited in. See `walkBusy` and `walkReady`. */
|
|
717
|
+
declare probePass: number
|
|
718
|
+
/**
|
|
719
|
+
* `OWES_NOTHING`, `OWES_ABOVE` or `OWES_RELOAD` — what an `invalidate` left for the next read
|
|
720
|
+
* to do. See `invalidateOne`.
|
|
721
|
+
*/
|
|
722
|
+
declare owes: number
|
|
723
|
+
/** The value this record is the state of — what `NotLanded` names as its source. */
|
|
724
|
+
declare callable: Reactive<unknown> | null
|
|
725
|
+
/** `null` on every value whose body is not an async generator, which is nearly all. */
|
|
726
|
+
declare stream: Stream | null
|
|
727
|
+
/**
|
|
728
|
+
* Whether a read may ever take the field straight, fixed at construction.
|
|
729
|
+
*
|
|
730
|
+
* WHETHER A READ MAY EVER TAKE THE FIELD STRAIGHT, fixed at construction: no expiry to
|
|
731
|
+
* check and no accumulation to materialise. `setDirect` reads this one field where it
|
|
732
|
+
* would otherwise read one per mechanism that needs the read to go a frame in.
|
|
733
|
+
*/
|
|
734
|
+
declare plain: boolean
|
|
735
|
+
/**
|
|
736
|
+
* Whether a settled write goes straight to `produce`, past no gate.
|
|
737
|
+
*
|
|
738
|
+
* NOTHING STANDS BETWEEN A SETTLED WRITE AND `produce`: no `schema`, no `transform`, and no
|
|
739
|
+
* stream whose writes stand apart from its chunks (9.11, 9.12). One field where the write and
|
|
740
|
+
* the recompute each tested two, maintained where a forwarded stream comes and goes.
|
|
741
|
+
*/
|
|
742
|
+
declare bareWrite: boolean
|
|
743
|
+
/**
|
|
744
|
+
* Whether a gated write can take `SIMPLE_PROTOTYPE`'s short path.
|
|
745
|
+
*
|
|
746
|
+
* EVERYTHING A GATED WRITE WOULD OTHERWISE ASK, IN ONE FIELD: no cap holding the
|
|
747
|
+
* production back, no expiry, no store to persist through, no
|
|
748
|
+
* declared `identity`, and `bareWrite`'s own three. A value carrying none of them has a
|
|
749
|
+
* `produce` -> `serve` -> `cascade` that reduces to three stores and a mark — which is
|
|
750
|
+
* what `SIMPLE_PROTOTYPE.set` spells out rather than calls.
|
|
751
|
+
*
|
|
752
|
+
* IT SELECTS A PROTOTYPE AND IS NOT READ ON THE WRITE PATH. See `SIMPLE_PROTOTYPE`.
|
|
753
|
+
*
|
|
754
|
+
* MAINTAINED, NOT FIXED: three of the fields under it move after construction —
|
|
755
|
+
* `bareWrite` and `plain` where a forwarded stream comes and goes, `identityFn` where an
|
|
756
|
+
* adoption takes the inner value's. `simpleShape` is called at each of those.
|
|
757
|
+
*/
|
|
758
|
+
declare simple: boolean
|
|
759
|
+
/**
|
|
760
|
+
* The scope this value was built in, and so the one its body runs under.
|
|
761
|
+
*
|
|
762
|
+
* 11.32 — THE SCOPE THIS VALUE WAS BUILT IN, and so the one its body runs under. `undefined` in a
|
|
763
|
+
* browser, and on a server for a value built outside any request.
|
|
764
|
+
*/
|
|
765
|
+
declare scope: Scope | undefined
|
|
766
|
+
/**
|
|
767
|
+
* Whether this value is one for the whole process, in every scope.
|
|
768
|
+
*
|
|
769
|
+
* 11.34, 13.13 — one value for the process, and so inside every scope a selection is made in. An
|
|
770
|
+
* entry of a `global` keyed value is one too, whichever request first reached it.
|
|
771
|
+
*/
|
|
772
|
+
declare global: boolean
|
|
773
|
+
/**
|
|
774
|
+
* The one weak handle every selection index holds this value by, or `null` where none does.
|
|
775
|
+
*
|
|
776
|
+
* 13.1 AND 13.6: a value filed for the bare sweep or carrying `tags` is findable by one, and
|
|
777
|
+
* every index holds the same handle, so an eviction drops it from all of them at once.
|
|
778
|
+
*/
|
|
779
|
+
declare ref: WeakRef<Cell> | null
|
|
780
|
+
/**
|
|
781
|
+
* Whether this value is filed for the bare selection — once, where it first becomes a load.
|
|
782
|
+
*
|
|
783
|
+
* See `fileProducer`. Not cleared by an eviction: dropping `ref` is what takes an evicted entry
|
|
784
|
+
* out, and this staying set is what keeps its next load from filing it again.
|
|
785
|
+
*/
|
|
786
|
+
declare filed: boolean
|
|
787
|
+
/**
|
|
788
|
+
* Whether what this value was built in has stopped — the setup or the request that owned it.
|
|
789
|
+
*
|
|
790
|
+
* 12.14. Set by `orphan`; the value lets go of its sources once nothing
|
|
791
|
+
* reads it (`releaseOrphan`), which may be at once or when the last reader outside its owner leaves.
|
|
792
|
+
*/
|
|
793
|
+
declare orphaned: boolean
|
|
794
|
+
|
|
795
|
+
constructor(
|
|
796
|
+
label: string,
|
|
797
|
+
body: ((args: unknown) => unknown) | null,
|
|
798
|
+
args: unknown,
|
|
799
|
+
options: ReactiveOptions<unknown, unknown, never> | undefined,
|
|
800
|
+
streaming: boolean,
|
|
801
|
+
) {
|
|
802
|
+
// Every field, in one order, `undefined` included.
|
|
803
|
+
this.readers = []
|
|
804
|
+
this.probeReaders = []
|
|
805
|
+
this.sources = []
|
|
806
|
+
this.probeSources = []
|
|
807
|
+
this.cursor = 0
|
|
808
|
+
this.seen = null
|
|
809
|
+
this.seenThrough = 0
|
|
810
|
+
this.scanningFrom = -1
|
|
811
|
+
this.label = label
|
|
812
|
+
this.body = body
|
|
813
|
+
this.args = args
|
|
814
|
+
this.value = undefined
|
|
815
|
+
this.landed = false
|
|
816
|
+
this.servable = false
|
|
817
|
+
this.direct = false
|
|
818
|
+
this.loading = false
|
|
819
|
+
this.failure = undefined
|
|
820
|
+
this.identityFn = comparatorFor(
|
|
821
|
+
options?.identity as Identity<unknown> | undefined,
|
|
822
|
+
)
|
|
823
|
+
this.declaredIdentityFn = this.identityFn
|
|
824
|
+
this.transform = (options?.transform ?? null) as
|
|
825
|
+
| ((value: unknown) => unknown)
|
|
826
|
+
| null
|
|
827
|
+
this.schema = (options?.schema ?? null) as Schema<unknown> | null
|
|
828
|
+
this.ring = EMPTY_RING
|
|
829
|
+
this.ringBase = 0
|
|
830
|
+
// 6.2 DECIDED ONCE, here rather than per production: a body is what makes a value a
|
|
831
|
+
// producer, and a plain state is the one this must not reach. See `expiryFor`.
|
|
832
|
+
this.expiry = expiryFor(
|
|
833
|
+
options?.ttl,
|
|
834
|
+
// A store is a producer too (8.2): there is somewhere to get the value back from, so
|
|
835
|
+
// `ttl` on a value holding one drops rather than being inert.
|
|
836
|
+
body !== null || options?.store !== undefined,
|
|
837
|
+
)
|
|
838
|
+
this.ordinal = 0
|
|
839
|
+
this.generation = 0
|
|
840
|
+
this.alive = true
|
|
841
|
+
this.state = CLEAN
|
|
842
|
+
this.running = false
|
|
843
|
+
this.aborted = false
|
|
844
|
+
this.adoption = null
|
|
845
|
+
this.cap = capFor(label, options?.throttle, options?.debounce)
|
|
846
|
+
const tags = options?.tags
|
|
847
|
+
this.tags =
|
|
848
|
+
tags === undefined
|
|
849
|
+
? null
|
|
850
|
+
: typeof tags === 'function'
|
|
851
|
+
? tags(args as never)
|
|
852
|
+
: tags
|
|
853
|
+
this.mutation =
|
|
854
|
+
(options as { [MUTATION]?: true } | undefined)?.[MUTATION] === true
|
|
855
|
+
const store = options?.store
|
|
856
|
+
this.store =
|
|
857
|
+
store === undefined
|
|
858
|
+
? null
|
|
859
|
+
: {
|
|
860
|
+
store: (typeof store === 'function'
|
|
861
|
+
? store(args as never)
|
|
862
|
+
: store) as Store<unknown>,
|
|
863
|
+
ttl: options?.ttl ?? Number.POSITIVE_INFINITY,
|
|
864
|
+
restoring: false,
|
|
865
|
+
writing: false,
|
|
866
|
+
pending: false,
|
|
867
|
+
unconfirmed: undefined,
|
|
868
|
+
}
|
|
869
|
+
this.by = (options?.by ?? null) as
|
|
870
|
+
| string
|
|
871
|
+
| ((element: unknown) => unknown)
|
|
872
|
+
| null
|
|
873
|
+
this.probePass = 0
|
|
874
|
+
this.owes = OWES_NOTHING
|
|
875
|
+
this.isEffect = false
|
|
876
|
+
this.isMirror = false
|
|
877
|
+
this.adopter = null
|
|
878
|
+
this.callable = null
|
|
879
|
+
this.stream = streaming ? newStream(null) : null
|
|
880
|
+
this.plain = this.expiry === null && this.stream === null
|
|
881
|
+
this.bareWrite =
|
|
882
|
+
this.schema === null && this.transform === null && !streaming
|
|
883
|
+
this.simple = false
|
|
884
|
+
this.scope = currentScope()
|
|
885
|
+
this.global = options?.global === true
|
|
886
|
+
this.ref = null
|
|
887
|
+
this.filed = false
|
|
888
|
+
this.orphaned = false
|
|
889
|
+
simpleShape(this)
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
notify(): void {
|
|
893
|
+
run(this)
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/** What a member's `this` is, and the one way from the value an app holds to its record. */
|
|
898
|
+
type Callable = { [CELL]: Cell }
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Throws where a value declares both `throttle` and `debounce`.
|
|
902
|
+
*
|
|
903
|
+
* 5.14 is a BUILD error and there is no build yet, so it lands at construction — the
|
|
904
|
+
* earliest moment that exists, and the one the app still has a stack for. Naming the value
|
|
905
|
+
* is the clause's own requirement; without it a page declaring both on one of forty values
|
|
906
|
+
* says only that something did. One statement for both producers of a window: a value's
|
|
907
|
+
* (5.8) and a keyed value's, which caps starts rather than wakes (5.19).
|
|
908
|
+
*/
|
|
909
|
+
export function refuseBothCaps(
|
|
910
|
+
label: string,
|
|
911
|
+
throttle: number | undefined,
|
|
912
|
+
debounce: number | undefined,
|
|
913
|
+
): void {
|
|
914
|
+
if (throttle !== undefined && debounce !== undefined)
|
|
915
|
+
throw new Error(
|
|
916
|
+
`${label} declares both throttle and debounce. A value is capped one way or the other.`,
|
|
917
|
+
)
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function capFor(
|
|
921
|
+
label: string,
|
|
922
|
+
throttle: number | undefined,
|
|
923
|
+
debounce: number | undefined,
|
|
924
|
+
): Cap | null {
|
|
925
|
+
refuseBothCaps(label, throttle, debounce)
|
|
926
|
+
const ms = throttle ?? debounce
|
|
927
|
+
if (ms === undefined) return null
|
|
928
|
+
return {
|
|
929
|
+
leading: throttle !== undefined,
|
|
930
|
+
ms,
|
|
931
|
+
timer: null,
|
|
932
|
+
held: undefined,
|
|
933
|
+
holding: false,
|
|
934
|
+
minted: undefined,
|
|
935
|
+
owed: false,
|
|
936
|
+
reloading: false,
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Compiles `identity` into a comparator, or `null` for the default `Object.is`.
|
|
942
|
+
*
|
|
943
|
+
* THE DEFAULT IS THE REFERENCE, and `structural` is exported for an app to pass.
|
|
944
|
+
*
|
|
945
|
+
* THE CASE FOR A STRUCTURAL DEFAULT IS REAL: a reload that fetched an equal
|
|
946
|
+
* payload builds a FRESH object, so a reference compare never fires and every reader
|
|
947
|
+
* wakes on a value that did not change. What decides it against that is the cost on
|
|
948
|
+
* the shape an app actually holds — a compare walks the whole value on EVERY write,
|
|
949
|
+
* where the wake it avoids is paid only when the payload really is equal. A grid of
|
|
950
|
+
* rows re-assembled per recompute pays the deep walk every time and collapses never.
|
|
951
|
+
*
|
|
952
|
+
* So the framework takes the cheap, predictable default — `===` is what JavaScript
|
|
953
|
+
* means by "the same value" — and the app names `identity: structural` on the values
|
|
954
|
+
* where re-materialisation is the common case. 5.5's "not equal wherever it cannot
|
|
955
|
+
* decide" is unchanged; it is just no longer the default reading.
|
|
956
|
+
*
|
|
957
|
+
* `identity` is discriminated by ARITY (5.3), once, at construction — not per compare.
|
|
958
|
+
*
|
|
959
|
+
* NULL IS THE DEFAULT, not a function that implements it. `produce` compares against
|
|
960
|
+
* `null` and calls `Object.is` directly, which takes an INDIRECT call off the write path:
|
|
961
|
+
* the site is polymorphic across the default, a projection closure and whatever an app
|
|
962
|
+
* passed, so it is not one an engine can speculate through. The saving is the indirection
|
|
963
|
+
* rather than the comparison — `Object.is` is itself the fastest spelling of it.
|
|
964
|
+
*/
|
|
965
|
+
function comparatorFor(
|
|
966
|
+
identity: Identity<unknown> | undefined,
|
|
967
|
+
): ((next: unknown, previous: unknown) => boolean) | null {
|
|
968
|
+
if (identity === undefined) return null
|
|
969
|
+
if (identity.length >= 2)
|
|
970
|
+
return identity as (next: unknown, previous: unknown) => boolean
|
|
971
|
+
const project = identity as (value: unknown) => unknown
|
|
972
|
+
return (next, previous) => project(next) === project(previous)
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Recomputes `simple` and re-seats the callable on the matching prototype.
|
|
977
|
+
*
|
|
978
|
+
* `simple` IN ONE PLACE, and the prototype that goes with it. A second copy of this
|
|
979
|
+
* conjunction is the drift that would let one of the four sites go on reading `true` after
|
|
980
|
+
* the field under it moved — and what the short write would then skip is a cap, a ring or a
|
|
981
|
+
* declared `identity`, silently.
|
|
982
|
+
*
|
|
983
|
+
* THE CALLABLE IS RE-SEATED ONLY WHERE IT IS ON ONE OF THESE TWO. A value that is adopting or
|
|
984
|
+
* forwarding a stream carries a prototype of its own and keeps it; the field is still
|
|
985
|
+
* maintained, so the seat it lands on when the adoption drops is the right one.
|
|
986
|
+
*/
|
|
987
|
+
function simpleShape(cell: Cell): void {
|
|
988
|
+
cell.simple =
|
|
989
|
+
cell.bareWrite &&
|
|
990
|
+
// `plain` carries `expiry === null` and `stream === null` already.
|
|
991
|
+
cell.plain &&
|
|
992
|
+
cell.cap === null &&
|
|
993
|
+
cell.store === null &&
|
|
994
|
+
cell.identityFn === null
|
|
995
|
+
const callable = cell.callable
|
|
996
|
+
if (callable === null) return
|
|
997
|
+
const seat = Object.getPrototypeOf(callable)
|
|
998
|
+
if (seat !== MEMBER_PROTOTYPE && seat !== SIMPLE_PROTOTYPE) return
|
|
999
|
+
Object.setPrototypeOf(
|
|
1000
|
+
callable as object,
|
|
1001
|
+
cell.simple ? SIMPLE_PROTOTYPE : MEMBER_PROTOTYPE,
|
|
1002
|
+
)
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* The effect queue a mark walk fills and the drain empties.
|
|
1007
|
+
*
|
|
1008
|
+
* THE EFFECT QUEUE. A mark walk runs no body, so something has to run the ones that
|
|
1009
|
+
* must: every EFFECT the walk reached lands here and is resolved by the drain at the end of
|
|
1010
|
+
* the turn. Derived values are NOT queued — nothing runs them until something reads them,
|
|
1011
|
+
* which is the whole of push-pull — save 2.13's aborted body, see `drainPending`.
|
|
1012
|
+
*
|
|
1013
|
+
* An array and a read cursor rather than `shift()`: an effect that writes appends to the
|
|
1014
|
+
* queue the drain is already walking, and shifting would move every remaining entry per
|
|
1015
|
+
* take.
|
|
1016
|
+
* A COUNT RATHER THAN THE ARRAY'S OWN LENGTH, and the array is never truncated.
|
|
1017
|
+
* `PENDING.length = 0` is what an engine reads as "this array is finished with", and it
|
|
1018
|
+
* hands the backing store back — so the next write's push allocated one again, on every
|
|
1019
|
+
* write that reaches an effect. Holding the capacity and moving a cursor over it makes a
|
|
1020
|
+
* push a plain indexed store.
|
|
1021
|
+
*/
|
|
1022
|
+
const PENDING: (Subscriber | null)[] = []
|
|
1023
|
+
let pendingCount = 0
|
|
1024
|
+
let pendingAt = 0
|
|
1025
|
+
/**
|
|
1026
|
+
* Whether a drain is already in flight.
|
|
1027
|
+
*
|
|
1028
|
+
* A write made from inside an effect appends to the queue the outer drain is walking rather than
|
|
1029
|
+
* opening a second one, which is what orders a nested write's effects AFTER the one that made it
|
|
1030
|
+
* rather than underneath it.
|
|
1031
|
+
*/
|
|
1032
|
+
let draining = false
|
|
1033
|
+
/** Whether a drain is already queued for the end of this turn. */
|
|
1034
|
+
let scheduled = false
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* Queues the drain for the end of this turn.
|
|
1038
|
+
*
|
|
1039
|
+
* THE DRAIN IS DEFERRED AND THE MARK IS NOT, which is the whole of the batching and is
|
|
1040
|
+
* only safe because reads PULL. A write stamps the graph synchronously, so a read taken
|
|
1041
|
+
* immediately after one is correct — it resolves what it finds marked. What waits for the
|
|
1042
|
+
* end of the turn is the part that touches the page.
|
|
1043
|
+
*
|
|
1044
|
+
* WITHOUT IT EVERY WRITE IS ITS OWN FLUSH. Measured against a binding that writes on every
|
|
1045
|
+
* wake: ten writes in one handler were ten `.data` writes where one value is ever seen,
|
|
1046
|
+
* and three values written together were five, because the binding reading all three ran
|
|
1047
|
+
* once per write. A diamond was already one — that is what the depth-first resolve buys,
|
|
1048
|
+
* and this is the same insight one turn up.
|
|
1049
|
+
*/
|
|
1050
|
+
function schedule(): void {
|
|
1051
|
+
if (scheduled || draining) return
|
|
1052
|
+
scheduled = true
|
|
1053
|
+
queueMicrotask(flush)
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function flush(): void {
|
|
1057
|
+
scheduled = false
|
|
1058
|
+
if (pendingCount === 0 || draining) return
|
|
1059
|
+
drainPending()
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Runs every pending effect now rather than at the end of the turn.
|
|
1064
|
+
*
|
|
1065
|
+
* THE ESCAPE FROM THE BATCH, and batching is what makes it necessary. Everything that has
|
|
1066
|
+
* to see the page as the writes left it — a test, a measurement, a server rendering to a
|
|
1067
|
+
* string — needs the turn to end on demand rather than when the microtask queue says so.
|
|
1068
|
+
* It is the only way to observe an effect in the same turn as the write that caused it.
|
|
1069
|
+
*/
|
|
1070
|
+
export function flushSync(): void {
|
|
1071
|
+
// IT DOES NOT CLEAR `scheduled`. The microtask already queued will fire and find the
|
|
1072
|
+
// queue empty, which costs nothing — where clearing it makes the NEXT write in this
|
|
1073
|
+
// same turn queue another one, and a write-then-flush loop pays `queueMicrotask` per
|
|
1074
|
+
// write rather than once per turn. Measured at 26.5 ns a write against 14.2 before the
|
|
1075
|
+
// flush was there at all.
|
|
1076
|
+
// 9.10: a wake a stream is holding for the next task is one this turn ends too.
|
|
1077
|
+
if (WAKING.length !== 0) wakeStreams()
|
|
1078
|
+
if (pendingCount === 0 || draining) return
|
|
1079
|
+
drainPending()
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* Sets `direct` where `when` holds and the value is `plain`.
|
|
1084
|
+
*
|
|
1085
|
+
* WHAT A VALUE CARRYING A `ttl` GIVES UP, stated once because four paths restore `direct`
|
|
1086
|
+
* and a fifth that forgot would serve an expired production with nothing about the output
|
|
1087
|
+
* saying so. `direct` is the one load and one branch the read path gets; an expiry cell
|
|
1088
|
+
* is never direct, so every read of it goes a frame in to `readIndirect` — which is
|
|
1089
|
+
* exactly where 6.5 and 6.6 are answered, and is why a value declaring no `ttl`
|
|
1090
|
+
* never reaches a clock. See `Expiry.ts`. A stream is never direct either, for the same
|
|
1091
|
+
* shape of reason: its read is where the accumulation is materialised (9.1).
|
|
1092
|
+
*/
|
|
1093
|
+
function setDirect(cell: Cell, when: boolean): void {
|
|
1094
|
+
cell.direct = when && cell.plain
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Marks a production's readers `wanted` and everything beyond them `CHECK`, running nothing.
|
|
1099
|
+
*
|
|
1100
|
+
* ONE WALK BEFORE ANYTHING RUNS, SETTING A FLAG AND RUNNING NOTHING. Direct readers of
|
|
1101
|
+
* the cell that produced are `DIRTY` — a source of theirs demonstrably moved. Everything
|
|
1102
|
+
* beyond them is `CHECK`, because whether the change REACHES them is not known until the
|
|
1103
|
+
* nodes between have been asked, and 5.2 says a derived value that recomputes to what it
|
|
1104
|
+
* already held wakes nobody.
|
|
1105
|
+
*
|
|
1106
|
+
* This is the ordering fix too: without the walk, A -> B, A -> C, B + C -> D resolves D
|
|
1107
|
+
* from B while C still holds its old value, and the production D mints there carries
|
|
1108
|
+
* `b_new + c_old` — a value the system was never in, which reaches `watch` and the
|
|
1109
|
+
* ordinal. With it, D's read of C finds C marked and pulls it forward first.
|
|
1110
|
+
*/
|
|
1111
|
+
function markReaders(cell: Cell, wanted: number): void {
|
|
1112
|
+
const readers = cell.readers
|
|
1113
|
+
for (let index = 0; index < readers.length; index += 1) {
|
|
1114
|
+
const reader = readers[index] as Subscriber
|
|
1115
|
+
// A mark never weakens, and stopping where one is already at least this strong
|
|
1116
|
+
// keeps the walk O(V) rather than O(paths) over a shared subgraph. Skipping one
|
|
1117
|
+
// that is RUNNING is what stops a body re-marking itself when a source it pulled
|
|
1118
|
+
// forward produces underneath it.
|
|
1119
|
+
if (!reader.alive || reader.running || reader.state >= wanted) continue
|
|
1120
|
+
// One node VISITED. Past the guard above, so it counts what the walk did rather than
|
|
1121
|
+
// what it was offered — the difference between the two is the whole of the O(V) claim.
|
|
1122
|
+
WORK.descents += 1
|
|
1123
|
+
const was = reader.state
|
|
1124
|
+
reader.state = wanted
|
|
1125
|
+
if (reader.isEffect) {
|
|
1126
|
+
// A MIRROR RUNS HERE rather than at the end of the turn: it carries a VALUE,
|
|
1127
|
+
// and a read taken before the drain has to find it. No body runs inside it —
|
|
1128
|
+
// the production it mints marks on through this same walk. BUT ONLY WHERE WHAT
|
|
1129
|
+
// IT MIRRORS HAS PRODUCED: an adopted DERIVED value reached by this walk is only
|
|
1130
|
+
// marked, still holding what it held, and copied here it is copied stale and the
|
|
1131
|
+
// reader left clean. That one is queued like any effect, and the drain's `CHECK`
|
|
1132
|
+
// pulls the derived body forward before the mirror copies anything.
|
|
1133
|
+
if (reader.isMirror && cell.state === CLEAN) {
|
|
1134
|
+
reader.state = CLEAN
|
|
1135
|
+
reader.notify()
|
|
1136
|
+
continue
|
|
1137
|
+
}
|
|
1138
|
+
// Queued once. An upgrade from `CHECK` to `DIRTY` finds it already there.
|
|
1139
|
+
if (was === CLEAN) {
|
|
1140
|
+
PENDING[pendingCount] = reader
|
|
1141
|
+
pendingCount += 1
|
|
1142
|
+
}
|
|
1143
|
+
continue
|
|
1144
|
+
}
|
|
1145
|
+
const below = reader as unknown as Cell
|
|
1146
|
+
// Marked is not directly servable, whatever it holds.
|
|
1147
|
+
below.direct = false
|
|
1148
|
+
// 2.13, 3.2, AND THE ONLY PLACE PUSH-PULL RUNS A DERIVED BODY. What is asking is a GUARD: a
|
|
1149
|
+
// reader on the probe edge asks `ready()` and never the value, so no read of its will pull
|
|
1150
|
+
// this body forward, and what the probe answers is about the value a read WOULD serve
|
|
1151
|
+
// (3.2) — a body whose inputs moved to a load is not ready, whatever its last run held. So
|
|
1152
|
+
// the drain pulls it, and the guard wakes only where a probe moved. A reader on the VALUE
|
|
1153
|
+
// edge needs none of this — its own read pulls — so this is the probe edge alone, and a
|
|
1154
|
+
// body nothing probes stays as lazy as 11.5 makes it.
|
|
1155
|
+
if (was === CLEAN && below.probeReaders.length !== 0) {
|
|
1156
|
+
PENDING[pendingCount] = reader
|
|
1157
|
+
pendingCount += 1
|
|
1158
|
+
}
|
|
1159
|
+
// 5.11: A CAPPED BODY IS A BARRIER IN THE CASCADE. The mark arrives here and
|
|
1160
|
+
// stops — nothing downstream is woken (5.8) and the body does not run, so the
|
|
1161
|
+
// load it would have issued is never issued rather than issued and discarded.
|
|
1162
|
+
// That is the whole difference between capping a value and capping a body: a
|
|
1163
|
+
// value's production has already been handed in and must be retained (5.9),
|
|
1164
|
+
// where a body's work has not started yet and 5.11 says not to start it.
|
|
1165
|
+
//
|
|
1166
|
+
// THE WINDOW IS ARMED HERE BECAUSE THIS IS WHERE A SOURCE PRODUCING IS VISIBLE.
|
|
1167
|
+
// A read cannot tell one change from ten, so a debounce armed at the read fires
|
|
1168
|
+
// a fixed interval after the first change rather than once the changes stop.
|
|
1169
|
+
//
|
|
1170
|
+
// THE LOAD IS NOT MEASURABLE. It was the reason to expect a cost here — one more
|
|
1171
|
+
// field read per marked node in the hottest walk in the core — and across two
|
|
1172
|
+
// runs the abide arm is flat on every graph row: chain 20 at 2.47x against a
|
|
1173
|
+
// 2.44x-2.50x baseline, DAG 5x3 at 1.96x against 1.93x, fan-in 100 unchanged. The
|
|
1174
|
+
// ratios wobble because the hand-written arm moves with machine state; `below` was
|
|
1175
|
+
// stored to on the line above, so this reads a line already in cache.
|
|
1176
|
+
const cap = below.cap
|
|
1177
|
+
if (cap !== null && below.body !== null) {
|
|
1178
|
+
if (windowHolds(below, cap)) {
|
|
1179
|
+
cap.owed = true
|
|
1180
|
+
// BACK TO CLEAN so the next source production marks it again and re-arms.
|
|
1181
|
+
// Left marked it takes this walk's own `state >= wanted` short-circuit,
|
|
1182
|
+
// and the changes after the first become invisible to the window.
|
|
1183
|
+
below.state = CLEAN
|
|
1184
|
+
setDirect(below, below.servable)
|
|
1185
|
+
continue
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
// Only a node this walk has not reached descends, and only where it HAS a
|
|
1189
|
+
// subtree: one already marked is already at least `CHECK`, which is the
|
|
1190
|
+
// strongest thing this walk could say, and one with no readers is the shape
|
|
1191
|
+
// push-pull makes most of — a fan of a hundred derived values that nothing
|
|
1192
|
+
// downstream reads was entering the walk a hundred times to loop over nothing.
|
|
1193
|
+
if (was === CLEAN && below.readers.length !== 0)
|
|
1194
|
+
markReaders(below, CHECK)
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* Pulls a marked value up to date, running its body only if a source produced.
|
|
1200
|
+
*
|
|
1201
|
+
* PULL. A `DIRTY` cell runs. A `CHECK` cell asks its sources first and runs only if one
|
|
1202
|
+
* of them produced — `produce` is what upgrades a reader from `CHECK` to `DIRTY`, so
|
|
1203
|
+
* "did anything actually reach me" is answered by the mark arriving during the walk
|
|
1204
|
+
* rather than by comparing versions afterwards.
|
|
1205
|
+
*/
|
|
1206
|
+
function resolve(cell: Cell): void {
|
|
1207
|
+
const state = cell.state
|
|
1208
|
+
if (state === CLEAN) return
|
|
1209
|
+
// On the stack already: its value is whatever its run has reached, and re-entering
|
|
1210
|
+
// is the cycle guard.
|
|
1211
|
+
if (cell.running) return
|
|
1212
|
+
if (state === CHECK) {
|
|
1213
|
+
const sources = cell.sources
|
|
1214
|
+
for (let index = 0; index < sources.length; index += 1) {
|
|
1215
|
+
// The CLEAN test is here rather than behind the call: a node this walk did
|
|
1216
|
+
// not reach is the common source, and asking it costs a load instead of a
|
|
1217
|
+
// frame. A body with a thousand sources of which one moved asks all thousand.
|
|
1218
|
+
const source = sources[index] as Cell
|
|
1219
|
+
if (source.state !== CLEAN) resolve(source)
|
|
1220
|
+
// Resolving that source produced, and the production marked this cell. The
|
|
1221
|
+
// rest of the sources are pulled forward by the body's own reads.
|
|
1222
|
+
if (cell.state === DIRTY) break
|
|
1223
|
+
}
|
|
1224
|
+
if (cell.state !== DIRTY) {
|
|
1225
|
+
cell.state = CLEAN
|
|
1226
|
+
// Nothing it reads moved, so what it already holds is servable again. The
|
|
1227
|
+
// mark cleared this on the way down and only here can it come back — a
|
|
1228
|
+
// `CHECK` that resolves to nothing never reaches `produce`.
|
|
1229
|
+
setDirect(cell, cell.servable)
|
|
1230
|
+
return
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
cell.state = CLEAN
|
|
1234
|
+
run(cell)
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* Runs a queued effect only if something it reads actually moved.
|
|
1239
|
+
*
|
|
1240
|
+
* An effect whose turn has come and which the drain's `DIRTY` arm did not take. `CHECK`
|
|
1241
|
+
* is the case eager could not express: the effect was reached by the mark walk, and
|
|
1242
|
+
* whether anything it reads actually moved is decided here rather than by running it and
|
|
1243
|
+
* letting it read the same values again.
|
|
1244
|
+
*/
|
|
1245
|
+
function resolveEffect(reader: Subscriber): void {
|
|
1246
|
+
if (!reader.alive) {
|
|
1247
|
+
reader.state = CLEAN
|
|
1248
|
+
return
|
|
1249
|
+
}
|
|
1250
|
+
// Brought current since it was queued — a guard's probe pulls the cell it queued (3.2) — so
|
|
1251
|
+
// there is nothing owed, and a second run would wake that guard onto what it already saw.
|
|
1252
|
+
if (reader.state === CLEAN) return
|
|
1253
|
+
if (reader.state === CHECK) {
|
|
1254
|
+
const sources = reader.sources
|
|
1255
|
+
for (let index = 0; index < sources.length; index += 1) {
|
|
1256
|
+
const source = sources[index] as unknown as Cell
|
|
1257
|
+
if (source.state !== CLEAN) resolve(source)
|
|
1258
|
+
if (reader.state === DIRTY) break
|
|
1259
|
+
}
|
|
1260
|
+
if (reader.state !== DIRTY) {
|
|
1261
|
+
reader.state = CLEAN
|
|
1262
|
+
return
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
// CLEARED BEFORE, not after: `running` is what blocks a re-mark while the body is on
|
|
1266
|
+
// the stack, and clearing after would drop a mark a nested write legitimately made.
|
|
1267
|
+
reader.state = CLEAN
|
|
1268
|
+
reader.notify()
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* Wakes the probe readers of everything downstream whose floor moved.
|
|
1273
|
+
*
|
|
1274
|
+
* A PROBE MOVED, AND NO VALUE DID. Going loading on a refresh, coming back on a settle,
|
|
1275
|
+
* losing what there was to serve on an invalidate — each changes what every reader
|
|
1276
|
+
* DOWNSTREAM reports (11.20's floor) and mints nothing for a cascade to carry.
|
|
1277
|
+
*
|
|
1278
|
+
* It walks the VALUE edge downstream to find the cells whose floor moved, and wakes the
|
|
1279
|
+
* `probeReaders` of each — never their `readers`. That distinction is the whole point: the
|
|
1280
|
+
* rows painting the value are not woken, and the guards asking about it are.
|
|
1281
|
+
*/
|
|
1282
|
+
function markProbes(cell: Cell, pass: number): void {
|
|
1283
|
+
if (cell.probePass === pass) return
|
|
1284
|
+
cell.probePass = pass
|
|
1285
|
+
markAsked(cell.probeReaders)
|
|
1286
|
+
// 13.3: a guard over a tag selection asked the SELECTION, so it hears through the selection's
|
|
1287
|
+
// signal rather than through this value's own probe readers.
|
|
1288
|
+
if (cell.tags !== null) markSignals(cell.tags)
|
|
1289
|
+
// The bare selection's guard hears every filed value's floor move; one load where nothing asks.
|
|
1290
|
+
if (EVERY_PRODUCER.probeReaders.length !== 0 && cell.filed)
|
|
1291
|
+
markAsked(EVERY_PRODUCER.probeReaders)
|
|
1292
|
+
// The floor of everything built from this one moved with it — what READ it, and what
|
|
1293
|
+
// ADOPTED it (11.50), which is on this list as its mirror rather than as itself.
|
|
1294
|
+
const readers = cell.readers
|
|
1295
|
+
for (let index = 0; index < readers.length; index += 1) {
|
|
1296
|
+
const reader = readers[index] as Subscriber
|
|
1297
|
+
if (!reader.isEffect) markProbes(reader as unknown as Cell, pass)
|
|
1298
|
+
else if (reader.adopter !== null)
|
|
1299
|
+
markProbes(reader.adopter as unknown as Cell, pass)
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
function markAsked(asked: Subscriber[]): void {
|
|
1304
|
+
for (let index = 0; index < asked.length; index += 1) {
|
|
1305
|
+
const reader = asked[index] as Subscriber
|
|
1306
|
+
if (!reader.alive || reader.running || reader.state === DIRTY) continue
|
|
1307
|
+
const was = reader.state
|
|
1308
|
+
reader.state = DIRTY
|
|
1309
|
+
if (reader.isEffect) {
|
|
1310
|
+
if (was === CLEAN) {
|
|
1311
|
+
PENDING[pendingCount] = reader
|
|
1312
|
+
pendingCount += 1
|
|
1313
|
+
}
|
|
1314
|
+
continue
|
|
1315
|
+
}
|
|
1316
|
+
const below = reader as unknown as Cell
|
|
1317
|
+
below.direct = false
|
|
1318
|
+
if (was === CLEAN && below.readers.length !== 0)
|
|
1319
|
+
markReaders(below, CHECK)
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* Opens a probe cascade for a move no production carries.
|
|
1325
|
+
*
|
|
1326
|
+
* The cascade's shape, for a move no production carries. Safe to share `probePass` with
|
|
1327
|
+
* `walkBusy` and `walkReady`: this marks and queues, and nothing runs until the drain
|
|
1328
|
+
* below it, so a probe read can never be inside this walk.
|
|
1329
|
+
*/
|
|
1330
|
+
function probesMoved(cell: Cell): void {
|
|
1331
|
+
PROBE_PASS += 1
|
|
1332
|
+
markProbes(cell, PROBE_PASS)
|
|
1333
|
+
if (draining || pendingCount === 0) return
|
|
1334
|
+
schedule()
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
/**
|
|
1338
|
+
* Wakes the guards subscribed to `signal`'s probe edge — a node that is nothing but that edge.
|
|
1339
|
+
*
|
|
1340
|
+
* What a `Keyed`'s statics hear an entry being reached or released through: membership is not a
|
|
1341
|
+
* probe of any entry, so a guard asked before a key existed has no entry to hear it from.
|
|
1342
|
+
*/
|
|
1343
|
+
export function signalMoved(signal: Tracked): void {
|
|
1344
|
+
if (signal.probeReaders.length === 0) return
|
|
1345
|
+
markAsked(signal.probeReaders)
|
|
1346
|
+
if (draining || pendingCount === 0) return
|
|
1347
|
+
schedule()
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/**
|
|
1351
|
+
* Marks a production's readers and schedules the effects it reached.
|
|
1352
|
+
*
|
|
1353
|
+
* THE CASCADE, WHICH NO LONGER RUNS A SINGLE DERIVED BODY. It marks, and it schedules the
|
|
1354
|
+
* effects the mark reached for the drain at the end of the turn (see `schedule`); everything
|
|
1355
|
+
* else is run by the read that wants it.
|
|
1356
|
+
*/
|
|
1357
|
+
function cascade(cell: Cell): void {
|
|
1358
|
+
// A DERIVED VALUE NOTHING READS is the shape push-pull creates most of: a fan of a
|
|
1359
|
+
// hundred, of which one is read. Testing the length here rather than entering the
|
|
1360
|
+
// walk takes a call frame off every one of the other ninety-nine.
|
|
1361
|
+
if (cell.readers.length !== 0) markReaders(cell, DIRTY)
|
|
1362
|
+
// A write made from inside an effect appends to the queue the outer drain is
|
|
1363
|
+
// walking. Opening a second drain here would run the nested write's effects
|
|
1364
|
+
// underneath the effect that made it and before that effect had finished.
|
|
1365
|
+
if (draining) return
|
|
1366
|
+
// NOTHING TO RUN IS THE COMMON WRITE, and it must not pay for the drain. Writing
|
|
1367
|
+
// `PENDING.length = 0` unconditionally cost 35 ns on a write with no readers at all
|
|
1368
|
+
// — six times the whole op — because a length store is not free even where the array
|
|
1369
|
+
// is already empty. Every write in a graph whose effects are all upstream of it
|
|
1370
|
+
// takes this arm.
|
|
1371
|
+
if (pendingCount === 0) return
|
|
1372
|
+
schedule()
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* Runs the queued effects, abandoning the queue if one throws.
|
|
1377
|
+
*
|
|
1378
|
+
* THE DRAIN, BEHIND ITS OWN FRAME. A `try`/`finally` in `cascade` is a `try`/`finally`
|
|
1379
|
+
* on every write, and the arm that needs it is the one that actually has an effect to
|
|
1380
|
+
* run — so the guard is up there and the handler is down here.
|
|
1381
|
+
*/
|
|
1382
|
+
function drainPending(): void {
|
|
1383
|
+
draining = true
|
|
1384
|
+
try {
|
|
1385
|
+
// Re-read `pendingCount` per turn: an effect that writes appends to the queue
|
|
1386
|
+
// this loop is walking, and those belong to this drain rather than the next write.
|
|
1387
|
+
while (pendingAt < pendingCount) {
|
|
1388
|
+
const reader = PENDING[pendingAt] as Subscriber
|
|
1389
|
+
// THE SLOT IS RELEASED, THE ARRAY IS NOT. Holding the capacity is what keeps a
|
|
1390
|
+
// push a plain indexed store, and a slot left pointing at a drained subscriber
|
|
1391
|
+
// holds its `notify` — and through it the effect body and the whole scope that
|
|
1392
|
+
// body closed over — until another effect happens to land on that index. A
|
|
1393
|
+
// disposed `watch` stayed reachable to the next high-water mark, which on a page
|
|
1394
|
+
// is every node the torn-down component built. One store per effect ALREADY RUN,
|
|
1395
|
+
// against a body call.
|
|
1396
|
+
PENDING[pendingAt] = null
|
|
1397
|
+
pendingAt += 1
|
|
1398
|
+
// A DIRECT READER OF WHAT WAS WRITTEN is `DIRTY`, which is the overwhelming
|
|
1399
|
+
// shape and asks nothing: there is no source to interrogate, because the
|
|
1400
|
+
// mark says one of them already produced.
|
|
1401
|
+
if (reader.state === DIRTY && reader.alive) {
|
|
1402
|
+
reader.state = CLEAN
|
|
1403
|
+
reader.notify()
|
|
1404
|
+
continue
|
|
1405
|
+
}
|
|
1406
|
+
resolveEffect(reader)
|
|
1407
|
+
}
|
|
1408
|
+
} finally {
|
|
1409
|
+
// The queue is abandoned rather than carried into the next write: an effect that
|
|
1410
|
+
// threw has already stopped its own watch (12.8), and a half-drained queue
|
|
1411
|
+
// outliving the throw would run it against a later write's marks. The tail is
|
|
1412
|
+
// released for the reason the loop above releases each slot; on the path that did
|
|
1413
|
+
// not throw the cursor has already reached the count and this walks nothing.
|
|
1414
|
+
//
|
|
1415
|
+
// THE MARK GOES WITH THE QUEUE, and that is what abandoning has to mean. A reader
|
|
1416
|
+
// left standing at `DIRTY` with nothing holding it takes `markReaders`' own
|
|
1417
|
+
// `state >= wanted` short-circuit on every later write, so it is never queued
|
|
1418
|
+
// again — one effect throwing left every sibling it had not reached permanently
|
|
1419
|
+
// deaf, silently, while the value went on being right. Back to `CLEAN` is the
|
|
1420
|
+
// state the next write's mark can reach.
|
|
1421
|
+
//
|
|
1422
|
+
// AN EFFECT ONLY, THOUGH. The queue is not effects alone: `markReaders` also pushes a
|
|
1423
|
+
// derived CELL on 2.13's aborted-body path, and for one of those `state` is not a
|
|
1424
|
+
// reminder to run — it IS the pull the next read takes (`state !== CLEAN` is what sends
|
|
1425
|
+
// a read a frame in). Forced back to `CLEAN` it is not deaf, it is settled: the read
|
|
1426
|
+
// path skips `resolve`, the aborted body is never re-run and the `ready()` guard that
|
|
1427
|
+
// queued it answers false for the life of the page — undoing, on any throw anywhere in
|
|
1428
|
+
// the drain, exactly what that push was added to repair.
|
|
1429
|
+
for (let index = pendingAt; index < pendingCount; index += 1) {
|
|
1430
|
+
const abandoned = PENDING[index] as Subscriber
|
|
1431
|
+
PENDING[index] = null
|
|
1432
|
+
if (abandoned.isEffect) abandoned.state = CLEAN
|
|
1433
|
+
}
|
|
1434
|
+
pendingAt = 0
|
|
1435
|
+
pendingCount = 0
|
|
1436
|
+
draining = false
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* Mints one production and serves it, or collapses it as a repeat; returns the ordinal.
|
|
1442
|
+
*
|
|
1443
|
+
* One production. A write that `identity` deems a repeat mints nothing and wakes nobody (5.2).
|
|
1444
|
+
*/
|
|
1445
|
+
function produce(cell: Cell, produced: unknown): number {
|
|
1446
|
+
// 7.9: AN ACCEPTED PRODUCTION SETTLES THE RELOAD AN INVALIDATE OWED — ahead of the gate, since
|
|
1447
|
+
// a write 5.2 collapses was still accepted. Left standing, the next read unserved this value
|
|
1448
|
+
// and reloaded over it. A look above (`OWES_ABOVE`) is not this value's mark and stays: 11.8
|
|
1449
|
+
// has a write stand without settling what it is built from.
|
|
1450
|
+
if (cell.owes === OWES_RELOAD) cell.owes = OWES_NOTHING
|
|
1451
|
+
const cap = cell.cap
|
|
1452
|
+
// 5.9: the gate compares against the last production MINTED. That is the held value
|
|
1453
|
+
// on everything that never caps, and `cap.minted` is where a window has moved the two
|
|
1454
|
+
// apart.
|
|
1455
|
+
//
|
|
1456
|
+
// A CAP OVER A BODY MOVES NEITHER. 5.11 caps the RUN there, so the production goes
|
|
1457
|
+
// straight out and `capped` — which writes `cap.minted` for a production — is never
|
|
1458
|
+
// reached. Read unconditionally, `previous` was `undefined` on every recompute of a
|
|
1459
|
+
// capped derived value, so 5.2 collapsed nothing and every reader woke on a value that
|
|
1460
|
+
// had not moved.
|
|
1461
|
+
// The same test `produce` already picks `capped` by, one frame down.
|
|
1462
|
+
if (cell.landed) {
|
|
1463
|
+
const previous =
|
|
1464
|
+
cap === null || cell.body !== null ? cell.value : cap.minted
|
|
1465
|
+
if (
|
|
1466
|
+
cell.identityFn === null
|
|
1467
|
+
? // `Object.is` rather than `===` for one case: a repeated `NaN` is the
|
|
1468
|
+
// same value and would otherwise wake every reader forever. Called
|
|
1469
|
+
// rather than spelled out — an engine intrinsifies it, and the
|
|
1470
|
+
// hand-written equivalent measured 0.80 ns against its 0.66 ns on the
|
|
1471
|
+
// same input.
|
|
1472
|
+
Object.is(produced, previous)
|
|
1473
|
+
: cell.identityFn(produced, previous)
|
|
1474
|
+
) {
|
|
1475
|
+
// The value did not move, so 5.2 mints nothing — but a load in flight is over and
|
|
1476
|
+
// `busy` has moved, which a reader that asked about it has to hear. A standing
|
|
1477
|
+
// failure is the third probe moving: 4.11 clears it below, and a guard on
|
|
1478
|
+
// `error` hears that the same way one on `busy` hears the load ending.
|
|
1479
|
+
//
|
|
1480
|
+
// 4.11: an ACCEPTED write clears the standing error, and a write the identity gate
|
|
1481
|
+
// collapses was still accepted — it produced nothing because the value was already
|
|
1482
|
+
// the one being written, not because it failed. Leaving the failure standing left
|
|
1483
|
+
// `s.error()` reporting a load that a later write superseded.
|
|
1484
|
+
const wasServable = cell.servable
|
|
1485
|
+
const settling = settleProbes(cell)
|
|
1486
|
+
cell.servable = true
|
|
1487
|
+
setDirect(cell, cell.state === CLEAN)
|
|
1488
|
+
// THE VALUE EDGE MOVED TOO WHERE THERE WAS NOTHING TO SERVE, whatever the gate
|
|
1489
|
+
// says about the payload — the same split `refused` makes, and for the same
|
|
1490
|
+
// reason. A read taken while this was unservable THREW, so a body that reached
|
|
1491
|
+
// it aborted: it is `CLEAN`, holds nothing, and nothing downstream will ever
|
|
1492
|
+
// pull it. Left silent, an `invalidate` whose reload landed the value it already
|
|
1493
|
+
// held stranded every value built from it at `ready()` false for the life of the
|
|
1494
|
+
// page. Over a value still being served nothing a reader can see moved (5.1) and
|
|
1495
|
+
// this stays quiet, which is the arm 5.2 is about.
|
|
1496
|
+
if (!wasServable) cascade(cell)
|
|
1497
|
+
if (settling) probesMoved(cell)
|
|
1498
|
+
return cell.ordinal
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
const moved = settleProbes(cell)
|
|
1502
|
+
// ONE CLOCK READ PER PRODUCTION, AND ONLY WHERE ONE WAS ASKED FOR. The `null` test is
|
|
1503
|
+
// a load and a compare; the `Date.now()` behind it is the part a value declaring no
|
|
1504
|
+
// `ttl` must not pay.
|
|
1505
|
+
const expiry = cell.expiry
|
|
1506
|
+
const mintedAt = expiry === null ? 0 : Date.now()
|
|
1507
|
+
// 6.10: AN `undefined` IS SERVED AND MOVES NO ORDINAL, so no cursor is handed one — it is not a
|
|
1508
|
+
// message, and not a JSON value either.
|
|
1509
|
+
if (produced !== undefined) cell.ordinal += 1
|
|
1510
|
+
if (expiry !== null) {
|
|
1511
|
+
expiry.at = mintedAt
|
|
1512
|
+
// A production is what 6.7 released being replaced, so the value is no longer
|
|
1513
|
+
// waiting for one (6.6). The timer is armed in `serve` rather than here, where
|
|
1514
|
+
// the value is not servable yet and the deadline it holds cannot be read.
|
|
1515
|
+
expiry.dropped = false
|
|
1516
|
+
}
|
|
1517
|
+
// A BODY'S CAP GATED THE RUN, NOT WHAT IT PRODUCED (5.11): the window has already
|
|
1518
|
+
// decided this run should happen, and holding its production too would delay it a
|
|
1519
|
+
// second window.
|
|
1520
|
+
if (cap !== null && cell.body === null)
|
|
1521
|
+
return capped(cell, cap, produced, moved)
|
|
1522
|
+
serve(cell, produced)
|
|
1523
|
+
cascade(cell)
|
|
1524
|
+
if (moved) probesMoved(cell)
|
|
1525
|
+
return cell.ordinal
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/**
|
|
1529
|
+
* Ends the load and the standing refusal; returns whether a probe moved.
|
|
1530
|
+
*
|
|
1531
|
+
* WHAT A PRODUCTION ENDS: the load in flight (`busy`) and a standing refusal (`error`, which 4.11
|
|
1532
|
+
* clears). What it hands back is whether a probe moved — either of those, or `ready`, there having
|
|
1533
|
+
* been nothing to serve — read BEFORE the change it describes, and it is the probe move the caller
|
|
1534
|
+
* owes. A plain write on a value already serving moves none of them, which is the common path.
|
|
1535
|
+
*/
|
|
1536
|
+
function settleProbes(cell: Cell): boolean {
|
|
1537
|
+
const moved = cell.loading || !cell.servable || cell.failure !== undefined
|
|
1538
|
+
cell.loading = false
|
|
1539
|
+
cell.failure = undefined
|
|
1540
|
+
return moved
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
/**
|
|
1544
|
+
* Makes a production what readers see: stores it, arms its deadline, persists it.
|
|
1545
|
+
*
|
|
1546
|
+
* THE READER-VISIBLE HALF OF A PRODUCTION, which is exactly what a window separates from
|
|
1547
|
+
* the production itself. One copy, reached from the uncapped write and from the release
|
|
1548
|
+
* alike — the alternative was a second spelling of the transition, which is the drift this
|
|
1549
|
+
* file has already paid for once.
|
|
1550
|
+
*/
|
|
1551
|
+
function serve(cell: Cell, value: unknown): void {
|
|
1552
|
+
cell.value = value
|
|
1553
|
+
cell.landed = true
|
|
1554
|
+
cell.servable = true
|
|
1555
|
+
// THE ONE PLACE `direct` IS SET TRUE ON A PRODUCTION, and it reads the state rather
|
|
1556
|
+
// than assuming it: a write can land on a value a mark walk has already reached, and
|
|
1557
|
+
// 11.8 has that write STAND without clearing the recompute owed to it.
|
|
1558
|
+
setDirect(cell, cell.state === CLEAN)
|
|
1559
|
+
// 6.3's ONE ARMING SITE, and it is here because this is the line that makes the value
|
|
1560
|
+
// servable — `armNext` reads the deadline off a production that has landed, and from
|
|
1561
|
+
// `produce` it would be reading one that had not.
|
|
1562
|
+
if (cell.expiry !== null) armNext(cell, cell.expiry)
|
|
1563
|
+
// 8.8: every production that reaches a reader is written, and nothing else is — a write the
|
|
1564
|
+
// identity gate collapsed never arrives here.
|
|
1565
|
+
if (cell.store !== null) persist(cell, cell.store, value)
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
/**
|
|
1569
|
+
* Whether a window holds this arrival, opening or pushing back the window either way.
|
|
1570
|
+
*
|
|
1571
|
+
* HOLD OR GO, for everything a window caps — a body's run (5.11), a refresh (5.24), a production
|
|
1572
|
+
* and a stream's chunks (5.17) — and the window is opened or pushed back either way. 5.12: a
|
|
1573
|
+
* throttle with no window open lets this one through and opens one, and re-arming inside an open
|
|
1574
|
+
* one would make it a debounce. 5.13: a debounce holds everything and every arrival pushes its
|
|
1575
|
+
* window back, so the release is what happens when they stop.
|
|
1576
|
+
*/
|
|
1577
|
+
function windowHolds(cell: Cell, cap: Cap): boolean {
|
|
1578
|
+
const open = cap.timer !== null
|
|
1579
|
+
if (!open || !cap.leading) openWindow(cell, cap)
|
|
1580
|
+
return open || !cap.leading
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/**
|
|
1584
|
+
* Whether an arrival passes the window; the first landing always does.
|
|
1585
|
+
*
|
|
1586
|
+
* 5.15: the first landing goes through, having nothing to collapse against — and a debounce's
|
|
1587
|
+
* window is not opened by it, where a throttle's is.
|
|
1588
|
+
*/
|
|
1589
|
+
function passes(cell: Cell, cap: Cap): boolean {
|
|
1590
|
+
if (cell.landed) return !windowHolds(cell, cap)
|
|
1591
|
+
if (cap.leading) openWindow(cell, cap)
|
|
1592
|
+
return true
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
/**
|
|
1596
|
+
* Whether a production wakes readers now; holds it for the window's close if not.
|
|
1597
|
+
*
|
|
1598
|
+
* A production's wake goes now or is held for the window's close — the pass-or-hold tail that a
|
|
1599
|
+
* write (`capped`) and a patch (5.23) share. The window moves `minted` apart from `value` either way.
|
|
1600
|
+
*/
|
|
1601
|
+
function wakes(cell: Cell, cap: Cap, produced: unknown): boolean {
|
|
1602
|
+
cap.minted = produced
|
|
1603
|
+
if (passes(cell, cap)) return true
|
|
1604
|
+
hold(cell, cap, produced)
|
|
1605
|
+
return false
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
/**
|
|
1609
|
+
* Whether a stream's readers are told of its chunks now, or at the window's close.
|
|
1610
|
+
*
|
|
1611
|
+
* A stream's chunks: the buffer already has them, so what the window holds is the telling (5.17).
|
|
1612
|
+
*/
|
|
1613
|
+
function tellsChunks(cell: Cell, cap: Cap | null): boolean {
|
|
1614
|
+
if (cap === null || passes(cell, cap)) return true
|
|
1615
|
+
cap.holding = true
|
|
1616
|
+
return false
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
/**
|
|
1620
|
+
* Serves a minted production now or holds it for the window; returns the ordinal.
|
|
1621
|
+
*
|
|
1622
|
+
* The production has minted; what is left is whether it reaches a reader now or waits for
|
|
1623
|
+
* the window. Behind its own frame because the uncapped write never arrives here.
|
|
1624
|
+
*/
|
|
1625
|
+
function capped(
|
|
1626
|
+
cell: Cell,
|
|
1627
|
+
cap: Cap,
|
|
1628
|
+
produced: unknown,
|
|
1629
|
+
moved: boolean,
|
|
1630
|
+
): number {
|
|
1631
|
+
// 5.15 CANNOT BE SATISFIED BY HOLDING THE FIRST LANDING: there is no held value to go
|
|
1632
|
+
// on serving, so a reader would get neither the value it had nor the one that arrived
|
|
1633
|
+
// — a `debounce` of 300 left a value not ready for 300 ms after construction. A window
|
|
1634
|
+
// collapses a RUN of changes and the first landing has nothing to collapse with.
|
|
1635
|
+
if (wakes(cell, cap, produced)) {
|
|
1636
|
+
serve(cell, produced)
|
|
1637
|
+
cascade(cell)
|
|
1638
|
+
if (moved) probesMoved(cell)
|
|
1639
|
+
}
|
|
1640
|
+
return cell.ordinal
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/**
|
|
1644
|
+
* Holds a production for the window's close and marks the value busy.
|
|
1645
|
+
*
|
|
1646
|
+
* HELD FOR THE WINDOW'S CLOSE, which `release` serves. 5.15: `loading` IS "work in flight", and a
|
|
1647
|
+
* window with something held is exactly that — so the probe answers from the field it already
|
|
1648
|
+
* reads rather than from a second one. `isBusy` is the leaf a spinner reads every frame and a load
|
|
1649
|
+
* it did not need was refused there once already.
|
|
1650
|
+
*/
|
|
1651
|
+
function hold(cell: Cell, cap: Cap, value: unknown): void {
|
|
1652
|
+
cap.held = value
|
|
1653
|
+
cap.holding = true
|
|
1654
|
+
cell.loading = true
|
|
1655
|
+
// Nothing a reader can see moved, and `busy` did.
|
|
1656
|
+
probesMoved(cell)
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
/**
|
|
1660
|
+
* Serves the held production as the window closes.
|
|
1661
|
+
*
|
|
1662
|
+
* The window closing, and the one wake per window that 5.12 and 5.13 each allow.
|
|
1663
|
+
*/
|
|
1664
|
+
function release(cell: Cell, cap: Cap): void {
|
|
1665
|
+
cap.holding = false
|
|
1666
|
+
const value = cap.held
|
|
1667
|
+
cap.held = undefined
|
|
1668
|
+
cell.loading = false
|
|
1669
|
+
serve(cell, value)
|
|
1670
|
+
cascade(cell)
|
|
1671
|
+
// The close is a `busy` edge (5.15) even though the value's readiness never moved.
|
|
1672
|
+
probesMoved(cell)
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
function releaseHeld(cell: Cell, cap: Cap): void {
|
|
1676
|
+
const stream = cell.stream
|
|
1677
|
+
if (stream !== null) {
|
|
1678
|
+
cap.holding = false
|
|
1679
|
+
releaseChunks(cell, stream)
|
|
1680
|
+
} else release(cell, cap)
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
function openWindow(cell: Cell, cap: Cap): void {
|
|
1684
|
+
// 5.13 NEEDS THIS: a debounce calls in here on every production inside its window, and
|
|
1685
|
+
// the reset is what "until the changes stop" means. Without it each write arms its own
|
|
1686
|
+
// timer and the first to fire releases — a fixed delay from the FIRST change, which
|
|
1687
|
+
// reads identically to a debounce under any run of writes made in one turn.
|
|
1688
|
+
if (cap.timer !== null) clearTimeout(cap.timer)
|
|
1689
|
+
// `armTimer` for 6.4's reason, which a window shares with a deadline: a timer never holds the
|
|
1690
|
+
// process open. The keyed window already armed through it, so the two agree.
|
|
1691
|
+
armTimer(cap, cap.ms, () => {
|
|
1692
|
+
cap.timer = null
|
|
1693
|
+
// 5.24: the reload the window held back, which is the run as well. A production held beside
|
|
1694
|
+
// it is served first — the reload re-reads a store that production has not been written to
|
|
1695
|
+
// yet, and skipping it dropped the write and left `holding` standing.
|
|
1696
|
+
if (cap.reloading) {
|
|
1697
|
+
cap.reloading = false
|
|
1698
|
+
cap.owed = false
|
|
1699
|
+
if (cap.holding) releaseHeld(cell, cap)
|
|
1700
|
+
reloadNow(cell)
|
|
1701
|
+
if (cap.leading) openWindow(cell, cap)
|
|
1702
|
+
return
|
|
1703
|
+
}
|
|
1704
|
+
// 5.11: the run the window held back. Marking and cascading is all it does — the
|
|
1705
|
+
// body is pulled forward by the read that wants it, which is the same laziness
|
|
1706
|
+
// every other recompute gets.
|
|
1707
|
+
if (cap.owed) {
|
|
1708
|
+
cap.owed = false
|
|
1709
|
+
cell.state = DIRTY
|
|
1710
|
+
cell.direct = false
|
|
1711
|
+
cascade(cell)
|
|
1712
|
+
return
|
|
1713
|
+
}
|
|
1714
|
+
// A throttle's window ending with nothing held is the window simply over: the
|
|
1715
|
+
// next write leads again. With something held it releases and opens another, so a
|
|
1716
|
+
// run of writes wakes once per window rather than twice at every boundary.
|
|
1717
|
+
if (!cap.holding) return
|
|
1718
|
+
// A STREAM'S WINDOW HOLDS CHUNKS rather than a production, and closing it tells the readers
|
|
1719
|
+
// what landed meanwhile. Branched HERE, in the timer, rather than inside `release`: a stream
|
|
1720
|
+
// call reachable from `release` sits on the path every capped write walks through `capped`,
|
|
1721
|
+
// and measured 8.4 ns -> 10.5 ns on a capped write under V8 with no stream in it.
|
|
1722
|
+
releaseHeld(cell, cap)
|
|
1723
|
+
// A write made by one of the effects this release woke has already opened the
|
|
1724
|
+
// next window, and opening a second here would leave `cap.timer` pointing at this
|
|
1725
|
+
// one while that one goes on to fire and clear it.
|
|
1726
|
+
if (cap.leading) openWindow(cell, cap)
|
|
1727
|
+
})
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
/**
|
|
1731
|
+
* Gates a value through `schema` and `transform`, then produces it.
|
|
1732
|
+
*
|
|
1733
|
+
* THE WAY IN, AND THE ORDER IS 4.3 THEN 4.4: the schema gates, and what it RETURNS is
|
|
1734
|
+
* what `transform` receives — not the original value. A transform reading the raw input
|
|
1735
|
+
* where a schema had normalised it is a second answer to what this value holds.
|
|
1736
|
+
* What it hands back is the ordinal, or the refusal a gate answered with (4.18) — `unknown`
|
|
1737
|
+
* because the one caller that cares tells the two apart by type.
|
|
1738
|
+
*/
|
|
1739
|
+
function accept(cell: Cell, value: unknown): unknown {
|
|
1740
|
+
let checked = value
|
|
1741
|
+
if (cell.schema !== null) {
|
|
1742
|
+
// 4.5: THE THROW IS CAUGHT AND CONVERTED rather than escaping the write. A schema
|
|
1743
|
+
// that threw past this would take down the caller of `s.set`, which is a template
|
|
1744
|
+
// expression or an event handler — 4.10 has a refused write fill `s.error`, and
|
|
1745
|
+
// nothing about a refusal is exceptional enough to unwind an app for.
|
|
1746
|
+
const answer = runSchema(cell.schema, value)
|
|
1747
|
+
// 15.8: a schema that refused is a `ValidationError` at 422 — the request parsed
|
|
1748
|
+
// and its contents are wrong, which is exactly what a schema that RAN found out.
|
|
1749
|
+
if (answer.issues !== null)
|
|
1750
|
+
return refused(cell, validationError<Found>(answer.issues))
|
|
1751
|
+
checked = answer.value
|
|
1752
|
+
}
|
|
1753
|
+
const transform = cell.transform
|
|
1754
|
+
if (transform === null) return produce(cell, checked)
|
|
1755
|
+
// 4.7 — A `transform` RUNS UNTRACKED: a write can be made from inside a tracked body
|
|
1756
|
+
// (`watch(() => target.set(…))`, a recompute that writes), and run tracked, the transform's
|
|
1757
|
+
// reads were attributed to that reader — which then re-ran on a value it never read, with
|
|
1758
|
+
// those reads spliced into its cursor-ordered `sources`.
|
|
1759
|
+
const shaped = untracked(transform, checked, undefined)
|
|
1760
|
+
// 4.6 AND 4.9: a `transform` that returned a refusal stops the write here — nothing is
|
|
1761
|
+
// stored, no production is minted, and the ordinal does not move. The test is behind
|
|
1762
|
+
// the `transform === null` arm, so a value that declares none never reaches it.
|
|
1763
|
+
if (isFailed(shaped)) return refused(cell, shaped)
|
|
1764
|
+
return produce(cell, shaped)
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
/**
|
|
1768
|
+
* `accept` for a stream: the same two gates, then `standWrite`.
|
|
1769
|
+
*
|
|
1770
|
+
* `accept` FOR A STREAM, AND A SECOND COPY OF THE GATES ON PURPOSE. The branch to `standWrite`
|
|
1771
|
+
* measured 8.4 ns -> 10.9 ns on a capped write under V8 wherever it sat on a value's write
|
|
1772
|
+
* path — in `accept`, at the top of `produce`, in the write member — though no
|
|
1773
|
+
* value ever took it; a bare read of `cell.stream` in the same place cost nothing. So a stream's
|
|
1774
|
+
* writes are reached by a prototype of their own (`STREAM_PROTOTYPE`) and never share a call
|
|
1775
|
+
* site with a value's. The copy is the two gates in 4.3's order, and a change to either is a
|
|
1776
|
+
* change to both.
|
|
1777
|
+
*/
|
|
1778
|
+
function acceptIntoStream(cell: Cell, stream: Stream, value: unknown): unknown {
|
|
1779
|
+
let checked = value
|
|
1780
|
+
if (cell.schema !== null) {
|
|
1781
|
+
const answer = runSchema(cell.schema, value)
|
|
1782
|
+
if (answer.issues !== null)
|
|
1783
|
+
return refused(cell, validationError<Found>(answer.issues))
|
|
1784
|
+
checked = answer.value
|
|
1785
|
+
}
|
|
1786
|
+
const transform = cell.transform
|
|
1787
|
+
let shaped = checked
|
|
1788
|
+
if (transform !== null) {
|
|
1789
|
+
shaped = untracked(transform, checked, undefined)
|
|
1790
|
+
if (isFailed(shaped)) return refused(cell, shaped)
|
|
1791
|
+
}
|
|
1792
|
+
return standWrite(cell, stream, shaped)
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
/**
|
|
1796
|
+
* A write to a stream, from its own `set`. A load handed to one lands through the
|
|
1797
|
+
* same gates when it settles, and a newer write supersedes it the way it would on a value.
|
|
1798
|
+
*/
|
|
1799
|
+
function writeStream(cell: Cell, value: unknown): unknown {
|
|
1800
|
+
cell.generation += 1
|
|
1801
|
+
// 9.11: a write replaces the write standing, and one following a `Reactive` lets go of it.
|
|
1802
|
+
if (cell.adoption !== null) unfollow(cell)
|
|
1803
|
+
if (isThenable(value) && !isReactive(value)) {
|
|
1804
|
+
const generation = cell.generation
|
|
1805
|
+
value.then(
|
|
1806
|
+
(settled) => {
|
|
1807
|
+
if (cell.generation === generation)
|
|
1808
|
+
acceptIntoStream(cell, cell.stream as Stream, settled)
|
|
1809
|
+
},
|
|
1810
|
+
(failed: unknown) => {
|
|
1811
|
+
if (cell.generation === generation) refused(cell, failed)
|
|
1812
|
+
},
|
|
1813
|
+
)
|
|
1814
|
+
return undefined
|
|
1815
|
+
}
|
|
1816
|
+
const answer = acceptIntoStream(cell, cell.stream as Stream, value)
|
|
1817
|
+
return typeof answer === 'number' ? undefined : answer
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
/**
|
|
1821
|
+
* Stands a write over a stream as its value, without minting an ordinal.
|
|
1822
|
+
*
|
|
1823
|
+
* 9.11 AND 9.12: A WRITE OVER A STREAM IS A VALUE, NOT A CHUNK. It stands at the version of the
|
|
1824
|
+
* accumulation it was made over, and the first chunk or patch that moves the version past it
|
|
1825
|
+
* puts the accumulation back on the next read — and it mints no ordinal, which is what a cursor
|
|
1826
|
+
* holds its place by: `ring[i]` is ordinal `ringBase + i + 1`, and an ordinal with nothing in the
|
|
1827
|
+
* ring behind it handed the cursor `undefined`.
|
|
1828
|
+
*/
|
|
1829
|
+
function standWrite(cell: Cell, stream: Stream, value: unknown): number {
|
|
1830
|
+
// 7.9, as `produce` settles it.
|
|
1831
|
+
if (cell.owes === OWES_RELOAD) cell.owes = OWES_NOTHING
|
|
1832
|
+
// 5.2 against what is served now, which on a stream may not have been materialised yet.
|
|
1833
|
+
if (cell.servable) {
|
|
1834
|
+
const previous = heldValue(cell)
|
|
1835
|
+
if (
|
|
1836
|
+
cell.identityFn === null
|
|
1837
|
+
? Object.is(value, previous)
|
|
1838
|
+
: cell.identityFn(value, previous)
|
|
1839
|
+
)
|
|
1840
|
+
return cell.ordinal
|
|
1841
|
+
}
|
|
1842
|
+
cell.value = value
|
|
1843
|
+
stream.materialisedAt = stream.version
|
|
1844
|
+
stream.written = true
|
|
1845
|
+
const moved = !cell.servable || cell.failure !== undefined
|
|
1846
|
+
landChunks(cell)
|
|
1847
|
+
cascade(cell)
|
|
1848
|
+
if (moved) probesMoved(cell)
|
|
1849
|
+
return cell.ordinal
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
/**
|
|
1853
|
+
* Leaves a failure standing as `s.error`; returns it.
|
|
1854
|
+
*
|
|
1855
|
+
* 4.10: WHAT A REFUSED WRITE LEAVES BEHIND is the standing `s.error`, and this is the one
|
|
1856
|
+
* statement of it — the rejected-load arm in `absorb` reached the same five lines and a
|
|
1857
|
+
* second copy is the drift this file is arranged against.
|
|
1858
|
+
*
|
|
1859
|
+
* ONE STATEMENT FOR EVERY PRODUCER: a refused write, a rejected load, and a body that threw or
|
|
1860
|
+
* returned a `Failed` in its own run all land here.
|
|
1861
|
+
*
|
|
1862
|
+
* WITH NOTHING SERVED THE FAILURE HAS TO PROPAGATE, AND 4.12 IS THE CASE THAT MUST NOT. A read
|
|
1863
|
+
* of an unservable value throws, so a reader parked on that throw — a `then` waiter, a body that
|
|
1864
|
+
* aborted — is answered by this failure or by nothing, and it opens a cascade like a production.
|
|
1865
|
+
* `servable` rather than `landed`: an invalidated value has landed and is unserved (7.8), and
|
|
1866
|
+
* testing `landed` left the reader its unserve woke parked on `NotLanded` for the life of the
|
|
1867
|
+
* value. A refusal over a value still being served changes nothing a reader can see, and waking
|
|
1868
|
+
* there would be a wake on an unchanged value (5.1).
|
|
1869
|
+
*/
|
|
1870
|
+
function refused(cell: Cell, failure: unknown): unknown {
|
|
1871
|
+
cell.loading = false
|
|
1872
|
+
cell.failure = failure
|
|
1873
|
+
// BOTH PROBES MOVED AND NEITHER IS ON THE VALUE EDGE: `busy` went false where the load
|
|
1874
|
+
// had set it true, and `error` went from nothing to this. A guard that asked about
|
|
1875
|
+
// either is woken here or never.
|
|
1876
|
+
probesMoved(cell)
|
|
1877
|
+
if (!cell.servable) cascade(cell)
|
|
1878
|
+
return failure
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
/**
|
|
1882
|
+
* Takes in a write or a body's answer, waiting on it first where it is a load.
|
|
1883
|
+
*
|
|
1884
|
+
* A load. 4.2: a load given to a write makes the value loaded, at construction and at
|
|
1885
|
+
* every write alike. `generation` is what makes a stale settle a no-op — a refresh
|
|
1886
|
+
* landing after an invalidate must not overwrite the newer run's answer.
|
|
1887
|
+
*
|
|
1888
|
+
* `answered` IS A BODY'S ANSWER, and 15.5 has one that settles to a `Failed` refuse. A write that
|
|
1889
|
+
* settles to one is not an answer, and 4.17 stores it as it would a settled one.
|
|
1890
|
+
*/
|
|
1891
|
+
function absorb(cell: Cell, result: unknown, answered: boolean): void {
|
|
1892
|
+
// THE BRAND IS READ BEFORE `then` (RULEBOOK 1.6). A `Reactive` is thenable by 1.4,
|
|
1893
|
+
// so a guard written the other way round settles a live value to one snapshot — a write
|
|
1894
|
+
// of `other` would hand back `other`'s current value instead of `other`.
|
|
1895
|
+
if (!isThenable(result) || isReactive(result)) {
|
|
1896
|
+
accept(cell, result)
|
|
1897
|
+
return
|
|
1898
|
+
}
|
|
1899
|
+
const generation = cell.generation
|
|
1900
|
+
// A PROBE MOVES WITHOUT A PRODUCTION — 11.22 derives a propagated probe on the read that
|
|
1901
|
+
// asks for it rather than pushing it. Going loading
|
|
1902
|
+
// changes what every reader downstream reports and mints no value for a cascade to
|
|
1903
|
+
// carry, so the flip opens one itself.
|
|
1904
|
+
beginLoad(cell)
|
|
1905
|
+
;(result as PromiseLike<unknown>).then(
|
|
1906
|
+
(settled) => {
|
|
1907
|
+
if (cell.generation !== generation) return
|
|
1908
|
+
if (answered && isFailed(settled)) refused(cell, settled)
|
|
1909
|
+
// 9.6 — a run's promise resolving to an async generator hands one back too. Its
|
|
1910
|
+
// probes move so `streamingOrSettled` hears the value turn.
|
|
1911
|
+
else if (isAsyncGenerator(settled)) {
|
|
1912
|
+
promoteToStream(cell, settled)
|
|
1913
|
+
probesMoved(cell)
|
|
1914
|
+
} else accept(cell, settled)
|
|
1915
|
+
},
|
|
1916
|
+
(failed) => {
|
|
1917
|
+
if (cell.generation !== generation) return
|
|
1918
|
+
refused(cell, failed)
|
|
1919
|
+
},
|
|
1920
|
+
)
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
/**
|
|
1924
|
+
* Makes a value mirror another; returns the refusal where that would close a cycle.
|
|
1925
|
+
*
|
|
1926
|
+
* ADOPTION. RULEBOOK 11.12: a `Reactive` a body hands back is ADOPTED, and is not stored
|
|
1927
|
+
* as a value — the outer value serves what the inner one serves and goes on serving it as
|
|
1928
|
+
* the inner produces. A `Reactive` the app wants to hold as data goes inside a record —
|
|
1929
|
+
* `reactive({ inner })` — where nothing mistakes it for a production.
|
|
1930
|
+
*
|
|
1931
|
+
* IT IS A `watch` OVER THE INNER VALUE, not a third subscriber mechanism: 12.3's narrowed
|
|
1932
|
+
* form is exactly "run this when that one changes", which is what mirroring is.
|
|
1933
|
+
*
|
|
1934
|
+
* 11.65 — THE ONE PLACE `adoption` IS SET, so it is the one place a cycle can form, and refusing
|
|
1935
|
+
* it here is what lets every walk of the chain be a plain loop to its end. The walk is over the
|
|
1936
|
+
* inner's chain, which cannot cycle for the same reason, and it runs per adoption rather than per
|
|
1937
|
+
* read. The refusal is handed back rather than thrown: 11.66 has the two callers answer it
|
|
1938
|
+
* differently.
|
|
1939
|
+
*/
|
|
1940
|
+
function adopt(cell: Cell, inner: Cell): Error | undefined {
|
|
1941
|
+
const standing = cell.adoption
|
|
1942
|
+
if (standing !== null && standing.source === inner) {
|
|
1943
|
+
// 11.18: the same one again is not a change, and re-subscribing would mint a
|
|
1944
|
+
// production out of a recompute that decided nothing. BUT A TRIGGER CAN HAVE
|
|
1945
|
+
// LEFT THIS VALUE LOADING OR WITH NOTHING TO SERVE, and nothing but a production
|
|
1946
|
+
// of the inner would ever settle it — which an inner with no producer never
|
|
1947
|
+
// makes. So the mirror runs again where there is something to settle, and the
|
|
1948
|
+
// identity gate is what keeps it waking nobody where the inner did not move.
|
|
1949
|
+
if (cell.loading || !cell.servable) mirror(cell, inner)
|
|
1950
|
+
return undefined
|
|
1951
|
+
}
|
|
1952
|
+
// The same source again was checked when it was first adopted, so only a new one is walked.
|
|
1953
|
+
const cycle = closesCycle(cell, inner)
|
|
1954
|
+
if (cycle !== undefined) return cycle
|
|
1955
|
+
// 11.19 MOVES WITH THE ADOPTION ITSELF, and no production carries that move. An adopter with
|
|
1956
|
+
// nothing to serve — `reactive()` before a `paid = payInvoice(…)` — re-adopted onto a call in
|
|
1957
|
+
// flight goes busy while the mirror's `unserve` finds nothing to lose and wakes no guard, so
|
|
1958
|
+
// `{paid.busy() ? 'paying…' : ''}` never painted the load it was asking about.
|
|
1959
|
+
const wasBusy = standing !== null && busyNow(standing.source)
|
|
1960
|
+
if (standing !== null) standing.stop()
|
|
1961
|
+
// 11.17: the adopted value's `identity` is inherited where the outer declared none —
|
|
1962
|
+
// THIS inner's, so a re-adoption onto another one does not keep the first one's.
|
|
1963
|
+
// Assigned before the first mirror, or the production that mirror makes is gated by
|
|
1964
|
+
// the wrong comparison.
|
|
1965
|
+
cell.identityFn = cell.declaredIdentityFn ?? inner.identityFn
|
|
1966
|
+
simpleShape(cell)
|
|
1967
|
+
const stop = watchOver([inner], cell, (): undefined => {
|
|
1968
|
+
mirror(cell, inner)
|
|
1969
|
+
return undefined
|
|
1970
|
+
})
|
|
1971
|
+
cell.adoption = { source: inner, stop }
|
|
1972
|
+
// 11.59: an adopting value's writes are the adopted value's, reached through a prototype of its
|
|
1973
|
+
// own rather than a branch on every value's write path — see `acceptIntoStream` for what such a
|
|
1974
|
+
// branch costs under V8 even where nothing takes it.
|
|
1975
|
+
Object.setPrototypeOf(cell.callable as object, ADOPTING_PROTOTYPE)
|
|
1976
|
+
if (!cell.loading && busyNow(inner) !== wasBusy) probesMoved(cell)
|
|
1977
|
+
return undefined
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
/** The refusal where adopting `inner` would close a cycle through `cell` (11.65). */
|
|
1981
|
+
function closesCycle(cell: Cell, inner: Cell): Error | undefined {
|
|
1982
|
+
for (
|
|
1983
|
+
let at: Cell | undefined = inner;
|
|
1984
|
+
at !== undefined;
|
|
1985
|
+
at = at.adoption?.source
|
|
1986
|
+
)
|
|
1987
|
+
if (at === cell)
|
|
1988
|
+
return new Error(
|
|
1989
|
+
'An adoption would close a cycle: these values would adopt each other, so no one of them holds the value. RULEBOOK 11.65.',
|
|
1990
|
+
)
|
|
1991
|
+
return undefined
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
/**
|
|
1995
|
+
* Stands a `Reactive` written over a stream as a write that follows it.
|
|
1996
|
+
*
|
|
1997
|
+
* 11.61 ON A STREAM IS 9.11'S WRITE, FOLLOWING, and it is the standing write rather than a second
|
|
1998
|
+
* mechanism: `written` over the version it was made at, with `value` re-landed through `standWrite`
|
|
1999
|
+
* each time the followed value moves, so it mints no ordinal (9.12). What it follows is held on
|
|
2000
|
+
* `adoption`, which is what the probes already walk (11.19, 11.50) and what `markProbes` reaches
|
|
2001
|
+
* through the mirror. The stream keeps its own prototype — a later write is a write over the stream
|
|
2002
|
+
* and replaces this one, where 11.59 would have handed it on — and `adoptedCellOf` stops here, so a
|
|
2003
|
+
* cursor and a replay are the stream's.
|
|
2004
|
+
*/
|
|
2005
|
+
function follow(cell: Cell, stream: Stream, inner: Cell): Error | undefined {
|
|
2006
|
+
const standing = cell.adoption
|
|
2007
|
+
// 11.18: the same one again is not a change.
|
|
2008
|
+
if (standing !== null && standing.source === inner) return undefined
|
|
2009
|
+
const cycle = closesCycle(cell, inner)
|
|
2010
|
+
if (cycle !== undefined) return cycle
|
|
2011
|
+
// A write supersedes a load a previous write left in flight.
|
|
2012
|
+
cell.generation += 1
|
|
2013
|
+
// 11.19 as `adopt` has it: following a call in flight is a busy move nothing mints.
|
|
2014
|
+
const wasBusy = standing !== null && busyNow(standing.source)
|
|
2015
|
+
if (standing !== null) unfollow(cell)
|
|
2016
|
+
// What `standWrite`'s identity gate compares against is what is served now.
|
|
2017
|
+
heldValue(cell)
|
|
2018
|
+
stream.materialisedAt = stream.version
|
|
2019
|
+
stream.written = true
|
|
2020
|
+
const stop = watchOver([inner], cell, (): undefined => {
|
|
2021
|
+
// The accumulation moved past the write. The drop is `wakeStreams`'s or the read's, not
|
|
2022
|
+
// this one's: a mirror runs inside the mark walk over `inner.readers`, and detaching here
|
|
2023
|
+
// would splice the list that walk is iterating.
|
|
2024
|
+
if (!stream.written || stream.materialisedAt !== stream.version)
|
|
2025
|
+
return undefined
|
|
2026
|
+
if (inner.owes !== OWES_NOTHING) settleOwed(inner)
|
|
2027
|
+
// 11.16: the followed value has nothing to serve, so neither has this.
|
|
2028
|
+
if (!inner.servable) unserve(cell)
|
|
2029
|
+
else acceptIntoStream(cell, stream, heldValue(inner))
|
|
2030
|
+
return undefined
|
|
2031
|
+
})
|
|
2032
|
+
cell.adoption = { source: inner, stop }
|
|
2033
|
+
if (!cell.loading && busyNow(inner) !== wasBusy) probesMoved(cell)
|
|
2034
|
+
return undefined
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
/** Lets go of the `Reactive` a stream's standing write follows. */
|
|
2038
|
+
function unfollow(cell: Cell): void {
|
|
2039
|
+
const standing = cell.adoption as Adoption
|
|
2040
|
+
cell.adoption = null
|
|
2041
|
+
standing.stop()
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
/**
|
|
2045
|
+
* Stops serving the value, waking both its readers and its guards.
|
|
2046
|
+
*
|
|
2047
|
+
* LOSING WHAT THERE WAS TO SERVE, AND BOTH EDGES HEAR IT, whichever way it was lost: a trigger's
|
|
2048
|
+
* debt settling (7.8), a re-adoption onto nothing (11.16), or a run aborting (11.58). The readers
|
|
2049
|
+
* hear it through `cascade` and the GUARDS through `probesMoved`. With only the first,
|
|
2050
|
+
* `{#if outer.ready()}{outer()}{:else}spinner{/if}` re-pointed onto an inner with nothing to serve
|
|
2051
|
+
* stays on the value branch permanently while the read under it throws, the inner never having
|
|
2052
|
+
* moved a probe of its own.
|
|
2053
|
+
*/
|
|
2054
|
+
function unserve(cell: Cell): void {
|
|
2055
|
+
if (!cell.servable) return
|
|
2056
|
+
cell.servable = false
|
|
2057
|
+
cell.direct = false
|
|
2058
|
+
cascade(cell)
|
|
2059
|
+
probesMoved(cell)
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
function mirror(cell: Cell, inner: Cell): void {
|
|
2063
|
+
// 7.13 through the adoption: the re-settle a re-run of the adopter makes reaches a reload the
|
|
2064
|
+
// adopted value owes, rather than finding it unservable and giving up.
|
|
2065
|
+
if (inner.owes !== OWES_NOTHING) settleOwed(inner)
|
|
2066
|
+
const adopted = inner.stream
|
|
2067
|
+
if (adopted !== null) {
|
|
2068
|
+
forwardStream(cell, inner, adopted)
|
|
2069
|
+
return
|
|
2070
|
+
}
|
|
2071
|
+
if (cell.stream !== null) stopForwarding(cell)
|
|
2072
|
+
if (!inner.servable) {
|
|
2073
|
+
// 11.16: the newly adopted has nothing to serve, so neither has this.
|
|
2074
|
+
unserve(cell)
|
|
2075
|
+
return
|
|
2076
|
+
}
|
|
2077
|
+
const value = heldValue(inner)
|
|
2078
|
+
// 11.13 FOR A PATCH (5.7): the inner woke holding the reference this already serves. Only a patch
|
|
2079
|
+
// does that — a production of the same reference is stopped at the inner's own identity gate —
|
|
2080
|
+
// and the gate `accept` runs here would read it as no change at all, so it wakes as a patch.
|
|
2081
|
+
if (cell.servable && !cell.loading && value === heldValue(cell)) {
|
|
2082
|
+
wakePatched(cell, value)
|
|
2083
|
+
return
|
|
2084
|
+
}
|
|
2085
|
+
// 11.13 mirrors the production into the outer value, and 11.14 runs the OUTER's own
|
|
2086
|
+
// `transform` on the way in — which is `accept`, and is why this does not call
|
|
2087
|
+
// `produce` directly.
|
|
2088
|
+
accept(cell, value)
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
/**
|
|
2092
|
+
* Stops mirroring the adopted value and restores the non-adopting prototype.
|
|
2093
|
+
*
|
|
2094
|
+
* 11.12 governs what a body HANDS BACK, so a run that hands back something else is no
|
|
2095
|
+
* longer adopting. Without this the mirror outlives the recompute that ended it: a body
|
|
2096
|
+
* reading `flag() ? inner : 99` goes on serving 99 until `inner` produces, and then
|
|
2097
|
+
* serves what `inner` produced — a value its own body never computed. The probes read
|
|
2098
|
+
* through `adoption` too, so a stale one is also a floor taken from a value nothing reads
|
|
2099
|
+
* any more.
|
|
2100
|
+
*/
|
|
2101
|
+
function dropAdoption(cell: Cell): void {
|
|
2102
|
+
if (cell.adoption === null) return
|
|
2103
|
+
unfollow(cell)
|
|
2104
|
+
const stream = cell.stream
|
|
2105
|
+
// 9.11: AN OWN STREAM'S `adoption` IS A WRITE IT FOLLOWS, not a stream it forwards — the
|
|
2106
|
+
// discriminator `adoptedCellOf` reads. Letting go of the write is all there is: the stream is
|
|
2107
|
+
// its own, so is its prototype, and `follow` never touched `identityFn`. Stopping it as a
|
|
2108
|
+
// forward nulled the stream a queued `wakeStreams` was about to read.
|
|
2109
|
+
if (stream !== null && stream.source === null) return
|
|
2110
|
+
if (stream !== null) stopForwarding(cell)
|
|
2111
|
+
// 11.17 inherited the identity for the length of the adoption only.
|
|
2112
|
+
cell.identityFn = cell.declaredIdentityFn
|
|
2113
|
+
simpleShape(cell)
|
|
2114
|
+
Object.setPrototypeOf(
|
|
2115
|
+
cell.callable as object,
|
|
2116
|
+
cell.simple ? SIMPLE_PROTOTYPE : MEMBER_PROTOTYPE,
|
|
2117
|
+
)
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
/**
|
|
2121
|
+
* Makes an adopter forward an adopted stream's buffer, ring and ordinals.
|
|
2122
|
+
*
|
|
2123
|
+
* 11.15 FOR A STREAM: the adopter takes the adopted value's buffer, ring and ordinals by
|
|
2124
|
+
* reference and materialises from them on its own reads, through its own gates (11.14, 9.8).
|
|
2125
|
+
* Mirroring the value instead copied the accumulation once per wake whether or not anything
|
|
2126
|
+
* read it, and handed a cursor over the adopter whole accumulations where it wanted chunks.
|
|
2127
|
+
*/
|
|
2128
|
+
function forwardStream(cell: Cell, inner: Cell, adopted: Stream): void {
|
|
2129
|
+
let stream = cell.stream
|
|
2130
|
+
if (stream === null) {
|
|
2131
|
+
stream = becomeStream(cell, inner)
|
|
2132
|
+
stream.ordinalOffset = cell.ordinal - inner.ringBase
|
|
2133
|
+
}
|
|
2134
|
+
// The adopted version is this one's: a chunk or a patch of the stream moves both, and a write
|
|
2135
|
+
// made on this value is made on the stream (11.59).
|
|
2136
|
+
stream.chunks = adopted.chunks
|
|
2137
|
+
stream.version = adopted.version
|
|
2138
|
+
stream.visible = adopted.visible
|
|
2139
|
+
if (adopted.written && adopted.materialisedAt === adopted.version) {
|
|
2140
|
+
stream.materialisedAt = stream.version
|
|
2141
|
+
stream.written = true
|
|
2142
|
+
gateInto(cell, inner.value)
|
|
2143
|
+
}
|
|
2144
|
+
cell.ring = inner.ring
|
|
2145
|
+
cell.ringBase = inner.ringBase + stream.ordinalOffset
|
|
2146
|
+
cell.ordinal = inner.ordinal + stream.ordinalOffset
|
|
2147
|
+
if (!inner.servable) {
|
|
2148
|
+
// 11.16, as for a value: the adopted has nothing to serve, so neither has this.
|
|
2149
|
+
unserve(cell)
|
|
2150
|
+
return
|
|
2151
|
+
}
|
|
2152
|
+
// The adopter's OWN load — the body run that handed the stream back — is over. `busy` goes
|
|
2153
|
+
// on answering through the adoption (11.19) for as long as the stream is open.
|
|
2154
|
+
const moved = settleProbes(cell)
|
|
2155
|
+
landChunks(cell)
|
|
2156
|
+
cascade(cell)
|
|
2157
|
+
if (moved) probesMoved(cell)
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
function stopForwarding(cell: Cell): void {
|
|
2161
|
+
cell.stream = null
|
|
2162
|
+
cell.plain = cell.expiry === null
|
|
2163
|
+
cell.bareWrite = cell.schema === null && cell.transform === null
|
|
2164
|
+
simpleShape(cell)
|
|
2165
|
+
cell.ring = EMPTY_RING
|
|
2166
|
+
// The ring is empty and the next production is `ordinal + 1`, at index zero.
|
|
2167
|
+
cell.ringBase = cell.ordinal
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
/**
|
|
2171
|
+
* Runs a value's body, tracking its reads; abandons the run where a read had not landed.
|
|
2172
|
+
*
|
|
2173
|
+
* THE ABORT. A body that reads a value which has not landed throws `NotLanded`, and
|
|
2174
|
+
* this catches it and returns — the body is ABANDONED, not retried in place. The read
|
|
2175
|
+
* that threw has already registered this cell as a reader of it (tracking happens
|
|
2176
|
+
* before the throw), so the source landing is what re-runs the body.
|
|
2177
|
+
*
|
|
2178
|
+
* "Throw a sentinel and re-run" was refused once on three grounds. Two are answered
|
|
2179
|
+
* by the reach starting the work (1.7) — the loads are in flight before the body
|
|
2180
|
+
* runs, so the abort costs a re-entry rather than a round trip. The third, that
|
|
2181
|
+
* nothing on a server wakes an abandoned body, is answered by the sink rather than
|
|
2182
|
+
* by a subscriber.
|
|
2183
|
+
*/
|
|
2184
|
+
function run(cell: Cell): void {
|
|
2185
|
+
const body = cell.body
|
|
2186
|
+
if (body === null) return
|
|
2187
|
+
// A body cannot meaningfully re-enter itself, and a cycle in the graph is the case
|
|
2188
|
+
// where the cascade would try: `a` reads `b` and `b` reads `a`. Without this the
|
|
2189
|
+
// recursion is unbounded and the failure is a stack overflow rather than a value.
|
|
2190
|
+
if (cell.running) return
|
|
2191
|
+
cell.generation += 1
|
|
2192
|
+
cell.state = CLEAN
|
|
2193
|
+
cell.running = true
|
|
2194
|
+
const reader = cell as unknown as Subscriber
|
|
2195
|
+
// No closure and no `withReader` frame: a run allocated one closure per recompute,
|
|
2196
|
+
// and a recompute is per write on every derived value in the graph.
|
|
2197
|
+
const previous = TRACKING.reader
|
|
2198
|
+
TRACKING.reader = reader
|
|
2199
|
+
// THE RUN'S CURSOR, spelled out rather than behind a call frame on a path walked once
|
|
2200
|
+
// per node per write. Zero is also "has diverged nowhere yet": see `tracking.ts`.
|
|
2201
|
+
reader.cursor = 0
|
|
2202
|
+
// The probe edge is rebuilt rather than cursored: a guard that stops being taken has
|
|
2203
|
+
// to stop waking, and a body reads few probes where it reads many values.
|
|
2204
|
+
if (reader.probeSources.length !== 0) detachProbes(reader)
|
|
2205
|
+
let produced: unknown
|
|
2206
|
+
try {
|
|
2207
|
+
// 11.32 — UNDER THE VALUE'S OWN SCOPE, not the ambient one. A drain runs every body a
|
|
2208
|
+
// turn's writes woke, and the turn is whichever request scheduled it — so without this a
|
|
2209
|
+
// body filed under one request reads the values of another.
|
|
2210
|
+
produced = runInScope(cell.scope, body, cell.args)
|
|
2211
|
+
} catch (thrown) {
|
|
2212
|
+
TRACKING.reader = previous
|
|
2213
|
+
if (reader.cursor < reader.sources.length) truncate(reader)
|
|
2214
|
+
cell.running = false
|
|
2215
|
+
if (thrown instanceof NotLanded) {
|
|
2216
|
+
cell.loading = true
|
|
2217
|
+
// 2.13: the mark a source's production makes is what finishes this run, and
|
|
2218
|
+
// `markReaders` queues it rather than leaving it for a read that may never come.
|
|
2219
|
+
cell.aborted = true
|
|
2220
|
+
// NOT EVERYTHING IT READS IS READY, so neither is this — 11.58, and the same
|
|
2221
|
+
// sentence 11.16 makes for a re-adoption. It stops serving rather than handing
|
|
2222
|
+
// back a figure it computed from a source that has since gone away, and that
|
|
2223
|
+
// is a move its readers have to hear even though the aborted run minted
|
|
2224
|
+
// nothing for a cascade to carry.
|
|
2225
|
+
if (cell.servable) unserve(cell)
|
|
2226
|
+
else probesMoved(cell)
|
|
2227
|
+
return
|
|
2228
|
+
}
|
|
2229
|
+
refused(cell, thrown)
|
|
2230
|
+
return
|
|
2231
|
+
}
|
|
2232
|
+
TRACKING.reader = previous
|
|
2233
|
+
cell.aborted = false
|
|
2234
|
+
// A body that read FEWER values than last time leaves a tail nothing reads, and
|
|
2235
|
+
// that is the only thing the truncation has to do — so the test is here and the call
|
|
2236
|
+
// is the rare arm.
|
|
2237
|
+
if (reader.cursor < reader.sources.length) truncate(reader)
|
|
2238
|
+
cell.running = false
|
|
2239
|
+
// A VALUE THAT WAS ADOPTING AND IS NOT ANY MORE. Behind the `adoption` load so every
|
|
2240
|
+
// other recompute pays one field test rather than the brand read below it.
|
|
2241
|
+
if (cell.adoption !== null) {
|
|
2242
|
+
if (isReactive(produced)) {
|
|
2243
|
+
adoptAnswer(cell, produced)
|
|
2244
|
+
return
|
|
2245
|
+
}
|
|
2246
|
+
dropAdoption(cell)
|
|
2247
|
+
}
|
|
2248
|
+
// 4.16: a body's first answer of `undefined` is an opening, and lands past the pipeline.
|
|
2249
|
+
if (produced === undefined && !cell.landed) {
|
|
2250
|
+
landOpening(cell)
|
|
2251
|
+
return
|
|
2252
|
+
}
|
|
2253
|
+
// The settled, untransformed recompute is the common shape, and `absorb` -> `accept`
|
|
2254
|
+
// -> `produce` is three frames behind it. A recompute runs per write on every
|
|
2255
|
+
// derived value in the graph, so the frames are paid per edge.
|
|
2256
|
+
//
|
|
2257
|
+
// SPELLING `produce` OUT HERE WAS TRIED AND IS NOT WORTH IT: copying the identity
|
|
2258
|
+
// gate and the seven stores into this branch took the recompute from
|
|
2259
|
+
// 23.58 ns to 22.42 ns and the chain of twenty from 583 to 563 — 5% and 3% — for
|
|
2260
|
+
// twenty-five duplicated lines of the most subtle logic in the file. A second copy
|
|
2261
|
+
// of the production rule is the drift every document here is arranged to stop.
|
|
2262
|
+
// BOTH GATES, NOT ONE. This arm exists to skip `absorb` -> `accept` -> `produce` for the
|
|
2263
|
+
// settled, ungated recompute — and `accept` is where 4.3 runs the schema, so testing
|
|
2264
|
+
// only `transform` here routed every body production straight past its own gate
|
|
2265
|
+
// (11.46). The gate was written, the tests were written, and this path answered them
|
|
2266
|
+
// without ever reaching it.
|
|
2267
|
+
const primitive =
|
|
2268
|
+
produced === null ||
|
|
2269
|
+
(typeof produced !== 'object' && typeof produced !== 'function')
|
|
2270
|
+
if (cell.bareWrite && primitive) {
|
|
2271
|
+
produce(cell, produced)
|
|
2272
|
+
return
|
|
2273
|
+
}
|
|
2274
|
+
// 15.5 — A BODY THAT RETURNS A REFUSAL REFUSES. Past the primitive arm, so a recompute
|
|
2275
|
+
// answering a number pays nothing for it, and ahead of the object arm, because a `Failed` is
|
|
2276
|
+
// an object with no `then` and that arm would store it as the value. It lands the way a
|
|
2277
|
+
// body's throw does above, a refusal being the same observable move whichever way the body
|
|
2278
|
+
// said it.
|
|
2279
|
+
if (!primitive && isFailed(produced)) {
|
|
2280
|
+
refused(cell, produced)
|
|
2281
|
+
return
|
|
2282
|
+
}
|
|
2283
|
+
// 9.6 — ON ANY RUN: `() => flag() ? rows() : 0` hands its generator back once the flag turns.
|
|
2284
|
+
if (!primitive && isAsyncGenerator(produced)) {
|
|
2285
|
+
promoteToStream(cell, produced)
|
|
2286
|
+
return
|
|
2287
|
+
}
|
|
2288
|
+
if (
|
|
2289
|
+
cell.bareWrite &&
|
|
2290
|
+
typeof (produced as PromiseLike<unknown>).then !== 'function'
|
|
2291
|
+
) {
|
|
2292
|
+
produce(cell, produced)
|
|
2293
|
+
return
|
|
2294
|
+
}
|
|
2295
|
+
// 11.12 BEFORE `absorb`, and the order is the whole of it: a `Reactive` is thenable
|
|
2296
|
+
// (1.4), so absorbing one would settle a live value to a single snapshot — which is
|
|
2297
|
+
// the hazard 1.6 names, answered here by adopting instead of storing.
|
|
2298
|
+
if (isReactive(produced)) {
|
|
2299
|
+
adoptAnswer(cell, produced)
|
|
2300
|
+
return
|
|
2301
|
+
}
|
|
2302
|
+
absorb(cell, produced, true)
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
/**
|
|
2306
|
+
* Adopts what a body handed back, refusing the body where that would close a cycle.
|
|
2307
|
+
*
|
|
2308
|
+
* 11.66 — a body's answer that 11.65 refuses is the body's failure.
|
|
2309
|
+
*/
|
|
2310
|
+
function adoptAnswer(cell: Cell, produced: unknown): void {
|
|
2311
|
+
const cycle = adopt(cell, (produced as Callable)[CELL])
|
|
2312
|
+
if (cycle !== undefined) refused(cell, cycle)
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
/**
|
|
2316
|
+
* Lands an opening `undefined` as loaded, past the pipeline.
|
|
2317
|
+
*
|
|
2318
|
+
* 4.16: AN OPENING `undefined` IS LOADED AND IS NOT A PRODUCTION THROUGH THE PIPELINE. No `schema`
|
|
2319
|
+
* judges it, no `transform` shapes it, no window holds it and no `store` writes it — it is the value
|
|
2320
|
+
* being empty, and the first real production is what those are for. It mints no ordinal (6.10).
|
|
2321
|
+
*/
|
|
2322
|
+
function landOpening(cell: Cell): void {
|
|
2323
|
+
const moved = settleProbes(cell)
|
|
2324
|
+
cell.value = undefined
|
|
2325
|
+
cell.landed = true
|
|
2326
|
+
cell.servable = true
|
|
2327
|
+
setDirect(cell, cell.state === CLEAN)
|
|
2328
|
+
cascade(cell)
|
|
2329
|
+
if (moved) probesMoved(cell)
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
/**
|
|
2333
|
+
* Starts a value's producer: a stream's generator, or a body's run.
|
|
2334
|
+
*
|
|
2335
|
+
* WHAT STARTS A PRODUCER, for the three callers that start one: construction, `refresh`
|
|
2336
|
+
* and `invalidate`. A stream is dispatched HERE rather than inside `run`, which a recompute
|
|
2337
|
+
* walks per node per write — and a stream is never recomputed, having no tracked source to
|
|
2338
|
+
* be marked by (14.13).
|
|
2339
|
+
*/
|
|
2340
|
+
function startProducer(cell: Cell): void {
|
|
2341
|
+
const stream = cell.stream
|
|
2342
|
+
if (stream === null || stream.source !== null) run(cell)
|
|
2343
|
+
else startStream(cell, stream)
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
/**
|
|
2347
|
+
* Supersedes the run in flight and tells its iterator to return.
|
|
2348
|
+
*
|
|
2349
|
+
* The run in flight stops reaching the value. A generator suspended inside an `await` takes the
|
|
2350
|
+
* request once it reaches a `yield`, and whatever it yields before then is dropped by the run
|
|
2351
|
+
* test in `pump`.
|
|
2352
|
+
*/
|
|
2353
|
+
function stopStream(stream: Stream): void {
|
|
2354
|
+
stream.run += 1
|
|
2355
|
+
const superseded = stream.iterator
|
|
2356
|
+
stream.iterator = null
|
|
2357
|
+
if (superseded !== null && typeof superseded.return === 'function')
|
|
2358
|
+
superseded.return().then(undefined, () => undefined)
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
function startStream(cell: Cell, stream: Stream): void {
|
|
2362
|
+
// UNTRACKED. Calling an async generator function runs none of its body — every read in
|
|
2363
|
+
// it happens after an `await`, where 14.13 has it register against nothing — so the
|
|
2364
|
+
// reader standing on this stack is not one this body could ever be attributed to.
|
|
2365
|
+
const iterator = untracked(
|
|
2366
|
+
openStream,
|
|
2367
|
+
cell.body as (args: unknown) => AsyncIterable<unknown>,
|
|
2368
|
+
cell.args,
|
|
2369
|
+
)
|
|
2370
|
+
pumpRun(cell, stream, iterator)
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
/**
|
|
2374
|
+
* 9.6 — a value whose run handed back an async generator: a streaming producer from that run on,
|
|
2375
|
+
* the generator being the run's stream, and every start after it the body's next one.
|
|
2376
|
+
*/
|
|
2377
|
+
function promoteToStream(cell: Cell, generator: AsyncIterable<unknown>): void {
|
|
2378
|
+
let stream = cell.stream
|
|
2379
|
+
if (stream === null) {
|
|
2380
|
+
stream = becomeStream(cell, null)
|
|
2381
|
+
Object.setPrototypeOf(cell.callable as object, STREAM_PROTOTYPE)
|
|
2382
|
+
}
|
|
2383
|
+
pumpRun(cell, stream, generator[Symbol.asyncIterator]())
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
/** Supersedes the run in flight with one reading `iterator`. */
|
|
2387
|
+
function pumpRun(
|
|
2388
|
+
cell: Cell,
|
|
2389
|
+
stream: Stream,
|
|
2390
|
+
iterator: AsyncIterator<unknown>,
|
|
2391
|
+
): void {
|
|
2392
|
+
stopStream(stream)
|
|
2393
|
+
const run = stream.run
|
|
2394
|
+
beginLoad(cell)
|
|
2395
|
+
stream.iterator = iterator
|
|
2396
|
+
// 11.32 — a generator's body runs inside each `next()`, so the pump is what carries the scope.
|
|
2397
|
+
runInScope(
|
|
2398
|
+
cell.scope,
|
|
2399
|
+
() => pump(cell, stream, iterator, run),
|
|
2400
|
+
undefined,
|
|
2401
|
+
).then(undefined, (thrown: unknown) =>
|
|
2402
|
+
pumpFailed(cell, stream, run, thrown),
|
|
2403
|
+
)
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
/**
|
|
2407
|
+
* Refuses a run whose pump threw past `next()` — landing a chunk, closing, or a gate a close reads
|
|
2408
|
+
* through (a `transform` that throws). 2.4 as for a generator's own throw, and the run stops.
|
|
2409
|
+
*/
|
|
2410
|
+
function pumpFailed(
|
|
2411
|
+
cell: Cell,
|
|
2412
|
+
stream: Stream,
|
|
2413
|
+
run: number,
|
|
2414
|
+
thrown: unknown,
|
|
2415
|
+
): void {
|
|
2416
|
+
if (stream.run !== run) return
|
|
2417
|
+
stopStream(stream)
|
|
2418
|
+
refused(cell, thrown)
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
function openStream(
|
|
2422
|
+
body: (args: unknown) => AsyncIterable<unknown>,
|
|
2423
|
+
args: unknown,
|
|
2424
|
+
): AsyncIterator<unknown> {
|
|
2425
|
+
return body(args)[Symbol.asyncIterator]()
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
async function pump(
|
|
2429
|
+
cell: Cell,
|
|
2430
|
+
stream: Stream,
|
|
2431
|
+
iterator: AsyncIterator<unknown>,
|
|
2432
|
+
run: number,
|
|
2433
|
+
): Promise<void> {
|
|
2434
|
+
// 9.7: nothing of this run is served until it says something, so the accumulation
|
|
2435
|
+
// being served is the previous run's until the first chunk replaces it.
|
|
2436
|
+
let begun = false
|
|
2437
|
+
for (;;) {
|
|
2438
|
+
let step: IteratorResult<unknown>
|
|
2439
|
+
try {
|
|
2440
|
+
step = await iterator.next()
|
|
2441
|
+
} catch (failed) {
|
|
2442
|
+
if (stream.run !== run) return
|
|
2443
|
+
stream.iterator = null
|
|
2444
|
+
// 2.4: what the stream had already said goes on being served, with the failure
|
|
2445
|
+
// standing beside it — and 2.2 where it had said nothing.
|
|
2446
|
+
refused(cell, failed)
|
|
2447
|
+
return
|
|
2448
|
+
}
|
|
2449
|
+
if (stream.run !== run) return
|
|
2450
|
+
if (step.done === true) break
|
|
2451
|
+
if (!begun) {
|
|
2452
|
+
begun = true
|
|
2453
|
+
beginAccumulation(cell, stream)
|
|
2454
|
+
}
|
|
2455
|
+
acceptChunk(cell, stream, step.value)
|
|
2456
|
+
}
|
|
2457
|
+
stream.iterator = null
|
|
2458
|
+
// THE CLOSE RELEASES WHAT A WINDOW IS HOLDING. A stream that has finished has nothing left to
|
|
2459
|
+
// collapse, and an await resolving here would otherwise be handed the accumulation as the last
|
|
2460
|
+
// release left it rather than the whole of it (2.7).
|
|
2461
|
+
const cap = cell.cap
|
|
2462
|
+
if (cap?.holding) {
|
|
2463
|
+
if (cap.timer !== null) clearTimeout(cap.timer)
|
|
2464
|
+
cap.timer = null
|
|
2465
|
+
cap.holding = false
|
|
2466
|
+
releaseChunks(cell, stream)
|
|
2467
|
+
}
|
|
2468
|
+
const moved = !begun || cell.loading
|
|
2469
|
+
if (!begun) {
|
|
2470
|
+
// A stream that closed with nothing in it has finished, and what it accumulated is
|
|
2471
|
+
// no chunks — a value, where waiting on would be a load that never ends.
|
|
2472
|
+
beginAccumulation(cell, stream)
|
|
2473
|
+
landChunks(cell)
|
|
2474
|
+
cascade(cell)
|
|
2475
|
+
}
|
|
2476
|
+
cell.loading = false
|
|
2477
|
+
// 3.7: `busy` held until the close, and this is the close.
|
|
2478
|
+
if (moved) probesMoved(cell)
|
|
2479
|
+
// 6.11, AFTER THE CASCADE ABOVE: the entry is what a read would answer with, so it is taken
|
|
2480
|
+
// once the last chunk has landed and the value is servable.
|
|
2481
|
+
closeRun(cell, stream)
|
|
2482
|
+
// 8.9: a stream is written once, at the close, with the accumulation and never a partial.
|
|
2483
|
+
if (cell.store !== null) writeStore(cell, cell.store, heldValue(cell))
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
function beginAccumulation(cell: Cell, stream: Stream): void {
|
|
2487
|
+
stream.closed = false
|
|
2488
|
+
const chunks: unknown[] = []
|
|
2489
|
+
stream.chunks = chunks
|
|
2490
|
+
stream.visible = 0
|
|
2491
|
+
stream.version += 1
|
|
2492
|
+
// A cursor holds ordinals, and `ring[i]` carries `ringBase + i + 1` — so the new run's
|
|
2493
|
+
// first chunk is the next ordinal, and a cursor from the previous run clamps forward.
|
|
2494
|
+
cell.ring = chunks
|
|
2495
|
+
cell.ringBase = cell.ordinal
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
/**
|
|
2499
|
+
* Pushes one chunk into the accumulation, as one ungated production.
|
|
2500
|
+
*
|
|
2501
|
+
* ONE CHUNK IS ONE PRODUCTION, and ungated: an accumulation that grew is a different value
|
|
2502
|
+
* whatever `identity` says about its last element.
|
|
2503
|
+
*/
|
|
2504
|
+
function acceptChunk(cell: Cell, stream: Stream, chunk: unknown): void {
|
|
2505
|
+
stream.chunks.push(chunk)
|
|
2506
|
+
cell.ordinal += 1
|
|
2507
|
+
// 3.8: `ready` from the first chunk, and `error` cleared by an accepted production (4.11).
|
|
2508
|
+
if (!cell.servable || cell.failure !== undefined) stream.probesOwed = true
|
|
2509
|
+
// 5.17: THE WINDOW CAPS THE WAKE, and the chunk is already in the buffer for a cursor.
|
|
2510
|
+
if (tellsChunks(cell, cell.cap)) releaseChunks(cell, stream)
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
/**
|
|
2514
|
+
* Tells readers of every chunk landed so far, waking them at most once per task.
|
|
2515
|
+
*
|
|
2516
|
+
* What a reader is told of: every chunk landed so far becomes what a read materialises, and the
|
|
2517
|
+
* wake goes out at most once per task (9.9).
|
|
2518
|
+
*/
|
|
2519
|
+
function releaseChunks(cell: Cell, stream: Stream): void {
|
|
2520
|
+
stream.visible = stream.chunks.length
|
|
2521
|
+
stream.version += 1
|
|
2522
|
+
landChunks(cell)
|
|
2523
|
+
wakeLater(cell, stream)
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
/**
|
|
2527
|
+
* The streams holding a wake, all drained by one task.
|
|
2528
|
+
*
|
|
2529
|
+
* THE STREAMS HOLDING A WAKE, and one task drains all of them — never a task per stream.
|
|
2530
|
+
* The pull is untouched: a read taken before the task runs materialises what has landed
|
|
2531
|
+
* (12.12), so what waits is only the telling.
|
|
2532
|
+
*/
|
|
2533
|
+
const WAKING: Cell[] = []
|
|
2534
|
+
let wakeQueued = false
|
|
2535
|
+
|
|
2536
|
+
function wakeLater(cell: Cell, stream: Stream): void {
|
|
2537
|
+
if (stream.waking) return
|
|
2538
|
+
stream.waking = true
|
|
2539
|
+
WAKING.push(cell)
|
|
2540
|
+
if (wakeQueued) return
|
|
2541
|
+
wakeQueued = true
|
|
2542
|
+
nextTask(wakeStreams)
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
function wakeStreams(): void {
|
|
2546
|
+
wakeQueued = false
|
|
2547
|
+
// The length is re-read per turn: a mirror the cascade runs can land on another stream.
|
|
2548
|
+
for (let index = 0; index < WAKING.length; index += 1) {
|
|
2549
|
+
const cell = WAKING[index] as Cell
|
|
2550
|
+
const stream = cell.stream as Stream
|
|
2551
|
+
stream.waking = false
|
|
2552
|
+
let owed = stream.probesOwed
|
|
2553
|
+
stream.probesOwed = false
|
|
2554
|
+
// 9.11: the chunk ended a write following a `Reactive`, whose probes were this one's.
|
|
2555
|
+
if (cell.adoption !== null) {
|
|
2556
|
+
unfollow(cell)
|
|
2557
|
+
owed = true
|
|
2558
|
+
}
|
|
2559
|
+
cascade(cell)
|
|
2560
|
+
if (owed) probesMoved(cell)
|
|
2561
|
+
}
|
|
2562
|
+
WAKING.length = 0
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
/**
|
|
2566
|
+
* Posts a function to the next task; made on first use.
|
|
2567
|
+
*
|
|
2568
|
+
* THE NEXT TASK. `setImmediate` where the runtime has one — bun, and so every server — and a
|
|
2569
|
+
* `MessageChannel` post in a browser, which has none and clamps a nested `setTimeout` to 4 ms.
|
|
2570
|
+
* The channel is made on first use, so a page that never streams never opens one.
|
|
2571
|
+
*/
|
|
2572
|
+
let postTask: ((run: () => void) => void) | null = null
|
|
2573
|
+
|
|
2574
|
+
function nextTask(run: () => void): void {
|
|
2575
|
+
if (postTask === null) postTask = taskPoster()
|
|
2576
|
+
postTask(run)
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
function taskPoster(): (run: () => void) => void {
|
|
2580
|
+
if (typeof setImmediate === 'function')
|
|
2581
|
+
return (run) => {
|
|
2582
|
+
setImmediate(run)
|
|
2583
|
+
}
|
|
2584
|
+
const channel = new MessageChannel()
|
|
2585
|
+
let queued: (() => void) | null = null
|
|
2586
|
+
channel.port1.onmessage = (): void => {
|
|
2587
|
+
const run = queued
|
|
2588
|
+
queued = null
|
|
2589
|
+
run?.()
|
|
2590
|
+
}
|
|
2591
|
+
return (run) => {
|
|
2592
|
+
queued = run
|
|
2593
|
+
channel.port2.postMessage(null)
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
function landChunks(cell: Cell): void {
|
|
2598
|
+
cell.failure = undefined
|
|
2599
|
+
cell.landed = true
|
|
2600
|
+
cell.servable = true
|
|
2601
|
+
cell.direct = false
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
/**
|
|
2605
|
+
* Materialises a stream's visible chunks into `value`, through both gates.
|
|
2606
|
+
*
|
|
2607
|
+
* 9.1 AND 9.8: the read that asks is where the accumulation becomes a `Stored`, through both
|
|
2608
|
+
* gates, once per chunk count however many reads ask.
|
|
2609
|
+
*/
|
|
2610
|
+
function materialise(cell: Cell, stream: Stream): void {
|
|
2611
|
+
if (stream.materialisedAt === stream.version || !cell.servable) return
|
|
2612
|
+
// 9.11: the accumulation moved past a write following a `Reactive`, which lets go of it here
|
|
2613
|
+
// where no chunk's wake did — a patch, or a run that closed empty. Only an own stream follows;
|
|
2614
|
+
// a forwarding one's `adoption` is what it forwards.
|
|
2615
|
+
if (cell.adoption !== null && stream.source === null) unfollow(cell)
|
|
2616
|
+
stream.materialisedAt = stream.version
|
|
2617
|
+
stream.written = false
|
|
2618
|
+
gateInto(cell, stream.chunks.slice(0, stream.visible))
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
/**
|
|
2622
|
+
* Gates a forwarded slice or write into `value`, untracked.
|
|
2623
|
+
*
|
|
2624
|
+
* What a forwarding value's read takes in, through its own gates (11.14, 9.8): a slice of the
|
|
2625
|
+
* accumulation, or a write standing on the stream it forwards.
|
|
2626
|
+
* 4.7: the gates run untracked, and this is reached from inside whatever read asked.
|
|
2627
|
+
*/
|
|
2628
|
+
function gateInto(cell: Cell, raw: unknown): void {
|
|
2629
|
+
untracked(gateThrough, cell, raw)
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
function gateThrough(cell: Cell, raw: unknown): void {
|
|
2633
|
+
let checked: unknown = raw
|
|
2634
|
+
if (cell.schema !== null) {
|
|
2635
|
+
const answer = runSchema(cell.schema, checked)
|
|
2636
|
+
if (answer.issues !== null) {
|
|
2637
|
+
// 4.9: nothing is stored, so the snapshot before this one goes on serving.
|
|
2638
|
+
refused(cell, validationError<Found>(answer.issues))
|
|
2639
|
+
return
|
|
2640
|
+
}
|
|
2641
|
+
checked = answer.value
|
|
2642
|
+
}
|
|
2643
|
+
const transform = cell.transform
|
|
2644
|
+
if (transform !== null) {
|
|
2645
|
+
const shaped = transform(checked)
|
|
2646
|
+
if (isFailed(shaped)) {
|
|
2647
|
+
refused(cell, shaped)
|
|
2648
|
+
return
|
|
2649
|
+
}
|
|
2650
|
+
checked = shaped
|
|
2651
|
+
}
|
|
2652
|
+
cell.value = checked
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
/**
|
|
2656
|
+
* What is held, for the paths that hand the value to somebody other than a reader — an
|
|
2657
|
+
* effect, a waiter, an adoption's mirror.
|
|
2658
|
+
*/
|
|
2659
|
+
function heldValue(cell: Cell): unknown {
|
|
2660
|
+
const stream = cell.stream
|
|
2661
|
+
if (stream !== null) materialise(cell, stream)
|
|
2662
|
+
return cell.value
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
/**
|
|
2666
|
+
* Releases the standing production at its deadline, waking nobody.
|
|
2667
|
+
*
|
|
2668
|
+
* 6.7: THE DROP, AND IT IS THE WHOLE OF WHAT A DEADLINE DOES. The production is
|
|
2669
|
+
* RELEASED rather than merely stopped being served, and that is the difference between a
|
|
2670
|
+
* `ttl` that bounds memory and one that only bounds freshness: a body returning a large
|
|
2671
|
+
* payload holds it until something replaces it, and on a value nothing reads again that
|
|
2672
|
+
* is the life of the process.
|
|
2673
|
+
*
|
|
2674
|
+
* 6.9: `loading` IS NOT SET, because nothing has been started. 6.6 has the producer run
|
|
2675
|
+
* on the READ that follows, so between the deadline and that read the value is neither
|
|
2676
|
+
* ready nor busy — which is exactly what those two probes mean, and is why `s.ready`
|
|
2677
|
+
* needs no clock of its own: it reads `servable`, and this is what moved it.
|
|
2678
|
+
*
|
|
2679
|
+
* 6.1: NOTHING IS WOKEN. A wake here would cause the read that runs the producer, and a
|
|
2680
|
+
* `ttl` would then reload every value on its deadline whether or not anything wanted it —
|
|
2681
|
+
* which is `s.refresh` (7.4) wearing a different name.
|
|
2682
|
+
*/
|
|
2683
|
+
function dropStanding(cell: Cell, expiry: Expiry): void {
|
|
2684
|
+
expiry.dropped = true
|
|
2685
|
+
cell.value = undefined
|
|
2686
|
+
cell.landed = false
|
|
2687
|
+
cell.servable = false
|
|
2688
|
+
cell.direct = false
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
/**
|
|
2692
|
+
* Arms the timer for the nearest deadline this value holds.
|
|
2693
|
+
*
|
|
2694
|
+
* 6.3: THE STANDING PRODUCTION'S DEADLINE, where a deadline reaches it. A value with nothing
|
|
2695
|
+
* left to expire arms nothing, which is what stops the callback below keeping every `ttl` value
|
|
2696
|
+
* alive for the life of the process: it names its cell.
|
|
2697
|
+
*/
|
|
2698
|
+
function armNext(cell: Cell, expiry: Expiry): void {
|
|
2699
|
+
if (expiry.timer !== null) return
|
|
2700
|
+
if (!standingLive(cell, expiry)) return
|
|
2701
|
+
const deadline = expiry.at + expiry.ms
|
|
2702
|
+
armTimer(expiry, deadline - Date.now(), () => {
|
|
2703
|
+
sweepExpiry(cell)
|
|
2704
|
+
})
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
/**
|
|
2708
|
+
* Whether the value has a producer to run again: a body or a store.
|
|
2709
|
+
*
|
|
2710
|
+
* WHETHER THERE IS A PRODUCER TO RUN AGAIN: a body, or a store (8.2), which is somewhere to get the
|
|
2711
|
+
* value back from. A value with neither holds what the app put there, and a deadline or a trigger
|
|
2712
|
+
* has nothing to reload it with (6.2, 7.6).
|
|
2713
|
+
*/
|
|
2714
|
+
function reloads(cell: Cell): boolean {
|
|
2715
|
+
return cell.body !== null || cell.store !== null
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
/**
|
|
2719
|
+
* Runs the producer again: the body, or a re-read of the store.
|
|
2720
|
+
*
|
|
2721
|
+
* RUN THE PRODUCER AGAIN, for a trigger (7.4), a debt a read settles (7.13) and a deadline a read
|
|
2722
|
+
* follows (6.6). `startProducer` rather than `run`: on a streaming producer the body is the
|
|
2723
|
+
* generator, and running it as a recompute would store the iterator it hands back (9.6).
|
|
2724
|
+
*/
|
|
2725
|
+
function reload(cell: Cell): void {
|
|
2726
|
+
if (cell.body !== null) startProducer(cell)
|
|
2727
|
+
else rereadStore(cell, cell.store as Persisted)
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
/**
|
|
2731
|
+
* Marks a load started and moves `busy`.
|
|
2732
|
+
*
|
|
2733
|
+
* A LOAD HAS STARTED, and `busy` is the probe it moves (3.7) — a guard that asked has to hear it,
|
|
2734
|
+
* and nothing a reader can see has moved yet. Called WHERE THE LOAD STARTS — a pending answer taken
|
|
2735
|
+
* in, a stream opened, a store read that did not answer at once — and nowhere ahead of it. A caller
|
|
2736
|
+
* that set `loading` on its way in made each of those find it already set and stay silent, so a
|
|
2737
|
+
* reload a read started was one no `busy` guard ever saw; and a synchronous body that is no load at
|
|
2738
|
+
* all woke every guard onto a `busy` that was false again by the time it ran.
|
|
2739
|
+
*/
|
|
2740
|
+
function beginLoad(cell: Cell): void {
|
|
2741
|
+
// Ahead of the early return: a deferred keyed entry is `loading` before its first start.
|
|
2742
|
+
if (!cell.filed && reloads(cell)) fileProducer(cell)
|
|
2743
|
+
if (cell.loading) return
|
|
2744
|
+
cell.loading = true
|
|
2745
|
+
probesMoved(cell)
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
/**
|
|
2749
|
+
* Whether a deadline reaches the standing production.
|
|
2750
|
+
*
|
|
2751
|
+
* WHETHER A DEADLINE REACHES THE STANDING PRODUCTION: there is a producer to reload it (6.2), 6.7
|
|
2752
|
+
* has not already released it, and it is being served — which is what keeps a value whose load is
|
|
2753
|
+
* in flight from arming against the production that load is replacing.
|
|
2754
|
+
*
|
|
2755
|
+
* AND WHAT IS SERVED WAS MINTED, which every producer but a streaming one answers yes to the moment
|
|
2756
|
+
* it is servable. A stream serves the run it is building (9.1), and that run has minted nothing
|
|
2757
|
+
* until it closes (6.11) — so a deadline has nothing to measure from and must not drop it.
|
|
2758
|
+
*/
|
|
2759
|
+
function standingLive(cell: Cell, expiry: Expiry): boolean {
|
|
2760
|
+
if (!reloads(cell) || expiry.dropped || !cell.servable) return false
|
|
2761
|
+
const stream = cell.stream
|
|
2762
|
+
return stream === null || stream.closed
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
/**
|
|
2766
|
+
* Drops what has expired and re-arms against the nearest deadline.
|
|
2767
|
+
*
|
|
2768
|
+
* 6.3's timer, fired — AND IT MAY BE EARLY. The standing deadline moves forward on every
|
|
2769
|
+
* production and re-arming per production would be a `setTimeout` per write. So the timer is
|
|
2770
|
+
* left where it was, and this re-arms against whatever is nearest now. An early fire drops nothing and
|
|
2771
|
+
* costs one re-arm per window.
|
|
2772
|
+
*/
|
|
2773
|
+
function sweepExpiry(cell: Cell): void {
|
|
2774
|
+
const expiry = cell.expiry
|
|
2775
|
+
if (expiry === null) return
|
|
2776
|
+
expiry.timer = null
|
|
2777
|
+
if (standingLive(cell, expiry) && Date.now() - expiry.at >= expiry.ms)
|
|
2778
|
+
dropStanding(cell, expiry)
|
|
2779
|
+
armNext(cell, expiry)
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
/**
|
|
2783
|
+
* The read's expiry check: drops a production past its deadline and reloads it.
|
|
2784
|
+
*
|
|
2785
|
+
* THE READ'S HALF. The timer above is what normally moves the probes, and this is the
|
|
2786
|
+
* backstop for one that fired late — a clamped background tab, a blocked loop, a `ttl`
|
|
2787
|
+
* under the clamp. 6.5 is written about the READ rather than about when the
|
|
2788
|
+
* timer got there.
|
|
2789
|
+
*
|
|
2790
|
+
* A VALUE WITH NO BODY REACHES HERE AND TURNS ROUND, one frame per read, and that is
|
|
2791
|
+
* deliberate: `setDirect` tests one field, and telling one from a body value there would put a
|
|
2792
|
+
* second load on the path every value in the design walks. 6.2 is the same sentence from the other side — with no body
|
|
2793
|
+
* there is nothing to call, so what the app put there stands.
|
|
2794
|
+
*/
|
|
2795
|
+
function expire(cell: Cell): void {
|
|
2796
|
+
if (!reloads(cell)) return
|
|
2797
|
+
const expiry = cell.expiry as Expiry
|
|
2798
|
+
if (standingLive(cell, expiry) && Date.now() - expiry.at >= expiry.ms)
|
|
2799
|
+
dropStanding(cell, expiry)
|
|
2800
|
+
// 6.6: THE PRODUCER RUNS ON THE READ THAT FOLLOWS, and `dropped` is what tells this
|
|
2801
|
+
// from an `invalidate` whose load is still in flight. No combination of the cell's own
|
|
2802
|
+
// fields separates them — both are unservable with a body — and reading it wrong runs
|
|
2803
|
+
// the body again on every read for as long as the load takes.
|
|
2804
|
+
if (!expiry.dropped) return
|
|
2805
|
+
expiry.dropped = false
|
|
2806
|
+
// Work is started NOW, which is the first moment 6.9 stops applying — and `busy` moves where
|
|
2807
|
+
// the load actually starts, not here (see `beginLoad`).
|
|
2808
|
+
reload(cell)
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
/**
|
|
2812
|
+
* The read's slow path, for every reason `direct` is false.
|
|
2813
|
+
*
|
|
2814
|
+
* EVERY REASON `direct` IS FALSE, told apart here rather than on the read. It is the
|
|
2815
|
+
* pull and the refusal in one frame, and it is correct however the flag got to false —
|
|
2816
|
+
* which is what lets the flag be conservative.
|
|
2817
|
+
*/
|
|
2818
|
+
function readIndirect(cell: Cell): unknown {
|
|
2819
|
+
if (cell.owes !== OWES_NOTHING) settleOwed(cell)
|
|
2820
|
+
if (cell.state !== CLEAN) resolve(cell)
|
|
2821
|
+
// 6.5: THE READ THAT FOLLOWS. Every value carrying an expiry arrives here and no
|
|
2822
|
+
// other value does, which is the whole of what `setDirect` buys.
|
|
2823
|
+
if (cell.expiry !== null) expire(cell)
|
|
2824
|
+
if (cell.stream !== null) materialise(cell, cell.stream)
|
|
2825
|
+
if (!cell.servable) {
|
|
2826
|
+
// 2.3: a failed producer with nothing landed is the one case holding no value
|
|
2827
|
+
// and a refusal that explains the absence.
|
|
2828
|
+
const failure = failureOf(cell)
|
|
2829
|
+
if (failure !== undefined) throw failure
|
|
2830
|
+
throw new NotLanded(cell.callable as object, cell.label)
|
|
2831
|
+
}
|
|
2832
|
+
return cell.value
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
/**
|
|
2836
|
+
* Every value that is not streaming shares this as its `ring`. The field stays monomorphically an
|
|
2837
|
+
* Array — CLAUDE.md's shape rule — and only a stream's own chunk buffer is ever pushed to. Frozen so
|
|
2838
|
+
* that if anything ever does push here, it says so rather than corrupting every other value at once.
|
|
2839
|
+
*/
|
|
2840
|
+
const EMPTY_RING = Object.freeze([]) as unknown as unknown[]
|
|
2841
|
+
|
|
2842
|
+
/**
|
|
2843
|
+
* The current probe pass, stamped on each cell a probe walk visits.
|
|
2844
|
+
*
|
|
2845
|
+
* A VISITED STAMP, NOT A STACK GUARD, and the difference is an asymptote. Added before
|
|
2846
|
+
* the recursion and removed after it, a guard only says a node is on the CURRENT PATH —
|
|
2847
|
+
* so a shared subgraph is re-walked once per path that reaches it, and a 26-cell DAG
|
|
2848
|
+
* with 130 edges cost 19,660 edge visits and 4,010 hash operations per probe read.
|
|
2849
|
+
*
|
|
2850
|
+
* The stamp MEMOISES, and it is sound for one reason worth stating: within a pass,
|
|
2851
|
+
* "already visited while the walk is still running" IMPLIES "not busy", because a busy
|
|
2852
|
+
* node short-circuits the whole walk at its first visit. So a second arrival at a
|
|
2853
|
+
* stamped node answers false without descending.
|
|
2854
|
+
*
|
|
2855
|
+
* This is the exhaustive case rather than the rare one: the walk short-circuits on
|
|
2856
|
+
* `true`, so the full traversal is the ALL-IDLE graph — the spinner read that renders
|
|
2857
|
+
* no spinner, every frame.
|
|
2858
|
+
*/
|
|
2859
|
+
let PROBE_PASS = 0
|
|
2860
|
+
|
|
2861
|
+
/**
|
|
2862
|
+
* Whether this value or anything it reads or adopts is loading.
|
|
2863
|
+
*
|
|
2864
|
+
* Propagation, derived on the read that asks (11.22) rather than held as a
|
|
2865
|
+
* subscription per source. 11.20: a value is busy where a source it read is busy —
|
|
2866
|
+
* and the premise is what `busy` MEANS, not which trigger produced it, so a refresh
|
|
2867
|
+
* over a landed source reports through here even though the body never re-ran.
|
|
2868
|
+
*/
|
|
2869
|
+
function walkBusy(cell: Cell, pass: number): boolean {
|
|
2870
|
+
if (cell.loading) return true
|
|
2871
|
+
const adoption = cell.adoption
|
|
2872
|
+
if (cell.sources.length === 0 && adoption === null) return false
|
|
2873
|
+
if (cell.probePass === pass) return false
|
|
2874
|
+
cell.probePass = pass
|
|
2875
|
+
// 11.19: an adopting value reports busy only where the adopted one does — and the
|
|
2876
|
+
// adopted is not in `sources`, because the cursor protocol owns that list and would
|
|
2877
|
+
// truncate anything the body did not read on its next run.
|
|
2878
|
+
if (adoption !== null && walkBusy(adoption.source, pass)) return true
|
|
2879
|
+
const sources = cell.sources
|
|
2880
|
+
for (let index = 0; index < sources.length; index += 1)
|
|
2881
|
+
if (walkBusy(sources[index] as Cell, pass)) return true
|
|
2882
|
+
return false
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
/**
|
|
2886
|
+
* `busy` for a reader: subscribes it to the probe, then answers.
|
|
2887
|
+
*
|
|
2888
|
+
* 3.3: READING A PROBE SUBSCRIBES. Without it the canonical template shape can never come
|
|
2889
|
+
* back: `{#if s.ready()}{s()}{:else}spinner{/if}` renders the spinner, and in that branch it
|
|
2890
|
+
* never reads `s()` — so it subscribes to NOTHING, and the value landing wakes nobody. It
|
|
2891
|
+
* is stuck on the spinner for the life of the page, with the value sitting right there.
|
|
2892
|
+
*
|
|
2893
|
+
* ON ITS OWN EDGE, which is 3.3's "that probe alone" and is bought by what the other
|
|
2894
|
+
* spelling costs the DOM. Subscribing a probe reader through the VALUE edge works and is
|
|
2895
|
+
* twenty lines lighter — and then a load STARTING wakes every reader of the value, which
|
|
2896
|
+
* on a fifty-row list is fifty `.data` writes with the same string in them. Measured with
|
|
2897
|
+
* `harness/measure`: a refresh that changed nothing reported 50 dataWrites, 50 of them
|
|
2898
|
+
* redundant. That is precisely what 5.2's identity gate exists to stop, and it cannot see
|
|
2899
|
+
* a wake nobody minted a production for.
|
|
2900
|
+
*/
|
|
2901
|
+
function isBusy(cell: Cell): boolean {
|
|
2902
|
+
probe(cell)
|
|
2903
|
+
return busyNow(cell)
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
/**
|
|
2907
|
+
* The standing refusal: the value's own, or the one on what it adopts.
|
|
2908
|
+
*
|
|
2909
|
+
* 11.50 FOR `error`: the standing refusal is the value's own, or else the one standing on what
|
|
2910
|
+
* it adopts. Unlike `busy` and `ready` it does not propagate from what a body READ (11.20) —
|
|
2911
|
+
* a derived value whose source failed has either a figure of its own or its own refusal.
|
|
2912
|
+
*/
|
|
2913
|
+
function failureOf(cell: Cell): unknown {
|
|
2914
|
+
let at = cell
|
|
2915
|
+
while (at.failure === undefined && at.adoption !== null)
|
|
2916
|
+
at = at.adoption.source
|
|
2917
|
+
return at.failure
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
/**
|
|
2921
|
+
* `busy` without subscribing.
|
|
2922
|
+
*
|
|
2923
|
+
* `busy` WITHOUT SUBSCRIBING, for a caller that is not a reader — `then` asks it and must not
|
|
2924
|
+
* become a probe edge of whatever body happens to be on the stack.
|
|
2925
|
+
*
|
|
2926
|
+
* A VALUE WITH NOTHING UNDER IT ANSWERS FROM ITS OWN FIELD, here and in `readyNow`. The walks
|
|
2927
|
+
* already test this, one frame in — hoisting it costs nothing to the graph case and is the whole
|
|
2928
|
+
* of the probe for a state, which is what `{#if user.ready()}` reads. Leaving it inside took a
|
|
2929
|
+
* leaf `ready` from 1.07 ns to 2.05 ns for a pass counter nobody was going to read.
|
|
2930
|
+
*/
|
|
2931
|
+
function busyNow(cell: Cell): boolean {
|
|
2932
|
+
if (cell.loading) return true
|
|
2933
|
+
if (cell.sources.length === 0 && cell.adoption === null) return false
|
|
2934
|
+
PROBE_PASS += 1
|
|
2935
|
+
return walkBusy(cell, PROBE_PASS)
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
/**
|
|
2939
|
+
* Whether this value and everything it reads or adopts has something to serve.
|
|
2940
|
+
*
|
|
2941
|
+
* THE OTHER HALF OF THE FLOOR, and the quantifiers are not a pair of preferences: `busy`
|
|
2942
|
+
* asks whether ANYTHING it reads is loading, `ready` whether EVERYTHING it reads has
|
|
2943
|
+
* something to serve. That is the only pairing under which `busy && !ready` reads as one
|
|
2944
|
+
* sentence, and it is what makes a value no better than the worst thing it was built from.
|
|
2945
|
+
*
|
|
2946
|
+
* WITHOUT IT A DERIVED VALUE OUTRANKED ITS SOURCE: `source.invalidate()` left the source
|
|
2947
|
+
* reporting `ready` false while the value derived from it reported true and went on
|
|
2948
|
+
* serving the figure it had computed before — right by its own lights, and a lie about the
|
|
2949
|
+
* data. Nothing in the output says so, which is why this is a walk and not a field.
|
|
2950
|
+
*
|
|
2951
|
+
* The stamp MEMOISES the same way `walkBusy`'s does and is sound for the mirrored reason:
|
|
2952
|
+
* this walk short-circuits on NOT ready, so a second arrival at a node the walk is still
|
|
2953
|
+
* inside is a node that has not refused, and answering `true` there is answering what the
|
|
2954
|
+
* first visit will.
|
|
2955
|
+
*/
|
|
2956
|
+
function walkReady(cell: Cell, pass: number): boolean {
|
|
2957
|
+
if (!cell.servable) return false
|
|
2958
|
+
const adoption = cell.adoption
|
|
2959
|
+
if (cell.sources.length === 0 && adoption === null) return true
|
|
2960
|
+
if (cell.probePass === pass) return true
|
|
2961
|
+
cell.probePass = pass
|
|
2962
|
+
if (adoption !== null && !walkReady(adoption.source, pass)) return false
|
|
2963
|
+
const sources = cell.sources
|
|
2964
|
+
for (let index = 0; index < sources.length; index += 1)
|
|
2965
|
+
if (!walkReady(sources[index] as Cell, pass)) return false
|
|
2966
|
+
return true
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
function isReady(cell: Cell): boolean {
|
|
2970
|
+
probe(cell)
|
|
2971
|
+
return readyNow(cell)
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
/**
|
|
2975
|
+
* The body run where it is owed, before a probe answers about it.
|
|
2976
|
+
*
|
|
2977
|
+
* 3.2 — A PROBE ANSWERS ABOUT THE VALUE A READ WOULD SERVE. Under push-pull a write leaves every
|
|
2978
|
+
* derived value it reached unrun, so a probe that walked what the last run left answered about a
|
|
2979
|
+
* value nothing would serve: `x.ready()` true over a body whose inputs had moved to a load, and the
|
|
2980
|
+
* read the guard admitted threw `NotLanded`. `peek` pulls for the same reason.
|
|
2981
|
+
*/
|
|
2982
|
+
function bringCurrent(cell: Cell): void {
|
|
2983
|
+
if (cell.owes !== OWES_NOTHING) settleOwed(cell)
|
|
2984
|
+
if (cell.state !== CLEAN) resolve(cell)
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
/** What every probe does before it answers: subscribes the reader (3.3), then brings `cell` current. */
|
|
2988
|
+
function probe(cell: Cell): void {
|
|
2989
|
+
const reader = TRACKING.reader
|
|
2990
|
+
if (reader !== null) trackProbe(cell, reader)
|
|
2991
|
+
bringCurrent(cell)
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
function readyNow(cell: Cell): boolean {
|
|
2995
|
+
if (!cell.servable) return false
|
|
2996
|
+
if (cell.sources.length === 0 && cell.adoption === null) return true
|
|
2997
|
+
PROBE_PASS += 1
|
|
2998
|
+
return walkReady(cell, PROBE_PASS)
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
/**
|
|
3002
|
+
* A promise of the value once it is servable and not busy, or of its failure.
|
|
3003
|
+
*
|
|
3004
|
+
* 2.7: SETTLED IS SERVABLE AND NOT BUSY, and it is both halves — `busy` as the probe answers
|
|
3005
|
+
* it, through what the value read and what it adopted. Resolving on `servable` alone handed a
|
|
3006
|
+
* caller awaiting a refresh the value the refresh was replacing, and a caller awaiting a stream
|
|
3007
|
+
* its first chunk; reading the value's own `loading` alone did the same through an adoption.
|
|
3008
|
+
*
|
|
3009
|
+
* `untilStreaming` answers `STREAMING` the moment the value is a stream instead (9.6), for a
|
|
3010
|
+
* reader that forwards its chunks rather than waiting out the run.
|
|
3011
|
+
*/
|
|
3012
|
+
function settled(cell: Cell, untilStreaming = false): Promise<unknown> {
|
|
3013
|
+
// 1.7: awaiting a value is reaching it, so what a read would pull is pulled here too — a reload
|
|
3014
|
+
// an invalidate owed, a recompute a write left marked, and a reload a `ttl` drop owes (6.6):
|
|
3015
|
+
// with nothing served and nothing loading, the await would wait on a probe nothing moves.
|
|
3016
|
+
//
|
|
3017
|
+
// ONLY ONCE THE TIMER HAS DROPPED IT. The read's half of 6.5 — a production still served past
|
|
3018
|
+
// its deadline — is not taken here, because 16.7's `ttl` of 0 makes every mutation's production
|
|
3019
|
+
// past its deadline the moment it lands, and the await that answers the call ran it again.
|
|
3020
|
+
if (cell.owes !== OWES_NOTHING) settleOwed(cell)
|
|
3021
|
+
if (cell.state !== CLEAN) resolve(cell)
|
|
3022
|
+
if (cell.expiry?.dropped) expire(cell)
|
|
3023
|
+
if (untilStreaming && cell.stream !== null)
|
|
3024
|
+
return Promise.resolve(STREAMING)
|
|
3025
|
+
if (cell.servable && !busyNow(cell)) return Promise.resolve(heldValue(cell))
|
|
3026
|
+
if (!cell.landed && !busyNow(cell)) {
|
|
3027
|
+
const failure = failureOf(cell)
|
|
3028
|
+
if (failure !== undefined) return Promise.reject(failure)
|
|
3029
|
+
}
|
|
3030
|
+
return new Promise((settle, fail) => {
|
|
3031
|
+
// THE ONE CELL IT WAITS ON, declared rather than tracked. It was registered outside
|
|
3032
|
+
// the tracking protocol, so without this a `CHECK` mark arriving from further
|
|
3033
|
+
// upstream would have nothing to resolve against.
|
|
3034
|
+
const waiter: Subscriber = newSubscriber([cell], null)
|
|
3035
|
+
waiter.notify = (): void => {
|
|
3036
|
+
// THE WAITER PULLS. A value built from a source that has just landed is marked and
|
|
3037
|
+
// not yet re-run — nothing else reads it — so asking whether it is busy before
|
|
3038
|
+
// pulling it answers from the run that aborted, and the await never settles.
|
|
3039
|
+
if (cell.state !== CLEAN) resolve(cell)
|
|
3040
|
+
if (untilStreaming && cell.stream !== null) {
|
|
3041
|
+
waiter.alive = false
|
|
3042
|
+
dropProbeReader(cell, waiter)
|
|
3043
|
+
settle(STREAMING)
|
|
3044
|
+
return
|
|
3045
|
+
}
|
|
3046
|
+
if (busyNow(cell)) return
|
|
3047
|
+
if (cell.servable) {
|
|
3048
|
+
waiter.alive = false
|
|
3049
|
+
dropProbeReader(cell, waiter)
|
|
3050
|
+
settle(heldValue(cell))
|
|
3051
|
+
return
|
|
3052
|
+
}
|
|
3053
|
+
if (cell.landed) return
|
|
3054
|
+
const failure = failureOf(cell)
|
|
3055
|
+
if (failure !== undefined) {
|
|
3056
|
+
waiter.alive = false
|
|
3057
|
+
dropProbeReader(cell, waiter)
|
|
3058
|
+
fail(failure)
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
// ON THE PROBE EDGE, because what it waits for is a probe: every way to settled moves
|
|
3062
|
+
// `busy` or `ready` here or upstream, and `markProbes` carries an upstream move down
|
|
3063
|
+
// the value and adoption edges — where a stream's close mints nothing a value reader
|
|
3064
|
+
// would be woken by.
|
|
3065
|
+
cell.probeReaders.push(waiter)
|
|
3066
|
+
})
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
/**
|
|
3070
|
+
* Runs an effect with this value each time it changes; returns the disposer.
|
|
3071
|
+
*
|
|
3072
|
+
* 12.9: `s.watch` IS `watch` NARROWED TO THIS VALUE, and now structurally rather than by
|
|
3073
|
+
* matching its policy clause for clause. 12.3 is the general form — sources given rather
|
|
3074
|
+
* than discovered — and one source given is what `s.watch` means. What was here before was
|
|
3075
|
+
* a second copy of the subscriber, the disposer, the re-entrancy guard and 12.8, which is
|
|
3076
|
+
* exactly the divergence 12.9 forbids: the copy had no `catch` at all, so a throwing effect
|
|
3077
|
+
* re-ran into the same throw on every write where `watch` stops.
|
|
3078
|
+
*
|
|
3079
|
+
* The value is handed over rather than read, so the effect never reaches the tracked path
|
|
3080
|
+
* and never throws `NotLanded` from this source.
|
|
3081
|
+
*/
|
|
3082
|
+
function watchCell(
|
|
3083
|
+
cell: Cell,
|
|
3084
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: `void` is an EFFECT'S RETURN POSITION and is load-bearing. `undefined` there refuses the ordinary effect — a block body that returns nothing infers `void`, which is not assignable to `undefined` — so the rule's own fix would reject every caller that does not return a disposer.
|
|
3085
|
+
effect: (value: unknown) => void | (() => void),
|
|
3086
|
+
): () => void {
|
|
3087
|
+
return owned(
|
|
3088
|
+
watchOver([cell], null, () => {
|
|
3089
|
+
// 2.2 holds here as a SKIP rather than a throw: an effect has nothing to be handed
|
|
3090
|
+
// until something has landed, and its turn comes when it does.
|
|
3091
|
+
if (!cell.servable) return undefined
|
|
3092
|
+
return effect(heldValue(cell))
|
|
3093
|
+
}),
|
|
3094
|
+
)
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
function patchCell(cell: Cell, mutate: (value: unknown) => void): void {
|
|
3098
|
+
// 2.2: there is nothing to mutate where nothing has landed, and handing the callback
|
|
3099
|
+
// `undefined` failed inside the app's own code instead.
|
|
3100
|
+
if (!cell.landed) throw new NotLanded(cell.callable as object, cell.label)
|
|
3101
|
+
const stream = cell.stream
|
|
3102
|
+
if (stream === null) {
|
|
3103
|
+
const cap = cell.cap
|
|
3104
|
+
// THE HEAD IS THE LAST PRODUCTION MINTED, which a window may be holding apart from the one
|
|
3105
|
+
// served: patching the served one overwrote the held production with it, and the write
|
|
3106
|
+
// the window held was never seen.
|
|
3107
|
+
const head = cap?.holding && cell.body === null ? cap.held : cell.value
|
|
3108
|
+
recordWrites(head, mutate, PATCH_SINKS.get(cell))
|
|
3109
|
+
// 5.7: REPLACES THE HEAD PRODUCTION RATHER THAN APPENDING ONE, so the ordinal does not
|
|
3110
|
+
// move: the follow's version tells a patch from a production by exactly that, and sends a
|
|
3111
|
+
// patch as its writes (18.11). A cursor reads the change off the object's patch epoch
|
|
3112
|
+
// instead. The wake was never the ordinal's doing — it is the cascade's.
|
|
3113
|
+
//
|
|
3114
|
+
// 5.23 — AND THE CASCADE IS WHAT A WINDOW CAPS. The object has already changed, so what is
|
|
3115
|
+
// held is the wake alone: readers are told once per window, and `busy` stands meanwhile
|
|
3116
|
+
// (5.15). A body's cap caps its runs instead (5.11), so a patch there wakes as before.
|
|
3117
|
+
//
|
|
3118
|
+
// 8.8 AS `serve` READS IT: a patch the window held is written by the release that serves
|
|
3119
|
+
// it, so a run of patches in one window is one write rather than one per patch (5.11).
|
|
3120
|
+
wakePatched(cell, head)
|
|
3121
|
+
return
|
|
3122
|
+
}
|
|
3123
|
+
// 9.13: the accumulation itself, so the next chunk lands after the change rather than
|
|
3124
|
+
// rebuilding a snapshot from chunks the patch never saw.
|
|
3125
|
+
const before = stream.chunks.length
|
|
3126
|
+
// A CURSOR BEHIND needs where the patch wrote, which its ops say: the follow's own sink, or one
|
|
3127
|
+
// of this patch's alone where nothing follows the value.
|
|
3128
|
+
const followed = PATCH_SINKS.get(cell)
|
|
3129
|
+
const sink =
|
|
3130
|
+
followed ??
|
|
3131
|
+
(hasCursorBehind(cell) ? { ops: [], lost: false } : undefined)
|
|
3132
|
+
const from = sink === undefined ? 0 : sink.ops.length
|
|
3133
|
+
recordWrites(stream.chunks, mutate, sink)
|
|
3134
|
+
// 5.7 AS THE VALUE BRANCH KEEPS IT: a patch mints no ordinal, so an accumulation it grew or
|
|
3135
|
+
// shrank moves the ring's base instead — `ring[i]` stays ordinal `ringBase + i + 1` and the
|
|
3136
|
+
// next chunk lands one past the ordinal a caught-up cursor holds. And what it added is shown.
|
|
3137
|
+
const grown = stream.chunks.length - before
|
|
3138
|
+
if (grown !== 0) {
|
|
3139
|
+
const edited =
|
|
3140
|
+
sink === undefined || sink.lost
|
|
3141
|
+
? undefined
|
|
3142
|
+
: firstWritten(sink.ops, from, before)
|
|
3143
|
+
if (edited !== undefined) keepPlaces(cell, edited, grown)
|
|
3144
|
+
cell.ringBase -= grown
|
|
3145
|
+
stream.visible = Math.min(
|
|
3146
|
+
Math.max(stream.visible + grown, 0),
|
|
3147
|
+
stream.chunks.length,
|
|
3148
|
+
)
|
|
3149
|
+
}
|
|
3150
|
+
stream.version += 1
|
|
3151
|
+
// 5.23, as for a chunk (5.17): the window's close is what tells the readers.
|
|
3152
|
+
if (tellsChunks(cell, cell.cap)) cascade(cell)
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
/**
|
|
3156
|
+
* The lowest index of an accumulation the ops from `from` on wrote at, or `undefined` where they
|
|
3157
|
+
* wrote none — a reorder is sent whole, and says nothing of where.
|
|
3158
|
+
*/
|
|
3159
|
+
function firstWritten(
|
|
3160
|
+
ops: PatchOp[],
|
|
3161
|
+
from: number,
|
|
3162
|
+
length: number,
|
|
3163
|
+
): number | undefined {
|
|
3164
|
+
let lowest: number | undefined
|
|
3165
|
+
for (let at = from; at < ops.length; at += 1) {
|
|
3166
|
+
const path = (ops[at] as PatchOp).path
|
|
3167
|
+
const slash = path.indexOf('/', 1)
|
|
3168
|
+
const step = path.slice(1, slash === -1 ? path.length : slash)
|
|
3169
|
+
const index = step === '-' ? length : Number(step)
|
|
3170
|
+
if (lowest === undefined || index < lowest) lowest = index
|
|
3171
|
+
}
|
|
3172
|
+
return lowest
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
function wakePatched(cell: Cell, head: unknown): void {
|
|
3176
|
+
const cap = cell.cap
|
|
3177
|
+
if (cap === null || cell.body !== null || wakes(cell, cap, head)) {
|
|
3178
|
+
cascade(cell)
|
|
3179
|
+
if (cell.store !== null) persist(cell, cell.store, cell.value)
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
/**
|
|
3184
|
+
* What an `invalidate` leaves behind: nothing owed, a look above, or a reload.
|
|
3185
|
+
*
|
|
3186
|
+
* WHAT AN INVALIDATE LEAVES BEHIND, and it is a debt rather than a mark. The mark walk's states
|
|
3187
|
+
* are the wrong tool: a node marked `CHECK` or `DIRTY` without its effects queued is one a later
|
|
3188
|
+
* write's walk stops at — `state >= wanted` — and the effects below it are never woken for a
|
|
3189
|
+
* change that really happened. So nothing in the graph's state moves; the invalidated value OWES
|
|
3190
|
+
* A RELOAD, the values built from it OWE A LOOK ABOVE, and none of them is directly servable, so
|
|
3191
|
+
* the read that reaches any of them goes a frame in and settles the debt first (7.12, 7.13).
|
|
3192
|
+
*/
|
|
3193
|
+
const OWES_NOTHING: number = 0
|
|
3194
|
+
const OWES_ABOVE: number = 1
|
|
3195
|
+
const OWES_RELOAD: number = 2
|
|
3196
|
+
|
|
3197
|
+
/**
|
|
3198
|
+
* Visits a value and each value down its adoption chain.
|
|
3199
|
+
*
|
|
3200
|
+
* 11.51 IS EVERY TRIGGER AND EVERY ADOPTER, and the chain is walked rather than recursed
|
|
3201
|
+
* into: the reach into `adoption.source` sat PAST the "no producer" return, so an adoption made by
|
|
3202
|
+
* `outer.set(inner)` (11.61) — which leaves `body` null — swallowed both triggers whole.
|
|
3203
|
+
*
|
|
3204
|
+
* 13.6 is what the "no producer" arm is for: an entry with nothing to reload is skipped, and what
|
|
3205
|
+
* it ADOPTS still has to be reached.
|
|
3206
|
+
*/
|
|
3207
|
+
function eachAdopted(cell: Cell, visit: (cell: Cell) => void): void {
|
|
3208
|
+
let at: Cell = cell
|
|
3209
|
+
for (;;) {
|
|
3210
|
+
visit(at)
|
|
3211
|
+
// 11.51: the data is behind whatever the body handed back, not in the body itself
|
|
3212
|
+
// — AFTER the run, so the reach is to what is adopted now rather than what was.
|
|
3213
|
+
const adoption = at.adoption
|
|
3214
|
+
if (adoption === null) return
|
|
3215
|
+
at = adoption.source
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
function refreshOne(cell: Cell): void {
|
|
3220
|
+
// 7.6: inert on a value with no producer. 8.2 makes a store one, so a refresh re-reads it —
|
|
3221
|
+
// another tab may have written.
|
|
3222
|
+
if (!reloads(cell)) return
|
|
3223
|
+
// 5.24 — A WINDOW HOLDS A REFRESH AS IT HOLDS A PRODUCTION: a throttle lets the first through
|
|
3224
|
+
// and opens, a debounce holds every one and is pushed back by each, and what a window held is
|
|
3225
|
+
// one reload when it closes. Three refreshes in a row were three loads, two of them superseded.
|
|
3226
|
+
const cap = cell.cap
|
|
3227
|
+
if (cap !== null && windowHolds(cell, cap)) {
|
|
3228
|
+
cap.reloading = true
|
|
3229
|
+
return
|
|
3230
|
+
}
|
|
3231
|
+
reloadNow(cell)
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
function reloadNow(cell: Cell): void {
|
|
3235
|
+
// A refresh IS the reload an invalidate owed, so the read after it must not run a second.
|
|
3236
|
+
if (cell.owes === OWES_RELOAD) reloadOwed(cell)
|
|
3237
|
+
else reload(cell)
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
/**
|
|
3241
|
+
* Settles an owed reload: stops serving, then runs the producer.
|
|
3242
|
+
*
|
|
3243
|
+
* 7.8: THE HELD VALUE WAS MARKED WRONG, so the reload that settles the mark is not served over —
|
|
3244
|
+
* by a refresh or by the read that follows (7.13) alike. Unserved AHEAD of the reload, and the
|
|
3245
|
+
* order is load-bearing: re-adopting the same value re-mirrors only where the adopter is not
|
|
3246
|
+
* serving (11.18), and that mirror is what reaches a reload the adopted value owes. What it costs
|
|
3247
|
+
* is a reload answering at once with the value it held: its readers were woken by the unserve, and
|
|
3248
|
+
* read the same value again.
|
|
3249
|
+
*/
|
|
3250
|
+
function reloadOwed(cell: Cell): void {
|
|
3251
|
+
cell.owes = OWES_NOTHING
|
|
3252
|
+
unserve(cell)
|
|
3253
|
+
reload(cell)
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
/**
|
|
3257
|
+
* Marks one value as owing a reload, without running or waking anything.
|
|
3258
|
+
*
|
|
3259
|
+
* 7.3, 7.12, 7.14 AND 7.17: A MARK, NOT A LOAD, AND NOT A MOVE. Nothing runs, nothing is sent and
|
|
3260
|
+
* no reader wakes — a guard asking `ready()` included — so a server saying "yours is stale" moves
|
|
3261
|
+
* nothing on screen. The mark is a debt the next HIT settles: a read, or a probe, which answers about
|
|
3262
|
+
* the value a read would serve (3.2) and so runs the reload itself. That is what keeps a guard that
|
|
3263
|
+
* asks later from admitting a read that throws, and from stranding a placeholder that never reads.
|
|
3264
|
+
*/
|
|
3265
|
+
function invalidateOne(cell: Cell): void {
|
|
3266
|
+
// 7.6 — sent on to the browsers following it whether or not there is a producer to mark.
|
|
3267
|
+
const followed = FOLLOWED.get(cell)
|
|
3268
|
+
if (followed !== undefined) for (const sendOn of followed) sendOn()
|
|
3269
|
+
if (!reloads(cell)) return
|
|
3270
|
+
// Not directly servable, so the next read goes a frame in and settles the debt.
|
|
3271
|
+
cell.direct = false
|
|
3272
|
+
// 7.15: work already in flight was started from what this says is wrong. A load's answer
|
|
3273
|
+
// lands nowhere once `generation` has moved; a stream is stopped outright.
|
|
3274
|
+
cell.generation += 1
|
|
3275
|
+
cell.loading = false
|
|
3276
|
+
const stream = cell.stream
|
|
3277
|
+
if (stream !== null && stream.source === null) stopStream(stream)
|
|
3278
|
+
cell.owes = OWES_RELOAD
|
|
3279
|
+
owedBelow(cell)
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
/**
|
|
3283
|
+
* Flags everything built from this value as owing a look above.
|
|
3284
|
+
*
|
|
3285
|
+
* Every value built from this one — read from it, or adopting it — goes a frame in on its next
|
|
3286
|
+
* read, which is where 7.13's pull happens. A value already owing something has had its own
|
|
3287
|
+
* downstream flagged, so the walk stops there.
|
|
3288
|
+
*/
|
|
3289
|
+
function owedBelow(cell: Cell): void {
|
|
3290
|
+
const readers = cell.readers
|
|
3291
|
+
for (let index = 0; index < readers.length; index += 1) {
|
|
3292
|
+
const reader = readers[index] as Subscriber
|
|
3293
|
+
const below = (reader.isEffect
|
|
3294
|
+
? reader.adopter
|
|
3295
|
+
: reader) as unknown as Cell | null
|
|
3296
|
+
if (below === null) continue
|
|
3297
|
+
below.direct = false
|
|
3298
|
+
if (below.owes !== OWES_NOTHING) continue
|
|
3299
|
+
below.owes = OWES_ABOVE
|
|
3300
|
+
owedBelow(below)
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
/**
|
|
3305
|
+
* Settles what an `invalidate` left this value owing.
|
|
3306
|
+
*
|
|
3307
|
+
* THE READ SETTLES THE DEBT. A value owing a reload runs its producer; one owing a look above
|
|
3308
|
+
* reloads whatever above it owes one, and the cascade from what that reload mints marks it the
|
|
3309
|
+
* ordinary way — so a reload that lands the same value leaves it serving what it held.
|
|
3310
|
+
*/
|
|
3311
|
+
function settleOwed(cell: Cell): void {
|
|
3312
|
+
if (cell.owes === OWES_RELOAD) {
|
|
3313
|
+
reloadOwed(cell)
|
|
3314
|
+
return
|
|
3315
|
+
}
|
|
3316
|
+
cell.owes = OWES_NOTHING
|
|
3317
|
+
pullAbove(cell)
|
|
3318
|
+
// Nothing above it changed what it computed, so it is servable straight again.
|
|
3319
|
+
if (cell.state === CLEAN) setDirect(cell, cell.servable)
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
function pullAbove(cell: Cell): void {
|
|
3323
|
+
const sources = cell.sources
|
|
3324
|
+
for (let index = 0; index < sources.length; index += 1) {
|
|
3325
|
+
const source = sources[index] as Cell
|
|
3326
|
+
if (source.owes !== OWES_NOTHING) settleOwed(source)
|
|
3327
|
+
}
|
|
3328
|
+
const adoption = cell.adoption
|
|
3329
|
+
if (adoption !== null && adoption.source.owes !== OWES_NOTHING)
|
|
3330
|
+
settleOwed(adoption.source)
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
/**
|
|
3334
|
+
* The general `set`: adopts a `Reactive`, absorbs a load, or gates a value.
|
|
3335
|
+
*
|
|
3336
|
+
* 4.18: a refusal comes back to the caller, and only a refusal — an accepted write is `void`,
|
|
3337
|
+
* whatever ordinal `accept` minted for it. Behind its own frame so the settled, ungated
|
|
3338
|
+
* write above it stays small enough to be inlined into whatever loop calls `set`.
|
|
3339
|
+
*/
|
|
3340
|
+
function setThroughGates(cell: Cell, value: unknown): unknown {
|
|
3341
|
+
// 11.61: A `Reactive` WRITTEN IS ADOPTED, never stored — the same sentence 11.12 makes about
|
|
3342
|
+
// one a body hands back, and the brand is read before `then` for the same reason (1.6). It is
|
|
3343
|
+
// off the common write path rather than in front of it: a settled primitive never reaches this
|
|
3344
|
+
// frame at all, and what does is a thenable or a callable.
|
|
3345
|
+
if (isReactive(value)) {
|
|
3346
|
+
// 11.66 — a write that would close a cycle throws, as misuse rather than a refusal of data.
|
|
3347
|
+
const cycle = adopt(cell, (value as unknown as Callable)[CELL])
|
|
3348
|
+
if (cycle !== undefined) throw cycle
|
|
3349
|
+
return undefined
|
|
3350
|
+
}
|
|
3351
|
+
if (!isThenable(value)) {
|
|
3352
|
+
const answer = accept(cell, value)
|
|
3353
|
+
return typeof answer === 'number' ? undefined : answer
|
|
3354
|
+
}
|
|
3355
|
+
absorb(cell, value, false)
|
|
3356
|
+
return undefined
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
/**
|
|
3360
|
+
* Lands what a value opens with: a `Reactive` adopted, anything else absorbed.
|
|
3361
|
+
*
|
|
3362
|
+
* 11.61 AT CONSTRUCTION — `reactive(other)` opens on `other` the way `s.set(other)` writes it, by
|
|
3363
|
+
* adopting it rather than storing it, so the two spellings agree (31.8). Stored, the value held
|
|
3364
|
+
* `other`'s getter, and a read handed back a function.
|
|
3365
|
+
*/
|
|
3366
|
+
function open(cell: Cell, initial: unknown): void {
|
|
3367
|
+
if (!isReactive(initial)) {
|
|
3368
|
+
absorb(cell, initial, false)
|
|
3369
|
+
return
|
|
3370
|
+
}
|
|
3371
|
+
const cycle = adopt(cell, (initial as unknown as Callable)[CELL])
|
|
3372
|
+
if (cycle !== undefined) throw cycle
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
/**
|
|
3376
|
+
* Starts the producer of a keyed entry a window was holding back.
|
|
3377
|
+
*
|
|
3378
|
+
* 5.19 AND 5.21: AN ENTRY A WINDOW IS HOLDING BACK is busy with nothing to serve, and starting it is
|
|
3379
|
+
* the window's call. Starting runs the producer the reach would have run.
|
|
3380
|
+
*/
|
|
3381
|
+
export function startDeferred(value: unknown): void {
|
|
3382
|
+
startProducer((value as Callable)[CELL])
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
/**
|
|
3386
|
+
* Ends the load of a keyed entry a window superseded before it started.
|
|
3387
|
+
*
|
|
3388
|
+
* 5.20: an entry the window superseded before it started. Nothing is in flight for it any more,
|
|
3389
|
+
* and a reader still holding it hears that.
|
|
3390
|
+
*/
|
|
3391
|
+
export function abandonDeferred(value: unknown): void {
|
|
3392
|
+
const cell = (value as Callable)[CELL]
|
|
3393
|
+
if (!cell.loading) return
|
|
3394
|
+
cell.loading = false
|
|
3395
|
+
probesMoved(cell)
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
// ── `store` ──────────────────────────────────────────────────────────────────────────────
|
|
3399
|
+
|
|
3400
|
+
/**
|
|
3401
|
+
* Calls the store's `get`, taking a throw as a miss.
|
|
3402
|
+
*
|
|
3403
|
+
* 8.15: a `get` that throws cannot read the value, and that is a miss — warned, because a store
|
|
3404
|
+
* that can never be read is one an app wants to hear about.
|
|
3405
|
+
*/
|
|
3406
|
+
function readStore(cell: Cell, persisted: Persisted): unknown {
|
|
3407
|
+
try {
|
|
3408
|
+
return persisted.store.get()
|
|
3409
|
+
} catch (failed) {
|
|
3410
|
+
warnStore(
|
|
3411
|
+
cell,
|
|
3412
|
+
'could not be read, and is taken as a miss (8.15)',
|
|
3413
|
+
failed,
|
|
3414
|
+
)
|
|
3415
|
+
return undefined
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3419
|
+
/**
|
|
3420
|
+
* The logger a store failure warns through.
|
|
3421
|
+
*
|
|
3422
|
+
* 8.12 — under the namespace the clause names; a failed write also reaches `onError`, through
|
|
3423
|
+
* `storeWriteFailed`.
|
|
3424
|
+
*/
|
|
3425
|
+
const STORE_LOG = namedLogger('abide:reactive')
|
|
3426
|
+
|
|
3427
|
+
function warnStore(cell: Cell, what: string, failed: unknown): void {
|
|
3428
|
+
STORE_LOG.warning(`${cell.label}: a store ${what}.`, failed)
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
/**
|
|
3432
|
+
* Restores a value from its store at construction, falling back on a miss.
|
|
3433
|
+
*
|
|
3434
|
+
* 8.4: A SYNCHRONOUS `get` IS NOT A LOAD — the value is there at construction and nothing is
|
|
3435
|
+
* pending. An unsettled one is a load like any other, and a write made while it is out supersedes
|
|
3436
|
+
* it the way a write supersedes any load: `generation` moves, and the answer lands nowhere.
|
|
3437
|
+
*/
|
|
3438
|
+
function restoreFirst(
|
|
3439
|
+
cell: Cell,
|
|
3440
|
+
persisted: Persisted,
|
|
3441
|
+
initial: unknown,
|
|
3442
|
+
): void {
|
|
3443
|
+
const answer = readStore(cell, persisted)
|
|
3444
|
+
if (!isThenable(answer)) {
|
|
3445
|
+
settleRestore(cell, persisted, answer, initial)
|
|
3446
|
+
return
|
|
3447
|
+
}
|
|
3448
|
+
cell.loading = true
|
|
3449
|
+
const generation = cell.generation
|
|
3450
|
+
answer.then(
|
|
3451
|
+
(value) => {
|
|
3452
|
+
if (cell.generation === generation)
|
|
3453
|
+
settleRestore(cell, persisted, value, initial)
|
|
3454
|
+
},
|
|
3455
|
+
(failed: unknown) => {
|
|
3456
|
+
if (cell.generation !== generation) return
|
|
3457
|
+
warnStore(
|
|
3458
|
+
cell,
|
|
3459
|
+
'could not be read, and is taken as a miss (8.15)',
|
|
3460
|
+
failed,
|
|
3461
|
+
)
|
|
3462
|
+
settleRestore(cell, persisted, undefined, initial)
|
|
3463
|
+
},
|
|
3464
|
+
)
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3467
|
+
function settleRestore(
|
|
3468
|
+
cell: Cell,
|
|
3469
|
+
persisted: Persisted,
|
|
3470
|
+
value: unknown,
|
|
3471
|
+
initial: unknown,
|
|
3472
|
+
): void {
|
|
3473
|
+
if (value !== undefined) {
|
|
3474
|
+
restore(cell, persisted, value)
|
|
3475
|
+
return
|
|
3476
|
+
}
|
|
3477
|
+
// A MISS IS THE VALUE WITHOUT A STORE: the body runs, or the initial value lands (8.3).
|
|
3478
|
+
if (cell.body !== null) startProducer(cell)
|
|
3479
|
+
else if (initial === undefined) landOpening(cell)
|
|
3480
|
+
else if (initial !== NO_INITIAL) open(cell, initial)
|
|
3481
|
+
else if (cell.loading) {
|
|
3482
|
+
cell.loading = false
|
|
3483
|
+
probesMoved(cell)
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
/**
|
|
3488
|
+
* Mints a stored value as a production, ungated and not written back.
|
|
3489
|
+
*
|
|
3490
|
+
* 8.5: A RESTORE IS NOT A WRITE. It mints the production straight — no `schema`, no `transform`,
|
|
3491
|
+
* those gating what goes in and a restore being what was already stored — and 8.8 does not write
|
|
3492
|
+
* it back.
|
|
3493
|
+
*/
|
|
3494
|
+
function restore(cell: Cell, persisted: Persisted, value: unknown): void {
|
|
3495
|
+
persisted.restoring = true
|
|
3496
|
+
try {
|
|
3497
|
+
const stream = cell.stream
|
|
3498
|
+
if (stream === null) produce(cell, value)
|
|
3499
|
+
else restoreStream(cell, stream, value as unknown[])
|
|
3500
|
+
} finally {
|
|
3501
|
+
persisted.restoring = false
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
/**
|
|
3506
|
+
* Restores a stream's stored accumulation as a finished run.
|
|
3507
|
+
*
|
|
3508
|
+
* 8.6 AND 8.7: the chunk buffer is seeded with the accumulation, and the stream reads as finished — ready,
|
|
3509
|
+
* and not busy — with its body never started.
|
|
3510
|
+
*/
|
|
3511
|
+
function restoreStream(cell: Cell, stream: Stream, chunks: unknown[]): void {
|
|
3512
|
+
beginAccumulation(cell, stream)
|
|
3513
|
+
for (let index = 0; index < chunks.length; index += 1) {
|
|
3514
|
+
stream.chunks.push(chunks[index])
|
|
3515
|
+
cell.ordinal += 1
|
|
3516
|
+
}
|
|
3517
|
+
stream.visible = stream.chunks.length
|
|
3518
|
+
stream.version += 1
|
|
3519
|
+
const moved = settleProbes(cell)
|
|
3520
|
+
landChunks(cell)
|
|
3521
|
+
cascade(cell)
|
|
3522
|
+
if (moved) probesMoved(cell)
|
|
3523
|
+
// 8.7 SAYS THE BODY NEVER STARTS, AND `pump` IS WHAT REACHES `closeRun` — so the
|
|
3524
|
+
// restored accumulation is entered as a closed run here or nothing ever does it.
|
|
3525
|
+
// Without this `standingLive` answers false for the life of the value, which takes
|
|
3526
|
+
// the `ttl` out of `expire` and `sweepExpiry` silently — it never drops and never
|
|
3527
|
+
// re-reads the store.
|
|
3528
|
+
closeRun(cell, stream)
|
|
3529
|
+
}
|
|
3530
|
+
|
|
3531
|
+
/**
|
|
3532
|
+
* Reloads a value whose only producer is its store.
|
|
3533
|
+
*
|
|
3534
|
+
* A refresh, an invalidate or an expiry of a value whose only producer is its store. A hit
|
|
3535
|
+
* restores; a miss leaves what is held being served, there being nothing newer to replace it with.
|
|
3536
|
+
*
|
|
3537
|
+
* 8.18: A WRITE THE APP HAS MADE IS THE NEWEST TRUTH, so while one is unconfirmed the store is not
|
|
3538
|
+
* asked at all and the written value stands in its place. Asking took the value BACKWARDS and left
|
|
3539
|
+
* it there: `s.set(99)` over a store still holding 1 leaves a `set` in flight, a `refresh` in the
|
|
3540
|
+
* same turn read 1 and produced it, and the queued write then landed 99 in the store — the cell
|
|
3541
|
+
* serving 1 and the store holding 99 for the life of the value, with `restoring` stopping the
|
|
3542
|
+
* reverted value from being written back and no probe moving to say so.
|
|
3543
|
+
*/
|
|
3544
|
+
function rereadStore(cell: Cell, persisted: Persisted): void {
|
|
3545
|
+
if (persisted.writing) {
|
|
3546
|
+
restore(cell, persisted, persisted.unconfirmed)
|
|
3547
|
+
return
|
|
3548
|
+
}
|
|
3549
|
+
const answer = readStore(cell, persisted)
|
|
3550
|
+
if (!isThenable(answer)) {
|
|
3551
|
+
settleReread(cell, persisted, answer)
|
|
3552
|
+
return
|
|
3553
|
+
}
|
|
3554
|
+
beginLoad(cell)
|
|
3555
|
+
const generation = cell.generation
|
|
3556
|
+
answer.then(
|
|
3557
|
+
(value) => {
|
|
3558
|
+
if (cell.generation !== generation) return
|
|
3559
|
+
settleReread(cell, persisted, value)
|
|
3560
|
+
},
|
|
3561
|
+
(failed: unknown) => {
|
|
3562
|
+
if (cell.generation !== generation) return
|
|
3563
|
+
warnStore(
|
|
3564
|
+
cell,
|
|
3565
|
+
'could not be read, and is taken as a miss (8.15)',
|
|
3566
|
+
failed,
|
|
3567
|
+
)
|
|
3568
|
+
settleReread(cell, persisted, undefined)
|
|
3569
|
+
},
|
|
3570
|
+
)
|
|
3571
|
+
}
|
|
3572
|
+
|
|
3573
|
+
/**
|
|
3574
|
+
* Lands a store re-read, letting an unconfirmed write win.
|
|
3575
|
+
*
|
|
3576
|
+
* 8.18 IS READ ON THE WAY OUT AS WELL AS ON THE WAY IN. A write made while the `get` was in flight
|
|
3577
|
+
* is newer than whatever that `get` answers, hit or miss, and `generation` does not separate them
|
|
3578
|
+
* — a write moves it only where it is a LOAD (4.2), and a settled `s.set` never touches it.
|
|
3579
|
+
*/
|
|
3580
|
+
function settleReread(cell: Cell, persisted: Persisted, value: unknown): void {
|
|
3581
|
+
if (persisted.writing) restore(cell, persisted, persisted.unconfirmed)
|
|
3582
|
+
else if (value !== undefined) restore(cell, persisted, value)
|
|
3583
|
+
else settleMiss(cell)
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
function settleMiss(cell: Cell): void {
|
|
3587
|
+
// 8.19: NOTHING LEFT TO SERVE. The miss reached a value that holds nothing — an expiry
|
|
3588
|
+
// released the standing production (6.7) and the re-read 6.6 owes it came back empty, and
|
|
3589
|
+
// there is no body to fall through to on a value whose only producer is its store. Left as a
|
|
3590
|
+
// miss it read as `NotLanded` with all three probes silent for the life of the value, and
|
|
3591
|
+
// `expire` returned at `dropped` on every later read, so nothing ever asked again.
|
|
3592
|
+
if (!cell.landed) {
|
|
3593
|
+
refused(
|
|
3594
|
+
cell,
|
|
3595
|
+
new Error(
|
|
3596
|
+
`${cell.label}: its store answered no value and nothing is held (RULEBOOK 8.19).`,
|
|
3597
|
+
),
|
|
3598
|
+
)
|
|
3599
|
+
return
|
|
3600
|
+
}
|
|
3601
|
+
const wasServable = cell.servable
|
|
3602
|
+
const wasLoading = cell.loading
|
|
3603
|
+
cell.servable = true
|
|
3604
|
+
cell.loading = false
|
|
3605
|
+
setDirect(cell, cell.state === CLEAN)
|
|
3606
|
+
if (cell.servable !== wasServable) cascade(cell)
|
|
3607
|
+
if (wasLoading || cell.servable !== wasServable) probesMoved(cell)
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
/**
|
|
3611
|
+
* Writes a production to the store, unless it is a restore or a stream's.
|
|
3612
|
+
*
|
|
3613
|
+
* A restore is what was just read, and a stream is written at its close (8.9) — neither is
|
|
3614
|
+
* persisted from here.
|
|
3615
|
+
*/
|
|
3616
|
+
function persist(cell: Cell, persisted: Persisted, value: unknown): void {
|
|
3617
|
+
if (persisted.restoring || cell.stream !== null) return
|
|
3618
|
+
writeStore(cell, persisted, value)
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
function writeStore(cell: Cell, persisted: Persisted, value: unknown): void {
|
|
3622
|
+
persisted.unconfirmed = value
|
|
3623
|
+
if (persisted.writing) {
|
|
3624
|
+
persisted.pending = true
|
|
3625
|
+
return
|
|
3626
|
+
}
|
|
3627
|
+
persisted.writing = true
|
|
3628
|
+
sendStore(cell, persisted, value)
|
|
3629
|
+
}
|
|
3630
|
+
|
|
3631
|
+
function storeWriteFailed(cell: Cell, failed: unknown): void {
|
|
3632
|
+
warnStore(cell, 'write failed, and the store stays attached (8.12)', failed)
|
|
3633
|
+
UNEXPECTED.report?.(failed)
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
/**
|
|
3637
|
+
* Hands one value to the store's `set`, warning where it fails.
|
|
3638
|
+
*
|
|
3639
|
+
* 8.11: the write never blocks a reader — the value is already held, and `set` is only how it
|
|
3640
|
+
* leaves. 8.12: a failed write warns and leaves the store attached, so the next is written.
|
|
3641
|
+
*/
|
|
3642
|
+
function sendStore(cell: Cell, persisted: Persisted, value: unknown): void {
|
|
3643
|
+
let sent: unknown
|
|
3644
|
+
try {
|
|
3645
|
+
sent = persisted.store.set(value, { ttl: persisted.ttl })
|
|
3646
|
+
} catch (failed) {
|
|
3647
|
+
storeWriteFailed(cell, failed)
|
|
3648
|
+
sent = undefined
|
|
3649
|
+
}
|
|
3650
|
+
if (!isThenable(sent)) {
|
|
3651
|
+
storeWritten(cell, persisted)
|
|
3652
|
+
return
|
|
3653
|
+
}
|
|
3654
|
+
sent.then(
|
|
3655
|
+
() => {
|
|
3656
|
+
storeWritten(cell, persisted)
|
|
3657
|
+
},
|
|
3658
|
+
(failed: unknown) => {
|
|
3659
|
+
storeWriteFailed(cell, failed)
|
|
3660
|
+
storeWritten(cell, persisted)
|
|
3661
|
+
},
|
|
3662
|
+
)
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3665
|
+
function storeWritten(cell: Cell, persisted: Persisted): void {
|
|
3666
|
+
if (!persisted.pending) {
|
|
3667
|
+
persisted.writing = false
|
|
3668
|
+
persisted.unconfirmed = undefined
|
|
3669
|
+
return
|
|
3670
|
+
}
|
|
3671
|
+
// `unconfirmed` IS ALREADY THE ONE TO SEND — 8.10 coalesces latest-wins, so every write made
|
|
3672
|
+
// while this one was out overwrote it, and the send below is the newest of them.
|
|
3673
|
+
persisted.pending = false
|
|
3674
|
+
sendStore(cell, persisted, persisted.unconfirmed)
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3677
|
+
// ── Selections over `tags`, and the bare sweep ──────────────────────────────────────────
|
|
3678
|
+
|
|
3679
|
+
/**
|
|
3680
|
+
* Each tag to the values carrying it, held weakly.
|
|
3681
|
+
*
|
|
3682
|
+
* THE INDEX HOLDS VALUES WEAKLY. A tag is a way to find a value, never a reason to keep one, and a
|
|
3683
|
+
* strong index would hold every tagged value a page ever made for the life of the process. A
|
|
3684
|
+
* collected value is pruned from its sets by the next sweep that finds it gone.
|
|
3685
|
+
*/
|
|
3686
|
+
const TAGGED = new Map<string, Set<WeakRef<Cell>>>()
|
|
3687
|
+
|
|
3688
|
+
/**
|
|
3689
|
+
* Every value whose producer is a LOAD, held weakly — what a bare selection sweeps.
|
|
3690
|
+
*
|
|
3691
|
+
* 13.6: A BARE SWEEP REACHES PRODUCERS, and of those it files the ones that LOAD: a value holding
|
|
3692
|
+
* a `store`, filed where it is built, and a body the first time it starts a load — a pending
|
|
3693
|
+
* answer or a stream (`beginLoad`). A synchronous body is a derivation: reloading it recomputes
|
|
3694
|
+
* what its sources already say, and whatever load it reads is filed on its own. And filing is
|
|
3695
|
+
* what every filed value pays — a weak handle measured ~230 ns a value under V8 against ~20 under
|
|
3696
|
+
* JSC — so a value with nothing to load never pays it: a derivation per cell per row stays at
|
|
3697
|
+
* what it cost before, and a load pays it beside the promise it already allocates.
|
|
3698
|
+
*
|
|
3699
|
+
* Held weakly for `TAGGED`'s reason. AN ARRAY, COMPACTED, RATHER THAN A `Set`: a `Set` of weak
|
|
3700
|
+
* handles measured ~120 ns a filing under JSC against ~10 for a push. What a `Set` bought was an
|
|
3701
|
+
* O(1) delete on eviction; here an evicted value drops its `ref` instead, and a handle whose value
|
|
3702
|
+
* no longer points back at it is dead, the same as one whose value was collected. Because a bare
|
|
3703
|
+
* sweep may never run, the filing that finds the array past `compactProducersAt` compacts it, and
|
|
3704
|
+
* the bar doubles past what survives, so the walk is amortised over the filings between two.
|
|
3705
|
+
*/
|
|
3706
|
+
let filedProducers: WeakRef<Cell>[] = []
|
|
3707
|
+
let compactProducersAt = 1024
|
|
3708
|
+
|
|
3709
|
+
/**
|
|
3710
|
+
* The probe signal a guard over a selection subscribes to.
|
|
3711
|
+
*
|
|
3712
|
+
* 13.3 AND 13.9: ONE SIGNAL PER SELECTION, keyed by its canonical tags. A guard over a selection
|
|
3713
|
+
* subscribes to this, never to each entry — the probe edge `trackProbe` makes, from a node that
|
|
3714
|
+
* is nothing but that edge.
|
|
3715
|
+
*/
|
|
3716
|
+
type Signal = { readers: Subscriber[]; probeReaders: Subscriber[] }
|
|
3717
|
+
const SIGNALS = new Map<string, Signal>()
|
|
3718
|
+
const SIGNALS_BY_TAG = new Map<string, Signal[]>()
|
|
3719
|
+
/**
|
|
3720
|
+
* The bare selection's one signal. Not a key in `SIGNALS`: `{ tags: [] }` canonicalises to the
|
|
3721
|
+
* empty set and matches nothing, where the bare selection matches every filed load in reach.
|
|
3722
|
+
*/
|
|
3723
|
+
const EVERY_PRODUCER: Signal = { readers: [], probeReaders: [] }
|
|
3724
|
+
|
|
3725
|
+
function refOf(cell: Cell): WeakRef<Cell> {
|
|
3726
|
+
let ref = cell.ref
|
|
3727
|
+
if (ref === null) {
|
|
3728
|
+
ref = new WeakRef(cell)
|
|
3729
|
+
cell.ref = ref
|
|
3730
|
+
}
|
|
3731
|
+
return ref
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
function tagCell(cell: Cell): void {
|
|
3735
|
+
const ref = refOf(cell)
|
|
3736
|
+
for (const tag of cell.tags as string[]) {
|
|
3737
|
+
let carrying = TAGGED.get(tag)
|
|
3738
|
+
if (carrying === undefined) {
|
|
3739
|
+
carrying = new Set()
|
|
3740
|
+
TAGGED.set(tag, carrying)
|
|
3741
|
+
}
|
|
3742
|
+
carrying.add(ref)
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
function fileProducer(cell: Cell): void {
|
|
3747
|
+
cell.filed = true
|
|
3748
|
+
filedProducers.push(refOf(cell))
|
|
3749
|
+
if (filedProducers.length > compactProducersAt) {
|
|
3750
|
+
filedProducers = liveProducers()
|
|
3751
|
+
compactProducersAt = Math.max(1024, filedProducers.length * 2)
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
|
|
3755
|
+
/** The handles whose value is still standing and still filed — neither collected nor evicted. */
|
|
3756
|
+
function liveProducers(): WeakRef<Cell>[] {
|
|
3757
|
+
const live: WeakRef<Cell>[] = []
|
|
3758
|
+
for (let index = 0; index < filedProducers.length; index += 1) {
|
|
3759
|
+
const ref = filedProducers[index] as WeakRef<Cell>
|
|
3760
|
+
if (ref.deref()?.ref === ref) live.push(ref)
|
|
3761
|
+
}
|
|
3762
|
+
return live
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
/**
|
|
3766
|
+
* Removes an evicted value from every selection index.
|
|
3767
|
+
*
|
|
3768
|
+
* What an eviction calls: an entry out of its `Keyed` is out of every selection with it, rather
|
|
3769
|
+
* than reachable by a sweep until a collection happens to run.
|
|
3770
|
+
*/
|
|
3771
|
+
export function leaveSelections(value: unknown): void {
|
|
3772
|
+
const cell = (value as Callable)[CELL]
|
|
3773
|
+
const ref = cell.ref
|
|
3774
|
+
if (ref === null) return
|
|
3775
|
+
// What takes it out of `filedProducers`: a handle its value no longer points back at is dead.
|
|
3776
|
+
cell.ref = null
|
|
3777
|
+
if (cell.tags === null) return
|
|
3778
|
+
for (const tag of cell.tags) {
|
|
3779
|
+
const carrying = TAGGED.get(tag)
|
|
3780
|
+
if (carrying === undefined) continue
|
|
3781
|
+
carrying.delete(ref)
|
|
3782
|
+
if (carrying.size === 0) TAGGED.delete(tag)
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
|
|
3786
|
+
/**
|
|
3787
|
+
* Takes every value filed so far out of the bare selection, each filed again by its next load.
|
|
3788
|
+
*
|
|
3789
|
+
* A TEST'S SEAM, with no caller in the framework — `bareSelection.test.ts` calls it before its
|
|
3790
|
+
* first case: a page's process ends with the page, and a test process runs every file before this
|
|
3791
|
+
* one in it. What those files left held — a module's `global`
|
|
3792
|
+
* value, a follow the page keeps for its life — is in reach of a bare sweep (13.13), and a bare
|
|
3793
|
+
* `refresh()` reloads it and reads `busy()` true while it asks.
|
|
3794
|
+
*/
|
|
3795
|
+
export function unfileProducers(): void {
|
|
3796
|
+
for (let index = 0; index < filedProducers.length; index += 1) {
|
|
3797
|
+
const cell = (filedProducers[index] as WeakRef<Cell>).deref()
|
|
3798
|
+
if (cell !== undefined) cell.filed = false
|
|
3799
|
+
}
|
|
3800
|
+
filedProducers = []
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3803
|
+
/**
|
|
3804
|
+
* The values a selection reaches in the current scope, `global` values included: those carrying
|
|
3805
|
+
* any of `tags`, or every filed load where `tags` is `null` — the bare selection.
|
|
3806
|
+
*
|
|
3807
|
+
* 13.8 — A SELECTION IS SCOPE-BOUNDED, and a tag with it, so a selection inside one request never
|
|
3808
|
+
* reaches the values another request filed. 13.13 — a `global` value is in every scope, so every
|
|
3809
|
+
* selection reaches it, and 13.10 is the bare case of that. In a browser every cell's scope is
|
|
3810
|
+
* `undefined`, and so is this.
|
|
3811
|
+
*/
|
|
3812
|
+
function inReach(tags: string[] | null): Cell[] {
|
|
3813
|
+
const scope = currentScope()
|
|
3814
|
+
if (tags === null) {
|
|
3815
|
+
filedProducers = liveProducers()
|
|
3816
|
+
const found: Cell[] = []
|
|
3817
|
+
for (let index = 0; index < filedProducers.length; index += 1) {
|
|
3818
|
+
const cell = (
|
|
3819
|
+
filedProducers[index] as WeakRef<Cell>
|
|
3820
|
+
).deref() as Cell
|
|
3821
|
+
if (cell.scope === scope || cell.global) found.push(cell)
|
|
3822
|
+
}
|
|
3823
|
+
return found
|
|
3824
|
+
}
|
|
3825
|
+
const found = new Set<Cell>()
|
|
3826
|
+
for (const tag of tags) {
|
|
3827
|
+
const refs = TAGGED.get(tag)
|
|
3828
|
+
if (refs === undefined) continue
|
|
3829
|
+
for (const ref of refs) {
|
|
3830
|
+
const cell = ref.deref()
|
|
3831
|
+
if (cell === undefined) refs.delete(ref)
|
|
3832
|
+
else if (cell.scope === scope || cell.global) found.add(cell)
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
return [...found]
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
function trackSignal(tags: string[] | null): void {
|
|
3839
|
+
const reader = TRACKING.reader
|
|
3840
|
+
if (reader === null) return
|
|
3841
|
+
if (tags === null) {
|
|
3842
|
+
trackProbe(EVERY_PRODUCER as unknown as Tracked, reader)
|
|
3843
|
+
return
|
|
3844
|
+
}
|
|
3845
|
+
const key = JSON.stringify([...new Set(tags)].sort())
|
|
3846
|
+
let signal = SIGNALS.get(key)
|
|
3847
|
+
if (signal === undefined) {
|
|
3848
|
+
signal = { readers: [], probeReaders: [] }
|
|
3849
|
+
SIGNALS.set(key, signal)
|
|
3850
|
+
for (const tag of tags) {
|
|
3851
|
+
const signals = SIGNALS_BY_TAG.get(tag)
|
|
3852
|
+
if (signals === undefined) SIGNALS_BY_TAG.set(tag, [signal])
|
|
3853
|
+
else signals.push(signal)
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
trackProbe(signal as unknown as Tracked, reader)
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
function markSignals(tags: string[]): void {
|
|
3860
|
+
for (const tag of tags) {
|
|
3861
|
+
const signals = SIGNALS_BY_TAG.get(tag)
|
|
3862
|
+
if (signals === undefined) continue
|
|
3863
|
+
for (const signal of signals) markAsked(signal.probeReaders)
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3867
|
+
/**
|
|
3868
|
+
* Whether any value in the selection is busy; a reader subscribes to the selection.
|
|
3869
|
+
*
|
|
3870
|
+
* `null` is the bare selection — every filed load in reach — and so for the three below.
|
|
3871
|
+
*/
|
|
3872
|
+
export function busyOver(tags: string[] | null): boolean {
|
|
3873
|
+
trackSignal(tags)
|
|
3874
|
+
for (const cell of inReach(tags)) if (busyNow(cell)) return true
|
|
3875
|
+
return false
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
/** Whether every value in the selection is ready; a reader subscribes to the selection. */
|
|
3879
|
+
export function readyOver(tags: string[] | null): boolean {
|
|
3880
|
+
trackSignal(tags)
|
|
3881
|
+
for (const cell of inReach(tags)) if (!readyNow(cell)) return false
|
|
3882
|
+
return true
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
/**
|
|
3886
|
+
* Invalidates every value in the selection, and what each adopts.
|
|
3887
|
+
*
|
|
3888
|
+
* THE BARE SWEEP VISITS EACH VALUE ALONE: whatever an adoption reaches that has a producer is filed
|
|
3889
|
+
* itself, so walking the chain as well would visit it twice.
|
|
3890
|
+
*/
|
|
3891
|
+
export function invalidateOver(tags: string[] | null): void {
|
|
3892
|
+
for (const cell of inReach(tags)) invalidateIn(cell, tags)
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
function invalidateIn(cell: Cell, tags: string[] | null): void {
|
|
3896
|
+
// 13.17 — A WRITE IN FLIGHT IS LEFT TO LAND: marked, its caller's await pulls the reload, and a
|
|
3897
|
+
// mutation's reload is the write sent again.
|
|
3898
|
+
if (cell.mutation && cell.loading) return
|
|
3899
|
+
if (tags === null) invalidateOne(cell)
|
|
3900
|
+
else eachAdopted(cell, invalidateOne)
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
/**
|
|
3904
|
+
* Refreshes the values in the selection, or marks the ones nothing reads.
|
|
3905
|
+
*
|
|
3906
|
+
* 13.7: RELOADED WHERE SOMETHING READS IT, MARKED WHERE NOTHING DOES. A mark is the recompute a
|
|
3907
|
+
* write would have owed it — `DIRTY` and not directly servable — so the next read pulls the reload
|
|
3908
|
+
* and until then nothing is requested. A stream is refreshed rather than marked: a pull runs a
|
|
3909
|
+
* BODY, and a stream's body is a generator that only `startStream` knows how to run.
|
|
3910
|
+
*/
|
|
3911
|
+
export function refreshOver(tags: string[] | null): void {
|
|
3912
|
+
for (const cell of inReach(tags)) {
|
|
3913
|
+
// 13.16 — a mutation's reload is its write sent again, so a sweep marks it instead.
|
|
3914
|
+
if (cell.mutation) invalidateIn(cell, tags)
|
|
3915
|
+
else if (
|
|
3916
|
+
readByAny(cell.readers) ||
|
|
3917
|
+
readByAny(cell.probeReaders) ||
|
|
3918
|
+
cell.stream !== null
|
|
3919
|
+
) {
|
|
3920
|
+
if (tags === null) refreshOne(cell)
|
|
3921
|
+
else eachAdopted(cell, refreshOne)
|
|
3922
|
+
} else if (cell.body !== null) {
|
|
3923
|
+
cell.state = DIRTY
|
|
3924
|
+
cell.direct = false
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
/**
|
|
3930
|
+
* Whether any of `readers` is something reading the value rather than a keyed entry's lifetime.
|
|
3931
|
+
*
|
|
3932
|
+
* THE LIFETIME IS THE ONE MIRROR WITH NO ADOPTER (`Subscriber.isMirror`): it subscribes to an entry
|
|
3933
|
+
* of a keyed value declaring `ttl` to hear its productions (11.45), and counted here it had 13.7
|
|
3934
|
+
* reload every such entry nothing reads.
|
|
3935
|
+
*/
|
|
3936
|
+
function readByAny(readers: Subscriber[]): boolean {
|
|
3937
|
+
for (let index = 0; index < readers.length; index += 1) {
|
|
3938
|
+
const reader = readers[index] as Subscriber
|
|
3939
|
+
if (!reader.isMirror || reader.adopter !== null) return true
|
|
3940
|
+
}
|
|
3941
|
+
return false
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
const MEMBERS = {
|
|
3945
|
+
peek(this: Callable): unknown {
|
|
3946
|
+
const cell = this[CELL]
|
|
3947
|
+
// PULLS, like a read. Under push-pull a write leaves every derived value it
|
|
3948
|
+
// reached unrun, so a `peek` that only read the field would answer with the
|
|
3949
|
+
// value the last read left behind — 2.2 moved the ABSENCE here, not staleness.
|
|
3950
|
+
bringCurrent(cell)
|
|
3951
|
+
// A `peek` IS A READ by the rulebook's own term, so 6.5 and 6.6 reach it
|
|
3952
|
+
// the same way. What it does not join is the FLOW (2.10) — an expiry is not a
|
|
3953
|
+
// subscription, and a `peek` that went on serving a production `s()` had already
|
|
3954
|
+
// dropped would be two answers to what is held.
|
|
3955
|
+
if (cell.expiry !== null) expire(cell)
|
|
3956
|
+
if (!cell.servable) return undefined
|
|
3957
|
+
return heldValue(cell)
|
|
3958
|
+
},
|
|
3959
|
+
busy(this: Callable): boolean {
|
|
3960
|
+
return isBusy(this[CELL])
|
|
3961
|
+
},
|
|
3962
|
+
ready(this: Callable): boolean {
|
|
3963
|
+
return isReady(this[CELL])
|
|
3964
|
+
},
|
|
3965
|
+
error(this: Callable): unknown {
|
|
3966
|
+
const cell = this[CELL]
|
|
3967
|
+
// 3.3 ON THE THIRD PROBE. `error` is a probe by the rulebook's own term, so a body
|
|
3968
|
+
// reading it subscribes the same way `busy` and `ready` do — without this edge
|
|
3969
|
+
// `{#if s.error()}` reads `undefined` once and nothing wakes it when a load fails,
|
|
3970
|
+
// because a refusal marks the VALUE readers and this body never read the value.
|
|
3971
|
+
probe(cell)
|
|
3972
|
+
return failureOf(cell)
|
|
3973
|
+
},
|
|
3974
|
+
/**
|
|
3975
|
+
* The shared `isFailedNamed`, since this member reads no cell.
|
|
3976
|
+
*
|
|
3977
|
+
* THE ONE MEMBER THAT READS NO CELL, so it is the shared function itself — `Rpc.isError` is the
|
|
3978
|
+
* same one. Its first parameter is the error, as the member's is.
|
|
3979
|
+
*/
|
|
3980
|
+
isError: isFailedNamed,
|
|
3981
|
+
set(this: Callable, value: unknown): unknown {
|
|
3982
|
+
const cell = this[CELL]
|
|
3983
|
+
cell.generation += 1
|
|
3984
|
+
// The settled, untransformed write is the overwhelming common shape, and the
|
|
3985
|
+
// general path is two call frames and a thenable probe behind it. Inlined here
|
|
3986
|
+
// rather than behind `isThenable` so a primitive write tests two types.
|
|
3987
|
+
// The same pair as the recompute arm above, and for the same reason: a `set` that
|
|
3988
|
+
// skipped `accept` skipped 4.3 with it.
|
|
3989
|
+
if (
|
|
3990
|
+
cell.bareWrite &&
|
|
3991
|
+
(value === null ||
|
|
3992
|
+
(typeof value !== 'object' && typeof value !== 'function') ||
|
|
3993
|
+
typeof (value as PromiseLike<unknown>).then !== 'function')
|
|
3994
|
+
) {
|
|
3995
|
+
produce(cell, value)
|
|
3996
|
+
return undefined
|
|
3997
|
+
}
|
|
3998
|
+
return setThroughGates(cell, value)
|
|
3999
|
+
},
|
|
4000
|
+
patch(this: Callable, mutate: (value: unknown) => void): void {
|
|
4001
|
+
patchCell(this[CELL], mutate)
|
|
4002
|
+
},
|
|
4003
|
+
refresh(this: Callable): void {
|
|
4004
|
+
eachAdopted(this[CELL], refreshOne)
|
|
4005
|
+
},
|
|
4006
|
+
invalidate(this: Callable): void {
|
|
4007
|
+
eachAdopted(this[CELL], invalidateOne)
|
|
4008
|
+
},
|
|
4009
|
+
watch(
|
|
4010
|
+
this: Callable,
|
|
4011
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: `void` is an EFFECT'S RETURN POSITION and is load-bearing. `undefined` there refuses the ordinary effect — a block body that returns nothing infers `void`, which is not assignable to `undefined` — so the rule's own fix would reject every caller that does not return a disposer.
|
|
4012
|
+
effect: (value: unknown) => void | (() => void),
|
|
4013
|
+
): () => void {
|
|
4014
|
+
return watchCell(this[CELL], effect)
|
|
4015
|
+
},
|
|
4016
|
+
[Symbol.asyncIterator](this: Callable): AsyncIterator<unknown> {
|
|
4017
|
+
const cell = this[CELL]
|
|
4018
|
+
// 2.5: "the production in flight, from its start". Starting one ordinal back is
|
|
4019
|
+
// what includes it rather than skipping to whatever comes next — and clamped at
|
|
4020
|
+
// zero, where a value nothing has produced into must WAIT rather than hand over
|
|
4021
|
+
// the `undefined` it is holding.
|
|
4022
|
+
// A STREAM'S PRODUCTION IN FLIGHT IS THE STREAM, so its start is the run's first
|
|
4023
|
+
// chunk rather than the latest one.
|
|
4024
|
+
const from =
|
|
4025
|
+
cell.stream !== null
|
|
4026
|
+
? cell.ringBase
|
|
4027
|
+
: cell.ordinal > 0
|
|
4028
|
+
? cell.ordinal - 1
|
|
4029
|
+
: 0
|
|
4030
|
+
return new Cursor(cell, from)
|
|
4031
|
+
},
|
|
4032
|
+
// biome-ignore lint/suspicious/noThenProperty: RULEBOOK 1.4 — a `Reactive` MUST be thenable, which is what this rule warns about. Banning it buys a discrimination rather than a semantics, and 16.4's in-process `Rpc` arm is what that discrimination would have cost. 1.6 is the guard that makes thenability safe — the brand is read before `then`.
|
|
4033
|
+
then(
|
|
4034
|
+
this: Callable,
|
|
4035
|
+
onSettled?: (value: unknown) => unknown,
|
|
4036
|
+
onFailed?: (error: unknown) => unknown,
|
|
4037
|
+
): Promise<unknown> {
|
|
4038
|
+
return settled(this[CELL]).then(onSettled, onFailed)
|
|
4039
|
+
},
|
|
4040
|
+
catch(
|
|
4041
|
+
this: Callable,
|
|
4042
|
+
onFailed: (error: unknown) => unknown,
|
|
4043
|
+
): Promise<unknown> {
|
|
4044
|
+
return settled(this[CELL]).catch(onFailed)
|
|
4045
|
+
},
|
|
4046
|
+
finally(this: Callable, onSettled: () => void): Promise<unknown> {
|
|
4047
|
+
return settled(this[CELL]).finally(onSettled)
|
|
4048
|
+
},
|
|
4049
|
+
}
|
|
4050
|
+
|
|
4051
|
+
/**
|
|
4052
|
+
* The cell `value` serves from, through every adoption.
|
|
4053
|
+
*
|
|
4054
|
+
* 11.12: an adopting value serves the adopted value's production, and an adoption of an adoption
|
|
4055
|
+
* goes on down — so a follow's version (18.7) and its feed name the cell that actually produced.
|
|
4056
|
+
*/
|
|
4057
|
+
export function servedCellOf(value: unknown): Retained {
|
|
4058
|
+
return adoptedCellOf(value)
|
|
4059
|
+
}
|
|
4060
|
+
|
|
4061
|
+
/**
|
|
4062
|
+
* When the production `value` serves stops being servable, as a wall-clock time.
|
|
4063
|
+
*
|
|
4064
|
+
* 21.8 — WHEN THE PRODUCTION A VALUE IS SERVING STOPS BEING SERVABLE, as a wall-clock time: its
|
|
4065
|
+
* own `ttl` from when it was minted (6.5), or its entry's from when the entry last produced
|
|
4066
|
+
* (11.45), through whatever it adopts. Infinity where neither bounds it.
|
|
4067
|
+
*/
|
|
4068
|
+
export function deadlineOf(value: unknown): number {
|
|
4069
|
+
const cell = adoptedCellOf(value)
|
|
4070
|
+
if (cell.expiry !== null) return cell.expiry.at + cell.expiry.ms
|
|
4071
|
+
const entry = ENTRY_LIVES.get(cell)
|
|
4072
|
+
return entry === undefined ? Number.POSITIVE_INFINITY : entry.at + entry.ms
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4075
|
+
/**
|
|
4076
|
+
* The generation of the load `value` has in flight, or `undefined`.
|
|
4077
|
+
*
|
|
4078
|
+
* 16.48 — WHICH LOAD A VALUE HAS IN FLIGHT, as the generation that started it, or `undefined` where
|
|
4079
|
+
* none is. Read off the cell rather than through `busy`, which is a probe and would subscribe the
|
|
4080
|
+
* caller that asked (3.3).
|
|
4081
|
+
*/
|
|
4082
|
+
export function loadOf(value: unknown): number | undefined {
|
|
4083
|
+
const cell = (value as Callable)[CELL]
|
|
4084
|
+
return cell.loading ? cell.generation : undefined
|
|
4085
|
+
}
|
|
4086
|
+
|
|
4087
|
+
/**
|
|
4088
|
+
* The generation `value`'s past stands at, or `undefined` while a load is in flight.
|
|
4089
|
+
*
|
|
4090
|
+
* What 21.15's `Cached` compares across a call, read through adoption as `servedCellOf` is.
|
|
4091
|
+
*/
|
|
4092
|
+
export function heldGenerationOf(value: unknown): number | undefined {
|
|
4093
|
+
const cell = adoptedCellOf(value)
|
|
4094
|
+
return cell.loading ? undefined : cell.generation
|
|
4095
|
+
}
|
|
4096
|
+
|
|
4097
|
+
/**
|
|
4098
|
+
* Whether `value` stands on a failure the next reach runs again rather than serves.
|
|
4099
|
+
*
|
|
4100
|
+
* 15.19, 15.20 — A REFUSAL BELOW 500 IS AN ANSWER, held as a production is: the caller asked for
|
|
4101
|
+
* something that is not there, and asking again changes nothing. One at 500 or above, and a throw,
|
|
4102
|
+
* is the server's own trouble, and holding it for a `ttl` replays an outage after it has passed.
|
|
4103
|
+
*/
|
|
4104
|
+
export function failedTransiently(value: unknown): boolean {
|
|
4105
|
+
const cell = (value as Callable)[CELL]
|
|
4106
|
+
const failure = cell.failure
|
|
4107
|
+
if (cell.loading || failure === undefined) return false
|
|
4108
|
+
return !isFailed(failure) || failure.status >= 500
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4111
|
+
/**
|
|
4112
|
+
* Ends `value`'s load in flight, refusing it with `failure`.
|
|
4113
|
+
*
|
|
4114
|
+
* 16.49 — THE LOAD IN FLIGHT, ENDED WHERE IT STANDS. The generation moves so a settle arriving after
|
|
4115
|
+
* is the stale one `absorb` already drops, a stream stops reaching the value, and the value refuses
|
|
4116
|
+
* with `failure` — which leaves what it held being served (2.4), a stream's chunks so far included.
|
|
4117
|
+
*/
|
|
4118
|
+
export function abandonLoad(value: unknown, failure: unknown): void {
|
|
4119
|
+
const cell = (value as Callable)[CELL]
|
|
4120
|
+
if (!cell.loading) return
|
|
4121
|
+
cell.generation += 1
|
|
4122
|
+
if (cell.stream !== null) stopStream(cell.stream)
|
|
4123
|
+
refused(cell, failure)
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
/**
|
|
4127
|
+
* `value` settled (1.4), or `STREAMING` once it turns out to stream (9.6) — whichever comes first.
|
|
4128
|
+
*
|
|
4129
|
+
* A body whose promise resolves to a generator is not a stream until it does, so a reader deciding
|
|
4130
|
+
* between framing chunks and answering one value has to wait for the first of the two — and is
|
|
4131
|
+
* answered which it was, rather than asking `isStreaming` after and reading a later turn's state.
|
|
4132
|
+
*/
|
|
4133
|
+
export function streamingOrSettled(value: unknown): Promise<unknown> {
|
|
4134
|
+
return settled((value as Callable)[CELL], true)
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4137
|
+
/**
|
|
4138
|
+
* Whether `value` is a streaming producer's `Reactive`.
|
|
4139
|
+
*
|
|
4140
|
+
* A streaming producer's own `Reactive` — a body declared as an async generator, or one whose run
|
|
4141
|
+
* handed one back (9.6).
|
|
4142
|
+
*/
|
|
4143
|
+
export function isStreaming(value: unknown): boolean {
|
|
4144
|
+
if (!isReactive(value)) return false
|
|
4145
|
+
const stream = (value as Partial<Callable>)[CELL]?.stream
|
|
4146
|
+
return stream !== null && stream !== undefined
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
/**
|
|
4150
|
+
* The chunks a streaming value has told its readers of, from index `from` on.
|
|
4151
|
+
*
|
|
4152
|
+
* 16.32 — THE CHUNKS A STREAMING VALUE HAS TOLD ITS READERS OF PAST `from`, for a writer sending each
|
|
4153
|
+
* once. Ungated they are read out of the accumulation in place, to `visible` rather than its length
|
|
4154
|
+
* (5.18): a read materialises a copy of the whole of it (9.1), which for a writer woken per chunk is
|
|
4155
|
+
* n²/2 elements copied to send n. A schema or a transform has to see the whole accumulation (9.8),
|
|
4156
|
+
* so a gated value answers from what a read would.
|
|
4157
|
+
*/
|
|
4158
|
+
export function* chunksFrom(value: unknown, from: number): Generator<unknown> {
|
|
4159
|
+
const cell = (value as Callable)[CELL]
|
|
4160
|
+
if (cell.schema === null && cell.transform === null) {
|
|
4161
|
+
const stream = cell.stream as Stream
|
|
4162
|
+
for (let index = from; index < stream.visible; index += 1)
|
|
4163
|
+
yield stream.chunks[index]
|
|
4164
|
+
return
|
|
4165
|
+
}
|
|
4166
|
+
const held = MEMBERS.peek.call(value as Callable)
|
|
4167
|
+
if (Array.isArray(held))
|
|
4168
|
+
for (let index = from; index < held.length; index += 1)
|
|
4169
|
+
yield held[index]
|
|
4170
|
+
}
|
|
4171
|
+
|
|
4172
|
+
function adoptedCellOf(value: unknown): Cell {
|
|
4173
|
+
let cell = (value as Callable)[CELL]
|
|
4174
|
+
// A stream's own `adoption` is a write it follows (9.11), and its past is its own.
|
|
4175
|
+
while (cell.adoption !== null && cell.stream?.source !== null)
|
|
4176
|
+
cell = cell.adoption.source
|
|
4177
|
+
return cell
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4180
|
+
/**
|
|
4181
|
+
* The keys of every `Reactive` member.
|
|
4182
|
+
*
|
|
4183
|
+
* What a value filed outside every scope forwards (`scoped.ts`): read off the record, so a member
|
|
4184
|
+
* added here is forwarded without a second list to keep in step.
|
|
4185
|
+
*/
|
|
4186
|
+
export const MEMBER_KEYS: readonly PropertyKey[] = Reflect.ownKeys(MEMBERS)
|
|
4187
|
+
|
|
4188
|
+
/**
|
|
4189
|
+
* Builds a `Reactive` prototype: every member, the overrides, and the brand.
|
|
4190
|
+
*
|
|
4191
|
+
* ONE PROTOTYPE, NOT A COPY OF EVERY MEMBER PER VALUE. `Object.assign(self, MEMBERS)` was 437
|
|
4192
|
+
* of the 501 ns a value cost to construct under bun's JSC and 855 of 985 under V8 —
|
|
4193
|
+
* 87% of the whole, on a path a table walks once per cell per row. The members are the
|
|
4194
|
+
* same objects either way; what changes is whether each instance carries its own slot
|
|
4195
|
+
* for them.
|
|
4196
|
+
*
|
|
4197
|
+
* Under `Function.prototype` so the value stays CALLABLE and `instanceof Function`
|
|
4198
|
+
* holds: callability is an internal slot, not something the prototype chain grants, but
|
|
4199
|
+
* `.call`, `.bind` and the rest are reached through it.
|
|
4200
|
+
*
|
|
4201
|
+
* THE BRAND MOVES HERE TOO, and 1.6's guard is unaffected: `isReactive` reads the
|
|
4202
|
+
* symbol, and an inherited read answers the same. The `defineProperty` it used to take
|
|
4203
|
+
* was another 24 ns per value for a non-enumerability nothing reads.
|
|
4204
|
+
*
|
|
4205
|
+
* IT IS NOT ONLY CONSTRUCTION. A function carrying 43 own properties is a big enough
|
|
4206
|
+
* object that V8 stops tracking it with a map, and every access to every field pays for
|
|
4207
|
+
* that — so moving the members off the instance made the V8 numbers 1.7x on a read,
|
|
4208
|
+
* 2.3x on `ready` and 3.7x on a write with one watcher (44.3 ns -> 12.1 ns), none of which
|
|
4209
|
+
* was the change's motive. Bun's JSC and WebKit are flat to slightly better.
|
|
4210
|
+
*
|
|
4211
|
+
* A SYNTHETIC PROBE SAID THE OPPOSITE and was wrong, which is the note worth keeping: a
|
|
4212
|
+
* stand-in with 14 no-op members read 2.33 ns -> 2.88 ns on the same path, so the trade
|
|
4213
|
+
* looked real until the same measurement was taken on this file. The stand-in's members
|
|
4214
|
+
* were small enough to keep the instance out of the slow mode the real ones put it in.
|
|
4215
|
+
*
|
|
4216
|
+
* THE BRAND IS PUT ON HERE RATHER THAN AT EACH PROTOTYPE, `scoped.ts`'s forwarder included. Every
|
|
4217
|
+
* prototype is the same three lines with a different override record, and the one of them that
|
|
4218
|
+
* mattered was the `defineProperty`: a prototype added without it is a value `isReactive` answers FALSE
|
|
4219
|
+
* for, which every guard in the design reads first (1.6) — so the slot would hand a `Reactive`
|
|
4220
|
+
* to `String()` and the template would render `[object Object]`, with nothing throwing.
|
|
4221
|
+
*/
|
|
4222
|
+
export function brandedPrototype(
|
|
4223
|
+
overrides?: Record<PropertyKey, unknown>,
|
|
4224
|
+
): Record<PropertyKey, unknown> {
|
|
4225
|
+
const prototype = Object.assign(
|
|
4226
|
+
Object.create(Function.prototype) as Record<PropertyKey, unknown>,
|
|
4227
|
+
MEMBERS,
|
|
4228
|
+
overrides,
|
|
4229
|
+
)
|
|
4230
|
+
Object.defineProperty(prototype, REACTIVE_BRAND, { value: true })
|
|
4231
|
+
return prototype
|
|
4232
|
+
}
|
|
4233
|
+
|
|
4234
|
+
const MEMBER_PROTOTYPE = brandedPrototype()
|
|
4235
|
+
|
|
4236
|
+
/**
|
|
4237
|
+
* The member prototype of a streaming value, whose writes stand over the stream.
|
|
4238
|
+
*
|
|
4239
|
+
* THE SAME MEMBERS WITH A STREAM'S WRITES — see `acceptIntoStream` for why they are not a branch.
|
|
4240
|
+
* Chosen once, when a value is built over a stream body, and kept for its life — a stream following
|
|
4241
|
+
* a `Reactive` it was set to (11.61) stays on it. A value ADOPTING a stream moves to
|
|
4242
|
+
* `ADOPTING_PROTOTYPE` instead, like any adoption.
|
|
4243
|
+
*/
|
|
4244
|
+
const STREAM_PROTOTYPE = brandedPrototype({
|
|
4245
|
+
set(this: Callable, value: unknown): unknown {
|
|
4246
|
+
const cell = this[CELL]
|
|
4247
|
+
// 11.61: a `Reactive` written is adopted, and on a stream that is a write following it.
|
|
4248
|
+
if (isReactive(value)) {
|
|
4249
|
+
// 11.66 — a cycle throws, as `setThroughGates` does.
|
|
4250
|
+
const cycle = follow(
|
|
4251
|
+
cell,
|
|
4252
|
+
cell.stream as Stream,
|
|
4253
|
+
(value as unknown as Callable)[CELL],
|
|
4254
|
+
)
|
|
4255
|
+
if (cycle !== undefined) throw cycle
|
|
4256
|
+
return undefined
|
|
4257
|
+
}
|
|
4258
|
+
return writeStream(cell, value)
|
|
4259
|
+
},
|
|
4260
|
+
})
|
|
4261
|
+
|
|
4262
|
+
/**
|
|
4263
|
+
* The member prototype of an adopting value, whose writes reach what it adopts.
|
|
4264
|
+
*
|
|
4265
|
+
* THE SAME MEMBERS WITH AN ADOPTING VALUE'S WRITES, which reach what it adopts (11.59) — through that
|
|
4266
|
+
* value's own members, so a stream's writes stay a stream's and an adoption of an adoption goes on
|
|
4267
|
+
* down — an adopting source is on this prototype for exactly as long as `adoption` is set. A
|
|
4268
|
+
* `Reactive` given to `set` is adopted HERE instead (11.61), or a value re-pointed at a new source
|
|
4269
|
+
* would hand that source to the one it is leaving.
|
|
4270
|
+
*/
|
|
4271
|
+
const ADOPTING_PROTOTYPE = brandedPrototype({
|
|
4272
|
+
set(this: Callable, value: unknown): unknown {
|
|
4273
|
+
const adoption = this[CELL].adoption
|
|
4274
|
+
if (adoption === null || isReactive(value))
|
|
4275
|
+
return MEMBERS.set.call(this, value)
|
|
4276
|
+
return (adoption.source.callable as Reactive<unknown>).set(value)
|
|
4277
|
+
},
|
|
4278
|
+
patch(this: Callable, mutate: (value: unknown) => void): void {
|
|
4279
|
+
const adoption = this[CELL].adoption
|
|
4280
|
+
if (adoption === null) patchCell(this[CELL], mutate)
|
|
4281
|
+
else (adoption.source.callable as Reactive<unknown>).patch(mutate)
|
|
4282
|
+
},
|
|
4283
|
+
})
|
|
4284
|
+
|
|
4285
|
+
/**
|
|
4286
|
+
* The member prototype of a `simple` value, with the common `set` spelled out.
|
|
4287
|
+
*
|
|
4288
|
+
* THE SAME MEMBERS WITH THE COMMON WRITE SPELLED OUT, and a prototype rather than a branch
|
|
4289
|
+
* for the reason `acceptIntoStream` already gives: a test in the shared `set` that a value
|
|
4290
|
+
* can never take still costs that value. Measured — `identity: structural`, which fails such
|
|
4291
|
+
* a test on its first load and goes on to the same `produce` it always did, went from 51 ns
|
|
4292
|
+
* to 57 with the test in `set`, and to 57 again with the short arm moved behind its own
|
|
4293
|
+
* frame instead, so it was never the size of `set`. The seat takes that value back to the
|
|
4294
|
+
* baseline path exactly, because it IS the baseline path.
|
|
4295
|
+
*
|
|
4296
|
+
* ON A `simple` VALUE THAT IS ALREADY SERVING, `produce` -> `serve` -> `cascade` reduces to
|
|
4297
|
+
* what is below: the cap, the expiry and the store are absent by `simple`; `moved`
|
|
4298
|
+
* is false by the three state tests, so no probe moves and nothing settles; no reload is
|
|
4299
|
+
* owed, so 7.9 has nothing to settle; and every status field `serve` writes is already at the
|
|
4300
|
+
* value it writes. The state tests are the `moved` computation and `produce`'s 7.9 test, read
|
|
4301
|
+
* here to prove the rest is dead rather than to pick a branch, and anything they refuse falls
|
|
4302
|
+
* through to `MEMBERS.set`, which is unchanged.
|
|
4303
|
+
*/
|
|
4304
|
+
const SIMPLE_PROTOTYPE = brandedPrototype({
|
|
4305
|
+
set(this: Callable, value: unknown): unknown {
|
|
4306
|
+
const cell = this[CELL]
|
|
4307
|
+
if (
|
|
4308
|
+
cell.servable &&
|
|
4309
|
+
!cell.loading &&
|
|
4310
|
+
cell.failure === undefined &&
|
|
4311
|
+
cell.owes !== OWES_RELOAD &&
|
|
4312
|
+
(value === null ||
|
|
4313
|
+
(typeof value !== 'object' && typeof value !== 'function') ||
|
|
4314
|
+
typeof (value as PromiseLike<unknown>).then !== 'function')
|
|
4315
|
+
) {
|
|
4316
|
+
// INSIDE THE ARM, so the fallback below bumps it once rather than twice.
|
|
4317
|
+
// A generation is only ever compared against a snapshot a load took, so
|
|
4318
|
+
// two bumps ahead of the snapshot discard exactly what one does — but the
|
|
4319
|
+
// field is the sort of thing a later reader comes to reason about, and a
|
|
4320
|
+
// write that counts as two is a thing to have to un-learn.
|
|
4321
|
+
cell.generation += 1
|
|
4322
|
+
// 5.2, and `simple` is what makes `Object.is` the whole gate.
|
|
4323
|
+
if (Object.is(value, cell.value)) {
|
|
4324
|
+
// The collapse arm's one lasting effect: `produce` restores `direct`
|
|
4325
|
+
// here, because a write can land on a value a mark walk has reached.
|
|
4326
|
+
cell.direct = cell.state === CLEAN
|
|
4327
|
+
return undefined
|
|
4328
|
+
}
|
|
4329
|
+
cell.value = value
|
|
4330
|
+
// 6.10: an `undefined` is served and moves no ordinal.
|
|
4331
|
+
if (value !== undefined) cell.ordinal += 1
|
|
4332
|
+
// `setDirect`, with `plain` known true by `simple`.
|
|
4333
|
+
cell.direct = cell.state === CLEAN
|
|
4334
|
+
if (cell.readers.length !== 0) markReaders(cell, DIRTY)
|
|
4335
|
+
// `cascade`'s tail. A write made from inside an effect appends to the drain
|
|
4336
|
+
// already walking rather than opening a second one.
|
|
4337
|
+
if (!draining && pendingCount !== 0) schedule()
|
|
4338
|
+
return undefined
|
|
4339
|
+
}
|
|
4340
|
+
return MEMBERS.set.call(this, value)
|
|
4341
|
+
},
|
|
4342
|
+
})
|
|
4343
|
+
|
|
4344
|
+
/**
|
|
4345
|
+
* Marks `cell`'s owner as stopped, and releases it where nothing reads it.
|
|
4346
|
+
*
|
|
4347
|
+
* 12.14 — what a component's setup or a request built a derived value in has ended. A value
|
|
4348
|
+
* still read from outside it — handed to something that outlives it — stays wired until that last
|
|
4349
|
+
* reader leaves (`RELEASING`), so nothing that reads it is left with a stale value.
|
|
4350
|
+
*/
|
|
4351
|
+
function orphan(cell: Cell): void {
|
|
4352
|
+
cell.orphaned = true
|
|
4353
|
+
if (cell.readers.length === 0 && cell.probeReaders.length === 0)
|
|
4354
|
+
releaseOrphan(cell)
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
/**
|
|
4358
|
+
* Lets go of everything an orphaned value reads, and leaves it owing a run.
|
|
4359
|
+
*
|
|
4360
|
+
* Left subscribed, a value a dropped row built stayed on its sources' reader lists for the life of
|
|
4361
|
+
* the page, and an adopting one's mirror ran on every write to what it adopted. Released, it holds
|
|
4362
|
+
* what it last served; a read that reaches it later — it escaped its owner — runs the body again
|
|
4363
|
+
* and subscribes afresh, so it is never stale, and from then on it is nobody's to release. A
|
|
4364
|
+
* stream is stopped rather than owed: it is never recomputed (14.13), only started.
|
|
4365
|
+
*/
|
|
4366
|
+
function releaseOrphan(cell: Cell): void {
|
|
4367
|
+
cell.orphaned = false
|
|
4368
|
+
const stream = cell.stream
|
|
4369
|
+
if (stream !== null && stream.source === null) stopStream(stream)
|
|
4370
|
+
dropAdoption(cell)
|
|
4371
|
+
detach(cell as unknown as Subscriber)
|
|
4372
|
+
cell.aborted = false
|
|
4373
|
+
if (cell.body !== null && stream === null) {
|
|
4374
|
+
cell.state = DIRTY
|
|
4375
|
+
cell.direct = false
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
RELEASING.unread = (source: Tracked): void => releaseOrphan(source as Cell)
|
|
4380
|
+
|
|
4381
|
+
/** Builds a `Reactive` over a new `Cell` and starts the work it is for (1.7). */
|
|
4382
|
+
export function createReactive<
|
|
4383
|
+
Stored,
|
|
4384
|
+
Accepted,
|
|
4385
|
+
Failures extends Failed<string, unknown>,
|
|
4386
|
+
Produced = Stored,
|
|
4387
|
+
>(
|
|
4388
|
+
label: string,
|
|
4389
|
+
body: ((args: unknown) => unknown) | null,
|
|
4390
|
+
args: unknown,
|
|
4391
|
+
initial: unknown,
|
|
4392
|
+
options: ReactiveOptions<Accepted, Stored, Failures> | undefined,
|
|
4393
|
+
// 5.19: a keyed value's window decides when this entry's producer starts, not the reach.
|
|
4394
|
+
deferred = false,
|
|
4395
|
+
// A KEYED ENTRY IS ITS CACHE'S, held for whoever reaches it next and not the setup or the
|
|
4396
|
+
// request that reached it first (12.4, 12.5) — so it is never orphaned by one. Only the entry:
|
|
4397
|
+
// what its body opens is owned as any body's is, or a rung's `watch` outlives its request.
|
|
4398
|
+
cached = false,
|
|
4399
|
+
): Reactive<Stored, Accepted, Failures, Produced> {
|
|
4400
|
+
// 11.41 IS A COMPILE ERROR THE TYPES CANNOT RAISE: a body declaring no parameter is still
|
|
4401
|
+
// assignable to the keyed overload's `(args: Args) => Computed`, so the checker falls through
|
|
4402
|
+
// to it and accepts the call. Like 5.14 it lands at construction instead, the earliest moment
|
|
4403
|
+
// there is — and a keyed value strips `args` before building an entry, so any that arrives
|
|
4404
|
+
// here was spelled on a form that has no args to gate.
|
|
4405
|
+
if ((options as { args?: unknown } | undefined)?.args !== undefined)
|
|
4406
|
+
throw new Error(
|
|
4407
|
+
`${label} declares args with no parameter to gate. args belongs to a keyed reactive, whose body declares one.`,
|
|
4408
|
+
)
|
|
4409
|
+
// 9.6 AT CONSTRUCTION for a declared stream; a body handing one back is promoted by its run.
|
|
4410
|
+
const streaming = body !== null && isStreamBody(body)
|
|
4411
|
+
const cell = new Cell(
|
|
4412
|
+
label,
|
|
4413
|
+
body,
|
|
4414
|
+
args,
|
|
4415
|
+
options as ReactiveOptions<unknown, unknown, never> | undefined,
|
|
4416
|
+
streaming,
|
|
4417
|
+
)
|
|
4418
|
+
// THE READ IS THE CALLABLE'S OWN BODY, not a call it makes. It stood as
|
|
4419
|
+
// `return read(self)` and that was a second frame on the hottest path in the design —
|
|
4420
|
+
// a template pays it per slot per row, and a recompute pays it once per source it
|
|
4421
|
+
// reads plus once for itself.
|
|
4422
|
+
//
|
|
4423
|
+
// IT CAPTURES `cell` AND NOTHING ELSE. Every other local here is dead by the time it
|
|
4424
|
+
// returns, so the scope it keeps alive is the one slot the read needs.
|
|
4425
|
+
const self = function readValue(): unknown {
|
|
4426
|
+
// TRACKING, WITH ITS COMMON ARM SPELLED OUT. A body reads the same values in the
|
|
4427
|
+
// same order on almost every run, so the shape that matters is "the source at the
|
|
4428
|
+
// cursor is the one we already had" — a pointer compare and an increment, with no
|
|
4429
|
+
// call and no set operation. `track` is the divergence.
|
|
4430
|
+
const reader = TRACKING.reader
|
|
4431
|
+
if (reader !== null) {
|
|
4432
|
+
const at = reader.cursor
|
|
4433
|
+
if (reader.sources[at] === cell) reader.cursor = at + 1
|
|
4434
|
+
else track(cell, reader)
|
|
4435
|
+
}
|
|
4436
|
+
// ONE LOAD AND ONE BRANCH FOR THE WHOLE COMMON PATH. `direct` is the pull and
|
|
4437
|
+
// the refusal answered at once — see its declaration. It stood as
|
|
4438
|
+
// three tests and stripping them to one measured 2.39 ns -> 1.86 ns on a read and
|
|
4439
|
+
// 4.03 ns -> 3.07 ns over a thousand values, which is memory traffic rather than
|
|
4440
|
+
// branches: a read walked four fields where the hand-written arm reads one
|
|
4441
|
+
// captured variable.
|
|
4442
|
+
if (cell.direct) return cell.value
|
|
4443
|
+
return readIndirect(cell)
|
|
4444
|
+
} as unknown as Callable
|
|
4445
|
+
// BEFORE THE FIELD, not after: an engine keys its inline caches on the shape a
|
|
4446
|
+
// property was added to, and re-pointing the prototype afterwards invalidates the
|
|
4447
|
+
// transition the store just built.
|
|
4448
|
+
Object.setPrototypeOf(
|
|
4449
|
+
self,
|
|
4450
|
+
streaming
|
|
4451
|
+
? STREAM_PROTOTYPE
|
|
4452
|
+
: cell.simple
|
|
4453
|
+
? SIMPLE_PROTOTYPE
|
|
4454
|
+
: MEMBER_PROTOTYPE,
|
|
4455
|
+
)
|
|
4456
|
+
self[CELL] = cell
|
|
4457
|
+
cell.callable = self as unknown as Reactive<unknown>
|
|
4458
|
+
if (cell.tags !== null) tagCell(cell)
|
|
4459
|
+
if (cell.store !== null) fileProducer(cell)
|
|
4460
|
+
// 12.14 — A DERIVED VALUE IS ITS OWNER'S, as a watch is: built in a setup or a request, it
|
|
4461
|
+
// lets go of what it reads once that ends and nothing else reads it. A plain value reads
|
|
4462
|
+
// nothing, so it has nothing to let go of.
|
|
4463
|
+
if (body !== null && !cached && owning()) owned(() => orphan(cell))
|
|
4464
|
+
|
|
4465
|
+
// 1.7: reaching an instance starts the work it is for. For the value form that is
|
|
4466
|
+
// the initial write; for a body it is the first run, which 11.4 has happening once
|
|
4467
|
+
// per key rather than once per read.
|
|
4468
|
+
//
|
|
4469
|
+
// AN ABSENT OPENING VALUE MINTS NOTHING: a value nobody has written reads as not landed
|
|
4470
|
+
// (2.2, 4.14) rather than serving an `undefined` its own type says is a `Stored`.
|
|
4471
|
+
// 8.3 AND 8.14: a store answers first, where the value is constructed, and whatever it misses
|
|
4472
|
+
// falls back to what the value would have done without one.
|
|
4473
|
+
if (cell.store !== null) restoreFirst(cell, cell.store, initial)
|
|
4474
|
+
else if (body === null) {
|
|
4475
|
+
if (initial === undefined) landOpening(cell)
|
|
4476
|
+
else if (initial !== NO_INITIAL) open(cell, initial)
|
|
4477
|
+
} else if (deferred) cell.loading = true
|
|
4478
|
+
else startProducer(cell)
|
|
4479
|
+
return self as unknown as Reactive<Stored, Accepted, Failures, Produced>
|
|
4480
|
+
}
|