@hypen-space/core 0.3.9 → 0.3.11

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 (80) hide show
  1. package/dist/{src/app.js → app.js} +15 -2
  2. package/dist/{src/app.js.map → app.js.map} +3 -3
  3. package/dist/{src/components → components}/builtin.js +22 -7
  4. package/dist/{src/components → components}/builtin.js.map +4 -4
  5. package/dist/context.js +387 -0
  6. package/dist/context.js.map +12 -0
  7. package/dist/{src/discovery.js → discovery.js} +18 -4
  8. package/dist/{src/discovery.js.map → discovery.js.map} +4 -4
  9. package/dist/disposable.js +377 -0
  10. package/dist/disposable.js.map +11 -0
  11. package/dist/engine.browser.js +347 -0
  12. package/dist/engine.browser.js.map +11 -0
  13. package/dist/engine.js +320 -0
  14. package/dist/engine.js.map +11 -0
  15. package/dist/events.js +282 -0
  16. package/dist/events.js.map +11 -0
  17. package/dist/{src/index.browser.js → index.browser.js} +278 -229
  18. package/dist/index.browser.js.map +22 -0
  19. package/dist/{src/index.js → index.js} +1377 -1330
  20. package/dist/index.js.map +31 -0
  21. package/dist/loader.js +286 -0
  22. package/dist/loader.js.map +11 -0
  23. package/dist/plugin.js +334 -0
  24. package/dist/plugin.js.map +11 -0
  25. package/dist/{src/remote → remote}/client.js +215 -11
  26. package/dist/remote/client.js.map +14 -0
  27. package/dist/{src/remote → remote}/index.js +234 -210
  28. package/dist/remote/index.js.map +19 -0
  29. package/dist/remote/server.js +1442 -0
  30. package/dist/remote/server.js.map +16 -0
  31. package/dist/renderer.js +264 -0
  32. package/dist/renderer.js.map +11 -0
  33. package/dist/{src/router.js → router.js} +209 -8
  34. package/dist/router.js.map +12 -0
  35. package/package.json +69 -69
  36. package/src/components/builtin.ts +9 -6
  37. package/src/context.ts +10 -7
  38. package/src/discovery.ts +3 -2
  39. package/src/disposable.ts +6 -2
  40. package/src/engine.browser.ts +30 -11
  41. package/src/engine.ts +6 -3
  42. package/src/events.ts +5 -1
  43. package/src/index.browser.ts +20 -0
  44. package/src/index.ts +2 -0
  45. package/src/loader.ts +8 -8
  46. package/src/logger.ts +28 -0
  47. package/src/plugin.ts +2 -1
  48. package/src/remote/client.ts +8 -9
  49. package/src/remote/server.ts +6 -3
  50. package/src/renderer.ts +4 -5
  51. package/src/router.ts +10 -7
  52. package/wasm-browser/hypen_engine_bg.wasm +0 -0
  53. package/wasm-browser/package.json +1 -1
  54. package/wasm-node/hypen_engine_bg.wasm +0 -0
  55. package/wasm-node/package.json +5 -3
  56. package/dist/src/context.js +0 -182
  57. package/dist/src/context.js.map +0 -11
  58. package/dist/src/engine.browser.js +0 -137
  59. package/dist/src/engine.browser.js.map +0 -10
  60. package/dist/src/engine.js +0 -119
  61. package/dist/src/engine.js.map +0 -10
  62. package/dist/src/events.js +0 -80
  63. package/dist/src/events.js.map +0 -10
  64. package/dist/src/index.browser.js.map +0 -22
  65. package/dist/src/index.js.map +0 -31
  66. package/dist/src/loader.js +0 -85
  67. package/dist/src/loader.js.map +0 -10
  68. package/dist/src/plugin.js +0 -134
  69. package/dist/src/plugin.js.map +0 -10
  70. package/dist/src/remote/client.js.map +0 -13
  71. package/dist/src/remote/index.js.map +0 -19
  72. package/dist/src/renderer.js +0 -66
  73. package/dist/src/renderer.js.map +0 -10
  74. package/dist/src/router.js.map +0 -11
  75. /package/dist/{src/remote → remote}/types.js +0 -0
  76. /package/dist/{src/remote → remote}/types.js.map +0 -0
  77. /package/dist/{src/resolver.js → resolver.js} +0 -0
  78. /package/dist/{src/resolver.js.map → resolver.js.map} +0 -0
  79. /package/dist/{src/state.js → state.js} +0 -0
  80. /package/dist/{src/state.js.map → state.js.map} +0 -0
@@ -0,0 +1,1442 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true,
7
+ configurable: true,
8
+ set: (newValue) => all[name] = () => newValue
9
+ });
10
+ };
11
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
+
13
+ // src/logger.ts
14
+ function isProduction() {
15
+ if (typeof process !== "undefined" && process.env) {
16
+ return false;
17
+ }
18
+ return false;
19
+ }
20
+ function setLogLevel(level) {
21
+ config.level = level;
22
+ }
23
+ function getLogLevel() {
24
+ return config.level;
25
+ }
26
+ function configureLogger(options) {
27
+ config = { ...config, ...options };
28
+ }
29
+ function enableLogging() {
30
+ config.level = "debug";
31
+ }
32
+ function disableLogging() {
33
+ config.level = "none";
34
+ }
35
+ function setDebugMode(enabled) {
36
+ config.level = enabled ? "debug" : "error";
37
+ }
38
+ function isDebugMode() {
39
+ return config.level === "debug";
40
+ }
41
+ function shouldLog(level) {
42
+ return LOG_LEVEL_ORDER[level] >= LOG_LEVEL_ORDER[config.level];
43
+ }
44
+ function formatTag(tag, level) {
45
+ const timestamp = config.timestamps ? `${new Date().toISOString()} ` : "";
46
+ if (config.colors && level !== "none") {
47
+ const color = LOG_LEVEL_COLORS[level];
48
+ return `${timestamp}${color}[${tag}]${RESET_COLOR}`;
49
+ }
50
+ return `${timestamp}[${tag}]`;
51
+ }
52
+
53
+ class Logger {
54
+ tag;
55
+ constructor(tag) {
56
+ this.tag = tag;
57
+ }
58
+ debug(...args) {
59
+ if (!shouldLog("debug"))
60
+ return;
61
+ if (config.handler) {
62
+ config.handler.debug(this.tag, ...args);
63
+ } else {
64
+ console.log(formatTag(this.tag, "debug"), ...args);
65
+ }
66
+ }
67
+ info(...args) {
68
+ if (!shouldLog("info"))
69
+ return;
70
+ if (config.handler) {
71
+ config.handler.info(this.tag, ...args);
72
+ } else {
73
+ console.info(formatTag(this.tag, "info"), ...args);
74
+ }
75
+ }
76
+ warn(...args) {
77
+ if (!shouldLog("warn"))
78
+ return;
79
+ if (config.handler) {
80
+ config.handler.warn(this.tag, ...args);
81
+ } else {
82
+ console.warn(formatTag(this.tag, "warn"), ...args);
83
+ }
84
+ }
85
+ error(...args) {
86
+ if (!shouldLog("error"))
87
+ return;
88
+ if (config.handler) {
89
+ config.handler.error(this.tag, ...args);
90
+ } else {
91
+ console.error(formatTag(this.tag, "error"), ...args);
92
+ }
93
+ }
94
+ time(label, fn) {
95
+ if (!shouldLog("debug")) {
96
+ return fn();
97
+ }
98
+ const start = performance.now();
99
+ try {
100
+ return fn();
101
+ } finally {
102
+ const duration = performance.now() - start;
103
+ this.debug(`${label}: ${duration.toFixed(2)}ms`);
104
+ }
105
+ }
106
+ async timeAsync(label, fn) {
107
+ if (!shouldLog("debug")) {
108
+ return fn();
109
+ }
110
+ const start = performance.now();
111
+ try {
112
+ return await fn();
113
+ } finally {
114
+ const duration = performance.now() - start;
115
+ this.debug(`${label}: ${duration.toFixed(2)}ms`);
116
+ }
117
+ }
118
+ child(subTag) {
119
+ return new Logger(`${this.tag}:${subTag}`);
120
+ }
121
+ debugIf(condition, ...args) {
122
+ if (condition)
123
+ this.debug(...args);
124
+ }
125
+ warnIf(condition, ...args) {
126
+ if (condition)
127
+ this.warn(...args);
128
+ }
129
+ errorIf(condition, ...args) {
130
+ if (condition)
131
+ this.error(...args);
132
+ }
133
+ loggedOnce = new Set;
134
+ warnOnce(key, ...args) {
135
+ if (this.loggedOnce.has(key))
136
+ return;
137
+ this.loggedOnce.add(key);
138
+ this.warn(...args);
139
+ }
140
+ debugOnce(key, ...args) {
141
+ if (this.loggedOnce.has(key))
142
+ return;
143
+ this.loggedOnce.add(key);
144
+ this.debug(...args);
145
+ }
146
+ }
147
+ function createLogger(tag) {
148
+ return new Logger(tag);
149
+ }
150
+ var LOG_LEVEL_ORDER, LOG_LEVEL_COLORS, RESET_COLOR = "\x1B[0m", config, logger, log, frameworkLoggers;
151
+ var init_logger = __esm(() => {
152
+ LOG_LEVEL_ORDER = {
153
+ debug: 0,
154
+ info: 1,
155
+ warn: 2,
156
+ error: 3,
157
+ none: 4
158
+ };
159
+ LOG_LEVEL_COLORS = {
160
+ debug: "\x1B[36m",
161
+ info: "\x1B[32m",
162
+ warn: "\x1B[33m",
163
+ error: "\x1B[31m"
164
+ };
165
+ config = {
166
+ level: isProduction() ? "error" : "debug",
167
+ colors: true,
168
+ timestamps: false
169
+ };
170
+ logger = createLogger("Hypen");
171
+ log = {
172
+ debug: (tag, ...args) => {
173
+ if (!shouldLog("debug"))
174
+ return;
175
+ console.log(formatTag(tag, "debug"), ...args);
176
+ },
177
+ info: (tag, ...args) => {
178
+ if (!shouldLog("info"))
179
+ return;
180
+ console.info(formatTag(tag, "info"), ...args);
181
+ },
182
+ warn: (tag, ...args) => {
183
+ if (!shouldLog("warn"))
184
+ return;
185
+ console.warn(formatTag(tag, "warn"), ...args);
186
+ },
187
+ error: (tag, ...args) => {
188
+ if (!shouldLog("error"))
189
+ return;
190
+ console.error(formatTag(tag, "error"), ...args);
191
+ }
192
+ };
193
+ frameworkLoggers = {
194
+ hypen: createLogger("Hypen"),
195
+ engine: createLogger("Engine"),
196
+ router: createLogger("Router"),
197
+ state: createLogger("State"),
198
+ events: createLogger("Events"),
199
+ remote: createLogger("Remote"),
200
+ renderer: createLogger("Renderer"),
201
+ module: createLogger("Module"),
202
+ lifecycle: createLogger("Lifecycle"),
203
+ loader: createLogger("Loader"),
204
+ context: createLogger("Context"),
205
+ discovery: createLogger("Discovery"),
206
+ plugin: createLogger("Plugin"),
207
+ canvas: createLogger("Canvas"),
208
+ debug: createLogger("Debug")
209
+ };
210
+ });
211
+
212
+ // src/state.ts
213
+ function deepClone(obj) {
214
+ if (obj === null || typeof obj !== "object") {
215
+ return obj;
216
+ }
217
+ if (typeof obj === "function") {
218
+ return obj;
219
+ }
220
+ if (typeof obj.__getSnapshot === "function") {
221
+ return obj.__getSnapshot();
222
+ }
223
+ if (obj instanceof WeakMap || obj instanceof WeakSet) {
224
+ return obj;
225
+ }
226
+ const visited = new WeakMap;
227
+ function cloneInternal(value) {
228
+ if (value === null || typeof value !== "object") {
229
+ return value;
230
+ }
231
+ if (typeof value === "function") {
232
+ return value;
233
+ }
234
+ if (visited.has(value)) {
235
+ return visited.get(value);
236
+ }
237
+ if (value instanceof WeakMap || value instanceof WeakSet) {
238
+ return value;
239
+ }
240
+ if (value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
241
+ try {
242
+ return structuredClone(value);
243
+ } catch {}
244
+ }
245
+ if (Array.isArray(value)) {
246
+ const arrClone = [];
247
+ visited.set(value, arrClone);
248
+ for (let i = 0;i < value.length; i++) {
249
+ arrClone[i] = cloneInternal(value[i]);
250
+ }
251
+ return arrClone;
252
+ }
253
+ const objClone = {};
254
+ visited.set(value, objClone);
255
+ for (const key in value) {
256
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
257
+ objClone[key] = cloneInternal(value[key]);
258
+ }
259
+ }
260
+ const symbolKeys = Object.getOwnPropertySymbols(value);
261
+ for (const sym of symbolKeys) {
262
+ objClone[sym] = cloneInternal(value[sym]);
263
+ }
264
+ return objClone;
265
+ }
266
+ return cloneInternal(obj);
267
+ }
268
+ function diffState(oldState, newState, basePath = "") {
269
+ const paths = [];
270
+ const newValues = {};
271
+ function diff(oldVal, newVal, path) {
272
+ if (oldVal === newVal)
273
+ return;
274
+ if (typeof oldVal !== "object" || typeof newVal !== "object" || oldVal === null || newVal === null) {
275
+ if (oldVal !== newVal) {
276
+ paths.push(path);
277
+ newValues[path] = newVal;
278
+ }
279
+ return;
280
+ }
281
+ if (Array.isArray(oldVal) || Array.isArray(newVal)) {
282
+ if (!Array.isArray(oldVal) || !Array.isArray(newVal) || oldVal.length !== newVal.length) {
283
+ paths.push(path);
284
+ newValues[path] = newVal;
285
+ return;
286
+ }
287
+ for (let i = 0;i < newVal.length; i++) {
288
+ const itemPath = path ? `${path}.${i}` : `${i}`;
289
+ diff(oldVal[i], newVal[i], itemPath);
290
+ }
291
+ return;
292
+ }
293
+ const oldKeys = new Set(Object.keys(oldVal));
294
+ const newKeys = new Set(Object.keys(newVal));
295
+ for (const key of newKeys) {
296
+ const propPath = path ? `${path}.${key}` : key;
297
+ if (!oldKeys.has(key)) {
298
+ paths.push(propPath);
299
+ newValues[propPath] = newVal[key];
300
+ } else {
301
+ diff(oldVal[key], newVal[key], propPath);
302
+ }
303
+ }
304
+ for (const key of oldKeys) {
305
+ if (!newKeys.has(key)) {
306
+ const propPath = path ? `${path}.${key}` : key;
307
+ paths.push(propPath);
308
+ newValues[propPath] = undefined;
309
+ }
310
+ }
311
+ }
312
+ diff(oldState, newState, basePath);
313
+ return { paths, newValues };
314
+ }
315
+ function createObservableState(initialState, options) {
316
+ const opts = options || { onChange: () => {} };
317
+ if (initialState === null || initialState === undefined) {
318
+ initialState = {};
319
+ }
320
+ if (initialState instanceof Number || initialState instanceof String || initialState instanceof Boolean) {
321
+ throw new TypeError("Cannot create observable state from primitive wrapper objects (Number, String, Boolean). " + "Use plain primitives or regular objects instead.");
322
+ }
323
+ initialState = deepClone(initialState);
324
+ let lastSnapshot = deepClone(initialState);
325
+ const pathPrefix = opts.pathPrefix || "";
326
+ let batchDepth = 0;
327
+ let pendingChange = null;
328
+ function notifyChange() {
329
+ if (batchDepth > 0)
330
+ return;
331
+ const change = diffState(lastSnapshot, state, pathPrefix);
332
+ if (change.paths.length > 0) {
333
+ lastSnapshot = deepClone(state);
334
+ if (pendingChange) {
335
+ change.paths.push(...pendingChange.paths);
336
+ Object.assign(change.newValues, pendingChange.newValues);
337
+ pendingChange = null;
338
+ }
339
+ opts.onChange(change);
340
+ }
341
+ }
342
+ let notificationPending = false;
343
+ function scheduleBatch() {
344
+ if (batchDepth === 0) {
345
+ if (!notificationPending) {
346
+ notificationPending = true;
347
+ queueMicrotask(() => {
348
+ notificationPending = false;
349
+ if (batchDepth === 0) {
350
+ notifyChange();
351
+ }
352
+ });
353
+ }
354
+ }
355
+ }
356
+ const proxyCache = new WeakMap;
357
+ function createProxy(target, basePath) {
358
+ const cached = proxyCache.get(target);
359
+ if (cached)
360
+ return cached;
361
+ const proxy = new Proxy(target, {
362
+ get(obj, prop) {
363
+ if (prop === IS_PROXY)
364
+ return true;
365
+ if (prop === RAW_TARGET)
366
+ return obj;
367
+ if (prop === "__beginBatch") {
368
+ return () => {
369
+ batchDepth++;
370
+ };
371
+ }
372
+ if (prop === "__endBatch") {
373
+ return () => {
374
+ batchDepth--;
375
+ if (batchDepth === 0) {
376
+ notifyChange();
377
+ }
378
+ };
379
+ }
380
+ if (prop === "__getSnapshot") {
381
+ return () => deepClone(obj);
382
+ }
383
+ const value = obj[prop];
384
+ if (value && typeof value === "object") {
385
+ if (value[IS_PROXY]) {
386
+ return value;
387
+ }
388
+ if (value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet) {
389
+ return value;
390
+ }
391
+ const cachedNested = proxyCache.get(value);
392
+ if (cachedNested) {
393
+ return cachedNested;
394
+ }
395
+ const nestedProxy = createProxy(value, basePath ? `${basePath}.${String(prop)}` : String(prop));
396
+ return nestedProxy;
397
+ }
398
+ return value;
399
+ },
400
+ set(obj, prop, value) {
401
+ const oldValue = obj[prop];
402
+ if (value && typeof value === "object" && value[IS_PROXY]) {
403
+ value = value[RAW_TARGET];
404
+ }
405
+ obj[prop] = value;
406
+ if (oldValue !== value) {
407
+ scheduleBatch();
408
+ }
409
+ return true;
410
+ },
411
+ deleteProperty(obj, prop) {
412
+ if (prop in obj) {
413
+ delete obj[prop];
414
+ scheduleBatch();
415
+ }
416
+ return true;
417
+ }
418
+ });
419
+ proxyCache.set(target, proxy);
420
+ return proxy;
421
+ }
422
+ const state = createProxy(initialState, pathPrefix);
423
+ return state;
424
+ }
425
+ function batchStateUpdates(state, fn) {
426
+ const s = state;
427
+ if (s.__beginBatch && s.__endBatch) {
428
+ s.__beginBatch();
429
+ try {
430
+ fn();
431
+ } finally {
432
+ s.__endBatch();
433
+ }
434
+ } else {
435
+ fn();
436
+ }
437
+ }
438
+ function getStateSnapshot(state) {
439
+ const s = state;
440
+ if (s.__getSnapshot) {
441
+ return s.__getSnapshot();
442
+ }
443
+ return deepClone(state);
444
+ }
445
+ function isStateProxy(value) {
446
+ return value !== null && typeof value === "object" && value[IS_PROXY] === true;
447
+ }
448
+ function unwrapProxy(value) {
449
+ if (value !== null && typeof value === "object" && value[IS_PROXY]) {
450
+ return value[RAW_TARGET];
451
+ }
452
+ return value;
453
+ }
454
+ var IS_PROXY, RAW_TARGET;
455
+ var init_state = __esm(() => {
456
+ IS_PROXY = Symbol.for("hypen.isProxy");
457
+ RAW_TARGET = Symbol.for("hypen.rawTarget");
458
+ });
459
+
460
+ // src/result.ts
461
+ function Ok(value) {
462
+ return { ok: true, value };
463
+ }
464
+ function Err(error) {
465
+ return { ok: false, error };
466
+ }
467
+ function isOk(result) {
468
+ return result.ok;
469
+ }
470
+ function isErr(result) {
471
+ return !result.ok;
472
+ }
473
+ async function fromPromise(promise, mapError) {
474
+ try {
475
+ const value = await promise;
476
+ return Ok(value);
477
+ } catch (e) {
478
+ if (mapError) {
479
+ return Err(mapError(e));
480
+ }
481
+ return Err(e);
482
+ }
483
+ }
484
+ function fromTry(fn, mapError) {
485
+ try {
486
+ return Ok(fn());
487
+ } catch (e) {
488
+ if (mapError) {
489
+ return Err(mapError(e));
490
+ }
491
+ return Err(e);
492
+ }
493
+ }
494
+ function map(result, fn) {
495
+ if (result.ok) {
496
+ return Ok(fn(result.value));
497
+ }
498
+ return result;
499
+ }
500
+ function mapErr(result, fn) {
501
+ if (!result.ok) {
502
+ return Err(fn(result.error));
503
+ }
504
+ return result;
505
+ }
506
+ function flatMap(result, fn) {
507
+ if (result.ok) {
508
+ return fn(result.value);
509
+ }
510
+ return result;
511
+ }
512
+ function unwrap(result) {
513
+ if (result.ok) {
514
+ return result.value;
515
+ }
516
+ throw result.error;
517
+ }
518
+ function unwrapOr(result, defaultValue) {
519
+ if (result.ok) {
520
+ return result.value;
521
+ }
522
+ return defaultValue;
523
+ }
524
+ function unwrapOrElse(result, fn) {
525
+ if (result.ok) {
526
+ return result.value;
527
+ }
528
+ return fn(result.error);
529
+ }
530
+ function match(result, handlers) {
531
+ if (result.ok) {
532
+ return handlers.ok(result.value);
533
+ }
534
+ return handlers.err(result.error);
535
+ }
536
+ function all(results) {
537
+ const values = [];
538
+ for (const result of results) {
539
+ if (!result.ok) {
540
+ return result;
541
+ }
542
+ values.push(result.value);
543
+ }
544
+ return Ok(values);
545
+ }
546
+ var HypenError, ActionError, ConnectionError, StateError;
547
+ var init_result = __esm(() => {
548
+ HypenError = class HypenError extends Error {
549
+ code;
550
+ context;
551
+ cause;
552
+ constructor(code, message, options) {
553
+ super(message);
554
+ this.name = "HypenError";
555
+ this.code = code;
556
+ this.context = options?.context;
557
+ this.cause = options?.cause;
558
+ Object.setPrototypeOf(this, new.target.prototype);
559
+ }
560
+ };
561
+ ActionError = class ActionError extends HypenError {
562
+ actionName;
563
+ constructor(actionName, cause) {
564
+ super("ACTION_ERROR", `Action handler "${actionName}" failed: ${cause instanceof Error ? cause.message : String(cause)}`, {
565
+ context: { actionName },
566
+ cause: cause instanceof Error ? cause : undefined
567
+ });
568
+ this.name = "ActionError";
569
+ this.actionName = actionName;
570
+ }
571
+ };
572
+ ConnectionError = class ConnectionError extends HypenError {
573
+ url;
574
+ attempt;
575
+ constructor(url, cause, attempt) {
576
+ super("CONNECTION_ERROR", `Connection to "${url}" failed${attempt ? ` (attempt ${attempt})` : ""}: ${cause instanceof Error ? cause.message : String(cause)}`, {
577
+ context: { url, attempt },
578
+ cause: cause instanceof Error ? cause : undefined
579
+ });
580
+ this.name = "ConnectionError";
581
+ this.url = url;
582
+ this.attempt = attempt;
583
+ }
584
+ };
585
+ StateError = class StateError extends HypenError {
586
+ path;
587
+ constructor(message, path, cause) {
588
+ super("STATE_ERROR", message, {
589
+ context: { path },
590
+ cause: cause instanceof Error ? cause : undefined
591
+ });
592
+ this.name = "StateError";
593
+ this.path = path;
594
+ }
595
+ };
596
+ });
597
+
598
+ // src/app.ts
599
+ var exports_app = {};
600
+ __export(exports_app, {
601
+ app: () => app,
602
+ HypenModuleInstance: () => HypenModuleInstance,
603
+ HypenAppBuilder: () => HypenAppBuilder,
604
+ HypenApp: () => HypenApp
605
+ });
606
+
607
+ class HypenAppBuilder {
608
+ initialState;
609
+ options;
610
+ createdHandler;
611
+ actionHandlers = new Map;
612
+ destroyedHandler;
613
+ disconnectHandler;
614
+ reconnectHandler;
615
+ expireHandler;
616
+ errorHandler;
617
+ template;
618
+ constructor(initialState, options) {
619
+ this.initialState = initialState;
620
+ this.options = options || {};
621
+ }
622
+ onCreated(fn) {
623
+ this.createdHandler = fn;
624
+ return this;
625
+ }
626
+ onAction(name, fn) {
627
+ this.actionHandlers.set(name, fn);
628
+ return this;
629
+ }
630
+ onDestroyed(fn) {
631
+ this.destroyedHandler = fn;
632
+ return this;
633
+ }
634
+ onDisconnect(fn) {
635
+ this.disconnectHandler = fn;
636
+ return this;
637
+ }
638
+ onReconnect(fn) {
639
+ this.reconnectHandler = fn;
640
+ return this;
641
+ }
642
+ onExpire(fn) {
643
+ this.expireHandler = fn;
644
+ return this;
645
+ }
646
+ onError(fn) {
647
+ this.errorHandler = fn;
648
+ return this;
649
+ }
650
+ ui(template) {
651
+ this.template = template;
652
+ return this.build();
653
+ }
654
+ build() {
655
+ const stateKeys = this.initialState !== null && typeof this.initialState === "object" ? Object.keys(this.initialState) : [];
656
+ return {
657
+ name: this.options.name,
658
+ actions: Array.from(this.actionHandlers.keys()),
659
+ stateKeys,
660
+ persist: this.options.persist,
661
+ version: this.options.version,
662
+ initialState: this.initialState,
663
+ template: this.template,
664
+ handlers: {
665
+ onCreated: this.createdHandler,
666
+ onAction: this.actionHandlers,
667
+ onDestroyed: this.destroyedHandler,
668
+ onDisconnect: this.disconnectHandler,
669
+ onReconnect: this.reconnectHandler,
670
+ onExpire: this.expireHandler,
671
+ onError: this.errorHandler
672
+ }
673
+ };
674
+ }
675
+ }
676
+
677
+ class HypenApp {
678
+ defineState(initial, options) {
679
+ return new HypenAppBuilder(initial, options);
680
+ }
681
+ }
682
+
683
+ class HypenModuleInstance {
684
+ engine;
685
+ definition;
686
+ state;
687
+ isDestroyed = false;
688
+ routerContext;
689
+ globalContext;
690
+ stateChangeCallbacks = [];
691
+ constructor(engine, definition, routerContext, globalContext) {
692
+ this.engine = engine;
693
+ this.definition = definition;
694
+ this.routerContext = routerContext;
695
+ this.globalContext = globalContext;
696
+ this.state = createObservableState(definition.initialState, {
697
+ onChange: (change) => {
698
+ this.engine.notifyStateChange(change.paths, change.newValues);
699
+ this.stateChangeCallbacks.forEach((cb) => cb());
700
+ }
701
+ });
702
+ this.engine.setModule(definition.name || "AnonymousModule", definition.actions, definition.stateKeys, getStateSnapshot(this.state));
703
+ for (const [actionName, handler] of definition.handlers.onAction) {
704
+ log3.debug(`Registering action handler: ${actionName} for module ${definition.name}`);
705
+ this.engine.onAction(actionName, async (action) => {
706
+ log3.debug(`Action handler fired: ${actionName}`, action);
707
+ const actionCtx = {
708
+ name: action.name,
709
+ payload: action.payload,
710
+ sender: action.sender
711
+ };
712
+ const next = {
713
+ router: this.routerContext?.root || null
714
+ };
715
+ const context = this.globalContext ? this.createGlobalContextAPI() : undefined;
716
+ const result = await this.executeAction(actionName, handler, {
717
+ action: actionCtx,
718
+ state: this.state,
719
+ next,
720
+ context
721
+ });
722
+ if (!result.ok) {
723
+ const shouldRethrow = await this.handleError(result.error, { actionName });
724
+ if (shouldRethrow) {
725
+ throw result.error;
726
+ }
727
+ } else {
728
+ log3.debug(`Action handler completed: ${actionName}`);
729
+ }
730
+ });
731
+ }
732
+ this.callCreatedHandler();
733
+ }
734
+ createGlobalContextAPI() {
735
+ if (!this.globalContext) {
736
+ throw new Error("Global context not available");
737
+ }
738
+ const ctx = this.globalContext;
739
+ const api = {
740
+ getModule: (id) => ctx.getModule(id),
741
+ hasModule: (id) => ctx.hasModule(id),
742
+ getModuleIds: () => ctx.getModuleIds(),
743
+ getGlobalState: () => ctx.getGlobalState(),
744
+ emit: (event, payload) => ctx.emit(event, payload),
745
+ on: (event, handler) => ctx.on(event, handler)
746
+ };
747
+ if (ctx.__router) {
748
+ api.__router = ctx.__router;
749
+ }
750
+ if (ctx.__hypenEngine) {
751
+ api.__hypenEngine = ctx.__hypenEngine;
752
+ }
753
+ return api;
754
+ }
755
+ async executeAction(actionName, handler, ctx) {
756
+ try {
757
+ const result = handler(ctx);
758
+ await result;
759
+ return Ok(undefined);
760
+ } catch (e) {
761
+ return Err(new ActionError(actionName, e));
762
+ }
763
+ }
764
+ async handleError(error, context) {
765
+ const errorCtx = {
766
+ error,
767
+ state: this.state,
768
+ actionName: context.actionName,
769
+ lifecycle: context.lifecycle
770
+ };
771
+ if (this.definition.handlers.onError) {
772
+ try {
773
+ const result = await this.definition.handlers.onError(errorCtx);
774
+ if (result && typeof result === "object") {
775
+ if ("handled" in result && result.handled) {
776
+ return false;
777
+ }
778
+ if ("rethrow" in result && result.rethrow) {
779
+ return true;
780
+ }
781
+ }
782
+ } catch (handlerError) {
783
+ log3.error("Error in onError handler:", handlerError);
784
+ }
785
+ }
786
+ if (this.globalContext) {
787
+ const eventContext = context.actionName ? `action:${context.actionName}` : context.lifecycle ? `lifecycle:${context.lifecycle}` : "unknown";
788
+ this.globalContext.emit("error", {
789
+ message: error.message,
790
+ error,
791
+ context: eventContext
792
+ });
793
+ }
794
+ log3.error(`${context.actionName ? `Action "${context.actionName}"` : `Lifecycle "${context.lifecycle}"`} error:`, error);
795
+ return false;
796
+ }
797
+ async callCreatedHandler() {
798
+ if (this.definition.handlers.onCreated) {
799
+ const context = this.globalContext ? this.createGlobalContextAPI() : undefined;
800
+ await this.definition.handlers.onCreated(this.state, context);
801
+ }
802
+ }
803
+ onStateChange(callback) {
804
+ this.stateChangeCallbacks.push(callback);
805
+ }
806
+ async destroy() {
807
+ if (this.isDestroyed)
808
+ return;
809
+ if (this.definition.handlers.onDestroyed) {
810
+ await this.definition.handlers.onDestroyed(this.state);
811
+ }
812
+ this.isDestroyed = true;
813
+ }
814
+ getState() {
815
+ return getStateSnapshot(this.state);
816
+ }
817
+ getLiveState() {
818
+ return this.state;
819
+ }
820
+ updateState(patch) {
821
+ Object.assign(this.state, patch);
822
+ }
823
+ }
824
+ var log3, app;
825
+ var init_app = __esm(() => {
826
+ init_result();
827
+ init_state();
828
+ init_logger();
829
+ log3 = createLogger("ModuleInstance");
830
+ app = new HypenApp;
831
+ });
832
+
833
+ // src/engine.ts
834
+ init_logger();
835
+ import { WasmEngine } from "../wasm-node/hypen_engine.js";
836
+ var log2 = frameworkLoggers.engine;
837
+ function unwrapForWasm(value) {
838
+ if (value === null || typeof value !== "object") {
839
+ return value;
840
+ }
841
+ if (typeof value.__getSnapshot === "function") {
842
+ return value.__getSnapshot();
843
+ }
844
+ try {
845
+ return structuredClone(value);
846
+ } catch {
847
+ return JSON.parse(JSON.stringify(value));
848
+ }
849
+ }
850
+
851
+ class Engine {
852
+ wasmEngine = null;
853
+ initialized = false;
854
+ async init() {
855
+ if (this.initialized)
856
+ return;
857
+ this.wasmEngine = new WasmEngine;
858
+ this.initialized = true;
859
+ }
860
+ ensureInitialized() {
861
+ if (!this.wasmEngine) {
862
+ throw new Error("Engine not initialized. Call init() first.");
863
+ }
864
+ return this.wasmEngine;
865
+ }
866
+ setRenderCallback(callback) {
867
+ const engine = this.ensureInitialized();
868
+ engine.setRenderCallback((patches) => {
869
+ callback(patches);
870
+ });
871
+ }
872
+ setComponentResolver(resolver) {
873
+ const engine = this.ensureInitialized();
874
+ engine.setComponentResolver((componentName, contextPath) => {
875
+ const result = resolver(componentName, contextPath);
876
+ return result;
877
+ });
878
+ }
879
+ renderSource(source) {
880
+ const engine = this.ensureInitialized();
881
+ engine.renderSource(source);
882
+ }
883
+ renderLazyComponent(source) {
884
+ const engine = this.ensureInitialized();
885
+ engine.renderLazyComponent(source);
886
+ }
887
+ renderInto(source, parentNodeId, state) {
888
+ const engine = this.ensureInitialized();
889
+ engine.renderInto(source, parentNodeId, unwrapForWasm(state));
890
+ }
891
+ notifyStateChange(paths, values) {
892
+ const engine = this.ensureInitialized();
893
+ if (paths.length === 0) {
894
+ return;
895
+ }
896
+ engine.updateStateSparse(paths, unwrapForWasm(values));
897
+ log2.debug("State changed (sparse):", paths);
898
+ }
899
+ notifyStateChangeFull(paths, currentState) {
900
+ const engine = this.ensureInitialized();
901
+ engine.updateState(unwrapForWasm(currentState));
902
+ if (paths.length > 0) {
903
+ log2.debug("State changed (full):", paths);
904
+ }
905
+ }
906
+ updateState(statePatch) {
907
+ const engine = this.ensureInitialized();
908
+ engine.updateState(unwrapForWasm(statePatch));
909
+ }
910
+ dispatchAction(name, payload) {
911
+ const engine = this.ensureInitialized();
912
+ engine.dispatchAction(name, payload ?? null);
913
+ }
914
+ onAction(actionName, handler) {
915
+ const engine = this.ensureInitialized();
916
+ engine.onAction(actionName, (action) => {
917
+ Promise.resolve(handler(action)).catch((err) => log2.error("Action handler error:", err));
918
+ });
919
+ }
920
+ setModule(name, actions, stateKeys, initialState) {
921
+ const engine = this.ensureInitialized();
922
+ engine.setModule(name, actions, stateKeys, initialState);
923
+ }
924
+ getRevision() {
925
+ const engine = this.ensureInitialized();
926
+ return Number(engine.getRevision());
927
+ }
928
+ clearTree() {
929
+ const engine = this.ensureInitialized();
930
+ engine.clearTree();
931
+ }
932
+ debugParseComponent(source) {
933
+ const engine = this.ensureInitialized();
934
+ return engine.debugParseComponent(source);
935
+ }
936
+ }
937
+
938
+ // src/remote/server.ts
939
+ init_app();
940
+
941
+ // src/remote/session.ts
942
+ class SessionManager {
943
+ activeSessions = new Map;
944
+ pendingSessions = new Map;
945
+ sessionConnections = new Map;
946
+ config;
947
+ constructor(config2 = {}) {
948
+ this.config = {
949
+ ttl: config2.ttl ?? 3600,
950
+ concurrent: config2.concurrent ?? "kick-old",
951
+ generateId: config2.generateId ?? (() => crypto.randomUUID())
952
+ };
953
+ }
954
+ getTtl() {
955
+ return this.config.ttl;
956
+ }
957
+ getConcurrentPolicy() {
958
+ return this.config.concurrent;
959
+ }
960
+ createSession(props) {
961
+ const now = new Date;
962
+ const session = {
963
+ id: this.config.generateId(),
964
+ ttl: this.config.ttl,
965
+ createdAt: now,
966
+ lastConnectedAt: now,
967
+ props
968
+ };
969
+ this.activeSessions.set(session.id, session);
970
+ return session;
971
+ }
972
+ getActiveSession(id) {
973
+ return this.activeSessions.get(id) ?? null;
974
+ }
975
+ getPendingSession(id) {
976
+ return this.pendingSessions.get(id) ?? null;
977
+ }
978
+ hasSession(id) {
979
+ return this.activeSessions.has(id) || this.pendingSessions.has(id);
980
+ }
981
+ suspendSession(sessionId, savedState, onExpire) {
982
+ const session = this.activeSessions.get(sessionId);
983
+ if (!session)
984
+ return;
985
+ this.activeSessions.delete(sessionId);
986
+ const expiryTimer = setTimeout(async () => {
987
+ const pending = this.pendingSessions.get(sessionId);
988
+ if (pending) {
989
+ this.pendingSessions.delete(sessionId);
990
+ await onExpire(pending.session);
991
+ }
992
+ }, session.ttl * 1000);
993
+ this.pendingSessions.set(sessionId, {
994
+ session,
995
+ savedState,
996
+ expiryTimer
997
+ });
998
+ }
999
+ resumeSession(sessionId) {
1000
+ const pending = this.pendingSessions.get(sessionId);
1001
+ if (!pending)
1002
+ return null;
1003
+ clearTimeout(pending.expiryTimer);
1004
+ this.pendingSessions.delete(sessionId);
1005
+ pending.session.lastConnectedAt = new Date;
1006
+ this.activeSessions.set(sessionId, pending.session);
1007
+ return {
1008
+ session: pending.session,
1009
+ savedState: pending.savedState
1010
+ };
1011
+ }
1012
+ destroySession(sessionId) {
1013
+ this.activeSessions.delete(sessionId);
1014
+ const pending = this.pendingSessions.get(sessionId);
1015
+ if (pending) {
1016
+ clearTimeout(pending.expiryTimer);
1017
+ this.pendingSessions.delete(sessionId);
1018
+ }
1019
+ this.sessionConnections.delete(sessionId);
1020
+ }
1021
+ trackConnection(sessionId, ws) {
1022
+ let connections = this.sessionConnections.get(sessionId);
1023
+ if (!connections) {
1024
+ connections = new Set;
1025
+ this.sessionConnections.set(sessionId, connections);
1026
+ }
1027
+ connections.add(ws);
1028
+ }
1029
+ untrackConnection(sessionId, ws) {
1030
+ const connections = this.sessionConnections.get(sessionId);
1031
+ if (connections) {
1032
+ connections.delete(ws);
1033
+ if (connections.size === 0) {
1034
+ this.sessionConnections.delete(sessionId);
1035
+ }
1036
+ }
1037
+ }
1038
+ getConnections(sessionId) {
1039
+ return this.sessionConnections.get(sessionId);
1040
+ }
1041
+ getConnectionCount(sessionId) {
1042
+ return this.sessionConnections.get(sessionId)?.size ?? 0;
1043
+ }
1044
+ getStats() {
1045
+ let totalConnections = 0;
1046
+ for (const connections of this.sessionConnections.values()) {
1047
+ totalConnections += connections.size;
1048
+ }
1049
+ return {
1050
+ activeSessions: this.activeSessions.size,
1051
+ pendingSessions: this.pendingSessions.size,
1052
+ totalConnections
1053
+ };
1054
+ }
1055
+ destroy() {
1056
+ for (const pending of this.pendingSessions.values()) {
1057
+ clearTimeout(pending.expiryTimer);
1058
+ }
1059
+ this.activeSessions.clear();
1060
+ this.pendingSessions.clear();
1061
+ this.sessionConnections.clear();
1062
+ }
1063
+ }
1064
+
1065
+ // src/remote/server.ts
1066
+ init_logger();
1067
+ var log4 = frameworkLoggers.remote;
1068
+
1069
+ class RemoteServer {
1070
+ _module = null;
1071
+ _moduleName = "App";
1072
+ _ui = "";
1073
+ _config = {};
1074
+ _sessionConfig = {};
1075
+ _onConnectionCallbacks = [];
1076
+ _onDisconnectionCallbacks = [];
1077
+ clients = new Map;
1078
+ nextClientId = 1;
1079
+ server = null;
1080
+ sessionManager = null;
1081
+ module(name, module) {
1082
+ this._moduleName = name;
1083
+ this._module = module;
1084
+ return this;
1085
+ }
1086
+ ui(dsl) {
1087
+ this._ui = dsl;
1088
+ return this;
1089
+ }
1090
+ config(config2) {
1091
+ this._config = { ...this._config, ...config2 };
1092
+ return this;
1093
+ }
1094
+ session(config2) {
1095
+ this._sessionConfig = config2;
1096
+ return this;
1097
+ }
1098
+ onConnection(callback) {
1099
+ this._onConnectionCallbacks.push(callback);
1100
+ return this;
1101
+ }
1102
+ onDisconnection(callback) {
1103
+ this._onDisconnectionCallbacks.push(callback);
1104
+ return this;
1105
+ }
1106
+ listen(port) {
1107
+ if (!this._module) {
1108
+ throw new Error("Module not set. Call .module() before .listen()");
1109
+ }
1110
+ if (!this._ui) {
1111
+ throw new Error("UI not set. Call .ui() before .listen()");
1112
+ }
1113
+ this.sessionManager = new SessionManager(this._sessionConfig);
1114
+ const finalPort = port ?? this._config.port ?? 3000;
1115
+ const hostname = this._config.hostname ?? "0.0.0.0";
1116
+ this.server = Bun.serve({
1117
+ port: finalPort,
1118
+ hostname,
1119
+ websocket: {
1120
+ open: (ws) => this.handleOpen(ws),
1121
+ message: (ws, message) => this.handleMessage(ws, message),
1122
+ close: (ws) => this.handleClose(ws)
1123
+ },
1124
+ fetch: (req, server) => {
1125
+ const url = new URL(req.url);
1126
+ if (server.upgrade(req, { data: undefined })) {
1127
+ return;
1128
+ }
1129
+ if (url.pathname === "/health") {
1130
+ return new Response("OK", { status: 200 });
1131
+ }
1132
+ if (url.pathname === "/stats") {
1133
+ const stats = this.sessionManager?.getStats() ?? {
1134
+ activeSessions: 0,
1135
+ pendingSessions: 0,
1136
+ totalConnections: 0
1137
+ };
1138
+ return new Response(JSON.stringify(stats), {
1139
+ headers: { "Content-Type": "application/json" }
1140
+ });
1141
+ }
1142
+ return new Response("Hypen Remote Server", { status: 200 });
1143
+ }
1144
+ });
1145
+ log4.info(`Hypen app streaming on ws://${hostname}:${finalPort}`);
1146
+ return this;
1147
+ }
1148
+ stop() {
1149
+ if (this.server) {
1150
+ this.server.stop();
1151
+ this.server = null;
1152
+ }
1153
+ if (this.sessionManager) {
1154
+ this.sessionManager.destroy();
1155
+ this.sessionManager = null;
1156
+ }
1157
+ }
1158
+ get url() {
1159
+ if (!this.server)
1160
+ return null;
1161
+ const hostname = this._config.hostname ?? "localhost";
1162
+ const port = this._config.port ?? 3000;
1163
+ return `ws://${hostname}:${port}`;
1164
+ }
1165
+ async handleOpen(ws) {
1166
+ try {
1167
+ const clientId = `client_${this.nextClientId++}`;
1168
+ const connectedAt = new Date;
1169
+ const engine = new Engine;
1170
+ await engine.init();
1171
+ const moduleInstance = new HypenModuleInstance(engine, this._module);
1172
+ const clientData = {
1173
+ id: clientId,
1174
+ engine,
1175
+ moduleInstance,
1176
+ revision: 0,
1177
+ connectedAt,
1178
+ sessionId: "",
1179
+ helloReceived: false
1180
+ };
1181
+ this.clients.set(ws, clientData);
1182
+ clientData.helloTimeout = setTimeout(() => {
1183
+ if (!clientData.helloReceived) {
1184
+ this.initializeSession(ws, clientData, undefined, undefined);
1185
+ }
1186
+ }, 1000);
1187
+ } catch (error) {
1188
+ log4.error("Error handling WebSocket open:", error);
1189
+ ws.close(1011, "Internal server error");
1190
+ }
1191
+ }
1192
+ async initializeSession(ws, clientData, requestedSessionId, props) {
1193
+ if (clientData.helloReceived)
1194
+ return;
1195
+ clientData.helloReceived = true;
1196
+ if (clientData.helloTimeout) {
1197
+ clearTimeout(clientData.helloTimeout);
1198
+ clientData.helloTimeout = undefined;
1199
+ }
1200
+ let session;
1201
+ let isNew = true;
1202
+ let isRestored = false;
1203
+ let restoredState = null;
1204
+ if (requestedSessionId && this.sessionManager) {
1205
+ const resumed = this.sessionManager.resumeSession(requestedSessionId);
1206
+ if (resumed) {
1207
+ session = resumed.session;
1208
+ restoredState = resumed.savedState;
1209
+ isNew = false;
1210
+ isRestored = true;
1211
+ await this.triggerReconnect(clientData, session, restoredState);
1212
+ } else {
1213
+ const activeSession = this.sessionManager.getActiveSession(requestedSessionId);
1214
+ if (activeSession) {
1215
+ const handled = await this.handleConcurrentConnection(ws, clientData, activeSession, props);
1216
+ if (!handled)
1217
+ return;
1218
+ session = activeSession;
1219
+ isNew = false;
1220
+ } else {
1221
+ session = this.sessionManager.createSession(props);
1222
+ }
1223
+ }
1224
+ } else if (this.sessionManager) {
1225
+ session = this.sessionManager.createSession(props);
1226
+ } else {
1227
+ session = {
1228
+ id: crypto.randomUUID(),
1229
+ ttl: 3600,
1230
+ createdAt: new Date,
1231
+ lastConnectedAt: new Date,
1232
+ props
1233
+ };
1234
+ }
1235
+ clientData.sessionId = session.id;
1236
+ this.sessionManager?.trackConnection(session.id, ws);
1237
+ const sessionAck = {
1238
+ type: "sessionAck",
1239
+ sessionId: session.id,
1240
+ isNew,
1241
+ isRestored
1242
+ };
1243
+ ws.send(JSON.stringify(sessionAck));
1244
+ this.setupRenderCallback(ws, clientData);
1245
+ const initialPatches = [];
1246
+ clientData.engine.setRenderCallback((patches) => {
1247
+ initialPatches.push(...patches);
1248
+ });
1249
+ clientData.engine.renderSource(this._ui);
1250
+ this.setupRenderCallback(ws, clientData);
1251
+ const initialMessage = {
1252
+ type: "initialTree",
1253
+ module: this._moduleName,
1254
+ state: clientData.moduleInstance.getState(),
1255
+ patches: initialPatches,
1256
+ revision: 0
1257
+ };
1258
+ ws.send(JSON.stringify(initialMessage));
1259
+ const client = {
1260
+ id: clientData.id,
1261
+ socket: ws,
1262
+ connectedAt: clientData.connectedAt
1263
+ };
1264
+ this._onConnectionCallbacks.forEach((cb) => cb(client));
1265
+ }
1266
+ setupRenderCallback(ws, clientData) {
1267
+ clientData.engine.setRenderCallback((patches) => {
1268
+ const data = this.clients.get(ws);
1269
+ if (!data)
1270
+ return;
1271
+ data.revision++;
1272
+ const message = {
1273
+ type: "patch",
1274
+ module: this._moduleName,
1275
+ patches,
1276
+ revision: data.revision
1277
+ };
1278
+ ws.send(JSON.stringify(message));
1279
+ if (this.sessionManager?.getConcurrentPolicy() === "allow-multiple" && data.sessionId) {
1280
+ const connections = this.sessionManager.getConnections(data.sessionId);
1281
+ if (connections) {
1282
+ for (const conn of connections) {
1283
+ if (conn !== ws) {
1284
+ conn.send(JSON.stringify(message));
1285
+ }
1286
+ }
1287
+ }
1288
+ }
1289
+ });
1290
+ }
1291
+ async handleConcurrentConnection(ws, clientData, existingSession, props) {
1292
+ const policy = this.sessionManager?.getConcurrentPolicy() ?? "kick-old";
1293
+ switch (policy) {
1294
+ case "kick-old": {
1295
+ const existingConnections = this.sessionManager?.getConnections(existingSession.id);
1296
+ if (existingConnections) {
1297
+ for (const conn of existingConnections) {
1298
+ const oldWs = conn;
1299
+ const expiredMsg = {
1300
+ type: "sessionExpired",
1301
+ sessionId: existingSession.id,
1302
+ reason: "kicked"
1303
+ };
1304
+ oldWs.send(JSON.stringify(expiredMsg));
1305
+ oldWs.close(1000, "Session taken by new connection");
1306
+ }
1307
+ }
1308
+ return true;
1309
+ }
1310
+ case "reject-new": {
1311
+ const expiredMsg = {
1312
+ type: "sessionExpired",
1313
+ sessionId: existingSession.id,
1314
+ reason: "kicked"
1315
+ };
1316
+ ws.send(JSON.stringify(expiredMsg));
1317
+ ws.close(1000, "Session already active");
1318
+ return false;
1319
+ }
1320
+ case "allow-multiple": {
1321
+ return true;
1322
+ }
1323
+ default:
1324
+ return true;
1325
+ }
1326
+ }
1327
+ async triggerReconnect(clientData, session, savedState) {
1328
+ const handler = this._module?.handlers.onReconnect;
1329
+ if (!handler)
1330
+ return;
1331
+ let restored = false;
1332
+ const restore = (state) => {
1333
+ restored = true;
1334
+ clientData.moduleInstance.updateState(state);
1335
+ };
1336
+ await handler({ session, restore });
1337
+ }
1338
+ handleMessage(ws, message) {
1339
+ try {
1340
+ const clientData = this.clients.get(ws);
1341
+ if (!clientData)
1342
+ return;
1343
+ const msg = JSON.parse(message.toString());
1344
+ switch (msg.type) {
1345
+ case "hello": {
1346
+ const helloMsg = msg;
1347
+ this.initializeSession(ws, clientData, helloMsg.sessionId, helloMsg.props);
1348
+ break;
1349
+ }
1350
+ case "dispatchAction": {
1351
+ const actionMsg = msg;
1352
+ clientData.engine.dispatchAction(actionMsg.action, actionMsg.payload);
1353
+ break;
1354
+ }
1355
+ default:
1356
+ break;
1357
+ }
1358
+ } catch (error) {
1359
+ log4.error("Error handling WebSocket message:", error);
1360
+ }
1361
+ }
1362
+ async handleClose(ws) {
1363
+ const clientData = this.clients.get(ws);
1364
+ if (!clientData)
1365
+ return;
1366
+ if (clientData.helloTimeout) {
1367
+ clearTimeout(clientData.helloTimeout);
1368
+ }
1369
+ const currentState = clientData.moduleInstance.getState();
1370
+ if (clientData.sessionId && this._module?.handlers.onDisconnect) {
1371
+ const session = this.sessionManager?.getActiveSession(clientData.sessionId);
1372
+ if (session) {
1373
+ await this._module.handlers.onDisconnect({
1374
+ state: currentState,
1375
+ session
1376
+ });
1377
+ }
1378
+ }
1379
+ if (clientData.sessionId && this.sessionManager) {
1380
+ this.sessionManager.untrackConnection(clientData.sessionId, ws);
1381
+ if (this.sessionManager.getConnectionCount(clientData.sessionId) === 0) {
1382
+ const session = this.sessionManager.getActiveSession(clientData.sessionId);
1383
+ if (session) {
1384
+ this.sessionManager.suspendSession(clientData.sessionId, currentState, async (expiredSession) => {
1385
+ if (this._module?.handlers.onExpire) {
1386
+ await this._module.handlers.onExpire({ session: expiredSession });
1387
+ }
1388
+ });
1389
+ }
1390
+ }
1391
+ }
1392
+ await clientData.moduleInstance.destroy();
1393
+ this.clients.delete(ws);
1394
+ const client = {
1395
+ id: clientData.id,
1396
+ socket: ws,
1397
+ connectedAt: clientData.connectedAt
1398
+ };
1399
+ this._onDisconnectionCallbacks.forEach((cb) => cb(client));
1400
+ }
1401
+ getClientCount() {
1402
+ return this.clients.size;
1403
+ }
1404
+ getSessionStats() {
1405
+ return this.sessionManager?.getStats() ?? {
1406
+ activeSessions: 0,
1407
+ pendingSessions: 0,
1408
+ totalConnections: 0
1409
+ };
1410
+ }
1411
+ broadcast(message) {
1412
+ const json = JSON.stringify(message);
1413
+ this.clients.forEach((_, ws) => {
1414
+ ws.send(json);
1415
+ });
1416
+ }
1417
+ }
1418
+ function serve(options) {
1419
+ const server = new RemoteServer().module(options.moduleName ?? "App", options.module).ui(options.ui);
1420
+ if (options.port || options.hostname) {
1421
+ server.config({
1422
+ port: options.port,
1423
+ hostname: options.hostname
1424
+ });
1425
+ }
1426
+ if (options.session) {
1427
+ server.session(options.session);
1428
+ }
1429
+ if (options.onConnection) {
1430
+ server.onConnection(options.onConnection);
1431
+ }
1432
+ if (options.onDisconnection) {
1433
+ server.onDisconnection(options.onDisconnection);
1434
+ }
1435
+ return server.listen(options.port);
1436
+ }
1437
+ export {
1438
+ serve,
1439
+ RemoteServer
1440
+ };
1441
+
1442
+ //# debugId=C7697DC74216A40C64756E2164756E21