@hypen-space/core 0.2.0

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 (49) hide show
  1. package/dist/chunk-5va59f7m.js +22 -0
  2. package/dist/chunk-5va59f7m.js.map +9 -0
  3. package/dist/engine.d.ts +101 -0
  4. package/dist/events.d.ts +78 -0
  5. package/dist/index.browser.d.ts +13 -0
  6. package/dist/index.d.ts +33 -0
  7. package/dist/remote/index.d.ts +6 -0
  8. package/dist/router.d.ts +93 -0
  9. package/dist/src/app.js +160 -0
  10. package/dist/src/app.js.map +10 -0
  11. package/dist/src/context.js +114 -0
  12. package/dist/src/context.js.map +10 -0
  13. package/dist/src/engine.browser.js +130 -0
  14. package/dist/src/engine.browser.js.map +10 -0
  15. package/dist/src/engine.js +101 -0
  16. package/dist/src/engine.js.map +10 -0
  17. package/dist/src/events.js +72 -0
  18. package/dist/src/events.js.map +10 -0
  19. package/dist/src/index.browser.js +51 -0
  20. package/dist/src/index.browser.js.map +9 -0
  21. package/dist/src/index.js +55 -0
  22. package/dist/src/index.js.map +9 -0
  23. package/dist/src/remote/client.js +176 -0
  24. package/dist/src/remote/client.js.map +10 -0
  25. package/dist/src/remote/index.js +9 -0
  26. package/dist/src/remote/index.js.map +9 -0
  27. package/dist/src/remote/types.js +2 -0
  28. package/dist/src/remote/types.js.map +9 -0
  29. package/dist/src/renderer.js +58 -0
  30. package/dist/src/renderer.js.map +10 -0
  31. package/dist/src/router.js +189 -0
  32. package/dist/src/router.js.map +10 -0
  33. package/dist/src/state.js +226 -0
  34. package/dist/src/state.js.map +10 -0
  35. package/dist/state.d.ts +30 -0
  36. package/package.json +124 -0
  37. package/src/app.ts +330 -0
  38. package/src/context.ts +201 -0
  39. package/src/engine.browser.ts +245 -0
  40. package/src/engine.ts +208 -0
  41. package/src/events.ts +126 -0
  42. package/src/index.browser.ts +104 -0
  43. package/src/index.ts +126 -0
  44. package/src/remote/client.ts +274 -0
  45. package/src/remote/index.ts +17 -0
  46. package/src/remote/types.ts +51 -0
  47. package/src/renderer.ts +102 -0
  48. package/src/router.ts +311 -0
  49. package/src/state.ts +363 -0
package/src/index.ts ADDED
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @hypen/core - Hypen Core Engine
3
+ *
4
+ * Platform-agnostic reactive UI runtime.
5
+ * Use with @hypen/web for browser rendering, or build your own renderer.
6
+ *
7
+ * ## Quick Start
8
+ *
9
+ * ```typescript
10
+ * import { Engine, app } from "@hypen/core";
11
+ *
12
+ * // Create a stateful module
13
+ * const counter = app
14
+ * .defineState({ count: 0 })
15
+ * .onAction("increment", ({ state }) => {
16
+ * state.count++;
17
+ * })
18
+ * .build();
19
+ *
20
+ * // Initialize the engine
21
+ * const engine = new Engine();
22
+ * await engine.init();
23
+ * ```
24
+ */
25
+
26
+ // ============================================================================
27
+ // ENGINE API
28
+ // ============================================================================
29
+
30
+ export { Engine } from "./engine.js";
31
+ export type {
32
+ Patch,
33
+ Action,
34
+ RenderCallback,
35
+ ActionHandler as EngineActionHandler,
36
+ ResolvedComponent,
37
+ ComponentResolver,
38
+ } from "./engine.js";
39
+
40
+ // Browser engine (explicit WASM init)
41
+ export { Engine as BrowserEngine } from "./engine.browser.js";
42
+ export type { EngineInitOptions } from "./engine.browser.js";
43
+
44
+ // ============================================================================
45
+ // APP / MODULE SYSTEM
46
+ // ============================================================================
47
+
48
+ export { app, HypenApp, HypenAppBuilder, HypenModuleInstance } from "./app.js";
49
+ export type {
50
+ IEngine,
51
+ RouterContext,
52
+ ActionContext,
53
+ ActionNext,
54
+ GlobalContext,
55
+ LifecycleHandler,
56
+ ActionHandlerContext,
57
+ ActionHandler,
58
+ HypenModuleDefinition,
59
+ HypenModule,
60
+ } from "./app.js";
61
+
62
+ // ============================================================================
63
+ // STATE MANAGEMENT
64
+ // ============================================================================
65
+
66
+ export {
67
+ createObservableState,
68
+ batchStateUpdates,
69
+ getStateSnapshot,
70
+ } from "./state.js";
71
+ export type {
72
+ StatePath,
73
+ StateChange,
74
+ StateObserverOptions,
75
+ } from "./state.js";
76
+
77
+ // ============================================================================
78
+ // RENDERER ABSTRACTION
79
+ // ============================================================================
80
+
81
+ export { BaseRenderer, ConsoleRenderer } from "./renderer.js";
82
+ export type { Renderer } from "./renderer.js";
83
+
84
+ // ============================================================================
85
+ // ROUTING
86
+ // ============================================================================
87
+
88
+ export { HypenRouter } from "./router.js";
89
+ export type {
90
+ RouteMatch,
91
+ RouteState,
92
+ RouteChangeCallback,
93
+ } from "./router.js";
94
+
95
+ // ============================================================================
96
+ // EVENTS
97
+ // ============================================================================
98
+
99
+ export { TypedEventEmitter, createEventEmitter } from "./events.js";
100
+ export type { EventHandler, HypenFrameworkEvents } from "./events.js";
101
+
102
+ // ============================================================================
103
+ // GLOBAL CONTEXT
104
+ // ============================================================================
105
+
106
+ export { HypenGlobalContext } from "./context.js";
107
+ export type { ModuleReference } from "./context.js";
108
+
109
+ // ============================================================================
110
+ // REMOTE UI
111
+ // ============================================================================
112
+
113
+ export { RemoteEngine } from "./remote/client.js";
114
+ export type {
115
+ RemoteMessage,
116
+ InitialTreeMessage,
117
+ PatchMessage,
118
+ DispatchActionMessage,
119
+ StateUpdateMessage,
120
+ RemoteClient,
121
+ RemoteServerConfig,
122
+ } from "./remote/types.js";
123
+ export type {
124
+ RemoteConnectionState,
125
+ RemoteEngineOptions,
126
+ } from "./remote/client.js";
@@ -0,0 +1,274 @@
1
+ /**
2
+ * RemoteEngine - Connect to a remote Hypen app over WebSocket
3
+ * Platform-agnostic client (uses standard WebSocket API)
4
+ */
5
+
6
+ import type {
7
+ RemoteMessage,
8
+ InitialTreeMessage,
9
+ PatchMessage,
10
+ DispatchActionMessage,
11
+ } from "./types.js";
12
+ import type { Patch } from "../engine.js";
13
+
14
+ export type RemoteConnectionState = "disconnected" | "connecting" | "connected" | "error";
15
+
16
+ export interface RemoteEngineOptions {
17
+ autoReconnect?: boolean;
18
+ reconnectInterval?: number;
19
+ maxReconnectAttempts?: number;
20
+ }
21
+
22
+ /**
23
+ * Client-side engine that connects to a remote Hypen app
24
+ */
25
+ export class RemoteEngine {
26
+ private ws: WebSocket | null = null;
27
+ private url: string;
28
+ private state: RemoteConnectionState = "disconnected";
29
+ private options: Required<RemoteEngineOptions>;
30
+ private reconnectAttempts = 0;
31
+ private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
32
+
33
+ // Callbacks
34
+ private patchCallbacks: Array<(patches: Patch[]) => void> = [];
35
+ private stateCallbacks: Array<(state: any) => void> = [];
36
+ private connectionCallbacks: Array<() => void> = [];
37
+ private disconnectionCallbacks: Array<() => void> = [];
38
+ private errorCallbacks: Array<(error: Error) => void> = [];
39
+
40
+ // State
41
+ private currentState: any = null;
42
+ private currentRevision = 0;
43
+ private moduleName: string = "";
44
+
45
+ constructor(url: string, options: RemoteEngineOptions = {}) {
46
+ this.url = url;
47
+ this.options = {
48
+ autoReconnect: options.autoReconnect ?? true,
49
+ reconnectInterval: options.reconnectInterval ?? 3000,
50
+ maxReconnectAttempts: options.maxReconnectAttempts ?? 10,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Connect to the remote server
56
+ */
57
+ async connect(): Promise<void> {
58
+ if (this.state === "connected" || this.state === "connecting") {
59
+ return;
60
+ }
61
+
62
+ this.state = "connecting";
63
+
64
+ return new Promise((resolve, reject) => {
65
+ try {
66
+ this.ws = new WebSocket(this.url);
67
+
68
+ this.ws.onopen = () => {
69
+ this.state = "connected";
70
+ this.reconnectAttempts = 0;
71
+ this.connectionCallbacks.forEach((cb) => cb());
72
+ resolve();
73
+ };
74
+
75
+ this.ws.onmessage = (event) => {
76
+ this.handleMessage(event.data);
77
+ };
78
+
79
+ this.ws.onerror = () => {
80
+ this.state = "error";
81
+ const error = new Error("WebSocket error");
82
+ this.errorCallbacks.forEach((cb) => cb(error));
83
+ reject(error);
84
+ };
85
+
86
+ this.ws.onclose = () => {
87
+ this.state = "disconnected";
88
+ this.disconnectionCallbacks.forEach((cb) => cb());
89
+ this.attemptReconnect();
90
+ };
91
+ } catch (error) {
92
+ this.state = "error";
93
+ reject(error);
94
+ }
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Disconnect from the remote server
100
+ */
101
+ disconnect(): void {
102
+ if (this.reconnectTimer) {
103
+ clearTimeout(this.reconnectTimer);
104
+ this.reconnectTimer = null;
105
+ }
106
+
107
+ if (this.ws) {
108
+ this.ws.close();
109
+ this.ws = null;
110
+ }
111
+
112
+ this.state = "disconnected";
113
+ }
114
+
115
+ /**
116
+ * Dispatch an action to the remote server
117
+ */
118
+ dispatchAction(action: string, payload?: any): void {
119
+ if (this.state !== "connected" || !this.ws) {
120
+ console.warn("Cannot dispatch action: not connected");
121
+ return;
122
+ }
123
+
124
+ const message: DispatchActionMessage = {
125
+ type: "dispatchAction",
126
+ module: this.moduleName,
127
+ action,
128
+ payload,
129
+ };
130
+
131
+ this.ws.send(JSON.stringify(message));
132
+ }
133
+
134
+ /**
135
+ * Register callback for patches
136
+ */
137
+ onPatches(callback: (patches: Patch[]) => void): this {
138
+ this.patchCallbacks.push(callback);
139
+ return this;
140
+ }
141
+
142
+ /**
143
+ * Register callback for state updates
144
+ */
145
+ onStateUpdate(callback: (state: any) => void): this {
146
+ this.stateCallbacks.push(callback);
147
+ return this;
148
+ }
149
+
150
+ /**
151
+ * Register callback for connection
152
+ */
153
+ onConnect(callback: () => void): this {
154
+ this.connectionCallbacks.push(callback);
155
+ return this;
156
+ }
157
+
158
+ /**
159
+ * Register callback for disconnection
160
+ */
161
+ onDisconnect(callback: () => void): this {
162
+ this.disconnectionCallbacks.push(callback);
163
+ return this;
164
+ }
165
+
166
+ /**
167
+ * Register callback for errors
168
+ */
169
+ onError(callback: (error: Error) => void): this {
170
+ this.errorCallbacks.push(callback);
171
+ return this;
172
+ }
173
+
174
+ /**
175
+ * Get current connection state
176
+ */
177
+ getConnectionState(): RemoteConnectionState {
178
+ return this.state;
179
+ }
180
+
181
+ /**
182
+ * Get current app state
183
+ */
184
+ getCurrentState(): any {
185
+ return this.currentState;
186
+ }
187
+
188
+ /**
189
+ * Get current revision
190
+ */
191
+ getRevision(): number {
192
+ return this.currentRevision;
193
+ }
194
+
195
+ private handleMessage(data: string): void {
196
+ try {
197
+ const message = JSON.parse(data) as RemoteMessage;
198
+
199
+ switch (message.type) {
200
+ case "initialTree":
201
+ this.handleInitialTree(message as InitialTreeMessage);
202
+ break;
203
+
204
+ case "patch":
205
+ this.handlePatch(message as PatchMessage);
206
+ break;
207
+
208
+ case "stateUpdate":
209
+ this.currentState = message.state;
210
+ this.stateCallbacks.forEach((cb) => cb(message.state));
211
+ break;
212
+ }
213
+ } catch (error) {
214
+ console.error("Error handling remote message:", error);
215
+ this.errorCallbacks.forEach((cb) =>
216
+ cb(error instanceof Error ? error : new Error(String(error)))
217
+ );
218
+ }
219
+ }
220
+
221
+ private handleInitialTree(message: InitialTreeMessage): void {
222
+ this.moduleName = message.module;
223
+ this.currentState = message.state;
224
+ this.currentRevision = message.revision;
225
+
226
+ // Apply initial patches
227
+ if (message.patches.length > 0) {
228
+ this.patchCallbacks.forEach((cb) => cb(message.patches));
229
+ }
230
+
231
+ // Notify state callbacks
232
+ this.stateCallbacks.forEach((cb) => cb(message.state));
233
+ }
234
+
235
+ private handlePatch(message: PatchMessage): void {
236
+ // Check revision ordering
237
+ if (message.revision <= this.currentRevision) {
238
+ console.warn(
239
+ `Out of order patch: expected > ${this.currentRevision}, got ${message.revision}`
240
+ );
241
+ return;
242
+ }
243
+
244
+ this.currentRevision = message.revision;
245
+
246
+ // Apply patches
247
+ if (message.patches.length > 0) {
248
+ this.patchCallbacks.forEach((cb) => cb(message.patches));
249
+ }
250
+ }
251
+
252
+ private attemptReconnect(): void {
253
+ if (!this.options.autoReconnect) {
254
+ return;
255
+ }
256
+
257
+ if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
258
+ console.error("Max reconnection attempts reached");
259
+ return;
260
+ }
261
+
262
+ this.reconnectAttempts++;
263
+
264
+ console.log(
265
+ `Attempting to reconnect (${this.reconnectAttempts}/${this.options.maxReconnectAttempts})...`
266
+ );
267
+
268
+ this.reconnectTimer = setTimeout(() => {
269
+ this.connect().catch((error) => {
270
+ console.error("Reconnection failed:", error);
271
+ });
272
+ }, this.options.reconnectInterval);
273
+ }
274
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Remote UI streaming for Hypen
3
+ *
4
+ * Client-side: Connect to remote Hypen apps
5
+ */
6
+
7
+ export { RemoteEngine } from "./client.js";
8
+ export type {
9
+ RemoteMessage,
10
+ InitialTreeMessage,
11
+ PatchMessage,
12
+ DispatchActionMessage,
13
+ StateUpdateMessage,
14
+ RemoteClient,
15
+ RemoteServerConfig,
16
+ } from "./types.js";
17
+ export type { RemoteConnectionState, RemoteEngineOptions } from "./client.js";
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Remote UI Protocol Types
3
+ */
4
+
5
+ import type { Patch } from "../engine.js";
6
+
7
+ export type RemoteMessage =
8
+ | InitialTreeMessage
9
+ | PatchMessage
10
+ | StateUpdateMessage
11
+ | DispatchActionMessage;
12
+
13
+ export interface InitialTreeMessage {
14
+ type: "initialTree";
15
+ module: string;
16
+ state: any;
17
+ patches: Patch[];
18
+ revision: number;
19
+ }
20
+
21
+ export interface PatchMessage {
22
+ type: "patch";
23
+ module: string;
24
+ patches: Patch[];
25
+ revision: number;
26
+ }
27
+
28
+ export interface StateUpdateMessage {
29
+ type: "stateUpdate";
30
+ module: string;
31
+ state: any;
32
+ revision: number;
33
+ }
34
+
35
+ export interface DispatchActionMessage {
36
+ type: "dispatchAction";
37
+ module: string;
38
+ action: string;
39
+ payload?: any;
40
+ }
41
+
42
+ export interface RemoteClient {
43
+ id: string;
44
+ socket: any;
45
+ connectedAt: Date;
46
+ }
47
+
48
+ export interface RemoteServerConfig {
49
+ port?: number;
50
+ hostname?: string;
51
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Renderer abstraction interface
3
+ *
4
+ * Renderers are pluggable and can be implemented for different platforms
5
+ * (DOM, Canvas, Native, etc.)
6
+ */
7
+
8
+ import type { Patch } from "./engine.js";
9
+
10
+ /**
11
+ * Renderer interface that all platform renderers must implement
12
+ */
13
+ export interface Renderer {
14
+ /**
15
+ * Apply a batch of patches to the render tree
16
+ */
17
+ applyPatches(patches: Patch[]): void;
18
+
19
+ /**
20
+ * Get a node by its ID (optional, for debugging)
21
+ */
22
+ getNode?(id: string): any;
23
+
24
+ /**
25
+ * Clear the entire render tree
26
+ */
27
+ clear?(): void;
28
+ }
29
+
30
+ /**
31
+ * Base renderer class with common utilities
32
+ */
33
+ export abstract class BaseRenderer implements Renderer {
34
+ protected nodes: Map<string, any> = new Map();
35
+
36
+ abstract applyPatches(patches: Patch[]): void;
37
+
38
+ getNode(id: string): any {
39
+ return this.nodes.get(id);
40
+ }
41
+
42
+ clear(): void {
43
+ this.nodes.clear();
44
+ }
45
+
46
+ /**
47
+ * Apply a single patch
48
+ */
49
+ protected applyPatch(patch: Patch): void {
50
+ switch (patch.type) {
51
+ case "create":
52
+ this.onCreate(patch.id!, patch.elementType!, patch.props || {});
53
+ break;
54
+ case "setProp":
55
+ this.onSetProp(patch.id!, patch.name!, patch.value);
56
+ break;
57
+ case "setText":
58
+ this.onSetText(patch.id!, patch.text!);
59
+ break;
60
+ case "insert":
61
+ this.onInsert(patch.parentId!, patch.id!, patch.beforeId);
62
+ break;
63
+ case "move":
64
+ this.onMove(patch.parentId!, patch.id!, patch.beforeId);
65
+ break;
66
+ case "remove":
67
+ this.onRemove(patch.id!);
68
+ break;
69
+ case "attachEvent":
70
+ this.onAttachEvent(patch.id!, patch.eventName!);
71
+ break;
72
+ case "detachEvent":
73
+ this.onDetachEvent(patch.id!, patch.eventName!);
74
+ break;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Platform-specific patch handlers
80
+ */
81
+ protected abstract onCreate(id: string, elementType: string, props: Record<string, any>): void;
82
+ protected abstract onSetProp(id: string, name: string, value: any): void;
83
+ protected abstract onSetText(id: string, text: string): void;
84
+ protected abstract onInsert(parentId: string, id: string, beforeId?: string): void;
85
+ protected abstract onMove(parentId: string, id: string, beforeId?: string): void;
86
+ protected abstract onRemove(id: string): void;
87
+ protected abstract onAttachEvent(id: string, eventName: string): void;
88
+ protected abstract onDetachEvent(id: string, eventName: string): void;
89
+ }
90
+
91
+ /**
92
+ * Console/Debug renderer that logs patches
93
+ */
94
+ export class ConsoleRenderer implements Renderer {
95
+ applyPatches(patches: Patch[]): void {
96
+ console.group("Patches:");
97
+ for (const patch of patches) {
98
+ console.log(patch);
99
+ }
100
+ console.groupEnd();
101
+ }
102
+ }