@abide/abide 0.45.0 → 0.47.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 +365 -320
- package/CHANGELOG.md +96 -0
- package/README.md +203 -168
- package/package.json +12 -8
- package/src/abideLsp.ts +11 -12
- package/src/abideResolverPlugin.ts +9 -4
- package/src/lib/bundle/disconnected.abide +3 -0
- package/src/lib/cli/printCommandHelp.ts +43 -0
- package/src/lib/cli/printSessionHelp.ts +2 -1
- package/src/lib/cli/{printHelp.ts → printTopLevelHelp.ts} +3 -40
- package/src/lib/cli/runCli.ts +2 -1
- package/src/lib/mcp/buildPrompts.ts +25 -0
- package/src/lib/mcp/dispatchMcpRequest.ts +8 -6
- package/src/lib/mcp/mcpResourceServerSlot.ts +5 -10
- package/src/lib/mcp/mcpSurface.ts +13 -252
- package/src/lib/mcp/mcpTools.ts +187 -0
- package/src/lib/mcp/renderPrompt.ts +25 -0
- package/src/lib/mcp/types/McpSurface.ts +15 -0
- package/src/lib/mcp/types/PromptDescriptor.ts +5 -0
- package/src/lib/mcp/types/PromptMessage.ts +2 -0
- package/src/lib/mcp/types/ToolDescriptor.ts +7 -0
- package/src/lib/mcp/types/ToolResult.ts +2 -0
- package/src/lib/server/agent.ts +1 -1
- package/src/lib/server/jsonl.ts +2 -2
- package/src/lib/server/rpc/defineRpc.ts +7 -1
- package/src/lib/server/runtime/containsTraversal.ts +21 -17
- package/src/lib/server/runtime/createAppAssetServer.ts +11 -16
- package/src/lib/server/runtime/createServer.ts +101 -71
- package/src/lib/server/runtime/createUiPageRenderer.ts +23 -4
- package/src/lib/server/runtime/devClientFingerprint.ts +3 -3
- package/src/lib/server/runtime/devHotModuleResponse.ts +2 -1
- package/src/lib/server/runtime/finalizeResponse.ts +32 -0
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +8 -17
- package/src/lib/server/socket.ts +1 -1
- package/src/lib/server/sockets/createSocketDispatcher.ts +28 -5
- package/src/lib/server/sockets/defineSocket.ts +26 -7
- package/src/lib/server/sockets/types/SocketRegistryEntry.ts +1 -1
- package/src/lib/server/sockets/types/SocketRoutes.ts +1 -1
- package/src/lib/server/sse.ts +2 -2
- package/src/lib/shared/DEFER.ts +8 -0
- package/src/lib/shared/activeCacheStore.ts +2 -2
- package/src/lib/shared/activePage.ts +2 -2
- package/src/lib/shared/attachRpcSelectorMethods.ts +42 -0
- package/src/lib/shared/attachSocketSelectorMethods.ts +34 -0
- package/src/lib/shared/basePath.ts +2 -2
- package/src/lib/shared/baseSlot.ts +6 -5
- package/src/lib/shared/bodyValueForKind.ts +1 -2
- package/src/lib/shared/buildSocketOverChannel.ts +40 -4
- package/src/lib/shared/cache.ts +477 -110
- package/src/lib/shared/cacheEntryFromSnapshot.ts +3 -22
- package/src/lib/shared/cacheStoreSlot.ts +9 -5
- package/src/lib/{server/runtime → shared}/createReachable.ts +2 -2
- package/src/lib/shared/createRemoteFunction.ts +58 -9
- package/src/lib/shared/createResolverSlot.ts +24 -31
- package/src/lib/shared/createSocketSubRegistry.ts +2 -2
- package/src/lib/shared/decodeRefJson.ts +4 -2
- package/src/lib/shared/decodeResponse.ts +8 -6
- package/src/lib/shared/done.ts +14 -0
- package/src/lib/shared/encodeRefJson.ts +4 -2
- package/src/lib/shared/globalCacheStoreSlot.ts +9 -6
- package/src/lib/shared/hydratingSlot.ts +12 -0
- package/src/lib/shared/isLayoutFile.ts +12 -0
- package/src/lib/shared/keyForRemoteCall.ts +2 -1
- package/src/lib/shared/keyPrefixForRemote.ts +12 -0
- package/src/lib/shared/matchRoute.ts +175 -0
- package/src/lib/shared/normalizePathname.ts +11 -0
- package/src/lib/shared/pageSlot.ts +14 -5
- package/src/lib/shared/pageUrlForFile.ts +3 -3
- package/src/lib/shared/parseRouteSegments.ts +16 -7
- package/src/lib/shared/patch.ts +41 -0
- package/src/lib/shared/peek.ts +35 -0
- package/src/lib/shared/prepareRemoteExport.ts +40 -0
- package/src/lib/shared/prepareRpcModule.ts +98 -16
- package/src/lib/shared/prepareSocketModule.ts +11 -15
- package/src/lib/shared/queryStringFromArgs.ts +11 -0
- package/src/lib/shared/reachable.ts +102 -0
- package/src/lib/shared/refresh.ts +22 -0
- package/src/lib/shared/requestScopeSlot.ts +10 -7
- package/src/lib/shared/routeParamsShape.ts +9 -9
- package/src/lib/shared/rpcErrorRegistry.ts +69 -0
- package/src/lib/shared/selectorPrefix.ts +3 -10
- package/src/lib/shared/setOwnProperty.ts +19 -0
- package/src/lib/shared/snippet.ts +1 -1
- package/src/lib/shared/subscribableProbes.ts +111 -0
- package/src/lib/shared/tailProbeSlot.ts +8 -1
- package/src/lib/shared/types/CacheEntry.ts +9 -15
- package/src/lib/shared/types/CacheOptions.ts +8 -0
- package/src/lib/shared/types/CacheSnapshotEntry.ts +0 -5
- package/src/lib/shared/types/RemoteCallable.ts +25 -7
- package/src/lib/shared/types/RemoteFunction.ts +48 -13
- package/src/lib/shared/types/ResolverSlot.ts +7 -4
- package/src/lib/shared/types/RpcError.ts +26 -0
- package/src/lib/shared/types/SmartReadOptions.ts +32 -0
- package/src/lib/shared/types/Socket.ts +54 -0
- package/src/lib/shared/types/SsrBootState.ts +27 -0
- package/src/lib/shared/types/SsrPayload.ts +21 -0
- package/src/lib/shared/types/Subscribable.ts +13 -13
- package/src/lib/shared/types/TailHooks.ts +2 -1
- package/src/lib/shared/url.ts +29 -14
- package/src/lib/shared/wakeHydrationPeeks.ts +20 -0
- package/src/lib/shared/writeTestSocketsDts.ts +1 -1
- package/src/lib/test/createScriptedSurface.ts +1 -1
- package/src/lib/test/createTestApp.ts +2 -2
- package/src/lib/test/createTestSocketChannel.ts +3 -3
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +5 -6
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +10 -7
- package/src/lib/ui/compile/abideUiPlugin.ts +2 -2
- package/src/lib/ui/compile/analyzeComponent.ts +28 -6
- package/src/lib/ui/compile/compileModule.ts +137 -11
- package/src/lib/ui/compile/compileShadow.ts +101 -84
- package/src/lib/ui/compile/desugarSignals.ts +120 -107
- package/src/lib/ui/compile/generateBuild.ts +47 -11
- package/src/lib/ui/compile/generateSSR.ts +117 -14
- package/src/lib/ui/compile/identifierReferencePattern.ts +13 -0
- package/src/lib/ui/compile/lowerContext.ts +10 -4
- package/src/lib/ui/compile/lowerScript.ts +72 -5
- package/src/lib/ui/compile/prepareNestedScript.ts +31 -17
- package/src/lib/ui/compile/resolveReactiveExport.ts +95 -0
- package/src/lib/ui/compile/signalCallee.ts +24 -0
- package/src/lib/ui/compile/stripEffects.ts +14 -11
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +5 -0
- package/src/lib/ui/dom/appendStatic.ts +9 -1
- package/src/lib/ui/dom/appendText.ts +15 -3
- package/src/lib/ui/dom/assertClaimedText.ts +20 -0
- package/src/lib/ui/dom/awaitBlock.ts +17 -56
- package/src/lib/ui/dom/bindSelectValue.ts +48 -0
- package/src/lib/ui/dom/each.ts +12 -1
- package/src/lib/ui/dom/hydrate.ts +10 -0
- package/src/lib/ui/effect.ts +4 -3
- package/src/lib/ui/installHotBridge.ts +4 -2
- package/src/lib/ui/remoteProxy.ts +18 -2
- package/src/lib/ui/renderToStream.ts +9 -13
- package/src/lib/ui/resumeSeedScript.ts +2 -2
- package/src/lib/ui/router.ts +21 -2
- package/src/lib/ui/runtime/RESUME.ts +0 -6
- package/src/lib/ui/runtime/ambientScopeBacking.ts +1 -1
- package/src/lib/ui/runtime/types/SsrRender.ts +3 -4
- package/src/lib/ui/scope.ts +6 -3
- package/src/lib/ui/seedBootState.ts +53 -0
- package/src/lib/ui/socketChannel.ts +2 -2
- package/src/lib/ui/socketProxy.ts +9 -3
- package/src/lib/ui/startClient.ts +16 -30
- package/src/lib/ui/state.ts +44 -13
- package/src/lib/ui/sync.ts +11 -5
- package/src/lib/ui/tryEncodeResume.ts +2 -5
- package/src/lib/ui/types/Scope.ts +14 -10
- package/src/lib/ui/watch.ts +140 -0
- package/src/serverEntry.ts +6 -6
- package/template/CLAUDE.md +1 -1
- package/template/package.json +1 -1
- package/template/src/server/rpc/getHello.ts +3 -3
- package/template/src/ui/pages/page.abide +2 -2
- package/template/test/app.test.ts +1 -1
- package/src/lib/server/reachable.ts +0 -45
- package/src/lib/server/sockets/types/Socket.ts +0 -23
- package/src/lib/shared/CACHE_WRAPPED.ts +0 -9
- package/src/lib/shared/baseResolver.ts +0 -10
- package/src/lib/shared/cacheKeyOf.ts +0 -7
- package/src/lib/shared/cacheKeyStore.ts +0 -8
- package/src/lib/shared/cacheStoreResolver.ts +0 -12
- package/src/lib/shared/globalCacheStoreResolver.ts +0 -12
- package/src/lib/shared/pageResolver.ts +0 -16
- package/src/lib/shared/recordCacheKey.ts +0 -6
- package/src/lib/shared/requestScopeResolver.ts +0 -12
- package/src/lib/shared/setBaseResolver.ts +0 -4
- package/src/lib/shared/setCacheStoreResolver.ts +0 -4
- package/src/lib/shared/setGlobalCacheStoreResolver.ts +0 -4
- package/src/lib/shared/setPageResolver.ts +0 -4
- package/src/lib/shared/setRequestScopeResolver.ts +0 -4
- package/src/lib/shared/toBunRoutePattern.ts +0 -28
- package/src/lib/shared/types/TailOptions.ts +0 -10
- package/src/lib/ui/deferResume.ts +0 -29
- package/src/lib/ui/matchRoute.ts +0 -106
- package/src/lib/ui/tail.ts +0 -324
- /package/src/lib/{server/sockets → shared}/types/SocketClientFrame.ts +0 -0
- /package/src/lib/{server/sockets → shared}/types/SocketServerFrame.ts +0 -0
|
@@ -1,44 +1,58 @@
|
|
|
1
1
|
import ts from 'typescript'
|
|
2
2
|
import { ABIDE_PACKAGE_NAME } from '../../shared/ABIDE_PACKAGE_NAME.ts'
|
|
3
3
|
import { parseTemplate } from './parseTemplate.ts'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
NESTED_REACTIVE_BINDINGS,
|
|
6
|
+
type ReactiveImportBindings,
|
|
7
|
+
reactiveImportBindings,
|
|
8
|
+
resolveReactiveExport,
|
|
9
|
+
} from './resolveReactiveExport.ts'
|
|
10
|
+
import { signalCallee } from './signalCallee.ts'
|
|
5
11
|
import type { CompiledShadow, ShadowDiagnostic, ShadowMapping } from './types/CompiledShadow.ts'
|
|
6
12
|
import type { TemplateNode } from './types/TemplateNode.ts'
|
|
7
13
|
|
|
8
14
|
/*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
Framework callables the `.abide` loader injects into a component's scope. `snippet`
|
|
16
|
+
and `scope` keep their real published types via imports so author calls type-check —
|
|
17
|
+
`scope()` is the internal lowering host (a captured handle `const s = scope()` + its
|
|
18
|
+
capability calls type-check against the real `Scope`; generated code targets it too).
|
|
19
|
+
`state`/`linked`/`computed`/`effect` are the imported reactive surface; when the author
|
|
20
|
+
imports one its own binding provides the type, so the ambient fallback for that name is
|
|
21
|
+
OMITTED (see `shadowPreamble`) to avoid a duplicate identifier. Otherwise
|
|
22
|
+
`state`/`linked`/`computed` are declared ambiently and `effect` is imported as a fallback
|
|
23
|
+
for a bare/nested use the top-level rewrite doesn't project (unused when projected — fine,
|
|
24
|
+
the shadow disables noUnusedLocals). `props` is the prop reader — destructured
|
|
25
|
+
(`const { a = 1 } = props<Shape>()`); declared returning its type argument (default
|
|
26
|
+
`Record<string, any>`) so each binding inherits its prop type and its `= default` narrows.
|
|
14
27
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
function shadowPreamble(importedReactives: ReadonlySet<string>): string {
|
|
29
|
+
/* Omit the ambient fallback for a primitive the author imports — its own binding is
|
|
30
|
+
the type, and a second declaration would be a duplicate-identifier error. */
|
|
31
|
+
const lines = [
|
|
32
|
+
importedReactives.has('effect')
|
|
33
|
+
? undefined
|
|
34
|
+
: `import { effect } from '${ABIDE_PACKAGE_NAME}/ui/effect'`,
|
|
35
|
+
importedReactives.has('watch')
|
|
36
|
+
? undefined
|
|
37
|
+
: `import { watch } from '${ABIDE_PACKAGE_NAME}/ui/watch'`,
|
|
38
|
+
`import { snippet } from '${ABIDE_PACKAGE_NAME}/shared/snippet'`,
|
|
39
|
+
`import { scope } from '${ABIDE_PACKAGE_NAME}/ui/currentScope'`,
|
|
40
|
+
importedReactives.has('state')
|
|
41
|
+
? undefined
|
|
42
|
+
: 'declare function state<T>(initial?: T, transform?: (next: T, previous: T) => T): { value: T }',
|
|
43
|
+
importedReactives.has('linked')
|
|
44
|
+
? undefined
|
|
45
|
+
: 'declare function linked<T>(seed: () => T, transform?: (next: T, previous: T) => T): { value: T }',
|
|
46
|
+
importedReactives.has('computed')
|
|
47
|
+
? undefined
|
|
48
|
+
: 'declare function computed<T>(compute: () => T): { readonly value: T }',
|
|
49
|
+
'declare const children: (() => void) | undefined',
|
|
50
|
+
/* `effect` is defined either way — the preamble import or the author's own import. */
|
|
51
|
+
'void [effect, snippet, scope]',
|
|
52
|
+
]
|
|
53
|
+
return `${lines.filter((line) => line !== undefined).join('\n')}\n`
|
|
18
54
|
}
|
|
19
55
|
|
|
20
|
-
/*
|
|
21
|
-
Framework callables the `.abide` loader injects into a component's scope. `effect`,
|
|
22
|
-
`snippet`, and `scope` keep their real published types via imports so author
|
|
23
|
-
calls type-check — `scope()` is the authored reactive surface (`scope().state(...)` /
|
|
24
|
-
`.computed(...)` / `.undo()` …), so it must resolve like any import. `state`/`linked`/
|
|
25
|
-
`computed` are ALSO declared ambiently as a fallback for the rare bare/nested use the
|
|
26
|
-
top-level rewrite doesn't project (their top-level declarations become value types, so
|
|
27
|
-
these are normally unused — fine, the shadow disables noUnusedLocals). `props` is the prop
|
|
28
|
-
reader — destructured (`const { a = 1 } = props<Shape>()`); declared returning its type
|
|
29
|
-
argument (default `Record<string, any>`) so each binding inherits its prop type and its
|
|
30
|
-
`= default` narrows.
|
|
31
|
-
*/
|
|
32
|
-
const SHADOW_PREAMBLE = `import { effect } from '${ABIDE_PACKAGE_NAME}/ui/effect'
|
|
33
|
-
import { snippet } from '${ABIDE_PACKAGE_NAME}/shared/snippet'
|
|
34
|
-
import { scope } from '${ABIDE_PACKAGE_NAME}/ui/scope'
|
|
35
|
-
declare function state<T>(initial?: T, transform?: (next: T, previous: T) => T): { value: T }
|
|
36
|
-
declare function linked<T>(seed: () => T, transform?: (next: T, previous: T) => T): { value: T }
|
|
37
|
-
declare function computed<T>(compute: () => T): { readonly value: T }
|
|
38
|
-
declare const children: (() => void) | undefined
|
|
39
|
-
void [effect, snippet, scope]
|
|
40
|
-
`
|
|
41
|
-
|
|
42
56
|
/*
|
|
43
57
|
Compiles a `.abide` component into its type-checking shadow — a synthetic TS
|
|
44
58
|
module that reconstructs the author scope with value types and references every
|
|
@@ -62,11 +76,11 @@ export function compileShadow(source: string, propsType = 'Record<string, any>')
|
|
|
62
76
|
const scriptStart = leadingScript ? source.indexOf('>', leadingScript.index) + 1 : 0
|
|
63
77
|
const templateStart = leadingScript ? (leadingScript.index ?? 0) + leadingScript[0].length : 0
|
|
64
78
|
|
|
65
|
-
const { imports, types, scope, propsShapes, diagnostics } = analyzeScript(
|
|
79
|
+
const { imports, types, scope, propsShapes, diagnostics, importedReactives } = analyzeScript(
|
|
66
80
|
scriptBody,
|
|
67
81
|
scriptStart,
|
|
68
82
|
)
|
|
69
|
-
builder.raw(
|
|
83
|
+
builder.raw(shadowPreamble(importedReactives))
|
|
70
84
|
/* `props()` defaults to the file's type — a page/layout's route param shape, else
|
|
71
85
|
`Record<string, any>` — so `const { id } = props()` infers from the route. */
|
|
72
86
|
builder.raw(`declare function props<T = ${propsType}>(): T\n`)
|
|
@@ -80,6 +94,17 @@ export function compileShadow(source: string, propsType = 'Record<string, any>')
|
|
|
80
94
|
for (const line of types) {
|
|
81
95
|
builder.flush(line)
|
|
82
96
|
}
|
|
97
|
+
/* The author's scope (value consts, functions, reactive projections, the `props()`
|
|
98
|
+
destructure) is hoisted to module scope — above `__Props` — for the same reason the
|
|
99
|
+
types are: a prop annotation can then reference a value const by `typeof`
|
|
100
|
+
(`size: keyof typeof sizes`), which fails when the const sits in the function body and
|
|
101
|
+
`__Props` at module scope can't see it. Narrowing is unaffected: a template's guard and
|
|
102
|
+
read are both in the render function below, so control-flow narrowing flows regardless
|
|
103
|
+
of where the binding is declared, and reactive projections are never rebound (writes go
|
|
104
|
+
through `.value`), so they narrow like consts even into nested handlers. */
|
|
105
|
+
for (const line of scope) {
|
|
106
|
+
builder.flush(line)
|
|
107
|
+
}
|
|
83
108
|
/* `__Props` is the parent-facing prop shape: each `props<Shape>()` destructure
|
|
84
109
|
contributes its whole `Shape` (intersected if there's more than one), or an empty
|
|
85
110
|
object for a component that reads no props. */
|
|
@@ -94,9 +119,6 @@ export function compileShadow(source: string, propsType = 'Record<string, any>')
|
|
|
94
119
|
builder.raw('export default async function (__props: __Props): Promise<void> {\n')
|
|
95
120
|
/* Reference props so an all-optional bag with no reads doesn't read as unused. */
|
|
96
121
|
builder.raw('void __props;\n')
|
|
97
|
-
for (const line of scope) {
|
|
98
|
-
builder.flush(line)
|
|
99
|
-
}
|
|
100
122
|
const templateNodes = parseTemplate(source.slice(templateStart), templateStart).nodes
|
|
101
123
|
/* Nested `<script>` blocks inline into the synchronous `build()` too, so a top-level
|
|
102
124
|
await in one is the same build-breaker — flag it, mapped via the node's body offset. */
|
|
@@ -184,6 +206,9 @@ type ScriptAnalysis = {
|
|
|
184
206
|
scope: ScopeLine[]
|
|
185
207
|
propsShapes: string[]
|
|
186
208
|
diagnostics: ShadowDiagnostic[]
|
|
209
|
+
/* The reactive primitives the author imports (`state`/`effect`), so the preamble
|
|
210
|
+
omits the ambient fallback for each and avoids a duplicate-identifier error. */
|
|
211
|
+
importedReactives: Set<string>
|
|
187
212
|
}
|
|
188
213
|
|
|
189
214
|
/* Pushes a diagnostic for every author binding whose name starts with the reserved `$$`
|
|
@@ -296,9 +321,14 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
|
|
|
296
321
|
const propsShapes: string[] = []
|
|
297
322
|
const diagnostics: ShadowDiagnostic[] = []
|
|
298
323
|
if (scriptBody.trim() === '') {
|
|
299
|
-
return { imports, types, scope, propsShapes, diagnostics }
|
|
324
|
+
return { imports, types, scope, propsShapes, diagnostics, importedReactives: new Set() }
|
|
300
325
|
}
|
|
301
326
|
const file = ts.createSourceFile('script.ts', scriptBody, ts.ScriptTarget.Latest, true)
|
|
327
|
+
/* The author's reactive import bindings (alias-safe) — recognition source for
|
|
328
|
+
`state`/`state.linked`/`state.computed`/`effect`, and the set of imported primitives
|
|
329
|
+
the preamble omits its ambient fallback for. */
|
|
330
|
+
const bindings = reactiveImportBindings(file)
|
|
331
|
+
const importedReactives = new Set(bindings.direct.values())
|
|
302
332
|
/* The `$$` prefix is reserved for the compiler's injected runtime (`$$each`, `$$model`,
|
|
303
333
|
`$$scope`, …), so an author binding may not start with it — that's the contract that
|
|
304
334
|
lets a user freely name a variable after any helper. Flag every such declaration. */
|
|
@@ -317,21 +347,9 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
|
|
|
317
347
|
|
|
318
348
|
for (const statement of file.statements) {
|
|
319
349
|
if (ts.isImportDeclaration(statement)) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const banned = specifier !== undefined ? INTERNAL_ONLY_IMPORTS[specifier] : undefined
|
|
324
|
-
if (banned !== undefined) {
|
|
325
|
-
/* Flag and drop it — leaving it out keeps `effect` resolving via the
|
|
326
|
-
preamble (no duplicate-identifier noise on top of our message). */
|
|
327
|
-
diagnostics.push({
|
|
328
|
-
start: scriptStart + statement.getStart(file),
|
|
329
|
-
length: statement.getEnd() - statement.getStart(file),
|
|
330
|
-
message: banned,
|
|
331
|
-
})
|
|
332
|
-
continue
|
|
333
|
-
}
|
|
334
|
-
/* Emit verbatim with a span so hover/go-to resolve on the imported names. */
|
|
350
|
+
/* Emit verbatim with a span so hover/go-to resolve on the imported names.
|
|
351
|
+
`abide/ui/state` / `abide/ui/effect` are ordinary author imports now — the
|
|
352
|
+
preamble drops its ambient fallback for whichever names are imported. */
|
|
335
353
|
imports.push({ text: verbatim(statement), segments: [span(statement, 0)] })
|
|
336
354
|
continue
|
|
337
355
|
}
|
|
@@ -340,17 +358,17 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
|
|
|
340
358
|
types.push({ text: verbatim(statement), segments: [span(statement, 0)] })
|
|
341
359
|
continue
|
|
342
360
|
}
|
|
343
|
-
const reactive = reactiveDeclarations(statement)
|
|
361
|
+
const reactive = reactiveDeclarations(statement, bindings)
|
|
344
362
|
if (reactive === undefined) {
|
|
345
363
|
/* Plain statement (function, const, expression) — emit verbatim, mapped. */
|
|
346
364
|
scope.push({ text: verbatim(statement), segments: [span(statement, 0)] })
|
|
347
365
|
continue
|
|
348
366
|
}
|
|
349
367
|
for (const declaration of reactive) {
|
|
350
|
-
scope.push(scopeLineFor(declaration, propsShapes, verbatim, span))
|
|
368
|
+
scope.push(scopeLineFor(declaration, propsShapes, verbatim, span, bindings))
|
|
351
369
|
}
|
|
352
370
|
}
|
|
353
|
-
return { imports, types, scope, propsShapes, diagnostics }
|
|
371
|
+
return { imports, types, scope, propsShapes, diagnostics, importedReactives }
|
|
354
372
|
}
|
|
355
373
|
|
|
356
374
|
/* Value-projects a nested control-flow `<script>` body the way `analyzeScript`
|
|
@@ -360,16 +378,20 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
|
|
|
360
378
|
reads a nested signal as its value type instead of the raw `State`/`Computed`. */
|
|
361
379
|
function projectNestedScript(code: string): string {
|
|
362
380
|
const file = ts.createSourceFile('nested.ts', code, ts.ScriptTarget.Latest, true)
|
|
381
|
+
/* A nested script can't import — it inherits the module-scope surface by canonical name. */
|
|
382
|
+
const bindings = NESTED_REACTIVE_BINDINGS
|
|
363
383
|
const verbatim = (node: ts.Node): string => code.slice(node.getStart(file), node.getEnd())
|
|
364
384
|
/* No mapping: a zero-length segment the caller drops. */
|
|
365
385
|
const span = (): ShadowMapping => ({ shadowStart: 0, sourceStart: 0, length: 0 })
|
|
366
386
|
return file.statements
|
|
367
387
|
.flatMap((statement) => {
|
|
368
|
-
const reactive = reactiveDeclarations(statement)
|
|
388
|
+
const reactive = reactiveDeclarations(statement, bindings)
|
|
369
389
|
if (reactive === undefined) {
|
|
370
390
|
return [verbatim(statement)]
|
|
371
391
|
}
|
|
372
|
-
return reactive.map(
|
|
392
|
+
return reactive.map(
|
|
393
|
+
(declaration) => scopeLineFor(declaration, [], verbatim, span, bindings).text,
|
|
394
|
+
)
|
|
373
395
|
})
|
|
374
396
|
.join('\n')
|
|
375
397
|
}
|
|
@@ -377,32 +399,20 @@ function projectNestedScript(code: string): string {
|
|
|
377
399
|
/* The `state`/`computed`/`linked`/`props()` declarations in a variable statement, or
|
|
378
400
|
undefined if it isn't one declaring them (so the caller emits it verbatim). A statement
|
|
379
401
|
mixing reactive and plain declarations is rare; treated as all-verbatim. */
|
|
380
|
-
function reactiveDeclarations(
|
|
402
|
+
function reactiveDeclarations(
|
|
403
|
+
statement: ts.Statement,
|
|
404
|
+
bindings: ReactiveImportBindings,
|
|
405
|
+
): ts.VariableDeclaration[] | undefined {
|
|
381
406
|
if (!ts.isVariableStatement(statement)) {
|
|
382
407
|
return undefined
|
|
383
408
|
}
|
|
384
409
|
const declarations = statement.declarationList.declarations
|
|
385
|
-
const reactive = declarations.filter(
|
|
410
|
+
const reactive = declarations.filter(
|
|
411
|
+
(declaration) => signalCallee(declaration, bindings) !== undefined,
|
|
412
|
+
)
|
|
386
413
|
return reactive.length === declarations.length && reactive.length > 0 ? reactive : undefined
|
|
387
414
|
}
|
|
388
415
|
|
|
389
|
-
/* The callee name of a `NAME = state(...)` / `linked(...)` / `computed(...)` /
|
|
390
|
-
`props()` decl — bare or the explicit scope form (`scope().state(...)` / `c.state(...)`),
|
|
391
|
-
receiver-agnostic (the method name marks it reactive). */
|
|
392
|
-
function signalCallee(declaration: ts.VariableDeclaration): string | undefined {
|
|
393
|
-
const initializer = declaration.initializer
|
|
394
|
-
if (initializer === undefined || !ts.isCallExpression(initializer)) {
|
|
395
|
-
return undefined
|
|
396
|
-
}
|
|
397
|
-
const callee = initializer.expression
|
|
398
|
-
const name = ts.isIdentifier(callee)
|
|
399
|
-
? callee.text
|
|
400
|
-
: ts.isPropertyAccessExpression(callee)
|
|
401
|
-
? callee.name.text
|
|
402
|
-
: undefined
|
|
403
|
-
return name !== undefined && REACTIVE_CALLEES.has(name) ? name : undefined
|
|
404
|
-
}
|
|
405
|
-
|
|
406
416
|
/* Builds one scope line for a reactive declaration, projecting it to its value
|
|
407
417
|
type. A `props()` destructure contributes its whole shape (pushed into `propsShapes`). */
|
|
408
418
|
function scopeLineFor(
|
|
@@ -410,13 +420,14 @@ function scopeLineFor(
|
|
|
410
420
|
propsShapes: string[],
|
|
411
421
|
verbatim: (node: ts.Node) => string,
|
|
412
422
|
span: (node: ts.Node, prefixLength: number) => ShadowMapping,
|
|
423
|
+
bindings: ReactiveImportBindings,
|
|
413
424
|
): ScopeLine {
|
|
414
425
|
const name = ts.isIdentifier(declaration.name) ? declaration.name.text : '_'
|
|
415
426
|
const call = declaration.initializer as ts.CallExpression
|
|
416
|
-
/*
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
427
|
+
/* The CANONICAL primitive drives value-projection — resolved purely from the import
|
|
428
|
+
binding (so an aliased `s(0)` / `state.computed(...)` projects correctly).
|
|
429
|
+
`verbatim(call.expression)` below keeps the AUTHOR's text for the trailing hover ref. */
|
|
430
|
+
const callee = resolveReactiveExport(call.expression, bindings)
|
|
420
431
|
if (callee === 'props') {
|
|
421
432
|
/* `const {…} = props<Shape>()`: the type arg (default `Record<string, any>`)
|
|
422
433
|
IS the parent-facing prop shape, and the destructure projects verbatim against
|
|
@@ -737,8 +748,10 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
737
748
|
emitNodes(node.children, builder)
|
|
738
749
|
return
|
|
739
750
|
case 'branch':
|
|
740
|
-
/* Reached
|
|
741
|
-
|
|
751
|
+
/* Reached for a `{#try}`'s `catch`/`finally`: the `try` handler emits its
|
|
752
|
+
children directly (guarded content + these branch nodes), so a `{:catch err}`
|
|
753
|
+
binds its error here as `any` (statically unknowable). The `await`/`if`/`switch`
|
|
754
|
+
handlers consume their own branches inline and never route through this case. */
|
|
742
755
|
builder.raw('{\n')
|
|
743
756
|
if (node.as !== undefined) {
|
|
744
757
|
builder.raw(`const ${node.as} = undefined as any;\n`)
|
|
@@ -759,9 +772,13 @@ function emitNode(node: TemplateNode, builder: Builder): void {
|
|
|
759
772
|
if (node.params !== undefined) {
|
|
760
773
|
builder.mapped(node.params, node.loc)
|
|
761
774
|
}
|
|
762
|
-
|
|
775
|
+
/* Wrap the body in `snippet(() => …)` so the shadow types the snippet as
|
|
776
|
+
`(args) => Snippet<…>` — mirroring the runtime lowering (which returns
|
|
777
|
+
`$$snippet(($host) => …)`) so a snippet passed to a `Snippet`-typed prop
|
|
778
|
+
type-checks instead of reading as `() => void`. */
|
|
779
|
+
builder.raw(') => snippet(() => {\n')
|
|
763
780
|
emitNodes(node.children, builder)
|
|
764
|
-
builder.raw('};\n')
|
|
781
|
+
builder.raw('});\n')
|
|
765
782
|
return
|
|
766
783
|
case 'script':
|
|
767
784
|
/* A scoped reactive `<script>`: value-project its reactive declarations to
|