@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
package/README.md CHANGED
@@ -136,7 +136,7 @@ const userModule = app
136
136
  state.user = await fetchUser(userId);
137
137
  state.loading = false;
138
138
  // State changes are auto-synced via Proxy
139
- // next.router is available for programmatic navigation
139
+ // context.router is available for programmatic navigation
140
140
  })
141
141
 
142
142
  .onAction("logout", ({ state }) => {
package/dist/app.d.ts ADDED
@@ -0,0 +1,379 @@
1
+ /**
2
+ * Hypen App Builder API
3
+ * Implements the stateful module system from RFC-0001
4
+ */
5
+ import type { Action } from "./types.js";
6
+ import type { Session } from "./remote/types.js";
7
+ import { HypenError } from "./result.js";
8
+ export interface IEngine {
9
+ setModule(name: string, actions: string[], stateKeys: string[], initialState: unknown): void;
10
+ onAction(actionName: string, handler: (action: Action) => void | Promise<void>): void;
11
+ notifyStateChange(paths: string[], changedValues: Record<string, unknown>): void;
12
+ }
13
+ import type { HypenRouter } from "./router.js";
14
+ import type { HypenGlobalContext, ModuleReference } from "./context.js";
15
+ export type ActionContext<P = unknown> = {
16
+ name: string;
17
+ payload?: P;
18
+ sender?: string;
19
+ };
20
+ export type GlobalContext = {
21
+ getModule: <T = unknown>(id: string) => ModuleReference<T>;
22
+ hasModule: (id: string) => boolean;
23
+ getModuleIds: () => string[];
24
+ getGlobalState: () => Record<string, unknown>;
25
+ emit: (event: string, payload?: unknown) => void;
26
+ on: (event: string, handler: (payload?: unknown) => void) => () => void;
27
+ router: HypenRouter | null;
28
+ };
29
+ export type LifecycleHandler<T> = (state: T, context?: GlobalContext) => void | Promise<void>;
30
+ /**
31
+ * Action handler context - all parameters available explicitly
32
+ */
33
+ export interface ActionHandlerContext<T, P = unknown> {
34
+ action: ActionContext<P>;
35
+ state: T;
36
+ context: GlobalContext;
37
+ }
38
+ /**
39
+ * Action handler - receives all context in a single object
40
+ */
41
+ export type ActionHandler<T, P = unknown> = (ctx: ActionHandlerContext<T, P>) => void | Promise<void>;
42
+ /**
43
+ * Context passed to onDisconnect handler
44
+ */
45
+ export interface DisconnectContext<T> {
46
+ /** Current state snapshot (ready to serialize and save) */
47
+ state: T;
48
+ /** Session information */
49
+ session: Session;
50
+ }
51
+ /**
52
+ * Context passed to onReconnect handler
53
+ */
54
+ export interface ReconnectContext<T> {
55
+ /** Session information */
56
+ session: Session;
57
+ /** Call this with saved state to restore it */
58
+ restore: (savedState: T) => void;
59
+ }
60
+ /**
61
+ * Context passed to onExpire handler
62
+ */
63
+ export interface ExpireContext {
64
+ /** Session information */
65
+ session: Session;
66
+ }
67
+ /**
68
+ * Handler called when client disconnects (session still alive for TTL)
69
+ */
70
+ export type DisconnectHandler<T> = (ctx: DisconnectContext<T>) => void | Promise<void>;
71
+ /**
72
+ * Handler called when client reconnects with existing session
73
+ */
74
+ export type ReconnectHandler<T> = (ctx: ReconnectContext<T>) => void | Promise<void>;
75
+ /**
76
+ * Handler called when session TTL expires (client never reconnected)
77
+ */
78
+ export type ExpireHandler = (ctx: ExpireContext) => void | Promise<void>;
79
+ /**
80
+ * Context passed to onError handler
81
+ */
82
+ export interface ErrorContext<T> {
83
+ /** The error that occurred */
84
+ error: HypenError;
85
+ /** Current state (for inspection, not mutation during error handling) */
86
+ state: T;
87
+ /** The action name if error occurred in an action handler */
88
+ actionName?: string;
89
+ /** The lifecycle phase if error occurred in a lifecycle handler */
90
+ lifecycle?: "created" | "destroyed" | "disconnect" | "reconnect" | "expire";
91
+ }
92
+ /**
93
+ * Error handler return type - controls error propagation
94
+ */
95
+ export type ErrorHandlerResult = void | {
96
+ handled: true;
97
+ } | {
98
+ retry: true;
99
+ } | {
100
+ rethrow: true;
101
+ };
102
+ /**
103
+ * Handler called when an error occurs in the module
104
+ */
105
+ export type ErrorHandler<T> = (ctx: ErrorContext<T>) => ErrorHandlerResult | Promise<ErrorHandlerResult>;
106
+ export interface HypenModuleDefinition<T = unknown> {
107
+ name?: string;
108
+ actions: string[];
109
+ stateKeys: string[];
110
+ persist?: boolean;
111
+ version?: number;
112
+ initialState: T;
113
+ /**
114
+ * Inline UI template for single-file components.
115
+ * Set via the `.ui(hypen`...`)` method.
116
+ */
117
+ template?: string;
118
+ handlers: {
119
+ onCreated?: LifecycleHandler<T>;
120
+ onAction: Map<string, ActionHandler<T, any>>;
121
+ onDestroyed?: LifecycleHandler<T>;
122
+ /** Called when client disconnects (session persists for TTL) */
123
+ onDisconnect?: DisconnectHandler<T>;
124
+ /** Called when client reconnects with existing session */
125
+ onReconnect?: ReconnectHandler<T>;
126
+ /** Called when session TTL expires */
127
+ onExpire?: ExpireHandler;
128
+ /** Called when any error occurs in the module */
129
+ onError?: ErrorHandler<T>;
130
+ };
131
+ }
132
+ /**
133
+ * Alias for HypenModuleDefinition for backward compatibility
134
+ */
135
+ export type HypenModule<T = unknown> = HypenModuleDefinition<T>;
136
+ /**
137
+ * Builder for creating Hypen app modules
138
+ */
139
+ export declare class HypenAppBuilder<T> {
140
+ private initialState;
141
+ private options;
142
+ private createdHandler?;
143
+ private actionHandlers;
144
+ private destroyedHandler?;
145
+ private disconnectHandler?;
146
+ private reconnectHandler?;
147
+ private expireHandler?;
148
+ private errorHandler?;
149
+ private template?;
150
+ private _registry?;
151
+ constructor(initialState: T, options?: {
152
+ persist?: boolean;
153
+ version?: number;
154
+ name?: string;
155
+ }, registry?: Map<string, HypenModuleDefinition>);
156
+ /**
157
+ * Register a handler for module creation
158
+ */
159
+ onCreated(fn: LifecycleHandler<T>): this;
160
+ /**
161
+ * Register a handler for a specific action.
162
+ * Optionally provide a payload type parameter for typed action payloads.
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * // Typed payload:
167
+ * .onAction<{ amount: number }>("add", ({ action, state }) => {
168
+ * action.payload.amount; // fully typed
169
+ * })
170
+ *
171
+ * // Untyped (payload is unknown):
172
+ * .onAction("add", ({ action, state }) => {
173
+ * action.payload; // unknown
174
+ * })
175
+ * ```
176
+ */
177
+ onAction<P = unknown>(name: string, fn: ActionHandler<T, P>): this;
178
+ /**
179
+ * Register a handler for module destruction
180
+ */
181
+ onDestroyed(fn: LifecycleHandler<T>): this;
182
+ /**
183
+ * Register a handler for client disconnection
184
+ * Called when client disconnects; session persists for TTL
185
+ * Use this to save state for later restoration
186
+ */
187
+ onDisconnect(fn: DisconnectHandler<T>): this;
188
+ /**
189
+ * Register a handler for session reconnection
190
+ * Called when client reconnects with an existing session ID
191
+ * Use restore() to hydrate state from saved data
192
+ */
193
+ onReconnect(fn: ReconnectHandler<T>): this;
194
+ /**
195
+ * Register a handler for session expiration
196
+ * Called when TTL expires and client never reconnected
197
+ * Use this to clean up stored state
198
+ */
199
+ onExpire(fn: ExpireHandler): this;
200
+ /**
201
+ * Register an error handler for the module
202
+ * Called when any error occurs in action handlers or lifecycle hooks
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * app
207
+ * .defineState({ count: 0 })
208
+ * .onAction("increment", ({ state }) => {
209
+ * if (state.count > 100) throw new Error("Count too high");
210
+ * state.count += 1;
211
+ * })
212
+ * .onError(({ error, actionName, state }) => {
213
+ * console.error(`Error in ${actionName}:`, error.message);
214
+ * // Optionally recover
215
+ * if (error.message.includes("too high")) {
216
+ * return { handled: true }; // Don't propagate
217
+ * }
218
+ * // Or retry
219
+ * // return { retry: true };
220
+ * })
221
+ * .build();
222
+ * ```
223
+ *
224
+ * @param fn - Error handler function
225
+ * @returns Builder for chaining
226
+ */
227
+ onError(fn: ErrorHandler<T>): this;
228
+ /**
229
+ * Define the component's UI template inline (single-file component).
230
+ *
231
+ * Use with the `hypen` tagged template literal and binding proxies:
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * import { app, hypen, state } from "@hypen-space/core";
236
+ *
237
+ * export default app
238
+ * .defineState({ count: 0 })
239
+ * .onAction("increment", ({ state }) => {
240
+ * state.count += 1;
241
+ * })
242
+ * .ui(hypen`
243
+ * Column {
244
+ * Text("Count: ${state.count}")
245
+ * Button { Text("+") }
246
+ * .onClick("@actions.increment")
247
+ * }
248
+ * `);
249
+ * ```
250
+ *
251
+ * @param template - The Hypen DSL template string
252
+ * @returns The built module definition (calls build() internally)
253
+ */
254
+ ui(template: string): HypenModuleDefinition<T>;
255
+ /**
256
+ * Build the module definition
257
+ */
258
+ build(): HypenModuleDefinition<T>;
259
+ }
260
+ /**
261
+ * Hypen App API — singleton factory and component registry.
262
+ *
263
+ * Modules built with a `name` are automatically registered here.
264
+ * Consumers (ManagedRouter, RemoteServer, ComponentResolver) read from this
265
+ * registry instead of requiring a separate ModuleRegistry instance.
266
+ */
267
+ export declare class HypenApp {
268
+ /** @internal */
269
+ readonly _registry: Map<string, HypenModuleDefinition<unknown>>;
270
+ /**
271
+ * Define the initial state for a module
272
+ */
273
+ defineState<T>(initial: T, options?: {
274
+ persist?: boolean;
275
+ version?: number;
276
+ name?: string;
277
+ }): HypenAppBuilder<T>;
278
+ /**
279
+ * Convenience: start a module builder with a name pre-set.
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * export default app
284
+ * .module("Settings")
285
+ * .defineState({ theme: "dark" })
286
+ * .ui(hypen`...`);
287
+ * ```
288
+ */
289
+ module(name: string): {
290
+ defineState: <T>(initial: T, options?: {
291
+ persist?: boolean;
292
+ version?: number;
293
+ }) => HypenAppBuilder<T>;
294
+ };
295
+ /**
296
+ * Get a registered module definition by component name.
297
+ */
298
+ get(name: string): HypenModuleDefinition | undefined;
299
+ /**
300
+ * Check if a module definition is registered.
301
+ */
302
+ has(name: string): boolean;
303
+ /**
304
+ * Read-only view of all registered component definitions.
305
+ */
306
+ get components(): ReadonlyMap<string, HypenModuleDefinition>;
307
+ /**
308
+ * Get all registered component names.
309
+ */
310
+ getNames(): string[];
311
+ /**
312
+ * Number of registered definitions.
313
+ */
314
+ get size(): number;
315
+ /**
316
+ * Unregister a module definition.
317
+ */
318
+ unregister(name: string): void;
319
+ /**
320
+ * Clear all registered definitions.
321
+ */
322
+ clear(): void;
323
+ }
324
+ /**
325
+ * The main app instance for creating modules
326
+ */
327
+ export declare const app: HypenApp;
328
+ /**
329
+ * Module Instance - manages a running module with typed state
330
+ */
331
+ export declare class HypenModuleInstance<T extends object = any> {
332
+ private engine;
333
+ private definition;
334
+ private state;
335
+ private isDestroyed;
336
+ private router;
337
+ private globalContext?;
338
+ private stateChangeCallbacks;
339
+ constructor(engine: IEngine, definition: HypenModuleDefinition<T>, router?: HypenRouter | null, globalContext?: HypenGlobalContext);
340
+ /**
341
+ * Create the global context API for this module
342
+ */
343
+ private createGlobalContextAPI;
344
+ /**
345
+ * Execute an action handler with Result-based error handling
346
+ * Handles both synchronous throws and async rejections
347
+ */
348
+ private executeAction;
349
+ /**
350
+ * Handle an error with module-level error handler support
351
+ * Falls back to default behavior (emit + log) if no handler or handler doesn't suppress
352
+ * @returns true if the error should be rethrown
353
+ */
354
+ private handleError;
355
+ /**
356
+ * Call the onCreated handler
357
+ */
358
+ private callCreatedHandler;
359
+ /**
360
+ * Register a callback to be notified when state changes
361
+ */
362
+ onStateChange(callback: () => void): void;
363
+ /**
364
+ * Destroy the module instance
365
+ */
366
+ destroy(): Promise<void>;
367
+ /**
368
+ * Get the current state (returns a snapshot)
369
+ */
370
+ getState(): T;
371
+ /**
372
+ * Get the live observable state
373
+ */
374
+ getLiveState(): T;
375
+ /**
376
+ * Update state directly (merges with existing state)
377
+ */
378
+ updateState(patch: Partial<T>): void;
379
+ }
package/dist/app.js CHANGED
@@ -361,7 +361,20 @@ function all(results) {
361
361
  }
362
362
  return Ok(values);
363
363
  }
364
- var HypenError, ActionError, ConnectionError, StateError;
364
+ function classifyEngineError(err) {
365
+ const message = err instanceof Error ? err.message : String(err);
366
+ if (message.startsWith("Parse error:")) {
367
+ return new ParseError(message);
368
+ }
369
+ if (message.startsWith("Invalid state:") || message.startsWith("Invalid state patch:")) {
370
+ return new StateError(message);
371
+ }
372
+ if (message.startsWith("Parent node not found:")) {
373
+ return new RenderError(message);
374
+ }
375
+ return new RenderError(message);
376
+ }
377
+ var HypenError, ActionError, ConnectionError, StateError, ParseError, RenderError;
365
378
  var init_result = __esm(() => {
366
379
  HypenError = class HypenError extends Error {
367
380
  code;
@@ -411,6 +424,25 @@ var init_result = __esm(() => {
411
424
  this.path = path;
412
425
  }
413
426
  };
427
+ ParseError = class ParseError extends HypenError {
428
+ source;
429
+ constructor(message, source, cause) {
430
+ super("PARSE_ERROR", message, {
431
+ context: { source },
432
+ cause: cause instanceof Error ? cause : undefined
433
+ });
434
+ this.name = "ParseError";
435
+ this.source = source;
436
+ }
437
+ };
438
+ RenderError = class RenderError extends HypenError {
439
+ constructor(message, cause) {
440
+ super("RENDER_ERROR", message, {
441
+ cause: cause instanceof Error ? cause : undefined
442
+ });
443
+ this.name = "RenderError";
444
+ }
445
+ };
414
446
  });
415
447
 
416
448
  // src/logger.ts
@@ -632,9 +664,11 @@ class HypenAppBuilder {
632
664
  expireHandler;
633
665
  errorHandler;
634
666
  template;
635
- constructor(initialState, options) {
667
+ _registry;
668
+ constructor(initialState, options, registry) {
636
669
  this.initialState = initialState;
637
670
  this.options = options || {};
671
+ this._registry = registry;
638
672
  }
639
673
  onCreated(fn) {
640
674
  this.createdHandler = fn;
@@ -670,7 +704,7 @@ class HypenAppBuilder {
670
704
  }
671
705
  build() {
672
706
  const stateKeys = this.initialState !== null && typeof this.initialState === "object" ? Object.keys(this.initialState) : [];
673
- return {
707
+ const definition = {
674
708
  name: this.options.name,
675
709
  actions: Array.from(this.actionHandlers.keys()),
676
710
  stateKeys,
@@ -688,12 +722,46 @@ class HypenAppBuilder {
688
722
  onError: this.errorHandler
689
723
  }
690
724
  };
725
+ if (this.options.name && this._registry) {
726
+ this._registry.set(this.options.name, definition);
727
+ }
728
+ return definition;
691
729
  }
692
730
  }
693
731
 
694
732
  class HypenApp {
733
+ _registry = new Map;
695
734
  defineState(initial, options) {
696
- return new HypenAppBuilder(initial, options);
735
+ return new HypenAppBuilder(initial, options, this._registry);
736
+ }
737
+ module(name) {
738
+ const registry = this._registry;
739
+ return {
740
+ defineState: (initial, options) => {
741
+ return new HypenAppBuilder(initial, { ...options, name }, registry);
742
+ }
743
+ };
744
+ }
745
+ get(name) {
746
+ return this._registry.get(name);
747
+ }
748
+ has(name) {
749
+ return this._registry.has(name);
750
+ }
751
+ get components() {
752
+ return this._registry;
753
+ }
754
+ getNames() {
755
+ return Array.from(this._registry.keys());
756
+ }
757
+ get size() {
758
+ return this._registry.size;
759
+ }
760
+ unregister(name) {
761
+ this._registry.delete(name);
762
+ }
763
+ clear() {
764
+ this._registry.clear();
697
765
  }
698
766
  }
699
767
 
@@ -702,13 +770,13 @@ class HypenModuleInstance {
702
770
  definition;
703
771
  state;
704
772
  isDestroyed = false;
705
- routerContext;
773
+ router;
706
774
  globalContext;
707
775
  stateChangeCallbacks = [];
708
- constructor(engine, definition, routerContext, globalContext) {
776
+ constructor(engine, definition, router, globalContext) {
709
777
  this.engine = engine;
710
778
  this.definition = definition;
711
- this.routerContext = routerContext;
779
+ this.router = router ?? null;
712
780
  this.globalContext = globalContext;
713
781
  const statePrefix = (definition.name || "").toLowerCase();
714
782
  this.state = createObservableState(definition.initialState, {
@@ -730,14 +798,10 @@ class HypenModuleInstance {
730
798
  payload: action.payload,
731
799
  sender: action.sender
732
800
  };
733
- const next = {
734
- router: this.routerContext?.root || null
735
- };
736
801
  const context = this.globalContext ? this.createGlobalContextAPI() : undefined;
737
802
  const result = await this.executeAction(actionName, handler, {
738
803
  action: actionCtx,
739
804
  state: this.state,
740
- next,
741
805
  context
742
806
  });
743
807
  if (!result.ok) {
@@ -750,6 +814,21 @@ class HypenModuleInstance {
750
814
  }
751
815
  });
752
816
  }
817
+ this.engine.onAction("__hypen_bind", (action) => {
818
+ const payload = action.payload;
819
+ if (!payload?.path)
820
+ return;
821
+ const segments = payload.path.split(".");
822
+ let target = this.state;
823
+ for (let i = 0;i < segments.length - 1; i++) {
824
+ const seg = segments[i];
825
+ target = target?.[seg];
826
+ if (target == null)
827
+ return;
828
+ }
829
+ const lastSeg = segments[segments.length - 1];
830
+ target[lastSeg] = payload.value;
831
+ });
753
832
  this.callCreatedHandler();
754
833
  }
755
834
  createGlobalContextAPI() {
@@ -763,11 +842,9 @@ class HypenModuleInstance {
763
842
  getModuleIds: () => ctx.getModuleIds(),
764
843
  getGlobalState: () => ctx.getGlobalState(),
765
844
  emit: (event, payload) => ctx.emit(event, payload),
766
- on: (event, handler) => ctx.on(event, handler)
845
+ on: (event, handler) => ctx.on(event, handler),
846
+ router: this.router
767
847
  };
768
- if (ctx.__router) {
769
- api.__router = ctx.__router;
770
- }
771
848
  if (ctx.__hypenEngine) {
772
849
  api.__hypenEngine = ctx.__hypenEngine;
773
850
  }
@@ -818,7 +895,15 @@ class HypenModuleInstance {
818
895
  async callCreatedHandler() {
819
896
  if (this.definition.handlers.onCreated) {
820
897
  const context = this.globalContext ? this.createGlobalContextAPI() : undefined;
821
- await this.definition.handlers.onCreated(this.state, context);
898
+ try {
899
+ await this.definition.handlers.onCreated(this.state, context);
900
+ } catch (e) {
901
+ const error = e instanceof HypenError ? e : new ActionError("onCreated", e);
902
+ const shouldRethrow = await this.handleError(error, { lifecycle: "created" });
903
+ if (shouldRethrow) {
904
+ throw error;
905
+ }
906
+ }
822
907
  }
823
908
  }
824
909
  onStateChange(callback) {
@@ -828,7 +913,15 @@ class HypenModuleInstance {
828
913
  if (this.isDestroyed)
829
914
  return;
830
915
  if (this.definition.handlers.onDestroyed) {
831
- await this.definition.handlers.onDestroyed(this.state);
916
+ try {
917
+ await this.definition.handlers.onDestroyed(this.state);
918
+ } catch (e) {
919
+ const error = e instanceof HypenError ? e : new ActionError("onDestroyed", e);
920
+ const shouldRethrow = await this.handleError(error, { lifecycle: "destroyed" });
921
+ if (shouldRethrow) {
922
+ throw error;
923
+ }
924
+ }
832
925
  }
833
926
  this.isDestroyed = true;
834
927
  }
@@ -859,4 +952,4 @@ export {
859
952
  HypenApp
860
953
  };
861
954
 
862
- //# debugId=047B2B99C6893ED464756E2164756E21
955
+ //# debugId=1CB58A0A6CF4765E64756E2164756E21