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,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
|
+
}
|