@hypen-space/core 0.2.2 → 0.2.3

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.
@@ -0,0 +1,30 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ export const memory: WebAssembly.Memory;
4
+ export const __wbg_wasmengine_free: (a: number, b: number) => void;
5
+ export const wasmengine_new: () => number;
6
+ export const wasmengine_setRenderCallback: (a: number, b: any) => void;
7
+ export const wasmengine_setComponentResolver: (a: number, b: any) => void;
8
+ export const wasmengine_registerPrimitive: (a: number, b: number, c: number) => void;
9
+ export const wasmengine_renderLazyComponent: (a: number, b: number, c: number) => [number, number];
10
+ export const wasmengine_renderInto: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
11
+ export const wasmengine_updateState: (a: number, b: any) => [number, number];
12
+ export const wasmengine_updateStateSparse: (a: number, b: any, c: any) => [number, number];
13
+ export const wasmengine_dispatchAction: (a: number, b: number, c: number, d: any) => [number, number];
14
+ export const wasmengine_onAction: (a: number, b: number, c: number, d: any) => void;
15
+ export const wasmengine_clearTree: (a: number) => void;
16
+ export const wasmengine_debugParseComponent: (a: number, b: number, c: number) => [number, number, number, number];
17
+ export const wasmengine_setModule: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: any) => [number, number];
18
+ export const wasmengine_getRevision: (a: number) => bigint;
19
+ export const patchesToJson: (a: any) => [number, number, number, number];
20
+ export const parseToJson: (a: number, b: number) => [number, number, number, number];
21
+ export const main: () => void;
22
+ export const wasmengine_renderSource: (a: number, b: number, c: number) => [number, number];
23
+ export const __wbindgen_exn_store: (a: number) => void;
24
+ export const __externref_table_alloc: () => number;
25
+ export const __wbindgen_export_2: WebAssembly.Table;
26
+ export const __wbindgen_malloc: (a: number, b: number) => number;
27
+ export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
28
+ export const __externref_table_dealloc: (a: number) => void;
29
+ export const __wbindgen_free: (a: number, b: number, c: number) => void;
30
+ export const __wbindgen_start: () => void;
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "hypen-engine",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "files": [
6
+ "hypen_engine_bg.wasm",
7
+ "hypen_engine.js",
8
+ "hypen_engine.d.ts"
9
+ ],
10
+ "main": "hypen_engine.js",
11
+ "types": "hypen_engine.d.ts",
12
+ "sideEffects": [
13
+ "./snippets/*"
14
+ ]
15
+ }
@@ -0,0 +1,425 @@
1
+ # Hypen Engine
2
+
3
+ The core reactive rendering engine for Hypen, written in Rust. Compiles to WASM for web/desktop or native binaries with UniFFI for mobile platforms.
4
+
5
+ ## Overview
6
+
7
+ Hypen Engine is a platform-agnostic UI engine that:
8
+ - **Expands** Hypen DSL components into an intermediate representation (IR)
9
+ - **Tracks** reactive dependencies between state and UI nodes
10
+ - **Reconciles** UI trees efficiently using keyed diffing
11
+ - **Generates** minimal platform-agnostic patches for renderers
12
+ - **Routes** actions and events between UI and application logic
13
+ - **Serializes** for Remote UI scenarios (client-server streaming)
14
+
15
+ ## Architecture
16
+
17
+ ```
18
+ ┌─────────────────────────────────────────────────────────┐
19
+ │ Hypen Engine │
20
+ ├─────────────────────────────────────────────────────────┤
21
+ │ Parser → IR → Reactive Graph → Reconciler → Patches │
22
+ │ ↓ ↓ │
23
+ │ Component Registry Platform Renderer│
24
+ │ Dependency Tracking (Web/iOS/Android)│
25
+ │ State Management │
26
+ └─────────────────────────────────────────────────────────┘
27
+ ```
28
+
29
+ ### Core Systems
30
+
31
+ 1. **IR & Component Expansion** (`src/ir/`)
32
+ - Canonical intermediate representation
33
+ - Component registry and resolution
34
+ - Props/slots expansion with defaults
35
+ - Stable NodeId generation
36
+
37
+ 2. **Reactive System** (`src/reactive/`)
38
+ - Dependency graph tracking `${state.*}` bindings
39
+ - Dirty marking on state changes
40
+ - Scheduling for efficient updates
41
+
42
+ 3. **Reconciliation** (`src/reconcile/`)
43
+ - Virtual instance tree (no platform objects)
44
+ - Keyed children diffing algorithm
45
+ - Minimal patch generation
46
+
47
+ 4. **Patch Types** (Platform-agnostic):
48
+ - `Create(id, type, props)` - Create new node
49
+ - `SetProp(id, name, value)` - Update property
50
+ - `SetText(id, text)` - Update text content
51
+ - `Insert(parent, id, before?)` - Insert into tree
52
+ - `Move(parent, id, before?)` - Reorder node
53
+ - `Remove(id)` - Remove from tree
54
+ - `AttachEvent(id, event)` / `DetachEvent(id, event)`
55
+
56
+ 5. **Action/Event Routing** (`src/dispatch/`)
57
+ - Map `@actions.*` to module handlers
58
+ - Forward UI events (click, input, etc.)
59
+ - Stable dispatch contract for SDKs
60
+
61
+ 6. **Lifecycle Management** (`src/lifecycle/`)
62
+ - Module lifecycle (created/destroyed)
63
+ - Component lifecycle (mount/unmount)
64
+ - Resource cache (images/fonts) with pluggable fetcher
65
+
66
+ 7. **Remote UI Serialization** (`src/serialize/`)
67
+ - Initial tree serialization
68
+ - Incremental patch streaming
69
+ - Revision tracking and optional integrity hashes
70
+ - JSON/CBOR format support
71
+
72
+ ## Usage
73
+
74
+ ### Basic Example
75
+
76
+ ```rust
77
+ use hypen_engine::{Engine, ir::{Element, Value, Component}};
78
+ use serde_json::json;
79
+
80
+ // Create engine
81
+ let mut engine = Engine::new();
82
+
83
+ // Register a custom component
84
+ engine.register_component(Component::new("Greeting", |props| {
85
+ Element::new("Text")
86
+ .with_prop("text", Value::Binding("${state.name}".to_string()))
87
+ }));
88
+
89
+ // Set render callback
90
+ engine.set_render_callback(|patches| {
91
+ for patch in patches {
92
+ println!("Patch: {:?}", patch);
93
+ }
94
+ });
95
+
96
+ // Register action handler
97
+ engine.on_action("greet", |action| {
98
+ println!("Hello from action: {:?}", action);
99
+ });
100
+
101
+ // Render UI
102
+ let ui = Element::new("Column")
103
+ .with_child(Element::new("Greeting"));
104
+
105
+ engine.render(&ui);
106
+
107
+ // Update state
108
+ engine.update_state(json!({
109
+ "name": "Alice"
110
+ }));
111
+ ```
112
+
113
+ ### With Module Host
114
+
115
+ ```rust
116
+ use hypen_engine::lifecycle::{Module, ModuleInstance};
117
+
118
+ // Create module definition
119
+ let module = Module::new("ProfilePage")
120
+ .with_actions(vec!["signIn".to_string(), "signOut".to_string()])
121
+ .with_state_keys(vec!["user".to_string()])
122
+ .with_persist(true);
123
+
124
+ // Create module instance
125
+ let instance = ModuleInstance::new(
126
+ module,
127
+ json!({ "user": null })
128
+ );
129
+
130
+ engine.set_module(instance);
131
+ ```
132
+
133
+ ## Compilation Targets
134
+
135
+ ### Native (Development)
136
+
137
+ ```bash
138
+ cargo build
139
+ cargo test
140
+ ```
141
+
142
+ ### WASM (Web/Desktop)
143
+
144
+ ```bash
145
+ # Install wasm-pack (one time)
146
+ cargo install wasm-pack
147
+
148
+ # Build for all WASM targets
149
+ ./build-wasm.sh
150
+
151
+ # Or build manually for specific targets:
152
+ wasm-pack build --target bundler # For webpack/vite
153
+ wasm-pack build --target nodejs # For Node.js
154
+ wasm-pack build --target web # For vanilla JS
155
+ ```
156
+
157
+ Output directories:
158
+ - `pkg/bundler/` - For use with bundlers (webpack, vite, rollup)
159
+ - `pkg/nodejs/` - For Node.js
160
+ - `pkg/web/` - For vanilla HTML/JS (see `example.html`)
161
+
162
+ ### JavaScript/TypeScript API
163
+
164
+ ```typescript
165
+ import init, { WasmEngine } from './pkg/web/hypen_engine.js';
166
+
167
+ // Initialize WASM
168
+ await init();
169
+
170
+ // Create engine
171
+ const engine = new WasmEngine();
172
+
173
+ // Set render callback
174
+ engine.setRenderCallback((patches) => {
175
+ console.log('Patches:', patches);
176
+ // Apply patches to your renderer
177
+ });
178
+
179
+ // Register action handlers
180
+ engine.onAction('increment', (action) => {
181
+ console.log('Increment action:', action);
182
+ });
183
+
184
+ // Initialize module
185
+ engine.setModule(
186
+ 'CounterModule',
187
+ ['increment', 'decrement'],
188
+ ['count'],
189
+ { count: 0 }
190
+ );
191
+
192
+ // Render Hypen DSL
193
+ const source = `
194
+ Column {
195
+ Text("Count: \${state.count}")
196
+ Button("@actions.increment") { Text("+1") }
197
+ }
198
+ `;
199
+ engine.renderSource(source);
200
+
201
+ // Update state
202
+ engine.updateState({ count: 42 });
203
+
204
+ // Dispatch action
205
+ engine.dispatchAction('increment', { amount: 1 });
206
+ ```
207
+
208
+ ### Testing WASM Build
209
+
210
+ Open `example.html` in a web server:
211
+
212
+ ```bash
213
+ # Using Python
214
+ python3 -m http.server 8000
215
+
216
+ # Using Node.js
217
+ npx serve .
218
+
219
+ # Then visit: http://localhost:8000/example.html
220
+ ```
221
+
222
+ ### Mobile (UniFFI)
223
+
224
+ ```bash
225
+ # Generate Swift/Kotlin bindings (coming soon)
226
+ cargo install uniffi_bindgen
227
+ uniffi-bindgen generate src/hypen_engine.udl --language swift
228
+ uniffi-bindgen generate src/hypen_engine.udl --language kotlin
229
+ ```
230
+
231
+ ## Project Structure
232
+
233
+ ```
234
+ hypen-engine-rs/
235
+ ├── src/
236
+ │ ├── lib.rs # Public API
237
+ │ ├── engine.rs # Main orchestrator
238
+ │ ├── wasi.rs # WASI interfaces
239
+ │ ├── ir/ # IR & component expansion
240
+ │ │ ├── node.rs # NodeId, Element, Props, Value
241
+ │ │ ├── component.rs # Component registry
242
+ │ │ └── expand.rs # AST → IR lowering
243
+ │ ├── reactive/ # Reactive system
244
+ │ │ ├── binding.rs # ${state.*} parsing
245
+ │ │ ├── graph.rs # Dependency tracking
246
+ │ │ └── scheduler.rs # Dirty marking
247
+ │ ├── reconcile/ # Reconciliation
248
+ │ │ ├── tree.rs # Instance tree
249
+ │ │ ├── diff.rs # Diffing algorithm
250
+ │ │ └── patch.rs # Patch types
251
+ │ ├── dispatch/ # Events & actions
252
+ │ │ ├── action.rs # Action dispatcher
253
+ │ │ └── event.rs # Event router
254
+ │ ├── lifecycle/ # Lifecycle
255
+ │ │ ├── module.rs # Module lifecycle
256
+ │ │ ├── component.rs # Component lifecycle
257
+ │ │ └── resource.rs # Resource cache
258
+ │ └── serialize/ # Serialization
259
+ │ └── remote.rs # Remote UI protocol
260
+ ├── Cargo.toml
261
+ └── README.md
262
+ ```
263
+
264
+ ## Key Data Structures
265
+
266
+ ### Element (IR Node)
267
+ ```rust
268
+ pub struct Element {
269
+ pub element_type: String, // "Column", "Text", etc.
270
+ pub props: IndexMap<String, Value>, // Properties
271
+ pub children: Vec<Element>, // Child elements
272
+ pub key: Option<String>, // For reconciliation
273
+ pub events: IndexMap<String, String>, // event → action
274
+ }
275
+ ```
276
+
277
+ ### Value (Props)
278
+ ```rust
279
+ pub enum Value {
280
+ Static(serde_json::Value), // Literal values
281
+ Binding(String), // ${state.user.name}
282
+ Action(String), // @actions.signIn
283
+ }
284
+ ```
285
+
286
+ ### Patch (Output)
287
+ ```rust
288
+ pub enum Patch {
289
+ Create { id, element_type, props },
290
+ SetProp { id, name, value },
291
+ SetText { id, text },
292
+ Insert { parent_id, id, before_id? },
293
+ Move { parent_id, id, before_id? },
294
+ Remove { id },
295
+ AttachEvent { id, event_name },
296
+ DetachEvent { id, event_name },
297
+ }
298
+ ```
299
+
300
+ ## Integration with Parser
301
+
302
+ The engine integrates with the Hypen parser from `../parser`:
303
+
304
+ ```rust
305
+ use hypen_parser::parse_component;
306
+ use hypen_engine::ast_to_ir;
307
+
308
+ let source = r#"
309
+ Column {
310
+ Text("Hello, ${state.name}")
311
+ Button("@actions.greet") { Text("Greet") }
312
+ }
313
+ "#;
314
+
315
+ let ast = parse_component(source)?;
316
+ let element = ast_to_ir(&ast); // Convert AST → IR
317
+ engine.render(&element);
318
+ ```
319
+
320
+ ### Full Example with Parser
321
+
322
+ ```rust
323
+ use hypen_engine::{Engine, ast_to_ir};
324
+ use hypen_parser::parse_component;
325
+ use serde_json::json;
326
+
327
+ fn main() -> Result<(), Box<dyn std::error::Error>> {
328
+ let mut engine = Engine::new();
329
+
330
+ // Set render callback
331
+ engine.set_render_callback(|patches| {
332
+ println!("Patches: {:#?}", patches);
333
+ });
334
+
335
+ // Parse Hypen DSL
336
+ let source = r#"
337
+ Column {
338
+ Text("Count: ${state.count}")
339
+ Button("@actions.increment") { Text("+1") }
340
+ }
341
+ "#;
342
+
343
+ let ast = parse_component(source)?;
344
+ let element = ast_to_ir(&ast);
345
+
346
+ // Render
347
+ engine.render(&element);
348
+
349
+ // Update state
350
+ engine.update_state(json!({"count": 42}));
351
+
352
+ Ok(())
353
+ }
354
+ ```
355
+
356
+ ## Performance Considerations
357
+
358
+ - **Keyed reconciliation**: Use `key` props for list items to minimize DOM churn
359
+ - **Dependency tracking**: Only re-render nodes affected by state changes
360
+ - **Lazy evaluation**: Bindings are resolved on-demand during reconciliation
361
+ - **Resource caching**: Images/fonts are cached with configurable eviction
362
+
363
+ ## Remote UI Protocol
364
+
365
+ For client-server streaming:
366
+
367
+ ```json
368
+ // Initial tree (client connects)
369
+ {
370
+ "type": "initialTree",
371
+ "module": "ProfilePage",
372
+ "state": { "user": null },
373
+ "patches": [...],
374
+ "revision": 0
375
+ }
376
+
377
+ // State update (server → client)
378
+ {
379
+ "type": "stateUpdate",
380
+ "module": "ProfilePage",
381
+ "state": { "user": { "name": "Alice" } }
382
+ }
383
+
384
+ // Incremental patches (server → client)
385
+ {
386
+ "type": "patch",
387
+ "module": "ProfilePage",
388
+ "patches": [{ "type": "setProp", ... }],
389
+ "revision": 42
390
+ }
391
+
392
+ // Action dispatch (client → server)
393
+ {
394
+ "type": "dispatchAction",
395
+ "module": "ProfilePage",
396
+ "action": "signIn",
397
+ "payload": { "provider": "google" }
398
+ }
399
+ ```
400
+
401
+ ## Testing
402
+
403
+ ```bash
404
+ # Run all tests
405
+ cargo test
406
+
407
+ # Run with output
408
+ cargo test -- --nocapture
409
+
410
+ # Test specific module
411
+ cargo test reactive::
412
+ ```
413
+
414
+ ## Contributing
415
+
416
+ This is part of the Hypen project. See the main repository for contribution guidelines.
417
+
418
+ ## License
419
+
420
+ See main Hypen project for license information.
421
+
422
+ ---
423
+
424
+ **Status**: ✅ Core systems implemented, WASM integration in progress
425
+ **Next**: Full keyed reconciliation, UniFFI bindings, platform renderers
@@ -0,0 +1,97 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /**
4
+ * Export patches as JSON string (for debugging)
5
+ */
6
+ export function patchesToJson(patches: any): string;
7
+ /**
8
+ * Parse Hypen DSL and return AST as JSON
9
+ */
10
+ export function parseToJson(source: string): string;
11
+ export function main(): void;
12
+ /**
13
+ * WASM-exported engine instance
14
+ * Uses Rc<RefCell<>> instead of Send+Sync for WASM single-threaded environment
15
+ */
16
+ export class WasmEngine {
17
+ free(): void;
18
+ [Symbol.dispose](): void;
19
+ /**
20
+ * Create a new engine instance
21
+ */
22
+ constructor();
23
+ /**
24
+ * Parse and render Hypen DSL source code
25
+ */
26
+ renderSource(source: string): void;
27
+ /**
28
+ * Set the render callback that receives patches
29
+ */
30
+ setRenderCallback(callback: Function): void;
31
+ /**
32
+ * Set the component resolver callback
33
+ * The resolver receives (componentName, contextPath) and should return
34
+ * { source: string, path: string } or null
35
+ *
36
+ * contextPath is the path of the component that's referencing this component
37
+ * The returned path should be the resolved absolute path to the component file
38
+ */
39
+ setComponentResolver(resolver: Function): void;
40
+ /**
41
+ * Register a primitive element (like Text, Button, etc.) to skip component resolution
42
+ * This prevents unnecessary resolver calls for built-in DOM elements
43
+ */
44
+ registerPrimitive(name: string): void;
45
+ /**
46
+ * Render a component source (for lazy loading routes)
47
+ * This allows rendering a component on-demand
48
+ */
49
+ renderLazyComponent(source: string): void;
50
+ /**
51
+ * Render a component into a specific parent node (subtree rendering)
52
+ * This is used for lazy routing where components are rendered on-demand into route containers
53
+ *
54
+ * # Arguments
55
+ * * `source` - The Hypen DSL source to parse and render
56
+ * * `parent_node_id_str` - The serialized node ID string of the parent element
57
+ * * `state_js` - The state to use for rendering (as JsValue)
58
+ */
59
+ renderInto(source: string, parent_node_id_str: string, state_js: any): void;
60
+ /**
61
+ * Update state from JavaScript
62
+ */
63
+ updateState(state_patch: any): void;
64
+ /**
65
+ * Update state using sparse path-value pairs
66
+ * More efficient than updateState for large state objects when only a few paths changed
67
+ *
68
+ * # Arguments
69
+ * * `paths_js` - Array of changed paths (e.g., ["user.name", "count"])
70
+ * * `values_js` - Object mapping paths to their new values (e.g., { "user.name": "Bob", "count": 42 })
71
+ */
72
+ updateStateSparse(paths_js: any, values_js: any): void;
73
+ /**
74
+ * Dispatch an action
75
+ */
76
+ dispatchAction(name: string, payload: any): void;
77
+ /**
78
+ * Register an action handler
79
+ */
80
+ onAction(action_name: string, handler: Function): void;
81
+ /**
82
+ * Clear the engine tree (useful when switching samples)
83
+ */
84
+ clearTree(): void;
85
+ /**
86
+ * Debug method to inspect parsed components
87
+ */
88
+ debugParseComponent(source: string): string;
89
+ /**
90
+ * Initialize a module
91
+ */
92
+ setModule(name: string, actions: string[], state_keys: string[], initial_state: any): void;
93
+ /**
94
+ * Get the current revision number
95
+ */
96
+ getRevision(): bigint;
97
+ }