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.
- package/README.md +5 -0
- package/dist/behavior-root.d.ts +13 -3
- package/dist/behavior-root.js +28 -5
- package/dist/cjs/behavior-root.cjs +32 -6
- package/dist/cjs/class-names.cjs +49 -0
- package/dist/cjs/constants.cjs +9 -0
- package/dist/cjs/css.cjs +6 -27
- package/dist/cjs/data-encoding.cjs +160 -0
- package/dist/cjs/dom-binding-handoff.cjs +93 -0
- package/dist/cjs/dom-binding-protocol.cjs +115 -0
- package/dist/cjs/dom-binding-styles.cjs +292 -0
- package/dist/cjs/dom-bindings.cjs +700 -0
- package/dist/cjs/dom-focused-move.cjs +66 -0
- package/dist/cjs/dom-tables.cjs +4 -94
- package/dist/cjs/error-codes.client.generated.cjs +33 -0
- package/dist/cjs/error-codes.server.generated.cjs +19 -0
- package/dist/cjs/hydration/control-capture.cjs +244 -0
- package/dist/cjs/hydration/event-capture.cjs +125 -43
- package/dist/cjs/hydration/interaction-config.cjs +50 -2
- package/dist/cjs/hydration-markers.cjs +37 -0
- package/dist/cjs/independent-hydration-protocol.cjs +106 -0
- package/dist/cjs/index.cjs +6 -0
- package/dist/cjs/runtime.cjs +2791 -215
- package/dist/cjs/runtime.server.cjs +716 -76
- package/dist/cjs/server/early-signals.cjs +45 -0
- package/dist/cjs/server/index.cjs +44 -0
- package/dist/cjs/server/rpc-batch.cjs +149 -0
- package/dist/cjs/server/rpc-stream.cjs +146 -0
- package/dist/cjs/server/rpc.cjs +34 -15
- package/dist/cjs/server/signal-query-observation.cjs +158 -0
- package/dist/cjs/server/streamed-region.cjs +52 -0
- package/dist/cjs/server/streamed-signals.cjs +512 -0
- package/dist/cjs/server-call.cjs +103 -0
- package/dist/cjs/server-rpc-batch-client.cjs +141 -0
- package/dist/cjs/server-rpc-client.cjs +94 -27
- package/dist/cjs/server-rpc-protocol.cjs +141 -0
- package/dist/cjs/server-rpc-stream-client.cjs +118 -0
- package/dist/cjs/signals/actions.cjs +474 -0
- package/dist/cjs/signals/client.cjs +4 -1
- package/dist/cjs/signals/computations.cjs +460 -0
- package/dist/cjs/signals/control-binding.cjs +368 -0
- package/dist/cjs/signals/derived-facade.cjs +50 -0
- package/dist/cjs/signals/document-owner.cjs +71 -0
- package/dist/cjs/signals/early-values.cjs +155 -0
- package/dist/cjs/signals/encoding.cjs +6 -95
- package/dist/cjs/signals/engine.cjs +276 -12
- package/dist/cjs/signals/errors.cjs +18 -0
- package/dist/cjs/signals/facade.cjs +477 -0
- package/dist/cjs/signals/graph.cjs +163 -20
- package/dist/cjs/signals/handle-protocol.cjs +36 -0
- package/dist/cjs/signals/index.cjs +81 -2
- package/dist/cjs/signals/native-read-client.cjs +10 -0
- package/dist/cjs/signals/owner-context.cjs +112 -0
- package/dist/cjs/signals/query-attempt-observer.cjs +68 -0
- package/dist/cjs/signals/query-facade.cjs +72 -0
- package/dist/cjs/signals/read-protocol.cjs +32 -0
- package/dist/cjs/signals/requests.cjs +287 -14
- package/dist/cjs/signals/scalar-computations.cjs +76 -0
- package/dist/cjs/signals/server.cjs +4 -1
- package/dist/cjs/signals/transition-candidate.cjs +567 -0
- package/dist/cjs/signals/transition-state.cjs +106 -0
- package/dist/cjs/signals/types.cjs +50 -0
- package/dist/cjs/stream-protocol.cjs +3 -0
- package/dist/cjs/streamed-signals-protocol.cjs +159 -0
- package/dist/cjs/style-values.cjs +122 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/class-names.d.ts +5 -0
- package/dist/class-names.js +25 -0
- package/dist/compiler/bundler.js +34 -6
- package/dist/compiler/compile.js +2234 -559
- package/dist/compiler/dom-binding-program.js +935 -0
- package/dist/compiler/dom-binding-request.js +57 -0
- package/dist/compiler/dom-bindings.js +2245 -0
- package/dist/compiler/hook-deps.js +26 -0
- package/dist/compiler/hydrate-boundaries.js +358 -15
- package/dist/compiler/index.d.ts +38 -1
- package/dist/compiler/index.js +1 -0
- package/dist/compiler/native-attribute-reads.js +127 -0
- package/dist/compiler/native-read-diagnostics.js +48 -9
- package/dist/compiler/native-read-types.js +55 -32
- package/dist/compiler/server-context.js +118 -0
- package/dist/compiler/signal-attempt-reads.js +479 -0
- package/dist/compiler/signal-declarations.js +557 -0
- package/dist/compiler/signal-start-reads.js +413 -0
- package/dist/compiler/slot-hooks.js +57 -10
- package/dist/compiler/template-ir.js +200 -0
- package/dist/compiler/typescript.d.ts +2 -0
- package/dist/compiler/typescript.js +2 -0
- package/dist/compiler/vite.d.ts +3 -1
- package/dist/compiler/vite.js +122 -4
- package/dist/compiler/volar.d.ts +6 -1
- package/dist/compiler/volar.js +674 -143
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +10 -0
- package/dist/css.d.ts +2 -5
- package/dist/css.js +2 -23
- package/dist/data-encoding.d.ts +9 -0
- package/dist/data-encoding.js +134 -0
- package/dist/dom-binding-classes.d.ts +15 -0
- package/dist/dom-binding-classes.js +129 -0
- package/dist/dom-binding-controls.d.ts +2 -0
- package/dist/dom-binding-controls.js +6 -0
- package/dist/dom-binding-handoff.d.ts +55 -0
- package/dist/dom-binding-handoff.js +62 -0
- package/dist/dom-binding-program.d.ts +300 -0
- package/dist/dom-binding-program.js +1625 -0
- package/dist/dom-binding-projections.d.ts +15 -0
- package/dist/dom-binding-projections.js +187 -0
- package/dist/dom-binding-protocol.d.ts +13 -0
- package/dist/dom-binding-protocol.js +88 -0
- package/dist/dom-binding-signals.d.ts +21 -0
- package/dist/dom-binding-signals.js +109 -0
- package/dist/dom-binding-styles.d.ts +17 -0
- package/dist/dom-binding-styles.js +272 -0
- package/dist/dom-bindings.d.ts +89 -0
- package/dist/dom-bindings.js +670 -0
- package/dist/dom-focused-move.d.ts +6 -0
- package/dist/dom-focused-move.js +42 -0
- package/dist/dom-tables.d.ts +1 -3
- package/dist/dom-tables.js +1 -91
- package/dist/error-codes.client.generated.d.ts +9 -0
- package/dist/error-codes.client.generated.js +33 -0
- package/dist/error-codes.server.generated.d.ts +5 -0
- package/dist/error-codes.server.generated.js +19 -0
- package/dist/hydration/control-capture.d.ts +62 -0
- package/dist/hydration/control-capture.js +223 -0
- package/dist/hydration/document-lifecycle.d.ts +23 -0
- package/dist/hydration/document-lifecycle.js +80 -0
- package/dist/hydration/event-capture.d.ts +20 -1
- package/dist/hydration/event-capture.js +128 -42
- package/dist/hydration/independent-island.d.ts +45 -0
- package/dist/hydration/independent-island.js +233 -0
- package/dist/hydration/index.d.ts +8 -1
- package/dist/hydration/index.js +55 -1
- package/dist/hydration/interaction-config.d.ts +5 -0
- package/dist/hydration/interaction-config.js +44 -1
- package/dist/hydration/stream-delivery.d.ts +32 -0
- package/dist/hydration/stream-delivery.js +333 -0
- package/dist/hydration/stream-receiver.d.ts +26 -0
- package/dist/hydration/stream-receiver.js +204 -0
- package/dist/hydration/stream-result-receiver.d.ts +70 -0
- package/dist/hydration/stream-result-receiver.js +246 -0
- package/dist/hydration/streamed-signals.d.ts +44 -0
- package/dist/hydration/streamed-signals.js +186 -0
- package/dist/hydration/types.d.ts +6 -0
- package/dist/hydration-markers.d.ts +7 -0
- package/dist/hydration-markers.js +10 -0
- package/dist/independent-hydration-protocol.d.ts +31 -0
- package/dist/independent-hydration-protocol.js +80 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/internal/signal-read.d.ts +1 -0
- package/dist/internal/signal-read.js +4 -0
- package/dist/jsx-runtime.d.ts +45 -6
- package/dist/public-types.d.ts +15 -1
- package/dist/runtime.d.ts +259 -21
- package/dist/runtime.js +2812 -220
- package/dist/runtime.server.d.ts +86 -6
- package/dist/runtime.server.js +712 -76
- package/dist/server/early-signals.d.ts +14 -0
- package/dist/server/early-signals.js +34 -0
- package/dist/server/index.d.ts +10 -2
- package/dist/server/index.js +48 -1
- package/dist/server/rpc-batch.d.ts +17 -0
- package/dist/server/rpc-batch.js +129 -0
- package/dist/server/rpc-stream.d.ts +9 -0
- package/dist/server/rpc-stream.js +125 -0
- package/dist/server/rpc.d.ts +5 -1
- package/dist/server/rpc.js +37 -13
- package/dist/server/signal-query-observation.d.ts +3 -0
- package/dist/server/signal-query-observation.js +134 -0
- package/dist/server/streamed-region.d.ts +15 -0
- package/dist/server/streamed-region.js +30 -0
- package/dist/server/streamed-signals.d.ts +45 -0
- package/dist/server/streamed-signals.js +488 -0
- package/dist/server-call.d.ts +41 -0
- package/dist/server-call.js +75 -0
- package/dist/server-rpc-batch-client.d.ts +7 -0
- package/dist/server-rpc-batch-client.js +119 -0
- package/dist/server-rpc-client.d.ts +3 -1
- package/dist/server-rpc-client.js +99 -27
- package/dist/server-rpc-protocol.d.ts +47 -0
- package/dist/server-rpc-protocol.js +109 -0
- package/dist/server-rpc-stream-client.d.ts +5 -0
- package/dist/server-rpc-stream-client.js +98 -0
- package/dist/signals/actions.d.ts +12 -0
- package/dist/signals/actions.js +466 -0
- package/dist/signals/client.d.ts +2 -0
- package/dist/signals/client.js +1 -0
- package/dist/signals/computations.d.ts +37 -0
- package/dist/signals/computations.js +455 -0
- package/dist/signals/control-binding.d.ts +31 -0
- package/dist/signals/control-binding.js +353 -0
- package/dist/signals/derived-facade.d.ts +3 -0
- package/dist/signals/derived-facade.js +25 -0
- package/dist/signals/document-owner.d.ts +10 -0
- package/dist/signals/document-owner.js +43 -0
- package/dist/signals/early-values.d.ts +24 -0
- package/dist/signals/early-values.js +124 -0
- package/dist/signals/encoding.d.ts +1 -3
- package/dist/signals/encoding.js +3 -93
- package/dist/signals/engine.d.ts +53 -4
- package/dist/signals/engine.js +275 -16
- package/dist/signals/errors.d.ts +7 -0
- package/dist/signals/errors.js +16 -0
- package/dist/signals/facade.d.ts +74 -0
- package/dist/signals/facade.js +452 -0
- package/dist/signals/graph.d.ts +50 -5
- package/dist/signals/graph.js +170 -19
- package/dist/signals/handle-protocol.d.ts +4 -0
- package/dist/signals/handle-protocol.js +11 -0
- package/dist/signals/index.d.ts +12 -3
- package/dist/signals/index.js +71 -2
- package/dist/signals/native-read-client.d.ts +3 -0
- package/dist/signals/native-read-client.js +10 -0
- package/dist/signals/owner-context.d.ts +22 -0
- package/dist/signals/owner-context.js +77 -0
- package/dist/signals/query-attempt-observer.d.ts +49 -0
- package/dist/signals/query-attempt-observer.js +41 -0
- package/dist/signals/query-facade.d.ts +3 -0
- package/dist/signals/query-facade.js +49 -0
- package/dist/signals/read-protocol.d.ts +32 -3
- package/dist/signals/read-protocol.js +26 -0
- package/dist/signals/requests.d.ts +38 -5
- package/dist/signals/requests.js +298 -15
- package/dist/signals/scalar-computations.d.ts +2 -0
- package/dist/signals/scalar-computations.js +60 -0
- package/dist/signals/server.d.ts +2 -0
- package/dist/signals/server.js +1 -0
- package/dist/signals/transition-candidate.d.ts +71 -0
- package/dist/signals/transition-candidate.js +557 -0
- package/dist/signals/transition-state.d.ts +51 -0
- package/dist/signals/transition-state.js +70 -0
- package/dist/signals/types.d.ts +95 -5
- package/dist/signals/types.js +20 -0
- package/dist/stream-protocol.d.ts +0 -5
- package/dist/stream-protocol.js +3 -0
- package/dist/streamed-signals-protocol.d.ts +60 -0
- package/dist/streamed-signals-protocol.js +130 -0
- package/dist/style-values.d.ts +3 -0
- package/dist/style-values.js +96 -0
- package/dist/version.js +1 -1
- 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$'
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
551
|
-
|
|
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
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
} else if (ts.
|
|
307
|
-
if (
|
|
308
|
-
checkName(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 (
|
|
322
|
-
|
|
323
|
-
|
|
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
|
+
}
|