@constela/runtime 0.11.1 → 0.12.0
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/dist/index.d.ts +156 -1
- package/dist/index.js +657 -84
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,26 @@ type CleanupFn = () => void;
|
|
|
17
17
|
type EffectFn = () => void | CleanupFn;
|
|
18
18
|
declare function createEffect(fn: EffectFn): () => void;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Computed - Derived reactive value
|
|
22
|
+
*
|
|
23
|
+
* A Computed holds a derived value that is automatically recalculated
|
|
24
|
+
* when its dependencies change. It tracks Signal dependencies and
|
|
25
|
+
* memoizes results for efficiency.
|
|
26
|
+
*/
|
|
27
|
+
interface Computed<T> {
|
|
28
|
+
get(): T;
|
|
29
|
+
subscribe?(fn: (value: T) => void): () => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates a computed value that automatically tracks dependencies
|
|
33
|
+
* and memoizes results.
|
|
34
|
+
*
|
|
35
|
+
* @param getter - Function that computes the derived value
|
|
36
|
+
* @returns Computed object with get() and subscribe() methods
|
|
37
|
+
*/
|
|
38
|
+
declare function createComputed<T>(getter: () => T): Computed<T>;
|
|
39
|
+
|
|
20
40
|
/**
|
|
21
41
|
* StateStore - Centralized state management
|
|
22
42
|
*
|
|
@@ -27,13 +47,62 @@ interface StateStore {
|
|
|
27
47
|
get(name: string): unknown;
|
|
28
48
|
set(name: string, value: unknown): void;
|
|
29
49
|
subscribe(name: string, fn: (value: unknown) => void): () => void;
|
|
50
|
+
getPath(name: string, path: string | (string | number)[]): unknown;
|
|
51
|
+
setPath(name: string, path: string | (string | number)[], value: unknown): void;
|
|
52
|
+
subscribeToPath(name: string, path: string | (string | number)[], fn: (value: unknown) => void): () => void;
|
|
30
53
|
}
|
|
31
54
|
interface StateDefinition {
|
|
32
55
|
type: string;
|
|
33
56
|
initial: unknown;
|
|
34
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* TypedStateStore - Generic interface for type-safe state access
|
|
60
|
+
*
|
|
61
|
+
* Usage:
|
|
62
|
+
* interface AppState {
|
|
63
|
+
* items: { id: number; liked: boolean }[];
|
|
64
|
+
* filter: string;
|
|
65
|
+
* }
|
|
66
|
+
* const state = createStateStore(definitions) as TypedStateStore<AppState>;
|
|
67
|
+
* state.get('items'); // returns { id: number; liked: boolean }[]
|
|
68
|
+
*/
|
|
69
|
+
interface TypedStateStore<T extends Record<string, unknown>> extends StateStore {
|
|
70
|
+
get<K extends keyof T>(name: K): T[K];
|
|
71
|
+
set<K extends keyof T>(name: K, value: T[K]): void;
|
|
72
|
+
subscribe<K extends keyof T>(name: K, fn: (value: T[K]) => void): () => void;
|
|
73
|
+
getPath<K extends keyof T>(name: K, path: string | (string | number)[]): unknown;
|
|
74
|
+
setPath<K extends keyof T>(name: K, path: string | (string | number)[], value: unknown): void;
|
|
75
|
+
subscribeToPath<K extends keyof T>(name: K, path: string | (string | number)[], fn: (value: unknown) => void): () => void;
|
|
76
|
+
}
|
|
35
77
|
declare function createStateStore(definitions: Record<string, StateDefinition>): StateStore;
|
|
36
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Typed State Store - Helper for creating type-safe state stores
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Creates a type-safe state store with inferred types
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* interface AppState {
|
|
88
|
+
* items: { id: number; liked: boolean }[];
|
|
89
|
+
* filter: string;
|
|
90
|
+
* count: number;
|
|
91
|
+
* }
|
|
92
|
+
*
|
|
93
|
+
* const state = createTypedStateStore<AppState>({
|
|
94
|
+
* items: { type: 'list', initial: [] },
|
|
95
|
+
* filter: { type: 'string', initial: '' },
|
|
96
|
+
* count: { type: 'number', initial: 0 },
|
|
97
|
+
* });
|
|
98
|
+
*
|
|
99
|
+
* state.get('items'); // correctly typed as { id: number; liked: boolean }[]
|
|
100
|
+
* state.set('count', 10); // type-checked
|
|
101
|
+
*/
|
|
102
|
+
declare function createTypedStateStore<T extends Record<string, unknown>>(definitions: {
|
|
103
|
+
[K in keyof T]: StateDefinition;
|
|
104
|
+
}): TypedStateStore<T>;
|
|
105
|
+
|
|
37
106
|
/**
|
|
38
107
|
* Expression Evaluator - Evaluates compiled expressions
|
|
39
108
|
*
|
|
@@ -88,6 +157,91 @@ interface StyleExprInput {
|
|
|
88
157
|
*/
|
|
89
158
|
declare function evaluateStyle(expr: StyleExprInput, ctx: EvaluationContext): string | undefined;
|
|
90
159
|
|
|
160
|
+
/**
|
|
161
|
+
* WebSocket Connection Module - Interface Stubs for TDD
|
|
162
|
+
*
|
|
163
|
+
* This file provides the interface definitions and stub implementations
|
|
164
|
+
* for WebSocket connection management in Constela runtime.
|
|
165
|
+
*
|
|
166
|
+
* TDD Red Phase: These stubs will fail tests until properly implemented.
|
|
167
|
+
*/
|
|
168
|
+
/**
|
|
169
|
+
* WebSocket connection interface for sending/receiving data
|
|
170
|
+
*/
|
|
171
|
+
interface WebSocketConnection {
|
|
172
|
+
/**
|
|
173
|
+
* Send data through the WebSocket connection
|
|
174
|
+
* Objects and arrays are JSON stringified
|
|
175
|
+
* @param data - The data to send
|
|
176
|
+
*/
|
|
177
|
+
send(data: unknown): void;
|
|
178
|
+
/**
|
|
179
|
+
* Close the WebSocket connection
|
|
180
|
+
*/
|
|
181
|
+
close(): void;
|
|
182
|
+
/**
|
|
183
|
+
* Get the current connection state
|
|
184
|
+
* @returns The connection state
|
|
185
|
+
*/
|
|
186
|
+
getState(): 'connecting' | 'open' | 'closing' | 'closed';
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Event handlers for WebSocket connection events
|
|
190
|
+
*/
|
|
191
|
+
interface WebSocketHandlers {
|
|
192
|
+
/** Called when connection is established */
|
|
193
|
+
onOpen?: () => Promise<void> | void;
|
|
194
|
+
/** Called when connection is closed */
|
|
195
|
+
onClose?: (code: number, reason: string) => Promise<void> | void;
|
|
196
|
+
/** Called when an error occurs */
|
|
197
|
+
onError?: (error: Event) => Promise<void> | void;
|
|
198
|
+
/** Called when a message is received (JSON parsed if possible) */
|
|
199
|
+
onMessage?: (data: unknown) => Promise<void> | void;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Connection manager for named WebSocket connections
|
|
203
|
+
*/
|
|
204
|
+
interface ConnectionManager {
|
|
205
|
+
/**
|
|
206
|
+
* Create a new named WebSocket connection
|
|
207
|
+
* If a connection with the same name exists, it will be closed first
|
|
208
|
+
*/
|
|
209
|
+
create(name: string, url: string, handlers: WebSocketHandlers): void;
|
|
210
|
+
/**
|
|
211
|
+
* Get a connection by name
|
|
212
|
+
* @returns The connection or undefined if not found
|
|
213
|
+
*/
|
|
214
|
+
get(name: string): WebSocketConnection | undefined;
|
|
215
|
+
/**
|
|
216
|
+
* Send data to a named connection
|
|
217
|
+
* @throws Error if connection not found
|
|
218
|
+
*/
|
|
219
|
+
send(name: string, data: unknown): void;
|
|
220
|
+
/**
|
|
221
|
+
* Close a named connection
|
|
222
|
+
* No-op if connection not found
|
|
223
|
+
*/
|
|
224
|
+
close(name: string): void;
|
|
225
|
+
/**
|
|
226
|
+
* Close all connections
|
|
227
|
+
*/
|
|
228
|
+
closeAll(): void;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Create a WebSocket connection with event handlers
|
|
232
|
+
*
|
|
233
|
+
* @param url - The WebSocket URL (e.g., "wss://api.example.com/ws")
|
|
234
|
+
* @param handlers - Event handlers for connection lifecycle
|
|
235
|
+
* @returns A WebSocketConnection interface for sending/closing
|
|
236
|
+
*/
|
|
237
|
+
declare function createWebSocketConnection(url: string, handlers: WebSocketHandlers): WebSocketConnection;
|
|
238
|
+
/**
|
|
239
|
+
* Create a connection manager for managing multiple named connections
|
|
240
|
+
*
|
|
241
|
+
* @returns A ConnectionManager interface
|
|
242
|
+
*/
|
|
243
|
+
declare function createConnectionManager(): ConnectionManager;
|
|
244
|
+
|
|
91
245
|
/**
|
|
92
246
|
* Action Executor - Executes compiled action steps
|
|
93
247
|
*
|
|
@@ -113,6 +267,7 @@ interface ActionContext {
|
|
|
113
267
|
path: string;
|
|
114
268
|
};
|
|
115
269
|
imports?: Record<string, unknown>;
|
|
270
|
+
connections?: ConnectionManager;
|
|
116
271
|
}
|
|
117
272
|
declare function executeAction(action: CompiledAction, ctx: ActionContext): Promise<void>;
|
|
118
273
|
|
|
@@ -203,4 +358,4 @@ interface HydrateOptions {
|
|
|
203
358
|
*/
|
|
204
359
|
declare function hydrateApp(options: HydrateOptions): AppInstance;
|
|
205
360
|
|
|
206
|
-
export { type ActionContext, type AppInstance, type EvaluationContext, type HydrateOptions, type RenderContext, type Signal, type StateStore, type StylePreset, createApp, createEffect, createSignal, createStateStore, evaluate, evaluateStyle, executeAction, hydrateApp, render };
|
|
361
|
+
export { type ActionContext, type AppInstance, type Computed, type ConnectionManager, type EvaluationContext, type HydrateOptions, type RenderContext, type Signal, type StateStore, type StylePreset, type TypedStateStore, type WebSocketConnection, type WebSocketHandlers, createApp, createComputed, createConnectionManager, createEffect, createSignal, createStateStore, createTypedStateStore, createWebSocketConnection, evaluate, evaluateStyle, executeAction, hydrateApp, render };
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,9 @@ var effectDependencies = /* @__PURE__ */ new Map();
|
|
|
9
9
|
function setCurrentEffect(effect) {
|
|
10
10
|
currentEffect = effect;
|
|
11
11
|
}
|
|
12
|
+
function getCurrentEffect() {
|
|
13
|
+
return currentEffect;
|
|
14
|
+
}
|
|
12
15
|
function registerEffectCleanup(effect) {
|
|
13
16
|
if (!effectDependencies.has(effect)) {
|
|
14
17
|
effectDependencies.set(effect, /* @__PURE__ */ new Set());
|
|
@@ -95,7 +98,121 @@ function createEffect(fn) {
|
|
|
95
98
|
};
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
// src/reactive/computed.ts
|
|
102
|
+
function createComputed(getter) {
|
|
103
|
+
let cachedValue;
|
|
104
|
+
let isDirty = true;
|
|
105
|
+
let isComputing = false;
|
|
106
|
+
let hasValue = false;
|
|
107
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
108
|
+
const effectSubscribers = /* @__PURE__ */ new Set();
|
|
109
|
+
const markDirty = () => {
|
|
110
|
+
if (!isDirty) {
|
|
111
|
+
isDirty = true;
|
|
112
|
+
const effects = [...effectSubscribers];
|
|
113
|
+
effects.forEach((effect) => effect());
|
|
114
|
+
if (subscribers.size > 0 && hasValue) {
|
|
115
|
+
const oldValue = cachedValue;
|
|
116
|
+
try {
|
|
117
|
+
compute();
|
|
118
|
+
if (!Object.is(cachedValue, oldValue)) {
|
|
119
|
+
notifySubscribers();
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const compute = () => {
|
|
127
|
+
if (isComputing) {
|
|
128
|
+
throw new Error("Circular dependency detected in computed");
|
|
129
|
+
}
|
|
130
|
+
cleanupEffect(markDirty);
|
|
131
|
+
isComputing = true;
|
|
132
|
+
const previousEffect = getCurrentEffect();
|
|
133
|
+
registerEffectCleanup(markDirty);
|
|
134
|
+
setCurrentEffect(markDirty);
|
|
135
|
+
try {
|
|
136
|
+
cachedValue = getter();
|
|
137
|
+
isDirty = false;
|
|
138
|
+
hasValue = true;
|
|
139
|
+
} finally {
|
|
140
|
+
isComputing = false;
|
|
141
|
+
setCurrentEffect(previousEffect);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const notifySubscribers = () => {
|
|
145
|
+
subscribers.forEach((fn) => {
|
|
146
|
+
try {
|
|
147
|
+
fn(cachedValue);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
console.error("Error in computed subscriber:", e);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
get() {
|
|
155
|
+
if (isDirty) {
|
|
156
|
+
compute();
|
|
157
|
+
}
|
|
158
|
+
const currentEff = getCurrentEffect();
|
|
159
|
+
if (currentEff && currentEff !== markDirty) {
|
|
160
|
+
effectSubscribers.add(currentEff);
|
|
161
|
+
}
|
|
162
|
+
return cachedValue;
|
|
163
|
+
},
|
|
164
|
+
subscribe(fn) {
|
|
165
|
+
if (isDirty) {
|
|
166
|
+
try {
|
|
167
|
+
compute();
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
subscribers.add(fn);
|
|
172
|
+
return () => {
|
|
173
|
+
subscribers.delete(fn);
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
98
179
|
// src/state/store.ts
|
|
180
|
+
function normalizePath(path) {
|
|
181
|
+
if (typeof path === "string") {
|
|
182
|
+
return path.split(".").map((segment) => {
|
|
183
|
+
const num = parseInt(segment, 10);
|
|
184
|
+
return isNaN(num) ? segment : num;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return path;
|
|
188
|
+
}
|
|
189
|
+
function getValueAtPath(obj, path) {
|
|
190
|
+
let current = obj;
|
|
191
|
+
for (const key2 of path) {
|
|
192
|
+
if (current == null) return void 0;
|
|
193
|
+
current = current[key2];
|
|
194
|
+
}
|
|
195
|
+
return current;
|
|
196
|
+
}
|
|
197
|
+
function setValueAtPath(obj, path, value) {
|
|
198
|
+
if (path.length === 0) return value;
|
|
199
|
+
const head2 = path[0];
|
|
200
|
+
const rest = path.slice(1);
|
|
201
|
+
const isArrayIndex = typeof head2 === "number";
|
|
202
|
+
let clone3;
|
|
203
|
+
if (isArrayIndex) {
|
|
204
|
+
clone3 = Array.isArray(obj) ? [...obj] : [];
|
|
205
|
+
} else {
|
|
206
|
+
clone3 = obj != null && typeof obj === "object" ? { ...obj } : {};
|
|
207
|
+
}
|
|
208
|
+
const objRecord = obj;
|
|
209
|
+
clone3[head2] = setValueAtPath(
|
|
210
|
+
objRecord?.[head2],
|
|
211
|
+
rest,
|
|
212
|
+
value
|
|
213
|
+
);
|
|
214
|
+
return clone3;
|
|
215
|
+
}
|
|
99
216
|
function createStateStore(definitions) {
|
|
100
217
|
const signals = /* @__PURE__ */ new Map();
|
|
101
218
|
for (const [name, def] of Object.entries(definitions)) {
|
|
@@ -136,10 +253,48 @@ function createStateStore(definitions) {
|
|
|
136
253
|
throw new Error(`State field "${name}" does not exist`);
|
|
137
254
|
}
|
|
138
255
|
return signal.subscribe(fn);
|
|
256
|
+
},
|
|
257
|
+
getPath(name, path) {
|
|
258
|
+
const signal = signals.get(name);
|
|
259
|
+
if (!signal) {
|
|
260
|
+
throw new Error(`State field "${name}" does not exist`);
|
|
261
|
+
}
|
|
262
|
+
const normalizedPath = normalizePath(path);
|
|
263
|
+
return getValueAtPath(signal.get(), normalizedPath);
|
|
264
|
+
},
|
|
265
|
+
setPath(name, path, value) {
|
|
266
|
+
const signal = signals.get(name);
|
|
267
|
+
if (!signal) {
|
|
268
|
+
throw new Error(`State field "${name}" does not exist`);
|
|
269
|
+
}
|
|
270
|
+
const normalizedPath = normalizePath(path);
|
|
271
|
+
const currentState = signal.get();
|
|
272
|
+
const newState = setValueAtPath(currentState, normalizedPath, value);
|
|
273
|
+
signal.set(newState);
|
|
274
|
+
},
|
|
275
|
+
subscribeToPath(name, path, fn) {
|
|
276
|
+
const signal = signals.get(name);
|
|
277
|
+
if (!signal) {
|
|
278
|
+
throw new Error(`State field "${name}" does not exist`);
|
|
279
|
+
}
|
|
280
|
+
const normalizedPath = normalizePath(path);
|
|
281
|
+
let previousValue = getValueAtPath(signal.get(), normalizedPath);
|
|
282
|
+
return signal.subscribe((newFieldValue) => {
|
|
283
|
+
const newValue = getValueAtPath(newFieldValue, normalizedPath);
|
|
284
|
+
if (newValue !== previousValue) {
|
|
285
|
+
previousValue = newValue;
|
|
286
|
+
fn(newValue);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
139
289
|
}
|
|
140
290
|
};
|
|
141
291
|
}
|
|
142
292
|
|
|
293
|
+
// src/state/typed.ts
|
|
294
|
+
function createTypedStateStore(definitions) {
|
|
295
|
+
return createStateStore(definitions);
|
|
296
|
+
}
|
|
297
|
+
|
|
143
298
|
// src/expression/evaluator.ts
|
|
144
299
|
function evaluate(expr, ctx) {
|
|
145
300
|
switch (expr.expr) {
|
|
@@ -408,7 +563,7 @@ function createEvalContext(ctx) {
|
|
|
408
563
|
}
|
|
409
564
|
async function executeAction(action, ctx) {
|
|
410
565
|
for (const step of action.steps) {
|
|
411
|
-
if (step.do === "set" || step.do === "update") {
|
|
566
|
+
if (step.do === "set" || step.do === "update" || step.do === "setPath") {
|
|
412
567
|
executeStepSync(step, ctx);
|
|
413
568
|
} else if (step.do === "if") {
|
|
414
569
|
await executeIfStep(step, ctx);
|
|
@@ -425,6 +580,9 @@ function executeStepSync(step, ctx) {
|
|
|
425
580
|
case "update":
|
|
426
581
|
executeUpdateStepSync(step, ctx);
|
|
427
582
|
break;
|
|
583
|
+
case "setPath":
|
|
584
|
+
executeSetPathStepSync(step, ctx);
|
|
585
|
+
break;
|
|
428
586
|
}
|
|
429
587
|
}
|
|
430
588
|
async function executeIfStep(step, ctx) {
|
|
@@ -432,7 +590,7 @@ async function executeIfStep(step, ctx) {
|
|
|
432
590
|
const condition = evaluate(step.condition, evalCtx);
|
|
433
591
|
const stepsToExecute = condition ? step.then : step.else || [];
|
|
434
592
|
for (const nestedStep of stepsToExecute) {
|
|
435
|
-
if (nestedStep.do === "set" || nestedStep.do === "update") {
|
|
593
|
+
if (nestedStep.do === "set" || nestedStep.do === "update" || nestedStep.do === "setPath") {
|
|
436
594
|
executeStepSync(nestedStep, ctx);
|
|
437
595
|
} else if (nestedStep.do === "if") {
|
|
438
596
|
await executeIfStep(nestedStep, ctx);
|
|
@@ -532,6 +690,31 @@ function executeUpdateStepSync(step, ctx) {
|
|
|
532
690
|
}
|
|
533
691
|
}
|
|
534
692
|
}
|
|
693
|
+
function executeSetPathStepSync(step, ctx) {
|
|
694
|
+
const evalCtx = createEvalContext(ctx);
|
|
695
|
+
const pathValue = evaluate(step.path, evalCtx);
|
|
696
|
+
let path;
|
|
697
|
+
if (typeof pathValue === "string") {
|
|
698
|
+
path = pathValue.split(".").map((segment) => {
|
|
699
|
+
const num = parseInt(segment, 10);
|
|
700
|
+
return isNaN(num) ? segment : num;
|
|
701
|
+
});
|
|
702
|
+
} else if (Array.isArray(pathValue)) {
|
|
703
|
+
path = pathValue.map((item) => {
|
|
704
|
+
if (typeof item === "object" && item !== null && "expr" in item) {
|
|
705
|
+
return evaluate(item, evalCtx);
|
|
706
|
+
}
|
|
707
|
+
return item;
|
|
708
|
+
});
|
|
709
|
+
} else {
|
|
710
|
+
path = [pathValue];
|
|
711
|
+
}
|
|
712
|
+
const newValue = evaluate(step.value, evalCtx);
|
|
713
|
+
ctx.state.setPath(step.target, path, newValue);
|
|
714
|
+
}
|
|
715
|
+
async function executeSetPathStep(step, ctx) {
|
|
716
|
+
executeSetPathStepSync(step, ctx);
|
|
717
|
+
}
|
|
535
718
|
async function executeStep(step, ctx) {
|
|
536
719
|
switch (step.do) {
|
|
537
720
|
case "set":
|
|
@@ -540,6 +723,9 @@ async function executeStep(step, ctx) {
|
|
|
540
723
|
case "update":
|
|
541
724
|
await executeUpdateStep(step, ctx);
|
|
542
725
|
break;
|
|
726
|
+
case "setPath":
|
|
727
|
+
await executeSetPathStep(step, ctx);
|
|
728
|
+
break;
|
|
543
729
|
case "fetch":
|
|
544
730
|
await executeFetchStep(step, ctx);
|
|
545
731
|
break;
|
|
@@ -570,6 +756,12 @@ async function executeStep(step, ctx) {
|
|
|
570
756
|
case "if":
|
|
571
757
|
await executeIfStep(step, ctx);
|
|
572
758
|
break;
|
|
759
|
+
case "send":
|
|
760
|
+
await executeSendStep(step, ctx);
|
|
761
|
+
break;
|
|
762
|
+
case "close":
|
|
763
|
+
await executeCloseStep(step, ctx);
|
|
764
|
+
break;
|
|
573
765
|
}
|
|
574
766
|
}
|
|
575
767
|
async function executeSetStep(target, value, ctx) {
|
|
@@ -924,6 +1116,18 @@ async function executeDomStep(step, ctx) {
|
|
|
924
1116
|
break;
|
|
925
1117
|
}
|
|
926
1118
|
}
|
|
1119
|
+
async function executeSendStep(step, ctx) {
|
|
1120
|
+
if (!ctx.connections) {
|
|
1121
|
+
throw new Error(`Connection "${step.connection}" not found`);
|
|
1122
|
+
}
|
|
1123
|
+
const evalCtx = createEvalContext(ctx);
|
|
1124
|
+
const data = evaluate(step.data, evalCtx);
|
|
1125
|
+
ctx.connections.send(step.connection, data);
|
|
1126
|
+
}
|
|
1127
|
+
async function executeCloseStep(step, ctx) {
|
|
1128
|
+
if (!ctx.connections) return;
|
|
1129
|
+
ctx.connections.close(step.connection);
|
|
1130
|
+
}
|
|
927
1131
|
|
|
928
1132
|
// ../../node_modules/.pnpm/marked@17.0.1/node_modules/marked/lib/marked.esm.js
|
|
929
1133
|
function L() {
|
|
@@ -13095,52 +13299,162 @@ function renderIf(node, ctx) {
|
|
|
13095
13299
|
}
|
|
13096
13300
|
return fragment;
|
|
13097
13301
|
}
|
|
13302
|
+
function createReactiveLocals(baseLocals, itemKey, itemSignal, indexKey, indexSignal) {
|
|
13303
|
+
return new Proxy(baseLocals, {
|
|
13304
|
+
get(target, prop) {
|
|
13305
|
+
if (prop === itemKey) {
|
|
13306
|
+
return itemSignal.get();
|
|
13307
|
+
}
|
|
13308
|
+
if (indexKey && prop === indexKey) {
|
|
13309
|
+
return indexSignal.get();
|
|
13310
|
+
}
|
|
13311
|
+
return target[prop];
|
|
13312
|
+
},
|
|
13313
|
+
has(target, prop) {
|
|
13314
|
+
if (prop === itemKey) return true;
|
|
13315
|
+
if (indexKey && prop === indexKey) return true;
|
|
13316
|
+
return prop in target;
|
|
13317
|
+
}
|
|
13318
|
+
});
|
|
13319
|
+
}
|
|
13098
13320
|
function renderEach(node, ctx) {
|
|
13099
13321
|
const anchor = document.createComment("each");
|
|
13322
|
+
const hasKey = !!node.key;
|
|
13323
|
+
let itemStateMap = /* @__PURE__ */ new Map();
|
|
13100
13324
|
let currentNodes = [];
|
|
13101
13325
|
let itemCleanups = [];
|
|
13102
13326
|
const effectCleanup = createEffect(() => {
|
|
13103
13327
|
const items = evaluate(node.items, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports } });
|
|
13104
|
-
|
|
13105
|
-
cleanup
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
|
|
13109
|
-
|
|
13110
|
-
oldNode.parentNode
|
|
13328
|
+
if (!hasKey || !node.key) {
|
|
13329
|
+
for (const cleanup of itemCleanups) {
|
|
13330
|
+
cleanup();
|
|
13331
|
+
}
|
|
13332
|
+
itemCleanups = [];
|
|
13333
|
+
for (const oldNode of currentNodes) {
|
|
13334
|
+
if (oldNode.parentNode) {
|
|
13335
|
+
oldNode.parentNode.removeChild(oldNode);
|
|
13336
|
+
}
|
|
13111
13337
|
}
|
|
13338
|
+
currentNodes = [];
|
|
13339
|
+
if (Array.isArray(items)) {
|
|
13340
|
+
items.forEach((item, index) => {
|
|
13341
|
+
const itemLocals = {
|
|
13342
|
+
...ctx.locals,
|
|
13343
|
+
[node.as]: item
|
|
13344
|
+
};
|
|
13345
|
+
if (node.index) {
|
|
13346
|
+
itemLocals[node.index] = index;
|
|
13347
|
+
}
|
|
13348
|
+
const localCleanups = [];
|
|
13349
|
+
const itemCtx = {
|
|
13350
|
+
...ctx,
|
|
13351
|
+
locals: itemLocals,
|
|
13352
|
+
cleanups: localCleanups
|
|
13353
|
+
};
|
|
13354
|
+
const itemNode = render(node.body, itemCtx);
|
|
13355
|
+
currentNodes.push(itemNode);
|
|
13356
|
+
itemCleanups.push(...localCleanups);
|
|
13357
|
+
if (anchor.parentNode) {
|
|
13358
|
+
let refNode = anchor.nextSibling;
|
|
13359
|
+
if (currentNodes.length > 1) {
|
|
13360
|
+
const lastExisting = currentNodes[currentNodes.length - 2];
|
|
13361
|
+
if (lastExisting) {
|
|
13362
|
+
refNode = lastExisting.nextSibling;
|
|
13363
|
+
}
|
|
13364
|
+
}
|
|
13365
|
+
anchor.parentNode.insertBefore(itemNode, refNode);
|
|
13366
|
+
}
|
|
13367
|
+
});
|
|
13368
|
+
}
|
|
13369
|
+
return;
|
|
13112
13370
|
}
|
|
13113
|
-
|
|
13371
|
+
const newItemStateMap = /* @__PURE__ */ new Map();
|
|
13372
|
+
const newNodes = [];
|
|
13373
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
13114
13374
|
if (Array.isArray(items)) {
|
|
13115
13375
|
items.forEach((item, index) => {
|
|
13116
|
-
const
|
|
13376
|
+
const tempLocals = {
|
|
13117
13377
|
...ctx.locals,
|
|
13118
|
-
[node.as]: item
|
|
13119
|
-
|
|
13120
|
-
if (node.index) {
|
|
13121
|
-
itemLocals[node.index] = index;
|
|
13122
|
-
}
|
|
13123
|
-
const localCleanups = [];
|
|
13124
|
-
const itemCtx = {
|
|
13125
|
-
...ctx,
|
|
13126
|
-
locals: itemLocals,
|
|
13127
|
-
cleanups: localCleanups
|
|
13378
|
+
[node.as]: item,
|
|
13379
|
+
...node.index ? { [node.index]: index } : {}
|
|
13128
13380
|
};
|
|
13129
|
-
const
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
|
|
13134
|
-
|
|
13135
|
-
|
|
13136
|
-
|
|
13137
|
-
refNode = lastExisting.nextSibling;
|
|
13138
|
-
}
|
|
13381
|
+
const keyValue = evaluate(node.key, {
|
|
13382
|
+
state: ctx.state,
|
|
13383
|
+
locals: tempLocals,
|
|
13384
|
+
...ctx.imports && { imports: ctx.imports }
|
|
13385
|
+
});
|
|
13386
|
+
if (seenKeys.has(keyValue)) {
|
|
13387
|
+
if (typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production") {
|
|
13388
|
+
console.warn(`Duplicate key "${keyValue}" in each loop. Keys should be unique.`);
|
|
13139
13389
|
}
|
|
13140
|
-
|
|
13390
|
+
}
|
|
13391
|
+
seenKeys.add(keyValue);
|
|
13392
|
+
const existingState = itemStateMap.get(keyValue);
|
|
13393
|
+
if (existingState) {
|
|
13394
|
+
existingState.itemSignal.set(item);
|
|
13395
|
+
existingState.indexSignal.set(index);
|
|
13396
|
+
newItemStateMap.set(keyValue, existingState);
|
|
13397
|
+
newNodes.push(existingState.node);
|
|
13398
|
+
} else {
|
|
13399
|
+
const itemSignal = createSignal(item);
|
|
13400
|
+
const indexSignal = createSignal(index);
|
|
13401
|
+
const reactiveLocals = createReactiveLocals(
|
|
13402
|
+
ctx.locals,
|
|
13403
|
+
node.as,
|
|
13404
|
+
itemSignal,
|
|
13405
|
+
node.index,
|
|
13406
|
+
indexSignal
|
|
13407
|
+
);
|
|
13408
|
+
const localCleanups = [];
|
|
13409
|
+
const itemCtx = {
|
|
13410
|
+
...ctx,
|
|
13411
|
+
locals: reactiveLocals,
|
|
13412
|
+
cleanups: localCleanups
|
|
13413
|
+
};
|
|
13414
|
+
const itemNode = render(node.body, itemCtx);
|
|
13415
|
+
const newState = {
|
|
13416
|
+
key: keyValue,
|
|
13417
|
+
node: itemNode,
|
|
13418
|
+
cleanups: localCleanups,
|
|
13419
|
+
itemSignal,
|
|
13420
|
+
indexSignal
|
|
13421
|
+
};
|
|
13422
|
+
newItemStateMap.set(keyValue, newState);
|
|
13423
|
+
newNodes.push(itemNode);
|
|
13141
13424
|
}
|
|
13142
13425
|
});
|
|
13143
13426
|
}
|
|
13427
|
+
for (const [key2, state] of itemStateMap) {
|
|
13428
|
+
if (!newItemStateMap.has(key2)) {
|
|
13429
|
+
for (const cleanup of state.cleanups) {
|
|
13430
|
+
cleanup();
|
|
13431
|
+
}
|
|
13432
|
+
if (state.node.parentNode) {
|
|
13433
|
+
state.node.parentNode.removeChild(state.node);
|
|
13434
|
+
}
|
|
13435
|
+
}
|
|
13436
|
+
}
|
|
13437
|
+
const activeElement = document.activeElement;
|
|
13438
|
+
const shouldRestoreFocus = activeElement && activeElement !== document.body;
|
|
13439
|
+
if (anchor.parentNode) {
|
|
13440
|
+
let refNode = anchor;
|
|
13441
|
+
for (const itemNode of newNodes) {
|
|
13442
|
+
const nextSibling = refNode.nextSibling;
|
|
13443
|
+
if (nextSibling !== itemNode) {
|
|
13444
|
+
anchor.parentNode.insertBefore(itemNode, refNode.nextSibling);
|
|
13445
|
+
}
|
|
13446
|
+
refNode = itemNode;
|
|
13447
|
+
}
|
|
13448
|
+
}
|
|
13449
|
+
if (shouldRestoreFocus && activeElement instanceof HTMLElement && document.activeElement !== activeElement) {
|
|
13450
|
+
activeElement.focus();
|
|
13451
|
+
}
|
|
13452
|
+
itemStateMap = newItemStateMap;
|
|
13453
|
+
currentNodes = newNodes;
|
|
13454
|
+
itemCleanups = [];
|
|
13455
|
+
for (const state of itemStateMap.values()) {
|
|
13456
|
+
itemCleanups.push(...state.cleanups);
|
|
13457
|
+
}
|
|
13144
13458
|
});
|
|
13145
13459
|
ctx.cleanups?.push(effectCleanup);
|
|
13146
13460
|
ctx.cleanups?.push(() => {
|
|
@@ -13263,6 +13577,20 @@ function createApp(program, mount) {
|
|
|
13263
13577
|
}
|
|
13264
13578
|
|
|
13265
13579
|
// src/hydrate.ts
|
|
13580
|
+
function createReactiveLocals2(baseLocals, itemSignal, indexSignal, itemName, indexName) {
|
|
13581
|
+
return new Proxy(baseLocals, {
|
|
13582
|
+
get(target, prop) {
|
|
13583
|
+
if (prop === itemName) return itemSignal.get();
|
|
13584
|
+
if (indexName && prop === indexName) return indexSignal.get();
|
|
13585
|
+
return target[prop];
|
|
13586
|
+
},
|
|
13587
|
+
has(target, prop) {
|
|
13588
|
+
if (prop === itemName) return true;
|
|
13589
|
+
if (indexName && prop === indexName) return true;
|
|
13590
|
+
return prop in target;
|
|
13591
|
+
}
|
|
13592
|
+
});
|
|
13593
|
+
}
|
|
13266
13594
|
function isEventHandler2(value) {
|
|
13267
13595
|
return typeof value === "object" && value !== null && "event" in value && "action" in value;
|
|
13268
13596
|
}
|
|
@@ -13693,6 +14021,8 @@ function hydrateEach(node, firstItemDomNode, ctx) {
|
|
|
13693
14021
|
if (!parent) return;
|
|
13694
14022
|
const anchor = document.createComment("each");
|
|
13695
14023
|
parent.insertBefore(anchor, firstItemDomNode);
|
|
14024
|
+
const hasKey = !!node.key;
|
|
14025
|
+
let itemStateMap = /* @__PURE__ */ new Map();
|
|
13696
14026
|
let currentNodes = [];
|
|
13697
14027
|
let itemCleanups = [];
|
|
13698
14028
|
const initialItems = evaluate(node.items, {
|
|
@@ -13704,29 +14034,85 @@ function hydrateEach(node, firstItemDomNode, ctx) {
|
|
|
13704
14034
|
let isFirstRun = true;
|
|
13705
14035
|
if (Array.isArray(initialItems) && initialItems.length > 0) {
|
|
13706
14036
|
let domNode = firstItemDomNode;
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
|
|
13712
|
-
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
|
|
13716
|
-
|
|
13717
|
-
|
|
13718
|
-
|
|
13719
|
-
|
|
13720
|
-
|
|
13721
|
-
|
|
13722
|
-
|
|
13723
|
-
|
|
13724
|
-
|
|
13725
|
-
|
|
13726
|
-
|
|
14037
|
+
if (hasKey && node.key) {
|
|
14038
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
14039
|
+
initialItems.forEach((item, index) => {
|
|
14040
|
+
if (!domNode) return;
|
|
14041
|
+
const tempLocals = {
|
|
14042
|
+
...ctx.locals,
|
|
14043
|
+
[node.as]: item,
|
|
14044
|
+
...node.index ? { [node.index]: index } : {}
|
|
14045
|
+
};
|
|
14046
|
+
const keyValue = evaluate(node.key, {
|
|
14047
|
+
state: ctx.state,
|
|
14048
|
+
locals: tempLocals,
|
|
14049
|
+
...ctx.imports && { imports: ctx.imports },
|
|
14050
|
+
...ctx.route && { route: ctx.route }
|
|
14051
|
+
});
|
|
14052
|
+
if (seenKeys.has(keyValue)) {
|
|
14053
|
+
if (typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production") {
|
|
14054
|
+
console.warn(`Duplicate key "${keyValue}" in each loop. Keys should be unique.`);
|
|
14055
|
+
}
|
|
14056
|
+
}
|
|
14057
|
+
seenKeys.add(keyValue);
|
|
14058
|
+
const itemSignal = createSignal(item);
|
|
14059
|
+
const indexSignal = createSignal(index);
|
|
14060
|
+
const reactiveLocals = createReactiveLocals2(
|
|
14061
|
+
ctx.locals,
|
|
14062
|
+
itemSignal,
|
|
14063
|
+
indexSignal,
|
|
14064
|
+
node.as,
|
|
14065
|
+
node.index
|
|
14066
|
+
);
|
|
14067
|
+
const localCleanups = [];
|
|
14068
|
+
const itemCtx = {
|
|
14069
|
+
...ctx,
|
|
14070
|
+
locals: reactiveLocals,
|
|
14071
|
+
cleanups: localCleanups
|
|
14072
|
+
};
|
|
14073
|
+
hydrate(node.body, domNode, itemCtx);
|
|
14074
|
+
const itemState = {
|
|
14075
|
+
key: keyValue,
|
|
14076
|
+
node: domNode,
|
|
14077
|
+
cleanups: localCleanups,
|
|
14078
|
+
itemSignal,
|
|
14079
|
+
indexSignal
|
|
14080
|
+
};
|
|
14081
|
+
itemStateMap.set(keyValue, itemState);
|
|
14082
|
+
currentNodes.push(domNode);
|
|
13727
14083
|
domNode = domNode.nextSibling;
|
|
14084
|
+
while (domNode && domNode.nodeType === Node.COMMENT_NODE) {
|
|
14085
|
+
domNode = domNode.nextSibling;
|
|
14086
|
+
}
|
|
14087
|
+
});
|
|
14088
|
+
for (const state of itemStateMap.values()) {
|
|
14089
|
+
itemCleanups.push(...state.cleanups);
|
|
13728
14090
|
}
|
|
13729
|
-
}
|
|
14091
|
+
} else {
|
|
14092
|
+
initialItems.forEach((item, index) => {
|
|
14093
|
+
if (!domNode) return;
|
|
14094
|
+
currentNodes.push(domNode);
|
|
14095
|
+
const itemLocals = {
|
|
14096
|
+
...ctx.locals,
|
|
14097
|
+
[node.as]: item
|
|
14098
|
+
};
|
|
14099
|
+
if (node.index) {
|
|
14100
|
+
itemLocals[node.index] = index;
|
|
14101
|
+
}
|
|
14102
|
+
const localCleanups = [];
|
|
14103
|
+
const itemCtx = {
|
|
14104
|
+
...ctx,
|
|
14105
|
+
locals: itemLocals,
|
|
14106
|
+
cleanups: localCleanups
|
|
14107
|
+
};
|
|
14108
|
+
hydrate(node.body, domNode, itemCtx);
|
|
14109
|
+
itemCleanups.push(...localCleanups);
|
|
14110
|
+
domNode = domNode.nextSibling;
|
|
14111
|
+
while (domNode && domNode.nodeType === Node.COMMENT_NODE) {
|
|
14112
|
+
domNode = domNode.nextSibling;
|
|
14113
|
+
}
|
|
14114
|
+
});
|
|
14115
|
+
}
|
|
13730
14116
|
}
|
|
13731
14117
|
const effectCleanup = createEffect(() => {
|
|
13732
14118
|
const items = evaluate(node.items, {
|
|
@@ -13739,48 +14125,141 @@ function hydrateEach(node, firstItemDomNode, ctx) {
|
|
|
13739
14125
|
isFirstRun = false;
|
|
13740
14126
|
return;
|
|
13741
14127
|
}
|
|
13742
|
-
|
|
13743
|
-
cleanup
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13748
|
-
oldNode.parentNode
|
|
14128
|
+
if (!hasKey || !node.key) {
|
|
14129
|
+
for (const cleanup of itemCleanups) {
|
|
14130
|
+
cleanup();
|
|
14131
|
+
}
|
|
14132
|
+
itemCleanups = [];
|
|
14133
|
+
for (const oldNode of currentNodes) {
|
|
14134
|
+
if (oldNode.parentNode) {
|
|
14135
|
+
oldNode.parentNode.removeChild(oldNode);
|
|
14136
|
+
}
|
|
13749
14137
|
}
|
|
14138
|
+
currentNodes = [];
|
|
14139
|
+
if (Array.isArray(items)) {
|
|
14140
|
+
items.forEach((item, index) => {
|
|
14141
|
+
const itemLocals = {
|
|
14142
|
+
...ctx.locals,
|
|
14143
|
+
[node.as]: item
|
|
14144
|
+
};
|
|
14145
|
+
if (node.index) {
|
|
14146
|
+
itemLocals[node.index] = index;
|
|
14147
|
+
}
|
|
14148
|
+
const localCleanups = [];
|
|
14149
|
+
const itemCtx = {
|
|
14150
|
+
state: ctx.state,
|
|
14151
|
+
actions: ctx.actions,
|
|
14152
|
+
locals: itemLocals,
|
|
14153
|
+
cleanups: localCleanups,
|
|
14154
|
+
...ctx.imports && { imports: ctx.imports }
|
|
14155
|
+
};
|
|
14156
|
+
const itemNode = render(node.body, itemCtx);
|
|
14157
|
+
currentNodes.push(itemNode);
|
|
14158
|
+
itemCleanups.push(...localCleanups);
|
|
14159
|
+
if (anchor.parentNode) {
|
|
14160
|
+
let refNode = anchor.nextSibling;
|
|
14161
|
+
if (currentNodes.length > 1) {
|
|
14162
|
+
const lastExisting = currentNodes[currentNodes.length - 2];
|
|
14163
|
+
if (lastExisting) {
|
|
14164
|
+
refNode = lastExisting.nextSibling;
|
|
14165
|
+
}
|
|
14166
|
+
}
|
|
14167
|
+
anchor.parentNode.insertBefore(itemNode, refNode);
|
|
14168
|
+
}
|
|
14169
|
+
});
|
|
14170
|
+
}
|
|
14171
|
+
return;
|
|
13750
14172
|
}
|
|
13751
|
-
|
|
14173
|
+
const newItemStateMap = /* @__PURE__ */ new Map();
|
|
14174
|
+
const newNodes = [];
|
|
14175
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
13752
14176
|
if (Array.isArray(items)) {
|
|
13753
14177
|
items.forEach((item, index) => {
|
|
13754
|
-
const
|
|
14178
|
+
const tempLocals = {
|
|
13755
14179
|
...ctx.locals,
|
|
13756
|
-
[node.as]: item
|
|
14180
|
+
[node.as]: item,
|
|
14181
|
+
...node.index ? { [node.index]: index } : {}
|
|
13757
14182
|
};
|
|
13758
|
-
|
|
13759
|
-
itemLocals[node.index] = index;
|
|
13760
|
-
}
|
|
13761
|
-
const localCleanups = [];
|
|
13762
|
-
const itemCtx = {
|
|
14183
|
+
const keyValue = evaluate(node.key, {
|
|
13763
14184
|
state: ctx.state,
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
itemCleanups.push(...localCleanups);
|
|
13772
|
-
if (anchor.parentNode) {
|
|
13773
|
-
let refNode = anchor.nextSibling;
|
|
13774
|
-
if (currentNodes.length > 1) {
|
|
13775
|
-
const lastExisting = currentNodes[currentNodes.length - 2];
|
|
13776
|
-
if (lastExisting) {
|
|
13777
|
-
refNode = lastExisting.nextSibling;
|
|
13778
|
-
}
|
|
14185
|
+
locals: tempLocals,
|
|
14186
|
+
...ctx.imports && { imports: ctx.imports },
|
|
14187
|
+
...ctx.route && { route: ctx.route }
|
|
14188
|
+
});
|
|
14189
|
+
if (seenKeys.has(keyValue)) {
|
|
14190
|
+
if (typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production") {
|
|
14191
|
+
console.warn(`Duplicate key "${keyValue}" in each loop. Keys should be unique.`);
|
|
13779
14192
|
}
|
|
13780
|
-
|
|
14193
|
+
}
|
|
14194
|
+
seenKeys.add(keyValue);
|
|
14195
|
+
const existingState = itemStateMap.get(keyValue);
|
|
14196
|
+
if (existingState) {
|
|
14197
|
+
existingState.itemSignal.set(item);
|
|
14198
|
+
existingState.indexSignal.set(index);
|
|
14199
|
+
newItemStateMap.set(keyValue, existingState);
|
|
14200
|
+
newNodes.push(existingState.node);
|
|
14201
|
+
} else {
|
|
14202
|
+
const itemSignal = createSignal(item);
|
|
14203
|
+
const indexSignal = createSignal(index);
|
|
14204
|
+
const reactiveLocals = createReactiveLocals2(
|
|
14205
|
+
ctx.locals,
|
|
14206
|
+
itemSignal,
|
|
14207
|
+
indexSignal,
|
|
14208
|
+
node.as,
|
|
14209
|
+
node.index
|
|
14210
|
+
);
|
|
14211
|
+
const localCleanups = [];
|
|
14212
|
+
const itemCtx = {
|
|
14213
|
+
state: ctx.state,
|
|
14214
|
+
actions: ctx.actions,
|
|
14215
|
+
locals: reactiveLocals,
|
|
14216
|
+
cleanups: localCleanups,
|
|
14217
|
+
...ctx.imports && { imports: ctx.imports }
|
|
14218
|
+
};
|
|
14219
|
+
const itemNode = render(node.body, itemCtx);
|
|
14220
|
+
const newState = {
|
|
14221
|
+
key: keyValue,
|
|
14222
|
+
node: itemNode,
|
|
14223
|
+
cleanups: localCleanups,
|
|
14224
|
+
itemSignal,
|
|
14225
|
+
indexSignal
|
|
14226
|
+
};
|
|
14227
|
+
newItemStateMap.set(keyValue, newState);
|
|
14228
|
+
newNodes.push(itemNode);
|
|
13781
14229
|
}
|
|
13782
14230
|
});
|
|
13783
14231
|
}
|
|
14232
|
+
for (const [key2, state] of itemStateMap) {
|
|
14233
|
+
if (!newItemStateMap.has(key2)) {
|
|
14234
|
+
for (const cleanup of state.cleanups) {
|
|
14235
|
+
cleanup();
|
|
14236
|
+
}
|
|
14237
|
+
if (state.node.parentNode) {
|
|
14238
|
+
state.node.parentNode.removeChild(state.node);
|
|
14239
|
+
}
|
|
14240
|
+
}
|
|
14241
|
+
}
|
|
14242
|
+
const activeElement = document.activeElement;
|
|
14243
|
+
const shouldRestoreFocus = activeElement && activeElement !== document.body;
|
|
14244
|
+
if (anchor.parentNode) {
|
|
14245
|
+
let refNode = anchor;
|
|
14246
|
+
for (const itemNode of newNodes) {
|
|
14247
|
+
const nextSibling = refNode.nextSibling;
|
|
14248
|
+
if (nextSibling !== itemNode) {
|
|
14249
|
+
anchor.parentNode.insertBefore(itemNode, refNode.nextSibling);
|
|
14250
|
+
}
|
|
14251
|
+
refNode = itemNode;
|
|
14252
|
+
}
|
|
14253
|
+
}
|
|
14254
|
+
if (shouldRestoreFocus && activeElement instanceof HTMLElement && document.activeElement !== activeElement) {
|
|
14255
|
+
activeElement.focus();
|
|
14256
|
+
}
|
|
14257
|
+
itemStateMap = newItemStateMap;
|
|
14258
|
+
currentNodes = newNodes;
|
|
14259
|
+
itemCleanups = [];
|
|
14260
|
+
for (const state of itemStateMap.values()) {
|
|
14261
|
+
itemCleanups.push(...state.cleanups);
|
|
14262
|
+
}
|
|
13784
14263
|
});
|
|
13785
14264
|
ctx.cleanups.push(effectCleanup);
|
|
13786
14265
|
ctx.cleanups.push(() => {
|
|
@@ -13809,11 +14288,105 @@ function initCopyButtons(container) {
|
|
|
13809
14288
|
});
|
|
13810
14289
|
});
|
|
13811
14290
|
}
|
|
14291
|
+
|
|
14292
|
+
// src/connection/websocket.ts
|
|
14293
|
+
function createWebSocketConnection(url, handlers) {
|
|
14294
|
+
const ws = new WebSocket(url);
|
|
14295
|
+
ws.onopen = () => {
|
|
14296
|
+
handlers.onOpen?.();
|
|
14297
|
+
};
|
|
14298
|
+
ws.onclose = (event) => {
|
|
14299
|
+
handlers.onClose?.(event.code, event.reason);
|
|
14300
|
+
};
|
|
14301
|
+
ws.onerror = (event) => {
|
|
14302
|
+
handlers.onError?.(event);
|
|
14303
|
+
};
|
|
14304
|
+
ws.onmessage = (event) => {
|
|
14305
|
+
let data = event.data;
|
|
14306
|
+
if (typeof data === "string") {
|
|
14307
|
+
try {
|
|
14308
|
+
data = JSON.parse(data);
|
|
14309
|
+
} catch {
|
|
14310
|
+
}
|
|
14311
|
+
}
|
|
14312
|
+
handlers.onMessage?.(data);
|
|
14313
|
+
};
|
|
14314
|
+
return {
|
|
14315
|
+
send(data) {
|
|
14316
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
14317
|
+
let message;
|
|
14318
|
+
if (typeof data === "string") {
|
|
14319
|
+
message = data;
|
|
14320
|
+
} else {
|
|
14321
|
+
message = JSON.stringify(data);
|
|
14322
|
+
}
|
|
14323
|
+
ws.send(message);
|
|
14324
|
+
}
|
|
14325
|
+
},
|
|
14326
|
+
close() {
|
|
14327
|
+
ws.close();
|
|
14328
|
+
},
|
|
14329
|
+
getState() {
|
|
14330
|
+
switch (ws.readyState) {
|
|
14331
|
+
case WebSocket.CONNECTING:
|
|
14332
|
+
return "connecting";
|
|
14333
|
+
case WebSocket.OPEN:
|
|
14334
|
+
return "open";
|
|
14335
|
+
case WebSocket.CLOSING:
|
|
14336
|
+
return "closing";
|
|
14337
|
+
case WebSocket.CLOSED:
|
|
14338
|
+
return "closed";
|
|
14339
|
+
default:
|
|
14340
|
+
return "closed";
|
|
14341
|
+
}
|
|
14342
|
+
}
|
|
14343
|
+
};
|
|
14344
|
+
}
|
|
14345
|
+
function createConnectionManager() {
|
|
14346
|
+
const connections = /* @__PURE__ */ new Map();
|
|
14347
|
+
return {
|
|
14348
|
+
create(name, url, handlers) {
|
|
14349
|
+
const existing = connections.get(name);
|
|
14350
|
+
if (existing) {
|
|
14351
|
+
existing.close();
|
|
14352
|
+
}
|
|
14353
|
+
const conn = createWebSocketConnection(url, handlers);
|
|
14354
|
+
connections.set(name, conn);
|
|
14355
|
+
},
|
|
14356
|
+
get(name) {
|
|
14357
|
+
return connections.get(name);
|
|
14358
|
+
},
|
|
14359
|
+
send(name, data) {
|
|
14360
|
+
const conn = connections.get(name);
|
|
14361
|
+
if (!conn) {
|
|
14362
|
+
throw new Error(`Connection "${name}" not found`);
|
|
14363
|
+
}
|
|
14364
|
+
conn.send(data);
|
|
14365
|
+
},
|
|
14366
|
+
close(name) {
|
|
14367
|
+
const conn = connections.get(name);
|
|
14368
|
+
if (conn) {
|
|
14369
|
+
conn.close();
|
|
14370
|
+
connections.delete(name);
|
|
14371
|
+
}
|
|
14372
|
+
},
|
|
14373
|
+
closeAll() {
|
|
14374
|
+
for (const conn of connections.values()) {
|
|
14375
|
+
conn.close();
|
|
14376
|
+
}
|
|
14377
|
+
connections.clear();
|
|
14378
|
+
}
|
|
14379
|
+
};
|
|
14380
|
+
}
|
|
13812
14381
|
export {
|
|
13813
14382
|
createApp,
|
|
14383
|
+
createComputed,
|
|
14384
|
+
createConnectionManager,
|
|
13814
14385
|
createEffect,
|
|
13815
14386
|
createSignal,
|
|
13816
14387
|
createStateStore,
|
|
14388
|
+
createTypedStateStore,
|
|
14389
|
+
createWebSocketConnection,
|
|
13817
14390
|
evaluate,
|
|
13818
14391
|
evaluateStyle,
|
|
13819
14392
|
executeAction,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Runtime DOM renderer for Constela UI framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"dompurify": "^3.3.1",
|
|
19
19
|
"marked": "^17.0.1",
|
|
20
20
|
"shiki": "^3.20.0",
|
|
21
|
-
"@constela/
|
|
22
|
-
"@constela/
|
|
21
|
+
"@constela/compiler": "0.9.0",
|
|
22
|
+
"@constela/core": "0.9.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/dompurify": "^3.2.0",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"tsup": "^8.0.0",
|
|
30
30
|
"typescript": "^5.3.0",
|
|
31
31
|
"vitest": "^2.0.0",
|
|
32
|
-
"@constela/server": "
|
|
32
|
+
"@constela/server": "5.0.0"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
35
|
"node": ">=20.0.0"
|