octane 0.2.12 → 0.2.13

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.
Files changed (243) hide show
  1. package/README.md +5 -0
  2. package/dist/behavior-root.d.ts +13 -3
  3. package/dist/behavior-root.js +28 -5
  4. package/dist/cjs/behavior-root.cjs +32 -6
  5. package/dist/cjs/class-names.cjs +49 -0
  6. package/dist/cjs/constants.cjs +9 -0
  7. package/dist/cjs/css.cjs +6 -27
  8. package/dist/cjs/data-encoding.cjs +160 -0
  9. package/dist/cjs/dom-binding-handoff.cjs +93 -0
  10. package/dist/cjs/dom-binding-protocol.cjs +115 -0
  11. package/dist/cjs/dom-binding-styles.cjs +292 -0
  12. package/dist/cjs/dom-bindings.cjs +700 -0
  13. package/dist/cjs/dom-focused-move.cjs +66 -0
  14. package/dist/cjs/dom-tables.cjs +4 -94
  15. package/dist/cjs/error-codes.client.generated.cjs +33 -0
  16. package/dist/cjs/error-codes.server.generated.cjs +19 -0
  17. package/dist/cjs/hydration/control-capture.cjs +244 -0
  18. package/dist/cjs/hydration/event-capture.cjs +125 -43
  19. package/dist/cjs/hydration/interaction-config.cjs +50 -2
  20. package/dist/cjs/hydration-markers.cjs +37 -0
  21. package/dist/cjs/independent-hydration-protocol.cjs +106 -0
  22. package/dist/cjs/index.cjs +6 -0
  23. package/dist/cjs/runtime.cjs +2791 -215
  24. package/dist/cjs/runtime.server.cjs +716 -76
  25. package/dist/cjs/server/early-signals.cjs +45 -0
  26. package/dist/cjs/server/index.cjs +44 -0
  27. package/dist/cjs/server/rpc-batch.cjs +149 -0
  28. package/dist/cjs/server/rpc-stream.cjs +146 -0
  29. package/dist/cjs/server/rpc.cjs +34 -15
  30. package/dist/cjs/server/signal-query-observation.cjs +158 -0
  31. package/dist/cjs/server/streamed-region.cjs +52 -0
  32. package/dist/cjs/server/streamed-signals.cjs +512 -0
  33. package/dist/cjs/server-call.cjs +103 -0
  34. package/dist/cjs/server-rpc-batch-client.cjs +141 -0
  35. package/dist/cjs/server-rpc-client.cjs +94 -27
  36. package/dist/cjs/server-rpc-protocol.cjs +141 -0
  37. package/dist/cjs/server-rpc-stream-client.cjs +118 -0
  38. package/dist/cjs/signals/actions.cjs +474 -0
  39. package/dist/cjs/signals/client.cjs +4 -1
  40. package/dist/cjs/signals/computations.cjs +460 -0
  41. package/dist/cjs/signals/control-binding.cjs +368 -0
  42. package/dist/cjs/signals/derived-facade.cjs +50 -0
  43. package/dist/cjs/signals/document-owner.cjs +71 -0
  44. package/dist/cjs/signals/early-values.cjs +155 -0
  45. package/dist/cjs/signals/encoding.cjs +6 -95
  46. package/dist/cjs/signals/engine.cjs +276 -12
  47. package/dist/cjs/signals/errors.cjs +18 -0
  48. package/dist/cjs/signals/facade.cjs +477 -0
  49. package/dist/cjs/signals/graph.cjs +163 -20
  50. package/dist/cjs/signals/handle-protocol.cjs +36 -0
  51. package/dist/cjs/signals/index.cjs +81 -2
  52. package/dist/cjs/signals/native-read-client.cjs +10 -0
  53. package/dist/cjs/signals/owner-context.cjs +112 -0
  54. package/dist/cjs/signals/query-attempt-observer.cjs +68 -0
  55. package/dist/cjs/signals/query-facade.cjs +72 -0
  56. package/dist/cjs/signals/read-protocol.cjs +32 -0
  57. package/dist/cjs/signals/requests.cjs +287 -14
  58. package/dist/cjs/signals/scalar-computations.cjs +76 -0
  59. package/dist/cjs/signals/server.cjs +4 -1
  60. package/dist/cjs/signals/transition-candidate.cjs +567 -0
  61. package/dist/cjs/signals/transition-state.cjs +106 -0
  62. package/dist/cjs/signals/types.cjs +50 -0
  63. package/dist/cjs/stream-protocol.cjs +3 -0
  64. package/dist/cjs/streamed-signals-protocol.cjs +159 -0
  65. package/dist/cjs/style-values.cjs +122 -0
  66. package/dist/cjs/version.cjs +1 -1
  67. package/dist/class-names.d.ts +5 -0
  68. package/dist/class-names.js +25 -0
  69. package/dist/compiler/bundler.js +34 -6
  70. package/dist/compiler/compile.js +2234 -559
  71. package/dist/compiler/dom-binding-program.js +935 -0
  72. package/dist/compiler/dom-binding-request.js +57 -0
  73. package/dist/compiler/dom-bindings.js +2245 -0
  74. package/dist/compiler/hook-deps.js +26 -0
  75. package/dist/compiler/hydrate-boundaries.js +358 -15
  76. package/dist/compiler/index.d.ts +38 -1
  77. package/dist/compiler/index.js +1 -0
  78. package/dist/compiler/native-attribute-reads.js +127 -0
  79. package/dist/compiler/native-read-diagnostics.js +48 -9
  80. package/dist/compiler/native-read-types.js +55 -32
  81. package/dist/compiler/server-context.js +118 -0
  82. package/dist/compiler/signal-attempt-reads.js +479 -0
  83. package/dist/compiler/signal-declarations.js +557 -0
  84. package/dist/compiler/signal-start-reads.js +413 -0
  85. package/dist/compiler/slot-hooks.js +57 -10
  86. package/dist/compiler/template-ir.js +200 -0
  87. package/dist/compiler/typescript.d.ts +2 -0
  88. package/dist/compiler/typescript.js +2 -0
  89. package/dist/compiler/vite.d.ts +3 -1
  90. package/dist/compiler/vite.js +122 -4
  91. package/dist/compiler/volar.d.ts +6 -1
  92. package/dist/compiler/volar.js +674 -143
  93. package/dist/constants.d.ts +1 -0
  94. package/dist/constants.js +10 -0
  95. package/dist/css.d.ts +2 -5
  96. package/dist/css.js +2 -23
  97. package/dist/data-encoding.d.ts +9 -0
  98. package/dist/data-encoding.js +134 -0
  99. package/dist/dom-binding-classes.d.ts +15 -0
  100. package/dist/dom-binding-classes.js +129 -0
  101. package/dist/dom-binding-controls.d.ts +2 -0
  102. package/dist/dom-binding-controls.js +6 -0
  103. package/dist/dom-binding-handoff.d.ts +55 -0
  104. package/dist/dom-binding-handoff.js +62 -0
  105. package/dist/dom-binding-program.d.ts +300 -0
  106. package/dist/dom-binding-program.js +1625 -0
  107. package/dist/dom-binding-projections.d.ts +15 -0
  108. package/dist/dom-binding-projections.js +187 -0
  109. package/dist/dom-binding-protocol.d.ts +13 -0
  110. package/dist/dom-binding-protocol.js +88 -0
  111. package/dist/dom-binding-signals.d.ts +21 -0
  112. package/dist/dom-binding-signals.js +109 -0
  113. package/dist/dom-binding-styles.d.ts +17 -0
  114. package/dist/dom-binding-styles.js +272 -0
  115. package/dist/dom-bindings.d.ts +89 -0
  116. package/dist/dom-bindings.js +670 -0
  117. package/dist/dom-focused-move.d.ts +6 -0
  118. package/dist/dom-focused-move.js +42 -0
  119. package/dist/dom-tables.d.ts +1 -3
  120. package/dist/dom-tables.js +1 -91
  121. package/dist/error-codes.client.generated.d.ts +9 -0
  122. package/dist/error-codes.client.generated.js +33 -0
  123. package/dist/error-codes.server.generated.d.ts +5 -0
  124. package/dist/error-codes.server.generated.js +19 -0
  125. package/dist/hydration/control-capture.d.ts +62 -0
  126. package/dist/hydration/control-capture.js +223 -0
  127. package/dist/hydration/document-lifecycle.d.ts +23 -0
  128. package/dist/hydration/document-lifecycle.js +80 -0
  129. package/dist/hydration/event-capture.d.ts +20 -1
  130. package/dist/hydration/event-capture.js +128 -42
  131. package/dist/hydration/independent-island.d.ts +45 -0
  132. package/dist/hydration/independent-island.js +233 -0
  133. package/dist/hydration/index.d.ts +8 -1
  134. package/dist/hydration/index.js +55 -1
  135. package/dist/hydration/interaction-config.d.ts +5 -0
  136. package/dist/hydration/interaction-config.js +44 -1
  137. package/dist/hydration/stream-delivery.d.ts +32 -0
  138. package/dist/hydration/stream-delivery.js +333 -0
  139. package/dist/hydration/stream-receiver.d.ts +26 -0
  140. package/dist/hydration/stream-receiver.js +204 -0
  141. package/dist/hydration/stream-result-receiver.d.ts +70 -0
  142. package/dist/hydration/stream-result-receiver.js +246 -0
  143. package/dist/hydration/streamed-signals.d.ts +44 -0
  144. package/dist/hydration/streamed-signals.js +186 -0
  145. package/dist/hydration/types.d.ts +6 -0
  146. package/dist/hydration-markers.d.ts +7 -0
  147. package/dist/hydration-markers.js +10 -0
  148. package/dist/independent-hydration-protocol.d.ts +31 -0
  149. package/dist/independent-hydration-protocol.js +80 -0
  150. package/dist/index.d.ts +2 -0
  151. package/dist/index.js +4 -0
  152. package/dist/internal/signal-read.d.ts +1 -0
  153. package/dist/internal/signal-read.js +4 -0
  154. package/dist/jsx-runtime.d.ts +45 -6
  155. package/dist/public-types.d.ts +15 -1
  156. package/dist/runtime.d.ts +259 -21
  157. package/dist/runtime.js +2812 -220
  158. package/dist/runtime.server.d.ts +86 -6
  159. package/dist/runtime.server.js +712 -76
  160. package/dist/server/early-signals.d.ts +14 -0
  161. package/dist/server/early-signals.js +34 -0
  162. package/dist/server/index.d.ts +10 -2
  163. package/dist/server/index.js +48 -1
  164. package/dist/server/rpc-batch.d.ts +17 -0
  165. package/dist/server/rpc-batch.js +129 -0
  166. package/dist/server/rpc-stream.d.ts +9 -0
  167. package/dist/server/rpc-stream.js +125 -0
  168. package/dist/server/rpc.d.ts +5 -1
  169. package/dist/server/rpc.js +37 -13
  170. package/dist/server/signal-query-observation.d.ts +3 -0
  171. package/dist/server/signal-query-observation.js +134 -0
  172. package/dist/server/streamed-region.d.ts +15 -0
  173. package/dist/server/streamed-region.js +30 -0
  174. package/dist/server/streamed-signals.d.ts +45 -0
  175. package/dist/server/streamed-signals.js +488 -0
  176. package/dist/server-call.d.ts +41 -0
  177. package/dist/server-call.js +75 -0
  178. package/dist/server-rpc-batch-client.d.ts +7 -0
  179. package/dist/server-rpc-batch-client.js +119 -0
  180. package/dist/server-rpc-client.d.ts +3 -1
  181. package/dist/server-rpc-client.js +99 -27
  182. package/dist/server-rpc-protocol.d.ts +47 -0
  183. package/dist/server-rpc-protocol.js +109 -0
  184. package/dist/server-rpc-stream-client.d.ts +5 -0
  185. package/dist/server-rpc-stream-client.js +98 -0
  186. package/dist/signals/actions.d.ts +12 -0
  187. package/dist/signals/actions.js +466 -0
  188. package/dist/signals/client.d.ts +2 -0
  189. package/dist/signals/client.js +1 -0
  190. package/dist/signals/computations.d.ts +37 -0
  191. package/dist/signals/computations.js +455 -0
  192. package/dist/signals/control-binding.d.ts +31 -0
  193. package/dist/signals/control-binding.js +353 -0
  194. package/dist/signals/derived-facade.d.ts +3 -0
  195. package/dist/signals/derived-facade.js +25 -0
  196. package/dist/signals/document-owner.d.ts +10 -0
  197. package/dist/signals/document-owner.js +43 -0
  198. package/dist/signals/early-values.d.ts +24 -0
  199. package/dist/signals/early-values.js +124 -0
  200. package/dist/signals/encoding.d.ts +1 -3
  201. package/dist/signals/encoding.js +3 -93
  202. package/dist/signals/engine.d.ts +53 -4
  203. package/dist/signals/engine.js +275 -16
  204. package/dist/signals/errors.d.ts +7 -0
  205. package/dist/signals/errors.js +16 -0
  206. package/dist/signals/facade.d.ts +74 -0
  207. package/dist/signals/facade.js +452 -0
  208. package/dist/signals/graph.d.ts +50 -5
  209. package/dist/signals/graph.js +170 -19
  210. package/dist/signals/handle-protocol.d.ts +4 -0
  211. package/dist/signals/handle-protocol.js +11 -0
  212. package/dist/signals/index.d.ts +12 -3
  213. package/dist/signals/index.js +71 -2
  214. package/dist/signals/native-read-client.d.ts +3 -0
  215. package/dist/signals/native-read-client.js +10 -0
  216. package/dist/signals/owner-context.d.ts +22 -0
  217. package/dist/signals/owner-context.js +77 -0
  218. package/dist/signals/query-attempt-observer.d.ts +49 -0
  219. package/dist/signals/query-attempt-observer.js +41 -0
  220. package/dist/signals/query-facade.d.ts +3 -0
  221. package/dist/signals/query-facade.js +49 -0
  222. package/dist/signals/read-protocol.d.ts +32 -3
  223. package/dist/signals/read-protocol.js +26 -0
  224. package/dist/signals/requests.d.ts +38 -5
  225. package/dist/signals/requests.js +298 -15
  226. package/dist/signals/scalar-computations.d.ts +2 -0
  227. package/dist/signals/scalar-computations.js +60 -0
  228. package/dist/signals/server.d.ts +2 -0
  229. package/dist/signals/server.js +1 -0
  230. package/dist/signals/transition-candidate.d.ts +71 -0
  231. package/dist/signals/transition-candidate.js +557 -0
  232. package/dist/signals/transition-state.d.ts +51 -0
  233. package/dist/signals/transition-state.js +70 -0
  234. package/dist/signals/types.d.ts +95 -5
  235. package/dist/signals/types.js +20 -0
  236. package/dist/stream-protocol.d.ts +0 -5
  237. package/dist/stream-protocol.js +3 -0
  238. package/dist/streamed-signals-protocol.d.ts +60 -0
  239. package/dist/streamed-signals-protocol.js +130 -0
  240. package/dist/style-values.d.ts +3 -0
  241. package/dist/style-values.js +96 -0
  242. package/dist/version.js +1 -1
  243. package/package.json +37 -1
@@ -0,0 +1,413 @@
1
+ import { builders as b } from '@tsrx/core';
2
+ import { createLexicalAnalysis } from './compile-universal.js';
3
+ import { inheritHookMemoOrigin } from './inline-hook-memo.js';
4
+
5
+ const SIGNAL_MODULES = new Set([
6
+ 'octane/signals',
7
+ 'octane/signals/client',
8
+ 'octane/signals/server',
9
+ ]);
10
+ const METADATA = new Set([
11
+ 'loc',
12
+ 'start',
13
+ 'end',
14
+ 'range',
15
+ 'metadata',
16
+ 'parent',
17
+ 'comments',
18
+ 'leadingComments',
19
+ 'trailingComments',
20
+ 'innerComments',
21
+ ]);
22
+
23
+ function children(node, visit) {
24
+ for (const key in node) {
25
+ if (METADATA.has(key) || key.startsWith('_octane')) continue;
26
+ const value = node[key];
27
+ if (Array.isArray(value)) {
28
+ for (const child of value) if (child?.type) visit(child);
29
+ } else if (value?.type) visit(value);
30
+ }
31
+ }
32
+
33
+ function unwrap(node) {
34
+ while (
35
+ [
36
+ 'TSAsExpression',
37
+ 'TSTypeAssertion',
38
+ 'TSSatisfiesExpression',
39
+ 'TSNonNullExpression',
40
+ 'ParenthesizedExpression',
41
+ ].includes(node?.type)
42
+ )
43
+ node = node.expression;
44
+ return node;
45
+ }
46
+
47
+ function isFunction(node) {
48
+ return ['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'].includes(
49
+ node.type,
50
+ );
51
+ }
52
+
53
+ function hasRender(node) {
54
+ if (node.type.startsWith('JSX')) return true;
55
+ if (isFunction(node)) return false;
56
+ let found = false;
57
+ children(node, (child) => {
58
+ found ||= hasRender(child);
59
+ });
60
+ return found;
61
+ }
62
+
63
+ /**
64
+ * Start adjacent strict reads or complete static native output reads of
65
+ * lexically proven immutable descriptors.
66
+ * Original reads remain the suspension/error/observation points. In particular,
67
+ * this does not hoist declarations, evaluate member receivers, cross statements,
68
+ * or enter a conditional arm before its authored control-flow boundary.
69
+ */
70
+ export function startIndependentSignalReads(ast, isText = null) {
71
+ if (
72
+ !(ast.body ?? []).some(
73
+ (node) => node.type === 'ImportDeclaration' && SIGNAL_MODULES.has(node.source?.value),
74
+ )
75
+ )
76
+ return ast;
77
+ const lexical = createLexicalAnalysis(ast);
78
+ const imports = new Map();
79
+ const declarations = new Map();
80
+ const names = new Set();
81
+ const binding = (node) =>
82
+ node?.type === 'Identifier'
83
+ ? lexical.resolveBinding(lexical.nodeScopes.get(node) ?? lexical.rootScope, node.name)
84
+ : null;
85
+ const recordFor = (node) => declarations.get(binding(node)?.scope)?.get(node.name);
86
+ function register(id, record, declarationScope) {
87
+ if (id?.type !== 'Identifier') return;
88
+ const scope = declarationScope
89
+ ? lexical.resolveBinding(declarationScope, id.name)?.scope
90
+ : binding(id)?.scope;
91
+ if (!scope) return;
92
+ let records = declarations.get(scope);
93
+ if (!records) declarations.set(scope, (records = new Map()));
94
+ records.set(id.name, record);
95
+ }
96
+ function collect(node, ownerFunction = null) {
97
+ if (node.type === 'Identifier') names.add(node.name);
98
+ if (
99
+ node.type === 'ImportDeclaration' &&
100
+ node.importKind !== 'type' &&
101
+ SIGNAL_MODULES.has(node.source?.value)
102
+ ) {
103
+ for (const specifier of node.specifiers ?? []) {
104
+ if (specifier.importKind === 'type') continue;
105
+ const factory = specifier.imported?.name ?? specifier.imported?.value;
106
+ if (
107
+ factory === 'query$' ||
108
+ factory === 'derived$' ||
109
+ specifier.type === 'ImportNamespaceSpecifier'
110
+ )
111
+ imports.set(specifier.local.name, {
112
+ factory,
113
+ source: node.source.value,
114
+ declaration: node,
115
+ });
116
+ }
117
+ }
118
+ if (node.type === 'VariableDeclaration') {
119
+ for (const declaration of node.declarations)
120
+ register(declaration.id, {
121
+ init: declaration.init,
122
+ kind: node.kind,
123
+ declaration,
124
+ ownerFunction,
125
+ });
126
+ } else if (node.type === 'FunctionDeclaration')
127
+ register(
128
+ node.id,
129
+ { init: node, kind: 'function', declaration: node },
130
+ lexical.nodeScopes.get(node),
131
+ );
132
+ children(node, (child) => collect(child, isFunction(node) ? node : ownerFunction));
133
+ }
134
+ collect(ast);
135
+ // Initializer reachability is insufficient when a selector was reassigned or
136
+ // installed in a mutable object. Fence any result captured by an already
137
+ // available closure, without attempting heap or assignment reconstruction.
138
+ const capturedResults = new Map();
139
+ const opaqueFunctions = new Set();
140
+ function collectCaptures(node, stack = []) {
141
+ const nested =
142
+ isFunction(node) || node.type === 'ClassDeclaration' || node.type === 'ClassExpression'
143
+ ? [...stack, node]
144
+ : stack;
145
+ if (node.type === 'Identifier' && !lexical.bindingNodes.has(node)) {
146
+ const record = recordFor(node);
147
+ const owner = record?.ownerFunction;
148
+ const index = owner ? nested.indexOf(owner) : -1;
149
+ const closure = index < 0 ? null : nested[index + 1];
150
+ if (closure) {
151
+ let captured = capturedResults.get(record);
152
+ if (!captured) capturedResults.set(record, (captured = new Set()));
153
+ captured.add(closure);
154
+ }
155
+ }
156
+ const callee = unwrap(node.callee);
157
+ if (
158
+ (node.type === 'CallExpression' || node.type === 'NewExpression') &&
159
+ callee?.type === 'Identifier' &&
160
+ (callee.name === 'eval' || callee.name === 'Function')
161
+ ) {
162
+ for (const owner of nested) opaqueFunctions.add(owner);
163
+ }
164
+ children(node, (child) => collectCaptures(child, nested));
165
+ }
166
+ collectCaptures(ast);
167
+ function capturedBefore(result, position) {
168
+ for (const closure of capturedResults.get(recordFor(result)) ?? []) {
169
+ if (
170
+ closure.type === 'FunctionDeclaration' ||
171
+ (closure.start ?? -Infinity) < (position ?? Infinity)
172
+ )
173
+ return true;
174
+ }
175
+ return false;
176
+ }
177
+ function factoryOf(init) {
178
+ const call = unwrap(init);
179
+ if (call?.type !== 'CallExpression' || call.optional) return null;
180
+ const callee = unwrap(call.callee);
181
+ const identifier =
182
+ callee?.type === 'Identifier'
183
+ ? callee
184
+ : callee?.type === 'MemberExpression' && !callee.computed && !callee.optional
185
+ ? callee.object
186
+ : null;
187
+ if (identifier?.type !== 'Identifier') return null;
188
+ const record = imports.get(identifier.name);
189
+ const resolved = binding(identifier);
190
+ if (
191
+ !record ||
192
+ resolved?.scope !== lexical.rootScope ||
193
+ resolved.importSource?.value !== record.source
194
+ )
195
+ return null;
196
+ const factory =
197
+ callee === identifier
198
+ ? record.factory
199
+ : record.factory === undefined
200
+ ? callee.property.name
201
+ : null;
202
+ return factory === 'query$' || factory === 'derived$' ? record : null;
203
+ }
204
+ function strictExpression(expression) {
205
+ const call = unwrap(expression);
206
+ if (call?.type !== 'CallExpression' || call.optional || call.arguments.length !== 0)
207
+ return null;
208
+ const member = unwrap(call.callee);
209
+ if (
210
+ member?.type !== 'MemberExpression' ||
211
+ member.optional ||
212
+ member.computed ||
213
+ member.property.name !== 'get' ||
214
+ member.object.type !== 'Identifier'
215
+ )
216
+ return null;
217
+ const descriptor = recordFor(member.object);
218
+ if (descriptor?.kind !== 'const') return null;
219
+ const factory = factoryOf(descriptor.init);
220
+ return factory ? { handle: member.object, descriptor, factory } : null;
221
+ }
222
+ function strictRead(statement) {
223
+ if (
224
+ statement.type !== 'VariableDeclaration' ||
225
+ statement.kind !== 'const' ||
226
+ statement.declarations.length !== 1
227
+ )
228
+ return null;
229
+ const declaration = statement.declarations[0];
230
+ if (declaration.id.type !== 'Identifier') return null;
231
+ const read = strictExpression(declaration.init);
232
+ return read ? { ...read, result: declaration.id } : null;
233
+ }
234
+ // Only complete static native output is traversed. Mount phases can evaluate
235
+ // an attribute or typed text before an earlier generic child, so a lexical
236
+ // prefix before an opaque node (or a mix of hole kinds) is not sufficient.
237
+ function outputReads(output) {
238
+ if (isText === null) return [];
239
+ const reads = [];
240
+ let text;
241
+ function visit(node) {
242
+ if (node.type === 'JSXText') return true;
243
+ if (node.type === 'JSXExpressionContainer') {
244
+ if (node.expression.type === 'JSXEmptyExpression') return true;
245
+ const read = strictExpression(node.expression);
246
+ if (read === null) return false;
247
+ const kind = isText(node.expression);
248
+ if (text !== undefined && text !== kind) return false;
249
+ text = kind;
250
+ reads.push(read);
251
+ return true;
252
+ }
253
+ if (node.type === 'JSXElement') {
254
+ const opening = node.openingElement;
255
+ const tag = opening.name;
256
+ if (
257
+ tag.type !== 'JSXIdentifier' ||
258
+ !/^[a-z][a-z0-9]*$/.test(tag.name) ||
259
+ /^(?:html|head|body|base|meta|link|title|style|script|template|noscript|svg|math|input|select|textarea|option|optgroup|iframe|object|embed|img|image|audio|video|source|track)$/.test(
260
+ tag.name,
261
+ )
262
+ )
263
+ return false;
264
+ for (const attribute of opening.attributes ?? []) {
265
+ if (
266
+ attribute.type !== 'JSXAttribute' ||
267
+ attribute.name.type !== 'JSXIdentifier' ||
268
+ /^(?:ref|key|is|style|children|innerHTML|dangerouslySetInnerHTML|on.*)$/i.test(
269
+ attribute.name.name,
270
+ ) ||
271
+ (attribute.value !== null && attribute.value.type !== 'Literal')
272
+ )
273
+ return false;
274
+ }
275
+ } else if (node.type !== 'JSXFragment') return false;
276
+ return (node.children ?? []).every(visit);
277
+ }
278
+ return output && visit(output) ? reads : [];
279
+ }
280
+ // Follow local aliases/helpers as well as direct captures: a later selector
281
+ // closing over an earlier read's result must run after that result is assigned.
282
+ function dependsOn(node, results, seen = new Set()) {
283
+ if (!node || seen.has(node)) return false;
284
+ seen.add(node);
285
+ if (node.type === 'Identifier' && !lexical.bindingNodes.has(node)) {
286
+ const resolved = binding(node);
287
+ if (
288
+ results.some(
289
+ (result) => result.name === node.name && binding(result)?.scope === resolved?.scope,
290
+ )
291
+ )
292
+ return true;
293
+ if (dependsOn(recordFor(node)?.init, results, seen)) return true;
294
+ }
295
+ let found = false;
296
+ children(node, (child) => {
297
+ found ||= dependsOn(child, results, seen);
298
+ });
299
+ return found;
300
+ }
301
+ let helperName = '_$startSignalReads';
302
+ while (names.has(helperName)) helperName += '$';
303
+ let helperImport = null;
304
+ function start(reads, origin, primitive = false) {
305
+ helperImport ??= reads[0].factory.declaration;
306
+ return inheritHookMemoOrigin(
307
+ // Guard only speculative receiver evaluation (including module TDZ).
308
+ // The original reads retain their normal observation/error boundary.
309
+ b.try(
310
+ b.block([
311
+ b.stmt(
312
+ b.call(
313
+ b.id(helperName),
314
+ b.array(reads.map((read) => b.id(read.handle.name))),
315
+ ...(primitive ? [b.literal(true)] : []),
316
+ ),
317
+ ),
318
+ ]),
319
+ b.catch_clause(null, null, b.block([])),
320
+ ),
321
+ origin,
322
+ );
323
+ }
324
+ function statements(body) {
325
+ let out = null;
326
+ for (let i = 0; i < body.length;) {
327
+ const first = strictRead(body[i]);
328
+ if (!first) {
329
+ const reads = outputReads(body[i]);
330
+ if (reads.length > 1) {
331
+ out ??= body.slice(0, i);
332
+ out.push(start(reads, body[i], true));
333
+ }
334
+ if (out) out.push(body[i]);
335
+ i++;
336
+ continue;
337
+ }
338
+ const run = [first];
339
+ let end = i + 1;
340
+ for (; end < body.length; end++) {
341
+ const next = strictRead(body[end]);
342
+ if (
343
+ !next ||
344
+ run.some((read) => capturedBefore(read.result, body[i].start)) ||
345
+ dependsOn(
346
+ next.descriptor.init,
347
+ run.map((read) => read.result),
348
+ )
349
+ )
350
+ break;
351
+ run.push(next);
352
+ }
353
+ if (run.length > 1) {
354
+ out ??= body.slice(0, i);
355
+ out.push(start(run, body[i]));
356
+ }
357
+ if (out) out.push(...body.slice(i, end));
358
+ i = end;
359
+ }
360
+ return out ?? body;
361
+ }
362
+ function transform(node, rendering = false, functionBody = false) {
363
+ if (node === null || typeof node !== 'object') return node;
364
+ if (Array.isArray(node)) {
365
+ const mapped = node.map((child) => transform(child, rendering));
366
+ return mapped.some((child, index) => child !== node[index]) ? mapped : node;
367
+ }
368
+ if (isFunction(node)) rendering = !opaqueFunctions.has(node) && hasRender(node.body);
369
+ let out = node;
370
+ for (const key in node) {
371
+ if (METADATA.has(key) || key.startsWith('_octane')) continue;
372
+ const mapped = transform(node[key], rendering, isFunction(node) && key === 'body');
373
+ if (mapped !== node[key]) {
374
+ if (out === node) out = { ...node };
375
+ out[key] = mapped;
376
+ }
377
+ }
378
+ if (
379
+ rendering &&
380
+ (node.type === 'BlockStatement' || node.type === 'JSXCodeBlock') &&
381
+ Array.isArray(node.body)
382
+ ) {
383
+ // Analyze original nodes, whose lexical binding identities are known.
384
+ const body = statements(node.body);
385
+ if (body !== node.body) {
386
+ const mapped = new Map(node.body.map((statement, index) => [statement, out.body[index]]));
387
+ out = { ...out, body: body.map((statement) => mapped.get(statement) ?? statement) };
388
+ }
389
+ // Do not turn a transparent render-only child block into a new scope.
390
+ if (node.type === 'JSXCodeBlock' && (functionBody || node.body.length > 0)) {
391
+ const reads = outputReads(node.render);
392
+ if (reads.length > 1)
393
+ out = { ...out, body: [...out.body, start(reads, node.render, true)] };
394
+ }
395
+ }
396
+ return out;
397
+ }
398
+ const lowered = transform(ast);
399
+ if (helperImport === null) return ast;
400
+ return {
401
+ ...lowered,
402
+ body: lowered.body.flatMap((statement) => {
403
+ if (statement !== helperImport) return [statement];
404
+ const helper = inheritHookMemoOrigin(
405
+ b.import_specifier('__startSignalReads', helperName),
406
+ statement,
407
+ );
408
+ return statement.specifiers.some((specifier) => specifier.type === 'ImportNamespaceSpecifier')
409
+ ? [statement, { ...statement, specifiers: [helper] }]
410
+ : [{ ...statement, specifiers: [...statement.specifiers, helper] }];
411
+ }),
412
+ };
413
+ }
@@ -26,6 +26,7 @@ import { nativeReadActivationIndex } from './native-read-codegen.js';
26
26
  import { findManualHookProviders, manualHookWrapperParameters } from './manual-hooks.js';
27
27
  import { findLeadingJsxImportSourcePragma } from './pragma.js';
28
28
  import { collectProvenContextBindings, isProvenContextUse } from './context-use.js';
29
+ import { signalDeclarationSourceEdits } from './signal-declarations.js';
29
30
  import {
30
31
  hookMethodName,
31
32
  hasHookMethods,
@@ -1337,7 +1338,7 @@ function parseHookSource(source, id) {
1337
1338
  * `inlineHookMemo: true` enables the production-client whole-AST memo path;
1338
1339
  * the default remains surgical. `manualSlots: true` permits memo and observed
1339
1340
  * getter rewrites without injecting or changing the authored slot policy.
1340
- * @returns {{ code: string, map: any, diagnostics?: any[] } | null}
1341
+ * @returns {{ code: string, map: any, streamedSignals?: true, diagnostics?: any[] } | null}
1341
1342
  */
1342
1343
  export function slotHooks(source, id, options) {
1343
1344
  const environment = options?.environment ?? 'client';
@@ -1381,6 +1382,7 @@ export function slotHooks(source, id, options) {
1381
1382
  );
1382
1383
  const manualProviders = options?.manualSlots ? findManualHookProviders(ast) : new Map();
1383
1384
  const nativeReadActivation = options?.nativeReads === true && importsNativeRenderer(ast);
1385
+ const signalLowering = signalDeclarationSourceEdits(ast, id, source);
1384
1386
  const canSpecializeRoot =
1385
1387
  !options?.manualSlots &&
1386
1388
  !options?.hmr &&
@@ -1391,6 +1393,7 @@ export function slotHooks(source, id, options) {
1391
1393
  !importInfo.importsHook &&
1392
1394
  !canSpecializeRoot &&
1393
1395
  !nativeReadActivation &&
1396
+ !signalLowering.usesSignals &&
1394
1397
  !manualProviders.size
1395
1398
  ) {
1396
1399
  return strongAnalysis?.diagnostics.length ? { code: source, map: null, ...strongHints } : null;
@@ -1417,6 +1420,8 @@ export function slotHooks(source, id, options) {
1417
1420
  const getterCalls = importInfo.importsHook ? collectStateGetterCalls(ast) : new WeakSet();
1418
1421
  if (
1419
1422
  !strongAnalysis?.enabled &&
1423
+ // The whole-AST memo path does not apply declaration identity edits.
1424
+ !signalLowering.usesSignals &&
1420
1425
  canPrint &&
1421
1426
  options?.inlineHookMemo === true &&
1422
1427
  environment === 'client' &&
@@ -1465,7 +1470,7 @@ export function slotHooks(source, id, options) {
1465
1470
  hash: hookSlotHash(id),
1466
1471
  nextId: 0,
1467
1472
  nextPuId: 0,
1468
- edits: [],
1473
+ edits: [...signalLowering.edits],
1469
1474
  decls: [],
1470
1475
  parallelHelpers: new Map(),
1471
1476
  provenContextBindings: collectProvenContextBindings(ast),
@@ -1485,7 +1490,7 @@ export function slotHooks(source, id, options) {
1485
1490
  if (canSpecializeRoot) {
1486
1491
  collectVoidRootEdits(ast, st, options.isVoidComponentImport);
1487
1492
  }
1488
- if (st.edits.length === 0 && !nativeReadActivation)
1493
+ if (st.edits.length === 0 && !nativeReadActivation && !signalLowering.usesSignals)
1489
1494
  return strongAnalysis?.diagnostics.length ? { code: source, map: null, ...strongHints } : null;
1490
1495
  const activation = nativeReadActivation
1491
1496
  ? requireParallelHelper(st, 'enableNativeReadCollection')
@@ -1526,6 +1531,35 @@ export function slotHooks(source, id, options) {
1526
1531
  const otherHelperImports = [...otherHelpers]
1527
1532
  .map(([request, specifiers]) => `import { ${specifiers.join(', ')} } from '${request}';\n`)
1528
1533
  .join('');
1534
+ const signalHelperImports = new Map();
1535
+ for (const helper of signalLowering.imports) {
1536
+ let specifiers = signalHelperImports.get(helper.source);
1537
+ if (specifiers === undefined) signalHelperImports.set(helper.source, (specifiers = []));
1538
+ specifiers.push(`${helper.imported} as ${helper.local}`);
1539
+ }
1540
+ let signalActivation = '';
1541
+ if (signalLowering.usesSignals) {
1542
+ const request =
1543
+ environment === 'server'
1544
+ ? 'octane/internal/server'
1545
+ : nativeReadActivation
1546
+ ? 'octane/internal/client'
1547
+ : 'octane/signals';
1548
+ const imported =
1549
+ environment === 'server'
1550
+ ? 'enableServerSignalBindings'
1551
+ : nativeReadActivation
1552
+ ? 'enableSignalBindings'
1553
+ : '__enableSignalDocument';
1554
+ const local = allocSlotName(st, `_$${imported}`);
1555
+ let specifiers = signalHelperImports.get(request);
1556
+ if (specifiers === undefined) signalHelperImports.set(request, (specifiers = []));
1557
+ specifiers.push(`${imported} as ${local}`);
1558
+ signalActivation = `${local}(1);\n`;
1559
+ }
1560
+ const signalImports = [...signalHelperImports]
1561
+ .map(([request, specifiers]) => `import { ${specifiers.join(', ')} } from '${request}';\n`)
1562
+ .join('');
1529
1563
  const profileImport =
1530
1564
  st.profile && !st.manualSlots
1531
1565
  ? "import { __profileHook as _$__profileHook } from 'octane/profiling';\n"
@@ -1535,15 +1569,22 @@ export function slotHooks(source, id, options) {
1535
1569
  ? `const ${st.slotBaseName} = /* @__PURE__ */ ${st.hookSlotsName}(${st.nextId});\n`
1536
1570
  : '';
1537
1571
  const block =
1538
- helperImport + otherHelperImports + profileImport + slotBase + st.decls.join('\n') + '\n';
1539
- if (activation !== null) {
1540
- // Native plain modules can render a local function during evaluation.
1541
- // Its slots and invocation collector must already exist at that call.
1572
+ helperImport +
1573
+ otherHelperImports +
1574
+ signalImports +
1575
+ profileImport +
1576
+ slotBase +
1577
+ st.decls.join('\n') +
1578
+ '\n' +
1579
+ signalActivation;
1580
+ if (activation !== null || signalLowering.usesSignals) {
1581
+ // Plain modules may read global signals or render during evaluation.
1582
+ // Their document capability and any render slots must already exist.
1542
1583
  // No newline is inserted, retaining the surgical pass's line mapping.
1543
1584
  const index = nativeReadActivationIndex(ast.body);
1544
1585
  st.edits.push({
1545
1586
  pos: ast.body[index]?.start ?? source.length,
1546
- text: `;${block.replace(/\n/g, ' ')}${activation}(1); `,
1587
+ text: `;${block.replace(/\n/g, ' ')}${activation === null ? '' : `${activation}(1); `}`,
1547
1588
  });
1548
1589
  }
1549
1590
  // Assemble disjoint edits once instead of copying the growing module for
@@ -1574,6 +1615,12 @@ export function slotHooks(source, id, options) {
1574
1615
  chunks.push(source.slice(0, cursor));
1575
1616
  code = chunks.reverse().join('');
1576
1617
  }
1577
- if (activation === null) code = code.endsWith('\n') ? code + block : code + '\n' + block;
1578
- return { code, map: null, ...strongHints };
1618
+ if (activation === null && !signalLowering.usesSignals)
1619
+ code = code.endsWith('\n') ? code + block : code + '\n' + block;
1620
+ return {
1621
+ code,
1622
+ map: null,
1623
+ ...(signalLowering.usesSignals || nativeReadActivation ? { streamedSignals: true } : null),
1624
+ ...strongHints,
1625
+ };
1579
1626
  }