@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
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import ts from 'typescript'
|
|
2
2
|
import { ABIDE_PACKAGE_NAME } from '../../shared/ABIDE_PACKAGE_NAME.ts'
|
|
3
|
+
import { attrLiftPosition } from './attrLiftPosition.ts'
|
|
4
|
+
import { isWhitespaceText } from './isWhitespaceText.ts'
|
|
5
|
+
import { type LiftPosition, liftAsyncSubExpressions } from './liftAsyncSubExpressions.ts'
|
|
3
6
|
import { parseTemplate } from './parseTemplate.ts'
|
|
4
7
|
import {
|
|
5
8
|
NESTED_REACTIVE_BINDINGS,
|
|
@@ -8,7 +11,9 @@ import {
|
|
|
8
11
|
resolveReactiveExport,
|
|
9
12
|
} from './resolveReactiveExport.ts'
|
|
10
13
|
import { signalCallee } from './signalCallee.ts'
|
|
14
|
+
import { LEADING_SCRIPT } from './templateStartOffset.ts'
|
|
11
15
|
import type { CompiledShadow, ShadowDiagnostic, ShadowMapping } from './types/CompiledShadow.ts'
|
|
16
|
+
import type { InterpolationClassifier } from './types/InterpolationClassifier.ts'
|
|
12
17
|
import type { TemplateNode } from './types/TemplateNode.ts'
|
|
13
18
|
|
|
14
19
|
/*
|
|
@@ -22,8 +27,10 @@ OMITTED (see `shadowPreamble`) to avoid a duplicate identifier. Otherwise
|
|
|
22
27
|
`state`/`linked`/`computed` are declared ambiently and `effect` is imported as a fallback
|
|
23
28
|
for a bare/nested use the top-level rewrite doesn't project (unused when projected — fine,
|
|
24
29
|
the shadow disables noUnusedLocals). `props` is the prop reader — destructured
|
|
25
|
-
(`const { a = 1 } = props<Shape>()`);
|
|
26
|
-
|
|
30
|
+
(`const { a = 1 } = props<Shape>()`); a required import (`abide/ui/props`), so the shadow
|
|
31
|
+
declares it only when imported, returning the file's contextual shape (route params or
|
|
32
|
+
`Record<string, any>`) intersected with its own type argument — additive, so declared props
|
|
33
|
+
like `children: Snippet` layer on top instead of re-spelling the route shape.
|
|
27
34
|
*/
|
|
28
35
|
function shadowPreamble(importedReactives: ReadonlySet<string>): string {
|
|
29
36
|
/* Omit the ambient fallback for a primitive the author imports — its own binding is
|
|
@@ -46,7 +53,6 @@ function shadowPreamble(importedReactives: ReadonlySet<string>): string {
|
|
|
46
53
|
importedReactives.has('computed')
|
|
47
54
|
? undefined
|
|
48
55
|
: 'declare function computed<T>(compute: () => T): { readonly value: T }',
|
|
49
|
-
'declare const children: (() => void) | undefined',
|
|
50
56
|
/* `effect` is defined either way — the preamble import or the author's own import. */
|
|
51
57
|
'void [effect, snippet, scope]',
|
|
52
58
|
]
|
|
@@ -68,23 +74,56 @@ The script's signal surface is rewritten to value types:
|
|
|
68
74
|
Everything else (functions, plain consts, imports) is emitted verbatim, so
|
|
69
75
|
expressions inside it (e.g. a computed's compute body) are checked and mapped too.
|
|
70
76
|
*/
|
|
71
|
-
export function compileShadow(
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
export function compileShadow(
|
|
78
|
+
source: string,
|
|
79
|
+
propsType = 'Record<string, any>',
|
|
80
|
+
classify?: InterpolationClassifier,
|
|
81
|
+
): CompiledShadow {
|
|
82
|
+
const builder = createBuilder(classify)
|
|
83
|
+
const leadingScript = source.match(LEADING_SCRIPT)
|
|
74
84
|
const scriptBody = leadingScript?.[1] ?? ''
|
|
75
85
|
/* Body starts just past the opening `<script …>`; template just past `</script>`. */
|
|
76
86
|
const scriptStart = leadingScript ? source.indexOf('>', leadingScript.index) + 1 : 0
|
|
77
87
|
const templateStart = leadingScript ? (leadingScript.index ?? 0) + leadingScript[0].length : 0
|
|
78
88
|
|
|
79
|
-
const { imports, types, scope, propsShapes, diagnostics, importedReactives } =
|
|
80
|
-
scriptBody,
|
|
81
|
-
scriptStart,
|
|
82
|
-
)
|
|
89
|
+
const { imports, types, scope, propsShapes, diagnostics, importedReactives, propsLocalName } =
|
|
90
|
+
analyzeScript(scriptBody, scriptStart)
|
|
83
91
|
builder.raw(shadowPreamble(importedReactives))
|
|
84
|
-
/*
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
/* The peek helpers the async-interpolation wrap targets (ADR-0032): `$$peek` unwraps a promise
|
|
93
|
+
sub-expression to its resolved value (`undefined` while pending) and `$$peekStream` reads an
|
|
94
|
+
async iterable's latest frame, so `getFoo()?.name`/`{#if getFoo()}` type-check against the
|
|
95
|
+
RESOLVED value the runtime peeks — not the raw `Promise`. Emitted only when a classifier is
|
|
96
|
+
present (the wrap is type-directed); their names carry the reserved `$$` prefix an author
|
|
97
|
+
binding can never take, so they cannot collide with user code. */
|
|
98
|
+
if (classify !== undefined) {
|
|
99
|
+
builder.raw('declare function $$peek<T>(v: T): Awaited<T> | undefined\n')
|
|
100
|
+
builder.raw(
|
|
101
|
+
'declare function $$peekStream<S>(v: S): (S extends AsyncIterable<infer F> ? F : unknown) | undefined\n',
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
/* `props` is a required import (`abide/ui/props`). The shadow owns its type so the
|
|
105
|
+
return is file-contextual — the route param shape (page/layout) or `Record<string,
|
|
106
|
+
any>` (component), intersected with the author's annotation `T` so declared props
|
|
107
|
+
(notably `children: Snippet`) are ADDITIVE and route params never need re-spelling.
|
|
108
|
+
Emitted only when imported: a missing import surfaces as "Cannot find name 'props'"
|
|
109
|
+
(or the author's local alias), so `abide check` flags it. Declared under the LOCAL
|
|
110
|
+
binding name (alias-safe, like `state`/`effect`) — a `props as p` import must get a
|
|
111
|
+
`p` declare, or `p()` reads as undefined once the import below is stripped. */
|
|
112
|
+
if (propsLocalName !== undefined) {
|
|
113
|
+
builder.raw(`declare function ${propsLocalName}<T = {}>(): (${propsType}) & T\n`)
|
|
114
|
+
}
|
|
115
|
+
const propsSpecifier = `${ABIDE_PACKAGE_NAME}/ui/props`
|
|
87
116
|
for (const line of imports) {
|
|
117
|
+
/* The `props` import is replaced by the contextual `declare function` above;
|
|
118
|
+
emitting it too would be a duplicate-identifier error. Matched by EXACT specifier
|
|
119
|
+
(either quote style, not a loose suffix match) so an unrelated user module merely
|
|
120
|
+
named `.../ui/props` survives verbatim. */
|
|
121
|
+
if (
|
|
122
|
+
line.text.includes(`from '${propsSpecifier}'`) ||
|
|
123
|
+
line.text.includes(`from "${propsSpecifier}"`)
|
|
124
|
+
) {
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
88
127
|
builder.flush(line)
|
|
89
128
|
}
|
|
90
129
|
/* Component-local `type`/`interface` declarations are hoisted to module scope —
|
|
@@ -123,6 +162,19 @@ export function compileShadow(source: string, propsType = 'Record<string, any>')
|
|
|
123
162
|
/* Nested `<script>` blocks inline into the synchronous `build()` too, so a top-level
|
|
124
163
|
await in one is the same build-breaker — flag it, mapped via the node's body offset. */
|
|
125
164
|
collectNestedScriptAwaitDiagnostics(templateNodes, diagnostics)
|
|
165
|
+
/* Emit the DOM-typed attachment aliases only when an element `attach` uses them: they
|
|
166
|
+
reference `HTMLElementTagNameMap`/`Element`, so a template with no element attach never
|
|
167
|
+
forces DOM lib into its shadow. `__ElementFor` maps a tag to its element interface
|
|
168
|
+
(HTML-first; unknown/custom tags fall back to `Element`); `__Attachment` sources its
|
|
169
|
+
return type from the real `attach` runtime signature so it never drifts. */
|
|
170
|
+
if (hasElementAttach(templateNodes)) {
|
|
171
|
+
builder.raw(
|
|
172
|
+
'type __ElementFor<T extends string> = T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T] : T extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[T] : T extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[T] : Element;\n',
|
|
173
|
+
)
|
|
174
|
+
builder.raw(
|
|
175
|
+
`type __Attachment<E extends Element> = (node: E) => ReturnType<Parameters<typeof import('${ABIDE_PACKAGE_NAME}/ui/dom/attach').attach>[1]>;\n`,
|
|
176
|
+
)
|
|
177
|
+
}
|
|
126
178
|
emitNodes(templateNodes, builder)
|
|
127
179
|
builder.raw('}\n')
|
|
128
180
|
return { ...builder.result(), diagnostics }
|
|
@@ -140,6 +192,12 @@ type Builder = {
|
|
|
140
192
|
mapped: (text: string, sourceLoc: number | undefined) => void
|
|
141
193
|
expr: (code: string, sourceLoc: number | undefined) => void
|
|
142
194
|
stmt: (code: string, sourceLoc: number | undefined) => void
|
|
195
|
+
/* Like `expr`/`stmt` but for a template interpolation: async (sub)expressions are wrapped in a
|
|
196
|
+
peek helper (`$$peek`/`$$peekStream`) so their RESOLVED type composes (ADR-0032). Every source
|
|
197
|
+
span still maps 1:1 — the inserted wrapper chars are unmapped — so diagnostics land precisely.
|
|
198
|
+
With no classifier (or no async sub-expression) these are byte-identical to `expr`/`stmt`. */
|
|
199
|
+
asyncExpr: (code: string, sourceLoc: number | undefined, position: LiftPosition) => void
|
|
200
|
+
asyncStmt: (code: string, sourceLoc: number | undefined, position: LiftPosition) => void
|
|
143
201
|
flush: (line: ScopeLine) => void
|
|
144
202
|
/* A fresh shadow-local binding name (`__<base>_<n>`) — for synthesised bindings
|
|
145
203
|
like an await's resolved value, kept distinct so nested blocks never collide. */
|
|
@@ -151,10 +209,55 @@ type Builder = {
|
|
|
151
209
|
emitted verbatim from the original script), offset-relative to the line start. */
|
|
152
210
|
type ScopeLine = { text: string; segments: ShadowMapping[] }
|
|
153
211
|
|
|
154
|
-
function createBuilder(): Builder {
|
|
212
|
+
function createBuilder(classify?: InterpolationClassifier): Builder {
|
|
155
213
|
let code = ''
|
|
156
214
|
let uniqueCounter = 0
|
|
157
215
|
const mappings: ShadowMapping[] = []
|
|
216
|
+
/* The non-blocking async (sub)expression spans in one interpolation — the peek-wrap targets.
|
|
217
|
+
A leading `await` (blocking) stays verbatim: `await X` already resolves in the async shadow
|
|
218
|
+
render fn. Fail-open: no classifier, an unparseable field, or the D4a `AsyncIterable`-in-`each`
|
|
219
|
+
throw all yield no spans, so the emit is verbatim (`collectValuePositionDiagnostics` still
|
|
220
|
+
reports the D4a error separately). */
|
|
221
|
+
const peekSpans = (exprCode: string, sourceLoc: number, position: LiftPosition) => {
|
|
222
|
+
if (classify === undefined) {
|
|
223
|
+
return []
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
let counter = 0
|
|
227
|
+
const result = liftAsyncSubExpressions(
|
|
228
|
+
exprCode,
|
|
229
|
+
sourceLoc,
|
|
230
|
+
classify,
|
|
231
|
+
() => `__v${counter++}`,
|
|
232
|
+
position,
|
|
233
|
+
)
|
|
234
|
+
return result.spans.filter((span) => !span.blocking)
|
|
235
|
+
} catch {
|
|
236
|
+
return []
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/* Appends `exprCode` mapped 1:1 to source, but wraps each async span in its peek helper — the
|
|
240
|
+
wrapper chars are unmapped, every original char keeps its true source offset. */
|
|
241
|
+
const emitPeeked = (exprCode: string, sourceLoc: number, position: LiftPosition): void => {
|
|
242
|
+
const spans = peekSpans(exprCode, sourceLoc, position)
|
|
243
|
+
if (spans.length === 0) {
|
|
244
|
+
builder.mapped(exprCode, sourceLoc)
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
let cursor = 0
|
|
248
|
+
for (const span of spans) {
|
|
249
|
+
if (span.start > cursor) {
|
|
250
|
+
builder.mapped(exprCode.slice(cursor, span.start), sourceLoc + cursor)
|
|
251
|
+
}
|
|
252
|
+
builder.raw(span.kind === 'asyncIterable' ? '$$peekStream(' : '$$peek(')
|
|
253
|
+
builder.mapped(exprCode.slice(span.start, span.end), sourceLoc + span.start)
|
|
254
|
+
builder.raw(')')
|
|
255
|
+
cursor = span.end
|
|
256
|
+
}
|
|
257
|
+
if (cursor < exprCode.length) {
|
|
258
|
+
builder.mapped(exprCode.slice(cursor), sourceLoc + cursor)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
158
261
|
const builder: Builder = {
|
|
159
262
|
unique(base) {
|
|
160
263
|
return `__${base}_${uniqueCounter++}`
|
|
@@ -188,6 +291,21 @@ function createBuilder(): Builder {
|
|
|
188
291
|
builder.expr(exprCode, sourceLoc)
|
|
189
292
|
code += ';\n'
|
|
190
293
|
},
|
|
294
|
+
asyncExpr(exprCode, sourceLoc, position) {
|
|
295
|
+
/* No source offset ⇒ can't map or classify — emit verbatim, like `expr`. */
|
|
296
|
+
if (sourceLoc === undefined) {
|
|
297
|
+
code += `(${exprCode})`
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
code += '('
|
|
301
|
+
emitPeeked(exprCode, sourceLoc, position)
|
|
302
|
+
code += ')'
|
|
303
|
+
},
|
|
304
|
+
asyncStmt(exprCode, sourceLoc, position) {
|
|
305
|
+
code += ';'
|
|
306
|
+
builder.asyncExpr(exprCode, sourceLoc, position)
|
|
307
|
+
code += ';\n'
|
|
308
|
+
},
|
|
191
309
|
flush(line) {
|
|
192
310
|
const base = code.length
|
|
193
311
|
for (const segment of line.segments) {
|
|
@@ -209,6 +327,10 @@ type ScriptAnalysis = {
|
|
|
209
327
|
/* The reactive primitives the author imports (`state`/`effect`), so the preamble
|
|
210
328
|
omits the ambient fallback for each and avoids a duplicate-identifier error. */
|
|
211
329
|
importedReactives: Set<string>
|
|
330
|
+
/* The LOCAL binding name the author's `props` import is bound to (alias-safe — `props`
|
|
331
|
+
for the canonical import, `p` for `props as p`), or undefined when not imported. The
|
|
332
|
+
`declare function` for `props` must target this name, not the canonical `'props'`. */
|
|
333
|
+
propsLocalName: string | undefined
|
|
212
334
|
}
|
|
213
335
|
|
|
214
336
|
/* Pushes a diagnostic for every author binding whose name starts with the reserved `$$`
|
|
@@ -321,7 +443,15 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
|
|
|
321
443
|
const propsShapes: string[] = []
|
|
322
444
|
const diagnostics: ShadowDiagnostic[] = []
|
|
323
445
|
if (scriptBody.trim() === '') {
|
|
324
|
-
return {
|
|
446
|
+
return {
|
|
447
|
+
imports,
|
|
448
|
+
types,
|
|
449
|
+
scope,
|
|
450
|
+
propsShapes,
|
|
451
|
+
diagnostics,
|
|
452
|
+
importedReactives: new Set(),
|
|
453
|
+
propsLocalName: undefined,
|
|
454
|
+
}
|
|
325
455
|
}
|
|
326
456
|
const file = ts.createSourceFile('script.ts', scriptBody, ts.ScriptTarget.Latest, true)
|
|
327
457
|
/* The author's reactive import bindings (alias-safe) — recognition source for
|
|
@@ -329,6 +459,15 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
|
|
|
329
459
|
the preamble omits its ambient fallback for. */
|
|
330
460
|
const bindings = reactiveImportBindings(file)
|
|
331
461
|
const importedReactives = new Set(bindings.direct.values())
|
|
462
|
+
/* The local name bound to `props` (alias-safe): the key whose value is the canonical
|
|
463
|
+
`'props'`. At most one — a file imports `props` from one specifier. */
|
|
464
|
+
let propsLocalName: string | undefined
|
|
465
|
+
for (const [local, canonical] of bindings.direct) {
|
|
466
|
+
if (canonical === 'props') {
|
|
467
|
+
propsLocalName = local
|
|
468
|
+
break
|
|
469
|
+
}
|
|
470
|
+
}
|
|
332
471
|
/* The `$$` prefix is reserved for the compiler's injected runtime (`$$each`, `$$model`,
|
|
333
472
|
`$$scope`, …), so an author binding may not start with it — that's the contract that
|
|
334
473
|
lets a user freely name a variable after any helper. Flag every such declaration. */
|
|
@@ -368,7 +507,7 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
|
|
|
368
507
|
scope.push(scopeLineFor(declaration, propsShapes, verbatim, span, bindings))
|
|
369
508
|
}
|
|
370
509
|
}
|
|
371
|
-
return { imports, types, scope, propsShapes, diagnostics, importedReactives }
|
|
510
|
+
return { imports, types, scope, propsShapes, diagnostics, importedReactives, propsLocalName }
|
|
372
511
|
}
|
|
373
512
|
|
|
374
513
|
/* Value-projects a nested control-flow `<script>` body the way `analyzeScript`
|
|
@@ -429,12 +568,20 @@ function scopeLineFor(
|
|
|
429
568
|
`verbatim(call.expression)` below keeps the AUTHOR's text for the trailing hover ref. */
|
|
430
569
|
const callee = resolveReactiveExport(call.expression, bindings)
|
|
431
570
|
if (callee === 'props') {
|
|
432
|
-
/*
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
571
|
+
/* The parent-facing prop shape. Like `state`/`computed` below, it comes from the
|
|
572
|
+
generic (`const {…} = props<Shape>()`) OR the destructure's binding annotation
|
|
573
|
+
(`const {…}: Shape = props()`) — either TS-idiomatic form resolves through the
|
|
574
|
+
shadow's own imports, so an imported/aliased type just works, no inline gotcha.
|
|
575
|
+
Default `Record<string, any>`; the destructure then projects verbatim so each
|
|
576
|
+
binding inherits its value type. */
|
|
577
|
+
const shape = call.typeArguments?.[0] ?? declaration.type
|
|
436
578
|
propsShapes.push(shape === undefined ? 'Record<string, any>' : verbatim(shape))
|
|
437
|
-
|
|
579
|
+
/* `let`, not `const`: a prop the component writes back through a two-way `bind:prop`
|
|
580
|
+
(`value += 1`, or forwarding it to another `bind:`) is a legal reassignment, so the
|
|
581
|
+
destructure must not read as a `const`. Bindability is dynamic (decided by what the
|
|
582
|
+
parent passed), so the shadow can't single out which props are written — it relaxes
|
|
583
|
+
the whole destructure, matching the runtime's usage-inferred upgrade. */
|
|
584
|
+
return { text: `let ${verbatim(declaration)};`, segments: [span(declaration, 4)] }
|
|
438
585
|
}
|
|
439
586
|
/* The rewrite drops the callee from the line, so hovering `state`/`computed`/
|
|
440
587
|
`linked` at its call site has nothing to resolve. Append the callee as a
|
|
@@ -446,14 +593,22 @@ function scopeLineFor(
|
|
|
446
593
|
})
|
|
447
594
|
if (callee === 'state') {
|
|
448
595
|
/* state<T>(initial): T is the value type — carry it onto the `let` so an
|
|
449
|
-
explicit annotation isn't lost to `any`/`any[]` inference of the initial.
|
|
450
|
-
|
|
596
|
+
explicit annotation isn't lost to `any`/`any[]` inference of the initial. The
|
|
597
|
+
type comes from the generic (`state<T>(v)`), else the binding annotation
|
|
598
|
+
(`let x: T = state(v)`) — either form pins the cell type, so a narrow/`any`
|
|
599
|
+
inference of the initial (`state([])` → `any[]`) can't leak. */
|
|
600
|
+
const typeNode = call.typeArguments?.[0] ?? declaration.type
|
|
451
601
|
const annotation = typeNode === undefined ? '' : `: ${verbatim(typeNode)}`
|
|
452
602
|
const init = call.arguments[0]
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
603
|
+
/* A literal `undefined` initial is the no-arg form spelled out (`state<T>(undefined)`):
|
|
604
|
+
the value is `T | undefined`, never `= (undefined)` checked against a non-optional
|
|
605
|
+
`T` — which would spuriously flag "undefined not assignable to T". */
|
|
606
|
+
const initIsUndefined =
|
|
607
|
+
init !== undefined && ts.isIdentifier(init) && init.text === 'undefined'
|
|
608
|
+
if (init === undefined || initIsUndefined) {
|
|
609
|
+
/* No initial (`state<T>()` / `state<T>(undefined)`): the value is `T | undefined`.
|
|
610
|
+
A definite-assignment assertion (`!`) gives that union without a use-before-
|
|
611
|
+
assign false-positive AND without control-flow narrowing it to just `undefined`
|
|
457
612
|
(an `= undefined` initializer, never reassigned in the shadow, would make
|
|
458
613
|
a guard like `x !== undefined` collapse to `never`). Unguarded access is
|
|
459
614
|
then correctly flagged possibly-undefined; a guard narrows cleanly. */
|
|
@@ -471,9 +626,12 @@ function scopeLineFor(
|
|
|
471
626
|
})
|
|
472
627
|
}
|
|
473
628
|
/* computed<T>(compute) / linked<T>(seed) — the only callees left: T is the value
|
|
474
|
-
type — the call's first arg is a thunk, so invoking it yields the value.
|
|
475
|
-
|
|
476
|
-
|
|
629
|
+
type — the call's first arg is a thunk, so invoking it yields the value. The type
|
|
630
|
+
comes from the generic (`computed<T>(fn)`), else the binding annotation
|
|
631
|
+
(`let x: T = state.computed(fn)`) — same fallback as `state` above, so a binding
|
|
632
|
+
annotation isn't dropped and `abide check` doesn't emit `never`-inference false
|
|
633
|
+
positives on the thunk's return. */
|
|
634
|
+
const typeNode = call.typeArguments?.[0] ?? declaration.type
|
|
477
635
|
const annotation = typeNode === undefined ? '' : `: ${verbatim(typeNode)}`
|
|
478
636
|
const fn = call.arguments[0]
|
|
479
637
|
/* `linked` is a writable `State<T>` at runtime (it reseeds AND accepts `.value =`
|
|
@@ -494,6 +652,28 @@ function scopeLineFor(
|
|
|
494
652
|
})
|
|
495
653
|
}
|
|
496
654
|
|
|
655
|
+
/* Whether any ELEMENT in the tree carries an `attach` — gates emitting the DOM-typed
|
|
656
|
+
attachment aliases. All nested content (block bodies, await/switch branches, snippet
|
|
657
|
+
bodies) routes through `children`, so a recursive `children` walk is complete. */
|
|
658
|
+
function hasElementAttach(nodes: TemplateNode[]): boolean {
|
|
659
|
+
for (const node of nodes) {
|
|
660
|
+
if (node === undefined) {
|
|
661
|
+
continue
|
|
662
|
+
}
|
|
663
|
+
if (node.kind === 'element') {
|
|
664
|
+
for (const attr of node.attrs) {
|
|
665
|
+
if (attr.kind === 'attach') {
|
|
666
|
+
return true
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if ('children' in node && node.children !== undefined && hasElementAttach(node.children)) {
|
|
671
|
+
return true
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return false
|
|
675
|
+
}
|
|
676
|
+
|
|
497
677
|
/* Emits a sibling list — each node standalone via `emitNode`. */
|
|
498
678
|
function emitNodes(nodes: TemplateNode[], builder: Builder): void {
|
|
499
679
|
for (const node of nodes) {
|
|
@@ -511,22 +691,43 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
511
691
|
case 'text':
|
|
512
692
|
for (const part of node.parts) {
|
|
513
693
|
if (part.kind === 'expression') {
|
|
514
|
-
builder.
|
|
694
|
+
builder.asyncStmt(part.code, part.loc, 'content')
|
|
515
695
|
}
|
|
516
696
|
}
|
|
517
697
|
return
|
|
518
698
|
case 'element':
|
|
519
699
|
for (const attr of node.attrs) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
700
|
+
if (attr.kind === 'attach') {
|
|
701
|
+
/* `attach={code}` types its callback's `node` param from the element's tag
|
|
702
|
+
(via `__ElementFor`) and checks the whole value is an attachment: an inline
|
|
703
|
+
arrow's `node` reads the specific DOM interface, and a non-function value is
|
|
704
|
+
rejected. Same IIFE-parameter contextual-typing trick the component-prop
|
|
705
|
+
path uses; the leading `;` guards an unterminated preceding statement. */
|
|
706
|
+
builder.raw(
|
|
707
|
+
`;((__attach: __Attachment<__ElementFor<${JSON.stringify(node.tag)}>>) => {})(`,
|
|
708
|
+
)
|
|
709
|
+
builder.expr(attr.code, attr.loc)
|
|
710
|
+
builder.raw(');\n')
|
|
711
|
+
} else if (attr.kind === 'interpolated') {
|
|
712
|
+
/* An interpolated value checks each `{expr}` part on its own offset. */
|
|
523
713
|
for (const part of attr.parts) {
|
|
524
714
|
if (part.kind === 'expression') {
|
|
525
|
-
builder.
|
|
715
|
+
builder.asyncStmt(part.code, part.loc, 'attribute')
|
|
526
716
|
}
|
|
527
717
|
}
|
|
528
718
|
} else if (attr.kind !== 'static') {
|
|
529
|
-
|
|
719
|
+
/* Every other dynamic attribute checks its single `code`. `attrLiftPosition` —
|
|
720
|
+
the shared gate the runtime front-end lifts through — decides whether the
|
|
721
|
+
value is async-liftable: only `name={code}` peek-wraps (its promise/stream
|
|
722
|
+
sub-expressions project to their resolved type), while a directive
|
|
723
|
+
(`on*`/`bind:`/`class:`/`style:`/`{...}`) checks its code verbatim, matching
|
|
724
|
+
that the runtime never injects a cell for it. */
|
|
725
|
+
const position = attrLiftPosition(attr)
|
|
726
|
+
if (position !== undefined) {
|
|
727
|
+
builder.asyncStmt(attr.code, attr.loc, position)
|
|
728
|
+
} else {
|
|
729
|
+
builder.stmt(attr.code, attr.loc)
|
|
730
|
+
}
|
|
530
731
|
}
|
|
531
732
|
}
|
|
532
733
|
emitNodes(node.children, builder)
|
|
@@ -548,6 +749,12 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
548
749
|
prop.name.startsWith('style:') ||
|
|
549
750
|
prop.name === 'attach'
|
|
550
751
|
const hasSpread = node.props.some((prop) => prop.spread)
|
|
752
|
+
/* Default slot content (`<Card>…</Card>`) lowers to a `children` Snippet prop at
|
|
753
|
+
runtime (see generateBuild.slotPart), so the completeness check must treat
|
|
754
|
+
`children` as supplied when it's present (handled below). Mirror the runtime's
|
|
755
|
+
predicate: any child that isn't whitespace-only text is renderable slot content
|
|
756
|
+
— an empty/whitespace slot sets no `children` layer, so it doesn't count. */
|
|
757
|
+
const hasSlotContent = node.children.some((child) => !isWhitespaceText(child))
|
|
551
758
|
for (const prop of node.props.filter(handled)) {
|
|
552
759
|
/* Lead with a defensive `;`: an IIFE/object-literal arg starts with `(` or
|
|
553
760
|
`{`, so without it a preceding scope statement left unterminated (a script
|
|
@@ -583,14 +790,30 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
583
790
|
builder.raw(');\n')
|
|
584
791
|
}
|
|
585
792
|
} else {
|
|
586
|
-
|
|
793
|
+
/* With slot content the child's `children` prop is supplied by the slot, so
|
|
794
|
+
check completeness against the shape with `children` made OPTIONAL rather
|
|
795
|
+
than removed — dropping it via a bare `Omit` collapses a children-only
|
|
796
|
+
shape to `{}`, a weak type that then lets excess props slip by. `Omit` +
|
|
797
|
+
`Partial<Pick<…>>` keeps every other prop required and `children` as a
|
|
798
|
+
known-optional key (a no-op `Partial<{}>` when the child declares none), so
|
|
799
|
+
completeness and excess-prop checks both stay intact. The explicit
|
|
800
|
+
`children={…}` attribute is dropped from the literal — the slot's key wins
|
|
801
|
+
(`composeProps`), and leaving it in would read as excess. */
|
|
802
|
+
const propsShape = `Parameters<typeof ${node.name}>[0]`
|
|
803
|
+
const completenessType = hasSlotContent
|
|
804
|
+
? `Omit<${propsShape}, 'children'> & Partial<Pick<${propsShape}, Extract<keyof ${propsShape}, 'children'>>>`
|
|
805
|
+
: propsShape
|
|
806
|
+
const literalProps = hasSlotContent
|
|
807
|
+
? dataProps.filter((prop) => prop.name !== 'children')
|
|
808
|
+
: dataProps
|
|
809
|
+
builder.raw(`;((__c: ${completenessType}): void => { void __c })({`)
|
|
587
810
|
/* A zero-length anchor right after `{`, pointing at the tag: a missing-
|
|
588
811
|
required-prop error spans the literal from `{`, so it overlaps this and
|
|
589
812
|
maps to the tag (an empty span trivially satisfies the source-text ==
|
|
590
813
|
shadow-text invariant). */
|
|
591
814
|
builder.mapped('', node.loc)
|
|
592
815
|
builder.raw('\n')
|
|
593
|
-
for (const prop of
|
|
816
|
+
for (const prop of literalProps) {
|
|
594
817
|
/* The key mapped to its source name (excess-prop errors land on the key);
|
|
595
818
|
the value verbatim-mapped (wrong-type errors land on the value).
|
|
596
819
|
Hyphenated names (`aria-label`, `data-*`) aren't valid identifiers, so
|
|
@@ -630,7 +853,7 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
630
853
|
)
|
|
631
854
|
const thenChildren = node.children.filter((child) => child.kind !== 'case')
|
|
632
855
|
builder.raw('if ')
|
|
633
|
-
builder.
|
|
856
|
+
builder.asyncExpr(node.condition, node.loc, 'if')
|
|
634
857
|
builder.raw(' {\n')
|
|
635
858
|
emitNodes(thenChildren, builder)
|
|
636
859
|
builder.raw('}')
|
|
@@ -639,7 +862,7 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
639
862
|
for (const branch of branches) {
|
|
640
863
|
if (branch.condition !== undefined) {
|
|
641
864
|
builder.raw(' else if ')
|
|
642
|
-
builder.
|
|
865
|
+
builder.asyncExpr(branch.condition, branch.loc, 'if')
|
|
643
866
|
builder.raw(' {\n')
|
|
644
867
|
} else {
|
|
645
868
|
builder.raw(' else {\n')
|
|
@@ -721,7 +944,7 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
721
944
|
non-case children (whitespace between cases) carry nothing and are
|
|
722
945
|
skipped. `break` keeps cases independent under `noFallthroughCasesInSwitch`. */
|
|
723
946
|
builder.raw('switch (')
|
|
724
|
-
builder.
|
|
947
|
+
builder.asyncExpr(node.subject, node.loc, 'switch')
|
|
725
948
|
builder.raw(') {\n')
|
|
726
949
|
for (const child of node.children) {
|
|
727
950
|
if (child.kind !== 'case') {
|
|
@@ -773,7 +996,7 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
773
996
|
builder.mapped(node.params, node.loc)
|
|
774
997
|
}
|
|
775
998
|
/* Wrap the body in `snippet(() => …)` so the shadow types the snippet as
|
|
776
|
-
`(args) =>
|
|
999
|
+
`(args) => SnippetValue` — mirroring the runtime lowering (which returns
|
|
777
1000
|
`$$snippet(($host) => …)`) so a snippet passed to a `Snippet`-typed prop
|
|
778
1001
|
type-checks instead of reading as `() => void`. */
|
|
779
1002
|
builder.raw(') => snippet(() => {\n')
|
|
@@ -11,17 +11,28 @@ SSR/client prop congruence rests on. No spread → the plain object literal of n
|
|
|
11
11
|
value thunks (+ the trailing slot). With a `{...expr}` spread → a `mergeProps` of
|
|
12
12
|
ordered layers — explicit-prop runs, `$$spreadProps(expr)` spreads, the slot —
|
|
13
13
|
resolved last-wins per key, so source order decides overrides (like JSX).
|
|
14
|
-
`lowerExpression` is the caller's expression lowering; `slotPart` is its
|
|
15
|
-
layer
|
|
14
|
+
`lowerExpression` is the caller's expression lowering; `slotPart` is its `children`
|
|
15
|
+
layer — slotted content as a `Snippet` under the `children` prop key (a builder-
|
|
16
|
+
returning callable for the client, a `$snip`-string-returning callable for SSR) — or
|
|
16
17
|
undefined when the component has no slotted children.
|
|
17
18
|
*/
|
|
18
19
|
export function composeProps(
|
|
19
20
|
props: ComponentProps,
|
|
20
21
|
lowerExpression: (code: string) => string,
|
|
21
22
|
slotPart: string | undefined,
|
|
23
|
+
/* The two-way bind accessors (`lowerContext`): `bindRead` lowers a target to its
|
|
24
|
+
current value, `bindWrite` to the statement that writes a value back. Shared with
|
|
25
|
+
element `bind:`, so a component bind accepts the same lvalue / `{ get, set }` targets. */
|
|
26
|
+
bindRead: (code: string) => string,
|
|
27
|
+
bindWrite: (code: string, valueExpr: string) => string,
|
|
22
28
|
): string {
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
/* A `bind:<name>={target}` prop: the normal `() => value` entry, annotated with a
|
|
30
|
+
`set` write-back channel (`bindProp`) so the child can push changes upstream. A
|
|
31
|
+
read-only consumer still just calls the thunk; the setter rides along unseen. */
|
|
32
|
+
const propThunk = (prop: { name: string; code: string; bind?: true }): string =>
|
|
33
|
+
prop.bind === true
|
|
34
|
+
? `${JSON.stringify(prop.name)}: $$bindProp(() => (${bindRead(prop.code)}), ($value) => { ${bindWrite(prop.code, '$value')} })`
|
|
35
|
+
: `${JSON.stringify(prop.name)}: () => (${lowerExpression(prop.code)})`
|
|
25
36
|
if (!props.some((prop) => prop.spread)) {
|
|
26
37
|
const parts = props.map(propThunk)
|
|
27
38
|
if (slotPart !== undefined) {
|