@constela/runtime 0.10.1 → 0.10.2
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 +306 -0
- package/dist/index.js +3 -0
- package/package.json +4 -4
package/README.md
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# @constela/runtime
|
|
2
|
+
|
|
3
|
+
Runtime DOM renderer for the Constela UI framework with fine-grained reactivity.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @constela/runtime
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
This package provides the client-side rendering engine for Constela applications. Key features:
|
|
14
|
+
|
|
15
|
+
- **Fine-grained Reactivity** - Signal-based updates without virtual DOM
|
|
16
|
+
- **Hydration** - Rehydrate server-rendered HTML
|
|
17
|
+
- **Markdown & Code** - Built-in Markdown and syntax highlighting support
|
|
18
|
+
|
|
19
|
+
## API Reference
|
|
20
|
+
|
|
21
|
+
### createApp
|
|
22
|
+
|
|
23
|
+
Creates and mounts a Constela application.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { createApp } from '@constela/runtime';
|
|
27
|
+
|
|
28
|
+
const app = createApp(compiledProgram, document.getElementById('app'));
|
|
29
|
+
|
|
30
|
+
// Later: cleanup
|
|
31
|
+
app.destroy();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Parameters:**
|
|
35
|
+
- `program: CompiledProgram` - Compiled program from `@constela/compiler`
|
|
36
|
+
- `mount: HTMLElement` - DOM element to mount to
|
|
37
|
+
|
|
38
|
+
**Returns:** `AppInstance`
|
|
39
|
+
|
|
40
|
+
### hydrateApp
|
|
41
|
+
|
|
42
|
+
Hydrates server-rendered HTML without DOM reconstruction.
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { hydrateApp } from '@constela/runtime';
|
|
46
|
+
|
|
47
|
+
const app = hydrateApp({
|
|
48
|
+
program: compiledProgram,
|
|
49
|
+
mount: document.getElementById('app'),
|
|
50
|
+
route: {
|
|
51
|
+
params: { id: '123' },
|
|
52
|
+
query: { tab: 'details' },
|
|
53
|
+
path: '/users/123'
|
|
54
|
+
},
|
|
55
|
+
imports: {
|
|
56
|
+
config: { apiUrl: 'https://api.example.com' }
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**HydrateOptions:**
|
|
62
|
+
- `program: CompiledProgram` - Compiled program
|
|
63
|
+
- `mount: HTMLElement` - Container element
|
|
64
|
+
- `route?: RouteContext` - Route parameters
|
|
65
|
+
- `imports?: Record<string, unknown>` - Import data
|
|
66
|
+
|
|
67
|
+
### AppInstance
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
interface AppInstance {
|
|
71
|
+
destroy(): void;
|
|
72
|
+
setState(name: string, value: unknown): void;
|
|
73
|
+
getState(name: string): unknown;
|
|
74
|
+
subscribe(name: string, fn: (value: unknown) => void): () => void;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### destroy()
|
|
79
|
+
|
|
80
|
+
Cleans up the application, removes event listeners, and clears state.
|
|
81
|
+
|
|
82
|
+
#### setState(name, value)
|
|
83
|
+
|
|
84
|
+
Updates a state field programmatically.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
app.setState('count', 10);
|
|
88
|
+
app.setState('user', { name: 'John', email: 'john@example.com' });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### getState(name)
|
|
92
|
+
|
|
93
|
+
Reads current state value.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const count = app.getState('count');
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### subscribe(name, fn)
|
|
100
|
+
|
|
101
|
+
Subscribes to state changes. Returns an unsubscribe function.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const unsubscribe = app.subscribe('count', (value) => {
|
|
105
|
+
console.log('count changed:', value);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Later: stop listening
|
|
109
|
+
unsubscribe();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Reactive Primitives
|
|
113
|
+
|
|
114
|
+
Low-level reactive APIs for advanced usage.
|
|
115
|
+
|
|
116
|
+
### createSignal
|
|
117
|
+
|
|
118
|
+
Creates a reactive signal with fine-grained dependency tracking.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { createSignal } from '@constela/runtime';
|
|
122
|
+
|
|
123
|
+
const count = createSignal(0);
|
|
124
|
+
|
|
125
|
+
// Read value (auto-tracks in effects)
|
|
126
|
+
console.log(count.get()); // 0
|
|
127
|
+
|
|
128
|
+
// Update value
|
|
129
|
+
count.set(1);
|
|
130
|
+
|
|
131
|
+
// Subscribe to changes
|
|
132
|
+
const unsubscribe = count.subscribe((value) => {
|
|
133
|
+
console.log('Value:', value);
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### createEffect
|
|
138
|
+
|
|
139
|
+
Creates a reactive side effect that auto-tracks dependencies.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { createSignal, createEffect } from '@constela/runtime';
|
|
143
|
+
|
|
144
|
+
const name = createSignal('World');
|
|
145
|
+
|
|
146
|
+
const cleanup = createEffect(() => {
|
|
147
|
+
console.log(`Hello, ${name.get()}!`);
|
|
148
|
+
|
|
149
|
+
// Optional cleanup function
|
|
150
|
+
return () => {
|
|
151
|
+
console.log('Effect cleaned up');
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
name.set('Constela'); // Logs: "Hello, Constela!"
|
|
156
|
+
|
|
157
|
+
cleanup(); // Stops the effect
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### createStateStore
|
|
161
|
+
|
|
162
|
+
Centralized state management with signal-based reactivity.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { createStateStore } from '@constela/runtime';
|
|
166
|
+
|
|
167
|
+
const store = createStateStore({
|
|
168
|
+
count: { type: 'number', initial: 0 },
|
|
169
|
+
name: { type: 'string', initial: '' }
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
store.get('count'); // 0
|
|
173
|
+
store.set('count', 5);
|
|
174
|
+
store.subscribe('count', (value) => console.log(value));
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Expression Evaluation
|
|
178
|
+
|
|
179
|
+
### evaluate
|
|
180
|
+
|
|
181
|
+
Evaluates compiled expressions.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { evaluate } from '@constela/runtime';
|
|
185
|
+
|
|
186
|
+
const result = evaluate(expression, {
|
|
187
|
+
state: stateStore,
|
|
188
|
+
locals: { item: { id: 1, name: 'Test' } },
|
|
189
|
+
route: { params: { id: '123' }, query: new URLSearchParams(), path: '/items/123' },
|
|
190
|
+
imports: { config: { apiUrl: '...' } },
|
|
191
|
+
data: { posts: [...] },
|
|
192
|
+
refs: { inputEl: document.querySelector('#input') }
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Supported Expressions:**
|
|
197
|
+
- Literals, state reads, variables
|
|
198
|
+
- Binary operations, logical not, conditionals
|
|
199
|
+
- Property access, array indexing
|
|
200
|
+
- Route parameters/query/path
|
|
201
|
+
- Imports and loaded data
|
|
202
|
+
- DOM refs
|
|
203
|
+
|
|
204
|
+
## Action Execution
|
|
205
|
+
|
|
206
|
+
### executeAction
|
|
207
|
+
|
|
208
|
+
Executes compiled actions.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { executeAction } from '@constela/runtime';
|
|
212
|
+
|
|
213
|
+
await executeAction(action, {
|
|
214
|
+
state: stateStore,
|
|
215
|
+
locals: {},
|
|
216
|
+
route: { ... },
|
|
217
|
+
imports: { ... },
|
|
218
|
+
refs: { ... },
|
|
219
|
+
subscriptions: []
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Supported Steps:**
|
|
224
|
+
- `set`, `update` - State mutations
|
|
225
|
+
- `fetch` - HTTP requests
|
|
226
|
+
- `storage` - localStorage/sessionStorage
|
|
227
|
+
- `clipboard` - Clipboard operations
|
|
228
|
+
- `navigate` - Page navigation
|
|
229
|
+
- `import`, `call` - Dynamic imports and function calls
|
|
230
|
+
- `subscribe`, `dispose` - Event subscriptions
|
|
231
|
+
- `dom` - DOM manipulation
|
|
232
|
+
- `if` - Conditional execution
|
|
233
|
+
|
|
234
|
+
## Rendering
|
|
235
|
+
|
|
236
|
+
### render
|
|
237
|
+
|
|
238
|
+
Renders compiled nodes to DOM.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { render } from '@constela/runtime';
|
|
242
|
+
|
|
243
|
+
const domNode = render(compiledNode, {
|
|
244
|
+
state: stateStore,
|
|
245
|
+
actions: compiledProgram.actions,
|
|
246
|
+
components: {},
|
|
247
|
+
locals: {},
|
|
248
|
+
route: { ... },
|
|
249
|
+
imports: { ... },
|
|
250
|
+
refs: {},
|
|
251
|
+
subscriptions: [],
|
|
252
|
+
cleanups: []
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Supported Nodes:**
|
|
257
|
+
- Element nodes with props and event handlers
|
|
258
|
+
- Text nodes with reactive updates
|
|
259
|
+
- Conditional rendering (`if/else`)
|
|
260
|
+
- List rendering (`each`)
|
|
261
|
+
- Markdown with sanitization
|
|
262
|
+
- Code blocks with Shiki highlighting
|
|
263
|
+
|
|
264
|
+
## Markdown & Code Blocks
|
|
265
|
+
|
|
266
|
+
The runtime includes built-in support for rendering Markdown and syntax-highlighted code.
|
|
267
|
+
|
|
268
|
+
### Markdown
|
|
269
|
+
|
|
270
|
+
Rendered using [marked](https://marked.js.org/) with [DOMPurify](https://github.com/cure53/DOMPurify) sanitization.
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"kind": "markdown",
|
|
275
|
+
"content": { "expr": "state", "name": "markdownContent" }
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Code Blocks
|
|
280
|
+
|
|
281
|
+
Rendered with [Shiki](https://shiki.style/) syntax highlighting.
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
{
|
|
285
|
+
"kind": "code",
|
|
286
|
+
"code": { "expr": "lit", "value": "const x = 1;" },
|
|
287
|
+
"language": { "expr": "lit", "value": "typescript" }
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Features:**
|
|
292
|
+
- Dual theme support (light/dark)
|
|
293
|
+
- Copy button with feedback
|
|
294
|
+
- Dynamic language loading
|
|
295
|
+
|
|
296
|
+
## Security
|
|
297
|
+
|
|
298
|
+
The runtime includes security measures:
|
|
299
|
+
|
|
300
|
+
- **Prototype Pollution Prevention** - Blocks `__proto__`, `constructor`, `prototype`
|
|
301
|
+
- **Safe Globals** - Only exposes `JSON`, `Math`, `Date`, `Object`, `Array`, `String`, `Number`, `Boolean`, `console`
|
|
302
|
+
- **HTML Sanitization** - DOMPurify for Markdown content
|
|
303
|
+
|
|
304
|
+
## License
|
|
305
|
+
|
|
306
|
+
MIT
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/runtime",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Runtime DOM renderer for Constela UI framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"dompurify": "^3.3.1",
|
|
19
19
|
"marked": "^17.0.1",
|
|
20
20
|
"shiki": "^3.20.0",
|
|
21
|
-
"@constela/
|
|
22
|
-
"@constela/
|
|
21
|
+
"@constela/core": "0.7.0",
|
|
22
|
+
"@constela/compiler": "0.7.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": "3.0.
|
|
32
|
+
"@constela/server": "3.0.1"
|
|
33
33
|
},
|
|
34
34
|
"engines": {
|
|
35
35
|
"node": ">=20.0.0"
|