@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.
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Component Loader
3
+ *
4
+ * Loads and registers Hypen components from file system.
5
+ * Works in both Node.js and Bun environments.
6
+ */
7
+ import type { HypenModuleDefinition } from "./app.js";
8
+ export interface ComponentDefinition {
9
+ name: string;
10
+ module: HypenModuleDefinition<any>;
11
+ template: string;
12
+ path: string;
13
+ }
14
+ export declare class ComponentLoader {
15
+ private components;
16
+ /**
17
+ * Register a component with its module and template
18
+ */
19
+ register(name: string, module: HypenModuleDefinition<any>, template: string, path?: string): void;
20
+ /**
21
+ * Get a registered component by name
22
+ */
23
+ get(name: string): ComponentDefinition | undefined;
24
+ /**
25
+ * Check if a component is registered
26
+ */
27
+ has(name: string): boolean;
28
+ /**
29
+ * Get all registered component names
30
+ */
31
+ getNames(): string[];
32
+ /**
33
+ * Get all registered components
34
+ */
35
+ getAll(): ComponentDefinition[];
36
+ /**
37
+ * Clear all registered components
38
+ */
39
+ clear(): void;
40
+ /**
41
+ * Load a component from a directory
42
+ * Expects: component.ts and component.hypen in the same directory
43
+ */
44
+ loadFromDirectory(name: string, dirPath: string): Promise<void>;
45
+ /**
46
+ * Auto-load all components from a directory
47
+ * Scans for subdirectories containing component.ts and component.hypen
48
+ */
49
+ loadFromComponentsDir(baseDir: string): Promise<void>;
50
+ }
51
+ export declare const componentLoader: ComponentLoader;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Configurable Debug Logger
3
+ *
4
+ * Provides environment-aware logging that can be disabled in production.
5
+ * Supports log levels, tagged output, and performance timing.
6
+ */
7
+ export type LogLevel = "debug" | "info" | "warn" | "error" | "none";
8
+ export interface LoggerConfig {
9
+ /** Minimum log level (default: "debug" in dev, "error" in prod) */
10
+ level: LogLevel;
11
+ /** Enable colored output (default: true) */
12
+ colors: boolean;
13
+ /** Include timestamps (default: false) */
14
+ timestamps: boolean;
15
+ /** Custom log handler (default: console) */
16
+ handler?: LogHandler;
17
+ }
18
+ export interface LogHandler {
19
+ debug(tag: string, ...args: unknown[]): void;
20
+ info(tag: string, ...args: unknown[]): void;
21
+ warn(tag: string, ...args: unknown[]): void;
22
+ error(tag: string, ...args: unknown[]): void;
23
+ }
24
+ /**
25
+ * Set the global log level
26
+ */
27
+ export declare function setLogLevel(level: LogLevel): void;
28
+ /**
29
+ * Get the current log level
30
+ */
31
+ export declare function getLogLevel(): LogLevel;
32
+ /**
33
+ * Configure the logger
34
+ */
35
+ export declare function configureLogger(options: Partial<LoggerConfig>): void;
36
+ /**
37
+ * Enable all logging (sets level to "debug")
38
+ */
39
+ export declare function enableLogging(): void;
40
+ /**
41
+ * Disable all logging (sets level to "none")
42
+ */
43
+ export declare function disableLogging(): void;
44
+ /**
45
+ * Enable debug mode - alias for enableLogging()
46
+ * Call this at app startup to see all debug logs
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * import { setDebugMode } from "@hypen-space/core";
51
+ * setDebugMode(true);
52
+ * ```
53
+ */
54
+ export declare function setDebugMode(enabled: boolean): void;
55
+ /**
56
+ * Check if debug mode is enabled
57
+ */
58
+ export declare function isDebugMode(): boolean;
59
+ /**
60
+ * Tagged logger instance
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * const log = createLogger("MyComponent");
65
+ * log.debug("initialized with", { props });
66
+ * log.error("failed to load", error);
67
+ * ```
68
+ */
69
+ export declare class Logger {
70
+ private readonly tag;
71
+ constructor(tag: string);
72
+ debug(...args: unknown[]): void;
73
+ info(...args: unknown[]): void;
74
+ warn(...args: unknown[]): void;
75
+ error(...args: unknown[]): void;
76
+ /**
77
+ * Time a function execution
78
+ */
79
+ time<T>(label: string, fn: () => T): T;
80
+ /**
81
+ * Time an async function execution
82
+ */
83
+ timeAsync<T>(label: string, fn: () => Promise<T>): Promise<T>;
84
+ /**
85
+ * Create a child logger with additional context
86
+ */
87
+ child(subTag: string): Logger;
88
+ /**
89
+ * Conditionally log based on a condition
90
+ */
91
+ debugIf(condition: boolean, ...args: unknown[]): void;
92
+ warnIf(condition: boolean, ...args: unknown[]): void;
93
+ errorIf(condition: boolean, ...args: unknown[]): void;
94
+ /**
95
+ * Log once (useful for deprecation warnings)
96
+ */
97
+ private loggedOnce;
98
+ warnOnce(key: string, ...args: unknown[]): void;
99
+ debugOnce(key: string, ...args: unknown[]): void;
100
+ }
101
+ /**
102
+ * Create a tagged logger
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * const log = createLogger("Router");
107
+ * log.info("navigating to", path);
108
+ * ```
109
+ */
110
+ export declare function createLogger(tag: string): Logger;
111
+ /**
112
+ * Default logger for general use
113
+ */
114
+ export declare const logger: Logger;
115
+ /**
116
+ * Shorthand logging functions for quick use
117
+ * Prefer createLogger() for component-specific logging
118
+ */
119
+ export declare const log: {
120
+ debug: (tag: string, ...args: unknown[]) => void;
121
+ info: (tag: string, ...args: unknown[]) => void;
122
+ warn: (tag: string, ...args: unknown[]) => void;
123
+ error: (tag: string, ...args: unknown[]) => void;
124
+ };
125
+ export declare const frameworkLoggers: {
126
+ hypen: Logger;
127
+ engine: Logger;
128
+ router: Logger;
129
+ state: Logger;
130
+ events: Logger;
131
+ remote: Logger;
132
+ renderer: Logger;
133
+ module: Logger;
134
+ lifecycle: Logger;
135
+ loader: Logger;
136
+ context: Logger;
137
+ discovery: Logger;
138
+ plugin: Logger;
139
+ canvas: Logger;
140
+ debug: Logger;
141
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Managed Router — orchestrates module mount/unmount on route changes.
3
+ *
4
+ * When the router navigates to a route:
5
+ * 1. Unmounts the previous module (destroy instance, unregister from GlobalContext)
6
+ * 2. Mounts the new module (create instance with namespaced state, register)
7
+ *
8
+ * Module names are used as state prefixes (lowercased) for isolation.
9
+ */
10
+ import type { IEngine, HypenModuleDefinition } from "./app.js";
11
+ import { HypenModuleInstance } from "./app.js";
12
+ import type { HypenRouter } from "./router.js";
13
+ import type { HypenGlobalContext } from "./context.js";
14
+ import { ModuleRegistry } from "./module-registry.js";
15
+ export interface RouteDefinition {
16
+ /** Route path pattern (e.g., "/", "/profile/:id") */
17
+ path: string;
18
+ /** Component name — used to look up in ModuleRegistry */
19
+ component: string;
20
+ /** Inline module definition (alternative to registry lookup) */
21
+ module?: HypenModuleDefinition;
22
+ }
23
+ export declare class ManagedRouter {
24
+ private router;
25
+ private engine;
26
+ private registry;
27
+ private globalContext;
28
+ private routes;
29
+ private activeModule;
30
+ private activeRoute;
31
+ private unsubscribe;
32
+ /** Cached instances for modules with persist=true */
33
+ private persistedModules;
34
+ constructor(router: HypenRouter, engine: IEngine, registry: ModuleRegistry, globalContext: HypenGlobalContext);
35
+ /**
36
+ * Add a route definition.
37
+ */
38
+ addRoute(route: RouteDefinition): this;
39
+ /**
40
+ * Start listening for route changes and mount the initial route.
41
+ */
42
+ start(): void;
43
+ /**
44
+ * Stop listening and unmount the active module.
45
+ * Also destroys all persisted modules.
46
+ */
47
+ stop(): void;
48
+ /**
49
+ * Get the currently active module instance.
50
+ */
51
+ getActiveModule(): HypenModuleInstance | null;
52
+ /**
53
+ * Get the currently active route.
54
+ */
55
+ getActiveRoute(): RouteDefinition | null;
56
+ private handleRouteChange;
57
+ private matchRoute;
58
+ private mount;
59
+ private unmountActive;
60
+ }
@@ -0,0 +1,42 @@
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
+ import type { HypenModuleDefinition } from "./app.js";
8
+ export declare class ModuleRegistry {
9
+ private definitions;
10
+ /**
11
+ * Register a module definition under a component name.
12
+ */
13
+ register(name: string, definition: HypenModuleDefinition): void;
14
+ /**
15
+ * Get a module definition by component name.
16
+ */
17
+ get(name: string): HypenModuleDefinition | undefined;
18
+ /**
19
+ * Check if a module definition exists.
20
+ */
21
+ has(name: string): boolean;
22
+ /**
23
+ * Get all registered definitions.
24
+ */
25
+ getAll(): Map<string, HypenModuleDefinition>;
26
+ /**
27
+ * Get all registered component names.
28
+ */
29
+ getNames(): string[];
30
+ /**
31
+ * Unregister a module definition.
32
+ */
33
+ unregister(name: string): void;
34
+ /**
35
+ * Number of registered definitions.
36
+ */
37
+ get size(): number;
38
+ /**
39
+ * Clear all registered definitions.
40
+ */
41
+ clear(): void;
42
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Enhanced Bun Plugin for Hypen
3
+ *
4
+ * Automatically pairs .hypen templates with their TypeScript modules.
5
+ *
6
+ * Usage:
7
+ * import Counter from "./Counter.hypen";
8
+ * // Returns: { module, template, name: "Counter" }
9
+ *
10
+ * Supported conventions:
11
+ * 1. Sibling: Name.ts + Name.hypen
12
+ * 2. Folder: Name/component.ts + Name/component.hypen
13
+ * 3. Index: Name/index.ts + Name/index.hypen
14
+ */
15
+ import type { BunPlugin } from "bun";
16
+ export interface HypenPluginOptions {
17
+ /**
18
+ * Enable debug logging
19
+ */
20
+ debug?: boolean;
21
+ /**
22
+ * Custom patterns for finding module files
23
+ * Default: ["sibling", "component", "index"]
24
+ */
25
+ patterns?: ("sibling" | "component" | "index")[];
26
+ }
27
+ /**
28
+ * Create the enhanced Hypen plugin for Bun
29
+ */
30
+ export declare function hypenPlugin(options?: HypenPluginOptions): BunPlugin;
31
+ /**
32
+ * Default plugin instance with standard options
33
+ */
34
+ export declare const defaultHypenPlugin: BunPlugin;
35
+ /**
36
+ * Register the plugin globally (call this in your preload file)
37
+ */
38
+ export declare function registerHypenPlugin(options?: HypenPluginOptions): void;
39
+ export default hypenPlugin;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * RemoteEngine - Connect to a remote Hypen app over WebSocket
3
+ * Platform-agnostic client (uses standard WebSocket API)
4
+ *
5
+ * Usage:
6
+ * ```typescript
7
+ * const engine = new RemoteEngine("ws://localhost:3000", {
8
+ * session: {
9
+ * id: localStorage.getItem("sessionId") ?? undefined,
10
+ * props: { platform: "web", version: "1.0" }
11
+ * }
12
+ * });
13
+ *
14
+ * engine.onSessionEstablished(({ sessionId, isNew, isRestored }) => {
15
+ * localStorage.setItem("sessionId", sessionId);
16
+ * console.log(isRestored ? "Welcome back!" : "New session");
17
+ * });
18
+ *
19
+ * engine.onSessionExpired((reason) => {
20
+ * localStorage.removeItem("sessionId");
21
+ * });
22
+ *
23
+ * const result = await engine.connect();
24
+ * if (!result.ok) {
25
+ * console.error("Connection failed:", result.error);
26
+ * }
27
+ * ```
28
+ */
29
+ import type { Patch } from "../types.js";
30
+ import { type Result, ConnectionError } from "../result.js";
31
+ import { type Disposable } from "../disposable.js";
32
+ export type RemoteConnectionState = "disconnected" | "connecting" | "connected" | "error";
33
+ /**
34
+ * Session configuration for the client
35
+ */
36
+ export interface SessionOptions {
37
+ /** Session ID to resume (omit for new session) */
38
+ id?: string;
39
+ /** Client metadata (platform, version, userId, etc.) */
40
+ props?: Record<string, unknown>;
41
+ }
42
+ /**
43
+ * Session information received from server
44
+ */
45
+ export interface SessionInfo {
46
+ sessionId: string;
47
+ isNew: boolean;
48
+ isRestored: boolean;
49
+ }
50
+ export interface RemoteEngineOptions {
51
+ autoReconnect?: boolean;
52
+ reconnectInterval?: number;
53
+ maxReconnectAttempts?: number;
54
+ /** Session configuration */
55
+ session?: SessionOptions;
56
+ }
57
+ /**
58
+ * Client-side engine that connects to a remote Hypen app
59
+ */
60
+ export declare class RemoteEngine implements Disposable {
61
+ private ws;
62
+ private readonly url;
63
+ private state;
64
+ private readonly options;
65
+ private reconnectAttempts;
66
+ private readonly disposables;
67
+ private reconnectDisposable;
68
+ private currentSessionId;
69
+ private readonly sessionOptions?;
70
+ private readonly patchCallbacks;
71
+ private readonly stateCallbacks;
72
+ private readonly connectionCallbacks;
73
+ private readonly disconnectionCallbacks;
74
+ private readonly errorCallbacks;
75
+ private readonly sessionEstablishedCallbacks;
76
+ private readonly sessionExpiredCallbacks;
77
+ private currentState;
78
+ private currentRevision;
79
+ private moduleName;
80
+ constructor(url: string, options?: RemoteEngineOptions);
81
+ /**
82
+ * Connect to the remote server
83
+ * Returns a Result indicating success or failure
84
+ */
85
+ connect(): Promise<Result<void, ConnectionError>>;
86
+ /**
87
+ * Send hello message to establish session
88
+ */
89
+ private sendHello;
90
+ /**
91
+ * Disconnect from the remote server and clean up resources
92
+ */
93
+ disconnect(): void;
94
+ /**
95
+ * Dispose all resources (alias for disconnect)
96
+ */
97
+ dispose(): void;
98
+ /**
99
+ * Dispatch an action to the remote server
100
+ */
101
+ dispatchAction(action: string, payload?: unknown): void;
102
+ /**
103
+ * Register callback for patches
104
+ */
105
+ onPatches(callback: (patches: Patch[]) => void): this;
106
+ /**
107
+ * Register callback for state updates
108
+ */
109
+ onStateUpdate(callback: (state: unknown) => void): this;
110
+ /**
111
+ * Register callback for connection
112
+ */
113
+ onConnect(callback: () => void): this;
114
+ /**
115
+ * Register callback for disconnection
116
+ */
117
+ onDisconnect(callback: () => void): this;
118
+ /**
119
+ * Register callback for errors
120
+ */
121
+ onError(callback: (error: Error) => void): this;
122
+ /**
123
+ * Register callback for session establishment
124
+ * Called when server confirms session (new or resumed)
125
+ */
126
+ onSessionEstablished(callback: (info: SessionInfo) => void): this;
127
+ /**
128
+ * Register callback for session expiration
129
+ * Called when session is kicked or expires
130
+ */
131
+ onSessionExpired(callback: (reason: string) => void): this;
132
+ /**
133
+ * Get current connection state
134
+ */
135
+ getConnectionState(): RemoteConnectionState;
136
+ /**
137
+ * Get current app state
138
+ */
139
+ getCurrentState(): unknown;
140
+ /**
141
+ * Get current revision
142
+ */
143
+ getRevision(): number;
144
+ /**
145
+ * Get current session ID
146
+ */
147
+ getSessionId(): string | null;
148
+ private handleMessage;
149
+ private handleSessionAck;
150
+ private handleSessionExpired;
151
+ private handleInitialTree;
152
+ private handlePatch;
153
+ private attemptReconnect;
154
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Remote UI streaming for Hypen
3
+ *
4
+ * Client-side: Connect to remote Hypen apps
5
+ * Server-side: Stream Hypen apps over WebSocket
6
+ */
7
+ export { RemoteEngine } from "./client.js";
8
+ export type { RemoteConnectionState, RemoteEngineOptions, SessionOptions, SessionInfo, } from "./client.js";
9
+ export { RemoteServer, serve } from "./server.js";
10
+ export { SessionManager } from "./session.js";
11
+ export type { SessionExpireCallback } from "./session.js";
12
+ export type { RemoteMessage, InitialTreeMessage, PatchMessage, DispatchActionMessage, StateUpdateMessage, HelloMessage, SessionAckMessage, SessionExpiredMessage, Session, SessionConfig, RemoteClient, RemoteServerConfig, } from "./types.js";
13
+ export type { Patch } from "../types.js";
@@ -0,0 +1,142 @@
1
+ /**
2
+ * RemoteServer - Stream Hypen apps over WebSocket with Session Management
3
+ *
4
+ * Usage:
5
+ * ```typescript
6
+ * import { RemoteServer, app } from "@hypen-space/core";
7
+ *
8
+ * const counter = app
9
+ * .defineState({ count: 0 })
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
+ * .build();
22
+ *
23
+ * new RemoteServer()
24
+ * .module("Counter", counter)
25
+ * .ui(`Column { Text("Count: \${state.count}") }`)
26
+ * .session({ ttl: 3600, concurrent: "kick-old" })
27
+ * .listen(3000);
28
+ * ```
29
+ */
30
+ import type { HypenModule } from "../app.js";
31
+ import type { RemoteMessage, RemoteClient, RemoteServerConfig, SessionConfig } from "./types.js";
32
+ /**
33
+ * Builder pattern for hosting Hypen apps over WebSocket
34
+ */
35
+ export declare class RemoteServer {
36
+ private _module;
37
+ private _moduleName;
38
+ private _ui;
39
+ private _config;
40
+ private _sessionConfig;
41
+ private _onConnectionCallbacks;
42
+ private _onDisconnectionCallbacks;
43
+ private clients;
44
+ private nextClientId;
45
+ private server;
46
+ private sessionManager;
47
+ /**
48
+ * Set the module for this app
49
+ */
50
+ module(name: string, module: HypenModule<any>): this;
51
+ /**
52
+ * Set the UI DSL string
53
+ */
54
+ ui(dsl: string): this;
55
+ /**
56
+ * Set server configuration
57
+ */
58
+ config(config: RemoteServerConfig): this;
59
+ /**
60
+ * Configure session management
61
+ */
62
+ session(config: SessionConfig): this;
63
+ /**
64
+ * Register connection callback
65
+ */
66
+ onConnection(callback: (client: RemoteClient) => void): this;
67
+ /**
68
+ * Register disconnection callback
69
+ */
70
+ onDisconnection(callback: (client: RemoteClient) => void): this;
71
+ /**
72
+ * Start the WebSocket server
73
+ */
74
+ listen(port?: number): this;
75
+ /**
76
+ * Stop the server
77
+ */
78
+ stop(): void;
79
+ /**
80
+ * Get the server URL
81
+ */
82
+ get url(): string | null;
83
+ /**
84
+ * Handle new WebSocket connection
85
+ * Waits for hello message before fully initializing
86
+ */
87
+ private handleOpen;
88
+ /**
89
+ * Initialize session for a client (new or resumed)
90
+ */
91
+ private initializeSession;
92
+ /**
93
+ * Set up the render callback for streaming patches
94
+ */
95
+ private setupRenderCallback;
96
+ /**
97
+ * Handle concurrent connection based on policy
98
+ * Returns true if connection is allowed, false if rejected
99
+ */
100
+ private handleConcurrentConnection;
101
+ /**
102
+ * Trigger onReconnect hook
103
+ */
104
+ private triggerReconnect;
105
+ /**
106
+ * Handle incoming WebSocket message
107
+ */
108
+ private handleMessage;
109
+ /**
110
+ * Handle WebSocket close - suspend session instead of destroying
111
+ */
112
+ private handleClose;
113
+ /**
114
+ * Get current client count
115
+ */
116
+ getClientCount(): number;
117
+ /**
118
+ * Get session stats
119
+ */
120
+ getSessionStats(): {
121
+ activeSessions: number;
122
+ pendingSessions: number;
123
+ totalConnections: number;
124
+ };
125
+ /**
126
+ * Broadcast a message to all connected clients
127
+ */
128
+ broadcast(message: RemoteMessage): void;
129
+ }
130
+ /**
131
+ * Convenience function to create and start a RemoteServer
132
+ */
133
+ export declare function serve(options: {
134
+ module: HypenModule<any>;
135
+ moduleName?: string;
136
+ ui: string;
137
+ port?: number;
138
+ hostname?: string;
139
+ session?: SessionConfig;
140
+ onConnection?: (client: RemoteClient) => void;
141
+ onDisconnection?: (client: RemoteClient) => void;
142
+ }): RemoteServer;