@hypen-space/core 0.4.956 → 0.5.2
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 +5 -8
- package/dist/app.js +13 -1168
- package/dist/app.js.map +3 -9
- package/dist/chunk-00zt3w6e.js +58 -0
- package/dist/chunk-00zt3w6e.js.map +10 -0
- package/dist/chunk-3zvyth50.js +87 -0
- package/dist/chunk-3zvyth50.js.map +10 -0
- package/dist/chunk-5gtx3pza.js +11 -0
- package/dist/chunk-5gtx3pza.js.map +9 -0
- package/dist/chunk-7222131a.js +191 -0
- package/dist/chunk-7222131a.js.map +10 -0
- package/dist/chunk-avchpwhf.js +200 -0
- package/dist/chunk-avchpwhf.js.map +10 -0
- package/dist/chunk-ec0tgy9a.js +154 -0
- package/dist/chunk-ec0tgy9a.js.map +10 -0
- package/dist/chunk-fnss4dm2.js +479 -0
- package/dist/chunk-fnss4dm2.js.map +10 -0
- package/dist/chunk-g2gz9pgg.js +133 -0
- package/dist/chunk-g2gz9pgg.js.map +10 -0
- package/dist/chunk-g72v6mmf.js +341 -0
- package/dist/chunk-g72v6mmf.js.map +10 -0
- package/dist/chunk-g7g3jxty.js +351 -0
- package/dist/chunk-g7g3jxty.js.map +10 -0
- package/dist/chunk-g830re2c.js +50 -0
- package/dist/chunk-g830re2c.js.map +10 -0
- package/dist/chunk-gtwh1sg7.js +127 -0
- package/dist/chunk-gtwh1sg7.js.map +10 -0
- package/dist/chunk-k12cwfst.js +72 -0
- package/dist/chunk-k12cwfst.js.map +10 -0
- package/dist/chunk-mbqqsz6r.js +128 -0
- package/dist/chunk-mbqqsz6r.js.map +10 -0
- package/dist/chunk-ne632swm.js +209 -0
- package/dist/chunk-ne632swm.js.map +10 -0
- package/dist/chunk-p3pj1bax.js +206 -0
- package/dist/chunk-p3pj1bax.js.map +10 -0
- package/dist/chunk-vshdk2pw.js +56 -0
- package/dist/chunk-vshdk2pw.js.map +10 -0
- package/dist/chunk-wp2ebn2m.js +51 -0
- package/dist/chunk-wp2ebn2m.js.map +10 -0
- package/dist/chunk-x4qv4jm9.js +88 -0
- package/dist/chunk-x4qv4jm9.js.map +10 -0
- package/dist/chunk-ycxp4drd.js +211 -0
- package/dist/chunk-ycxp4drd.js.map +10 -0
- package/dist/components/builtin.js +14 -1246
- package/dist/components/builtin.js.map +3 -10
- package/dist/context.js +7 -362
- package/dist/context.js.map +3 -6
- package/dist/datasource.js +5 -76
- package/dist/datasource.js.map +3 -4
- package/dist/disposable.js +20 -367
- package/dist/disposable.js.map +3 -5
- package/dist/engine-base.d.ts +38 -2
- package/dist/engine-base.js +45 -412
- package/dist/engine-base.js.map +4 -6
- package/dist/events.js +7 -285
- package/dist/events.js.map +3 -5
- package/dist/hypen.js +8 -71
- package/dist/hypen.js.map +3 -4
- package/dist/index.browser.js +58 -2152
- package/dist/index.browser.js.map +3 -16
- package/dist/index.d.ts +2 -0
- package/dist/index.js +132 -3058
- package/dist/index.js.map +4 -22
- package/dist/logger.js +16 -220
- package/dist/logger.js.map +3 -4
- package/dist/managed-router.js +12 -1367
- package/dist/managed-router.js.map +3 -10
- package/dist/portable.js +6 -70
- package/dist/portable.js.map +3 -4
- package/dist/remote/client.js +9 -1010
- package/dist/remote/client.js.map +3 -8
- package/dist/remote/index.d.ts +2 -0
- package/dist/remote/index.js +511 -1066
- package/dist/remote/index.js.map +4 -9
- package/dist/remote/remote-session.d.ts +245 -0
- package/dist/remote/session.js +5 -147
- package/dist/remote/session.js.map +3 -4
- package/dist/renderer.js +7 -271
- package/dist/renderer.js.map +3 -5
- package/dist/resolver.js +5 -484
- package/dist/resolver.js.map +3 -5
- package/dist/result.js +25 -211
- package/dist/result.js.map +3 -4
- package/dist/retry.js +10 -336
- package/dist/retry.js.map +3 -5
- package/dist/router.js +9 -807
- package/dist/router.js.map +3 -8
- package/dist/state.js +10 -274
- package/dist/state.js.map +3 -5
- package/dist/validate.d.ts +19 -0
- package/package.json +13 -1
- package/src/engine-base.ts +87 -4
- package/src/index.ts +5 -0
- package/src/remote/index.ts +16 -0
- package/src/remote/remote-session.ts +998 -0
- package/src/validate.ts +37 -0
package/dist/remote/index.js.map
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/
|
|
3
|
+
"sources": ["../src/remote/remote-session.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * Result Type for Error Handling\n *\n * A lightweight Result type for explicit error handling without exceptions.\n * Provides type-safe error propagation and composition.\n */\n\n/**\n * Represents either a successful value or an error\n */\nexport type Result<T, E = Error> =\n | { readonly ok: true; readonly value: T }\n | { readonly ok: false; readonly error: E };\n\n/**\n * Create a successful Result\n */\nexport function Ok<T>(value: T): Result<T, never> {\n return { ok: true, value };\n}\n\n/**\n * Create a failed Result\n */\nexport function Err<E>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\n/**\n * Check if a Result is Ok\n */\nexport function isOk<T, E>(result: Result<T, E>): result is { ok: true; value: T } {\n return result.ok;\n}\n\n/**\n * Check if a Result is Err\n */\nexport function isErr<T, E>(result: Result<T, E>): result is { ok: false; error: E } {\n return !result.ok;\n}\n\n/**\n * Wrap a Promise in a Result, catching any thrown errors\n */\nexport async function fromPromise<T, E = Error>(\n promise: Promise<T>,\n mapError?: (e: unknown) => E\n): Promise<Result<T, E>> {\n try {\n const value = await promise;\n return Ok(value);\n } catch (e) {\n if (mapError) {\n return Err(mapError(e));\n }\n return Err(e as E);\n }\n}\n\n/**\n * Wrap a synchronous function in a Result, catching any thrown errors\n */\nexport function fromTry<T, E = Error>(\n fn: () => T,\n mapError?: (e: unknown) => E\n): Result<T, E> {\n try {\n return Ok(fn());\n } catch (e) {\n if (mapError) {\n return Err(mapError(e));\n }\n return Err(e as E);\n }\n}\n\n/**\n * Map over a successful Result value\n */\nexport function map<T, U, E>(\n result: Result<T, E>,\n fn: (value: T) => U\n): Result<U, E> {\n if (result.ok) {\n return Ok(fn(result.value));\n }\n return result;\n}\n\n/**\n * Map over a failed Result error\n */\nexport function mapErr<T, E, F>(\n result: Result<T, E>,\n fn: (error: E) => F\n): Result<T, F> {\n if (!result.ok) {\n return Err(fn(result.error));\n }\n return result;\n}\n\n/**\n * Chain Results together (flatMap)\n */\nexport function flatMap<T, U, E>(\n result: Result<T, E>,\n fn: (value: T) => Result<U, E>\n): Result<U, E> {\n if (result.ok) {\n return fn(result.value);\n }\n return result;\n}\n\n/**\n * Unwrap a Result, throwing if it's an error\n */\nexport function unwrap<T, E>(result: Result<T, E>): T {\n if (result.ok) {\n return result.value;\n }\n throw result.error;\n}\n\n/**\n * Unwrap a Result with a default value\n */\nexport function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {\n if (result.ok) {\n return result.value;\n }\n return defaultValue;\n}\n\n/**\n * Unwrap a Result with a lazy default value\n */\nexport function unwrapOrElse<T, E>(result: Result<T, E>, fn: (error: E) => T): T {\n if (result.ok) {\n return result.value;\n }\n return fn(result.error);\n}\n\n/**\n * Match on a Result, providing handlers for both cases\n */\nexport function match<T, E, U>(\n result: Result<T, E>,\n handlers: {\n ok: (value: T) => U;\n err: (error: E) => U;\n }\n): U {\n if (result.ok) {\n return handlers.ok(result.value);\n }\n return handlers.err(result.error);\n}\n\n/**\n * Combine multiple Results into a single Result containing an array\n * Returns the first error encountered, or Ok with all values\n */\nexport function all<T, E>(results: Result<T, E>[]): Result<T[], E> {\n const values: T[] = [];\n for (const result of results) {\n if (!result.ok) {\n return result;\n }\n values.push(result.value);\n }\n return Ok(values);\n}\n\n/**\n * Base class for typed errors with context\n */\nexport class HypenError extends Error {\n readonly code: string;\n readonly context?: Record<string, unknown>;\n override readonly cause?: Error;\n\n constructor(\n code: string,\n message: string,\n options?: { context?: Record<string, unknown>; cause?: Error }\n ) {\n super(message);\n this.name = 'HypenError';\n this.code = code;\n this.context = options?.context;\n this.cause = options?.cause;\n\n // Maintain proper prototype chain\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Error thrown when an action handler fails\n */\nexport class ActionError extends HypenError {\n readonly actionName: string;\n\n constructor(actionName: string, cause?: unknown) {\n super(\n 'ACTION_ERROR',\n `Action handler \"${actionName}\" failed: ${cause instanceof Error ? cause.message : String(cause)}`,\n {\n context: { actionName },\n cause: cause instanceof Error ? cause : undefined,\n }\n );\n this.name = 'ActionError';\n this.actionName = actionName;\n }\n}\n\n/**\n * Error thrown when a connection fails\n */\nexport class ConnectionError extends HypenError {\n readonly url: string;\n readonly attempt?: number;\n\n constructor(url: string, cause?: unknown, attempt?: number) {\n super(\n 'CONNECTION_ERROR',\n `Connection to \"${url}\" failed${attempt ? ` (attempt ${attempt})` : ''}: ${\n cause instanceof Error ? cause.message : String(cause)\n }`,\n {\n context: { url, attempt },\n cause: cause instanceof Error ? cause : undefined,\n }\n );\n this.name = 'ConnectionError';\n this.url = url;\n this.attempt = attempt;\n }\n}\n\n/**\n * Error thrown when state operations fail\n */\nexport class StateError extends HypenError {\n readonly path?: string;\n\n constructor(message: string, path?: string, cause?: unknown) {\n super('STATE_ERROR', message, {\n context: { path },\n cause: cause instanceof Error ? cause : undefined,\n });\n this.name = 'StateError';\n this.path = path;\n }\n}\n\n/**\n * Error thrown when parsing Hypen DSL source fails\n */\nexport class ParseError extends HypenError {\n readonly source?: string;\n\n constructor(message: string, source?: string, cause?: unknown) {\n super('PARSE_ERROR', message, {\n context: { source },\n cause: cause instanceof Error ? cause : undefined,\n });\n this.name = 'ParseError';\n this.source = source;\n }\n}\n\n/**\n * Error thrown when rendering fails\n */\nexport class RenderError extends HypenError {\n constructor(message: string, cause?: unknown) {\n super('RENDER_ERROR', message, {\n cause: cause instanceof Error ? cause : undefined,\n });\n this.name = 'RenderError';\n }\n}\n\n/**\n * Classify a WASM engine error string into the appropriate HypenError subclass.\n * The engine returns JsValue error strings with known prefixes.\n */\nexport function classifyEngineError(err: unknown): HypenError {\n // Handle structured WASM errors: { type: string, message: string }\n if (err && typeof err === 'object' && 'message' in err && 'type' in err) {\n const structured = err as { type: string; message: string };\n switch (structured.type) {\n case 'parseError': return new ParseError(structured.message);\n case 'stateError': return new StateError(structured.message);\n case 'renderError': return new RenderError(structured.message);\n case 'actionError': return new HypenError('ACTION_ERROR', structured.message);\n default: return new HypenError('UNKNOWN_ERROR', structured.message);\n }\n }\n const message = err instanceof Error ? err.message : String(err);\n\n if (message.startsWith('Parse error:')) {\n return new ParseError(message);\n }\n if (message.startsWith('Invalid state:') || message.startsWith('Invalid state patch:')) {\n return new StateError(message);\n }\n if (message.startsWith('Parent node not found:')) {\n return new RenderError(message);\n }\n\n return new RenderError(message);\n}\n",
|
|
6
|
-
"/**\n * Configurable Debug Logger\n *\n * Provides environment-aware logging that can be disabled in production.\n * Supports log levels, tagged output, and performance timing.\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\" | \"none\";\n\nexport interface LoggerConfig {\n /** Minimum log level (default: \"debug\" in dev, \"error\" in prod) */\n level: LogLevel;\n /** Enable colored output (default: true) */\n colors: boolean;\n /** Include timestamps (default: false) */\n timestamps: boolean;\n /** Custom log handler (default: console) */\n handler?: LogHandler;\n}\n\nexport interface LogHandler {\n debug(tag: string, ...args: unknown[]): void;\n info(tag: string, ...args: unknown[]): void;\n warn(tag: string, ...args: unknown[]): void;\n error(tag: string, ...args: unknown[]): void;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst LOG_LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n none: 4,\n};\n\nconst LOG_LEVEL_COLORS: Record<Exclude<LogLevel, \"none\">, string> = {\n debug: \"\\x1b[36m\", // Cyan\n info: \"\\x1b[32m\", // Green\n warn: \"\\x1b[33m\", // Yellow\n error: \"\\x1b[31m\", // Red\n};\n\nconst RESET_COLOR = \"\\x1b[0m\";\n\n// ============================================================================\n// Global Configuration\n// ============================================================================\n\n/**\n * Detect if running in production environment\n */\nfunction isProduction(): boolean {\n if (typeof process !== \"undefined\" && process.env) {\n return process.env.NODE_ENV === \"production\";\n }\n return false;\n}\n\n/**\n * Default configuration\n */\nlet config: LoggerConfig = {\n level: isProduction() ? \"error\" : \"info\",\n colors: true,\n timestamps: false,\n};\n\n// ============================================================================\n// Configuration API\n// ============================================================================\n\n/**\n * Set the global log level\n */\nexport function setLogLevel(level: LogLevel): void {\n config.level = level;\n}\n\n/**\n * Get the current log level\n */\nexport function getLogLevel(): LogLevel {\n return config.level;\n}\n\n/**\n * Configure the logger\n */\nexport function configureLogger(options: Partial<LoggerConfig>): void {\n config = { ...config, ...options };\n}\n\n/**\n * Enable all logging (sets level to \"debug\")\n */\nexport function enableLogging(): void {\n config.level = \"debug\";\n}\n\n/**\n * Disable all logging (sets level to \"none\")\n */\nexport function disableLogging(): void {\n config.level = \"none\";\n}\n\n/**\n * Enable debug mode - alias for enableLogging()\n * Call this at app startup to see all debug logs\n *\n * @example\n * ```typescript\n * import { setDebugMode } from \"@hypen-space/core\";\n * setDebugMode(true);\n * ```\n */\nexport function setDebugMode(enabled: boolean): void {\n config.level = enabled ? \"debug\" : \"error\";\n}\n\n/**\n * Check if debug mode is enabled\n */\nexport function isDebugMode(): boolean {\n return config.level === \"debug\";\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\nfunction shouldLog(level: LogLevel): boolean {\n return LOG_LEVEL_ORDER[level] >= LOG_LEVEL_ORDER[config.level];\n}\n\nfunction formatTag(tag: string, level: LogLevel): string {\n const timestamp = config.timestamps ? `${new Date().toISOString()} ` : \"\";\n\n if (config.colors && level !== \"none\") {\n const color = LOG_LEVEL_COLORS[level as Exclude<LogLevel, \"none\">];\n return `${timestamp}${color}[${tag}]${RESET_COLOR}`;\n }\n\n return `${timestamp}[${tag}]`;\n}\n\n// ============================================================================\n// Logger Class\n// ============================================================================\n\n/**\n * Tagged logger instance\n *\n * @example\n * ```typescript\n * const log = createLogger(\"MyComponent\");\n * log.debug(\"initialized with\", { props });\n * log.error(\"failed to load\", error);\n * ```\n */\nexport class Logger {\n private readonly tag: string;\n\n constructor(tag: string) {\n this.tag = tag;\n }\n\n debug(...args: unknown[]): void {\n if (!shouldLog(\"debug\")) return;\n\n if (config.handler) {\n config.handler.debug(this.tag, ...args);\n } else {\n console.log(formatTag(this.tag, \"debug\"), ...args);\n }\n }\n\n info(...args: unknown[]): void {\n if (!shouldLog(\"info\")) return;\n\n if (config.handler) {\n config.handler.info(this.tag, ...args);\n } else {\n console.info(formatTag(this.tag, \"info\"), ...args);\n }\n }\n\n warn(...args: unknown[]): void {\n if (!shouldLog(\"warn\")) return;\n\n if (config.handler) {\n config.handler.warn(this.tag, ...args);\n } else {\n console.warn(formatTag(this.tag, \"warn\"), ...args);\n }\n }\n\n error(...args: unknown[]): void {\n if (!shouldLog(\"error\")) return;\n\n if (config.handler) {\n config.handler.error(this.tag, ...args);\n } else {\n console.error(formatTag(this.tag, \"error\"), ...args);\n }\n }\n\n /**\n * Time a function execution\n */\n time<T>(label: string, fn: () => T): T {\n if (!shouldLog(\"debug\")) {\n return fn();\n }\n\n const start = performance.now();\n try {\n return fn();\n } finally {\n const duration = performance.now() - start;\n this.debug(`${label}: ${duration.toFixed(2)}ms`);\n }\n }\n\n /**\n * Time an async function execution\n */\n async timeAsync<T>(label: string, fn: () => Promise<T>): Promise<T> {\n if (!shouldLog(\"debug\")) {\n return fn();\n }\n\n const start = performance.now();\n try {\n return await fn();\n } finally {\n const duration = performance.now() - start;\n this.debug(`${label}: ${duration.toFixed(2)}ms`);\n }\n }\n\n /**\n * Create a child logger with additional context\n */\n child(subTag: string): Logger {\n return new Logger(`${this.tag}:${subTag}`);\n }\n\n /**\n * Conditionally log based on a condition\n */\n debugIf(condition: boolean, ...args: unknown[]): void {\n if (condition) this.debug(...args);\n }\n\n warnIf(condition: boolean, ...args: unknown[]): void {\n if (condition) this.warn(...args);\n }\n\n errorIf(condition: boolean, ...args: unknown[]): void {\n if (condition) this.error(...args);\n }\n\n /**\n * Log once (useful for deprecation warnings)\n */\n private loggedOnce = new Set<string>();\n\n warnOnce(key: string, ...args: unknown[]): void {\n if (this.loggedOnce.has(key)) return;\n this.loggedOnce.add(key);\n this.warn(...args);\n }\n\n debugOnce(key: string, ...args: unknown[]): void {\n if (this.loggedOnce.has(key)) return;\n this.loggedOnce.add(key);\n this.debug(...args);\n }\n}\n\n// ============================================================================\n// Factory Functions\n// ============================================================================\n\n/**\n * Create a tagged logger\n *\n * @example\n * ```typescript\n * const log = createLogger(\"Router\");\n * log.info(\"navigating to\", path);\n * ```\n */\nexport function createLogger(tag: string): Logger {\n return new Logger(tag);\n}\n\n// ============================================================================\n// Default Logger Instance\n// ============================================================================\n\n/**\n * Default logger for general use\n */\nexport const logger = createLogger(\"Hypen\");\n\n// ============================================================================\n// Shorthand Functions (untagged)\n// ============================================================================\n\n/**\n * Shorthand logging functions for quick use\n * Prefer createLogger() for component-specific logging\n */\nexport const log = {\n debug: (tag: string, ...args: unknown[]): void => {\n if (!shouldLog(\"debug\")) return;\n console.log(formatTag(tag, \"debug\"), ...args);\n },\n\n info: (tag: string, ...args: unknown[]): void => {\n if (!shouldLog(\"info\")) return;\n console.info(formatTag(tag, \"info\"), ...args);\n },\n\n warn: (tag: string, ...args: unknown[]): void => {\n if (!shouldLog(\"warn\")) return;\n console.warn(formatTag(tag, \"warn\"), ...args);\n },\n\n error: (tag: string, ...args: unknown[]): void => {\n if (!shouldLog(\"error\")) return;\n console.error(formatTag(tag, \"error\"), ...args);\n },\n};\n\n// ============================================================================\n// Predefined Loggers for Framework Components\n// ============================================================================\n\nexport const frameworkLoggers = {\n hypen: createLogger(\"Hypen\"),\n engine: createLogger(\"Engine\"),\n router: createLogger(\"Router\"),\n state: createLogger(\"State\"),\n events: createLogger(\"Events\"),\n remote: createLogger(\"Remote\"),\n renderer: createLogger(\"Renderer\"),\n module: createLogger(\"Module\"),\n lifecycle: createLogger(\"Lifecycle\"),\n loader: createLogger(\"Loader\"),\n context: createLogger(\"Context\"),\n discovery: createLogger(\"Discovery\"),\n plugin: createLogger(\"Plugin\"),\n canvas: createLogger(\"Canvas\"),\n debug: createLogger(\"Debug\"),\n};\n",
|
|
7
|
-
"/**\n * Disposable Pattern for Resource Management\n *\n * Provides a consistent way to manage and clean up resources like\n * event listeners, timers, WebSocket connections, etc.\n */\n\nimport { frameworkLoggers } from \"./logger.js\";\n\nconst log = frameworkLoggers.lifecycle;\n\n/**\n * Interface for objects that can be disposed\n */\nexport interface Disposable {\n dispose(): void;\n}\n\n/**\n * Check if an object is Disposable\n */\nexport function isDisposable(obj: unknown): obj is Disposable {\n return (\n obj !== null &&\n typeof obj === 'object' &&\n 'dispose' in obj &&\n typeof (obj as Disposable).dispose === 'function'\n );\n}\n\n/**\n * A stack of disposables that are disposed in LIFO order\n */\nexport class DisposableStack implements Disposable {\n private stack: Disposable[] = [];\n private disposed = false;\n\n /**\n * Add a disposable to the stack and return it\n */\n add<T extends Disposable>(disposable: T): T {\n if (this.disposed) {\n // If already disposed, immediately dispose the new item\n disposable.dispose();\n return disposable;\n }\n this.stack.push(disposable);\n return disposable;\n }\n\n /**\n * Add a cleanup callback to the stack\n */\n addCallback(callback: () => void): void {\n this.add({ dispose: callback });\n }\n\n /**\n * Add a value with a custom dispose function\n */\n addValue<T>(value: T, dispose: (value: T) => void): T {\n this.add({ dispose: () => dispose(value) });\n return value;\n }\n\n /**\n * Dispose all items in reverse order (LIFO)\n */\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n\n while (this.stack.length > 0) {\n const item = this.stack.pop()!;\n try {\n item.dispose();\n } catch (error) {\n // Log but continue disposing other items\n log.error('Error during dispose:', error);\n }\n }\n }\n\n /**\n * Check if this stack has been disposed\n */\n get isDisposed(): boolean {\n return this.disposed;\n }\n\n /**\n * Get the number of items in the stack\n */\n get size(): number {\n return this.stack.length;\n }\n}\n\n/**\n * Create a disposable from an event listener\n */\nexport function disposableListener(\n target: EventTarget,\n event: string,\n handler: EventListenerOrEventListenerObject,\n options?: AddEventListenerOptions\n): Disposable {\n target.addEventListener(event, handler, options);\n return {\n dispose: () => target.removeEventListener(event, handler, options),\n };\n}\n\n/**\n * Create a disposable from a timeout\n */\nexport function disposableTimeout(\n callback: () => void,\n ms: number\n): Disposable & { id: ReturnType<typeof setTimeout> } {\n const id = setTimeout(callback, ms);\n return {\n id,\n dispose: () => clearTimeout(id),\n };\n}\n\n/**\n * Create a disposable from an interval\n */\nexport function disposableInterval(\n callback: () => void,\n ms: number\n): Disposable & { id: ReturnType<typeof setInterval> } {\n const id = setInterval(callback, ms);\n return {\n id,\n dispose: () => clearInterval(id),\n };\n}\n\n/**\n * Create a disposable from a WebSocket\n */\nexport function disposableWebSocket(ws: WebSocket): Disposable {\n return {\n dispose: () => {\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\n ws.close();\n }\n },\n };\n}\n\n/**\n * Create a disposable from an AbortController\n */\nexport function disposableAbortController(): Disposable & { controller: AbortController; signal: AbortSignal } {\n const controller = new AbortController();\n return {\n controller,\n signal: controller.signal,\n dispose: () => controller.abort(),\n };\n}\n\n/**\n * Create a disposable subscription (for event emitters, observables, etc.)\n */\nexport function disposableSubscription(unsubscribe: () => void): Disposable {\n return { dispose: unsubscribe };\n}\n\n/**\n * Symbol used to store disposables on DOM elements\n */\nconst ELEMENT_DISPOSABLES = Symbol('hypen.disposables');\n\n/**\n * Get or create a DisposableStack for an HTML element\n */\nexport function getElementDisposables(element: HTMLElement): DisposableStack {\n const existing = (element as any)[ELEMENT_DISPOSABLES];\n if (existing instanceof DisposableStack) {\n return existing;\n }\n const stack = new DisposableStack();\n (element as any)[ELEMENT_DISPOSABLES] = stack;\n return stack;\n}\n\n/**\n * Dispose all disposables attached to an element\n */\nexport function disposeElement(element: HTMLElement): void {\n const stack = (element as any)[ELEMENT_DISPOSABLES];\n if (stack instanceof DisposableStack) {\n stack.dispose();\n delete (element as any)[ELEMENT_DISPOSABLES];\n }\n}\n\n/**\n * Check if an element has disposables\n */\nexport function hasElementDisposables(element: HTMLElement): boolean {\n return (element as any)[ELEMENT_DISPOSABLES] instanceof DisposableStack;\n}\n\n/**\n * Decorator/helper to make a class disposable\n * Tracks all resources and disposes them when dispose() is called\n */\nexport class DisposableMixin {\n protected disposables = new DisposableStack();\n\n /**\n * Register a disposable to be cleaned up\n */\n protected track<T extends Disposable>(disposable: T): T {\n return this.disposables.add(disposable);\n }\n\n /**\n * Register a cleanup callback\n */\n protected onDispose(callback: () => void): void {\n this.disposables.addCallback(callback);\n }\n\n /**\n * Dispose all tracked resources\n */\n dispose(): void {\n this.disposables.dispose();\n }\n}\n\n/**\n * Create a composite disposable that disposes multiple items together\n */\nexport function compositeDisposable(...disposables: Disposable[]): Disposable {\n return {\n dispose: () => {\n for (const d of disposables) {\n try {\n d.dispose();\n } catch (error) {\n log.error('Error during dispose:', error);\n }\n }\n },\n };\n}\n\n/**\n * Run a function with automatic cleanup on exit\n * Similar to Python's context managers or C#'s using statement\n */\nexport async function using<T extends Disposable, R>(\n resource: T | (() => T),\n fn: (resource: T) => R | Promise<R>\n): Promise<R> {\n const r = typeof resource === 'function' ? resource() : resource;\n try {\n return await fn(r);\n } finally {\n r.dispose();\n }\n}\n\n/**\n * Synchronous version of using()\n */\nexport function usingSync<T extends Disposable, R>(\n resource: T | (() => T),\n fn: (resource: T) => R\n): R {\n const r = typeof resource === 'function' ? resource() : resource;\n try {\n return fn(r);\n } finally {\n r.dispose();\n }\n}\n",
|
|
8
|
-
"/**\n * Retry Utility for Network Operations\n *\n * Provides configurable retry logic with exponential/linear backoff\n * for handling transient failures in network operations.\n */\n\nimport { type Result, Ok, Err } from \"./result.js\";\n\n/**\n * Options for retry behavior\n */\nexport interface RetryOptions {\n /** Maximum number of attempts (default: 3) */\n maxAttempts?: number;\n /** Initial delay in milliseconds (default: 1000) */\n delayMs?: number;\n /** Backoff strategy (default: 'exponential') */\n backoff?: \"linear\" | \"exponential\" | \"none\";\n /** Maximum delay cap in milliseconds (default: 30000) */\n maxDelayMs?: number;\n /** Jitter factor 0-1 to randomize delays (default: 0.1) */\n jitter?: number;\n /** Callback on each retry attempt */\n onRetry?: (attempt: number, error: Error, nextDelayMs: number) => void;\n /** Optional predicate to determine if error is retryable */\n shouldRetry?: (error: Error) => boolean;\n /** AbortSignal for cancellation */\n signal?: AbortSignal;\n}\n\n/**\n * Default retry options\n */\nconst DEFAULT_OPTIONS: Required<Omit<RetryOptions, \"onRetry\" | \"shouldRetry\" | \"signal\">> = {\n maxAttempts: 3,\n delayMs: 1000,\n backoff: \"exponential\",\n maxDelayMs: 30000,\n jitter: 0.1,\n};\n\n/**\n * Calculate delay for a given attempt\n */\nfunction calculateDelay(\n attempt: number,\n options: Required<Omit<RetryOptions, \"onRetry\" | \"shouldRetry\" | \"signal\">>\n): number {\n let delay: number;\n\n switch (options.backoff) {\n case \"exponential\":\n // 2^(attempt-1) * delayMs: 1x, 2x, 4x, 8x...\n delay = options.delayMs * Math.pow(2, attempt - 1);\n break;\n case \"linear\":\n // attempt * delayMs: 1x, 2x, 3x, 4x...\n delay = options.delayMs * attempt;\n break;\n case \"none\":\n delay = options.delayMs;\n break;\n }\n\n // Apply jitter (randomize ±jitter%)\n if (options.jitter > 0) {\n const jitterRange = delay * options.jitter;\n delay += (Math.random() * 2 - 1) * jitterRange;\n }\n\n // Cap at maxDelayMs\n return Math.min(delay, options.maxDelayMs);\n}\n\n/**\n * Sleep for a given duration, respecting abort signal\n */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new Error(\"Retry aborted\"));\n return;\n }\n\n const timeoutId = setTimeout(resolve, ms);\n\n signal?.addEventListener(\"abort\", () => {\n clearTimeout(timeoutId);\n reject(new Error(\"Retry aborted\"));\n });\n });\n}\n\n/**\n * Retry a function with configurable backoff\n *\n * @example\n * ```typescript\n * // Basic usage\n * const result = await retry(() => fetch('/api/data'));\n *\n * // With options\n * const result = await retry(\n * () => fetch('/api/data'),\n * {\n * maxAttempts: 5,\n * delayMs: 2000,\n * backoff: 'exponential',\n * onRetry: (n, err) => console.log(`Attempt ${n} failed: ${err.message}`)\n * }\n * );\n * ```\n */\nexport async function retry<T>(\n fn: () => T | Promise<T>,\n options: RetryOptions = {}\n): Promise<T> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n let lastError: Error = new Error(\"No attempts made\");\n\n for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {\n try {\n // Check for abort before each attempt\n if (opts.signal?.aborted) {\n throw new Error(\"Retry aborted\");\n }\n\n return await fn();\n } catch (e) {\n lastError = e instanceof Error ? e : new Error(String(e));\n\n // Check if we should retry this error\n if (opts.shouldRetry && !opts.shouldRetry(lastError)) {\n throw lastError;\n }\n\n // If this was the last attempt, don't wait\n if (attempt === opts.maxAttempts) {\n break;\n }\n\n // Calculate delay and notify\n const delayMs = calculateDelay(attempt, opts);\n opts.onRetry?.(attempt, lastError, delayMs);\n\n // Wait before next attempt\n await sleep(delayMs, opts.signal);\n }\n }\n\n throw lastError;\n}\n\n/**\n * Retry a function, returning a Result instead of throwing\n *\n * @example\n * ```typescript\n * const result = await retryResult(() => fetch('/api/data'));\n * if (result.ok) {\n * console.log('Success:', result.value);\n * } else {\n * console.error('All retries failed:', result.error);\n * }\n * ```\n */\nexport async function retryResult<T>(\n fn: () => T | Promise<T>,\n options: RetryOptions = {}\n): Promise<Result<T, Error>> {\n try {\n const value = await retry(fn, options);\n return Ok(value);\n } catch (e) {\n return Err(e instanceof Error ? e : new Error(String(e)));\n }\n}\n\n/**\n * Create a retryable version of a function\n *\n * @example\n * ```typescript\n * const fetchWithRetry = withRetry(\n * (url: string) => fetch(url),\n * { maxAttempts: 3 }\n * );\n *\n * const response = await fetchWithRetry('/api/data');\n * ```\n */\nexport function withRetry<TArgs extends unknown[], TReturn>(\n fn: (...args: TArgs) => TReturn | Promise<TReturn>,\n options: RetryOptions = {}\n): (...args: TArgs) => Promise<TReturn> {\n return (...args: TArgs) => retry(() => fn(...args), options);\n}\n\n/**\n * Predefined retry conditions\n */\nexport const RetryConditions = {\n /**\n * Retry on network errors (fetch failures, timeouts)\n */\n networkErrors: (error: Error): boolean => {\n const message = error.message.toLowerCase();\n return (\n message.includes(\"network\") ||\n message.includes(\"fetch\") ||\n message.includes(\"timeout\") ||\n message.includes(\"econnrefused\") ||\n message.includes(\"econnreset\") ||\n message.includes(\"socket\")\n );\n },\n\n /**\n * Retry on specific HTTP status codes (from fetch Response)\n */\n httpRetryable: (error: Error & { status?: number }): boolean => {\n const status = error.status;\n if (!status) return false;\n // Retry on 408, 429, 500, 502, 503, 504\n return [408, 429, 500, 502, 503, 504].includes(status);\n },\n\n /**\n * Retry on transient WebSocket errors\n */\n websocketErrors: (error: Error): boolean => {\n const message = error.message.toLowerCase();\n return (\n message.includes(\"websocket\") ||\n message.includes(\"connection\") ||\n message.includes(\"close\")\n );\n },\n\n /**\n * Combine multiple conditions (retry if any match)\n */\n any:\n (...conditions: Array<(error: Error) => boolean>) =>\n (error: Error): boolean =>\n conditions.some((c) => c(error)),\n\n /**\n * Combine multiple conditions (retry if all match)\n */\n all:\n (...conditions: Array<(error: Error) => boolean>) =>\n (error: Error): boolean =>\n conditions.every((c) => c(error)),\n};\n\n/**\n * Preset configurations for common use cases\n */\nexport const RetryPresets = {\n /**\n * Aggressive retry for critical operations\n */\n aggressive: {\n maxAttempts: 10,\n delayMs: 500,\n backoff: \"exponential\" as const,\n maxDelayMs: 60000,\n jitter: 0.2,\n },\n\n /**\n * Conservative retry for non-critical operations\n */\n conservative: {\n maxAttempts: 3,\n delayMs: 2000,\n backoff: \"linear\" as const,\n maxDelayMs: 10000,\n jitter: 0.1,\n },\n\n /**\n * Fast retry for local operations (short delays)\n */\n fast: {\n maxAttempts: 5,\n delayMs: 100,\n backoff: \"exponential\" as const,\n maxDelayMs: 2000,\n jitter: 0,\n },\n\n /**\n * WebSocket reconnection preset\n */\n websocket: {\n maxAttempts: 10,\n delayMs: 1000,\n backoff: \"exponential\" as const,\n maxDelayMs: 30000,\n jitter: 0.1,\n shouldRetry: RetryConditions.websocketErrors,\n },\n};\n",
|
|
9
|
-
"/**\n * RemoteEngine - Connect to a remote Hypen app over WebSocket\n * Platform-agnostic client (uses standard WebSocket API)\n *\n * Usage:\n * ```typescript\n * const engine = new RemoteEngine(\"ws://localhost:3000\", {\n * session: {\n * id: localStorage.getItem(\"sessionId\") ?? undefined,\n * props: { platform: \"web\", version: \"1.0\" }\n * }\n * });\n *\n * engine.onSessionEstablished(({ sessionId, isNew, isRestored }) => {\n * localStorage.setItem(\"sessionId\", sessionId);\n * console.log(isRestored ? \"Welcome back!\" : \"New session\");\n * });\n *\n * engine.onSessionExpired((reason) => {\n * localStorage.removeItem(\"sessionId\");\n * });\n *\n * const result = await engine.connect();\n * if (!result.ok) {\n * console.error(\"Connection failed:\", result.error);\n * }\n * ```\n */\n\nimport type {\n RemoteMessage,\n InitialTreeMessage,\n PatchMessage,\n DispatchActionMessage,\n UpdateStateMessage,\n SubscribeStateMessage,\n HelloMessage,\n SessionAckMessage,\n SessionExpiredMessage,\n} from \"./types.js\";\nimport type { Patch } from \"../types.js\";\nimport {\n type Result,\n Ok,\n Err,\n ConnectionError,\n} from \"../result.js\";\nimport {\n type Disposable,\n DisposableStack,\n disposableTimeout,\n disposableWebSocket,\n disposableListener,\n} from \"../disposable.js\";\nimport { retry, type RetryOptions } from \"../retry.js\";\nimport { frameworkLoggers } from \"../logger.js\";\n\nconst log = frameworkLoggers.remote;\n\nexport type RemoteConnectionState =\n | \"disconnected\"\n | \"connecting\"\n | \"connected\"\n | \"error\";\n\n/**\n * Session configuration for the client\n */\nexport interface SessionOptions {\n /** Session ID to resume (omit for new session) */\n id?: string;\n /** Client metadata (platform, version, userId, etc.) */\n props?: Record<string, unknown>;\n /** Optional persist/routing key for Durable Object routing (withKey) */\n persistKey?: string;\n}\n\n/**\n * Session information received from server\n */\nexport interface SessionInfo {\n sessionId: string;\n isNew: boolean;\n isRestored: boolean;\n}\n\n/**\n * Navigation options for platform back-button integration.\n *\n * When enabled, the client listens for platform back events (browser popstate,\n * Android back button) and dispatches a configurable action to the server.\n * The server module handles navigation via its own route stack.\n */\nexport interface NavigationOptions {\n /** Action name dispatched on back navigation (default: \"navigateBack\") */\n backAction?: string;\n /**\n * State key that represents the current view/route on the server.\n * When this key changes in a stateUpdate, the client pushes a browser\n * history entry so the back button works. (default: \"currentView\")\n */\n viewStateKey?: string;\n}\n\nexport interface RemoteEngineOptions {\n autoReconnect?: boolean;\n reconnectInterval?: number;\n maxReconnectAttempts?: number;\n /** Session configuration */\n session?: SessionOptions;\n /**\n * Enable platform back-button integration.\n * Pass `true` for defaults, or an object to customize action/key names.\n */\n navigation?: boolean | NavigationOptions;\n}\n\ninterface ResolvedNavigationOptions {\n backAction: string;\n viewStateKey: string;\n}\n\ninterface RequiredOptions {\n autoReconnect: boolean;\n reconnectInterval: number;\n maxReconnectAttempts: number;\n session?: SessionOptions;\n navigation?: ResolvedNavigationOptions;\n}\n\n/**\n * Client-side engine that connects to a remote Hypen app\n */\nexport class RemoteEngine implements Disposable {\n private ws: WebSocket | null = null;\n private readonly url: string;\n private state: RemoteConnectionState = \"disconnected\";\n private readonly options: RequiredOptions;\n private reconnectAttempts = 0;\n\n // Resource management\n private readonly disposables = new DisposableStack();\n private reconnectDisposable: Disposable | null = null;\n\n // Session state\n private currentSessionId: string | null = null;\n private readonly sessionOptions?: SessionOptions;\n\n // Callbacks\n private readonly patchCallbacks: Array<(patches: Patch[]) => void> = [];\n private readonly stateCallbacks: Array<(state: unknown) => void> = [];\n private readonly connectionCallbacks: Array<() => void> = [];\n private readonly disconnectionCallbacks: Array<() => void> = [];\n private readonly errorCallbacks: Array<(error: Error) => void> = [];\n private readonly sessionEstablishedCallbacks: Array<(info: SessionInfo) => void> = [];\n private readonly sessionExpiredCallbacks: Array<(reason: string) => void> = [];\n\n // State\n private currentState: unknown = null;\n private currentRevision = 0;\n private moduleName: string = \"\";\n\n // Navigation\n private navigationDisposable: Disposable | null = null;\n private lastViewValue: string | null = null;\n private handlingPopState = false;\n\n constructor(url: string, options: RemoteEngineOptions = {}) {\n this.url = url;\n const navOpt = options.navigation;\n const navigation = navOpt\n ? {\n backAction: (typeof navOpt === \"object\" ? navOpt.backAction : undefined) ?? \"navigateBack\",\n viewStateKey: (typeof navOpt === \"object\" ? navOpt.viewStateKey : undefined) ?? \"currentView\",\n }\n : undefined;\n\n this.options = {\n autoReconnect: options.autoReconnect ?? true,\n reconnectInterval: options.reconnectInterval ?? 3000,\n maxReconnectAttempts: options.maxReconnectAttempts ?? 10,\n session: options.session,\n navigation,\n };\n this.sessionOptions = options.session;\n\n // If session ID was provided, use it as current\n if (options.session?.id) {\n this.currentSessionId = options.session.id;\n }\n }\n\n /**\n * Connect to the remote server\n * Returns a Result indicating success or failure\n */\n async connect(): Promise<Result<void, ConnectionError>> {\n if (this.state === \"connected\" || this.state === \"connecting\") {\n return Ok(undefined);\n }\n\n this.state = \"connecting\";\n\n return new Promise((resolve) => {\n try {\n this.ws = new WebSocket(this.url);\n\n // Track the WebSocket for cleanup\n this.disposables.add(disposableWebSocket(this.ws));\n\n // Set up message handler\n const messageHandler = (event: MessageEvent) => {\n this.handleMessage(event.data);\n };\n this.disposables.add(\n disposableListener(this.ws, \"message\", messageHandler as EventListener)\n );\n\n // Set up error handler\n const errorHandler = () => {\n this.state = \"error\";\n const error = new ConnectionError(this.url, new Error(\"WebSocket error\"));\n this.errorCallbacks.forEach((cb) => cb(error));\n resolve(Err(error));\n };\n this.disposables.add(\n disposableListener(this.ws, \"error\", errorHandler)\n );\n\n // Set up close handler\n const closeHandler = () => {\n this.state = \"disconnected\";\n this.disconnectionCallbacks.forEach((cb) => cb());\n this.attemptReconnect();\n };\n this.disposables.add(\n disposableListener(this.ws, \"close\", closeHandler)\n );\n\n // Set up open handler\n this.ws.onopen = () => {\n this.state = \"connected\";\n this.reconnectAttempts = 0;\n\n // Cancel any pending reconnect\n if (this.reconnectDisposable) {\n this.reconnectDisposable.dispose();\n this.reconnectDisposable = null;\n }\n\n // Send hello message with session info\n this.sendHello();\n\n this.connectionCallbacks.forEach((cb) => cb());\n resolve(Ok(undefined));\n };\n } catch (e) {\n this.state = \"error\";\n const error = new ConnectionError(this.url, e);\n resolve(Err(error));\n }\n });\n }\n\n /**\n * Send hello message to establish session\n */\n private sendHello(): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n\n const hello: HelloMessage = {\n type: \"hello\",\n sessionId: this.currentSessionId ?? this.sessionOptions?.id,\n props: this.sessionOptions?.props,\n persistKey: this.sessionOptions?.persistKey,\n };\n\n this.ws.send(JSON.stringify(hello));\n }\n\n /**\n * Disconnect from the remote server and clean up resources\n */\n disconnect(): void {\n // Cancel any pending reconnect\n if (this.reconnectDisposable) {\n this.reconnectDisposable.dispose();\n this.reconnectDisposable = null;\n }\n\n // Close WebSocket if open\n if (this.ws) {\n if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {\n this.ws.close();\n }\n this.ws = null;\n }\n\n this.state = \"disconnected\";\n }\n\n /**\n * Dispose all resources (alias for disconnect)\n */\n dispose(): void {\n this.disconnect();\n if (this.navigationDisposable) {\n this.navigationDisposable.dispose();\n this.navigationDisposable = null;\n }\n this.disposables.dispose();\n }\n\n // ── Navigation ────────────────────────────────────────────────\n\n /**\n * Set up the browser popstate listener for back-button integration.\n * Called automatically after the initial tree is received.\n */\n private setupNavigation(): void {\n const nav = this.options.navigation;\n if (!nav || typeof window === \"undefined\") return;\n if (this.navigationDisposable) return; // already set up\n\n this.navigationDisposable = disposableListener(window, \"popstate\", () => {\n if (this.handlingPopState) return;\n\n // Browser went back — dispatch the back action to the server\n log.debug(\"Back navigation detected, dispatching:\", nav.backAction);\n this.handlingPopState = true;\n this.dispatchAction(nav.backAction);\n // Flag cleared when we receive the next state update\n });\n }\n\n /**\n * Track view changes from the server and push browser history entries.\n * This keeps the browser history stack in sync with server-side navigation\n * so that the back button has entries to pop.\n */\n private trackViewChange(state: unknown): void {\n const nav = this.options.navigation;\n if (!nav || typeof window === \"undefined\") return;\n\n const viewValue = this.resolveStateKey(state, nav.viewStateKey);\n if (viewValue === undefined || viewValue === this.lastViewValue) return;\n\n if (this.handlingPopState) {\n // This state update was caused by our own back action — don't push\n this.handlingPopState = false;\n this.lastViewValue = viewValue;\n return;\n }\n\n // Only push if we had a previous view (skip the initial load)\n if (this.lastViewValue !== null) {\n window.history.pushState({ view: viewValue }, \"\", \"\");\n }\n this.lastViewValue = viewValue;\n }\n\n /**\n * Resolve a dot-path key from state (e.g., \"currentView\" or \"ui.page\").\n */\n private resolveStateKey(state: unknown, key: string): string | undefined {\n if (!state || typeof state !== \"object\") return undefined;\n const parts = key.split(\".\");\n let current: any = state;\n for (const part of parts) {\n if (current == null || typeof current !== \"object\") return undefined;\n current = current[part];\n }\n return typeof current === \"string\" ? current : undefined;\n }\n\n /**\n * Dispatch an action to the remote server\n */\n dispatchAction(action: string, payload?: unknown): void {\n if (this.state !== \"connected\" || !this.ws) {\n log.warn(\"Cannot dispatch action: not connected\");\n return;\n }\n\n const message: DispatchActionMessage = {\n type: \"dispatchAction\",\n module: this.moduleName,\n action,\n payload,\n };\n\n this.ws.send(JSON.stringify(message));\n }\n\n /**\n * Subscribe to state updates after each render.\n * Only subscribed clients receive stateUpdate messages (avoids overhead for native clients).\n */\n subscribeState(): void {\n if (this.state !== \"connected\" || !this.ws) {\n log.warn(\"Cannot subscribe to state: not connected\");\n return;\n }\n\n const message: SubscribeStateMessage = { type: \"subscribeState\" };\n this.ws.send(JSON.stringify(message));\n }\n\n /**\n * Override the remote module's state (e.g. for time-travel).\n * The server will update state, re-render, and send back patches.\n */\n updateState(state: unknown): void {\n if (this.state !== \"connected\" || !this.ws) {\n log.warn(\"Cannot update state: not connected\");\n return;\n }\n\n const message: UpdateStateMessage = {\n type: \"updateState\",\n module: this.moduleName,\n state,\n };\n\n this.ws.send(JSON.stringify(message));\n }\n\n /**\n * Register callback for patches\n */\n onPatches(callback: (patches: Patch[]) => void): this {\n this.patchCallbacks.push(callback);\n return this;\n }\n\n /**\n * Register callback for state updates\n */\n onStateUpdate(callback: (state: unknown) => void): this {\n this.stateCallbacks.push(callback);\n return this;\n }\n\n /**\n * Register callback for connection\n */\n onConnect(callback: () => void): this {\n this.connectionCallbacks.push(callback);\n return this;\n }\n\n /**\n * Register callback for disconnection\n */\n onDisconnect(callback: () => void): this {\n this.disconnectionCallbacks.push(callback);\n return this;\n }\n\n /**\n * Register callback for errors\n */\n onError(callback: (error: Error) => void): this {\n this.errorCallbacks.push(callback);\n return this;\n }\n\n /**\n * Register callback for session establishment\n * Called when server confirms session (new or resumed)\n */\n onSessionEstablished(callback: (info: SessionInfo) => void): this {\n this.sessionEstablishedCallbacks.push(callback);\n return this;\n }\n\n /**\n * Register callback for session expiration\n * Called when session is kicked or expires\n */\n onSessionExpired(callback: (reason: string) => void): this {\n this.sessionExpiredCallbacks.push(callback);\n return this;\n }\n\n /**\n * Get current connection state\n */\n getConnectionState(): RemoteConnectionState {\n return this.state;\n }\n\n /**\n * Get current app state\n */\n getCurrentState(): unknown {\n return this.currentState;\n }\n\n /**\n * Get current revision\n */\n getRevision(): number {\n return this.currentRevision;\n }\n\n /**\n * Get current session ID\n */\n getSessionId(): string | null {\n return this.currentSessionId;\n }\n\n private handleMessage(data: string): void {\n try {\n const message = JSON.parse(data) as RemoteMessage;\n\n switch (message.type) {\n case \"sessionAck\":\n this.handleSessionAck(message as SessionAckMessage);\n break;\n\n case \"sessionExpired\":\n this.handleSessionExpired(message as SessionExpiredMessage);\n break;\n\n case \"initialTree\":\n this.handleInitialTree(message as InitialTreeMessage);\n break;\n\n case \"patch\":\n this.handlePatch(message as PatchMessage);\n break;\n\n case \"stateUpdate\":\n this.currentState = (message as { state: unknown }).state;\n this.trackViewChange(this.currentState);\n this.stateCallbacks.forEach((cb) => cb(this.currentState));\n break;\n }\n } catch (e) {\n log.error(\"Error handling remote message:\", e);\n const error = e instanceof Error ? e : new Error(String(e));\n this.errorCallbacks.forEach((cb) => cb(error));\n }\n }\n\n private handleSessionAck(message: SessionAckMessage): void {\n this.currentSessionId = message.sessionId;\n\n const info: SessionInfo = {\n sessionId: message.sessionId,\n isNew: message.isNew,\n isRestored: message.isRestored,\n };\n\n this.sessionEstablishedCallbacks.forEach((cb) => cb(info));\n }\n\n private handleSessionExpired(message: SessionExpiredMessage): void {\n this.currentSessionId = null;\n this.sessionExpiredCallbacks.forEach((cb) => cb(message.reason));\n }\n\n private handleInitialTree(message: InitialTreeMessage): void {\n this.moduleName = message.module;\n this.currentState = message.state;\n this.currentRevision = message.revision;\n\n // Apply initial patches\n if (message.patches.length > 0) {\n this.patchCallbacks.forEach((cb) => cb(message.patches));\n }\n\n // Initialize navigation tracking from initial state\n this.trackViewChange(message.state);\n this.setupNavigation();\n\n // Auto-subscribe to state when navigation is enabled so we can\n // track view changes and push browser history entries.\n if (this.options.navigation) {\n this.subscribeState();\n }\n\n // Notify state callbacks\n this.stateCallbacks.forEach((cb) => cb(message.state));\n }\n\n private handlePatch(message: PatchMessage): void {\n // Check revision ordering\n if (message.revision <= this.currentRevision) {\n log.warn(`Out of order patch: expected > ${this.currentRevision}, got ${message.revision}`);\n return;\n }\n\n this.currentRevision = message.revision;\n\n // Apply patches\n if (message.patches.length > 0) {\n this.patchCallbacks.forEach((cb) => cb(message.patches));\n }\n }\n\n private attemptReconnect(): void {\n if (!this.options.autoReconnect) {\n return;\n }\n\n // Use disposable timeout to start reconnection after initial delay\n this.reconnectDisposable = disposableTimeout(() => {\n this.reconnectDisposable = null;\n\n retry(\n async () => {\n const result = await this.connect();\n if (!result.ok) {\n throw result.error;\n }\n },\n {\n maxAttempts: this.options.maxReconnectAttempts,\n delayMs: this.options.reconnectInterval,\n backoff: \"exponential\",\n maxDelayMs: 30000,\n jitter: 0.1,\n onRetry: (attempt, error) => {\n log.debug(`Reconnection attempt ${attempt}/${this.options.maxReconnectAttempts} failed: ${error.message}`);\n },\n }\n ).catch((error) => {\n log.error(\"Max reconnection attempts reached:\", error.message);\n this.errorCallbacks.forEach((cb) =>\n cb(new ConnectionError(this.url, error, this.options.maxReconnectAttempts))\n );\n });\n }, this.options.reconnectInterval);\n }\n}\n",
|
|
10
|
-
"/**\n * Session Management for Remote UI\n *\n * Manages session lifecycle including:\n * - Creating new sessions\n * - Suspending sessions on disconnect (pending reconnection)\n * - Resuming sessions on reconnect\n * - Expiring sessions after TTL\n * - Handling concurrent connections\n */\n\nimport type { Session, SessionConfig } from \"./types.js\";\n\n/**\n * Internal representation of a pending (disconnected) session\n */\ninterface PendingSession {\n session: Session;\n savedState: unknown;\n expiryTimer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Callback invoked when a session expires\n */\nexport type SessionExpireCallback = (session: Session) => void | Promise<void>;\n\n/**\n * Manages session lifecycle for remote UI connections\n */\nexport class SessionManager {\n /** Active sessions (currently connected) */\n private activeSessions = new Map<string, Session>();\n\n /** Pending sessions (disconnected, waiting for reconnect within TTL) */\n private pendingSessions = new Map<string, PendingSession>();\n\n /** Maps session ID to connected WebSocket(s) for concurrent handling */\n private sessionConnections = new Map<string, Set<unknown>>();\n\n /** Resolved configuration with defaults */\n private config: Required<SessionConfig>;\n\n constructor(config: SessionConfig = {}) {\n this.config = {\n ttl: config.ttl ?? 3600, // 1 hour default\n concurrent: config.concurrent ?? \"kick-old\",\n generateId: config.generateId ?? (() => crypto.randomUUID()),\n };\n }\n\n /**\n * Get the configured TTL in seconds\n */\n getTtl(): number {\n return this.config.ttl;\n }\n\n /**\n * Get the concurrent connection policy\n */\n getConcurrentPolicy(): \"kick-old\" | \"reject-new\" | \"allow-multiple\" {\n return this.config.concurrent;\n }\n\n /**\n * Create a new session\n */\n createSession(props?: Record<string, any>): Session {\n const now = new Date();\n const session: Session = {\n id: this.config.generateId(),\n ttl: this.config.ttl,\n createdAt: now,\n lastConnectedAt: now,\n props,\n };\n this.activeSessions.set(session.id, session);\n return session;\n }\n\n /**\n * Get an active (connected) session by ID\n */\n getActiveSession(id: string): Session | null {\n return this.activeSessions.get(id) ?? null;\n }\n\n /**\n * Get a pending (disconnected) session by ID\n */\n getPendingSession(id: string): PendingSession | null {\n return this.pendingSessions.get(id) ?? null;\n }\n\n /**\n * Check if a session exists (either active or pending)\n */\n hasSession(id: string): boolean {\n return this.activeSessions.has(id) || this.pendingSessions.has(id);\n }\n\n /**\n * Suspend a session when client disconnects\n * Moves from active to pending with a TTL timer\n *\n * @param sessionId - The session to suspend\n * @param savedState - State snapshot to restore on reconnect\n * @param onExpire - Callback when TTL expires\n */\n suspendSession(\n sessionId: string,\n savedState: unknown,\n onExpire: SessionExpireCallback\n ): void {\n const session = this.activeSessions.get(sessionId);\n if (!session) return;\n\n // Remove from active\n this.activeSessions.delete(sessionId);\n\n // Set up expiry timer\n const expiryTimer = setTimeout(async () => {\n const pending = this.pendingSessions.get(sessionId);\n if (pending) {\n this.pendingSessions.delete(sessionId);\n await onExpire(pending.session);\n }\n }, session.ttl * 1000);\n\n // Add to pending\n this.pendingSessions.set(sessionId, {\n session,\n savedState,\n expiryTimer,\n });\n }\n\n /**\n * Resume a pending session when client reconnects\n *\n * @param sessionId - The session to resume\n * @returns Session and saved state, or null if not found/expired\n */\n resumeSession(\n sessionId: string\n ): { session: Session; savedState: unknown } | null {\n const pending = this.pendingSessions.get(sessionId);\n if (!pending) return null;\n\n // Clear expiry timer\n clearTimeout(pending.expiryTimer);\n this.pendingSessions.delete(sessionId);\n\n // Update last connected time\n pending.session.lastConnectedAt = new Date();\n\n // Move back to active\n this.activeSessions.set(sessionId, pending.session);\n\n return {\n session: pending.session,\n savedState: pending.savedState,\n };\n }\n\n /**\n * Destroy a session completely (both active and pending)\n */\n destroySession(sessionId: string): void {\n this.activeSessions.delete(sessionId);\n\n const pending = this.pendingSessions.get(sessionId);\n if (pending) {\n clearTimeout(pending.expiryTimer);\n this.pendingSessions.delete(sessionId);\n }\n\n this.sessionConnections.delete(sessionId);\n }\n\n /**\n * Track a WebSocket connection for a session\n * Used for concurrent connection handling\n */\n trackConnection(sessionId: string, ws: unknown): void {\n let connections = this.sessionConnections.get(sessionId);\n if (!connections) {\n connections = new Set();\n this.sessionConnections.set(sessionId, connections);\n }\n connections.add(ws);\n }\n\n /**\n * Untrack a WebSocket connection\n */\n untrackConnection(sessionId: string, ws: unknown): void {\n const connections = this.sessionConnections.get(sessionId);\n if (connections) {\n connections.delete(ws);\n if (connections.size === 0) {\n this.sessionConnections.delete(sessionId);\n }\n }\n }\n\n /**\n * Get all connections for a session\n */\n getConnections(sessionId: string): Set<unknown> | undefined {\n return this.sessionConnections.get(sessionId);\n }\n\n /**\n * Get connection count for a session\n */\n getConnectionCount(sessionId: string): number {\n return this.sessionConnections.get(sessionId)?.size ?? 0;\n }\n\n /**\n * Get stats about current sessions\n */\n getStats(): {\n activeSessions: number;\n pendingSessions: number;\n totalConnections: number;\n } {\n let totalConnections = 0;\n for (const connections of this.sessionConnections.values()) {\n totalConnections += connections.size;\n }\n\n return {\n activeSessions: this.activeSessions.size,\n pendingSessions: this.pendingSessions.size,\n totalConnections,\n };\n }\n\n /**\n * Clean up all sessions and timers\n * Call this when shutting down the server\n */\n destroy(): void {\n // Clear all pending session timers\n for (const pending of this.pendingSessions.values()) {\n clearTimeout(pending.expiryTimer);\n }\n\n this.activeSessions.clear();\n this.pendingSessions.clear();\n this.sessionConnections.clear();\n }\n}\n"
|
|
5
|
+
"/**\n * RemoteSession - Transport-agnostic per-client session for Hypen remote apps.\n *\n * One `RemoteSession` owns one logical client: a dedicated engine instance,\n * a module instance, a session id, and the logic that drives the Hypen remote\n * protocol (hello → sessionAck → initialTree → streaming patches).\n *\n * It does NOT know how bytes reach the client. That is the job of the\n * `SessionTransport` interface — a minimal `{ send, close }` contract that\n * can be backed by a Bun `ServerWebSocket`, an SSE `ReadableStream`, an\n * in-memory queue (see `AsyncQueueTransport`), or anything else.\n *\n * Backend devs can plug Hypen into an existing Express/Fastify/ws/uWebSockets\n * setup by:\n *\n * 1. Calling `server.prepare()` once (discovery + session manager init).\n * 2. Calling `server.createSession(transport)` per client.\n * 3. Forwarding incoming JSON messages via `session.receive(msg)`.\n * 4. Calling `session.destroy()` when the transport closes.\n *\n * Or, for a pure async-iterator style:\n *\n * const transport = new AsyncQueueTransport();\n * const session = server.createSession(transport);\n * for await (const out of transport.stream()) { ...forward to client... }\n */\n\nimport type {\n HypenApp,\n HypenModule,\n HypenModuleDefinition,\n} from \"../app.js\";\nimport { HypenModuleInstance, HypenApp as HypenAppClass } from \"../app.js\";\nimport { HypenGlobalContext } from \"../context.js\";\nimport { HypenRouter } from \"../router.js\";\nimport { ManagedRouter } from \"../managed-router.js\";\nimport type {\n DispatchActionMessage,\n HelloMessage,\n InitialTreeMessage,\n PatchMessage,\n RemoteClient,\n RemoteMessage,\n Session,\n SessionAckMessage,\n SessionExpiredMessage,\n StateUpdateMessage,\n UpdateStateMessage,\n} from \"./types.js\";\nimport { SessionManager } from \"./session.js\";\nimport type { Patch } from \"../types.js\";\nimport { frameworkLoggers } from \"../logger.js\";\nimport { BaseEngine } from \"../engine-base.js\";\n\nconst log = frameworkLoggers.remote;\n\n/**\n * Server → client messages emitted by a `RemoteSession`.\n */\nexport type OutgoingMessage =\n | InitialTreeMessage\n | PatchMessage\n | StateUpdateMessage\n | SessionAckMessage\n | SessionExpiredMessage;\n\n/**\n * Minimal contract a `RemoteSession` needs to reach the client. Implement\n * this to plug Hypen into any transport.\n *\n * `send` receives a structured message — serialize however you like\n * (JSON, MessagePack, protobuf). Built-in adapters use `JSON.stringify`.\n */\nexport interface SessionTransport {\n send(message: OutgoingMessage): void;\n close(code?: number, reason?: string): void;\n}\n\n/**\n * The subset of `RemoteServer` state a session needs. Kept as an interface so\n * sessions can be used standalone in tests and so alternate hosts (e.g. a\n * Cloudflare Durable Object wrapper) can satisfy it without subclassing\n * `RemoteServer`.\n */\nexport interface SessionHost {\n readonly module: HypenModule<any>;\n readonly moduleName: string;\n readonly ui: string;\n readonly resources: Record<string, string>;\n readonly app: HypenApp | null;\n readonly syncActions: boolean;\n readonly sessionManager: SessionManager;\n readonly discoveredComponents: Map<\n string,\n { template: string; module?: HypenModuleDefinition<any> }\n >;\n\n /**\n * Construct a fresh engine for this session. Called once per session.\n *\n * Each session owns its own engine instance, so this must return a new\n * engine every call (not a shared singleton). This is the seam that keeps\n * `RemoteSession` engine-agnostic: `@hypen-space/server` returns\n * `new Engine()` (Node/Bun WASM), a Cloudflare Durable Object returns a\n * `CFEngine`, etc. The returned engine may be un-initialised — the session\n * calls `init()` itself.\n */\n createEngine(): BaseEngine;\n\n /** Other live sessions (for syncActions fan-out and allow-multiple peer broadcast). */\n otherSessions(self: RemoteSession): Iterable<RemoteSession>;\n\n /** Sessions that currently share `sessionId` (for kick-old / reject-new). */\n sessionsForId(sessionId: string): Iterable<RemoteSession>;\n\n /** Fired after hello → initialTree completes. */\n onSessionReady(session: RemoteSession, client: RemoteClient): void;\n\n /** Fired when the session is fully torn down. */\n onSessionDestroyed(session: RemoteSession, client: RemoteClient): void;\n}\n\n/**\n * Options passed when constructing a session.\n */\nexport interface RemoteSessionOptions {\n /** Internal client id (for logs and `RemoteClient.id`). Auto-generated if omitted. */\n clientId?: string;\n /**\n * Grace period in ms before a connected client that has not sent `hello`\n * is auto-initialised as a legacy (no-session-id) client. Set `null` to\n * disable the grace period entirely — useful for transports like SSE where\n * the first message may be deliberately delayed.\n * @default 1000\n */\n helloGraceMs?: number | null;\n /**\n * `socket` value stored on the `RemoteClient` record surfaced to\n * `onConnection`/`onDisconnection` callbacks. Pass through the raw\n * transport handle (e.g. a `ServerWebSocket`) so user code has the\n * escape hatch when it needs it.\n */\n socketHandle?: unknown;\n}\n\nlet nextSessionCounter = 1;\n\n/**\n * One client's worth of server-side state. Transport-agnostic.\n */\nexport class RemoteSession {\n readonly id: string;\n readonly connectedAt: Date;\n\n private readonly host: SessionHost;\n private readonly transport: SessionTransport;\n private readonly socketHandle: unknown;\n\n readonly engine: BaseEngine;\n private _moduleInstance: HypenModuleInstance<any> | null = null;\n\n private _sessionId: string | null = null;\n private _helloReceived = false;\n private _stateSubscribed = false;\n private _revision = 0;\n private _destroyed = false;\n private helloTimeout: ReturnType<typeof setTimeout> | null = null;\n /**\n * Per-session ManagedRouter auto-wired from `Router {}` blocks found\n * in the primary template. `null` when auto-wiring is disabled, no\n * Routers are present, or no registered modules matched any route's\n * element list. Torn down alongside the session in `destroy()`.\n */\n private _autoManagedRouter: ManagedRouter | null = null;\n /**\n * Toggled by `RemoteServer.disableAutoRouter()` when the host wants\n * to wire a ManagedRouter by hand via `onSessionCreate` instead.\n */\n autoRouterEnabled: boolean = true;\n\n // Promises that let callers hook into session lifecycle without racing\n // against the hello handshake or the transport close event.\n // `ready` — resolves after the hello handshake, primary module\n // construction, and initial render have completed.\n // `moduleInstance` is guaranteed non-null here.\n // `closed` — resolves after `destroy()` has finished its teardown\n // (onDisconnect hook, module teardown, session suspend).\n readonly ready: Promise<void>;\n readonly closed: Promise<void>;\n private _resolveReady!: () => void;\n private _resolveClosed!: () => void;\n\n constructor(\n host: SessionHost,\n transport: SessionTransport,\n options: RemoteSessionOptions = {}\n ) {\n this.host = host;\n this.transport = transport;\n this.id = options.clientId ?? `client_${nextSessionCounter++}`;\n this.connectedAt = new Date();\n this.socketHandle = options.socketHandle;\n\n this.ready = new Promise<void>((resolve) => {\n this._resolveReady = resolve;\n });\n this.closed = new Promise<void>((resolve) => {\n this._resolveClosed = resolve;\n });\n\n this.engine = host.createEngine();\n // init() is synchronous for wasm-node but declared async.\n this.engine.init().catch((err) => log.error(\"Engine init failed:\", err));\n\n this.setupComponentResolver();\n if (Object.keys(host.resources).length > 0) {\n this.engine.registerResources(host.resources);\n }\n\n log.info(`Session ${this.id} created, engine initialized`);\n\n // Auto-initialise legacy clients that never send `hello`.\n const graceMs = options.helloGraceMs ?? 1000;\n if (graceMs !== null) {\n this.helloTimeout = setTimeout(() => {\n if (!this._helloReceived) {\n this.initializeSession(undefined, undefined).catch((err) =>\n log.error(\"Error initializing legacy session:\", err)\n );\n }\n }, graceMs);\n }\n }\n\n get sessionId(): string | null {\n return this._sessionId;\n }\n\n get moduleInstance(): HypenModuleInstance<any> | null {\n return this._moduleInstance;\n }\n\n get helloReceived(): boolean {\n return this._helloReceived;\n }\n\n get stateSubscribed(): boolean {\n return this._stateSubscribed;\n }\n\n get revision(): number {\n return this._revision;\n }\n\n get isDestroyed(): boolean {\n return this._destroyed;\n }\n\n /**\n * The `RemoteClient` record surfaced to `onConnection`/`onDisconnection`\n * callbacks. Useful to construct consistently from both the Bun adapter\n * and custom transports.\n */\n toRemoteClient(): RemoteClient {\n return {\n id: this.id,\n socket: this.socketHandle,\n connectedAt: this.connectedAt,\n };\n }\n\n /**\n * Feed a client → server message into this session.\n *\n * Invoked by the transport adapter when it receives a message. Accepts\n * either a parsed `RemoteMessage` or a raw JSON string.\n */\n async receive(raw: RemoteMessage | string | Buffer): Promise<void> {\n if (this._destroyed) return;\n\n // `Buffer<ArrayBufferLike>`-as-narrowed-union doesn't reliably collapse\n // to `RemoteMessage` in the `else` branch under the current @types/node,\n // so we project the non-raw case through an explicit assertion.\n let msg: RemoteMessage;\n try {\n if (typeof raw === \"string\" || raw instanceof Buffer) {\n msg = JSON.parse(raw.toString()) as RemoteMessage;\n } else {\n msg = raw as RemoteMessage;\n }\n } catch (err) {\n log.error(`Invalid message on session ${this.id}:`, err);\n return;\n }\n\n try {\n switch (msg.type) {\n case \"hello\": {\n const hello = msg as HelloMessage;\n await this.initializeSession(hello.sessionId, hello.props);\n break;\n }\n\n case \"dispatchAction\": {\n const action = msg as DispatchActionMessage;\n this.engine.dispatchAction(action.action, action.payload);\n\n if (this.host.syncActions) {\n for (const other of this.host.otherSessions(this)) {\n if (!other._helloReceived) continue;\n other.engine.dispatchAction(action.action, action.payload);\n }\n }\n break;\n }\n\n case \"updateState\": {\n const stateMsg = msg as UpdateStateMessage;\n this._moduleInstance?.updateState(stateMsg.state);\n\n if (this.host.syncActions) {\n for (const other of this.host.otherSessions(this)) {\n if (!other._helloReceived || !other._moduleInstance) continue;\n other._moduleInstance.updateState(stateMsg.state);\n }\n }\n break;\n }\n\n case \"subscribeState\": {\n this._stateSubscribed = true;\n log.info(`Session ${this.id} subscribed to state updates`);\n break;\n }\n\n default:\n // Unknown message type — ignore.\n break;\n }\n } catch (err) {\n log.error(`Error handling message on session ${this.id}:`, err);\n }\n }\n\n /**\n * Send an outgoing message through this session's transport. Exposed so\n * peer sessions can broadcast to each other (allow-multiple) without\n * touching transport internals.\n */\n send(message: OutgoingMessage): void {\n if (this._destroyed) return;\n try {\n this.transport.send(message);\n } catch (err) {\n log.error(`Failed to send message on session ${this.id}:`, err);\n }\n }\n\n /**\n * Notify the client their session is gone and close the transport.\n */\n expireAndClose(reason: \"ttl\" | \"kicked\" | \"manual\"): void {\n if (!this._sessionId) {\n this.transport.close(1000, \"Session closed\");\n return;\n }\n const expired: SessionExpiredMessage = {\n type: \"sessionExpired\",\n sessionId: this._sessionId,\n reason,\n };\n this.send(expired);\n this.transport.close(1000, \"Session \" + reason);\n }\n\n /**\n * Tear down the session. Call this from the transport adapter when the\n * underlying connection closes, or manually to evict a client.\n *\n * Runs `onDisconnect`, suspends the session so it can be resumed later,\n * and fires the host's `onSessionDestroyed` hook.\n */\n async destroy(): Promise<void> {\n if (this._destroyed) return;\n this._destroyed = true;\n\n if (this.helloTimeout) {\n clearTimeout(this.helloTimeout);\n this.helloTimeout = null;\n }\n\n const currentState = this._moduleInstance?.getState() ?? {};\n\n // onDisconnect hook\n if (this._sessionId && this.host.module.handlers.onDisconnect) {\n const session = this.host.sessionManager.getActiveSession(this._sessionId);\n if (session) {\n try {\n await this.host.module.handlers.onDisconnect({\n state: currentState,\n session,\n });\n } catch (err) {\n log.error(`onDisconnect threw on session ${this.id}:`, err);\n }\n }\n }\n\n // Suspend session if no other connections remain for it\n if (this._sessionId) {\n this.host.sessionManager.untrackConnection(this._sessionId, this);\n if (this.host.sessionManager.getConnectionCount(this._sessionId) === 0) {\n const session = this.host.sessionManager.getActiveSession(\n this._sessionId\n );\n if (session) {\n this.host.sessionManager.suspendSession(\n this._sessionId,\n currentState,\n async (expiredSession) => {\n if (this.host.module.handlers.onExpire) {\n await this.host.module.handlers.onExpire({\n session: expiredSession,\n });\n }\n }\n );\n }\n }\n }\n\n if (this._autoManagedRouter) {\n try {\n await this._autoManagedRouter.stop();\n } catch (err) {\n log.error(`Auto-router stop failed on ${this.id}:`, err);\n }\n this._autoManagedRouter = null;\n }\n\n if (this._moduleInstance) {\n try {\n await this._moduleInstance.destroy();\n } catch (err) {\n log.error(`ModuleInstance.destroy() failed on ${this.id}:`, err);\n }\n }\n\n this.host.onSessionDestroyed(this, this.toRemoteClient());\n // Ensure `ready` never dangles: if teardown happens before init (e.g.\n // transport dies before hello arrives), unblock any waiter so they can\n // observe `closed` and clean up without deadlocking.\n this._resolveReady();\n this._resolveClosed();\n }\n\n // ------------------------------------------------------------------\n // internals\n // ------------------------------------------------------------------\n\n private setupComponentResolver(): void {\n this.engine.setComponentResolver((componentName, _contextPath) => {\n const comp = this.host.discoveredComponents.get(componentName);\n if (!comp) return null;\n return { source: comp.template, path: componentName };\n });\n }\n\n private registerNestedModules(): void {\n const primary = this.host.moduleName;\n\n if (this.host.app) {\n for (const [name, def] of this.host.app.components) {\n if (name === primary) continue;\n const stateKeys =\n def.initialState !== null && typeof def.initialState === \"object\"\n ? Object.keys(def.initialState as object)\n : [];\n const snapshot =\n def.initialState !== null && typeof def.initialState === \"object\"\n ? structuredClone(def.initialState)\n : {};\n this.engine.registerModule(\n name,\n def.actions ?? [],\n stateKeys,\n snapshot\n );\n log.info(\n `Registered nested module \"${name}\" (${def.actions?.length ?? 0} actions, ${stateKeys.length} state keys)`\n );\n }\n }\n\n for (const [name, comp] of this.host.discoveredComponents) {\n if (name === primary) continue;\n if (!comp.module) continue;\n if (this.host.app?.has(name)) continue;\n\n const def = comp.module;\n\n // Skip pure UI components (no state, no actions, no lifecycle).\n // `loadDiscoveredComponents` creates an auto-stateless module\n // (`app.defineState({}).build()`) for every .hypen file without a\n // sidecar .ts, which means pure UI components like Notifications,\n // Profile, Messages, BottomNav, Feed, etc. all get registered as\n // nested modules. That registration doesn't shadow unscoped\n // `@state.x` bindings (those still hit the primary module), but\n // it does occupy a scope slot the engine may otherwise route\n // dependency updates through — and every extra module carries\n // revision bookkeeping overhead per render. When the module has\n // no state and no actions, registering it is pure noise.\n const hasState =\n def.initialState !== null &&\n typeof def.initialState === \"object\" &&\n Object.keys(def.initialState as object).length > 0;\n const hasActions = (def.actions?.length ?? 0) > 0;\n if (!hasState && !hasActions) continue;\n\n const stateKeys =\n def.initialState !== null && typeof def.initialState === \"object\"\n ? Object.keys(def.initialState as object)\n : [];\n const snapshot =\n def.initialState !== null && typeof def.initialState === \"object\"\n ? JSON.parse(JSON.stringify(def.initialState))\n : {};\n this.engine.registerModule(name, def.actions ?? [], stateKeys, snapshot);\n log.info(`Registered nested module \"${name}\" from discovery`);\n }\n }\n\n private async initializeSession(\n requestedSessionId: string | undefined,\n props: Record<string, any> | undefined\n ): Promise<void> {\n if (this._helloReceived || this._destroyed) return;\n this._helloReceived = true;\n log.info(\n `Initializing session for ${this.id} (sessionId: ${requestedSessionId ?? \"new\"})`\n );\n\n if (this.helloTimeout) {\n clearTimeout(this.helloTimeout);\n this.helloTimeout = null;\n }\n\n let session: Session;\n let isNew = true;\n let isRestored = false;\n let restoredState: unknown = null;\n\n const sm = this.host.sessionManager;\n\n if (requestedSessionId) {\n const resumed = sm.resumeSession(requestedSessionId);\n if (resumed) {\n session = resumed.session;\n restoredState = resumed.savedState;\n isNew = false;\n isRestored = true;\n } else {\n const activeSession = sm.getActiveSession(requestedSessionId);\n if (activeSession) {\n const allowed = this.resolveConcurrentConnection(\n activeSession,\n props\n );\n if (!allowed) return;\n session = activeSession;\n isNew = false;\n } else {\n session = sm.createSession(props);\n }\n }\n } else {\n session = sm.createSession(props);\n }\n\n this._sessionId = session.id;\n sm.trackConnection(session.id, this);\n\n const sessionAck: SessionAckMessage = {\n type: \"sessionAck\",\n sessionId: session.id,\n isNew,\n isRestored,\n };\n this.send(sessionAck);\n log.info(`Sent sessionAck to ${this.id} (session: ${session.id})`);\n\n // Create ModuleInstance HERE (not in constructor) so that onCreated fires\n // right before renderSource. See original server.ts comment for why.\n if (!this._moduleInstance) {\n this._moduleInstance = new HypenModuleInstance(\n this.engine,\n this.host.module\n );\n }\n\n if (isRestored) {\n await this.triggerReconnect(session, restoredState);\n }\n\n await this._moduleInstance.waitForReady();\n\n this.registerNestedModules();\n\n // Flush state proxy microtasks so onCreated mutations propagate.\n await new Promise<void>((resolve) => queueMicrotask(resolve));\n\n // Capture initial render patches (initial tree + any re-render from onCreated state).\n const initialPatches: Patch[] = [];\n this.engine.setRenderCallback((patches) => {\n initialPatches.push(...patches);\n });\n\n try {\n this.engine.renderSource(this.host.ui);\n } catch (err) {\n log.error(`Failed to render UI for ${this.id}:`, err);\n this.transport.close(1011, \"Render failed\");\n return;\n }\n\n // Switch to the streaming callback for subsequent patches.\n this.setupStreamingRenderCallback();\n\n const initialMessage: InitialTreeMessage = {\n type: \"initialTree\",\n module: this.host.moduleName,\n state: this._moduleInstance.getState(),\n patches: initialPatches,\n revision: 0,\n };\n this.send(initialMessage);\n log.info(\n `Sent initialTree to ${this.id} (${initialPatches.length} patches)`\n );\n\n this.host.onSessionReady(this, this.toRemoteClient());\n this._resolveReady();\n\n // Auto-wire a ManagedRouter from the template's own Router {} blocks.\n // The user gets routing \"for free\" from the DSL — no addRoute calls\n // in their server code. Guarded by `_autoRouterEnabled` so callers\n // that want bespoke wiring can opt out via\n // `RemoteServer.disableAutoRouter()`.\n if (this.autoRouterEnabled) {\n this.autoWireManagedRouter();\n }\n }\n\n /**\n * Parse Router {} blocks out of the primary template and spin up a\n * per-session ManagedRouter bound to the shared engine, auto-adding\n * one route per Route() — components resolved from the HypenApp\n * registry. The primary module's `location` field (if present) is\n * kept in sync with the router path so the Router IR re-renders on\n * navigation.\n *\n * Both top-level routers (`moduleScope` = primary / unscoped) and\n * routers nested inside per-route module templates are registered\n * here, flattened into a single route table against the session's\n * `HypenRouter`. The URL is a single string, so nested-router routes\n * share the parent's URL space — authors who want sub-path semantics\n * must spell out the full prefix in their `Route(path: ...)`. On\n * pattern conflicts (multiple routes matching the same path),\n * `ManagedRouter` picks first-match, and the engine emits routers\n * in outer→inner order so primary routes win ties naturally.\n */\n private autoWireManagedRouter(): void {\n // A registry is only needed to resolve component-backed routes; inline\n // routes (`Route(path) { ...markup... }`) need none. Fall back to an empty\n // registry so a single-module app with an inline `Router {}` still gets its\n // `@router.push` handler + `state.location` mirror wired.\n const app = this.host.app ?? new HypenAppClass();\n\n // Collect router blocks from both the primary template AND every\n // discovered child component template. `discoverRouters` walks a\n // single IR tree and does not resolve `Foo()` component references\n // — child templates live in separate source strings in\n // `host.discoveredComponents`. Running discover on each separately\n // and concatenating (primary first) gives us the true cross-tree\n // router inventory. Without this pass, a nested `module Home {\n // Router { ... } }` block declared in `Home/component.hypen`\n // (the canonical `.source()`-discovery shape) is silently\n // invisible to the SDK and the route never mounts.\n const discovered: ReturnType<BaseEngine[\"discoverRouters\"]> = [];\n const runDiscover = (source: string, label: string): void => {\n try {\n const blocks = this.engine.discoverRouters(source);\n for (const b of blocks) discovered.push(b);\n } catch (err) {\n log.error(`Auto-router: discoverRouters failed on ${label}:`, err);\n }\n };\n runDiscover(this.host.ui, this.id);\n for (const [name, comp] of this.host.discoveredComponents) {\n if (comp.template) runDiscover(comp.template, `${this.id} / ${name}`);\n }\n if (discovered.length === 0) return;\n\n const primaryScope = this.host.moduleName.toLowerCase();\n\n // Build a single top-level ManagedRouter covering every route from\n // every discovered router block (primary AND nested). Multiple\n // `Router {}` blocks flatten into one route list — the engine's\n // own Router IR still renders them independently, but the SDK only\n // needs a single `HypenRouter` per session.\n const router = new HypenRouter();\n const globalContext = new HypenGlobalContext();\n const managed = new ManagedRouter(router, this.engine, app, globalContext);\n\n const primary = this._moduleInstance;\n if (primary) {\n globalContext.registerModule(primaryScope, primary);\n }\n\n let added = 0;\n const seenPaths = new Set<string>();\n for (const block of discovered) {\n for (const route of block.routes) {\n // First path wins — outer Router blocks emit first.\n if (seenPaths.has(route.path)) {\n log.debug(\n `Auto-router: path \"${route.path}\" already registered; ignoring nested duplicate`\n );\n continue;\n }\n const component = this.pickComponent(route.elementNames);\n if (!component) {\n log.debug(\n `Auto-router: no registered module matched route \"${route.path}\" — skipping`\n );\n continue;\n }\n managed.addRoute({ path: route.path, component });\n seenPaths.add(route.path);\n added += 1;\n }\n }\n\n // `added` counts component-backed routes (mounted via ManagedRouter).\n // Routes can also be fully INLINE — `Route(path: \"/x\") { ...markup... }`\n // with no component — in which case the engine's own Router IR renders\n // them straight from `state.location`, and there's nothing for\n // ManagedRouter to mount. We still need the URL→`location` mirror and the\n // `@router.push` action handler so navigation works. So: bail only if no\n // Router blocks were discovered at all; otherwise wire the router even\n // when `added === 0`.\n const locationKey = this.primaryHasLocationKey() ? \"location\" : null;\n if (added === 0 && !locationKey) {\n // No component routes and no `location` state to drive inline routes —\n // nothing this router could affect.\n log.debug(`Auto-router: nothing to mount for ${this.id}`);\n return;\n }\n\n // Mirror router path into primary module state's `location` so the\n // engine-level Router IR reconciles to the matching Route subtree.\n // Deferring via `queueMicrotask` keeps the engine write off the\n // synchronous notify path (Rust rejects re-entrant WASM state\n // proxy calls).\n if (locationKey) {\n router.onNavigate((rs) => {\n const path = rs.currentPath;\n queueMicrotask(() => {\n try {\n this._moduleInstance?.updateState({ [locationKey]: path });\n } catch (err) {\n log.error(`Auto-router: state.${locationKey} sync failed:`, err);\n }\n });\n });\n }\n\n // `start()` installs the `@router.push`/`@router.back`/... action handlers\n // and subscribes the router. Needed for both component and inline routes.\n managed.start();\n // HypenRouter doesn't fire onNavigate on subscribe; kick an\n // explicit push so the initial route mounts.\n router.push(router.getCurrentPath());\n\n this._autoManagedRouter = managed;\n }\n\n /** Pick the first element name that resolves to a registered module. */\n private pickComponent(elementNames: string[]): string | null {\n const app = this.host.app;\n if (!app) return null;\n for (const name of elementNames) {\n if (app.has(name)) return name;\n }\n return null;\n }\n\n /** Does the primary module's initial state have a `location` field? */\n private primaryHasLocationKey(): boolean {\n const state = this._moduleInstance?.getState();\n return (\n state !== null &&\n state !== undefined &&\n typeof state === \"object\" &&\n \"location\" in state\n );\n }\n\n private setupStreamingRenderCallback(): void {\n this.engine.setRenderCallback((patches) => {\n if (this._destroyed) return;\n this._revision++;\n log.info(\n `Streaming ${patches.length} patches to ${this.id} (rev ${this._revision})`\n );\n\n const patchMessage: PatchMessage = {\n type: \"patch\",\n module: this.host.moduleName,\n patches,\n revision: this._revision,\n };\n this.send(patchMessage);\n\n if (this._stateSubscribed && this._moduleInstance) {\n const stateMessage: StateUpdateMessage = {\n type: \"stateUpdate\",\n module: this.host.moduleName,\n state: this._moduleInstance.getState(),\n revision: this._revision,\n };\n this.send(stateMessage);\n }\n\n // allow-multiple: fan out to other sessions sharing our sessionId.\n if (\n this._sessionId &&\n this.host.sessionManager.getConcurrentPolicy() === \"allow-multiple\"\n ) {\n for (const peer of this.host.sessionsForId(this._sessionId)) {\n if (peer === this) continue;\n peer.send(patchMessage);\n if (peer._stateSubscribed && this._moduleInstance) {\n const stateMessage: StateUpdateMessage = {\n type: \"stateUpdate\",\n module: this.host.moduleName,\n state: this._moduleInstance.getState(),\n revision: this._revision,\n };\n peer.send(stateMessage);\n }\n }\n }\n });\n }\n\n private resolveConcurrentConnection(\n existingSession: Session,\n _props: Record<string, any> | undefined\n ): boolean {\n const policy = this.host.sessionManager.getConcurrentPolicy();\n\n switch (policy) {\n case \"kick-old\": {\n for (const peer of this.host.sessionsForId(existingSession.id)) {\n if (peer === this) continue;\n peer.expireAndClose(\"kicked\");\n }\n return true;\n }\n case \"reject-new\": {\n this.expireAndClose(\"kicked\");\n return false;\n }\n case \"allow-multiple\":\n default:\n return true;\n }\n }\n\n private async triggerReconnect(\n session: Session,\n savedState: unknown\n ): Promise<void> {\n const handler = this.host.module.handlers.onReconnect;\n if (!handler) return;\n\n const restore = (state: unknown) => {\n if (state === null || typeof state !== \"object\") {\n log.warn(\n \"restore() called with non-object state, ignoring:\",\n typeof state\n );\n return;\n }\n this._moduleInstance?.updateState(state as Record<string, unknown>);\n };\n\n await handler({ session, restore });\n // `savedState` is threaded through for handlers that don't call restore().\n void savedState;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Built-in transports\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a Bun `ServerWebSocket` as a `SessionTransport`.\n *\n * Each outgoing message is JSON-serialized. The caller is responsible for\n * calling `session.receive()` on incoming messages and `session.destroy()`\n * on close.\n */\nexport function createBunWebSocketTransport(\n ws: { send(data: string): void; close(code?: number, reason?: string): void }\n): SessionTransport {\n return {\n send(message) {\n ws.send(JSON.stringify(message));\n },\n close(code, reason) {\n ws.close(code, reason);\n },\n };\n}\n\n/**\n * In-memory transport that buffers outgoing messages and exposes them as\n * an async iterator via `.stream()`. Lets you drive a session from code\n * that prefers `for await` over callbacks — e.g. piping to SSE, HTTP/2\n * server push, or gRPC streaming.\n *\n * @example\n * ```ts\n * const transport = new AsyncQueueTransport();\n * const session = server.createSession(transport);\n * for await (const msg of transport.stream()) {\n * res.write(`data: ${JSON.stringify(msg)}\\n\\n`); // SSE\n * }\n * ```\n */\nexport class AsyncQueueTransport implements SessionTransport {\n private queue: OutgoingMessage[] = [];\n private waiters: Array<\n (value: IteratorResult<OutgoingMessage, void>) => void\n > = [];\n private closed = false;\n\n send(message: OutgoingMessage): void {\n if (this.closed) return;\n const w = this.waiters.shift();\n if (w) {\n w({ value: message, done: false });\n } else {\n this.queue.push(message);\n }\n }\n\n close(_code?: number, _reason?: string): void {\n if (this.closed) return;\n this.closed = true;\n while (this.waiters.length) {\n const w = this.waiters.shift()!;\n w({ value: undefined, done: true });\n }\n }\n\n /**\n * Async iterator over outgoing messages. Completes once the transport is\n * closed and the queue is drained.\n */\n stream(): AsyncIterableIterator<OutgoingMessage> {\n const self = this;\n const iter: AsyncIterableIterator<OutgoingMessage> = {\n [Symbol.asyncIterator]() {\n return iter;\n },\n async next(): Promise<IteratorResult<OutgoingMessage, void>> {\n if (self.queue.length) {\n return { value: self.queue.shift()!, done: false };\n }\n if (self.closed) {\n return { value: undefined, done: true };\n }\n return new Promise((resolve) => {\n self.waiters.push(resolve);\n });\n },\n async return(): Promise<IteratorResult<OutgoingMessage, void>> {\n self.close();\n return { value: undefined, done: true };\n },\n };\n return iter;\n }\n}\n"
|
|
11
6
|
],
|
|
12
|
-
"mappings": "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",
|
|
13
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "EC47228D45CD496064756E2164756E21",
|
|
14
9
|
"names": []
|
|
15
10
|
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RemoteSession - Transport-agnostic per-client session for Hypen remote apps.
|
|
3
|
+
*
|
|
4
|
+
* One `RemoteSession` owns one logical client: a dedicated engine instance,
|
|
5
|
+
* a module instance, a session id, and the logic that drives the Hypen remote
|
|
6
|
+
* protocol (hello → sessionAck → initialTree → streaming patches).
|
|
7
|
+
*
|
|
8
|
+
* It does NOT know how bytes reach the client. That is the job of the
|
|
9
|
+
* `SessionTransport` interface — a minimal `{ send, close }` contract that
|
|
10
|
+
* can be backed by a Bun `ServerWebSocket`, an SSE `ReadableStream`, an
|
|
11
|
+
* in-memory queue (see `AsyncQueueTransport`), or anything else.
|
|
12
|
+
*
|
|
13
|
+
* Backend devs can plug Hypen into an existing Express/Fastify/ws/uWebSockets
|
|
14
|
+
* setup by:
|
|
15
|
+
*
|
|
16
|
+
* 1. Calling `server.prepare()` once (discovery + session manager init).
|
|
17
|
+
* 2. Calling `server.createSession(transport)` per client.
|
|
18
|
+
* 3. Forwarding incoming JSON messages via `session.receive(msg)`.
|
|
19
|
+
* 4. Calling `session.destroy()` when the transport closes.
|
|
20
|
+
*
|
|
21
|
+
* Or, for a pure async-iterator style:
|
|
22
|
+
*
|
|
23
|
+
* const transport = new AsyncQueueTransport();
|
|
24
|
+
* const session = server.createSession(transport);
|
|
25
|
+
* for await (const out of transport.stream()) { ...forward to client... }
|
|
26
|
+
*/
|
|
27
|
+
import type { HypenApp, HypenModule, HypenModuleDefinition } from "../app.js";
|
|
28
|
+
import { HypenModuleInstance } from "../app.js";
|
|
29
|
+
import type { InitialTreeMessage, PatchMessage, RemoteClient, RemoteMessage, SessionAckMessage, SessionExpiredMessage, StateUpdateMessage } from "./types.js";
|
|
30
|
+
import { SessionManager } from "./session.js";
|
|
31
|
+
import { BaseEngine } from "../engine-base.js";
|
|
32
|
+
/**
|
|
33
|
+
* Server → client messages emitted by a `RemoteSession`.
|
|
34
|
+
*/
|
|
35
|
+
export type OutgoingMessage = InitialTreeMessage | PatchMessage | StateUpdateMessage | SessionAckMessage | SessionExpiredMessage;
|
|
36
|
+
/**
|
|
37
|
+
* Minimal contract a `RemoteSession` needs to reach the client. Implement
|
|
38
|
+
* this to plug Hypen into any transport.
|
|
39
|
+
*
|
|
40
|
+
* `send` receives a structured message — serialize however you like
|
|
41
|
+
* (JSON, MessagePack, protobuf). Built-in adapters use `JSON.stringify`.
|
|
42
|
+
*/
|
|
43
|
+
export interface SessionTransport {
|
|
44
|
+
send(message: OutgoingMessage): void;
|
|
45
|
+
close(code?: number, reason?: string): void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The subset of `RemoteServer` state a session needs. Kept as an interface so
|
|
49
|
+
* sessions can be used standalone in tests and so alternate hosts (e.g. a
|
|
50
|
+
* Cloudflare Durable Object wrapper) can satisfy it without subclassing
|
|
51
|
+
* `RemoteServer`.
|
|
52
|
+
*/
|
|
53
|
+
export interface SessionHost {
|
|
54
|
+
readonly module: HypenModule<any>;
|
|
55
|
+
readonly moduleName: string;
|
|
56
|
+
readonly ui: string;
|
|
57
|
+
readonly resources: Record<string, string>;
|
|
58
|
+
readonly app: HypenApp | null;
|
|
59
|
+
readonly syncActions: boolean;
|
|
60
|
+
readonly sessionManager: SessionManager;
|
|
61
|
+
readonly discoveredComponents: Map<string, {
|
|
62
|
+
template: string;
|
|
63
|
+
module?: HypenModuleDefinition<any>;
|
|
64
|
+
}>;
|
|
65
|
+
/**
|
|
66
|
+
* Construct a fresh engine for this session. Called once per session.
|
|
67
|
+
*
|
|
68
|
+
* Each session owns its own engine instance, so this must return a new
|
|
69
|
+
* engine every call (not a shared singleton). This is the seam that keeps
|
|
70
|
+
* `RemoteSession` engine-agnostic: `@hypen-space/server` returns
|
|
71
|
+
* `new Engine()` (Node/Bun WASM), a Cloudflare Durable Object returns a
|
|
72
|
+
* `CFEngine`, etc. The returned engine may be un-initialised — the session
|
|
73
|
+
* calls `init()` itself.
|
|
74
|
+
*/
|
|
75
|
+
createEngine(): BaseEngine;
|
|
76
|
+
/** Other live sessions (for syncActions fan-out and allow-multiple peer broadcast). */
|
|
77
|
+
otherSessions(self: RemoteSession): Iterable<RemoteSession>;
|
|
78
|
+
/** Sessions that currently share `sessionId` (for kick-old / reject-new). */
|
|
79
|
+
sessionsForId(sessionId: string): Iterable<RemoteSession>;
|
|
80
|
+
/** Fired after hello → initialTree completes. */
|
|
81
|
+
onSessionReady(session: RemoteSession, client: RemoteClient): void;
|
|
82
|
+
/** Fired when the session is fully torn down. */
|
|
83
|
+
onSessionDestroyed(session: RemoteSession, client: RemoteClient): void;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Options passed when constructing a session.
|
|
87
|
+
*/
|
|
88
|
+
export interface RemoteSessionOptions {
|
|
89
|
+
/** Internal client id (for logs and `RemoteClient.id`). Auto-generated if omitted. */
|
|
90
|
+
clientId?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Grace period in ms before a connected client that has not sent `hello`
|
|
93
|
+
* is auto-initialised as a legacy (no-session-id) client. Set `null` to
|
|
94
|
+
* disable the grace period entirely — useful for transports like SSE where
|
|
95
|
+
* the first message may be deliberately delayed.
|
|
96
|
+
* @default 1000
|
|
97
|
+
*/
|
|
98
|
+
helloGraceMs?: number | null;
|
|
99
|
+
/**
|
|
100
|
+
* `socket` value stored on the `RemoteClient` record surfaced to
|
|
101
|
+
* `onConnection`/`onDisconnection` callbacks. Pass through the raw
|
|
102
|
+
* transport handle (e.g. a `ServerWebSocket`) so user code has the
|
|
103
|
+
* escape hatch when it needs it.
|
|
104
|
+
*/
|
|
105
|
+
socketHandle?: unknown;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* One client's worth of server-side state. Transport-agnostic.
|
|
109
|
+
*/
|
|
110
|
+
export declare class RemoteSession {
|
|
111
|
+
readonly id: string;
|
|
112
|
+
readonly connectedAt: Date;
|
|
113
|
+
private readonly host;
|
|
114
|
+
private readonly transport;
|
|
115
|
+
private readonly socketHandle;
|
|
116
|
+
readonly engine: BaseEngine;
|
|
117
|
+
private _moduleInstance;
|
|
118
|
+
private _sessionId;
|
|
119
|
+
private _helloReceived;
|
|
120
|
+
private _stateSubscribed;
|
|
121
|
+
private _revision;
|
|
122
|
+
private _destroyed;
|
|
123
|
+
private helloTimeout;
|
|
124
|
+
/**
|
|
125
|
+
* Per-session ManagedRouter auto-wired from `Router {}` blocks found
|
|
126
|
+
* in the primary template. `null` when auto-wiring is disabled, no
|
|
127
|
+
* Routers are present, or no registered modules matched any route's
|
|
128
|
+
* element list. Torn down alongside the session in `destroy()`.
|
|
129
|
+
*/
|
|
130
|
+
private _autoManagedRouter;
|
|
131
|
+
/**
|
|
132
|
+
* Toggled by `RemoteServer.disableAutoRouter()` when the host wants
|
|
133
|
+
* to wire a ManagedRouter by hand via `onSessionCreate` instead.
|
|
134
|
+
*/
|
|
135
|
+
autoRouterEnabled: boolean;
|
|
136
|
+
readonly ready: Promise<void>;
|
|
137
|
+
readonly closed: Promise<void>;
|
|
138
|
+
private _resolveReady;
|
|
139
|
+
private _resolveClosed;
|
|
140
|
+
constructor(host: SessionHost, transport: SessionTransport, options?: RemoteSessionOptions);
|
|
141
|
+
get sessionId(): string | null;
|
|
142
|
+
get moduleInstance(): HypenModuleInstance<any> | null;
|
|
143
|
+
get helloReceived(): boolean;
|
|
144
|
+
get stateSubscribed(): boolean;
|
|
145
|
+
get revision(): number;
|
|
146
|
+
get isDestroyed(): boolean;
|
|
147
|
+
/**
|
|
148
|
+
* The `RemoteClient` record surfaced to `onConnection`/`onDisconnection`
|
|
149
|
+
* callbacks. Useful to construct consistently from both the Bun adapter
|
|
150
|
+
* and custom transports.
|
|
151
|
+
*/
|
|
152
|
+
toRemoteClient(): RemoteClient;
|
|
153
|
+
/**
|
|
154
|
+
* Feed a client → server message into this session.
|
|
155
|
+
*
|
|
156
|
+
* Invoked by the transport adapter when it receives a message. Accepts
|
|
157
|
+
* either a parsed `RemoteMessage` or a raw JSON string.
|
|
158
|
+
*/
|
|
159
|
+
receive(raw: RemoteMessage | string | Buffer): Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* Send an outgoing message through this session's transport. Exposed so
|
|
162
|
+
* peer sessions can broadcast to each other (allow-multiple) without
|
|
163
|
+
* touching transport internals.
|
|
164
|
+
*/
|
|
165
|
+
send(message: OutgoingMessage): void;
|
|
166
|
+
/**
|
|
167
|
+
* Notify the client their session is gone and close the transport.
|
|
168
|
+
*/
|
|
169
|
+
expireAndClose(reason: "ttl" | "kicked" | "manual"): void;
|
|
170
|
+
/**
|
|
171
|
+
* Tear down the session. Call this from the transport adapter when the
|
|
172
|
+
* underlying connection closes, or manually to evict a client.
|
|
173
|
+
*
|
|
174
|
+
* Runs `onDisconnect`, suspends the session so it can be resumed later,
|
|
175
|
+
* and fires the host's `onSessionDestroyed` hook.
|
|
176
|
+
*/
|
|
177
|
+
destroy(): Promise<void>;
|
|
178
|
+
private setupComponentResolver;
|
|
179
|
+
private registerNestedModules;
|
|
180
|
+
private initializeSession;
|
|
181
|
+
/**
|
|
182
|
+
* Parse Router {} blocks out of the primary template and spin up a
|
|
183
|
+
* per-session ManagedRouter bound to the shared engine, auto-adding
|
|
184
|
+
* one route per Route() — components resolved from the HypenApp
|
|
185
|
+
* registry. The primary module's `location` field (if present) is
|
|
186
|
+
* kept in sync with the router path so the Router IR re-renders on
|
|
187
|
+
* navigation.
|
|
188
|
+
*
|
|
189
|
+
* Both top-level routers (`moduleScope` = primary / unscoped) and
|
|
190
|
+
* routers nested inside per-route module templates are registered
|
|
191
|
+
* here, flattened into a single route table against the session's
|
|
192
|
+
* `HypenRouter`. The URL is a single string, so nested-router routes
|
|
193
|
+
* share the parent's URL space — authors who want sub-path semantics
|
|
194
|
+
* must spell out the full prefix in their `Route(path: ...)`. On
|
|
195
|
+
* pattern conflicts (multiple routes matching the same path),
|
|
196
|
+
* `ManagedRouter` picks first-match, and the engine emits routers
|
|
197
|
+
* in outer→inner order so primary routes win ties naturally.
|
|
198
|
+
*/
|
|
199
|
+
private autoWireManagedRouter;
|
|
200
|
+
/** Pick the first element name that resolves to a registered module. */
|
|
201
|
+
private pickComponent;
|
|
202
|
+
/** Does the primary module's initial state have a `location` field? */
|
|
203
|
+
private primaryHasLocationKey;
|
|
204
|
+
private setupStreamingRenderCallback;
|
|
205
|
+
private resolveConcurrentConnection;
|
|
206
|
+
private triggerReconnect;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Wrap a Bun `ServerWebSocket` as a `SessionTransport`.
|
|
210
|
+
*
|
|
211
|
+
* Each outgoing message is JSON-serialized. The caller is responsible for
|
|
212
|
+
* calling `session.receive()` on incoming messages and `session.destroy()`
|
|
213
|
+
* on close.
|
|
214
|
+
*/
|
|
215
|
+
export declare function createBunWebSocketTransport(ws: {
|
|
216
|
+
send(data: string): void;
|
|
217
|
+
close(code?: number, reason?: string): void;
|
|
218
|
+
}): SessionTransport;
|
|
219
|
+
/**
|
|
220
|
+
* In-memory transport that buffers outgoing messages and exposes them as
|
|
221
|
+
* an async iterator via `.stream()`. Lets you drive a session from code
|
|
222
|
+
* that prefers `for await` over callbacks — e.g. piping to SSE, HTTP/2
|
|
223
|
+
* server push, or gRPC streaming.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```ts
|
|
227
|
+
* const transport = new AsyncQueueTransport();
|
|
228
|
+
* const session = server.createSession(transport);
|
|
229
|
+
* for await (const msg of transport.stream()) {
|
|
230
|
+
* res.write(`data: ${JSON.stringify(msg)}\n\n`); // SSE
|
|
231
|
+
* }
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
export declare class AsyncQueueTransport implements SessionTransport {
|
|
235
|
+
private queue;
|
|
236
|
+
private waiters;
|
|
237
|
+
private closed;
|
|
238
|
+
send(message: OutgoingMessage): void;
|
|
239
|
+
close(_code?: number, _reason?: string): void;
|
|
240
|
+
/**
|
|
241
|
+
* Async iterator over outgoing messages. Completes once the transport is
|
|
242
|
+
* closed and the queue is drained.
|
|
243
|
+
*/
|
|
244
|
+
stream(): AsyncIterableIterator<OutgoingMessage>;
|
|
245
|
+
}
|