@hypen-space/core 0.4.2 → 0.4.5

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 (66) hide show
  1. package/README.md +1 -1
  2. package/dist/app.d.ts +379 -0
  3. package/dist/app.js +111 -18
  4. package/dist/app.js.map +4 -4
  5. package/dist/components/builtin.d.ts +50 -0
  6. package/dist/components/builtin.js +114 -21
  7. package/dist/components/builtin.js.map +5 -5
  8. package/dist/context.d.ts +90 -0
  9. package/dist/discovery.d.ts +90 -0
  10. package/dist/discovery.js +121 -23
  11. package/dist/discovery.js.map +5 -5
  12. package/dist/disposable.d.ts +122 -0
  13. package/dist/engine.browser.d.ts +101 -0
  14. package/dist/engine.browser.js +192 -5
  15. package/dist/engine.browser.js.map +5 -4
  16. package/dist/engine.d.ts +85 -0
  17. package/dist/engine.js +192 -5
  18. package/dist/engine.js.map +5 -4
  19. package/dist/events.d.ts +78 -0
  20. package/dist/hypen.d.ts +127 -0
  21. package/dist/index.browser.d.ts +25 -0
  22. package/dist/index.browser.js +116 -19
  23. package/dist/index.browser.js.map +6 -6
  24. package/dist/index.d.ts +76 -0
  25. package/dist/index.js +373 -1235
  26. package/dist/index.js.map +10 -15
  27. package/dist/loader.d.ts +51 -0
  28. package/dist/logger.d.ts +141 -0
  29. package/dist/managed-router.d.ts +59 -0
  30. package/dist/plugin.d.ts +39 -0
  31. package/dist/remote/client.d.ts +154 -0
  32. package/dist/remote/client.js +34 -2
  33. package/dist/remote/client.js.map +3 -3
  34. package/dist/remote/index.d.ts +13 -0
  35. package/dist/remote/index.js +472 -26
  36. package/dist/remote/index.js.map +8 -7
  37. package/dist/remote/server.d.ts +173 -0
  38. package/dist/remote/server.js +472 -26
  39. package/dist/remote/server.js.map +8 -7
  40. package/dist/remote/session.d.ts +115 -0
  41. package/dist/remote/types.d.ts +98 -0
  42. package/dist/renderer.d.ts +54 -0
  43. package/dist/renderer.js +6 -2
  44. package/dist/renderer.js.map +3 -3
  45. package/dist/resolver.d.ts +95 -0
  46. package/dist/result.d.ts +134 -0
  47. package/dist/retry.d.ts +150 -0
  48. package/dist/router.d.ts +94 -0
  49. package/dist/state.d.ts +42 -0
  50. package/dist/types.d.ts +30 -0
  51. package/package.json +2 -1
  52. package/src/app.ts +162 -41
  53. package/src/components/builtin.ts +3 -4
  54. package/src/discovery.ts +2 -2
  55. package/src/engine.browser.ts +27 -4
  56. package/src/engine.ts +27 -4
  57. package/src/index.browser.ts +0 -2
  58. package/src/index.ts +3 -2
  59. package/src/managed-router.ts +5 -6
  60. package/src/remote/server.ts +116 -21
  61. package/src/result.ts +48 -0
  62. package/wasm-browser/hypen_engine_bg.wasm +0 -0
  63. package/wasm-browser/package.json +1 -1
  64. package/wasm-node/hypen_engine_bg.wasm +0 -0
  65. package/wasm-node/package.json +5 -3
  66. package/src/module-registry.ts +0 -76
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  import { app } from "../app.js";
7
- import type { HypenRouter } from "../router.js";
8
7
  import { frameworkLoggers } from "../logger.js";
9
8
 
10
9
  const log = frameworkLoggers.router;
@@ -36,7 +35,7 @@ export const Router = app
36
35
  }
37
36
 
38
37
  // Subscribe to router changes
39
- const router = (context as any).__router as HypenRouter;
38
+ const router = context.router;
40
39
  if (!router) {
41
40
  log.error("Router not found in context");
42
41
  return;
@@ -149,7 +148,7 @@ export const Link = app
149
148
  { name: "__Link" }
150
149
  )
151
150
  .onAction("navigate", ({ state, context }) => {
152
- const router = (context as any)?.__router as HypenRouter;
151
+ const router = context?.router;
153
152
  if (!router) {
154
153
  log.error("Link requires router context");
155
154
  return;
@@ -162,7 +161,7 @@ export const Link = app
162
161
  if (!context) return;
163
162
 
164
163
  // Check if current path matches this link's target
165
- const router = (context as any).__router as HypenRouter;
164
+ const router = context.router;
166
165
  if (router) {
167
166
  router.onNavigate((routeState) => {
168
167
  state.isActive = routeState.currentPath === state.to;
package/src/discovery.ts CHANGED
@@ -43,7 +43,7 @@ export interface DiscoveryOptions {
43
43
 
44
44
  /**
45
45
  * Recursively scan subdirectories
46
- * Default: false
46
+ * Default: true
47
47
  */
48
48
  recursive?: boolean;
49
49
 
@@ -95,7 +95,7 @@ export async function discoverComponents(
95
95
  ): Promise<DiscoveredComponent[]> {
96
96
  const {
97
97
  patterns = ["single-file", "folder", "sibling", "index"],
98
- recursive = false,
98
+ recursive = true,
99
99
  debug = false,
100
100
  } = options;
101
101
 
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { frameworkLoggers } from "./logger.js";
7
+ import { classifyEngineError } from "./result.js";
7
8
  import type { Patch, Action, RenderCallback, ActionHandler, ResolvedComponent, ComponentResolver } from "./types.js";
8
9
 
9
10
  // Re-export types so existing consumers of "./engine.browser.js" still work
@@ -125,10 +126,16 @@ export class Engine {
125
126
 
126
127
  /**
127
128
  * Parse and render Hypen DSL source code
129
+ * @throws {ParseError} if the source fails to parse
130
+ * @throws {RenderError} if rendering fails
128
131
  */
129
132
  renderSource(source: string): void {
130
133
  const engine = this.ensureInitialized();
131
- engine.renderSource(source);
134
+ try {
135
+ engine.renderSource(source);
136
+ } catch (err) {
137
+ throw classifyEngineError(err);
138
+ }
132
139
  }
133
140
 
134
141
  /**
@@ -141,15 +148,22 @@ export class Engine {
141
148
 
142
149
  /**
143
150
  * Render a component into a specific parent node (subtree rendering)
151
+ * @throws {ParseError} if the source fails to parse
152
+ * @throws {RenderError} if the parent node is not found or rendering fails
144
153
  */
145
154
  renderInto(source: string, parentNodeId: string, state: Record<string, any>): void {
146
155
  const engine = this.ensureInitialized();
147
156
  const safeState = JSON.parse(JSON.stringify(state));
148
- engine.renderInto(source, parentNodeId, safeState);
157
+ try {
158
+ engine.renderInto(source, parentNodeId, safeState);
159
+ } catch (err) {
160
+ throw classifyEngineError(err);
161
+ }
149
162
  }
150
163
 
151
164
  /**
152
165
  * Notify the engine of state changes using sparse updates
166
+ * @throws {StateError} if the state patch is invalid
153
167
  */
154
168
  notifyStateChange(paths: string[], values: Record<string, any>): void {
155
169
  const engine = this.ensureInitialized();
@@ -159,7 +173,11 @@ export class Engine {
159
173
  }
160
174
 
161
175
  const plainValues = JSON.parse(JSON.stringify(values));
162
- engine.updateStateSparse(paths, plainValues);
176
+ try {
177
+ engine.updateStateSparse(paths, plainValues);
178
+ } catch (err) {
179
+ throw classifyEngineError(err);
180
+ }
163
181
  log.debug("State changed (sparse):", paths);
164
182
  }
165
183
 
@@ -189,11 +207,16 @@ export class Engine {
189
207
 
190
208
  /**
191
209
  * Dispatch an action
210
+ * @throws {HypenError} if the action dispatch fails
192
211
  */
193
212
  dispatchAction(name: string, payload?: any): void {
194
213
  const engine = this.ensureInitialized();
195
214
  log.debug(`Action dispatched: ${name}`);
196
- engine.dispatchAction(name, payload ?? null);
215
+ try {
216
+ engine.dispatchAction(name, payload ?? null);
217
+ } catch (err) {
218
+ throw classifyEngineError(err);
219
+ }
197
220
  }
198
221
 
199
222
  /**
package/src/engine.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  // WASM module types
7
7
  import { WasmEngine } from "../wasm-node/hypen_engine.js";
8
8
  import { frameworkLoggers } from "./logger.js";
9
+ import { classifyEngineError } from "./result.js";
9
10
  import type { Patch, Action, RenderCallback, ActionHandler, ResolvedComponent, ComponentResolver } from "./types.js";
10
11
 
11
12
  // Re-export types so existing consumers of "./engine.js" still work
@@ -89,10 +90,16 @@ export class Engine {
89
90
 
90
91
  /**
91
92
  * Parse and render Hypen DSL source code
93
+ * @throws {ParseError} if the source fails to parse
94
+ * @throws {RenderError} if rendering fails
92
95
  */
93
96
  renderSource(source: string): void {
94
97
  const engine = this.ensureInitialized();
95
- engine.renderSource(source);
98
+ try {
99
+ engine.renderSource(source);
100
+ } catch (err) {
101
+ throw classifyEngineError(err);
102
+ }
96
103
  }
97
104
 
98
105
  /**
@@ -105,14 +112,21 @@ export class Engine {
105
112
 
106
113
  /**
107
114
  * Render a component into a specific parent node (subtree rendering)
115
+ * @throws {ParseError} if the source fails to parse
116
+ * @throws {RenderError} if the parent node is not found or rendering fails
108
117
  */
109
118
  renderInto(source: string, parentNodeId: string, state: Record<string, any>): void {
110
119
  const engine = this.ensureInitialized();
111
- engine.renderInto(source, parentNodeId, unwrapForWasm(state));
120
+ try {
121
+ engine.renderInto(source, parentNodeId, unwrapForWasm(state));
122
+ } catch (err) {
123
+ throw classifyEngineError(err);
124
+ }
112
125
  }
113
126
 
114
127
  /**
115
128
  * Notify the engine of state changes using sparse updates
129
+ * @throws {StateError} if the state patch is invalid
116
130
  */
117
131
  notifyStateChange(paths: string[], values: Record<string, any>): void {
118
132
  const engine = this.ensureInitialized();
@@ -121,7 +135,11 @@ export class Engine {
121
135
  return;
122
136
  }
123
137
 
124
- engine.updateStateSparse(paths, unwrapForWasm(values));
138
+ try {
139
+ engine.updateStateSparse(paths, unwrapForWasm(values));
140
+ } catch (err) {
141
+ throw classifyEngineError(err);
142
+ }
125
143
  log.debug("State changed (sparse):", paths);
126
144
  }
127
145
 
@@ -149,10 +167,15 @@ export class Engine {
149
167
 
150
168
  /**
151
169
  * Dispatch an action
170
+ * @throws {HypenError} if the action dispatch fails
152
171
  */
153
172
  dispatchAction(name: string, payload?: any): void {
154
173
  const engine = this.ensureInitialized();
155
- engine.dispatchAction(name, payload ?? null);
174
+ try {
175
+ engine.dispatchAction(name, payload ?? null);
176
+ } catch (err) {
177
+ throw classifyEngineError(err);
178
+ }
156
179
  }
157
180
 
158
181
  /**
@@ -28,9 +28,7 @@ export type {
28
28
  export { app, HypenApp, HypenAppBuilder, HypenModuleInstance } from "./app.js";
29
29
  export type {
30
30
  IEngine,
31
- RouterContext,
32
31
  ActionContext,
33
- ActionNext,
34
32
  GlobalContext,
35
33
  LifecycleHandler,
36
34
  ActionHandlerContext,
package/src/index.ts CHANGED
@@ -74,9 +74,7 @@ export type { StateProxy, ItemProxy } from "./hypen.js";
74
74
 
75
75
  export type {
76
76
  IEngine,
77
- RouterContext,
78
77
  ActionContext,
79
- ActionNext,
80
78
  GlobalContext,
81
79
  LifecycleHandler,
82
80
  ActionHandlerContext,
@@ -245,6 +243,9 @@ export {
245
243
  ActionError,
246
244
  ConnectionError,
247
245
  StateError,
246
+ ParseError,
247
+ RenderError,
248
+ classifyEngineError,
248
249
  } from "./result.js";
249
250
  export type { Result } from "./result.js";
250
251
 
@@ -9,10 +9,9 @@
9
9
  */
10
10
 
11
11
  import type { IEngine, HypenModuleDefinition } from "./app.js";
12
- import { HypenModuleInstance } from "./app.js";
12
+ import { HypenModuleInstance, HypenApp } from "./app.js";
13
13
  import type { HypenRouter, RouteState } from "./router.js";
14
14
  import type { HypenGlobalContext } from "./context.js";
15
- import { ModuleRegistry } from "./module-registry.js";
16
15
  import { createLogger } from "./logger.js";
17
16
 
18
17
  const log = createLogger("ManagedRouter");
@@ -20,7 +19,7 @@ const log = createLogger("ManagedRouter");
20
19
  export interface RouteDefinition {
21
20
  /** Route path pattern (e.g., "/", "/profile/:id") */
22
21
  path: string;
23
- /** Component name — used to look up in ModuleRegistry */
22
+ /** Component name — used to look up in app registry */
24
23
  component: string;
25
24
  /** Inline module definition (alternative to registry lookup) */
26
25
  module?: HypenModuleDefinition;
@@ -29,7 +28,7 @@ export interface RouteDefinition {
29
28
  export class ManagedRouter {
30
29
  private router: HypenRouter;
31
30
  private engine: IEngine;
32
- private registry: ModuleRegistry;
31
+ private registry: HypenApp;
33
32
  private globalContext: HypenGlobalContext;
34
33
  private routes: RouteDefinition[] = [];
35
34
  private activeModule: HypenModuleInstance | null = null;
@@ -41,7 +40,7 @@ export class ManagedRouter {
41
40
  constructor(
42
41
  router: HypenRouter,
43
42
  engine: IEngine,
44
- registry: ModuleRegistry,
43
+ registry: HypenApp,
45
44
  globalContext: HypenGlobalContext
46
45
  ) {
47
46
  this.router = router;
@@ -161,7 +160,7 @@ export class ManagedRouter {
161
160
  const instance = new HypenModuleInstance(
162
161
  this.engine,
163
162
  namedDef as HypenModuleDefinition<object>,
164
- { root: this.router },
163
+ this.router,
165
164
  this.globalContext
166
165
  );
167
166
 
@@ -1,36 +1,36 @@
1
1
  /**
2
2
  * RemoteServer - Stream Hypen apps over WebSocket with Session Management
3
3
  *
4
- * Usage:
4
+ * Usage with inline UI:
5
5
  * ```typescript
6
6
  * import { RemoteServer, app } from "@hypen-space/core";
7
7
  *
8
8
  * const counter = app
9
9
  * .defineState({ count: 0 })
10
10
  * .onAction("increment", ({ state }) => state.count++)
11
- * .onDisconnect(async ({ state, session }) => {
12
- * await redis.set(`session:${session.id}`, JSON.stringify(state));
13
- * })
14
- * .onReconnect(async ({ session, restore }) => {
15
- * const saved = await redis.get(`session:${session.id}`);
16
- * if (saved) restore(JSON.parse(saved));
17
- * })
18
- * .onExpire(async ({ session }) => {
19
- * await redis.del(`session:${session.id}`);
20
- * })
21
11
  * .build();
22
12
  *
23
13
  * new RemoteServer()
24
14
  * .module("Counter", counter)
25
15
  * .ui(`Column { Text("Count: \${state.count}") }`)
26
- * .session({ ttl: 3600, concurrent: "kick-old" })
27
16
  * .listen(3000);
28
17
  * ```
18
+ *
19
+ * Usage with directory-based discovery (auto-resolves imports):
20
+ * ```typescript
21
+ * import { RemoteServer } from "@hypen-space/core";
22
+ * import chatModule from "./apps/chat/Chat/component.js";
23
+ *
24
+ * await new RemoteServer()
25
+ * .source("./apps/chat")
26
+ * .module("Chat", chatModule)
27
+ * .listen(3001);
28
+ * ```
29
29
  */
30
30
 
31
31
  import { Engine } from "../engine.js";
32
32
  import { HypenModuleInstance } from "../app.js";
33
- import type { HypenModule } from "../app.js";
33
+ import type { HypenModule, HypenModuleDefinition } from "../app.js";
34
34
  import type {
35
35
  RemoteMessage,
36
36
  RemoteClient,
@@ -44,10 +44,15 @@ import type {
44
44
  Session,
45
45
  SessionConfig,
46
46
  } from "./types.js";
47
- import type { Patch } from "../types.js";
47
+ import type { Patch, ResolvedComponent } from "../types.js";
48
48
  import type { ServerWebSocket } from "bun";
49
49
  import { SessionManager } from "./session.js";
50
50
  import { frameworkLoggers } from "../logger.js";
51
+ import {
52
+ discoverComponents,
53
+ loadDiscoveredComponents,
54
+ type DiscoveredComponent,
55
+ } from "../discovery.js";
51
56
 
52
57
  const log = frameworkLoggers.remote;
53
58
 
@@ -72,6 +77,8 @@ export class RemoteServer {
72
77
  private _module: HypenModule<any> | null = null;
73
78
  private _moduleName: string = "App";
74
79
  private _ui: string = "";
80
+ private _sourceDir: string | null = null;
81
+ private _discoveredComponents: Map<string, { template: string; module?: HypenModuleDefinition<any> }> = new Map();
75
82
  private _config: RemoteServerConfig = {};
76
83
  private _sessionConfig: SessionConfig = {};
77
84
  private _onConnectionCallbacks: Array<(client: RemoteClient) => void> = [];
@@ -98,6 +105,26 @@ export class RemoteServer {
98
105
  return this;
99
106
  }
100
107
 
108
+ /**
109
+ * Set a source directory for automatic component discovery.
110
+ *
111
+ * All .hypen and .ts components in the directory (and subdirectories) are
112
+ * discovered automatically. The entry component's template is used as the
113
+ * UI, so calling .ui() is optional when .source() is set.
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * new RemoteServer()
118
+ * .source("./apps/chat")
119
+ * .module("Chat", chatModule)
120
+ * .listen(3001);
121
+ * ```
122
+ */
123
+ source(dir: string): this {
124
+ this._sourceDir = dir;
125
+ return this;
126
+ }
127
+
101
128
  /**
102
129
  * Set server configuration
103
130
  */
@@ -133,13 +160,20 @@ export class RemoteServer {
133
160
  /**
134
161
  * Start the WebSocket server
135
162
  */
136
- listen(port?: number): this {
163
+ async listen(port?: number): Promise<this> {
137
164
  if (!this._module) {
138
165
  throw new Error("Module not set. Call .module() before .listen()");
139
166
  }
140
167
 
168
+ // Run component discovery if source directory is set
169
+ if (this._sourceDir) {
170
+ await this.discoverFromSource();
171
+ }
172
+
141
173
  if (!this._ui) {
142
- throw new Error("UI not set. Call .ui() before .listen()");
174
+ throw new Error(
175
+ "UI not set. Call .ui() or .source() before .listen()"
176
+ );
143
177
  }
144
178
 
145
179
  // Initialize session manager
@@ -214,6 +248,56 @@ export class RemoteServer {
214
248
  return `ws://${hostname}:${port}`;
215
249
  }
216
250
 
251
+ /**
252
+ * Discover components from the source directory.
253
+ * Populates _discoveredComponents and sets _ui from the entry component
254
+ * if not already set via .ui().
255
+ */
256
+ private async discoverFromSource(): Promise<void> {
257
+ const dir = this._sourceDir!;
258
+ log.info(`Discovering components from ${dir}...`);
259
+
260
+ const discovered = await discoverComponents(dir);
261
+ const loaded = await loadDiscoveredComponents(discovered);
262
+
263
+ for (const [name, { module, template }] of loaded) {
264
+ this._discoveredComponents.set(name, { template, module });
265
+ }
266
+
267
+ log.info(
268
+ `Discovered ${this._discoveredComponents.size} components: ${Array.from(this._discoveredComponents.keys()).join(", ")}`
269
+ );
270
+
271
+ // If .ui() was not called, use the entry component's template
272
+ if (!this._ui) {
273
+ const entry = this._discoveredComponents.get(this._moduleName);
274
+ if (entry) {
275
+ this._ui = entry.template;
276
+ }
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Set up a component resolver on a client engine so that imports
282
+ * and component references in templates are resolved from discovered
283
+ * components.
284
+ */
285
+ private setupComponentResolver(engine: Engine): void {
286
+ if (this._discoveredComponents.size === 0) return;
287
+
288
+ engine.setComponentResolver(
289
+ (componentName: string, _contextPath: string | null) => {
290
+ const comp = this._discoveredComponents.get(componentName);
291
+ if (!comp) return null;
292
+
293
+ return {
294
+ source: comp.template,
295
+ path: componentName,
296
+ };
297
+ }
298
+ );
299
+ }
300
+
217
301
  /**
218
302
  * Handle new WebSocket connection
219
303
  * Waits for hello message before fully initializing
@@ -227,6 +311,9 @@ export class RemoteServer {
227
311
  const engine = new Engine();
228
312
  await engine.init();
229
313
 
314
+ // Wire up component resolver if we have discovered components
315
+ this.setupComponentResolver(engine);
316
+
230
317
  // Create module instance
231
318
  const moduleInstance = new HypenModuleInstance(engine, this._module!);
232
319
 
@@ -618,19 +705,27 @@ export class RemoteServer {
618
705
  /**
619
706
  * Convenience function to create and start a RemoteServer
620
707
  */
621
- export function serve(options: {
708
+ export async function serve(options: {
622
709
  module: HypenModule<any>;
623
710
  moduleName?: string;
624
- ui: string;
711
+ ui?: string;
712
+ source?: string;
625
713
  port?: number;
626
714
  hostname?: string;
627
715
  session?: SessionConfig;
628
716
  onConnection?: (client: RemoteClient) => void;
629
717
  onDisconnection?: (client: RemoteClient) => void;
630
- }): RemoteServer {
718
+ }): Promise<RemoteServer> {
631
719
  const server = new RemoteServer()
632
- .module(options.moduleName ?? "App", options.module)
633
- .ui(options.ui);
720
+ .module(options.moduleName ?? "App", options.module);
721
+
722
+ if (options.source) {
723
+ server.source(options.source);
724
+ }
725
+
726
+ if (options.ui) {
727
+ server.ui(options.ui);
728
+ }
634
729
 
635
730
  if (options.port || options.hostname) {
636
731
  server.config({
package/src/result.ts CHANGED
@@ -258,3 +258,51 @@ export class StateError extends HypenError {
258
258
  this.path = path;
259
259
  }
260
260
  }
261
+
262
+ /**
263
+ * Error thrown when parsing Hypen DSL source fails
264
+ */
265
+ export class ParseError extends HypenError {
266
+ readonly source?: string;
267
+
268
+ constructor(message: string, source?: string, cause?: unknown) {
269
+ super('PARSE_ERROR', message, {
270
+ context: { source },
271
+ cause: cause instanceof Error ? cause : undefined,
272
+ });
273
+ this.name = 'ParseError';
274
+ this.source = source;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Error thrown when rendering fails
280
+ */
281
+ export class RenderError extends HypenError {
282
+ constructor(message: string, cause?: unknown) {
283
+ super('RENDER_ERROR', message, {
284
+ cause: cause instanceof Error ? cause : undefined,
285
+ });
286
+ this.name = 'RenderError';
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Classify a WASM engine error string into the appropriate HypenError subclass.
292
+ * The engine returns JsValue error strings with known prefixes.
293
+ */
294
+ export function classifyEngineError(err: unknown): HypenError {
295
+ const message = err instanceof Error ? err.message : String(err);
296
+
297
+ if (message.startsWith('Parse error:')) {
298
+ return new ParseError(message);
299
+ }
300
+ if (message.startsWith('Invalid state:') || message.startsWith('Invalid state patch:')) {
301
+ return new StateError(message);
302
+ }
303
+ if (message.startsWith('Parent node not found:')) {
304
+ return new RenderError(message);
305
+ }
306
+
307
+ return new RenderError(message);
308
+ }
Binary file
@@ -5,7 +5,7 @@
5
5
  "Hypen Contributors"
6
6
  ],
7
7
  "description": "A Rust implementation of the Hypen engine",
8
- "version": "0.4.1",
8
+ "version": "0.4.2",
9
9
  "license": "MIT",
10
10
  "repository": {
11
11
  "type": "git",
Binary file
@@ -4,7 +4,7 @@
4
4
  "Hypen Contributors"
5
5
  ],
6
6
  "description": "A Rust implementation of the Hypen engine",
7
- "version": "0.4.1",
7
+ "version": "0.4.2",
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
@@ -16,5 +16,7 @@
16
16
  "hypen_engine.d.ts"
17
17
  ],
18
18
  "main": "hypen_engine.js",
19
- "types": "hypen_engine.d.ts"
20
- }
19
+ "types": "hypen_engine.d.ts",
20
+ "type": "commonjs",
21
+ "sideEffects": false
22
+ }
@@ -1,76 +0,0 @@
1
- /**
2
- * Module Registry — maps component names to module definitions.
3
- *
4
- * Used by the import resolution pipeline and ManagedRouter to look up
5
- * module definitions by their component name (e.g., "HomePage", "ProfilePage").
6
- */
7
-
8
- import type { HypenModuleDefinition } from "./app.js";
9
- import { createLogger } from "./logger.js";
10
-
11
- const log = createLogger("ModuleRegistry");
12
-
13
- export class ModuleRegistry {
14
- private definitions = new Map<string, HypenModuleDefinition>();
15
-
16
- /**
17
- * Register a module definition under a component name.
18
- */
19
- register(name: string, definition: HypenModuleDefinition): void {
20
- if (this.definitions.has(name)) {
21
- log.warn(`Module "${name}" is already registered. Overwriting.`);
22
- }
23
- this.definitions.set(name, definition);
24
- log.debug(`Registered module definition: ${name}`);
25
- }
26
-
27
- /**
28
- * Get a module definition by component name.
29
- */
30
- get(name: string): HypenModuleDefinition | undefined {
31
- return this.definitions.get(name);
32
- }
33
-
34
- /**
35
- * Check if a module definition exists.
36
- */
37
- has(name: string): boolean {
38
- return this.definitions.has(name);
39
- }
40
-
41
- /**
42
- * Get all registered definitions.
43
- */
44
- getAll(): Map<string, HypenModuleDefinition> {
45
- return new Map(this.definitions);
46
- }
47
-
48
- /**
49
- * Get all registered component names.
50
- */
51
- getNames(): string[] {
52
- return Array.from(this.definitions.keys());
53
- }
54
-
55
- /**
56
- * Unregister a module definition.
57
- */
58
- unregister(name: string): void {
59
- this.definitions.delete(name);
60
- log.debug(`Unregistered module definition: ${name}`);
61
- }
62
-
63
- /**
64
- * Number of registered definitions.
65
- */
66
- get size(): number {
67
- return this.definitions.size;
68
- }
69
-
70
- /**
71
- * Clear all registered definitions.
72
- */
73
- clear(): void {
74
- this.definitions.clear();
75
- }
76
- }