@constela/runtime 0.12.0 → 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.
Files changed (3) hide show
  1. package/README.md +102 -1
  2. package/dist/index.js +30 -2
  3. package/package.json +4 -4
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.js CHANGED
@@ -422,12 +422,40 @@ function evaluate(expr, ctx) {
422
422
  }
423
423
  case "style":
424
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
+ }
425
431
  default: {
426
432
  const _exhaustiveCheck = expr;
427
433
  throw new Error(`Unknown expression type: ${JSON.stringify(_exhaustiveCheck)}`);
428
434
  }
429
435
  }
430
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
+ }
431
459
  function getNestedValue(obj, path) {
432
460
  const forbiddenKeys = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
433
461
  const parts = path.split(".");
@@ -13165,7 +13193,7 @@ function renderElement(node, ctx) {
13165
13193
  }
13166
13194
  let payload = void 0;
13167
13195
  if (handler.payload) {
13168
- payload = evaluate(handler.payload, {
13196
+ payload = evaluatePayload(handler.payload, {
13169
13197
  state: ctx.state,
13170
13198
  locals: { ...ctx.locals, ...eventLocals },
13171
13199
  ...ctx.imports && { imports: ctx.imports }
@@ -13711,7 +13739,7 @@ function hydrateElement(node, el, ctx) {
13711
13739
  }
13712
13740
  let payload = void 0;
13713
13741
  if (handler.payload) {
13714
- payload = evaluate(handler.payload, {
13742
+ payload = evaluatePayload(handler.payload, {
13715
13743
  state: ctx.state,
13716
13744
  locals: { ...ctx.locals, ...eventLocals },
13717
13745
  ...ctx.imports && { imports: ctx.imports },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/runtime",
3
- "version": "0.12.0",
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/compiler": "0.9.0",
22
- "@constela/core": "0.9.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": "5.0.0"
32
+ "@constela/server": "5.0.1"
33
33
  },
34
34
  "engines": {
35
35
  "node": ">=20.0.0"