@hypen-space/core 0.4.37 → 0.4.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +13 -14
  2. package/dist/app.js +304 -227
  3. package/dist/app.js.map +5 -5
  4. package/dist/components/builtin.d.ts +17 -0
  5. package/dist/components/builtin.js +336 -256
  6. package/dist/components/builtin.js.map +6 -6
  7. package/dist/context.d.ts +11 -20
  8. package/dist/context.js +69 -101
  9. package/dist/context.js.map +3 -3
  10. package/dist/datasource.js +80 -0
  11. package/dist/datasource.js.map +10 -0
  12. package/dist/disposable.js +60 -63
  13. package/dist/disposable.js.map +2 -2
  14. package/dist/events.js +60 -63
  15. package/dist/events.js.map +2 -2
  16. package/dist/hypen.js +78 -0
  17. package/dist/hypen.js.map +10 -0
  18. package/dist/index.browser.d.ts +2 -1
  19. package/dist/index.browser.js +327 -281
  20. package/dist/index.browser.js.map +7 -8
  21. package/dist/index.d.ts +0 -3
  22. package/dist/index.js +547 -246
  23. package/dist/index.js.map +9 -9
  24. package/dist/logger.js +60 -64
  25. package/dist/logger.js.map +2 -2
  26. package/dist/remote/client.js +258 -158
  27. package/dist/remote/client.js.map +6 -6
  28. package/dist/remote/index.d.ts +6 -5
  29. package/dist/remote/index.js +255 -1985
  30. package/dist/remote/index.js.map +6 -13
  31. package/dist/remote/session.js +151 -0
  32. package/dist/remote/session.js.map +10 -0
  33. package/dist/renderer.js +60 -63
  34. package/dist/renderer.js.map +2 -2
  35. package/dist/result.js +235 -0
  36. package/dist/result.js.map +10 -0
  37. package/dist/retry.js +344 -0
  38. package/dist/retry.js.map +11 -0
  39. package/dist/router.js +62 -70
  40. package/dist/router.js.map +2 -2
  41. package/dist/state.js +3 -8
  42. package/dist/state.js.map +2 -2
  43. package/package.json +11 -56
  44. package/src/components/builtin.ts +78 -56
  45. package/src/context.ts +22 -65
  46. package/src/index.browser.ts +5 -4
  47. package/src/index.ts +10 -23
  48. package/src/remote/index.ts +9 -5
  49. package/src/result.ts +11 -0
  50. package/dist/discovery.d.ts +0 -90
  51. package/dist/discovery.js +0 -1334
  52. package/dist/discovery.js.map +0 -15
  53. package/dist/engine.browser.d.ts +0 -116
  54. package/dist/engine.browser.js +0 -479
  55. package/dist/engine.browser.js.map +0 -12
  56. package/dist/engine.d.ts +0 -107
  57. package/dist/engine.js +0 -543
  58. package/dist/engine.js.map +0 -12
  59. package/dist/loader.d.ts +0 -51
  60. package/dist/loader.js +0 -292
  61. package/dist/loader.js.map +0 -11
  62. package/dist/plugin.d.ts +0 -39
  63. package/dist/plugin.js +0 -685
  64. package/dist/plugin.js.map +0 -12
  65. package/dist/remote/server.d.ts +0 -188
  66. package/dist/remote/server.js +0 -2270
  67. package/dist/remote/server.js.map +0 -19
  68. package/src/discovery.ts +0 -527
  69. package/src/engine.browser.ts +0 -302
  70. package/src/engine.ts +0 -282
  71. package/src/loader.ts +0 -136
  72. package/src/plugin.ts +0 -220
  73. package/src/remote/server.ts +0 -879
  74. package/wasm-browser/README.md +0 -594
  75. package/wasm-browser/hypen_engine.d.ts +0 -389
  76. package/wasm-browser/hypen_engine.js +0 -1070
  77. package/wasm-browser/hypen_engine_bg.wasm +0 -0
  78. package/wasm-browser/hypen_engine_bg.wasm.d.ts +0 -37
  79. package/wasm-browser/package.json +0 -24
  80. package/wasm-node/README.md +0 -594
  81. package/wasm-node/hypen_engine.d.ts +0 -327
  82. package/wasm-node/hypen_engine.js +0 -979
  83. package/wasm-node/hypen_engine_bg.wasm +0 -0
  84. package/wasm-node/hypen_engine_bg.wasm.d.ts +0 -37
  85. package/wasm-node/package.json +0 -22
@@ -21,264 +21,220 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
21
21
  throw Error('Dynamic require of "' + x + '" is not supported');
22
22
  });
23
23
 
24
- // src/state.ts
25
- function deepClone(obj) {
26
- if (obj === null || typeof obj !== "object") {
27
- return obj;
24
+ // src/result.ts
25
+ function Ok(value) {
26
+ return { ok: true, value };
27
+ }
28
+ function Err(error) {
29
+ return { ok: false, error };
30
+ }
31
+ function isOk(result) {
32
+ return result.ok;
33
+ }
34
+ function isErr(result) {
35
+ return !result.ok;
36
+ }
37
+ async function fromPromise(promise, mapError) {
38
+ try {
39
+ const value = await promise;
40
+ return Ok(value);
41
+ } catch (e) {
42
+ if (mapError) {
43
+ return Err(mapError(e));
44
+ }
45
+ return Err(e);
28
46
  }
29
- if (typeof obj === "function") {
30
- return obj;
47
+ }
48
+ function fromTry(fn, mapError) {
49
+ try {
50
+ return Ok(fn());
51
+ } catch (e) {
52
+ if (mapError) {
53
+ return Err(mapError(e));
54
+ }
55
+ return Err(e);
31
56
  }
32
- if (typeof obj.__getSnapshot === "function") {
33
- return obj.__getSnapshot();
57
+ }
58
+ function map(result, fn) {
59
+ if (result.ok) {
60
+ return Ok(fn(result.value));
34
61
  }
35
- if (obj instanceof WeakMap || obj instanceof WeakSet) {
36
- return obj;
62
+ return result;
63
+ }
64
+ function mapErr(result, fn) {
65
+ if (!result.ok) {
66
+ return Err(fn(result.error));
37
67
  }
38
- const visited = new WeakMap;
39
- function cloneInternal(value) {
40
- if (value === null || typeof value !== "object") {
41
- return value;
42
- }
43
- if (typeof value === "function") {
44
- return value;
45
- }
46
- if (visited.has(value)) {
47
- return visited.get(value);
48
- }
49
- if (value instanceof WeakMap || value instanceof WeakSet) {
50
- return value;
51
- }
52
- if (value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
53
- try {
54
- return structuredClone(value);
55
- } catch {}
56
- }
57
- if (Array.isArray(value)) {
58
- const arrClone = [];
59
- visited.set(value, arrClone);
60
- for (let i = 0;i < value.length; i++) {
61
- arrClone[i] = cloneInternal(value[i]);
62
- }
63
- return arrClone;
64
- }
65
- const objClone = {};
66
- visited.set(value, objClone);
67
- for (const key in value) {
68
- if (Object.prototype.hasOwnProperty.call(value, key)) {
69
- objClone[key] = cloneInternal(value[key]);
70
- }
71
- }
72
- const symbolKeys = Object.getOwnPropertySymbols(value);
73
- for (const sym of symbolKeys) {
74
- objClone[sym] = cloneInternal(value[sym]);
75
- }
76
- return objClone;
68
+ return result;
69
+ }
70
+ function flatMap(result, fn) {
71
+ if (result.ok) {
72
+ return fn(result.value);
77
73
  }
78
- return cloneInternal(obj);
74
+ return result;
79
75
  }
80
- function diffState(oldState, newState, basePath = "") {
81
- const paths = [];
82
- const newValues = {};
83
- function diff(oldVal, newVal, path) {
84
- if (oldVal === newVal)
85
- return;
86
- if (typeof oldVal !== "object" || typeof newVal !== "object" || oldVal === null || newVal === null) {
87
- if (oldVal !== newVal) {
88
- paths.push(path);
89
- newValues[path] = newVal;
90
- }
91
- return;
92
- }
93
- if (Array.isArray(oldVal) || Array.isArray(newVal)) {
94
- if (!Array.isArray(oldVal) || !Array.isArray(newVal) || oldVal.length !== newVal.length) {
95
- paths.push(path);
96
- newValues[path] = newVal;
97
- return;
98
- }
99
- for (let i = 0;i < newVal.length; i++) {
100
- const itemPath = path ? `${path}.${i}` : `${i}`;
101
- diff(oldVal[i], newVal[i], itemPath);
102
- }
103
- return;
104
- }
105
- const oldKeys = new Set(Object.keys(oldVal));
106
- const newKeys = new Set(Object.keys(newVal));
107
- for (const key of newKeys) {
108
- const propPath = path ? `${path}.${key}` : key;
109
- if (!oldKeys.has(key)) {
110
- paths.push(propPath);
111
- newValues[propPath] = newVal[key];
112
- } else {
113
- diff(oldVal[key], newVal[key], propPath);
114
- }
115
- }
116
- for (const key of oldKeys) {
117
- if (!newKeys.has(key)) {
118
- const propPath = path ? `${path}.${key}` : key;
119
- paths.push(propPath);
120
- newValues[propPath] = undefined;
121
- }
122
- }
76
+ function unwrap(result) {
77
+ if (result.ok) {
78
+ return result.value;
123
79
  }
124
- diff(oldState, newState, basePath);
125
- return { paths, newValues };
80
+ throw result.error;
126
81
  }
127
- function createObservableState(initialState, options) {
128
- const opts = options || { onChange: () => {} };
129
- if (initialState === null || initialState === undefined) {
130
- initialState = {};
131
- }
132
- if (initialState instanceof Number || initialState instanceof String || initialState instanceof Boolean) {
133
- throw new TypeError("Cannot create observable state from primitive wrapper objects (Number, String, Boolean). " + "Use plain primitives or regular objects instead.");
134
- }
135
- initialState = deepClone(initialState);
136
- let lastSnapshot = deepClone(initialState);
137
- const pathPrefix = opts.pathPrefix || "";
138
- let batchDepth = 0;
139
- let pendingChange = null;
140
- function notifyChange() {
141
- if (batchDepth > 0)
142
- return;
143
- const change = diffState(lastSnapshot, state, pathPrefix);
144
- if (change.paths.length > 0) {
145
- lastSnapshot = deepClone(state);
146
- if (pendingChange) {
147
- change.paths.push(...pendingChange.paths);
148
- Object.assign(change.newValues, pendingChange.newValues);
149
- pendingChange = null;
150
- }
151
- opts.onChange(change);
152
- }
82
+ function unwrapOr(result, defaultValue) {
83
+ if (result.ok) {
84
+ return result.value;
153
85
  }
154
- let notificationPending = false;
155
- function scheduleBatch() {
156
- if (batchDepth === 0) {
157
- if (!notificationPending) {
158
- notificationPending = true;
159
- queueMicrotask(() => {
160
- notificationPending = false;
161
- if (batchDepth === 0) {
162
- notifyChange();
163
- } else {}
164
- });
165
- }
166
- } else {
167
- pendingChange = pendingChange || { paths: [], newValues: {} };
86
+ return defaultValue;
87
+ }
88
+ function unwrapOrElse(result, fn) {
89
+ if (result.ok) {
90
+ return result.value;
91
+ }
92
+ return fn(result.error);
93
+ }
94
+ function match(result, handlers) {
95
+ if (result.ok) {
96
+ return handlers.ok(result.value);
97
+ }
98
+ return handlers.err(result.error);
99
+ }
100
+ function all(results) {
101
+ const values = [];
102
+ for (const result of results) {
103
+ if (!result.ok) {
104
+ return result;
168
105
  }
106
+ values.push(result.value);
169
107
  }
170
- const proxyCache = new WeakMap;
171
- function createProxy(target, basePath) {
172
- const cached = proxyCache.get(target);
173
- if (cached)
174
- return cached;
175
- const proxy = new Proxy(target, {
176
- get(obj, prop) {
177
- if (prop === IS_PROXY)
178
- return true;
179
- if (prop === RAW_TARGET)
180
- return obj;
181
- if (prop === "__beginBatch") {
182
- return () => {
183
- batchDepth++;
184
- };
185
- }
186
- if (prop === "__endBatch") {
187
- return () => {
188
- batchDepth--;
189
- if (batchDepth === 0) {
190
- notifyChange();
191
- }
192
- };
193
- }
194
- if (prop === "__getSnapshot") {
195
- return () => deepClone(obj);
196
- }
197
- const value = obj[prop];
198
- if (value && typeof value === "object") {
199
- if (value[IS_PROXY]) {
200
- return value;
201
- }
202
- if (value instanceof Date || value instanceof RegExp || value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet) {
203
- return value;
204
- }
205
- const cachedNested = proxyCache.get(value);
206
- if (cachedNested) {
207
- return cachedNested;
208
- }
209
- const nestedProxy = createProxy(value, basePath ? `${basePath}.${String(prop)}` : String(prop));
210
- return nestedProxy;
211
- }
212
- return value;
213
- },
214
- set(obj, prop, value) {
215
- const oldValue = obj[prop];
216
- if (value && typeof value === "object" && value[IS_PROXY]) {
217
- value = value[RAW_TARGET];
218
- }
219
- obj[prop] = value;
220
- if (oldValue !== value) {
221
- scheduleBatch();
222
- }
223
- return true;
224
- },
225
- deleteProperty(obj, prop) {
226
- const existed = Object.prototype.hasOwnProperty.call(obj, prop);
227
- const result = delete obj[prop];
228
- if (existed) {
229
- scheduleBatch();
230
- }
231
- return result;
232
- }
108
+ return Ok(values);
109
+ }
110
+
111
+ class HypenError extends Error {
112
+ code;
113
+ context;
114
+ cause;
115
+ constructor(code, message, options) {
116
+ super(message);
117
+ this.name = "HypenError";
118
+ this.code = code;
119
+ this.context = options?.context;
120
+ this.cause = options?.cause;
121
+ Object.setPrototypeOf(this, new.target.prototype);
122
+ }
123
+ }
124
+
125
+ class ActionError extends HypenError {
126
+ actionName;
127
+ constructor(actionName, cause) {
128
+ super("ACTION_ERROR", `Action handler "${actionName}" failed: ${cause instanceof Error ? cause.message : String(cause)}`, {
129
+ context: { actionName },
130
+ cause: cause instanceof Error ? cause : undefined
233
131
  });
234
- proxyCache.set(target, proxy);
235
- return proxy;
132
+ this.name = "ActionError";
133
+ this.actionName = actionName;
236
134
  }
237
- const state = createProxy(initialState, pathPrefix);
238
- return state;
239
135
  }
240
- function batchStateUpdates(state, fn) {
241
- const s = state;
242
- if (s.__beginBatch && s.__endBatch) {
243
- s.__beginBatch();
244
- try {
245
- fn();
246
- } finally {
247
- s.__endBatch();
248
- }
249
- } else {
250
- fn();
136
+
137
+ class ConnectionError extends HypenError {
138
+ url;
139
+ attempt;
140
+ constructor(url, cause, attempt) {
141
+ super("CONNECTION_ERROR", `Connection to "${url}" failed${attempt ? ` (attempt ${attempt})` : ""}: ${cause instanceof Error ? cause.message : String(cause)}`, {
142
+ context: { url, attempt },
143
+ cause: cause instanceof Error ? cause : undefined
144
+ });
145
+ this.name = "ConnectionError";
146
+ this.url = url;
147
+ this.attempt = attempt;
148
+ }
149
+ }
150
+
151
+ class StateError extends HypenError {
152
+ path;
153
+ constructor(message, path, cause) {
154
+ super("STATE_ERROR", message, {
155
+ context: { path },
156
+ cause: cause instanceof Error ? cause : undefined
157
+ });
158
+ this.name = "StateError";
159
+ this.path = path;
251
160
  }
252
161
  }
253
- function getStateSnapshot(state) {
254
- const s = state;
255
- if (s.__getSnapshot) {
256
- return s.__getSnapshot();
162
+
163
+ class ParseError extends HypenError {
164
+ source;
165
+ constructor(message, source, cause) {
166
+ super("PARSE_ERROR", message, {
167
+ context: { source },
168
+ cause: cause instanceof Error ? cause : undefined
169
+ });
170
+ this.name = "ParseError";
171
+ this.source = source;
257
172
  }
258
- return deepClone(state);
259
173
  }
260
- function isStateProxy(value) {
261
- return value !== null && typeof value === "object" && value[IS_PROXY] === true;
174
+
175
+ class RenderError extends HypenError {
176
+ constructor(message, cause) {
177
+ super("RENDER_ERROR", message, {
178
+ cause: cause instanceof Error ? cause : undefined
179
+ });
180
+ this.name = "RenderError";
181
+ }
262
182
  }
263
- function unwrapProxy(value) {
264
- if (value !== null && typeof value === "object" && value[IS_PROXY]) {
265
- return value[RAW_TARGET];
183
+ function classifyEngineError(err) {
184
+ if (err && typeof err === "object" && "message" in err && "type" in err) {
185
+ const structured = err;
186
+ switch (structured.type) {
187
+ case "parseError":
188
+ return new ParseError(structured.message);
189
+ case "stateError":
190
+ return new StateError(structured.message);
191
+ case "renderError":
192
+ return new RenderError(structured.message);
193
+ case "actionError":
194
+ return new HypenError("ACTION_ERROR", structured.message);
195
+ default:
196
+ return new HypenError("UNKNOWN_ERROR", structured.message);
197
+ }
198
+ }
199
+ const message = err instanceof Error ? err.message : String(err);
200
+ if (message.startsWith("Parse error:")) {
201
+ return new ParseError(message);
202
+ }
203
+ if (message.startsWith("Invalid state:") || message.startsWith("Invalid state patch:")) {
204
+ return new StateError(message);
205
+ }
206
+ if (message.startsWith("Parent node not found:")) {
207
+ return new RenderError(message);
266
208
  }
267
- return value;
209
+ return new RenderError(message);
268
210
  }
269
- var IS_PROXY, RAW_TARGET;
270
- var init_state = __esm(() => {
271
- IS_PROXY = Symbol.for("hypen.isProxy");
272
- RAW_TARGET = Symbol.for("hypen.rawTarget");
273
- });
274
211
 
275
212
  // src/logger.ts
213
+ var LOG_LEVEL_ORDER = {
214
+ debug: 0,
215
+ info: 1,
216
+ warn: 2,
217
+ error: 3,
218
+ none: 4
219
+ };
220
+ var LOG_LEVEL_COLORS = {
221
+ debug: "\x1B[36m",
222
+ info: "\x1B[32m",
223
+ warn: "\x1B[33m",
224
+ error: "\x1B[31m"
225
+ };
226
+ var RESET_COLOR = "\x1B[0m";
276
227
  function isProduction() {
277
228
  if (typeof process !== "undefined" && process.env) {
278
229
  return false;
279
230
  }
280
231
  return false;
281
232
  }
233
+ var config = {
234
+ level: isProduction() ? "error" : "info",
235
+ colors: true,
236
+ timestamps: false
237
+ };
282
238
  function setLogLevel(level) {
283
239
  config.level = level;
284
240
  }
@@ -399,908 +355,59 @@ class Logger {
399
355
  this.loggedOnce.add(key);
400
356
  this.warn(...args);
401
357
  }
402
- debugOnce(key, ...args) {
403
- if (this.loggedOnce.has(key))
404
- return;
405
- this.loggedOnce.add(key);
406
- this.debug(...args);
407
- }
408
- }
409
- function createLogger(tag) {
410
- return new Logger(tag);
411
- }
412
- var LOG_LEVEL_ORDER, LOG_LEVEL_COLORS, RESET_COLOR = "\x1B[0m", config, logger, log, frameworkLoggers;
413
- var init_logger = __esm(() => {
414
- LOG_LEVEL_ORDER = {
415
- debug: 0,
416
- info: 1,
417
- warn: 2,
418
- error: 3,
419
- none: 4
420
- };
421
- LOG_LEVEL_COLORS = {
422
- debug: "\x1B[36m",
423
- info: "\x1B[32m",
424
- warn: "\x1B[33m",
425
- error: "\x1B[31m"
426
- };
427
- config = {
428
- level: isProduction() ? "error" : "info",
429
- colors: true,
430
- timestamps: false
431
- };
432
- logger = createLogger("Hypen");
433
- log = {
434
- debug: (tag, ...args) => {
435
- if (!shouldLog("debug"))
436
- return;
437
- console.log(formatTag(tag, "debug"), ...args);
438
- },
439
- info: (tag, ...args) => {
440
- if (!shouldLog("info"))
441
- return;
442
- console.info(formatTag(tag, "info"), ...args);
443
- },
444
- warn: (tag, ...args) => {
445
- if (!shouldLog("warn"))
446
- return;
447
- console.warn(formatTag(tag, "warn"), ...args);
448
- },
449
- error: (tag, ...args) => {
450
- if (!shouldLog("error"))
451
- return;
452
- console.error(formatTag(tag, "error"), ...args);
453
- }
454
- };
455
- frameworkLoggers = {
456
- hypen: createLogger("Hypen"),
457
- engine: createLogger("Engine"),
458
- router: createLogger("Router"),
459
- state: createLogger("State"),
460
- events: createLogger("Events"),
461
- remote: createLogger("Remote"),
462
- renderer: createLogger("Renderer"),
463
- module: createLogger("Module"),
464
- lifecycle: createLogger("Lifecycle"),
465
- loader: createLogger("Loader"),
466
- context: createLogger("Context"),
467
- discovery: createLogger("Discovery"),
468
- plugin: createLogger("Plugin"),
469
- canvas: createLogger("Canvas"),
470
- debug: createLogger("Debug")
471
- };
472
- });
473
-
474
- // src/result.ts
475
- function Ok(value) {
476
- return { ok: true, value };
477
- }
478
- function Err(error) {
479
- return { ok: false, error };
480
- }
481
- function classifyEngineError(err) {
482
- const message = err instanceof Error ? err.message : String(err);
483
- if (message.startsWith("Parse error:")) {
484
- return new ParseError(message);
485
- }
486
- if (message.startsWith("Invalid state:") || message.startsWith("Invalid state patch:")) {
487
- return new StateError(message);
488
- }
489
- if (message.startsWith("Parent node not found:")) {
490
- return new RenderError(message);
491
- }
492
- return new RenderError(message);
493
- }
494
- var HypenError, ActionError, ConnectionError, StateError, ParseError, RenderError;
495
- var init_result = __esm(() => {
496
- HypenError = class HypenError extends Error {
497
- code;
498
- context;
499
- cause;
500
- constructor(code, message, options) {
501
- super(message);
502
- this.name = "HypenError";
503
- this.code = code;
504
- this.context = options?.context;
505
- this.cause = options?.cause;
506
- Object.setPrototypeOf(this, new.target.prototype);
507
- }
508
- };
509
- ActionError = class ActionError extends HypenError {
510
- actionName;
511
- constructor(actionName, cause) {
512
- super("ACTION_ERROR", `Action handler "${actionName}" failed: ${cause instanceof Error ? cause.message : String(cause)}`, {
513
- context: { actionName },
514
- cause: cause instanceof Error ? cause : undefined
515
- });
516
- this.name = "ActionError";
517
- this.actionName = actionName;
518
- }
519
- };
520
- ConnectionError = class ConnectionError extends HypenError {
521
- url;
522
- attempt;
523
- constructor(url, cause, attempt) {
524
- super("CONNECTION_ERROR", `Connection to "${url}" failed${attempt ? ` (attempt ${attempt})` : ""}: ${cause instanceof Error ? cause.message : String(cause)}`, {
525
- context: { url, attempt },
526
- cause: cause instanceof Error ? cause : undefined
527
- });
528
- this.name = "ConnectionError";
529
- this.url = url;
530
- this.attempt = attempt;
531
- }
532
- };
533
- StateError = class StateError extends HypenError {
534
- path;
535
- constructor(message, path, cause) {
536
- super("STATE_ERROR", message, {
537
- context: { path },
538
- cause: cause instanceof Error ? cause : undefined
539
- });
540
- this.name = "StateError";
541
- this.path = path;
542
- }
543
- };
544
- ParseError = class ParseError extends HypenError {
545
- source;
546
- constructor(message, source, cause) {
547
- super("PARSE_ERROR", message, {
548
- context: { source },
549
- cause: cause instanceof Error ? cause : undefined
550
- });
551
- this.name = "ParseError";
552
- this.source = source;
553
- }
554
- };
555
- RenderError = class RenderError extends HypenError {
556
- constructor(message, cause) {
557
- super("RENDER_ERROR", message, {
558
- cause: cause instanceof Error ? cause : undefined
559
- });
560
- this.name = "RenderError";
561
- }
562
- };
563
- });
564
-
565
- // src/datasource.ts
566
- class DataSourceManager {
567
- plugins = new Map;
568
- configs = new Map;
569
- state = new Map;
570
- engine;
571
- constructor(engine) {
572
- this.engine = engine;
573
- }
574
- async use(plugin, config2) {
575
- const { name } = plugin;
576
- this.state.set(name, {});
577
- this.plugins.set(name, plugin);
578
- this.configs.set(name, config2);
579
- await plugin.connect(config2, (change) => {
580
- const current = this.state.get(name) ?? {};
581
- for (const path of change.paths) {
582
- if (path in change.values) {
583
- current[path] = change.values[path];
584
- }
585
- }
586
- this.state.set(name, current);
587
- this.engine.setContext(name, current);
588
- });
589
- }
590
- get(name) {
591
- return this.plugins.get(name);
592
- }
593
- has(name) {
594
- return this.plugins.has(name);
595
- }
596
- getNames() {
597
- return Array.from(this.plugins.keys());
598
- }
599
- getStatus(name) {
600
- return this.plugins.get(name)?.status;
601
- }
602
- async remove(name) {
603
- const plugin = this.plugins.get(name);
604
- if (plugin) {
605
- await plugin.disconnect();
606
- this.plugins.delete(name);
607
- this.configs.delete(name);
608
- this.state.delete(name);
609
- this.engine.removeContext(name);
610
- }
611
- }
612
- async disconnectAll() {
613
- const names = Array.from(this.plugins.keys());
614
- await Promise.all(names.map((name) => this.remove(name)));
615
- }
616
- }
617
-
618
- // src/app.ts
619
- var exports_app = {};
620
- __export(exports_app, {
621
- app: () => app,
622
- HypenModuleInstance: () => HypenModuleInstance,
623
- HypenAppBuilder: () => HypenAppBuilder,
624
- HypenApp: () => HypenApp
625
- });
626
-
627
- class HypenAppBuilder {
628
- initialState;
629
- options;
630
- createdHandler;
631
- actionHandlers = new Map;
632
- destroyedHandler;
633
- disconnectHandler;
634
- reconnectHandler;
635
- expireHandler;
636
- errorHandler;
637
- template;
638
- _registry;
639
- dataSourceEntries = [];
640
- constructor(initialState, options, registry) {
641
- this.initialState = initialState;
642
- this.options = options || {};
643
- this._registry = registry;
644
- }
645
- onCreated(fn) {
646
- this.createdHandler = fn;
647
- return this;
648
- }
649
- onAction(name, fn) {
650
- this.actionHandlers.set(name, fn);
651
- return this;
652
- }
653
- onDestroyed(fn) {
654
- this.destroyedHandler = fn;
655
- return this;
656
- }
657
- onDisconnect(fn) {
658
- this.disconnectHandler = fn;
659
- return this;
660
- }
661
- onReconnect(fn) {
662
- this.reconnectHandler = fn;
663
- return this;
664
- }
665
- onExpire(fn) {
666
- this.expireHandler = fn;
667
- return this;
668
- }
669
- onError(fn) {
670
- this.errorHandler = fn;
671
- return this;
672
- }
673
- useDataSource(plugin, config2) {
674
- this.dataSourceEntries.push({ plugin, config: config2 });
675
- return this;
676
- }
677
- ui(template) {
678
- this.template = template;
679
- return this.build();
680
- }
681
- uiFile(path) {
682
- const fs = (() => ({}));
683
- this.template = fs.readFileSync(path, "utf-8").trim();
684
- return this.build();
685
- }
686
- build() {
687
- const stateKeys = this.initialState !== null && typeof this.initialState === "object" ? Object.keys(this.initialState) : [];
688
- const definition = {
689
- name: this.options.name,
690
- actions: Array.from(this.actionHandlers.keys()),
691
- stateKeys,
692
- persist: this.options.persist,
693
- version: this.options.version,
694
- initialState: this.initialState,
695
- template: this.template,
696
- dataSources: this.dataSourceEntries.length > 0 ? this.dataSourceEntries : undefined,
697
- handlers: {
698
- onCreated: this.createdHandler,
699
- onAction: this.actionHandlers,
700
- onDestroyed: this.destroyedHandler,
701
- onDisconnect: this.disconnectHandler,
702
- onReconnect: this.reconnectHandler,
703
- onExpire: this.expireHandler,
704
- onError: this.errorHandler
705
- }
706
- };
707
- if (this.options.name && this._registry) {
708
- this._registry.set(this.options.name, definition);
709
- }
710
- return definition;
711
- }
712
- }
713
-
714
- class HypenApp {
715
- _registry = new Map;
716
- defineState(initial, options) {
717
- return new HypenAppBuilder(initial, options, this._registry);
718
- }
719
- module(name) {
720
- const registry = this._registry;
721
- return {
722
- defineState: (initial, options) => {
723
- return new HypenAppBuilder(initial, { ...options, name }, registry);
724
- }
725
- };
726
- }
727
- get(name) {
728
- return this._registry.get(name);
729
- }
730
- has(name) {
731
- return this._registry.has(name);
732
- }
733
- get components() {
734
- return this._registry;
735
- }
736
- getNames() {
737
- return Array.from(this._registry.keys());
738
- }
739
- get size() {
740
- return this._registry.size;
741
- }
742
- unregister(name) {
743
- this._registry.delete(name);
744
- }
745
- clear() {
746
- this._registry.clear();
747
- }
748
- }
749
-
750
- class HypenModuleInstance {
751
- engine;
752
- definition;
753
- state;
754
- isDestroyed = false;
755
- router;
756
- globalContext;
757
- stateChangeCallbacks = [];
758
- dataSourceManager;
759
- dataSourceAccessor = {};
760
- constructor(engine, definition, router, globalContext) {
761
- this.engine = engine;
762
- this.definition = definition;
763
- this.router = router ?? null;
764
- this.globalContext = globalContext;
765
- const statePrefix = (definition.name || "").toLowerCase();
766
- this.state = createObservableState(definition.initialState, {
767
- onChange: (change) => {
768
- this.engine.notifyStateChange(change.paths, change.newValues);
769
- this.stateChangeCallbacks.forEach((cb) => cb());
770
- },
771
- pathPrefix: statePrefix || undefined
772
- });
773
- const snapshot = getStateSnapshot(this.state);
774
- const prefixedState = statePrefix ? { [statePrefix]: snapshot } : snapshot;
775
- this.engine.setModule(definition.name || "AnonymousModule", definition.actions, definition.stateKeys, prefixedState);
776
- for (const [actionName, handler] of definition.handlers.onAction) {
777
- log2.debug(`Registering action handler: ${actionName} for module ${definition.name}`);
778
- this.engine.onAction(actionName, async (action) => {
779
- log2.debug(`Action handler fired: ${actionName}`, action);
780
- const actionCtx = {
781
- name: action.name,
782
- payload: action.payload,
783
- sender: action.sender
784
- };
785
- const context = this.globalContext ? this.createGlobalContextAPI() : undefined;
786
- const result = await this.executeAction(actionName, handler, {
787
- action: actionCtx,
788
- state: this.state,
789
- context,
790
- dataSources: this.dataSourceAccessor
791
- });
792
- if (!result.ok) {
793
- const shouldRethrow = await this.handleError(result.error, { actionName });
794
- if (shouldRethrow) {
795
- throw result.error;
796
- }
797
- } else {
798
- log2.debug(`Action handler completed: ${actionName}`);
799
- }
800
- });
801
- }
802
- this.engine.onAction("__hypen_bind", (action) => {
803
- const payload = action.payload;
804
- if (!payload?.path)
805
- return;
806
- const segments = payload.path.split(".");
807
- let target = this.state;
808
- for (let i = 0;i < segments.length - 1; i++) {
809
- const seg = segments[i];
810
- target = target?.[seg];
811
- if (target == null)
812
- return;
813
- }
814
- const lastSeg = segments[segments.length - 1];
815
- target[lastSeg] = payload.value;
816
- });
817
- this.callCreatedHandler();
818
- }
819
- createGlobalContextAPI() {
820
- if (!this.globalContext) {
821
- throw new Error("Global context not available");
822
- }
823
- const ctx = this.globalContext;
824
- const api = {
825
- getModule: (id) => ctx.getModule(id),
826
- hasModule: (id) => ctx.hasModule(id),
827
- getModuleIds: () => ctx.getModuleIds(),
828
- getGlobalState: () => ctx.getGlobalState(),
829
- emit: (event, payload) => ctx.emit(event, payload),
830
- on: (event, handler) => ctx.on(event, handler),
831
- router: this.router
832
- };
833
- const ctxRecord = ctx;
834
- if (ctxRecord.__hypenEngine) {
835
- api.__hypenEngine = ctxRecord.__hypenEngine;
836
- }
837
- return api;
838
- }
839
- async executeAction(actionName, handler, ctx) {
840
- try {
841
- const result = handler(ctx);
842
- await result;
843
- return Ok(undefined);
844
- } catch (e) {
845
- return Err(new ActionError(actionName, e));
846
- }
847
- }
848
- async handleError(error, context) {
849
- const errorCtx = {
850
- error,
851
- state: this.state,
852
- actionName: context.actionName,
853
- lifecycle: context.lifecycle
854
- };
855
- if (this.definition.handlers.onError) {
856
- try {
857
- const result = await this.definition.handlers.onError(errorCtx);
858
- if (result && typeof result === "object") {
859
- if ("handled" in result && result.handled) {
860
- return false;
861
- }
862
- if ("rethrow" in result && result.rethrow) {
863
- return true;
864
- }
865
- }
866
- } catch (handlerError) {
867
- log2.error("Error in onError handler:", handlerError);
868
- }
869
- }
870
- if (this.globalContext) {
871
- const eventContext = context.actionName ? `action:${context.actionName}` : context.lifecycle ? `lifecycle:${context.lifecycle}` : "unknown";
872
- this.globalContext.emit("error", {
873
- message: error.message,
874
- error,
875
- context: eventContext
876
- });
877
- }
878
- log2.error(`${context.actionName ? `Action "${context.actionName}"` : `Lifecycle "${context.lifecycle}"`} error:`, error);
879
- return false;
880
- }
881
- async callCreatedHandler() {
882
- if (this.definition.dataSources?.length) {
883
- const dsEngine = this.engine;
884
- this.dataSourceManager = new DataSourceManager(dsEngine);
885
- for (const { plugin, config: config2 } of this.definition.dataSources) {
886
- try {
887
- await this.dataSourceManager.use(plugin, config2);
888
- this.dataSourceAccessor[plugin.name] = new Proxy(plugin, {
889
- get(target, prop) {
890
- if (typeof prop === "string" && !(prop in target)) {
891
- return (...args) => target.call(prop, ...args);
892
- }
893
- return target[prop];
894
- }
895
- });
896
- } catch (e) {
897
- log2.error(`Failed to connect data source "${plugin.name}":`, e);
898
- }
899
- }
900
- }
901
- if (this.definition.handlers.onCreated) {
902
- const context = this.globalContext ? this.createGlobalContextAPI() : undefined;
903
- try {
904
- await this.definition.handlers.onCreated(this.state, context);
905
- } catch (e) {
906
- const error = e instanceof HypenError ? e : new ActionError("onCreated", e);
907
- const shouldRethrow = await this.handleError(error, { lifecycle: "created" });
908
- if (shouldRethrow) {
909
- throw error;
910
- }
911
- }
912
- }
913
- }
914
- onStateChange(callback) {
915
- this.stateChangeCallbacks.push(callback);
916
- }
917
- async destroy() {
918
- if (this.isDestroyed)
919
- return;
920
- if (this.dataSourceManager) {
921
- try {
922
- await this.dataSourceManager.disconnectAll();
923
- } catch (e) {
924
- log2.error("Error disconnecting data sources:", e);
925
- }
926
- this.dataSourceManager = undefined;
927
- this.dataSourceAccessor = {};
928
- }
929
- if (this.definition.handlers.onDestroyed) {
930
- try {
931
- await this.definition.handlers.onDestroyed(this.state);
932
- } catch (e) {
933
- const error = e instanceof HypenError ? e : new ActionError("onDestroyed", e);
934
- const shouldRethrow = await this.handleError(error, { lifecycle: "destroyed" });
935
- if (shouldRethrow) {
936
- throw error;
937
- }
938
- }
939
- }
940
- this.isDestroyed = true;
941
- }
942
- getState() {
943
- return getStateSnapshot(this.state);
944
- }
945
- getLiveState() {
946
- return this.state;
947
- }
948
- updateState(patch) {
949
- Object.assign(this.state, patch);
950
- }
951
- }
952
- var log2, app;
953
- var init_app = __esm(() => {
954
- init_result();
955
- init_state();
956
- init_logger();
957
- log2 = createLogger("ModuleInstance");
958
- app = new HypenApp;
959
- });
960
-
961
- // node:path
962
- var exports_path = {};
963
- __export(exports_path, {
964
- sep: () => sep,
965
- resolve: () => resolve,
966
- relative: () => relative,
967
- posix: () => posix,
968
- parse: () => parse,
969
- normalize: () => normalize,
970
- join: () => join,
971
- isAbsolute: () => isAbsolute,
972
- format: () => format,
973
- extname: () => extname,
974
- dirname: () => dirname,
975
- delimiter: () => delimiter,
976
- default: () => path_default,
977
- basename: () => basename,
978
- _makeLong: () => _makeLong
979
- });
980
- function assertPath(path) {
981
- if (typeof path !== "string")
982
- throw TypeError("Path must be a string. Received " + JSON.stringify(path));
983
- }
984
- function normalizeStringPosix(path, allowAboveRoot) {
985
- var res = "", lastSegmentLength = 0, lastSlash = -1, dots = 0, code;
986
- for (var i = 0;i <= path.length; ++i) {
987
- if (i < path.length)
988
- code = path.charCodeAt(i);
989
- else if (code === 47)
990
- break;
991
- else
992
- code = 47;
993
- if (code === 47) {
994
- if (lastSlash === i - 1 || dots === 1)
995
- ;
996
- else if (lastSlash !== i - 1 && dots === 2) {
997
- if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 || res.charCodeAt(res.length - 2) !== 46) {
998
- if (res.length > 2) {
999
- var lastSlashIndex = res.lastIndexOf("/");
1000
- if (lastSlashIndex !== res.length - 1) {
1001
- if (lastSlashIndex === -1)
1002
- res = "", lastSegmentLength = 0;
1003
- else
1004
- res = res.slice(0, lastSlashIndex), lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
1005
- lastSlash = i, dots = 0;
1006
- continue;
1007
- }
1008
- } else if (res.length === 2 || res.length === 1) {
1009
- res = "", lastSegmentLength = 0, lastSlash = i, dots = 0;
1010
- continue;
1011
- }
1012
- }
1013
- if (allowAboveRoot) {
1014
- if (res.length > 0)
1015
- res += "/..";
1016
- else
1017
- res = "..";
1018
- lastSegmentLength = 2;
1019
- }
1020
- } else {
1021
- if (res.length > 0)
1022
- res += "/" + path.slice(lastSlash + 1, i);
1023
- else
1024
- res = path.slice(lastSlash + 1, i);
1025
- lastSegmentLength = i - lastSlash - 1;
1026
- }
1027
- lastSlash = i, dots = 0;
1028
- } else if (code === 46 && dots !== -1)
1029
- ++dots;
1030
- else
1031
- dots = -1;
1032
- }
1033
- return res;
1034
- }
1035
- function _format(sep, pathObject) {
1036
- var dir = pathObject.dir || pathObject.root, base = pathObject.base || (pathObject.name || "") + (pathObject.ext || "");
1037
- if (!dir)
1038
- return base;
1039
- if (dir === pathObject.root)
1040
- return dir + base;
1041
- return dir + sep + base;
1042
- }
1043
- function resolve() {
1044
- var resolvedPath = "", resolvedAbsolute = false, cwd;
1045
- for (var i = arguments.length - 1;i >= -1 && !resolvedAbsolute; i--) {
1046
- var path;
1047
- if (i >= 0)
1048
- path = arguments[i];
1049
- else {
1050
- if (cwd === undefined)
1051
- cwd = process.cwd();
1052
- path = cwd;
1053
- }
1054
- if (assertPath(path), path.length === 0)
1055
- continue;
1056
- resolvedPath = path + "/" + resolvedPath, resolvedAbsolute = path.charCodeAt(0) === 47;
1057
- }
1058
- if (resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute), resolvedAbsolute)
1059
- if (resolvedPath.length > 0)
1060
- return "/" + resolvedPath;
1061
- else
1062
- return "/";
1063
- else if (resolvedPath.length > 0)
1064
- return resolvedPath;
1065
- else
1066
- return ".";
1067
- }
1068
- function normalize(path) {
1069
- if (assertPath(path), path.length === 0)
1070
- return ".";
1071
- var isAbsolute = path.charCodeAt(0) === 47, trailingSeparator = path.charCodeAt(path.length - 1) === 47;
1072
- if (path = normalizeStringPosix(path, !isAbsolute), path.length === 0 && !isAbsolute)
1073
- path = ".";
1074
- if (path.length > 0 && trailingSeparator)
1075
- path += "/";
1076
- if (isAbsolute)
1077
- return "/" + path;
1078
- return path;
1079
- }
1080
- function isAbsolute(path) {
1081
- return assertPath(path), path.length > 0 && path.charCodeAt(0) === 47;
1082
- }
1083
- function join() {
1084
- if (arguments.length === 0)
1085
- return ".";
1086
- var joined;
1087
- for (var i = 0;i < arguments.length; ++i) {
1088
- var arg = arguments[i];
1089
- if (assertPath(arg), arg.length > 0)
1090
- if (joined === undefined)
1091
- joined = arg;
1092
- else
1093
- joined += "/" + arg;
1094
- }
1095
- if (joined === undefined)
1096
- return ".";
1097
- return normalize(joined);
1098
- }
1099
- function relative(from, to) {
1100
- if (assertPath(from), assertPath(to), from === to)
1101
- return "";
1102
- if (from = resolve(from), to = resolve(to), from === to)
1103
- return "";
1104
- var fromStart = 1;
1105
- for (;fromStart < from.length; ++fromStart)
1106
- if (from.charCodeAt(fromStart) !== 47)
1107
- break;
1108
- var fromEnd = from.length, fromLen = fromEnd - fromStart, toStart = 1;
1109
- for (;toStart < to.length; ++toStart)
1110
- if (to.charCodeAt(toStart) !== 47)
1111
- break;
1112
- var toEnd = to.length, toLen = toEnd - toStart, length = fromLen < toLen ? fromLen : toLen, lastCommonSep = -1, i = 0;
1113
- for (;i <= length; ++i) {
1114
- if (i === length) {
1115
- if (toLen > length) {
1116
- if (to.charCodeAt(toStart + i) === 47)
1117
- return to.slice(toStart + i + 1);
1118
- else if (i === 0)
1119
- return to.slice(toStart + i);
1120
- } else if (fromLen > length) {
1121
- if (from.charCodeAt(fromStart + i) === 47)
1122
- lastCommonSep = i;
1123
- else if (i === 0)
1124
- lastCommonSep = 0;
1125
- }
1126
- break;
1127
- }
1128
- var fromCode = from.charCodeAt(fromStart + i), toCode = to.charCodeAt(toStart + i);
1129
- if (fromCode !== toCode)
1130
- break;
1131
- else if (fromCode === 47)
1132
- lastCommonSep = i;
1133
- }
1134
- var out = "";
1135
- for (i = fromStart + lastCommonSep + 1;i <= fromEnd; ++i)
1136
- if (i === fromEnd || from.charCodeAt(i) === 47)
1137
- if (out.length === 0)
1138
- out += "..";
1139
- else
1140
- out += "/..";
1141
- if (out.length > 0)
1142
- return out + to.slice(toStart + lastCommonSep);
1143
- else {
1144
- if (toStart += lastCommonSep, to.charCodeAt(toStart) === 47)
1145
- ++toStart;
1146
- return to.slice(toStart);
1147
- }
1148
- }
1149
- function _makeLong(path) {
1150
- return path;
1151
- }
1152
- function dirname(path) {
1153
- if (assertPath(path), path.length === 0)
1154
- return ".";
1155
- var code = path.charCodeAt(0), hasRoot = code === 47, end = -1, matchedSlash = true;
1156
- for (var i = path.length - 1;i >= 1; --i)
1157
- if (code = path.charCodeAt(i), code === 47) {
1158
- if (!matchedSlash) {
1159
- end = i;
1160
- break;
1161
- }
1162
- } else
1163
- matchedSlash = false;
1164
- if (end === -1)
1165
- return hasRoot ? "/" : ".";
1166
- if (hasRoot && end === 1)
1167
- return "//";
1168
- return path.slice(0, end);
1169
- }
1170
- function basename(path, ext) {
1171
- if (ext !== undefined && typeof ext !== "string")
1172
- throw TypeError('"ext" argument must be a string');
1173
- assertPath(path);
1174
- var start = 0, end = -1, matchedSlash = true, i;
1175
- if (ext !== undefined && ext.length > 0 && ext.length <= path.length) {
1176
- if (ext.length === path.length && ext === path)
1177
- return "";
1178
- var extIdx = ext.length - 1, firstNonSlashEnd = -1;
1179
- for (i = path.length - 1;i >= 0; --i) {
1180
- var code = path.charCodeAt(i);
1181
- if (code === 47) {
1182
- if (!matchedSlash) {
1183
- start = i + 1;
1184
- break;
1185
- }
1186
- } else {
1187
- if (firstNonSlashEnd === -1)
1188
- matchedSlash = false, firstNonSlashEnd = i + 1;
1189
- if (extIdx >= 0)
1190
- if (code === ext.charCodeAt(extIdx)) {
1191
- if (--extIdx === -1)
1192
- end = i;
1193
- } else
1194
- extIdx = -1, end = firstNonSlashEnd;
1195
- }
1196
- }
1197
- if (start === end)
1198
- end = firstNonSlashEnd;
1199
- else if (end === -1)
1200
- end = path.length;
1201
- return path.slice(start, end);
1202
- } else {
1203
- for (i = path.length - 1;i >= 0; --i)
1204
- if (path.charCodeAt(i) === 47) {
1205
- if (!matchedSlash) {
1206
- start = i + 1;
1207
- break;
1208
- }
1209
- } else if (end === -1)
1210
- matchedSlash = false, end = i + 1;
1211
- if (end === -1)
1212
- return "";
1213
- return path.slice(start, end);
358
+ debugOnce(key, ...args) {
359
+ if (this.loggedOnce.has(key))
360
+ return;
361
+ this.loggedOnce.add(key);
362
+ this.debug(...args);
1214
363
  }
1215
364
  }
1216
- function extname(path) {
1217
- assertPath(path);
1218
- var startDot = -1, startPart = 0, end = -1, matchedSlash = true, preDotState = 0;
1219
- for (var i = path.length - 1;i >= 0; --i) {
1220
- var code = path.charCodeAt(i);
1221
- if (code === 47) {
1222
- if (!matchedSlash) {
1223
- startPart = i + 1;
1224
- break;
1225
- }
1226
- continue;
1227
- }
1228
- if (end === -1)
1229
- matchedSlash = false, end = i + 1;
1230
- if (code === 46) {
1231
- if (startDot === -1)
1232
- startDot = i;
1233
- else if (preDotState !== 1)
1234
- preDotState = 1;
1235
- } else if (startDot !== -1)
1236
- preDotState = -1;
1237
- }
1238
- if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
1239
- return "";
1240
- return path.slice(startDot, end);
1241
- }
1242
- function format(pathObject) {
1243
- if (pathObject === null || typeof pathObject !== "object")
1244
- throw TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof pathObject);
1245
- return _format("/", pathObject);
1246
- }
1247
- function parse(path) {
1248
- assertPath(path);
1249
- var ret = { root: "", dir: "", base: "", ext: "", name: "" };
1250
- if (path.length === 0)
1251
- return ret;
1252
- var code = path.charCodeAt(0), isAbsolute2 = code === 47, start;
1253
- if (isAbsolute2)
1254
- ret.root = "/", start = 1;
1255
- else
1256
- start = 0;
1257
- var startDot = -1, startPart = 0, end = -1, matchedSlash = true, i = path.length - 1, preDotState = 0;
1258
- for (;i >= start; --i) {
1259
- if (code = path.charCodeAt(i), code === 47) {
1260
- if (!matchedSlash) {
1261
- startPart = i + 1;
1262
- break;
1263
- }
1264
- continue;
1265
- }
1266
- if (end === -1)
1267
- matchedSlash = false, end = i + 1;
1268
- if (code === 46) {
1269
- if (startDot === -1)
1270
- startDot = i;
1271
- else if (preDotState !== 1)
1272
- preDotState = 1;
1273
- } else if (startDot !== -1)
1274
- preDotState = -1;
1275
- }
1276
- if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
1277
- if (end !== -1)
1278
- if (startPart === 0 && isAbsolute2)
1279
- ret.base = ret.name = path.slice(1, end);
1280
- else
1281
- ret.base = ret.name = path.slice(startPart, end);
1282
- } else {
1283
- if (startPart === 0 && isAbsolute2)
1284
- ret.name = path.slice(1, startDot), ret.base = path.slice(1, end);
1285
- else
1286
- ret.name = path.slice(startPart, startDot), ret.base = path.slice(startPart, end);
1287
- ret.ext = path.slice(startDot, end);
1288
- }
1289
- if (startPart > 0)
1290
- ret.dir = path.slice(0, startPart - 1);
1291
- else if (isAbsolute2)
1292
- ret.dir = "/";
1293
- return ret;
365
+ function createLogger(tag) {
366
+ return new Logger(tag);
1294
367
  }
1295
- var sep = "/", delimiter = ":", posix, path_default;
1296
- var init_path = __esm(() => {
1297
- posix = ((p) => (p.posix = p, p))({ resolve, normalize, isAbsolute, join, relative, _makeLong, dirname, basename, extname, format, parse, sep, delimiter, win32: null, posix: null });
1298
- path_default = posix;
1299
- });
368
+ var logger = createLogger("Hypen");
369
+ var log = {
370
+ debug: (tag, ...args) => {
371
+ if (!shouldLog("debug"))
372
+ return;
373
+ console.log(formatTag(tag, "debug"), ...args);
374
+ },
375
+ info: (tag, ...args) => {
376
+ if (!shouldLog("info"))
377
+ return;
378
+ console.info(formatTag(tag, "info"), ...args);
379
+ },
380
+ warn: (tag, ...args) => {
381
+ if (!shouldLog("warn"))
382
+ return;
383
+ console.warn(formatTag(tag, "warn"), ...args);
384
+ },
385
+ error: (tag, ...args) => {
386
+ if (!shouldLog("error"))
387
+ return;
388
+ console.error(formatTag(tag, "error"), ...args);
389
+ }
390
+ };
391
+ var frameworkLoggers = {
392
+ hypen: createLogger("Hypen"),
393
+ engine: createLogger("Engine"),
394
+ router: createLogger("Router"),
395
+ state: createLogger("State"),
396
+ events: createLogger("Events"),
397
+ remote: createLogger("Remote"),
398
+ renderer: createLogger("Renderer"),
399
+ module: createLogger("Module"),
400
+ lifecycle: createLogger("Lifecycle"),
401
+ loader: createLogger("Loader"),
402
+ context: createLogger("Context"),
403
+ discovery: createLogger("Discovery"),
404
+ plugin: createLogger("Plugin"),
405
+ canvas: createLogger("Canvas"),
406
+ debug: createLogger("Debug")
407
+ };
1300
408
 
1301
409
  // src/disposable.ts
1302
- init_logger();
1303
- var log3 = frameworkLoggers.lifecycle;
410
+ var log2 = frameworkLoggers.lifecycle;
1304
411
  function isDisposable(obj) {
1305
412
  return obj !== null && typeof obj === "object" && "dispose" in obj && typeof obj.dispose === "function";
1306
413
  }
@@ -1332,7 +439,7 @@ class DisposableStack {
1332
439
  try {
1333
440
  item.dispose();
1334
441
  } catch (error) {
1335
- log3.error("Error during dispose:", error);
442
+ log2.error("Error during dispose:", error);
1336
443
  }
1337
444
  }
1338
445
  }
@@ -1423,7 +530,7 @@ function compositeDisposable(...disposables) {
1423
530
  try {
1424
531
  d.dispose();
1425
532
  } catch (error) {
1426
- log3.error("Error during dispose:", error);
533
+ log2.error("Error during dispose:", error);
1427
534
  }
1428
535
  }
1429
536
  }
@@ -1446,11 +553,7 @@ function usingSync(resource, fn) {
1446
553
  }
1447
554
  }
1448
555
 
1449
- // src/remote/client.ts
1450
- init_result();
1451
-
1452
556
  // src/retry.ts
1453
- init_result();
1454
557
  var DEFAULT_OPTIONS = {
1455
558
  maxAttempts: 3,
1456
559
  delayMs: 1000,
@@ -1514,6 +617,17 @@ async function retry(fn, options = {}) {
1514
617
  }
1515
618
  throw lastError;
1516
619
  }
620
+ async function retryResult(fn, options = {}) {
621
+ try {
622
+ const value = await retry(fn, options);
623
+ return Ok(value);
624
+ } catch (e) {
625
+ return Err(e instanceof Error ? e : new Error(String(e)));
626
+ }
627
+ }
628
+ function withRetry(fn, options = {}) {
629
+ return (...args) => retry(() => fn(...args), options);
630
+ }
1517
631
  var RetryConditions = {
1518
632
  networkErrors: (error) => {
1519
633
  const message = error.message.toLowerCase();
@@ -1565,8 +679,7 @@ var RetryPresets = {
1565
679
  };
1566
680
 
1567
681
  // src/remote/client.ts
1568
- init_logger();
1569
- var log4 = frameworkLoggers.remote;
682
+ var log3 = frameworkLoggers.remote;
1570
683
 
1571
684
  class RemoteEngine {
1572
685
  ws = null;
@@ -1674,7 +787,7 @@ class RemoteEngine {
1674
787
  }
1675
788
  dispatchAction(action, payload) {
1676
789
  if (this.state !== "connected" || !this.ws) {
1677
- log4.warn("Cannot dispatch action: not connected");
790
+ log3.warn("Cannot dispatch action: not connected");
1678
791
  return;
1679
792
  }
1680
793
  const message = {
@@ -1687,7 +800,7 @@ class RemoteEngine {
1687
800
  }
1688
801
  subscribeState() {
1689
802
  if (this.state !== "connected" || !this.ws) {
1690
- log4.warn("Cannot subscribe to state: not connected");
803
+ log3.warn("Cannot subscribe to state: not connected");
1691
804
  return;
1692
805
  }
1693
806
  const message = { type: "subscribeState" };
@@ -1695,7 +808,7 @@ class RemoteEngine {
1695
808
  }
1696
809
  updateState(state) {
1697
810
  if (this.state !== "connected" || !this.ws) {
1698
- log4.warn("Cannot update state: not connected");
811
+ log3.warn("Cannot update state: not connected");
1699
812
  return;
1700
813
  }
1701
814
  const message = {
@@ -1767,7 +880,7 @@ class RemoteEngine {
1767
880
  break;
1768
881
  }
1769
882
  } catch (e) {
1770
- log4.error("Error handling remote message:", e);
883
+ log3.error("Error handling remote message:", e);
1771
884
  const error = e instanceof Error ? e : new Error(String(e));
1772
885
  this.errorCallbacks.forEach((cb) => cb(error));
1773
886
  }
@@ -1796,7 +909,7 @@ class RemoteEngine {
1796
909
  }
1797
910
  handlePatch(message) {
1798
911
  if (message.revision <= this.currentRevision) {
1799
- log4.warn(`Out of order patch: expected > ${this.currentRevision}, got ${message.revision}`);
912
+ log3.warn(`Out of order patch: expected > ${this.currentRevision}, got ${message.revision}`);
1800
913
  return;
1801
914
  }
1802
915
  this.currentRevision = message.revision;
@@ -1822,171 +935,16 @@ class RemoteEngine {
1822
935
  maxDelayMs: 30000,
1823
936
  jitter: 0.1,
1824
937
  onRetry: (attempt, error) => {
1825
- log4.debug(`Reconnection attempt ${attempt}/${this.options.maxReconnectAttempts} failed: ${error.message}`);
938
+ log3.debug(`Reconnection attempt ${attempt}/${this.options.maxReconnectAttempts} failed: ${error.message}`);
1826
939
  }
1827
940
  }).catch((error) => {
1828
- log4.error("Max reconnection attempts reached:", error.message);
941
+ log3.error("Max reconnection attempts reached:", error.message);
1829
942
  this.errorCallbacks.forEach((cb) => cb(new ConnectionError(this.url, error, this.options.maxReconnectAttempts)));
1830
943
  });
1831
944
  }, this.options.reconnectInterval);
1832
945
  }
1833
946
  }
1834
947
 
1835
- // src/engine.ts
1836
- init_logger();
1837
- init_result();
1838
- import { WasmEngine } from "../wasm-node/hypen_engine.js";
1839
- var log5 = frameworkLoggers.engine;
1840
- function unwrapForWasm(value) {
1841
- if (value === null || typeof value !== "object") {
1842
- return value;
1843
- }
1844
- if (typeof value.__getSnapshot === "function") {
1845
- return value.__getSnapshot();
1846
- }
1847
- try {
1848
- return structuredClone(value);
1849
- } catch {
1850
- return JSON.parse(JSON.stringify(value));
1851
- }
1852
- }
1853
-
1854
- class Engine {
1855
- wasmEngine = null;
1856
- initialized = false;
1857
- async init() {
1858
- if (this.initialized)
1859
- return;
1860
- this.wasmEngine = new WasmEngine;
1861
- this.initialized = true;
1862
- }
1863
- ensureInitialized() {
1864
- if (!this.wasmEngine) {
1865
- throw new Error("Engine not initialized. Call init() first.");
1866
- }
1867
- return this.wasmEngine;
1868
- }
1869
- setRenderCallback(callback) {
1870
- const engine = this.ensureInitialized();
1871
- engine.setRenderCallback((patches) => {
1872
- callback(patches);
1873
- });
1874
- }
1875
- setComponentResolver(resolver) {
1876
- const engine = this.ensureInitialized();
1877
- engine.setComponentResolver((componentName, contextPath) => {
1878
- const result = resolver(componentName, contextPath);
1879
- return result;
1880
- });
1881
- }
1882
- renderSource(source) {
1883
- const engine = this.ensureInitialized();
1884
- try {
1885
- engine.renderSource(source);
1886
- } catch (err) {
1887
- throw classifyEngineError(err);
1888
- }
1889
- }
1890
- renderLazyComponent(source) {
1891
- const engine = this.ensureInitialized();
1892
- engine.renderLazyComponent(source);
1893
- }
1894
- renderInto(source, parentNodeId, state) {
1895
- const engine = this.ensureInitialized();
1896
- try {
1897
- engine.renderInto(source, parentNodeId, unwrapForWasm(state));
1898
- } catch (err) {
1899
- throw classifyEngineError(err);
1900
- }
1901
- }
1902
- notifyStateChange(paths, values) {
1903
- const engine = this.ensureInitialized();
1904
- if (paths.length === 0) {
1905
- return;
1906
- }
1907
- try {
1908
- engine.updateStateSparse(paths, unwrapForWasm(values));
1909
- } catch (err) {
1910
- throw classifyEngineError(err);
1911
- }
1912
- log5.debug("State changed (sparse):", paths);
1913
- }
1914
- notifyStateChangeFull(paths, currentState) {
1915
- const engine = this.ensureInitialized();
1916
- engine.updateState(unwrapForWasm(currentState));
1917
- if (paths.length > 0) {
1918
- log5.debug("State changed (full):", paths);
1919
- }
1920
- }
1921
- updateState(statePatch) {
1922
- const engine = this.ensureInitialized();
1923
- engine.updateState(unwrapForWasm(statePatch));
1924
- }
1925
- dispatchAction(name, payload) {
1926
- const engine = this.ensureInitialized();
1927
- try {
1928
- engine.dispatchAction(name, payload ?? null);
1929
- } catch (err) {
1930
- throw classifyEngineError(err);
1931
- }
1932
- }
1933
- _onErrorHandler;
1934
- setOnError(handler) {
1935
- this._onErrorHandler = handler;
1936
- }
1937
- onAction(actionName, handler) {
1938
- const engine = this.ensureInitialized();
1939
- const handleError = (err) => {
1940
- log5.error("Action handler error:", err);
1941
- if (this._onErrorHandler) {
1942
- this._onErrorHandler(err instanceof Error ? err : new Error(String(err)));
1943
- }
1944
- };
1945
- engine.onAction(actionName, (action) => {
1946
- try {
1947
- const result = handler(action);
1948
- if (result && typeof result.catch === "function") {
1949
- result.catch(handleError);
1950
- }
1951
- } catch (err) {
1952
- handleError(err);
1953
- }
1954
- });
1955
- }
1956
- setModule(name, actions, stateKeys, initialState) {
1957
- const engine = this.ensureInitialized();
1958
- engine.setModule(name, actions, stateKeys, initialState);
1959
- }
1960
- getRevision() {
1961
- const engine = this.ensureInitialized();
1962
- return Number(engine.getRevision());
1963
- }
1964
- clearTree() {
1965
- const engine = this.ensureInitialized();
1966
- engine.clearTree();
1967
- }
1968
- debugParseComponent(source) {
1969
- const engine = this.ensureInitialized();
1970
- return engine.debugParseComponent(source);
1971
- }
1972
- setContext(name, data) {
1973
- const engine = this.ensureInitialized();
1974
- try {
1975
- engine.setContext(name, unwrapForWasm(data));
1976
- } catch (err) {
1977
- throw classifyEngineError(err);
1978
- }
1979
- log5.debug(`Data source "${name}" context set`);
1980
- }
1981
- removeContext(name) {
1982
- const engine = this.ensureInitialized();
1983
- engine.removeContext(name);
1984
- }
1985
- }
1986
-
1987
- // src/remote/server.ts
1988
- init_app();
1989
-
1990
948
  // src/remote/session.ts
1991
949
  class SessionManager {
1992
950
  activeSessions = new Map;
@@ -2110,697 +1068,9 @@ class SessionManager {
2110
1068
  this.sessionConnections.clear();
2111
1069
  }
2112
1070
  }
2113
-
2114
- // src/remote/server.ts
2115
- init_logger();
2116
-
2117
- // src/discovery.ts
2118
- init_path();
2119
- init_logger();
2120
- var {existsSync, readdirSync, readFileSync, watch} = (() => ({}));
2121
- async function discoverComponents(baseDir, options = {}) {
2122
- const {
2123
- patterns = ["single-file", "folder", "sibling", "index"],
2124
- recursive = true,
2125
- debug = false
2126
- } = options;
2127
- const log6 = debug ? (...args) => frameworkLoggers.discovery.debug(...args) : () => {};
2128
- const resolvedDir = resolve(baseDir);
2129
- const components = [];
2130
- const seen = new Set;
2131
- log6("Scanning directory:", resolvedDir);
2132
- log6("Patterns:", patterns);
2133
- const addTwoFileComponent = (name, hypenPath, modulePath) => {
2134
- if (seen.has(name)) {
2135
- log6(`Skipping duplicate: ${name}`);
2136
- return;
2137
- }
2138
- seen.add(name);
2139
- const templateRaw = readFileSync(hypenPath, "utf-8");
2140
- const template = templateRaw.trim();
2141
- components.push({
2142
- name,
2143
- hypenPath,
2144
- modulePath,
2145
- template,
2146
- hasModule: modulePath !== null,
2147
- isSingleFile: false
2148
- });
2149
- log6(`Found: ${name} (two-file, ${modulePath ? "with module" : "stateless"})`);
2150
- };
2151
- const addSingleFileComponent = (name, modulePath, template) => {
2152
- if (seen.has(name)) {
2153
- log6(`Skipping duplicate: ${name}`);
2154
- return;
2155
- }
2156
- seen.add(name);
2157
- components.push({
2158
- name,
2159
- hypenPath: null,
2160
- modulePath,
2161
- template,
2162
- hasModule: true,
2163
- isSingleFile: true
2164
- });
2165
- log6(`Found: ${name} (single-file with inline template)`);
2166
- };
2167
- const scanForFolderComponents = (dir) => {
2168
- if (!existsSync(dir))
2169
- return;
2170
- const entries = readdirSync(dir, { withFileTypes: true });
2171
- for (const entry of entries) {
2172
- if (!entry.isDirectory())
2173
- continue;
2174
- const folderPath = join(dir, entry.name);
2175
- const componentName = entry.name;
2176
- if (patterns.includes("folder")) {
2177
- const hypenPath = join(folderPath, "component.hypen");
2178
- if (existsSync(hypenPath)) {
2179
- const modulePath = join(folderPath, "component.ts");
2180
- addTwoFileComponent(componentName, hypenPath, existsSync(modulePath) ? modulePath : null);
2181
- continue;
2182
- }
2183
- }
2184
- if (patterns.includes("index")) {
2185
- const hypenPath = join(folderPath, "index.hypen");
2186
- if (existsSync(hypenPath)) {
2187
- const modulePath = join(folderPath, "index.ts");
2188
- addTwoFileComponent(componentName, hypenPath, existsSync(modulePath) ? modulePath : null);
2189
- continue;
2190
- }
2191
- }
2192
- if (recursive) {
2193
- scanForFolderComponents(folderPath);
2194
- }
2195
- }
2196
- };
2197
- const scanForSiblingComponents = (dir) => {
2198
- if (!existsSync(dir))
2199
- return;
2200
- const entries = readdirSync(dir, { withFileTypes: true });
2201
- for (const entry of entries) {
2202
- if (entry.isDirectory()) {
2203
- if (recursive) {
2204
- scanForSiblingComponents(join(dir, entry.name));
2205
- }
2206
- continue;
2207
- }
2208
- if (!entry.name.endsWith(".hypen"))
2209
- continue;
2210
- const hypenPath = join(dir, entry.name);
2211
- const baseName = basename(entry.name, ".hypen");
2212
- if (baseName === "component" || baseName === "index")
2213
- continue;
2214
- const modulePath = join(dir, `${baseName}.ts`);
2215
- addTwoFileComponent(baseName, hypenPath, existsSync(modulePath) ? modulePath : null);
2216
- }
2217
- };
2218
- const scanForSingleFileComponents = async (dir) => {
2219
- if (!existsSync(dir))
2220
- return;
2221
- const entries = readdirSync(dir, { withFileTypes: true });
2222
- for (const entry of entries) {
2223
- if (entry.isDirectory()) {
2224
- if (recursive) {
2225
- await scanForSingleFileComponents(join(dir, entry.name));
2226
- }
2227
- continue;
2228
- }
2229
- if (!entry.name.endsWith(".ts"))
2230
- continue;
2231
- if (entry.name.startsWith(".") || entry.name.includes(".test.") || entry.name.includes(".spec."))
2232
- continue;
2233
- const baseName = basename(entry.name, ".ts");
2234
- if (baseName === "component" || baseName === "index")
2235
- continue;
2236
- const hypenPath = join(dir, `${baseName}.hypen`);
2237
- if (existsSync(hypenPath))
2238
- continue;
2239
- const modulePath = join(dir, entry.name);
2240
- const content = readFileSync(modulePath, "utf-8");
2241
- if (content.includes(".ui(") || content.includes(".ui(hypen")) {
2242
- try {
2243
- const moduleExport = await import(modulePath);
2244
- const module = moduleExport.default;
2245
- if (module && typeof module === "object" && module.template) {
2246
- addSingleFileComponent(baseName, modulePath, module.template);
2247
- } else if (module && typeof module === "object") {
2248
- frameworkLoggers.discovery.warn(`Skipping "${entry.name}": has .ui() pattern but module has no .template property. Did you forget to call .ui(hypen\`...\`)?`);
2249
- }
2250
- } catch (e) {
2251
- log6(`Failed to import potential single-file component: ${entry.name}`, e);
2252
- }
2253
- }
2254
- }
2255
- };
2256
- if (patterns.includes("folder") || patterns.includes("index")) {
2257
- scanForFolderComponents(resolvedDir);
2258
- }
2259
- if (patterns.includes("sibling")) {
2260
- scanForSiblingComponents(resolvedDir);
2261
- }
2262
- if (patterns.includes("single-file")) {
2263
- await scanForSingleFileComponents(resolvedDir);
2264
- }
2265
- log6(`Discovered ${components.length} components`);
2266
- return components;
2267
- }
2268
- async function loadDiscoveredComponents(components) {
2269
- const { app: app2 } = await Promise.resolve().then(() => (init_app(), exports_app));
2270
- const loaded = new Map;
2271
- for (const component of components) {
2272
- let module;
2273
- let template = component.template;
2274
- if (component.modulePath) {
2275
- const moduleExport = await import(component.modulePath);
2276
- module = moduleExport.default;
2277
- if (component.isSingleFile && module.template) {
2278
- template = module.template;
2279
- }
2280
- } else {
2281
- module = app2.defineState({}).build();
2282
- }
2283
- loaded.set(component.name, {
2284
- name: component.name,
2285
- module,
2286
- template
2287
- });
2288
- }
2289
- return loaded;
2290
- }
2291
-
2292
- // src/remote/server.ts
2293
- var log6 = frameworkLoggers.remote;
2294
-
2295
- class RemoteServer {
2296
- _module = null;
2297
- _moduleName = "App";
2298
- _ui = "";
2299
- _sourceDir = null;
2300
- _discoveredComponents = new Map;
2301
- _config = {};
2302
- _sessionConfig = {};
2303
- _onConnectionCallbacks = [];
2304
- _onDisconnectionCallbacks = [];
2305
- clients = new Map;
2306
- nextClientId = 1;
2307
- server = null;
2308
- sessionManager = null;
2309
- _syncActions = false;
2310
- module(name, module) {
2311
- this._moduleName = name;
2312
- this._module = module;
2313
- return this;
2314
- }
2315
- ui(dsl) {
2316
- this._ui = dsl;
2317
- return this;
2318
- }
2319
- source(dir) {
2320
- this._sourceDir = dir;
2321
- return this;
2322
- }
2323
- config(config2) {
2324
- this._config = { ...this._config, ...config2 };
2325
- return this;
2326
- }
2327
- session(config2) {
2328
- this._sessionConfig = config2;
2329
- return this;
2330
- }
2331
- syncActions() {
2332
- this._syncActions = true;
2333
- return this;
2334
- }
2335
- onConnection(callback) {
2336
- this._onConnectionCallbacks.push(callback);
2337
- return this;
2338
- }
2339
- onDisconnection(callback) {
2340
- this._onDisconnectionCallbacks.push(callback);
2341
- return this;
2342
- }
2343
- async listen(port) {
2344
- if (!this._module) {
2345
- throw new Error("Module not set. Call .module() before .listen()");
2346
- }
2347
- if (this._sourceDir) {
2348
- await this.discoverFromSource();
2349
- }
2350
- if (!this._ui) {
2351
- throw new Error("UI not set. Call .ui() or .source() before .listen()");
2352
- }
2353
- this.sessionManager = new SessionManager(this._sessionConfig);
2354
- const finalPort = port ?? this._config.port ?? 3000;
2355
- const hostname = this._config.hostname ?? "0.0.0.0";
2356
- this.server = Bun.serve({
2357
- port: finalPort,
2358
- hostname,
2359
- websocket: {
2360
- open: (ws) => this.handleOpen(ws),
2361
- message: (ws, message) => this.handleMessage(ws, message),
2362
- close: (ws) => this.handleClose(ws)
2363
- },
2364
- fetch: (req, server) => {
2365
- const url = new URL(req.url);
2366
- if (server.upgrade(req, { data: undefined })) {
2367
- return;
2368
- }
2369
- if (url.pathname === "/health") {
2370
- return new Response("OK", { status: 200 });
2371
- }
2372
- if (url.pathname === "/stats") {
2373
- const stats = this.sessionManager?.getStats() ?? {
2374
- activeSessions: 0,
2375
- pendingSessions: 0,
2376
- totalConnections: 0
2377
- };
2378
- return new Response(JSON.stringify(stats), {
2379
- headers: { "Content-Type": "application/json" }
2380
- });
2381
- }
2382
- return new Response("Hypen Remote Server", { status: 200 });
2383
- }
2384
- });
2385
- log6.info(`Hypen app streaming on ws://${hostname}:${finalPort}`);
2386
- return this;
2387
- }
2388
- stop() {
2389
- if (this.server) {
2390
- this.server.stop();
2391
- this.server = null;
2392
- }
2393
- if (this.sessionManager) {
2394
- this.sessionManager.destroy();
2395
- this.sessionManager = null;
2396
- }
2397
- }
2398
- get url() {
2399
- if (!this.server)
2400
- return null;
2401
- const hostname = this._config.hostname ?? "localhost";
2402
- const port = this._config.port ?? 3000;
2403
- return `ws://${hostname}:${port}`;
2404
- }
2405
- async discoverFromSource() {
2406
- const dir = this._sourceDir;
2407
- log6.info(`Discovering components from ${dir}...`);
2408
- const discovered = await discoverComponents(dir);
2409
- const loaded = await loadDiscoveredComponents(discovered);
2410
- for (const [name, { module, template }] of loaded) {
2411
- this._discoveredComponents.set(name, { template, module });
2412
- }
2413
- log6.info(`Discovered ${this._discoveredComponents.size} components: ${Array.from(this._discoveredComponents.keys()).join(", ")}`);
2414
- if (!this._ui) {
2415
- const entry = this._discoveredComponents.get(this._moduleName);
2416
- if (entry) {
2417
- this._ui = entry.template;
2418
- }
2419
- }
2420
- }
2421
- setupComponentResolver(engine) {
2422
- if (this._discoveredComponents.size === 0)
2423
- return;
2424
- engine.setComponentResolver((componentName, _contextPath) => {
2425
- const comp = this._discoveredComponents.get(componentName);
2426
- if (!comp) {
2427
- log6.warn(`Component "${componentName}" not found. Available: ${Array.from(this._discoveredComponents.keys()).join(", ") || "(none)"}`);
2428
- return null;
2429
- }
2430
- return {
2431
- source: comp.template,
2432
- path: componentName
2433
- };
2434
- });
2435
- }
2436
- handleOpen(ws) {
2437
- try {
2438
- const clientId = `client_${this.nextClientId++}`;
2439
- const connectedAt = new Date;
2440
- const engine = new Engine;
2441
- engine.init().catch((err) => log6.error("Engine init failed:", err));
2442
- this.setupComponentResolver(engine);
2443
- const moduleInstance = new HypenModuleInstance(engine, this._module);
2444
- log6.info(`Client ${clientId} connected, engine initialized`);
2445
- const clientData = {
2446
- id: clientId,
2447
- engine,
2448
- moduleInstance,
2449
- revision: 0,
2450
- connectedAt,
2451
- sessionId: "",
2452
- helloReceived: false,
2453
- stateSubscribed: false
2454
- };
2455
- this.clients.set(ws, clientData);
2456
- clientData.helloTimeout = setTimeout(() => {
2457
- if (!clientData.helloReceived) {
2458
- this.initializeSession(ws, clientData, undefined, undefined).catch((err) => log6.error("Error initializing legacy session:", err));
2459
- }
2460
- }, 1000);
2461
- } catch (error) {
2462
- log6.error("Error handling WebSocket open:", error);
2463
- ws.close(1011, "Internal server error");
2464
- }
2465
- }
2466
- async initializeSession(ws, clientData, requestedSessionId, props) {
2467
- if (clientData.helloReceived)
2468
- return;
2469
- clientData.helloReceived = true;
2470
- log6.info(`Initializing session for ${clientData.id} (sessionId: ${requestedSessionId ?? "new"})`);
2471
- if (clientData.helloTimeout) {
2472
- clearTimeout(clientData.helloTimeout);
2473
- clientData.helloTimeout = undefined;
2474
- }
2475
- let session;
2476
- let isNew = true;
2477
- let isRestored = false;
2478
- let restoredState = null;
2479
- if (requestedSessionId && this.sessionManager) {
2480
- const resumed = this.sessionManager.resumeSession(requestedSessionId);
2481
- if (resumed) {
2482
- session = resumed.session;
2483
- restoredState = resumed.savedState;
2484
- isNew = false;
2485
- isRestored = true;
2486
- await this.triggerReconnect(clientData, session, restoredState);
2487
- } else {
2488
- const activeSession = this.sessionManager.getActiveSession(requestedSessionId);
2489
- if (activeSession) {
2490
- const handled = await this.handleConcurrentConnection(ws, clientData, activeSession, props);
2491
- if (!handled)
2492
- return;
2493
- session = activeSession;
2494
- isNew = false;
2495
- } else {
2496
- session = this.sessionManager.createSession(props);
2497
- }
2498
- }
2499
- } else if (this.sessionManager) {
2500
- session = this.sessionManager.createSession(props);
2501
- } else {
2502
- session = {
2503
- id: crypto.randomUUID(),
2504
- ttl: 3600,
2505
- createdAt: new Date,
2506
- lastConnectedAt: new Date,
2507
- props
2508
- };
2509
- }
2510
- clientData.sessionId = session.id;
2511
- this.sessionManager?.trackConnection(session.id, ws);
2512
- const sessionAck = {
2513
- type: "sessionAck",
2514
- sessionId: session.id,
2515
- isNew,
2516
- isRestored
2517
- };
2518
- ws.send(JSON.stringify(sessionAck));
2519
- log6.info(`Sent sessionAck to ${clientData.id} (session: ${session.id})`);
2520
- const initialPatches = [];
2521
- clientData.engine.setRenderCallback((patches) => {
2522
- initialPatches.push(...patches);
2523
- });
2524
- try {
2525
- clientData.engine.renderSource(this._ui);
2526
- } catch (renderError) {
2527
- log6.error(`Failed to render UI for ${clientData.id}:`, renderError);
2528
- ws.close(1011, "Render failed");
2529
- return;
2530
- }
2531
- this.setupRenderCallback(ws, clientData);
2532
- const initialMessage = {
2533
- type: "initialTree",
2534
- module: this._moduleName,
2535
- state: clientData.moduleInstance.getState(),
2536
- patches: initialPatches,
2537
- revision: 0
2538
- };
2539
- try {
2540
- ws.send(JSON.stringify(initialMessage));
2541
- } catch (sendError) {
2542
- log6.error(`Failed to send initialTree to ${clientData.id}:`, sendError);
2543
- return;
2544
- }
2545
- log6.info(`Sent initialTree to ${clientData.id} (${initialPatches.length} patches)`);
2546
- const client = {
2547
- id: clientData.id,
2548
- socket: ws,
2549
- connectedAt: clientData.connectedAt
2550
- };
2551
- this._onConnectionCallbacks.forEach((cb) => cb(client));
2552
- }
2553
- setupRenderCallback(ws, clientData) {
2554
- clientData.engine.setRenderCallback((patches) => {
2555
- const data = this.clients.get(ws);
2556
- if (!data)
2557
- return;
2558
- data.revision++;
2559
- const patchMessage = {
2560
- type: "patch",
2561
- module: this._moduleName,
2562
- patches,
2563
- revision: data.revision
2564
- };
2565
- ws.send(JSON.stringify(patchMessage));
2566
- if (data.stateSubscribed) {
2567
- const stateMessage = {
2568
- type: "stateUpdate",
2569
- module: this._moduleName,
2570
- state: data.moduleInstance.getState(),
2571
- revision: data.revision
2572
- };
2573
- ws.send(JSON.stringify(stateMessage));
2574
- }
2575
- if (this.sessionManager?.getConcurrentPolicy() === "allow-multiple" && data.sessionId) {
2576
- const connections = this.sessionManager.getConnections(data.sessionId);
2577
- if (connections) {
2578
- for (const conn of connections) {
2579
- if (conn !== ws) {
2580
- const otherWs = conn;
2581
- const otherData = this.clients.get(otherWs);
2582
- otherWs.send(JSON.stringify(patchMessage));
2583
- if (otherData?.stateSubscribed) {
2584
- const stateMessage = {
2585
- type: "stateUpdate",
2586
- module: this._moduleName,
2587
- state: data.moduleInstance.getState(),
2588
- revision: data.revision
2589
- };
2590
- otherWs.send(JSON.stringify(stateMessage));
2591
- }
2592
- }
2593
- }
2594
- }
2595
- }
2596
- });
2597
- }
2598
- async handleConcurrentConnection(ws, clientData, existingSession, props) {
2599
- const policy = this.sessionManager?.getConcurrentPolicy() ?? "kick-old";
2600
- switch (policy) {
2601
- case "kick-old": {
2602
- const existingConnections = this.sessionManager?.getConnections(existingSession.id);
2603
- if (existingConnections) {
2604
- for (const conn of existingConnections) {
2605
- const oldWs = conn;
2606
- const expiredMsg = {
2607
- type: "sessionExpired",
2608
- sessionId: existingSession.id,
2609
- reason: "kicked"
2610
- };
2611
- oldWs.send(JSON.stringify(expiredMsg));
2612
- oldWs.close(1000, "Session taken by new connection");
2613
- }
2614
- }
2615
- return true;
2616
- }
2617
- case "reject-new": {
2618
- const expiredMsg = {
2619
- type: "sessionExpired",
2620
- sessionId: existingSession.id,
2621
- reason: "kicked"
2622
- };
2623
- ws.send(JSON.stringify(expiredMsg));
2624
- ws.close(1000, "Session already active");
2625
- return false;
2626
- }
2627
- case "allow-multiple": {
2628
- return true;
2629
- }
2630
- default:
2631
- return true;
2632
- }
2633
- }
2634
- async triggerReconnect(clientData, session, savedState) {
2635
- const handler = this._module?.handlers.onReconnect;
2636
- if (!handler)
2637
- return;
2638
- let restored = false;
2639
- const restore = (state) => {
2640
- if (state === null || typeof state !== "object") {
2641
- log6.warn("restore() called with non-object state, ignoring:", typeof state);
2642
- return;
2643
- }
2644
- restored = true;
2645
- clientData.moduleInstance.updateState(state);
2646
- };
2647
- await handler({ session, restore });
2648
- }
2649
- handleMessage(ws, message) {
2650
- try {
2651
- const clientData = this.clients.get(ws);
2652
- if (!clientData)
2653
- return;
2654
- const msg = JSON.parse(message.toString());
2655
- switch (msg.type) {
2656
- case "hello": {
2657
- const helloMsg = msg;
2658
- this.initializeSession(ws, clientData, helloMsg.sessionId, helloMsg.props).catch((err) => log6.error("Error initializing session from hello:", err));
2659
- break;
2660
- }
2661
- case "dispatchAction": {
2662
- const actionMsg = msg;
2663
- clientData.engine.dispatchAction(actionMsg.action, actionMsg.payload);
2664
- if (this._syncActions) {
2665
- for (const [otherWs, otherClient] of this.clients) {
2666
- if (otherWs === ws || !otherClient.helloReceived)
2667
- continue;
2668
- otherClient.engine.dispatchAction(actionMsg.action, actionMsg.payload);
2669
- }
2670
- }
2671
- break;
2672
- }
2673
- case "updateState": {
2674
- const stateMsg = msg;
2675
- clientData.moduleInstance.updateState(stateMsg.state);
2676
- if (this._syncActions) {
2677
- for (const [otherWs, otherClient] of this.clients) {
2678
- if (otherWs === ws || !otherClient.helloReceived)
2679
- continue;
2680
- otherClient.moduleInstance.updateState(stateMsg.state);
2681
- }
2682
- }
2683
- break;
2684
- }
2685
- case "subscribeState": {
2686
- clientData.stateSubscribed = true;
2687
- log6.info(`Client ${clientData.id} subscribed to state updates`);
2688
- break;
2689
- }
2690
- default:
2691
- break;
2692
- }
2693
- } catch (error) {
2694
- log6.error("Error handling WebSocket message:", error);
2695
- }
2696
- }
2697
- async handleClose(ws) {
2698
- const clientData = this.clients.get(ws);
2699
- if (!clientData)
2700
- return;
2701
- if (clientData.helloTimeout) {
2702
- clearTimeout(clientData.helloTimeout);
2703
- }
2704
- const currentState = clientData.moduleInstance.getState();
2705
- if (clientData.sessionId && this._module?.handlers.onDisconnect) {
2706
- const session = this.sessionManager?.getActiveSession(clientData.sessionId);
2707
- if (session) {
2708
- await this._module.handlers.onDisconnect({
2709
- state: currentState,
2710
- session
2711
- });
2712
- }
2713
- }
2714
- if (clientData.sessionId && this.sessionManager) {
2715
- this.sessionManager.untrackConnection(clientData.sessionId, ws);
2716
- if (this.sessionManager.getConnectionCount(clientData.sessionId) === 0) {
2717
- const session = this.sessionManager.getActiveSession(clientData.sessionId);
2718
- if (session) {
2719
- this.sessionManager.suspendSession(clientData.sessionId, currentState, async (expiredSession) => {
2720
- if (this._module?.handlers.onExpire) {
2721
- await this._module.handlers.onExpire({ session: expiredSession });
2722
- }
2723
- });
2724
- }
2725
- }
2726
- }
2727
- await clientData.moduleInstance.destroy();
2728
- this.clients.delete(ws);
2729
- const client = {
2730
- id: clientData.id,
2731
- socket: ws,
2732
- connectedAt: clientData.connectedAt
2733
- };
2734
- this._onDisconnectionCallbacks.forEach((cb) => cb(client));
2735
- }
2736
- async reload() {
2737
- if (this._sourceDir) {
2738
- this._ui = "";
2739
- await this.discoverFromSource();
2740
- }
2741
- if (!this._ui) {
2742
- log6.warn("Reload skipped: no UI template available");
2743
- return;
2744
- }
2745
- for (const [ws, clientData] of this.clients) {
2746
- if (!clientData.helloReceived)
2747
- continue;
2748
- this.setupComponentResolver(clientData.engine);
2749
- try {
2750
- clientData.engine.renderSource(this._ui);
2751
- log6.info(`Hot-reloaded client ${clientData.id}`);
2752
- } catch (e) {
2753
- log6.error(`Failed to hot-reload client ${clientData.id}:`, e);
2754
- }
2755
- }
2756
- }
2757
- getClientCount() {
2758
- return this.clients.size;
2759
- }
2760
- getSessionStats() {
2761
- return this.sessionManager?.getStats() ?? {
2762
- activeSessions: 0,
2763
- pendingSessions: 0,
2764
- totalConnections: 0
2765
- };
2766
- }
2767
- broadcast(message) {
2768
- const json = JSON.stringify(message);
2769
- this.clients.forEach((_, ws) => {
2770
- ws.send(json);
2771
- });
2772
- }
2773
- }
2774
- async function serve(options) {
2775
- const server = new RemoteServer().module(options.moduleName ?? "App", options.module);
2776
- if (options.source) {
2777
- server.source(options.source);
2778
- }
2779
- if (options.ui) {
2780
- server.ui(options.ui);
2781
- }
2782
- if (options.port || options.hostname) {
2783
- server.config({
2784
- port: options.port,
2785
- hostname: options.hostname
2786
- });
2787
- }
2788
- if (options.session) {
2789
- server.session(options.session);
2790
- }
2791
- if (options.onConnection) {
2792
- server.onConnection(options.onConnection);
2793
- }
2794
- if (options.onDisconnection) {
2795
- server.onDisconnection(options.onDisconnection);
2796
- }
2797
- return server.listen(options.port);
2798
- }
2799
1071
  export {
2800
- serve,
2801
1072
  SessionManager,
2802
- RemoteServer,
2803
1073
  RemoteEngine
2804
1074
  };
2805
1075
 
2806
- //# debugId=BB07A59B30B1F5FD64756E2164756E21
1076
+ //# debugId=49B0214C312B8BBB64756E2164756E21