@hypen-space/core 0.2.1 → 0.2.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.
Files changed (2) hide show
  1. package/README.md +199 -154
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,28 @@
1
1
  # @hypen-space/core
2
2
 
3
- Platform-agnostic reactive UI runtime for the Hypen declarative UI language.
3
+ The reactive runtime for Hypen - a declarative UI language that separates what your UI looks like from how it behaves.
4
+
5
+ ## What is Hypen?
6
+
7
+ Hypen lets you write UI templates in a clean, declarative syntax:
8
+
9
+ ```hypen
10
+ Column {
11
+ Text("Hello, ${state.user.name}!")
12
+ Button(onClick: @actions.logout) {
13
+ Text("Sign Out")
14
+ }
15
+ }
16
+ ```
17
+
18
+ The `@hypen-space/core` package provides the engine that:
19
+
20
+ 1. **Parses** your Hypen templates
21
+ 2. **Tracks** reactive state bindings (like `${state.user.name}`)
22
+ 3. **Generates patches** when state changes (instead of re-rendering everything)
23
+ 4. **Dispatches actions** triggered from the UI (like `@actions.logout`)
24
+
25
+ You provide a renderer that applies those patches to your platform (DOM, Canvas, Native, etc).
4
26
 
5
27
  ## Installation
6
28
 
@@ -10,12 +32,54 @@ npm install @hypen-space/core
10
32
  bun add @hypen-space/core
11
33
  ```
12
34
 
35
+ ## Core Concepts
36
+
37
+ ### Templates
38
+
39
+ Hypen templates describe your UI structure. Components can have:
40
+
41
+ - **Arguments**: `Text("Hello")` or `Button(disabled: true)`
42
+ - **Children**: Nested inside `{ }` braces
43
+ - **Applicators**: Chained styling like `.padding(16).color(blue)`
44
+
45
+ ### State Bindings
46
+
47
+ Use `${state.path}` to bind template values to your module's state:
48
+
49
+ ```hypen
50
+ Text("Count: ${state.count}")
51
+ Text("User: ${state.user.name}")
52
+ ```
53
+
54
+ When state changes, only the affected parts of the UI update.
55
+
56
+ ### Actions
57
+
58
+ Use `@actions.name` to dispatch events from the UI to your module:
59
+
60
+ ```hypen
61
+ Button(onClick: @actions.increment) { Text("+") }
62
+ Button(onClick: @actions.submitForm) { Text("Submit") }
63
+ ```
64
+
65
+ ### Patches
66
+
67
+ The engine doesn't manipulate the UI directly. Instead, it emits **patches** - minimal instructions describing what changed:
68
+
69
+ ```typescript
70
+ { type: "Create", id: "1", elementType: "Text", props: { text: "Hello" } }
71
+ { type: "SetProp", id: "1", name: "text", value: "Hello, World" }
72
+ { type: "Remove", id: "1" }
73
+ ```
74
+
75
+ Your renderer applies these patches to the actual platform.
76
+
13
77
  ## Quick Start
14
78
 
15
79
  ```typescript
16
80
  import { Engine, app } from "@hypen-space/core";
17
81
 
18
- // 1. Define a stateful module
82
+ // 1. Define your module's state and actions
19
83
  const counter = app
20
84
  .defineState({ count: 0 })
21
85
  .onAction("increment", ({ state }) => state.count++)
@@ -26,14 +90,14 @@ const counter = app
26
90
  const engine = new Engine();
27
91
  await engine.init();
28
92
 
29
- // 3. Set up render callback (you provide the renderer)
93
+ // 3. Connect your renderer
30
94
  engine.setRenderCallback((patches) => {
31
- // Apply patches to your platform (DOM, Canvas, Native, etc.)
32
95
  myRenderer.applyPatches(patches);
33
96
  });
34
97
 
35
- // 4. Register module and render
98
+ // 4. Register the module and render
36
99
  engine.setModule("counter", counter.actions, counter.stateKeys, counter.initialState);
100
+
37
101
  engine.renderSource(`
38
102
  Column {
39
103
  Text("Count: \${state.count}")
@@ -45,75 +109,9 @@ engine.renderSource(`
45
109
  `);
46
110
  ```
47
111
 
48
- ## Component Discovery
49
-
50
- Auto-discover `.hypen` components from the filesystem:
51
-
52
- ```typescript
53
- import { discoverComponents, loadDiscoveredComponents, watchComponents } from "@hypen-space/core";
54
-
55
- // Discover components in a directory
56
- const components = await discoverComponents("./src/components", {
57
- patterns: ["folder", "sibling", "index"], // Naming conventions
58
- recursive: false,
59
- debug: true,
60
- });
61
-
62
- // Load discovered components with their modules
63
- const loaded = await loadDiscoveredComponents(components);
64
-
65
- // Watch for changes (hot reload)
66
- const watcher = watchComponents("./src/components", {
67
- onAdd: (c) => console.log("Added:", c.name),
68
- onUpdate: (c) => console.log("Updated:", c.name),
69
- onRemove: (name) => console.log("Removed:", name),
70
- onChange: (all) => console.log("All components:", all.length),
71
- });
72
-
73
- // Stop watching
74
- watcher.stop();
75
- ```
76
-
77
- ### Supported Patterns
78
-
79
- ```
80
- # Folder-based (recommended)
81
- Counter/
82
- ├── component.hypen
83
- └── component.ts
112
+ ## Modules
84
113
 
85
- # Sibling files
86
- Counter.hypen
87
- Counter.ts
88
-
89
- # Index-based
90
- Counter/
91
- ├── index.hypen
92
- └── index.ts
93
- ```
94
-
95
- ## Component Loader
96
-
97
- Register and manage components:
98
-
99
- ```typescript
100
- import { componentLoader, ComponentLoader } from "@hypen-space/core";
101
-
102
- // Use the global loader
103
- componentLoader.register("Counter", counterModule, counterTemplate);
104
- componentLoader.get("Counter"); // Get component
105
- componentLoader.has("Counter"); // Check if exists
106
- componentLoader.getNames(); // List all names
107
-
108
- // Or create your own
109
- const loader = new ComponentLoader();
110
- await loader.loadFromDirectory("Counter", "./src/components/Counter");
111
- await loader.loadFromComponentsDir("./src/components"); // Auto-load all
112
- ```
113
-
114
- ## Module System
115
-
116
- Create stateful modules with the fluent `app` builder:
114
+ Modules manage state and handle actions. Use the `app` builder to define them:
117
115
 
118
116
  ```typescript
119
117
  import { app } from "@hypen-space/core";
@@ -126,13 +124,13 @@ interface UserState {
126
124
  const userModule = app
127
125
  .defineState<UserState>({ user: null, loading: false })
128
126
 
129
- // Lifecycle hooks
127
+ // Called when module is created
130
128
  .onCreated(async ({ state, next }) => {
131
129
  state.loading = true;
132
- next(); // Sync state to engine
130
+ next(); // Sync state changes to engine
133
131
  })
134
132
 
135
- // Action handlers
133
+ // Handle actions dispatched from UI
136
134
  .onAction("loadUser", async ({ state, action, next }) => {
137
135
  const userId = action.payload?.id;
138
136
  state.user = await fetchUser(userId);
@@ -145,16 +143,19 @@ const userModule = app
145
143
  next();
146
144
  })
147
145
 
146
+ // Called when module is destroyed
148
147
  .onDestroyed(({ state }) => {
149
- console.log("Module cleanup");
148
+ console.log("Cleanup");
150
149
  })
151
150
 
152
151
  .build();
153
152
  ```
154
153
 
155
- ## State Management
154
+ The `next()` callback synchronizes state changes with the engine after async operations.
155
+
156
+ ## State
156
157
 
157
- Observable state with automatic change tracking:
158
+ State is automatically tracked via Proxy. Mutations trigger UI updates:
158
159
 
159
160
  ```typescript
160
161
  import { createObservableState, batchStateUpdates, getStateSnapshot } from "@hypen-space/core";
@@ -169,19 +170,50 @@ const state = createObservableState({ count: 0, items: [] }, {
169
170
  state.count = 5;
170
171
  state.items.push("item");
171
172
 
172
- // Batch multiple updates
173
+ // Batch multiple updates into one render cycle
173
174
  batchStateUpdates(state, () => {
174
175
  state.count = 10;
175
176
  state.items = ["a", "b", "c"];
176
177
  });
177
178
 
178
- // Get immutable snapshot
179
+ // Get an immutable snapshot
179
180
  const snapshot = getStateSnapshot(state);
180
181
  ```
181
182
 
183
+ ## Custom Renderers
184
+
185
+ Extend `BaseRenderer` to render to any platform:
186
+
187
+ ```typescript
188
+ import { BaseRenderer } from "@hypen-space/core";
189
+
190
+ class MyRenderer extends BaseRenderer {
191
+ protected onCreate(id: string, type: string, props: Record<string, any>) {
192
+ // Create an element
193
+ }
194
+ protected onSetProp(id: string, name: string, value: any) {
195
+ // Update a property
196
+ }
197
+ protected onSetText(id: string, text: string) {
198
+ // Set text content
199
+ }
200
+ protected onInsert(parentId: string, id: string, beforeId?: string) {
201
+ // Insert into parent
202
+ }
203
+ protected onMove(parentId: string, id: string, beforeId?: string) {
204
+ // Reorder element
205
+ }
206
+ protected onRemove(id: string) {
207
+ // Remove element
208
+ }
209
+ }
210
+ ```
211
+
212
+ See `@hypen-space/web` for a DOM renderer implementation.
213
+
182
214
  ## Routing
183
215
 
184
- Built-in hash-based or pathname-based routing:
216
+ Built-in hash or pathname routing:
185
217
 
186
218
  ```typescript
187
219
  import { HypenRouter } from "@hypen-space/core";
@@ -191,46 +223,90 @@ const router = new HypenRouter();
191
223
  router.navigate("/products/123");
192
224
 
193
225
  router.subscribe((state) => {
194
- console.log("Route:", state.currentPath);
195
- console.log("Params:", state.params);
226
+ console.log("Path:", state.currentPath);
227
+ console.log("Params:", state.params); // { id: "123" }
196
228
  console.log("Query:", state.query);
197
229
  });
198
230
  ```
199
231
 
200
- ## Built-in Components
232
+ Use built-in components in templates:
201
233
 
202
- Framework-provided components for routing:
234
+ ```hypen
235
+ Router {
236
+ Route(path: "/") { HomePage }
237
+ Route(path: "/products/:id") { ProductPage }
238
+ }
239
+ Link(to: "/products/42") { Text("View Product") }
240
+ ```
241
+
242
+ ## Component Discovery
243
+
244
+ For larger apps, organize components as files and auto-discover them:
203
245
 
204
246
  ```typescript
205
- import { Router, Route, Link } from "@hypen-space/core";
206
-
207
- // Use in templates:
208
- // Router {
209
- // Route(path: "/") { HomePage }
210
- // Route(path: "/products") { ProductList }
211
- // }
212
- // Link(to: "/products") { Text("View Products") }
247
+ import { discoverComponents, loadDiscoveredComponents } from "@hypen-space/core";
248
+
249
+ const components = await discoverComponents("./src/components");
250
+ const loaded = await loadDiscoveredComponents(components);
213
251
  ```
214
252
 
215
- ## Remote UI (WebSocket)
253
+ Supported file patterns:
254
+
255
+ ```
256
+ Counter/
257
+ ├── component.hypen # Template
258
+ └── component.ts # Module
216
259
 
217
- Connect to a remote Hypen server:
260
+ Counter.hypen # Or sibling files
261
+ Counter.ts
262
+
263
+ Counter/
264
+ ├── index.hypen # Or index-based
265
+ └── index.ts
266
+ ```
267
+
268
+ Watch for changes (hot reload):
269
+
270
+ ```typescript
271
+ import { watchComponents } from "@hypen-space/core";
272
+
273
+ const watcher = watchComponents("./src/components", {
274
+ onUpdate: (c) => console.log("Updated:", c.name),
275
+ });
276
+ ```
277
+
278
+ ## Component Loader
279
+
280
+ Register components programmatically:
281
+
282
+ ```typescript
283
+ import { componentLoader, ComponentLoader } from "@hypen-space/core";
284
+
285
+ // Global loader
286
+ componentLoader.register("Counter", counterModule, counterTemplate);
287
+ componentLoader.get("Counter");
288
+ componentLoader.has("Counter");
289
+
290
+ // Or create your own
291
+ const loader = new ComponentLoader();
292
+ await loader.loadFromComponentsDir("./src/components");
293
+ ```
294
+
295
+ ## Remote UI
296
+
297
+ Connect to a Hypen server for server-driven UI:
218
298
 
219
299
  ```typescript
220
300
  import { RemoteEngine } from "@hypen-space/core";
221
301
 
222
302
  const remote = new RemoteEngine("ws://localhost:3000", {
223
303
  autoReconnect: true,
224
- reconnectInterval: 3000,
225
- maxReconnectAttempts: 10,
226
304
  });
227
305
 
228
306
  remote
229
307
  .onPatches((patches) => renderer.applyPatches(patches))
230
308
  .onStateUpdate((state) => console.log("Server state:", state))
231
- .onConnect(() => console.log("Connected"))
232
- .onDisconnect(() => console.log("Disconnected"))
233
- .onError((error) => console.error("Error:", error));
309
+ .onConnect(() => console.log("Connected"));
234
310
 
235
311
  await remote.connect();
236
312
  remote.dispatchAction("loadData", { page: 1 });
@@ -238,86 +314,55 @@ remote.dispatchAction("loadData", { page: 1 });
238
314
 
239
315
  ## Global Context
240
316
 
241
- Cross-module communication:
317
+ Share state and events across modules:
242
318
 
243
319
  ```typescript
244
320
  import { HypenGlobalContext } from "@hypen-space/core";
245
321
 
246
322
  const context = new HypenGlobalContext();
247
323
 
248
- context.registerModule("auth", authModuleInstance);
249
- context.registerModule("cart", cartModuleInstance);
324
+ context.registerModule("auth", authModule);
325
+ context.registerModule("cart", cartModule);
250
326
 
251
- const auth = context.getModule("auth");
252
- const cartState = context.getModule("cart").getState();
327
+ // Cross-module access
328
+ const user = context.getModule("auth").getState().user;
253
329
 
254
- // Event system
255
- context.on("userLoggedIn", (user) => console.log("Logged in:", user));
330
+ // Event bus
331
+ context.on("userLoggedIn", (user) => { /* ... */ });
256
332
  context.emit("userLoggedIn", { id: "1", name: "Ian" });
257
333
  ```
258
334
 
259
- ## Custom Renderer
260
-
261
- Implement the `Renderer` interface for any platform:
335
+ ## Browser vs Node.js
262
336
 
263
337
  ```typescript
264
- import { BaseRenderer, type Patch } from "@hypen-space/core";
338
+ // Node.js / Bundler - WASM loads automatically
339
+ import { Engine } from "@hypen-space/core";
340
+ const engine = new Engine();
341
+ await engine.init();
265
342
 
266
- class MyRenderer extends BaseRenderer {
267
- protected onCreate(id: string, type: string, props: Record<string, any>) {
268
- // Create element
269
- }
270
- protected onSetProp(id: string, name: string, value: any) {
271
- // Set property
272
- }
273
- protected onSetText(id: string, text: string) {
274
- // Set text
275
- }
276
- protected onInsert(parentId: string, id: string, beforeId?: string) {
277
- // Insert into parent
278
- }
279
- protected onMove(parentId: string, id: string, beforeId?: string) {
280
- // Move element
281
- }
282
- protected onRemove(id: string) {
283
- // Remove element
284
- }
285
- }
343
+ // Browser - specify WASM path
344
+ import { BrowserEngine } from "@hypen-space/core";
345
+ const engine = new BrowserEngine();
346
+ await engine.init({ wasmPath: "/hypen_engine_bg.wasm" });
286
347
  ```
287
348
 
288
- ## Exports
349
+ ## Package Exports
289
350
 
290
351
  | Export | Description |
291
352
  |--------|-------------|
292
- | `@hypen-space/core` | Main entry - Engine, app, state, router, discovery, loader |
293
- | `@hypen-space/core/engine` | Low-level WASM engine API |
294
- | `@hypen-space/core/engine/browser` | Browser-optimized engine (explicit WASM init) |
295
- | `@hypen-space/core/app` | Module builder and HypenModuleInstance |
353
+ | `@hypen-space/core` | Main entry point |
354
+ | `@hypen-space/core/engine` | Low-level WASM engine |
355
+ | `@hypen-space/core/engine/browser` | Browser-optimized engine |
356
+ | `@hypen-space/core/app` | Module builder |
296
357
  | `@hypen-space/core/state` | Observable state utilities |
297
- | `@hypen-space/core/renderer` | Abstract renderer interface |
358
+ | `@hypen-space/core/renderer` | Abstract renderer |
298
359
  | `@hypen-space/core/router` | Routing system |
299
- | `@hypen-space/core/events` | Typed event emitter |
300
360
  | `@hypen-space/core/context` | Global context |
301
361
  | `@hypen-space/core/remote` | Remote UI protocol |
302
- | `@hypen-space/core/remote/client` | WebSocket client |
303
362
  | `@hypen-space/core/loader` | Component loader |
304
363
  | `@hypen-space/core/discovery` | Component discovery |
305
364
  | `@hypen-space/core/components` | Built-in Router, Route, Link |
306
365
 
307
- ## Browser vs Node.js
308
-
309
- ```typescript
310
- // Node.js / Bundler (auto WASM init)
311
- import { Engine } from "@hypen-space/core";
312
- const engine = new Engine();
313
- await engine.init();
314
-
315
- // Browser (explicit WASM path)
316
- import { BrowserEngine } from "@hypen-space/core";
317
- const engine = new BrowserEngine();
318
- await engine.init({ wasmPath: "/hypen_engine_bg.wasm" });
319
- ```
320
-
321
366
  ## Requirements
322
367
 
323
368
  - Node.js >= 18.0.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypen-space/core",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Hypen core engine - Platform-agnostic reactive UI runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",