@constela/runtime 0.11.0 → 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 +672 -85
- 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,11 +98,139 @@ 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)) {
|
|
102
|
-
|
|
219
|
+
let initialValue = def.initial;
|
|
220
|
+
if (name === "theme" && typeof window !== "undefined") {
|
|
221
|
+
try {
|
|
222
|
+
const stored = localStorage.getItem("theme");
|
|
223
|
+
if (stored !== null) {
|
|
224
|
+
try {
|
|
225
|
+
initialValue = JSON.parse(stored);
|
|
226
|
+
} catch {
|
|
227
|
+
initialValue = stored;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
signals.set(name, createSignal(initialValue));
|
|
103
234
|
}
|
|
104
235
|
return {
|
|
105
236
|
get(name) {
|
|
@@ -122,10 +253,48 @@ function createStateStore(definitions) {
|
|
|
122
253
|
throw new Error(`State field "${name}" does not exist`);
|
|
123
254
|
}
|
|
124
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
|
+
});
|
|
125
289
|
}
|
|
126
290
|
};
|
|
127
291
|
}
|
|
128
292
|
|
|
293
|
+
// src/state/typed.ts
|
|
294
|
+
function createTypedStateStore(definitions) {
|
|
295
|
+
return createStateStore(definitions);
|
|
296
|
+
}
|
|
297
|
+
|
|
129
298
|
// src/expression/evaluator.ts
|
|
130
299
|
function evaluate(expr, ctx) {
|
|
131
300
|
switch (expr.expr) {
|
|
@@ -394,7 +563,7 @@ function createEvalContext(ctx) {
|
|
|
394
563
|
}
|
|
395
564
|
async function executeAction(action, ctx) {
|
|
396
565
|
for (const step of action.steps) {
|
|
397
|
-
if (step.do === "set" || step.do === "update") {
|
|
566
|
+
if (step.do === "set" || step.do === "update" || step.do === "setPath") {
|
|
398
567
|
executeStepSync(step, ctx);
|
|
399
568
|
} else if (step.do === "if") {
|
|
400
569
|
await executeIfStep(step, ctx);
|
|
@@ -411,6 +580,9 @@ function executeStepSync(step, ctx) {
|
|
|
411
580
|
case "update":
|
|
412
581
|
executeUpdateStepSync(step, ctx);
|
|
413
582
|
break;
|
|
583
|
+
case "setPath":
|
|
584
|
+
executeSetPathStepSync(step, ctx);
|
|
585
|
+
break;
|
|
414
586
|
}
|
|
415
587
|
}
|
|
416
588
|
async function executeIfStep(step, ctx) {
|
|
@@ -418,7 +590,7 @@ async function executeIfStep(step, ctx) {
|
|
|
418
590
|
const condition = evaluate(step.condition, evalCtx);
|
|
419
591
|
const stepsToExecute = condition ? step.then : step.else || [];
|
|
420
592
|
for (const nestedStep of stepsToExecute) {
|
|
421
|
-
if (nestedStep.do === "set" || nestedStep.do === "update") {
|
|
593
|
+
if (nestedStep.do === "set" || nestedStep.do === "update" || nestedStep.do === "setPath") {
|
|
422
594
|
executeStepSync(nestedStep, ctx);
|
|
423
595
|
} else if (nestedStep.do === "if") {
|
|
424
596
|
await executeIfStep(nestedStep, ctx);
|
|
@@ -518,6 +690,31 @@ function executeUpdateStepSync(step, ctx) {
|
|
|
518
690
|
}
|
|
519
691
|
}
|
|
520
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
|
+
}
|
|
521
718
|
async function executeStep(step, ctx) {
|
|
522
719
|
switch (step.do) {
|
|
523
720
|
case "set":
|
|
@@ -526,6 +723,9 @@ async function executeStep(step, ctx) {
|
|
|
526
723
|
case "update":
|
|
527
724
|
await executeUpdateStep(step, ctx);
|
|
528
725
|
break;
|
|
726
|
+
case "setPath":
|
|
727
|
+
await executeSetPathStep(step, ctx);
|
|
728
|
+
break;
|
|
529
729
|
case "fetch":
|
|
530
730
|
await executeFetchStep(step, ctx);
|
|
531
731
|
break;
|
|
@@ -556,6 +756,12 @@ async function executeStep(step, ctx) {
|
|
|
556
756
|
case "if":
|
|
557
757
|
await executeIfStep(step, ctx);
|
|
558
758
|
break;
|
|
759
|
+
case "send":
|
|
760
|
+
await executeSendStep(step, ctx);
|
|
761
|
+
break;
|
|
762
|
+
case "close":
|
|
763
|
+
await executeCloseStep(step, ctx);
|
|
764
|
+
break;
|
|
559
765
|
}
|
|
560
766
|
}
|
|
561
767
|
async function executeSetStep(target, value, ctx) {
|
|
@@ -910,6 +1116,18 @@ async function executeDomStep(step, ctx) {
|
|
|
910
1116
|
break;
|
|
911
1117
|
}
|
|
912
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
|
+
}
|
|
913
1131
|
|
|
914
1132
|
// ../../node_modules/.pnpm/marked@17.0.1/node_modules/marked/lib/marked.esm.js
|
|
915
1133
|
function L() {
|
|
@@ -13081,52 +13299,162 @@ function renderIf(node, ctx) {
|
|
|
13081
13299
|
}
|
|
13082
13300
|
return fragment;
|
|
13083
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
|
+
}
|
|
13084
13320
|
function renderEach(node, ctx) {
|
|
13085
13321
|
const anchor = document.createComment("each");
|
|
13322
|
+
const hasKey = !!node.key;
|
|
13323
|
+
let itemStateMap = /* @__PURE__ */ new Map();
|
|
13086
13324
|
let currentNodes = [];
|
|
13087
13325
|
let itemCleanups = [];
|
|
13088
13326
|
const effectCleanup = createEffect(() => {
|
|
13089
13327
|
const items = evaluate(node.items, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports } });
|
|
13090
|
-
|
|
13091
|
-
cleanup
|
|
13092
|
-
|
|
13093
|
-
itemCleanups = [];
|
|
13094
|
-
for (const oldNode of currentNodes) {
|
|
13095
|
-
if (oldNode.parentNode) {
|
|
13096
|
-
oldNode.parentNode.removeChild(oldNode);
|
|
13328
|
+
if (!hasKey || !node.key) {
|
|
13329
|
+
for (const cleanup of itemCleanups) {
|
|
13330
|
+
cleanup();
|
|
13097
13331
|
}
|
|
13332
|
+
itemCleanups = [];
|
|
13333
|
+
for (const oldNode of currentNodes) {
|
|
13334
|
+
if (oldNode.parentNode) {
|
|
13335
|
+
oldNode.parentNode.removeChild(oldNode);
|
|
13336
|
+
}
|
|
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;
|
|
13098
13370
|
}
|
|
13099
|
-
|
|
13371
|
+
const newItemStateMap = /* @__PURE__ */ new Map();
|
|
13372
|
+
const newNodes = [];
|
|
13373
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
13100
13374
|
if (Array.isArray(items)) {
|
|
13101
13375
|
items.forEach((item, index) => {
|
|
13102
|
-
const
|
|
13376
|
+
const tempLocals = {
|
|
13103
13377
|
...ctx.locals,
|
|
13104
|
-
[node.as]: item
|
|
13378
|
+
[node.as]: item,
|
|
13379
|
+
...node.index ? { [node.index]: index } : {}
|
|
13105
13380
|
};
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
|
|
13109
|
-
|
|
13110
|
-
|
|
13111
|
-
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
};
|
|
13115
|
-
const itemNode = render(node.body, itemCtx);
|
|
13116
|
-
currentNodes.push(itemNode);
|
|
13117
|
-
itemCleanups.push(...localCleanups);
|
|
13118
|
-
if (anchor.parentNode) {
|
|
13119
|
-
let refNode = anchor.nextSibling;
|
|
13120
|
-
if (currentNodes.length > 1) {
|
|
13121
|
-
const lastExisting = currentNodes[currentNodes.length - 2];
|
|
13122
|
-
if (lastExisting) {
|
|
13123
|
-
refNode = lastExisting.nextSibling;
|
|
13124
|
-
}
|
|
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.`);
|
|
13125
13389
|
}
|
|
13126
|
-
|
|
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);
|
|
13127
13424
|
}
|
|
13128
13425
|
});
|
|
13129
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
|
+
}
|
|
13130
13458
|
});
|
|
13131
13459
|
ctx.cleanups?.push(effectCleanup);
|
|
13132
13460
|
ctx.cleanups?.push(() => {
|
|
@@ -13249,6 +13577,20 @@ function createApp(program, mount) {
|
|
|
13249
13577
|
}
|
|
13250
13578
|
|
|
13251
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
|
+
}
|
|
13252
13594
|
function isEventHandler2(value) {
|
|
13253
13595
|
return typeof value === "object" && value !== null && "event" in value && "action" in value;
|
|
13254
13596
|
}
|
|
@@ -13679,6 +14021,8 @@ function hydrateEach(node, firstItemDomNode, ctx) {
|
|
|
13679
14021
|
if (!parent) return;
|
|
13680
14022
|
const anchor = document.createComment("each");
|
|
13681
14023
|
parent.insertBefore(anchor, firstItemDomNode);
|
|
14024
|
+
const hasKey = !!node.key;
|
|
14025
|
+
let itemStateMap = /* @__PURE__ */ new Map();
|
|
13682
14026
|
let currentNodes = [];
|
|
13683
14027
|
let itemCleanups = [];
|
|
13684
14028
|
const initialItems = evaluate(node.items, {
|
|
@@ -13690,29 +14034,85 @@ function hydrateEach(node, firstItemDomNode, ctx) {
|
|
|
13690
14034
|
let isFirstRun = true;
|
|
13691
14035
|
if (Array.isArray(initialItems) && initialItems.length > 0) {
|
|
13692
14036
|
let domNode = firstItemDomNode;
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
|
|
13705
|
-
|
|
13706
|
-
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
|
|
13712
|
-
|
|
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);
|
|
13713
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);
|
|
13714
14090
|
}
|
|
13715
|
-
}
|
|
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
|
+
}
|
|
13716
14116
|
}
|
|
13717
14117
|
const effectCleanup = createEffect(() => {
|
|
13718
14118
|
const items = evaluate(node.items, {
|
|
@@ -13725,48 +14125,141 @@ function hydrateEach(node, firstItemDomNode, ctx) {
|
|
|
13725
14125
|
isFirstRun = false;
|
|
13726
14126
|
return;
|
|
13727
14127
|
}
|
|
13728
|
-
|
|
13729
|
-
cleanup
|
|
13730
|
-
|
|
13731
|
-
|
|
13732
|
-
|
|
13733
|
-
|
|
13734
|
-
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
|
+
}
|
|
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
|
+
});
|
|
13735
14170
|
}
|
|
14171
|
+
return;
|
|
13736
14172
|
}
|
|
13737
|
-
|
|
14173
|
+
const newItemStateMap = /* @__PURE__ */ new Map();
|
|
14174
|
+
const newNodes = [];
|
|
14175
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
13738
14176
|
if (Array.isArray(items)) {
|
|
13739
14177
|
items.forEach((item, index) => {
|
|
13740
|
-
const
|
|
14178
|
+
const tempLocals = {
|
|
13741
14179
|
...ctx.locals,
|
|
13742
|
-
[node.as]: item
|
|
14180
|
+
[node.as]: item,
|
|
14181
|
+
...node.index ? { [node.index]: index } : {}
|
|
13743
14182
|
};
|
|
13744
|
-
|
|
13745
|
-
itemLocals[node.index] = index;
|
|
13746
|
-
}
|
|
13747
|
-
const localCleanups = [];
|
|
13748
|
-
const itemCtx = {
|
|
14183
|
+
const keyValue = evaluate(node.key, {
|
|
13749
14184
|
state: ctx.state,
|
|
13750
|
-
|
|
13751
|
-
|
|
13752
|
-
|
|
13753
|
-
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
itemCleanups.push(...localCleanups);
|
|
13758
|
-
if (anchor.parentNode) {
|
|
13759
|
-
let refNode = anchor.nextSibling;
|
|
13760
|
-
if (currentNodes.length > 1) {
|
|
13761
|
-
const lastExisting = currentNodes[currentNodes.length - 2];
|
|
13762
|
-
if (lastExisting) {
|
|
13763
|
-
refNode = lastExisting.nextSibling;
|
|
13764
|
-
}
|
|
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.`);
|
|
13765
14192
|
}
|
|
13766
|
-
|
|
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);
|
|
13767
14229
|
}
|
|
13768
14230
|
});
|
|
13769
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
|
+
}
|
|
13770
14263
|
});
|
|
13771
14264
|
ctx.cleanups.push(effectCleanup);
|
|
13772
14265
|
ctx.cleanups.push(() => {
|
|
@@ -13795,11 +14288,105 @@ function initCopyButtons(container) {
|
|
|
13795
14288
|
});
|
|
13796
14289
|
});
|
|
13797
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
|
+
}
|
|
13798
14381
|
export {
|
|
13799
14382
|
createApp,
|
|
14383
|
+
createComputed,
|
|
14384
|
+
createConnectionManager,
|
|
13800
14385
|
createEffect,
|
|
13801
14386
|
createSignal,
|
|
13802
14387
|
createStateStore,
|
|
14388
|
+
createTypedStateStore,
|
|
14389
|
+
createWebSocketConnection,
|
|
13803
14390
|
evaluate,
|
|
13804
14391
|
evaluateStyle,
|
|
13805
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/compiler": "0.
|
|
22
|
-
"@constela/core": "0.
|
|
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"
|