@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.
- package/dist/chunk-5va59f7m.js +22 -0
- package/dist/chunk-5va59f7m.js.map +9 -0
- package/dist/engine.d.ts +101 -0
- package/dist/events.d.ts +78 -0
- package/dist/index.browser.d.ts +13 -0
- package/dist/index.d.ts +33 -0
- package/dist/remote/index.d.ts +6 -0
- package/dist/router.d.ts +93 -0
- package/dist/src/app.js +160 -0
- package/dist/src/app.js.map +10 -0
- package/dist/src/context.js +114 -0
- package/dist/src/context.js.map +10 -0
- package/dist/src/engine.browser.js +130 -0
- package/dist/src/engine.browser.js.map +10 -0
- package/dist/src/engine.js +101 -0
- package/dist/src/engine.js.map +10 -0
- package/dist/src/events.js +72 -0
- package/dist/src/events.js.map +10 -0
- package/dist/src/index.browser.js +51 -0
- package/dist/src/index.browser.js.map +9 -0
- package/dist/src/index.js +55 -0
- package/dist/src/index.js.map +9 -0
- package/dist/src/remote/client.js +176 -0
- package/dist/src/remote/client.js.map +10 -0
- package/dist/src/remote/index.js +9 -0
- package/dist/src/remote/index.js.map +9 -0
- package/dist/src/remote/types.js +2 -0
- package/dist/src/remote/types.js.map +9 -0
- package/dist/src/renderer.js +58 -0
- package/dist/src/renderer.js.map +10 -0
- package/dist/src/router.js +189 -0
- package/dist/src/router.js.map +10 -0
- package/dist/src/state.js +226 -0
- package/dist/src/state.js.map +10 -0
- package/dist/state.d.ts +30 -0
- package/package.json +124 -0
- package/src/app.ts +330 -0
- package/src/context.ts +201 -0
- package/src/engine.browser.ts +245 -0
- package/src/engine.ts +208 -0
- package/src/events.ts +126 -0
- package/src/index.browser.ts +104 -0
- package/src/index.ts +126 -0
- package/src/remote/client.ts +274 -0
- package/src/remote/index.ts +17 -0
- package/src/remote/types.ts +51 -0
- package/src/renderer.ts +102 -0
- package/src/router.ts +311 -0
- 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
|
+
}
|
package/src/renderer.ts
ADDED
|
@@ -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
|
+
}
|