octane 0.2.11 → 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 (264) 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/context-epoch.cjs +38 -0
  8. package/dist/cjs/css.cjs +6 -27
  9. package/dist/cjs/data-encoding.cjs +160 -0
  10. package/dist/cjs/dom-binding-handoff.cjs +93 -0
  11. package/dist/cjs/dom-binding-protocol.cjs +115 -0
  12. package/dist/cjs/dom-binding-styles.cjs +292 -0
  13. package/dist/cjs/dom-bindings.cjs +700 -0
  14. package/dist/cjs/dom-focused-move.cjs +66 -0
  15. package/dist/cjs/dom-stage.cjs +896 -0
  16. package/dist/cjs/dom-tables.cjs +4 -94
  17. package/dist/cjs/error-codes.client.generated.cjs +33 -0
  18. package/dist/cjs/error-codes.server.generated.cjs +24 -0
  19. package/dist/cjs/hydration/control-capture.cjs +244 -0
  20. package/dist/cjs/hydration/event-capture.cjs +125 -43
  21. package/dist/cjs/hydration/interaction-config.cjs +50 -2
  22. package/dist/cjs/hydration-markers.cjs +37 -0
  23. package/dist/cjs/independent-hydration-protocol.cjs +106 -0
  24. package/dist/cjs/index.cjs +9 -0
  25. package/dist/cjs/runtime.cjs +6397 -1683
  26. package/dist/cjs/runtime.server.cjs +1465 -213
  27. package/dist/cjs/server/early-signals.cjs +45 -0
  28. package/dist/cjs/server/index.cjs +47 -0
  29. package/dist/cjs/server/rpc-batch.cjs +149 -0
  30. package/dist/cjs/server/rpc-stream.cjs +146 -0
  31. package/dist/cjs/server/rpc.cjs +34 -15
  32. package/dist/cjs/server/signal-query-observation.cjs +158 -0
  33. package/dist/cjs/server/streamed-region.cjs +52 -0
  34. package/dist/cjs/server/streamed-signals.cjs +512 -0
  35. package/dist/cjs/server-call.cjs +103 -0
  36. package/dist/cjs/server-rpc-batch-client.cjs +141 -0
  37. package/dist/cjs/server-rpc-client.cjs +94 -27
  38. package/dist/cjs/server-rpc-protocol.cjs +141 -0
  39. package/dist/cjs/server-rpc-stream-client.cjs +118 -0
  40. package/dist/cjs/signals/actions.cjs +474 -0
  41. package/dist/cjs/signals/client.cjs +4 -1
  42. package/dist/cjs/signals/computations.cjs +460 -0
  43. package/dist/cjs/signals/control-binding.cjs +368 -0
  44. package/dist/cjs/signals/derived-facade.cjs +50 -0
  45. package/dist/cjs/signals/document-owner.cjs +71 -0
  46. package/dist/cjs/signals/early-values.cjs +155 -0
  47. package/dist/cjs/signals/encoding.cjs +6 -95
  48. package/dist/cjs/signals/engine.cjs +276 -12
  49. package/dist/cjs/signals/errors.cjs +18 -0
  50. package/dist/cjs/signals/facade.cjs +477 -0
  51. package/dist/cjs/signals/graph.cjs +166 -20
  52. package/dist/cjs/signals/handle-protocol.cjs +36 -0
  53. package/dist/cjs/signals/index.cjs +81 -2
  54. package/dist/cjs/signals/native-read-client.cjs +10 -0
  55. package/dist/cjs/signals/owner-context.cjs +112 -0
  56. package/dist/cjs/signals/query-attempt-observer.cjs +68 -0
  57. package/dist/cjs/signals/query-facade.cjs +72 -0
  58. package/dist/cjs/signals/read-protocol.cjs +54 -0
  59. package/dist/cjs/signals/requests.cjs +287 -14
  60. package/dist/cjs/signals/scalar-computations.cjs +76 -0
  61. package/dist/cjs/signals/server.cjs +4 -1
  62. package/dist/cjs/signals/transition-candidate.cjs +567 -0
  63. package/dist/cjs/signals/transition-state.cjs +106 -0
  64. package/dist/cjs/signals/types.cjs +50 -0
  65. package/dist/cjs/stream-protocol.cjs +3 -0
  66. package/dist/cjs/streamed-signals-protocol.cjs +159 -0
  67. package/dist/cjs/style-values.cjs +122 -0
  68. package/dist/cjs/trusted-html.cjs +30 -0
  69. package/dist/cjs/version.cjs +1 -1
  70. package/dist/class-names.d.ts +5 -0
  71. package/dist/class-names.js +25 -0
  72. package/dist/compiler/bundler.js +51 -22
  73. package/dist/compiler/compile.js +2534 -581
  74. package/dist/compiler/dom-binding-program.js +935 -0
  75. package/dist/compiler/dom-binding-request.js +57 -0
  76. package/dist/compiler/dom-bindings.js +2245 -0
  77. package/dist/compiler/hook-deps.js +759 -10
  78. package/dist/compiler/hydrate-boundaries.js +358 -15
  79. package/dist/compiler/index.d.ts +39 -2
  80. package/dist/compiler/index.js +1 -0
  81. package/dist/compiler/native-attribute-reads.js +127 -0
  82. package/dist/compiler/native-change-diagnostics.js +32 -38
  83. package/dist/compiler/native-read-diagnostics.js +69 -8
  84. package/dist/compiler/native-read-types.js +80 -34
  85. package/dist/compiler/parser.node.js +1 -1
  86. package/dist/compiler/renderer-boundaries.js +40 -0
  87. package/dist/compiler/server-context.js +118 -0
  88. package/dist/compiler/signal-attempt-reads.js +479 -0
  89. package/dist/compiler/signal-declarations.js +557 -0
  90. package/dist/compiler/signal-start-reads.js +413 -0
  91. package/dist/compiler/slot-hooks.d.ts +3 -1
  92. package/dist/compiler/slot-hooks.js +168 -34
  93. package/dist/compiler/strong-auto-memo.js +90 -0
  94. package/dist/compiler/strong-html.js +143 -0
  95. package/dist/compiler/strong-mode.js +764 -66
  96. package/dist/compiler/strong-template-policy.js +307 -0
  97. package/dist/compiler/template-ir.js +200 -0
  98. package/dist/compiler/typescript.d.ts +2 -0
  99. package/dist/compiler/typescript.js +2 -0
  100. package/dist/compiler/vite.d.ts +3 -1
  101. package/dist/compiler/vite.js +122 -4
  102. package/dist/compiler/volar.LICENSES.txt +1 -1
  103. package/dist/compiler/volar.d.ts +6 -1
  104. package/dist/compiler/volar.js +750 -1433
  105. package/dist/constants.d.ts +1 -0
  106. package/dist/constants.js +10 -0
  107. package/dist/context-epoch.d.ts +2 -0
  108. package/dist/context-epoch.js +13 -0
  109. package/dist/css.d.ts +2 -5
  110. package/dist/css.js +2 -23
  111. package/dist/data-encoding.d.ts +9 -0
  112. package/dist/data-encoding.js +134 -0
  113. package/dist/dom-binding-classes.d.ts +15 -0
  114. package/dist/dom-binding-classes.js +129 -0
  115. package/dist/dom-binding-controls.d.ts +2 -0
  116. package/dist/dom-binding-controls.js +6 -0
  117. package/dist/dom-binding-handoff.d.ts +55 -0
  118. package/dist/dom-binding-handoff.js +62 -0
  119. package/dist/dom-binding-program.d.ts +300 -0
  120. package/dist/dom-binding-program.js +1625 -0
  121. package/dist/dom-binding-projections.d.ts +15 -0
  122. package/dist/dom-binding-projections.js +187 -0
  123. package/dist/dom-binding-protocol.d.ts +13 -0
  124. package/dist/dom-binding-protocol.js +88 -0
  125. package/dist/dom-binding-signals.d.ts +21 -0
  126. package/dist/dom-binding-signals.js +109 -0
  127. package/dist/dom-binding-styles.d.ts +17 -0
  128. package/dist/dom-binding-styles.js +272 -0
  129. package/dist/dom-bindings.d.ts +89 -0
  130. package/dist/dom-bindings.js +670 -0
  131. package/dist/dom-focused-move.d.ts +6 -0
  132. package/dist/dom-focused-move.js +42 -0
  133. package/dist/dom-stage.d.ts +52 -0
  134. package/dist/dom-stage.js +872 -0
  135. package/dist/dom-tables.d.ts +1 -3
  136. package/dist/dom-tables.js +1 -91
  137. package/dist/error-codes.client.generated.d.ts +9 -0
  138. package/dist/error-codes.client.generated.js +33 -0
  139. package/dist/error-codes.server.generated.d.ts +6 -0
  140. package/dist/error-codes.server.generated.js +24 -0
  141. package/dist/hydration/control-capture.d.ts +62 -0
  142. package/dist/hydration/control-capture.js +223 -0
  143. package/dist/hydration/document-lifecycle.d.ts +23 -0
  144. package/dist/hydration/document-lifecycle.js +80 -0
  145. package/dist/hydration/event-capture.d.ts +20 -1
  146. package/dist/hydration/event-capture.js +128 -42
  147. package/dist/hydration/independent-island.d.ts +45 -0
  148. package/dist/hydration/independent-island.js +233 -0
  149. package/dist/hydration/index.d.ts +8 -1
  150. package/dist/hydration/index.js +55 -1
  151. package/dist/hydration/interaction-config.d.ts +5 -0
  152. package/dist/hydration/interaction-config.js +44 -1
  153. package/dist/hydration/stream-delivery.d.ts +32 -0
  154. package/dist/hydration/stream-delivery.js +333 -0
  155. package/dist/hydration/stream-receiver.d.ts +26 -0
  156. package/dist/hydration/stream-receiver.js +204 -0
  157. package/dist/hydration/stream-result-receiver.d.ts +70 -0
  158. package/dist/hydration/stream-result-receiver.js +246 -0
  159. package/dist/hydration/streamed-signals.d.ts +44 -0
  160. package/dist/hydration/streamed-signals.js +186 -0
  161. package/dist/hydration/types.d.ts +6 -0
  162. package/dist/hydration-markers.d.ts +7 -0
  163. package/dist/hydration-markers.js +10 -0
  164. package/dist/independent-hydration-protocol.d.ts +31 -0
  165. package/dist/independent-hydration-protocol.js +80 -0
  166. package/dist/index.d.ts +3 -0
  167. package/dist/index.js +6 -0
  168. package/dist/internal/signal-read.d.ts +1 -0
  169. package/dist/internal/signal-read.js +4 -0
  170. package/dist/jsx-runtime-strong.d.ts +33 -0
  171. package/dist/jsx-runtime.d.ts +58 -2
  172. package/dist/public-types.d.ts +15 -1
  173. package/dist/runtime.d.ts +297 -23
  174. package/dist/runtime.js +6405 -1679
  175. package/dist/runtime.server.d.ts +110 -27
  176. package/dist/runtime.server.js +1458 -213
  177. package/dist/server/early-signals.d.ts +14 -0
  178. package/dist/server/early-signals.js +34 -0
  179. package/dist/server/index.d.ts +11 -2
  180. package/dist/server/index.js +50 -1
  181. package/dist/server/rpc-batch.d.ts +17 -0
  182. package/dist/server/rpc-batch.js +129 -0
  183. package/dist/server/rpc-stream.d.ts +9 -0
  184. package/dist/server/rpc-stream.js +125 -0
  185. package/dist/server/rpc.d.ts +5 -1
  186. package/dist/server/rpc.js +37 -13
  187. package/dist/server/signal-query-observation.d.ts +3 -0
  188. package/dist/server/signal-query-observation.js +134 -0
  189. package/dist/server/streamed-region.d.ts +15 -0
  190. package/dist/server/streamed-region.js +30 -0
  191. package/dist/server/streamed-signals.d.ts +45 -0
  192. package/dist/server/streamed-signals.js +488 -0
  193. package/dist/server-call.d.ts +41 -0
  194. package/dist/server-call.js +75 -0
  195. package/dist/server-rpc-batch-client.d.ts +7 -0
  196. package/dist/server-rpc-batch-client.js +119 -0
  197. package/dist/server-rpc-client.d.ts +3 -1
  198. package/dist/server-rpc-client.js +99 -27
  199. package/dist/server-rpc-protocol.d.ts +47 -0
  200. package/dist/server-rpc-protocol.js +109 -0
  201. package/dist/server-rpc-stream-client.d.ts +5 -0
  202. package/dist/server-rpc-stream-client.js +98 -0
  203. package/dist/signals/actions.d.ts +12 -0
  204. package/dist/signals/actions.js +466 -0
  205. package/dist/signals/client.d.ts +2 -0
  206. package/dist/signals/client.js +1 -0
  207. package/dist/signals/computations.d.ts +37 -0
  208. package/dist/signals/computations.js +455 -0
  209. package/dist/signals/control-binding.d.ts +31 -0
  210. package/dist/signals/control-binding.js +353 -0
  211. package/dist/signals/derived-facade.d.ts +3 -0
  212. package/dist/signals/derived-facade.js +25 -0
  213. package/dist/signals/document-owner.d.ts +10 -0
  214. package/dist/signals/document-owner.js +43 -0
  215. package/dist/signals/early-values.d.ts +24 -0
  216. package/dist/signals/early-values.js +124 -0
  217. package/dist/signals/encoding.d.ts +1 -3
  218. package/dist/signals/encoding.js +3 -93
  219. package/dist/signals/engine.d.ts +53 -4
  220. package/dist/signals/engine.js +275 -16
  221. package/dist/signals/errors.d.ts +7 -0
  222. package/dist/signals/errors.js +16 -0
  223. package/dist/signals/facade.d.ts +74 -0
  224. package/dist/signals/facade.js +452 -0
  225. package/dist/signals/graph.d.ts +52 -6
  226. package/dist/signals/graph.js +174 -19
  227. package/dist/signals/handle-protocol.d.ts +4 -0
  228. package/dist/signals/handle-protocol.js +11 -0
  229. package/dist/signals/index.d.ts +12 -3
  230. package/dist/signals/index.js +71 -2
  231. package/dist/signals/native-read-client.d.ts +3 -0
  232. package/dist/signals/native-read-client.js +10 -0
  233. package/dist/signals/owner-context.d.ts +22 -0
  234. package/dist/signals/owner-context.js +77 -0
  235. package/dist/signals/query-attempt-observer.d.ts +49 -0
  236. package/dist/signals/query-attempt-observer.js +41 -0
  237. package/dist/signals/query-facade.d.ts +3 -0
  238. package/dist/signals/query-facade.js +49 -0
  239. package/dist/signals/read-protocol.d.ts +37 -2
  240. package/dist/signals/read-protocol.js +44 -0
  241. package/dist/signals/requests.d.ts +38 -5
  242. package/dist/signals/requests.js +298 -15
  243. package/dist/signals/scalar-computations.d.ts +2 -0
  244. package/dist/signals/scalar-computations.js +60 -0
  245. package/dist/signals/server.d.ts +2 -0
  246. package/dist/signals/server.js +1 -0
  247. package/dist/signals/transition-candidate.d.ts +71 -0
  248. package/dist/signals/transition-candidate.js +557 -0
  249. package/dist/signals/transition-state.d.ts +51 -0
  250. package/dist/signals/transition-state.js +70 -0
  251. package/dist/signals/types.d.ts +95 -5
  252. package/dist/signals/types.js +20 -0
  253. package/dist/stream-protocol.d.ts +0 -5
  254. package/dist/stream-protocol.js +3 -0
  255. package/dist/streamed-signals-protocol.d.ts +60 -0
  256. package/dist/streamed-signals-protocol.js +130 -0
  257. package/dist/style-values.d.ts +3 -0
  258. package/dist/style-values.js +96 -0
  259. package/dist/trusted-html.d.ts +17 -0
  260. package/dist/trusted-html.js +6 -0
  261. package/dist/universal-core.d.ts +6 -0
  262. package/dist/universal-core.js +214 -202
  263. package/dist/version.js +1 -1
  264. package/package.json +48 -5
@@ -0,0 +1,557 @@
1
+ import { builders as b, strongHash } from '@tsrx/core';
2
+ import { createLexicalAnalysis } from './compile-universal.js';
3
+ import { inheritHookMemoOrigin } from './inline-hook-memo.js';
4
+ import { normalizeTextTypeFilename } from './text-type-facts.js';
5
+
6
+ const SIGNAL_MODULES = new Set([
7
+ 'octane/signals',
8
+ 'octane/signals/client',
9
+ 'octane/signals/server',
10
+ ]);
11
+
12
+ const SIGNAL_FACTORIES = new Map([
13
+ ['signal$', '__signalAt'],
14
+ ['derived$', '__derivedAt'],
15
+ ['query$', '__queryAt'],
16
+ ]);
17
+
18
+ const SCALAR_UNARY_OPERATORS = new Set(['!', '+', '-', '~', 'typeof', 'void']);
19
+ const SCALAR_BINARY_OPERATORS = new Set([
20
+ '-',
21
+ '*',
22
+ '/',
23
+ '%',
24
+ '**',
25
+ '|',
26
+ '&',
27
+ '^',
28
+ '<<',
29
+ '>>',
30
+ '>>>',
31
+ '==',
32
+ '!=',
33
+ '===',
34
+ '!==',
35
+ '<',
36
+ '>',
37
+ '<=',
38
+ '>=',
39
+ 'in',
40
+ 'instanceof',
41
+ ]);
42
+
43
+ function unwrapExpression(node) {
44
+ while (
45
+ node?.type === 'TSAsExpression' ||
46
+ node?.type === 'TSTypeAssertion' ||
47
+ node?.type === 'TSSatisfiesExpression' ||
48
+ node?.type === 'TSNonNullExpression' ||
49
+ node?.type === 'ParenthesizedExpression'
50
+ )
51
+ node = node.expression;
52
+ return node;
53
+ }
54
+
55
+ // These syntax forms either return primitives or throw. Type annotations and
56
+ // calls (even an unshadowed, but globally replaceable String) are not proof.
57
+ function isScalarResult(expression) {
58
+ const node = unwrapExpression(expression);
59
+ if (!node) return false;
60
+ if (node.type === 'Literal') {
61
+ return (
62
+ node.regex === undefined &&
63
+ (node.value === null || ['string', 'number', 'boolean', 'bigint'].includes(typeof node.value))
64
+ );
65
+ }
66
+ if (node.type === 'TemplateLiteral') return true;
67
+ if (node.type === 'UnaryExpression') return SCALAR_UNARY_OPERATORS.has(node.operator);
68
+ if (node.type === 'BinaryExpression') return SCALAR_BINARY_OPERATORS.has(node.operator);
69
+ if (node.type === 'ConditionalExpression') {
70
+ return isScalarResult(node.consequent) && isScalarResult(node.alternate);
71
+ }
72
+ if (node.type === 'LogicalExpression') {
73
+ return isScalarResult(node.left) && isScalarResult(node.right);
74
+ }
75
+ return false;
76
+ }
77
+
78
+ function declarationHelper(factory, call) {
79
+ if (factory !== 'derived$') return SIGNAL_FACTORIES.get(factory);
80
+ const args = call.arguments ?? [];
81
+ const compute = unwrapExpression(args[0]);
82
+ if (
83
+ (compute?.type !== 'ArrowFunctionExpression' && compute?.type !== 'FunctionExpression') ||
84
+ compute.params.length !== 0
85
+ )
86
+ return '__derivedAt';
87
+ const options = unwrapExpression(args[1]);
88
+ if (options !== undefined) {
89
+ // A stable key does not change the result proof. Unknown options, spreads
90
+ // and accessors can change the sync assertion, so retain the general path.
91
+ if (options.type !== 'ObjectExpression') return '__derivedAt';
92
+ let sync;
93
+ const seen = new Set();
94
+ for (const property of options.properties) {
95
+ const key = property.key?.name ?? property.key?.value;
96
+ if (
97
+ property.type !== 'Property' ||
98
+ property.kind !== 'init' ||
99
+ property.computed ||
100
+ seen.has(key)
101
+ )
102
+ return '__derivedAt';
103
+ seen.add(key);
104
+ if (key === 'key' && validLiteralKey(property.value)) continue;
105
+ if (key !== 'sync' || property.value?.type !== 'Literal') return '__derivedAt';
106
+ sync = property.value.value;
107
+ }
108
+ if (seen.has('sync')) return sync === true ? '__derivedScalarAt' : '__derivedAt';
109
+ }
110
+ if (compute.async || compute.generator) return '__derivedAt';
111
+ const result =
112
+ compute.body.type === 'BlockStatement'
113
+ ? compute.body.body.length === 1 && compute.body.body[0].type === 'ReturnStatement'
114
+ ? compute.body.body[0].argument
115
+ : undefined
116
+ : compute.body;
117
+ return isScalarResult(result) ? '__derivedScalarAt' : '__derivedAt';
118
+ }
119
+
120
+ function validLiteralKey(value) {
121
+ return value?.type === 'Literal' && typeof value.value === 'string' && value.value.trim() !== '';
122
+ }
123
+
124
+ // Declarations create lazy descriptors, not live cells. Only omit construction
125
+ // when its validation and eager option reads are provably unobservable; a PURE
126
+ // call still evaluates any effectful arguments. Unknown callbacks, observable
127
+ // option reads, and malformed overloads must keep their effects and diagnostics.
128
+ function pureSignalDeclaration(factory, call) {
129
+ const args = call.arguments ?? [];
130
+ if (args.some((argument) => argument.type === 'SpreadElement')) return false;
131
+ const functionAt = (index) => {
132
+ const value = unwrapExpression(args[index]);
133
+ return value?.type === 'ArrowFunctionExpression' || value?.type === 'FunctionExpression';
134
+ };
135
+ const count = factory === 'query$' ? 2 : 1;
136
+ if (args.length < count || args.length > count + 1) return false;
137
+ if (factory !== 'signal$' && !functionAt(0)) return false;
138
+ if (factory === 'query$' && !functionAt(1)) return false;
139
+ const options = unwrapExpression(args[count]);
140
+ if (options === undefined || (options.type === 'Literal' && options.value === null)) return true;
141
+ if (options.type !== 'ObjectExpression') return false;
142
+ let ownsKey = false;
143
+ for (const property of options.properties) {
144
+ if (property.type !== 'Property' || property.computed) return false;
145
+ const key = property.key?.name ?? property.key?.value;
146
+ // sync is read only when a general derived cell starts, never at declaration.
147
+ if (factory === 'derived$' && key === 'sync') continue;
148
+ if (property.kind !== 'init') return false;
149
+ if (key === 'key' && validLiteralKey(property.value)) {
150
+ ownsKey = true;
151
+ continue;
152
+ }
153
+ if (
154
+ factory === 'query$' &&
155
+ key === 'kind' &&
156
+ property.value?.type === 'Literal' &&
157
+ (property.value.value === 'promise' || property.value.value === 'stream')
158
+ )
159
+ continue;
160
+ return false;
161
+ }
162
+ // Without an own field the declaration can observe an inherited key accessor.
163
+ return ownsKey;
164
+ }
165
+
166
+ const AST_METADATA = new Set([
167
+ 'loc',
168
+ 'start',
169
+ 'end',
170
+ 'range',
171
+ 'metadata',
172
+ 'parent',
173
+ 'leadingComments',
174
+ 'trailingComments',
175
+ 'innerComments',
176
+ 'comments',
177
+ ]);
178
+
179
+ function identifierNames(root) {
180
+ const names = new Set();
181
+ const seen = new WeakSet();
182
+ function visit(node) {
183
+ if (node === null || typeof node !== 'object' || seen.has(node)) return;
184
+ seen.add(node);
185
+ if (Array.isArray(node)) {
186
+ for (const child of node) visit(child);
187
+ return;
188
+ }
189
+ if (node.type === 'Identifier') names.add(node.name);
190
+ for (const key in node) {
191
+ if (!AST_METADATA.has(key) && !key.startsWith('_octane')) visit(node[key]);
192
+ }
193
+ }
194
+ visit(root);
195
+ return names;
196
+ }
197
+
198
+ function allocateName(used, preferred) {
199
+ let name = preferred;
200
+ let suffix = 0;
201
+ while (used.has(name)) name = `${preferred}$${++suffix}`;
202
+ used.add(name);
203
+ return name;
204
+ }
205
+
206
+ function functionOwner(node, parent) {
207
+ let name = node.id?.name;
208
+ if (
209
+ name === undefined &&
210
+ parent?.type === 'VariableDeclarator' &&
211
+ parent.id?.type === 'Identifier'
212
+ ) {
213
+ name = parent.id.name;
214
+ }
215
+ if (name === undefined && parent?.type === 'Property' && parent.computed !== true) {
216
+ name = parent.key?.name ?? parent.key?.value;
217
+ }
218
+ return `${name ?? '<anonymous>'}@${node.start ?? 0}`;
219
+ }
220
+
221
+ function lexicalOwners(root) {
222
+ const owners = new WeakMap();
223
+ const seen = new WeakSet();
224
+ function visit(node, path, parent = null) {
225
+ if (node === null || typeof node !== 'object' || seen.has(node)) return;
226
+ seen.add(node);
227
+ if (Array.isArray(node)) {
228
+ for (const child of node) visit(child, path, parent);
229
+ return;
230
+ }
231
+ let childPath = path;
232
+ if (
233
+ node.type === 'FunctionDeclaration' ||
234
+ node.type === 'FunctionExpression' ||
235
+ node.type === 'ArrowFunctionExpression'
236
+ ) {
237
+ childPath = [...path, functionOwner(node, parent)];
238
+ }
239
+ owners.set(node, childPath);
240
+ for (const key in node) {
241
+ if (!AST_METADATA.has(key) && !key.startsWith('_octane')) visit(node[key], childPath, node);
242
+ }
243
+ }
244
+ visit(root, ['module']);
245
+ return owners;
246
+ }
247
+
248
+ function mapAst(node, replace) {
249
+ if (node === null || typeof node !== 'object') return node;
250
+ if (Array.isArray(node)) {
251
+ let out = null;
252
+ for (let i = 0; i < node.length; i++) {
253
+ const mapped = mapAst(node[i], replace);
254
+ if (out === null && mapped !== node[i]) out = node.slice(0, i);
255
+ if (out !== null) out.push(mapped);
256
+ }
257
+ return out ?? node;
258
+ }
259
+ const replacement = replace(node);
260
+ if (replacement !== null) node = replacement;
261
+ let out = null;
262
+ for (const key in node) {
263
+ if (AST_METADATA.has(key) || key.startsWith('_octane')) continue;
264
+ const child = node[key];
265
+ if (child === null || typeof child !== 'object') continue;
266
+ const mapped = mapAst(child, replace);
267
+ if (mapped !== child) {
268
+ out ??= { ...node };
269
+ out[key] = mapped;
270
+ }
271
+ }
272
+ return out ?? node;
273
+ }
274
+
275
+ function propertyName(member) {
276
+ if (member.computed === true) {
277
+ return member.property?.type === 'Literal' && typeof member.property.value === 'string'
278
+ ? member.property.value
279
+ : null;
280
+ }
281
+ return member.property?.name ?? null;
282
+ }
283
+
284
+ function signalSite(filename, owner, node) {
285
+ const position = node.start ?? `${node.loc?.start?.line ?? 0}:${node.loc?.start?.column ?? 0}`;
286
+ const scope = owner.length === 1 ? 'g' : 'i';
287
+ return `${scope}:${strongHash(
288
+ `octane:signal-site:2\0${filename}\0${owner.join('/')}\0${position}`,
289
+ )}`;
290
+ }
291
+
292
+ // Arguments may start inside parentheses or at a nested callee's replacement
293
+ // offset. Replace the call delimiter itself so source edits never overlap.
294
+ function callOpenParen(node, source) {
295
+ let pos = node.typeArguments?.end ?? node.callee.end;
296
+ while (pos < node.end) {
297
+ if (
298
+ /\s/.test(source[pos]) ||
299
+ source[pos] === ')' ||
300
+ source[pos] === '?' ||
301
+ source[pos] === '.'
302
+ ) {
303
+ pos++;
304
+ } else if (source.startsWith('/*', pos)) {
305
+ pos = source.indexOf('*/', pos + 2) + 2;
306
+ } else if (source.startsWith('//', pos)) {
307
+ while (pos < node.end && source[pos] !== '\n' && source[pos] !== '\r') pos++;
308
+ } else return source[pos] === '(' ? pos : -1;
309
+ }
310
+ return -1;
311
+ }
312
+
313
+ /**
314
+ * Give owner-facade signal declarations a client/server-stable authored site.
315
+ * Existing explicit Scope methods are deliberately outside this transform.
316
+ */
317
+ export function lowerSignalDeclarations(ast, filename) {
318
+ const cleanFilename = normalizeTextTypeFilename(filename) ?? filename;
319
+ const lexical = createLexicalAnalysis(ast);
320
+ const owners = lexicalOwners(ast);
321
+ const usedNames = identifierNames(ast);
322
+ const namedImports = new Map();
323
+ const namespaceImports = new Map();
324
+ const importRecords = new Map();
325
+
326
+ for (const statement of ast.body ?? []) {
327
+ if (
328
+ statement.type !== 'ImportDeclaration' ||
329
+ statement.importKind === 'type' ||
330
+ !SIGNAL_MODULES.has(statement.source?.value)
331
+ ) {
332
+ continue;
333
+ }
334
+ for (const specifier of statement.specifiers ?? []) {
335
+ if (specifier.importKind === 'type') continue;
336
+ if (specifier.type === 'ImportNamespaceSpecifier') {
337
+ namespaceImports.set(specifier.local.name, statement.source.value);
338
+ continue;
339
+ }
340
+ if (specifier.type !== 'ImportSpecifier') continue;
341
+ const imported = specifier.imported?.name ?? specifier.imported?.value;
342
+ if (SIGNAL_FACTORIES.has(imported)) {
343
+ namedImports.set(specifier.local.name, {
344
+ declaration: statement,
345
+ factory: imported,
346
+ source: statement.source.value,
347
+ });
348
+ }
349
+ }
350
+ }
351
+
352
+ function helperFor(record, call) {
353
+ let helpers = importRecords.get(record.declaration);
354
+ if (helpers === undefined) importRecords.set(record.declaration, (helpers = new Map()));
355
+ const imported = declarationHelper(record.factory, call);
356
+ let local = helpers.get(imported);
357
+ if (local === undefined) {
358
+ local = allocateName(usedNames, `_$${imported}`);
359
+ helpers.set(imported, local);
360
+ }
361
+ return b.id(local);
362
+ }
363
+
364
+ function trustedFactory(node) {
365
+ const callee = node.callee;
366
+ const scope = lexical.nodeScopes.get(callee) ?? lexical.rootScope;
367
+ if (callee?.type === 'Identifier') {
368
+ const record = namedImports.get(callee.name);
369
+ if (record === undefined) return null;
370
+ const binding = lexical.resolveBinding(scope, callee.name);
371
+ if (binding?.scope !== lexical.rootScope || binding.importSource?.value !== record.source) {
372
+ return null;
373
+ }
374
+ return { callee: helperFor(record, node), factory: record.factory };
375
+ }
376
+ if (
377
+ (callee?.type === 'MemberExpression' || callee?.type === 'OptionalMemberExpression') &&
378
+ callee.object?.type === 'Identifier'
379
+ ) {
380
+ const factory = propertyName(callee);
381
+ if (!SIGNAL_FACTORIES.has(factory)) return null;
382
+ const source = namespaceImports.get(callee.object.name);
383
+ const binding = lexical.resolveBinding(scope, callee.object.name);
384
+ if (
385
+ source === undefined ||
386
+ binding?.scope !== lexical.rootScope ||
387
+ binding.importSource?.value !== source
388
+ ) {
389
+ return null;
390
+ }
391
+ return {
392
+ callee: b.member(b.id(callee.object.name), declarationHelper(factory, node)),
393
+ factory,
394
+ };
395
+ }
396
+ return null;
397
+ }
398
+
399
+ let changed = false;
400
+ let lowered = mapAst(ast, (node) => {
401
+ if (node.type !== 'CallExpression' && node.type !== 'OptionalCallExpression') return null;
402
+ const trusted = trustedFactory(node);
403
+ if (trusted === null) return null;
404
+ changed = true;
405
+ const site = signalSite(cleanFilename, owners.get(node) ?? ['module'], node);
406
+ return {
407
+ ...node,
408
+ ...(pureSignalDeclaration(trusted.factory, node) ? { __octanePure: true } : null),
409
+ callee: inheritHookMemoOrigin(trusted.callee, node.callee),
410
+ arguments: [
411
+ inheritHookMemoOrigin(b.literal(site, JSON.stringify(site)), node),
412
+ ...(node.arguments ?? []),
413
+ ],
414
+ };
415
+ });
416
+
417
+ if (!changed) return ast;
418
+ lowered = {
419
+ ...lowered,
420
+ _octaneSignalDeclarations: true,
421
+ body: lowered.body.map((statement) => {
422
+ const helpers = importRecords.get(statement);
423
+ if (helpers === undefined || helpers.size === 0) return statement;
424
+ const generated = [...helpers].map(([imported, local]) =>
425
+ inheritHookMemoOrigin(b.import_specifier(imported, local), statement),
426
+ );
427
+ return { ...statement, specifiers: [...statement.specifiers, ...generated] };
428
+ }),
429
+ };
430
+ return lowered;
431
+ }
432
+
433
+ /**
434
+ * Plain `.ts`/`.js` helpers use the compiler's surgical source pass rather than
435
+ * the full TSRX printer. Return byte-offset edits and collision-safe imports so
436
+ * that pass can share the exact declaration identity contract without a second
437
+ * parse/print cycle.
438
+ */
439
+ export function signalDeclarationSourceEdits(ast, filename, source) {
440
+ const cleanFilename = normalizeTextTypeFilename(filename) ?? filename;
441
+ const lexical = createLexicalAnalysis(ast);
442
+ const owners = lexicalOwners(ast);
443
+ const usedNames = identifierNames(ast);
444
+ const namedImports = new Map();
445
+ const namespaceImports = new Map();
446
+ const helpers = new Map();
447
+
448
+ for (const statement of ast.body ?? []) {
449
+ if (
450
+ statement.type !== 'ImportDeclaration' ||
451
+ statement.importKind === 'type' ||
452
+ !SIGNAL_MODULES.has(statement.source?.value)
453
+ ) {
454
+ continue;
455
+ }
456
+ for (const specifier of statement.specifiers ?? []) {
457
+ if (specifier.importKind === 'type') continue;
458
+ if (specifier.type === 'ImportNamespaceSpecifier') {
459
+ namespaceImports.set(specifier.local.name, statement.source.value);
460
+ continue;
461
+ }
462
+ if (specifier.type !== 'ImportSpecifier') continue;
463
+ const imported = specifier.imported?.name ?? specifier.imported?.value;
464
+ if (SIGNAL_FACTORIES.has(imported)) {
465
+ namedImports.set(specifier.local.name, {
466
+ factory: imported,
467
+ source: statement.source.value,
468
+ });
469
+ }
470
+ }
471
+ }
472
+
473
+ const helperFor = (record, call) => {
474
+ const imported = declarationHelper(record.factory, call);
475
+ const key = `${record.source}\0${imported}`;
476
+ let helper = helpers.get(key);
477
+ if (helper === undefined) {
478
+ helper = {
479
+ imported,
480
+ local: allocateName(usedNames, `_$${imported}`),
481
+ source: record.source,
482
+ };
483
+ helpers.set(key, helper);
484
+ }
485
+ return helper.local;
486
+ };
487
+ const edits = [];
488
+ const seen = new WeakSet();
489
+ const visit = (node) => {
490
+ if (node === null || typeof node !== 'object' || seen.has(node)) return;
491
+ seen.add(node);
492
+ if (Array.isArray(node)) {
493
+ for (const child of node) visit(child);
494
+ return;
495
+ }
496
+ if (node.type === 'CallExpression' || node.type === 'OptionalCallExpression') {
497
+ const callee = node.callee;
498
+ const scope = lexical.nodeScopes.get(callee) ?? lexical.rootScope;
499
+ let replacement = null;
500
+ let pure = false;
501
+ if (callee?.type === 'Identifier') {
502
+ const record = namedImports.get(callee.name);
503
+ const binding = lexical.resolveBinding(scope, callee.name);
504
+ if (
505
+ record !== undefined &&
506
+ binding?.scope === lexical.rootScope &&
507
+ binding.importSource?.value === record.source
508
+ ) {
509
+ replacement = helperFor(record, node);
510
+ pure = pureSignalDeclaration(record.factory, node);
511
+ }
512
+ } else if (
513
+ (callee?.type === 'MemberExpression' || callee?.type === 'OptionalMemberExpression') &&
514
+ callee.object?.type === 'Identifier'
515
+ ) {
516
+ const factory = propertyName(callee);
517
+ const importSource = namespaceImports.get(callee.object.name);
518
+ const binding = lexical.resolveBinding(scope, callee.object.name);
519
+ if (
520
+ SIGNAL_FACTORIES.has(factory) &&
521
+ importSource !== undefined &&
522
+ binding?.scope === lexical.rootScope &&
523
+ binding.importSource?.value === importSource
524
+ ) {
525
+ replacement = `${callee.object.name}.${declarationHelper(factory, node)}`;
526
+ pure = pureSignalDeclaration(factory, node);
527
+ }
528
+ }
529
+ if (replacement !== null) {
530
+ const opening = callOpenParen(node, source);
531
+ if (opening === -1) return;
532
+ edits.push({
533
+ pos: callee.start,
534
+ end: callee.end,
535
+ text: `${pure ? '/* @__PURE__ */ ' : ''}${replacement}`,
536
+ });
537
+ const first = node.arguments?.[0];
538
+ const site = signalSite(cleanFilename, owners.get(node) ?? ['module'], node);
539
+ edits.push({
540
+ pos: opening,
541
+ end: opening + 1,
542
+ text: `(${JSON.stringify(site)}${first === undefined ? '' : ', '}`,
543
+ });
544
+ }
545
+ }
546
+ for (const key in node) {
547
+ if (!AST_METADATA.has(key) && !key.startsWith('_octane')) visit(node[key]);
548
+ }
549
+ };
550
+ visit(ast);
551
+ return {
552
+ edits,
553
+ imports: [...helpers.values()],
554
+ usedNames,
555
+ usesSignals: edits.length > 0,
556
+ };
557
+ }