@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
@@ -10,6 +10,205 @@ var __export = (target, all) => {
10
10
  };
11
11
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
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
+
13
212
  // src/state.ts
14
213
  function deepClone(obj) {
15
214
  if (obj === null || typeof obj !== "object") {
@@ -396,223 +595,37 @@ var init_result = __esm(() => {
396
595
  };
397
596
  });
398
597
 
399
- // src/logger.ts
400
- function isProduction() {
401
- if (typeof process !== "undefined" && process.env) {
402
- return false;
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 || {};
403
621
  }
404
- return false;
405
- }
406
- function setLogLevel(level) {
407
- config.level = level;
408
- }
409
- function getLogLevel() {
410
- return config.level;
411
- }
412
- function configureLogger(options) {
413
- config = { ...config, ...options };
414
- }
415
- function enableLogging() {
416
- config.level = "debug";
417
- }
418
- function disableLogging() {
419
- config.level = "none";
420
- }
421
- function shouldLog(level) {
422
- return LOG_LEVEL_ORDER[level] >= LOG_LEVEL_ORDER[config.level];
423
- }
424
- function formatTag(tag, level) {
425
- const timestamp = config.timestamps ? `${new Date().toISOString()} ` : "";
426
- if (config.colors && level !== "none") {
427
- const color = LOG_LEVEL_COLORS[level];
428
- return `${timestamp}${color}[${tag}]${RESET_COLOR}`;
622
+ onCreated(fn) {
623
+ this.createdHandler = fn;
624
+ return this;
429
625
  }
430
- return `${timestamp}[${tag}]`;
431
- }
432
-
433
- class Logger {
434
- tag;
435
- constructor(tag) {
436
- this.tag = tag;
437
- }
438
- debug(...args) {
439
- if (!shouldLog("debug"))
440
- return;
441
- if (config.handler) {
442
- config.handler.debug(this.tag, ...args);
443
- } else {
444
- console.log(formatTag(this.tag, "debug"), ...args);
445
- }
446
- }
447
- info(...args) {
448
- if (!shouldLog("info"))
449
- return;
450
- if (config.handler) {
451
- config.handler.info(this.tag, ...args);
452
- } else {
453
- console.info(formatTag(this.tag, "info"), ...args);
454
- }
455
- }
456
- warn(...args) {
457
- if (!shouldLog("warn"))
458
- return;
459
- if (config.handler) {
460
- config.handler.warn(this.tag, ...args);
461
- } else {
462
- console.warn(formatTag(this.tag, "warn"), ...args);
463
- }
464
- }
465
- error(...args) {
466
- if (!shouldLog("error"))
467
- return;
468
- if (config.handler) {
469
- config.handler.error(this.tag, ...args);
470
- } else {
471
- console.error(formatTag(this.tag, "error"), ...args);
472
- }
473
- }
474
- time(label, fn) {
475
- if (!shouldLog("debug")) {
476
- return fn();
477
- }
478
- const start = performance.now();
479
- try {
480
- return fn();
481
- } finally {
482
- const duration = performance.now() - start;
483
- this.debug(`${label}: ${duration.toFixed(2)}ms`);
484
- }
485
- }
486
- async timeAsync(label, fn) {
487
- if (!shouldLog("debug")) {
488
- return fn();
489
- }
490
- const start = performance.now();
491
- try {
492
- return await fn();
493
- } finally {
494
- const duration = performance.now() - start;
495
- this.debug(`${label}: ${duration.toFixed(2)}ms`);
496
- }
497
- }
498
- child(subTag) {
499
- return new Logger(`${this.tag}:${subTag}`);
500
- }
501
- debugIf(condition, ...args) {
502
- if (condition)
503
- this.debug(...args);
504
- }
505
- warnIf(condition, ...args) {
506
- if (condition)
507
- this.warn(...args);
508
- }
509
- errorIf(condition, ...args) {
510
- if (condition)
511
- this.error(...args);
512
- }
513
- loggedOnce = new Set;
514
- warnOnce(key, ...args) {
515
- if (this.loggedOnce.has(key))
516
- return;
517
- this.loggedOnce.add(key);
518
- this.warn(...args);
519
- }
520
- debugOnce(key, ...args) {
521
- if (this.loggedOnce.has(key))
522
- return;
523
- this.loggedOnce.add(key);
524
- this.debug(...args);
525
- }
526
- }
527
- function createLogger(tag) {
528
- return new Logger(tag);
529
- }
530
- var LOG_LEVEL_ORDER, LOG_LEVEL_COLORS, RESET_COLOR = "\x1B[0m", config, logger, log, frameworkLoggers;
531
- var init_logger = __esm(() => {
532
- LOG_LEVEL_ORDER = {
533
- debug: 0,
534
- info: 1,
535
- warn: 2,
536
- error: 3,
537
- none: 4
538
- };
539
- LOG_LEVEL_COLORS = {
540
- debug: "\x1B[36m",
541
- info: "\x1B[32m",
542
- warn: "\x1B[33m",
543
- error: "\x1B[31m"
544
- };
545
- config = {
546
- level: isProduction() ? "error" : "debug",
547
- colors: true,
548
- timestamps: false
549
- };
550
- logger = createLogger("Hypen");
551
- log = {
552
- debug: (tag, ...args) => {
553
- if (!shouldLog("debug"))
554
- return;
555
- console.log(formatTag(tag, "debug"), ...args);
556
- },
557
- info: (tag, ...args) => {
558
- if (!shouldLog("info"))
559
- return;
560
- console.info(formatTag(tag, "info"), ...args);
561
- },
562
- warn: (tag, ...args) => {
563
- if (!shouldLog("warn"))
564
- return;
565
- console.warn(formatTag(tag, "warn"), ...args);
566
- },
567
- error: (tag, ...args) => {
568
- if (!shouldLog("error"))
569
- return;
570
- console.error(formatTag(tag, "error"), ...args);
571
- }
572
- };
573
- frameworkLoggers = {
574
- engine: createLogger("Engine"),
575
- router: createLogger("Router"),
576
- state: createLogger("State"),
577
- events: createLogger("Events"),
578
- remote: createLogger("Remote"),
579
- renderer: createLogger("Renderer"),
580
- module: createLogger("Module"),
581
- lifecycle: createLogger("Lifecycle")
582
- };
583
- });
584
-
585
- // src/app.ts
586
- var exports_app = {};
587
- __export(exports_app, {
588
- app: () => app,
589
- HypenModuleInstance: () => HypenModuleInstance,
590
- HypenAppBuilder: () => HypenAppBuilder,
591
- HypenApp: () => HypenApp
592
- });
593
-
594
- class HypenAppBuilder {
595
- initialState;
596
- options;
597
- createdHandler;
598
- actionHandlers = new Map;
599
- destroyedHandler;
600
- disconnectHandler;
601
- reconnectHandler;
602
- expireHandler;
603
- errorHandler;
604
- template;
605
- constructor(initialState, options) {
606
- this.initialState = initialState;
607
- this.options = options || {};
608
- }
609
- onCreated(fn) {
610
- this.createdHandler = fn;
611
- return this;
612
- }
613
- onAction(name, fn) {
614
- this.actionHandlers.set(name, fn);
615
- return this;
626
+ onAction(name, fn) {
627
+ this.actionHandlers.set(name, fn);
628
+ return this;
616
629
  }
617
630
  onDestroyed(fn) {
618
631
  this.destroyedHandler = fn;
@@ -688,9 +701,9 @@ class HypenModuleInstance {
688
701
  });
689
702
  this.engine.setModule(definition.name || "AnonymousModule", definition.actions, definition.stateKeys, getStateSnapshot(this.state));
690
703
  for (const [actionName, handler] of definition.handlers.onAction) {
691
- log2.debug(`Registering action handler: ${actionName} for module ${definition.name}`);
704
+ log4.debug(`Registering action handler: ${actionName} for module ${definition.name}`);
692
705
  this.engine.onAction(actionName, async (action) => {
693
- log2.debug(`Action handler fired: ${actionName}`, action);
706
+ log4.debug(`Action handler fired: ${actionName}`, action);
694
707
  const actionCtx = {
695
708
  name: action.name,
696
709
  payload: action.payload,
@@ -712,7 +725,7 @@ class HypenModuleInstance {
712
725
  throw result.error;
713
726
  }
714
727
  } else {
715
- log2.debug(`Action handler completed: ${actionName}`);
728
+ log4.debug(`Action handler completed: ${actionName}`);
716
729
  }
717
730
  });
718
731
  }
@@ -767,7 +780,7 @@ class HypenModuleInstance {
767
780
  }
768
781
  }
769
782
  } catch (handlerError) {
770
- log2.error("Error in onError handler:", handlerError);
783
+ log4.error("Error in onError handler:", handlerError);
771
784
  }
772
785
  }
773
786
  if (this.globalContext) {
@@ -778,7 +791,7 @@ class HypenModuleInstance {
778
791
  context: eventContext
779
792
  });
780
793
  }
781
- log2.error(`${context.actionName ? `Action "${context.actionName}"` : `Lifecycle "${context.lifecycle}"`} error:`, error);
794
+ log4.error(`${context.actionName ? `Action "${context.actionName}"` : `Lifecycle "${context.lifecycle}"`} error:`, error);
782
795
  return false;
783
796
  }
784
797
  async callCreatedHandler() {
@@ -808,17 +821,19 @@ class HypenModuleInstance {
808
821
  Object.assign(this.state, patch);
809
822
  }
810
823
  }
811
- var log2, app;
824
+ var log4, app;
812
825
  var init_app = __esm(() => {
813
826
  init_result();
814
827
  init_state();
815
828
  init_logger();
816
- log2 = createLogger("ModuleInstance");
829
+ log4 = createLogger("ModuleInstance");
817
830
  app = new HypenApp;
818
831
  });
819
832
 
820
833
  // src/engine.ts
834
+ init_logger();
821
835
  import { WasmEngine } from "../wasm-node/hypen_engine.js";
836
+ var log2 = frameworkLoggers.engine;
822
837
  function unwrapForWasm(value) {
823
838
  if (value === null || typeof value !== "object") {
824
839
  return value;
@@ -879,13 +894,13 @@ class Engine {
879
894
  return;
880
895
  }
881
896
  engine.updateStateSparse(paths, unwrapForWasm(values));
882
- console.debug("[Hypen] State changed (sparse):", paths);
897
+ log2.debug("State changed (sparse):", paths);
883
898
  }
884
899
  notifyStateChangeFull(paths, currentState) {
885
900
  const engine = this.ensureInitialized();
886
901
  engine.updateState(unwrapForWasm(currentState));
887
902
  if (paths.length > 0) {
888
- console.debug("[Hypen] State changed (full):", paths);
903
+ log2.debug("State changed (full):", paths);
889
904
  }
890
905
  }
891
906
  updateState(statePatch) {
@@ -899,7 +914,7 @@ class Engine {
899
914
  onAction(actionName, handler) {
900
915
  const engine = this.ensureInitialized();
901
916
  engine.onAction(actionName, (action) => {
902
- Promise.resolve(handler(action)).catch(console.error);
917
+ Promise.resolve(handler(action)).catch((err) => log2.error("Action handler error:", err));
903
918
  });
904
919
  }
905
920
  setModule(name, actions, stateKeys, initialState) {
@@ -921,6 +936,8 @@ class Engine {
921
936
  }
922
937
 
923
938
  // src/engine.browser.ts
939
+ init_logger();
940
+ var log3 = frameworkLoggers.engine;
924
941
  var wasmInit = null;
925
942
  var WasmEngineClass = null;
926
943
  function mapToObject(value) {
@@ -959,7 +976,7 @@ class Engine2 {
959
976
  this.wasmEngine = new WasmEngineClass;
960
977
  this.initialized = true;
961
978
  } catch (error) {
962
- console.error("[Hypen] Failed to initialize WASM engine:", error);
979
+ log3.error("Failed to initialize WASM engine:", error);
963
980
  throw error;
964
981
  }
965
982
  }
@@ -995,12 +1012,21 @@ class Engine2 {
995
1012
  const safeState = JSON.parse(JSON.stringify(state));
996
1013
  engine.renderInto(source, parentNodeId, safeState);
997
1014
  }
998
- notifyStateChange(paths, currentState) {
1015
+ notifyStateChange(paths, values) {
1016
+ const engine = this.ensureInitialized();
1017
+ if (paths.length === 0) {
1018
+ return;
1019
+ }
1020
+ const plainValues = JSON.parse(JSON.stringify(values));
1021
+ engine.updateStateSparse(paths, plainValues);
1022
+ log3.debug("State changed (sparse):", paths);
1023
+ }
1024
+ notifyStateChangeFull(paths, currentState) {
999
1025
  const engine = this.ensureInitialized();
1000
1026
  const plainObject = JSON.parse(JSON.stringify(currentState));
1001
1027
  engine.updateState(plainObject);
1002
1028
  if (paths.length > 0) {
1003
- console.debug("[Hypen] State changed:", paths);
1029
+ log3.debug("State changed (full):", paths);
1004
1030
  }
1005
1031
  }
1006
1032
  updateState(statePatch) {
@@ -1010,7 +1036,7 @@ class Engine2 {
1010
1036
  }
1011
1037
  dispatchAction(name, payload) {
1012
1038
  const engine = this.ensureInitialized();
1013
- console.log(`[Engine] Action dispatched: ${name}`);
1039
+ log3.debug(`Action dispatched: ${name}`);
1014
1040
  engine.dispatchAction(name, payload ?? null);
1015
1041
  }
1016
1042
  onAction(actionName, handler) {
@@ -1020,7 +1046,7 @@ class Engine2 {
1020
1046
  ...action,
1021
1047
  payload: action.payload ? mapToObject(action.payload) : action.payload
1022
1048
  };
1023
- Promise.resolve(handler(normalizedAction)).catch(console.error);
1049
+ Promise.resolve(handler(normalizedAction)).catch((err) => log3.error("Action handler error:", err));
1024
1050
  });
1025
1051
  }
1026
1052
  setModule(name, actions, stateKeys, initialState) {
@@ -1042,6 +1068,9 @@ class Engine2 {
1042
1068
  }
1043
1069
 
1044
1070
  // src/renderer.ts
1071
+ init_logger();
1072
+ var log5 = frameworkLoggers.renderer;
1073
+
1045
1074
  class BaseRenderer {
1046
1075
  nodes = new Map;
1047
1076
  getNode(id) {
@@ -1082,16 +1111,14 @@ class BaseRenderer {
1082
1111
 
1083
1112
  class ConsoleRenderer {
1084
1113
  applyPatches(patches) {
1085
- console.group("Patches:");
1086
- for (const patch of patches) {
1087
- console.log(patch);
1088
- }
1089
- console.groupEnd();
1114
+ log5.debug("Patches:", patches);
1090
1115
  }
1091
1116
  }
1092
1117
 
1093
1118
  // src/router.ts
1094
1119
  init_state();
1120
+ init_logger();
1121
+ var log6 = frameworkLoggers.router;
1095
1122
 
1096
1123
  class HypenRouter {
1097
1124
  state;
@@ -1129,7 +1156,7 @@ class HypenRouter {
1129
1156
  this.updatePath(newPath, false);
1130
1157
  });
1131
1158
  this.isInitialized = true;
1132
- console.log("Router initialized at:", initialPath);
1159
+ log6.debug("Router initialized at:", initialPath);
1133
1160
  }
1134
1161
  getPathFromBrowser() {
1135
1162
  if (typeof window === "undefined")
@@ -1150,21 +1177,21 @@ class HypenRouter {
1150
1177
  return query;
1151
1178
  }
1152
1179
  push(path) {
1153
- console.log("Router.push:", path);
1180
+ log6.debug("push:", path);
1154
1181
  this.updatePath(path, true);
1155
1182
  }
1156
1183
  replace(path) {
1157
- console.log("Router.replace:", path);
1184
+ log6.debug("replace:", path);
1158
1185
  this.updatePath(path, true, true);
1159
1186
  }
1160
1187
  back() {
1161
- console.log("Router.back");
1188
+ log6.debug("back");
1162
1189
  if (typeof window !== "undefined") {
1163
1190
  window.history.back();
1164
1191
  }
1165
1192
  }
1166
1193
  forward() {
1167
- console.log("Router.forward");
1194
+ log6.debug("forward");
1168
1195
  if (typeof window !== "undefined") {
1169
1196
  window.history.forward();
1170
1197
  }
@@ -1260,7 +1287,7 @@ class HypenRouter {
1260
1287
  try {
1261
1288
  callback(this.getState());
1262
1289
  } catch (error) {
1263
- console.error("[HypenRouter] Error in route subscriber:", error);
1290
+ log6.error("Error in route subscriber:", error);
1264
1291
  }
1265
1292
  return () => {
1266
1293
  this.subscribers.delete(callback);
@@ -1272,7 +1299,7 @@ class HypenRouter {
1272
1299
  try {
1273
1300
  callback(routeState);
1274
1301
  } catch (error) {
1275
- console.error("[HypenRouter] Error in route subscriber:", error);
1302
+ log6.error("Error in route subscriber:", error);
1276
1303
  }
1277
1304
  });
1278
1305
  }
@@ -1289,6 +1316,9 @@ class HypenRouter {
1289
1316
  }
1290
1317
 
1291
1318
  // src/events.ts
1319
+ init_logger();
1320
+ var log7 = frameworkLoggers.events;
1321
+
1292
1322
  class TypedEventEmitter {
1293
1323
  eventBus = new Map;
1294
1324
  emit(event, payload) {
@@ -1300,7 +1330,7 @@ class TypedEventEmitter {
1300
1330
  try {
1301
1331
  handler(payload);
1302
1332
  } catch (error) {
1303
- console.error(`Error in event handler for "${String(event)}":`, error);
1333
+ log7.error(`Error in event handler for "${String(event)}":`, error);
1304
1334
  }
1305
1335
  });
1306
1336
  }
@@ -1352,6 +1382,9 @@ function createEventEmitter() {
1352
1382
  }
1353
1383
 
1354
1384
  // src/context.ts
1385
+ init_logger();
1386
+ var log8 = frameworkLoggers.context;
1387
+
1355
1388
  class HypenGlobalContext {
1356
1389
  modules = new Map;
1357
1390
  typedEvents;
@@ -1364,14 +1397,14 @@ class HypenGlobalContext {
1364
1397
  }
1365
1398
  registerModule(id, instance) {
1366
1399
  if (this.modules.has(id)) {
1367
- console.warn(`Module "${id}" is already registered. Overwriting.`);
1400
+ log8.warn(`Module "${id}" is already registered. Overwriting.`);
1368
1401
  }
1369
1402
  this.modules.set(id, instance);
1370
- console.log(`Registered module: ${id}`);
1403
+ log8.debug(`Registered module: ${id}`);
1371
1404
  }
1372
1405
  unregisterModule(id) {
1373
1406
  this.modules.delete(id);
1374
- console.log(`Unregistered module: ${id}`);
1407
+ log8.debug(`Unregistered module: ${id}`);
1375
1408
  }
1376
1409
  getModule(id) {
1377
1410
  const module = this.modules.get(id);
@@ -1400,14 +1433,14 @@ class HypenGlobalContext {
1400
1433
  emit(event, payload) {
1401
1434
  const handlers = this.legacyEventBus.get(event);
1402
1435
  if (!handlers || handlers.size === 0) {
1403
- console.log(`Event "${event}" emitted but no listeners`);
1436
+ log8.debug(`Event "${event}" emitted but no listeners`);
1404
1437
  } else {
1405
- console.log(`Emitting event: ${event}`, payload);
1438
+ log8.debug(`Emitting event: ${event}`, payload);
1406
1439
  handlers.forEach((handler) => {
1407
1440
  try {
1408
1441
  handler(payload);
1409
1442
  } catch (error) {
1410
- console.error(`Error in event handler for "${event}":`, error);
1443
+ log8.error(`Error in event handler for "${event}":`, error);
1411
1444
  }
1412
1445
  });
1413
1446
  }
@@ -1421,7 +1454,7 @@ class HypenGlobalContext {
1421
1454
  }
1422
1455
  const handlers = this.legacyEventBus.get(event);
1423
1456
  handlers.add(handler);
1424
- console.log(`Listening to event: ${event}`);
1457
+ log8.debug(`Listening to event: ${event}`);
1425
1458
  return () => {
1426
1459
  handlers.delete(handler);
1427
1460
  if (handlers.size === 0) {
@@ -1454,10 +1487,9 @@ class HypenGlobalContext {
1454
1487
  }
1455
1488
  }
1456
1489
 
1457
- // src/remote/client.ts
1458
- init_result();
1459
-
1460
1490
  // src/disposable.ts
1491
+ init_logger();
1492
+ var log9 = frameworkLoggers.lifecycle;
1461
1493
  function isDisposable(obj) {
1462
1494
  return obj !== null && typeof obj === "object" && "dispose" in obj && typeof obj.dispose === "function";
1463
1495
  }
@@ -1489,7 +1521,7 @@ class DisposableStack {
1489
1521
  try {
1490
1522
  item.dispose();
1491
1523
  } catch (error) {
1492
- console.error("[DisposableStack] Error during dispose:", error);
1524
+ log9.error("Error during dispose:", error);
1493
1525
  }
1494
1526
  }
1495
1527
  }
@@ -1580,7 +1612,7 @@ function compositeDisposable(...disposables) {
1580
1612
  try {
1581
1613
  d.dispose();
1582
1614
  } catch (error) {
1583
- console.error("[compositeDisposable] Error during dispose:", error);
1615
+ log9.error("Error during dispose:", error);
1584
1616
  }
1585
1617
  }
1586
1618
  }
@@ -1603,6 +1635,9 @@ function usingSync(resource, fn) {
1603
1635
  }
1604
1636
  }
1605
1637
 
1638
+ // src/remote/client.ts
1639
+ init_result();
1640
+
1606
1641
  // src/retry.ts
1607
1642
  init_result();
1608
1643
  var DEFAULT_OPTIONS = {
@@ -1730,6 +1765,9 @@ var RetryPresets = {
1730
1765
  };
1731
1766
 
1732
1767
  // src/remote/client.ts
1768
+ init_logger();
1769
+ var log10 = frameworkLoggers.remote;
1770
+
1733
1771
  class RemoteEngine {
1734
1772
  ws = null;
1735
1773
  url;
@@ -1836,7 +1874,7 @@ class RemoteEngine {
1836
1874
  }
1837
1875
  dispatchAction(action, payload) {
1838
1876
  if (this.state !== "connected" || !this.ws) {
1839
- console.warn("Cannot dispatch action: not connected");
1877
+ log10.warn("Cannot dispatch action: not connected");
1840
1878
  return;
1841
1879
  }
1842
1880
  const message = {
@@ -1909,7 +1947,7 @@ class RemoteEngine {
1909
1947
  break;
1910
1948
  }
1911
1949
  } catch (e) {
1912
- console.error("Error handling remote message:", e);
1950
+ log10.error("Error handling remote message:", e);
1913
1951
  const error = e instanceof Error ? e : new Error(String(e));
1914
1952
  this.errorCallbacks.forEach((cb) => cb(error));
1915
1953
  }
@@ -1938,7 +1976,7 @@ class RemoteEngine {
1938
1976
  }
1939
1977
  handlePatch(message) {
1940
1978
  if (message.revision <= this.currentRevision) {
1941
- console.warn(`Out of order patch: expected > ${this.currentRevision}, got ${message.revision}`);
1979
+ log10.warn(`Out of order patch: expected > ${this.currentRevision}, got ${message.revision}`);
1942
1980
  return;
1943
1981
  }
1944
1982
  this.currentRevision = message.revision;
@@ -1964,1209 +2002,1216 @@ class RemoteEngine {
1964
2002
  maxDelayMs: 30000,
1965
2003
  jitter: 0.1,
1966
2004
  onRetry: (attempt, error) => {
1967
- console.log(`Reconnection attempt ${attempt}/${this.options.maxReconnectAttempts} failed: ${error.message}`);
2005
+ log10.debug(`Reconnection attempt ${attempt}/${this.options.maxReconnectAttempts} failed: ${error.message}`);
1968
2006
  }
1969
2007
  }).catch((error) => {
1970
- console.error("Max reconnection attempts reached:", error.message);
2008
+ log10.error("Max reconnection attempts reached:", error.message);
1971
2009
  this.errorCallbacks.forEach((cb) => cb(new ConnectionError(this.url, error, this.options.maxReconnectAttempts)));
1972
2010
  });
1973
2011
  }, this.options.reconnectInterval);
1974
2012
  }
1975
2013
  }
1976
2014
 
1977
- // src/loader.ts
1978
- import { existsSync, readdirSync, readFileSync } from "fs";
1979
- import { join } from "path";
2015
+ // src/remote/server.ts
2016
+ init_app();
1980
2017
 
1981
- class ComponentLoader {
1982
- components = new Map;
1983
- register(name, module, template, path) {
1984
- this.components.set(name, {
1985
- name,
1986
- module,
1987
- template,
1988
- path: path || name
1989
- });
2018
+ // src/remote/session.ts
2019
+ class SessionManager {
2020
+ activeSessions = new Map;
2021
+ pendingSessions = new Map;
2022
+ sessionConnections = new Map;
2023
+ config;
2024
+ constructor(config2 = {}) {
2025
+ this.config = {
2026
+ ttl: config2.ttl ?? 3600,
2027
+ concurrent: config2.concurrent ?? "kick-old",
2028
+ generateId: config2.generateId ?? (() => crypto.randomUUID())
2029
+ };
1990
2030
  }
1991
- get(name) {
1992
- return this.components.get(name);
2031
+ getTtl() {
2032
+ return this.config.ttl;
1993
2033
  }
1994
- has(name) {
1995
- return this.components.has(name);
2034
+ getConcurrentPolicy() {
2035
+ return this.config.concurrent;
1996
2036
  }
1997
- getNames() {
1998
- return Array.from(this.components.keys());
2037
+ createSession(props) {
2038
+ const now = new Date;
2039
+ const session = {
2040
+ id: this.config.generateId(),
2041
+ ttl: this.config.ttl,
2042
+ createdAt: now,
2043
+ lastConnectedAt: now,
2044
+ props
2045
+ };
2046
+ this.activeSessions.set(session.id, session);
2047
+ return session;
1999
2048
  }
2000
- getAll() {
2001
- return Array.from(this.components.values());
2049
+ getActiveSession(id) {
2050
+ return this.activeSessions.get(id) ?? null;
2002
2051
  }
2003
- clear() {
2004
- this.components.clear();
2052
+ getPendingSession(id) {
2053
+ return this.pendingSessions.get(id) ?? null;
2005
2054
  }
2006
- async loadFromDirectory(name, dirPath) {
2007
- try {
2008
- const modulePath = join(dirPath, "component.ts");
2009
- const moduleExport = await import(modulePath);
2010
- const module = moduleExport.default;
2011
- const templatePath = join(dirPath, "component.hypen");
2012
- const template = readFileSync(templatePath, "utf-8");
2013
- this.register(name, module, template, dirPath);
2014
- console.log(`✓ Loaded component: ${name} from ${dirPath}`);
2015
- } catch (error) {
2016
- console.error(`✗ Failed to load component ${name} from ${dirPath}:`, error);
2017
- throw error;
2018
- }
2055
+ hasSession(id) {
2056
+ return this.activeSessions.has(id) || this.pendingSessions.has(id);
2019
2057
  }
2020
- async loadFromComponentsDir(baseDir) {
2021
- try {
2022
- if (!existsSync(baseDir)) {
2023
- console.warn(`⚠ Components directory not found: ${baseDir}`);
2024
- return;
2025
- }
2026
- const entries = readdirSync(baseDir, { withFileTypes: true });
2027
- for (const entry of entries) {
2028
- if (!entry.isDirectory())
2029
- continue;
2030
- const componentDir = join(baseDir, entry.name);
2031
- const hypenPath = join(componentDir, "component.hypen");
2032
- if (existsSync(hypenPath)) {
2033
- await this.loadFromDirectory(entry.name, componentDir);
2034
- }
2058
+ suspendSession(sessionId, savedState, onExpire) {
2059
+ const session = this.activeSessions.get(sessionId);
2060
+ if (!session)
2061
+ return;
2062
+ this.activeSessions.delete(sessionId);
2063
+ const expiryTimer = setTimeout(async () => {
2064
+ const pending = this.pendingSessions.get(sessionId);
2065
+ if (pending) {
2066
+ this.pendingSessions.delete(sessionId);
2067
+ await onExpire(pending.session);
2035
2068
  }
2036
- console.log(`✓ Loaded ${this.components.size} components from ${baseDir}`);
2037
- } catch (error) {
2038
- console.error(`✗ Failed to load components from ${baseDir}:`, error);
2039
- throw error;
2040
- }
2069
+ }, session.ttl * 1000);
2070
+ this.pendingSessions.set(sessionId, {
2071
+ session,
2072
+ savedState,
2073
+ expiryTimer
2074
+ });
2041
2075
  }
2042
- }
2043
- var componentLoader = new ComponentLoader;
2044
-
2045
- // src/resolver.ts
2046
- class ComponentResolver {
2047
- cache = new Map;
2048
- options;
2049
- constructor(options = {}) {
2050
- this.options = {
2051
- baseDir: options.baseDir || process.cwd(),
2052
- cache: options.cache ?? true,
2053
- customFetch: options.customFetch || this.defaultFetch.bind(this)
2076
+ resumeSession(sessionId) {
2077
+ const pending = this.pendingSessions.get(sessionId);
2078
+ if (!pending)
2079
+ return null;
2080
+ clearTimeout(pending.expiryTimer);
2081
+ this.pendingSessions.delete(sessionId);
2082
+ pending.session.lastConnectedAt = new Date;
2083
+ this.activeSessions.set(sessionId, pending.session);
2084
+ return {
2085
+ session: pending.session,
2086
+ savedState: pending.savedState
2054
2087
  };
2055
2088
  }
2056
- async resolve(importStmt) {
2057
- const sourcePath = this.getSourcePath(importStmt.source);
2058
- if (this.options.cache && this.cache.has(sourcePath)) {
2059
- const cached = this.cache.get(sourcePath);
2060
- return this.extractComponents(importStmt.clause, cached);
2061
- }
2062
- let component;
2063
- if (importStmt.source.type === "local") {
2064
- component = await this.resolveLocal(importStmt.source.path);
2065
- } else {
2066
- component = await this.resolveUrl(importStmt.source.url);
2067
- }
2068
- if (this.options.cache) {
2069
- this.cache.set(sourcePath, component);
2070
- }
2071
- return this.extractComponents(importStmt.clause, component);
2072
- }
2073
- async resolveLocal(path) {
2074
- throw new Error(`Dynamic local component resolution not yet implemented: ${path}
2075
- ` + `Please use the build-components.ts script to generate component imports.`);
2076
- }
2077
- async resolveUrl(url) {
2078
- try {
2079
- const response = await this.options.customFetch(url);
2080
- const data = JSON.parse(response);
2081
- if (!data.module || !data.template) {
2082
- throw new Error(`Invalid component format from ${url}. Expected { module, template }`);
2083
- }
2084
- return data;
2085
- } catch (error) {
2086
- throw new Error(`Failed to resolve component from ${url}: ${error instanceof Error ? error.message : String(error)}`);
2089
+ destroySession(sessionId) {
2090
+ this.activeSessions.delete(sessionId);
2091
+ const pending = this.pendingSessions.get(sessionId);
2092
+ if (pending) {
2093
+ clearTimeout(pending.expiryTimer);
2094
+ this.pendingSessions.delete(sessionId);
2087
2095
  }
2096
+ this.sessionConnections.delete(sessionId);
2088
2097
  }
2089
- async defaultFetch(url) {
2090
- const response = await fetch(url);
2091
- if (!response.ok) {
2092
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2098
+ trackConnection(sessionId, ws) {
2099
+ let connections = this.sessionConnections.get(sessionId);
2100
+ if (!connections) {
2101
+ connections = new Set;
2102
+ this.sessionConnections.set(sessionId, connections);
2093
2103
  }
2094
- return response.text();
2104
+ connections.add(ws);
2095
2105
  }
2096
- extractComponents(clause, component) {
2097
- if (clause.type === "default") {
2098
- return {
2099
- [clause.name]: component
2100
- };
2101
- } else {
2102
- const result = {};
2103
- for (const name of clause.names) {
2104
- result[name] = component;
2106
+ untrackConnection(sessionId, ws) {
2107
+ const connections = this.sessionConnections.get(sessionId);
2108
+ if (connections) {
2109
+ connections.delete(ws);
2110
+ if (connections.size === 0) {
2111
+ this.sessionConnections.delete(sessionId);
2105
2112
  }
2106
- return result;
2107
2113
  }
2108
2114
  }
2109
- getSourcePath(source) {
2110
- return source.type === "local" ? source.path : source.url;
2115
+ getConnections(sessionId) {
2116
+ return this.sessionConnections.get(sessionId);
2111
2117
  }
2112
- clearCache() {
2113
- this.cache.clear();
2118
+ getConnectionCount(sessionId) {
2119
+ return this.sessionConnections.get(sessionId)?.size ?? 0;
2114
2120
  }
2115
- static parseImports(text) {
2116
- const imports = [];
2117
- const importRegex = /import\s+(?:(\{[^}]*\})|(\w+))\s+from\s+["']([^"']+)["']/g;
2118
- let match2;
2119
- while ((match2 = importRegex.exec(text)) !== null) {
2120
- const [, namedImports, defaultImport, source] = match2;
2121
- if (!source)
2122
- continue;
2123
- let clause;
2124
- if (namedImports) {
2125
- const names = namedImports.slice(1, -1).split(",").map((n) => n.trim()).filter((n) => n.length > 0);
2126
- clause = { type: "named", names };
2127
- } else if (defaultImport) {
2128
- clause = { type: "default", name: defaultImport };
2129
- } else {
2130
- continue;
2131
- }
2132
- const sourceObj = source.startsWith("http://") || source.startsWith("https://") ? { type: "url", url: source } : { type: "local", path: source };
2133
- imports.push({ clause, source: sourceObj });
2121
+ getStats() {
2122
+ let totalConnections = 0;
2123
+ for (const connections of this.sessionConnections.values()) {
2124
+ totalConnections += connections.size;
2134
2125
  }
2135
- return imports;
2126
+ return {
2127
+ activeSessions: this.activeSessions.size,
2128
+ pendingSessions: this.pendingSessions.size,
2129
+ totalConnections
2130
+ };
2131
+ }
2132
+ destroy() {
2133
+ for (const pending of this.pendingSessions.values()) {
2134
+ clearTimeout(pending.expiryTimer);
2135
+ }
2136
+ this.activeSessions.clear();
2137
+ this.pendingSessions.clear();
2138
+ this.sessionConnections.clear();
2136
2139
  }
2137
2140
  }
2138
2141
 
2139
- // src/discovery.ts
2140
- import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, watch } from "fs";
2141
- import { join as join2, basename, resolve } from "path";
2142
- function removeImports(text) {
2143
- return text.replace(/import\s+(?:\{[^}]+\}|\w+)\s+from\s+["'][^"']+["']\s*/g, "");
2144
- }
2145
- async function discoverComponents(baseDir, options = {}) {
2146
- const {
2147
- patterns = ["single-file", "folder", "sibling", "index"],
2148
- recursive = false,
2149
- debug = false
2150
- } = options;
2151
- const log3 = debug ? (...args) => console.log("[discovery]", ...args) : () => {};
2152
- const resolvedDir = resolve(baseDir);
2153
- const components = [];
2154
- const seen = new Set;
2155
- log3("Scanning directory:", resolvedDir);
2156
- log3("Patterns:", patterns);
2157
- const addTwoFileComponent = (name, hypenPath, modulePath) => {
2158
- if (seen.has(name)) {
2159
- log3(`Skipping duplicate: ${name}`);
2160
- return;
2142
+ // src/remote/server.ts
2143
+ init_logger();
2144
+ var log11 = frameworkLoggers.remote;
2145
+
2146
+ class RemoteServer {
2147
+ _module = null;
2148
+ _moduleName = "App";
2149
+ _ui = "";
2150
+ _config = {};
2151
+ _sessionConfig = {};
2152
+ _onConnectionCallbacks = [];
2153
+ _onDisconnectionCallbacks = [];
2154
+ clients = new Map;
2155
+ nextClientId = 1;
2156
+ server = null;
2157
+ sessionManager = null;
2158
+ module(name, module) {
2159
+ this._moduleName = name;
2160
+ this._module = module;
2161
+ return this;
2162
+ }
2163
+ ui(dsl) {
2164
+ this._ui = dsl;
2165
+ return this;
2166
+ }
2167
+ config(config2) {
2168
+ this._config = { ...this._config, ...config2 };
2169
+ return this;
2170
+ }
2171
+ session(config2) {
2172
+ this._sessionConfig = config2;
2173
+ return this;
2174
+ }
2175
+ onConnection(callback) {
2176
+ this._onConnectionCallbacks.push(callback);
2177
+ return this;
2178
+ }
2179
+ onDisconnection(callback) {
2180
+ this._onDisconnectionCallbacks.push(callback);
2181
+ return this;
2182
+ }
2183
+ listen(port) {
2184
+ if (!this._module) {
2185
+ throw new Error("Module not set. Call .module() before .listen()");
2161
2186
  }
2162
- seen.add(name);
2163
- const templateRaw = readFileSync2(hypenPath, "utf-8");
2164
- const template = removeImports(templateRaw).trim();
2165
- components.push({
2166
- name,
2167
- hypenPath,
2168
- modulePath,
2169
- template,
2170
- hasModule: modulePath !== null,
2171
- isSingleFile: false
2172
- });
2173
- log3(`Found: ${name} (two-file, ${modulePath ? "with module" : "stateless"})`);
2174
- };
2175
- const addSingleFileComponent = (name, modulePath, template) => {
2176
- if (seen.has(name)) {
2177
- log3(`Skipping duplicate: ${name}`);
2178
- return;
2187
+ if (!this._ui) {
2188
+ throw new Error("UI not set. Call .ui() before .listen()");
2179
2189
  }
2180
- seen.add(name);
2181
- components.push({
2182
- name,
2183
- hypenPath: null,
2184
- modulePath,
2185
- template,
2186
- hasModule: true,
2187
- isSingleFile: true
2188
- });
2189
- log3(`Found: ${name} (single-file with inline template)`);
2190
- };
2191
- const scanForFolderComponents = (dir) => {
2192
- if (!existsSync2(dir))
2193
- return;
2194
- const entries = readdirSync2(dir, { withFileTypes: true });
2195
- for (const entry of entries) {
2196
- if (!entry.isDirectory())
2197
- continue;
2198
- const folderPath = join2(dir, entry.name);
2199
- const componentName = entry.name;
2200
- if (patterns.includes("folder")) {
2201
- const hypenPath = join2(folderPath, "component.hypen");
2202
- if (existsSync2(hypenPath)) {
2203
- const modulePath = join2(folderPath, "component.ts");
2204
- addTwoFileComponent(componentName, hypenPath, existsSync2(modulePath) ? modulePath : null);
2205
- continue;
2190
+ this.sessionManager = new SessionManager(this._sessionConfig);
2191
+ const finalPort = port ?? this._config.port ?? 3000;
2192
+ const hostname = this._config.hostname ?? "0.0.0.0";
2193
+ this.server = Bun.serve({
2194
+ port: finalPort,
2195
+ hostname,
2196
+ websocket: {
2197
+ open: (ws) => this.handleOpen(ws),
2198
+ message: (ws, message) => this.handleMessage(ws, message),
2199
+ close: (ws) => this.handleClose(ws)
2200
+ },
2201
+ fetch: (req, server) => {
2202
+ const url = new URL(req.url);
2203
+ if (server.upgrade(req, { data: undefined })) {
2204
+ return;
2206
2205
  }
2207
- }
2208
- if (patterns.includes("index")) {
2209
- const hypenPath = join2(folderPath, "index.hypen");
2210
- if (existsSync2(hypenPath)) {
2211
- const modulePath = join2(folderPath, "index.ts");
2212
- addTwoFileComponent(componentName, hypenPath, existsSync2(modulePath) ? modulePath : null);
2213
- continue;
2206
+ if (url.pathname === "/health") {
2207
+ return new Response("OK", { status: 200 });
2214
2208
  }
2215
- }
2216
- if (recursive) {
2217
- scanForFolderComponents(folderPath);
2218
- }
2219
- }
2220
- };
2221
- const scanForSiblingComponents = (dir) => {
2222
- if (!existsSync2(dir))
2223
- return;
2224
- const entries = readdirSync2(dir, { withFileTypes: true });
2225
- for (const entry of entries) {
2226
- if (entry.isDirectory()) {
2227
- if (recursive) {
2228
- scanForSiblingComponents(join2(dir, entry.name));
2209
+ if (url.pathname === "/stats") {
2210
+ const stats = this.sessionManager?.getStats() ?? {
2211
+ activeSessions: 0,
2212
+ pendingSessions: 0,
2213
+ totalConnections: 0
2214
+ };
2215
+ return new Response(JSON.stringify(stats), {
2216
+ headers: { "Content-Type": "application/json" }
2217
+ });
2229
2218
  }
2230
- continue;
2219
+ return new Response("Hypen Remote Server", { status: 200 });
2231
2220
  }
2232
- if (!entry.name.endsWith(".hypen"))
2233
- continue;
2234
- const hypenPath = join2(dir, entry.name);
2235
- const baseName = basename(entry.name, ".hypen");
2236
- if (baseName === "component" || baseName === "index")
2237
- continue;
2238
- const modulePath = join2(dir, `${baseName}.ts`);
2239
- addTwoFileComponent(baseName, hypenPath, existsSync2(modulePath) ? modulePath : null);
2221
+ });
2222
+ log11.info(`Hypen app streaming on ws://${hostname}:${finalPort}`);
2223
+ return this;
2224
+ }
2225
+ stop() {
2226
+ if (this.server) {
2227
+ this.server.stop();
2228
+ this.server = null;
2240
2229
  }
2241
- };
2242
- const scanForSingleFileComponents = async (dir) => {
2243
- if (!existsSync2(dir))
2244
- return;
2245
- const entries = readdirSync2(dir, { withFileTypes: true });
2246
- for (const entry of entries) {
2247
- if (entry.isDirectory()) {
2248
- if (recursive) {
2249
- await scanForSingleFileComponents(join2(dir, entry.name));
2250
- }
2251
- continue;
2252
- }
2253
- if (!entry.name.endsWith(".ts"))
2254
- continue;
2255
- if (entry.name.startsWith(".") || entry.name.includes(".test.") || entry.name.includes(".spec."))
2256
- continue;
2257
- const baseName = basename(entry.name, ".ts");
2258
- if (baseName === "component" || baseName === "index")
2259
- continue;
2260
- const hypenPath = join2(dir, `${baseName}.hypen`);
2261
- if (existsSync2(hypenPath))
2262
- continue;
2263
- const modulePath = join2(dir, entry.name);
2264
- const content = readFileSync2(modulePath, "utf-8");
2265
- if (content.includes(".ui(") || content.includes(".ui(hypen")) {
2266
- try {
2267
- const moduleExport = await import(modulePath);
2268
- const module = moduleExport.default;
2269
- if (module && typeof module === "object" && module.template) {
2270
- addSingleFileComponent(baseName, modulePath, module.template);
2271
- }
2272
- } catch (e) {
2273
- log3(`Failed to import potential single-file component: ${entry.name}`, e);
2274
- }
2275
- }
2230
+ if (this.sessionManager) {
2231
+ this.sessionManager.destroy();
2232
+ this.sessionManager = null;
2276
2233
  }
2277
- };
2278
- if (patterns.includes("folder") || patterns.includes("index")) {
2279
- scanForFolderComponents(resolvedDir);
2280
2234
  }
2281
- if (patterns.includes("sibling")) {
2282
- scanForSiblingComponents(resolvedDir);
2235
+ get url() {
2236
+ if (!this.server)
2237
+ return null;
2238
+ const hostname = this._config.hostname ?? "localhost";
2239
+ const port = this._config.port ?? 3000;
2240
+ return `ws://${hostname}:${port}`;
2283
2241
  }
2284
- if (patterns.includes("single-file")) {
2285
- await scanForSingleFileComponents(resolvedDir);
2242
+ async handleOpen(ws) {
2243
+ try {
2244
+ const clientId = `client_${this.nextClientId++}`;
2245
+ const connectedAt = new Date;
2246
+ const engine = new Engine;
2247
+ await engine.init();
2248
+ const moduleInstance = new HypenModuleInstance(engine, this._module);
2249
+ const clientData = {
2250
+ id: clientId,
2251
+ engine,
2252
+ moduleInstance,
2253
+ revision: 0,
2254
+ connectedAt,
2255
+ sessionId: "",
2256
+ helloReceived: false
2257
+ };
2258
+ this.clients.set(ws, clientData);
2259
+ clientData.helloTimeout = setTimeout(() => {
2260
+ if (!clientData.helloReceived) {
2261
+ this.initializeSession(ws, clientData, undefined, undefined);
2262
+ }
2263
+ }, 1000);
2264
+ } catch (error) {
2265
+ log11.error("Error handling WebSocket open:", error);
2266
+ ws.close(1011, "Internal server error");
2267
+ }
2286
2268
  }
2287
- log3(`Discovered ${components.length} components`);
2288
- return components;
2289
- }
2290
- async function loadDiscoveredComponents(components) {
2291
- const { app: app2 } = await Promise.resolve().then(() => (init_app(), exports_app));
2292
- const loaded = new Map;
2293
- for (const component of components) {
2294
- let module;
2295
- let template = component.template;
2296
- if (component.modulePath) {
2297
- const moduleExport = await import(component.modulePath);
2298
- module = moduleExport.default;
2299
- if (component.isSingleFile && module.template) {
2300
- template = module.template;
2269
+ async initializeSession(ws, clientData, requestedSessionId, props) {
2270
+ if (clientData.helloReceived)
2271
+ return;
2272
+ clientData.helloReceived = true;
2273
+ if (clientData.helloTimeout) {
2274
+ clearTimeout(clientData.helloTimeout);
2275
+ clientData.helloTimeout = undefined;
2276
+ }
2277
+ let session;
2278
+ let isNew = true;
2279
+ let isRestored = false;
2280
+ let restoredState = null;
2281
+ if (requestedSessionId && this.sessionManager) {
2282
+ const resumed = this.sessionManager.resumeSession(requestedSessionId);
2283
+ if (resumed) {
2284
+ session = resumed.session;
2285
+ restoredState = resumed.savedState;
2286
+ isNew = false;
2287
+ isRestored = true;
2288
+ await this.triggerReconnect(clientData, session, restoredState);
2289
+ } else {
2290
+ const activeSession = this.sessionManager.getActiveSession(requestedSessionId);
2291
+ if (activeSession) {
2292
+ const handled = await this.handleConcurrentConnection(ws, clientData, activeSession, props);
2293
+ if (!handled)
2294
+ return;
2295
+ session = activeSession;
2296
+ isNew = false;
2297
+ } else {
2298
+ session = this.sessionManager.createSession(props);
2299
+ }
2301
2300
  }
2301
+ } else if (this.sessionManager) {
2302
+ session = this.sessionManager.createSession(props);
2302
2303
  } else {
2303
- module = app2.defineState({}).build();
2304
+ session = {
2305
+ id: crypto.randomUUID(),
2306
+ ttl: 3600,
2307
+ createdAt: new Date,
2308
+ lastConnectedAt: new Date,
2309
+ props
2310
+ };
2304
2311
  }
2305
- loaded.set(component.name, {
2306
- name: component.name,
2307
- module,
2308
- template
2312
+ clientData.sessionId = session.id;
2313
+ this.sessionManager?.trackConnection(session.id, ws);
2314
+ const sessionAck = {
2315
+ type: "sessionAck",
2316
+ sessionId: session.id,
2317
+ isNew,
2318
+ isRestored
2319
+ };
2320
+ ws.send(JSON.stringify(sessionAck));
2321
+ this.setupRenderCallback(ws, clientData);
2322
+ const initialPatches = [];
2323
+ clientData.engine.setRenderCallback((patches) => {
2324
+ initialPatches.push(...patches);
2309
2325
  });
2326
+ clientData.engine.renderSource(this._ui);
2327
+ this.setupRenderCallback(ws, clientData);
2328
+ const initialMessage = {
2329
+ type: "initialTree",
2330
+ module: this._moduleName,
2331
+ state: clientData.moduleInstance.getState(),
2332
+ patches: initialPatches,
2333
+ revision: 0
2334
+ };
2335
+ ws.send(JSON.stringify(initialMessage));
2336
+ const client = {
2337
+ id: clientData.id,
2338
+ socket: ws,
2339
+ connectedAt: clientData.connectedAt
2340
+ };
2341
+ this._onConnectionCallbacks.forEach((cb) => cb(client));
2310
2342
  }
2311
- return loaded;
2312
- }
2313
- function watchComponents(baseDir, options = {}) {
2314
- const resolvedDir = resolve(baseDir);
2315
- const {
2316
- onChange,
2317
- onAdd,
2318
- onRemove,
2319
- onUpdate,
2320
- debug = false,
2321
- ...discoveryOptions
2322
- } = options;
2323
- const log3 = debug ? (...args) => console.log("[discovery:watch]", ...args) : () => {};
2324
- let currentComponents = new Map;
2325
- let debounceTimer = null;
2326
- const initialScan = async () => {
2327
- const components = await discoverComponents(resolvedDir, discoveryOptions);
2328
- currentComponents = new Map(components.map((c) => [c.name, c]));
2329
- onChange?.(components);
2330
- };
2331
- const rescan = async () => {
2332
- if (debounceTimer) {
2333
- clearTimeout(debounceTimer);
2334
- }
2335
- debounceTimer = setTimeout(async () => {
2336
- log3("Rescanning...");
2337
- const newComponents = await discoverComponents(resolvedDir, discoveryOptions);
2338
- const newMap = new Map(newComponents.map((c) => [c.name, c]));
2339
- for (const [name, component] of newMap) {
2340
- const existing = currentComponents.get(name);
2341
- if (!existing) {
2342
- log3("Added:", name);
2343
- onAdd?.(component);
2344
- } else if (existing.template !== component.template || existing.modulePath !== component.modulePath) {
2345
- log3("Updated:", name);
2346
- onUpdate?.(component);
2343
+ setupRenderCallback(ws, clientData) {
2344
+ clientData.engine.setRenderCallback((patches) => {
2345
+ const data = this.clients.get(ws);
2346
+ if (!data)
2347
+ return;
2348
+ data.revision++;
2349
+ const message = {
2350
+ type: "patch",
2351
+ module: this._moduleName,
2352
+ patches,
2353
+ revision: data.revision
2354
+ };
2355
+ ws.send(JSON.stringify(message));
2356
+ if (this.sessionManager?.getConcurrentPolicy() === "allow-multiple" && data.sessionId) {
2357
+ const connections = this.sessionManager.getConnections(data.sessionId);
2358
+ if (connections) {
2359
+ for (const conn of connections) {
2360
+ if (conn !== ws) {
2361
+ conn.send(JSON.stringify(message));
2362
+ }
2363
+ }
2347
2364
  }
2348
2365
  }
2349
- for (const name of currentComponents.keys()) {
2350
- if (!newMap.has(name)) {
2351
- log3("Removed:", name);
2352
- onRemove?.(name);
2366
+ });
2367
+ }
2368
+ async handleConcurrentConnection(ws, clientData, existingSession, props) {
2369
+ const policy = this.sessionManager?.getConcurrentPolicy() ?? "kick-old";
2370
+ switch (policy) {
2371
+ case "kick-old": {
2372
+ const existingConnections = this.sessionManager?.getConnections(existingSession.id);
2373
+ if (existingConnections) {
2374
+ for (const conn of existingConnections) {
2375
+ const oldWs = conn;
2376
+ const expiredMsg = {
2377
+ type: "sessionExpired",
2378
+ sessionId: existingSession.id,
2379
+ reason: "kicked"
2380
+ };
2381
+ oldWs.send(JSON.stringify(expiredMsg));
2382
+ oldWs.close(1000, "Session taken by new connection");
2383
+ }
2353
2384
  }
2385
+ return true;
2354
2386
  }
2355
- currentComponents = newMap;
2356
- onChange?.(newComponents);
2357
- }, 100);
2358
- };
2359
- const watcher = watch(resolvedDir, { recursive: true }, (event, filename) => {
2360
- if (!filename)
2361
- return;
2362
- if (filename.endsWith(".hypen") || filename.endsWith(".ts")) {
2363
- log3("File changed:", filename);
2364
- rescan();
2365
- }
2366
- });
2367
- initialScan();
2368
- return {
2369
- stop: () => {
2370
- watcher.close();
2371
- if (debounceTimer) {
2372
- clearTimeout(debounceTimer);
2387
+ case "reject-new": {
2388
+ const expiredMsg = {
2389
+ type: "sessionExpired",
2390
+ sessionId: existingSession.id,
2391
+ reason: "kicked"
2392
+ };
2393
+ ws.send(JSON.stringify(expiredMsg));
2394
+ ws.close(1000, "Session already active");
2395
+ return false;
2373
2396
  }
2374
- }
2375
- };
2376
- }
2377
- async function generateComponentsCode(baseDir, options = {}) {
2378
- const components = await discoverComponents(baseDir, options);
2379
- const resolvedDir = resolve(baseDir);
2380
- let code = `/**
2381
- * Auto-generated component imports
2382
- * Generated by @hypen-space/core discovery
2383
- */
2384
-
2385
- `;
2386
- for (const component of components) {
2387
- const relativePath = component.modulePath ? "./" + component.modulePath.replace(resolvedDir + "/", "").replace(/\.ts$/, ".js") : null;
2388
- if (relativePath) {
2389
- code += `import ${component.name}Module from "${relativePath}";
2390
- `;
2397
+ case "allow-multiple": {
2398
+ return true;
2399
+ }
2400
+ default:
2401
+ return true;
2391
2402
  }
2392
2403
  }
2393
- code += `
2394
- import { app } from "@hypen-space/core";
2395
-
2396
- `;
2397
- for (const component of components) {
2398
- if (component.isSingleFile) {
2399
- code += `export const ${component.name} = {
2400
- module: ${component.name}Module,
2401
- template: ${component.name}Module.template,
2402
- };
2403
-
2404
- `;
2405
- } else if (component.hasModule) {
2406
- const templateJson = JSON.stringify(component.template);
2407
- code += `export const ${component.name} = {
2408
- module: ${component.name}Module,
2409
- template: ${templateJson},
2410
- };
2411
-
2412
- `;
2413
- } else {
2414
- const templateJson = JSON.stringify(component.template);
2415
- code += `const ${component.name}Module = app.defineState({}).build();
2416
- export const ${component.name} = {
2417
- module: ${component.name}Module,
2418
- template: ${templateJson},
2419
- };
2420
-
2421
- `;
2422
- }
2404
+ async triggerReconnect(clientData, session, savedState) {
2405
+ const handler = this._module?.handlers.onReconnect;
2406
+ if (!handler)
2407
+ return;
2408
+ let restored = false;
2409
+ const restore = (state) => {
2410
+ restored = true;
2411
+ clientData.moduleInstance.updateState(state);
2412
+ };
2413
+ await handler({ session, restore });
2423
2414
  }
2424
- return code;
2425
- }
2426
-
2427
- // src/plugin.ts
2428
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
2429
- import { dirname, basename as basename2, join as join3, resolve as resolve2 } from "path";
2430
- function findModulePath(hypenPath, patterns) {
2431
- const dir = dirname(hypenPath);
2432
- const baseName = basename2(hypenPath, ".hypen");
2433
- const parentDir = dirname(dir);
2434
- const folderName = basename2(dir);
2435
- for (const pattern of patterns) {
2436
- let candidatePath = null;
2437
- switch (pattern) {
2438
- case "sibling":
2439
- candidatePath = join3(dir, `${baseName}.ts`);
2440
- break;
2441
- case "component":
2442
- if (baseName === "component") {
2443
- candidatePath = join3(dir, "component.ts");
2415
+ handleMessage(ws, message) {
2416
+ try {
2417
+ const clientData = this.clients.get(ws);
2418
+ if (!clientData)
2419
+ return;
2420
+ const msg = JSON.parse(message.toString());
2421
+ switch (msg.type) {
2422
+ case "hello": {
2423
+ const helloMsg = msg;
2424
+ this.initializeSession(ws, clientData, helloMsg.sessionId, helloMsg.props);
2425
+ break;
2444
2426
  }
2445
- break;
2446
- case "index":
2447
- if (baseName === "index") {
2448
- candidatePath = join3(dir, "index.ts");
2427
+ case "dispatchAction": {
2428
+ const actionMsg = msg;
2429
+ clientData.engine.dispatchAction(actionMsg.action, actionMsg.payload);
2430
+ break;
2449
2431
  }
2450
- break;
2432
+ default:
2433
+ break;
2434
+ }
2435
+ } catch (error) {
2436
+ log11.error("Error handling WebSocket message:", error);
2451
2437
  }
2452
- if (candidatePath && existsSync3(candidatePath)) {
2453
- return candidatePath;
2438
+ }
2439
+ async handleClose(ws) {
2440
+ const clientData = this.clients.get(ws);
2441
+ if (!clientData)
2442
+ return;
2443
+ if (clientData.helloTimeout) {
2444
+ clearTimeout(clientData.helloTimeout);
2445
+ }
2446
+ const currentState = clientData.moduleInstance.getState();
2447
+ if (clientData.sessionId && this._module?.handlers.onDisconnect) {
2448
+ const session = this.sessionManager?.getActiveSession(clientData.sessionId);
2449
+ if (session) {
2450
+ await this._module.handlers.onDisconnect({
2451
+ state: currentState,
2452
+ session
2453
+ });
2454
+ }
2455
+ }
2456
+ if (clientData.sessionId && this.sessionManager) {
2457
+ this.sessionManager.untrackConnection(clientData.sessionId, ws);
2458
+ if (this.sessionManager.getConnectionCount(clientData.sessionId) === 0) {
2459
+ const session = this.sessionManager.getActiveSession(clientData.sessionId);
2460
+ if (session) {
2461
+ this.sessionManager.suspendSession(clientData.sessionId, currentState, async (expiredSession) => {
2462
+ if (this._module?.handlers.onExpire) {
2463
+ await this._module.handlers.onExpire({ session: expiredSession });
2464
+ }
2465
+ });
2466
+ }
2467
+ }
2454
2468
  }
2469
+ await clientData.moduleInstance.destroy();
2470
+ this.clients.delete(ws);
2471
+ const client = {
2472
+ id: clientData.id,
2473
+ socket: ws,
2474
+ connectedAt: clientData.connectedAt
2475
+ };
2476
+ this._onDisconnectionCallbacks.forEach((cb) => cb(client));
2455
2477
  }
2456
- return null;
2457
- }
2458
- function getComponentName(hypenPath) {
2459
- const baseName = basename2(hypenPath, ".hypen");
2460
- if (baseName === "component" || baseName === "index") {
2461
- return basename2(dirname(hypenPath));
2478
+ getClientCount() {
2479
+ return this.clients.size;
2462
2480
  }
2463
- return baseName;
2464
- }
2465
- function parseImports(text) {
2466
- const imports = [];
2467
- const importRegex = /import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+["']([^"']+)["']/g;
2468
- let match2;
2469
- while ((match2 = importRegex.exec(text)) !== null) {
2470
- const [, namedImports, defaultImport, source] = match2;
2471
- if (!source)
2472
- continue;
2473
- let names;
2474
- if (namedImports) {
2475
- names = namedImports.split(",").map((n) => n.trim()).filter((n) => n.length > 0);
2476
- } else if (defaultImport) {
2477
- names = [defaultImport];
2478
- } else {
2479
- continue;
2480
- }
2481
- imports.push({ names, source });
2481
+ getSessionStats() {
2482
+ return this.sessionManager?.getStats() ?? {
2483
+ activeSessions: 0,
2484
+ pendingSessions: 0,
2485
+ totalConnections: 0
2486
+ };
2487
+ }
2488
+ broadcast(message) {
2489
+ const json = JSON.stringify(message);
2490
+ this.clients.forEach((_, ws) => {
2491
+ ws.send(json);
2492
+ });
2482
2493
  }
2483
- return imports;
2484
- }
2485
- function removeImports2(text) {
2486
- return text.replace(/import\s+(?:\{[^}]+\}|\w+)\s+from\s+["'][^"']+["']\s*/g, "");
2487
- }
2488
- function hypenPlugin(options = {}) {
2489
- const { debug = false, patterns = ["sibling", "component", "index"] } = options;
2490
- const log3 = debug ? (...args) => console.log("[hypen-plugin]", ...args) : () => {};
2491
- return {
2492
- name: "hypen-loader",
2493
- async setup(build) {
2494
- build.onLoad({ filter: /\.hypen$/ }, async (args) => {
2495
- const hypenPath = resolve2(args.path);
2496
- log3("Loading:", hypenPath);
2497
- const templateRaw = readFileSync3(hypenPath, "utf-8");
2498
- const imports = parseImports(templateRaw);
2499
- const template = removeImports2(templateRaw).trim();
2500
- if (imports.length > 0) {
2501
- log3("Found imports:", imports);
2502
- }
2503
- const componentName = getComponentName(hypenPath);
2504
- log3("Component name:", componentName);
2505
- const modulePath = findModulePath(hypenPath, patterns);
2506
- log3("Module path:", modulePath);
2507
- let contents;
2508
- if (modulePath) {
2509
- const relativeModulePath = modulePath.replace(/\.ts$/, ".js");
2510
- contents = `
2511
- import _module from "${relativeModulePath}";
2512
- export const module = _module;
2513
- export const template = ${JSON.stringify(template)};
2514
- export const name = ${JSON.stringify(componentName)};
2515
- export default { module: _module, template: ${JSON.stringify(template)}, name: ${JSON.stringify(componentName)} };
2516
- `;
2517
- } else {
2518
- log3("No module found, creating stateless component");
2519
- contents = `
2520
- import { app } from "@hypen-space/core";
2521
- const _module = app.defineState({}).build();
2522
- export const module = _module;
2523
- export const template = ${JSON.stringify(template)};
2524
- export const name = ${JSON.stringify(componentName)};
2525
- export default { module: _module, template: ${JSON.stringify(template)}, name: ${JSON.stringify(componentName)} };
2526
- `;
2527
- }
2528
- return {
2529
- contents,
2530
- loader: "js"
2531
- };
2532
- });
2533
- }
2534
- };
2535
- }
2536
- var defaultHypenPlugin = hypenPlugin();
2537
- function registerHypenPlugin(options) {
2538
- Bun.plugin(hypenPlugin(options));
2539
2494
  }
2540
- var plugin_default = hypenPlugin;
2541
-
2542
- // src/components/builtin.ts
2543
- init_app();
2544
- var Router = app.defineState({
2545
- currentPath: "/",
2546
- matchedRoute: null,
2547
- routeParams: {}
2548
- }, { name: "__Router" }).onCreated((state, context) => {
2549
- if (!context) {
2550
- console.error("[Router] Requires global context");
2551
- return;
2495
+ function serve(options) {
2496
+ const server = new RemoteServer().module(options.moduleName ?? "App", options.module).ui(options.ui);
2497
+ if (options.port || options.hostname) {
2498
+ server.config({
2499
+ port: options.port,
2500
+ hostname: options.hostname
2501
+ });
2552
2502
  }
2553
- const router = context.__router;
2554
- if (!router) {
2555
- console.error("[Router] Router not found in context");
2556
- return;
2503
+ if (options.session) {
2504
+ server.session(options.session);
2557
2505
  }
2558
- const hypenEngine = context.__hypenEngine;
2559
- const updateRouteVisibility = async (currentPath) => {
2560
- const routeElements = document.querySelectorAll('[data-hypen-type="route"]');
2561
- let matchFound = false;
2562
- for (let index = 0;index < routeElements.length; index++) {
2563
- const routeEl = routeElements[index];
2564
- const htmlEl = routeEl;
2565
- const routePath = htmlEl.dataset.routePath || "/";
2566
- const isMatch = routePath === currentPath;
2567
- htmlEl.style.display = isMatch ? "flex" : "none";
2568
- if (isMatch) {
2569
- matchFound = true;
2570
- const componentName = htmlEl.dataset.routeComponent;
2571
- const isLazy = htmlEl.dataset.routeLazy === "true";
2572
- const hasContent = htmlEl.children.length > 0;
2573
- if (componentName && !hasContent && hypenEngine) {
2574
- try {
2575
- await hypenEngine.renderLazyRoute(routePath, componentName, htmlEl);
2576
- } catch (err) {
2577
- console.error(`[Router] Failed to render route ${routePath}:`, err);
2578
- }
2579
- }
2580
- }
2581
- }
2582
- if (!matchFound) {
2583
- console.warn(`[Router] No route matched path: ${currentPath}. Available routes:`, Array.from(routeElements).map((el) => el.dataset.routePath));
2584
- }
2585
- };
2586
- setTimeout(() => {
2587
- updateRouteVisibility(state.currentPath);
2588
- }, 100);
2589
- router.onNavigate((routeState) => {
2590
- state.currentPath = routeState.currentPath;
2591
- state.routeParams = routeState.params;
2592
- updateRouteVisibility(routeState.currentPath);
2593
- });
2594
- }).build();
2595
- var Route = app.defineState({}, { name: "__Route" }).build();
2596
- var Link = app.defineState({
2597
- to: "/",
2598
- isActive: false
2599
- }, { name: "__Link" }).onAction("navigate", ({ state, context }) => {
2600
- const router = context?.__router;
2601
- if (!router) {
2602
- console.error("[Link] Requires router context");
2603
- return;
2506
+ if (options.onConnection) {
2507
+ server.onConnection(options.onConnection);
2604
2508
  }
2605
- const targetPath = state.to;
2606
- router.push(targetPath);
2607
- }).onCreated((state, context) => {
2608
- if (!context)
2609
- return;
2610
- const router = context.__router;
2611
- if (router) {
2612
- router.onNavigate((routeState) => {
2613
- state.isActive = routeState.currentPath === state.to;
2614
- });
2509
+ if (options.onDisconnection) {
2510
+ server.onDisconnection(options.onDisconnection);
2615
2511
  }
2616
- }).build();
2512
+ return server.listen(options.port);
2513
+ }
2617
2514
 
2618
- // src/index.ts
2619
- init_app();
2515
+ // src/loader.ts
2516
+ init_logger();
2517
+ import { existsSync, readdirSync, readFileSync } from "fs";
2518
+ import { join } from "path";
2519
+ var log12 = frameworkLoggers.loader;
2620
2520
 
2621
- // src/hypen.ts
2622
- function createBindingProxy(root) {
2623
- const handler = {
2624
- get(_, prop) {
2625
- if (prop === Symbol.toPrimitive || prop === "toString" || prop === "valueOf") {
2626
- return () => `\${${root}}`;
2627
- }
2628
- if (typeof prop === "symbol") {
2521
+ class ComponentLoader {
2522
+ components = new Map;
2523
+ register(name, module, template, path) {
2524
+ this.components.set(name, {
2525
+ name,
2526
+ module,
2527
+ template,
2528
+ path: path || name
2529
+ });
2530
+ }
2531
+ get(name) {
2532
+ return this.components.get(name);
2533
+ }
2534
+ has(name) {
2535
+ return this.components.has(name);
2536
+ }
2537
+ getNames() {
2538
+ return Array.from(this.components.keys());
2539
+ }
2540
+ getAll() {
2541
+ return Array.from(this.components.values());
2542
+ }
2543
+ clear() {
2544
+ this.components.clear();
2545
+ }
2546
+ async loadFromDirectory(name, dirPath) {
2547
+ try {
2548
+ const modulePath = join(dirPath, "component.ts");
2549
+ const moduleExport = await import(modulePath);
2550
+ const module = moduleExport.default;
2551
+ const templatePath = join(dirPath, "component.hypen");
2552
+ const template = readFileSync(templatePath, "utf-8");
2553
+ this.register(name, module, template, dirPath);
2554
+ log12.debug(`Loaded component: ${name} from ${dirPath}`);
2555
+ } catch (error) {
2556
+ log12.error(`Failed to load component ${name} from ${dirPath}:`, error);
2557
+ throw error;
2558
+ }
2559
+ }
2560
+ async loadFromComponentsDir(baseDir) {
2561
+ try {
2562
+ if (!existsSync(baseDir)) {
2563
+ log12.warn(`Components directory not found: ${baseDir}`);
2629
2564
  return;
2630
2565
  }
2631
- if (prop === "toJSON") {
2632
- return () => `\${${root}}`;
2566
+ const entries = readdirSync(baseDir, { withFileTypes: true });
2567
+ for (const entry of entries) {
2568
+ if (!entry.isDirectory())
2569
+ continue;
2570
+ const componentDir = join(baseDir, entry.name);
2571
+ const hypenPath = join(componentDir, "component.hypen");
2572
+ if (existsSync(hypenPath)) {
2573
+ await this.loadFromDirectory(entry.name, componentDir);
2574
+ }
2633
2575
  }
2634
- return createBindingProxy(`${root}.${prop}`);
2635
- },
2636
- has() {
2637
- return true;
2638
- },
2639
- ownKeys() {
2640
- return [];
2641
- },
2642
- getOwnPropertyDescriptor() {
2643
- return {
2644
- configurable: true,
2645
- enumerable: true
2646
- };
2576
+ log12.debug(`Loaded ${this.components.size} components from ${baseDir}`);
2577
+ } catch (error) {
2578
+ log12.error(`Failed to load components from ${baseDir}:`, error);
2579
+ throw error;
2647
2580
  }
2648
- };
2649
- return new Proxy({}, handler);
2650
- }
2651
- var state = createBindingProxy("state");
2652
- var item = createBindingProxy("item");
2653
- var index = {
2654
- [Symbol.toPrimitive]: () => "${index}",
2655
- toString: () => "${index}",
2656
- valueOf: () => "${index}",
2657
- toJSON: () => "${index}"
2658
- };
2659
- function hypen(strings, ...expressions) {
2660
- let result = strings[0];
2661
- for (let i = 0;i < expressions.length; i++) {
2662
- const expr = expressions[i];
2663
- result += String(expr);
2664
- result += strings[i + 1];
2665
2581
  }
2666
- return result.trim();
2667
2582
  }
2583
+ var componentLoader = new ComponentLoader;
2668
2584
 
2669
- // src/index.ts
2670
- init_state();
2671
-
2672
- // src/remote/server.ts
2673
- init_app();
2674
-
2675
- // src/remote/session.ts
2676
- class SessionManager {
2677
- activeSessions = new Map;
2678
- pendingSessions = new Map;
2679
- sessionConnections = new Map;
2680
- config;
2681
- constructor(config2 = {}) {
2682
- this.config = {
2683
- ttl: config2.ttl ?? 3600,
2684
- concurrent: config2.concurrent ?? "kick-old",
2685
- generateId: config2.generateId ?? (() => crypto.randomUUID())
2686
- };
2687
- }
2688
- getTtl() {
2689
- return this.config.ttl;
2690
- }
2691
- getConcurrentPolicy() {
2692
- return this.config.concurrent;
2693
- }
2694
- createSession(props) {
2695
- const now = new Date;
2696
- const session = {
2697
- id: this.config.generateId(),
2698
- ttl: this.config.ttl,
2699
- createdAt: now,
2700
- lastConnectedAt: now,
2701
- props
2585
+ // src/resolver.ts
2586
+ class ComponentResolver {
2587
+ cache = new Map;
2588
+ options;
2589
+ constructor(options = {}) {
2590
+ this.options = {
2591
+ baseDir: options.baseDir || process.cwd(),
2592
+ cache: options.cache ?? true,
2593
+ customFetch: options.customFetch || this.defaultFetch.bind(this)
2702
2594
  };
2703
- this.activeSessions.set(session.id, session);
2704
- return session;
2705
- }
2706
- getActiveSession(id) {
2707
- return this.activeSessions.get(id) ?? null;
2708
2595
  }
2709
- getPendingSession(id) {
2710
- return this.pendingSessions.get(id) ?? null;
2596
+ async resolve(importStmt) {
2597
+ const sourcePath = this.getSourcePath(importStmt.source);
2598
+ if (this.options.cache && this.cache.has(sourcePath)) {
2599
+ const cached = this.cache.get(sourcePath);
2600
+ return this.extractComponents(importStmt.clause, cached);
2601
+ }
2602
+ let component;
2603
+ if (importStmt.source.type === "local") {
2604
+ component = await this.resolveLocal(importStmt.source.path);
2605
+ } else {
2606
+ component = await this.resolveUrl(importStmt.source.url);
2607
+ }
2608
+ if (this.options.cache) {
2609
+ this.cache.set(sourcePath, component);
2610
+ }
2611
+ return this.extractComponents(importStmt.clause, component);
2711
2612
  }
2712
- hasSession(id) {
2713
- return this.activeSessions.has(id) || this.pendingSessions.has(id);
2613
+ async resolveLocal(path) {
2614
+ throw new Error(`Dynamic local component resolution not yet implemented: ${path}
2615
+ ` + `Please use the build-components.ts script to generate component imports.`);
2714
2616
  }
2715
- suspendSession(sessionId, savedState, onExpire) {
2716
- const session = this.activeSessions.get(sessionId);
2717
- if (!session)
2718
- return;
2719
- this.activeSessions.delete(sessionId);
2720
- const expiryTimer = setTimeout(async () => {
2721
- const pending = this.pendingSessions.get(sessionId);
2722
- if (pending) {
2723
- this.pendingSessions.delete(sessionId);
2724
- await onExpire(pending.session);
2617
+ async resolveUrl(url) {
2618
+ try {
2619
+ const response = await this.options.customFetch(url);
2620
+ const data = JSON.parse(response);
2621
+ if (!data.module || !data.template) {
2622
+ throw new Error(`Invalid component format from ${url}. Expected { module, template }`);
2725
2623
  }
2726
- }, session.ttl * 1000);
2727
- this.pendingSessions.set(sessionId, {
2728
- session,
2729
- savedState,
2730
- expiryTimer
2731
- });
2732
- }
2733
- resumeSession(sessionId) {
2734
- const pending = this.pendingSessions.get(sessionId);
2735
- if (!pending)
2736
- return null;
2737
- clearTimeout(pending.expiryTimer);
2738
- this.pendingSessions.delete(sessionId);
2739
- pending.session.lastConnectedAt = new Date;
2740
- this.activeSessions.set(sessionId, pending.session);
2741
- return {
2742
- session: pending.session,
2743
- savedState: pending.savedState
2744
- };
2745
- }
2746
- destroySession(sessionId) {
2747
- this.activeSessions.delete(sessionId);
2748
- const pending = this.pendingSessions.get(sessionId);
2749
- if (pending) {
2750
- clearTimeout(pending.expiryTimer);
2751
- this.pendingSessions.delete(sessionId);
2624
+ return data;
2625
+ } catch (error) {
2626
+ throw new Error(`Failed to resolve component from ${url}: ${error instanceof Error ? error.message : String(error)}`);
2752
2627
  }
2753
- this.sessionConnections.delete(sessionId);
2754
2628
  }
2755
- trackConnection(sessionId, ws) {
2756
- let connections = this.sessionConnections.get(sessionId);
2757
- if (!connections) {
2758
- connections = new Set;
2759
- this.sessionConnections.set(sessionId, connections);
2629
+ async defaultFetch(url) {
2630
+ const response = await fetch(url);
2631
+ if (!response.ok) {
2632
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2760
2633
  }
2761
- connections.add(ws);
2634
+ return response.text();
2762
2635
  }
2763
- untrackConnection(sessionId, ws) {
2764
- const connections = this.sessionConnections.get(sessionId);
2765
- if (connections) {
2766
- connections.delete(ws);
2767
- if (connections.size === 0) {
2768
- this.sessionConnections.delete(sessionId);
2636
+ extractComponents(clause, component) {
2637
+ if (clause.type === "default") {
2638
+ return {
2639
+ [clause.name]: component
2640
+ };
2641
+ } else {
2642
+ const result = {};
2643
+ for (const name of clause.names) {
2644
+ result[name] = component;
2769
2645
  }
2646
+ return result;
2770
2647
  }
2771
2648
  }
2772
- getConnections(sessionId) {
2773
- return this.sessionConnections.get(sessionId);
2774
- }
2775
- getConnectionCount(sessionId) {
2776
- return this.sessionConnections.get(sessionId)?.size ?? 0;
2649
+ getSourcePath(source) {
2650
+ return source.type === "local" ? source.path : source.url;
2777
2651
  }
2778
- getStats() {
2779
- let totalConnections = 0;
2780
- for (const connections of this.sessionConnections.values()) {
2781
- totalConnections += connections.size;
2782
- }
2783
- return {
2784
- activeSessions: this.activeSessions.size,
2785
- pendingSessions: this.pendingSessions.size,
2786
- totalConnections
2787
- };
2652
+ clearCache() {
2653
+ this.cache.clear();
2788
2654
  }
2789
- destroy() {
2790
- for (const pending of this.pendingSessions.values()) {
2791
- clearTimeout(pending.expiryTimer);
2655
+ static parseImports(text) {
2656
+ const imports = [];
2657
+ const importRegex = /import\s+(?:(\{[^}]*\})|(\w+))\s+from\s+["']([^"']+)["']/g;
2658
+ let match2;
2659
+ while ((match2 = importRegex.exec(text)) !== null) {
2660
+ const [, namedImports, defaultImport, source] = match2;
2661
+ if (!source)
2662
+ continue;
2663
+ let clause;
2664
+ if (namedImports) {
2665
+ const names = namedImports.slice(1, -1).split(",").map((n) => n.trim()).filter((n) => n.length > 0);
2666
+ clause = { type: "named", names };
2667
+ } else if (defaultImport) {
2668
+ clause = { type: "default", name: defaultImport };
2669
+ } else {
2670
+ continue;
2671
+ }
2672
+ const sourceObj = source.startsWith("http://") || source.startsWith("https://") ? { type: "url", url: source } : { type: "local", path: source };
2673
+ imports.push({ clause, source: sourceObj });
2792
2674
  }
2793
- this.activeSessions.clear();
2794
- this.pendingSessions.clear();
2795
- this.sessionConnections.clear();
2675
+ return imports;
2796
2676
  }
2797
2677
  }
2798
2678
 
2799
- // src/remote/server.ts
2800
- class RemoteServer {
2801
- _module = null;
2802
- _moduleName = "App";
2803
- _ui = "";
2804
- _config = {};
2805
- _sessionConfig = {};
2806
- _onConnectionCallbacks = [];
2807
- _onDisconnectionCallbacks = [];
2808
- clients = new Map;
2809
- nextClientId = 1;
2810
- server = null;
2811
- sessionManager = null;
2812
- module(name, module) {
2813
- this._moduleName = name;
2814
- this._module = module;
2815
- return this;
2816
- }
2817
- ui(dsl) {
2818
- this._ui = dsl;
2819
- return this;
2820
- }
2821
- config(config2) {
2822
- this._config = { ...this._config, ...config2 };
2823
- return this;
2824
- }
2825
- session(config2) {
2826
- this._sessionConfig = config2;
2827
- return this;
2828
- }
2829
- onConnection(callback) {
2830
- this._onConnectionCallbacks.push(callback);
2831
- return this;
2832
- }
2833
- onDisconnection(callback) {
2834
- this._onDisconnectionCallbacks.push(callback);
2835
- return this;
2836
- }
2837
- listen(port) {
2838
- if (!this._module) {
2839
- throw new Error("Module not set. Call .module() before .listen()");
2840
- }
2841
- if (!this._ui) {
2842
- throw new Error("UI not set. Call .ui() before .listen()");
2679
+ // src/discovery.ts
2680
+ init_logger();
2681
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, watch } from "fs";
2682
+ import { join as join2, basename, resolve } from "path";
2683
+ function removeImports(text) {
2684
+ return text.replace(/import\s+(?:\{[^}]+\}|\w+)\s+from\s+["'][^"']+["']\s*/g, "");
2685
+ }
2686
+ async function discoverComponents(baseDir, options = {}) {
2687
+ const {
2688
+ patterns = ["single-file", "folder", "sibling", "index"],
2689
+ recursive = false,
2690
+ debug = false
2691
+ } = options;
2692
+ const log13 = debug ? (...args) => frameworkLoggers.discovery.debug(...args) : () => {};
2693
+ const resolvedDir = resolve(baseDir);
2694
+ const components = [];
2695
+ const seen = new Set;
2696
+ log13("Scanning directory:", resolvedDir);
2697
+ log13("Patterns:", patterns);
2698
+ const addTwoFileComponent = (name, hypenPath, modulePath) => {
2699
+ if (seen.has(name)) {
2700
+ log13(`Skipping duplicate: ${name}`);
2701
+ return;
2843
2702
  }
2844
- this.sessionManager = new SessionManager(this._sessionConfig);
2845
- const finalPort = port ?? this._config.port ?? 3000;
2846
- const hostname = this._config.hostname ?? "0.0.0.0";
2847
- this.server = Bun.serve({
2848
- port: finalPort,
2849
- hostname,
2850
- websocket: {
2851
- open: (ws) => this.handleOpen(ws),
2852
- message: (ws, message) => this.handleMessage(ws, message),
2853
- close: (ws) => this.handleClose(ws)
2854
- },
2855
- fetch: (req, server) => {
2856
- const url = new URL(req.url);
2857
- if (server.upgrade(req, { data: undefined })) {
2858
- return;
2703
+ seen.add(name);
2704
+ const templateRaw = readFileSync2(hypenPath, "utf-8");
2705
+ const template = removeImports(templateRaw).trim();
2706
+ components.push({
2707
+ name,
2708
+ hypenPath,
2709
+ modulePath,
2710
+ template,
2711
+ hasModule: modulePath !== null,
2712
+ isSingleFile: false
2713
+ });
2714
+ log13(`Found: ${name} (two-file, ${modulePath ? "with module" : "stateless"})`);
2715
+ };
2716
+ const addSingleFileComponent = (name, modulePath, template) => {
2717
+ if (seen.has(name)) {
2718
+ log13(`Skipping duplicate: ${name}`);
2719
+ return;
2720
+ }
2721
+ seen.add(name);
2722
+ components.push({
2723
+ name,
2724
+ hypenPath: null,
2725
+ modulePath,
2726
+ template,
2727
+ hasModule: true,
2728
+ isSingleFile: true
2729
+ });
2730
+ log13(`Found: ${name} (single-file with inline template)`);
2731
+ };
2732
+ const scanForFolderComponents = (dir) => {
2733
+ if (!existsSync2(dir))
2734
+ return;
2735
+ const entries = readdirSync2(dir, { withFileTypes: true });
2736
+ for (const entry of entries) {
2737
+ if (!entry.isDirectory())
2738
+ continue;
2739
+ const folderPath = join2(dir, entry.name);
2740
+ const componentName = entry.name;
2741
+ if (patterns.includes("folder")) {
2742
+ const hypenPath = join2(folderPath, "component.hypen");
2743
+ if (existsSync2(hypenPath)) {
2744
+ const modulePath = join2(folderPath, "component.ts");
2745
+ addTwoFileComponent(componentName, hypenPath, existsSync2(modulePath) ? modulePath : null);
2746
+ continue;
2859
2747
  }
2860
- if (url.pathname === "/health") {
2861
- return new Response("OK", { status: 200 });
2748
+ }
2749
+ if (patterns.includes("index")) {
2750
+ const hypenPath = join2(folderPath, "index.hypen");
2751
+ if (existsSync2(hypenPath)) {
2752
+ const modulePath = join2(folderPath, "index.ts");
2753
+ addTwoFileComponent(componentName, hypenPath, existsSync2(modulePath) ? modulePath : null);
2754
+ continue;
2862
2755
  }
2863
- if (url.pathname === "/stats") {
2864
- const stats = this.sessionManager?.getStats() ?? {
2865
- activeSessions: 0,
2866
- pendingSessions: 0,
2867
- totalConnections: 0
2868
- };
2869
- return new Response(JSON.stringify(stats), {
2870
- headers: { "Content-Type": "application/json" }
2871
- });
2756
+ }
2757
+ if (recursive) {
2758
+ scanForFolderComponents(folderPath);
2759
+ }
2760
+ }
2761
+ };
2762
+ const scanForSiblingComponents = (dir) => {
2763
+ if (!existsSync2(dir))
2764
+ return;
2765
+ const entries = readdirSync2(dir, { withFileTypes: true });
2766
+ for (const entry of entries) {
2767
+ if (entry.isDirectory()) {
2768
+ if (recursive) {
2769
+ scanForSiblingComponents(join2(dir, entry.name));
2872
2770
  }
2873
- return new Response("Hypen Remote Server", { status: 200 });
2771
+ continue;
2874
2772
  }
2875
- });
2876
- console.log(`\uD83D\uDE80 Hypen app streaming on ws://${hostname}:${finalPort}`);
2877
- return this;
2878
- }
2879
- stop() {
2880
- if (this.server) {
2881
- this.server.stop();
2882
- this.server = null;
2773
+ if (!entry.name.endsWith(".hypen"))
2774
+ continue;
2775
+ const hypenPath = join2(dir, entry.name);
2776
+ const baseName = basename(entry.name, ".hypen");
2777
+ if (baseName === "component" || baseName === "index")
2778
+ continue;
2779
+ const modulePath = join2(dir, `${baseName}.ts`);
2780
+ addTwoFileComponent(baseName, hypenPath, existsSync2(modulePath) ? modulePath : null);
2883
2781
  }
2884
- if (this.sessionManager) {
2885
- this.sessionManager.destroy();
2886
- this.sessionManager = null;
2782
+ };
2783
+ const scanForSingleFileComponents = async (dir) => {
2784
+ if (!existsSync2(dir))
2785
+ return;
2786
+ const entries = readdirSync2(dir, { withFileTypes: true });
2787
+ for (const entry of entries) {
2788
+ if (entry.isDirectory()) {
2789
+ if (recursive) {
2790
+ await scanForSingleFileComponents(join2(dir, entry.name));
2791
+ }
2792
+ continue;
2793
+ }
2794
+ if (!entry.name.endsWith(".ts"))
2795
+ continue;
2796
+ if (entry.name.startsWith(".") || entry.name.includes(".test.") || entry.name.includes(".spec."))
2797
+ continue;
2798
+ const baseName = basename(entry.name, ".ts");
2799
+ if (baseName === "component" || baseName === "index")
2800
+ continue;
2801
+ const hypenPath = join2(dir, `${baseName}.hypen`);
2802
+ if (existsSync2(hypenPath))
2803
+ continue;
2804
+ const modulePath = join2(dir, entry.name);
2805
+ const content = readFileSync2(modulePath, "utf-8");
2806
+ if (content.includes(".ui(") || content.includes(".ui(hypen")) {
2807
+ try {
2808
+ const moduleExport = await import(modulePath);
2809
+ const module = moduleExport.default;
2810
+ if (module && typeof module === "object" && module.template) {
2811
+ addSingleFileComponent(baseName, modulePath, module.template);
2812
+ }
2813
+ } catch (e) {
2814
+ log13(`Failed to import potential single-file component: ${entry.name}`, e);
2815
+ }
2816
+ }
2887
2817
  }
2818
+ };
2819
+ if (patterns.includes("folder") || patterns.includes("index")) {
2820
+ scanForFolderComponents(resolvedDir);
2888
2821
  }
2889
- get url() {
2890
- if (!this.server)
2891
- return null;
2892
- const hostname = this._config.hostname ?? "localhost";
2893
- const port = this._config.port ?? 3000;
2894
- return `ws://${hostname}:${port}`;
2822
+ if (patterns.includes("sibling")) {
2823
+ scanForSiblingComponents(resolvedDir);
2895
2824
  }
2896
- async handleOpen(ws) {
2897
- try {
2898
- const clientId = `client_${this.nextClientId++}`;
2899
- const connectedAt = new Date;
2900
- const engine = new Engine;
2901
- await engine.init();
2902
- const moduleInstance = new HypenModuleInstance(engine, this._module);
2903
- const clientData = {
2904
- id: clientId,
2905
- engine,
2906
- moduleInstance,
2907
- revision: 0,
2908
- connectedAt,
2909
- sessionId: "",
2910
- helloReceived: false
2911
- };
2912
- this.clients.set(ws, clientData);
2913
- clientData.helloTimeout = setTimeout(() => {
2914
- if (!clientData.helloReceived) {
2915
- this.initializeSession(ws, clientData, undefined, undefined);
2916
- }
2917
- }, 1000);
2918
- } catch (error) {
2919
- console.error("Error handling WebSocket open:", error);
2920
- ws.close(1011, "Internal server error");
2825
+ if (patterns.includes("single-file")) {
2826
+ await scanForSingleFileComponents(resolvedDir);
2827
+ }
2828
+ log13(`Discovered ${components.length} components`);
2829
+ return components;
2830
+ }
2831
+ async function loadDiscoveredComponents(components) {
2832
+ const { app: app2 } = await Promise.resolve().then(() => (init_app(), exports_app));
2833
+ const loaded = new Map;
2834
+ for (const component of components) {
2835
+ let module;
2836
+ let template = component.template;
2837
+ if (component.modulePath) {
2838
+ const moduleExport = await import(component.modulePath);
2839
+ module = moduleExport.default;
2840
+ if (component.isSingleFile && module.template) {
2841
+ template = module.template;
2842
+ }
2843
+ } else {
2844
+ module = app2.defineState({}).build();
2921
2845
  }
2846
+ loaded.set(component.name, {
2847
+ name: component.name,
2848
+ module,
2849
+ template
2850
+ });
2922
2851
  }
2923
- async initializeSession(ws, clientData, requestedSessionId, props) {
2924
- if (clientData.helloReceived)
2925
- return;
2926
- clientData.helloReceived = true;
2927
- if (clientData.helloTimeout) {
2928
- clearTimeout(clientData.helloTimeout);
2929
- clientData.helloTimeout = undefined;
2852
+ return loaded;
2853
+ }
2854
+ function watchComponents(baseDir, options = {}) {
2855
+ const resolvedDir = resolve(baseDir);
2856
+ const {
2857
+ onChange,
2858
+ onAdd,
2859
+ onRemove,
2860
+ onUpdate,
2861
+ debug = false,
2862
+ ...discoveryOptions
2863
+ } = options;
2864
+ const log13 = debug ? (...args) => frameworkLoggers.discovery.debug("[watch]", ...args) : () => {};
2865
+ let currentComponents = new Map;
2866
+ let debounceTimer = null;
2867
+ const initialScan = async () => {
2868
+ const components = await discoverComponents(resolvedDir, discoveryOptions);
2869
+ currentComponents = new Map(components.map((c) => [c.name, c]));
2870
+ onChange?.(components);
2871
+ };
2872
+ const rescan = async () => {
2873
+ if (debounceTimer) {
2874
+ clearTimeout(debounceTimer);
2930
2875
  }
2931
- let session;
2932
- let isNew = true;
2933
- let isRestored = false;
2934
- let restoredState = null;
2935
- if (requestedSessionId && this.sessionManager) {
2936
- const resumed = this.sessionManager.resumeSession(requestedSessionId);
2937
- if (resumed) {
2938
- session = resumed.session;
2939
- restoredState = resumed.savedState;
2940
- isNew = false;
2941
- isRestored = true;
2942
- await this.triggerReconnect(clientData, session, restoredState);
2943
- } else {
2944
- const activeSession = this.sessionManager.getActiveSession(requestedSessionId);
2945
- if (activeSession) {
2946
- const handled = await this.handleConcurrentConnection(ws, clientData, activeSession, props);
2947
- if (!handled)
2948
- return;
2949
- session = activeSession;
2950
- isNew = false;
2951
- } else {
2952
- session = this.sessionManager.createSession(props);
2876
+ debounceTimer = setTimeout(async () => {
2877
+ log13("Rescanning...");
2878
+ const newComponents = await discoverComponents(resolvedDir, discoveryOptions);
2879
+ const newMap = new Map(newComponents.map((c) => [c.name, c]));
2880
+ for (const [name, component] of newMap) {
2881
+ const existing = currentComponents.get(name);
2882
+ if (!existing) {
2883
+ log13("Added:", name);
2884
+ onAdd?.(component);
2885
+ } else if (existing.template !== component.template || existing.modulePath !== component.modulePath) {
2886
+ log13("Updated:", name);
2887
+ onUpdate?.(component);
2953
2888
  }
2954
2889
  }
2955
- } else if (this.sessionManager) {
2956
- session = this.sessionManager.createSession(props);
2890
+ for (const name of currentComponents.keys()) {
2891
+ if (!newMap.has(name)) {
2892
+ log13("Removed:", name);
2893
+ onRemove?.(name);
2894
+ }
2895
+ }
2896
+ currentComponents = newMap;
2897
+ onChange?.(newComponents);
2898
+ }, 100);
2899
+ };
2900
+ const watcher = watch(resolvedDir, { recursive: true }, (event, filename) => {
2901
+ if (!filename)
2902
+ return;
2903
+ if (filename.endsWith(".hypen") || filename.endsWith(".ts")) {
2904
+ log13("File changed:", filename);
2905
+ rescan();
2906
+ }
2907
+ });
2908
+ initialScan();
2909
+ return {
2910
+ stop: () => {
2911
+ watcher.close();
2912
+ if (debounceTimer) {
2913
+ clearTimeout(debounceTimer);
2914
+ }
2915
+ }
2916
+ };
2917
+ }
2918
+ async function generateComponentsCode(baseDir, options = {}) {
2919
+ const components = await discoverComponents(baseDir, options);
2920
+ const resolvedDir = resolve(baseDir);
2921
+ let code = `/**
2922
+ * Auto-generated component imports
2923
+ * Generated by @hypen-space/core discovery
2924
+ */
2925
+
2926
+ `;
2927
+ for (const component of components) {
2928
+ const relativePath = component.modulePath ? "./" + component.modulePath.replace(resolvedDir + "/", "").replace(/\.ts$/, ".js") : null;
2929
+ if (relativePath) {
2930
+ code += `import ${component.name}Module from "${relativePath}";
2931
+ `;
2932
+ }
2933
+ }
2934
+ code += `
2935
+ import { app } from "@hypen-space/core";
2936
+
2937
+ `;
2938
+ for (const component of components) {
2939
+ if (component.isSingleFile) {
2940
+ code += `export const ${component.name} = {
2941
+ module: ${component.name}Module,
2942
+ template: ${component.name}Module.template,
2943
+ };
2944
+
2945
+ `;
2946
+ } else if (component.hasModule) {
2947
+ const templateJson = JSON.stringify(component.template);
2948
+ code += `export const ${component.name} = {
2949
+ module: ${component.name}Module,
2950
+ template: ${templateJson},
2951
+ };
2952
+
2953
+ `;
2957
2954
  } else {
2958
- session = {
2959
- id: crypto.randomUUID(),
2960
- ttl: 3600,
2961
- createdAt: new Date,
2962
- lastConnectedAt: new Date,
2963
- props
2964
- };
2955
+ const templateJson = JSON.stringify(component.template);
2956
+ code += `const ${component.name}Module = app.defineState({}).build();
2957
+ export const ${component.name} = {
2958
+ module: ${component.name}Module,
2959
+ template: ${templateJson},
2960
+ };
2961
+
2962
+ `;
2965
2963
  }
2966
- clientData.sessionId = session.id;
2967
- this.sessionManager?.trackConnection(session.id, ws);
2968
- const sessionAck = {
2969
- type: "sessionAck",
2970
- sessionId: session.id,
2971
- isNew,
2972
- isRestored
2973
- };
2974
- ws.send(JSON.stringify(sessionAck));
2975
- this.setupRenderCallback(ws, clientData);
2976
- const initialPatches = [];
2977
- clientData.engine.setRenderCallback((patches) => {
2978
- initialPatches.push(...patches);
2979
- });
2980
- clientData.engine.renderSource(this._ui);
2981
- this.setupRenderCallback(ws, clientData);
2982
- const initialMessage = {
2983
- type: "initialTree",
2984
- module: this._moduleName,
2985
- state: clientData.moduleInstance.getState(),
2986
- patches: initialPatches,
2987
- revision: 0
2988
- };
2989
- ws.send(JSON.stringify(initialMessage));
2990
- const client = {
2991
- id: clientData.id,
2992
- socket: ws,
2993
- connectedAt: clientData.connectedAt
2994
- };
2995
- this._onConnectionCallbacks.forEach((cb) => cb(client));
2996
2964
  }
2997
- setupRenderCallback(ws, clientData) {
2998
- clientData.engine.setRenderCallback((patches) => {
2999
- const data = this.clients.get(ws);
3000
- if (!data)
3001
- return;
3002
- data.revision++;
3003
- const message = {
3004
- type: "patch",
3005
- module: this._moduleName,
3006
- patches,
3007
- revision: data.revision
3008
- };
3009
- ws.send(JSON.stringify(message));
3010
- if (this.sessionManager?.getConcurrentPolicy() === "allow-multiple" && data.sessionId) {
3011
- const connections = this.sessionManager.getConnections(data.sessionId);
3012
- if (connections) {
3013
- for (const conn of connections) {
3014
- if (conn !== ws) {
3015
- conn.send(JSON.stringify(message));
3016
- }
3017
- }
2965
+ return code;
2966
+ }
2967
+
2968
+ // src/plugin.ts
2969
+ init_logger();
2970
+ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
2971
+ import { dirname, basename as basename2, join as join3, resolve as resolve2 } from "path";
2972
+ function findModulePath(hypenPath, patterns) {
2973
+ const dir = dirname(hypenPath);
2974
+ const baseName = basename2(hypenPath, ".hypen");
2975
+ const parentDir = dirname(dir);
2976
+ const folderName = basename2(dir);
2977
+ for (const pattern of patterns) {
2978
+ let candidatePath = null;
2979
+ switch (pattern) {
2980
+ case "sibling":
2981
+ candidatePath = join3(dir, `${baseName}.ts`);
2982
+ break;
2983
+ case "component":
2984
+ if (baseName === "component") {
2985
+ candidatePath = join3(dir, "component.ts");
3018
2986
  }
3019
- }
3020
- });
2987
+ break;
2988
+ case "index":
2989
+ if (baseName === "index") {
2990
+ candidatePath = join3(dir, "index.ts");
2991
+ }
2992
+ break;
2993
+ }
2994
+ if (candidatePath && existsSync3(candidatePath)) {
2995
+ return candidatePath;
2996
+ }
3021
2997
  }
3022
- async handleConcurrentConnection(ws, clientData, existingSession, props) {
3023
- const policy = this.sessionManager?.getConcurrentPolicy() ?? "kick-old";
3024
- switch (policy) {
3025
- case "kick-old": {
3026
- const existingConnections = this.sessionManager?.getConnections(existingSession.id);
3027
- if (existingConnections) {
3028
- for (const conn of existingConnections) {
3029
- const oldWs = conn;
3030
- const expiredMsg = {
3031
- type: "sessionExpired",
3032
- sessionId: existingSession.id,
3033
- reason: "kicked"
3034
- };
3035
- oldWs.send(JSON.stringify(expiredMsg));
3036
- oldWs.close(1000, "Session taken by new connection");
3037
- }
2998
+ return null;
2999
+ }
3000
+ function getComponentName(hypenPath) {
3001
+ const baseName = basename2(hypenPath, ".hypen");
3002
+ if (baseName === "component" || baseName === "index") {
3003
+ return basename2(dirname(hypenPath));
3004
+ }
3005
+ return baseName;
3006
+ }
3007
+ function parseImports(text) {
3008
+ const imports = [];
3009
+ const importRegex = /import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+["']([^"']+)["']/g;
3010
+ let match2;
3011
+ while ((match2 = importRegex.exec(text)) !== null) {
3012
+ const [, namedImports, defaultImport, source] = match2;
3013
+ if (!source)
3014
+ continue;
3015
+ let names;
3016
+ if (namedImports) {
3017
+ names = namedImports.split(",").map((n) => n.trim()).filter((n) => n.length > 0);
3018
+ } else if (defaultImport) {
3019
+ names = [defaultImport];
3020
+ } else {
3021
+ continue;
3022
+ }
3023
+ imports.push({ names, source });
3024
+ }
3025
+ return imports;
3026
+ }
3027
+ function removeImports2(text) {
3028
+ return text.replace(/import\s+(?:\{[^}]+\}|\w+)\s+from\s+["'][^"']+["']\s*/g, "");
3029
+ }
3030
+ function hypenPlugin(options = {}) {
3031
+ const { debug = false, patterns = ["sibling", "component", "index"] } = options;
3032
+ const log13 = debug ? (...args) => frameworkLoggers.plugin.debug(...args) : () => {};
3033
+ return {
3034
+ name: "hypen-loader",
3035
+ async setup(build) {
3036
+ build.onLoad({ filter: /\.hypen$/ }, async (args) => {
3037
+ const hypenPath = resolve2(args.path);
3038
+ log13("Loading:", hypenPath);
3039
+ const templateRaw = readFileSync3(hypenPath, "utf-8");
3040
+ const imports = parseImports(templateRaw);
3041
+ const template = removeImports2(templateRaw).trim();
3042
+ if (imports.length > 0) {
3043
+ log13("Found imports:", imports);
3044
+ }
3045
+ const componentName = getComponentName(hypenPath);
3046
+ log13("Component name:", componentName);
3047
+ const modulePath = findModulePath(hypenPath, patterns);
3048
+ log13("Module path:", modulePath);
3049
+ let contents;
3050
+ if (modulePath) {
3051
+ const relativeModulePath = modulePath.replace(/\.ts$/, ".js");
3052
+ contents = `
3053
+ import _module from "${relativeModulePath}";
3054
+ export const module = _module;
3055
+ export const template = ${JSON.stringify(template)};
3056
+ export const name = ${JSON.stringify(componentName)};
3057
+ export default { module: _module, template: ${JSON.stringify(template)}, name: ${JSON.stringify(componentName)} };
3058
+ `;
3059
+ } else {
3060
+ log13("No module found, creating stateless component");
3061
+ contents = `
3062
+ import { app } from "@hypen-space/core";
3063
+ const _module = app.defineState({}).build();
3064
+ export const module = _module;
3065
+ export const template = ${JSON.stringify(template)};
3066
+ export const name = ${JSON.stringify(componentName)};
3067
+ export default { module: _module, template: ${JSON.stringify(template)}, name: ${JSON.stringify(componentName)} };
3068
+ `;
3038
3069
  }
3039
- return true;
3040
- }
3041
- case "reject-new": {
3042
- const expiredMsg = {
3043
- type: "sessionExpired",
3044
- sessionId: existingSession.id,
3045
- reason: "kicked"
3070
+ return {
3071
+ contents,
3072
+ loader: "js"
3046
3073
  };
3047
- ws.send(JSON.stringify(expiredMsg));
3048
- ws.close(1000, "Session already active");
3049
- return false;
3050
- }
3051
- case "allow-multiple": {
3052
- return true;
3053
- }
3054
- default:
3055
- return true;
3074
+ });
3056
3075
  }
3076
+ };
3077
+ }
3078
+ var defaultHypenPlugin = hypenPlugin();
3079
+ function registerHypenPlugin(options) {
3080
+ Bun.plugin(hypenPlugin(options));
3081
+ }
3082
+ var plugin_default = hypenPlugin;
3083
+
3084
+ // src/components/builtin.ts
3085
+ init_app();
3086
+ init_logger();
3087
+ var log13 = frameworkLoggers.router;
3088
+ var Router = app.defineState({
3089
+ currentPath: "/",
3090
+ matchedRoute: null,
3091
+ routeParams: {}
3092
+ }, { name: "__Router" }).onCreated((state, context) => {
3093
+ if (!context) {
3094
+ log13.error("Requires global context");
3095
+ return;
3057
3096
  }
3058
- async triggerReconnect(clientData, session, savedState) {
3059
- const handler = this._module?.handlers.onReconnect;
3060
- if (!handler)
3061
- return;
3062
- let restored = false;
3063
- const restore = (state2) => {
3064
- restored = true;
3065
- clientData.moduleInstance.updateState(state2);
3066
- };
3067
- await handler({ session, restore });
3097
+ const router = context.__router;
3098
+ if (!router) {
3099
+ log13.error("Router not found in context");
3100
+ return;
3068
3101
  }
3069
- handleMessage(ws, message) {
3070
- try {
3071
- const clientData = this.clients.get(ws);
3072
- if (!clientData)
3073
- return;
3074
- const msg = JSON.parse(message.toString());
3075
- switch (msg.type) {
3076
- case "hello": {
3077
- const helloMsg = msg;
3078
- this.initializeSession(ws, clientData, helloMsg.sessionId, helloMsg.props);
3079
- break;
3080
- }
3081
- case "dispatchAction": {
3082
- const actionMsg = msg;
3083
- clientData.engine.dispatchAction(actionMsg.action, actionMsg.payload);
3084
- break;
3102
+ const hypenEngine = context.__hypenEngine;
3103
+ const updateRouteVisibility = async (currentPath) => {
3104
+ const routeElements = document.querySelectorAll('[data-hypen-type="route"]');
3105
+ let matchFound = false;
3106
+ for (let index = 0;index < routeElements.length; index++) {
3107
+ const routeEl = routeElements[index];
3108
+ const htmlEl = routeEl;
3109
+ const routePath = htmlEl.dataset.routePath || "/";
3110
+ const isMatch = routePath === currentPath;
3111
+ htmlEl.style.display = isMatch ? "flex" : "none";
3112
+ if (isMatch) {
3113
+ matchFound = true;
3114
+ const componentName = htmlEl.dataset.routeComponent;
3115
+ const isLazy = htmlEl.dataset.routeLazy === "true";
3116
+ const hasContent = htmlEl.children.length > 0;
3117
+ if (componentName && !hasContent && hypenEngine) {
3118
+ try {
3119
+ await hypenEngine.renderLazyRoute(routePath, componentName, htmlEl);
3120
+ } catch (err) {
3121
+ log13.error(`Failed to render route ${routePath}:`, err);
3122
+ }
3085
3123
  }
3086
- default:
3087
- break;
3088
- }
3089
- } catch (error) {
3090
- console.error("Error handling WebSocket message:", error);
3091
- }
3092
- }
3093
- async handleClose(ws) {
3094
- const clientData = this.clients.get(ws);
3095
- if (!clientData)
3096
- return;
3097
- if (clientData.helloTimeout) {
3098
- clearTimeout(clientData.helloTimeout);
3099
- }
3100
- const currentState = clientData.moduleInstance.getState();
3101
- if (clientData.sessionId && this._module?.handlers.onDisconnect) {
3102
- const session = this.sessionManager?.getActiveSession(clientData.sessionId);
3103
- if (session) {
3104
- await this._module.handlers.onDisconnect({
3105
- state: currentState,
3106
- session
3107
- });
3108
3124
  }
3109
3125
  }
3110
- if (clientData.sessionId && this.sessionManager) {
3111
- this.sessionManager.untrackConnection(clientData.sessionId, ws);
3112
- if (this.sessionManager.getConnectionCount(clientData.sessionId) === 0) {
3113
- const session = this.sessionManager.getActiveSession(clientData.sessionId);
3114
- if (session) {
3115
- this.sessionManager.suspendSession(clientData.sessionId, currentState, async (expiredSession) => {
3116
- if (this._module?.handlers.onExpire) {
3117
- await this._module.handlers.onExpire({ session: expiredSession });
3118
- }
3119
- });
3120
- }
3121
- }
3126
+ if (!matchFound) {
3127
+ log13.warn(`No route matched path: ${currentPath}. Available routes:`, Array.from(routeElements).map((el) => el.dataset.routePath));
3122
3128
  }
3123
- await clientData.moduleInstance.destroy();
3124
- this.clients.delete(ws);
3125
- const client = {
3126
- id: clientData.id,
3127
- socket: ws,
3128
- connectedAt: clientData.connectedAt
3129
- };
3130
- this._onDisconnectionCallbacks.forEach((cb) => cb(client));
3131
- }
3132
- getClientCount() {
3133
- return this.clients.size;
3134
- }
3135
- getSessionStats() {
3136
- return this.sessionManager?.getStats() ?? {
3137
- activeSessions: 0,
3138
- pendingSessions: 0,
3139
- totalConnections: 0
3140
- };
3129
+ };
3130
+ setTimeout(() => {
3131
+ updateRouteVisibility(state.currentPath);
3132
+ }, 100);
3133
+ router.onNavigate((routeState) => {
3134
+ state.currentPath = routeState.currentPath;
3135
+ state.routeParams = routeState.params;
3136
+ updateRouteVisibility(routeState.currentPath);
3137
+ });
3138
+ }).build();
3139
+ var Route = app.defineState({}, { name: "__Route" }).build();
3140
+ var Link = app.defineState({
3141
+ to: "/",
3142
+ isActive: false
3143
+ }, { name: "__Link" }).onAction("navigate", ({ state, context }) => {
3144
+ const router = context?.__router;
3145
+ if (!router) {
3146
+ log13.error("Link requires router context");
3147
+ return;
3141
3148
  }
3142
- broadcast(message) {
3143
- const json = JSON.stringify(message);
3144
- this.clients.forEach((_, ws) => {
3145
- ws.send(json);
3149
+ const targetPath = state.to;
3150
+ router.push(targetPath);
3151
+ }).onCreated((state, context) => {
3152
+ if (!context)
3153
+ return;
3154
+ const router = context.__router;
3155
+ if (router) {
3156
+ router.onNavigate((routeState) => {
3157
+ state.isActive = routeState.currentPath === state.to;
3146
3158
  });
3147
3159
  }
3160
+ }).build();
3161
+
3162
+ // src/index.ts
3163
+ init_app();
3164
+
3165
+ // src/hypen.ts
3166
+ function createBindingProxy(root) {
3167
+ const handler = {
3168
+ get(_, prop) {
3169
+ if (prop === Symbol.toPrimitive || prop === "toString" || prop === "valueOf") {
3170
+ return () => `\${${root}}`;
3171
+ }
3172
+ if (typeof prop === "symbol") {
3173
+ return;
3174
+ }
3175
+ if (prop === "toJSON") {
3176
+ return () => `\${${root}}`;
3177
+ }
3178
+ return createBindingProxy(`${root}.${prop}`);
3179
+ },
3180
+ has() {
3181
+ return true;
3182
+ },
3183
+ ownKeys() {
3184
+ return [];
3185
+ },
3186
+ getOwnPropertyDescriptor() {
3187
+ return {
3188
+ configurable: true,
3189
+ enumerable: true
3190
+ };
3191
+ }
3192
+ };
3193
+ return new Proxy({}, handler);
3148
3194
  }
3149
- function serve(options) {
3150
- const server = new RemoteServer().module(options.moduleName ?? "App", options.module).ui(options.ui);
3151
- if (options.port || options.hostname) {
3152
- server.config({
3153
- port: options.port,
3154
- hostname: options.hostname
3155
- });
3156
- }
3157
- if (options.session) {
3158
- server.session(options.session);
3159
- }
3160
- if (options.onConnection) {
3161
- server.onConnection(options.onConnection);
3162
- }
3163
- if (options.onDisconnection) {
3164
- server.onDisconnection(options.onDisconnection);
3195
+ var state = createBindingProxy("state");
3196
+ var item = createBindingProxy("item");
3197
+ var index = {
3198
+ [Symbol.toPrimitive]: () => "${index}",
3199
+ toString: () => "${index}",
3200
+ valueOf: () => "${index}",
3201
+ toJSON: () => "${index}"
3202
+ };
3203
+ function hypen(strings, ...expressions) {
3204
+ let result = strings[0];
3205
+ for (let i = 0;i < expressions.length; i++) {
3206
+ const expr = expressions[i];
3207
+ result += String(expr);
3208
+ result += strings[i + 1];
3165
3209
  }
3166
- return server.listen(options.port);
3210
+ return result.trim();
3167
3211
  }
3168
3212
 
3169
3213
  // src/index.ts
3214
+ init_state();
3170
3215
  init_result();
3171
3216
  init_logger();
3172
3217
  export {
@@ -3180,6 +3225,7 @@ export {
3180
3225
  unwrap,
3181
3226
  state,
3182
3227
  setLogLevel,
3228
+ setDebugMode,
3183
3229
  serve,
3184
3230
  retryResult,
3185
3231
  retry,
@@ -3195,6 +3241,7 @@ export {
3195
3241
  isOk,
3196
3242
  isErr,
3197
3243
  isDisposable,
3244
+ isDebugMode,
3198
3245
  index,
3199
3246
  hypenPlugin,
3200
3247
  hypen,
@@ -3258,4 +3305,4 @@ export {
3258
3305
  ActionError
3259
3306
  };
3260
3307
 
3261
- //# debugId=5FEDED160A511C9E64756E2164756E21
3308
+ //# debugId=1C99977DF3016DFC64756E2164756E21