routeflow-api 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +93 -0
  2. package/dist/adapters/cassandra.cjs +117 -0
  3. package/dist/adapters/cassandra.cjs.map +1 -0
  4. package/dist/adapters/cassandra.d.cts +37 -0
  5. package/dist/adapters/cassandra.d.ts +37 -0
  6. package/dist/adapters/cassandra.js +90 -0
  7. package/dist/adapters/cassandra.js.map +1 -0
  8. package/dist/adapters/dynamodb.cjs +180 -0
  9. package/dist/adapters/dynamodb.cjs.map +1 -0
  10. package/dist/adapters/dynamodb.d.cts +48 -0
  11. package/dist/adapters/dynamodb.d.ts +48 -0
  12. package/dist/adapters/dynamodb.js +153 -0
  13. package/dist/adapters/dynamodb.js.map +1 -0
  14. package/dist/adapters/elasticsearch.cjs +120 -0
  15. package/dist/adapters/elasticsearch.cjs.map +1 -0
  16. package/dist/adapters/elasticsearch.d.cts +43 -0
  17. package/dist/adapters/elasticsearch.d.ts +43 -0
  18. package/dist/adapters/elasticsearch.js +93 -0
  19. package/dist/adapters/elasticsearch.js.map +1 -0
  20. package/dist/adapters/mongodb.cjs +159 -0
  21. package/dist/adapters/mongodb.cjs.map +1 -0
  22. package/dist/adapters/mongodb.d.cts +54 -0
  23. package/dist/adapters/mongodb.d.ts +54 -0
  24. package/dist/adapters/mongodb.js +132 -0
  25. package/dist/adapters/mongodb.js.map +1 -0
  26. package/dist/adapters/mysql.cjs +159 -0
  27. package/dist/adapters/mysql.cjs.map +1 -0
  28. package/dist/adapters/mysql.d.cts +63 -0
  29. package/dist/adapters/mysql.d.ts +63 -0
  30. package/dist/adapters/mysql.js +132 -0
  31. package/dist/adapters/mysql.js.map +1 -0
  32. package/dist/adapters/opensearch.cjs +120 -0
  33. package/dist/adapters/opensearch.cjs.map +1 -0
  34. package/dist/adapters/opensearch.d.cts +2 -0
  35. package/dist/adapters/opensearch.d.ts +2 -0
  36. package/dist/adapters/opensearch.js +93 -0
  37. package/dist/adapters/opensearch.js.map +1 -0
  38. package/dist/adapters/postgres.cjs +271 -0
  39. package/dist/adapters/postgres.cjs.map +1 -0
  40. package/dist/adapters/postgres.d.cts +81 -0
  41. package/dist/adapters/postgres.d.ts +81 -0
  42. package/dist/adapters/postgres.js +244 -0
  43. package/dist/adapters/postgres.js.map +1 -0
  44. package/dist/adapters/redis.cjs +153 -0
  45. package/dist/adapters/redis.cjs.map +1 -0
  46. package/dist/adapters/redis.d.cts +40 -0
  47. package/dist/adapters/redis.d.ts +40 -0
  48. package/dist/adapters/redis.js +126 -0
  49. package/dist/adapters/redis.js.map +1 -0
  50. package/dist/adapters/snowflake.cjs +117 -0
  51. package/dist/adapters/snowflake.cjs.map +1 -0
  52. package/dist/adapters/snowflake.d.cts +37 -0
  53. package/dist/adapters/snowflake.d.ts +37 -0
  54. package/dist/adapters/snowflake.js +90 -0
  55. package/dist/adapters/snowflake.js.map +1 -0
  56. package/dist/client/index.cjs +484 -0
  57. package/dist/client/index.cjs.map +1 -0
  58. package/dist/client/index.d.cts +174 -0
  59. package/dist/client/index.d.ts +174 -0
  60. package/dist/client/index.js +455 -0
  61. package/dist/client/index.js.map +1 -0
  62. package/dist/index.cjs +935 -0
  63. package/dist/index.cjs.map +1 -0
  64. package/dist/index.d.cts +190 -0
  65. package/dist/index.d.ts +190 -0
  66. package/dist/index.js +890 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/types-tPDla8AE.d.cts +75 -0
  69. package/dist/types-tPDla8AE.d.ts +75 -0
  70. package/package.json +157 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/errors.ts","../../src/adapters/redis/redis-adapter.ts"],"sourcesContent":["/**\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 { ChangeEvent, DatabaseAdapter } from '../../core/types.js'\nimport { ReactiveApiError } from '../../core/errors.js'\nimport type { RedisAdapterOptions, RedisChangePayload, RedisSubscriber } from './types.js'\n\n/**\n * Redis adapter for RouteFlow.\n *\n * Each watched table maps to one Redis pub/sub channel. Publishers are expected\n * to publish JSON payloads in the RouteFlow change-event shape.\n */\nexport class RedisAdapter implements DatabaseAdapter {\n private readonly subscriber: RedisSubscriber\n private readonly channelPrefix: string\n private readonly onError?: RedisAdapterOptions['onError']\n private readonly listeners: Map<string, Set<(event: ChangeEvent) => void>> = new Map()\n private readonly handleMessageBound = (channel: string, payload: string) => {\n this.handleMessage(channel, payload)\n }\n private readonly handleErrorBound = (error: Error) => {\n this.onError?.(error)\n }\n private connected = false\n\n constructor(options: RedisAdapterOptions) {\n this.subscriber = options.subscriber\n this.channelPrefix = options.channelPrefix ?? 'flux'\n this.onError = options.onError\n }\n\n async connect(): Promise<void> {\n if (this.connected) return\n\n this.subscriber.on('message', this.handleMessageBound)\n this.subscriber.on('error', this.handleErrorBound)\n\n for (const table of this.listeners.keys()) {\n await this.subscriber.subscribe(channelName(this.channelPrefix, table))\n }\n\n this.connected = true\n }\n\n async disconnect(): Promise<void> {\n if (!this.connected) return\n\n removeListener(this.subscriber, 'message', this.handleMessageBound)\n removeListener(this.subscriber, 'error', this.handleErrorBound)\n\n for (const table of this.listeners.keys()) {\n await this.subscriber.unsubscribe(channelName(this.channelPrefix, table))\n }\n\n await this.subscriber.quit?.()\n await this.subscriber.disconnect?.()\n this.listeners.clear()\n this.connected = false\n }\n\n onChange(table: string, callback: (event: ChangeEvent) => void): () => void {\n const hadTable = this.listeners.has(table)\n if (!hadTable) {\n this.listeners.set(table, new Set())\n }\n\n this.listeners.get(table)!.add(callback)\n\n if (!hadTable && this.connected) {\n void this.subscriber.subscribe(channelName(this.channelPrefix, table))\n }\n\n return () => {\n const callbacks = this.listeners.get(table)\n if (!callbacks) return\n\n callbacks.delete(callback)\n if (callbacks.size === 0) {\n this.listeners.delete(table)\n if (this.connected) {\n void this.subscriber.unsubscribe(channelName(this.channelPrefix, table))\n }\n }\n }\n }\n\n private handleMessage(channel: string, payload: string): void {\n const table = parseChannelName(this.channelPrefix, channel)\n if (!table) return\n\n let data: unknown\n try {\n data = JSON.parse(payload)\n } catch (error) {\n throw new ReactiveApiError(\n 'REDIS_PAYLOAD_INVALID',\n `Failed to parse Redis payload for \"${channel}\": ${errorMessage(error)}`,\n )\n }\n\n if (!isRedisChangePayload(data)) return\n\n const callbacks = this.listeners.get(data.table)\n if (!callbacks) return\n\n const event: ChangeEvent = {\n table: data.table,\n operation: data.operation,\n newRow: data.newRow,\n oldRow: data.oldRow,\n timestamp: data.timestamp ?? Date.now(),\n }\n\n for (const callback of callbacks) {\n callback(event)\n }\n }\n}\n\nfunction channelName(prefix: string, table: string): string {\n return `${prefix}:${table}`\n}\n\nfunction parseChannelName(prefix: string, channel: string): string | null {\n const expectedPrefix = `${prefix}:`\n if (!channel.startsWith(expectedPrefix)) return null\n return channel.slice(expectedPrefix.length)\n}\n\nfunction removeListener(\n subscriber: RedisSubscriber,\n event: 'message' | 'error',\n listener: (...args: any[]) => void,\n): void {\n if (subscriber.off) {\n subscriber.off(event, listener)\n return\n }\n\n subscriber.removeListener?.(event, listener)\n}\n\nfunction isRedisChangePayload(value: unknown): value is RedisChangePayload {\n if (typeof value !== 'object' || value === null) return false\n const record = value as Record<string, unknown>\n return (\n typeof record['table'] === 'string' &&\n (record['operation'] === 'INSERT' ||\n record['operation'] === 'UPDATE' ||\n record['operation'] === 'DELETE')\n )\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/adapters/snowflake.ts
21
+ var snowflake_exports = {};
22
+ __export(snowflake_exports, {
23
+ SnowflakeAdapter: () => SnowflakeAdapter
24
+ });
25
+ module.exports = __toCommonJS(snowflake_exports);
26
+
27
+ // src/core/errors.ts
28
+ var ReactiveApiError = class extends Error {
29
+ /** Machine-readable error code (e.g. 'ADAPTER_NOT_CONNECTED', 'INVALID_ROUTE') */
30
+ code;
31
+ constructor(code, message) {
32
+ super(message);
33
+ this.name = "ReactiveApiError";
34
+ this.code = code;
35
+ Object.setPrototypeOf(this, new.target.prototype);
36
+ }
37
+ };
38
+
39
+ // src/adapters/snowflake/snowflake-adapter.ts
40
+ var SnowflakeAdapter = class {
41
+ source;
42
+ onError;
43
+ listeners = /* @__PURE__ */ new Map();
44
+ handleChangeBound = (event) => {
45
+ this.handleChange(event);
46
+ };
47
+ handleErrorBound = (error) => {
48
+ this.onError?.(error);
49
+ };
50
+ connected = false;
51
+ constructor(options) {
52
+ this.source = options.source;
53
+ this.onError = options.onError;
54
+ }
55
+ async connect() {
56
+ if (this.connected) return;
57
+ this.source.on("change", this.handleChangeBound);
58
+ this.source.on("error", this.handleErrorBound);
59
+ try {
60
+ await this.source.start?.();
61
+ } catch (error) {
62
+ throw new ReactiveApiError(
63
+ "SNOWFLAKE_SOURCE_START_FAILED",
64
+ `Failed to start Snowflake change source: ${errorMessage(error)}`
65
+ );
66
+ }
67
+ this.connected = true;
68
+ }
69
+ async disconnect() {
70
+ if (!this.connected) return;
71
+ removeListener(this.source, "change", this.handleChangeBound);
72
+ removeListener(this.source, "error", this.handleErrorBound);
73
+ await this.source.stop?.();
74
+ this.listeners.clear();
75
+ this.connected = false;
76
+ }
77
+ onChange(table, callback) {
78
+ if (!this.listeners.has(table)) {
79
+ this.listeners.set(table, /* @__PURE__ */ new Set());
80
+ }
81
+ this.listeners.get(table).add(callback);
82
+ return () => {
83
+ const callbacks = this.listeners.get(table);
84
+ if (!callbacks) return;
85
+ callbacks.delete(callback);
86
+ if (callbacks.size === 0) {
87
+ this.listeners.delete(table);
88
+ }
89
+ };
90
+ }
91
+ handleChange(change) {
92
+ const callbacks = this.listeners.get(change.table);
93
+ if (!callbacks) return;
94
+ const event = {
95
+ ...change,
96
+ timestamp: change.timestamp ?? Date.now()
97
+ };
98
+ for (const callback of callbacks) {
99
+ callback(event);
100
+ }
101
+ }
102
+ };
103
+ function removeListener(source, event, listener) {
104
+ if (source.off) {
105
+ source.off(event, listener);
106
+ return;
107
+ }
108
+ source.removeListener?.(event, listener);
109
+ }
110
+ function errorMessage(error) {
111
+ return error instanceof Error ? error.message : String(error);
112
+ }
113
+ // Annotate the CommonJS export names for ESM import in node:
114
+ 0 && (module.exports = {
115
+ SnowflakeAdapter
116
+ });
117
+ //# sourceMappingURL=snowflake.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/snowflake.ts","../../src/core/errors.ts","../../src/adapters/snowflake/snowflake-adapter.ts"],"sourcesContent":["export { SnowflakeAdapter } from './snowflake/snowflake-adapter.js'\nexport type { SnowflakeAdapterOptions } from './snowflake/types.js'\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 { ChangeEvent, DatabaseAdapter } from '../../core/types.js'\nimport { ReactiveApiError } from '../../core/errors.js'\nimport type {\n SnowflakeAdapterOptions,\n SnowflakeChangeEvent,\n SnowflakeChangeSource,\n} from './types.js'\n\nexport class SnowflakeAdapter implements DatabaseAdapter {\n private readonly source: SnowflakeChangeSource\n private readonly onError?: SnowflakeAdapterOptions['onError']\n private readonly listeners: Map<string, Set<(event: ChangeEvent) => void>> = new Map()\n private readonly handleChangeBound = (event: SnowflakeChangeEvent) => {\n this.handleChange(event)\n }\n private readonly handleErrorBound = (error: Error) => {\n this.onError?.(error)\n }\n private connected = false\n\n constructor(options: SnowflakeAdapterOptions) {\n this.source = options.source\n this.onError = options.onError\n }\n\n async connect(): Promise<void> {\n if (this.connected) return\n\n this.source.on('change', this.handleChangeBound)\n this.source.on('error', this.handleErrorBound)\n\n try {\n await this.source.start?.()\n } catch (error) {\n throw new ReactiveApiError(\n 'SNOWFLAKE_SOURCE_START_FAILED',\n `Failed to start Snowflake change source: ${errorMessage(error)}`,\n )\n }\n\n this.connected = true\n }\n\n async disconnect(): Promise<void> {\n if (!this.connected) return\n\n removeListener(this.source, 'change', this.handleChangeBound)\n removeListener(this.source, 'error', this.handleErrorBound)\n await this.source.stop?.()\n this.listeners.clear()\n this.connected = false\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\n return () => {\n const callbacks = this.listeners.get(table)\n if (!callbacks) return\n\n callbacks.delete(callback)\n if (callbacks.size === 0) {\n this.listeners.delete(table)\n }\n }\n }\n\n private handleChange(change: SnowflakeChangeEvent): void {\n const callbacks = this.listeners.get(change.table)\n if (!callbacks) return\n\n const event: ChangeEvent = {\n ...change,\n timestamp: change.timestamp ?? Date.now(),\n }\n\n for (const callback of callbacks) {\n callback(event)\n }\n }\n}\n\nfunction removeListener(\n source: SnowflakeChangeSource,\n event: 'change' | 'error',\n listener: (...args: any[]) => void,\n): void {\n if (source.off) {\n source.off(event, listener)\n return\n }\n\n source.removeListener?.(event, listener)\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACIO,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;;;ACPO,IAAM,mBAAN,MAAkD;AAAA,EACtC;AAAA,EACA;AAAA,EACA,YAA4D,oBAAI,IAAI;AAAA,EACpE,oBAAoB,CAAC,UAAgC;AACpE,SAAK,aAAa,KAAK;AAAA,EACzB;AAAA,EACiB,mBAAmB,CAAC,UAAiB;AACpD,SAAK,UAAU,KAAK;AAAA,EACtB;AAAA,EACQ,YAAY;AAAA,EAEpB,YAAY,SAAkC;AAC5C,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ;AAAA,EACzB;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,UAAW;AAEpB,SAAK,OAAO,GAAG,UAAU,KAAK,iBAAiB;AAC/C,SAAK,OAAO,GAAG,SAAS,KAAK,gBAAgB;AAE7C,QAAI;AACF,YAAM,KAAK,OAAO,QAAQ;AAAA,IAC5B,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR;AAAA,QACA,4CAA4C,aAAa,KAAK,CAAC;AAAA,MACjE;AAAA,IACF;AAEA,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,aAA4B;AAChC,QAAI,CAAC,KAAK,UAAW;AAErB,mBAAe,KAAK,QAAQ,UAAU,KAAK,iBAAiB;AAC5D,mBAAe,KAAK,QAAQ,SAAS,KAAK,gBAAgB;AAC1D,UAAM,KAAK,OAAO,OAAO;AACzB,SAAK,UAAU,MAAM;AACrB,SAAK,YAAY;AAAA,EACnB;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;AAEvC,WAAO,MAAM;AACX,YAAM,YAAY,KAAK,UAAU,IAAI,KAAK;AAC1C,UAAI,CAAC,UAAW;AAEhB,gBAAU,OAAO,QAAQ;AACzB,UAAI,UAAU,SAAS,GAAG;AACxB,aAAK,UAAU,OAAO,KAAK;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,aAAa,QAAoC;AACvD,UAAM,YAAY,KAAK,UAAU,IAAI,OAAO,KAAK;AACjD,QAAI,CAAC,UAAW;AAEhB,UAAM,QAAqB;AAAA,MACzB,GAAG;AAAA,MACH,WAAW,OAAO,aAAa,KAAK,IAAI;AAAA,IAC1C;AAEA,eAAW,YAAY,WAAW;AAChC,eAAS,KAAK;AAAA,IAChB;AAAA,EACF;AACF;AAEA,SAAS,eACP,QACA,OACA,UACM;AACN,MAAI,OAAO,KAAK;AACd,WAAO,IAAI,OAAO,QAAQ;AAC1B;AAAA,EACF;AAEA,SAAO,iBAAiB,OAAO,QAAQ;AACzC;AAEA,SAAS,aAAa,OAAwB;AAC5C,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;","names":[]}
@@ -0,0 +1,37 @@
1
+ import { C as ChangeEvent, D as DatabaseAdapter } from '../types-tPDla8AE.cjs';
2
+
3
+ interface SnowflakeChangeEvent<T = unknown> {
4
+ table: string;
5
+ operation: ChangeEvent<T>['operation'];
6
+ newRow: T | null;
7
+ oldRow: T | null;
8
+ timestamp?: number;
9
+ }
10
+ interface SnowflakeChangeSource {
11
+ on(event: 'change', listener: (event: SnowflakeChangeEvent) => void): void;
12
+ on(event: 'error', listener: (error: Error) => void): void;
13
+ off?(event: 'change' | 'error', listener: (...args: any[]) => void): void;
14
+ removeListener?(event: 'change' | 'error', listener: (...args: any[]) => void): void;
15
+ start?(): Promise<void> | void;
16
+ stop?(): Promise<void> | void;
17
+ }
18
+ interface SnowflakeAdapterOptions {
19
+ source: SnowflakeChangeSource;
20
+ onError?: (error: unknown) => void;
21
+ }
22
+
23
+ declare class SnowflakeAdapter implements DatabaseAdapter {
24
+ private readonly source;
25
+ private readonly onError?;
26
+ private readonly listeners;
27
+ private readonly handleChangeBound;
28
+ private readonly handleErrorBound;
29
+ private connected;
30
+ constructor(options: SnowflakeAdapterOptions);
31
+ connect(): Promise<void>;
32
+ disconnect(): Promise<void>;
33
+ onChange(table: string, callback: (event: ChangeEvent) => void): () => void;
34
+ private handleChange;
35
+ }
36
+
37
+ export { SnowflakeAdapter, type SnowflakeAdapterOptions };
@@ -0,0 +1,37 @@
1
+ import { C as ChangeEvent, D as DatabaseAdapter } from '../types-tPDla8AE.js';
2
+
3
+ interface SnowflakeChangeEvent<T = unknown> {
4
+ table: string;
5
+ operation: ChangeEvent<T>['operation'];
6
+ newRow: T | null;
7
+ oldRow: T | null;
8
+ timestamp?: number;
9
+ }
10
+ interface SnowflakeChangeSource {
11
+ on(event: 'change', listener: (event: SnowflakeChangeEvent) => void): void;
12
+ on(event: 'error', listener: (error: Error) => void): void;
13
+ off?(event: 'change' | 'error', listener: (...args: any[]) => void): void;
14
+ removeListener?(event: 'change' | 'error', listener: (...args: any[]) => void): void;
15
+ start?(): Promise<void> | void;
16
+ stop?(): Promise<void> | void;
17
+ }
18
+ interface SnowflakeAdapterOptions {
19
+ source: SnowflakeChangeSource;
20
+ onError?: (error: unknown) => void;
21
+ }
22
+
23
+ declare class SnowflakeAdapter implements DatabaseAdapter {
24
+ private readonly source;
25
+ private readonly onError?;
26
+ private readonly listeners;
27
+ private readonly handleChangeBound;
28
+ private readonly handleErrorBound;
29
+ private connected;
30
+ constructor(options: SnowflakeAdapterOptions);
31
+ connect(): Promise<void>;
32
+ disconnect(): Promise<void>;
33
+ onChange(table: string, callback: (event: ChangeEvent) => void): () => void;
34
+ private handleChange;
35
+ }
36
+
37
+ export { SnowflakeAdapter, type SnowflakeAdapterOptions };
@@ -0,0 +1,90 @@
1
+ // src/core/errors.ts
2
+ var ReactiveApiError = class extends Error {
3
+ /** Machine-readable error code (e.g. 'ADAPTER_NOT_CONNECTED', 'INVALID_ROUTE') */
4
+ code;
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.name = "ReactiveApiError";
8
+ this.code = code;
9
+ Object.setPrototypeOf(this, new.target.prototype);
10
+ }
11
+ };
12
+
13
+ // src/adapters/snowflake/snowflake-adapter.ts
14
+ var SnowflakeAdapter = class {
15
+ source;
16
+ onError;
17
+ listeners = /* @__PURE__ */ new Map();
18
+ handleChangeBound = (event) => {
19
+ this.handleChange(event);
20
+ };
21
+ handleErrorBound = (error) => {
22
+ this.onError?.(error);
23
+ };
24
+ connected = false;
25
+ constructor(options) {
26
+ this.source = options.source;
27
+ this.onError = options.onError;
28
+ }
29
+ async connect() {
30
+ if (this.connected) return;
31
+ this.source.on("change", this.handleChangeBound);
32
+ this.source.on("error", this.handleErrorBound);
33
+ try {
34
+ await this.source.start?.();
35
+ } catch (error) {
36
+ throw new ReactiveApiError(
37
+ "SNOWFLAKE_SOURCE_START_FAILED",
38
+ `Failed to start Snowflake change source: ${errorMessage(error)}`
39
+ );
40
+ }
41
+ this.connected = true;
42
+ }
43
+ async disconnect() {
44
+ if (!this.connected) return;
45
+ removeListener(this.source, "change", this.handleChangeBound);
46
+ removeListener(this.source, "error", this.handleErrorBound);
47
+ await this.source.stop?.();
48
+ this.listeners.clear();
49
+ this.connected = false;
50
+ }
51
+ onChange(table, callback) {
52
+ if (!this.listeners.has(table)) {
53
+ this.listeners.set(table, /* @__PURE__ */ new Set());
54
+ }
55
+ this.listeners.get(table).add(callback);
56
+ return () => {
57
+ const callbacks = this.listeners.get(table);
58
+ if (!callbacks) return;
59
+ callbacks.delete(callback);
60
+ if (callbacks.size === 0) {
61
+ this.listeners.delete(table);
62
+ }
63
+ };
64
+ }
65
+ handleChange(change) {
66
+ const callbacks = this.listeners.get(change.table);
67
+ if (!callbacks) return;
68
+ const event = {
69
+ ...change,
70
+ timestamp: change.timestamp ?? Date.now()
71
+ };
72
+ for (const callback of callbacks) {
73
+ callback(event);
74
+ }
75
+ }
76
+ };
77
+ function removeListener(source, event, listener) {
78
+ if (source.off) {
79
+ source.off(event, listener);
80
+ return;
81
+ }
82
+ source.removeListener?.(event, listener);
83
+ }
84
+ function errorMessage(error) {
85
+ return error instanceof Error ? error.message : String(error);
86
+ }
87
+ export {
88
+ SnowflakeAdapter
89
+ };
90
+ //# sourceMappingURL=snowflake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/errors.ts","../../src/adapters/snowflake/snowflake-adapter.ts"],"sourcesContent":["/**\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 { ChangeEvent, DatabaseAdapter } from '../../core/types.js'\nimport { ReactiveApiError } from '../../core/errors.js'\nimport type {\n SnowflakeAdapterOptions,\n SnowflakeChangeEvent,\n SnowflakeChangeSource,\n} from './types.js'\n\nexport class SnowflakeAdapter implements DatabaseAdapter {\n private readonly source: SnowflakeChangeSource\n private readonly onError?: SnowflakeAdapterOptions['onError']\n private readonly listeners: Map<string, Set<(event: ChangeEvent) => void>> = new Map()\n private readonly handleChangeBound = (event: SnowflakeChangeEvent) => {\n this.handleChange(event)\n }\n private readonly handleErrorBound = (error: Error) => {\n this.onError?.(error)\n }\n private connected = false\n\n constructor(options: SnowflakeAdapterOptions) {\n this.source = options.source\n this.onError = options.onError\n }\n\n async connect(): Promise<void> {\n if (this.connected) return\n\n this.source.on('change', this.handleChangeBound)\n this.source.on('error', this.handleErrorBound)\n\n try {\n await this.source.start?.()\n } catch (error) {\n throw new ReactiveApiError(\n 'SNOWFLAKE_SOURCE_START_FAILED',\n `Failed to start Snowflake change source: ${errorMessage(error)}`,\n )\n }\n\n this.connected = true\n }\n\n async disconnect(): Promise<void> {\n if (!this.connected) return\n\n removeListener(this.source, 'change', this.handleChangeBound)\n removeListener(this.source, 'error', this.handleErrorBound)\n await this.source.stop?.()\n this.listeners.clear()\n this.connected = false\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\n return () => {\n const callbacks = this.listeners.get(table)\n if (!callbacks) return\n\n callbacks.delete(callback)\n if (callbacks.size === 0) {\n this.listeners.delete(table)\n }\n }\n }\n\n private handleChange(change: SnowflakeChangeEvent): void {\n const callbacks = this.listeners.get(change.table)\n if (!callbacks) return\n\n const event: ChangeEvent = {\n ...change,\n timestamp: change.timestamp ?? Date.now(),\n }\n\n for (const callback of callbacks) {\n callback(event)\n }\n }\n}\n\nfunction removeListener(\n source: SnowflakeChangeSource,\n event: 'change' | 'error',\n listener: (...args: any[]) => void,\n): void {\n if (source.off) {\n source.off(event, listener)\n return\n }\n\n source.removeListener?.(event, listener)\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n"],"mappings":";AAIO,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;;;ACPO,IAAM,mBAAN,MAAkD;AAAA,EACtC;AAAA,EACA;AAAA,EACA,YAA4D,oBAAI,IAAI;AAAA,EACpE,oBAAoB,CAAC,UAAgC;AACpE,SAAK,aAAa,KAAK;AAAA,EACzB;AAAA,EACiB,mBAAmB,CAAC,UAAiB;AACpD,SAAK,UAAU,KAAK;AAAA,EACtB;AAAA,EACQ,YAAY;AAAA,EAEpB,YAAY,SAAkC;AAC5C,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ;AAAA,EACzB;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,UAAW;AAEpB,SAAK,OAAO,GAAG,UAAU,KAAK,iBAAiB;AAC/C,SAAK,OAAO,GAAG,SAAS,KAAK,gBAAgB;AAE7C,QAAI;AACF,YAAM,KAAK,OAAO,QAAQ;AAAA,IAC5B,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR;AAAA,QACA,4CAA4C,aAAa,KAAK,CAAC;AAAA,MACjE;AAAA,IACF;AAEA,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,aAA4B;AAChC,QAAI,CAAC,KAAK,UAAW;AAErB,mBAAe,KAAK,QAAQ,UAAU,KAAK,iBAAiB;AAC5D,mBAAe,KAAK,QAAQ,SAAS,KAAK,gBAAgB;AAC1D,UAAM,KAAK,OAAO,OAAO;AACzB,SAAK,UAAU,MAAM;AACrB,SAAK,YAAY;AAAA,EACnB;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;AAEvC,WAAO,MAAM;AACX,YAAM,YAAY,KAAK,UAAU,IAAI,KAAK;AAC1C,UAAI,CAAC,UAAW;AAEhB,gBAAU,OAAO,QAAQ;AACzB,UAAI,UAAU,SAAS,GAAG;AACxB,aAAK,UAAU,OAAO,KAAK;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,aAAa,QAAoC;AACvD,UAAM,YAAY,KAAK,UAAU,IAAI,OAAO,KAAK;AACjD,QAAI,CAAC,UAAW;AAEhB,UAAM,QAAqB;AAAA,MACzB,GAAG;AAAA,MACH,WAAW,OAAO,aAAa,KAAK,IAAI;AAAA,IAC1C;AAEA,eAAW,YAAY,WAAW;AAChC,eAAS,KAAK;AAAA,IAChB;AAAA,EACF;AACF;AAEA,SAAS,eACP,QACA,OACA,UACM;AACN,MAAI,OAAO,KAAK;AACd,WAAO,IAAI,OAAO,QAAQ;AAC1B;AAAA,EACF;AAEA,SAAO,iBAAiB,OAAO,QAAQ;AACzC;AAEA,SAAS,aAAa,OAAwB;AAC5C,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;","names":[]}