@constela/runtime 0.11.1 → 0.12.1

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 CHANGED
@@ -40,6 +40,81 @@ Becomes an interactive app with:
40
40
 
41
41
  ## Features
42
42
 
43
+ ### Fine-grained State Updates (setPath)
44
+
45
+ Update nested values without replacing entire arrays:
46
+
47
+ ```json
48
+ {
49
+ "do": "setPath",
50
+ "target": "posts",
51
+ "path": [5, "liked"],
52
+ "value": { "expr": "lit", "value": true }
53
+ }
54
+ ```
55
+
56
+ Dynamic path with variables:
57
+
58
+ ```json
59
+ {
60
+ "do": "setPath",
61
+ "target": "posts",
62
+ "path": { "expr": "var", "name": "payload", "path": "index" },
63
+ "field": "liked",
64
+ "value": { "expr": "lit", "value": true }
65
+ }
66
+ ```
67
+
68
+ ### Key-based List Diffing
69
+
70
+ Efficient list updates - only changed items re-render:
71
+
72
+ ```json
73
+ {
74
+ "kind": "each",
75
+ "items": { "expr": "state", "name": "posts" },
76
+ "as": "post",
77
+ "key": { "expr": "var", "name": "post", "path": "id" },
78
+ "body": { ... }
79
+ }
80
+ ```
81
+
82
+ Benefits:
83
+ - Add/remove items: Only affected DOM nodes change
84
+ - Reorder: DOM nodes move without recreation
85
+ - Update item: Only that item re-renders
86
+ - Input state preserved during updates
87
+
88
+ ### WebSocket Connections
89
+
90
+ Real-time data with declarative WebSocket:
91
+
92
+ ```json
93
+ {
94
+ "connections": {
95
+ "chat": {
96
+ "type": "websocket",
97
+ "url": "wss://api.example.com/ws",
98
+ "onMessage": { "action": "handleMessage" },
99
+ "onOpen": { "action": "connectionOpened" },
100
+ "onClose": { "action": "connectionClosed" }
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ Send messages:
107
+
108
+ ```json
109
+ { "do": "send", "connection": "chat", "data": { "expr": "state", "name": "inputText" } }
110
+ ```
111
+
112
+ Close connection:
113
+
114
+ ```json
115
+ { "do": "close", "connection": "chat" }
116
+ ```
117
+
43
118
  ### Markdown Rendering
44
119
 
45
120
  ```json
@@ -148,17 +223,43 @@ interface AppInstance {
148
223
  ### Reactive Primitives
149
224
 
150
225
  ```typescript
151
- import { createSignal, createEffect } from '@constela/runtime';
226
+ import { createSignal, createEffect, createComputed } from '@constela/runtime';
152
227
 
153
228
  const count = createSignal(0);
154
229
  count.get(); // Read
155
230
  count.set(1); // Write
156
231
 
232
+ // Computed values with automatic dependency tracking
233
+ const doubled = createComputed(() => count.get() * 2);
234
+ doubled.get(); // Returns memoized value
235
+
157
236
  const cleanup = createEffect(() => {
158
237
  console.log(`Count: ${count.get()}`);
159
238
  });
160
239
  ```
161
240
 
241
+ ### TypedStateStore (TypeScript)
242
+
243
+ Type-safe state access for TypeScript developers:
244
+
245
+ ```typescript
246
+ import { createTypedStateStore } from '@constela/runtime';
247
+
248
+ interface AppState {
249
+ posts: { id: number; liked: boolean }[];
250
+ filter: string;
251
+ }
252
+
253
+ const state = createTypedStateStore<AppState>({
254
+ posts: { type: 'list', initial: [] },
255
+ filter: { type: 'string', initial: '' },
256
+ });
257
+
258
+ state.get('posts'); // Type: { id: number; liked: boolean }[]
259
+ state.set('filter', 'recent'); // OK
260
+ state.set('filter', 123); // TypeScript error
261
+ ```
262
+
162
263
  ## License
163
264
 
164
265
  MIT
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) {
@@ -267,12 +422,40 @@ function evaluate(expr, ctx) {
267
422
  }
268
423
  case "style":
269
424
  return evaluateStyle(expr, ctx);
425
+ case "concat": {
426
+ return expr.items.map((item) => {
427
+ const val = evaluate(item, ctx);
428
+ return val == null ? "" : String(val);
429
+ }).join("");
430
+ }
270
431
  default: {
271
432
  const _exhaustiveCheck = expr;
272
433
  throw new Error(`Unknown expression type: ${JSON.stringify(_exhaustiveCheck)}`);
273
434
  }
274
435
  }
275
436
  }
437
+ function isExpression(value) {
438
+ return typeof value === "object" && value !== null && Object.prototype.hasOwnProperty.call(value, "expr") && typeof value.expr === "string";
439
+ }
440
+ function evaluatePayload(payload, ctx) {
441
+ if (isExpression(payload)) {
442
+ return evaluate(payload, ctx);
443
+ }
444
+ if (typeof payload === "object" && payload !== null) {
445
+ const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
446
+ const result = {};
447
+ for (const [key2, value] of Object.entries(payload)) {
448
+ if (forbiddenKeys.has(key2)) continue;
449
+ if (isExpression(value)) {
450
+ result[key2] = evaluate(value, ctx);
451
+ } else {
452
+ result[key2] = value;
453
+ }
454
+ }
455
+ return result;
456
+ }
457
+ return payload;
458
+ }
276
459
  function getNestedValue(obj, path) {
277
460
  const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
278
461
  const parts = path.split(".");
@@ -408,7 +591,7 @@ function createEvalContext(ctx) {
408
591
  }
409
592
  async function executeAction(action, ctx) {
410
593
  for (const step of action.steps) {
411
- if (step.do === "set" || step.do === "update") {
594
+ if (step.do === "set" || step.do === "update" || step.do === "setPath") {
412
595
  executeStepSync(step, ctx);
413
596
  } else if (step.do === "if") {
414
597
  await executeIfStep(step, ctx);
@@ -425,6 +608,9 @@ function executeStepSync(step, ctx) {
425
608
  case "update":
426
609
  executeUpdateStepSync(step, ctx);
427
610
  break;
611
+ case "setPath":
612
+ executeSetPathStepSync(step, ctx);
613
+ break;
428
614
  }
429
615
  }
430
616
  async function executeIfStep(step, ctx) {
@@ -432,7 +618,7 @@ async function executeIfStep(step, ctx) {
432
618
  const condition = evaluate(step.condition, evalCtx);
433
619
  const stepsToExecute = condition ? step.then : step.else || [];
434
620
  for (const nestedStep of stepsToExecute) {
435
- if (nestedStep.do === "set" || nestedStep.do === "update") {
621
+ if (nestedStep.do === "set" || nestedStep.do === "update" || nestedStep.do === "setPath") {
436
622
  executeStepSync(nestedStep, ctx);
437
623
  } else if (nestedStep.do === "if") {
438
624
  await executeIfStep(nestedStep, ctx);
@@ -532,6 +718,31 @@ function executeUpdateStepSync(step, ctx) {
532
718
  }
533
719
  }
534
720
  }
721
+ function executeSetPathStepSync(step, ctx) {
722
+ const evalCtx = createEvalContext(ctx);
723
+ const pathValue = evaluate(step.path, evalCtx);
724
+ let path;
725
+ if (typeof pathValue === "string") {
726
+ path = pathValue.split(".").map((segment) => {
727
+ const num = parseInt(segment, 10);
728
+ return isNaN(num) ? segment : num;
729
+ });
730
+ } else if (Array.isArray(pathValue)) {
731
+ path = pathValue.map((item) => {
732
+ if (typeof item === "object" && item !== null && "expr" in item) {
733
+ return evaluate(item, evalCtx);
734
+ }
735
+ return item;
736
+ });
737
+ } else {
738
+ path = [pathValue];
739
+ }
740
+ const newValue = evaluate(step.value, evalCtx);
741
+ ctx.state.setPath(step.target, path, newValue);
742
+ }
743
+ async function executeSetPathStep(step, ctx) {
744
+ executeSetPathStepSync(step, ctx);
745
+ }
535
746
  async function executeStep(step, ctx) {
536
747
  switch (step.do) {
537
748
  case "set":
@@ -540,6 +751,9 @@ async function executeStep(step, ctx) {
540
751
  case "update":
541
752
  await executeUpdateStep(step, ctx);
542
753
  break;
754
+ case "setPath":
755
+ await executeSetPathStep(step, ctx);
756
+ break;
543
757
  case "fetch":
544
758
  await executeFetchStep(step, ctx);
545
759
  break;
@@ -570,6 +784,12 @@ async function executeStep(step, ctx) {
570
784
  case "if":
571
785
  await executeIfStep(step, ctx);
572
786
  break;
787
+ case "send":
788
+ await executeSendStep(step, ctx);
789
+ break;
790
+ case "close":
791
+ await executeCloseStep(step, ctx);
792
+ break;
573
793
  }
574
794
  }
575
795
  async function executeSetStep(target, value, ctx) {
@@ -924,6 +1144,18 @@ async function executeDomStep(step, ctx) {
924
1144
  break;
925
1145
  }
926
1146
  }
1147
+ async function executeSendStep(step, ctx) {
1148
+ if (!ctx.connections) {
1149
+ throw new Error(`Connection "${step.connection}" not found`);
1150
+ }
1151
+ const evalCtx = createEvalContext(ctx);
1152
+ const data = evaluate(step.data, evalCtx);
1153
+ ctx.connections.send(step.connection, data);
1154
+ }
1155
+ async function executeCloseStep(step, ctx) {
1156
+ if (!ctx.connections) return;
1157
+ ctx.connections.close(step.connection);
1158
+ }
927
1159
 
928
1160
  // ../../node_modules/.pnpm/marked@17.0.1/node_modules/marked/lib/marked.esm.js
929
1161
  function L() {
@@ -12961,7 +13193,7 @@ function renderElement(node, ctx) {
12961
13193
  }
12962
13194
  let payload = void 0;
12963
13195
  if (handler.payload) {
12964
- payload = evaluate(handler.payload, {
13196
+ payload = evaluatePayload(handler.payload, {
12965
13197
  state: ctx.state,
12966
13198
  locals: { ...ctx.locals, ...eventLocals },
12967
13199
  ...ctx.imports && { imports: ctx.imports }
@@ -13095,52 +13327,162 @@ function renderIf(node, ctx) {
13095
13327
  }
13096
13328
  return fragment;
13097
13329
  }
13330
+ function createReactiveLocals(baseLocals, itemKey, itemSignal, indexKey, indexSignal) {
13331
+ return new Proxy(baseLocals, {
13332
+ get(target, prop) {
13333
+ if (prop === itemKey) {
13334
+ return itemSignal.get();
13335
+ }
13336
+ if (indexKey && prop === indexKey) {
13337
+ return indexSignal.get();
13338
+ }
13339
+ return target[prop];
13340
+ },
13341
+ has(target, prop) {
13342
+ if (prop === itemKey) return true;
13343
+ if (indexKey && prop === indexKey) return true;
13344
+ return prop in target;
13345
+ }
13346
+ });
13347
+ }
13098
13348
  function renderEach(node, ctx) {
13099
13349
  const anchor = document.createComment("each");
13350
+ const hasKey = !!node.key;
13351
+ let itemStateMap = /* @__PURE__ */ new Map();
13100
13352
  let currentNodes = [];
13101
13353
  let itemCleanups = [];
13102
13354
  const effectCleanup = createEffect(() => {
13103
13355
  const items = evaluate(node.items, { state: ctx.state, locals: ctx.locals, ...ctx.imports && { imports: ctx.imports } });
13104
- for (const cleanup of itemCleanups) {
13105
- cleanup();
13106
- }
13107
- itemCleanups = [];
13108
- for (const oldNode of currentNodes) {
13109
- if (oldNode.parentNode) {
13110
- oldNode.parentNode.removeChild(oldNode);
13356
+ if (!hasKey || !node.key) {
13357
+ for (const cleanup of itemCleanups) {
13358
+ cleanup();
13359
+ }
13360
+ itemCleanups = [];
13361
+ for (const oldNode of currentNodes) {
13362
+ if (oldNode.parentNode) {
13363
+ oldNode.parentNode.removeChild(oldNode);
13364
+ }
13111
13365
  }
13366
+ currentNodes = [];
13367
+ if (Array.isArray(items)) {
13368
+ items.forEach((item, index) => {
13369
+ const itemLocals = {
13370
+ ...ctx.locals,
13371
+ [node.as]: item
13372
+ };
13373
+ if (node.index) {
13374
+ itemLocals[node.index] = index;
13375
+ }
13376
+ const localCleanups = [];
13377
+ const itemCtx = {
13378
+ ...ctx,
13379
+ locals: itemLocals,
13380
+ cleanups: localCleanups
13381
+ };
13382
+ const itemNode = render(node.body, itemCtx);
13383
+ currentNodes.push(itemNode);
13384
+ itemCleanups.push(...localCleanups);
13385
+ if (anchor.parentNode) {
13386
+ let refNode = anchor.nextSibling;
13387
+ if (currentNodes.length > 1) {
13388
+ const lastExisting = currentNodes[currentNodes.length - 2];
13389
+ if (lastExisting) {
13390
+ refNode = lastExisting.nextSibling;
13391
+ }
13392
+ }
13393
+ anchor.parentNode.insertBefore(itemNode, refNode);
13394
+ }
13395
+ });
13396
+ }
13397
+ return;
13112
13398
  }
13113
- currentNodes = [];
13399
+ const newItemStateMap = /* @__PURE__ */ new Map();
13400
+ const newNodes = [];
13401
+ const seenKeys = /* @__PURE__ */ new Set();
13114
13402
  if (Array.isArray(items)) {
13115
13403
  items.forEach((item, index) => {
13116
- const itemLocals = {
13404
+ const tempLocals = {
13117
13405
  ...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
13406
+ [node.as]: item,
13407
+ ...node.index ? { [node.index]: index } : {}
13128
13408
  };
13129
- const itemNode = render(node.body, itemCtx);
13130
- currentNodes.push(itemNode);
13131
- itemCleanups.push(...localCleanups);
13132
- if (anchor.parentNode) {
13133
- let refNode = anchor.nextSibling;
13134
- if (currentNodes.length > 1) {
13135
- const lastExisting = currentNodes[currentNodes.length - 2];
13136
- if (lastExisting) {
13137
- refNode = lastExisting.nextSibling;
13138
- }
13409
+ const keyValue = evaluate(node.key, {
13410
+ state: ctx.state,
13411
+ locals: tempLocals,
13412
+ ...ctx.imports && { imports: ctx.imports }
13413
+ });
13414
+ if (seenKeys.has(keyValue)) {
13415
+ if (typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production") {
13416
+ console.warn(`Duplicate key "${keyValue}" in each loop. Keys should be unique.`);
13139
13417
  }
13140
- anchor.parentNode.insertBefore(itemNode, refNode);
13418
+ }
13419
+ seenKeys.add(keyValue);
13420
+ const existingState = itemStateMap.get(keyValue);
13421
+ if (existingState) {
13422
+ existingState.itemSignal.set(item);
13423
+ existingState.indexSignal.set(index);
13424
+ newItemStateMap.set(keyValue, existingState);
13425
+ newNodes.push(existingState.node);
13426
+ } else {
13427
+ const itemSignal = createSignal(item);
13428
+ const indexSignal = createSignal(index);
13429
+ const reactiveLocals = createReactiveLocals(
13430
+ ctx.locals,
13431
+ node.as,
13432
+ itemSignal,
13433
+ node.index,
13434
+ indexSignal
13435
+ );
13436
+ const localCleanups = [];
13437
+ const itemCtx = {
13438
+ ...ctx,
13439
+ locals: reactiveLocals,
13440
+ cleanups: localCleanups
13441
+ };
13442
+ const itemNode = render(node.body, itemCtx);
13443
+ const newState = {
13444
+ key: keyValue,
13445
+ node: itemNode,
13446
+ cleanups: localCleanups,
13447
+ itemSignal,
13448
+ indexSignal
13449
+ };
13450
+ newItemStateMap.set(keyValue, newState);
13451
+ newNodes.push(itemNode);
13141
13452
  }
13142
13453
  });
13143
13454
  }
13455
+ for (const [key2, state] of itemStateMap) {
13456
+ if (!newItemStateMap.has(key2)) {
13457
+ for (const cleanup of state.cleanups) {
13458
+ cleanup();
13459
+ }
13460
+ if (state.node.parentNode) {
13461
+ state.node.parentNode.removeChild(state.node);
13462
+ }
13463
+ }
13464
+ }
13465
+ const activeElement = document.activeElement;
13466
+ const shouldRestoreFocus = activeElement && activeElement !== document.body;
13467
+ if (anchor.parentNode) {
13468
+ let refNode = anchor;
13469
+ for (const itemNode of newNodes) {
13470
+ const nextSibling = refNode.nextSibling;
13471
+ if (nextSibling !== itemNode) {
13472
+ anchor.parentNode.insertBefore(itemNode, refNode.nextSibling);
13473
+ }
13474
+ refNode = itemNode;
13475
+ }
13476
+ }
13477
+ if (shouldRestoreFocus && activeElement instanceof HTMLElement && document.activeElement !== activeElement) {
13478
+ activeElement.focus();
13479
+ }
13480
+ itemStateMap = newItemStateMap;
13481
+ currentNodes = newNodes;
13482
+ itemCleanups = [];
13483
+ for (const state of itemStateMap.values()) {
13484
+ itemCleanups.push(...state.cleanups);
13485
+ }
13144
13486
  });
13145
13487
  ctx.cleanups?.push(effectCleanup);
13146
13488
  ctx.cleanups?.push(() => {
@@ -13263,6 +13605,20 @@ function createApp(program, mount) {
13263
13605
  }
13264
13606
 
13265
13607
  // src/hydrate.ts
13608
+ function createReactiveLocals2(baseLocals, itemSignal, indexSignal, itemName, indexName) {
13609
+ return new Proxy(baseLocals, {
13610
+ get(target, prop) {
13611
+ if (prop === itemName) return itemSignal.get();
13612
+ if (indexName && prop === indexName) return indexSignal.get();
13613
+ return target[prop];
13614
+ },
13615
+ has(target, prop) {
13616
+ if (prop === itemName) return true;
13617
+ if (indexName && prop === indexName) return true;
13618
+ return prop in target;
13619
+ }
13620
+ });
13621
+ }
13266
13622
  function isEventHandler2(value) {
13267
13623
  return typeof value === "object" && value !== null && "event" in value && "action" in value;
13268
13624
  }
@@ -13383,7 +13739,7 @@ function hydrateElement(node, el, ctx) {
13383
13739
  }
13384
13740
  let payload = void 0;
13385
13741
  if (handler.payload) {
13386
- payload = evaluate(handler.payload, {
13742
+ payload = evaluatePayload(handler.payload, {
13387
13743
  state: ctx.state,
13388
13744
  locals: { ...ctx.locals, ...eventLocals },
13389
13745
  ...ctx.imports && { imports: ctx.imports },
@@ -13693,6 +14049,8 @@ function hydrateEach(node, firstItemDomNode, ctx) {
13693
14049
  if (!parent) return;
13694
14050
  const anchor = document.createComment("each");
13695
14051
  parent.insertBefore(anchor, firstItemDomNode);
14052
+ const hasKey = !!node.key;
14053
+ let itemStateMap = /* @__PURE__ */ new Map();
13696
14054
  let currentNodes = [];
13697
14055
  let itemCleanups = [];
13698
14056
  const initialItems = evaluate(node.items, {
@@ -13704,29 +14062,85 @@ function hydrateEach(node, firstItemDomNode, ctx) {
13704
14062
  let isFirstRun = true;
13705
14063
  if (Array.isArray(initialItems) && initialItems.length > 0) {
13706
14064
  let domNode = firstItemDomNode;
13707
- initialItems.forEach((item, index) => {
13708
- if (!domNode) return;
13709
- currentNodes.push(domNode);
13710
- const itemLocals = {
13711
- ...ctx.locals,
13712
- [node.as]: item
13713
- };
13714
- if (node.index) {
13715
- itemLocals[node.index] = index;
13716
- }
13717
- const localCleanups = [];
13718
- const itemCtx = {
13719
- ...ctx,
13720
- locals: itemLocals,
13721
- cleanups: localCleanups
13722
- };
13723
- hydrate(node.body, domNode, itemCtx);
13724
- itemCleanups.push(...localCleanups);
13725
- domNode = domNode.nextSibling;
13726
- while (domNode && domNode.nodeType === Node.COMMENT_NODE) {
14065
+ if (hasKey && node.key) {
14066
+ const seenKeys = /* @__PURE__ */ new Set();
14067
+ initialItems.forEach((item, index) => {
14068
+ if (!domNode) return;
14069
+ const tempLocals = {
14070
+ ...ctx.locals,
14071
+ [node.as]: item,
14072
+ ...node.index ? { [node.index]: index } : {}
14073
+ };
14074
+ const keyValue = evaluate(node.key, {
14075
+ state: ctx.state,
14076
+ locals: tempLocals,
14077
+ ...ctx.imports && { imports: ctx.imports },
14078
+ ...ctx.route && { route: ctx.route }
14079
+ });
14080
+ if (seenKeys.has(keyValue)) {
14081
+ if (typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production") {
14082
+ console.warn(`Duplicate key "${keyValue}" in each loop. Keys should be unique.`);
14083
+ }
14084
+ }
14085
+ seenKeys.add(keyValue);
14086
+ const itemSignal = createSignal(item);
14087
+ const indexSignal = createSignal(index);
14088
+ const reactiveLocals = createReactiveLocals2(
14089
+ ctx.locals,
14090
+ itemSignal,
14091
+ indexSignal,
14092
+ node.as,
14093
+ node.index
14094
+ );
14095
+ const localCleanups = [];
14096
+ const itemCtx = {
14097
+ ...ctx,
14098
+ locals: reactiveLocals,
14099
+ cleanups: localCleanups
14100
+ };
14101
+ hydrate(node.body, domNode, itemCtx);
14102
+ const itemState = {
14103
+ key: keyValue,
14104
+ node: domNode,
14105
+ cleanups: localCleanups,
14106
+ itemSignal,
14107
+ indexSignal
14108
+ };
14109
+ itemStateMap.set(keyValue, itemState);
14110
+ currentNodes.push(domNode);
13727
14111
  domNode = domNode.nextSibling;
14112
+ while (domNode && domNode.nodeType === Node.COMMENT_NODE) {
14113
+ domNode = domNode.nextSibling;
14114
+ }
14115
+ });
14116
+ for (const state of itemStateMap.values()) {
14117
+ itemCleanups.push(...state.cleanups);
13728
14118
  }
13729
- });
14119
+ } else {
14120
+ initialItems.forEach((item, index) => {
14121
+ if (!domNode) return;
14122
+ currentNodes.push(domNode);
14123
+ const itemLocals = {
14124
+ ...ctx.locals,
14125
+ [node.as]: item
14126
+ };
14127
+ if (node.index) {
14128
+ itemLocals[node.index] = index;
14129
+ }
14130
+ const localCleanups = [];
14131
+ const itemCtx = {
14132
+ ...ctx,
14133
+ locals: itemLocals,
14134
+ cleanups: localCleanups
14135
+ };
14136
+ hydrate(node.body, domNode, itemCtx);
14137
+ itemCleanups.push(...localCleanups);
14138
+ domNode = domNode.nextSibling;
14139
+ while (domNode && domNode.nodeType === Node.COMMENT_NODE) {
14140
+ domNode = domNode.nextSibling;
14141
+ }
14142
+ });
14143
+ }
13730
14144
  }
13731
14145
  const effectCleanup = createEffect(() => {
13732
14146
  const items = evaluate(node.items, {
@@ -13739,48 +14153,141 @@ function hydrateEach(node, firstItemDomNode, ctx) {
13739
14153
  isFirstRun = false;
13740
14154
  return;
13741
14155
  }
13742
- for (const cleanup of itemCleanups) {
13743
- cleanup();
13744
- }
13745
- itemCleanups = [];
13746
- for (const oldNode of currentNodes) {
13747
- if (oldNode.parentNode) {
13748
- oldNode.parentNode.removeChild(oldNode);
14156
+ if (!hasKey || !node.key) {
14157
+ for (const cleanup of itemCleanups) {
14158
+ cleanup();
13749
14159
  }
14160
+ itemCleanups = [];
14161
+ for (const oldNode of currentNodes) {
14162
+ if (oldNode.parentNode) {
14163
+ oldNode.parentNode.removeChild(oldNode);
14164
+ }
14165
+ }
14166
+ currentNodes = [];
14167
+ if (Array.isArray(items)) {
14168
+ items.forEach((item, index) => {
14169
+ const itemLocals = {
14170
+ ...ctx.locals,
14171
+ [node.as]: item
14172
+ };
14173
+ if (node.index) {
14174
+ itemLocals[node.index] = index;
14175
+ }
14176
+ const localCleanups = [];
14177
+ const itemCtx = {
14178
+ state: ctx.state,
14179
+ actions: ctx.actions,
14180
+ locals: itemLocals,
14181
+ cleanups: localCleanups,
14182
+ ...ctx.imports && { imports: ctx.imports }
14183
+ };
14184
+ const itemNode = render(node.body, itemCtx);
14185
+ currentNodes.push(itemNode);
14186
+ itemCleanups.push(...localCleanups);
14187
+ if (anchor.parentNode) {
14188
+ let refNode = anchor.nextSibling;
14189
+ if (currentNodes.length > 1) {
14190
+ const lastExisting = currentNodes[currentNodes.length - 2];
14191
+ if (lastExisting) {
14192
+ refNode = lastExisting.nextSibling;
14193
+ }
14194
+ }
14195
+ anchor.parentNode.insertBefore(itemNode, refNode);
14196
+ }
14197
+ });
14198
+ }
14199
+ return;
13750
14200
  }
13751
- currentNodes = [];
14201
+ const newItemStateMap = /* @__PURE__ */ new Map();
14202
+ const newNodes = [];
14203
+ const seenKeys = /* @__PURE__ */ new Set();
13752
14204
  if (Array.isArray(items)) {
13753
14205
  items.forEach((item, index) => {
13754
- const itemLocals = {
14206
+ const tempLocals = {
13755
14207
  ...ctx.locals,
13756
- [node.as]: item
14208
+ [node.as]: item,
14209
+ ...node.index ? { [node.index]: index } : {}
13757
14210
  };
13758
- if (node.index) {
13759
- itemLocals[node.index] = index;
13760
- }
13761
- const localCleanups = [];
13762
- const itemCtx = {
14211
+ const keyValue = evaluate(node.key, {
13763
14212
  state: ctx.state,
13764
- actions: ctx.actions,
13765
- locals: itemLocals,
13766
- cleanups: localCleanups,
13767
- ...ctx.imports && { imports: ctx.imports }
13768
- };
13769
- const itemNode = render(node.body, itemCtx);
13770
- currentNodes.push(itemNode);
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
- }
14213
+ locals: tempLocals,
14214
+ ...ctx.imports && { imports: ctx.imports },
14215
+ ...ctx.route && { route: ctx.route }
14216
+ });
14217
+ if (seenKeys.has(keyValue)) {
14218
+ if (typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production") {
14219
+ console.warn(`Duplicate key "${keyValue}" in each loop. Keys should be unique.`);
13779
14220
  }
13780
- anchor.parentNode.insertBefore(itemNode, refNode);
14221
+ }
14222
+ seenKeys.add(keyValue);
14223
+ const existingState = itemStateMap.get(keyValue);
14224
+ if (existingState) {
14225
+ existingState.itemSignal.set(item);
14226
+ existingState.indexSignal.set(index);
14227
+ newItemStateMap.set(keyValue, existingState);
14228
+ newNodes.push(existingState.node);
14229
+ } else {
14230
+ const itemSignal = createSignal(item);
14231
+ const indexSignal = createSignal(index);
14232
+ const reactiveLocals = createReactiveLocals2(
14233
+ ctx.locals,
14234
+ itemSignal,
14235
+ indexSignal,
14236
+ node.as,
14237
+ node.index
14238
+ );
14239
+ const localCleanups = [];
14240
+ const itemCtx = {
14241
+ state: ctx.state,
14242
+ actions: ctx.actions,
14243
+ locals: reactiveLocals,
14244
+ cleanups: localCleanups,
14245
+ ...ctx.imports && { imports: ctx.imports }
14246
+ };
14247
+ const itemNode = render(node.body, itemCtx);
14248
+ const newState = {
14249
+ key: keyValue,
14250
+ node: itemNode,
14251
+ cleanups: localCleanups,
14252
+ itemSignal,
14253
+ indexSignal
14254
+ };
14255
+ newItemStateMap.set(keyValue, newState);
14256
+ newNodes.push(itemNode);
13781
14257
  }
13782
14258
  });
13783
14259
  }
14260
+ for (const [key2, state] of itemStateMap) {
14261
+ if (!newItemStateMap.has(key2)) {
14262
+ for (const cleanup of state.cleanups) {
14263
+ cleanup();
14264
+ }
14265
+ if (state.node.parentNode) {
14266
+ state.node.parentNode.removeChild(state.node);
14267
+ }
14268
+ }
14269
+ }
14270
+ const activeElement = document.activeElement;
14271
+ const shouldRestoreFocus = activeElement && activeElement !== document.body;
14272
+ if (anchor.parentNode) {
14273
+ let refNode = anchor;
14274
+ for (const itemNode of newNodes) {
14275
+ const nextSibling = refNode.nextSibling;
14276
+ if (nextSibling !== itemNode) {
14277
+ anchor.parentNode.insertBefore(itemNode, refNode.nextSibling);
14278
+ }
14279
+ refNode = itemNode;
14280
+ }
14281
+ }
14282
+ if (shouldRestoreFocus && activeElement instanceof HTMLElement && document.activeElement !== activeElement) {
14283
+ activeElement.focus();
14284
+ }
14285
+ itemStateMap = newItemStateMap;
14286
+ currentNodes = newNodes;
14287
+ itemCleanups = [];
14288
+ for (const state of itemStateMap.values()) {
14289
+ itemCleanups.push(...state.cleanups);
14290
+ }
13784
14291
  });
13785
14292
  ctx.cleanups.push(effectCleanup);
13786
14293
  ctx.cleanups.push(() => {
@@ -13809,11 +14316,105 @@ function initCopyButtons(container) {
13809
14316
  });
13810
14317
  });
13811
14318
  }
14319
+
14320
+ // src/connection/websocket.ts
14321
+ function createWebSocketConnection(url, handlers) {
14322
+ const ws = new WebSocket(url);
14323
+ ws.onopen = () => {
14324
+ handlers.onOpen?.();
14325
+ };
14326
+ ws.onclose = (event) => {
14327
+ handlers.onClose?.(event.code, event.reason);
14328
+ };
14329
+ ws.onerror = (event) => {
14330
+ handlers.onError?.(event);
14331
+ };
14332
+ ws.onmessage = (event) => {
14333
+ let data = event.data;
14334
+ if (typeof data === "string") {
14335
+ try {
14336
+ data = JSON.parse(data);
14337
+ } catch {
14338
+ }
14339
+ }
14340
+ handlers.onMessage?.(data);
14341
+ };
14342
+ return {
14343
+ send(data) {
14344
+ if (ws.readyState === WebSocket.OPEN) {
14345
+ let message;
14346
+ if (typeof data === "string") {
14347
+ message = data;
14348
+ } else {
14349
+ message = JSON.stringify(data);
14350
+ }
14351
+ ws.send(message);
14352
+ }
14353
+ },
14354
+ close() {
14355
+ ws.close();
14356
+ },
14357
+ getState() {
14358
+ switch (ws.readyState) {
14359
+ case WebSocket.CONNECTING:
14360
+ return "connecting";
14361
+ case WebSocket.OPEN:
14362
+ return "open";
14363
+ case WebSocket.CLOSING:
14364
+ return "closing";
14365
+ case WebSocket.CLOSED:
14366
+ return "closed";
14367
+ default:
14368
+ return "closed";
14369
+ }
14370
+ }
14371
+ };
14372
+ }
14373
+ function createConnectionManager() {
14374
+ const connections = /* @__PURE__ */ new Map();
14375
+ return {
14376
+ create(name, url, handlers) {
14377
+ const existing = connections.get(name);
14378
+ if (existing) {
14379
+ existing.close();
14380
+ }
14381
+ const conn = createWebSocketConnection(url, handlers);
14382
+ connections.set(name, conn);
14383
+ },
14384
+ get(name) {
14385
+ return connections.get(name);
14386
+ },
14387
+ send(name, data) {
14388
+ const conn = connections.get(name);
14389
+ if (!conn) {
14390
+ throw new Error(`Connection "${name}" not found`);
14391
+ }
14392
+ conn.send(data);
14393
+ },
14394
+ close(name) {
14395
+ const conn = connections.get(name);
14396
+ if (conn) {
14397
+ conn.close();
14398
+ connections.delete(name);
14399
+ }
14400
+ },
14401
+ closeAll() {
14402
+ for (const conn of connections.values()) {
14403
+ conn.close();
14404
+ }
14405
+ connections.clear();
14406
+ }
14407
+ };
14408
+ }
13812
14409
  export {
13813
14410
  createApp,
14411
+ createComputed,
14412
+ createConnectionManager,
13814
14413
  createEffect,
13815
14414
  createSignal,
13816
14415
  createStateStore,
14416
+ createTypedStateStore,
14417
+ createWebSocketConnection,
13817
14418
  evaluate,
13818
14419
  evaluateStyle,
13819
14420
  executeAction,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/runtime",
3
- "version": "0.11.1",
3
+ "version": "0.12.1",
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/core": "0.8.0",
22
- "@constela/compiler": "0.8.0"
21
+ "@constela/compiler": "0.9.1",
22
+ "@constela/core": "0.9.1"
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": "4.1.0"
32
+ "@constela/server": "5.0.1"
33
33
  },
34
34
  "engines": {
35
35
  "node": ">=20.0.0"