@hypen-space/core 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +322 -0
- package/dist/components/builtin.d.ts +50 -0
- package/dist/context.d.ts +90 -0
- package/dist/discovery.d.ts +90 -0
- package/dist/discovery.js +10 -5
- package/dist/discovery.js.map +3 -3
- package/dist/disposable.d.ts +122 -0
- package/dist/engine.browser.d.ts +95 -0
- package/dist/engine.d.ts +79 -0
- 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 +6 -2
- package/dist/index.browser.js.map +3 -3
- package/dist/index.d.ts +76 -0
- package/dist/index.js +238 -1196
- package/dist/index.js.map +7 -12
- package/dist/loader.d.ts +51 -0
- package/dist/logger.d.ts +141 -0
- package/dist/managed-router.d.ts +60 -0
- package/dist/module-registry.d.ts +42 -0
- package/dist/plugin.d.ts +39 -0
- package/dist/remote/client.d.ts +154 -0
- package/dist/remote/index.d.ts +13 -0
- package/dist/remote/server.d.ts +142 -0
- 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 +116 -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/wasm-browser/hypen_engine_bg.wasm +0 -0
- package/wasm-node/package.json +4 -2
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
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 RouterContext = {
|
|
16
|
+
root: HypenRouter;
|
|
17
|
+
current?: HypenRouter;
|
|
18
|
+
parent?: HypenRouter;
|
|
19
|
+
};
|
|
20
|
+
export type ActionContext = {
|
|
21
|
+
name: string;
|
|
22
|
+
payload?: unknown;
|
|
23
|
+
sender?: string;
|
|
24
|
+
};
|
|
25
|
+
export type ActionNext = {
|
|
26
|
+
router: HypenRouter;
|
|
27
|
+
};
|
|
28
|
+
export type GlobalContext = {
|
|
29
|
+
getModule: <T = unknown>(id: string) => ModuleReference<T>;
|
|
30
|
+
hasModule: (id: string) => boolean;
|
|
31
|
+
getModuleIds: () => string[];
|
|
32
|
+
getGlobalState: () => Record<string, unknown>;
|
|
33
|
+
emit: (event: string, payload?: unknown) => void;
|
|
34
|
+
on: (event: string, handler: (payload?: unknown) => void) => () => void;
|
|
35
|
+
__router?: unknown;
|
|
36
|
+
};
|
|
37
|
+
export type LifecycleHandler<T> = (state: T, context?: GlobalContext) => void | Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Action handler context - all parameters available explicitly
|
|
40
|
+
*/
|
|
41
|
+
export interface ActionHandlerContext<T> {
|
|
42
|
+
action: ActionContext;
|
|
43
|
+
state: T;
|
|
44
|
+
next: ActionNext;
|
|
45
|
+
context: GlobalContext;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Action handler - receives all context in a single object
|
|
49
|
+
*/
|
|
50
|
+
export type ActionHandler<T> = (ctx: ActionHandlerContext<T>) => void | Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Context passed to onDisconnect handler
|
|
53
|
+
*/
|
|
54
|
+
export interface DisconnectContext<T> {
|
|
55
|
+
/** Current state snapshot (ready to serialize and save) */
|
|
56
|
+
state: T;
|
|
57
|
+
/** Session information */
|
|
58
|
+
session: Session;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Context passed to onReconnect handler
|
|
62
|
+
*/
|
|
63
|
+
export interface ReconnectContext<T> {
|
|
64
|
+
/** Session information */
|
|
65
|
+
session: Session;
|
|
66
|
+
/** Call this with saved state to restore it */
|
|
67
|
+
restore: (savedState: T) => void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Context passed to onExpire handler
|
|
71
|
+
*/
|
|
72
|
+
export interface ExpireContext {
|
|
73
|
+
/** Session information */
|
|
74
|
+
session: Session;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Handler called when client disconnects (session still alive for TTL)
|
|
78
|
+
*/
|
|
79
|
+
export type DisconnectHandler<T> = (ctx: DisconnectContext<T>) => void | Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Handler called when client reconnects with existing session
|
|
82
|
+
*/
|
|
83
|
+
export type ReconnectHandler<T> = (ctx: ReconnectContext<T>) => void | Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Handler called when session TTL expires (client never reconnected)
|
|
86
|
+
*/
|
|
87
|
+
export type ExpireHandler = (ctx: ExpireContext) => void | Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Context passed to onError handler
|
|
90
|
+
*/
|
|
91
|
+
export interface ErrorContext<T> {
|
|
92
|
+
/** The error that occurred */
|
|
93
|
+
error: HypenError;
|
|
94
|
+
/** Current state (for inspection, not mutation during error handling) */
|
|
95
|
+
state: T;
|
|
96
|
+
/** The action name if error occurred in an action handler */
|
|
97
|
+
actionName?: string;
|
|
98
|
+
/** The lifecycle phase if error occurred in a lifecycle handler */
|
|
99
|
+
lifecycle?: "created" | "destroyed" | "disconnect" | "reconnect" | "expire";
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Error handler return type - controls error propagation
|
|
103
|
+
*/
|
|
104
|
+
export type ErrorHandlerResult = void | {
|
|
105
|
+
handled: true;
|
|
106
|
+
} | {
|
|
107
|
+
retry: true;
|
|
108
|
+
} | {
|
|
109
|
+
rethrow: true;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Handler called when an error occurs in the module
|
|
113
|
+
*/
|
|
114
|
+
export type ErrorHandler<T> = (ctx: ErrorContext<T>) => ErrorHandlerResult | Promise<ErrorHandlerResult>;
|
|
115
|
+
export interface HypenModuleDefinition<T = unknown> {
|
|
116
|
+
name?: string;
|
|
117
|
+
actions: string[];
|
|
118
|
+
stateKeys: string[];
|
|
119
|
+
persist?: boolean;
|
|
120
|
+
version?: number;
|
|
121
|
+
initialState: T;
|
|
122
|
+
/**
|
|
123
|
+
* Inline UI template for single-file components.
|
|
124
|
+
* Set via the `.ui(hypen`...`)` method.
|
|
125
|
+
*/
|
|
126
|
+
template?: string;
|
|
127
|
+
handlers: {
|
|
128
|
+
onCreated?: LifecycleHandler<T>;
|
|
129
|
+
onAction: Map<string, ActionHandler<T>>;
|
|
130
|
+
onDestroyed?: LifecycleHandler<T>;
|
|
131
|
+
/** Called when client disconnects (session persists for TTL) */
|
|
132
|
+
onDisconnect?: DisconnectHandler<T>;
|
|
133
|
+
/** Called when client reconnects with existing session */
|
|
134
|
+
onReconnect?: ReconnectHandler<T>;
|
|
135
|
+
/** Called when session TTL expires */
|
|
136
|
+
onExpire?: ExpireHandler;
|
|
137
|
+
/** Called when any error occurs in the module */
|
|
138
|
+
onError?: ErrorHandler<T>;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Alias for HypenModuleDefinition for backward compatibility
|
|
143
|
+
*/
|
|
144
|
+
export type HypenModule<T = unknown> = HypenModuleDefinition<T>;
|
|
145
|
+
/**
|
|
146
|
+
* Builder for creating Hypen app modules
|
|
147
|
+
*/
|
|
148
|
+
export declare class HypenAppBuilder<T> {
|
|
149
|
+
private initialState;
|
|
150
|
+
private options;
|
|
151
|
+
private createdHandler?;
|
|
152
|
+
private actionHandlers;
|
|
153
|
+
private destroyedHandler?;
|
|
154
|
+
private disconnectHandler?;
|
|
155
|
+
private reconnectHandler?;
|
|
156
|
+
private expireHandler?;
|
|
157
|
+
private errorHandler?;
|
|
158
|
+
private template?;
|
|
159
|
+
constructor(initialState: T, options?: {
|
|
160
|
+
persist?: boolean;
|
|
161
|
+
version?: number;
|
|
162
|
+
name?: string;
|
|
163
|
+
});
|
|
164
|
+
/**
|
|
165
|
+
* Register a handler for module creation
|
|
166
|
+
*/
|
|
167
|
+
onCreated(fn: LifecycleHandler<T>): this;
|
|
168
|
+
/**
|
|
169
|
+
* Register a handler for a specific action
|
|
170
|
+
*/
|
|
171
|
+
onAction(name: string, fn: ActionHandler<T>): this;
|
|
172
|
+
/**
|
|
173
|
+
* Register a handler for module destruction
|
|
174
|
+
*/
|
|
175
|
+
onDestroyed(fn: LifecycleHandler<T>): this;
|
|
176
|
+
/**
|
|
177
|
+
* Register a handler for client disconnection
|
|
178
|
+
* Called when client disconnects; session persists for TTL
|
|
179
|
+
* Use this to save state for later restoration
|
|
180
|
+
*/
|
|
181
|
+
onDisconnect(fn: DisconnectHandler<T>): this;
|
|
182
|
+
/**
|
|
183
|
+
* Register a handler for session reconnection
|
|
184
|
+
* Called when client reconnects with an existing session ID
|
|
185
|
+
* Use restore() to hydrate state from saved data
|
|
186
|
+
*/
|
|
187
|
+
onReconnect(fn: ReconnectHandler<T>): this;
|
|
188
|
+
/**
|
|
189
|
+
* Register a handler for session expiration
|
|
190
|
+
* Called when TTL expires and client never reconnected
|
|
191
|
+
* Use this to clean up stored state
|
|
192
|
+
*/
|
|
193
|
+
onExpire(fn: ExpireHandler): this;
|
|
194
|
+
/**
|
|
195
|
+
* Register an error handler for the module
|
|
196
|
+
* Called when any error occurs in action handlers or lifecycle hooks
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* app
|
|
201
|
+
* .defineState({ count: 0 })
|
|
202
|
+
* .onAction("increment", ({ state }) => {
|
|
203
|
+
* if (state.count > 100) throw new Error("Count too high");
|
|
204
|
+
* state.count += 1;
|
|
205
|
+
* })
|
|
206
|
+
* .onError(({ error, actionName, state }) => {
|
|
207
|
+
* console.error(`Error in ${actionName}:`, error.message);
|
|
208
|
+
* // Optionally recover
|
|
209
|
+
* if (error.message.includes("too high")) {
|
|
210
|
+
* return { handled: true }; // Don't propagate
|
|
211
|
+
* }
|
|
212
|
+
* // Or retry
|
|
213
|
+
* // return { retry: true };
|
|
214
|
+
* })
|
|
215
|
+
* .build();
|
|
216
|
+
* ```
|
|
217
|
+
*
|
|
218
|
+
* @param fn - Error handler function
|
|
219
|
+
* @returns Builder for chaining
|
|
220
|
+
*/
|
|
221
|
+
onError(fn: ErrorHandler<T>): this;
|
|
222
|
+
/**
|
|
223
|
+
* Define the component's UI template inline (single-file component).
|
|
224
|
+
*
|
|
225
|
+
* Use with the `hypen` tagged template literal and binding proxies:
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```typescript
|
|
229
|
+
* import { app, hypen, state } from "@hypen-space/core";
|
|
230
|
+
*
|
|
231
|
+
* export default app
|
|
232
|
+
* .defineState({ count: 0 })
|
|
233
|
+
* .onAction("increment", ({ state }) => {
|
|
234
|
+
* state.count += 1;
|
|
235
|
+
* })
|
|
236
|
+
* .ui(hypen`
|
|
237
|
+
* Column {
|
|
238
|
+
* Text("Count: ${state.count}")
|
|
239
|
+
* Button { Text("+") }
|
|
240
|
+
* .onClick("@actions.increment")
|
|
241
|
+
* }
|
|
242
|
+
* `);
|
|
243
|
+
* ```
|
|
244
|
+
*
|
|
245
|
+
* @param template - The Hypen DSL template string
|
|
246
|
+
* @returns The built module definition (calls build() internally)
|
|
247
|
+
*/
|
|
248
|
+
ui(template: string): HypenModuleDefinition<T>;
|
|
249
|
+
/**
|
|
250
|
+
* Build the module definition
|
|
251
|
+
*/
|
|
252
|
+
build(): HypenModuleDefinition<T>;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Hypen App API
|
|
256
|
+
*/
|
|
257
|
+
export declare class HypenApp {
|
|
258
|
+
/**
|
|
259
|
+
* Define the initial state for a module
|
|
260
|
+
*/
|
|
261
|
+
defineState<T>(initial: T, options?: {
|
|
262
|
+
persist?: boolean;
|
|
263
|
+
version?: number;
|
|
264
|
+
name?: string;
|
|
265
|
+
}): HypenAppBuilder<T>;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The main app instance for creating modules
|
|
269
|
+
*/
|
|
270
|
+
export declare const app: HypenApp;
|
|
271
|
+
/**
|
|
272
|
+
* Module Instance - manages a running module with typed state
|
|
273
|
+
*/
|
|
274
|
+
export declare class HypenModuleInstance<T extends object = any> {
|
|
275
|
+
private engine;
|
|
276
|
+
private definition;
|
|
277
|
+
private state;
|
|
278
|
+
private isDestroyed;
|
|
279
|
+
private routerContext?;
|
|
280
|
+
private globalContext?;
|
|
281
|
+
private stateChangeCallbacks;
|
|
282
|
+
constructor(engine: IEngine, definition: HypenModuleDefinition<T>, routerContext?: RouterContext, globalContext?: HypenGlobalContext);
|
|
283
|
+
/**
|
|
284
|
+
* Create the global context API for this module
|
|
285
|
+
*/
|
|
286
|
+
private createGlobalContextAPI;
|
|
287
|
+
/**
|
|
288
|
+
* Execute an action handler with Result-based error handling
|
|
289
|
+
* Handles both synchronous throws and async rejections
|
|
290
|
+
*/
|
|
291
|
+
private executeAction;
|
|
292
|
+
/**
|
|
293
|
+
* Handle an error with module-level error handler support
|
|
294
|
+
* Falls back to default behavior (emit + log) if no handler or handler doesn't suppress
|
|
295
|
+
* @returns true if the error should be rethrown
|
|
296
|
+
*/
|
|
297
|
+
private handleError;
|
|
298
|
+
/**
|
|
299
|
+
* Call the onCreated handler
|
|
300
|
+
*/
|
|
301
|
+
private callCreatedHandler;
|
|
302
|
+
/**
|
|
303
|
+
* Register a callback to be notified when state changes
|
|
304
|
+
*/
|
|
305
|
+
onStateChange(callback: () => void): void;
|
|
306
|
+
/**
|
|
307
|
+
* Destroy the module instance
|
|
308
|
+
*/
|
|
309
|
+
destroy(): Promise<void>;
|
|
310
|
+
/**
|
|
311
|
+
* Get the current state (returns a snapshot)
|
|
312
|
+
*/
|
|
313
|
+
getState(): T;
|
|
314
|
+
/**
|
|
315
|
+
* Get the live observable state
|
|
316
|
+
*/
|
|
317
|
+
getLiveState(): T;
|
|
318
|
+
/**
|
|
319
|
+
* Update state directly (merges with existing state)
|
|
320
|
+
*/
|
|
321
|
+
updateState(patch: Partial<T>): void;
|
|
322
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Hypen Components
|
|
3
|
+
* Framework-provided components like Router and Route
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Router Component
|
|
7
|
+
* Automatically matches routes and renders the appropriate child Route
|
|
8
|
+
*
|
|
9
|
+
* Usage in Hypen DSL:
|
|
10
|
+
* Router {
|
|
11
|
+
* Route(path: "/") { HomePage }
|
|
12
|
+
* Route(path: "/products") { ProductList }
|
|
13
|
+
* Route(path: "/users/:id") { UserProfile }
|
|
14
|
+
* }
|
|
15
|
+
*/
|
|
16
|
+
export declare const Router: import("../app.js").HypenModuleDefinition<{
|
|
17
|
+
currentPath: string;
|
|
18
|
+
matchedRoute: any;
|
|
19
|
+
routeParams: Record<string, string>;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Route Component
|
|
23
|
+
* Defines a route pattern and its content
|
|
24
|
+
* This is just a marker component - Router processes it
|
|
25
|
+
*
|
|
26
|
+
* Props:
|
|
27
|
+
* - path: string - Route pattern (e.g., "/", "/users/:id", "/dashboard/*")
|
|
28
|
+
*
|
|
29
|
+
* Usage:
|
|
30
|
+
* Route(path: "/products") {
|
|
31
|
+
* ProductList
|
|
32
|
+
* }
|
|
33
|
+
*/
|
|
34
|
+
export declare const Route: import("../app.js").HypenModuleDefinition<{}>;
|
|
35
|
+
/**
|
|
36
|
+
* Navigation Link Component
|
|
37
|
+
* Navigates to a route when clicked
|
|
38
|
+
*
|
|
39
|
+
* Props:
|
|
40
|
+
* - to: string - Target path
|
|
41
|
+
*
|
|
42
|
+
* Usage:
|
|
43
|
+
* Link(to: "/products") {
|
|
44
|
+
* Text("View Products")
|
|
45
|
+
* }
|
|
46
|
+
*/
|
|
47
|
+
export declare const Link: import("../app.js").HypenModuleDefinition<{
|
|
48
|
+
to: string;
|
|
49
|
+
isActive: boolean;
|
|
50
|
+
}>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Context - Cross-module communication and state access
|
|
3
|
+
*/
|
|
4
|
+
import type { HypenModuleInstance } from "./app.js";
|
|
5
|
+
import { TypedEventEmitter, type HypenFrameworkEvents } from "./events.js";
|
|
6
|
+
export type ModuleReference<T = unknown> = {
|
|
7
|
+
state: T;
|
|
8
|
+
setState: (patch: Partial<T>) => void;
|
|
9
|
+
getState: () => T;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* @deprecated Use TypedEventEmitter from events.ts for type-safe events
|
|
13
|
+
*/
|
|
14
|
+
export type EventHandler = (payload?: unknown) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Global Context - Provides access to all modules and cross-module communication
|
|
17
|
+
*
|
|
18
|
+
* @template TEvents - Custom event map (extends HypenFrameworkEvents)
|
|
19
|
+
*/
|
|
20
|
+
export declare class HypenGlobalContext<TEvents extends Record<string, unknown> = HypenFrameworkEvents> {
|
|
21
|
+
private modules;
|
|
22
|
+
private typedEvents;
|
|
23
|
+
/**
|
|
24
|
+
* @deprecated Legacy event bus for backward compatibility
|
|
25
|
+
*/
|
|
26
|
+
private legacyEventBus;
|
|
27
|
+
constructor();
|
|
28
|
+
/**
|
|
29
|
+
* Get the typed event emitter for type-safe event handling
|
|
30
|
+
*/
|
|
31
|
+
get events(): TypedEventEmitter<TEvents>;
|
|
32
|
+
/**
|
|
33
|
+
* Register a module instance with an ID
|
|
34
|
+
*/
|
|
35
|
+
registerModule(id: string, instance: HypenModuleInstance): void;
|
|
36
|
+
/**
|
|
37
|
+
* Unregister a module
|
|
38
|
+
*/
|
|
39
|
+
unregisterModule(id: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Get a module by ID with type safety
|
|
42
|
+
*/
|
|
43
|
+
getModule<T = unknown>(id: string): ModuleReference<T>;
|
|
44
|
+
/**
|
|
45
|
+
* Check if a module exists
|
|
46
|
+
*/
|
|
47
|
+
hasModule(id: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Get all registered module IDs
|
|
50
|
+
*/
|
|
51
|
+
getModuleIds(): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Get the entire app state tree (snapshot)
|
|
54
|
+
*/
|
|
55
|
+
getGlobalState(): Record<string, unknown>;
|
|
56
|
+
/**
|
|
57
|
+
* Emit an event to the event bus (legacy API for backward compatibility)
|
|
58
|
+
* @deprecated Use `context.events.emit()` for type-safe events
|
|
59
|
+
*/
|
|
60
|
+
emit(event: string, payload?: unknown): void;
|
|
61
|
+
/**
|
|
62
|
+
* Subscribe to an event (legacy API for backward compatibility)
|
|
63
|
+
* @deprecated Use `context.events.on()` for type-safe events
|
|
64
|
+
*/
|
|
65
|
+
on(event: string, handler: EventHandler): () => void;
|
|
66
|
+
/**
|
|
67
|
+
* Unsubscribe from an event (legacy API)
|
|
68
|
+
* @deprecated Use the unsubscribe function returned by `context.events.on()`
|
|
69
|
+
*/
|
|
70
|
+
off(event: string, handler: EventHandler): void;
|
|
71
|
+
/**
|
|
72
|
+
* Clear all event listeners for an event (legacy API)
|
|
73
|
+
* @deprecated Use `context.events.removeAllListeners()`
|
|
74
|
+
*/
|
|
75
|
+
clearEvent(event: string): void;
|
|
76
|
+
/**
|
|
77
|
+
* Clear all event listeners (legacy API)
|
|
78
|
+
* @deprecated Use `context.events.clearAll()`
|
|
79
|
+
*/
|
|
80
|
+
clearAllEvents(): void;
|
|
81
|
+
/**
|
|
82
|
+
* Get debug info about the context
|
|
83
|
+
*/
|
|
84
|
+
debug(): {
|
|
85
|
+
modules: string[];
|
|
86
|
+
events: string[];
|
|
87
|
+
typedEvents: Array<keyof TEvents>;
|
|
88
|
+
state: Record<string, unknown>;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Discovery
|
|
3
|
+
*
|
|
4
|
+
* Automatically discovers Hypen components from the filesystem.
|
|
5
|
+
*
|
|
6
|
+
* Supports multiple conventions:
|
|
7
|
+
* 1. Single-file: ComponentName.ts with .ui(hypen`...`) inline template
|
|
8
|
+
* 2. Folder-based: ComponentName/component.ts + ComponentName/component.hypen
|
|
9
|
+
* 3. Sibling files: ComponentName.ts + ComponentName.hypen
|
|
10
|
+
* 4. Index-based: ComponentName/index.ts + ComponentName/index.hypen
|
|
11
|
+
*/
|
|
12
|
+
import type { HypenModuleDefinition } from "./app.js";
|
|
13
|
+
export interface DiscoveredComponent {
|
|
14
|
+
name: string;
|
|
15
|
+
/** Path to .hypen file (null for single-file components with inline templates) */
|
|
16
|
+
hypenPath: string | null;
|
|
17
|
+
/** Path to .ts module file */
|
|
18
|
+
modulePath: string | null;
|
|
19
|
+
/** The UI template (from .hypen file or inline) */
|
|
20
|
+
template: string;
|
|
21
|
+
/** Whether this component has a module (state/actions) */
|
|
22
|
+
hasModule: boolean;
|
|
23
|
+
/** Whether this is a single-file component with inline template */
|
|
24
|
+
isSingleFile: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface DiscoveryOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Which naming patterns to look for
|
|
29
|
+
* Default: ["single-file", "folder", "sibling", "index"]
|
|
30
|
+
*
|
|
31
|
+
* - single-file: ComponentName.ts with .ui(hypen`...`) inline template
|
|
32
|
+
* - folder: ComponentName/component.ts + ComponentName/component.hypen
|
|
33
|
+
* - sibling: ComponentName.ts + ComponentName.hypen
|
|
34
|
+
* - index: ComponentName/index.ts + ComponentName/index.hypen
|
|
35
|
+
*/
|
|
36
|
+
patterns?: ("single-file" | "folder" | "sibling" | "index")[];
|
|
37
|
+
/**
|
|
38
|
+
* Recursively scan subdirectories
|
|
39
|
+
* Default: false
|
|
40
|
+
*/
|
|
41
|
+
recursive?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Enable debug logging
|
|
44
|
+
* Default: false
|
|
45
|
+
*/
|
|
46
|
+
debug?: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface WatchOptions extends DiscoveryOptions {
|
|
49
|
+
/**
|
|
50
|
+
* Callback when components change
|
|
51
|
+
*/
|
|
52
|
+
onChange?: (components: DiscoveredComponent[]) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Callback when a component is added
|
|
55
|
+
*/
|
|
56
|
+
onAdd?: (component: DiscoveredComponent) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Callback when a component is removed
|
|
59
|
+
*/
|
|
60
|
+
onRemove?: (name: string) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Callback when a component is updated
|
|
63
|
+
*/
|
|
64
|
+
onUpdate?: (component: DiscoveredComponent) => void;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Discover all Hypen components in a directory
|
|
68
|
+
*/
|
|
69
|
+
export declare function discoverComponents(baseDir: string, options?: DiscoveryOptions): Promise<DiscoveredComponent[]>;
|
|
70
|
+
/**
|
|
71
|
+
* Load discovered components into a map for use with Hypen
|
|
72
|
+
*/
|
|
73
|
+
export declare function loadDiscoveredComponents(components: DiscoveredComponent[]): Promise<Map<string, {
|
|
74
|
+
name: string;
|
|
75
|
+
module: HypenModuleDefinition<any>;
|
|
76
|
+
template: string;
|
|
77
|
+
}>>;
|
|
78
|
+
/**
|
|
79
|
+
* Watch a directory for component changes
|
|
80
|
+
*/
|
|
81
|
+
export declare function watchComponents(baseDir: string, options?: WatchOptions): {
|
|
82
|
+
stop: () => void;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Generate a components object from discovered components
|
|
86
|
+
* Useful for static imports / code generation
|
|
87
|
+
*/
|
|
88
|
+
export declare function generateComponentsCode(baseDir: string, options?: DiscoveryOptions & {
|
|
89
|
+
outputDir?: string;
|
|
90
|
+
}): Promise<string>;
|
package/dist/discovery.js
CHANGED
|
@@ -854,7 +854,7 @@ var init_app = __esm(() => {
|
|
|
854
854
|
// src/discovery.ts
|
|
855
855
|
init_logger();
|
|
856
856
|
import { existsSync, readdirSync, readFileSync, watch } from "fs";
|
|
857
|
-
import { join, basename, resolve } from "path";
|
|
857
|
+
import { join, basename, resolve, relative } from "path";
|
|
858
858
|
async function discoverComponents(baseDir, options = {}) {
|
|
859
859
|
const {
|
|
860
860
|
patterns = ["single-file", "folder", "sibling", "index"],
|
|
@@ -1090,6 +1090,7 @@ function watchComponents(baseDir, options = {}) {
|
|
|
1090
1090
|
async function generateComponentsCode(baseDir, options = {}) {
|
|
1091
1091
|
const components = await discoverComponents(baseDir, options);
|
|
1092
1092
|
const resolvedDir = resolve(baseDir);
|
|
1093
|
+
const outputBase = options.outputDir ? resolve(options.outputDir) : resolvedDir;
|
|
1093
1094
|
let code = `/**
|
|
1094
1095
|
* Auto-generated component imports
|
|
1095
1096
|
* Generated by @hypen-space/core discovery
|
|
@@ -1097,9 +1098,13 @@ async function generateComponentsCode(baseDir, options = {}) {
|
|
|
1097
1098
|
|
|
1098
1099
|
`;
|
|
1099
1100
|
for (const component of components) {
|
|
1100
|
-
|
|
1101
|
-
if (
|
|
1102
|
-
|
|
1101
|
+
let importPath = null;
|
|
1102
|
+
if (component.modulePath) {
|
|
1103
|
+
const rel = relative(outputBase, component.modulePath).replace(/\.ts$/, ".js");
|
|
1104
|
+
importPath = rel.startsWith(".") ? rel : "./" + rel;
|
|
1105
|
+
}
|
|
1106
|
+
if (importPath) {
|
|
1107
|
+
code += `import ${component.name}Module from "${importPath}";
|
|
1103
1108
|
`;
|
|
1104
1109
|
}
|
|
1105
1110
|
}
|
|
@@ -1143,4 +1148,4 @@ export {
|
|
|
1143
1148
|
discoverComponents
|
|
1144
1149
|
};
|
|
1145
1150
|
|
|
1146
|
-
//# debugId=
|
|
1151
|
+
//# debugId=85977D1E65CCBA9B64756E2164756E21
|