declare module 'redweb' { export { defineHandlerSchema, defineRedwebSchemas } from 'redweb/schema'; export type { SocketSchema, SchemaInput, SchemaOutput, HandlerSchema, RedwebSchemas, MessageOf, HandlerMessage, HandlerInput, SocketHandler, SocketAction } from 'redweb/schema'; import { Application as ExpressApplication, RequestHandler } from 'express'; import { CorsOptions } from 'cors'; import { Server as NodeHttpServer } from 'http'; import { Server as NodeHttpsServer } from 'https'; import { WebSocket, ServerOptions } from 'ws'; import { Buffer } from 'buffer'; import { EventEmitter } from 'events'; /** ─────────────────── HTTP / CORE ─────────────────── */ export type RedWebEncoding = 'json' | 'urlencoded'; export interface RedWebOptions { port?: number; bind?: string; publicPaths?: string[]; services?: Array<{ serviceName: string; method: string; function: RequestHandler }>; listen?: boolean; listenCallback?: () => void; encoding?: RedWebEncoding; ssl?: { key: string; cert: string }; server?: ExpressApplication; corsOptions?: CorsOptions | false; publicOrigin?: string; exposeErrors?: boolean; logger?: RedWebLogger | null; } export type RedWebSocket = WebSocket & { clientKey: string; __redwebClientKey: string; remoteAddress: string; isAssigned: boolean; sendJson(data: unknown): boolean; broadcast(data: unknown): number; context?: RedWebConnectionContext; /** Present on an attached page connection; checks the actual page class and connection lifetime. */ page?(PageClass: new () => Page): Page; joinRoom?(roomId: string): boolean; /** Bounded permission check followed by atomic membership insertion. */ enterRoom?(roomId: string): Promise; leaveRoom?(roomId: string): boolean; roomBroadcast?(roomId: string, data: unknown, options?: { except?: RedWebSocket }): number; createSession?(sessionId: string, data: unknown): boolean; resumeSession?(sessionId: string): unknown | null; publishEvent?(type: string, payload: unknown): Promise; sendEvent?(type: string, payload: unknown, metadata?: ProtocolMetadata): boolean; sendProtocolError?(code: string, message: string, metadata?: ProtocolMetadata): boolean; sendBinaryEvent?(value: unknown): Promise; }; /** Original wire envelope accompanying the schema-validated payload passed to BaseHandler.handle(). */ export interface SocketMessageFrame extends ProtocolMetadata { v?: string; type: string; payload?: unknown; } export interface RedWebConnectionContext extends RequestContext { readonly connectionId: string; readonly principal: unknown; session: unknown | null; metadata: Record; readonly protocol?: Readonly<{ version: string }>; } /** A deliberate, safe command rejection, unlike an unexpected application error. */ export class ClientError extends Error { constructor(message: string); } export interface ClientEvent { type: string; payload: unknown; } export interface ConnectedClientsOptions { /** Must still equal the admitted principal. Returning another identity never transfers a connection. */ identity(context: RedWebConnectionContext): Identity | undefined | false | Promise; page(): new () => Page; project(client: ConnectedClient, room: string, online: readonly Identity[]): Partial | Promise>; authorizationTimeoutMs?: number; projectionTimeoutMs?: number; /** Explicitly classify domain errors safe to display; all other failures remain private. */ reject?(error: unknown): string | undefined; errorState?(message: string): Partial; /** Optional non-HTML client support. Redweb performs the final checked send. */ raw?: { update(state: Partial): ClientEvent | Promise; reject?(message: string): ClientEvent | Promise; }; } export class ConnectedClient { private constructor(); readonly socket: RedWebSocket; readonly identity: Identity; readonly page: Page; readonly rooms: readonly string[]; /** Requires exactly one current room; use rooms for multi-room applications. */ readonly room: string; /** Reserve room capacity, run a synchronous domain commit, and roll back new membership if it rejects. */ join(room: string, commit?: () => unknown): Promise; leave(room: string): boolean; } export class ConnectedClients { constructor(options: ConnectedClientsOptions); get(socket: RedWebSocket): ConnectedClient; refresh(room: string): Promise; bind(): { handler(definition: Definition, callback: ( client: ConnectedClient, payload: import('redweb/schema').MessageOf, ) => unknown): import('redweb/schema').SocketHandler>; }; } export function connectedClients(options: ConnectedClientsOptions): ConnectedClients; export interface AdmissionContext { signal: AbortSignal; networkIdentity: string; route: SocketRoute; } export interface AdmissionOptions { authenticate?: ( request: import('http').IncomingMessage, context: AdmissionContext ) => unknown | false | Promise; origins?: string[] | (( origin: string | undefined, request: import('http').IncomingMessage ) => boolean | Promise); place?: ( principal: unknown, request: import('http').IncomingMessage, context: AdmissionContext ) => string | false | null | undefined | Promise; allowedPlacementOrigins?: string[]; allowInsecurePlacement?: boolean; timeoutMs?: number; } export interface MessageRateLimit { capacity: number; refillPerSecond: number; action?: 'drop' | 'disconnect'; } export interface TransportLimits { maxConnections?: number; maxBufferedBytes?: number; maxPendingMessages?: number; messageRate?: MessageRateLimit; slowConsumerAction?: 'drop' | 'disconnect'; } export interface HeartbeatOptions { intervalMs: number; timeoutMs: number; } export type RoomOptions = { maxRooms?: number; maxMembersPerRoom?: number; maxRoomsPerConnection?: number; maxRoomIdLength?: number; } & ({ authorize?: undefined; authorizationTimeoutMs?: never; maxPendingAuthorizations?: never; maxPendingPerConnection?: never } | { /** Grants subscription until explicit leave/disconnect; not per-message receive authorization. */ authorize: (context: Readonly, roomId: string) => boolean | Promise; authorizationTimeoutMs?: number; maxPendingAuthorizations?: number; maxPendingPerConnection?: number; }); export interface SessionOptions { ttlMs?: number; maxSessions?: number; maxSessionIdLength?: number; sweepIntervalMs?: number; } export interface MetricsSink { increment?(name: string, value: number, attributes: Readonly<{ route: string }>): void | Promise; gauge?(name: string, value: number, attributes: Readonly<{ route: string }>): void | Promise; observe?(name: string, value: number, attributes: Readonly<{ route: string }>): void | Promise; } export interface DistributionEvent { id: string; source: string; type: string; payload: T; } export interface DistributionAdapter { start?(signal?: AbortSignal): void | Promise; publish(channel: string, serializedEvent: string, signal?: AbortSignal): void | Promise; subscribe( channel: string, onEvent: (serializedEvent: string | DistributionEvent) => void, signal?: AbortSignal ): void | (() => void | Promise) | Promise void | Promise)>; unsubscribe?(channel: string, signal?: AbortSignal): void | Promise; close?(signal?: AbortSignal): void | Promise; } export interface DistributionOptions { adapter: DistributionAdapter; channel: string; nodeId?: string; maxEventBytes?: number; maxSeenEvents?: number; seenTtlMs?: number; lifecycleTimeoutMs?: number; publishTimeoutMs?: number; maxConcurrentPublishes?: number; maxConcurrentEvents?: number; required?: boolean; onEvent(event: DistributionEvent, route: SocketRoute): void | Promise; } export interface ProtocolMetadata { requestId?: string; sequence?: number; } export interface ProtocolBinaryCodec { maxBytes?: number; encode(value: unknown, context: RedWebConnectionContext): Buffer | Uint8Array | ArrayBuffer | Promise; decode(buffer: Buffer, context: RedWebConnectionContext): unknown | Promise; } export interface ProtocolOptions { versions: readonly string[]; required?: boolean; queryParameter?: string; header?: string; binary?: false | ProtocolBinaryCodec; } /** ─────────────────── SOCKET SERVER ─────────────────── */ export interface DevelopmentOptions { /** Explicit local-only inspection; rejected when NODE_ENV is production. */ inspect?: boolean; } export interface LiveDevelopmentOptions extends DevelopmentOptions { /** Loopback-only browser refresh. False overrides REDWEB_DEV_REFRESH=1. */ refresh?: boolean; } export interface InspectionList { readonly items: readonly T[]; readonly total: number; readonly truncated: boolean; } export type InspectionSection = { readonly available: false } | ({ readonly available: true } & T); export interface InspectionMembers { readonly className: string; readonly actions: InspectionList; readonly states: InspectionList; } export interface InspectionPage extends InspectionMembers { readonly path: string; readonly live: boolean; readonly shared: boolean; readonly instanceMetadata: 'observed' | 'unobserved'; readonly instances: InspectionList<{ readonly id: number; readonly disposed: boolean; readonly components: InspectionList; }>; } export interface InspectionSession { /** Inspector-local IDs; never page tokens, credentials or socket IDs. */ readonly render: number; readonly instance: number; readonly route: string; readonly status: 'connected' | 'detaching' | 'pending' | 'retained'; readonly reactive: boolean; } export interface InspectionEvent { readonly sequence: number; readonly render: number; readonly route: string; readonly kind: 'state-invalidated' | 'flush-started' | 'flush-completed' | 'flush-superseded' | 'flush-failed'; readonly state?: string; readonly component?: string; readonly affectedOwners?: InspectionList; readonly snapshot?: boolean; readonly dirtyOwners?: InspectionList; readonly durationMs?: number; } export interface DevelopmentSnapshot { readonly schemaVersion: 1; readonly mode: 'development'; readonly pages: InspectionSection<{ readonly registrations: InspectionList; readonly sessions: InspectionList; readonly closing?: boolean; readonly rendering?: number; readonly connections?: Readonly>; }>; readonly sockets: InspectionSection<{ readonly routes: InspectionList<{ readonly path: string; readonly handlers: InspectionList; readonly registeredConnections: number; readonly draining: boolean; readonly rooms: number; readonly sessions: number; }>; readonly pendingUpgrades: number; readonly draining: boolean; }>; /** Flush completion is not a network delivery guarantee. */ readonly history: InspectionList & { readonly limit: number }; } export interface SocketServerOptions { development?: DevelopmentOptions; server?: NodeHttpServer; port?: number; bind?: string; listen?: boolean; routes?: Array SocketRoute>; ssl?: { key: string; cert: string }; fallbackToRoot?: boolean; closeServerOnShutdown?: boolean; listenCallback?: () => void; logger?: RedWebLogger | null; } export interface RedWebLogger { log?(message?: any, ...optionalParams: any[]): void; warn?(message?: any, ...optionalParams: any[]): void; error?(message?: any, ...optionalParams: any[]): void; } /** ─────────────────── ROUTES & HANDLERS ─────────────────── */ export interface SocketRouteConfig { /** Opt-in server-side clients, with RoomRegistry membership and private page projection. */ connections?: ConnectedClients; path: string; handlers: Array BaseHandler>; services?: Array SocketService>; allowDuplicateConnections?: boolean; websocketOptions?: Omit & { /** Closing-handshake deadline in milliseconds (1..2147483647). Default: 5000. Not an idle timeout. */ closeTimeout?: number; }; trustProxy?: boolean; getClientKey?: (request: import('http').IncomingMessage) => string; exposeErrors?: boolean; logger?: RedWebLogger | null; shutdownTimeoutMs?: number; admission?: AdmissionOptions | AdmissionOptions['authenticate']; limits?: TransportLimits; orderedMessages?: boolean; heartbeat?: HeartbeatOptions; rooms?: boolean | RoomOptions; sessions?: boolean | SessionOptions; metrics?: MetricsSink; distribution?: false | DistributionOptions; drainHandlers?: boolean; protocol?: false | ProtocolOptions; maxPendingUpgrades?: number; } /** Socket‑side autonomous service (game loops, timers, etc.) */ export abstract class SocketService { name: string; tickRateMs: number | null; route: SocketRoute; protected _tickHandle: NodeJS.Timeout | null; constructor(name: string, tickRateMs?: number); /** Called once when the route is initialised */ onInit(route: SocketRoute): void; /** Optional recurring tick (respecting tickRateMs) */ onTick?(...args: any[]): unknown; /** Called on process shutdown / route removal */ onShutdown(): void; } export abstract class FixedStepService extends SocketService { maxCatchUpTicks: number; maxRetainedLagMs: number; tick: number; accumulatorMs: number; constructor(name: string, tickRateMs: number, maxCatchUpTicks?: number, maxRetainedLagMs?: number); onTick?(stepMs: number, tick: number): void | Promise; onLagDropped?(droppedLagMs: number): void; pulse(): Promise; onShutdown(): Promise; } /** Message handler, triggered by client messages */ export class BaseHandler { static with(payload: unknown): import('redweb/schema').SocketAction; name: string; constructor(name: string | import('redweb/schema').HandlerSchema); handleMessage( socket: RedWebSocket, message: any ): Promise; validateMessage(message: any, socket: RedWebSocket): boolean | Promise; handle(socket: RedWebSocket, message: any, frame?: SocketMessageFrame): unknown; onMessage(socket: RedWebSocket, message: any, frame?: SocketMessageFrame): unknown; acceptsBinary?(socket: RedWebSocket, buffer: Buffer): boolean; handleBinaryMessage(socket: RedWebSocket, buffer: Buffer): Promise; onBinaryMessage(socket: RedWebSocket, buffer: Buffer): unknown; onInitialContact(socket: RedWebSocket, request?: import('http').IncomingMessage): unknown; } export class SocketRoute { path: string; handlers: BaseHandler[]; services: SocketService[]; clients: Map; rooms: RoomRegistry | null; sessions: SessionRegistry | null; distribution: unknown | null; protocolPolicy: unknown | null; draining: boolean; allowDuplicateConnections?: boolean; websocketOptions?: SocketRouteConfig['websocketOptions']; constructor(config: SocketRouteConfig); addHandler(handler: new () => BaseHandler): boolean; resolveRemoteAddress(request: import('http').IncomingMessage): string; connectionOpenCallback(socket: RedWebSocket, request?: import('http').IncomingMessage): unknown; connectionCloseCallback?(socket: RedWebSocket): unknown; handleMessage(sock: RedWebSocket, data: any): Promise; handleBinaryMessage(socket: RedWebSocket, buffer: Buffer): Promise; beginDrain(): boolean; isReady(): boolean; publish(type: string, payload: unknown): Promise; shutdown(): Promise; } /** ─────────────────── SERVER BASE ─────────────────── */ export class BaseSocketServer { server: NodeHttpServer; routes: SocketRoute[]; ownsServer: boolean; constructor(server: NodeHttpServer, options?: SocketServerOptions); addRoute(route: new () => SocketRoute): SocketRoute; isReady(): boolean; inspect(): DevelopmentSnapshot | null; beginDrain(): boolean; shutdown(): Promise; } /** ─────────────────── REGISTRY & UTIL TYPES ─────────────────── */ export function sendJson(socket: WebSocket, data: unknown): boolean; export class RoomRegistry { constructor(options?: RoomOptions); join(roomId: string, socket: RedWebSocket): boolean; enter(roomId: string, socket: RedWebSocket): Promise; leave(roomId: string, socket: RedWebSocket): boolean; leaveAll(socket: RedWebSocket): number; members(roomId: string): RedWebSocket[]; roomsFor(socket: RedWebSocket): string[]; has(roomId: string, socket: RedWebSocket): boolean; broadcast(roomId: string, data: unknown, options?: { except?: RedWebSocket }): number; broadcastFrom(socket: RedWebSocket, roomId: string, data: unknown, options?: { except?: RedWebSocket }): number; clear(): void; close(): boolean; readonly size: number; } export class SessionRegistry { constructor(options?: SessionOptions, logger?: RedWebLogger | null); create(sessionId: string, data: T, socket?: RedWebSocket): boolean; resume(sessionId: string, socket: RedWebSocket): T | null; release(socket: RedWebSocket): boolean; remove(sessionId: string): boolean; get(sessionId: string): T | undefined; sweep(): void; stop(): void; readonly size: number; } export interface SocketWrapper { socket: RedWebSocket; id: string; send: (type: string, payload: Record) => void; getSanitized?(): Record; } export type SocketMessage = { type: string; [key: string]: any; }; /** Generic event‑driven registry for socket objects */ /** Generic event-driven registry for socket objects */ export class SocketRegistry extends EventEmitter { protected items: T[]; constructor(); /** Adds a socket-bound object to the registry */ add(item: T): void; /** * Removes a socket-bound object by reference or id (default key: 'id') * @param itemOrId Object or ID string * @param by Key name to match against (default is 'id') */ remove(itemOrId: T | string, by?: keyof T): boolean; /** Returns a shallow copy of all registered items */ all(): T[]; /** Returns the number of registered items */ count(): number; } /** ─────────────────── CONCRETE SERVERS ─────────────────── */ export class SocketServer extends BaseSocketServer { constructor(options?: SocketServerOptions); } export class SecureSocketServer extends BaseSocketServer { constructor(options?: SocketServerOptions); } export class BaseHttpServer { app: ExpressApplication; server?: NodeHttpServer; constructor(options?: RedWebOptions); shutdown?(): Promise; } export class HttpServer extends BaseHttpServer { constructor(options?: RedWebOptions); shutdown(): Promise; } export class HttpsServer extends BaseHttpServer { constructor(options?: RedWebOptions); shutdown(): Promise; } /** ─────────────────── LIVE HTML ─────────────────── */ const htmlFragmentBrand: unique symbol; const htmlAttributeBrand: unique symbol; const htmlUrlBrand: unique symbol; export interface HtmlFragment { readonly [htmlFragmentBrand]: true; toString(): string; } export interface HtmlAttribute { readonly [htmlAttributeBrand]: true; } export interface HtmlUrl { readonly [htmlUrlBrand]: true; } export interface RedWebRequest { readonly path: string; readonly url: string; readonly method: string; readonly headers: Readonly>; readonly params: Readonly>; readonly query: Readonly>; readonly body: unknown; get(name: string): string | undefined; } export interface LivePageRequest extends RedWebRequest {} export interface RequestContext { readonly request: RedWebRequest; readonly params: Readonly>; readonly query: Readonly>; readonly body: unknown; readonly principal?: Principal; readonly signal: AbortSignal; } export interface LivePageRequestContext extends RequestContext {} /** The original page request/identity is retained across normal reconnects. */ export interface LivePageConnectionContext extends LivePageRequestContext { socket: RedWebSocket; } export abstract class LivePage { protected readonly _connections: Set; loading?(context: LivePageRequestContext): void | Promise; render?(context: LivePageRequestContext): string | HtmlFragment | Promise; connected?(context: LivePageConnectionContext): void | Promise; disconnected?(context: LivePageConnectionContext): void | Promise; disposed?(): void | Promise; dispose(): Promise; } export type PageOptions = { /** Use a registered custom route for this private live page's typed TSX commands and rendering. */ socket?: new () => SocketRoute; template?: string; css?: string | readonly string[]; live?: boolean; head?: PageHead; cache?: PageCache; layout?: PageLayout; } & ({ scope?: 'connection' | 'shared'; shared?: boolean; authorize?: undefined; authorizationTimeoutMs?: never; } | { scope?: 'connection'; shared?: false; /** Checked before construction/loading, on connection, and before actions/state writes. */ authorize: (context: LivePageRequestContext) => boolean | Promise; authorizationTimeoutMs?: number; }); export type PageLayout = (content: HtmlFragment, context: LivePageRequestContext) => HtmlFragment; export interface PageHead { title?: string; description?: string; canonical?: string; image?: string; robots?: string; } export interface PageCache { maxAge?: number; staleWhileRevalidate?: number; immutable?: boolean; } export interface StateOptions { writable?: boolean; } export interface LiveStateDecorator { (target: object, propertyKey: string): void; (value: undefined, context: ClassFieldDecoratorContext): (this: This, initialValue: Value) => Value; } export interface LiveResourceDecorator extends LiveStateDecorator {} export interface LiveInjectDecorator { (target: object, propertyKey: string): void; (value: undefined, context: ClassFieldDecoratorContext): (this: This, initialValue: Value) => Value; } /** A server-owned keyed publisher for connection-scoped page state. */ export class LiveResource { publish(key: Key, value: Value): number; } export function liveResource(): LiveResource; export interface LiveActionDecorator { (target: object, propertyKey: string, descriptor: PropertyDescriptor): void | PropertyDescriptor; any>( value: Value, context: ClassMethodDecoratorContext ): Value; } /** The validated (possibly transformed) value passed as an action's first argument. */ export type ActionInput = import('redweb/schema').SchemaOutput; export interface ValidatedActionDecorator { any>(target: object, propertyKey: string, descriptor: TypedPropertyDescriptor): void; any>( value: Value, context: ClassMethodDecoratorContext ): Value; } export interface LiveViewDecorator { (target: object, propertyKey: string, descriptor: PropertyDescriptor): void | PropertyDescriptor; HtmlFragment>( value: Value, context: ClassMethodDecoratorContext ): Value; } export function page(path: string, options?: PageOptions): ClassDecorator; export function component(): ClassDecorator; export function component(render: (properties: Props) => HtmlFragment): (properties: Props) => HtmlFragment; export function state(options?: StateOptions): LiveStateDecorator; /** Keeps this state field synchronized with values published for its server-derived key. */ export function resource( source: LiveResource, select: (page: any) => Key | null | undefined ): LiveResourceDecorator; /** Receives an explicit instance from defineApp({ providers }). */ export function inject(provider: string): LiveInjectDecorator; export interface UploadedFile { readonly stream: import('stream').Readable; readonly type: string; readonly name: string | null; } export function upload(options?: { maxBytes?: number; accept?: string | readonly string[] }): LiveActionDecorator; export function action(): LiveActionDecorator; export interface ActionAuthorization { authorize: (context: LivePageConnectionContext, input: Input) => boolean | Promise; /** Bounds permission checks, not application execution; defaults to 5000ms. */ authorizationTimeoutMs?: number; } export function action(options: { input: Schema; /** Bounds input validation, not application execution; defaults to 5000ms. */ validationTimeoutMs?: number; } & (ActionAuthorization> | { authorize?: undefined; authorizationTimeoutMs?: never })): ValidatedActionDecorator>; /** Authorized actions use a fixed (input, context) shape, including buttons without a payload. */ export function action(options: ActionAuthorization): ValidatedActionDecorator; export function view(stateName: string): LiveViewDecorator; export function html(strings: TemplateStringsArray, ...values: unknown[]): HtmlFragment; export function attribute(value: string | number | bigint | boolean): HtmlAttribute; export function url(value: string): HtmlUrl; export function each(items: readonly Item[], render: (item: Item, index: number) => HtmlFragment): HtmlFragment; /** Escaped JS/TS/TSX token markup, including references inside rw-* JSX expressions. */ export function highlightCode(source: string, language: string): HtmlFragment; export function codeBlock(code: unknown, options?: { language?: string; label?: string; /** Defaults to Redweb's JS/TS/TSX highlighter. false keeps escaped plain text. */ highlight?: false | ((source: string, language: string) => HtmlFragment); }): HtmlFragment; export type LivePageClass = new () => object; export interface LiveHtmlServerBaseOptions extends Omit { /** Custom routes share the live-page SocketServer; paths must be unique. */ socketRoutes?: Array SocketRoute>; development?: LiveDevelopmentOptions; pages: readonly LivePageClass[]; templateRoot?: string; livePaths?: { socket?: string; client?: string; runtime?: string; css?: string; /** Same-origin bounded streaming endpoint used by rw-upload and rw-paste. */ upload?: string; }; sessionTtlMs?: number; maxSessions?: number; maxConcurrentRenders?: number; /** Phase-local render/route and final owned-HTTP cleanup bound; defaults to 1000ms, not a total application deadline. */ shutdownTimeoutMs?: number; /** Maximum time Redweb will receive a live page upload stream before cancelling it; defaults to 30000ms. */ uploadTimeoutMs?: number; heartbeat?: HeartbeatOptions; origins?: string[] | ((origin: string | undefined, request: import('http').IncomingMessage) => boolean | Promise); /** Explicit application-owned instances available through @inject fields on pages. */ providers?: Record; } export type LiveHtmlAuthentication = { authenticate(request: import('http').IncomingMessage | import('express').Request): string | number | bigint | boolean | null | undefined | Promise; /** Bounds identity lookup, not external application work; defaults to 5000ms. */ authenticationTimeoutMs?: number; } | { authenticate?: undefined; authenticationTimeoutMs?: never }; export type LiveHtmlServerOptions = LiveHtmlServerBaseOptions & LiveHtmlAuthentication; export type LiveHtmlStartOptions = Omit & LiveHtmlAuthentication; export class LiveHtmlServer { app: ExpressApplication; server: NodeHttpServer | NodeHttpsServer; http: HttpServer | HttpsServer; sockets: SocketServer | null; constructor(options: LiveHtmlServerOptions); /** Revoke matching in-process sessions/renders; credential invalidation remains application-owned. */ revoke(principal: string | number | bigint | true): Promise; /** Local metadata only; null unless development inspection was explicitly enabled. */ inspect(): DevelopmentSnapshot | null; shutdown(): Promise; } export function start( pageOrPages: LivePageClass | readonly LivePageClass[], options?: LiveHtmlStartOptions ): LiveHtmlServer; export interface ApplicationContext { app: ExpressApplication; server: NodeHttpServer | NodeHttpsServer; http: HttpServer | HttpsServer; sockets: SocketServer | null; services: ApplicationService[]; } /** Application-wide resources; route-specific SocketService classes stay on their routes. */ export interface ApplicationService { onInit(app: ApplicationContext, signal: AbortSignal): void | Promise; onShutdown(): void | Promise; } export type ApplicationOptions = Omit & LiveHtmlAuthentication & { pages?: readonly LivePageClass[]; sockets?: ReadonlyArray SocketRoute>; services?: ReadonlyArray ApplicationService>; /** Application-owned instances available to decorated page fields. Redweb never constructs them. */ providers?: Record; httpServices?: RedWebOptions['services']; startupTimeoutMs?: number; /** Install process signal handlers only when run() is called; defaults to true. */ signals?: boolean; }; export class Application { constructor(options?: ApplicationOptions); /** Copied definition; use {...app.options, port: 0} to define an independent test instance. */ readonly options: Readonly; readonly app: ExpressApplication | null; readonly server: NodeHttpServer | NodeHttpsServer | null; readonly http: HttpServer | HttpsServer | null; readonly sockets: SocketServer | null; readonly services: ApplicationService[]; /** Resolves after services initialize and the single HTTP/WS listener is ready. Cannot restart after shutdown. */ run(): Promise; shutdown(): Promise; revoke(principal: string | number | bigint | true): Promise; inspect(): DevelopmentSnapshot | null; } /** Inert until run(); repeated run calls share startup until shutdown; shutdown is idempotent. */ export function defineApp(options?: ApplicationOptions): Application; export interface StaticExportOptions { outDir: string; templateRoot?: string; logger?: RedWebLogger | null; } export interface StaticExportResult { readonly pages: readonly string[]; readonly assets: readonly string[]; } export function exportStatic( pageOrPages: LivePageClass | readonly LivePageClass[], options: StaticExportOptions ): Promise; export interface SiteOptions { origin?: string; css?: string | readonly string[]; head?: PageHead; cache?: PageCache; layout?: PageLayout; } export type SitePageOptions = PageOptions & { live?: false }; export interface SiteExportOptions extends StaticExportOptions { publicDir?: string; } export interface StaticSite { page(path: string, options?: SitePageOptions): ClassDecorator; export( pageOrPages: LivePageClass | readonly LivePageClass[], options: SiteExportOptions ): Promise; } export function defineSite(options?: SiteOptions): StaticSite; export class HtmlRenderer { static template(filePath: string, rootDir: string): string; static stylesheet(filePath: string, rootDir: string): string; static render(source: string, page: object, options?: { live?: boolean }): string; static collection(page: object, name: string, value: unknown): string; static statePayload(name: string, value: unknown, page?: object): { name: string; value: string; html: boolean }; static head(metadata?: PageHead): string; static document( markup: string, config?: (Record & { runtimePath: string }) | null, stylesheets?: string[], metadata?: PageHead ): string; } /** ─────────────────── CONSTANTS ─────────────────── */ export const METHODS: { GET: 'get'; POST: 'post'; PUT: 'put'; PATCH: 'patch'; DELETE: 'delete'; OPTIONS: 'options'; HEAD: 'head'; ALL: 'all'; }; export const ENCODINGS: { json: 'json'; urlencoded: 'urlencoded'; }; export const HTTP_OPTIONS: RedWebOptions; export const SOCKET_OPTIONS: SocketServerOptions; export const ERROR_CODES: typeof import('redweb/client').ERROR_CODES; }