@hypen-space/core 0.2.4 → 0.2.7
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/src/app.js +267 -9
- package/dist/src/app.js.map +4 -3
- package/dist/src/components/builtin.js +411 -8
- package/dist/src/components/builtin.js.map +5 -3
- package/dist/src/context.js +92 -7
- package/dist/src/context.js.map +4 -3
- package/dist/src/discovery.js +412 -9
- package/dist/src/discovery.js.map +5 -3
- package/dist/src/engine.browser.js +29 -7
- package/dist/src/engine.browser.js.map +2 -2
- package/dist/src/engine.js +29 -4
- package/dist/src/engine.js.map +2 -2
- package/dist/src/events.js +29 -4
- package/dist/src/events.js.map +2 -2
- package/dist/src/index.browser.js +1111 -33
- package/dist/src/index.browser.js.map +12 -3
- package/dist/src/index.js +1778 -59
- package/dist/src/index.js.map +18 -3
- package/dist/src/loader.js +29 -4
- package/dist/src/loader.js.map +2 -2
- package/dist/src/plugin.js +29 -4
- package/dist/src/plugin.js.map +2 -2
- package/dist/src/remote/client.js +29 -4
- package/dist/src/remote/client.js.map +2 -2
- package/dist/src/remote/index.js +197 -5
- package/dist/src/remote/index.js.map +4 -3
- package/dist/src/renderer.js +29 -4
- package/dist/src/renderer.js.map +2 -2
- package/dist/src/resolver.js +29 -4
- package/dist/src/resolver.js.map +2 -2
- package/dist/src/router.js +253 -8
- package/dist/src/router.js.map +4 -3
- package/dist/src/state.js +29 -4
- package/dist/src/state.js.map +2 -2
- package/package.json +57 -57
- package/dist/chunk-5va59f7m.js +0 -22
- package/dist/chunk-5va59f7m.js.map +0 -9
|
@@ -1,35 +1,1113 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __export = (target, all) => {
|
|
19
|
+
for (var name in all)
|
|
20
|
+
__defProp(target, name, {
|
|
21
|
+
get: all[name],
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
set: (newValue) => all[name] = () => newValue
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
28
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
29
|
+
|
|
30
|
+
// src/state.ts
|
|
31
|
+
function deepClone(obj) {
|
|
32
|
+
if (obj === null || typeof obj !== "object") {
|
|
33
|
+
return obj;
|
|
34
|
+
}
|
|
35
|
+
const visited = new WeakMap;
|
|
36
|
+
function cloneInternal(value) {
|
|
37
|
+
if (value === null || typeof value !== "object") {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
if (visited.has(value)) {
|
|
41
|
+
return visited.get(value);
|
|
42
|
+
}
|
|
43
|
+
if (value instanceof Date) {
|
|
44
|
+
return new Date(value.getTime());
|
|
45
|
+
}
|
|
46
|
+
if (value instanceof RegExp) {
|
|
47
|
+
return new RegExp(value.source, value.flags);
|
|
48
|
+
}
|
|
49
|
+
if (value instanceof Map) {
|
|
50
|
+
const mapClone = new Map;
|
|
51
|
+
visited.set(value, mapClone);
|
|
52
|
+
for (const [k, v] of value.entries()) {
|
|
53
|
+
mapClone.set(cloneInternal(k), cloneInternal(v));
|
|
54
|
+
}
|
|
55
|
+
return mapClone;
|
|
56
|
+
}
|
|
57
|
+
if (value instanceof Set) {
|
|
58
|
+
const setClone = new Set;
|
|
59
|
+
visited.set(value, setClone);
|
|
60
|
+
for (const item of value.values()) {
|
|
61
|
+
setClone.add(cloneInternal(item));
|
|
62
|
+
}
|
|
63
|
+
return setClone;
|
|
64
|
+
}
|
|
65
|
+
if (value instanceof WeakMap || value instanceof WeakSet) {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
if (Array.isArray(value)) {
|
|
69
|
+
const arrClone = [];
|
|
70
|
+
visited.set(value, arrClone);
|
|
71
|
+
for (let i = 0;i < value.length; i++) {
|
|
72
|
+
arrClone[i] = cloneInternal(value[i]);
|
|
73
|
+
}
|
|
74
|
+
return arrClone;
|
|
75
|
+
}
|
|
76
|
+
const objClone = {};
|
|
77
|
+
visited.set(value, objClone);
|
|
78
|
+
for (const key in value) {
|
|
79
|
+
if (value.hasOwnProperty(key)) {
|
|
80
|
+
objClone[key] = cloneInternal(value[key]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const symbolKeys = Object.getOwnPropertySymbols(value);
|
|
84
|
+
for (const sym of symbolKeys) {
|
|
85
|
+
objClone[sym] = cloneInternal(value[sym]);
|
|
86
|
+
}
|
|
87
|
+
return objClone;
|
|
88
|
+
}
|
|
89
|
+
return cloneInternal(obj);
|
|
90
|
+
}
|
|
91
|
+
function diffState(oldState, newState, basePath = "") {
|
|
92
|
+
const paths = [];
|
|
93
|
+
const newValues = {};
|
|
94
|
+
function diff(oldVal, newVal, path) {
|
|
95
|
+
if (oldVal === newVal)
|
|
96
|
+
return;
|
|
97
|
+
if (typeof oldVal !== "object" || typeof newVal !== "object" || oldVal === null || newVal === null) {
|
|
98
|
+
if (oldVal !== newVal) {
|
|
99
|
+
paths.push(path);
|
|
100
|
+
newValues[path] = newVal;
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(oldVal) || Array.isArray(newVal)) {
|
|
105
|
+
if (!Array.isArray(oldVal) || !Array.isArray(newVal) || oldVal.length !== newVal.length) {
|
|
106
|
+
paths.push(path);
|
|
107
|
+
newValues[path] = newVal;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
for (let i = 0;i < newVal.length; i++) {
|
|
111
|
+
const itemPath = path ? `${path}.${i}` : `${i}`;
|
|
112
|
+
diff(oldVal[i], newVal[i], itemPath);
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const oldKeys = new Set(Object.keys(oldVal));
|
|
117
|
+
const newKeys = new Set(Object.keys(newVal));
|
|
118
|
+
for (const key of newKeys) {
|
|
119
|
+
const propPath = path ? `${path}.${key}` : key;
|
|
120
|
+
if (!oldKeys.has(key)) {
|
|
121
|
+
paths.push(propPath);
|
|
122
|
+
newValues[propPath] = newVal[key];
|
|
123
|
+
} else {
|
|
124
|
+
diff(oldVal[key], newVal[key], propPath);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
for (const key of oldKeys) {
|
|
128
|
+
if (!newKeys.has(key)) {
|
|
129
|
+
const propPath = path ? `${path}.${key}` : key;
|
|
130
|
+
paths.push(propPath);
|
|
131
|
+
newValues[propPath] = undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
diff(oldState, newState, basePath);
|
|
136
|
+
return { paths, newValues };
|
|
137
|
+
}
|
|
138
|
+
function createObservableState(initialState, options) {
|
|
139
|
+
const opts = options || { onChange: () => {} };
|
|
140
|
+
if (initialState === null || initialState === undefined) {
|
|
141
|
+
initialState = {};
|
|
142
|
+
}
|
|
143
|
+
if (initialState instanceof Number || initialState instanceof String || initialState instanceof Boolean) {
|
|
144
|
+
throw new TypeError("Cannot create observable state from primitive wrapper objects (Number, String, Boolean). " + "Use plain primitives or regular objects instead.");
|
|
145
|
+
}
|
|
146
|
+
initialState = deepClone(initialState);
|
|
147
|
+
let lastSnapshot = deepClone(initialState);
|
|
148
|
+
const pathPrefix = opts.pathPrefix || "";
|
|
149
|
+
let batchDepth = 0;
|
|
150
|
+
let pendingChange = null;
|
|
151
|
+
function notifyChange() {
|
|
152
|
+
if (batchDepth > 0)
|
|
153
|
+
return;
|
|
154
|
+
const change = diffState(lastSnapshot, state, pathPrefix);
|
|
155
|
+
if (change.paths.length > 0) {
|
|
156
|
+
lastSnapshot = deepClone(state);
|
|
157
|
+
if (pendingChange) {
|
|
158
|
+
change.paths.push(...pendingChange.paths);
|
|
159
|
+
Object.assign(change.newValues, pendingChange.newValues);
|
|
160
|
+
pendingChange = null;
|
|
161
|
+
}
|
|
162
|
+
opts.onChange(change);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
let notificationPending = false;
|
|
166
|
+
function scheduleBatch() {
|
|
167
|
+
if (batchDepth === 0) {
|
|
168
|
+
if (!notificationPending) {
|
|
169
|
+
notificationPending = true;
|
|
170
|
+
queueMicrotask(() => {
|
|
171
|
+
notificationPending = false;
|
|
172
|
+
if (batchDepth === 0) {
|
|
173
|
+
notifyChange();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const proxyCache = new WeakMap;
|
|
180
|
+
function createProxy(target, basePath) {
|
|
181
|
+
if (proxyCache.has(target)) {
|
|
182
|
+
return proxyCache.get(target);
|
|
183
|
+
}
|
|
184
|
+
const proxy = new Proxy(target, {
|
|
185
|
+
get(obj, prop) {
|
|
186
|
+
const value = obj[prop];
|
|
187
|
+
if (prop === "__beginBatch") {
|
|
188
|
+
return () => {
|
|
189
|
+
batchDepth++;
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (prop === "__endBatch") {
|
|
193
|
+
return () => {
|
|
194
|
+
batchDepth--;
|
|
195
|
+
if (batchDepth === 0) {
|
|
196
|
+
notifyChange();
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (prop === "__getSnapshot") {
|
|
201
|
+
return () => deepClone(obj);
|
|
202
|
+
}
|
|
203
|
+
if (value && typeof value === "object") {
|
|
204
|
+
if (value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet) {
|
|
205
|
+
return value;
|
|
206
|
+
}
|
|
207
|
+
return createProxy(value, basePath ? `${basePath}.${String(prop)}` : String(prop));
|
|
208
|
+
}
|
|
209
|
+
return value;
|
|
210
|
+
},
|
|
211
|
+
set(obj, prop, value) {
|
|
212
|
+
const oldValue = obj[prop];
|
|
213
|
+
obj[prop] = value;
|
|
214
|
+
if (oldValue !== value) {
|
|
215
|
+
scheduleBatch();
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
},
|
|
219
|
+
deleteProperty(obj, prop) {
|
|
220
|
+
if (prop in obj) {
|
|
221
|
+
delete obj[prop];
|
|
222
|
+
scheduleBatch();
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
proxyCache.set(target, proxy);
|
|
228
|
+
return proxy;
|
|
229
|
+
}
|
|
230
|
+
const state = createProxy(initialState, pathPrefix);
|
|
231
|
+
return state;
|
|
232
|
+
}
|
|
233
|
+
function batchStateUpdates(state, fn) {
|
|
234
|
+
const s = state;
|
|
235
|
+
if (s.__beginBatch && s.__endBatch) {
|
|
236
|
+
s.__beginBatch();
|
|
237
|
+
try {
|
|
238
|
+
fn();
|
|
239
|
+
} finally {
|
|
240
|
+
s.__endBatch();
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
fn();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function getStateSnapshot(state) {
|
|
247
|
+
const s = state;
|
|
248
|
+
if (s.__getSnapshot) {
|
|
249
|
+
return s.__getSnapshot();
|
|
250
|
+
}
|
|
251
|
+
return deepClone(state);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/app.ts
|
|
255
|
+
var exports_app = {};
|
|
256
|
+
__export(exports_app, {
|
|
257
|
+
app: () => app,
|
|
258
|
+
HypenModuleInstance: () => HypenModuleInstance,
|
|
259
|
+
HypenAppBuilder: () => HypenAppBuilder,
|
|
260
|
+
HypenApp: () => HypenApp
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
class HypenAppBuilder {
|
|
264
|
+
initialState;
|
|
265
|
+
options;
|
|
266
|
+
createdHandler;
|
|
267
|
+
actionHandlers = new Map;
|
|
268
|
+
destroyedHandler;
|
|
269
|
+
constructor(initialState, options) {
|
|
270
|
+
this.initialState = initialState;
|
|
271
|
+
this.options = options || {};
|
|
272
|
+
}
|
|
273
|
+
onCreated(fn) {
|
|
274
|
+
this.createdHandler = fn;
|
|
275
|
+
return this;
|
|
276
|
+
}
|
|
277
|
+
onAction(name, fn) {
|
|
278
|
+
this.actionHandlers.set(name, fn);
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
onDestroyed(fn) {
|
|
282
|
+
this.destroyedHandler = fn;
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
build() {
|
|
286
|
+
const stateKeys = this.initialState !== null && typeof this.initialState === "object" ? Object.keys(this.initialState) : [];
|
|
287
|
+
return {
|
|
288
|
+
name: this.options.name,
|
|
289
|
+
actions: Array.from(this.actionHandlers.keys()),
|
|
290
|
+
stateKeys,
|
|
291
|
+
persist: this.options.persist,
|
|
292
|
+
version: this.options.version,
|
|
293
|
+
initialState: this.initialState,
|
|
294
|
+
handlers: {
|
|
295
|
+
onCreated: this.createdHandler,
|
|
296
|
+
onAction: this.actionHandlers,
|
|
297
|
+
onDestroyed: this.destroyedHandler
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
class HypenApp {
|
|
304
|
+
defineState(initial, options) {
|
|
305
|
+
return new HypenAppBuilder(initial, options);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
class HypenModuleInstance {
|
|
310
|
+
engine;
|
|
311
|
+
definition;
|
|
312
|
+
state;
|
|
313
|
+
isDestroyed = false;
|
|
314
|
+
routerContext;
|
|
315
|
+
globalContext;
|
|
316
|
+
stateChangeCallbacks = [];
|
|
317
|
+
constructor(engine, definition, routerContext, globalContext) {
|
|
318
|
+
this.engine = engine;
|
|
319
|
+
this.definition = definition;
|
|
320
|
+
this.routerContext = routerContext;
|
|
321
|
+
this.globalContext = globalContext;
|
|
322
|
+
this.state = createObservableState(definition.initialState, {
|
|
323
|
+
onChange: (change) => {
|
|
324
|
+
this.engine.notifyStateChange(change.paths, change.newValues);
|
|
325
|
+
this.stateChangeCallbacks.forEach((cb) => cb());
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
this.engine.setModule(definition.name || "AnonymousModule", definition.actions, definition.stateKeys, getStateSnapshot(this.state));
|
|
329
|
+
for (const [actionName, handler] of definition.handlers.onAction) {
|
|
330
|
+
console.log(`[ModuleInstance] Registering action handler: ${actionName} for module ${definition.name}`);
|
|
331
|
+
this.engine.onAction(actionName, async (action) => {
|
|
332
|
+
console.log(`[ModuleInstance] Action handler fired: ${actionName}`, action);
|
|
333
|
+
const actionCtx = {
|
|
334
|
+
name: action.name,
|
|
335
|
+
payload: action.payload,
|
|
336
|
+
sender: action.sender
|
|
337
|
+
};
|
|
338
|
+
const next = {
|
|
339
|
+
router: this.routerContext?.root || null
|
|
340
|
+
};
|
|
341
|
+
const context = this.globalContext ? this.createGlobalContextAPI() : undefined;
|
|
342
|
+
try {
|
|
343
|
+
await handler({
|
|
344
|
+
action: actionCtx,
|
|
345
|
+
state: this.state,
|
|
346
|
+
next,
|
|
347
|
+
context
|
|
348
|
+
});
|
|
349
|
+
console.log(`[ModuleInstance] Action handler completed: ${actionName}`);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error(`[ModuleInstance] Action handler error for ${actionName}:`, error);
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
this.callCreatedHandler();
|
|
357
|
+
}
|
|
358
|
+
createGlobalContextAPI() {
|
|
359
|
+
if (!this.globalContext) {
|
|
360
|
+
throw new Error("Global context not available");
|
|
361
|
+
}
|
|
362
|
+
const ctx = this.globalContext;
|
|
363
|
+
const api = {
|
|
364
|
+
getModule: (id) => ctx.getModule(id),
|
|
365
|
+
hasModule: (id) => ctx.hasModule(id),
|
|
366
|
+
getModuleIds: () => ctx.getModuleIds(),
|
|
367
|
+
getGlobalState: () => ctx.getGlobalState(),
|
|
368
|
+
emit: (event, payload) => ctx.emit(event, payload),
|
|
369
|
+
on: (event, handler) => ctx.on(event, handler)
|
|
370
|
+
};
|
|
371
|
+
if (ctx.__router) {
|
|
372
|
+
api.__router = ctx.__router;
|
|
373
|
+
}
|
|
374
|
+
if (ctx.__hypenEngine) {
|
|
375
|
+
api.__hypenEngine = ctx.__hypenEngine;
|
|
376
|
+
}
|
|
377
|
+
return api;
|
|
378
|
+
}
|
|
379
|
+
async callCreatedHandler() {
|
|
380
|
+
if (this.definition.handlers.onCreated) {
|
|
381
|
+
const context = this.globalContext ? this.createGlobalContextAPI() : undefined;
|
|
382
|
+
await this.definition.handlers.onCreated(this.state, context);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
onStateChange(callback) {
|
|
386
|
+
this.stateChangeCallbacks.push(callback);
|
|
387
|
+
}
|
|
388
|
+
async destroy() {
|
|
389
|
+
if (this.isDestroyed)
|
|
390
|
+
return;
|
|
391
|
+
if (this.definition.handlers.onDestroyed) {
|
|
392
|
+
await this.definition.handlers.onDestroyed(this.state);
|
|
393
|
+
}
|
|
394
|
+
this.isDestroyed = true;
|
|
395
|
+
}
|
|
396
|
+
getState() {
|
|
397
|
+
return getStateSnapshot(this.state);
|
|
398
|
+
}
|
|
399
|
+
getLiveState() {
|
|
400
|
+
return this.state;
|
|
401
|
+
}
|
|
402
|
+
updateState(patch) {
|
|
403
|
+
Object.assign(this.state, patch);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
var app;
|
|
407
|
+
var init_app = __esm(() => {
|
|
408
|
+
app = new HypenApp;
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// src/engine.browser.ts
|
|
412
|
+
var wasmInit = null;
|
|
413
|
+
var WasmEngineClass = null;
|
|
414
|
+
function mapToObject(value) {
|
|
415
|
+
if (value instanceof Map) {
|
|
416
|
+
const obj = {};
|
|
417
|
+
for (const [key, val] of value.entries()) {
|
|
418
|
+
obj[key] = mapToObject(val);
|
|
419
|
+
}
|
|
420
|
+
return obj;
|
|
421
|
+
} else if (Array.isArray(value)) {
|
|
422
|
+
return value.map(mapToObject);
|
|
423
|
+
} else if (value && typeof value === "object" && value.constructor === Object) {
|
|
424
|
+
const obj = {};
|
|
425
|
+
for (const [key, val] of Object.entries(value)) {
|
|
426
|
+
obj[key] = mapToObject(val);
|
|
427
|
+
}
|
|
428
|
+
return obj;
|
|
429
|
+
}
|
|
430
|
+
return value;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
class Engine {
|
|
434
|
+
wasmEngine = null;
|
|
435
|
+
initialized = false;
|
|
436
|
+
async init(options = {}) {
|
|
437
|
+
if (this.initialized)
|
|
438
|
+
return;
|
|
439
|
+
const wasmPath = options.wasmPath ?? "https://unpkg.com/@hypen-space/core/wasm-browser/hypen_engine_bg.wasm";
|
|
440
|
+
try {
|
|
441
|
+
const wasmModule = await import("../wasm-browser/hypen_engine.js");
|
|
442
|
+
wasmInit = wasmModule.default;
|
|
443
|
+
WasmEngineClass = wasmModule.WasmEngine;
|
|
444
|
+
await wasmInit(wasmPath);
|
|
445
|
+
this.wasmEngine = new WasmEngineClass;
|
|
446
|
+
this.initialized = true;
|
|
447
|
+
} catch (error) {
|
|
448
|
+
console.error("[Hypen] Failed to initialize WASM engine:", error);
|
|
449
|
+
throw error;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
ensureInitialized() {
|
|
453
|
+
if (!this.wasmEngine) {
|
|
454
|
+
throw new Error("Engine not initialized. Call init() first.");
|
|
455
|
+
}
|
|
456
|
+
return this.wasmEngine;
|
|
457
|
+
}
|
|
458
|
+
setRenderCallback(callback) {
|
|
459
|
+
const engine = this.ensureInitialized();
|
|
460
|
+
engine.setRenderCallback((patches) => {
|
|
461
|
+
callback(patches);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
setComponentResolver(resolver) {
|
|
465
|
+
const engine = this.ensureInitialized();
|
|
466
|
+
engine.setComponentResolver((componentName, contextPath) => {
|
|
467
|
+
const result = resolver(componentName, contextPath);
|
|
468
|
+
return result;
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
renderSource(source) {
|
|
472
|
+
const engine = this.ensureInitialized();
|
|
473
|
+
engine.renderSource(source);
|
|
474
|
+
}
|
|
475
|
+
renderLazyComponent(source) {
|
|
476
|
+
const engine = this.ensureInitialized();
|
|
477
|
+
engine.renderLazyComponent(source);
|
|
478
|
+
}
|
|
479
|
+
renderInto(source, parentNodeId, state) {
|
|
480
|
+
const engine = this.ensureInitialized();
|
|
481
|
+
const safeState = JSON.parse(JSON.stringify(state));
|
|
482
|
+
engine.renderInto(source, parentNodeId, safeState);
|
|
483
|
+
}
|
|
484
|
+
notifyStateChange(paths, currentState) {
|
|
485
|
+
const engine = this.ensureInitialized();
|
|
486
|
+
const plainObject = JSON.parse(JSON.stringify(currentState));
|
|
487
|
+
engine.updateState(plainObject);
|
|
488
|
+
if (paths.length > 0) {
|
|
489
|
+
console.debug("[Hypen] State changed:", paths);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
updateState(statePatch) {
|
|
493
|
+
const engine = this.ensureInitialized();
|
|
494
|
+
const plainObject = JSON.parse(JSON.stringify(statePatch));
|
|
495
|
+
engine.updateState(plainObject);
|
|
496
|
+
}
|
|
497
|
+
dispatchAction(name, payload) {
|
|
498
|
+
const engine = this.ensureInitialized();
|
|
499
|
+
console.log(`[Engine] Action dispatched: ${name}`);
|
|
500
|
+
engine.dispatchAction(name, payload ?? null);
|
|
501
|
+
}
|
|
502
|
+
onAction(actionName, handler) {
|
|
503
|
+
const engine = this.ensureInitialized();
|
|
504
|
+
engine.onAction(actionName, (action) => {
|
|
505
|
+
const normalizedAction = {
|
|
506
|
+
...action,
|
|
507
|
+
payload: action.payload ? mapToObject(action.payload) : action.payload
|
|
508
|
+
};
|
|
509
|
+
Promise.resolve(handler(normalizedAction)).catch(console.error);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
setModule(name, actions, stateKeys, initialState) {
|
|
513
|
+
const engine = this.ensureInitialized();
|
|
514
|
+
engine.setModule(name, actions, stateKeys, initialState);
|
|
515
|
+
}
|
|
516
|
+
getRevision() {
|
|
517
|
+
const engine = this.ensureInitialized();
|
|
518
|
+
return engine.getRevision();
|
|
519
|
+
}
|
|
520
|
+
clearTree() {
|
|
521
|
+
const engine = this.ensureInitialized();
|
|
522
|
+
engine.clearTree();
|
|
523
|
+
}
|
|
524
|
+
debugParseComponent(source) {
|
|
525
|
+
const engine = this.ensureInitialized();
|
|
526
|
+
return engine.debugParseComponent(source);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/renderer.ts
|
|
531
|
+
class BaseRenderer {
|
|
532
|
+
nodes = new Map;
|
|
533
|
+
getNode(id) {
|
|
534
|
+
return this.nodes.get(id);
|
|
535
|
+
}
|
|
536
|
+
clear() {
|
|
537
|
+
this.nodes.clear();
|
|
538
|
+
}
|
|
539
|
+
applyPatch(patch) {
|
|
540
|
+
switch (patch.type) {
|
|
541
|
+
case "create":
|
|
542
|
+
this.onCreate(patch.id, patch.elementType, patch.props || {});
|
|
543
|
+
break;
|
|
544
|
+
case "setProp":
|
|
545
|
+
this.onSetProp(patch.id, patch.name, patch.value);
|
|
546
|
+
break;
|
|
547
|
+
case "setText":
|
|
548
|
+
this.onSetText(patch.id, patch.text);
|
|
549
|
+
break;
|
|
550
|
+
case "insert":
|
|
551
|
+
this.onInsert(patch.parentId, patch.id, patch.beforeId);
|
|
552
|
+
break;
|
|
553
|
+
case "move":
|
|
554
|
+
this.onMove(patch.parentId, patch.id, patch.beforeId);
|
|
555
|
+
break;
|
|
556
|
+
case "remove":
|
|
557
|
+
this.onRemove(patch.id);
|
|
558
|
+
break;
|
|
559
|
+
case "attachEvent":
|
|
560
|
+
this.onAttachEvent(patch.id, patch.eventName);
|
|
561
|
+
break;
|
|
562
|
+
case "detachEvent":
|
|
563
|
+
this.onDetachEvent(patch.id, patch.eventName);
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
class ConsoleRenderer {
|
|
570
|
+
applyPatches(patches) {
|
|
571
|
+
console.group("Patches:");
|
|
572
|
+
for (const patch of patches) {
|
|
573
|
+
console.log(patch);
|
|
574
|
+
}
|
|
575
|
+
console.groupEnd();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/router.ts
|
|
580
|
+
class HypenRouter {
|
|
581
|
+
state;
|
|
582
|
+
subscribers = new Set;
|
|
583
|
+
isInitialized = false;
|
|
584
|
+
isUpdating = false;
|
|
585
|
+
constructor() {
|
|
586
|
+
this.state = createObservableState({
|
|
587
|
+
currentPath: "/",
|
|
588
|
+
params: {},
|
|
589
|
+
query: {},
|
|
590
|
+
previousPath: null
|
|
591
|
+
}, {
|
|
592
|
+
onChange: () => {
|
|
593
|
+
this.notifySubscribers();
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
if (typeof window !== "undefined") {
|
|
597
|
+
this.initializeBrowserSync();
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
initializeBrowserSync() {
|
|
601
|
+
const initialPath = this.getPathFromBrowser();
|
|
602
|
+
this.state.currentPath = initialPath;
|
|
603
|
+
this.state.params = {};
|
|
604
|
+
this.state.query = this.parseQuery();
|
|
605
|
+
window.addEventListener("popstate", () => {
|
|
606
|
+
const newPath = this.getPathFromBrowser();
|
|
607
|
+
this.updatePath(newPath, false);
|
|
608
|
+
});
|
|
609
|
+
window.addEventListener("hashchange", () => {
|
|
610
|
+
if (this.isUpdating)
|
|
611
|
+
return;
|
|
612
|
+
const newPath = this.getPathFromBrowser();
|
|
613
|
+
this.updatePath(newPath, false);
|
|
614
|
+
});
|
|
615
|
+
this.isInitialized = true;
|
|
616
|
+
console.log("Router initialized at:", initialPath);
|
|
617
|
+
}
|
|
618
|
+
getPathFromBrowser() {
|
|
619
|
+
if (typeof window === "undefined")
|
|
620
|
+
return "/";
|
|
621
|
+
const hash = window.location.hash.slice(1);
|
|
622
|
+
if (hash)
|
|
623
|
+
return hash;
|
|
624
|
+
return window.location.pathname;
|
|
625
|
+
}
|
|
626
|
+
parseQuery() {
|
|
627
|
+
if (typeof window === "undefined")
|
|
628
|
+
return {};
|
|
629
|
+
const query = {};
|
|
630
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
631
|
+
searchParams.forEach((value, key) => {
|
|
632
|
+
query[key] = value;
|
|
633
|
+
});
|
|
634
|
+
return query;
|
|
635
|
+
}
|
|
636
|
+
push(path) {
|
|
637
|
+
console.log("Router.push:", path);
|
|
638
|
+
this.updatePath(path, true);
|
|
639
|
+
}
|
|
640
|
+
replace(path) {
|
|
641
|
+
console.log("Router.replace:", path);
|
|
642
|
+
this.updatePath(path, true, true);
|
|
643
|
+
}
|
|
644
|
+
back() {
|
|
645
|
+
console.log("Router.back");
|
|
646
|
+
if (typeof window !== "undefined") {
|
|
647
|
+
window.history.back();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
forward() {
|
|
651
|
+
console.log("Router.forward");
|
|
652
|
+
if (typeof window !== "undefined") {
|
|
653
|
+
window.history.forward();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
updatePath(path, updateBrowser, replace = false) {
|
|
657
|
+
if (this.isUpdating)
|
|
658
|
+
return;
|
|
659
|
+
this.isUpdating = true;
|
|
660
|
+
try {
|
|
661
|
+
const oldPath = this.state.currentPath;
|
|
662
|
+
this.state.previousPath = oldPath;
|
|
663
|
+
this.state.currentPath = path;
|
|
664
|
+
this.state.query = this.parseQuery();
|
|
665
|
+
this.notifySubscribers();
|
|
666
|
+
if (updateBrowser && typeof window !== "undefined") {
|
|
667
|
+
const url = "#" + path;
|
|
668
|
+
if (replace) {
|
|
669
|
+
window.history.replaceState(null, "", url);
|
|
670
|
+
} else {
|
|
671
|
+
window.history.pushState(null, "", url);
|
|
672
|
+
}
|
|
673
|
+
const hashChangeEvent = new HashChangeEvent("hashchange", {
|
|
674
|
+
oldURL: window.location.href.replace(window.location.hash, "#" + oldPath),
|
|
675
|
+
newURL: window.location.href
|
|
676
|
+
});
|
|
677
|
+
window.dispatchEvent(hashChangeEvent);
|
|
678
|
+
}
|
|
679
|
+
} finally {
|
|
680
|
+
this.isUpdating = false;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
getCurrentPath() {
|
|
684
|
+
return this.state.currentPath;
|
|
685
|
+
}
|
|
686
|
+
getParams() {
|
|
687
|
+
return { ...this.state.params };
|
|
688
|
+
}
|
|
689
|
+
getQuery() {
|
|
690
|
+
return { ...this.state.query };
|
|
691
|
+
}
|
|
692
|
+
getState() {
|
|
693
|
+
return getStateSnapshot(this.state);
|
|
694
|
+
}
|
|
695
|
+
matchPath(pattern, path) {
|
|
696
|
+
if (!pattern || typeof pattern !== "string") {
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
if (!path || typeof path !== "string") {
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
if (pattern === path) {
|
|
703
|
+
return {
|
|
704
|
+
params: {},
|
|
705
|
+
query: this.state.query,
|
|
706
|
+
path
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
if (pattern.endsWith("/*")) {
|
|
710
|
+
const prefix = pattern.slice(0, -2);
|
|
711
|
+
if (path === prefix || path.startsWith(prefix + "/")) {
|
|
712
|
+
return {
|
|
713
|
+
params: {},
|
|
714
|
+
query: this.state.query,
|
|
715
|
+
path
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
const paramNames = [];
|
|
721
|
+
const regexPattern = pattern.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name) => {
|
|
722
|
+
paramNames.push(name);
|
|
723
|
+
return "([^/]+)";
|
|
724
|
+
}).replace(/\*/g, ".*");
|
|
725
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
726
|
+
const match = path.match(regex);
|
|
727
|
+
if (!match)
|
|
728
|
+
return null;
|
|
729
|
+
const params = {};
|
|
730
|
+
paramNames.forEach((name, i) => {
|
|
731
|
+
const value = match[i + 1];
|
|
732
|
+
if (value !== undefined) {
|
|
733
|
+
params[name] = decodeURIComponent(value);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
return {
|
|
737
|
+
params,
|
|
738
|
+
query: this.state.query,
|
|
739
|
+
path
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
onNavigate(callback) {
|
|
743
|
+
this.subscribers.add(callback);
|
|
744
|
+
try {
|
|
745
|
+
callback(this.getState());
|
|
746
|
+
} catch (error) {
|
|
747
|
+
console.error("[HypenRouter] Error in route subscriber:", error);
|
|
748
|
+
}
|
|
749
|
+
return () => {
|
|
750
|
+
this.subscribers.delete(callback);
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
notifySubscribers() {
|
|
754
|
+
const routeState = this.getState();
|
|
755
|
+
this.subscribers.forEach((callback) => {
|
|
756
|
+
try {
|
|
757
|
+
callback(routeState);
|
|
758
|
+
} catch (error) {
|
|
759
|
+
console.error("[HypenRouter] Error in route subscriber:", error);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
isActive(pattern) {
|
|
764
|
+
return this.matchPath(pattern, this.state.currentPath) !== null;
|
|
765
|
+
}
|
|
766
|
+
buildUrl(path, query) {
|
|
767
|
+
if (!query || Object.keys(query).length === 0) {
|
|
768
|
+
return path;
|
|
769
|
+
}
|
|
770
|
+
const queryString = new URLSearchParams(query).toString();
|
|
771
|
+
return `${path}?${queryString}`;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/events.ts
|
|
776
|
+
class TypedEventEmitter {
|
|
777
|
+
eventBus = new Map;
|
|
778
|
+
emit(event, payload) {
|
|
779
|
+
const handlers = this.eventBus.get(event);
|
|
780
|
+
if (!handlers || handlers.size === 0) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
handlers.forEach((handler) => {
|
|
784
|
+
try {
|
|
785
|
+
handler(payload);
|
|
786
|
+
} catch (error) {
|
|
787
|
+
console.error(`Error in event handler for "${String(event)}":`, error);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
on(event, handler) {
|
|
792
|
+
if (!this.eventBus.has(event)) {
|
|
793
|
+
this.eventBus.set(event, new Set);
|
|
794
|
+
}
|
|
795
|
+
const handlers = this.eventBus.get(event);
|
|
796
|
+
handlers.add(handler);
|
|
797
|
+
return () => {
|
|
798
|
+
handlers.delete(handler);
|
|
799
|
+
if (handlers.size === 0) {
|
|
800
|
+
this.eventBus.delete(event);
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
once(event, handler) {
|
|
805
|
+
const wrappedHandler = (payload) => {
|
|
806
|
+
handler(payload);
|
|
807
|
+
unsubscribe();
|
|
808
|
+
};
|
|
809
|
+
const unsubscribe = this.on(event, wrappedHandler);
|
|
810
|
+
return unsubscribe;
|
|
811
|
+
}
|
|
812
|
+
off(event, handler) {
|
|
813
|
+
const handlers = this.eventBus.get(event);
|
|
814
|
+
if (handlers) {
|
|
815
|
+
handlers.delete(handler);
|
|
816
|
+
if (handlers.size === 0) {
|
|
817
|
+
this.eventBus.delete(event);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
removeAllListeners(event) {
|
|
822
|
+
this.eventBus.delete(event);
|
|
823
|
+
}
|
|
824
|
+
clearAll() {
|
|
825
|
+
this.eventBus.clear();
|
|
826
|
+
}
|
|
827
|
+
listenerCount(event) {
|
|
828
|
+
return this.eventBus.get(event)?.size ?? 0;
|
|
829
|
+
}
|
|
830
|
+
eventNames() {
|
|
831
|
+
return Array.from(this.eventBus.keys());
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
function createEventEmitter() {
|
|
835
|
+
return new TypedEventEmitter;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/context.ts
|
|
839
|
+
class HypenGlobalContext {
|
|
840
|
+
modules = new Map;
|
|
841
|
+
typedEvents;
|
|
842
|
+
legacyEventBus = new Map;
|
|
843
|
+
constructor() {
|
|
844
|
+
this.typedEvents = new TypedEventEmitter;
|
|
845
|
+
}
|
|
846
|
+
get events() {
|
|
847
|
+
return this.typedEvents;
|
|
848
|
+
}
|
|
849
|
+
registerModule(id, instance) {
|
|
850
|
+
if (this.modules.has(id)) {
|
|
851
|
+
console.warn(`Module "${id}" is already registered. Overwriting.`);
|
|
852
|
+
}
|
|
853
|
+
this.modules.set(id, instance);
|
|
854
|
+
console.log(`Registered module: ${id}`);
|
|
855
|
+
}
|
|
856
|
+
unregisterModule(id) {
|
|
857
|
+
this.modules.delete(id);
|
|
858
|
+
console.log(`Unregistered module: ${id}`);
|
|
859
|
+
}
|
|
860
|
+
getModule(id) {
|
|
861
|
+
const module = this.modules.get(id);
|
|
862
|
+
if (!module) {
|
|
863
|
+
throw new Error(`Module "${id}" not found. Available modules: ${Array.from(this.modules.keys()).join(", ")}`);
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
state: module.getLiveState(),
|
|
867
|
+
setState: (patch) => module.updateState(patch),
|
|
868
|
+
getState: () => module.getState()
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
hasModule(id) {
|
|
872
|
+
return this.modules.has(id);
|
|
873
|
+
}
|
|
874
|
+
getModuleIds() {
|
|
875
|
+
return Array.from(this.modules.keys());
|
|
876
|
+
}
|
|
877
|
+
getGlobalState() {
|
|
878
|
+
const state = {};
|
|
879
|
+
this.modules.forEach((module, id) => {
|
|
880
|
+
state[id] = module.getState();
|
|
881
|
+
});
|
|
882
|
+
return state;
|
|
883
|
+
}
|
|
884
|
+
emit(event, payload) {
|
|
885
|
+
const handlers = this.legacyEventBus.get(event);
|
|
886
|
+
if (!handlers || handlers.size === 0) {
|
|
887
|
+
console.log(`Event "${event}" emitted but no listeners`);
|
|
888
|
+
} else {
|
|
889
|
+
console.log(`Emitting event: ${event}`, payload);
|
|
890
|
+
handlers.forEach((handler) => {
|
|
891
|
+
try {
|
|
892
|
+
handler(payload);
|
|
893
|
+
} catch (error) {
|
|
894
|
+
console.error(`Error in event handler for "${event}":`, error);
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
if (this.typedEvents.listenerCount(event) > 0) {
|
|
899
|
+
this.typedEvents.emit(event, payload);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
on(event, handler) {
|
|
903
|
+
if (!this.legacyEventBus.has(event)) {
|
|
904
|
+
this.legacyEventBus.set(event, new Set);
|
|
905
|
+
}
|
|
906
|
+
const handlers = this.legacyEventBus.get(event);
|
|
907
|
+
handlers.add(handler);
|
|
908
|
+
console.log(`Listening to event: ${event}`);
|
|
909
|
+
return () => {
|
|
910
|
+
handlers.delete(handler);
|
|
911
|
+
if (handlers.size === 0) {
|
|
912
|
+
this.legacyEventBus.delete(event);
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
off(event, handler) {
|
|
917
|
+
const handlers = this.legacyEventBus.get(event);
|
|
918
|
+
if (handlers) {
|
|
919
|
+
handlers.delete(handler);
|
|
920
|
+
if (handlers.size === 0) {
|
|
921
|
+
this.legacyEventBus.delete(event);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
clearEvent(event) {
|
|
926
|
+
this.legacyEventBus.delete(event);
|
|
927
|
+
}
|
|
928
|
+
clearAllEvents() {
|
|
929
|
+
this.legacyEventBus.clear();
|
|
930
|
+
}
|
|
931
|
+
debug() {
|
|
932
|
+
return {
|
|
933
|
+
modules: this.getModuleIds(),
|
|
934
|
+
events: Array.from(this.legacyEventBus.keys()),
|
|
935
|
+
typedEvents: this.typedEvents.eventNames(),
|
|
936
|
+
state: this.getGlobalState()
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// src/remote/client.ts
|
|
942
|
+
class RemoteEngine {
|
|
943
|
+
ws = null;
|
|
944
|
+
url;
|
|
945
|
+
state = "disconnected";
|
|
946
|
+
options;
|
|
947
|
+
reconnectAttempts = 0;
|
|
948
|
+
reconnectTimer = null;
|
|
949
|
+
patchCallbacks = [];
|
|
950
|
+
stateCallbacks = [];
|
|
951
|
+
connectionCallbacks = [];
|
|
952
|
+
disconnectionCallbacks = [];
|
|
953
|
+
errorCallbacks = [];
|
|
954
|
+
currentState = null;
|
|
955
|
+
currentRevision = 0;
|
|
956
|
+
moduleName = "";
|
|
957
|
+
constructor(url, options = {}) {
|
|
958
|
+
this.url = url;
|
|
959
|
+
this.options = {
|
|
960
|
+
autoReconnect: options.autoReconnect ?? true,
|
|
961
|
+
reconnectInterval: options.reconnectInterval ?? 3000,
|
|
962
|
+
maxReconnectAttempts: options.maxReconnectAttempts ?? 10
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
async connect() {
|
|
966
|
+
if (this.state === "connected" || this.state === "connecting") {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
this.state = "connecting";
|
|
970
|
+
return new Promise((resolve, reject) => {
|
|
971
|
+
try {
|
|
972
|
+
this.ws = new WebSocket(this.url);
|
|
973
|
+
this.ws.onopen = () => {
|
|
974
|
+
this.state = "connected";
|
|
975
|
+
this.reconnectAttempts = 0;
|
|
976
|
+
this.connectionCallbacks.forEach((cb) => cb());
|
|
977
|
+
resolve();
|
|
978
|
+
};
|
|
979
|
+
this.ws.onmessage = (event) => {
|
|
980
|
+
this.handleMessage(event.data);
|
|
981
|
+
};
|
|
982
|
+
this.ws.onerror = () => {
|
|
983
|
+
this.state = "error";
|
|
984
|
+
const error = new Error("WebSocket error");
|
|
985
|
+
this.errorCallbacks.forEach((cb) => cb(error));
|
|
986
|
+
reject(error);
|
|
987
|
+
};
|
|
988
|
+
this.ws.onclose = () => {
|
|
989
|
+
this.state = "disconnected";
|
|
990
|
+
this.disconnectionCallbacks.forEach((cb) => cb());
|
|
991
|
+
this.attemptReconnect();
|
|
992
|
+
};
|
|
993
|
+
} catch (error) {
|
|
994
|
+
this.state = "error";
|
|
995
|
+
reject(error);
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
disconnect() {
|
|
1000
|
+
if (this.reconnectTimer) {
|
|
1001
|
+
clearTimeout(this.reconnectTimer);
|
|
1002
|
+
this.reconnectTimer = null;
|
|
1003
|
+
}
|
|
1004
|
+
if (this.ws) {
|
|
1005
|
+
this.ws.close();
|
|
1006
|
+
this.ws = null;
|
|
1007
|
+
}
|
|
1008
|
+
this.state = "disconnected";
|
|
1009
|
+
}
|
|
1010
|
+
dispatchAction(action, payload) {
|
|
1011
|
+
if (this.state !== "connected" || !this.ws) {
|
|
1012
|
+
console.warn("Cannot dispatch action: not connected");
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const message = {
|
|
1016
|
+
type: "dispatchAction",
|
|
1017
|
+
module: this.moduleName,
|
|
1018
|
+
action,
|
|
1019
|
+
payload
|
|
1020
|
+
};
|
|
1021
|
+
this.ws.send(JSON.stringify(message));
|
|
1022
|
+
}
|
|
1023
|
+
onPatches(callback) {
|
|
1024
|
+
this.patchCallbacks.push(callback);
|
|
1025
|
+
return this;
|
|
1026
|
+
}
|
|
1027
|
+
onStateUpdate(callback) {
|
|
1028
|
+
this.stateCallbacks.push(callback);
|
|
1029
|
+
return this;
|
|
1030
|
+
}
|
|
1031
|
+
onConnect(callback) {
|
|
1032
|
+
this.connectionCallbacks.push(callback);
|
|
1033
|
+
return this;
|
|
1034
|
+
}
|
|
1035
|
+
onDisconnect(callback) {
|
|
1036
|
+
this.disconnectionCallbacks.push(callback);
|
|
1037
|
+
return this;
|
|
1038
|
+
}
|
|
1039
|
+
onError(callback) {
|
|
1040
|
+
this.errorCallbacks.push(callback);
|
|
1041
|
+
return this;
|
|
1042
|
+
}
|
|
1043
|
+
getConnectionState() {
|
|
1044
|
+
return this.state;
|
|
1045
|
+
}
|
|
1046
|
+
getCurrentState() {
|
|
1047
|
+
return this.currentState;
|
|
1048
|
+
}
|
|
1049
|
+
getRevision() {
|
|
1050
|
+
return this.currentRevision;
|
|
1051
|
+
}
|
|
1052
|
+
handleMessage(data) {
|
|
1053
|
+
try {
|
|
1054
|
+
const message = JSON.parse(data);
|
|
1055
|
+
switch (message.type) {
|
|
1056
|
+
case "initialTree":
|
|
1057
|
+
this.handleInitialTree(message);
|
|
1058
|
+
break;
|
|
1059
|
+
case "patch":
|
|
1060
|
+
this.handlePatch(message);
|
|
1061
|
+
break;
|
|
1062
|
+
case "stateUpdate":
|
|
1063
|
+
this.currentState = message.state;
|
|
1064
|
+
this.stateCallbacks.forEach((cb) => cb(message.state));
|
|
1065
|
+
break;
|
|
1066
|
+
}
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
console.error("Error handling remote message:", error);
|
|
1069
|
+
this.errorCallbacks.forEach((cb) => cb(error instanceof Error ? error : new Error(String(error))));
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
handleInitialTree(message) {
|
|
1073
|
+
this.moduleName = message.module;
|
|
1074
|
+
this.currentState = message.state;
|
|
1075
|
+
this.currentRevision = message.revision;
|
|
1076
|
+
if (message.patches.length > 0) {
|
|
1077
|
+
this.patchCallbacks.forEach((cb) => cb(message.patches));
|
|
1078
|
+
}
|
|
1079
|
+
this.stateCallbacks.forEach((cb) => cb(message.state));
|
|
1080
|
+
}
|
|
1081
|
+
handlePatch(message) {
|
|
1082
|
+
if (message.revision <= this.currentRevision) {
|
|
1083
|
+
console.warn(`Out of order patch: expected > ${this.currentRevision}, got ${message.revision}`);
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
this.currentRevision = message.revision;
|
|
1087
|
+
if (message.patches.length > 0) {
|
|
1088
|
+
this.patchCallbacks.forEach((cb) => cb(message.patches));
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
attemptReconnect() {
|
|
1092
|
+
if (!this.options.autoReconnect) {
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
|
|
1096
|
+
console.error("Max reconnection attempts reached");
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
this.reconnectAttempts++;
|
|
1100
|
+
console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.options.maxReconnectAttempts})...`);
|
|
1101
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1102
|
+
this.connect().catch((error) => {
|
|
1103
|
+
console.error("Reconnection failed:", error);
|
|
1104
|
+
});
|
|
1105
|
+
}, this.options.reconnectInterval);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// src/index.browser.ts
|
|
1110
|
+
init_app();
|
|
33
1111
|
export {
|
|
34
1112
|
getStateSnapshot,
|
|
35
1113
|
createObservableState,
|
|
@@ -48,4 +1126,4 @@ export {
|
|
|
48
1126
|
BaseRenderer
|
|
49
1127
|
};
|
|
50
1128
|
|
|
51
|
-
//# debugId=
|
|
1129
|
+
//# debugId=8A28A7B95ACB037064756E2164756E21
|