@abide/abide 0.54.0 → 0.56.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 +330 -498
- package/CHANGELOG.md +63 -0
- package/README.md +166 -146
- package/package.json +2 -3
- package/src/lib/server/rpc/defineRpc.ts +2 -1
- package/src/lib/server/rpc/types/RpcHelper.ts +29 -22
- package/src/lib/server/runtime/SSR_SWAP_SCRIPT.ts +3 -2
- package/src/lib/server/runtime/createUiPageRenderer.ts +34 -26
- package/src/lib/shared/SEEDS.ts +33 -0
- package/src/lib/shared/SOCKET_SEED.ts +4 -5
- package/src/lib/shared/cache.ts +169 -75
- package/src/lib/shared/createRemoteFunction.ts +4 -1
- package/src/lib/shared/encodeSeedValue.ts +24 -0
- package/src/lib/shared/jsonSchemaForType.ts +3 -16
- package/src/lib/shared/peek.ts +3 -6
- package/src/lib/ui/amendReaderHook.ts +1 -1
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +1 -6
- package/src/lib/ui/compile/analyzeComponent.ts +0 -2
- package/src/lib/ui/compile/awaitSubjectExpr.ts +23 -0
- package/src/lib/ui/compile/cachedShadowProgram.ts +2 -1
- package/src/lib/ui/compile/compileComponent.ts +1 -10
- package/src/lib/ui/compile/compileShadow.ts +113 -8
- package/src/lib/ui/compile/componentOrdinals.ts +39 -0
- package/src/lib/ui/compile/desugarSignals.ts +195 -44
- package/src/lib/ui/compile/generateBuild.ts +25 -24
- package/src/lib/ui/compile/generateSSR.ts +37 -24
- package/src/lib/ui/compile/hoistableAwaits.ts +1 -11
- package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +46 -5
- package/src/lib/ui/compile/liftScriptSeed.ts +47 -0
- package/src/lib/ui/compile/lowerContext.ts +26 -6
- package/src/lib/ui/compile/lowerScript.ts +1 -11
- package/src/lib/ui/compile/renameSignalRefs.ts +7 -21
- package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +1 -1
- package/src/lib/ui/compile/shadowInterpolationClassifier.ts +1 -1
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +3 -5
- package/src/lib/ui/compile/wrapReactiveSeeds.ts +44 -0
- package/src/lib/ui/compile/wrapSeed.ts +28 -0
- package/src/lib/ui/computed.ts +16 -0
- package/src/lib/ui/createScope.ts +4 -3
- package/src/lib/ui/dom/anchorCursor.ts +2 -1
- package/src/lib/ui/dom/anchoredBranch.ts +14 -3
- package/src/lib/ui/dom/appendStatic.ts +11 -20
- package/src/lib/ui/dom/appendText.ts +41 -43
- package/src/lib/ui/dom/appendTextAt.ts +10 -57
- package/src/lib/ui/dom/awaitBlock.ts +30 -44
- package/src/lib/ui/dom/awaitSubject.ts +118 -0
- package/src/lib/ui/dom/claimText.ts +38 -0
- package/src/lib/ui/dom/cloneStatic.ts +2 -8
- package/src/lib/ui/dom/discardAndRebuild.ts +23 -0
- package/src/lib/ui/dom/discardBoundary.ts +5 -4
- package/src/lib/ui/dom/each.ts +3 -5
- package/src/lib/ui/dom/fillBoundary.ts +2 -1
- package/src/lib/ui/dom/hydrate.ts +5 -12
- package/src/lib/ui/dom/mountChild.ts +73 -5
- package/src/lib/ui/dom/mountSwappableRange.ts +2 -1
- package/src/lib/ui/dom/openMarker.ts +2 -4
- package/src/lib/ui/dom/outlet.ts +4 -3
- package/src/lib/ui/dom/readCell.ts +26 -11
- package/src/lib/ui/dom/skeleton.ts +2 -7
- package/src/lib/ui/dom/switchBlock.ts +2 -1
- package/src/lib/ui/dom/tryBlock.ts +26 -38
- package/src/lib/ui/dom/when.ts +6 -2
- package/src/lib/ui/linked.ts +32 -1
- package/src/lib/ui/resumeSeedScript.ts +1 -1
- package/src/lib/ui/router.ts +12 -24
- package/src/lib/ui/runtime/AsyncCellError.ts +2 -1
- package/src/lib/ui/runtime/BRANCH_SEGMENT.ts +24 -0
- package/src/lib/ui/runtime/CELL_SEED.ts +4 -6
- package/src/lib/ui/runtime/DOC_SEED.ts +5 -6
- package/src/lib/ui/runtime/RENDER.ts +7 -5
- package/src/lib/ui/runtime/RESUME.ts +5 -7
- package/src/lib/ui/runtime/SEED_MARKS.ts +14 -0
- package/src/lib/ui/runtime/SuspenseSignal.ts +2 -2
- package/src/lib/ui/runtime/claimChild.ts +5 -7
- package/src/lib/ui/runtime/claimExpected.ts +2 -6
- package/src/lib/ui/runtime/claimMarker.ts +13 -0
- package/src/lib/ui/runtime/claimRun.ts +22 -0
- package/src/lib/ui/runtime/consumeSeed.ts +38 -0
- package/src/lib/ui/runtime/createAsyncCell.ts +66 -8
- package/src/lib/ui/runtime/createComputedNode.ts +1 -0
- package/src/lib/ui/runtime/createEffectNode.ts +1 -0
- package/src/lib/ui/runtime/createSignalNode.ts +1 -0
- package/src/lib/ui/runtime/parkCursor.ts +10 -0
- package/src/lib/ui/runtime/readNode.ts +21 -1
- package/src/lib/ui/runtime/renderPath.ts +2 -2
- package/src/lib/ui/runtime/routeBareSeed.ts +28 -0
- package/src/lib/ui/runtime/runHydrationPass.ts +69 -0
- package/src/lib/ui/runtime/runNode.ts +14 -0
- package/src/lib/ui/runtime/types/HydrationCursor.ts +8 -0
- package/src/lib/ui/runtime/types/ReactiveNode.ts +5 -0
- package/src/lib/ui/settleAsyncCells.ts +30 -3
- package/src/lib/ui/startClient.ts +0 -7
- package/src/lib/ui/tryEncodeResume.ts +6 -15
- package/src/lib/ui/watch.ts +3 -11
- package/src/lib/ui/dom/mountStreamedChild.ts +0 -84
- package/src/lib/ui/dom/readCellBlocking.ts +0 -29
- package/src/lib/ui/runtime/restoreWarmSeeds.ts +0 -20
- package/src/lib/ui/runtime/warmSeedBackup.ts +0 -10
|
@@ -4,12 +4,13 @@ import { OUTLET_TAG } from '../runtime/OUTLET_TAG.ts'
|
|
|
4
4
|
import { ANCHOR } from '../runtime/RANGE_MARKER.ts'
|
|
5
5
|
import { asOutlet } from './asOutlet.ts'
|
|
6
6
|
import { awaitPlan } from './awaitPlan.ts'
|
|
7
|
+
import { awaitSubjectExpr } from './awaitSubjectExpr.ts'
|
|
7
8
|
import { bindListenEvent } from './bindListenEvent.ts'
|
|
9
|
+
import { componentOrdinals } from './componentOrdinals.ts'
|
|
8
10
|
import { composeProps } from './composeProps.ts'
|
|
9
11
|
import { eachPlan } from './eachPlan.ts'
|
|
10
12
|
import { elementPlan } from './elementPlan.ts'
|
|
11
13
|
import { groupBindParts } from './groupBindParts.ts'
|
|
12
|
-
import { hoistableChildRenders } from './hoistableChildRenders.ts'
|
|
13
14
|
import { ifPlan } from './ifPlan.ts'
|
|
14
15
|
import { interpolatedTemplateLiteral } from './interpolatedTemplateLiteral.ts'
|
|
15
16
|
import { isControlFlow } from './isControlFlow.ts'
|
|
@@ -75,23 +76,12 @@ export function generateBuild(
|
|
|
75
76
|
derivedNames: ReadonlySet<string>,
|
|
76
77
|
computedNames: ReadonlySet<string>,
|
|
77
78
|
isLayout = false,
|
|
78
|
-
/* `linked` / async `computed` names, lowered to `$$readCell(name)` in template exprs
|
|
79
|
+
/* `linked` / async `computed` names, lowered to `$$readCell(name)` in template exprs — one
|
|
80
|
+
read form whose pause-vs-peek (a blocking `await` cell suspends the region; a streaming
|
|
81
|
+
cell peeks `undefined`) is decided at runtime by the cell's own `blocking` bit (ADR-0042). */
|
|
79
82
|
cellReadNames: ReadonlySet<string> = new Set(),
|
|
80
|
-
/* The subset that are BLOCKING `await` cells (ADR-0042), lowered to `$$readCellBlocking(name)`
|
|
81
|
-
on the CLIENT so a pending read suspends its render region. Server keeps `$$readCell` (the
|
|
82
|
-
SSR barrier resolves blocking cells before the template, so the read never suspends). */
|
|
83
|
-
blockingCellNames: ReadonlySet<string> = new Set(),
|
|
84
83
|
): string {
|
|
85
84
|
const nextVar = makeVarNamer()
|
|
86
|
-
/* A per-body source-order ordinal for each `<Child/>` mount site, so two same-type siblings
|
|
87
|
-
get distinct render-path segments. Combined with the ambient branch/row path a control-flow
|
|
88
|
-
ancestor pushes, this makes every mounted instance's id unique and stable (ADR render-path
|
|
89
|
-
identity). Drawn in document-order walk, matching the SSR back-end's identical walk. */
|
|
90
|
-
let childOrdinal = 0
|
|
91
|
-
/* ADR-0039: the SAME hoistable-child set the SSR back-end computes (identical inputs → identical
|
|
92
|
-
node identities), so the client emits the dual-mode `$$mountStreamedChild` for exactly the
|
|
93
|
-
children the server may stream. A non-hoistable child keeps the plain `$$mountChild`. */
|
|
94
|
-
const streamCapable = hoistableChildRenders(nodes, cellReadNames)
|
|
95
85
|
|
|
96
86
|
/* In a layout, `<slot/>` outlets are rewritten to `OUTLET_TAG` elements up front
|
|
97
87
|
(`asOutlet`) so the static-clone path carries them as ordinary structure. `asOutlet`
|
|
@@ -105,6 +95,12 @@ export function generateBuild(
|
|
|
105
95
|
position structurally (the drift the shared context exists to prevent). */
|
|
106
96
|
const { markText, elIndex, anIndex } = skeletonContext(rootNodes)
|
|
107
97
|
|
|
98
|
+
/* Each `<Child/>` mount site's render-path ordinal, from the ONE shared document-order
|
|
99
|
+
walk the SSR back-end also reads (`componentOrdinals`) — so two same-type siblings get
|
|
100
|
+
distinct, SSR-congruent segments regardless of either back-end's emission order. The
|
|
101
|
+
returned lookup throws on a miss (a structural divergence from the shared walk). */
|
|
102
|
+
const childOrdinal = componentOrdinals(rootNodes)
|
|
103
|
+
|
|
108
104
|
/* The hole's index, assigned by the shared skeletonContext walk — the sole numberer. A
|
|
109
105
|
missing entry means this back-end reached a hole the shared walk didn't number: a
|
|
110
106
|
structural divergence between the two, surfaced loudly at compile time rather than as a
|
|
@@ -121,11 +117,13 @@ export function generateBuild(
|
|
|
121
117
|
const {
|
|
122
118
|
expression: lowerExpression,
|
|
123
119
|
statement: lowerStatement,
|
|
120
|
+
script: lowerScript,
|
|
124
121
|
withNestedScripts,
|
|
125
122
|
withShadow,
|
|
123
|
+
isShadowed,
|
|
126
124
|
bindRead,
|
|
127
125
|
bindWrite,
|
|
128
|
-
} = lowerContext(stateNames, derivedNames, computedNames, cellReadNames
|
|
126
|
+
} = lowerContext(stateNames, derivedNames, computedNames, cellReadNames)
|
|
129
127
|
|
|
130
128
|
/* Maps a plan `Binding`'s classification to the client `ShadowKind`: a `reactive` value
|
|
131
129
|
derefs as a `.value` cell (`derived`), a `plain` value as the bare local (`plain`). The
|
|
@@ -285,7 +283,7 @@ export function generateBuild(
|
|
|
285
283
|
runs as a bind, in document order, so its signals are declared before the later
|
|
286
284
|
siblings that deref them (the enclosing `withNestedScripts` puts those names in
|
|
287
285
|
scope). */
|
|
288
|
-
binds.push(`${
|
|
286
|
+
binds.push(`${lowerScript(node.code)}\n`)
|
|
289
287
|
return ''
|
|
290
288
|
}
|
|
291
289
|
if (node.kind === 'snippet') {
|
|
@@ -419,7 +417,7 @@ export function generateBuild(
|
|
|
419
417
|
/* Emits code appending `node` to `parentVar`. */
|
|
420
418
|
function generateChild(node: TemplateNode, parentVar: string, before = 'null'): string {
|
|
421
419
|
if (node.kind === 'script') {
|
|
422
|
-
return `${
|
|
420
|
+
return `${lowerScript(node.code)}\n`
|
|
423
421
|
}
|
|
424
422
|
/* A `<style>` emits no DOM — its CSS is bundled and its scope attribute is
|
|
425
423
|
already stamped onto the elements it covers (see `staticHtml`/`skeletonMarkup`). */
|
|
@@ -616,11 +614,11 @@ export function generateBuild(
|
|
|
616
614
|
cell, so `<Icon>` from `{#for {icon: Icon} of …}` mounts the component the cell
|
|
617
615
|
holds, not the cell object (whose `.build` is undefined → `build is not a
|
|
618
616
|
function`). SSR emits the same lowering for congruence. */
|
|
619
|
-
/*
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
return
|
|
617
|
+
/* `mountChild` is dual-mode (ADR-0039): on hydrate it probes the cursor to adopt an
|
|
618
|
+
inlined `[ … ]` range OR a streamed `abide:await` boundary, so the client build
|
|
619
|
+
needs no hoistable-child knowledge — inline-vs-stream is decided server-side per
|
|
620
|
+
render, and the adopter reads which happened off the markup. */
|
|
621
|
+
return `$$mountChild(${parentVar}, ${lowerExpression(node.name)}, ${propsArg(node)}, ${before}, ${childOrdinal(node)});\n`
|
|
624
622
|
}
|
|
625
623
|
|
|
626
624
|
/* An await block: pending → resolved(value) / error branches. Each branch is a
|
|
@@ -649,8 +647,11 @@ export function generateBuild(
|
|
|
649
647
|
const pendingThunk = hasRenderableContent(plan.pending)
|
|
650
648
|
? branchThunk(plan.pending)
|
|
651
649
|
: 'undefined'
|
|
650
|
+
/* The subject lowers through the ONE shared ADR-0047 decision site (`awaitSubjectExpr`),
|
|
651
|
+
so the bare-cell classification can't drift from the SSR back-end's. */
|
|
652
|
+
const subject = awaitSubjectExpr(node.promise, cellReadNames, isShadowed, lowerExpression)
|
|
652
653
|
return (
|
|
653
|
-
`$$awaitBlock(${parentVar}, $$nextBlockId(), () => (${
|
|
654
|
+
`$$awaitBlock(${parentVar}, $$nextBlockId(), () => (${subject}), ` +
|
|
654
655
|
`${pendingThunk}, ` +
|
|
655
656
|
`${thenThunk}, ` +
|
|
656
657
|
`${catchThunk}, ${before});\n`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assertExhaustive } from '../../shared/assertExhaustive.ts'
|
|
2
2
|
import { escapeRegex } from '../../shared/escapeRegex.ts'
|
|
3
|
+
import { caseSegment, ELSE_SEGMENT, THEN_SEGMENT } from '../runtime/BRANCH_SEGMENT.ts'
|
|
3
4
|
import { OUTLET_CLOSE, OUTLET_OPEN } from '../runtime/OUTLET_MARKER.ts'
|
|
4
5
|
import { OUTLET_TAG } from '../runtime/OUTLET_TAG.ts'
|
|
5
6
|
import {
|
|
@@ -9,6 +10,8 @@ import {
|
|
|
9
10
|
} from '../runtime/RANGE_MARKER.ts'
|
|
10
11
|
import { asOutlet } from './asOutlet.ts'
|
|
11
12
|
import { awaitPlan } from './awaitPlan.ts'
|
|
13
|
+
import { awaitSubjectExpr } from './awaitSubjectExpr.ts'
|
|
14
|
+
import { componentOrdinals } from './componentOrdinals.ts'
|
|
12
15
|
import { composeProps } from './composeProps.ts'
|
|
13
16
|
import { eachPlan } from './eachPlan.ts'
|
|
14
17
|
import { elementPlan } from './elementPlan.ts'
|
|
@@ -74,10 +77,6 @@ export function generateSSR(
|
|
|
74
77
|
/* Unique temp var names (child render results); runtime block ids are
|
|
75
78
|
allocated separately at runtime via `$ctx.next++`. */
|
|
76
79
|
const nextVar = makeVarNamer()
|
|
77
|
-
/* A per-body source-order ordinal for each `<Child/>` render site — the render-path segment the
|
|
78
|
-
child roots under, matching the client `mountChild`'s `childOrdinal` (`generateBuild`) drawn
|
|
79
|
-
in the identical document-order walk, so both compose the same cell scope ids. */
|
|
80
|
-
let childOrdinal = 0
|
|
81
80
|
|
|
82
81
|
/* The enclosing `<select bind:value>`s, innermost last: each carries the JS var holding
|
|
83
82
|
its bound value and whether it's a `multiple` (array) select, so an `<option>` rendered
|
|
@@ -88,9 +87,10 @@ export function generateSSR(
|
|
|
88
87
|
/* The shared signal→`model` lowering + branch-scoped nested-script deref scope. */
|
|
89
88
|
const {
|
|
90
89
|
expression: lowerExpression,
|
|
91
|
-
|
|
90
|
+
script,
|
|
92
91
|
withNestedScripts,
|
|
93
92
|
withShadow,
|
|
93
|
+
isShadowed,
|
|
94
94
|
bindRead,
|
|
95
95
|
bindWrite,
|
|
96
96
|
} = lowerContext(stateNames, derivedNames, computedNames, cellReadNames)
|
|
@@ -102,7 +102,7 @@ export function generateSSR(
|
|
|
102
102
|
|
|
103
103
|
/* A scoped-script body for SSR: the shared lowering, then strip effects
|
|
104
104
|
(client-only lifecycle that emits no HTML) — the one SSR-side asymmetry. */
|
|
105
|
-
const lowerScript = (code: string): string => stripEffects(
|
|
105
|
+
const lowerScript = (code: string): string => stripEffects(script(code))
|
|
106
106
|
|
|
107
107
|
function push(target: string, literal: string): string {
|
|
108
108
|
return `${target}.push(${JSON.stringify(literal)});\n`
|
|
@@ -159,6 +159,13 @@ export function generateSSR(
|
|
|
159
159
|
through the generic element path exactly as the client clones it. */
|
|
160
160
|
const rootNodes = isLayout ? nodes.map(asOutlet) : nodes
|
|
161
161
|
|
|
162
|
+
/* Each `<Child/>` render site's render-path ordinal, from the ONE shared document-order
|
|
163
|
+
walk the client back-end also reads (`componentOrdinals`) — so both compose the same
|
|
164
|
+
cell scope ids and streamed-boundary ids regardless of either back-end's emission
|
|
165
|
+
order. The returned lookup throws on a miss (a structural divergence from the shared
|
|
166
|
+
walk), surfaced loudly at compile time. */
|
|
167
|
+
const childOrdinal = componentOrdinals(rootNodes)
|
|
168
|
+
|
|
162
169
|
/* ADR-0034: the await blocks whose promise-start hoists to the synchronous render prefix so
|
|
163
170
|
independent flights overlap instead of serializing. Server-only — this rewires only the SSR
|
|
164
171
|
promise SOURCE (a blocking `await $flightN`, a streaming `promise: () => $flightN`); the
|
|
@@ -362,10 +369,10 @@ export function generateSSR(
|
|
|
362
369
|
then the `$$readCell` truthy test — so sync and async branches interleave and each
|
|
363
370
|
resolves at render time, mirroring the client `switchBlock` cond-chain. */
|
|
364
371
|
const plan = ifPlan(node)
|
|
365
|
-
/* The client build lowers a simple `if`/`else` through `when`
|
|
366
|
-
`
|
|
367
|
-
|
|
368
|
-
`<Child/>`'s scope id composes identically. */
|
|
372
|
+
/* The client build lowers a simple `if`/`else` through `when` and an `if` with any
|
|
373
|
+
`elseif` through `switchBlock` over `[then, ...branches]`. Segments come from the
|
|
374
|
+
SAME alphabet those runtimes key their branches with (`BRANCH_SEGMENT`), so a
|
|
375
|
+
nested `<Child/>`'s scope id composes identically — no literal to drift. */
|
|
369
376
|
const clauses: string[] = []
|
|
370
377
|
const conditional = (
|
|
371
378
|
condition: string,
|
|
@@ -385,12 +392,12 @@ export function generateSSR(
|
|
|
385
392
|
)
|
|
386
393
|
}
|
|
387
394
|
}
|
|
388
|
-
/* `then` is the `when`
|
|
395
|
+
/* `then` is the `when` then-branch, or the `switchBlock` case at index 0. */
|
|
389
396
|
conditional(
|
|
390
397
|
node.condition,
|
|
391
398
|
node.asyncSubject,
|
|
392
399
|
plan.thenChildren,
|
|
393
|
-
JSON.stringify(plan.hasElseif ?
|
|
400
|
+
JSON.stringify(plan.hasElseif ? caseSegment(0) : THEN_SEGMENT),
|
|
394
401
|
)
|
|
395
402
|
for (let index = 0; index < plan.branches.length; index += 1) {
|
|
396
403
|
const branch = plan.branches[index] as Extract<TemplateNode, { kind: 'case' }>
|
|
@@ -401,7 +408,7 @@ export function generateSSR(
|
|
|
401
408
|
branch.condition,
|
|
402
409
|
branch.asyncSubject,
|
|
403
410
|
branch.children,
|
|
404
|
-
JSON.stringify(
|
|
411
|
+
JSON.stringify(caseSegment(index + 1)),
|
|
405
412
|
)
|
|
406
413
|
}
|
|
407
414
|
}
|
|
@@ -409,11 +416,11 @@ export function generateSSR(
|
|
|
409
416
|
.map((clause, index) => `${index === 0 ? 'if' : ' else if'} ${clause}`)
|
|
410
417
|
.join('')
|
|
411
418
|
if (plan.elseBranch !== undefined) {
|
|
412
|
-
/* `else` is the `when`
|
|
419
|
+
/* `else` is the `when` else-branch, or (for a cond-chain) the `switchBlock`
|
|
413
420
|
default at its own `[then, ...branches]` index. */
|
|
414
421
|
const elseSegment = plan.hasElseif
|
|
415
|
-
?
|
|
416
|
-
:
|
|
422
|
+
? caseSegment(plan.branches.indexOf(plan.elseBranch) + 1)
|
|
423
|
+
: ELSE_SEGMENT
|
|
417
424
|
code += ` else {\n${withPathBranch(JSON.stringify(elseSegment), plan.elseBranch.children, target)}}`
|
|
418
425
|
}
|
|
419
426
|
return `${anchor}${openRange(target)}${code}\n${closeRange(target)}`
|
|
@@ -436,12 +443,12 @@ export function generateSSR(
|
|
|
436
443
|
for (let index = 0; index < plan.cases.length; index += 1) {
|
|
437
444
|
const branch = plan.cases[index] as Extract<TemplateNode, { kind: 'case' }>
|
|
438
445
|
if (branch.match !== undefined) {
|
|
439
|
-
code += `${started ? 'else ' : ''}if ($s === (${lowerExpression(branch.match)})) {\n${withPathBranch(JSON.stringify(
|
|
446
|
+
code += `${started ? 'else ' : ''}if ($s === (${lowerExpression(branch.match)})) {\n${withPathBranch(JSON.stringify(caseSegment(index)), branch.children, target)}}\n`
|
|
440
447
|
started = true
|
|
441
448
|
}
|
|
442
449
|
}
|
|
443
450
|
if (plan.fallback !== undefined) {
|
|
444
|
-
const fallbackSegment =
|
|
451
|
+
const fallbackSegment = caseSegment(plan.cases.indexOf(plan.fallback))
|
|
445
452
|
code += `${started ? 'else ' : ''}{\n${withPathBranch(JSON.stringify(fallbackSegment), plan.fallback.children, target)}}\n`
|
|
446
453
|
}
|
|
447
454
|
if (node.asyncSubject === true) {
|
|
@@ -583,10 +590,10 @@ export function generateSSR(
|
|
|
583
590
|
/* The tag lowers like any reference (see generateBuild): a static import is left bare, a
|
|
584
591
|
reactive/loop/await binding derefs — SSR registers such a binding as `plain`, so it
|
|
585
592
|
reads the bare local holding the resolved component, keeping SSR and client congruent.
|
|
586
|
-
Root the child's render-path at this mount site's
|
|
593
|
+
Root the child's render-path at this mount site's shared-walk ordinal — the same
|
|
587
594
|
segment the client's `mountChild` pushes — so the child's cells and block ids (ADR-0037)
|
|
588
595
|
get an id matching the client's. `$$withPath` sets the path across the child's awaits. */
|
|
589
|
-
const ordinal = childOrdinal
|
|
596
|
+
const ordinal = childOrdinal(node)
|
|
590
597
|
const renderExpr = `$$withPath(${ordinal}, () => ${lowerExpression(node.name)}.render(${propsExpr}, $ctx))`
|
|
591
598
|
if (hoistableChildSet.has(node)) {
|
|
592
599
|
/* Hoistable (ADR-0037/0039): start the render as an isolated flight in the prefix, and
|
|
@@ -595,7 +602,7 @@ export function generateSSR(
|
|
|
595
602
|
flight already settled (byte-identical to the old inline await), or a streaming
|
|
596
603
|
`abide:await:CHILDPATH` boundary + html-only await if it is still pending. The
|
|
597
604
|
boundary id is the child's render-path (`$$renderPath(ordinal)`), congruent with the
|
|
598
|
-
client's `
|
|
605
|
+
client's dual-mode `mountChild`. */
|
|
599
606
|
const flightName = `$cf${childFlightCounter++}`
|
|
600
607
|
childFlightDecls.push(
|
|
601
608
|
`const ${flightName} = $$flight(() => $$isolateCellBarrier(() => ${renderExpr}));`,
|
|
@@ -787,6 +794,12 @@ export function generateSSR(
|
|
|
787
794
|
: generateStreamingAwait(node, target)
|
|
788
795
|
}
|
|
789
796
|
|
|
797
|
+
/* The lowered `{#await X}` subject (ADR-0047) — the ONE shared decision site, so the
|
|
798
|
+
bare-cell classification can't drift from the client back-end's. Used by the
|
|
799
|
+
blocking, streaming, and flight-hoist emit sites. */
|
|
800
|
+
const subjectExprFor = (promiseCode: string): string =>
|
|
801
|
+
awaitSubjectExpr(promiseCode, cellReadNames, isShadowed, lowerExpression)
|
|
802
|
+
|
|
790
803
|
/*
|
|
791
804
|
A blocking await — `await`ed at its structural position in the async render pass and
|
|
792
805
|
its resolved branch rendered INLINE into `target`, between the boundary markers. This
|
|
@@ -819,7 +832,7 @@ export function generateSSR(
|
|
|
819
832
|
let code = `const ${id} = $$blockId($ctx);\n`
|
|
820
833
|
code += `${target}.push("<!--abide:await:" + ${id} + "-->");\n`
|
|
821
834
|
code += `try {\n`
|
|
822
|
-
code += `const ${resolved} = await (${hoistedPromise ??
|
|
835
|
+
code += `const ${resolved} = await (${hoistedPromise ?? subjectExprFor(node.promise)});\n`
|
|
823
836
|
code += `{\n`
|
|
824
837
|
code += `const ${plan.resolvedAs} = ${resolved};\n`
|
|
825
838
|
code += withBindings(withShadow, plan.resolvedBindings, ssrBindingKind, () =>
|
|
@@ -891,7 +904,7 @@ export function generateSSR(
|
|
|
891
904
|
const hoistedPromise = flightNameByNode.get(node)
|
|
892
905
|
code +=
|
|
893
906
|
`$awaits.push({ id: ${id}, ` +
|
|
894
|
-
`promise: () => (${hoistedPromise ??
|
|
907
|
+
`promise: () => (${hoistedPromise ?? subjectExprFor(node.promise)}), ` +
|
|
895
908
|
`then: ${settled(plan.resolvedAs, plan.resolvedBindings, plan.resolvedChildren)}, ` +
|
|
896
909
|
`${catchProp}});\n`
|
|
897
910
|
return code
|
|
@@ -939,7 +952,7 @@ export function generateSSR(
|
|
|
939
952
|
const body = generateInto(rootNodes, '$out')
|
|
940
953
|
let flightDecls = ''
|
|
941
954
|
for (const [node, name] of flightNameByNode) {
|
|
942
|
-
flightDecls += `const ${name} = $$flight(() => (${
|
|
955
|
+
flightDecls += `const ${name} = $$flight(() => (${subjectExprFor(node.promise)}));\n`
|
|
943
956
|
}
|
|
944
957
|
/* Child-render flights emitted during the body walk above (ADR-0037 Phase 2) — appended after
|
|
945
958
|
the await flights; both are independent prefix promise-starts, so order between them is free. */
|
|
@@ -78,7 +78,7 @@ function walkNode(
|
|
|
78
78
|
if (
|
|
79
79
|
!conditional &&
|
|
80
80
|
!multiRow &&
|
|
81
|
-
|
|
81
|
+
expressionIsPrefixEvaluable(node.promise, binders, cellReadNames)
|
|
82
82
|
) {
|
|
83
83
|
flights.push({ node, name: `$flight${counter.next++}` })
|
|
84
84
|
}
|
|
@@ -152,16 +152,6 @@ function walkNode(
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
/* True when EVERY referenced identifier of `promise` is prefix-evaluable: none is a template-local
|
|
156
|
-
binder and none is an async-cell name. Delegates to the shared `expressionIsPrefixEvaluable`. */
|
|
157
|
-
function promiseIsPrefixEvaluable(
|
|
158
|
-
promise: string,
|
|
159
|
-
binders: ReadonlySet<string>,
|
|
160
|
-
cellReadNames: ReadonlySet<string>,
|
|
161
|
-
): boolean {
|
|
162
|
-
return expressionIsPrefixEvaluable(promise, binders, cellReadNames)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
155
|
/* A single-element array literal like `[remountKey]` — the only `{#for}` source that renders its
|
|
166
156
|
body exactly once, so a hoisted flight inside it maps to one row (no shared-flight hazard). */
|
|
167
157
|
function isSingleElementLiteral(items: string): boolean {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import ts from 'typescript'
|
|
2
2
|
import { classifyInterpolationType } from './classifyInterpolationType.ts'
|
|
3
3
|
|
|
4
|
-
/* The
|
|
4
|
+
/* The three TypeScript diagnostics a bare async read (ADR-0032) raises spuriously in a template.
|
|
5
5
|
2339 — a property "missing" on the un-awaited `Promise`/`AsyncIterable`; 2801 — a condition the
|
|
6
|
-
raw promise makes "always defined"
|
|
7
|
-
|
|
6
|
+
raw promise makes "always defined"; 2488 — a promise source "not iterable" in a plain `{#for}`.
|
|
7
|
+
All are correct against the raw type but wrong against the runtime PEEK (the resolved value,
|
|
8
|
+
`undefined` while pending), which the shadow can't see. */
|
|
8
9
|
const PROPERTY_DOES_NOT_EXIST = 2339
|
|
9
10
|
const CONDITION_ALWAYS_DEFINED = 2801
|
|
11
|
+
const SOURCE_NOT_ITERABLE = 2488
|
|
10
12
|
|
|
11
13
|
/*
|
|
12
14
|
Whether a shadow diagnostic is one the ADR-0032 bare-async-read peek makes spurious. The runtime
|
|
@@ -20,11 +22,16 @@ caller can drop them:
|
|
|
20
22
|
property EXISTS on the awaited (promise) or frame (iterable) type — the peek's real member.
|
|
21
23
|
- 2801 (always-defined condition): the flagged subject itself classifies as a promise/iterable —
|
|
22
24
|
the peek is genuinely nullable (pending → `undefined`), so it is not always-defined.
|
|
25
|
+
- 2488 (source not iterable): a plain `{#for}` source classifies as a promise whose AWAITED type
|
|
26
|
+
is itself iterable — the runtime lifts it to a peek-cell reading the resolved collection (empty
|
|
27
|
+
while pending), so iterating it is valid (matches `typeDirectedInterpolation`'s peek-cell lift).
|
|
23
28
|
|
|
24
|
-
Deliberately narrow so it never hides a real mistake: only these
|
|
29
|
+
Deliberately narrow so it never hides a real mistake: only these three codes; 2339 only via `?.`
|
|
25
30
|
(a bare `.name` on a promise stays an error, nudging toward the `?.` the peek needs) and only when
|
|
26
31
|
the property resolves on the awaited/frame type (a typo `?.namex` stays); 2801 only when the
|
|
27
|
-
subject is actually async (a bare `{#if someFn}` — a forgotten call — stays)
|
|
32
|
+
subject is actually async (a bare `{#if someFn}` — a forgotten call — stays); 2488 only for a
|
|
33
|
+
PROMISE whose awaited type is iterable (a raw `AsyncIterable` source stays the sanctioned D4a error,
|
|
34
|
+
and a `Promise<number>` source stays a real mistake the peek can't rescue). Callers additionally
|
|
28
35
|
gate on the diagnostic mapping into the template region, so `<script>` code (where a forgotten
|
|
29
36
|
`await` must surface) is never touched.
|
|
30
37
|
*/
|
|
@@ -41,6 +48,40 @@ export function isSpuriousAsyncReadDiagnostic(
|
|
|
41
48
|
if (code === CONDITION_ALWAYS_DEFINED) {
|
|
42
49
|
return isAsyncSubject(shadowFile, checker, start, length)
|
|
43
50
|
}
|
|
51
|
+
if (code === SOURCE_NOT_ITERABLE) {
|
|
52
|
+
return isResolvedIterableAsyncSource(shadowFile, checker, start, length)
|
|
53
|
+
}
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* A 2488 on a plain `{#for}` source whose expression classifies as a PROMISE and whose awaited
|
|
58
|
+
type is itself iterable — the ADR-0032 peek lifts it to the resolved collection, not an error.
|
|
59
|
+
Excludes a raw `AsyncIterable` (kind `asyncIterable`, the D4a error) and a promise of a
|
|
60
|
+
non-iterable (the peek would still not iterate). */
|
|
61
|
+
function isResolvedIterableAsyncSource(
|
|
62
|
+
shadowFile: ts.SourceFile,
|
|
63
|
+
checker: ts.TypeChecker,
|
|
64
|
+
start: number,
|
|
65
|
+
length: number,
|
|
66
|
+
): boolean {
|
|
67
|
+
const source = deepestNodeAt(shadowFile, start, length)
|
|
68
|
+
const sourceType = checker.getTypeAtLocation(source)
|
|
69
|
+
if (classifyInterpolationType(sourceType, source, checker) !== 'promise') {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
const awaited = checker.getAwaitedType(sourceType)
|
|
73
|
+
return awaited !== undefined && hasSyncIterator(awaited, checker)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Whether a type is synchronously iterable — an array/tuple or anything carrying the well-known
|
|
77
|
+
`Symbol.iterator` member (mangled to `__@iterator@<id>`, matched by prefix like
|
|
78
|
+
`classifyInterpolationType`'s async twin). */
|
|
79
|
+
function hasSyncIterator(type: ts.Type, checker: ts.TypeChecker): boolean {
|
|
80
|
+
for (const property of checker.getPropertiesOfType(type)) {
|
|
81
|
+
if ((property.escapedName as string).startsWith('__@iterator')) {
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
}
|
|
44
85
|
return false
|
|
45
86
|
}
|
|
46
87
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { liftAsyncSubExpressions } from './liftAsyncSubExpressions.ts'
|
|
2
|
+
import type { InjectedCell } from './lowerAsyncInterpolations.ts'
|
|
3
|
+
import type { InterpolationClassifier } from './types/InterpolationClassifier.ts'
|
|
4
|
+
|
|
5
|
+
/* The pieces a script-seed lift yields: the async sub-expressions hoisted out of a
|
|
6
|
+
`state`/`linked`/`computed` seed argument (each an injected peek-cell) plus the seed
|
|
7
|
+
rewritten to reference them by bare `__vsN`. `desugarSignals` turns `cells` into
|
|
8
|
+
`scope().trackedComputed(...)` consts emitted BEFORE the owning declaration and parses
|
|
9
|
+
`rewrittenSeedText` back into the lowered seed. */
|
|
10
|
+
export type ScriptSeedLift = { cells: InjectedCell[]; rewrittenSeedText: string }
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
The script-side twin of the template `lowerAsyncInterpolations` walk (ADR-0032): runs
|
|
14
|
+
`liftAsyncSubExpressions` over a `state`/`state.linked`/`state.computed` seed ARGUMENT so a
|
|
15
|
+
buried promise/stream sub-expression (`getSession()?.filteredSources ?? []`) lifts to an
|
|
16
|
+
injected streaming peek-cell — reading `undefined` while pending so `?.`/`??` compose — exactly
|
|
17
|
+
as it would in a `{#if getSession()?.x}` template position. Only PROPER sub-expressions lift: a
|
|
18
|
+
seed that IS a single async expression (`computed(getSession())`, `linked(getStream())`) is left
|
|
19
|
+
whole for the existing whole-seed classification / runtime probe, so this never double-wraps.
|
|
20
|
+
|
|
21
|
+
`classify` absent (no warm program) ⇒ no lift — a buried bare-async sub-expression needs the
|
|
22
|
+
checker to know it is a promise, and a seed carrying a top-level `await` is already routed by the
|
|
23
|
+
caller's whole-seed classification, so fail-open here is a plain no-op. `seedLoc` is the seed's
|
|
24
|
+
absolute source offset, so each lifted span resolves against the shadow's source→shadow mappings.
|
|
25
|
+
*/
|
|
26
|
+
export function liftScriptSeed(
|
|
27
|
+
seedText: string,
|
|
28
|
+
seedLoc: number,
|
|
29
|
+
classify: InterpolationClassifier | undefined,
|
|
30
|
+
mint: () => string,
|
|
31
|
+
): ScriptSeedLift | undefined {
|
|
32
|
+
if (classify === undefined) {
|
|
33
|
+
return undefined
|
|
34
|
+
}
|
|
35
|
+
const result = liftAsyncSubExpressions(seedText, seedLoc, classify, mint, 'content')
|
|
36
|
+
if (result.lifts.length === 0) {
|
|
37
|
+
return undefined
|
|
38
|
+
}
|
|
39
|
+
/* A single span covering the whole seed is the whole-seed case (`computed(getSession())`) —
|
|
40
|
+
leave it to the existing classification (`isPromiseComputed`) / the runtime probe rather
|
|
41
|
+
than pointlessly wrapping the seed in its own cell. Only proper sub-expressions lift. */
|
|
42
|
+
const only = result.spans.length === 1 ? result.spans[0] : undefined
|
|
43
|
+
if (only !== undefined && seedText.slice(only.start, only.end).trim() === seedText.trim()) {
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
return { cells: result.lifts, rewrittenSeedText: result.code }
|
|
47
|
+
}
|
|
@@ -6,6 +6,7 @@ import { signalRefsTransformer } from './renameSignalRefs.ts'
|
|
|
6
6
|
import { TS_PRINTER } from './TS_PRINTER.ts'
|
|
7
7
|
import type { TemplateNode } from './types/TemplateNode.ts'
|
|
8
8
|
import { unwrapParens } from './unwrapParens.ts'
|
|
9
|
+
import { wrapReactiveSeedsTransformer } from './wrapReactiveSeeds.ts'
|
|
9
10
|
|
|
10
11
|
/* Compiled once — strips the trailing `;` off every lowered expression. */
|
|
11
12
|
const TRAILING_SEMICOLON = /;$/
|
|
@@ -25,11 +26,10 @@ export function lowerContext(
|
|
|
25
26
|
stateNames: ReadonlySet<string>,
|
|
26
27
|
derivedNames: ReadonlySet<string>,
|
|
27
28
|
computedNames: ReadonlySet<string> = new Set(),
|
|
28
|
-
/* `linked` / async `computed` names, read through `$$readCell(name)
|
|
29
|
+
/* `linked` / async `computed` names, read through `$$readCell(name)` — one read form
|
|
30
|
+
whose pause-vs-peek is decided at runtime by the cell's own `blocking` bit (ADR-0042),
|
|
31
|
+
so both back-ends lower a cell read identically with nothing site-specific to thread. */
|
|
29
32
|
cellReadNames: ReadonlySet<string> = new Set(),
|
|
30
|
-
/* The subset that are BLOCKING `await` cells (ADR-0042), read through `$$readCellBlocking(name)`
|
|
31
|
-
(suspend-on-pending). Passed only by the CLIENT back-end; SSR leaves it empty. */
|
|
32
|
-
blockingCellNames: ReadonlySet<string> = new Set(),
|
|
33
33
|
) {
|
|
34
34
|
/* The typed branch-local shadow stack: one auto-popping value owning both kinds.
|
|
35
35
|
`derived` names deref to `.value` like a `computed` (block value params the client
|
|
@@ -44,9 +44,13 @@ export function lowerContext(
|
|
|
44
44
|
one tree — the two string passes would each parse + reprint. `localDerived` is
|
|
45
45
|
snapshotted per call (as the transformer's block-local shadow set) so a binding
|
|
46
46
|
pushed mid-compile is honoured AND shadows a same-named component signal. */
|
|
47
|
-
function lowerOnce(
|
|
47
|
+
function lowerOnce(
|
|
48
|
+
code: string,
|
|
49
|
+
leadingTransformers: ts.TransformerFactory<ts.SourceFile>[] = [],
|
|
50
|
+
): string {
|
|
48
51
|
const source = ts.createSourceFile('expr.ts', code, ts.ScriptTarget.Latest, true)
|
|
49
52
|
const result = ts.transform(source, [
|
|
53
|
+
...leadingTransformers,
|
|
50
54
|
signalRefsTransformer(
|
|
51
55
|
stateNames,
|
|
52
56
|
derivedNames,
|
|
@@ -54,7 +58,6 @@ export function lowerContext(
|
|
|
54
58
|
new Set(scope.names('derived')),
|
|
55
59
|
new Set(scope.names('plain')),
|
|
56
60
|
cellReadNames,
|
|
57
|
-
blockingCellNames,
|
|
58
61
|
),
|
|
59
62
|
docAccessTransformer('$$model'),
|
|
60
63
|
])
|
|
@@ -75,6 +78,16 @@ export function lowerContext(
|
|
|
75
78
|
return lowerOnce(code)
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
/* A nested branch `<script>` body. Same lowering as `statement`, but first normalises its
|
|
82
|
+
literal `state.computed`/`state.linked` seeds into thunks (`wrapReactiveSeeds`) so a bare-
|
|
83
|
+
value seed (`state.computed(a * 2)`) matches the top-level desugar rather than reaching the
|
|
84
|
+
runtime primitive un-thunked (which would call the value as the compute function and crash).
|
|
85
|
+
The wrap runs BEFORE the reference rename so it wraps the raw seed and the renamed refs land
|
|
86
|
+
inside the thunk. */
|
|
87
|
+
function script(code: string): string {
|
|
88
|
+
return lowerOnce(code, [wrapReactiveSeedsTransformer()])
|
|
89
|
+
}
|
|
90
|
+
|
|
78
91
|
/* A two-way bind target is either an LVALUE (`count`, `model.lines[i]`) — reads as
|
|
79
92
|
itself, writes by assignment — or an ACCESSOR object from `bind:value={{ get, set }}`
|
|
80
93
|
— reads via `.get()`, writes via `.set(v)`. The accessor is the only way to bind a
|
|
@@ -136,11 +149,18 @@ export function lowerContext(
|
|
|
136
149
|
return {
|
|
137
150
|
expression,
|
|
138
151
|
statement,
|
|
152
|
+
script,
|
|
139
153
|
withNestedScripts,
|
|
140
154
|
/* The raw branch-local shadow registration both back-ends drive `withBindings`
|
|
141
155
|
through: a block's bindings flow to a `ShadowKind` only via that one shared loop,
|
|
142
156
|
never through a per-block `scope.withShadow` call here. */
|
|
143
157
|
withShadow: scope.withShadow,
|
|
158
|
+
/* Whether `name` is currently shadowed by a block binding (either kind) at this point
|
|
159
|
+
of the walk — the live query classification sites (e.g. `awaitSubjectExpr`) consult
|
|
160
|
+
so a binder that shadows a component cell name is lowered as the local it is, never
|
|
161
|
+
wrapped as the cell it hides. */
|
|
162
|
+
isShadowed: (name: string): boolean =>
|
|
163
|
+
scope.names('plain').has(name) || scope.names('derived').has(name),
|
|
144
164
|
bindRead,
|
|
145
165
|
bindWrite,
|
|
146
166
|
}
|
|
@@ -91,19 +91,10 @@ export function lowerScript(
|
|
|
91
91
|
derivedNames: Set<string>
|
|
92
92
|
computedNames: Set<string>
|
|
93
93
|
cellReadNames: Set<string>
|
|
94
|
-
/* Template-injected + script-level `await` cells (ADR-0042), read via `$$readCellBlocking`. */
|
|
95
|
-
blockingCellNames: Set<string>
|
|
96
94
|
droppedReactiveImports: Set<string>
|
|
97
95
|
} {
|
|
98
96
|
const source = ts.createSourceFile('component.ts', scriptBody, ts.ScriptTarget.Latest, true)
|
|
99
|
-
const {
|
|
100
|
-
transformer,
|
|
101
|
-
stateNames,
|
|
102
|
-
derivedNames,
|
|
103
|
-
computedNames,
|
|
104
|
-
cellReadNames,
|
|
105
|
-
blockingCellNames: allBlockingNames,
|
|
106
|
-
} = desugarSignals(
|
|
97
|
+
const { transformer, stateNames, derivedNames, computedNames, cellReadNames } = desugarSignals(
|
|
107
98
|
source,
|
|
108
99
|
injectedCellNames,
|
|
109
100
|
blockingCellNames,
|
|
@@ -194,7 +185,6 @@ export function lowerScript(
|
|
|
194
185
|
derivedNames,
|
|
195
186
|
computedNames,
|
|
196
187
|
cellReadNames,
|
|
197
|
-
blockingCellNames: allBlockingNames,
|
|
198
188
|
droppedReactiveImports,
|
|
199
189
|
}
|
|
200
190
|
}
|
|
@@ -54,12 +54,10 @@ export function signalRefsTransformer(
|
|
|
54
54
|
blockLocalPlain: ReadonlySet<string> = new Set(),
|
|
55
55
|
/* Component signals read through `$$readCell(name)` — every `linked` and every async
|
|
56
56
|
`computed` (see `desugarSignals`). A nearer lexical binding of the same name shadows
|
|
57
|
-
them like any other signal.
|
|
57
|
+
them like any other signal. Whether a read PAUSES (blocking `await` cell) or peeks
|
|
58
|
+
(streaming) is decided at runtime by the cell itself (`readCell` consults `cell.blocking`),
|
|
59
|
+
so there is one read form here for every cell and nothing site-specific to thread. */
|
|
58
60
|
cellReadNames: ReadonlySet<string> = new Set(),
|
|
59
|
-
/* The subset of `cellReadNames` that are BLOCKING `await` cells (ADR-0042), read through
|
|
60
|
-
`$$readCellBlocking(name)` (suspend-on-pending) instead of `$$readCell`. Non-empty only on
|
|
61
|
-
the CLIENT template lowering; the script pass and SSR keep it empty (`$$readCell`). */
|
|
62
|
-
blockingCellNames: ReadonlySet<string> = new Set(),
|
|
63
61
|
): ts.TransformerFactory<ts.SourceFile> {
|
|
64
62
|
/* The signal names that a nested binding can shadow — only these matter for
|
|
65
63
|
scope tracking, so we ignore every other local binding. */
|
|
@@ -160,7 +158,6 @@ export function signalRefsTransformer(
|
|
|
160
158
|
computedNames,
|
|
161
159
|
blockLocal,
|
|
162
160
|
cellReadNames,
|
|
163
|
-
blockingCellNames,
|
|
164
161
|
)
|
|
165
162
|
if (replacement !== undefined) {
|
|
166
163
|
return ts.factory.createPropertyAssignment(node.name.text, replacement)
|
|
@@ -174,7 +171,6 @@ export function signalRefsTransformer(
|
|
|
174
171
|
computedNames,
|
|
175
172
|
blockLocal,
|
|
176
173
|
cellReadNames,
|
|
177
|
-
blockingCellNames,
|
|
178
174
|
)
|
|
179
175
|
if (replacement !== undefined) {
|
|
180
176
|
return replacement
|
|
@@ -393,7 +389,6 @@ function referenceFor(
|
|
|
393
389
|
computedNames: ReadonlySet<string>,
|
|
394
390
|
blockLocal: ReadonlySet<string> = new Set(),
|
|
395
391
|
cellReadNames: ReadonlySet<string> = new Set(),
|
|
396
|
-
blockingCellNames: ReadonlySet<string> = new Set(),
|
|
397
392
|
): ts.Expression | undefined {
|
|
398
393
|
if (blockLocal.has(name)) {
|
|
399
394
|
return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(name), 'value')
|
|
@@ -403,19 +398,10 @@ function referenceFor(
|
|
|
403
398
|
if (name === 'scope') {
|
|
404
399
|
return ts.factory.createIdentifier('$$scope')
|
|
405
400
|
}
|
|
406
|
-
/* A
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (blockingCellNames.has(name)) {
|
|
411
|
-
return ts.factory.createCallExpression(
|
|
412
|
-
ts.factory.createIdentifier('$$readCellBlocking'),
|
|
413
|
-
undefined,
|
|
414
|
-
[ts.factory.createIdentifier(name)],
|
|
415
|
-
)
|
|
416
|
-
}
|
|
417
|
-
/* A `linked` / async `computed` cell → `$$readCell(name)`: one read shape that peeks an
|
|
418
|
-
async cell and reads `.value` off a sync one. */
|
|
401
|
+
/* A `linked` / async `computed` cell → `$$readCell(name)`: ONE read shape for every cell,
|
|
402
|
+
script and template. It peeks a sync cell's `.value`, and for an async cell decides at
|
|
403
|
+
runtime whether to pause (blocking `await`) or peek `undefined` (streaming) by the cell's
|
|
404
|
+
own `blocking` bit — so no blocking/streaming distinction is threaded through codegen. */
|
|
419
405
|
if (cellReadNames.has(name)) {
|
|
420
406
|
return ts.factory.createCallExpression(
|
|
421
407
|
ts.factory.createIdentifier('$$readCell'),
|
|
@@ -41,9 +41,9 @@ export function seedTypeClassifierForRoot(
|
|
|
41
41
|
return undefined
|
|
42
42
|
}
|
|
43
43
|
const checker = program.getTypeChecker()
|
|
44
|
+
const mappings = shadows.get(abidePath)?.mappings ?? []
|
|
44
45
|
return (loc, code) => {
|
|
45
46
|
try {
|
|
46
|
-
const mappings = shadows.get(abidePath)?.mappings ?? []
|
|
47
47
|
const offset = sourceToShadowOffset(mappings, loc)
|
|
48
48
|
if (offset === undefined) {
|
|
49
49
|
return undefined
|