routeflow-api 0.2.0 → 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/README.md +10 -10
- package/dist/index.cjs +47 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -15
- package/dist/index.d.ts +9 -15
- package/dist/index.js +47 -13
- package/dist/index.js.map +1 -1
- package/package.json +25 -25
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ await app.listen()
|
|
|
46
46
|
### Client
|
|
47
47
|
|
|
48
48
|
```typescript
|
|
49
|
-
import { createClient } from 'routeflow/client'
|
|
49
|
+
import { createClient } from 'routeflow-api/client'
|
|
50
50
|
|
|
51
51
|
const client = createClient('http://localhost:3000')
|
|
52
52
|
|
|
@@ -61,31 +61,31 @@ const unsubscribe = client.subscribe('/items/live', (items) => {
|
|
|
61
61
|
|
|
62
62
|
```typescript
|
|
63
63
|
// PostgreSQL
|
|
64
|
-
import { PostgresAdapter } from 'routeflow/adapters/postgres'
|
|
64
|
+
import { PostgresAdapter } from 'routeflow-api/adapters/postgres'
|
|
65
65
|
|
|
66
66
|
// MongoDB
|
|
67
|
-
import { MongoDbAdapter } from 'routeflow/adapters/mongodb'
|
|
67
|
+
import { MongoDbAdapter } from 'routeflow-api/adapters/mongodb'
|
|
68
68
|
|
|
69
69
|
// MySQL
|
|
70
|
-
import { MySqlAdapter } from 'routeflow/adapters/mysql'
|
|
70
|
+
import { MySqlAdapter } from 'routeflow-api/adapters/mysql'
|
|
71
71
|
|
|
72
72
|
// Redis
|
|
73
|
-
import { RedisAdapter } from 'routeflow/adapters/redis'
|
|
73
|
+
import { RedisAdapter } from 'routeflow-api/adapters/redis'
|
|
74
74
|
|
|
75
75
|
// DynamoDB
|
|
76
|
-
import { DynamoDbAdapter } from 'routeflow/adapters/dynamodb'
|
|
76
|
+
import { DynamoDbAdapter } from 'routeflow-api/adapters/dynamodb'
|
|
77
77
|
|
|
78
78
|
// Elasticsearch
|
|
79
|
-
import { ElasticsearchAdapter } from 'routeflow/adapters/elasticsearch'
|
|
79
|
+
import { ElasticsearchAdapter } from 'routeflow-api/adapters/elasticsearch'
|
|
80
80
|
|
|
81
81
|
// OpenSearch
|
|
82
|
-
import { OpenSearchAdapter } from 'routeflow/adapters/opensearch'
|
|
82
|
+
import { OpenSearchAdapter } from 'routeflow-api/adapters/opensearch'
|
|
83
83
|
|
|
84
84
|
// Snowflake
|
|
85
|
-
import { SnowflakeAdapter } from 'routeflow/adapters/snowflake'
|
|
85
|
+
import { SnowflakeAdapter } from 'routeflow-api/adapters/snowflake'
|
|
86
86
|
|
|
87
87
|
// Cassandra
|
|
88
|
-
import { CassandraAdapter } from 'routeflow/adapters/cassandra'
|
|
88
|
+
import { CassandraAdapter } from 'routeflow-api/adapters/cassandra'
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -44,22 +44,38 @@ __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
|
|
50
52
|
var ROUTE_METADATA = /* @__PURE__ */ Symbol("reactive-api:route");
|
|
53
|
+
var routeFnStore = /* @__PURE__ */ new WeakMap();
|
|
51
54
|
function Route(method, path) {
|
|
55
|
+
const metadata = { method, path };
|
|
52
56
|
return function(target, propertyKey) {
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
if (typeof target === "function") {
|
|
58
|
+
routeFnStore.set(target, metadata);
|
|
59
|
+
} else if (typeof propertyKey === "string") {
|
|
60
|
+
const fn = target[propertyKey];
|
|
61
|
+
if (fn) routeFnStore.set(fn, metadata);
|
|
62
|
+
Reflect.defineMetadata(ROUTE_METADATA, metadata, target, propertyKey);
|
|
63
|
+
}
|
|
55
64
|
};
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
// src/core/decorator/reactive.ts
|
|
59
68
|
var REACTIVE_METADATA = /* @__PURE__ */ Symbol("reactive-api:reactive");
|
|
69
|
+
var reactiveFnStore = /* @__PURE__ */ new WeakMap();
|
|
60
70
|
function Reactive(options) {
|
|
61
71
|
return function(target, propertyKey) {
|
|
62
|
-
|
|
72
|
+
if (typeof target === "function") {
|
|
73
|
+
reactiveFnStore.set(target, options);
|
|
74
|
+
} else if (typeof propertyKey === "string") {
|
|
75
|
+
const fn = target[propertyKey];
|
|
76
|
+
if (fn) reactiveFnStore.set(fn, options);
|
|
77
|
+
Reflect.defineMetadata(REACTIVE_METADATA, options, target, propertyKey);
|
|
78
|
+
}
|
|
63
79
|
};
|
|
64
80
|
}
|
|
65
81
|
|
|
@@ -798,6 +814,8 @@ var ReactiveApp = class {
|
|
|
798
814
|
options;
|
|
799
815
|
/** Collected route patterns for reactive endpoints */
|
|
800
816
|
reactivePatterns = [];
|
|
817
|
+
/** All registered routes (for .routeflow/info.json) */
|
|
818
|
+
registeredRoutes = [];
|
|
801
819
|
constructor(options) {
|
|
802
820
|
this.options = {
|
|
803
821
|
transport: "websocket",
|
|
@@ -829,17 +847,10 @@ var ReactiveApp = class {
|
|
|
829
847
|
(name) => name !== "constructor" && typeof proto[name] === "function"
|
|
830
848
|
);
|
|
831
849
|
for (const methodName of methodNames) {
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
proto,
|
|
835
|
-
methodName
|
|
836
|
-
);
|
|
850
|
+
const fn = proto[methodName];
|
|
851
|
+
const routeMeta = routeFnStore.get(fn) ?? Reflect.getMetadata(ROUTE_METADATA, proto, methodName);
|
|
837
852
|
if (!routeMeta) continue;
|
|
838
|
-
const reactiveMeta = Reflect.getMetadata(
|
|
839
|
-
REACTIVE_METADATA,
|
|
840
|
-
proto,
|
|
841
|
-
methodName
|
|
842
|
-
);
|
|
853
|
+
const reactiveMeta = reactiveFnStore.get(fn) ?? Reflect.getMetadata(REACTIVE_METADATA, proto, methodName);
|
|
843
854
|
const handler = instance[methodName];
|
|
844
855
|
this.fastify.route({
|
|
845
856
|
method: routeMeta.method,
|
|
@@ -861,6 +872,7 @@ var ReactiveApp = class {
|
|
|
861
872
|
}
|
|
862
873
|
}
|
|
863
874
|
});
|
|
875
|
+
this.registeredRoutes.push({ method: routeMeta.method, path: routeMeta.path, reactive: !!reactiveMeta });
|
|
864
876
|
if (reactiveMeta) {
|
|
865
877
|
const endpoint = {
|
|
866
878
|
routePath: routeMeta.path,
|
|
@@ -904,6 +916,28 @@ var ReactiveApp = class {
|
|
|
904
916
|
console.log(
|
|
905
917
|
`[RouteFlow] Listening on port ${listenPort} (transport: ${this.options.transport})`
|
|
906
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
|
+
}
|
|
907
941
|
}
|
|
908
942
|
/**
|
|
909
943
|
* Gracefully shut down the server and disconnect from the database.
|
package/dist/index.cjs.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 } 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BAAO;AACP,qBAAyC;;;ACElC,IAAM,iBAAiB,uBAAO,oBAAoB;AAclD,SAAS,MAAM,QAAoB,MAAc;AACtD,SAAO,SAAU,QAAgB,aAA2B;AAC1D,UAAM,WAA0B,EAAE,QAAQ,KAAK;AAC/C,YAAQ,eAAe,gBAAgB,UAAU,QAAQ,WAAW;AAAA,EACtE;AACF;;;ACnBO,IAAM,oBAAoB,uBAAO,uBAAuB;AAiBxD,SAAS,SAAS,SAA0B;AACjD,SAAO,SAAU,QAAgB,aAA2B;AAC1D,YAAQ,eAAe,mBAAmB,SAAS,QAAQ,WAAW;AAAA,EACxE;AACF;;;ACpBO,IAAM,mBAAN,cAA+B,MAAM;AAAA;AAAA,EAEjC;AAAA,EAET,YAAY,MAAc,SAAiB;AACzC,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAEZ,WAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AACF;;;ACYO,IAAM,iBAAN,MAAqB;AAAA,EAS1B,YAA6B,SAA0B;AAA1B;AAAA,EAA2B;AAAA,EAA3B;AAAA,EARZ,YAAgC,CAAC;AAAA;AAAA,EAEjC,gBAA2C,oBAAI,IAAI;AAAA;AAAA,EAEnD,gBAAyC,oBAAI,IAAI;AAAA;AAAA,EAEjD,iBAA6D,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA,EAOtF,iBAAiB,UAAkC;AACjD,SAAK,UAAU,KAAK,QAAQ;AAE5B,UAAM,SAAS,MAAM,QAAQ,SAAS,QAAQ,KAAK,IAC/C,SAAS,QAAQ,QACjB,CAAC,SAAS,QAAQ,KAAK;AAE3B,eAAW,SAAS,QAAQ;AAC1B,WAAK,kBAAkB,KAAK;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,UAAU,UAAkB,MAAc,KAAc,QAAsB;AAC5E,SAAK,cAAc,IAAI,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAwB;AAClC,SAAK,cAAc,OAAO,QAAQ;AAGlC,eAAW,OAAO,KAAK,eAAe,KAAK,GAAG;AAC5C,UAAI,IAAI,WAAW,GAAG,QAAQ,GAAG,GAAG;AAClC,qBAAa,KAAK,eAAe,IAAI,GAAG,CAAC;AACzC,aAAK,eAAe,OAAO,GAAG;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,eAAW,eAAe,KAAK,cAAc,OAAO,GAAG;AACrD,kBAAY;AAAA,IACd;AACA,SAAK,cAAc,MAAM;AAEzB,eAAW,SAAS,KAAK,eAAe,OAAO,GAAG;AAChD,mBAAa,KAAK;AAAA,IACpB;AACA,SAAK,eAAe,MAAM;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAkB,OAAqB;AAC7C,QAAI,KAAK,cAAc,IAAI,KAAK,EAAG;AAEnC,UAAM,cAAc,KAAK,QAAQ,SAAS,OAAO,CAAC,UAAU;AAC1D,WAAK,cAAc,KAAK;AAAA,IAC1B,CAAC;AACD,SAAK,cAAc,IAAI,OAAO,WAAW;AAAA,EAC3C;AAAA,EAEQ,cAAc,OAA0B;AAE9C,UAAM,oBAAoB,KAAK,UAAU,OAAO,CAAC,OAAO;AACtD,YAAM,SAAS,MAAM,QAAQ,GAAG,QAAQ,KAAK,IACzC,GAAG,QAAQ,QACX,CAAC,GAAG,QAAQ,KAAK;AACrB,aAAO,OAAO,SAAS,MAAM,KAAK;AAAA,IACpC,CAAC;AAED,eAAW,YAAY,mBAAmB;AAExC,iBAAW,CAAC,UAAU,GAAG,KAAK,KAAK,eAAe;AAChD,YAAI,CAAC,mBAAmB,IAAI,MAAM,SAAS,SAAS,EAAG;AAGvD,YAAI,SAAS,QAAQ,QAAQ;AAC3B,cAAI;AACF,gBAAI,CAAC,SAAS,QAAQ,OAAO,OAAO,IAAI,GAAG,EAAG;AAAA,UAChD,SAAS,KAAK;AAEZ;AAAA,UACF;AAAA,QACF;AAEA,aAAK,aAAa,UAAU,UAAU,KAAK,KAAK;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,aACN,UACA,UACA,KACA,QACM;AACN,UAAM,aAAa,SAAS,QAAQ;AAEpC,QAAI,eAAe,UAAa,aAAa,GAAG;AAC9C,YAAM,WAAW,GAAG,QAAQ,IAAI,IAAI,IAAI;AACxC,YAAM,WAAW,KAAK,eAAe,IAAI,QAAQ;AACjD,UAAI,aAAa,OAAW,cAAa,QAAQ;AAEjD,YAAM,QAAQ,WAAW,MAAM;AAC7B,aAAK,eAAe,OAAO,QAAQ;AACnC,aAAK,YAAY,UAAU,GAAG;AAAA,MAChC,GAAG,UAAU;AAEb,WAAK,eAAe,IAAI,UAAU,KAAK;AAAA,IACzC,OAAO;AACL,WAAK,YAAY,UAAU,GAAG;AAAA,IAChC;AAAA,EACF;AAAA,EAEQ,YAAY,UAA4B,KAAyB;AACvE,aAAS,QAAQ,IAAI,GAAG,EAAE;AAAA,MACxB,CAAC,SAAS,IAAI,OAAO,IAAI,MAAM,IAAI;AAAA,MACnC,CAAC,QAAiB;AAChB,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,cAAM,IAAI,iBAAiB,iBAAiB,4BAA4B,OAAO,EAAE;AAAA,MACnF;AAAA,IACF;AAAA,EACF;AACF;AAYO,SAAS,mBAAmB,cAAsB,SAA0B;AACjF,QAAM,WAAW,QACd,QAAQ,sBAAsB,MAAM,EACpC,QAAQ,8BAA8B,SAAS;AAElD,QAAM,QAAQ,IAAI,OAAO,IAAI,QAAQ,GAAG;AACxC,SAAO,MAAM,KAAK,YAAY;AAChC;AAOO,SAAS,cAAc,cAAsB,SAAyC;AAC3F,QAAM,aAAuB,CAAC;AAC9B,QAAM,WAAW,QACd,QAAQ,sBAAsB,MAAM,EACpC,QAAQ,8BAA8B,CAAC,GAAG,SAAiB;AAC1D,eAAW,KAAK,IAAI;AACpB,WAAO;AAAA,EACT,CAAC;AAEH,QAAM,QAAQ,IAAI,OAAO,IAAI,QAAQ,GAAG;AACxC,QAAM,QAAQ,aAAa,MAAM,KAAK;AACtC,MAAI,CAAC,MAAO,QAAO,CAAC;AAEpB,SAAO,OAAO,YAAY,WAAW,IAAI,CAAC,MAAM,MAAM,CAAC,MAAM,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC;AAC7E;;;AClNA,yBAA2B;AAC3B,gBAA2C;AA8B3C,SAAS,mBAAmB,OAA2C;AACrE,SACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAkC,MAAM,MAAM,eAC/C,OAAQ,MAAkC,MAAM,MAAM;AAE1D;AAcO,IAAM,qBAAN,MAAyB;AAAA,EAK9B,YACmB,QAEA,eACjB;AAHiB;AAEA;AAGjB,SAAK,MAAM,IAAI,0BAAgB,EAAE,UAAU,KAAK,CAAC;AACjD,SAAK,IAAI,GAAG,cAAc,CAAC,IAAI,QAAQ,KAAK,iBAAiB,IAAI,GAAG,CAAC;AAAA,EACvE;AAAA,EAPmB;AAAA,EAEA;AAAA,EAPF;AAAA;AAAA,EAEA,iBAAsC,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,EAgB/D,OAAO,YAA8B;AACnC,eAAW,GAAG,WAAW,CAAC,KAAK,QAAQ,SAAS;AAC9C,WAAK,IAAI,cAAc,KAAK,QAAQ,MAAM,CAAC,OAAO;AAChD,aAAK,IAAI,KAAK,cAAc,IAAI,GAAG;AAAA,MACrC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,IAAI,MAAM,CAAC,QAAS,MAAM,OAAO,GAAG,IAAI,QAAQ,CAAE;AAAA,IACzD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAMQ,iBAAiB,IAAe,MAA6B;AACnE,UAAM,eAAW,+BAAW;AAE5B,OAAG,GAAG,WAAW,CAAC,QAAQ;AACxB,UAAI;AACJ,UAAI;AACF,iBAAS,KAAK,MAAM,IAAI,SAAS,CAAC;AAAA,MACpC,QAAQ;AACN,aAAK,UAAU,IAAI,gBAAgB,4BAA4B;AAC/D;AAAA,MACF;AAEA,UAAI,CAAC,mBAAmB,MAAM,GAAG;AAC/B,aAAK,UAAU,IAAI,mBAAmB,8CAA8C;AACpF;AAAA,MACF;AAEA,WAAK,gBAAgB,IAAI,UAAU,MAAM;AAAA,IAC3C,CAAC;AAED,OAAG,GAAG,SAAS,MAAM;AACnB,WAAK,OAAO,YAAY,QAAQ;AAChC,WAAK,eAAe,OAAO,QAAQ;AAAA,IACrC,CAAC;AAED,OAAG,GAAG,SAAS,CAAC,QAAQ;AACtB,WAAK,OAAO,YAAY,QAAQ;AAChC,WAAK,eAAe,OAAO,QAAQ;AAEnC,UAAI,QAAQ,IAAI,UAAU,MAAM,cAAc;AAC5C,gBAAQ,MAAM,gCAAgC,IAAI,OAAO;AAAA,MAC3D;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,gBAAgB,IAAe,UAAkB,KAA6B;AACpF,UAAM,EAAE,MAAM,QAAQ,CAAC,EAAE,IAAI;AAG7B,UAAM,UAAU,KAAK,cAAc,KAAK,CAAC,MAAM,KAAK,eAAe,MAAM,CAAC,CAAC;AAE3E,QAAI,CAAC,SAAS;AACZ,WAAK,UAAU,IAAI,wBAAwB,wCAAwC,IAAI,EAAE;AACzF;AAAA,IACF;AAEA,UAAM,SAAS,cAAc,MAAM,OAAO;AAE1C,UAAM,MAAe;AAAA,MACnB;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN,SAAS,CAAC;AAAA,IACZ;AAEA,UAAM,SAAS,CAAC,gBAAwB,SAAwB;AAC9D,UAAI,GAAG,eAAe,oBAAU,KAAM;AACtC,YAAMA,OAAqB,EAAE,MAAM,UAAU,MAAM,gBAAgB,KAAK;AACxE,SAAG,KAAK,KAAK,UAAUA,IAAG,CAAC;AAAA,IAC7B;AAGA,SAAK,OAAO,YAAY,QAAQ;AAChC,SAAK,eAAe,IAAI,UAAU,OAAO;AACzC,SAAK,OAAO,UAAU,UAAU,MAAM,KAAK,MAAM;AAAA,EACnD;AAAA,EAEQ,eAAe,cAAsB,SAA0B;AACrE,UAAM,WAAW,QACd,QAAQ,sBAAsB,MAAM,EACpC,QAAQ,8BAA8B,SAAS;AAClD,WAAO,IAAI,OAAO,IAAI,QAAQ,GAAG,EAAE,KAAK,YAAY;AAAA,EACtD;AAAA,EAEQ,UAAU,IAAe,MAAc,SAAuB;AACpE,QAAI,GAAG,eAAe,oBAAU,KAAM;AACtC,UAAM,MAAoB,EAAE,MAAM,SAAS,MAAM,QAAQ;AACzD,OAAG,KAAK,KAAK,UAAU,GAAG,CAAC;AAAA,EAC7B;AACF;;;ACzKA,IAAAC,sBAA2B;AA0BpB,IAAM,eAAN,MAAmB;AAAA,EAIxB,YACmB,QACA,eACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA;AAAA,EAJF,cAAyC,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,EAWlE,SAAS,SAAgC;AACvC,YAAQ,IAAI,mBAAmB,OAAO,KAAqB,UAAwB;AACjF,YAAM,QAAQ,IAAI;AAClB,YAAM,OAAO,MAAM,MAAM;AAEzB,UAAI,CAAC,MAAM;AACT,cAAM,IAAI,iBAAiB,oBAAoB,gCAAgC;AAAA,MACjF;AAEA,YAAM,cAAc,mBAAmB,IAAI;AAC3C,YAAM,UAAU,KAAK,cAAc,KAAK,CAAC,MAAM,KAAK,eAAe,aAAa,CAAC,CAAC;AAElF,UAAI,CAAC,SAAS;AACZ,cAAM,KAAK,GAAG;AACd,cAAM,IAAI;AAAA,UACR;AAAA,UACA,wCAAwC,WAAW;AAAA,QACrD;AAAA,MACF;AAEA,YAAM,SAAS,cAAc,aAAa,OAAO;AACjD,YAAM,cAAc,EAAE,GAAG,MAAM;AAC/B,aAAO,YAAY,MAAM;AAEzB,YAAM,MAAe;AAAA,QACnB;AAAA,QACA,OAAO;AAAA,QACP,MAAM;AAAA,QACN,SAAS,IAAI;AAAA,MACf;AAEA,YAAM,eAAW,gCAAW;AAG5B,YAAM,IAAI,UAAU,KAAK;AAAA,QACvB,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,YAAY;AAAA,QACZ,qBAAqB;AAAA;AAAA,MACvB,CAAC;AAGD,YAAM,IAAI,MAAM,iBAAiB;AAEjC,WAAK,YAAY,IAAI,UAAU,KAAK;AAEpC,YAAM,SAAS,CAAC,gBAAwB,SAAwB;AAC9D,YAAI,MAAM,IAAI,UAAW;AACzB,cAAM,UAAU,KAAK,UAAU,EAAE,MAAM,UAAU,MAAM,gBAAgB,KAAK,CAAC;AAC7E,cAAM,IAAI,MAAM,SAAS,OAAO;AAAA;AAAA,CAAM;AAAA,MACxC;AAEA,WAAK,OAAO,UAAU,UAAU,aAAa,KAAK,MAAM;AAGxD,UAAI,IAAI,GAAG,SAAS,MAAM;AACxB,aAAK,OAAO,YAAY,QAAQ;AAChC,aAAK,YAAY,OAAO,QAAQ;AAChC,YAAI,CAAC,MAAM,IAAI,UAAW,OAAM,IAAI,IAAI;AAAA,MAC1C,CAAC;AAGD,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,YAAI,IAAI,GAAG,SAAS,OAAO;AAC3B,YAAI,IAAI,GAAG,SAAS,OAAO;AAAA,MAC7B,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,eAAW,SAAS,KAAK,YAAY,OAAO,GAAG;AAC7C,UAAI,CAAC,MAAM,IAAI,UAAW,OAAM,IAAI,IAAI;AAAA,IAC1C;AACA,SAAK,YAAY,MAAM;AAAA,EACzB;AAAA,EAEQ,eAAe,cAAsB,SAA0B;AACrE,UAAM,WAAW,QACd,QAAQ,sBAAsB,MAAM,EACpC,QAAQ,8BAA8B,SAAS;AAClD,WAAO,IAAI,OAAO,IAAI,QAAQ,GAAG,EAAE,KAAK,YAAY;AAAA,EACtD;AACF;;;ACxEO,IAAM,sBAA4D;AAAA,EACvE;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,YAAY,MAAM;AAAA,IAC5B,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,kBAAkB,mBAAmB,qBAAqB;AAAA,IAC3E,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,UAAU,iBAAiB;AAAA,IACrC,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,cAAc,SAAS,QAAQ;AAAA,IACzC,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,iBAAiB;AAAA,IAClC,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,OAAO;AAAA,IACjB,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,SAAS,WAAW;AAAA,IACjC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,kBAAkB;AAAA,IAC5B,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,QAAQ;AAAA,IAClB,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,SAAS;AAAA,IACnB,YAAY,CAAC,SAAS,eAAe;AAAA,IACrC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,cAAc;AAAA,IACxB,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,gBAAgB;AAAA,IAC1B,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,QAAQ;AAAA,IAClB,YAAY,CAAC,aAAa;AAAA,IAC1B,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,WAAW;AAAA,IACrB,YAAY,CAAC,eAAe,OAAO;AAAA,IACnC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,MAAM;AAAA,IAChB,YAAY,CAAC,aAAa;AAAA,IAC1B,gBAAgB,CAAC,iBAAiB;AAAA,IAClC,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,eAAe;AAAA,IAC5B,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,aAAa;AAAA,IACvB,YAAY,CAAC,eAAe;AAAA,IAC5B,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,sBAAsB;AAAA,IACnC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,iBAAiB;AAAA,IAC3B,YAAY,CAAC,sBAAsB;AAAA,IACnC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,iBAAiB;AAAA,IAC3B,YAAY,CAAC,sBAAsB;AAAA,IACnC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,WAAW,yBAAyB;AAAA,IAC9C,YAAY,CAAC,sBAAsB;AAAA,IACnC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,WAAW;AAAA,IACxB,gBAAgB,CAAC,iBAAiB;AAAA,IAClC,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,WAAW;AAAA,IACxB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,WAAW;AAAA,IACrB,YAAY,CAAC,UAAU,OAAO;AAAA,IAC9B,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,UAAU,OAAO;AAAA,IAC9B,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,kBAAkB,eAAe;AAAA,IAC3C,YAAY,CAAC,QAAQ;AAAA,IACrB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AACF;AAEA,SAAS,sBAAsB,OAAuB;AACpD,SAAO,MAAM,YAAY,EAAE,QAAQ,eAAe,GAAG,EAAE,KAAK;AAC9D;AAEO,SAAS,mBAAmB,MAAqD;AACtF,QAAM,aAAa,sBAAsB,IAAI;AAE7C,SAAO,oBAAoB,KAAK,CAAC,aAAa;AAC5C,QAAI,sBAAsB,SAAS,IAAI,MAAM,WAAY,QAAO;AAChE,QAAI,sBAAsB,SAAS,GAAG,MAAM,WAAY,QAAO;AAE/D,WAAO,SAAS,QAAQ,KAAK,CAAC,UAAU,sBAAsB,KAAK,MAAM,UAAU;AAAA,EACrF,CAAC;AACH;AAEO,SAAS,uBACd,UACsC;AACtC,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,oBAAoB,OAAO,CAAC,aAAa,SAAS,WAAW,SAAS,QAAQ,CAAC;AACxF;AAEO,SAAS,sBACd,UACsC;AACtC,QAAM,YAAY,uBAAuB,QAAQ;AACjD,SAAO,UAAU,OAAO,CAAC,aAAa,SAAS,SAAS,UAAU;AACpE;;;ACjSO,IAAM,gBAAN,MAA+C;AAAA,EACnC,YAA4D,oBAAI,IAAI;AAAA,EAC7E,YAAY;AAAA;AAAA,EAGpB,MAAM,UAAyB;AAC7B,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA,EAGA,MAAM,aAA4B;AAChC,SAAK,UAAU,MAAM;AACrB,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS,OAAe,UAAoD;AAC1E,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,IAAI,CAAC;AAAA,IACrC;AACA,SAAK,UAAU,IAAI,KAAK,EAAG,IAAI,QAAQ;AAEvC,WAAO,MAAM;AACX,WAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAQ;AAAA,IAC5C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,KAAK,OAAe,OAAuD;AACzE,UAAM,YAAyB;AAAA,MAC7B,GAAG;AAAA,MACH;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,IACtB;AAEA,UAAM,YAAY,KAAK,UAAU,IAAI,KAAK;AAC1C,QAAI,CAAC,UAAW;AAEhB,eAAW,MAAM,WAAW;AAC1B,SAAG,SAAS;AAAA,IACd;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AACF;;;AC/CO,IAAM,iBAAN,MAAmE;AAAA,EACvD,YAA4D,oBAAI,IAAI;AAAA,EACpE,UAA4C,oBAAI,IAAI;AAAA,EACpD,SAAqD,oBAAI,IAAI;AAAA,EAC7D,eAA4B,oBAAI,IAAI;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY;AAAA,EAEpB,YAAY,SAAyC;AACnD,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,MAAM,QAAQ,QAAQ,MAAM,KAAK,IAAI;AAC1C,SAAK,cAAc,QAAQ;AAC3B,SAAK,UAAU,QAAQ;AAAA,EACzB;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,YAAY;AAEjB,eAAW,SAAS,KAAK,UAAU,KAAK,GAAG;AACzC,WAAK,cAAc,KAAK;AAAA,IAC1B;AAAA,EACF;AAAA,EAEA,MAAM,aAA4B;AAChC,SAAK,YAAY;AAEjB,eAAW,SAAS,KAAK,OAAO,OAAO,GAAG;AACxC,mBAAa,KAAK;AAAA,IACpB;AAEA,SAAK,OAAO,MAAM;AAClB,SAAK,aAAa,MAAM;AAAA,EAC1B;AAAA,EAEA,SAAS,OAAe,UAAoD;AAC1E,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,IAAI,CAAC;AAAA,IACrC;AAEA,SAAK,UAAU,IAAI,KAAK,EAAG,IAAI,QAAQ;AACvC,SAAK,cAAc,KAAK;AAExB,WAAO,MAAM;AACX,YAAM,YAAY,KAAK,UAAU,IAAI,KAAK;AAC1C,UAAI,CAAC,UAAW;AAEhB,gBAAU,OAAO,QAAQ;AAEzB,UAAI,UAAU,SAAS,GAAG;AACxB,aAAK,UAAU,OAAO,KAAK;AAC3B,aAAK,YAAY,KAAK;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,cAAc,OAAqB;AACzC,QAAI,CAAC,KAAK,aAAa,KAAK,aAAa,IAAI,KAAK,EAAG;AAErD,SAAK,aAAa,IAAI,KAAK;AAC3B,SAAK,KAAK,KAAK,KAAK;AAAA,EACtB;AAAA,EAEQ,YAAY,OAAqB;AACvC,UAAM,QAAQ,KAAK,OAAO,IAAI,KAAK;AACnC,QAAI,MAAO,cAAa,KAAK;AAE7B,SAAK,OAAO,OAAO,KAAK;AACxB,SAAK,aAAa,OAAO,KAAK;AAC9B,SAAK,QAAQ,OAAO,KAAK;AAAA,EAC3B;AAAA,EAEQ,aAAa,OAAqB;AACxC,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AACjD,WAAK,YAAY,KAAK;AACtB;AAAA,IACF;AAEA,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK,KAAK,KAAK,KAAK;AAAA,IACtB,GAAG,KAAK,UAAU;AAElB,SAAK,OAAO,IAAI,OAAO,KAAK;AAAA,EAC9B;AAAA,EAEA,MAAc,KAAK,OAA8B;AAC/C,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AACjD,WAAK,YAAY,KAAK;AACtB;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,YAAY;AAAA,QACpC;AAAA,QACA,QAAQ,KAAK,QAAQ,IAAI,KAAK;AAAA,MAChC,CAAC;AAED,WAAK,QAAQ,IAAI,OAAO,OAAO,MAAM;AAErC,iBAAW,SAAS,OAAO,QAAQ;AACjC,aAAK,KAAK,OAAO,KAAK;AAAA,MACxB;AAAA,IACF,SAAS,OAAO;AACd,WAAK,UAAU,OAAO,EAAE,MAAM,CAAC;AAAA,IACjC,UAAE;AACA,WAAK,aAAa,OAAO,KAAK;AAC9B,WAAK,aAAa,KAAK;AAAA,IACzB;AAAA,EACF;AAAA,EAEQ,KACN,cACA,OACM;AACN,UAAM,YAAyB;AAAA,MAC7B,GAAG;AAAA,MACH,OAAO,MAAM,SAAS;AAAA,MACtB,WAAW,MAAM,aAAa,KAAK,IAAI;AAAA,IACzC;AAEA,UAAM,YAAY,KAAK,UAAU,IAAI,UAAU,KAAK;AACpD,QAAI,CAAC,UAAW;AAEhB,eAAW,YAAY,WAAW;AAChC,eAAS,SAAS;AAAA,IACpB;AAAA,EACF;AACF;;;ATzGO,IAAM,cAAN,MAAkB;AAAA,EACN;AAAA,EACA;AAAA,EACT,YAAiC;AAAA,EACxB;AAAA;AAAA,EAEA,mBAA6B,CAAC;AAAA,EAE/C,YAAY,SAAqB;AAC/B,SAAK,UAAU;AAAA,MACb,WAAW;AAAA,MACX,MAAM;AAAA,MACN,GAAG;AAAA,IACL;AACA,SAAK,cAAU,eAAAC,SAAQ,EAAE,QAAQ,MAAM,CAAC;AACxC,SAAK,SAAS,IAAI,eAAe,KAAK,QAAQ,OAAO;AAGrD,SAAK,QAAQ,gBAAgB,CAAC,OAAO,MAAM,UAAU;AACnD,UAAI,iBAAiB,kBAAkB;AACrC,cAAM,SAAU,MAAqD,cAAc;AACnF,cAAM,OAAO,MAAM,EAAE,KAAK,EAAE,OAAO,MAAM,MAAM,SAAS,MAAM,QAAQ,CAAC;AAAA,MACzE,OAAO;AACL,cAAM,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,kBAAkB,SAAS,MAAM,QAAQ,CAAC;AAAA,MAC5E;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SAAS,iBAAyC;AAChD,UAAM,WAAW,IAAI,gBAAgB;AACrC,UAAM,QAAQ,OAAO,eAAe,QAAQ;AAE5C,UAAM,cAAc,OAAO,oBAAoB,KAAK,EAAE;AAAA,MACpD,CAAC,SACC,SAAS,iBACT,OAAQ,MAAkC,IAAI,MAAM;AAAA,IACxD;AAEA,eAAW,cAAc,aAAa;AACpC,YAAM,YAAuC,QAAQ;AAAA,QACnD;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,UAAI,CAAC,UAAW;AAEhB,YAAM,eAA4C,QAAQ;AAAA,QACxD;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,YAAM,UAAW,SAAqC,UAAU;AAKhE,WAAK,QAAQ,MAAM;AAAA,QACjB,QAAQ,UAAU;AAAA,QAClB,KAAK,UAAU;AAAA,QACf,SAAS,OAAO,KAAK,UAAU;AAC7B,gBAAM,MAAe;AAAA,YACnB,QAAQ,IAAI;AAAA,YACZ,OAAO,IAAI;AAAA,YACX,MAAM,IAAI;AAAA,YACV,SAAS,IAAI;AAAA,UACf;AACA,cAAI;AACF,kBAAM,SAAS,MAAM,QAAQ,KAAK,UAAU,GAAG;AAC/C,mBAAO,MAAM,KAAK,MAAM;AAAA,UAC1B,SAAS,KAAK;AACZ,gBAAI,eAAe,iBAAkB,OAAM;AAC3C,kBAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,kBAAM,IAAI,iBAAiB,iBAAiB,GAAG;AAAA,UACjD;AAAA,QACF;AAAA,MACF,CAAC;AAGD,UAAI,cAAc;AAChB,cAAM,WAA6B;AAAA,UACjC,WAAW,UAAU;AAAA,UACrB,SAAS;AAAA,UACT,SAAS,CAAC,QAAiB,QAAQ,KAAK,UAAU,GAAG;AAAA,QACvD;AACA,aAAK,OAAO,iBAAiB,QAAQ;AACrC,aAAK,iBAAiB,KAAK,UAAU,IAAI;AAAA,MAC3C;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAA8B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,MAA8B;AACzC,UAAM,aAAa,QAAQ,KAAK,QAAQ;AAExC,UAAM,KAAK,QAAQ,QAAQ,QAAQ;AAEnC,QAAI,KAAK,QAAQ,cAAc,aAAa;AAC1C,WAAK,YAAY,IAAI,mBAAmB,KAAK,QAAQ,KAAK,gBAAgB;AAAA,IAC5E,WAAW,KAAK,QAAQ,cAAc,OAAO;AAC3C,YAAM,eAAe,IAAI,aAAa,KAAK,QAAQ,KAAK,gBAAgB;AACxE,mBAAa,SAAS,KAAK,OAAO;AAClC,WAAK,YAAY;AAAA,IACnB;AAEA,UAAM,KAAK,QAAQ,MAAM;AAEzB,QAAI,KAAK,qBAAqB,oBAAoB;AAChD,WAAK,UAAU,OAAO,KAAK,QAAQ,MAAM;AAAA,IAC3C;AAEA,UAAM,KAAK,QAAQ,OAAO,EAAE,MAAM,YAAY,MAAM,UAAU,CAAC;AAC/D,YAAQ;AAAA,MACN,iCAAiC,UAAU,gBAAgB,KAAK,QAAQ,SAAS;AAAA,IACnF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,SAAK,OAAO,QAAQ;AACpB,QAAI,KAAK,UAAW,OAAM,KAAK,UAAU,MAAM;AAC/C,UAAM,KAAK,QAAQ,MAAM;AACzB,UAAM,KAAK,QAAQ,QAAQ,WAAW;AAAA,EACxC;AACF;AAYO,SAAS,UAAU,SAAkC;AAC1D,SAAO,IAAI,YAAY,OAAO;AAChC;","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
|
@@ -5,33 +5,24 @@ export { a as Context } from './types-tPDla8AE.cjs';
|
|
|
5
5
|
/**
|
|
6
6
|
* Registers a class method as an HTTP endpoint.
|
|
7
7
|
*
|
|
8
|
+
* Works with both TypeScript legacy decorators (experimentalDecorators) and
|
|
9
|
+
* TC39 Stage 3 decorators (as used by esbuild/tsx without legacy flag).
|
|
10
|
+
*
|
|
8
11
|
* @param method - HTTP verb
|
|
9
12
|
* @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
13
|
*/
|
|
17
|
-
declare function Route(method: HttpMethod, path: string): (target:
|
|
14
|
+
declare function Route(method: HttpMethod, path: string): (target: any, propertyKey: any) => void;
|
|
18
15
|
|
|
19
16
|
/**
|
|
20
17
|
* Marks a route handler as reactive — when the watched table(s) change,
|
|
21
18
|
* the handler is re-executed and the result is pushed to all subscribed clients.
|
|
22
19
|
*
|
|
23
20
|
* Must be used together with @Route.
|
|
21
|
+
* Works with both TypeScript legacy decorators and TC39 Stage 3 decorators.
|
|
24
22
|
*
|
|
25
23
|
* @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
24
|
*/
|
|
34
|
-
declare function Reactive(options: ReactiveOptions): (target:
|
|
25
|
+
declare function Reactive(options: ReactiveOptions): (target: any, propertyKey: any) => void;
|
|
35
26
|
|
|
36
27
|
/**
|
|
37
28
|
* Base error class for all RouteFlow errors.
|
|
@@ -149,6 +140,8 @@ declare class ReactiveApp {
|
|
|
149
140
|
private readonly options;
|
|
150
141
|
/** Collected route patterns for reactive endpoints */
|
|
151
142
|
private readonly reactivePatterns;
|
|
143
|
+
/** All registered routes (for .routeflow/info.json) */
|
|
144
|
+
private readonly registeredRoutes;
|
|
152
145
|
constructor(options: AppOptions);
|
|
153
146
|
/**
|
|
154
147
|
* Register a controller class. Scans its methods for @Route and @Reactive
|
|
@@ -170,6 +163,7 @@ declare class ReactiveApp {
|
|
|
170
163
|
* @param port - Override the port set in AppOptions
|
|
171
164
|
*/
|
|
172
165
|
listen(port?: number): Promise<void>;
|
|
166
|
+
private writeInfo;
|
|
173
167
|
/**
|
|
174
168
|
* Gracefully shut down the server and disconnect from the database.
|
|
175
169
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -5,33 +5,24 @@ export { a as Context } from './types-tPDla8AE.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* Registers a class method as an HTTP endpoint.
|
|
7
7
|
*
|
|
8
|
+
* Works with both TypeScript legacy decorators (experimentalDecorators) and
|
|
9
|
+
* TC39 Stage 3 decorators (as used by esbuild/tsx without legacy flag).
|
|
10
|
+
*
|
|
8
11
|
* @param method - HTTP verb
|
|
9
12
|
* @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
13
|
*/
|
|
17
|
-
declare function Route(method: HttpMethod, path: string): (target:
|
|
14
|
+
declare function Route(method: HttpMethod, path: string): (target: any, propertyKey: any) => void;
|
|
18
15
|
|
|
19
16
|
/**
|
|
20
17
|
* Marks a route handler as reactive — when the watched table(s) change,
|
|
21
18
|
* the handler is re-executed and the result is pushed to all subscribed clients.
|
|
22
19
|
*
|
|
23
20
|
* Must be used together with @Route.
|
|
21
|
+
* Works with both TypeScript legacy decorators and TC39 Stage 3 decorators.
|
|
24
22
|
*
|
|
25
23
|
* @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
24
|
*/
|
|
34
|
-
declare function Reactive(options: ReactiveOptions): (target:
|
|
25
|
+
declare function Reactive(options: ReactiveOptions): (target: any, propertyKey: any) => void;
|
|
35
26
|
|
|
36
27
|
/**
|
|
37
28
|
* Base error class for all RouteFlow errors.
|
|
@@ -149,6 +140,8 @@ declare class ReactiveApp {
|
|
|
149
140
|
private readonly options;
|
|
150
141
|
/** Collected route patterns for reactive endpoints */
|
|
151
142
|
private readonly reactivePatterns;
|
|
143
|
+
/** All registered routes (for .routeflow/info.json) */
|
|
144
|
+
private readonly registeredRoutes;
|
|
152
145
|
constructor(options: AppOptions);
|
|
153
146
|
/**
|
|
154
147
|
* Register a controller class. Scans its methods for @Route and @Reactive
|
|
@@ -170,6 +163,7 @@ declare class ReactiveApp {
|
|
|
170
163
|
* @param port - Override the port set in AppOptions
|
|
171
164
|
*/
|
|
172
165
|
listen(port?: number): Promise<void>;
|
|
166
|
+
private writeInfo;
|
|
173
167
|
/**
|
|
174
168
|
* Gracefully shut down the server and disconnect from the database.
|
|
175
169
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,37 @@
|
|
|
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
|
|
6
8
|
var ROUTE_METADATA = /* @__PURE__ */ Symbol("reactive-api:route");
|
|
9
|
+
var routeFnStore = /* @__PURE__ */ new WeakMap();
|
|
7
10
|
function Route(method, path) {
|
|
11
|
+
const metadata = { method, path };
|
|
8
12
|
return function(target, propertyKey) {
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
if (typeof target === "function") {
|
|
14
|
+
routeFnStore.set(target, metadata);
|
|
15
|
+
} else if (typeof propertyKey === "string") {
|
|
16
|
+
const fn = target[propertyKey];
|
|
17
|
+
if (fn) routeFnStore.set(fn, metadata);
|
|
18
|
+
Reflect.defineMetadata(ROUTE_METADATA, metadata, target, propertyKey);
|
|
19
|
+
}
|
|
11
20
|
};
|
|
12
21
|
}
|
|
13
22
|
|
|
14
23
|
// src/core/decorator/reactive.ts
|
|
15
24
|
var REACTIVE_METADATA = /* @__PURE__ */ Symbol("reactive-api:reactive");
|
|
25
|
+
var reactiveFnStore = /* @__PURE__ */ new WeakMap();
|
|
16
26
|
function Reactive(options) {
|
|
17
27
|
return function(target, propertyKey) {
|
|
18
|
-
|
|
28
|
+
if (typeof target === "function") {
|
|
29
|
+
reactiveFnStore.set(target, options);
|
|
30
|
+
} else if (typeof propertyKey === "string") {
|
|
31
|
+
const fn = target[propertyKey];
|
|
32
|
+
if (fn) reactiveFnStore.set(fn, options);
|
|
33
|
+
Reflect.defineMetadata(REACTIVE_METADATA, options, target, propertyKey);
|
|
34
|
+
}
|
|
19
35
|
};
|
|
20
36
|
}
|
|
21
37
|
|
|
@@ -754,6 +770,8 @@ var ReactiveApp = class {
|
|
|
754
770
|
options;
|
|
755
771
|
/** Collected route patterns for reactive endpoints */
|
|
756
772
|
reactivePatterns = [];
|
|
773
|
+
/** All registered routes (for .routeflow/info.json) */
|
|
774
|
+
registeredRoutes = [];
|
|
757
775
|
constructor(options) {
|
|
758
776
|
this.options = {
|
|
759
777
|
transport: "websocket",
|
|
@@ -785,17 +803,10 @@ var ReactiveApp = class {
|
|
|
785
803
|
(name) => name !== "constructor" && typeof proto[name] === "function"
|
|
786
804
|
);
|
|
787
805
|
for (const methodName of methodNames) {
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
proto,
|
|
791
|
-
methodName
|
|
792
|
-
);
|
|
806
|
+
const fn = proto[methodName];
|
|
807
|
+
const routeMeta = routeFnStore.get(fn) ?? Reflect.getMetadata(ROUTE_METADATA, proto, methodName);
|
|
793
808
|
if (!routeMeta) continue;
|
|
794
|
-
const reactiveMeta = Reflect.getMetadata(
|
|
795
|
-
REACTIVE_METADATA,
|
|
796
|
-
proto,
|
|
797
|
-
methodName
|
|
798
|
-
);
|
|
809
|
+
const reactiveMeta = reactiveFnStore.get(fn) ?? Reflect.getMetadata(REACTIVE_METADATA, proto, methodName);
|
|
799
810
|
const handler = instance[methodName];
|
|
800
811
|
this.fastify.route({
|
|
801
812
|
method: routeMeta.method,
|
|
@@ -817,6 +828,7 @@ var ReactiveApp = class {
|
|
|
817
828
|
}
|
|
818
829
|
}
|
|
819
830
|
});
|
|
831
|
+
this.registeredRoutes.push({ method: routeMeta.method, path: routeMeta.path, reactive: !!reactiveMeta });
|
|
820
832
|
if (reactiveMeta) {
|
|
821
833
|
const endpoint = {
|
|
822
834
|
routePath: routeMeta.path,
|
|
@@ -860,6 +872,28 @@ var ReactiveApp = class {
|
|
|
860
872
|
console.log(
|
|
861
873
|
`[RouteFlow] Listening on port ${listenPort} (transport: ${this.options.transport})`
|
|
862
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
|
+
}
|
|
863
897
|
}
|
|
864
898
|
/**
|
|
865
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 } 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","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":";AAAA,OAAO;AACP,SAAS,eAAe,iBAAiB;AACzC,SAAS,YAAY;AACrB,OAAO,aAAkC;;;ACAlC,IAAM,iBAAiB,uBAAO,oBAAoB;AAMlD,IAAM,eAAe,oBAAI,QAA+B;AAWxD,SAAS,MAAM,QAAoB,MAAc;AACtD,QAAM,WAA0B,EAAE,QAAQ,KAAK;AAG/C,SAAO,SAAU,QAAa,aAAwB;AACpD,QAAI,OAAO,WAAW,YAAY;AAEhC,mBAAa,IAAI,QAAQ,QAAQ;AAAA,IACnC,WAAW,OAAO,gBAAgB,UAAU;AAG1C,YAAM,KAAK,OAAO,WAAW;AAC7B,UAAI,GAAI,cAAa,IAAI,IAAI,QAAQ;AACrC,cAAQ,eAAe,gBAAgB,UAAU,QAAQ,WAAW;AAAA,IACtE;AAAA,EACF;AACF;;;ACjCO,IAAM,oBAAoB,uBAAO,uBAAuB;AAKxD,IAAM,kBAAkB,oBAAI,QAAiC;AAW7D,SAAS,SAAS,SAA0B;AAEjD,SAAO,SAAU,QAAa,aAAwB;AACpD,QAAI,OAAO,WAAW,YAAY;AAEhC,sBAAgB,IAAI,QAAQ,OAAO;AAAA,IACrC,WAAW,OAAO,gBAAgB,UAAU;AAG1C,YAAM,KAAK,OAAO,WAAW;AAC7B,UAAI,GAAI,iBAAgB,IAAI,IAAI,OAAO;AACvC,cAAQ,eAAe,mBAAmB,SAAS,QAAQ,WAAW;AAAA,IACxE;AAAA,EACF;AACF;;;AC7BO,IAAM,mBAAN,cAA+B,MAAM;AAAA;AAAA,EAEjC;AAAA,EAET,YAAY,MAAc,SAAiB;AACzC,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAEZ,WAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AACF;;;ACYO,IAAM,iBAAN,MAAqB;AAAA,EAS1B,YAA6B,SAA0B;AAA1B;AAAA,EAA2B;AAAA,EAA3B;AAAA,EARZ,YAAgC,CAAC;AAAA;AAAA,EAEjC,gBAA2C,oBAAI,IAAI;AAAA;AAAA,EAEnD,gBAAyC,oBAAI,IAAI;AAAA;AAAA,EAEjD,iBAA6D,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA,EAOtF,iBAAiB,UAAkC;AACjD,SAAK,UAAU,KAAK,QAAQ;AAE5B,UAAM,SAAS,MAAM,QAAQ,SAAS,QAAQ,KAAK,IAC/C,SAAS,QAAQ,QACjB,CAAC,SAAS,QAAQ,KAAK;AAE3B,eAAW,SAAS,QAAQ;AAC1B,WAAK,kBAAkB,KAAK;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,UAAU,UAAkB,MAAc,KAAc,QAAsB;AAC5E,SAAK,cAAc,IAAI,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAwB;AAClC,SAAK,cAAc,OAAO,QAAQ;AAGlC,eAAW,OAAO,KAAK,eAAe,KAAK,GAAG;AAC5C,UAAI,IAAI,WAAW,GAAG,QAAQ,GAAG,GAAG;AAClC,qBAAa,KAAK,eAAe,IAAI,GAAG,CAAC;AACzC,aAAK,eAAe,OAAO,GAAG;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,eAAW,eAAe,KAAK,cAAc,OAAO,GAAG;AACrD,kBAAY;AAAA,IACd;AACA,SAAK,cAAc,MAAM;AAEzB,eAAW,SAAS,KAAK,eAAe,OAAO,GAAG;AAChD,mBAAa,KAAK;AAAA,IACpB;AACA,SAAK,eAAe,MAAM;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAkB,OAAqB;AAC7C,QAAI,KAAK,cAAc,IAAI,KAAK,EAAG;AAEnC,UAAM,cAAc,KAAK,QAAQ,SAAS,OAAO,CAAC,UAAU;AAC1D,WAAK,cAAc,KAAK;AAAA,IAC1B,CAAC;AACD,SAAK,cAAc,IAAI,OAAO,WAAW;AAAA,EAC3C;AAAA,EAEQ,cAAc,OAA0B;AAE9C,UAAM,oBAAoB,KAAK,UAAU,OAAO,CAAC,OAAO;AACtD,YAAM,SAAS,MAAM,QAAQ,GAAG,QAAQ,KAAK,IACzC,GAAG,QAAQ,QACX,CAAC,GAAG,QAAQ,KAAK;AACrB,aAAO,OAAO,SAAS,MAAM,KAAK;AAAA,IACpC,CAAC;AAED,eAAW,YAAY,mBAAmB;AAExC,iBAAW,CAAC,UAAU,GAAG,KAAK,KAAK,eAAe;AAChD,YAAI,CAAC,mBAAmB,IAAI,MAAM,SAAS,SAAS,EAAG;AAGvD,YAAI,SAAS,QAAQ,QAAQ;AAC3B,cAAI;AACF,gBAAI,CAAC,SAAS,QAAQ,OAAO,OAAO,IAAI,GAAG,EAAG;AAAA,UAChD,SAAS,KAAK;AAEZ;AAAA,UACF;AAAA,QACF;AAEA,aAAK,aAAa,UAAU,UAAU,KAAK,KAAK;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,aACN,UACA,UACA,KACA,QACM;AACN,UAAM,aAAa,SAAS,QAAQ;AAEpC,QAAI,eAAe,UAAa,aAAa,GAAG;AAC9C,YAAM,WAAW,GAAG,QAAQ,IAAI,IAAI,IAAI;AACxC,YAAM,WAAW,KAAK,eAAe,IAAI,QAAQ;AACjD,UAAI,aAAa,OAAW,cAAa,QAAQ;AAEjD,YAAM,QAAQ,WAAW,MAAM;AAC7B,aAAK,eAAe,OAAO,QAAQ;AACnC,aAAK,YAAY,UAAU,GAAG;AAAA,MAChC,GAAG,UAAU;AAEb,WAAK,eAAe,IAAI,UAAU,KAAK;AAAA,IACzC,OAAO;AACL,WAAK,YAAY,UAAU,GAAG;AAAA,IAChC;AAAA,EACF;AAAA,EAEQ,YAAY,UAA4B,KAAyB;AACvE,aAAS,QAAQ,IAAI,GAAG,EAAE;AAAA,MACxB,CAAC,SAAS,IAAI,OAAO,IAAI,MAAM,IAAI;AAAA,MACnC,CAAC,QAAiB;AAChB,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,cAAM,IAAI,iBAAiB,iBAAiB,4BAA4B,OAAO,EAAE;AAAA,MACnF;AAAA,IACF;AAAA,EACF;AACF;AAYO,SAAS,mBAAmB,cAAsB,SAA0B;AACjF,QAAM,WAAW,QACd,QAAQ,sBAAsB,MAAM,EACpC,QAAQ,8BAA8B,SAAS;AAElD,QAAM,QAAQ,IAAI,OAAO,IAAI,QAAQ,GAAG;AACxC,SAAO,MAAM,KAAK,YAAY;AAChC;AAOO,SAAS,cAAc,cAAsB,SAAyC;AAC3F,QAAM,aAAuB,CAAC;AAC9B,QAAM,WAAW,QACd,QAAQ,sBAAsB,MAAM,EACpC,QAAQ,8BAA8B,CAAC,GAAG,SAAiB;AAC1D,eAAW,KAAK,IAAI;AACpB,WAAO;AAAA,EACT,CAAC;AAEH,QAAM,QAAQ,IAAI,OAAO,IAAI,QAAQ,GAAG;AACxC,QAAM,QAAQ,aAAa,MAAM,KAAK;AACtC,MAAI,CAAC,MAAO,QAAO,CAAC;AAEpB,SAAO,OAAO,YAAY,WAAW,IAAI,CAAC,MAAM,MAAM,CAAC,MAAM,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC;AAC7E;;;AClNA,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB,iBAAiB;AA8B3C,SAAS,mBAAmB,OAA2C;AACrE,SACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAkC,MAAM,MAAM,eAC/C,OAAQ,MAAkC,MAAM,MAAM;AAE1D;AAcO,IAAM,qBAAN,MAAyB;AAAA,EAK9B,YACmB,QAEA,eACjB;AAHiB;AAEA;AAGjB,SAAK,MAAM,IAAI,gBAAgB,EAAE,UAAU,KAAK,CAAC;AACjD,SAAK,IAAI,GAAG,cAAc,CAAC,IAAI,QAAQ,KAAK,iBAAiB,IAAI,GAAG,CAAC;AAAA,EACvE;AAAA,EAPmB;AAAA,EAEA;AAAA,EAPF;AAAA;AAAA,EAEA,iBAAsC,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,EAgB/D,OAAO,YAA8B;AACnC,eAAW,GAAG,WAAW,CAAC,KAAK,QAAQ,SAAS;AAC9C,WAAK,IAAI,cAAc,KAAK,QAAQ,MAAM,CAAC,OAAO;AAChD,aAAK,IAAI,KAAK,cAAc,IAAI,GAAG;AAAA,MACrC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,IAAI,MAAM,CAAC,QAAS,MAAM,OAAO,GAAG,IAAI,QAAQ,CAAE;AAAA,IACzD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAMQ,iBAAiB,IAAe,MAA6B;AACnE,UAAM,WAAW,WAAW;AAE5B,OAAG,GAAG,WAAW,CAAC,QAAQ;AACxB,UAAI;AACJ,UAAI;AACF,iBAAS,KAAK,MAAM,IAAI,SAAS,CAAC;AAAA,MACpC,QAAQ;AACN,aAAK,UAAU,IAAI,gBAAgB,4BAA4B;AAC/D;AAAA,MACF;AAEA,UAAI,CAAC,mBAAmB,MAAM,GAAG;AAC/B,aAAK,UAAU,IAAI,mBAAmB,8CAA8C;AACpF;AAAA,MACF;AAEA,WAAK,gBAAgB,IAAI,UAAU,MAAM;AAAA,IAC3C,CAAC;AAED,OAAG,GAAG,SAAS,MAAM;AACnB,WAAK,OAAO,YAAY,QAAQ;AAChC,WAAK,eAAe,OAAO,QAAQ;AAAA,IACrC,CAAC;AAED,OAAG,GAAG,SAAS,CAAC,QAAQ;AACtB,WAAK,OAAO,YAAY,QAAQ;AAChC,WAAK,eAAe,OAAO,QAAQ;AAEnC,UAAI,QAAQ,IAAI,UAAU,MAAM,cAAc;AAC5C,gBAAQ,MAAM,gCAAgC,IAAI,OAAO;AAAA,MAC3D;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,gBAAgB,IAAe,UAAkB,KAA6B;AACpF,UAAM,EAAE,MAAM,QAAQ,CAAC,EAAE,IAAI;AAG7B,UAAM,UAAU,KAAK,cAAc,KAAK,CAAC,MAAM,KAAK,eAAe,MAAM,CAAC,CAAC;AAE3E,QAAI,CAAC,SAAS;AACZ,WAAK,UAAU,IAAI,wBAAwB,wCAAwC,IAAI,EAAE;AACzF;AAAA,IACF;AAEA,UAAM,SAAS,cAAc,MAAM,OAAO;AAE1C,UAAM,MAAe;AAAA,MACnB;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN,SAAS,CAAC;AAAA,IACZ;AAEA,UAAM,SAAS,CAAC,gBAAwB,SAAwB;AAC9D,UAAI,GAAG,eAAe,UAAU,KAAM;AACtC,YAAMA,OAAqB,EAAE,MAAM,UAAU,MAAM,gBAAgB,KAAK;AACxE,SAAG,KAAK,KAAK,UAAUA,IAAG,CAAC;AAAA,IAC7B;AAGA,SAAK,OAAO,YAAY,QAAQ;AAChC,SAAK,eAAe,IAAI,UAAU,OAAO;AACzC,SAAK,OAAO,UAAU,UAAU,MAAM,KAAK,MAAM;AAAA,EACnD;AAAA,EAEQ,eAAe,cAAsB,SAA0B;AACrE,UAAM,WAAW,QACd,QAAQ,sBAAsB,MAAM,EACpC,QAAQ,8BAA8B,SAAS;AAClD,WAAO,IAAI,OAAO,IAAI,QAAQ,GAAG,EAAE,KAAK,YAAY;AAAA,EACtD;AAAA,EAEQ,UAAU,IAAe,MAAc,SAAuB;AACpE,QAAI,GAAG,eAAe,UAAU,KAAM;AACtC,UAAM,MAAoB,EAAE,MAAM,SAAS,MAAM,QAAQ;AACzD,OAAG,KAAK,KAAK,UAAU,GAAG,CAAC;AAAA,EAC7B;AACF;;;ACzKA,SAAS,cAAAC,mBAAkB;AA0BpB,IAAM,eAAN,MAAmB;AAAA,EAIxB,YACmB,QACA,eACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAFgB;AAAA,EACA;AAAA;AAAA,EAJF,cAAyC,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,EAWlE,SAAS,SAAgC;AACvC,YAAQ,IAAI,mBAAmB,OAAO,KAAqB,UAAwB;AACjF,YAAM,QAAQ,IAAI;AAClB,YAAM,OAAO,MAAM,MAAM;AAEzB,UAAI,CAAC,MAAM;AACT,cAAM,IAAI,iBAAiB,oBAAoB,gCAAgC;AAAA,MACjF;AAEA,YAAM,cAAc,mBAAmB,IAAI;AAC3C,YAAM,UAAU,KAAK,cAAc,KAAK,CAAC,MAAM,KAAK,eAAe,aAAa,CAAC,CAAC;AAElF,UAAI,CAAC,SAAS;AACZ,cAAM,KAAK,GAAG;AACd,cAAM,IAAI;AAAA,UACR;AAAA,UACA,wCAAwC,WAAW;AAAA,QACrD;AAAA,MACF;AAEA,YAAM,SAAS,cAAc,aAAa,OAAO;AACjD,YAAM,cAAc,EAAE,GAAG,MAAM;AAC/B,aAAO,YAAY,MAAM;AAEzB,YAAM,MAAe;AAAA,QACnB;AAAA,QACA,OAAO;AAAA,QACP,MAAM;AAAA,QACN,SAAS,IAAI;AAAA,MACf;AAEA,YAAM,WAAWC,YAAW;AAG5B,YAAM,IAAI,UAAU,KAAK;AAAA,QACvB,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,YAAY;AAAA,QACZ,qBAAqB;AAAA;AAAA,MACvB,CAAC;AAGD,YAAM,IAAI,MAAM,iBAAiB;AAEjC,WAAK,YAAY,IAAI,UAAU,KAAK;AAEpC,YAAM,SAAS,CAAC,gBAAwB,SAAwB;AAC9D,YAAI,MAAM,IAAI,UAAW;AACzB,cAAM,UAAU,KAAK,UAAU,EAAE,MAAM,UAAU,MAAM,gBAAgB,KAAK,CAAC;AAC7E,cAAM,IAAI,MAAM,SAAS,OAAO;AAAA;AAAA,CAAM;AAAA,MACxC;AAEA,WAAK,OAAO,UAAU,UAAU,aAAa,KAAK,MAAM;AAGxD,UAAI,IAAI,GAAG,SAAS,MAAM;AACxB,aAAK,OAAO,YAAY,QAAQ;AAChC,aAAK,YAAY,OAAO,QAAQ;AAChC,YAAI,CAAC,MAAM,IAAI,UAAW,OAAM,IAAI,IAAI;AAAA,MAC1C,CAAC;AAGD,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,YAAI,IAAI,GAAG,SAAS,OAAO;AAC3B,YAAI,IAAI,GAAG,SAAS,OAAO;AAAA,MAC7B,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,eAAW,SAAS,KAAK,YAAY,OAAO,GAAG;AAC7C,UAAI,CAAC,MAAM,IAAI,UAAW,OAAM,IAAI,IAAI;AAAA,IAC1C;AACA,SAAK,YAAY,MAAM;AAAA,EACzB;AAAA,EAEQ,eAAe,cAAsB,SAA0B;AACrE,UAAM,WAAW,QACd,QAAQ,sBAAsB,MAAM,EACpC,QAAQ,8BAA8B,SAAS;AAClD,WAAO,IAAI,OAAO,IAAI,QAAQ,GAAG,EAAE,KAAK,YAAY;AAAA,EACtD;AACF;;;ACxEO,IAAM,sBAA4D;AAAA,EACvE;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,YAAY,MAAM;AAAA,IAC5B,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,kBAAkB,mBAAmB,qBAAqB;AAAA,IAC3E,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,UAAU,iBAAiB;AAAA,IACrC,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,cAAc,SAAS,QAAQ;AAAA,IACzC,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,iBAAiB;AAAA,IAClC,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,OAAO;AAAA,IACjB,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,SAAS,WAAW;AAAA,IACjC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,kBAAkB;AAAA,IAC5B,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,QAAQ;AAAA,IAClB,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,SAAS;AAAA,IACnB,YAAY,CAAC,SAAS,eAAe;AAAA,IACrC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,cAAc;AAAA,IACxB,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,gBAAgB;AAAA,IAC1B,YAAY,CAAC,OAAO;AAAA,IACpB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,QAAQ;AAAA,IAClB,YAAY,CAAC,aAAa;AAAA,IAC1B,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,WAAW;AAAA,IACrB,YAAY,CAAC,eAAe,OAAO;AAAA,IACnC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,MAAM;AAAA,IAChB,YAAY,CAAC,aAAa;AAAA,IAC1B,gBAAgB,CAAC,iBAAiB;AAAA,IAClC,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,eAAe;AAAA,IAC5B,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,aAAa;AAAA,IACvB,YAAY,CAAC,eAAe;AAAA,IAC5B,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,sBAAsB;AAAA,IACnC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,iBAAiB;AAAA,IAC3B,YAAY,CAAC,sBAAsB;AAAA,IACnC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,iBAAiB;AAAA,IAC3B,YAAY,CAAC,sBAAsB;AAAA,IACnC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,WAAW,yBAAyB;AAAA,IAC9C,YAAY,CAAC,sBAAsB;AAAA,IACnC,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,WAAW;AAAA,IACxB,gBAAgB,CAAC,iBAAiB;AAAA,IAClC,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,WAAW;AAAA,IACxB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,WAAW;AAAA,IACrB,YAAY,CAAC,UAAU,OAAO;AAAA,IAC9B,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC;AAAA,IACV,YAAY,CAAC,UAAU,OAAO;AAAA,IAC9B,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS,CAAC,kBAAkB,eAAe;AAAA,IAC3C,YAAY,CAAC,QAAQ;AAAA,IACrB,gBAAgB,CAAC,mBAAmB,qBAAqB;AAAA,IACzD,MAAM;AAAA,EACR;AACF;AAEA,SAAS,sBAAsB,OAAuB;AACpD,SAAO,MAAM,YAAY,EAAE,QAAQ,eAAe,GAAG,EAAE,KAAK;AAC9D;AAEO,SAAS,mBAAmB,MAAqD;AACtF,QAAM,aAAa,sBAAsB,IAAI;AAE7C,SAAO,oBAAoB,KAAK,CAAC,aAAa;AAC5C,QAAI,sBAAsB,SAAS,IAAI,MAAM,WAAY,QAAO;AAChE,QAAI,sBAAsB,SAAS,GAAG,MAAM,WAAY,QAAO;AAE/D,WAAO,SAAS,QAAQ,KAAK,CAAC,UAAU,sBAAsB,KAAK,MAAM,UAAU;AAAA,EACrF,CAAC;AACH;AAEO,SAAS,uBACd,UACsC;AACtC,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,oBAAoB,OAAO,CAAC,aAAa,SAAS,WAAW,SAAS,QAAQ,CAAC;AACxF;AAEO,SAAS,sBACd,UACsC;AACtC,QAAM,YAAY,uBAAuB,QAAQ;AACjD,SAAO,UAAU,OAAO,CAAC,aAAa,SAAS,SAAS,UAAU;AACpE;;;ACjSO,IAAM,gBAAN,MAA+C;AAAA,EACnC,YAA4D,oBAAI,IAAI;AAAA,EAC7E,YAAY;AAAA;AAAA,EAGpB,MAAM,UAAyB;AAC7B,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA,EAGA,MAAM,aAA4B;AAChC,SAAK,UAAU,MAAM;AACrB,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS,OAAe,UAAoD;AAC1E,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,IAAI,CAAC;AAAA,IACrC;AACA,SAAK,UAAU,IAAI,KAAK,EAAG,IAAI,QAAQ;AAEvC,WAAO,MAAM;AACX,WAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAQ;AAAA,IAC5C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,KAAK,OAAe,OAAuD;AACzE,UAAM,YAAyB;AAAA,MAC7B,GAAG;AAAA,MACH;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,IACtB;AAEA,UAAM,YAAY,KAAK,UAAU,IAAI,KAAK;AAC1C,QAAI,CAAC,UAAW;AAEhB,eAAW,MAAM,WAAW;AAC1B,SAAG,SAAS;AAAA,IACd;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AACF;;;AC/CO,IAAM,iBAAN,MAAmE;AAAA,EACvD,YAA4D,oBAAI,IAAI;AAAA,EACpE,UAA4C,oBAAI,IAAI;AAAA,EACpD,SAAqD,oBAAI,IAAI;AAAA,EAC7D,eAA4B,oBAAI,IAAI;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY;AAAA,EAEpB,YAAY,SAAyC;AACnD,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,MAAM,QAAQ,QAAQ,MAAM,KAAK,IAAI;AAC1C,SAAK,cAAc,QAAQ;AAC3B,SAAK,UAAU,QAAQ;AAAA,EACzB;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,YAAY;AAEjB,eAAW,SAAS,KAAK,UAAU,KAAK,GAAG;AACzC,WAAK,cAAc,KAAK;AAAA,IAC1B;AAAA,EACF;AAAA,EAEA,MAAM,aAA4B;AAChC,SAAK,YAAY;AAEjB,eAAW,SAAS,KAAK,OAAO,OAAO,GAAG;AACxC,mBAAa,KAAK;AAAA,IACpB;AAEA,SAAK,OAAO,MAAM;AAClB,SAAK,aAAa,MAAM;AAAA,EAC1B;AAAA,EAEA,SAAS,OAAe,UAAoD;AAC1E,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,IAAI,CAAC;AAAA,IACrC;AAEA,SAAK,UAAU,IAAI,KAAK,EAAG,IAAI,QAAQ;AACvC,SAAK,cAAc,KAAK;AAExB,WAAO,MAAM;AACX,YAAM,YAAY,KAAK,UAAU,IAAI,KAAK;AAC1C,UAAI,CAAC,UAAW;AAEhB,gBAAU,OAAO,QAAQ;AAEzB,UAAI,UAAU,SAAS,GAAG;AACxB,aAAK,UAAU,OAAO,KAAK;AAC3B,aAAK,YAAY,KAAK;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,cAAc,OAAqB;AACzC,QAAI,CAAC,KAAK,aAAa,KAAK,aAAa,IAAI,KAAK,EAAG;AAErD,SAAK,aAAa,IAAI,KAAK;AAC3B,SAAK,KAAK,KAAK,KAAK;AAAA,EACtB;AAAA,EAEQ,YAAY,OAAqB;AACvC,UAAM,QAAQ,KAAK,OAAO,IAAI,KAAK;AACnC,QAAI,MAAO,cAAa,KAAK;AAE7B,SAAK,OAAO,OAAO,KAAK;AACxB,SAAK,aAAa,OAAO,KAAK;AAC9B,SAAK,QAAQ,OAAO,KAAK;AAAA,EAC3B;AAAA,EAEQ,aAAa,OAAqB;AACxC,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AACjD,WAAK,YAAY,KAAK;AACtB;AAAA,IACF;AAEA,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK,KAAK,KAAK,KAAK;AAAA,IACtB,GAAG,KAAK,UAAU;AAElB,SAAK,OAAO,IAAI,OAAO,KAAK;AAAA,EAC9B;AAAA,EAEA,MAAc,KAAK,OAA8B;AAC/C,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AACjD,WAAK,YAAY,KAAK;AACtB;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,YAAY;AAAA,QACpC;AAAA,QACA,QAAQ,KAAK,QAAQ,IAAI,KAAK;AAAA,MAChC,CAAC;AAED,WAAK,QAAQ,IAAI,OAAO,OAAO,MAAM;AAErC,iBAAW,SAAS,OAAO,QAAQ;AACjC,aAAK,KAAK,OAAO,KAAK;AAAA,MACxB;AAAA,IACF,SAAS,OAAO;AACd,WAAK,UAAU,OAAO,EAAE,MAAM,CAAC;AAAA,IACjC,UAAE;AACA,WAAK,aAAa,OAAO,KAAK;AAC9B,WAAK,aAAa,KAAK;AAAA,IACzB;AAAA,EACF;AAAA,EAEQ,KACN,cACA,OACM;AACN,UAAM,YAAyB;AAAA,MAC7B,GAAG;AAAA,MACH,OAAO,MAAM,SAAS;AAAA,MACtB,WAAW,MAAM,aAAa,KAAK,IAAI;AAAA,IACzC;AAEA,UAAM,YAAY,KAAK,UAAU,IAAI,UAAU,KAAK;AACpD,QAAI,CAAC,UAAW;AAEhB,eAAW,YAAY,WAAW;AAChC,eAAS,SAAS;AAAA,IACpB;AAAA,EACF;AACF;;;ATvGO,IAAM,cAAN,MAAkB;AAAA,EACN;AAAA,EACA;AAAA,EACT,YAAiC;AAAA,EACxB;AAAA;AAAA,EAEA,mBAA6B,CAAC;AAAA;AAAA,EAE9B,mBAA+E,CAAC;AAAA,EAEjG,YAAY,SAAqB;AAC/B,SAAK,UAAU;AAAA,MACb,WAAW;AAAA,MACX,MAAM;AAAA,MACN,GAAG;AAAA,IACL;AACA,SAAK,UAAU,QAAQ,EAAE,QAAQ,MAAM,CAAC;AACxC,SAAK,SAAS,IAAI,eAAe,KAAK,QAAQ,OAAO;AAGrD,SAAK,QAAQ,gBAAgB,CAAC,OAAO,MAAM,UAAU;AACnD,UAAI,iBAAiB,kBAAkB;AACrC,cAAM,SAAU,MAAqD,cAAc;AACnF,cAAM,OAAO,MAAM,EAAE,KAAK,EAAE,OAAO,MAAM,MAAM,SAAS,MAAM,QAAQ,CAAC;AAAA,MACzE,OAAO;AACL,cAAM,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,kBAAkB,SAAS,MAAM,QAAQ,CAAC;AAAA,MAC5E;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,SAAS,iBAAyC;AAChD,UAAM,WAAW,IAAI,gBAAgB;AACrC,UAAM,QAAQ,OAAO,eAAe,QAAQ;AAE5C,UAAM,cAAc,OAAO,oBAAoB,KAAK,EAAE;AAAA,MACpD,CAAC,SACC,SAAS,iBACT,OAAQ,MAAkC,IAAI,MAAM;AAAA,IACxD;AAEA,eAAW,cAAc,aAAa;AACpC,YAAM,KAAM,MAAkC,UAAU;AACxD,YAAM,YACJ,aAAa,IAAI,EAAE,KAClB,QAAQ,YAAY,gBAAgB,OAAO,UAAU;AACxD,UAAI,CAAC,UAAW;AAEhB,YAAM,eACJ,gBAAgB,IAAI,EAAE,KACrB,QAAQ,YAAY,mBAAmB,OAAO,UAAU;AAE3D,YAAM,UAAW,SAAqC,UAAU;AAKhE,WAAK,QAAQ,MAAM;AAAA,QACjB,QAAQ,UAAU;AAAA,QAClB,KAAK,UAAU;AAAA,QACf,SAAS,OAAO,KAAK,UAAU;AAC7B,gBAAM,MAAe;AAAA,YACnB,QAAQ,IAAI;AAAA,YACZ,OAAO,IAAI;AAAA,YACX,MAAM,IAAI;AAAA,YACV,SAAS,IAAI;AAAA,UACf;AACA,cAAI;AACF,kBAAM,SAAS,MAAM,QAAQ,KAAK,UAAU,GAAG;AAC/C,mBAAO,MAAM,KAAK,MAAM;AAAA,UAC1B,SAAS,KAAK;AACZ,gBAAI,eAAe,iBAAkB,OAAM;AAC3C,kBAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,kBAAM,IAAI,iBAAiB,iBAAiB,GAAG;AAAA,UACjD;AAAA,QACF;AAAA,MACF,CAAC;AAED,WAAK,iBAAiB,KAAK,EAAE,QAAQ,UAAU,QAAQ,MAAM,UAAU,MAAM,UAAU,CAAC,CAAC,aAAa,CAAC;AAGvG,UAAI,cAAc;AAChB,cAAM,WAA6B;AAAA,UACjC,WAAW,UAAU;AAAA,UACrB,SAAS;AAAA,UACT,SAAS,CAAC,QAAiB,QAAQ,KAAK,UAAU,GAAG;AAAA,QACvD;AACA,aAAK,OAAO,iBAAiB,QAAQ;AACrC,aAAK,iBAAiB,KAAK,UAAU,IAAI;AAAA,MAC3C;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAA8B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO,MAA8B;AACzC,UAAM,aAAa,QAAQ,KAAK,QAAQ;AAExC,UAAM,KAAK,QAAQ,QAAQ,QAAQ;AAEnC,QAAI,KAAK,QAAQ,cAAc,aAAa;AAC1C,WAAK,YAAY,IAAI,mBAAmB,KAAK,QAAQ,KAAK,gBAAgB;AAAA,IAC5E,WAAW,KAAK,QAAQ,cAAc,OAAO;AAC3C,YAAM,eAAe,IAAI,aAAa,KAAK,QAAQ,KAAK,gBAAgB;AACxE,mBAAa,SAAS,KAAK,OAAO;AAClC,WAAK,YAAY;AAAA,IACnB;AAEA,UAAM,KAAK,QAAQ,MAAM;AAEzB,QAAI,KAAK,qBAAqB,oBAAoB;AAChD,WAAK,UAAU,OAAO,KAAK,QAAQ,MAAM;AAAA,IAC3C;AAEA,UAAM,KAAK,QAAQ,OAAO,EAAE,MAAM,YAAY,MAAM,UAAU,CAAC;AAC/D,YAAQ;AAAA,MACN,iCAAiC,UAAU,gBAAgB,KAAK,QAAQ,SAAS;AAAA,IACnF;AAEA,SAAK,UAAU,UAAU;AAAA,EAC3B;AAAA,EAEQ,UAAU,MAAoB;AACpC,QAAI;AACF,YAAM,MAAM,KAAK,QAAQ,IAAI,GAAG,YAAY;AAC5C,gBAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAClC;AAAA,QACE,KAAK,KAAK,WAAW;AAAA,QACrB,KAAK;AAAA,UACH;AAAA,YACE;AAAA,YACA,WAAW,KAAK,QAAQ;AAAA,YACxB,SAAS,KAAK,QAAQ,QAAQ,YAAY;AAAA,YAC1C,QAAQ,KAAK;AAAA,YACb,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,SAAK,OAAO,QAAQ;AACpB,QAAI,KAAK,UAAW,OAAM,KAAK,UAAU,MAAM;AAC/C,UAAM,KAAK,QAAQ,MAAM;AACzB,UAAM,KAAK,QAAQ,QAAQ,WAAW;AAAA,EACxC;AACF;AAYO,SAAS,UAAU,SAAkC;AAC1D,SAAO,IAAI,YAAY,OAAO;AAChC;","names":["msg","randomUUID","randomUUID"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "routeflow-api",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "RouteFlow — REST API with real-time database push subscriptions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"cassandra"
|
|
30
30
|
],
|
|
31
31
|
"type": "module",
|
|
32
|
-
"main": "./dist/index.
|
|
33
|
-
"module": "./dist/index.
|
|
32
|
+
"main": "./dist/index.cjs",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
34
|
"types": "./dist/index.d.ts",
|
|
35
35
|
"files": [
|
|
36
36
|
"dist",
|
|
@@ -39,58 +39,58 @@
|
|
|
39
39
|
"exports": {
|
|
40
40
|
".": {
|
|
41
41
|
"types": "./dist/index.d.ts",
|
|
42
|
-
"import": "./dist/index.
|
|
43
|
-
"require": "./dist/index.
|
|
42
|
+
"import": "./dist/index.js",
|
|
43
|
+
"require": "./dist/index.cjs"
|
|
44
44
|
},
|
|
45
45
|
"./client": {
|
|
46
46
|
"types": "./dist/client/index.d.ts",
|
|
47
|
-
"import": "./dist/client/index.
|
|
48
|
-
"require": "./dist/client/index.
|
|
47
|
+
"import": "./dist/client/index.js",
|
|
48
|
+
"require": "./dist/client/index.cjs"
|
|
49
49
|
},
|
|
50
50
|
"./adapters/postgres": {
|
|
51
51
|
"types": "./dist/adapters/postgres.d.ts",
|
|
52
|
-
"import": "./dist/adapters/postgres.
|
|
53
|
-
"require": "./dist/adapters/postgres.
|
|
52
|
+
"import": "./dist/adapters/postgres.js",
|
|
53
|
+
"require": "./dist/adapters/postgres.cjs"
|
|
54
54
|
},
|
|
55
55
|
"./adapters/mongodb": {
|
|
56
56
|
"types": "./dist/adapters/mongodb.d.ts",
|
|
57
|
-
"import": "./dist/adapters/mongodb.
|
|
58
|
-
"require": "./dist/adapters/mongodb.
|
|
57
|
+
"import": "./dist/adapters/mongodb.js",
|
|
58
|
+
"require": "./dist/adapters/mongodb.cjs"
|
|
59
59
|
},
|
|
60
60
|
"./adapters/mysql": {
|
|
61
61
|
"types": "./dist/adapters/mysql.d.ts",
|
|
62
|
-
"import": "./dist/adapters/mysql.
|
|
63
|
-
"require": "./dist/adapters/mysql.
|
|
62
|
+
"import": "./dist/adapters/mysql.js",
|
|
63
|
+
"require": "./dist/adapters/mysql.cjs"
|
|
64
64
|
},
|
|
65
65
|
"./adapters/redis": {
|
|
66
66
|
"types": "./dist/adapters/redis.d.ts",
|
|
67
|
-
"import": "./dist/adapters/redis.
|
|
68
|
-
"require": "./dist/adapters/redis.
|
|
67
|
+
"import": "./dist/adapters/redis.js",
|
|
68
|
+
"require": "./dist/adapters/redis.cjs"
|
|
69
69
|
},
|
|
70
70
|
"./adapters/dynamodb": {
|
|
71
71
|
"types": "./dist/adapters/dynamodb.d.ts",
|
|
72
|
-
"import": "./dist/adapters/dynamodb.
|
|
73
|
-
"require": "./dist/adapters/dynamodb.
|
|
72
|
+
"import": "./dist/adapters/dynamodb.js",
|
|
73
|
+
"require": "./dist/adapters/dynamodb.cjs"
|
|
74
74
|
},
|
|
75
75
|
"./adapters/elasticsearch": {
|
|
76
76
|
"types": "./dist/adapters/elasticsearch.d.ts",
|
|
77
|
-
"import": "./dist/adapters/elasticsearch.
|
|
78
|
-
"require": "./dist/adapters/elasticsearch.
|
|
77
|
+
"import": "./dist/adapters/elasticsearch.js",
|
|
78
|
+
"require": "./dist/adapters/elasticsearch.cjs"
|
|
79
79
|
},
|
|
80
80
|
"./adapters/opensearch": {
|
|
81
81
|
"types": "./dist/adapters/opensearch.d.ts",
|
|
82
|
-
"import": "./dist/adapters/opensearch.
|
|
83
|
-
"require": "./dist/adapters/opensearch.
|
|
82
|
+
"import": "./dist/adapters/opensearch.js",
|
|
83
|
+
"require": "./dist/adapters/opensearch.cjs"
|
|
84
84
|
},
|
|
85
85
|
"./adapters/snowflake": {
|
|
86
86
|
"types": "./dist/adapters/snowflake.d.ts",
|
|
87
|
-
"import": "./dist/adapters/snowflake.
|
|
88
|
-
"require": "./dist/adapters/snowflake.
|
|
87
|
+
"import": "./dist/adapters/snowflake.js",
|
|
88
|
+
"require": "./dist/adapters/snowflake.cjs"
|
|
89
89
|
},
|
|
90
90
|
"./adapters/cassandra": {
|
|
91
91
|
"types": "./dist/adapters/cassandra.d.ts",
|
|
92
|
-
"import": "./dist/adapters/cassandra.
|
|
93
|
-
"require": "./dist/adapters/cassandra.
|
|
92
|
+
"import": "./dist/adapters/cassandra.js",
|
|
93
|
+
"require": "./dist/adapters/cassandra.cjs"
|
|
94
94
|
}
|
|
95
95
|
},
|
|
96
96
|
"scripts": {
|