routeflow-api 0.2.0

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.
Files changed (70) hide show
  1. package/README.md +93 -0
  2. package/dist/adapters/cassandra.cjs +117 -0
  3. package/dist/adapters/cassandra.cjs.map +1 -0
  4. package/dist/adapters/cassandra.d.cts +37 -0
  5. package/dist/adapters/cassandra.d.ts +37 -0
  6. package/dist/adapters/cassandra.js +90 -0
  7. package/dist/adapters/cassandra.js.map +1 -0
  8. package/dist/adapters/dynamodb.cjs +180 -0
  9. package/dist/adapters/dynamodb.cjs.map +1 -0
  10. package/dist/adapters/dynamodb.d.cts +48 -0
  11. package/dist/adapters/dynamodb.d.ts +48 -0
  12. package/dist/adapters/dynamodb.js +153 -0
  13. package/dist/adapters/dynamodb.js.map +1 -0
  14. package/dist/adapters/elasticsearch.cjs +120 -0
  15. package/dist/adapters/elasticsearch.cjs.map +1 -0
  16. package/dist/adapters/elasticsearch.d.cts +43 -0
  17. package/dist/adapters/elasticsearch.d.ts +43 -0
  18. package/dist/adapters/elasticsearch.js +93 -0
  19. package/dist/adapters/elasticsearch.js.map +1 -0
  20. package/dist/adapters/mongodb.cjs +159 -0
  21. package/dist/adapters/mongodb.cjs.map +1 -0
  22. package/dist/adapters/mongodb.d.cts +54 -0
  23. package/dist/adapters/mongodb.d.ts +54 -0
  24. package/dist/adapters/mongodb.js +132 -0
  25. package/dist/adapters/mongodb.js.map +1 -0
  26. package/dist/adapters/mysql.cjs +159 -0
  27. package/dist/adapters/mysql.cjs.map +1 -0
  28. package/dist/adapters/mysql.d.cts +63 -0
  29. package/dist/adapters/mysql.d.ts +63 -0
  30. package/dist/adapters/mysql.js +132 -0
  31. package/dist/adapters/mysql.js.map +1 -0
  32. package/dist/adapters/opensearch.cjs +120 -0
  33. package/dist/adapters/opensearch.cjs.map +1 -0
  34. package/dist/adapters/opensearch.d.cts +2 -0
  35. package/dist/adapters/opensearch.d.ts +2 -0
  36. package/dist/adapters/opensearch.js +93 -0
  37. package/dist/adapters/opensearch.js.map +1 -0
  38. package/dist/adapters/postgres.cjs +271 -0
  39. package/dist/adapters/postgres.cjs.map +1 -0
  40. package/dist/adapters/postgres.d.cts +81 -0
  41. package/dist/adapters/postgres.d.ts +81 -0
  42. package/dist/adapters/postgres.js +244 -0
  43. package/dist/adapters/postgres.js.map +1 -0
  44. package/dist/adapters/redis.cjs +153 -0
  45. package/dist/adapters/redis.cjs.map +1 -0
  46. package/dist/adapters/redis.d.cts +40 -0
  47. package/dist/adapters/redis.d.ts +40 -0
  48. package/dist/adapters/redis.js +126 -0
  49. package/dist/adapters/redis.js.map +1 -0
  50. package/dist/adapters/snowflake.cjs +117 -0
  51. package/dist/adapters/snowflake.cjs.map +1 -0
  52. package/dist/adapters/snowflake.d.cts +37 -0
  53. package/dist/adapters/snowflake.d.ts +37 -0
  54. package/dist/adapters/snowflake.js +90 -0
  55. package/dist/adapters/snowflake.js.map +1 -0
  56. package/dist/client/index.cjs +484 -0
  57. package/dist/client/index.cjs.map +1 -0
  58. package/dist/client/index.d.cts +174 -0
  59. package/dist/client/index.d.ts +174 -0
  60. package/dist/client/index.js +455 -0
  61. package/dist/client/index.js.map +1 -0
  62. package/dist/index.cjs +935 -0
  63. package/dist/index.cjs.map +1 -0
  64. package/dist/index.d.cts +190 -0
  65. package/dist/index.d.ts +190 -0
  66. package/dist/index.js +890 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/types-tPDla8AE.d.cts +75 -0
  69. package/dist/types-tPDla8AE.d.ts +75 -0
  70. package/package.json +157 -0
@@ -0,0 +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 } from './core/decorator/route.js'\nimport { REACTIVE_METADATA } 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 routeMeta: RouteMetadata | undefined = Reflect.getMetadata(\n ROUTE_METADATA,\n proto,\n methodName,\n )\n if (!routeMeta) continue\n\n const reactiveMeta: ReactiveOptions | undefined = Reflect.getMetadata(\n REACTIVE_METADATA,\n proto,\n methodName,\n )\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 * Registers a class method as an HTTP endpoint.\n *\n * @param method - HTTP verb\n * @param path - Route path, may include Fastify-style params (e.g. '/users/:id')\n *\n * @example\n * ```ts\n * @Route('GET', '/users/:id')\n * async getUser(ctx: Context) { ... }\n * ```\n */\nexport function Route(method: HttpMethod, path: string) {\n return function (target: object, propertyKey: string): void {\n const metadata: RouteMetadata = { method, path }\n Reflect.defineMetadata(ROUTE_METADATA, metadata, target, propertyKey)\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 * 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 *\n * @param options - Reactive configuration (watch, filter, debounce)\n *\n * @example\n * ```ts\n * @Reactive({ watch: 'orders', filter: (event, ctx) => event.newRow?.userId === ctx.params.userId })\n * @Route('GET', '/orders/:userId/live')\n * async getLiveOrders(ctx: Context) { ... }\n * ```\n */\nexport function Reactive(options: ReactiveOptions) {\n return function (target: object, propertyKey: string): void {\n Reflect.defineMetadata(REACTIVE_METADATA, options, target, propertyKey)\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"]}
@@ -0,0 +1,190 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ import { H as HttpMethod, R as ReactiveOptions, D as DatabaseAdapter, C as ChangeEvent, A as AppOptions } from './types-tPDla8AE.cjs';
3
+ export { a as Context } from './types-tPDla8AE.cjs';
4
+
5
+ /**
6
+ * Registers a class method as an HTTP endpoint.
7
+ *
8
+ * @param method - HTTP verb
9
+ * @param path - Route path, may include Fastify-style params (e.g. '/users/:id')
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * @Route('GET', '/users/:id')
14
+ * async getUser(ctx: Context) { ... }
15
+ * ```
16
+ */
17
+ declare function Route(method: HttpMethod, path: string): (target: object, propertyKey: string) => void;
18
+
19
+ /**
20
+ * Marks a route handler as reactive — when the watched table(s) change,
21
+ * the handler is re-executed and the result is pushed to all subscribed clients.
22
+ *
23
+ * Must be used together with @Route.
24
+ *
25
+ * @param options - Reactive configuration (watch, filter, debounce)
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * @Reactive({ watch: 'orders', filter: (event, ctx) => event.newRow?.userId === ctx.params.userId })
30
+ * @Route('GET', '/orders/:userId/live')
31
+ * async getLiveOrders(ctx: Context) { ... }
32
+ * ```
33
+ */
34
+ declare function Reactive(options: ReactiveOptions): (target: object, propertyKey: string) => void;
35
+
36
+ /**
37
+ * Base error class for all RouteFlow errors.
38
+ * Always use this instead of plain `Error` throughout the framework.
39
+ */
40
+ declare class ReactiveApiError extends Error {
41
+ /** Machine-readable error code (e.g. 'ADAPTER_NOT_CONNECTED', 'INVALID_ROUTE') */
42
+ readonly code: string;
43
+ constructor(code: string, message: string);
44
+ }
45
+
46
+ type DatabaseCategory = 'rdbms' | 'nosql' | 'time-series' | 'search-engine' | 'cloud-data-warehouse' | 'in-memory' | 'newsql';
47
+ type DatabaseSupportMode = 'native-adapter' | 'polling-adapter' | 'external-cdc-bridge';
48
+ type DatabaseSupportTier = 'official' | 'experimental';
49
+ type DatabaseKey = 'postgresql' | 'mysql' | 'mariadb' | 'oracle-db' | 'ms-sql-server' | 'sqlite' | 'mongodb' | 'redis' | 'cassandra' | 'dynamodb' | 'neo4j' | 'elasticsearch' | 'hbase' | 'couchdb' | 'influxdb' | 'timescaledb' | 'prometheus' | 'opensearch' | 'solr' | 'snowflake' | 'bigquery' | 'redshift' | 'azure-synapse' | 'memcached' | 'voltdb' | 'cockroachdb' | 'tidb' | 'spanner';
50
+ interface DatabaseSupportDescriptor {
51
+ key: DatabaseKey;
52
+ name: string;
53
+ aliases: string[];
54
+ categories: DatabaseCategory[];
55
+ supportedModes: DatabaseSupportMode[];
56
+ tier: DatabaseSupportTier;
57
+ }
58
+ declare const SUPPORTED_DATABASES: readonly DatabaseSupportDescriptor[];
59
+ declare function getDatabaseSupport(name: string): DatabaseSupportDescriptor | undefined;
60
+ declare function listSupportedDatabases(category?: DatabaseCategory): readonly DatabaseSupportDescriptor[];
61
+ declare function listOfficialDatabases(category?: DatabaseCategory): readonly DatabaseSupportDescriptor[];
62
+
63
+ /**
64
+ * In-memory database adapter for testing and local development.
65
+ * No real database connection is needed — changes are triggered manually via `emit()`.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const adapter = new MemoryAdapter()
70
+ * await adapter.connect()
71
+ *
72
+ * adapter.onChange('orders', (event) => console.log(event))
73
+ *
74
+ * adapter.emit('orders', { operation: 'INSERT', newRow: { id: 1 }, oldRow: null })
75
+ * ```
76
+ */
77
+ declare class MemoryAdapter implements DatabaseAdapter {
78
+ private readonly listeners;
79
+ private connected;
80
+ /** No-op — MemoryAdapter requires no real connection. */
81
+ connect(): Promise<void>;
82
+ /** No-op — clears all listeners on disconnect. */
83
+ disconnect(): Promise<void>;
84
+ /**
85
+ * Register a listener for changes on a specific table.
86
+ * @returns An unsubscribe function.
87
+ */
88
+ onChange(table: string, callback: (event: ChangeEvent) => void): () => void;
89
+ /**
90
+ * Manually emit a change event on a table.
91
+ * Useful in tests and examples to simulate DB changes without a real database.
92
+ *
93
+ * @param table - Table name to emit the event on
94
+ * @param event - Change event data (table and timestamp are filled in automatically)
95
+ */
96
+ emit(table: string, event: Omit<ChangeEvent, 'table' | 'timestamp'>): void;
97
+ /** Returns true if connect() has been called and disconnect() has not. */
98
+ get isConnected(): boolean;
99
+ }
100
+
101
+ interface PollingReadResult<T = unknown, TCursor = unknown> {
102
+ events: Array<Omit<ChangeEvent<T>, 'table' | 'timestamp'> & Partial<Pick<ChangeEvent<T>, 'table' | 'timestamp'>>>;
103
+ cursor?: TCursor;
104
+ }
105
+ interface PollingReadContext<TCursor = unknown> {
106
+ cursor: TCursor | undefined;
107
+ table: string;
108
+ }
109
+ interface PollingAdapterOptions<TCursor = unknown> {
110
+ intervalMs?: number;
111
+ now?: () => number;
112
+ onError?: (error: unknown, context: {
113
+ table: string;
114
+ }) => void;
115
+ readChanges: (context: PollingReadContext<TCursor>) => Promise<PollingReadResult<unknown, TCursor>>;
116
+ }
117
+ /**
118
+ * Generic polling-based adapter for databases without a native RouteFlow adapter yet.
119
+ * It works with any backend as long as callers can periodically read a change feed.
120
+ */
121
+ declare class PollingAdapter<TCursor = unknown> implements DatabaseAdapter {
122
+ private readonly listeners;
123
+ private readonly cursors;
124
+ private readonly timers;
125
+ private readonly activeTables;
126
+ private readonly intervalMs;
127
+ private readonly now;
128
+ private readonly readChanges;
129
+ private readonly onError?;
130
+ private connected;
131
+ constructor(options: PollingAdapterOptions<TCursor>);
132
+ connect(): Promise<void>;
133
+ disconnect(): Promise<void>;
134
+ onChange(table: string, callback: (event: ChangeEvent) => void): () => void;
135
+ private ensurePolling;
136
+ private stopPolling;
137
+ private scheduleNext;
138
+ private poll;
139
+ private emit;
140
+ }
141
+
142
+ /**
143
+ * Main application class. Use `createApp()` to instantiate.
144
+ */
145
+ declare class ReactiveApp {
146
+ private readonly fastify;
147
+ private readonly engine;
148
+ private transport;
149
+ private readonly options;
150
+ /** Collected route patterns for reactive endpoints */
151
+ private readonly reactivePatterns;
152
+ constructor(options: AppOptions);
153
+ /**
154
+ * Register a controller class. Scans its methods for @Route and @Reactive
155
+ * decorators and registers HTTP routes and reactive endpoints accordingly.
156
+ *
157
+ * @param ControllerClass - A class constructor whose methods may be decorated
158
+ * with @Route and/or @Reactive.
159
+ */
160
+ register(ControllerClass: new () => object): this;
161
+ /**
162
+ * Access the underlying Fastify instance for supplemental routes such as
163
+ * health checks, static assets, or demo pages.
164
+ */
165
+ getFastify(): FastifyInstance;
166
+ /**
167
+ * Start the HTTP server.
168
+ * Connects the database adapter before accepting connections.
169
+ *
170
+ * @param port - Override the port set in AppOptions
171
+ */
172
+ listen(port?: number): Promise<void>;
173
+ /**
174
+ * Gracefully shut down the server and disconnect from the database.
175
+ */
176
+ close(): Promise<void>;
177
+ }
178
+ /**
179
+ * Create a new RouteFlow application.
180
+ *
181
+ * @example
182
+ * ```ts
183
+ * const app = createApp({ adapter: new MemoryAdapter(), port: 3000 })
184
+ * app.register(MyController)
185
+ * await app.listen()
186
+ * ```
187
+ */
188
+ declare function createApp(options: AppOptions): ReactiveApp;
189
+
190
+ export { AppOptions, ChangeEvent, DatabaseAdapter, type DatabaseCategory, type DatabaseKey, type DatabaseSupportDescriptor, type DatabaseSupportMode, type DatabaseSupportTier, HttpMethod, MemoryAdapter, PollingAdapter, type PollingAdapterOptions, type PollingReadContext, type PollingReadResult, Reactive, ReactiveApiError, ReactiveApp, ReactiveOptions, Route, SUPPORTED_DATABASES, createApp, getDatabaseSupport, listOfficialDatabases, listSupportedDatabases };
@@ -0,0 +1,190 @@
1
+ import { FastifyInstance } from 'fastify';
2
+ import { H as HttpMethod, R as ReactiveOptions, D as DatabaseAdapter, C as ChangeEvent, A as AppOptions } from './types-tPDla8AE.js';
3
+ export { a as Context } from './types-tPDla8AE.js';
4
+
5
+ /**
6
+ * Registers a class method as an HTTP endpoint.
7
+ *
8
+ * @param method - HTTP verb
9
+ * @param path - Route path, may include Fastify-style params (e.g. '/users/:id')
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * @Route('GET', '/users/:id')
14
+ * async getUser(ctx: Context) { ... }
15
+ * ```
16
+ */
17
+ declare function Route(method: HttpMethod, path: string): (target: object, propertyKey: string) => void;
18
+
19
+ /**
20
+ * Marks a route handler as reactive — when the watched table(s) change,
21
+ * the handler is re-executed and the result is pushed to all subscribed clients.
22
+ *
23
+ * Must be used together with @Route.
24
+ *
25
+ * @param options - Reactive configuration (watch, filter, debounce)
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * @Reactive({ watch: 'orders', filter: (event, ctx) => event.newRow?.userId === ctx.params.userId })
30
+ * @Route('GET', '/orders/:userId/live')
31
+ * async getLiveOrders(ctx: Context) { ... }
32
+ * ```
33
+ */
34
+ declare function Reactive(options: ReactiveOptions): (target: object, propertyKey: string) => void;
35
+
36
+ /**
37
+ * Base error class for all RouteFlow errors.
38
+ * Always use this instead of plain `Error` throughout the framework.
39
+ */
40
+ declare class ReactiveApiError extends Error {
41
+ /** Machine-readable error code (e.g. 'ADAPTER_NOT_CONNECTED', 'INVALID_ROUTE') */
42
+ readonly code: string;
43
+ constructor(code: string, message: string);
44
+ }
45
+
46
+ type DatabaseCategory = 'rdbms' | 'nosql' | 'time-series' | 'search-engine' | 'cloud-data-warehouse' | 'in-memory' | 'newsql';
47
+ type DatabaseSupportMode = 'native-adapter' | 'polling-adapter' | 'external-cdc-bridge';
48
+ type DatabaseSupportTier = 'official' | 'experimental';
49
+ type DatabaseKey = 'postgresql' | 'mysql' | 'mariadb' | 'oracle-db' | 'ms-sql-server' | 'sqlite' | 'mongodb' | 'redis' | 'cassandra' | 'dynamodb' | 'neo4j' | 'elasticsearch' | 'hbase' | 'couchdb' | 'influxdb' | 'timescaledb' | 'prometheus' | 'opensearch' | 'solr' | 'snowflake' | 'bigquery' | 'redshift' | 'azure-synapse' | 'memcached' | 'voltdb' | 'cockroachdb' | 'tidb' | 'spanner';
50
+ interface DatabaseSupportDescriptor {
51
+ key: DatabaseKey;
52
+ name: string;
53
+ aliases: string[];
54
+ categories: DatabaseCategory[];
55
+ supportedModes: DatabaseSupportMode[];
56
+ tier: DatabaseSupportTier;
57
+ }
58
+ declare const SUPPORTED_DATABASES: readonly DatabaseSupportDescriptor[];
59
+ declare function getDatabaseSupport(name: string): DatabaseSupportDescriptor | undefined;
60
+ declare function listSupportedDatabases(category?: DatabaseCategory): readonly DatabaseSupportDescriptor[];
61
+ declare function listOfficialDatabases(category?: DatabaseCategory): readonly DatabaseSupportDescriptor[];
62
+
63
+ /**
64
+ * In-memory database adapter for testing and local development.
65
+ * No real database connection is needed — changes are triggered manually via `emit()`.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const adapter = new MemoryAdapter()
70
+ * await adapter.connect()
71
+ *
72
+ * adapter.onChange('orders', (event) => console.log(event))
73
+ *
74
+ * adapter.emit('orders', { operation: 'INSERT', newRow: { id: 1 }, oldRow: null })
75
+ * ```
76
+ */
77
+ declare class MemoryAdapter implements DatabaseAdapter {
78
+ private readonly listeners;
79
+ private connected;
80
+ /** No-op — MemoryAdapter requires no real connection. */
81
+ connect(): Promise<void>;
82
+ /** No-op — clears all listeners on disconnect. */
83
+ disconnect(): Promise<void>;
84
+ /**
85
+ * Register a listener for changes on a specific table.
86
+ * @returns An unsubscribe function.
87
+ */
88
+ onChange(table: string, callback: (event: ChangeEvent) => void): () => void;
89
+ /**
90
+ * Manually emit a change event on a table.
91
+ * Useful in tests and examples to simulate DB changes without a real database.
92
+ *
93
+ * @param table - Table name to emit the event on
94
+ * @param event - Change event data (table and timestamp are filled in automatically)
95
+ */
96
+ emit(table: string, event: Omit<ChangeEvent, 'table' | 'timestamp'>): void;
97
+ /** Returns true if connect() has been called and disconnect() has not. */
98
+ get isConnected(): boolean;
99
+ }
100
+
101
+ interface PollingReadResult<T = unknown, TCursor = unknown> {
102
+ events: Array<Omit<ChangeEvent<T>, 'table' | 'timestamp'> & Partial<Pick<ChangeEvent<T>, 'table' | 'timestamp'>>>;
103
+ cursor?: TCursor;
104
+ }
105
+ interface PollingReadContext<TCursor = unknown> {
106
+ cursor: TCursor | undefined;
107
+ table: string;
108
+ }
109
+ interface PollingAdapterOptions<TCursor = unknown> {
110
+ intervalMs?: number;
111
+ now?: () => number;
112
+ onError?: (error: unknown, context: {
113
+ table: string;
114
+ }) => void;
115
+ readChanges: (context: PollingReadContext<TCursor>) => Promise<PollingReadResult<unknown, TCursor>>;
116
+ }
117
+ /**
118
+ * Generic polling-based adapter for databases without a native RouteFlow adapter yet.
119
+ * It works with any backend as long as callers can periodically read a change feed.
120
+ */
121
+ declare class PollingAdapter<TCursor = unknown> implements DatabaseAdapter {
122
+ private readonly listeners;
123
+ private readonly cursors;
124
+ private readonly timers;
125
+ private readonly activeTables;
126
+ private readonly intervalMs;
127
+ private readonly now;
128
+ private readonly readChanges;
129
+ private readonly onError?;
130
+ private connected;
131
+ constructor(options: PollingAdapterOptions<TCursor>);
132
+ connect(): Promise<void>;
133
+ disconnect(): Promise<void>;
134
+ onChange(table: string, callback: (event: ChangeEvent) => void): () => void;
135
+ private ensurePolling;
136
+ private stopPolling;
137
+ private scheduleNext;
138
+ private poll;
139
+ private emit;
140
+ }
141
+
142
+ /**
143
+ * Main application class. Use `createApp()` to instantiate.
144
+ */
145
+ declare class ReactiveApp {
146
+ private readonly fastify;
147
+ private readonly engine;
148
+ private transport;
149
+ private readonly options;
150
+ /** Collected route patterns for reactive endpoints */
151
+ private readonly reactivePatterns;
152
+ constructor(options: AppOptions);
153
+ /**
154
+ * Register a controller class. Scans its methods for @Route and @Reactive
155
+ * decorators and registers HTTP routes and reactive endpoints accordingly.
156
+ *
157
+ * @param ControllerClass - A class constructor whose methods may be decorated
158
+ * with @Route and/or @Reactive.
159
+ */
160
+ register(ControllerClass: new () => object): this;
161
+ /**
162
+ * Access the underlying Fastify instance for supplemental routes such as
163
+ * health checks, static assets, or demo pages.
164
+ */
165
+ getFastify(): FastifyInstance;
166
+ /**
167
+ * Start the HTTP server.
168
+ * Connects the database adapter before accepting connections.
169
+ *
170
+ * @param port - Override the port set in AppOptions
171
+ */
172
+ listen(port?: number): Promise<void>;
173
+ /**
174
+ * Gracefully shut down the server and disconnect from the database.
175
+ */
176
+ close(): Promise<void>;
177
+ }
178
+ /**
179
+ * Create a new RouteFlow application.
180
+ *
181
+ * @example
182
+ * ```ts
183
+ * const app = createApp({ adapter: new MemoryAdapter(), port: 3000 })
184
+ * app.register(MyController)
185
+ * await app.listen()
186
+ * ```
187
+ */
188
+ declare function createApp(options: AppOptions): ReactiveApp;
189
+
190
+ export { AppOptions, ChangeEvent, DatabaseAdapter, type DatabaseCategory, type DatabaseKey, type DatabaseSupportDescriptor, type DatabaseSupportMode, type DatabaseSupportTier, HttpMethod, MemoryAdapter, PollingAdapter, type PollingAdapterOptions, type PollingReadContext, type PollingReadResult, Reactive, ReactiveApiError, ReactiveApp, ReactiveOptions, Route, SUPPORTED_DATABASES, createApp, getDatabaseSupport, listOfficialDatabases, listSupportedDatabases };