@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.
- package/dist/{src/app.js → app.js} +15 -2
- package/dist/{src/app.js.map → app.js.map} +3 -3
- package/dist/{src/components → components}/builtin.js +22 -7
- package/dist/{src/components → components}/builtin.js.map +4 -4
- package/dist/context.js +387 -0
- package/dist/context.js.map +12 -0
- package/dist/{src/discovery.js → discovery.js} +18 -4
- package/dist/{src/discovery.js.map → discovery.js.map} +4 -4
- package/dist/disposable.js +377 -0
- package/dist/disposable.js.map +11 -0
- package/dist/engine.browser.js +347 -0
- package/dist/engine.browser.js.map +11 -0
- package/dist/engine.js +320 -0
- package/dist/engine.js.map +11 -0
- package/dist/events.js +282 -0
- package/dist/events.js.map +11 -0
- package/dist/{src/index.browser.js → index.browser.js} +278 -229
- package/dist/index.browser.js.map +22 -0
- package/dist/{src/index.js → index.js} +1377 -1330
- package/dist/index.js.map +31 -0
- package/dist/loader.js +286 -0
- package/dist/loader.js.map +11 -0
- package/dist/plugin.js +334 -0
- package/dist/plugin.js.map +11 -0
- package/dist/{src/remote → remote}/client.js +215 -11
- package/dist/remote/client.js.map +14 -0
- package/dist/{src/remote → remote}/index.js +234 -210
- package/dist/remote/index.js.map +19 -0
- package/dist/remote/server.js +1442 -0
- package/dist/remote/server.js.map +16 -0
- package/dist/renderer.js +264 -0
- package/dist/renderer.js.map +11 -0
- package/dist/{src/router.js → router.js} +209 -8
- package/dist/router.js.map +12 -0
- package/package.json +69 -69
- package/src/components/builtin.ts +9 -6
- package/src/context.ts +10 -7
- package/src/discovery.ts +3 -2
- package/src/disposable.ts +6 -2
- package/src/engine.browser.ts +30 -11
- package/src/engine.ts +6 -3
- package/src/events.ts +5 -1
- package/src/index.browser.ts +20 -0
- package/src/index.ts +2 -0
- package/src/loader.ts +8 -8
- package/src/logger.ts +28 -0
- package/src/plugin.ts +2 -1
- package/src/remote/client.ts +8 -9
- package/src/remote/server.ts +6 -3
- package/src/renderer.ts +4 -5
- package/src/router.ts +10 -7
- package/wasm-browser/hypen_engine_bg.wasm +0 -0
- package/wasm-browser/package.json +1 -1
- package/wasm-node/hypen_engine_bg.wasm +0 -0
- package/wasm-node/package.json +5 -3
- package/dist/src/context.js +0 -182
- package/dist/src/context.js.map +0 -11
- package/dist/src/engine.browser.js +0 -137
- package/dist/src/engine.browser.js.map +0 -10
- package/dist/src/engine.js +0 -119
- package/dist/src/engine.js.map +0 -10
- package/dist/src/events.js +0 -80
- package/dist/src/events.js.map +0 -10
- package/dist/src/index.browser.js.map +0 -22
- package/dist/src/index.js.map +0 -31
- package/dist/src/loader.js +0 -85
- package/dist/src/loader.js.map +0 -10
- package/dist/src/plugin.js +0 -134
- package/dist/src/plugin.js.map +0 -10
- package/dist/src/remote/client.js.map +0 -13
- package/dist/src/remote/index.js.map +0 -19
- package/dist/src/renderer.js +0 -66
- package/dist/src/renderer.js.map +0 -10
- package/dist/src/router.js.map +0 -11
- /package/dist/{src/remote → remote}/types.js +0 -0
- /package/dist/{src/remote → remote}/types.js.map +0 -0
- /package/dist/{src/resolver.js → resolver.js} +0 -0
- /package/dist/{src/resolver.js.map → resolver.js.map} +0 -0
- /package/dist/{src/state.js → state.js} +0 -0
- /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/
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
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
|
-
|
|
704
|
+
log4.debug(`Registering action handler: ${actionName} for module ${definition.name}`);
|
|
692
705
|
this.engine.onAction(actionName, async (action) => {
|
|
693
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
824
|
+
var log4, app;
|
|
812
825
|
var init_app = __esm(() => {
|
|
813
826
|
init_result();
|
|
814
827
|
init_state();
|
|
815
828
|
init_logger();
|
|
816
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1180
|
+
log6.debug("push:", path);
|
|
1154
1181
|
this.updatePath(path, true);
|
|
1155
1182
|
}
|
|
1156
1183
|
replace(path) {
|
|
1157
|
-
|
|
1184
|
+
log6.debug("replace:", path);
|
|
1158
1185
|
this.updatePath(path, true, true);
|
|
1159
1186
|
}
|
|
1160
1187
|
back() {
|
|
1161
|
-
|
|
1188
|
+
log6.debug("back");
|
|
1162
1189
|
if (typeof window !== "undefined") {
|
|
1163
1190
|
window.history.back();
|
|
1164
1191
|
}
|
|
1165
1192
|
}
|
|
1166
1193
|
forward() {
|
|
1167
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1400
|
+
log8.warn(`Module "${id}" is already registered. Overwriting.`);
|
|
1368
1401
|
}
|
|
1369
1402
|
this.modules.set(id, instance);
|
|
1370
|
-
|
|
1403
|
+
log8.debug(`Registered module: ${id}`);
|
|
1371
1404
|
}
|
|
1372
1405
|
unregisterModule(id) {
|
|
1373
1406
|
this.modules.delete(id);
|
|
1374
|
-
|
|
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
|
-
|
|
1436
|
+
log8.debug(`Event "${event}" emitted but no listeners`);
|
|
1404
1437
|
} else {
|
|
1405
|
-
|
|
1438
|
+
log8.debug(`Emitting event: ${event}`, payload);
|
|
1406
1439
|
handlers.forEach((handler) => {
|
|
1407
1440
|
try {
|
|
1408
1441
|
handler(payload);
|
|
1409
1442
|
} catch (error) {
|
|
1410
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2005
|
+
log10.debug(`Reconnection attempt ${attempt}/${this.options.maxReconnectAttempts} failed: ${error.message}`);
|
|
1968
2006
|
}
|
|
1969
2007
|
}).catch((error) => {
|
|
1970
|
-
|
|
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/
|
|
1978
|
-
|
|
1979
|
-
import { join } from "path";
|
|
2015
|
+
// src/remote/server.ts
|
|
2016
|
+
init_app();
|
|
1980
2017
|
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
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
|
-
|
|
1992
|
-
return this.
|
|
2031
|
+
getTtl() {
|
|
2032
|
+
return this.config.ttl;
|
|
1993
2033
|
}
|
|
1994
|
-
|
|
1995
|
-
return this.
|
|
2034
|
+
getConcurrentPolicy() {
|
|
2035
|
+
return this.config.concurrent;
|
|
1996
2036
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
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
|
-
|
|
2001
|
-
return
|
|
2049
|
+
getActiveSession(id) {
|
|
2050
|
+
return this.activeSessions.get(id) ?? null;
|
|
2002
2051
|
}
|
|
2003
|
-
|
|
2004
|
-
this.
|
|
2052
|
+
getPendingSession(id) {
|
|
2053
|
+
return this.pendingSessions.get(id) ?? null;
|
|
2005
2054
|
}
|
|
2006
|
-
|
|
2007
|
-
|
|
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
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
const
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
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
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2069
|
+
}, session.ttl * 1000);
|
|
2070
|
+
this.pendingSessions.set(sessionId, {
|
|
2071
|
+
session,
|
|
2072
|
+
savedState,
|
|
2073
|
+
expiryTimer
|
|
2074
|
+
});
|
|
2041
2075
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
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
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
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
|
-
|
|
2090
|
-
|
|
2091
|
-
if (!
|
|
2092
|
-
|
|
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
|
-
|
|
2104
|
+
connections.add(ws);
|
|
2095
2105
|
}
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
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
|
-
|
|
2110
|
-
return
|
|
2115
|
+
getConnections(sessionId) {
|
|
2116
|
+
return this.sessionConnections.get(sessionId);
|
|
2111
2117
|
}
|
|
2112
|
-
|
|
2113
|
-
this.
|
|
2118
|
+
getConnectionCount(sessionId) {
|
|
2119
|
+
return this.sessionConnections.get(sessionId)?.size ?? 0;
|
|
2114
2120
|
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
const
|
|
2118
|
-
|
|
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
|
|
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/
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
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
|
-
|
|
2163
|
-
|
|
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
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
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
|
-
|
|
2219
|
+
return new Response("Hypen Remote Server", { status: 200 });
|
|
2231
2220
|
}
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
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
|
-
|
|
2243
|
-
|
|
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
|
-
|
|
2282
|
-
|
|
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
|
-
|
|
2285
|
-
|
|
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
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
let
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
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
|
-
|
|
2304
|
+
session = {
|
|
2305
|
+
id: crypto.randomUUID(),
|
|
2306
|
+
ttl: 3600,
|
|
2307
|
+
createdAt: new Date,
|
|
2308
|
+
lastConnectedAt: new Date,
|
|
2309
|
+
props
|
|
2310
|
+
};
|
|
2304
2311
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
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
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
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
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
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
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
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
|
-
|
|
2378
|
-
|
|
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
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
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
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
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
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2427
|
+
case "dispatchAction": {
|
|
2428
|
+
const actionMsg = msg;
|
|
2429
|
+
clientData.engine.dispatchAction(actionMsg.action, actionMsg.payload);
|
|
2430
|
+
break;
|
|
2449
2431
|
}
|
|
2450
|
-
|
|
2432
|
+
default:
|
|
2433
|
+
break;
|
|
2434
|
+
}
|
|
2435
|
+
} catch (error) {
|
|
2436
|
+
log11.error("Error handling WebSocket message:", error);
|
|
2451
2437
|
}
|
|
2452
|
-
|
|
2453
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
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
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
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
|
-
|
|
2554
|
-
|
|
2555
|
-
console.error("[Router] Router not found in context");
|
|
2556
|
-
return;
|
|
2503
|
+
if (options.session) {
|
|
2504
|
+
server.session(options.session);
|
|
2557
2505
|
}
|
|
2558
|
-
|
|
2559
|
-
|
|
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
|
-
|
|
2606
|
-
|
|
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
|
-
|
|
2512
|
+
return server.listen(options.port);
|
|
2513
|
+
}
|
|
2617
2514
|
|
|
2618
|
-
// src/
|
|
2619
|
-
|
|
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
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
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
|
-
|
|
2632
|
-
|
|
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
|
-
|
|
2635
|
-
}
|
|
2636
|
-
|
|
2637
|
-
|
|
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/
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
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
|
-
|
|
2710
|
-
|
|
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
|
-
|
|
2713
|
-
|
|
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
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
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
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
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
|
-
|
|
2756
|
-
|
|
2757
|
-
if (!
|
|
2758
|
-
|
|
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
|
-
|
|
2634
|
+
return response.text();
|
|
2762
2635
|
}
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
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
|
-
|
|
2773
|
-
return
|
|
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
|
-
|
|
2779
|
-
|
|
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
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
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
|
-
|
|
2794
|
-
this.pendingSessions.clear();
|
|
2795
|
-
this.sessionConnections.clear();
|
|
2675
|
+
return imports;
|
|
2796
2676
|
}
|
|
2797
2677
|
}
|
|
2798
2678
|
|
|
2799
|
-
// src/
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
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
|
-
|
|
2845
|
-
const
|
|
2846
|
-
const
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
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
|
-
|
|
2861
|
-
|
|
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
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
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
|
-
|
|
2771
|
+
continue;
|
|
2874
2772
|
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
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
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
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
|
-
|
|
2890
|
-
|
|
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
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
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
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
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
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
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
|
-
|
|
2956
|
-
|
|
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
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
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
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
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
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
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
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
const expiredMsg = {
|
|
3043
|
-
type: "sessionExpired",
|
|
3044
|
-
sessionId: existingSession.id,
|
|
3045
|
-
reason: "kicked"
|
|
3070
|
+
return {
|
|
3071
|
+
contents,
|
|
3072
|
+
loader: "js"
|
|
3046
3073
|
};
|
|
3047
|
-
|
|
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
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
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
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
const
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
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 (
|
|
3111
|
-
|
|
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
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
}
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
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
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
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
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
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
|
|
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=
|
|
3308
|
+
//# debugId=1C99977DF3016DFC64756E2164756E21
|