routeflow-api 0.2.1 → 0.2.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/dist/index.cjs CHANGED
@@ -44,6 +44,8 @@ __export(src_exports, {
44
44
  });
45
45
  module.exports = __toCommonJS(src_exports);
46
46
  var import_reflect_metadata = require("reflect-metadata");
47
+ var import_node_fs = require("fs");
48
+ var import_node_path = require("path");
47
49
  var import_fastify = __toESM(require("fastify"), 1);
48
50
 
49
51
  // src/core/decorator/route.ts
@@ -812,6 +814,8 @@ var ReactiveApp = class {
812
814
  options;
813
815
  /** Collected route patterns for reactive endpoints */
814
816
  reactivePatterns = [];
817
+ /** All registered routes (for .routeflow/info.json) */
818
+ registeredRoutes = [];
815
819
  constructor(options) {
816
820
  this.options = {
817
821
  transport: "websocket",
@@ -868,6 +872,7 @@ var ReactiveApp = class {
868
872
  }
869
873
  }
870
874
  });
875
+ this.registeredRoutes.push({ method: routeMeta.method, path: routeMeta.path, reactive: !!reactiveMeta });
871
876
  if (reactiveMeta) {
872
877
  const endpoint = {
873
878
  routePath: routeMeta.path,
@@ -911,6 +916,28 @@ var ReactiveApp = class {
911
916
  console.log(
912
917
  `[RouteFlow] Listening on port ${listenPort} (transport: ${this.options.transport})`
913
918
  );
919
+ this.writeInfo(listenPort);
920
+ }
921
+ writeInfo(port) {
922
+ try {
923
+ const dir = (0, import_node_path.join)(process.cwd(), ".routeflow");
924
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
925
+ (0, import_node_fs.writeFileSync)(
926
+ (0, import_node_path.join)(dir, "info.json"),
927
+ JSON.stringify(
928
+ {
929
+ port,
930
+ transport: this.options.transport,
931
+ adapter: this.options.adapter.constructor.name,
932
+ routes: this.registeredRoutes,
933
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
934
+ },
935
+ null,
936
+ 2
937
+ )
938
+ );
939
+ } catch {
940
+ }
914
941
  }
915
942
  /**
916
943
  * Gracefully shut down the server and disconnect from the database.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/core/decorator/route.ts","../src/core/decorator/reactive.ts","../src/core/errors.ts","../src/core/reactive/engine.ts","../src/core/transport/websocket-transport.ts","../src/core/transport/sse-transport.ts","../src/core/database-support.ts","../src/core/adapter/memory-adapter.ts","../src/core/adapter/polling-adapter.ts"],"sourcesContent":["import 'reflect-metadata'\nimport Fastify, { FastifyInstance } from 'fastify'\nimport type { AppOptions, Context, ReactiveEndpoint } from './core/types.js'\nimport { ROUTE_METADATA, routeFnStore } from './core/decorator/route.js'\nimport { REACTIVE_METADATA, reactiveFnStore } from './core/decorator/reactive.js'\nimport type { RouteMetadata, ReactiveOptions } from './core/types.js'\nimport { ReactiveEngine } from './core/reactive/engine.js'\nimport { WebSocketTransport } from './core/transport/websocket-transport.js'\nimport { SseTransport } from './core/transport/sse-transport.js'\nimport { ReactiveApiError } from './core/errors.js'\n\nexport { Route } from './core/decorator/route.js'\nexport { Reactive } from './core/decorator/reactive.js'\nexport { ReactiveApiError } from './core/errors.js'\nexport {\n SUPPORTED_DATABASES,\n getDatabaseSupport,\n listOfficialDatabases,\n listSupportedDatabases,\n} from './core/database-support.js'\nexport type {\n Context,\n ChangeEvent,\n DatabaseAdapter,\n ReactiveOptions,\n AppOptions,\n HttpMethod,\n} from './core/types.js'\nexport type {\n DatabaseCategory,\n DatabaseKey,\n DatabaseSupportDescriptor,\n DatabaseSupportMode,\n DatabaseSupportTier,\n} from './core/database-support.js'\n\n// Adapters (built-in)\nexport { MemoryAdapter, PollingAdapter } from './core/adapter/index.js'\nexport type {\n PollingAdapterOptions,\n PollingReadContext,\n PollingReadResult,\n} from './core/adapter/index.js'\n\ntype AnyTransport = WebSocketTransport | SseTransport\n\n/**\n * Main application class. Use `createApp()` to instantiate.\n */\nexport class ReactiveApp {\n private readonly fastify: FastifyInstance\n private readonly engine: ReactiveEngine\n private transport: AnyTransport | null = null\n private readonly options: Required<AppOptions>\n /** Collected route patterns for reactive endpoints */\n private readonly reactivePatterns: string[] = []\n\n constructor(options: AppOptions) {\n this.options = {\n transport: 'websocket',\n port: 3000,\n ...options,\n }\n this.fastify = Fastify({ logger: false })\n this.engine = new ReactiveEngine(this.options.adapter)\n\n // Register a global error handler that serialises ReactiveApiError properly\n this.fastify.setErrorHandler((error, _req, reply) => {\n if (error instanceof ReactiveApiError) {\n const status = (error as ReactiveApiError & { statusCode?: number }).statusCode ?? 500\n reply.status(status).send({ error: error.code, message: error.message })\n } else {\n reply.status(500).send({ error: 'INTERNAL_ERROR', message: error.message })\n }\n })\n }\n\n /**\n * Register a controller class. Scans its methods for @Route and @Reactive\n * decorators and registers HTTP routes and reactive endpoints accordingly.\n *\n * @param ControllerClass - A class constructor whose methods may be decorated\n * with @Route and/or @Reactive.\n */\n register(ControllerClass: new () => object): this {\n const instance = new ControllerClass()\n const proto = Object.getPrototypeOf(instance) as object\n\n const methodNames = Object.getOwnPropertyNames(proto).filter(\n (name) =>\n name !== 'constructor' &&\n typeof (proto as Record<string, unknown>)[name] === 'function',\n )\n\n for (const methodName of methodNames) {\n const fn = (proto as Record<string, unknown>)[methodName] as object\n const routeMeta: RouteMetadata | undefined =\n routeFnStore.get(fn) ??\n (Reflect.getMetadata(ROUTE_METADATA, proto, methodName) as RouteMetadata | undefined)\n if (!routeMeta) continue\n\n const reactiveMeta: ReactiveOptions | undefined =\n reactiveFnStore.get(fn) ??\n (Reflect.getMetadata(REACTIVE_METADATA, proto, methodName) as ReactiveOptions | undefined)\n\n const handler = (instance as Record<string, unknown>)[methodName] as (\n ctx: Context,\n ) => Promise<unknown>\n\n // Register HTTP route with Fastify\n this.fastify.route({\n method: routeMeta.method,\n url: routeMeta.path,\n handler: async (req, reply) => {\n const ctx: Context = {\n params: req.params as Record<string, string>,\n query: req.query as Record<string, string>,\n body: req.body,\n headers: req.headers as Record<string, string>,\n }\n try {\n const result = await handler.call(instance, ctx)\n return reply.send(result)\n } catch (err) {\n if (err instanceof ReactiveApiError) throw err\n const msg = err instanceof Error ? err.message : String(err)\n throw new ReactiveApiError('HANDLER_ERROR', msg)\n }\n },\n })\n\n // Register reactive endpoint if @Reactive is present\n if (reactiveMeta) {\n const endpoint: ReactiveEndpoint = {\n routePath: routeMeta.path,\n options: reactiveMeta,\n handler: (ctx: Context) => handler.call(instance, ctx),\n }\n this.engine.registerEndpoint(endpoint)\n this.reactivePatterns.push(routeMeta.path)\n }\n }\n\n return this\n }\n\n /**\n * Access the underlying Fastify instance for supplemental routes such as\n * health checks, static assets, or demo pages.\n */\n getFastify(): FastifyInstance {\n return this.fastify\n }\n\n /**\n * Start the HTTP server.\n * Connects the database adapter before accepting connections.\n *\n * @param port - Override the port set in AppOptions\n */\n async listen(port?: number): Promise<void> {\n const listenPort = port ?? this.options.port\n\n await this.options.adapter.connect()\n\n if (this.options.transport === 'websocket') {\n this.transport = new WebSocketTransport(this.engine, this.reactivePatterns)\n } else if (this.options.transport === 'sse') {\n const sseTransport = new SseTransport(this.engine, this.reactivePatterns)\n sseTransport.register(this.fastify)\n this.transport = sseTransport\n }\n\n await this.fastify.ready()\n\n if (this.transport instanceof WebSocketTransport) {\n this.transport.attach(this.fastify.server)\n }\n\n await this.fastify.listen({ port: listenPort, host: '0.0.0.0' })\n console.log(\n `[RouteFlow] Listening on port ${listenPort} (transport: ${this.options.transport})`,\n )\n }\n\n /**\n * Gracefully shut down the server and disconnect from the database.\n */\n async close(): Promise<void> {\n this.engine.destroy()\n if (this.transport) await this.transport.close()\n await this.fastify.close()\n await this.options.adapter.disconnect()\n }\n}\n\n/**\n * Create a new RouteFlow application.\n *\n * @example\n * ```ts\n * const app = createApp({ adapter: new MemoryAdapter(), port: 3000 })\n * app.register(MyController)\n * await app.listen()\n * ```\n */\nexport function createApp(options: AppOptions): ReactiveApp {\n return new ReactiveApp(options)\n}\n","import type { HttpMethod, RouteMetadata } from '../types.js'\n\n/** Symbol key used to store @Route metadata on a method. */\nexport const ROUTE_METADATA = Symbol('reactive-api:route')\n\n/**\n * Function-keyed store for TC39 (new-style) decorator compat.\n * Legacy decorators use Reflect.defineMetadata; TC39 decorators store here.\n */\nexport const routeFnStore = new WeakMap<object, RouteMetadata>()\n\n/**\n * Registers a class method as an HTTP endpoint.\n *\n * Works with both TypeScript legacy decorators (experimentalDecorators) and\n * TC39 Stage 3 decorators (as used by esbuild/tsx without legacy flag).\n *\n * @param method - HTTP verb\n * @param path - Route path, may include Fastify-style params (e.g. '/users/:id')\n */\nexport function Route(method: HttpMethod, path: string) {\n const metadata: RouteMetadata = { method, path }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return function (target: any, propertyKey: any): void {\n if (typeof target === 'function') {\n // TC39 decorator: target is the method function itself\n routeFnStore.set(target, metadata)\n } else if (typeof propertyKey === 'string') {\n // Legacy decorator: target is the prototype, propertyKey is method name\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const fn = target[propertyKey] as object\n if (fn) routeFnStore.set(fn, metadata)\n Reflect.defineMetadata(ROUTE_METADATA, metadata, target, propertyKey)\n }\n }\n}\n","import type { ReactiveOptions } from '../types.js'\n\n/** Symbol key used to store @Reactive metadata on a method. */\nexport const REACTIVE_METADATA = Symbol('reactive-api:reactive')\n\n/**\n * Function-keyed store for TC39 (new-style) decorator compat.\n */\nexport const reactiveFnStore = new WeakMap<object, ReactiveOptions>()\n\n/**\n * Marks a route handler as reactive — when the watched table(s) change,\n * the handler is re-executed and the result is pushed to all subscribed clients.\n *\n * Must be used together with @Route.\n * Works with both TypeScript legacy decorators and TC39 Stage 3 decorators.\n *\n * @param options - Reactive configuration (watch, filter, debounce)\n */\nexport function Reactive(options: ReactiveOptions) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return function (target: any, propertyKey: any): void {\n if (typeof target === 'function') {\n // TC39 decorator: target is the method function itself\n reactiveFnStore.set(target, options)\n } else if (typeof propertyKey === 'string') {\n // Legacy decorator: target is the prototype, propertyKey is method name\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const fn = target[propertyKey] as object\n if (fn) reactiveFnStore.set(fn, options)\n Reflect.defineMetadata(REACTIVE_METADATA, options, target, propertyKey)\n }\n }\n}\n","/**\n * Base error class for all RouteFlow errors.\n * Always use this instead of plain `Error` throughout the framework.\n */\nexport class ReactiveApiError extends Error {\n /** Machine-readable error code (e.g. 'ADAPTER_NOT_CONNECTED', 'INVALID_ROUTE') */\n readonly code: string\n\n constructor(code: string, message: string) {\n super(message)\n this.name = 'ReactiveApiError'\n this.code = code\n // Restore prototype chain (required when extending built-ins in TS)\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n","import type {\n ChangeEvent,\n Context,\n DatabaseAdapter,\n PushFn,\n ReactiveEndpoint,\n} from '../types.js'\nimport { ReactiveApiError } from '../errors.js'\n\ninterface Subscription {\n /** The concrete path the client subscribed to (e.g. '/orders/123/live') */\n path: string\n /** Context built from the subscribed path */\n ctx: Context\n /** Function to call when a push is ready */\n pushFn: PushFn\n}\n\n/**\n * Core reactive engine.\n *\n * Responsibilities:\n * 1. Holds the registry of @Reactive endpoints\n * 2. Subscribes to the DatabaseAdapter for each watched table\n * 3. On a ChangeEvent, fans out to matching subscribers after applying filters\n * 4. Supports optional per-subscriber debouncing\n */\nexport class ReactiveEngine {\n private readonly endpoints: ReactiveEndpoint[] = []\n /** clientId → Subscription */\n private readonly subscriptions: Map<string, Subscription> = new Map()\n /** table → adapter unsubscribe fn */\n private readonly tableWatchers: Map<string, () => void> = new Map()\n /** \"clientId:path\" → debounce timer id */\n private readonly debounceTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n\n constructor(private readonly adapter: DatabaseAdapter) {}\n\n /**\n * Register a reactive endpoint so the engine can fan-out pushes to subscribers.\n */\n registerEndpoint(endpoint: ReactiveEndpoint): void {\n this.endpoints.push(endpoint)\n\n const tables = Array.isArray(endpoint.options.watch)\n ? endpoint.options.watch\n : [endpoint.options.watch]\n\n for (const table of tables) {\n this.setupTableWatcher(table)\n }\n }\n\n /**\n * Subscribe a WebSocket client to a path.\n * When the watched table(s) change and the filter passes, pushFn is called.\n *\n * @param clientId - Unique identifier for the client connection\n * @param path - The concrete path the client subscribed to\n * @param ctx - Context built from the subscribed path\n * @param pushFn - Callback to deliver data to the client\n */\n subscribe(clientId: string, path: string, ctx: Context, pushFn: PushFn): void {\n this.subscriptions.set(clientId, { path, ctx, pushFn })\n }\n\n /**\n * Remove a client's subscription and clean up any pending debounce timers.\n */\n unsubscribe(clientId: string): void {\n this.subscriptions.delete(clientId)\n\n // Clean up any pending debounce timers for this client\n for (const key of this.debounceTimers.keys()) {\n if (key.startsWith(`${clientId}:`)) {\n clearTimeout(this.debounceTimers.get(key))\n this.debounceTimers.delete(key)\n }\n }\n }\n\n /**\n * Tear down all table watchers. Call this when the app shuts down.\n */\n destroy(): void {\n for (const unsubscribe of this.tableWatchers.values()) {\n unsubscribe()\n }\n this.tableWatchers.clear()\n\n for (const timer of this.debounceTimers.values()) {\n clearTimeout(timer)\n }\n this.debounceTimers.clear()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private setupTableWatcher(table: string): void {\n if (this.tableWatchers.has(table)) return // already watching\n\n const unsubscribe = this.adapter.onChange(table, (event) => {\n this.onChangeEvent(event)\n })\n this.tableWatchers.set(table, unsubscribe)\n }\n\n private onChangeEvent(event: ChangeEvent): void {\n // Find endpoints that watch this table\n const matchingEndpoints = this.endpoints.filter((ep) => {\n const tables = Array.isArray(ep.options.watch)\n ? ep.options.watch\n : [ep.options.watch]\n return tables.includes(event.table)\n })\n\n for (const endpoint of matchingEndpoints) {\n // Find all subscribers that are on this endpoint's route path\n for (const [clientId, sub] of this.subscriptions) {\n if (!pathMatchesPattern(sub.path, endpoint.routePath)) continue\n\n // Apply optional filter\n if (endpoint.options.filter) {\n try {\n if (!endpoint.options.filter(event, sub.ctx)) continue\n } catch (err) {\n // Filter threw — skip this subscriber rather than crashing\n continue\n }\n }\n\n this.schedulePush(clientId, endpoint, sub, event)\n }\n }\n }\n\n private schedulePush(\n clientId: string,\n endpoint: ReactiveEndpoint,\n sub: Subscription,\n _event: ChangeEvent,\n ): void {\n const debounceMs = endpoint.options.debounce\n\n if (debounceMs !== undefined && debounceMs > 0) {\n const timerKey = `${clientId}:${sub.path}`\n const existing = this.debounceTimers.get(timerKey)\n if (existing !== undefined) clearTimeout(existing)\n\n const timer = setTimeout(() => {\n this.debounceTimers.delete(timerKey)\n this.executePush(endpoint, sub)\n }, debounceMs)\n\n this.debounceTimers.set(timerKey, timer)\n } else {\n this.executePush(endpoint, sub)\n }\n }\n\n private executePush(endpoint: ReactiveEndpoint, sub: Subscription): void {\n endpoint.handler(sub.ctx).then(\n (data) => sub.pushFn(sub.path, data),\n (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err)\n throw new ReactiveApiError('HANDLER_ERROR', `Reactive handler failed: ${message}`)\n },\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Path matching utility\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if a concrete path matches a route pattern with named params.\n *\n * pathMatchesPattern('/orders/123/live', '/orders/:userId/live') → true\n * pathMatchesPattern('/orders/123/live', '/items/:id/live') → false\n */\nexport function pathMatchesPattern(concretePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&') // escape regex special chars except *\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)') // :param → capture group\n\n const regex = new RegExp(`^${regexStr}$`)\n return regex.test(concretePath)\n}\n\n/**\n * Extract named path params from a concrete path given a route pattern.\n *\n * extractParams('/orders/123/live', '/orders/:userId/live') → { userId: '123' }\n */\nexport function extractParams(concretePath: string, pattern: string): Record<string, string> {\n const paramNames: string[] = []\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name: string) => {\n paramNames.push(name)\n return '([^/]+)'\n })\n\n const regex = new RegExp(`^${regexStr}$`)\n const match = concretePath.match(regex)\n if (!match) return {}\n\n return Object.fromEntries(paramNames.map((name, i) => [name, match[i + 1]]))\n}\n","import { IncomingMessage, Server as HttpServer } from 'node:http'\nimport { randomUUID } from 'node:crypto'\nimport { WebSocketServer, WebSocket } from 'ws'\nimport type { ReactiveEngine } from '../reactive/engine.js'\nimport { extractParams } from '../reactive/engine.js'\nimport type { Context } from '../types.js'\nimport { ReactiveApiError } from '../errors.js'\n\n/** Message sent by the client to subscribe to a reactive path. */\ninterface SubscribeMessage {\n type: 'subscribe'\n path: string\n /** Optional query params the client wants included in the Context */\n query?: Record<string, string>\n}\n\n/** Message pushed by the server when data changes. */\ninterface UpdateMessage {\n type: 'update'\n path: string\n data: unknown\n}\n\n/** Message sent on error. */\ninterface ErrorMessage {\n type: 'error'\n code: string\n message: string\n}\n\ntype ServerMessage = UpdateMessage | ErrorMessage\n\nfunction isSubscribeMessage(value: unknown): value is SubscribeMessage {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>)['type'] === 'subscribe' &&\n typeof (value as Record<string, unknown>)['path'] === 'string'\n )\n}\n\n/**\n * WebSocket transport layer.\n *\n * Attaches a `ws` server to the underlying Node HTTP server. Clients connect\n * and send a subscribe message; the transport builds a Context and registers\n * the subscription with the ReactiveEngine.\n *\n * Protocol:\n * - Client → Server: `{ \"type\": \"subscribe\", \"path\": \"/orders/123/live\" }`\n * - Server → Client: `{ \"type\": \"update\", \"path\": \"/orders/123/live\", \"data\": [...] }`\n * - Server → Client: `{ \"type\": \"error\", \"code\": \"...\", \"message\": \"...\" }`\n */\nexport class WebSocketTransport {\n private readonly wss: WebSocketServer\n /** Maps clientId → registered route pattern, for param extraction */\n private readonly clientPatterns: Map<string, string> = new Map()\n\n constructor(\n private readonly engine: ReactiveEngine,\n /** All registered route patterns (e.g. ['/orders/:userId/live']) */\n private readonly routePatterns: string[],\n ) {\n // noServer=true so we can attach to Fastify's underlying http.Server manually\n this.wss = new WebSocketServer({ noServer: true })\n this.wss.on('connection', (ws, req) => this.handleConnection(ws, req))\n }\n\n /**\n * Attach to the raw Node.js HTTP server so WebSocket upgrade requests are\n * handled alongside Fastify routes.\n */\n attach(httpServer: HttpServer): void {\n httpServer.on('upgrade', (req, socket, head) => {\n this.wss.handleUpgrade(req, socket, head, (ws) => {\n this.wss.emit('connection', ws, req)\n })\n })\n }\n\n /** Gracefully close all connections. */\n async close(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.wss.close((err) => (err ? reject(err) : resolve()))\n })\n }\n\n // ---------------------------------------------------------------------------\n // Connection handling\n // ---------------------------------------------------------------------------\n\n private handleConnection(ws: WebSocket, _req: IncomingMessage): void {\n const clientId = randomUUID()\n\n ws.on('message', (raw) => {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw.toString())\n } catch {\n this.sendError(ws, 'INVALID_JSON', 'Message must be valid JSON')\n return\n }\n\n if (!isSubscribeMessage(parsed)) {\n this.sendError(ws, 'INVALID_MESSAGE', 'Expected { type: \"subscribe\", path: string }')\n return\n }\n\n this.handleSubscribe(ws, clientId, parsed)\n })\n\n ws.on('close', () => {\n this.engine.unsubscribe(clientId)\n this.clientPatterns.delete(clientId)\n })\n\n ws.on('error', (err) => {\n this.engine.unsubscribe(clientId)\n this.clientPatterns.delete(clientId)\n // ws errors are expected (client disconnect); just log in dev\n if (process.env['NODE_ENV'] !== 'production') {\n console.error('[RouteFlow] WebSocket error:', err.message)\n }\n })\n }\n\n private handleSubscribe(ws: WebSocket, clientId: string, msg: SubscribeMessage): void {\n const { path, query = {} } = msg\n\n // Find the matching route pattern\n const pattern = this.routePatterns.find((p) => this.matchesPattern(path, p))\n\n if (!pattern) {\n this.sendError(ws, 'NO_REACTIVE_ENDPOINT', `No reactive endpoint found for path: ${path}`)\n return\n }\n\n const params = extractParams(path, pattern)\n\n const ctx: Context = {\n params,\n query,\n body: undefined,\n headers: {},\n }\n\n const pushFn = (subscribedPath: string, data: unknown): void => {\n if (ws.readyState !== WebSocket.OPEN) return\n const msg: UpdateMessage = { type: 'update', path: subscribedPath, data }\n ws.send(JSON.stringify(msg))\n }\n\n // Unsubscribe any previous subscription for this client before re-subscribing\n this.engine.unsubscribe(clientId)\n this.clientPatterns.set(clientId, pattern)\n this.engine.subscribe(clientId, path, ctx, pushFn)\n }\n\n private matchesPattern(concretePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)')\n return new RegExp(`^${regexStr}$`).test(concretePath)\n }\n\n private sendError(ws: WebSocket, code: string, message: string): void {\n if (ws.readyState !== WebSocket.OPEN) return\n const msg: ErrorMessage = { type: 'error', code, message }\n ws.send(JSON.stringify(msg))\n }\n}\n","import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'\nimport { randomUUID } from 'node:crypto'\nimport type { ReactiveEngine } from '../reactive/engine.js'\nimport { extractParams } from '../reactive/engine.js'\nimport type { Context } from '../types.js'\nimport { ReactiveApiError } from '../errors.js'\n\n/**\n * Server-Sent Events transport layer.\n *\n * Clients connect via a GET request to `/_sse/subscribe?path=<encodedPath>`.\n * The server sends a stream of `data:` events in the standard SSE format.\n *\n * Event format (one JSON object per `data:` line):\n * ```\n * data: {\"type\":\"update\",\"path\":\"/orders/123/live\",\"data\":[...]}\n *\n * ```\n *\n * Unlike WebSocket, SSE is strictly server-to-client (unidirectional).\n * The subscribed path is passed as a query param on the initial GET request.\n *\n * Advantages over WebSocket in some environments:\n * - Works over plain HTTP/1.1 (no upgrade required)\n * - Automatic reconnection handled natively by the browser `EventSource`\n * - Firewall/proxy friendly\n */\nexport class SseTransport {\n /** clientId → reply (kept open) */\n private readonly connections: Map<string, FastifyReply> = new Map()\n\n constructor(\n private readonly engine: ReactiveEngine,\n private readonly routePatterns: string[],\n ) {}\n\n /**\n * Register the SSE subscription endpoint on the Fastify instance.\n * Must be called before `fastify.listen()`.\n */\n register(fastify: FastifyInstance): void {\n fastify.get('/_sse/subscribe', async (req: FastifyRequest, reply: FastifyReply) => {\n const query = req.query as Record<string, string>\n const path = query['path']\n\n if (!path) {\n throw new ReactiveApiError('SSE_MISSING_PATH', 'Query param \"path\" is required')\n }\n\n const decodedPath = decodeURIComponent(path)\n const pattern = this.routePatterns.find((p) => this.matchesPattern(decodedPath, p))\n\n if (!pattern) {\n reply.code(404)\n throw new ReactiveApiError(\n 'SSE_NO_REACTIVE_ENDPOINT',\n `No reactive endpoint found for path: ${decodedPath}`,\n )\n }\n\n const params = extractParams(decodedPath, pattern)\n const clientQuery = { ...query }\n delete clientQuery['path']\n\n const ctx: Context = {\n params,\n query: clientQuery,\n body: undefined,\n headers: req.headers as Record<string, string>,\n }\n\n const clientId = randomUUID()\n\n // Set SSE headers\n reply.raw.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no', // disable nginx buffering\n })\n\n // Send initial comment to establish connection\n reply.raw.write(': connected\\n\\n')\n\n this.connections.set(clientId, reply)\n\n const pushFn = (subscribedPath: string, data: unknown): void => {\n if (reply.raw.destroyed) return\n const payload = JSON.stringify({ type: 'update', path: subscribedPath, data })\n reply.raw.write(`data: ${payload}\\n\\n`)\n }\n\n this.engine.subscribe(clientId, decodedPath, ctx, pushFn)\n\n // Cleanup when client disconnects\n req.raw.on('close', () => {\n this.engine.unsubscribe(clientId)\n this.connections.delete(clientId)\n if (!reply.raw.destroyed) reply.raw.end()\n })\n\n // Keep the connection open — Fastify needs this to not auto-close\n await new Promise<void>((resolve) => {\n req.raw.on('close', resolve)\n req.raw.on('error', resolve)\n })\n })\n }\n\n /** Close all open SSE connections. */\n async close(): Promise<void> {\n for (const reply of this.connections.values()) {\n if (!reply.raw.destroyed) reply.raw.end()\n }\n this.connections.clear()\n }\n\n private matchesPattern(concretePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)')\n return new RegExp(`^${regexStr}$`).test(concretePath)\n }\n}\n","export type DatabaseCategory =\n | 'rdbms'\n | 'nosql'\n | 'time-series'\n | 'search-engine'\n | 'cloud-data-warehouse'\n | 'in-memory'\n | 'newsql'\n\nexport type DatabaseSupportMode = 'native-adapter' | 'polling-adapter' | 'external-cdc-bridge'\nexport type DatabaseSupportTier = 'official' | 'experimental'\n\nexport type DatabaseKey =\n | 'postgresql'\n | 'mysql'\n | 'mariadb'\n | 'oracle-db'\n | 'ms-sql-server'\n | 'sqlite'\n | 'mongodb'\n | 'redis'\n | 'cassandra'\n | 'dynamodb'\n | 'neo4j'\n | 'elasticsearch'\n | 'hbase'\n | 'couchdb'\n | 'influxdb'\n | 'timescaledb'\n | 'prometheus'\n | 'opensearch'\n | 'solr'\n | 'snowflake'\n | 'bigquery'\n | 'redshift'\n | 'azure-synapse'\n | 'memcached'\n | 'voltdb'\n | 'cockroachdb'\n | 'tidb'\n | 'spanner'\n\nexport interface DatabaseSupportDescriptor {\n key: DatabaseKey\n name: string\n aliases: string[]\n categories: DatabaseCategory[]\n supportedModes: DatabaseSupportMode[]\n tier: DatabaseSupportTier\n}\n\nexport const SUPPORTED_DATABASES: readonly DatabaseSupportDescriptor[] = [\n {\n key: 'postgresql',\n name: 'PostgreSQL',\n aliases: ['postgres', 'psql'],\n categories: ['rdbms'],\n supportedModes: ['native-adapter', 'polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'mysql',\n name: 'MySQL',\n aliases: [],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'mariadb',\n name: 'MariaDB',\n aliases: [],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'oracle-db',\n name: 'Oracle DB',\n aliases: ['oracle', 'oracle database'],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'ms-sql-server',\n name: 'MS SQL Server',\n aliases: ['sql server', 'mssql', 'ms sql'],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'sqlite',\n name: 'SQLite',\n aliases: [],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter'],\n tier: 'experimental',\n },\n {\n key: 'mongodb',\n name: 'MongoDB',\n aliases: ['mongo'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'redis',\n name: 'Redis',\n aliases: [],\n categories: ['nosql', 'in-memory'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'cassandra',\n name: 'Cassandra',\n aliases: ['apache cassandra'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'dynamodb',\n name: 'DynamoDB',\n aliases: ['dynamo'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'neo4j',\n name: 'Neo4j',\n aliases: [],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'elasticsearch',\n name: 'Elasticsearch',\n aliases: ['elastic'],\n categories: ['nosql', 'search-engine'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'hbase',\n name: 'HBase',\n aliases: ['apache hbase'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'couchdb',\n name: 'CouchDB',\n aliases: ['apache couchdb'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'influxdb',\n name: 'InfluxDB',\n aliases: ['influx'],\n categories: ['time-series'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'timescaledb',\n name: 'TimescaleDB',\n aliases: ['timescale'],\n categories: ['time-series', 'rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'prometheus',\n name: 'Prometheus',\n aliases: ['prom'],\n categories: ['time-series'],\n supportedModes: ['polling-adapter'],\n tier: 'experimental',\n },\n {\n key: 'opensearch',\n name: 'OpenSearch',\n aliases: [],\n categories: ['search-engine'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'solr',\n name: 'Solr',\n aliases: ['apache solr'],\n categories: ['search-engine'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'snowflake',\n name: 'Snowflake',\n aliases: [],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'bigquery',\n name: 'BigQuery',\n aliases: ['google bigquery'],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'redshift',\n name: 'Redshift',\n aliases: ['amazon redshift'],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'azure-synapse',\n name: 'Azure Synapse',\n aliases: ['synapse', 'azure synapse analytics'],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'memcached',\n name: 'Memcached',\n aliases: [],\n categories: ['in-memory'],\n supportedModes: ['polling-adapter'],\n tier: 'experimental',\n },\n {\n key: 'voltdb',\n name: 'VoltDB',\n aliases: [],\n categories: ['in-memory'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'cockroachdb',\n name: 'CockroachDB',\n aliases: ['cockroach'],\n categories: ['newsql', 'rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'tidb',\n name: 'TiDB',\n aliases: [],\n categories: ['newsql', 'rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'spanner',\n name: 'Spanner',\n aliases: ['google spanner', 'cloud spanner'],\n categories: ['newsql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n] as const\n\nfunction normaliseDatabaseName(value: string): string {\n return value.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim()\n}\n\nexport function getDatabaseSupport(name: string): DatabaseSupportDescriptor | undefined {\n const normalised = normaliseDatabaseName(name)\n\n return SUPPORTED_DATABASES.find((database) => {\n if (normaliseDatabaseName(database.name) === normalised) return true\n if (normaliseDatabaseName(database.key) === normalised) return true\n\n return database.aliases.some((alias) => normaliseDatabaseName(alias) === normalised)\n })\n}\n\nexport function listSupportedDatabases(\n category?: DatabaseCategory,\n): readonly DatabaseSupportDescriptor[] {\n if (!category) return SUPPORTED_DATABASES\n return SUPPORTED_DATABASES.filter((database) => database.categories.includes(category))\n}\n\nexport function listOfficialDatabases(\n category?: DatabaseCategory,\n): readonly DatabaseSupportDescriptor[] {\n const databases = listSupportedDatabases(category)\n return databases.filter((database) => database.tier === 'official')\n}\n","import type { ChangeEvent, DatabaseAdapter } from '../types.js'\n\n/**\n * In-memory database adapter for testing and local development.\n * No real database connection is needed — changes are triggered manually via `emit()`.\n *\n * @example\n * ```ts\n * const adapter = new MemoryAdapter()\n * await adapter.connect()\n *\n * adapter.onChange('orders', (event) => console.log(event))\n *\n * adapter.emit('orders', { operation: 'INSERT', newRow: { id: 1 }, oldRow: null })\n * ```\n */\nexport class MemoryAdapter implements DatabaseAdapter {\n private readonly listeners: Map<string, Set<(event: ChangeEvent) => void>> = new Map()\n private connected = false\n\n /** No-op — MemoryAdapter requires no real connection. */\n async connect(): Promise<void> {\n this.connected = true\n }\n\n /** No-op — clears all listeners on disconnect. */\n async disconnect(): Promise<void> {\n this.listeners.clear()\n this.connected = false\n }\n\n /**\n * Register a listener for changes on a specific table.\n * @returns An unsubscribe function.\n */\n onChange(table: string, callback: (event: ChangeEvent) => void): () => void {\n if (!this.listeners.has(table)) {\n this.listeners.set(table, new Set())\n }\n this.listeners.get(table)!.add(callback)\n\n return () => {\n this.listeners.get(table)?.delete(callback)\n }\n }\n\n /**\n * Manually emit a change event on a table.\n * Useful in tests and examples to simulate DB changes without a real database.\n *\n * @param table - Table name to emit the event on\n * @param event - Change event data (table and timestamp are filled in automatically)\n */\n emit(table: string, event: Omit<ChangeEvent, 'table' | 'timestamp'>): void {\n const fullEvent: ChangeEvent = {\n ...event,\n table,\n timestamp: Date.now(),\n }\n\n const callbacks = this.listeners.get(table)\n if (!callbacks) return\n\n for (const cb of callbacks) {\n cb(fullEvent)\n }\n }\n\n /** Returns true if connect() has been called and disconnect() has not. */\n get isConnected(): boolean {\n return this.connected\n }\n}\n","import type { ChangeEvent, DatabaseAdapter } from '../types.js'\n\nexport interface PollingReadResult<T = unknown, TCursor = unknown> {\n events: Array<Omit<ChangeEvent<T>, 'table' | 'timestamp'> & Partial<Pick<ChangeEvent<T>, 'table' | 'timestamp'>>>\n cursor?: TCursor\n}\n\nexport interface PollingReadContext<TCursor = unknown> {\n cursor: TCursor | undefined\n table: string\n}\n\nexport interface PollingAdapterOptions<TCursor = unknown> {\n intervalMs?: number\n now?: () => number\n onError?: (error: unknown, context: { table: string }) => void\n readChanges: (\n context: PollingReadContext<TCursor>,\n ) => Promise<PollingReadResult<unknown, TCursor>>\n}\n\n/**\n * Generic polling-based adapter for databases without a native RouteFlow adapter yet.\n * It works with any backend as long as callers can periodically read a change feed.\n */\nexport class PollingAdapter<TCursor = unknown> implements DatabaseAdapter {\n private readonly listeners: Map<string, Set<(event: ChangeEvent) => void>> = new Map()\n private readonly cursors: Map<string, TCursor | undefined> = new Map()\n private readonly timers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n private readonly activeTables: Set<string> = new Set()\n private readonly intervalMs: number\n private readonly now: () => number\n private readonly readChanges: PollingAdapterOptions<TCursor>['readChanges']\n private readonly onError?: PollingAdapterOptions<TCursor>['onError']\n private connected = false\n\n constructor(options: PollingAdapterOptions<TCursor>) {\n this.intervalMs = options.intervalMs ?? 1_000\n this.now = options.now ?? (() => Date.now())\n this.readChanges = options.readChanges\n this.onError = options.onError\n }\n\n async connect(): Promise<void> {\n this.connected = true\n\n for (const table of this.listeners.keys()) {\n this.ensurePolling(table)\n }\n }\n\n async disconnect(): Promise<void> {\n this.connected = false\n\n for (const timer of this.timers.values()) {\n clearTimeout(timer)\n }\n\n this.timers.clear()\n this.activeTables.clear()\n }\n\n onChange(table: string, callback: (event: ChangeEvent) => void): () => void {\n if (!this.listeners.has(table)) {\n this.listeners.set(table, new Set())\n }\n\n this.listeners.get(table)!.add(callback)\n this.ensurePolling(table)\n\n return () => {\n const callbacks = this.listeners.get(table)\n if (!callbacks) return\n\n callbacks.delete(callback)\n\n if (callbacks.size === 0) {\n this.listeners.delete(table)\n this.stopPolling(table)\n }\n }\n }\n\n private ensurePolling(table: string): void {\n if (!this.connected || this.activeTables.has(table)) return\n\n this.activeTables.add(table)\n void this.poll(table)\n }\n\n private stopPolling(table: string): void {\n const timer = this.timers.get(table)\n if (timer) clearTimeout(timer)\n\n this.timers.delete(table)\n this.activeTables.delete(table)\n this.cursors.delete(table)\n }\n\n private scheduleNext(table: string): void {\n if (!this.connected || !this.listeners.has(table)) {\n this.stopPolling(table)\n return\n }\n\n const timer = setTimeout(() => {\n void this.poll(table)\n }, this.intervalMs)\n\n this.timers.set(table, timer)\n }\n\n private async poll(table: string): Promise<void> {\n if (!this.connected || !this.listeners.has(table)) {\n this.stopPolling(table)\n return\n }\n\n try {\n const result = await this.readChanges({\n table,\n cursor: this.cursors.get(table),\n })\n\n this.cursors.set(table, result.cursor)\n\n for (const event of result.events) {\n this.emit(table, event)\n }\n } catch (error) {\n this.onError?.(error, { table })\n } finally {\n this.activeTables.delete(table)\n this.scheduleNext(table)\n }\n }\n\n private emit(\n defaultTable: string,\n event: Omit<ChangeEvent, 'table' | 'timestamp'> & Partial<Pick<ChangeEvent, 'table' | 'timestamp'>>,\n ): void {\n const fullEvent: ChangeEvent = {\n ...event,\n table: event.table ?? defaultTable,\n timestamp: event.timestamp ?? this.now(),\n }\n\n const callbacks = this.listeners.get(fullEvent.table)\n if (!callbacks) return\n\n for (const callback of callbacks) {\n callback(fullEvent)\n }\n }\n}\n"],"mappings":"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","names":["msg","import_node_crypto","Fastify"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/core/decorator/route.ts","../src/core/decorator/reactive.ts","../src/core/errors.ts","../src/core/reactive/engine.ts","../src/core/transport/websocket-transport.ts","../src/core/transport/sse-transport.ts","../src/core/database-support.ts","../src/core/adapter/memory-adapter.ts","../src/core/adapter/polling-adapter.ts"],"sourcesContent":["import 'reflect-metadata'\nimport { writeFileSync, mkdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport Fastify, { FastifyInstance } from 'fastify'\nimport type { AppOptions, Context, ReactiveEndpoint } from './core/types.js'\nimport { ROUTE_METADATA, routeFnStore } from './core/decorator/route.js'\nimport { REACTIVE_METADATA, reactiveFnStore } from './core/decorator/reactive.js'\nimport type { RouteMetadata, ReactiveOptions } from './core/types.js'\nimport { ReactiveEngine } from './core/reactive/engine.js'\nimport { WebSocketTransport } from './core/transport/websocket-transport.js'\nimport { SseTransport } from './core/transport/sse-transport.js'\nimport { ReactiveApiError } from './core/errors.js'\n\nexport { Route } from './core/decorator/route.js'\nexport { Reactive } from './core/decorator/reactive.js'\nexport { ReactiveApiError } from './core/errors.js'\nexport {\n SUPPORTED_DATABASES,\n getDatabaseSupport,\n listOfficialDatabases,\n listSupportedDatabases,\n} from './core/database-support.js'\nexport type {\n Context,\n ChangeEvent,\n DatabaseAdapter,\n ReactiveOptions,\n AppOptions,\n HttpMethod,\n} from './core/types.js'\nexport type {\n DatabaseCategory,\n DatabaseKey,\n DatabaseSupportDescriptor,\n DatabaseSupportMode,\n DatabaseSupportTier,\n} from './core/database-support.js'\n\n// Adapters (built-in)\nexport { MemoryAdapter, PollingAdapter } from './core/adapter/index.js'\nexport type {\n PollingAdapterOptions,\n PollingReadContext,\n PollingReadResult,\n} from './core/adapter/index.js'\n\ntype AnyTransport = WebSocketTransport | SseTransport\n\n/**\n * Main application class. Use `createApp()` to instantiate.\n */\nexport class ReactiveApp {\n private readonly fastify: FastifyInstance\n private readonly engine: ReactiveEngine\n private transport: AnyTransport | null = null\n private readonly options: Required<AppOptions>\n /** Collected route patterns for reactive endpoints */\n private readonly reactivePatterns: string[] = []\n /** All registered routes (for .routeflow/info.json) */\n private readonly registeredRoutes: Array<{ method: string; path: string; reactive: boolean }> = []\n\n constructor(options: AppOptions) {\n this.options = {\n transport: 'websocket',\n port: 3000,\n ...options,\n }\n this.fastify = Fastify({ logger: false })\n this.engine = new ReactiveEngine(this.options.adapter)\n\n // Register a global error handler that serialises ReactiveApiError properly\n this.fastify.setErrorHandler((error, _req, reply) => {\n if (error instanceof ReactiveApiError) {\n const status = (error as ReactiveApiError & { statusCode?: number }).statusCode ?? 500\n reply.status(status).send({ error: error.code, message: error.message })\n } else {\n reply.status(500).send({ error: 'INTERNAL_ERROR', message: error.message })\n }\n })\n }\n\n /**\n * Register a controller class. Scans its methods for @Route and @Reactive\n * decorators and registers HTTP routes and reactive endpoints accordingly.\n *\n * @param ControllerClass - A class constructor whose methods may be decorated\n * with @Route and/or @Reactive.\n */\n register(ControllerClass: new () => object): this {\n const instance = new ControllerClass()\n const proto = Object.getPrototypeOf(instance) as object\n\n const methodNames = Object.getOwnPropertyNames(proto).filter(\n (name) =>\n name !== 'constructor' &&\n typeof (proto as Record<string, unknown>)[name] === 'function',\n )\n\n for (const methodName of methodNames) {\n const fn = (proto as Record<string, unknown>)[methodName] as object\n const routeMeta: RouteMetadata | undefined =\n routeFnStore.get(fn) ??\n (Reflect.getMetadata(ROUTE_METADATA, proto, methodName) as RouteMetadata | undefined)\n if (!routeMeta) continue\n\n const reactiveMeta: ReactiveOptions | undefined =\n reactiveFnStore.get(fn) ??\n (Reflect.getMetadata(REACTIVE_METADATA, proto, methodName) as ReactiveOptions | undefined)\n\n const handler = (instance as Record<string, unknown>)[methodName] as (\n ctx: Context,\n ) => Promise<unknown>\n\n // Register HTTP route with Fastify\n this.fastify.route({\n method: routeMeta.method,\n url: routeMeta.path,\n handler: async (req, reply) => {\n const ctx: Context = {\n params: req.params as Record<string, string>,\n query: req.query as Record<string, string>,\n body: req.body,\n headers: req.headers as Record<string, string>,\n }\n try {\n const result = await handler.call(instance, ctx)\n return reply.send(result)\n } catch (err) {\n if (err instanceof ReactiveApiError) throw err\n const msg = err instanceof Error ? err.message : String(err)\n throw new ReactiveApiError('HANDLER_ERROR', msg)\n }\n },\n })\n\n this.registeredRoutes.push({ method: routeMeta.method, path: routeMeta.path, reactive: !!reactiveMeta })\n\n // Register reactive endpoint if @Reactive is present\n if (reactiveMeta) {\n const endpoint: ReactiveEndpoint = {\n routePath: routeMeta.path,\n options: reactiveMeta,\n handler: (ctx: Context) => handler.call(instance, ctx),\n }\n this.engine.registerEndpoint(endpoint)\n this.reactivePatterns.push(routeMeta.path)\n }\n }\n\n return this\n }\n\n /**\n * Access the underlying Fastify instance for supplemental routes such as\n * health checks, static assets, or demo pages.\n */\n getFastify(): FastifyInstance {\n return this.fastify\n }\n\n /**\n * Start the HTTP server.\n * Connects the database adapter before accepting connections.\n *\n * @param port - Override the port set in AppOptions\n */\n async listen(port?: number): Promise<void> {\n const listenPort = port ?? this.options.port\n\n await this.options.adapter.connect()\n\n if (this.options.transport === 'websocket') {\n this.transport = new WebSocketTransport(this.engine, this.reactivePatterns)\n } else if (this.options.transport === 'sse') {\n const sseTransport = new SseTransport(this.engine, this.reactivePatterns)\n sseTransport.register(this.fastify)\n this.transport = sseTransport\n }\n\n await this.fastify.ready()\n\n if (this.transport instanceof WebSocketTransport) {\n this.transport.attach(this.fastify.server)\n }\n\n await this.fastify.listen({ port: listenPort, host: '0.0.0.0' })\n console.log(\n `[RouteFlow] Listening on port ${listenPort} (transport: ${this.options.transport})`,\n )\n\n this.writeInfo(listenPort)\n }\n\n private writeInfo(port: number): void {\n try {\n const dir = join(process.cwd(), '.routeflow')\n mkdirSync(dir, { recursive: true })\n writeFileSync(\n join(dir, 'info.json'),\n JSON.stringify(\n {\n port,\n transport: this.options.transport,\n adapter: this.options.adapter.constructor.name,\n routes: this.registeredRoutes,\n startedAt: new Date().toISOString(),\n },\n null,\n 2,\n ),\n )\n } catch {\n // 저장 실패 시 서버 동작에 영향 없음\n }\n }\n\n /**\n * Gracefully shut down the server and disconnect from the database.\n */\n async close(): Promise<void> {\n this.engine.destroy()\n if (this.transport) await this.transport.close()\n await this.fastify.close()\n await this.options.adapter.disconnect()\n }\n}\n\n/**\n * Create a new RouteFlow application.\n *\n * @example\n * ```ts\n * const app = createApp({ adapter: new MemoryAdapter(), port: 3000 })\n * app.register(MyController)\n * await app.listen()\n * ```\n */\nexport function createApp(options: AppOptions): ReactiveApp {\n return new ReactiveApp(options)\n}\n","import type { HttpMethod, RouteMetadata } from '../types.js'\n\n/** Symbol key used to store @Route metadata on a method. */\nexport const ROUTE_METADATA = Symbol('reactive-api:route')\n\n/**\n * Function-keyed store for TC39 (new-style) decorator compat.\n * Legacy decorators use Reflect.defineMetadata; TC39 decorators store here.\n */\nexport const routeFnStore = new WeakMap<object, RouteMetadata>()\n\n/**\n * Registers a class method as an HTTP endpoint.\n *\n * Works with both TypeScript legacy decorators (experimentalDecorators) and\n * TC39 Stage 3 decorators (as used by esbuild/tsx without legacy flag).\n *\n * @param method - HTTP verb\n * @param path - Route path, may include Fastify-style params (e.g. '/users/:id')\n */\nexport function Route(method: HttpMethod, path: string) {\n const metadata: RouteMetadata = { method, path }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return function (target: any, propertyKey: any): void {\n if (typeof target === 'function') {\n // TC39 decorator: target is the method function itself\n routeFnStore.set(target, metadata)\n } else if (typeof propertyKey === 'string') {\n // Legacy decorator: target is the prototype, propertyKey is method name\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const fn = target[propertyKey] as object\n if (fn) routeFnStore.set(fn, metadata)\n Reflect.defineMetadata(ROUTE_METADATA, metadata, target, propertyKey)\n }\n }\n}\n","import type { ReactiveOptions } from '../types.js'\n\n/** Symbol key used to store @Reactive metadata on a method. */\nexport const REACTIVE_METADATA = Symbol('reactive-api:reactive')\n\n/**\n * Function-keyed store for TC39 (new-style) decorator compat.\n */\nexport const reactiveFnStore = new WeakMap<object, ReactiveOptions>()\n\n/**\n * Marks a route handler as reactive — when the watched table(s) change,\n * the handler is re-executed and the result is pushed to all subscribed clients.\n *\n * Must be used together with @Route.\n * Works with both TypeScript legacy decorators and TC39 Stage 3 decorators.\n *\n * @param options - Reactive configuration (watch, filter, debounce)\n */\nexport function Reactive(options: ReactiveOptions) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return function (target: any, propertyKey: any): void {\n if (typeof target === 'function') {\n // TC39 decorator: target is the method function itself\n reactiveFnStore.set(target, options)\n } else if (typeof propertyKey === 'string') {\n // Legacy decorator: target is the prototype, propertyKey is method name\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const fn = target[propertyKey] as object\n if (fn) reactiveFnStore.set(fn, options)\n Reflect.defineMetadata(REACTIVE_METADATA, options, target, propertyKey)\n }\n }\n}\n","/**\n * Base error class for all RouteFlow errors.\n * Always use this instead of plain `Error` throughout the framework.\n */\nexport class ReactiveApiError extends Error {\n /** Machine-readable error code (e.g. 'ADAPTER_NOT_CONNECTED', 'INVALID_ROUTE') */\n readonly code: string\n\n constructor(code: string, message: string) {\n super(message)\n this.name = 'ReactiveApiError'\n this.code = code\n // Restore prototype chain (required when extending built-ins in TS)\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n","import type {\n ChangeEvent,\n Context,\n DatabaseAdapter,\n PushFn,\n ReactiveEndpoint,\n} from '../types.js'\nimport { ReactiveApiError } from '../errors.js'\n\ninterface Subscription {\n /** The concrete path the client subscribed to (e.g. '/orders/123/live') */\n path: string\n /** Context built from the subscribed path */\n ctx: Context\n /** Function to call when a push is ready */\n pushFn: PushFn\n}\n\n/**\n * Core reactive engine.\n *\n * Responsibilities:\n * 1. Holds the registry of @Reactive endpoints\n * 2. Subscribes to the DatabaseAdapter for each watched table\n * 3. On a ChangeEvent, fans out to matching subscribers after applying filters\n * 4. Supports optional per-subscriber debouncing\n */\nexport class ReactiveEngine {\n private readonly endpoints: ReactiveEndpoint[] = []\n /** clientId → Subscription */\n private readonly subscriptions: Map<string, Subscription> = new Map()\n /** table → adapter unsubscribe fn */\n private readonly tableWatchers: Map<string, () => void> = new Map()\n /** \"clientId:path\" → debounce timer id */\n private readonly debounceTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n\n constructor(private readonly adapter: DatabaseAdapter) {}\n\n /**\n * Register a reactive endpoint so the engine can fan-out pushes to subscribers.\n */\n registerEndpoint(endpoint: ReactiveEndpoint): void {\n this.endpoints.push(endpoint)\n\n const tables = Array.isArray(endpoint.options.watch)\n ? endpoint.options.watch\n : [endpoint.options.watch]\n\n for (const table of tables) {\n this.setupTableWatcher(table)\n }\n }\n\n /**\n * Subscribe a WebSocket client to a path.\n * When the watched table(s) change and the filter passes, pushFn is called.\n *\n * @param clientId - Unique identifier for the client connection\n * @param path - The concrete path the client subscribed to\n * @param ctx - Context built from the subscribed path\n * @param pushFn - Callback to deliver data to the client\n */\n subscribe(clientId: string, path: string, ctx: Context, pushFn: PushFn): void {\n this.subscriptions.set(clientId, { path, ctx, pushFn })\n }\n\n /**\n * Remove a client's subscription and clean up any pending debounce timers.\n */\n unsubscribe(clientId: string): void {\n this.subscriptions.delete(clientId)\n\n // Clean up any pending debounce timers for this client\n for (const key of this.debounceTimers.keys()) {\n if (key.startsWith(`${clientId}:`)) {\n clearTimeout(this.debounceTimers.get(key))\n this.debounceTimers.delete(key)\n }\n }\n }\n\n /**\n * Tear down all table watchers. Call this when the app shuts down.\n */\n destroy(): void {\n for (const unsubscribe of this.tableWatchers.values()) {\n unsubscribe()\n }\n this.tableWatchers.clear()\n\n for (const timer of this.debounceTimers.values()) {\n clearTimeout(timer)\n }\n this.debounceTimers.clear()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private setupTableWatcher(table: string): void {\n if (this.tableWatchers.has(table)) return // already watching\n\n const unsubscribe = this.adapter.onChange(table, (event) => {\n this.onChangeEvent(event)\n })\n this.tableWatchers.set(table, unsubscribe)\n }\n\n private onChangeEvent(event: ChangeEvent): void {\n // Find endpoints that watch this table\n const matchingEndpoints = this.endpoints.filter((ep) => {\n const tables = Array.isArray(ep.options.watch)\n ? ep.options.watch\n : [ep.options.watch]\n return tables.includes(event.table)\n })\n\n for (const endpoint of matchingEndpoints) {\n // Find all subscribers that are on this endpoint's route path\n for (const [clientId, sub] of this.subscriptions) {\n if (!pathMatchesPattern(sub.path, endpoint.routePath)) continue\n\n // Apply optional filter\n if (endpoint.options.filter) {\n try {\n if (!endpoint.options.filter(event, sub.ctx)) continue\n } catch (err) {\n // Filter threw — skip this subscriber rather than crashing\n continue\n }\n }\n\n this.schedulePush(clientId, endpoint, sub, event)\n }\n }\n }\n\n private schedulePush(\n clientId: string,\n endpoint: ReactiveEndpoint,\n sub: Subscription,\n _event: ChangeEvent,\n ): void {\n const debounceMs = endpoint.options.debounce\n\n if (debounceMs !== undefined && debounceMs > 0) {\n const timerKey = `${clientId}:${sub.path}`\n const existing = this.debounceTimers.get(timerKey)\n if (existing !== undefined) clearTimeout(existing)\n\n const timer = setTimeout(() => {\n this.debounceTimers.delete(timerKey)\n this.executePush(endpoint, sub)\n }, debounceMs)\n\n this.debounceTimers.set(timerKey, timer)\n } else {\n this.executePush(endpoint, sub)\n }\n }\n\n private executePush(endpoint: ReactiveEndpoint, sub: Subscription): void {\n endpoint.handler(sub.ctx).then(\n (data) => sub.pushFn(sub.path, data),\n (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err)\n throw new ReactiveApiError('HANDLER_ERROR', `Reactive handler failed: ${message}`)\n },\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Path matching utility\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if a concrete path matches a route pattern with named params.\n *\n * pathMatchesPattern('/orders/123/live', '/orders/:userId/live') → true\n * pathMatchesPattern('/orders/123/live', '/items/:id/live') → false\n */\nexport function pathMatchesPattern(concretePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&') // escape regex special chars except *\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)') // :param → capture group\n\n const regex = new RegExp(`^${regexStr}$`)\n return regex.test(concretePath)\n}\n\n/**\n * Extract named path params from a concrete path given a route pattern.\n *\n * extractParams('/orders/123/live', '/orders/:userId/live') → { userId: '123' }\n */\nexport function extractParams(concretePath: string, pattern: string): Record<string, string> {\n const paramNames: string[] = []\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name: string) => {\n paramNames.push(name)\n return '([^/]+)'\n })\n\n const regex = new RegExp(`^${regexStr}$`)\n const match = concretePath.match(regex)\n if (!match) return {}\n\n return Object.fromEntries(paramNames.map((name, i) => [name, match[i + 1]]))\n}\n","import { IncomingMessage, Server as HttpServer } from 'node:http'\nimport { randomUUID } from 'node:crypto'\nimport { WebSocketServer, WebSocket } from 'ws'\nimport type { ReactiveEngine } from '../reactive/engine.js'\nimport { extractParams } from '../reactive/engine.js'\nimport type { Context } from '../types.js'\nimport { ReactiveApiError } from '../errors.js'\n\n/** Message sent by the client to subscribe to a reactive path. */\ninterface SubscribeMessage {\n type: 'subscribe'\n path: string\n /** Optional query params the client wants included in the Context */\n query?: Record<string, string>\n}\n\n/** Message pushed by the server when data changes. */\ninterface UpdateMessage {\n type: 'update'\n path: string\n data: unknown\n}\n\n/** Message sent on error. */\ninterface ErrorMessage {\n type: 'error'\n code: string\n message: string\n}\n\ntype ServerMessage = UpdateMessage | ErrorMessage\n\nfunction isSubscribeMessage(value: unknown): value is SubscribeMessage {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>)['type'] === 'subscribe' &&\n typeof (value as Record<string, unknown>)['path'] === 'string'\n )\n}\n\n/**\n * WebSocket transport layer.\n *\n * Attaches a `ws` server to the underlying Node HTTP server. Clients connect\n * and send a subscribe message; the transport builds a Context and registers\n * the subscription with the ReactiveEngine.\n *\n * Protocol:\n * - Client → Server: `{ \"type\": \"subscribe\", \"path\": \"/orders/123/live\" }`\n * - Server → Client: `{ \"type\": \"update\", \"path\": \"/orders/123/live\", \"data\": [...] }`\n * - Server → Client: `{ \"type\": \"error\", \"code\": \"...\", \"message\": \"...\" }`\n */\nexport class WebSocketTransport {\n private readonly wss: WebSocketServer\n /** Maps clientId → registered route pattern, for param extraction */\n private readonly clientPatterns: Map<string, string> = new Map()\n\n constructor(\n private readonly engine: ReactiveEngine,\n /** All registered route patterns (e.g. ['/orders/:userId/live']) */\n private readonly routePatterns: string[],\n ) {\n // noServer=true so we can attach to Fastify's underlying http.Server manually\n this.wss = new WebSocketServer({ noServer: true })\n this.wss.on('connection', (ws, req) => this.handleConnection(ws, req))\n }\n\n /**\n * Attach to the raw Node.js HTTP server so WebSocket upgrade requests are\n * handled alongside Fastify routes.\n */\n attach(httpServer: HttpServer): void {\n httpServer.on('upgrade', (req, socket, head) => {\n this.wss.handleUpgrade(req, socket, head, (ws) => {\n this.wss.emit('connection', ws, req)\n })\n })\n }\n\n /** Gracefully close all connections. */\n async close(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.wss.close((err) => (err ? reject(err) : resolve()))\n })\n }\n\n // ---------------------------------------------------------------------------\n // Connection handling\n // ---------------------------------------------------------------------------\n\n private handleConnection(ws: WebSocket, _req: IncomingMessage): void {\n const clientId = randomUUID()\n\n ws.on('message', (raw) => {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw.toString())\n } catch {\n this.sendError(ws, 'INVALID_JSON', 'Message must be valid JSON')\n return\n }\n\n if (!isSubscribeMessage(parsed)) {\n this.sendError(ws, 'INVALID_MESSAGE', 'Expected { type: \"subscribe\", path: string }')\n return\n }\n\n this.handleSubscribe(ws, clientId, parsed)\n })\n\n ws.on('close', () => {\n this.engine.unsubscribe(clientId)\n this.clientPatterns.delete(clientId)\n })\n\n ws.on('error', (err) => {\n this.engine.unsubscribe(clientId)\n this.clientPatterns.delete(clientId)\n // ws errors are expected (client disconnect); just log in dev\n if (process.env['NODE_ENV'] !== 'production') {\n console.error('[RouteFlow] WebSocket error:', err.message)\n }\n })\n }\n\n private handleSubscribe(ws: WebSocket, clientId: string, msg: SubscribeMessage): void {\n const { path, query = {} } = msg\n\n // Find the matching route pattern\n const pattern = this.routePatterns.find((p) => this.matchesPattern(path, p))\n\n if (!pattern) {\n this.sendError(ws, 'NO_REACTIVE_ENDPOINT', `No reactive endpoint found for path: ${path}`)\n return\n }\n\n const params = extractParams(path, pattern)\n\n const ctx: Context = {\n params,\n query,\n body: undefined,\n headers: {},\n }\n\n const pushFn = (subscribedPath: string, data: unknown): void => {\n if (ws.readyState !== WebSocket.OPEN) return\n const msg: UpdateMessage = { type: 'update', path: subscribedPath, data }\n ws.send(JSON.stringify(msg))\n }\n\n // Unsubscribe any previous subscription for this client before re-subscribing\n this.engine.unsubscribe(clientId)\n this.clientPatterns.set(clientId, pattern)\n this.engine.subscribe(clientId, path, ctx, pushFn)\n }\n\n private matchesPattern(concretePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)')\n return new RegExp(`^${regexStr}$`).test(concretePath)\n }\n\n private sendError(ws: WebSocket, code: string, message: string): void {\n if (ws.readyState !== WebSocket.OPEN) return\n const msg: ErrorMessage = { type: 'error', code, message }\n ws.send(JSON.stringify(msg))\n }\n}\n","import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'\nimport { randomUUID } from 'node:crypto'\nimport type { ReactiveEngine } from '../reactive/engine.js'\nimport { extractParams } from '../reactive/engine.js'\nimport type { Context } from '../types.js'\nimport { ReactiveApiError } from '../errors.js'\n\n/**\n * Server-Sent Events transport layer.\n *\n * Clients connect via a GET request to `/_sse/subscribe?path=<encodedPath>`.\n * The server sends a stream of `data:` events in the standard SSE format.\n *\n * Event format (one JSON object per `data:` line):\n * ```\n * data: {\"type\":\"update\",\"path\":\"/orders/123/live\",\"data\":[...]}\n *\n * ```\n *\n * Unlike WebSocket, SSE is strictly server-to-client (unidirectional).\n * The subscribed path is passed as a query param on the initial GET request.\n *\n * Advantages over WebSocket in some environments:\n * - Works over plain HTTP/1.1 (no upgrade required)\n * - Automatic reconnection handled natively by the browser `EventSource`\n * - Firewall/proxy friendly\n */\nexport class SseTransport {\n /** clientId → reply (kept open) */\n private readonly connections: Map<string, FastifyReply> = new Map()\n\n constructor(\n private readonly engine: ReactiveEngine,\n private readonly routePatterns: string[],\n ) {}\n\n /**\n * Register the SSE subscription endpoint on the Fastify instance.\n * Must be called before `fastify.listen()`.\n */\n register(fastify: FastifyInstance): void {\n fastify.get('/_sse/subscribe', async (req: FastifyRequest, reply: FastifyReply) => {\n const query = req.query as Record<string, string>\n const path = query['path']\n\n if (!path) {\n throw new ReactiveApiError('SSE_MISSING_PATH', 'Query param \"path\" is required')\n }\n\n const decodedPath = decodeURIComponent(path)\n const pattern = this.routePatterns.find((p) => this.matchesPattern(decodedPath, p))\n\n if (!pattern) {\n reply.code(404)\n throw new ReactiveApiError(\n 'SSE_NO_REACTIVE_ENDPOINT',\n `No reactive endpoint found for path: ${decodedPath}`,\n )\n }\n\n const params = extractParams(decodedPath, pattern)\n const clientQuery = { ...query }\n delete clientQuery['path']\n\n const ctx: Context = {\n params,\n query: clientQuery,\n body: undefined,\n headers: req.headers as Record<string, string>,\n }\n\n const clientId = randomUUID()\n\n // Set SSE headers\n reply.raw.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no', // disable nginx buffering\n })\n\n // Send initial comment to establish connection\n reply.raw.write(': connected\\n\\n')\n\n this.connections.set(clientId, reply)\n\n const pushFn = (subscribedPath: string, data: unknown): void => {\n if (reply.raw.destroyed) return\n const payload = JSON.stringify({ type: 'update', path: subscribedPath, data })\n reply.raw.write(`data: ${payload}\\n\\n`)\n }\n\n this.engine.subscribe(clientId, decodedPath, ctx, pushFn)\n\n // Cleanup when client disconnects\n req.raw.on('close', () => {\n this.engine.unsubscribe(clientId)\n this.connections.delete(clientId)\n if (!reply.raw.destroyed) reply.raw.end()\n })\n\n // Keep the connection open — Fastify needs this to not auto-close\n await new Promise<void>((resolve) => {\n req.raw.on('close', resolve)\n req.raw.on('error', resolve)\n })\n })\n }\n\n /** Close all open SSE connections. */\n async close(): Promise<void> {\n for (const reply of this.connections.values()) {\n if (!reply.raw.destroyed) reply.raw.end()\n }\n this.connections.clear()\n }\n\n private matchesPattern(concretePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)')\n return new RegExp(`^${regexStr}$`).test(concretePath)\n }\n}\n","export type DatabaseCategory =\n | 'rdbms'\n | 'nosql'\n | 'time-series'\n | 'search-engine'\n | 'cloud-data-warehouse'\n | 'in-memory'\n | 'newsql'\n\nexport type DatabaseSupportMode = 'native-adapter' | 'polling-adapter' | 'external-cdc-bridge'\nexport type DatabaseSupportTier = 'official' | 'experimental'\n\nexport type DatabaseKey =\n | 'postgresql'\n | 'mysql'\n | 'mariadb'\n | 'oracle-db'\n | 'ms-sql-server'\n | 'sqlite'\n | 'mongodb'\n | 'redis'\n | 'cassandra'\n | 'dynamodb'\n | 'neo4j'\n | 'elasticsearch'\n | 'hbase'\n | 'couchdb'\n | 'influxdb'\n | 'timescaledb'\n | 'prometheus'\n | 'opensearch'\n | 'solr'\n | 'snowflake'\n | 'bigquery'\n | 'redshift'\n | 'azure-synapse'\n | 'memcached'\n | 'voltdb'\n | 'cockroachdb'\n | 'tidb'\n | 'spanner'\n\nexport interface DatabaseSupportDescriptor {\n key: DatabaseKey\n name: string\n aliases: string[]\n categories: DatabaseCategory[]\n supportedModes: DatabaseSupportMode[]\n tier: DatabaseSupportTier\n}\n\nexport const SUPPORTED_DATABASES: readonly DatabaseSupportDescriptor[] = [\n {\n key: 'postgresql',\n name: 'PostgreSQL',\n aliases: ['postgres', 'psql'],\n categories: ['rdbms'],\n supportedModes: ['native-adapter', 'polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'mysql',\n name: 'MySQL',\n aliases: [],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'mariadb',\n name: 'MariaDB',\n aliases: [],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'oracle-db',\n name: 'Oracle DB',\n aliases: ['oracle', 'oracle database'],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'ms-sql-server',\n name: 'MS SQL Server',\n aliases: ['sql server', 'mssql', 'ms sql'],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'sqlite',\n name: 'SQLite',\n aliases: [],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter'],\n tier: 'experimental',\n },\n {\n key: 'mongodb',\n name: 'MongoDB',\n aliases: ['mongo'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'redis',\n name: 'Redis',\n aliases: [],\n categories: ['nosql', 'in-memory'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'cassandra',\n name: 'Cassandra',\n aliases: ['apache cassandra'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'dynamodb',\n name: 'DynamoDB',\n aliases: ['dynamo'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'neo4j',\n name: 'Neo4j',\n aliases: [],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'elasticsearch',\n name: 'Elasticsearch',\n aliases: ['elastic'],\n categories: ['nosql', 'search-engine'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'hbase',\n name: 'HBase',\n aliases: ['apache hbase'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'couchdb',\n name: 'CouchDB',\n aliases: ['apache couchdb'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'influxdb',\n name: 'InfluxDB',\n aliases: ['influx'],\n categories: ['time-series'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'timescaledb',\n name: 'TimescaleDB',\n aliases: ['timescale'],\n categories: ['time-series', 'rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'prometheus',\n name: 'Prometheus',\n aliases: ['prom'],\n categories: ['time-series'],\n supportedModes: ['polling-adapter'],\n tier: 'experimental',\n },\n {\n key: 'opensearch',\n name: 'OpenSearch',\n aliases: [],\n categories: ['search-engine'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'solr',\n name: 'Solr',\n aliases: ['apache solr'],\n categories: ['search-engine'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'snowflake',\n name: 'Snowflake',\n aliases: [],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'bigquery',\n name: 'BigQuery',\n aliases: ['google bigquery'],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'redshift',\n name: 'Redshift',\n aliases: ['amazon redshift'],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'azure-synapse',\n name: 'Azure Synapse',\n aliases: ['synapse', 'azure synapse analytics'],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'memcached',\n name: 'Memcached',\n aliases: [],\n categories: ['in-memory'],\n supportedModes: ['polling-adapter'],\n tier: 'experimental',\n },\n {\n key: 'voltdb',\n name: 'VoltDB',\n aliases: [],\n categories: ['in-memory'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'cockroachdb',\n name: 'CockroachDB',\n aliases: ['cockroach'],\n categories: ['newsql', 'rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'tidb',\n name: 'TiDB',\n aliases: [],\n categories: ['newsql', 'rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'spanner',\n name: 'Spanner',\n aliases: ['google spanner', 'cloud spanner'],\n categories: ['newsql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n] as const\n\nfunction normaliseDatabaseName(value: string): string {\n return value.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim()\n}\n\nexport function getDatabaseSupport(name: string): DatabaseSupportDescriptor | undefined {\n const normalised = normaliseDatabaseName(name)\n\n return SUPPORTED_DATABASES.find((database) => {\n if (normaliseDatabaseName(database.name) === normalised) return true\n if (normaliseDatabaseName(database.key) === normalised) return true\n\n return database.aliases.some((alias) => normaliseDatabaseName(alias) === normalised)\n })\n}\n\nexport function listSupportedDatabases(\n category?: DatabaseCategory,\n): readonly DatabaseSupportDescriptor[] {\n if (!category) return SUPPORTED_DATABASES\n return SUPPORTED_DATABASES.filter((database) => database.categories.includes(category))\n}\n\nexport function listOfficialDatabases(\n category?: DatabaseCategory,\n): readonly DatabaseSupportDescriptor[] {\n const databases = listSupportedDatabases(category)\n return databases.filter((database) => database.tier === 'official')\n}\n","import type { ChangeEvent, DatabaseAdapter } from '../types.js'\n\n/**\n * In-memory database adapter for testing and local development.\n * No real database connection is needed — changes are triggered manually via `emit()`.\n *\n * @example\n * ```ts\n * const adapter = new MemoryAdapter()\n * await adapter.connect()\n *\n * adapter.onChange('orders', (event) => console.log(event))\n *\n * adapter.emit('orders', { operation: 'INSERT', newRow: { id: 1 }, oldRow: null })\n * ```\n */\nexport class MemoryAdapter implements DatabaseAdapter {\n private readonly listeners: Map<string, Set<(event: ChangeEvent) => void>> = new Map()\n private connected = false\n\n /** No-op — MemoryAdapter requires no real connection. */\n async connect(): Promise<void> {\n this.connected = true\n }\n\n /** No-op — clears all listeners on disconnect. */\n async disconnect(): Promise<void> {\n this.listeners.clear()\n this.connected = false\n }\n\n /**\n * Register a listener for changes on a specific table.\n * @returns An unsubscribe function.\n */\n onChange(table: string, callback: (event: ChangeEvent) => void): () => void {\n if (!this.listeners.has(table)) {\n this.listeners.set(table, new Set())\n }\n this.listeners.get(table)!.add(callback)\n\n return () => {\n this.listeners.get(table)?.delete(callback)\n }\n }\n\n /**\n * Manually emit a change event on a table.\n * Useful in tests and examples to simulate DB changes without a real database.\n *\n * @param table - Table name to emit the event on\n * @param event - Change event data (table and timestamp are filled in automatically)\n */\n emit(table: string, event: Omit<ChangeEvent, 'table' | 'timestamp'>): void {\n const fullEvent: ChangeEvent = {\n ...event,\n table,\n timestamp: Date.now(),\n }\n\n const callbacks = this.listeners.get(table)\n if (!callbacks) return\n\n for (const cb of callbacks) {\n cb(fullEvent)\n }\n }\n\n /** Returns true if connect() has been called and disconnect() has not. */\n get isConnected(): boolean {\n return this.connected\n }\n}\n","import type { ChangeEvent, DatabaseAdapter } from '../types.js'\n\nexport interface PollingReadResult<T = unknown, TCursor = unknown> {\n events: Array<Omit<ChangeEvent<T>, 'table' | 'timestamp'> & Partial<Pick<ChangeEvent<T>, 'table' | 'timestamp'>>>\n cursor?: TCursor\n}\n\nexport interface PollingReadContext<TCursor = unknown> {\n cursor: TCursor | undefined\n table: string\n}\n\nexport interface PollingAdapterOptions<TCursor = unknown> {\n intervalMs?: number\n now?: () => number\n onError?: (error: unknown, context: { table: string }) => void\n readChanges: (\n context: PollingReadContext<TCursor>,\n ) => Promise<PollingReadResult<unknown, TCursor>>\n}\n\n/**\n * Generic polling-based adapter for databases without a native RouteFlow adapter yet.\n * It works with any backend as long as callers can periodically read a change feed.\n */\nexport class PollingAdapter<TCursor = unknown> implements DatabaseAdapter {\n private readonly listeners: Map<string, Set<(event: ChangeEvent) => void>> = new Map()\n private readonly cursors: Map<string, TCursor | undefined> = new Map()\n private readonly timers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n private readonly activeTables: Set<string> = new Set()\n private readonly intervalMs: number\n private readonly now: () => number\n private readonly readChanges: PollingAdapterOptions<TCursor>['readChanges']\n private readonly onError?: PollingAdapterOptions<TCursor>['onError']\n private connected = false\n\n constructor(options: PollingAdapterOptions<TCursor>) {\n this.intervalMs = options.intervalMs ?? 1_000\n this.now = options.now ?? (() => Date.now())\n this.readChanges = options.readChanges\n this.onError = options.onError\n }\n\n async connect(): Promise<void> {\n this.connected = true\n\n for (const table of this.listeners.keys()) {\n this.ensurePolling(table)\n }\n }\n\n async disconnect(): Promise<void> {\n this.connected = false\n\n for (const timer of this.timers.values()) {\n clearTimeout(timer)\n }\n\n this.timers.clear()\n this.activeTables.clear()\n }\n\n onChange(table: string, callback: (event: ChangeEvent) => void): () => void {\n if (!this.listeners.has(table)) {\n this.listeners.set(table, new Set())\n }\n\n this.listeners.get(table)!.add(callback)\n this.ensurePolling(table)\n\n return () => {\n const callbacks = this.listeners.get(table)\n if (!callbacks) return\n\n callbacks.delete(callback)\n\n if (callbacks.size === 0) {\n this.listeners.delete(table)\n this.stopPolling(table)\n }\n }\n }\n\n private ensurePolling(table: string): void {\n if (!this.connected || this.activeTables.has(table)) return\n\n this.activeTables.add(table)\n void this.poll(table)\n }\n\n private stopPolling(table: string): void {\n const timer = this.timers.get(table)\n if (timer) clearTimeout(timer)\n\n this.timers.delete(table)\n this.activeTables.delete(table)\n this.cursors.delete(table)\n }\n\n private scheduleNext(table: string): void {\n if (!this.connected || !this.listeners.has(table)) {\n this.stopPolling(table)\n return\n }\n\n const timer = setTimeout(() => {\n void this.poll(table)\n }, this.intervalMs)\n\n this.timers.set(table, timer)\n }\n\n private async poll(table: string): Promise<void> {\n if (!this.connected || !this.listeners.has(table)) {\n this.stopPolling(table)\n return\n }\n\n try {\n const result = await this.readChanges({\n table,\n cursor: this.cursors.get(table),\n })\n\n this.cursors.set(table, result.cursor)\n\n for (const event of result.events) {\n this.emit(table, event)\n }\n } catch (error) {\n this.onError?.(error, { table })\n } finally {\n this.activeTables.delete(table)\n this.scheduleNext(table)\n }\n }\n\n private emit(\n defaultTable: string,\n event: Omit<ChangeEvent, 'table' | 'timestamp'> & Partial<Pick<ChangeEvent, 'table' | 'timestamp'>>,\n ): void {\n const fullEvent: ChangeEvent = {\n ...event,\n table: event.table ?? defaultTable,\n timestamp: event.timestamp ?? this.now(),\n }\n\n const callbacks = this.listeners.get(fullEvent.table)\n if (!callbacks) return\n\n for (const callback of callbacks) {\n callback(fullEvent)\n }\n }\n}\n"],"mappings":"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","names":["msg","import_node_crypto","Fastify"]}
package/dist/index.d.cts CHANGED
@@ -140,6 +140,8 @@ declare class ReactiveApp {
140
140
  private readonly options;
141
141
  /** Collected route patterns for reactive endpoints */
142
142
  private readonly reactivePatterns;
143
+ /** All registered routes (for .routeflow/info.json) */
144
+ private readonly registeredRoutes;
143
145
  constructor(options: AppOptions);
144
146
  /**
145
147
  * Register a controller class. Scans its methods for @Route and @Reactive
@@ -161,6 +163,7 @@ declare class ReactiveApp {
161
163
  * @param port - Override the port set in AppOptions
162
164
  */
163
165
  listen(port?: number): Promise<void>;
166
+ private writeInfo;
164
167
  /**
165
168
  * Gracefully shut down the server and disconnect from the database.
166
169
  */
package/dist/index.d.ts CHANGED
@@ -140,6 +140,8 @@ declare class ReactiveApp {
140
140
  private readonly options;
141
141
  /** Collected route patterns for reactive endpoints */
142
142
  private readonly reactivePatterns;
143
+ /** All registered routes (for .routeflow/info.json) */
144
+ private readonly registeredRoutes;
143
145
  constructor(options: AppOptions);
144
146
  /**
145
147
  * Register a controller class. Scans its methods for @Route and @Reactive
@@ -161,6 +163,7 @@ declare class ReactiveApp {
161
163
  * @param port - Override the port set in AppOptions
162
164
  */
163
165
  listen(port?: number): Promise<void>;
166
+ private writeInfo;
164
167
  /**
165
168
  * Gracefully shut down the server and disconnect from the database.
166
169
  */
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // src/index.ts
2
2
  import "reflect-metadata";
3
+ import { writeFileSync, mkdirSync } from "fs";
4
+ import { join } from "path";
3
5
  import Fastify from "fastify";
4
6
 
5
7
  // src/core/decorator/route.ts
@@ -768,6 +770,8 @@ var ReactiveApp = class {
768
770
  options;
769
771
  /** Collected route patterns for reactive endpoints */
770
772
  reactivePatterns = [];
773
+ /** All registered routes (for .routeflow/info.json) */
774
+ registeredRoutes = [];
771
775
  constructor(options) {
772
776
  this.options = {
773
777
  transport: "websocket",
@@ -824,6 +828,7 @@ var ReactiveApp = class {
824
828
  }
825
829
  }
826
830
  });
831
+ this.registeredRoutes.push({ method: routeMeta.method, path: routeMeta.path, reactive: !!reactiveMeta });
827
832
  if (reactiveMeta) {
828
833
  const endpoint = {
829
834
  routePath: routeMeta.path,
@@ -867,6 +872,28 @@ var ReactiveApp = class {
867
872
  console.log(
868
873
  `[RouteFlow] Listening on port ${listenPort} (transport: ${this.options.transport})`
869
874
  );
875
+ this.writeInfo(listenPort);
876
+ }
877
+ writeInfo(port) {
878
+ try {
879
+ const dir = join(process.cwd(), ".routeflow");
880
+ mkdirSync(dir, { recursive: true });
881
+ writeFileSync(
882
+ join(dir, "info.json"),
883
+ JSON.stringify(
884
+ {
885
+ port,
886
+ transport: this.options.transport,
887
+ adapter: this.options.adapter.constructor.name,
888
+ routes: this.registeredRoutes,
889
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
890
+ },
891
+ null,
892
+ 2
893
+ )
894
+ );
895
+ } catch {
896
+ }
870
897
  }
871
898
  /**
872
899
  * Gracefully shut down the server and disconnect from the database.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/core/decorator/route.ts","../src/core/decorator/reactive.ts","../src/core/errors.ts","../src/core/reactive/engine.ts","../src/core/transport/websocket-transport.ts","../src/core/transport/sse-transport.ts","../src/core/database-support.ts","../src/core/adapter/memory-adapter.ts","../src/core/adapter/polling-adapter.ts"],"sourcesContent":["import 'reflect-metadata'\nimport Fastify, { FastifyInstance } from 'fastify'\nimport type { AppOptions, Context, ReactiveEndpoint } from './core/types.js'\nimport { ROUTE_METADATA, routeFnStore } from './core/decorator/route.js'\nimport { REACTIVE_METADATA, reactiveFnStore } from './core/decorator/reactive.js'\nimport type { RouteMetadata, ReactiveOptions } from './core/types.js'\nimport { ReactiveEngine } from './core/reactive/engine.js'\nimport { WebSocketTransport } from './core/transport/websocket-transport.js'\nimport { SseTransport } from './core/transport/sse-transport.js'\nimport { ReactiveApiError } from './core/errors.js'\n\nexport { Route } from './core/decorator/route.js'\nexport { Reactive } from './core/decorator/reactive.js'\nexport { ReactiveApiError } from './core/errors.js'\nexport {\n SUPPORTED_DATABASES,\n getDatabaseSupport,\n listOfficialDatabases,\n listSupportedDatabases,\n} from './core/database-support.js'\nexport type {\n Context,\n ChangeEvent,\n DatabaseAdapter,\n ReactiveOptions,\n AppOptions,\n HttpMethod,\n} from './core/types.js'\nexport type {\n DatabaseCategory,\n DatabaseKey,\n DatabaseSupportDescriptor,\n DatabaseSupportMode,\n DatabaseSupportTier,\n} from './core/database-support.js'\n\n// Adapters (built-in)\nexport { MemoryAdapter, PollingAdapter } from './core/adapter/index.js'\nexport type {\n PollingAdapterOptions,\n PollingReadContext,\n PollingReadResult,\n} from './core/adapter/index.js'\n\ntype AnyTransport = WebSocketTransport | SseTransport\n\n/**\n * Main application class. Use `createApp()` to instantiate.\n */\nexport class ReactiveApp {\n private readonly fastify: FastifyInstance\n private readonly engine: ReactiveEngine\n private transport: AnyTransport | null = null\n private readonly options: Required<AppOptions>\n /** Collected route patterns for reactive endpoints */\n private readonly reactivePatterns: string[] = []\n\n constructor(options: AppOptions) {\n this.options = {\n transport: 'websocket',\n port: 3000,\n ...options,\n }\n this.fastify = Fastify({ logger: false })\n this.engine = new ReactiveEngine(this.options.adapter)\n\n // Register a global error handler that serialises ReactiveApiError properly\n this.fastify.setErrorHandler((error, _req, reply) => {\n if (error instanceof ReactiveApiError) {\n const status = (error as ReactiveApiError & { statusCode?: number }).statusCode ?? 500\n reply.status(status).send({ error: error.code, message: error.message })\n } else {\n reply.status(500).send({ error: 'INTERNAL_ERROR', message: error.message })\n }\n })\n }\n\n /**\n * Register a controller class. Scans its methods for @Route and @Reactive\n * decorators and registers HTTP routes and reactive endpoints accordingly.\n *\n * @param ControllerClass - A class constructor whose methods may be decorated\n * with @Route and/or @Reactive.\n */\n register(ControllerClass: new () => object): this {\n const instance = new ControllerClass()\n const proto = Object.getPrototypeOf(instance) as object\n\n const methodNames = Object.getOwnPropertyNames(proto).filter(\n (name) =>\n name !== 'constructor' &&\n typeof (proto as Record<string, unknown>)[name] === 'function',\n )\n\n for (const methodName of methodNames) {\n const fn = (proto as Record<string, unknown>)[methodName] as object\n const routeMeta: RouteMetadata | undefined =\n routeFnStore.get(fn) ??\n (Reflect.getMetadata(ROUTE_METADATA, proto, methodName) as RouteMetadata | undefined)\n if (!routeMeta) continue\n\n const reactiveMeta: ReactiveOptions | undefined =\n reactiveFnStore.get(fn) ??\n (Reflect.getMetadata(REACTIVE_METADATA, proto, methodName) as ReactiveOptions | undefined)\n\n const handler = (instance as Record<string, unknown>)[methodName] as (\n ctx: Context,\n ) => Promise<unknown>\n\n // Register HTTP route with Fastify\n this.fastify.route({\n method: routeMeta.method,\n url: routeMeta.path,\n handler: async (req, reply) => {\n const ctx: Context = {\n params: req.params as Record<string, string>,\n query: req.query as Record<string, string>,\n body: req.body,\n headers: req.headers as Record<string, string>,\n }\n try {\n const result = await handler.call(instance, ctx)\n return reply.send(result)\n } catch (err) {\n if (err instanceof ReactiveApiError) throw err\n const msg = err instanceof Error ? err.message : String(err)\n throw new ReactiveApiError('HANDLER_ERROR', msg)\n }\n },\n })\n\n // Register reactive endpoint if @Reactive is present\n if (reactiveMeta) {\n const endpoint: ReactiveEndpoint = {\n routePath: routeMeta.path,\n options: reactiveMeta,\n handler: (ctx: Context) => handler.call(instance, ctx),\n }\n this.engine.registerEndpoint(endpoint)\n this.reactivePatterns.push(routeMeta.path)\n }\n }\n\n return this\n }\n\n /**\n * Access the underlying Fastify instance for supplemental routes such as\n * health checks, static assets, or demo pages.\n */\n getFastify(): FastifyInstance {\n return this.fastify\n }\n\n /**\n * Start the HTTP server.\n * Connects the database adapter before accepting connections.\n *\n * @param port - Override the port set in AppOptions\n */\n async listen(port?: number): Promise<void> {\n const listenPort = port ?? this.options.port\n\n await this.options.adapter.connect()\n\n if (this.options.transport === 'websocket') {\n this.transport = new WebSocketTransport(this.engine, this.reactivePatterns)\n } else if (this.options.transport === 'sse') {\n const sseTransport = new SseTransport(this.engine, this.reactivePatterns)\n sseTransport.register(this.fastify)\n this.transport = sseTransport\n }\n\n await this.fastify.ready()\n\n if (this.transport instanceof WebSocketTransport) {\n this.transport.attach(this.fastify.server)\n }\n\n await this.fastify.listen({ port: listenPort, host: '0.0.0.0' })\n console.log(\n `[RouteFlow] Listening on port ${listenPort} (transport: ${this.options.transport})`,\n )\n }\n\n /**\n * Gracefully shut down the server and disconnect from the database.\n */\n async close(): Promise<void> {\n this.engine.destroy()\n if (this.transport) await this.transport.close()\n await this.fastify.close()\n await this.options.adapter.disconnect()\n }\n}\n\n/**\n * Create a new RouteFlow application.\n *\n * @example\n * ```ts\n * const app = createApp({ adapter: new MemoryAdapter(), port: 3000 })\n * app.register(MyController)\n * await app.listen()\n * ```\n */\nexport function createApp(options: AppOptions): ReactiveApp {\n return new ReactiveApp(options)\n}\n","import type { HttpMethod, RouteMetadata } from '../types.js'\n\n/** Symbol key used to store @Route metadata on a method. */\nexport const ROUTE_METADATA = Symbol('reactive-api:route')\n\n/**\n * Function-keyed store for TC39 (new-style) decorator compat.\n * Legacy decorators use Reflect.defineMetadata; TC39 decorators store here.\n */\nexport const routeFnStore = new WeakMap<object, RouteMetadata>()\n\n/**\n * Registers a class method as an HTTP endpoint.\n *\n * Works with both TypeScript legacy decorators (experimentalDecorators) and\n * TC39 Stage 3 decorators (as used by esbuild/tsx without legacy flag).\n *\n * @param method - HTTP verb\n * @param path - Route path, may include Fastify-style params (e.g. '/users/:id')\n */\nexport function Route(method: HttpMethod, path: string) {\n const metadata: RouteMetadata = { method, path }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return function (target: any, propertyKey: any): void {\n if (typeof target === 'function') {\n // TC39 decorator: target is the method function itself\n routeFnStore.set(target, metadata)\n } else if (typeof propertyKey === 'string') {\n // Legacy decorator: target is the prototype, propertyKey is method name\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const fn = target[propertyKey] as object\n if (fn) routeFnStore.set(fn, metadata)\n Reflect.defineMetadata(ROUTE_METADATA, metadata, target, propertyKey)\n }\n }\n}\n","import type { ReactiveOptions } from '../types.js'\n\n/** Symbol key used to store @Reactive metadata on a method. */\nexport const REACTIVE_METADATA = Symbol('reactive-api:reactive')\n\n/**\n * Function-keyed store for TC39 (new-style) decorator compat.\n */\nexport const reactiveFnStore = new WeakMap<object, ReactiveOptions>()\n\n/**\n * Marks a route handler as reactive — when the watched table(s) change,\n * the handler is re-executed and the result is pushed to all subscribed clients.\n *\n * Must be used together with @Route.\n * Works with both TypeScript legacy decorators and TC39 Stage 3 decorators.\n *\n * @param options - Reactive configuration (watch, filter, debounce)\n */\nexport function Reactive(options: ReactiveOptions) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return function (target: any, propertyKey: any): void {\n if (typeof target === 'function') {\n // TC39 decorator: target is the method function itself\n reactiveFnStore.set(target, options)\n } else if (typeof propertyKey === 'string') {\n // Legacy decorator: target is the prototype, propertyKey is method name\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const fn = target[propertyKey] as object\n if (fn) reactiveFnStore.set(fn, options)\n Reflect.defineMetadata(REACTIVE_METADATA, options, target, propertyKey)\n }\n }\n}\n","/**\n * Base error class for all RouteFlow errors.\n * Always use this instead of plain `Error` throughout the framework.\n */\nexport class ReactiveApiError extends Error {\n /** Machine-readable error code (e.g. 'ADAPTER_NOT_CONNECTED', 'INVALID_ROUTE') */\n readonly code: string\n\n constructor(code: string, message: string) {\n super(message)\n this.name = 'ReactiveApiError'\n this.code = code\n // Restore prototype chain (required when extending built-ins in TS)\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n","import type {\n ChangeEvent,\n Context,\n DatabaseAdapter,\n PushFn,\n ReactiveEndpoint,\n} from '../types.js'\nimport { ReactiveApiError } from '../errors.js'\n\ninterface Subscription {\n /** The concrete path the client subscribed to (e.g. '/orders/123/live') */\n path: string\n /** Context built from the subscribed path */\n ctx: Context\n /** Function to call when a push is ready */\n pushFn: PushFn\n}\n\n/**\n * Core reactive engine.\n *\n * Responsibilities:\n * 1. Holds the registry of @Reactive endpoints\n * 2. Subscribes to the DatabaseAdapter for each watched table\n * 3. On a ChangeEvent, fans out to matching subscribers after applying filters\n * 4. Supports optional per-subscriber debouncing\n */\nexport class ReactiveEngine {\n private readonly endpoints: ReactiveEndpoint[] = []\n /** clientId → Subscription */\n private readonly subscriptions: Map<string, Subscription> = new Map()\n /** table → adapter unsubscribe fn */\n private readonly tableWatchers: Map<string, () => void> = new Map()\n /** \"clientId:path\" → debounce timer id */\n private readonly debounceTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n\n constructor(private readonly adapter: DatabaseAdapter) {}\n\n /**\n * Register a reactive endpoint so the engine can fan-out pushes to subscribers.\n */\n registerEndpoint(endpoint: ReactiveEndpoint): void {\n this.endpoints.push(endpoint)\n\n const tables = Array.isArray(endpoint.options.watch)\n ? endpoint.options.watch\n : [endpoint.options.watch]\n\n for (const table of tables) {\n this.setupTableWatcher(table)\n }\n }\n\n /**\n * Subscribe a WebSocket client to a path.\n * When the watched table(s) change and the filter passes, pushFn is called.\n *\n * @param clientId - Unique identifier for the client connection\n * @param path - The concrete path the client subscribed to\n * @param ctx - Context built from the subscribed path\n * @param pushFn - Callback to deliver data to the client\n */\n subscribe(clientId: string, path: string, ctx: Context, pushFn: PushFn): void {\n this.subscriptions.set(clientId, { path, ctx, pushFn })\n }\n\n /**\n * Remove a client's subscription and clean up any pending debounce timers.\n */\n unsubscribe(clientId: string): void {\n this.subscriptions.delete(clientId)\n\n // Clean up any pending debounce timers for this client\n for (const key of this.debounceTimers.keys()) {\n if (key.startsWith(`${clientId}:`)) {\n clearTimeout(this.debounceTimers.get(key))\n this.debounceTimers.delete(key)\n }\n }\n }\n\n /**\n * Tear down all table watchers. Call this when the app shuts down.\n */\n destroy(): void {\n for (const unsubscribe of this.tableWatchers.values()) {\n unsubscribe()\n }\n this.tableWatchers.clear()\n\n for (const timer of this.debounceTimers.values()) {\n clearTimeout(timer)\n }\n this.debounceTimers.clear()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private setupTableWatcher(table: string): void {\n if (this.tableWatchers.has(table)) return // already watching\n\n const unsubscribe = this.adapter.onChange(table, (event) => {\n this.onChangeEvent(event)\n })\n this.tableWatchers.set(table, unsubscribe)\n }\n\n private onChangeEvent(event: ChangeEvent): void {\n // Find endpoints that watch this table\n const matchingEndpoints = this.endpoints.filter((ep) => {\n const tables = Array.isArray(ep.options.watch)\n ? ep.options.watch\n : [ep.options.watch]\n return tables.includes(event.table)\n })\n\n for (const endpoint of matchingEndpoints) {\n // Find all subscribers that are on this endpoint's route path\n for (const [clientId, sub] of this.subscriptions) {\n if (!pathMatchesPattern(sub.path, endpoint.routePath)) continue\n\n // Apply optional filter\n if (endpoint.options.filter) {\n try {\n if (!endpoint.options.filter(event, sub.ctx)) continue\n } catch (err) {\n // Filter threw — skip this subscriber rather than crashing\n continue\n }\n }\n\n this.schedulePush(clientId, endpoint, sub, event)\n }\n }\n }\n\n private schedulePush(\n clientId: string,\n endpoint: ReactiveEndpoint,\n sub: Subscription,\n _event: ChangeEvent,\n ): void {\n const debounceMs = endpoint.options.debounce\n\n if (debounceMs !== undefined && debounceMs > 0) {\n const timerKey = `${clientId}:${sub.path}`\n const existing = this.debounceTimers.get(timerKey)\n if (existing !== undefined) clearTimeout(existing)\n\n const timer = setTimeout(() => {\n this.debounceTimers.delete(timerKey)\n this.executePush(endpoint, sub)\n }, debounceMs)\n\n this.debounceTimers.set(timerKey, timer)\n } else {\n this.executePush(endpoint, sub)\n }\n }\n\n private executePush(endpoint: ReactiveEndpoint, sub: Subscription): void {\n endpoint.handler(sub.ctx).then(\n (data) => sub.pushFn(sub.path, data),\n (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err)\n throw new ReactiveApiError('HANDLER_ERROR', `Reactive handler failed: ${message}`)\n },\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Path matching utility\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if a concrete path matches a route pattern with named params.\n *\n * pathMatchesPattern('/orders/123/live', '/orders/:userId/live') → true\n * pathMatchesPattern('/orders/123/live', '/items/:id/live') → false\n */\nexport function pathMatchesPattern(concretePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&') // escape regex special chars except *\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)') // :param → capture group\n\n const regex = new RegExp(`^${regexStr}$`)\n return regex.test(concretePath)\n}\n\n/**\n * Extract named path params from a concrete path given a route pattern.\n *\n * extractParams('/orders/123/live', '/orders/:userId/live') → { userId: '123' }\n */\nexport function extractParams(concretePath: string, pattern: string): Record<string, string> {\n const paramNames: string[] = []\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name: string) => {\n paramNames.push(name)\n return '([^/]+)'\n })\n\n const regex = new RegExp(`^${regexStr}$`)\n const match = concretePath.match(regex)\n if (!match) return {}\n\n return Object.fromEntries(paramNames.map((name, i) => [name, match[i + 1]]))\n}\n","import { IncomingMessage, Server as HttpServer } from 'node:http'\nimport { randomUUID } from 'node:crypto'\nimport { WebSocketServer, WebSocket } from 'ws'\nimport type { ReactiveEngine } from '../reactive/engine.js'\nimport { extractParams } from '../reactive/engine.js'\nimport type { Context } from '../types.js'\nimport { ReactiveApiError } from '../errors.js'\n\n/** Message sent by the client to subscribe to a reactive path. */\ninterface SubscribeMessage {\n type: 'subscribe'\n path: string\n /** Optional query params the client wants included in the Context */\n query?: Record<string, string>\n}\n\n/** Message pushed by the server when data changes. */\ninterface UpdateMessage {\n type: 'update'\n path: string\n data: unknown\n}\n\n/** Message sent on error. */\ninterface ErrorMessage {\n type: 'error'\n code: string\n message: string\n}\n\ntype ServerMessage = UpdateMessage | ErrorMessage\n\nfunction isSubscribeMessage(value: unknown): value is SubscribeMessage {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>)['type'] === 'subscribe' &&\n typeof (value as Record<string, unknown>)['path'] === 'string'\n )\n}\n\n/**\n * WebSocket transport layer.\n *\n * Attaches a `ws` server to the underlying Node HTTP server. Clients connect\n * and send a subscribe message; the transport builds a Context and registers\n * the subscription with the ReactiveEngine.\n *\n * Protocol:\n * - Client → Server: `{ \"type\": \"subscribe\", \"path\": \"/orders/123/live\" }`\n * - Server → Client: `{ \"type\": \"update\", \"path\": \"/orders/123/live\", \"data\": [...] }`\n * - Server → Client: `{ \"type\": \"error\", \"code\": \"...\", \"message\": \"...\" }`\n */\nexport class WebSocketTransport {\n private readonly wss: WebSocketServer\n /** Maps clientId → registered route pattern, for param extraction */\n private readonly clientPatterns: Map<string, string> = new Map()\n\n constructor(\n private readonly engine: ReactiveEngine,\n /** All registered route patterns (e.g. ['/orders/:userId/live']) */\n private readonly routePatterns: string[],\n ) {\n // noServer=true so we can attach to Fastify's underlying http.Server manually\n this.wss = new WebSocketServer({ noServer: true })\n this.wss.on('connection', (ws, req) => this.handleConnection(ws, req))\n }\n\n /**\n * Attach to the raw Node.js HTTP server so WebSocket upgrade requests are\n * handled alongside Fastify routes.\n */\n attach(httpServer: HttpServer): void {\n httpServer.on('upgrade', (req, socket, head) => {\n this.wss.handleUpgrade(req, socket, head, (ws) => {\n this.wss.emit('connection', ws, req)\n })\n })\n }\n\n /** Gracefully close all connections. */\n async close(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.wss.close((err) => (err ? reject(err) : resolve()))\n })\n }\n\n // ---------------------------------------------------------------------------\n // Connection handling\n // ---------------------------------------------------------------------------\n\n private handleConnection(ws: WebSocket, _req: IncomingMessage): void {\n const clientId = randomUUID()\n\n ws.on('message', (raw) => {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw.toString())\n } catch {\n this.sendError(ws, 'INVALID_JSON', 'Message must be valid JSON')\n return\n }\n\n if (!isSubscribeMessage(parsed)) {\n this.sendError(ws, 'INVALID_MESSAGE', 'Expected { type: \"subscribe\", path: string }')\n return\n }\n\n this.handleSubscribe(ws, clientId, parsed)\n })\n\n ws.on('close', () => {\n this.engine.unsubscribe(clientId)\n this.clientPatterns.delete(clientId)\n })\n\n ws.on('error', (err) => {\n this.engine.unsubscribe(clientId)\n this.clientPatterns.delete(clientId)\n // ws errors are expected (client disconnect); just log in dev\n if (process.env['NODE_ENV'] !== 'production') {\n console.error('[RouteFlow] WebSocket error:', err.message)\n }\n })\n }\n\n private handleSubscribe(ws: WebSocket, clientId: string, msg: SubscribeMessage): void {\n const { path, query = {} } = msg\n\n // Find the matching route pattern\n const pattern = this.routePatterns.find((p) => this.matchesPattern(path, p))\n\n if (!pattern) {\n this.sendError(ws, 'NO_REACTIVE_ENDPOINT', `No reactive endpoint found for path: ${path}`)\n return\n }\n\n const params = extractParams(path, pattern)\n\n const ctx: Context = {\n params,\n query,\n body: undefined,\n headers: {},\n }\n\n const pushFn = (subscribedPath: string, data: unknown): void => {\n if (ws.readyState !== WebSocket.OPEN) return\n const msg: UpdateMessage = { type: 'update', path: subscribedPath, data }\n ws.send(JSON.stringify(msg))\n }\n\n // Unsubscribe any previous subscription for this client before re-subscribing\n this.engine.unsubscribe(clientId)\n this.clientPatterns.set(clientId, pattern)\n this.engine.subscribe(clientId, path, ctx, pushFn)\n }\n\n private matchesPattern(concretePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)')\n return new RegExp(`^${regexStr}$`).test(concretePath)\n }\n\n private sendError(ws: WebSocket, code: string, message: string): void {\n if (ws.readyState !== WebSocket.OPEN) return\n const msg: ErrorMessage = { type: 'error', code, message }\n ws.send(JSON.stringify(msg))\n }\n}\n","import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'\nimport { randomUUID } from 'node:crypto'\nimport type { ReactiveEngine } from '../reactive/engine.js'\nimport { extractParams } from '../reactive/engine.js'\nimport type { Context } from '../types.js'\nimport { ReactiveApiError } from '../errors.js'\n\n/**\n * Server-Sent Events transport layer.\n *\n * Clients connect via a GET request to `/_sse/subscribe?path=<encodedPath>`.\n * The server sends a stream of `data:` events in the standard SSE format.\n *\n * Event format (one JSON object per `data:` line):\n * ```\n * data: {\"type\":\"update\",\"path\":\"/orders/123/live\",\"data\":[...]}\n *\n * ```\n *\n * Unlike WebSocket, SSE is strictly server-to-client (unidirectional).\n * The subscribed path is passed as a query param on the initial GET request.\n *\n * Advantages over WebSocket in some environments:\n * - Works over plain HTTP/1.1 (no upgrade required)\n * - Automatic reconnection handled natively by the browser `EventSource`\n * - Firewall/proxy friendly\n */\nexport class SseTransport {\n /** clientId → reply (kept open) */\n private readonly connections: Map<string, FastifyReply> = new Map()\n\n constructor(\n private readonly engine: ReactiveEngine,\n private readonly routePatterns: string[],\n ) {}\n\n /**\n * Register the SSE subscription endpoint on the Fastify instance.\n * Must be called before `fastify.listen()`.\n */\n register(fastify: FastifyInstance): void {\n fastify.get('/_sse/subscribe', async (req: FastifyRequest, reply: FastifyReply) => {\n const query = req.query as Record<string, string>\n const path = query['path']\n\n if (!path) {\n throw new ReactiveApiError('SSE_MISSING_PATH', 'Query param \"path\" is required')\n }\n\n const decodedPath = decodeURIComponent(path)\n const pattern = this.routePatterns.find((p) => this.matchesPattern(decodedPath, p))\n\n if (!pattern) {\n reply.code(404)\n throw new ReactiveApiError(\n 'SSE_NO_REACTIVE_ENDPOINT',\n `No reactive endpoint found for path: ${decodedPath}`,\n )\n }\n\n const params = extractParams(decodedPath, pattern)\n const clientQuery = { ...query }\n delete clientQuery['path']\n\n const ctx: Context = {\n params,\n query: clientQuery,\n body: undefined,\n headers: req.headers as Record<string, string>,\n }\n\n const clientId = randomUUID()\n\n // Set SSE headers\n reply.raw.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no', // disable nginx buffering\n })\n\n // Send initial comment to establish connection\n reply.raw.write(': connected\\n\\n')\n\n this.connections.set(clientId, reply)\n\n const pushFn = (subscribedPath: string, data: unknown): void => {\n if (reply.raw.destroyed) return\n const payload = JSON.stringify({ type: 'update', path: subscribedPath, data })\n reply.raw.write(`data: ${payload}\\n\\n`)\n }\n\n this.engine.subscribe(clientId, decodedPath, ctx, pushFn)\n\n // Cleanup when client disconnects\n req.raw.on('close', () => {\n this.engine.unsubscribe(clientId)\n this.connections.delete(clientId)\n if (!reply.raw.destroyed) reply.raw.end()\n })\n\n // Keep the connection open — Fastify needs this to not auto-close\n await new Promise<void>((resolve) => {\n req.raw.on('close', resolve)\n req.raw.on('error', resolve)\n })\n })\n }\n\n /** Close all open SSE connections. */\n async close(): Promise<void> {\n for (const reply of this.connections.values()) {\n if (!reply.raw.destroyed) reply.raw.end()\n }\n this.connections.clear()\n }\n\n private matchesPattern(concretePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)')\n return new RegExp(`^${regexStr}$`).test(concretePath)\n }\n}\n","export type DatabaseCategory =\n | 'rdbms'\n | 'nosql'\n | 'time-series'\n | 'search-engine'\n | 'cloud-data-warehouse'\n | 'in-memory'\n | 'newsql'\n\nexport type DatabaseSupportMode = 'native-adapter' | 'polling-adapter' | 'external-cdc-bridge'\nexport type DatabaseSupportTier = 'official' | 'experimental'\n\nexport type DatabaseKey =\n | 'postgresql'\n | 'mysql'\n | 'mariadb'\n | 'oracle-db'\n | 'ms-sql-server'\n | 'sqlite'\n | 'mongodb'\n | 'redis'\n | 'cassandra'\n | 'dynamodb'\n | 'neo4j'\n | 'elasticsearch'\n | 'hbase'\n | 'couchdb'\n | 'influxdb'\n | 'timescaledb'\n | 'prometheus'\n | 'opensearch'\n | 'solr'\n | 'snowflake'\n | 'bigquery'\n | 'redshift'\n | 'azure-synapse'\n | 'memcached'\n | 'voltdb'\n | 'cockroachdb'\n | 'tidb'\n | 'spanner'\n\nexport interface DatabaseSupportDescriptor {\n key: DatabaseKey\n name: string\n aliases: string[]\n categories: DatabaseCategory[]\n supportedModes: DatabaseSupportMode[]\n tier: DatabaseSupportTier\n}\n\nexport const SUPPORTED_DATABASES: readonly DatabaseSupportDescriptor[] = [\n {\n key: 'postgresql',\n name: 'PostgreSQL',\n aliases: ['postgres', 'psql'],\n categories: ['rdbms'],\n supportedModes: ['native-adapter', 'polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'mysql',\n name: 'MySQL',\n aliases: [],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'mariadb',\n name: 'MariaDB',\n aliases: [],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'oracle-db',\n name: 'Oracle DB',\n aliases: ['oracle', 'oracle database'],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'ms-sql-server',\n name: 'MS SQL Server',\n aliases: ['sql server', 'mssql', 'ms sql'],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'sqlite',\n name: 'SQLite',\n aliases: [],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter'],\n tier: 'experimental',\n },\n {\n key: 'mongodb',\n name: 'MongoDB',\n aliases: ['mongo'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'redis',\n name: 'Redis',\n aliases: [],\n categories: ['nosql', 'in-memory'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'cassandra',\n name: 'Cassandra',\n aliases: ['apache cassandra'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'dynamodb',\n name: 'DynamoDB',\n aliases: ['dynamo'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'neo4j',\n name: 'Neo4j',\n aliases: [],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'elasticsearch',\n name: 'Elasticsearch',\n aliases: ['elastic'],\n categories: ['nosql', 'search-engine'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'hbase',\n name: 'HBase',\n aliases: ['apache hbase'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'couchdb',\n name: 'CouchDB',\n aliases: ['apache couchdb'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'influxdb',\n name: 'InfluxDB',\n aliases: ['influx'],\n categories: ['time-series'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'timescaledb',\n name: 'TimescaleDB',\n aliases: ['timescale'],\n categories: ['time-series', 'rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'prometheus',\n name: 'Prometheus',\n aliases: ['prom'],\n categories: ['time-series'],\n supportedModes: ['polling-adapter'],\n tier: 'experimental',\n },\n {\n key: 'opensearch',\n name: 'OpenSearch',\n aliases: [],\n categories: ['search-engine'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'solr',\n name: 'Solr',\n aliases: ['apache solr'],\n categories: ['search-engine'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'snowflake',\n name: 'Snowflake',\n aliases: [],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'bigquery',\n name: 'BigQuery',\n aliases: ['google bigquery'],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'redshift',\n name: 'Redshift',\n aliases: ['amazon redshift'],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'azure-synapse',\n name: 'Azure Synapse',\n aliases: ['synapse', 'azure synapse analytics'],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'memcached',\n name: 'Memcached',\n aliases: [],\n categories: ['in-memory'],\n supportedModes: ['polling-adapter'],\n tier: 'experimental',\n },\n {\n key: 'voltdb',\n name: 'VoltDB',\n aliases: [],\n categories: ['in-memory'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'cockroachdb',\n name: 'CockroachDB',\n aliases: ['cockroach'],\n categories: ['newsql', 'rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'tidb',\n name: 'TiDB',\n aliases: [],\n categories: ['newsql', 'rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'spanner',\n name: 'Spanner',\n aliases: ['google spanner', 'cloud spanner'],\n categories: ['newsql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n] as const\n\nfunction normaliseDatabaseName(value: string): string {\n return value.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim()\n}\n\nexport function getDatabaseSupport(name: string): DatabaseSupportDescriptor | undefined {\n const normalised = normaliseDatabaseName(name)\n\n return SUPPORTED_DATABASES.find((database) => {\n if (normaliseDatabaseName(database.name) === normalised) return true\n if (normaliseDatabaseName(database.key) === normalised) return true\n\n return database.aliases.some((alias) => normaliseDatabaseName(alias) === normalised)\n })\n}\n\nexport function listSupportedDatabases(\n category?: DatabaseCategory,\n): readonly DatabaseSupportDescriptor[] {\n if (!category) return SUPPORTED_DATABASES\n return SUPPORTED_DATABASES.filter((database) => database.categories.includes(category))\n}\n\nexport function listOfficialDatabases(\n category?: DatabaseCategory,\n): readonly DatabaseSupportDescriptor[] {\n const databases = listSupportedDatabases(category)\n return databases.filter((database) => database.tier === 'official')\n}\n","import type { ChangeEvent, DatabaseAdapter } from '../types.js'\n\n/**\n * In-memory database adapter for testing and local development.\n * No real database connection is needed — changes are triggered manually via `emit()`.\n *\n * @example\n * ```ts\n * const adapter = new MemoryAdapter()\n * await adapter.connect()\n *\n * adapter.onChange('orders', (event) => console.log(event))\n *\n * adapter.emit('orders', { operation: 'INSERT', newRow: { id: 1 }, oldRow: null })\n * ```\n */\nexport class MemoryAdapter implements DatabaseAdapter {\n private readonly listeners: Map<string, Set<(event: ChangeEvent) => void>> = new Map()\n private connected = false\n\n /** No-op — MemoryAdapter requires no real connection. */\n async connect(): Promise<void> {\n this.connected = true\n }\n\n /** No-op — clears all listeners on disconnect. */\n async disconnect(): Promise<void> {\n this.listeners.clear()\n this.connected = false\n }\n\n /**\n * Register a listener for changes on a specific table.\n * @returns An unsubscribe function.\n */\n onChange(table: string, callback: (event: ChangeEvent) => void): () => void {\n if (!this.listeners.has(table)) {\n this.listeners.set(table, new Set())\n }\n this.listeners.get(table)!.add(callback)\n\n return () => {\n this.listeners.get(table)?.delete(callback)\n }\n }\n\n /**\n * Manually emit a change event on a table.\n * Useful in tests and examples to simulate DB changes without a real database.\n *\n * @param table - Table name to emit the event on\n * @param event - Change event data (table and timestamp are filled in automatically)\n */\n emit(table: string, event: Omit<ChangeEvent, 'table' | 'timestamp'>): void {\n const fullEvent: ChangeEvent = {\n ...event,\n table,\n timestamp: Date.now(),\n }\n\n const callbacks = this.listeners.get(table)\n if (!callbacks) return\n\n for (const cb of callbacks) {\n cb(fullEvent)\n }\n }\n\n /** Returns true if connect() has been called and disconnect() has not. */\n get isConnected(): boolean {\n return this.connected\n }\n}\n","import type { ChangeEvent, DatabaseAdapter } from '../types.js'\n\nexport interface PollingReadResult<T = unknown, TCursor = unknown> {\n events: Array<Omit<ChangeEvent<T>, 'table' | 'timestamp'> & Partial<Pick<ChangeEvent<T>, 'table' | 'timestamp'>>>\n cursor?: TCursor\n}\n\nexport interface PollingReadContext<TCursor = unknown> {\n cursor: TCursor | undefined\n table: string\n}\n\nexport interface PollingAdapterOptions<TCursor = unknown> {\n intervalMs?: number\n now?: () => number\n onError?: (error: unknown, context: { table: string }) => void\n readChanges: (\n context: PollingReadContext<TCursor>,\n ) => Promise<PollingReadResult<unknown, TCursor>>\n}\n\n/**\n * Generic polling-based adapter for databases without a native RouteFlow adapter yet.\n * It works with any backend as long as callers can periodically read a change feed.\n */\nexport class PollingAdapter<TCursor = unknown> implements DatabaseAdapter {\n private readonly listeners: Map<string, Set<(event: ChangeEvent) => void>> = new Map()\n private readonly cursors: Map<string, TCursor | undefined> = new Map()\n private readonly timers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n private readonly activeTables: Set<string> = new Set()\n private readonly intervalMs: number\n private readonly now: () => number\n private readonly readChanges: PollingAdapterOptions<TCursor>['readChanges']\n private readonly onError?: PollingAdapterOptions<TCursor>['onError']\n private connected = false\n\n constructor(options: PollingAdapterOptions<TCursor>) {\n this.intervalMs = options.intervalMs ?? 1_000\n this.now = options.now ?? (() => Date.now())\n this.readChanges = options.readChanges\n this.onError = options.onError\n }\n\n async connect(): Promise<void> {\n this.connected = true\n\n for (const table of this.listeners.keys()) {\n this.ensurePolling(table)\n }\n }\n\n async disconnect(): Promise<void> {\n this.connected = false\n\n for (const timer of this.timers.values()) {\n clearTimeout(timer)\n }\n\n this.timers.clear()\n this.activeTables.clear()\n }\n\n onChange(table: string, callback: (event: ChangeEvent) => void): () => void {\n if (!this.listeners.has(table)) {\n this.listeners.set(table, new Set())\n }\n\n this.listeners.get(table)!.add(callback)\n this.ensurePolling(table)\n\n return () => {\n const callbacks = this.listeners.get(table)\n if (!callbacks) return\n\n callbacks.delete(callback)\n\n if (callbacks.size === 0) {\n this.listeners.delete(table)\n this.stopPolling(table)\n }\n }\n }\n\n private ensurePolling(table: string): void {\n if (!this.connected || this.activeTables.has(table)) return\n\n this.activeTables.add(table)\n void this.poll(table)\n }\n\n private stopPolling(table: string): void {\n const timer = this.timers.get(table)\n if (timer) clearTimeout(timer)\n\n this.timers.delete(table)\n this.activeTables.delete(table)\n this.cursors.delete(table)\n }\n\n private scheduleNext(table: string): void {\n if (!this.connected || !this.listeners.has(table)) {\n this.stopPolling(table)\n return\n }\n\n const timer = setTimeout(() => {\n void this.poll(table)\n }, this.intervalMs)\n\n this.timers.set(table, timer)\n }\n\n private async poll(table: string): Promise<void> {\n if (!this.connected || !this.listeners.has(table)) {\n this.stopPolling(table)\n return\n }\n\n try {\n const result = await this.readChanges({\n table,\n cursor: this.cursors.get(table),\n })\n\n this.cursors.set(table, result.cursor)\n\n for (const event of result.events) {\n this.emit(table, event)\n }\n } catch (error) {\n this.onError?.(error, { table })\n } finally {\n this.activeTables.delete(table)\n this.scheduleNext(table)\n }\n }\n\n private emit(\n defaultTable: string,\n event: Omit<ChangeEvent, 'table' | 'timestamp'> & Partial<Pick<ChangeEvent, 'table' | 'timestamp'>>,\n ): void {\n const fullEvent: ChangeEvent = {\n ...event,\n table: event.table ?? defaultTable,\n timestamp: event.timestamp ?? this.now(),\n }\n\n const callbacks = this.listeners.get(fullEvent.table)\n if (!callbacks) return\n\n for (const callback of callbacks) {\n callback(fullEvent)\n }\n }\n}\n"],"mappings":"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","names":["msg","randomUUID","randomUUID"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/core/decorator/route.ts","../src/core/decorator/reactive.ts","../src/core/errors.ts","../src/core/reactive/engine.ts","../src/core/transport/websocket-transport.ts","../src/core/transport/sse-transport.ts","../src/core/database-support.ts","../src/core/adapter/memory-adapter.ts","../src/core/adapter/polling-adapter.ts"],"sourcesContent":["import 'reflect-metadata'\nimport { writeFileSync, mkdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport Fastify, { FastifyInstance } from 'fastify'\nimport type { AppOptions, Context, ReactiveEndpoint } from './core/types.js'\nimport { ROUTE_METADATA, routeFnStore } from './core/decorator/route.js'\nimport { REACTIVE_METADATA, reactiveFnStore } from './core/decorator/reactive.js'\nimport type { RouteMetadata, ReactiveOptions } from './core/types.js'\nimport { ReactiveEngine } from './core/reactive/engine.js'\nimport { WebSocketTransport } from './core/transport/websocket-transport.js'\nimport { SseTransport } from './core/transport/sse-transport.js'\nimport { ReactiveApiError } from './core/errors.js'\n\nexport { Route } from './core/decorator/route.js'\nexport { Reactive } from './core/decorator/reactive.js'\nexport { ReactiveApiError } from './core/errors.js'\nexport {\n SUPPORTED_DATABASES,\n getDatabaseSupport,\n listOfficialDatabases,\n listSupportedDatabases,\n} from './core/database-support.js'\nexport type {\n Context,\n ChangeEvent,\n DatabaseAdapter,\n ReactiveOptions,\n AppOptions,\n HttpMethod,\n} from './core/types.js'\nexport type {\n DatabaseCategory,\n DatabaseKey,\n DatabaseSupportDescriptor,\n DatabaseSupportMode,\n DatabaseSupportTier,\n} from './core/database-support.js'\n\n// Adapters (built-in)\nexport { MemoryAdapter, PollingAdapter } from './core/adapter/index.js'\nexport type {\n PollingAdapterOptions,\n PollingReadContext,\n PollingReadResult,\n} from './core/adapter/index.js'\n\ntype AnyTransport = WebSocketTransport | SseTransport\n\n/**\n * Main application class. Use `createApp()` to instantiate.\n */\nexport class ReactiveApp {\n private readonly fastify: FastifyInstance\n private readonly engine: ReactiveEngine\n private transport: AnyTransport | null = null\n private readonly options: Required<AppOptions>\n /** Collected route patterns for reactive endpoints */\n private readonly reactivePatterns: string[] = []\n /** All registered routes (for .routeflow/info.json) */\n private readonly registeredRoutes: Array<{ method: string; path: string; reactive: boolean }> = []\n\n constructor(options: AppOptions) {\n this.options = {\n transport: 'websocket',\n port: 3000,\n ...options,\n }\n this.fastify = Fastify({ logger: false })\n this.engine = new ReactiveEngine(this.options.adapter)\n\n // Register a global error handler that serialises ReactiveApiError properly\n this.fastify.setErrorHandler((error, _req, reply) => {\n if (error instanceof ReactiveApiError) {\n const status = (error as ReactiveApiError & { statusCode?: number }).statusCode ?? 500\n reply.status(status).send({ error: error.code, message: error.message })\n } else {\n reply.status(500).send({ error: 'INTERNAL_ERROR', message: error.message })\n }\n })\n }\n\n /**\n * Register a controller class. Scans its methods for @Route and @Reactive\n * decorators and registers HTTP routes and reactive endpoints accordingly.\n *\n * @param ControllerClass - A class constructor whose methods may be decorated\n * with @Route and/or @Reactive.\n */\n register(ControllerClass: new () => object): this {\n const instance = new ControllerClass()\n const proto = Object.getPrototypeOf(instance) as object\n\n const methodNames = Object.getOwnPropertyNames(proto).filter(\n (name) =>\n name !== 'constructor' &&\n typeof (proto as Record<string, unknown>)[name] === 'function',\n )\n\n for (const methodName of methodNames) {\n const fn = (proto as Record<string, unknown>)[methodName] as object\n const routeMeta: RouteMetadata | undefined =\n routeFnStore.get(fn) ??\n (Reflect.getMetadata(ROUTE_METADATA, proto, methodName) as RouteMetadata | undefined)\n if (!routeMeta) continue\n\n const reactiveMeta: ReactiveOptions | undefined =\n reactiveFnStore.get(fn) ??\n (Reflect.getMetadata(REACTIVE_METADATA, proto, methodName) as ReactiveOptions | undefined)\n\n const handler = (instance as Record<string, unknown>)[methodName] as (\n ctx: Context,\n ) => Promise<unknown>\n\n // Register HTTP route with Fastify\n this.fastify.route({\n method: routeMeta.method,\n url: routeMeta.path,\n handler: async (req, reply) => {\n const ctx: Context = {\n params: req.params as Record<string, string>,\n query: req.query as Record<string, string>,\n body: req.body,\n headers: req.headers as Record<string, string>,\n }\n try {\n const result = await handler.call(instance, ctx)\n return reply.send(result)\n } catch (err) {\n if (err instanceof ReactiveApiError) throw err\n const msg = err instanceof Error ? err.message : String(err)\n throw new ReactiveApiError('HANDLER_ERROR', msg)\n }\n },\n })\n\n this.registeredRoutes.push({ method: routeMeta.method, path: routeMeta.path, reactive: !!reactiveMeta })\n\n // Register reactive endpoint if @Reactive is present\n if (reactiveMeta) {\n const endpoint: ReactiveEndpoint = {\n routePath: routeMeta.path,\n options: reactiveMeta,\n handler: (ctx: Context) => handler.call(instance, ctx),\n }\n this.engine.registerEndpoint(endpoint)\n this.reactivePatterns.push(routeMeta.path)\n }\n }\n\n return this\n }\n\n /**\n * Access the underlying Fastify instance for supplemental routes such as\n * health checks, static assets, or demo pages.\n */\n getFastify(): FastifyInstance {\n return this.fastify\n }\n\n /**\n * Start the HTTP server.\n * Connects the database adapter before accepting connections.\n *\n * @param port - Override the port set in AppOptions\n */\n async listen(port?: number): Promise<void> {\n const listenPort = port ?? this.options.port\n\n await this.options.adapter.connect()\n\n if (this.options.transport === 'websocket') {\n this.transport = new WebSocketTransport(this.engine, this.reactivePatterns)\n } else if (this.options.transport === 'sse') {\n const sseTransport = new SseTransport(this.engine, this.reactivePatterns)\n sseTransport.register(this.fastify)\n this.transport = sseTransport\n }\n\n await this.fastify.ready()\n\n if (this.transport instanceof WebSocketTransport) {\n this.transport.attach(this.fastify.server)\n }\n\n await this.fastify.listen({ port: listenPort, host: '0.0.0.0' })\n console.log(\n `[RouteFlow] Listening on port ${listenPort} (transport: ${this.options.transport})`,\n )\n\n this.writeInfo(listenPort)\n }\n\n private writeInfo(port: number): void {\n try {\n const dir = join(process.cwd(), '.routeflow')\n mkdirSync(dir, { recursive: true })\n writeFileSync(\n join(dir, 'info.json'),\n JSON.stringify(\n {\n port,\n transport: this.options.transport,\n adapter: this.options.adapter.constructor.name,\n routes: this.registeredRoutes,\n startedAt: new Date().toISOString(),\n },\n null,\n 2,\n ),\n )\n } catch {\n // 저장 실패 시 서버 동작에 영향 없음\n }\n }\n\n /**\n * Gracefully shut down the server and disconnect from the database.\n */\n async close(): Promise<void> {\n this.engine.destroy()\n if (this.transport) await this.transport.close()\n await this.fastify.close()\n await this.options.adapter.disconnect()\n }\n}\n\n/**\n * Create a new RouteFlow application.\n *\n * @example\n * ```ts\n * const app = createApp({ adapter: new MemoryAdapter(), port: 3000 })\n * app.register(MyController)\n * await app.listen()\n * ```\n */\nexport function createApp(options: AppOptions): ReactiveApp {\n return new ReactiveApp(options)\n}\n","import type { HttpMethod, RouteMetadata } from '../types.js'\n\n/** Symbol key used to store @Route metadata on a method. */\nexport const ROUTE_METADATA = Symbol('reactive-api:route')\n\n/**\n * Function-keyed store for TC39 (new-style) decorator compat.\n * Legacy decorators use Reflect.defineMetadata; TC39 decorators store here.\n */\nexport const routeFnStore = new WeakMap<object, RouteMetadata>()\n\n/**\n * Registers a class method as an HTTP endpoint.\n *\n * Works with both TypeScript legacy decorators (experimentalDecorators) and\n * TC39 Stage 3 decorators (as used by esbuild/tsx without legacy flag).\n *\n * @param method - HTTP verb\n * @param path - Route path, may include Fastify-style params (e.g. '/users/:id')\n */\nexport function Route(method: HttpMethod, path: string) {\n const metadata: RouteMetadata = { method, path }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return function (target: any, propertyKey: any): void {\n if (typeof target === 'function') {\n // TC39 decorator: target is the method function itself\n routeFnStore.set(target, metadata)\n } else if (typeof propertyKey === 'string') {\n // Legacy decorator: target is the prototype, propertyKey is method name\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const fn = target[propertyKey] as object\n if (fn) routeFnStore.set(fn, metadata)\n Reflect.defineMetadata(ROUTE_METADATA, metadata, target, propertyKey)\n }\n }\n}\n","import type { ReactiveOptions } from '../types.js'\n\n/** Symbol key used to store @Reactive metadata on a method. */\nexport const REACTIVE_METADATA = Symbol('reactive-api:reactive')\n\n/**\n * Function-keyed store for TC39 (new-style) decorator compat.\n */\nexport const reactiveFnStore = new WeakMap<object, ReactiveOptions>()\n\n/**\n * Marks a route handler as reactive — when the watched table(s) change,\n * the handler is re-executed and the result is pushed to all subscribed clients.\n *\n * Must be used together with @Route.\n * Works with both TypeScript legacy decorators and TC39 Stage 3 decorators.\n *\n * @param options - Reactive configuration (watch, filter, debounce)\n */\nexport function Reactive(options: ReactiveOptions) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return function (target: any, propertyKey: any): void {\n if (typeof target === 'function') {\n // TC39 decorator: target is the method function itself\n reactiveFnStore.set(target, options)\n } else if (typeof propertyKey === 'string') {\n // Legacy decorator: target is the prototype, propertyKey is method name\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const fn = target[propertyKey] as object\n if (fn) reactiveFnStore.set(fn, options)\n Reflect.defineMetadata(REACTIVE_METADATA, options, target, propertyKey)\n }\n }\n}\n","/**\n * Base error class for all RouteFlow errors.\n * Always use this instead of plain `Error` throughout the framework.\n */\nexport class ReactiveApiError extends Error {\n /** Machine-readable error code (e.g. 'ADAPTER_NOT_CONNECTED', 'INVALID_ROUTE') */\n readonly code: string\n\n constructor(code: string, message: string) {\n super(message)\n this.name = 'ReactiveApiError'\n this.code = code\n // Restore prototype chain (required when extending built-ins in TS)\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n","import type {\n ChangeEvent,\n Context,\n DatabaseAdapter,\n PushFn,\n ReactiveEndpoint,\n} from '../types.js'\nimport { ReactiveApiError } from '../errors.js'\n\ninterface Subscription {\n /** The concrete path the client subscribed to (e.g. '/orders/123/live') */\n path: string\n /** Context built from the subscribed path */\n ctx: Context\n /** Function to call when a push is ready */\n pushFn: PushFn\n}\n\n/**\n * Core reactive engine.\n *\n * Responsibilities:\n * 1. Holds the registry of @Reactive endpoints\n * 2. Subscribes to the DatabaseAdapter for each watched table\n * 3. On a ChangeEvent, fans out to matching subscribers after applying filters\n * 4. Supports optional per-subscriber debouncing\n */\nexport class ReactiveEngine {\n private readonly endpoints: ReactiveEndpoint[] = []\n /** clientId → Subscription */\n private readonly subscriptions: Map<string, Subscription> = new Map()\n /** table → adapter unsubscribe fn */\n private readonly tableWatchers: Map<string, () => void> = new Map()\n /** \"clientId:path\" → debounce timer id */\n private readonly debounceTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n\n constructor(private readonly adapter: DatabaseAdapter) {}\n\n /**\n * Register a reactive endpoint so the engine can fan-out pushes to subscribers.\n */\n registerEndpoint(endpoint: ReactiveEndpoint): void {\n this.endpoints.push(endpoint)\n\n const tables = Array.isArray(endpoint.options.watch)\n ? endpoint.options.watch\n : [endpoint.options.watch]\n\n for (const table of tables) {\n this.setupTableWatcher(table)\n }\n }\n\n /**\n * Subscribe a WebSocket client to a path.\n * When the watched table(s) change and the filter passes, pushFn is called.\n *\n * @param clientId - Unique identifier for the client connection\n * @param path - The concrete path the client subscribed to\n * @param ctx - Context built from the subscribed path\n * @param pushFn - Callback to deliver data to the client\n */\n subscribe(clientId: string, path: string, ctx: Context, pushFn: PushFn): void {\n this.subscriptions.set(clientId, { path, ctx, pushFn })\n }\n\n /**\n * Remove a client's subscription and clean up any pending debounce timers.\n */\n unsubscribe(clientId: string): void {\n this.subscriptions.delete(clientId)\n\n // Clean up any pending debounce timers for this client\n for (const key of this.debounceTimers.keys()) {\n if (key.startsWith(`${clientId}:`)) {\n clearTimeout(this.debounceTimers.get(key))\n this.debounceTimers.delete(key)\n }\n }\n }\n\n /**\n * Tear down all table watchers. Call this when the app shuts down.\n */\n destroy(): void {\n for (const unsubscribe of this.tableWatchers.values()) {\n unsubscribe()\n }\n this.tableWatchers.clear()\n\n for (const timer of this.debounceTimers.values()) {\n clearTimeout(timer)\n }\n this.debounceTimers.clear()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private setupTableWatcher(table: string): void {\n if (this.tableWatchers.has(table)) return // already watching\n\n const unsubscribe = this.adapter.onChange(table, (event) => {\n this.onChangeEvent(event)\n })\n this.tableWatchers.set(table, unsubscribe)\n }\n\n private onChangeEvent(event: ChangeEvent): void {\n // Find endpoints that watch this table\n const matchingEndpoints = this.endpoints.filter((ep) => {\n const tables = Array.isArray(ep.options.watch)\n ? ep.options.watch\n : [ep.options.watch]\n return tables.includes(event.table)\n })\n\n for (const endpoint of matchingEndpoints) {\n // Find all subscribers that are on this endpoint's route path\n for (const [clientId, sub] of this.subscriptions) {\n if (!pathMatchesPattern(sub.path, endpoint.routePath)) continue\n\n // Apply optional filter\n if (endpoint.options.filter) {\n try {\n if (!endpoint.options.filter(event, sub.ctx)) continue\n } catch (err) {\n // Filter threw — skip this subscriber rather than crashing\n continue\n }\n }\n\n this.schedulePush(clientId, endpoint, sub, event)\n }\n }\n }\n\n private schedulePush(\n clientId: string,\n endpoint: ReactiveEndpoint,\n sub: Subscription,\n _event: ChangeEvent,\n ): void {\n const debounceMs = endpoint.options.debounce\n\n if (debounceMs !== undefined && debounceMs > 0) {\n const timerKey = `${clientId}:${sub.path}`\n const existing = this.debounceTimers.get(timerKey)\n if (existing !== undefined) clearTimeout(existing)\n\n const timer = setTimeout(() => {\n this.debounceTimers.delete(timerKey)\n this.executePush(endpoint, sub)\n }, debounceMs)\n\n this.debounceTimers.set(timerKey, timer)\n } else {\n this.executePush(endpoint, sub)\n }\n }\n\n private executePush(endpoint: ReactiveEndpoint, sub: Subscription): void {\n endpoint.handler(sub.ctx).then(\n (data) => sub.pushFn(sub.path, data),\n (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err)\n throw new ReactiveApiError('HANDLER_ERROR', `Reactive handler failed: ${message}`)\n },\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Path matching utility\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if a concrete path matches a route pattern with named params.\n *\n * pathMatchesPattern('/orders/123/live', '/orders/:userId/live') → true\n * pathMatchesPattern('/orders/123/live', '/items/:id/live') → false\n */\nexport function pathMatchesPattern(concretePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&') // escape regex special chars except *\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)') // :param → capture group\n\n const regex = new RegExp(`^${regexStr}$`)\n return regex.test(concretePath)\n}\n\n/**\n * Extract named path params from a concrete path given a route pattern.\n *\n * extractParams('/orders/123/live', '/orders/:userId/live') → { userId: '123' }\n */\nexport function extractParams(concretePath: string, pattern: string): Record<string, string> {\n const paramNames: string[] = []\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, name: string) => {\n paramNames.push(name)\n return '([^/]+)'\n })\n\n const regex = new RegExp(`^${regexStr}$`)\n const match = concretePath.match(regex)\n if (!match) return {}\n\n return Object.fromEntries(paramNames.map((name, i) => [name, match[i + 1]]))\n}\n","import { IncomingMessage, Server as HttpServer } from 'node:http'\nimport { randomUUID } from 'node:crypto'\nimport { WebSocketServer, WebSocket } from 'ws'\nimport type { ReactiveEngine } from '../reactive/engine.js'\nimport { extractParams } from '../reactive/engine.js'\nimport type { Context } from '../types.js'\nimport { ReactiveApiError } from '../errors.js'\n\n/** Message sent by the client to subscribe to a reactive path. */\ninterface SubscribeMessage {\n type: 'subscribe'\n path: string\n /** Optional query params the client wants included in the Context */\n query?: Record<string, string>\n}\n\n/** Message pushed by the server when data changes. */\ninterface UpdateMessage {\n type: 'update'\n path: string\n data: unknown\n}\n\n/** Message sent on error. */\ninterface ErrorMessage {\n type: 'error'\n code: string\n message: string\n}\n\ntype ServerMessage = UpdateMessage | ErrorMessage\n\nfunction isSubscribeMessage(value: unknown): value is SubscribeMessage {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<string, unknown>)['type'] === 'subscribe' &&\n typeof (value as Record<string, unknown>)['path'] === 'string'\n )\n}\n\n/**\n * WebSocket transport layer.\n *\n * Attaches a `ws` server to the underlying Node HTTP server. Clients connect\n * and send a subscribe message; the transport builds a Context and registers\n * the subscription with the ReactiveEngine.\n *\n * Protocol:\n * - Client → Server: `{ \"type\": \"subscribe\", \"path\": \"/orders/123/live\" }`\n * - Server → Client: `{ \"type\": \"update\", \"path\": \"/orders/123/live\", \"data\": [...] }`\n * - Server → Client: `{ \"type\": \"error\", \"code\": \"...\", \"message\": \"...\" }`\n */\nexport class WebSocketTransport {\n private readonly wss: WebSocketServer\n /** Maps clientId → registered route pattern, for param extraction */\n private readonly clientPatterns: Map<string, string> = new Map()\n\n constructor(\n private readonly engine: ReactiveEngine,\n /** All registered route patterns (e.g. ['/orders/:userId/live']) */\n private readonly routePatterns: string[],\n ) {\n // noServer=true so we can attach to Fastify's underlying http.Server manually\n this.wss = new WebSocketServer({ noServer: true })\n this.wss.on('connection', (ws, req) => this.handleConnection(ws, req))\n }\n\n /**\n * Attach to the raw Node.js HTTP server so WebSocket upgrade requests are\n * handled alongside Fastify routes.\n */\n attach(httpServer: HttpServer): void {\n httpServer.on('upgrade', (req, socket, head) => {\n this.wss.handleUpgrade(req, socket, head, (ws) => {\n this.wss.emit('connection', ws, req)\n })\n })\n }\n\n /** Gracefully close all connections. */\n async close(): Promise<void> {\n return new Promise((resolve, reject) => {\n this.wss.close((err) => (err ? reject(err) : resolve()))\n })\n }\n\n // ---------------------------------------------------------------------------\n // Connection handling\n // ---------------------------------------------------------------------------\n\n private handleConnection(ws: WebSocket, _req: IncomingMessage): void {\n const clientId = randomUUID()\n\n ws.on('message', (raw) => {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw.toString())\n } catch {\n this.sendError(ws, 'INVALID_JSON', 'Message must be valid JSON')\n return\n }\n\n if (!isSubscribeMessage(parsed)) {\n this.sendError(ws, 'INVALID_MESSAGE', 'Expected { type: \"subscribe\", path: string }')\n return\n }\n\n this.handleSubscribe(ws, clientId, parsed)\n })\n\n ws.on('close', () => {\n this.engine.unsubscribe(clientId)\n this.clientPatterns.delete(clientId)\n })\n\n ws.on('error', (err) => {\n this.engine.unsubscribe(clientId)\n this.clientPatterns.delete(clientId)\n // ws errors are expected (client disconnect); just log in dev\n if (process.env['NODE_ENV'] !== 'production') {\n console.error('[RouteFlow] WebSocket error:', err.message)\n }\n })\n }\n\n private handleSubscribe(ws: WebSocket, clientId: string, msg: SubscribeMessage): void {\n const { path, query = {} } = msg\n\n // Find the matching route pattern\n const pattern = this.routePatterns.find((p) => this.matchesPattern(path, p))\n\n if (!pattern) {\n this.sendError(ws, 'NO_REACTIVE_ENDPOINT', `No reactive endpoint found for path: ${path}`)\n return\n }\n\n const params = extractParams(path, pattern)\n\n const ctx: Context = {\n params,\n query,\n body: undefined,\n headers: {},\n }\n\n const pushFn = (subscribedPath: string, data: unknown): void => {\n if (ws.readyState !== WebSocket.OPEN) return\n const msg: UpdateMessage = { type: 'update', path: subscribedPath, data }\n ws.send(JSON.stringify(msg))\n }\n\n // Unsubscribe any previous subscription for this client before re-subscribing\n this.engine.unsubscribe(clientId)\n this.clientPatterns.set(clientId, pattern)\n this.engine.subscribe(clientId, path, ctx, pushFn)\n }\n\n private matchesPattern(concretePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)')\n return new RegExp(`^${regexStr}$`).test(concretePath)\n }\n\n private sendError(ws: WebSocket, code: string, message: string): void {\n if (ws.readyState !== WebSocket.OPEN) return\n const msg: ErrorMessage = { type: 'error', code, message }\n ws.send(JSON.stringify(msg))\n }\n}\n","import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'\nimport { randomUUID } from 'node:crypto'\nimport type { ReactiveEngine } from '../reactive/engine.js'\nimport { extractParams } from '../reactive/engine.js'\nimport type { Context } from '../types.js'\nimport { ReactiveApiError } from '../errors.js'\n\n/**\n * Server-Sent Events transport layer.\n *\n * Clients connect via a GET request to `/_sse/subscribe?path=<encodedPath>`.\n * The server sends a stream of `data:` events in the standard SSE format.\n *\n * Event format (one JSON object per `data:` line):\n * ```\n * data: {\"type\":\"update\",\"path\":\"/orders/123/live\",\"data\":[...]}\n *\n * ```\n *\n * Unlike WebSocket, SSE is strictly server-to-client (unidirectional).\n * The subscribed path is passed as a query param on the initial GET request.\n *\n * Advantages over WebSocket in some environments:\n * - Works over plain HTTP/1.1 (no upgrade required)\n * - Automatic reconnection handled natively by the browser `EventSource`\n * - Firewall/proxy friendly\n */\nexport class SseTransport {\n /** clientId → reply (kept open) */\n private readonly connections: Map<string, FastifyReply> = new Map()\n\n constructor(\n private readonly engine: ReactiveEngine,\n private readonly routePatterns: string[],\n ) {}\n\n /**\n * Register the SSE subscription endpoint on the Fastify instance.\n * Must be called before `fastify.listen()`.\n */\n register(fastify: FastifyInstance): void {\n fastify.get('/_sse/subscribe', async (req: FastifyRequest, reply: FastifyReply) => {\n const query = req.query as Record<string, string>\n const path = query['path']\n\n if (!path) {\n throw new ReactiveApiError('SSE_MISSING_PATH', 'Query param \"path\" is required')\n }\n\n const decodedPath = decodeURIComponent(path)\n const pattern = this.routePatterns.find((p) => this.matchesPattern(decodedPath, p))\n\n if (!pattern) {\n reply.code(404)\n throw new ReactiveApiError(\n 'SSE_NO_REACTIVE_ENDPOINT',\n `No reactive endpoint found for path: ${decodedPath}`,\n )\n }\n\n const params = extractParams(decodedPath, pattern)\n const clientQuery = { ...query }\n delete clientQuery['path']\n\n const ctx: Context = {\n params,\n query: clientQuery,\n body: undefined,\n headers: req.headers as Record<string, string>,\n }\n\n const clientId = randomUUID()\n\n // Set SSE headers\n reply.raw.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no', // disable nginx buffering\n })\n\n // Send initial comment to establish connection\n reply.raw.write(': connected\\n\\n')\n\n this.connections.set(clientId, reply)\n\n const pushFn = (subscribedPath: string, data: unknown): void => {\n if (reply.raw.destroyed) return\n const payload = JSON.stringify({ type: 'update', path: subscribedPath, data })\n reply.raw.write(`data: ${payload}\\n\\n`)\n }\n\n this.engine.subscribe(clientId, decodedPath, ctx, pushFn)\n\n // Cleanup when client disconnects\n req.raw.on('close', () => {\n this.engine.unsubscribe(clientId)\n this.connections.delete(clientId)\n if (!reply.raw.destroyed) reply.raw.end()\n })\n\n // Keep the connection open — Fastify needs this to not auto-close\n await new Promise<void>((resolve) => {\n req.raw.on('close', resolve)\n req.raw.on('error', resolve)\n })\n })\n }\n\n /** Close all open SSE connections. */\n async close(): Promise<void> {\n for (const reply of this.connections.values()) {\n if (!reply.raw.destroyed) reply.raw.end()\n }\n this.connections.clear()\n }\n\n private matchesPattern(concretePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '([^/]+)')\n return new RegExp(`^${regexStr}$`).test(concretePath)\n }\n}\n","export type DatabaseCategory =\n | 'rdbms'\n | 'nosql'\n | 'time-series'\n | 'search-engine'\n | 'cloud-data-warehouse'\n | 'in-memory'\n | 'newsql'\n\nexport type DatabaseSupportMode = 'native-adapter' | 'polling-adapter' | 'external-cdc-bridge'\nexport type DatabaseSupportTier = 'official' | 'experimental'\n\nexport type DatabaseKey =\n | 'postgresql'\n | 'mysql'\n | 'mariadb'\n | 'oracle-db'\n | 'ms-sql-server'\n | 'sqlite'\n | 'mongodb'\n | 'redis'\n | 'cassandra'\n | 'dynamodb'\n | 'neo4j'\n | 'elasticsearch'\n | 'hbase'\n | 'couchdb'\n | 'influxdb'\n | 'timescaledb'\n | 'prometheus'\n | 'opensearch'\n | 'solr'\n | 'snowflake'\n | 'bigquery'\n | 'redshift'\n | 'azure-synapse'\n | 'memcached'\n | 'voltdb'\n | 'cockroachdb'\n | 'tidb'\n | 'spanner'\n\nexport interface DatabaseSupportDescriptor {\n key: DatabaseKey\n name: string\n aliases: string[]\n categories: DatabaseCategory[]\n supportedModes: DatabaseSupportMode[]\n tier: DatabaseSupportTier\n}\n\nexport const SUPPORTED_DATABASES: readonly DatabaseSupportDescriptor[] = [\n {\n key: 'postgresql',\n name: 'PostgreSQL',\n aliases: ['postgres', 'psql'],\n categories: ['rdbms'],\n supportedModes: ['native-adapter', 'polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'mysql',\n name: 'MySQL',\n aliases: [],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'mariadb',\n name: 'MariaDB',\n aliases: [],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'oracle-db',\n name: 'Oracle DB',\n aliases: ['oracle', 'oracle database'],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'ms-sql-server',\n name: 'MS SQL Server',\n aliases: ['sql server', 'mssql', 'ms sql'],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'sqlite',\n name: 'SQLite',\n aliases: [],\n categories: ['rdbms'],\n supportedModes: ['polling-adapter'],\n tier: 'experimental',\n },\n {\n key: 'mongodb',\n name: 'MongoDB',\n aliases: ['mongo'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'redis',\n name: 'Redis',\n aliases: [],\n categories: ['nosql', 'in-memory'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'cassandra',\n name: 'Cassandra',\n aliases: ['apache cassandra'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'dynamodb',\n name: 'DynamoDB',\n aliases: ['dynamo'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'neo4j',\n name: 'Neo4j',\n aliases: [],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'elasticsearch',\n name: 'Elasticsearch',\n aliases: ['elastic'],\n categories: ['nosql', 'search-engine'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'hbase',\n name: 'HBase',\n aliases: ['apache hbase'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'couchdb',\n name: 'CouchDB',\n aliases: ['apache couchdb'],\n categories: ['nosql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'influxdb',\n name: 'InfluxDB',\n aliases: ['influx'],\n categories: ['time-series'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'timescaledb',\n name: 'TimescaleDB',\n aliases: ['timescale'],\n categories: ['time-series', 'rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'prometheus',\n name: 'Prometheus',\n aliases: ['prom'],\n categories: ['time-series'],\n supportedModes: ['polling-adapter'],\n tier: 'experimental',\n },\n {\n key: 'opensearch',\n name: 'OpenSearch',\n aliases: [],\n categories: ['search-engine'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'solr',\n name: 'Solr',\n aliases: ['apache solr'],\n categories: ['search-engine'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'snowflake',\n name: 'Snowflake',\n aliases: [],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'official',\n },\n {\n key: 'bigquery',\n name: 'BigQuery',\n aliases: ['google bigquery'],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'redshift',\n name: 'Redshift',\n aliases: ['amazon redshift'],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'azure-synapse',\n name: 'Azure Synapse',\n aliases: ['synapse', 'azure synapse analytics'],\n categories: ['cloud-data-warehouse'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'memcached',\n name: 'Memcached',\n aliases: [],\n categories: ['in-memory'],\n supportedModes: ['polling-adapter'],\n tier: 'experimental',\n },\n {\n key: 'voltdb',\n name: 'VoltDB',\n aliases: [],\n categories: ['in-memory'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'cockroachdb',\n name: 'CockroachDB',\n aliases: ['cockroach'],\n categories: ['newsql', 'rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'tidb',\n name: 'TiDB',\n aliases: [],\n categories: ['newsql', 'rdbms'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n {\n key: 'spanner',\n name: 'Spanner',\n aliases: ['google spanner', 'cloud spanner'],\n categories: ['newsql'],\n supportedModes: ['polling-adapter', 'external-cdc-bridge'],\n tier: 'experimental',\n },\n] as const\n\nfunction normaliseDatabaseName(value: string): string {\n return value.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim()\n}\n\nexport function getDatabaseSupport(name: string): DatabaseSupportDescriptor | undefined {\n const normalised = normaliseDatabaseName(name)\n\n return SUPPORTED_DATABASES.find((database) => {\n if (normaliseDatabaseName(database.name) === normalised) return true\n if (normaliseDatabaseName(database.key) === normalised) return true\n\n return database.aliases.some((alias) => normaliseDatabaseName(alias) === normalised)\n })\n}\n\nexport function listSupportedDatabases(\n category?: DatabaseCategory,\n): readonly DatabaseSupportDescriptor[] {\n if (!category) return SUPPORTED_DATABASES\n return SUPPORTED_DATABASES.filter((database) => database.categories.includes(category))\n}\n\nexport function listOfficialDatabases(\n category?: DatabaseCategory,\n): readonly DatabaseSupportDescriptor[] {\n const databases = listSupportedDatabases(category)\n return databases.filter((database) => database.tier === 'official')\n}\n","import type { ChangeEvent, DatabaseAdapter } from '../types.js'\n\n/**\n * In-memory database adapter for testing and local development.\n * No real database connection is needed — changes are triggered manually via `emit()`.\n *\n * @example\n * ```ts\n * const adapter = new MemoryAdapter()\n * await adapter.connect()\n *\n * adapter.onChange('orders', (event) => console.log(event))\n *\n * adapter.emit('orders', { operation: 'INSERT', newRow: { id: 1 }, oldRow: null })\n * ```\n */\nexport class MemoryAdapter implements DatabaseAdapter {\n private readonly listeners: Map<string, Set<(event: ChangeEvent) => void>> = new Map()\n private connected = false\n\n /** No-op — MemoryAdapter requires no real connection. */\n async connect(): Promise<void> {\n this.connected = true\n }\n\n /** No-op — clears all listeners on disconnect. */\n async disconnect(): Promise<void> {\n this.listeners.clear()\n this.connected = false\n }\n\n /**\n * Register a listener for changes on a specific table.\n * @returns An unsubscribe function.\n */\n onChange(table: string, callback: (event: ChangeEvent) => void): () => void {\n if (!this.listeners.has(table)) {\n this.listeners.set(table, new Set())\n }\n this.listeners.get(table)!.add(callback)\n\n return () => {\n this.listeners.get(table)?.delete(callback)\n }\n }\n\n /**\n * Manually emit a change event on a table.\n * Useful in tests and examples to simulate DB changes without a real database.\n *\n * @param table - Table name to emit the event on\n * @param event - Change event data (table and timestamp are filled in automatically)\n */\n emit(table: string, event: Omit<ChangeEvent, 'table' | 'timestamp'>): void {\n const fullEvent: ChangeEvent = {\n ...event,\n table,\n timestamp: Date.now(),\n }\n\n const callbacks = this.listeners.get(table)\n if (!callbacks) return\n\n for (const cb of callbacks) {\n cb(fullEvent)\n }\n }\n\n /** Returns true if connect() has been called and disconnect() has not. */\n get isConnected(): boolean {\n return this.connected\n }\n}\n","import type { ChangeEvent, DatabaseAdapter } from '../types.js'\n\nexport interface PollingReadResult<T = unknown, TCursor = unknown> {\n events: Array<Omit<ChangeEvent<T>, 'table' | 'timestamp'> & Partial<Pick<ChangeEvent<T>, 'table' | 'timestamp'>>>\n cursor?: TCursor\n}\n\nexport interface PollingReadContext<TCursor = unknown> {\n cursor: TCursor | undefined\n table: string\n}\n\nexport interface PollingAdapterOptions<TCursor = unknown> {\n intervalMs?: number\n now?: () => number\n onError?: (error: unknown, context: { table: string }) => void\n readChanges: (\n context: PollingReadContext<TCursor>,\n ) => Promise<PollingReadResult<unknown, TCursor>>\n}\n\n/**\n * Generic polling-based adapter for databases without a native RouteFlow adapter yet.\n * It works with any backend as long as callers can periodically read a change feed.\n */\nexport class PollingAdapter<TCursor = unknown> implements DatabaseAdapter {\n private readonly listeners: Map<string, Set<(event: ChangeEvent) => void>> = new Map()\n private readonly cursors: Map<string, TCursor | undefined> = new Map()\n private readonly timers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n private readonly activeTables: Set<string> = new Set()\n private readonly intervalMs: number\n private readonly now: () => number\n private readonly readChanges: PollingAdapterOptions<TCursor>['readChanges']\n private readonly onError?: PollingAdapterOptions<TCursor>['onError']\n private connected = false\n\n constructor(options: PollingAdapterOptions<TCursor>) {\n this.intervalMs = options.intervalMs ?? 1_000\n this.now = options.now ?? (() => Date.now())\n this.readChanges = options.readChanges\n this.onError = options.onError\n }\n\n async connect(): Promise<void> {\n this.connected = true\n\n for (const table of this.listeners.keys()) {\n this.ensurePolling(table)\n }\n }\n\n async disconnect(): Promise<void> {\n this.connected = false\n\n for (const timer of this.timers.values()) {\n clearTimeout(timer)\n }\n\n this.timers.clear()\n this.activeTables.clear()\n }\n\n onChange(table: string, callback: (event: ChangeEvent) => void): () => void {\n if (!this.listeners.has(table)) {\n this.listeners.set(table, new Set())\n }\n\n this.listeners.get(table)!.add(callback)\n this.ensurePolling(table)\n\n return () => {\n const callbacks = this.listeners.get(table)\n if (!callbacks) return\n\n callbacks.delete(callback)\n\n if (callbacks.size === 0) {\n this.listeners.delete(table)\n this.stopPolling(table)\n }\n }\n }\n\n private ensurePolling(table: string): void {\n if (!this.connected || this.activeTables.has(table)) return\n\n this.activeTables.add(table)\n void this.poll(table)\n }\n\n private stopPolling(table: string): void {\n const timer = this.timers.get(table)\n if (timer) clearTimeout(timer)\n\n this.timers.delete(table)\n this.activeTables.delete(table)\n this.cursors.delete(table)\n }\n\n private scheduleNext(table: string): void {\n if (!this.connected || !this.listeners.has(table)) {\n this.stopPolling(table)\n return\n }\n\n const timer = setTimeout(() => {\n void this.poll(table)\n }, this.intervalMs)\n\n this.timers.set(table, timer)\n }\n\n private async poll(table: string): Promise<void> {\n if (!this.connected || !this.listeners.has(table)) {\n this.stopPolling(table)\n return\n }\n\n try {\n const result = await this.readChanges({\n table,\n cursor: this.cursors.get(table),\n })\n\n this.cursors.set(table, result.cursor)\n\n for (const event of result.events) {\n this.emit(table, event)\n }\n } catch (error) {\n this.onError?.(error, { table })\n } finally {\n this.activeTables.delete(table)\n this.scheduleNext(table)\n }\n }\n\n private emit(\n defaultTable: string,\n event: Omit<ChangeEvent, 'table' | 'timestamp'> & Partial<Pick<ChangeEvent, 'table' | 'timestamp'>>,\n ): void {\n const fullEvent: ChangeEvent = {\n ...event,\n table: event.table ?? defaultTable,\n timestamp: event.timestamp ?? this.now(),\n }\n\n const callbacks = this.listeners.get(fullEvent.table)\n if (!callbacks) return\n\n for (const callback of callbacks) {\n callback(fullEvent)\n }\n }\n}\n"],"mappings":"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","names":["msg","randomUUID","randomUUID"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "routeflow-api",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "RouteFlow — REST API with real-time database push subscriptions",
5
5
  "license": "MIT",
6
6
  "repository": {