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,127 @@
1
+ // Direct native bindings recognize capability syntax, not structural `.get`
2
+ // methods. Share this syntax boundary between runtime and virtual TSX lowering.
3
+ function unwrapExpression(node) {
4
+ while (
5
+ node &&
6
+ (node.type === 'TSAsExpression' ||
7
+ node.type === 'TSTypeAssertion' ||
8
+ node.type === 'TSNonNullExpression' ||
9
+ node.type === 'TSSatisfiesExpression' ||
10
+ node.type === 'TSInstantiationExpression' ||
11
+ node.type === 'ChainExpression' ||
12
+ node.type === 'ParenthesizedExpression')
13
+ ) {
14
+ node = node.expression;
15
+ }
16
+ return node;
17
+ }
18
+
19
+ export function isDirectSignalHandleExpression(node) {
20
+ node = unwrapExpression(node);
21
+ if (!node) return false;
22
+ if (node.type === 'Identifier') return node.name.endsWith('$');
23
+ if (node.type === 'MemberExpression' || node.type === 'OptionalMemberExpression') {
24
+ const name = node.computed
25
+ ? node.property?.type === 'Literal' && typeof node.property.value === 'string'
26
+ ? node.property.value
27
+ : null
28
+ : node.property?.name;
29
+ return typeof name === 'string' && name.endsWith('$');
30
+ }
31
+ if (node.type === 'CallExpression' || node.type === 'OptionalCallExpression') {
32
+ const callee = unwrapExpression(node.callee);
33
+ const name =
34
+ callee?.type === 'Identifier'
35
+ ? callee.name
36
+ : callee?.type === 'MemberExpression' || callee?.type === 'OptionalMemberExpression'
37
+ ? callee.computed
38
+ ? callee.property?.value
39
+ : callee.property?.name
40
+ : null;
41
+ return /^_?\$?__(?:signal|derived|query)At$/.test(name ?? '');
42
+ }
43
+ return false;
44
+ }
45
+
46
+ function rejectNestedHandles(node) {
47
+ if (!node || typeof node !== 'object') return;
48
+ if (isDirectSignalHandleExpression(node))
49
+ throw new Error(
50
+ 'Native JSX signal projections do not support handles inside callbacks, constructors, or tagged templates.',
51
+ );
52
+ for (const key of Object.keys(node)) {
53
+ if (!['metadata', 'loc', 'start', 'end'].includes(key)) rejectNestedHandles(node[key]);
54
+ }
55
+ }
56
+
57
+ /** Only an explicitly configured native attribute admits these sampled reads. */
58
+ export function lowerNativeAttributeReads(expression, read) {
59
+ let reactive = false;
60
+ const rewrite = (node) => {
61
+ if (!node || typeof node !== 'object') return node;
62
+ if (Array.isArray(node)) return node.map(rewrite);
63
+ if (isDirectSignalHandleExpression(node)) {
64
+ reactive = true;
65
+ return read(node);
66
+ }
67
+ if (node.type === 'MemberExpression' || node.type === 'OptionalMemberExpression') {
68
+ if (isDirectSignalHandleExpression(node.object))
69
+ throw new Error(
70
+ 'Native JSX signal projections require .get() before reading a signal payload property.',
71
+ );
72
+ return {
73
+ ...node,
74
+ object: rewrite(node.object),
75
+ property: node.computed ? rewrite(node.property) : node.property,
76
+ };
77
+ }
78
+ if (node.type === 'CallExpression' || node.type === 'OptionalCallExpression') {
79
+ const callee = unwrapExpression(node.callee);
80
+ const handleGet =
81
+ (callee?.type === 'MemberExpression' || callee?.type === 'OptionalMemberExpression') &&
82
+ (callee.computed ? callee.property?.value : callee.property?.name) === 'get' &&
83
+ isDirectSignalHandleExpression(callee.object);
84
+ if (handleGet) reactive = true;
85
+ return {
86
+ ...node,
87
+ // Keep a method call as a member call so `this` and evaluation order
88
+ // survive a computed style selection. Explicit get keeps its receiver.
89
+ callee:
90
+ !handleGet &&
91
+ (callee?.type === 'MemberExpression' || callee?.type === 'OptionalMemberExpression')
92
+ ? rewrite(node.callee)
93
+ : node.callee,
94
+ arguments: node.arguments.map(rewrite),
95
+ };
96
+ }
97
+ if (
98
+ [
99
+ 'FunctionExpression',
100
+ 'ArrowFunctionExpression',
101
+ 'ClassExpression',
102
+ 'NewExpression',
103
+ 'TaggedTemplateExpression',
104
+ ].includes(node.type)
105
+ ) {
106
+ rejectNestedHandles(node);
107
+ return node;
108
+ }
109
+ if (node.type === 'Property') {
110
+ const value = rewrite(node.value);
111
+ return value === node.value ? node : { ...node, value, shorthand: false };
112
+ }
113
+ let result = node;
114
+ for (const key of Object.keys(node)) {
115
+ if (['metadata', 'loc', 'start', 'end'].includes(key)) continue;
116
+ const value = node[key];
117
+ if (!value || typeof value !== 'object') continue;
118
+ const next = rewrite(value);
119
+ if (next !== value) {
120
+ if (result === node) result = { ...node };
121
+ result[key] = next;
122
+ }
123
+ }
124
+ return result;
125
+ };
126
+ return { expression: rewrite(expression), reactive };
127
+ }
@@ -5,9 +5,11 @@ export { NATIVE_SIGNAL_NAME, NATIVE_MEMO_READ } from './native-read-facts.js';
5
5
 
6
6
  const SIGNALS_MODULE = 'octane/signals';
7
7
  const CLIENT_MODULE = 'octane/signals/client';
8
+ const SIGNAL_MODULES = new Set([SIGNALS_MODULE, CLIENT_MODULE, 'octane/signals/server']);
8
9
  const LOCAL_MODULES = new Set([CLIENT_MODULE, 'octane/signals/server']);
9
10
  const HANDLE_TYPES = new Set(['SignalHandle', 'Resource', 'WritableSignal', 'DerivedSignal']);
10
- const SIGNAL_METHODS = new Set(['signal$', 'derived$', 'asyncSignal$']);
11
+ const SIGNAL_METHODS = new Set(['signal$', 'derived$']);
12
+ const SIGNAL_FACTORIES = new Set(['signal$', 'derived$', 'query$', 'createResource']);
11
13
  const LOCAL_HOOKS = new Set(['useSignal$']);
12
14
  const FUNCTION_TYPES = new Set([
13
15
  'ArrowFunctionExpression',
@@ -189,8 +191,12 @@ export function analyzeNativeReadDiagnostics(ast, source, filename, options = {}
189
191
  }
190
192
  }
191
193
  function importedValue(module, name) {
194
+ if (module === 'octane/behavior' && (name === 'adoptBindings' || name === 'mountBindings'))
195
+ return { kind: 'builtin', name, bindingSource: true };
192
196
  if (module === SIGNALS_MODULE && name === 'createScope')
193
197
  return { kind: 'builtin', name: 'createScope' };
198
+ if (SIGNAL_MODULES.has(module) && SIGNAL_FACTORIES.has(name))
199
+ return { kind: 'builtin', name, capability: true };
194
200
  if (LOCAL_MODULES.has(module) && name === 'useDerived$')
195
201
  return { kind: 'unsupportedHook', name };
196
202
  if (LOCAL_MODULES.has(module) && LOCAL_HOOKS.has(name))
@@ -382,7 +388,12 @@ export function analyzeNativeReadDiagnostics(ast, source, filename, options = {}
382
388
  if (callee.kind === 'function') return callee.result;
383
389
  if (callee.kind === 'builtin') {
384
390
  if (callee.name === 'createScope') return SCOPE;
385
- if (SIGNAL_METHODS.has(callee.name) || LOCAL_HOOKS.has(callee.name)) return HANDLE;
391
+ if (
392
+ SIGNAL_METHODS.has(callee.name) ||
393
+ SIGNAL_FACTORIES.has(callee.name) ||
394
+ LOCAL_HOOKS.has(callee.name)
395
+ )
396
+ return HANDLE;
386
397
  }
387
398
  return UNKNOWN;
388
399
  }
@@ -464,6 +475,16 @@ export function analyzeNativeReadDiagnostics(ast, source, filename, options = {}
464
475
  }
465
476
  }
466
477
  } while (changed);
478
+ // BindingSource uses a fixed protocol key even when its snapshot carries
479
+ // native handles. Exempt only callbacks reached through the source argument
480
+ // of a directly imported bindings API, never arbitrary getSnapshot methods.
481
+ const bindingSnapshots = new WeakSet();
482
+ for (const node of nodes) {
483
+ if (node.type !== 'CallExpression' && node.type !== 'OptionalCallExpression') continue;
484
+ if (recordFor(unwrap(node.callee))?.forced?.bindingSource !== true) continue;
485
+ const snapshot = memberValue(valueOf(node.arguments[2]), 'getSnapshot');
486
+ if (snapshot.kind === 'function') bindingSnapshots.add(snapshot.fn);
487
+ }
467
488
  function containsHandle(value, active = new Set()) {
468
489
  if (value.kind === 'handle') return true;
469
490
  if (value.kind !== 'object' || active.has(value)) return false;
@@ -510,6 +531,15 @@ export function analyzeNativeReadDiagnostics(ast, source, filename, options = {}
510
531
  `Native signal handles and functions exposing handles or live reads must end in $. Rename ${JSON.stringify(name)} to ${JSON.stringify(name + '$')}; sampled values keep ordinary names.`,
511
532
  );
512
533
  }
534
+ function createsCapability(node, value) {
535
+ node = unwrap(node);
536
+ if (!node || !isCapability(value)) return false;
537
+ return (
538
+ node.type === 'CallExpression' ||
539
+ node.type === 'OptionalCallExpression' ||
540
+ FUNCTION_TYPES.has(node.type)
541
+ );
542
+ }
513
543
  function isDomStyleProperty(node) {
514
544
  let object = parents.get(node);
515
545
  let container = parents.get(object);
@@ -533,7 +563,13 @@ export function analyzeNativeReadDiagnostics(ast, source, filename, options = {}
533
563
  }
534
564
  for (const record of allRecords) {
535
565
  const value = recordValue(record);
536
- for (const declaration of record.declarations) checkName(declaration, record.name, value);
566
+ if (
567
+ record.expressions.some(({ expression }) =>
568
+ createsCapability(expression, valueOf(expression)),
569
+ )
570
+ ) {
571
+ for (const declaration of record.declarations) checkName(declaration, record.name, value);
572
+ }
537
573
  }
538
574
  for (const node of nodes) {
539
575
  if (
@@ -547,14 +583,17 @@ export function analyzeNativeReadDiagnostics(ast, source, filename, options = {}
547
583
  'useDerived$ is not available. Create derived$ on an explicitly owned Scope and pass the handle to the component.',
548
584
  );
549
585
  } else if (node.type === 'Property' && parents.get(node)?.type === 'ObjectExpression') {
550
- if (!isDomStyleProperty(node))
551
- checkName(node.key, propertyName(node.key, node.computed), valueOf(node.value));
586
+ const value = valueOf(node.value);
587
+ const bindingSnapshot =
588
+ propertyName(node.key, node.computed) === 'getSnapshot' &&
589
+ bindingSnapshots.has(unwrap(node.value));
590
+ if (!isDomStyleProperty(node) && !bindingSnapshot && createsCapability(node.value, value))
591
+ checkName(node.key, propertyName(node.key, node.computed), value);
552
592
  } else if (node.type === 'AssignmentExpression' && node.operator === '=') {
553
593
  const left = unwrap(node.left);
554
- if (left?.type === 'MemberExpression')
555
- checkName(left.property, propertyName(left.property, left.computed), valueOf(node.right));
556
- } else if (node.type === 'ExportSpecifier' && parents.get(node)?.source == null) {
557
- checkName(node.exported, node.exported.name ?? node.exported.value, valueOf(node.local));
594
+ const value = valueOf(node.right);
595
+ if (left?.type === 'MemberExpression' && createsCapability(node.right, value))
596
+ checkName(left.property, propertyName(left.property, left.computed), value);
558
597
  } else if (node.type === 'CallExpression' || node.type === 'OptionalCallExpression') {
559
598
  const callee = valueOf(node.callee);
560
599
  if (callee.kind === 'builtin' && callee.name === 'useMemo') {
@@ -57,6 +57,22 @@ export function validateNativeSignalNames(program, file) {
57
57
  if (property) recordSignatures(symbolType(property), nativeReadSignatures);
58
58
  }
59
59
  }
60
+ function recordFactory(exports, name) {
61
+ const factory = exports.get(name);
62
+ const type = factory && symbolType(factory);
63
+ if (!type) return;
64
+ for (const signature of checker.getSignaturesOfType(type, ts.SignatureKind.Call)) {
65
+ const returned = checker.getReturnTypeOfSignature(signature);
66
+ recordReads(returned, ['get', 'latest', 'snapshot']);
67
+ for (const property of checker.getPropertiesOfType(returned)) {
68
+ for (const declaration of property.declarations ?? []) {
69
+ if (!declaration.name || !ts.isComputedPropertyName(declaration.name)) continue;
70
+ const marker = canonical(checker.getSymbolAtLocation(declaration.name.expression));
71
+ if (marker?.name === 'SIGNAL_HANDLE') brands.add(marker);
72
+ }
73
+ }
74
+ }
75
+ }
60
76
  function inspectModule(symbol, request) {
61
77
  symbol = canonical(symbol);
62
78
  if (!symbol || inspectedModules.has(symbol)) return;
@@ -77,24 +93,14 @@ export function validateNativeSignalNames(program, file) {
77
93
  if (type)
78
94
  for (const signature of checker.getSignaturesOfType(type, ts.SignatureKind.Call))
79
95
  recordReads(checker.getReturnTypeOfSignature(signature), ['get']);
96
+ for (const name of ['signal$', 'derived$', 'query$']) recordFactory(exports, name);
80
97
  } else if (NATIVE_MODULES.has(request)) {
81
98
  // A project may import only the optional local hook entry. Follow the
82
99
  // trusted export's actual return type to the same nominal declaration;
83
100
  // do not require a redundant bare-engine import or brand lookalikes.
84
- const hook = exports.get('useSignal$');
85
- const type = hook && symbolType(hook);
86
- if (type)
87
- for (const signature of checker.getSignaturesOfType(type, ts.SignatureKind.Call)) {
88
- const returned = checker.getReturnTypeOfSignature(signature);
89
- recordReads(returned, ['get', 'latest', 'snapshot']);
90
- for (const property of checker.getPropertiesOfType(returned)) {
91
- for (const declaration of property.declarations ?? []) {
92
- if (!declaration.name || !ts.isComputedPropertyName(declaration.name)) continue;
93
- const marker = canonical(checker.getSymbolAtLocation(declaration.name.expression));
94
- if (marker?.name === 'SIGNAL_HANDLE') brands.add(marker);
95
- }
96
- }
97
- }
101
+ for (const name of ['useSignal$', 'signal$', 'derived$', 'query$']) {
102
+ recordFactory(exports, name);
103
+ }
98
104
  } else if (request === 'octane') {
99
105
  const memo = exports.get('useMemo');
100
106
  if (memo) recordSignatures(symbolType(memo), memoSignatures);
@@ -265,10 +271,27 @@ export function validateNativeSignalNames(program, file) {
265
271
  `Native signal handles and functions exposing handles or live reads must end in $. Rename ${JSON.stringify(text)} to ${JSON.stringify(text + '$')}; sampled values keep ordinary names.`,
266
272
  );
267
273
  }
268
- function valueExport(node) {
269
- if (node.isTypeOnly || node.parent.parent.isTypeOnly) return false;
270
- const symbol = canonical(checker.getSymbolAtLocation(node.name));
271
- return symbol && (symbol.flags & ts.SymbolFlags.Value) !== 0;
274
+ function unwrapExpression(node) {
275
+ while (
276
+ node &&
277
+ (ts.isParenthesizedExpression(node) ||
278
+ ts.isAsExpression(node) ||
279
+ ts.isTypeAssertionExpression(node) ||
280
+ ts.isNonNullExpression(node) ||
281
+ ts.isSatisfiesExpression(node))
282
+ ) {
283
+ node = node.expression;
284
+ }
285
+ return node;
286
+ }
287
+ function createsCapability(node) {
288
+ node = unwrapExpression(node);
289
+ if (!node) return false;
290
+ if (ts.isCallExpression(node)) return exposesHandle(checker.getTypeAtLocation(node));
291
+ return (
292
+ (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&
293
+ (exposesHandle(checker.getTypeAtLocation(node)) || exposesLiveRead(node))
294
+ );
272
295
  }
273
296
  function isDomStyleProperty(node) {
274
297
  let object = node.parent;
@@ -297,17 +320,15 @@ export function validateNativeSignalNames(program, file) {
297
320
  return ts.isIdentifier(tag) && /^[a-z]/.test(tag.text);
298
321
  }
299
322
  function visit(node) {
300
- if (ts.isVariableDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node)) {
301
- checkName(node.name);
302
- } else if (ts.isImportSpecifier(node)) {
303
- if (!node.isTypeOnly && !node.parent.parent.isTypeOnly) checkName(node.name);
304
- } else if (ts.isExportSpecifier(node)) {
305
- if (valueExport(node)) checkName(node.name, node.propertyName ?? node.name);
306
- } else if (ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node)) {
307
- if (!isDomStyleProperty(node))
308
- checkName(node.name, ts.isPropertyAssignment(node) ? node.initializer : node.name);
309
- } else if (ts.isPropertyDeclaration(node) || ts.isPropertySignature(node)) {
310
- checkName(node.name);
323
+ if (ts.isVariableDeclaration(node)) {
324
+ if (node.initializer && createsCapability(node.initializer))
325
+ checkName(node.name, node.initializer);
326
+ } else if (ts.isPropertyAssignment(node)) {
327
+ if (!isDomStyleProperty(node) && createsCapability(node.initializer))
328
+ checkName(node.name, node.initializer);
329
+ } else if (ts.isPropertyDeclaration(node)) {
330
+ if (node.initializer && createsCapability(node.initializer))
331
+ checkName(node.name, node.initializer);
311
332
  } else if (
312
333
  ts.isFunctionDeclaration(node) ||
313
334
  ts.isMethodDeclaration(node) ||
@@ -318,9 +339,11 @@ export function validateNativeSignalNames(program, file) {
318
339
  ts.isBinaryExpression(node) &&
319
340
  node.operatorToken.kind === ts.SyntaxKind.EqualsToken
320
341
  ) {
321
- if (ts.isPropertyAccessExpression(node.left)) checkName(node.left.name, node.right);
322
- else if (ts.isElementAccessExpression(node.left))
323
- checkName(node.left.argumentExpression, node.right);
342
+ if (createsCapability(node.right)) {
343
+ if (ts.isPropertyAccessExpression(node.left)) checkName(node.left.name, node.right);
344
+ else if (ts.isElementAccessExpression(node.left))
345
+ checkName(node.left.argumentExpression, node.right);
346
+ }
324
347
  } else if (ts.isCallExpression(node)) {
325
348
  const declaration = checker.getResolvedSignature(node)?.declaration;
326
349
  if (
@@ -0,0 +1,118 @@
1
+ // Shared syntax-level recognition for runtime server registration and editor call signatures.
2
+ export function unwrapServerFunctionInitializer(node) {
3
+ let current = node;
4
+ while (
5
+ current &&
6
+ (current.type === 'TSAsExpression' ||
7
+ current.type === 'TSSatisfiesExpression' ||
8
+ current.type === 'TSNonNullExpression' ||
9
+ current.type === 'TypeCastExpression' ||
10
+ current.type === 'ParenthesizedExpression')
11
+ ) {
12
+ current = current.expression;
13
+ }
14
+ return current;
15
+ }
16
+
17
+ export function collectServerFunctionNodes(statements) {
18
+ const functions = new Map();
19
+ const aliases = new Map();
20
+ for (const statement of statements) {
21
+ const declaration =
22
+ statement.type === 'ExportNamedDeclaration' ? statement.declaration : statement;
23
+ if (!declaration) continue;
24
+ if (declaration.type === 'FunctionDeclaration' && declaration.id) {
25
+ functions.set(declaration.id.name, declaration);
26
+ continue;
27
+ }
28
+ if (declaration.type !== 'VariableDeclaration') continue;
29
+ for (const item of declaration.declarations ?? []) {
30
+ if (item.id?.type !== 'Identifier' || !item.init) continue;
31
+ const init = unwrapServerFunctionInitializer(item.init);
32
+ if (init?.type === 'FunctionExpression' || init?.type === 'ArrowFunctionExpression') {
33
+ functions.set(item.id.name, init);
34
+ } else if (init?.type === 'Identifier') {
35
+ aliases.set(item.id.name, init.name);
36
+ }
37
+ }
38
+ }
39
+ for (const [local, target] of aliases) {
40
+ let name = target;
41
+ const seen = new Set([local]);
42
+ while (!seen.has(name)) {
43
+ seen.add(name);
44
+ const fn = functions.get(name);
45
+ if (fn !== undefined) {
46
+ functions.set(local, fn);
47
+ break;
48
+ }
49
+ name = aliases.get(name);
50
+ if (name === undefined) break;
51
+ }
52
+ }
53
+ return functions;
54
+ }
55
+
56
+ export function serverContextTypeImports(statements) {
57
+ const named = new Set();
58
+ const namespaces = new Set();
59
+ for (const statement of statements) {
60
+ if (statement.type !== 'ImportDeclaration' || statement.source?.value !== 'octane/server') {
61
+ continue;
62
+ }
63
+ for (const specifier of statement.specifiers ?? []) {
64
+ if (specifier.type === 'ImportNamespaceSpecifier') {
65
+ namespaces.add(specifier.local.name);
66
+ continue;
67
+ }
68
+ if (specifier.type !== 'ImportSpecifier') continue;
69
+ const imported = specifier.imported?.name ?? specifier.imported?.value;
70
+ if (imported === 'ServerCallContext') named.add(specifier.local.name);
71
+ }
72
+ }
73
+ return { named, namespaces };
74
+ }
75
+
76
+ export function serverContextTypeName(type, imports) {
77
+ while (
78
+ type &&
79
+ (type.type === 'TSTypeAnnotation' ||
80
+ type.type === 'TSParenthesizedType' ||
81
+ type.type === 'TSOptionalType')
82
+ ) {
83
+ type = type.typeAnnotation;
84
+ }
85
+ if (type?.type !== 'TSTypeReference') return false;
86
+ const name = type.typeName;
87
+ if (name?.type === 'Identifier') return imports.named.has(name.name);
88
+ if (name?.type === 'TSQualifiedName') {
89
+ return (
90
+ name.left?.type === 'Identifier' &&
91
+ imports.namespaces.has(name.left.name) &&
92
+ name.right?.name === 'ServerCallContext'
93
+ );
94
+ }
95
+ return (
96
+ name?.type === 'MemberExpression' &&
97
+ name.object?.type === 'Identifier' &&
98
+ imports.namespaces.has(name.object.name) &&
99
+ name.property?.name === 'ServerCallContext'
100
+ );
101
+ }
102
+
103
+ export function serverFunctionContextIndex(fn, imports, filename) {
104
+ let contextIndex = -1;
105
+ const params = fn.params ?? [];
106
+ for (let index = 0; index < params.length; index++) {
107
+ let parameter = params[index];
108
+ if (parameter.type === 'AssignmentPattern') parameter = parameter.left;
109
+ if (!serverContextTypeName(parameter.typeAnnotation, imports)) continue;
110
+ if (index !== params.length - 1) {
111
+ throw new Error(
112
+ `ServerCallContext must be the final parameter of a \`module server\` function (${filename}).`,
113
+ );
114
+ }
115
+ contextIndex = index;
116
+ }
117
+ return contextIndex;
118
+ }