@abide/abide 0.48.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +85 -59
- package/CHANGELOG.md +216 -0
- package/README.md +21 -10
- package/package.json +15 -4
- package/src/abideLsp.ts +5 -6
- package/src/abideResolverPlugin.ts +197 -169
- package/src/build.ts +79 -56
- package/src/buildCli.ts +3 -5
- package/src/buildDisconnected.ts +2 -5
- package/src/bundleApp.ts +2 -3
- package/src/checkAbide.ts +12 -2
- package/src/compile.ts +2 -4
- package/src/devEntry.ts +113 -28
- package/src/discoveryEntry.ts +3 -2
- package/src/lib/bundle/disconnected.abide +69 -73
- package/src/lib/bundle/infoPlist.ts +13 -7
- package/src/lib/bundle/installDownloads.ts +13 -3
- package/src/lib/bundle/installMacMenu.ts +13 -3
- package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
- package/src/lib/bundle/waitForServer.ts +6 -1
- package/src/lib/cli/parseArgvForRpc.ts +36 -9
- package/src/lib/cli/tokenizeArgvFlags.ts +4 -2
- package/src/lib/mcp/createMcpResourceServer.ts +13 -2
- package/src/lib/mcp/mcpTools.ts +15 -6
- package/src/lib/mcp/toolResultFromResponse.ts +40 -12
- package/src/lib/server/error.ts +6 -6
- package/src/lib/server/json.ts +17 -3
- package/src/lib/server/rpc/defineRpc.ts +58 -24
- package/src/lib/server/rpc/parseArgs.ts +113 -14
- package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
- package/src/lib/server/rpc/runWithRpcTimeout.ts +12 -1
- package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
- package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
- package/src/lib/server/rpc/validationError.ts +1 -1
- package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
- package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
- package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
- package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
- package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
- package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
- package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
- package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
- package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
- package/src/lib/server/runtime/createServer.ts +120 -324
- package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
- package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
- package/src/lib/server/runtime/finalizeResponse.ts +11 -1
- package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
- package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
- package/src/lib/server/runtime/pathStore.ts +15 -0
- package/src/lib/server/runtime/registryManifests.ts +1 -1
- package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
- package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +24 -16
- package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
- package/src/lib/server/runtime/streamFromIterator.ts +30 -2
- package/src/lib/server/runtime/textResponse.ts +23 -0
- package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
- package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
- package/src/lib/server/runtime/types/RequestStore.ts +27 -0
- package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
- package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
- package/src/lib/server/sockets/defineSocket.ts +5 -4
- package/src/lib/server/sse.ts +5 -1
- package/src/lib/shared/ASYNC_CELL.ts +5 -0
- package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
- package/src/lib/shared/HTTP_METHODS.ts +6 -0
- package/src/lib/shared/HttpError.ts +1 -5
- package/src/lib/shared/MCP_PATH.ts +6 -0
- package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
- package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
- package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
- package/src/lib/shared/activeCacheStore.ts +1 -0
- package/src/lib/shared/activePage.ts +1 -0
- package/src/lib/shared/buildArtifact.ts +20 -0
- package/src/lib/shared/buildRpcRequest.ts +6 -2
- package/src/lib/shared/buildSocketOverChannel.ts +4 -3
- package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
- package/src/lib/shared/cache.ts +156 -131
- package/src/lib/shared/canonicalJson.ts +24 -1
- package/src/lib/shared/changeAffectsClient.ts +12 -7
- package/src/lib/shared/createCacheStore.ts +31 -8
- package/src/lib/shared/createChannelLog.ts +24 -0
- package/src/lib/shared/createLifecycleChannel.ts +7 -1
- package/src/lib/shared/createReachable.ts +47 -78
- package/src/lib/shared/createRemoteFunction.ts +52 -30
- package/src/lib/shared/createRpcServerProgram.ts +820 -0
- package/src/lib/shared/decodeRefJson.ts +4 -4
- package/src/lib/shared/decodeResponse.ts +2 -2
- package/src/lib/shared/decodeWireBody.ts +35 -0
- package/src/lib/shared/detectRpcMethod.ts +8 -1
- package/src/lib/shared/done.ts +8 -2
- package/src/lib/shared/encodeRefJson.ts +4 -4
- package/src/lib/shared/encodeWireBody.ts +18 -0
- package/src/lib/shared/exitOnBuildFailure.ts +4 -3
- package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
- package/src/lib/shared/generateDeclarations.ts +72 -0
- package/src/lib/shared/hasSeedableRequest.ts +25 -0
- package/src/lib/shared/hydrationWindow.ts +53 -0
- package/src/lib/shared/isAsyncCell.ts +11 -0
- package/src/lib/shared/isAsyncIterable.ts +8 -0
- package/src/lib/shared/isSubscribable.ts +3 -3
- package/src/lib/shared/isThenable.ts +9 -0
- package/src/lib/shared/jsonSchemaForType.ts +258 -0
- package/src/lib/shared/lenientDecode.ts +15 -0
- package/src/lib/shared/loadProjectTsConfig.ts +28 -0
- package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
- package/src/lib/shared/matchRoute.ts +4 -14
- package/src/lib/shared/parseEnv.ts +17 -6
- package/src/lib/shared/peek.ts +14 -0
- package/src/lib/shared/pending.ts +9 -6
- package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
- package/src/lib/shared/prepareRpcModule.ts +91 -93
- package/src/lib/shared/prepareSocketModule.ts +3 -2
- package/src/lib/shared/probeRegistries.ts +5 -18
- package/src/lib/shared/reachable.ts +7 -10
- package/src/lib/shared/refresh.ts +12 -2
- package/src/lib/shared/refreshing.ts +8 -2
- package/src/lib/shared/resolvedCellsSlot.ts +13 -0
- package/src/lib/shared/reviveWireField.ts +49 -0
- package/src/lib/shared/reviveWireOutput.ts +36 -0
- package/src/lib/shared/rpcServerForRoot.ts +25 -0
- package/src/lib/shared/scanPages.ts +25 -0
- package/src/lib/shared/serializeEnv.ts +18 -6
- package/src/lib/shared/snapshotShippable.ts +8 -7
- package/src/lib/shared/snippet.ts +11 -6
- package/src/lib/shared/streamedCellsSlot.ts +14 -0
- package/src/lib/shared/subscribableFromResponse.ts +4 -4
- package/src/lib/shared/subscribableProbes.ts +1 -1
- package/src/lib/shared/tailProbeSlot.ts +1 -1
- package/src/lib/shared/types/AsyncComputed.ts +20 -0
- package/src/lib/shared/types/AsyncState.ts +13 -0
- package/src/lib/shared/types/CacheEntry.ts +7 -7
- package/src/lib/shared/types/CacheOptions.ts +23 -37
- package/src/lib/shared/types/CachePolicy.ts +25 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
- package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
- package/src/lib/shared/types/HttpMethod.ts +3 -1
- package/src/lib/shared/types/InputCoercion.ts +17 -0
- package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
- package/src/lib/shared/types/OutputWirePlan.ts +17 -0
- package/src/lib/shared/types/PagesScan.ts +7 -0
- package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
- package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
- package/src/lib/shared/types/RemoteCallable.ts +8 -7
- package/src/lib/shared/types/RemoteFunction.ts +16 -15
- package/src/lib/shared/types/ResolvedCells.ts +11 -0
- package/src/lib/shared/types/ReturnBody.ts +15 -0
- package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
- package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
- package/src/lib/shared/types/Socket.ts +4 -4
- package/src/lib/shared/types/SsrPayload.ts +5 -1
- package/src/lib/shared/types/StreamPolicy.ts +11 -0
- package/src/lib/shared/types/StreamedCells.ts +19 -0
- package/src/lib/shared/types/StreamedResolution.ts +24 -6
- package/src/lib/shared/types/TailHooks.ts +1 -1
- package/src/lib/shared/types/WireKind.ts +11 -0
- package/src/lib/shared/url.ts +5 -0
- package/src/lib/shared/validationHttpError.ts +33 -0
- package/src/lib/shared/warmSeedKey.ts +16 -0
- package/src/lib/shared/wireJsonReplacer.ts +30 -0
- package/src/lib/shared/writeRpcDts.ts +11 -1
- package/src/lib/shared/writeTestSocketsDts.ts +1 -1
- package/src/lib/test/createTestApp.ts +19 -1
- package/src/lib/ui/README.md +1 -1
- package/src/lib/ui/activePendingCells.ts +14 -0
- package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
- package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -7
- package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
- package/src/lib/ui/compile/analyzeComponent.ts +89 -6
- package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
- package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
- package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
- package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
- package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
- package/src/lib/ui/compile/bindListenEvent.ts +5 -6
- package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
- package/src/lib/ui/compile/catchBinding.ts +7 -5
- package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
- package/src/lib/ui/compile/compileComponent.ts +19 -3
- package/src/lib/ui/compile/compileModule.ts +27 -58
- package/src/lib/ui/compile/compileSSR.ts +52 -13
- package/src/lib/ui/compile/compileShadow.ts +265 -42
- package/src/lib/ui/compile/composeProps.ts +15 -4
- package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
- package/src/lib/ui/compile/createShadowProgram.ts +35 -6
- package/src/lib/ui/compile/declaredNames.ts +36 -0
- package/src/lib/ui/compile/desugarSignals.ts +419 -37
- package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
- package/src/lib/ui/compile/generateBuild.ts +74 -17
- package/src/lib/ui/compile/generateSSR.ts +286 -75
- package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
- package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
- package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
- package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
- package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
- package/src/lib/ui/compile/isWhitespaceText.ts +10 -2
- package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
- package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
- package/src/lib/ui/compile/lowerContext.ts +10 -0
- package/src/lib/ui/compile/lowerDocAccess.ts +51 -10
- package/src/lib/ui/compile/lowerScript.ts +40 -4
- package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
- package/src/lib/ui/compile/parseTemplate.ts +18 -1090
- package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
- package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +26 -40
- package/src/lib/ui/compile/resolveReactiveExport.ts +4 -7
- package/src/lib/ui/compile/scopeCss.ts +27 -4
- package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
- package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
- package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
- package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
- package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
- package/src/lib/ui/compile/templateStartOffset.ts +15 -0
- package/src/lib/ui/compile/tryPlan.ts +4 -3
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
- package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
- package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
- package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
- package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
- package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
- package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
- package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
- package/src/lib/ui/computed.ts +28 -1
- package/src/lib/ui/createScope.ts +35 -68
- package/src/lib/ui/dom/anchoredBranch.ts +142 -0
- package/src/lib/ui/dom/appendText.ts +8 -3
- package/src/lib/ui/dom/awaitBlock.ts +62 -75
- package/src/lib/ui/dom/bindProp.ts +22 -0
- package/src/lib/ui/dom/bindableProp.ts +47 -0
- package/src/lib/ui/dom/cellPending.ts +24 -0
- package/src/lib/ui/dom/discardBoundary.ts +24 -6
- package/src/lib/ui/dom/disposeRange.ts +2 -1
- package/src/lib/ui/dom/each.ts +41 -12
- package/src/lib/ui/dom/eachAsync.ts +39 -3
- package/src/lib/ui/dom/fillBefore.ts +6 -7
- package/src/lib/ui/dom/fillBoundary.ts +2 -3
- package/src/lib/ui/dom/fillRange.ts +4 -4
- package/src/lib/ui/dom/hydrate.ts +6 -12
- package/src/lib/ui/dom/isComment.ts +1 -1
- package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
- package/src/lib/ui/dom/mergeProps.ts +1 -1
- package/src/lib/ui/dom/mount.ts +1 -2
- package/src/lib/ui/dom/mountChild.ts +9 -40
- package/src/lib/ui/dom/mountRange.ts +2 -3
- package/src/lib/ui/dom/mountSlot.ts +1 -1
- package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
- package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
- package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
- package/src/lib/ui/dom/on.ts +2 -2
- package/src/lib/ui/dom/readCell.ts +40 -0
- package/src/lib/ui/dom/restProps.ts +2 -2
- package/src/lib/ui/dom/spreadProps.ts +3 -4
- package/src/lib/ui/dom/switchBlock.ts +30 -7
- package/src/lib/ui/dom/tryBlock.ts +233 -70
- package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
- package/src/lib/ui/dom/when.ts +12 -2
- package/src/lib/ui/dom/withScope.ts +9 -2
- package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
- package/src/lib/ui/flight.ts +56 -0
- package/src/lib/ui/html.ts +12 -1
- package/src/lib/ui/isolateCellBarrier.ts +17 -0
- package/src/lib/ui/linked.ts +48 -2
- package/src/lib/ui/props.ts +17 -0
- package/src/lib/ui/remoteProxy.ts +84 -159
- package/src/lib/ui/renderChain.ts +53 -13
- package/src/lib/ui/renderToStream.ts +22 -18
- package/src/lib/ui/resumeSeedScript.ts +1 -1
- package/src/lib/ui/router.ts +86 -53
- package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
- package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
- package/src/lib/ui/runtime/CHILD_PRESENT.ts +2 -2
- package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
- package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
- package/src/lib/ui/runtime/RENDER.ts +10 -7
- package/src/lib/ui/runtime/RESUME.ts +4 -4
- package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
- package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
- package/src/lib/ui/runtime/applyPatchToTree.ts +17 -4
- package/src/lib/ui/runtime/blockId.ts +31 -0
- package/src/lib/ui/runtime/boundaryFor.ts +11 -0
- package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
- package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
- package/src/lib/ui/runtime/createDoc.ts +31 -54
- package/src/lib/ui/runtime/createEffectNode.ts +9 -0
- package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
- package/src/lib/ui/runtime/flushEffects.ts +33 -1
- package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
- package/src/lib/ui/runtime/nextBlockId.ts +8 -7
- package/src/lib/ui/runtime/renderPath.ts +16 -0
- package/src/lib/ui/runtime/scope.ts +11 -0
- package/src/lib/ui/runtime/toTeardown.ts +10 -3
- package/src/lib/ui/runtime/types/Boundary.ts +9 -0
- package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
- package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
- package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
- package/src/lib/ui/runtime/types/UiProps.ts +6 -5
- package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
- package/src/lib/ui/runtime/withPath.ts +16 -0
- package/src/lib/ui/runtime/withPathFrom.ts +20 -0
- package/src/lib/ui/runtime/withoutHydration.ts +22 -0
- package/src/lib/ui/seedStreamedResolution.ts +31 -6
- package/src/lib/ui/settleAsyncCells.ts +24 -0
- package/src/lib/ui/socketProxy.ts +1 -1
- package/src/lib/ui/startClient.ts +16 -31
- package/src/lib/ui/state.ts +9 -3
- package/src/lib/ui/trackedComputed.ts +68 -0
- package/src/lib/ui/types/Scope.ts +8 -24
- package/src/lib/ui/watch.ts +3 -3
- package/src/serverEntry.ts +14 -0
- package/template/src/server/rpc/getHello.ts +15 -13
- package/template/test/app.test.ts +1 -1
- package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
- package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
- package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
- package/src/lib/shared/hasReplayableRequest.ts +0 -17
- package/src/lib/shared/hydratingSlot.ts +0 -12
- package/src/lib/shared/outboxProbeSlot.ts +0 -20
- package/src/lib/shared/types/Outbox.ts +0 -9
- package/src/lib/shared/types/OutboxEntry.ts +0 -27
- package/src/lib/shared/types/SmartReadOptions.ts +0 -36
- package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
- package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
- package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
- package/src/lib/ui/compile/markupTokens.ts +0 -313
- package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
- package/src/lib/ui/dom/applyResolved.ts +0 -87
- package/src/lib/ui/dom/scopeLabel.ts +0 -21
- package/src/lib/ui/dom/text.ts +0 -20
- package/src/lib/ui/history.ts +0 -101
- package/src/lib/ui/installHotBridge.ts +0 -93
- package/src/lib/ui/installInspectorBridge.ts +0 -140
- package/src/lib/ui/outbox.ts +0 -35
- package/src/lib/ui/persist.ts +0 -115
- package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
- package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
- package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
- package/src/lib/ui/runtime/captureModelDoc.ts +0 -28
- package/src/lib/ui/runtime/hotInstances.ts +0 -10
- package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
- package/src/lib/ui/runtime/hotReplace.ts +0 -38
- package/src/lib/ui/runtime/liveScopes.ts +0 -15
- package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
- package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
- package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
- package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
- package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
- package/src/lib/ui/seedResolved.ts +0 -28
- package/src/lib/ui/sync.ts +0 -48
- package/src/lib/ui/types/History.ts +0 -14
- package/src/lib/ui/types/PersistHandle.ts +0 -11
- package/src/lib/ui/types/PersistenceStore.ts +0 -12
- package/src/lib/ui/types/ResolvedFrame.ts +0 -15
- package/src/lib/ui/types/SyncTransport.ts +0 -13
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import ts from 'typescript'
|
|
2
|
+
import { AbideCompileError } from './AbideCompileError.ts'
|
|
3
|
+
import type { InjectedCell } from './lowerAsyncInterpolations.ts'
|
|
4
|
+
import type { InterpolationClassifier } from './types/InterpolationClassifier.ts'
|
|
5
|
+
import type { InterpolationKind } from './types/InterpolationKind.ts'
|
|
6
|
+
|
|
7
|
+
/* The value/content position a lifted (sub)expression sits in — only `each` (a plain `{#for}`
|
|
8
|
+
source) forbids an `AsyncIterable` (D4a). Every other position renders a stream's latest frame. */
|
|
9
|
+
export type LiftPosition = 'content' | 'attribute' | 'if' | 'switch' | 'each'
|
|
10
|
+
|
|
11
|
+
/* A lifted (sub)expression's span within the interpolation `code` and how it lifts: `start`/`end`
|
|
12
|
+
index `code`, `kind` picks the peek flavour, `blocking` marks a leading `await`. The runtime uses
|
|
13
|
+
these to rewrite the span to a cell ref; the type-check shadow uses the non-blocking ones to wrap
|
|
14
|
+
the seed in a peek helper so its resolved type composes (ADR-0032). */
|
|
15
|
+
export type LiftSpan = {
|
|
16
|
+
start: number
|
|
17
|
+
end: number
|
|
18
|
+
kind: 'promise' | 'asyncIterable'
|
|
19
|
+
blocking: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* The binary operators whose result reacts to a pending-`undefined` operand: the walk does NOT
|
|
23
|
+
lift the composition, it descends into the operands so the operator composes the peek(s)
|
|
24
|
+
(ADR-0032 D1). `?.` needs no entry — a member/call on a promise is sync-typed, so the default
|
|
25
|
+
sync-recurse already reaches and lifts the async base. */
|
|
26
|
+
const PENDING_TOLERANT = new Set<ts.SyntaxKind>([
|
|
27
|
+
ts.SyntaxKind.QuestionQuestionToken,
|
|
28
|
+
ts.SyntaxKind.BarBarToken,
|
|
29
|
+
ts.SyntaxKind.AmpersandAmpersandToken,
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
ADR-0032 — the sub-expression walk. Given an interpolation's verbatim `code`, its absolute source
|
|
34
|
+
offset `loc`, and the warm classifier, lifts every promise/`AsyncIterable`-typed (sub)expression
|
|
35
|
+
into an injected peek-cell and rewrites it in place to a bare `__vN` reference, returning the
|
|
36
|
+
rewritten code and the cells to inject. Pending is `undefined` at the value level, so `??` / `?.` /
|
|
37
|
+
member access compose around the peek (`getFoo() ?? 'Loading…'` shows the fallback while pending).
|
|
38
|
+
|
|
39
|
+
Three rules, top-down (ADR D1 "The walk"):
|
|
40
|
+
1. `await X` → lift `X` BLOCKING (SSR-inline); syntactic, needs no classifier.
|
|
41
|
+
2. `a ?? b` / `||` / `&&`→ NOT lifted; recurse into the operands (pending stays visible to the op).
|
|
42
|
+
3. any other promise/`AsyncIterable` node → lift as a unit, STREAMING; a sync node recurses
|
|
43
|
+
(a sync parent can wrap an async child: `String(getFoo())`, `` `${getFoo()}` ``).
|
|
44
|
+
|
|
45
|
+
`classify` absent (no warm program) ⇒ only rule 1 fires — fail-open: `await` still works, a bare
|
|
46
|
+
async position degrades to today's stringify. A promise seed is emitted `await X` so the cell
|
|
47
|
+
UNWRAPS to the resolved value; an `AsyncIterable` seed stays bare (its latest frame). D4: an
|
|
48
|
+
`AsyncIterable` in a plain `{#for}` source, or an `await` on an `AsyncIterable`, throw.
|
|
49
|
+
*/
|
|
50
|
+
export function liftAsyncSubExpressions(
|
|
51
|
+
code: string,
|
|
52
|
+
loc: number,
|
|
53
|
+
classify: InterpolationClassifier | undefined,
|
|
54
|
+
mint: () => string,
|
|
55
|
+
position: LiftPosition = 'content',
|
|
56
|
+
): { code: string; lifts: InjectedCell[]; spans: LiftSpan[] } {
|
|
57
|
+
const source = ts.createSourceFile('__lift.ts', code, ts.ScriptTarget.Latest, true)
|
|
58
|
+
const first = source.statements[0]
|
|
59
|
+
/* Only a single bare expression is walkable; anything else (a statement, empty) passes through. */
|
|
60
|
+
if (first === undefined || source.statements.length !== 1 || !ts.isExpressionStatement(first)) {
|
|
61
|
+
return { code, lifts: [], spans: [] }
|
|
62
|
+
}
|
|
63
|
+
const lifts: InjectedCell[] = []
|
|
64
|
+
/* Each lifted span, in walk (source) order; spliced right-to-left so earlier offsets stay valid.
|
|
65
|
+
`start`/`end` index `code`; `name` replaces that span. */
|
|
66
|
+
const edits: { start: number; end: number; name: string }[] = []
|
|
67
|
+
/* The same lifted spans with their kind/tier — the shadow's peek-wrap consumes these directly. */
|
|
68
|
+
const spans: LiftSpan[] = []
|
|
69
|
+
|
|
70
|
+
const text = (node: ts.Node): string => code.slice(node.getStart(source), node.getEnd())
|
|
71
|
+
|
|
72
|
+
/* The checker kind of a sub-node, keyed by its absolute source offset through the linear
|
|
73
|
+
interpolation→shadow mapping. `undefined` when no classifier (only `await` lifts then). */
|
|
74
|
+
const kindOf = (node: ts.Node): InterpolationKind | undefined => {
|
|
75
|
+
if (classify === undefined) {
|
|
76
|
+
return undefined
|
|
77
|
+
}
|
|
78
|
+
const start = node.getStart(source)
|
|
79
|
+
return classify(loc + start, code.slice(start, node.getEnd()))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Record a lift of `node`'s span. `seed` is the RAW (sub)expression text — `analyzeComponent`
|
|
83
|
+
wraps a `promise` seed as `computed(async () => await (<seed>))` (so `await` is a keyword in
|
|
84
|
+
an async context — a bare-text `await (X)` reparsed at module scope becomes a call to a
|
|
85
|
+
function `await`) and an `asyncIterable` seed as `computed(<seed>)` (a bare stream thunk). */
|
|
86
|
+
const lift = (
|
|
87
|
+
node: ts.Node,
|
|
88
|
+
seed: string,
|
|
89
|
+
kind: 'promise' | 'asyncIterable',
|
|
90
|
+
blocking: boolean,
|
|
91
|
+
): void => {
|
|
92
|
+
const name = mint()
|
|
93
|
+
const start = node.getStart(source)
|
|
94
|
+
const end = node.getEnd()
|
|
95
|
+
lifts.push({ name, code: seed, kind, blocking })
|
|
96
|
+
edits.push({ start, end, name })
|
|
97
|
+
spans.push({ start, end, kind, blocking })
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const visit = (node: ts.Node): void => {
|
|
101
|
+
/* A nested function is its own evaluation scope — an async (sub)expression inside a callback
|
|
102
|
+
(`items.map(x => fetchName(x))`, `items.map(async x => await load(x))`) must NOT be hoisted
|
|
103
|
+
to a top-level cell: its parameters/closure vars would become free identifiers, and it
|
|
104
|
+
would run once instead of per row. Stop at the boundary, mirroring
|
|
105
|
+
`desugarSignals.hasTopLevelAwait`. */
|
|
106
|
+
if (
|
|
107
|
+
ts.isArrowFunction(node) ||
|
|
108
|
+
ts.isFunctionExpression(node) ||
|
|
109
|
+
ts.isFunctionDeclaration(node)
|
|
110
|
+
) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
if (ts.isAwaitExpression(node)) {
|
|
114
|
+
/* `await X` → a BLOCKING promise cell (its operand). D4b: `await` on a stream is
|
|
115
|
+
meaningless. The operand text is already a bounded unary — no parens needed. */
|
|
116
|
+
if (kindOf(node.expression) === 'asyncIterable') {
|
|
117
|
+
throw new AbideCompileError(
|
|
118
|
+
'[abide] `await` unwraps a promise, but this is an `AsyncIterable` — drop the `await`; a stream auto-tracks (render its frames, or `{#for await}`).',
|
|
119
|
+
loc + node.getStart(source),
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
lift(node, text(node.expression), 'promise', true)
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
if (ts.isBinaryExpression(node) && PENDING_TOLERANT.has(node.operatorToken.kind)) {
|
|
126
|
+
/* Pending-tolerant: don't lift the composition — descend so the operator sees the
|
|
127
|
+
operands' `undefined` (`getFoo() ?? 'Loading…'`, `x || getFoo()`). */
|
|
128
|
+
visit(node.left)
|
|
129
|
+
visit(node.right)
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
const kind = kindOf(node)
|
|
133
|
+
if (kind === 'promise') {
|
|
134
|
+
/* Lift as a unit, streaming; the cell unwraps the seed to its resolved value. */
|
|
135
|
+
lift(node, text(node), 'promise', false)
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
if (kind === 'asyncIterable') {
|
|
139
|
+
if (position === 'each') {
|
|
140
|
+
throw new AbideCompileError(
|
|
141
|
+
"[abide] an `AsyncIterable` can't drive a plain `{#for}` — iterate its frames with `{#for await}`.",
|
|
142
|
+
loc + node.getStart(source),
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
/* A stream cell: its latest frame. */
|
|
146
|
+
lift(node, text(node), 'asyncIterable', false)
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
/* Sync (or classifier-absent): a sync parent can still wrap an async child. */
|
|
150
|
+
ts.forEachChild(node, visit)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
visit(first.expression)
|
|
154
|
+
|
|
155
|
+
if (edits.length === 0) {
|
|
156
|
+
return { code, lifts: [], spans: [] }
|
|
157
|
+
}
|
|
158
|
+
edits.sort((a, b) => b.start - a.start)
|
|
159
|
+
let rewritten = code
|
|
160
|
+
for (const edit of edits) {
|
|
161
|
+
rewritten = rewritten.slice(0, edit.start) + edit.name + rewritten.slice(edit.end)
|
|
162
|
+
}
|
|
163
|
+
/* Spans in ascending source order — the shadow's peek-wrap walks them left-to-right. */
|
|
164
|
+
spans.sort((a, b) => a.start - b.start)
|
|
165
|
+
return { code: rewritten, lifts, spans }
|
|
166
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { asyncInterpolationFields } from './asyncInterpolationFields.ts'
|
|
2
|
+
import { liftAsyncSubExpressions } from './liftAsyncSubExpressions.ts'
|
|
3
|
+
import type { InterpolationClassifier } from './types/InterpolationClassifier.ts'
|
|
4
|
+
import type { TemplateNode } from './types/TemplateNode.ts'
|
|
5
|
+
|
|
6
|
+
/* A synthetic peek-cell an async (sub)expression lowers to: a unique local name, the RAW seed
|
|
7
|
+
expression, its `kind` (a `promise` cell UNWRAPS to the resolved value; an `asyncIterable` cell
|
|
8
|
+
tracks its latest frame), and whether the author wrote `await` (BLOCKING: joins the SSR barrier,
|
|
9
|
+
resolved inline; else STREAMING: ships pending-`undefined`, resolves on the client). By kind,
|
|
10
|
+
`analyzeComponent` prepends `const <name> = computed(async () => await (<code>))` (promise) or
|
|
11
|
+
`const <name> = computed(<code>)` (stream); `desugarSignals` routes it (by injected name) to a
|
|
12
|
+
`trackedComputed` cell read via `$$readCell(<name>)`. */
|
|
13
|
+
export type InjectedCell = {
|
|
14
|
+
name: string
|
|
15
|
+
code: string
|
|
16
|
+
kind: 'promise' | 'asyncIterable'
|
|
17
|
+
blocking: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* The result of the async-interpolation lowering: the (mutated-in-place) template plus the cells
|
|
21
|
+
the lifted (sub)expressions were rewritten into (empty when none). */
|
|
22
|
+
export type LoweredInterpolations = { nodes: TemplateNode[]; cells: InjectedCell[] }
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
ADR-0032 type-directed lowering of async (sub)expressions, in EVERY position — content
|
|
26
|
+
interpolations and value positions (attribute, `{#if}`/`{#switch}` subject, plain `{#for}` source)
|
|
27
|
+
alike. Each interpolation is walked (`liftAsyncSubExpressions`): a promise/`AsyncIterable`-typed
|
|
28
|
+
(sub)expression is lifted to an injected peek-cell and rewritten IN PLACE to a bare `__vN`
|
|
29
|
+
reference, so pending reads as `undefined` and composes with `??`/`?.`. A leading `await` marks the
|
|
30
|
+
SSR BLOCKING tier; no `await` is STREAMING. Runs even without a classifier — then only syntactic
|
|
31
|
+
`await`s lift (fail-open), matching the pre-classifier path for everything else.
|
|
32
|
+
*/
|
|
33
|
+
export function lowerAsyncInterpolations(
|
|
34
|
+
nodes: TemplateNode[],
|
|
35
|
+
classify: InterpolationClassifier | undefined,
|
|
36
|
+
): LoweredInterpolations {
|
|
37
|
+
const counter = { n: 0 }
|
|
38
|
+
const mint = (): string => `__v${counter.n++}`
|
|
39
|
+
const cells: InjectedCell[] = []
|
|
40
|
+
lowerList(nodes, classify, mint, cells)
|
|
41
|
+
return { nodes, cells }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Lifts every async (sub)expression in a sibling list, then recurses into children. Each node's
|
|
45
|
+
async-liftable fields come from the shared `asyncInterpolationFields` plan (the single reading the
|
|
46
|
+
shadow front-end also drives from), so the two can't disagree on which interpolations are async;
|
|
47
|
+
this side owns the RENDERING — lift the field to a peek-cell, write the `__vN` reference back, and
|
|
48
|
+
for a control-flow subject that collapsed to one bare cell mark `asyncSubject`. */
|
|
49
|
+
function lowerList(
|
|
50
|
+
nodes: TemplateNode[],
|
|
51
|
+
classify: InterpolationClassifier | undefined,
|
|
52
|
+
mint: () => string,
|
|
53
|
+
cells: InjectedCell[],
|
|
54
|
+
): void {
|
|
55
|
+
for (const node of nodes) {
|
|
56
|
+
for (const field of asyncInterpolationFields(node)) {
|
|
57
|
+
const lifted = cells.length
|
|
58
|
+
const result = liftAsyncSubExpressions(
|
|
59
|
+
field.code,
|
|
60
|
+
field.loc,
|
|
61
|
+
classify,
|
|
62
|
+
mint,
|
|
63
|
+
field.position,
|
|
64
|
+
)
|
|
65
|
+
for (const lift of result.lifts) {
|
|
66
|
+
cells.push(lift)
|
|
67
|
+
}
|
|
68
|
+
field.write(result.code)
|
|
69
|
+
if (field.subject) {
|
|
70
|
+
field.setAsyncSubject(isBareLiftedCell(result.code, cells, lifted))
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if ('children' in node) {
|
|
74
|
+
lowerList(node.children, classify, mint, cells)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* True when a control-flow subject's WHOLE expression was lifted to one peek-cell — the
|
|
80
|
+
rewritten code is exactly a single injected name (`{#if getX()}` → `__v3`), not a compound
|
|
81
|
+
the peek composes into (`{#if getX()?.ok}` → `__v3?.ok`, still the falsy-else peek). `cells`
|
|
82
|
+
past index `lifted` are the ones this subject minted, so an equal name means the subject IS
|
|
83
|
+
that bare cell. Only then do the back-ends read its pending facet to hold the block. */
|
|
84
|
+
function isBareLiftedCell(code: string, cells: InjectedCell[], lifted: number): boolean {
|
|
85
|
+
const name = code.trim()
|
|
86
|
+
for (let index = lifted; index < cells.length; index++) {
|
|
87
|
+
if (cells[index]?.name === name) {
|
|
88
|
+
return true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
@@ -25,6 +25,8 @@ export function lowerContext(
|
|
|
25
25
|
stateNames: ReadonlySet<string>,
|
|
26
26
|
derivedNames: ReadonlySet<string>,
|
|
27
27
|
computedNames: ReadonlySet<string> = new Set(),
|
|
28
|
+
/* `linked` / async `computed` names, read through `$$readCell(name)`. */
|
|
29
|
+
cellReadNames: ReadonlySet<string> = new Set(),
|
|
28
30
|
) {
|
|
29
31
|
/* The typed branch-local shadow stack: one auto-popping value owning both kinds.
|
|
30
32
|
`derived` names deref to `.value` like a `computed` (block value params the client
|
|
@@ -48,6 +50,7 @@ export function lowerContext(
|
|
|
48
50
|
computedNames,
|
|
49
51
|
new Set(scope.names('derived')),
|
|
50
52
|
new Set(scope.names('plain')),
|
|
53
|
+
cellReadNames,
|
|
51
54
|
),
|
|
52
55
|
docAccessTransformer('$$model'),
|
|
53
56
|
])
|
|
@@ -101,6 +104,13 @@ export function lowerContext(
|
|
|
101
104
|
return `(${expression(code)}).set(${valueExpr});`
|
|
102
105
|
}
|
|
103
106
|
guardWritableBind(code)
|
|
107
|
+
/* A `linked` cell (a writable `State`) is read through `$$readCell`, but that call is
|
|
108
|
+
not an lvalue — the WRITE goes straight to the cell's `.value` setter (`NAME.value =
|
|
109
|
+
…`). The name is a real local holding the cell, so it needs no reference lowering. */
|
|
110
|
+
const name = code.trim()
|
|
111
|
+
if (cellReadNames.has(name)) {
|
|
112
|
+
return `${name}.value = ${valueExpr};`
|
|
113
|
+
}
|
|
104
114
|
return statement(`${code} = ${valueExpr}`)
|
|
105
115
|
}
|
|
106
116
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import ts from 'typescript'
|
|
2
2
|
import { escapeKey } from '../runtime/escapeKey.ts'
|
|
3
|
-
import { TS_PRINTER } from './TS_PRINTER.ts'
|
|
4
3
|
|
|
5
4
|
/*
|
|
6
5
|
The linchpin compiler pass. Rewrites idiomatic data access on a reactive document
|
|
@@ -23,15 +22,10 @@ many — literal keys at compile time, dynamic ones wrapped in a runtime
|
|
|
23
22
|
`escapeKey(...)`. Reads are lowered to `read(path)`; a later pass hoists
|
|
24
23
|
static-path reads to a `cell` bound once at component init (the string-free hot
|
|
25
24
|
path the bench measured). Index expressions are themselves visited, so a read
|
|
26
|
-
used as an index lowers too.
|
|
25
|
+
used as an index lowers too. Exposed as `docAccessTransformer` (a
|
|
26
|
+
`ts.TransformerFactory`) so the script pipeline chains it after
|
|
27
|
+
`signalRefsTransformer` over a single parsed tree (see `lowerScript`).
|
|
27
28
|
*/
|
|
28
|
-
export function lowerDocAccess(code: string, docName: string): string {
|
|
29
|
-
const source = ts.createSourceFile('component.ts', code, ts.ScriptTarget.Latest, true)
|
|
30
|
-
const result = ts.transform(source, [docAccessTransformer(docName)])
|
|
31
|
-
const output = TS_PRINTER.printFile(result.transformed[0] as ts.SourceFile)
|
|
32
|
-
result.dispose()
|
|
33
|
-
return output
|
|
34
|
-
}
|
|
35
29
|
|
|
36
30
|
/* A path segment is either a literal key or a runtime expression (a dynamic index). */
|
|
37
31
|
type Segment = { kind: 'literal'; value: string } | { kind: 'expression'; node: ts.Expression }
|
|
@@ -39,6 +33,30 @@ type Segment = { kind: 'literal'; value: string } | { kind: 'expression'; node:
|
|
|
39
33
|
/* Maps a compound-assignment operator to its plain binary counterpart. Logical
|
|
40
34
|
assignments (`||=`/`&&=`/`??=`) lower to an unconditional replace of the
|
|
41
35
|
combined value — consistent with how `+=` lowers (the patch always writes). */
|
|
36
|
+
/* Methods that mutate a doc-rooted container in place — Array, Map and Set (the three
|
|
37
|
+
mutable containers the doc codec serializes). Called on a doc value these can't lower to
|
|
38
|
+
a bare `readCall` (which would mutate the live tree by reference and never emit a patch —
|
|
39
|
+
so nothing re-renders); they route through `$$mutateDocContainer`, which
|
|
40
|
+
clones-applies-replaces so a real patch fires. The names are disjoint across the three
|
|
41
|
+
container kinds, so one set routes them all and the runtime helper decides the kind by the
|
|
42
|
+
value's type. `push` is handled separately above (fine-grained `add` patches). */
|
|
43
|
+
const MUTATING_CONTAINER_METHODS = new Set([
|
|
44
|
+
// Array
|
|
45
|
+
'pop',
|
|
46
|
+
'shift',
|
|
47
|
+
'unshift',
|
|
48
|
+
'splice',
|
|
49
|
+
'sort',
|
|
50
|
+
'reverse',
|
|
51
|
+
'fill',
|
|
52
|
+
'copyWithin',
|
|
53
|
+
// Map / Set (`delete` + `clear` shared; `set` is Map, `add` is Set)
|
|
54
|
+
'set',
|
|
55
|
+
'add',
|
|
56
|
+
'delete',
|
|
57
|
+
'clear',
|
|
58
|
+
])
|
|
59
|
+
|
|
42
60
|
const COMPOUND_OPERATORS = new Map<ts.SyntaxKind, ts.BinaryOperator>([
|
|
43
61
|
[ts.SyntaxKind.PlusEqualsToken, ts.SyntaxKind.PlusToken],
|
|
44
62
|
[ts.SyntaxKind.MinusEqualsToken, ts.SyntaxKind.MinusToken],
|
|
@@ -151,6 +169,7 @@ export function docAccessTransformer(docName: string): ts.TransformerFactory<ts.
|
|
|
151
169
|
)
|
|
152
170
|
/* `node.arguments.length > 0` above guarantees `adds` is non-empty, so the
|
|
153
171
|
single-element branch's `adds[0]` is defined. */
|
|
172
|
+
// biome-ignore lint/style/noNonNullAssertion: length === 1 guarantees adds[0] is defined
|
|
154
173
|
return adds.length === 1 ? adds[0]! : ts.factory.createCommaListExpression(adds)
|
|
155
174
|
}
|
|
156
175
|
}
|
|
@@ -162,10 +181,32 @@ export function docAccessTransformer(docName: string): ts.TransformerFactory<ts.
|
|
|
162
181
|
const access = node.expression
|
|
163
182
|
const segments = pathSegments(access.expression)
|
|
164
183
|
if (segments) {
|
|
165
|
-
const read = docCall(docName, 'read', [buildPath(segments)])
|
|
166
184
|
const args = node.arguments.map(
|
|
167
185
|
(arg) => ts.visitNode(arg, visit) as ts.Expression,
|
|
168
186
|
)
|
|
187
|
+
/* An in-place-mutating container method (Array/Map/Set) on a doc path →
|
|
188
|
+
route through `$$mutateDocContainer(doc, path, member, [args])` so the
|
|
189
|
+
mutation lands as a patch instead of silently mutating the live tree by
|
|
190
|
+
reference. Optional chaining (`model.items?.splice(…)`) keeps the bare-call
|
|
191
|
+
semantics below — skip-if-absent is the author's explicit choice, and a
|
|
192
|
+
nullish container has nothing to mutate. */
|
|
193
|
+
if (
|
|
194
|
+
MUTATING_CONTAINER_METHODS.has(access.name.text) &&
|
|
195
|
+
!access.questionDotToken &&
|
|
196
|
+
!node.questionDotToken
|
|
197
|
+
) {
|
|
198
|
+
return ts.factory.createCallExpression(
|
|
199
|
+
ts.factory.createIdentifier('$$mutateDocContainer'),
|
|
200
|
+
undefined,
|
|
201
|
+
[
|
|
202
|
+
ts.factory.createIdentifier(docName),
|
|
203
|
+
buildPath(segments),
|
|
204
|
+
ts.factory.createStringLiteral(access.name.text),
|
|
205
|
+
ts.factory.createArrayLiteralExpression(args),
|
|
206
|
+
],
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
const read = docCall(docName, 'read', [buildPath(segments)])
|
|
169
210
|
/* Optional chaining is the author's explicit skip-if-absent: a nullish
|
|
170
211
|
read short-circuits the whole call to `undefined`. Keep it bare —
|
|
171
212
|
routing it through the throwing guard would invert that semantics.
|
|
@@ -7,12 +7,13 @@ import { docAccessTransformer } from './lowerDocAccess.ts'
|
|
|
7
7
|
import { signalRefsTransformer } from './renameSignalRefs.ts'
|
|
8
8
|
import { stripEffectsTransformer } from './stripEffects.ts'
|
|
9
9
|
import { TS_PRINTER } from './TS_PRINTER.ts'
|
|
10
|
+
import type { SeedTypeClassifier } from './types/SeedTypeClassifier.ts'
|
|
10
11
|
|
|
11
12
|
/* The `abide/ui/*` modules the reactive surface is imported from. An author's import of
|
|
12
13
|
one is compiler-recognised and lowered, so its binding is often fully consumed — a plain
|
|
13
14
|
`state(...)` becomes `$$model`/`$$scope` with no `state` reference left. Such a dead
|
|
14
|
-
import is dropped from the emitted module (see `deadReactiveImport`) so the output
|
|
15
|
-
spurious static `@abide/ui` dependency —
|
|
15
|
+
import is dropped from the emitted module (see `deadReactiveImport`) so the output carries
|
|
16
|
+
no spurious static `@abide/ui` dependency — a dead import a bundler would still resolve. */
|
|
16
17
|
const REACTIVE_IMPORT_SPECIFIERS = new Set([
|
|
17
18
|
`${ABIDE_PACKAGE_NAME}/ui/state`,
|
|
18
19
|
`${ABIDE_PACKAGE_NAME}/ui/effect`,
|
|
@@ -61,6 +62,25 @@ export function lowerScript(
|
|
|
61
62
|
desugared away. Folded into the dead-import usage check so a live import isn't dropped
|
|
62
63
|
(→ `ReferenceError: state is not defined` in the branch). Empty for a nested script. */
|
|
63
64
|
externalUsage = '',
|
|
65
|
+
/* Names of synthetic `const __cN = computed(...)` cells `analyzeComponent` prepended for
|
|
66
|
+
asyncIterable interpolations (ADR-0019 Stage D). `computed` here is an unimported callee,
|
|
67
|
+
so `desugarSignals` recognizes these declarations by name — routing each to an eager
|
|
68
|
+
`trackedComputed` stream cell — rather than by import resolution. Empty on every other path. */
|
|
69
|
+
injectedCellNames: ReadonlySet<string> = new Set(),
|
|
70
|
+
/* The subset of `injectedCellNames` whose author wrote `await` (ADR-0032): a BLOCKING peek-cell
|
|
71
|
+
that joins the SSR barrier (resolved inline), vs a streaming one that ships pending. Threaded
|
|
72
|
+
to `desugarSignals` so `injectedComputedStatements` passes the right `trackedComputed`
|
|
73
|
+
streaming flag. Empty on every other path. */
|
|
74
|
+
blockingCellNames: ReadonlySet<string> = new Set(),
|
|
75
|
+
/* Names the template writes/forwards (`writtenTemplateNames`), so a `props()` binding used
|
|
76
|
+
two-way is desugared to a writable cell rather than a read-only derive. Empty for a nested
|
|
77
|
+
script (which declares no props). */
|
|
78
|
+
templateWrittenNames: ReadonlySet<string> = new Set(),
|
|
79
|
+
/* Type-directed seed classifier + this script's absolute source base (ADR-0023), threaded
|
|
80
|
+
to `desugarSignals` so a no-marker `computed(seed)` routes on the seed's checker type.
|
|
81
|
+
Absent ⇒ fail-open to the `isBareCallComputed` syntax heuristic (today's behavior). */
|
|
82
|
+
seedClassify?: SeedTypeClassifier,
|
|
83
|
+
scriptBase = 0,
|
|
64
84
|
): {
|
|
65
85
|
body: string
|
|
66
86
|
imports: string
|
|
@@ -68,13 +88,28 @@ export function lowerScript(
|
|
|
68
88
|
stateNames: Set<string>
|
|
69
89
|
derivedNames: Set<string>
|
|
70
90
|
computedNames: Set<string>
|
|
91
|
+
cellReadNames: Set<string>
|
|
71
92
|
droppedReactiveImports: Set<string>
|
|
72
93
|
} {
|
|
73
94
|
const source = ts.createSourceFile('component.ts', scriptBody, ts.ScriptTarget.Latest, true)
|
|
74
|
-
const { transformer, stateNames, derivedNames, computedNames } = desugarSignals(
|
|
95
|
+
const { transformer, stateNames, derivedNames, computedNames, cellReadNames } = desugarSignals(
|
|
96
|
+
source,
|
|
97
|
+
injectedCellNames,
|
|
98
|
+
blockingCellNames,
|
|
99
|
+
templateWrittenNames,
|
|
100
|
+
seedClassify,
|
|
101
|
+
scriptBase,
|
|
102
|
+
)
|
|
75
103
|
const result = ts.transform(source, [
|
|
76
104
|
transformer,
|
|
77
|
-
signalRefsTransformer(
|
|
105
|
+
signalRefsTransformer(
|
|
106
|
+
stateNames,
|
|
107
|
+
derivedNames,
|
|
108
|
+
computedNames,
|
|
109
|
+
new Set(),
|
|
110
|
+
new Set(),
|
|
111
|
+
cellReadNames,
|
|
112
|
+
),
|
|
78
113
|
docAccessTransformer('$$model'),
|
|
79
114
|
])
|
|
80
115
|
const transformed = result.transformed[0] as ts.SourceFile
|
|
@@ -126,6 +161,7 @@ export function lowerScript(
|
|
|
126
161
|
stateNames,
|
|
127
162
|
derivedNames,
|
|
128
163
|
computedNames,
|
|
164
|
+
cellReadNames,
|
|
129
165
|
droppedReactiveImports,
|
|
130
166
|
}
|
|
131
167
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type ts from 'typescript'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Finds the shadow AST node for a template interpolation given its span in shadow
|
|
5
|
+
coordinates. An interpolation is emitted as a parenthesised expression, so the
|
|
6
|
+
node we want starts and ends exactly on the span; we descend only into nodes that
|
|
7
|
+
still cover the whole span and keep the deepest exact match (the innermost node
|
|
8
|
+
whose `getStart()/getEnd()` equal the span bounds).
|
|
9
|
+
*/
|
|
10
|
+
export function nodeAtShadowOffset(
|
|
11
|
+
sourceFile: ts.SourceFile,
|
|
12
|
+
offset: number,
|
|
13
|
+
length: number,
|
|
14
|
+
): ts.Node | undefined {
|
|
15
|
+
let best: ts.Node | undefined
|
|
16
|
+
const visit = (node: ts.Node): void => {
|
|
17
|
+
const start = node.getStart(sourceFile)
|
|
18
|
+
const end = node.getEnd()
|
|
19
|
+
if (start <= offset && end >= offset + length) {
|
|
20
|
+
if (start === offset && end === offset + length) {
|
|
21
|
+
best = node
|
|
22
|
+
}
|
|
23
|
+
node.forEachChild(visit)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
visit(sourceFile)
|
|
27
|
+
return best
|
|
28
|
+
}
|