@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.
- package/README.md +1 -1
- package/dist/app.d.ts +379 -0
- package/dist/app.js +111 -18
- package/dist/app.js.map +4 -4
- package/dist/components/builtin.d.ts +50 -0
- package/dist/components/builtin.js +114 -21
- package/dist/components/builtin.js.map +5 -5
- package/dist/context.d.ts +90 -0
- package/dist/discovery.d.ts +90 -0
- package/dist/discovery.js +121 -23
- package/dist/discovery.js.map +5 -5
- package/dist/disposable.d.ts +122 -0
- package/dist/engine.browser.d.ts +101 -0
- package/dist/engine.browser.js +192 -5
- package/dist/engine.browser.js.map +5 -4
- package/dist/engine.d.ts +85 -0
- package/dist/engine.js +192 -5
- package/dist/engine.js.map +5 -4
- package/dist/events.d.ts +78 -0
- package/dist/hypen.d.ts +127 -0
- package/dist/index.browser.d.ts +25 -0
- package/dist/index.browser.js +116 -19
- package/dist/index.browser.js.map +6 -6
- package/dist/index.d.ts +76 -0
- package/dist/index.js +373 -1235
- package/dist/index.js.map +10 -15
- package/dist/loader.d.ts +51 -0
- package/dist/logger.d.ts +141 -0
- package/dist/managed-router.d.ts +59 -0
- package/dist/plugin.d.ts +39 -0
- package/dist/remote/client.d.ts +154 -0
- package/dist/remote/client.js +34 -2
- package/dist/remote/client.js.map +3 -3
- package/dist/remote/index.d.ts +13 -0
- package/dist/remote/index.js +472 -26
- package/dist/remote/index.js.map +8 -7
- package/dist/remote/server.d.ts +173 -0
- package/dist/remote/server.js +472 -26
- package/dist/remote/server.js.map +8 -7
- package/dist/remote/session.d.ts +115 -0
- package/dist/remote/types.d.ts +98 -0
- package/dist/renderer.d.ts +54 -0
- package/dist/renderer.js +6 -2
- package/dist/renderer.js.map +3 -3
- package/dist/resolver.d.ts +95 -0
- package/dist/result.d.ts +134 -0
- package/dist/retry.d.ts +150 -0
- package/dist/router.d.ts +94 -0
- package/dist/state.d.ts +42 -0
- package/dist/types.d.ts +30 -0
- package/package.json +2 -1
- package/src/app.ts +162 -41
- package/src/components/builtin.ts +3 -4
- package/src/discovery.ts +2 -2
- package/src/engine.browser.ts +27 -4
- package/src/engine.ts +27 -4
- package/src/index.browser.ts +0 -2
- package/src/index.ts +3 -2
- package/src/managed-router.ts +5 -6
- package/src/remote/server.ts +116 -21
- package/src/result.ts +48 -0
- package/wasm-browser/hypen_engine_bg.wasm +0 -0
- package/wasm-browser/package.json +1 -1
- package/wasm-node/hypen_engine_bg.wasm +0 -0
- package/wasm-node/package.json +5 -3
- package/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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
773
|
+
router;
|
|
706
774
|
globalContext;
|
|
707
775
|
stateChangeCallbacks = [];
|
|
708
|
-
constructor(engine, definition,
|
|
776
|
+
constructor(engine, definition, router, globalContext) {
|
|
709
777
|
this.engine = engine;
|
|
710
778
|
this.definition = definition;
|
|
711
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
955
|
+
//# debugId=1CB58A0A6CF4765E64756E2164756E21
|