abxbus 2.4.1
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 +647 -0
- package/dist/cjs/async_context.d.ts +12 -0
- package/dist/cjs/async_context.js +70 -0
- package/dist/cjs/async_context.js.map +7 -0
- package/dist/cjs/base_event.d.ts +207 -0
- package/dist/cjs/base_event.js +871 -0
- package/dist/cjs/base_event.js.map +7 -0
- package/dist/cjs/bridge_jsonl.d.ts +26 -0
- package/dist/cjs/bridge_jsonl.js +170 -0
- package/dist/cjs/bridge_jsonl.js.map +7 -0
- package/dist/cjs/bridge_nats.d.ts +20 -0
- package/dist/cjs/bridge_nats.js +108 -0
- package/dist/cjs/bridge_nats.js.map +7 -0
- package/dist/cjs/bridge_postgres.d.ts +31 -0
- package/dist/cjs/bridge_postgres.js +251 -0
- package/dist/cjs/bridge_postgres.js.map +7 -0
- package/dist/cjs/bridge_redis.d.ts +34 -0
- package/dist/cjs/bridge_redis.js +175 -0
- package/dist/cjs/bridge_redis.js.map +7 -0
- package/dist/cjs/bridge_sqlite.d.ts +30 -0
- package/dist/cjs/bridge_sqlite.js +255 -0
- package/dist/cjs/bridge_sqlite.js.map +7 -0
- package/dist/cjs/bridges.d.ts +49 -0
- package/dist/cjs/bridges.js +326 -0
- package/dist/cjs/bridges.js.map +7 -0
- package/dist/cjs/event_bus.d.ts +127 -0
- package/dist/cjs/event_bus.js +1058 -0
- package/dist/cjs/event_bus.js.map +7 -0
- package/dist/cjs/event_handler.d.ts +139 -0
- package/dist/cjs/event_handler.js +299 -0
- package/dist/cjs/event_handler.js.map +7 -0
- package/dist/cjs/event_history.d.ts +45 -0
- package/dist/cjs/event_history.js +192 -0
- package/dist/cjs/event_history.js.map +7 -0
- package/dist/cjs/event_result.d.ts +86 -0
- package/dist/cjs/event_result.js +446 -0
- package/dist/cjs/event_result.js.map +7 -0
- package/dist/cjs/events_suck.d.ts +40 -0
- package/dist/cjs/events_suck.js +59 -0
- package/dist/cjs/events_suck.js.map +7 -0
- package/dist/cjs/helpers.d.ts +1 -0
- package/dist/cjs/helpers.js +84 -0
- package/dist/cjs/helpers.js.map +7 -0
- package/dist/cjs/index.d.ts +17 -0
- package/dist/cjs/index.js +54 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/lock_manager.d.ts +70 -0
- package/dist/cjs/lock_manager.js +343 -0
- package/dist/cjs/lock_manager.js.map +7 -0
- package/dist/cjs/logging.d.ts +16 -0
- package/dist/cjs/logging.js +216 -0
- package/dist/cjs/logging.js.map +7 -0
- package/dist/cjs/middlewares.d.ts +13 -0
- package/dist/cjs/middlewares.js +17 -0
- package/dist/cjs/middlewares.js.map +7 -0
- package/dist/cjs/optional_deps.d.ts +3 -0
- package/dist/cjs/optional_deps.js +64 -0
- package/dist/cjs/optional_deps.js.map +7 -0
- package/dist/cjs/package.json +5 -0
- package/dist/cjs/retry.d.ts +52 -0
- package/dist/cjs/retry.js +257 -0
- package/dist/cjs/retry.js.map +7 -0
- package/dist/cjs/timing.d.ts +3 -0
- package/dist/cjs/timing.js +76 -0
- package/dist/cjs/timing.js.map +7 -0
- package/dist/cjs/type_inference.test.d.ts +1 -0
- package/dist/cjs/types.d.ts +36 -0
- package/dist/cjs/types.js +104 -0
- package/dist/cjs/types.js.map +7 -0
- package/dist/esm/async_context.js +50 -0
- package/dist/esm/async_context.js.map +7 -0
- package/dist/esm/base_event.js +857 -0
- package/dist/esm/base_event.js.map +7 -0
- package/dist/esm/bridge_jsonl.js +150 -0
- package/dist/esm/bridge_jsonl.js.map +7 -0
- package/dist/esm/bridge_nats.js +88 -0
- package/dist/esm/bridge_nats.js.map +7 -0
- package/dist/esm/bridge_postgres.js +231 -0
- package/dist/esm/bridge_postgres.js.map +7 -0
- package/dist/esm/bridge_redis.js +155 -0
- package/dist/esm/bridge_redis.js.map +7 -0
- package/dist/esm/bridge_sqlite.js +235 -0
- package/dist/esm/bridge_sqlite.js.map +7 -0
- package/dist/esm/bridges.js +306 -0
- package/dist/esm/bridges.js.map +7 -0
- package/dist/esm/event_bus.js +1046 -0
- package/dist/esm/event_bus.js.map +7 -0
- package/dist/esm/event_handler.js +279 -0
- package/dist/esm/event_handler.js.map +7 -0
- package/dist/esm/event_history.js +172 -0
- package/dist/esm/event_history.js.map +7 -0
- package/dist/esm/event_result.js +426 -0
- package/dist/esm/event_result.js.map +7 -0
- package/dist/esm/events_suck.js +39 -0
- package/dist/esm/events_suck.js.map +7 -0
- package/dist/esm/helpers.js +64 -0
- package/dist/esm/helpers.js.map +7 -0
- package/dist/esm/index.js +47 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/lock_manager.js +323 -0
- package/dist/esm/lock_manager.js.map +7 -0
- package/dist/esm/logging.js +196 -0
- package/dist/esm/logging.js.map +7 -0
- package/dist/esm/middlewares.js +1 -0
- package/dist/esm/middlewares.js.map +7 -0
- package/dist/esm/optional_deps.js +44 -0
- package/dist/esm/optional_deps.js.map +7 -0
- package/dist/esm/retry.js +237 -0
- package/dist/esm/retry.js.map +7 -0
- package/dist/esm/timing.js +56 -0
- package/dist/esm/timing.js.map +7 -0
- package/dist/esm/types.js +84 -0
- package/dist/esm/types.js.map +7 -0
- package/dist/types/async_context.d.ts +12 -0
- package/dist/types/base_event.d.ts +207 -0
- package/dist/types/bridge_jsonl.d.ts +26 -0
- package/dist/types/bridge_nats.d.ts +20 -0
- package/dist/types/bridge_postgres.d.ts +31 -0
- package/dist/types/bridge_redis.d.ts +34 -0
- package/dist/types/bridge_sqlite.d.ts +30 -0
- package/dist/types/bridges.d.ts +49 -0
- package/dist/types/event_bus.d.ts +127 -0
- package/dist/types/event_handler.d.ts +139 -0
- package/dist/types/event_history.d.ts +45 -0
- package/dist/types/event_result.d.ts +86 -0
- package/dist/types/events_suck.d.ts +40 -0
- package/dist/types/helpers.d.ts +1 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/lock_manager.d.ts +70 -0
- package/dist/types/logging.d.ts +16 -0
- package/dist/types/middlewares.d.ts +13 -0
- package/dist/types/optional_deps.d.ts +3 -0
- package/dist/types/retry.d.ts +52 -0
- package/dist/types/timing.d.ts +3 -0
- package/dist/types/type_inference.test.d.ts +1 -0
- package/dist/types/types.d.ts +36 -0
- package/package.json +87 -0
|
@@ -0,0 +1,251 @@
|
|
|
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
|
+
var bridge_postgres_exports = {};
|
|
20
|
+
__export(bridge_postgres_exports, {
|
|
21
|
+
PostgresEventBridge: () => PostgresEventBridge
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(bridge_postgres_exports);
|
|
24
|
+
var import_base_event = require("./base_event.js");
|
|
25
|
+
var import_event_bus = require("./event_bus.js");
|
|
26
|
+
var import_optional_deps = require("./optional_deps.js");
|
|
27
|
+
const randomSuffix = () => Math.random().toString(36).slice(2, 10);
|
|
28
|
+
const IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
29
|
+
const DEFAULT_POSTGRES_TABLE = "abxbus_events";
|
|
30
|
+
const DEFAULT_POSTGRES_CHANNEL = "abxbus_events";
|
|
31
|
+
const EVENT_PAYLOAD_COLUMN = "event_payload";
|
|
32
|
+
const validateIdentifier = (value, label) => {
|
|
33
|
+
if (!IDENTIFIER_RE.test(value)) {
|
|
34
|
+
throw new Error(`Invalid ${label}: ${JSON.stringify(value)}. Use only [A-Za-z0-9_] and start with a letter/_`);
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
};
|
|
38
|
+
const indexName = (table, suffix) => validateIdentifier(`${table}_${suffix}`.slice(0, 63), "index name");
|
|
39
|
+
const parseTableUrl = (table_url) => {
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = new URL(table_url);
|
|
43
|
+
} catch {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"PostgresEventBridge URL must include at least database in path, e.g. postgresql://user:pass@host:5432/dbname[/tablename]"
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
49
|
+
if (segments.length < 1) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"PostgresEventBridge URL must include at least database in path, e.g. postgresql://user:pass@host:5432/dbname[/tablename]"
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const db_name = segments[0];
|
|
55
|
+
const table = segments.length >= 2 ? validateIdentifier(segments[1], "table name") : DEFAULT_POSTGRES_TABLE;
|
|
56
|
+
const dsn_url = new URL(parsed.toString());
|
|
57
|
+
dsn_url.pathname = `/${db_name}`;
|
|
58
|
+
return { dsn: dsn_url.toString(), table };
|
|
59
|
+
};
|
|
60
|
+
const splitBridgePayload = (payload) => {
|
|
61
|
+
const event_fields = {};
|
|
62
|
+
const event_payload = { ...payload };
|
|
63
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
64
|
+
if (key.startsWith("event_")) {
|
|
65
|
+
event_fields[key] = value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { event_fields, event_payload };
|
|
69
|
+
};
|
|
70
|
+
class PostgresEventBridge {
|
|
71
|
+
table_url;
|
|
72
|
+
dsn;
|
|
73
|
+
table;
|
|
74
|
+
channel;
|
|
75
|
+
name;
|
|
76
|
+
inbound_bus;
|
|
77
|
+
running;
|
|
78
|
+
client;
|
|
79
|
+
table_columns;
|
|
80
|
+
notification_handler;
|
|
81
|
+
constructor(table_url, channel, name) {
|
|
82
|
+
(0, import_optional_deps.assertOptionalDependencyAvailable)("PostgresEventBridge", "pg");
|
|
83
|
+
const parsed = parseTableUrl(table_url);
|
|
84
|
+
this.table_url = table_url;
|
|
85
|
+
this.dsn = parsed.dsn;
|
|
86
|
+
this.table = parsed.table;
|
|
87
|
+
const derived_channel = channel ?? DEFAULT_POSTGRES_CHANNEL;
|
|
88
|
+
this.channel = validateIdentifier(derived_channel.slice(0, 63), "channel name");
|
|
89
|
+
this.name = name ?? `PostgresEventBridge_${randomSuffix()}`;
|
|
90
|
+
this.inbound_bus = new import_event_bus.EventBus(this.name, { max_history_size: 0 });
|
|
91
|
+
this.running = false;
|
|
92
|
+
this.client = null;
|
|
93
|
+
this.table_columns = /* @__PURE__ */ new Set(["event_id", "event_created_at", "event_type", EVENT_PAYLOAD_COLUMN]);
|
|
94
|
+
this.notification_handler = null;
|
|
95
|
+
this.dispatch = this.dispatch.bind(this);
|
|
96
|
+
this.emit = this.emit.bind(this);
|
|
97
|
+
this.on = this.on.bind(this);
|
|
98
|
+
}
|
|
99
|
+
on(event_pattern, handler) {
|
|
100
|
+
this.ensureStarted();
|
|
101
|
+
if (typeof event_pattern === "string") {
|
|
102
|
+
this.inbound_bus.on(event_pattern, handler);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.inbound_bus.on(event_pattern, handler);
|
|
106
|
+
}
|
|
107
|
+
async emit(event) {
|
|
108
|
+
this.ensureStarted();
|
|
109
|
+
if (!this.client) await this.start();
|
|
110
|
+
const payload = event.toJSON();
|
|
111
|
+
const { event_fields, event_payload } = splitBridgePayload(payload);
|
|
112
|
+
const write_payload = { ...event_fields, [EVENT_PAYLOAD_COLUMN]: event_payload };
|
|
113
|
+
const keys = Object.keys(write_payload).sort();
|
|
114
|
+
await this.ensureColumns(keys);
|
|
115
|
+
const columns_sql = keys.map((key) => `"${key}"`).join(", ");
|
|
116
|
+
const placeholders_sql = keys.map((_, index) => `$${index + 1}`).join(", ");
|
|
117
|
+
const values = keys.map(
|
|
118
|
+
(key) => write_payload[key] === null || write_payload[key] === void 0 ? null : JSON.stringify(write_payload[key])
|
|
119
|
+
);
|
|
120
|
+
const update_fields = keys.filter((key) => key !== "event_id");
|
|
121
|
+
let upsert_sql = `INSERT INTO "${this.table}" (${columns_sql}) VALUES (${placeholders_sql})`;
|
|
122
|
+
if (update_fields.length > 0) {
|
|
123
|
+
const updates_sql = update_fields.map((key) => `"${key}" = EXCLUDED."${key}"`).join(", ");
|
|
124
|
+
upsert_sql += ` ON CONFLICT ("event_id") DO UPDATE SET ${updates_sql}`;
|
|
125
|
+
} else {
|
|
126
|
+
upsert_sql += ' ON CONFLICT ("event_id") DO NOTHING';
|
|
127
|
+
}
|
|
128
|
+
await this.client.query(upsert_sql, values);
|
|
129
|
+
await this.client.query("SELECT pg_notify($1, $2)", [this.channel, JSON.stringify(String(event.event_id))]);
|
|
130
|
+
}
|
|
131
|
+
async dispatch(event) {
|
|
132
|
+
return this.emit(event);
|
|
133
|
+
}
|
|
134
|
+
async start() {
|
|
135
|
+
if (this.running) return;
|
|
136
|
+
if (!(0, import_optional_deps.isNodeRuntime)()) {
|
|
137
|
+
throw new Error("PostgresEventBridge is only supported in Node.js runtimes");
|
|
138
|
+
}
|
|
139
|
+
const mod = await (0, import_optional_deps.importOptionalDependency)("PostgresEventBridge", "pg");
|
|
140
|
+
const Client = mod.Client ?? mod.default?.Client;
|
|
141
|
+
this.client = new Client({ connectionString: this.dsn });
|
|
142
|
+
this.client.on("error", () => {
|
|
143
|
+
});
|
|
144
|
+
await this.client.connect();
|
|
145
|
+
await this.ensureTableExists();
|
|
146
|
+
await this.refreshColumnCache();
|
|
147
|
+
await this.ensureColumns(["event_id", "event_created_at", "event_type", EVENT_PAYLOAD_COLUMN]);
|
|
148
|
+
await this.ensureBaseIndexes();
|
|
149
|
+
this.notification_handler = (msg) => {
|
|
150
|
+
if (msg.channel !== this.channel || !msg.payload) return;
|
|
151
|
+
void this.dispatchByEventId(msg.payload).catch(() => {
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
this.client.on("notification", this.notification_handler);
|
|
155
|
+
await this.client.query(`LISTEN ${this.channel}`);
|
|
156
|
+
this.running = true;
|
|
157
|
+
}
|
|
158
|
+
async close() {
|
|
159
|
+
this.running = false;
|
|
160
|
+
if (this.client) {
|
|
161
|
+
try {
|
|
162
|
+
await this.client.query(`UNLISTEN ${this.channel}`);
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
if (this.notification_handler) {
|
|
166
|
+
this.client.off("notification", this.notification_handler);
|
|
167
|
+
this.notification_handler = null;
|
|
168
|
+
}
|
|
169
|
+
await this.client.end();
|
|
170
|
+
this.client = null;
|
|
171
|
+
}
|
|
172
|
+
this.inbound_bus.destroy();
|
|
173
|
+
}
|
|
174
|
+
ensureStarted() {
|
|
175
|
+
if (this.running) return;
|
|
176
|
+
void this.start().catch((error) => {
|
|
177
|
+
console.error("[abxbus] PostgresEventBridge failed to start", error);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async dispatchByEventId(event_id) {
|
|
181
|
+
if (!this.running || !this.client) return;
|
|
182
|
+
const result = await this.client.query(`SELECT * FROM "${this.table}" WHERE "event_id" = $1`, [event_id]);
|
|
183
|
+
const row = result.rows?.[0];
|
|
184
|
+
if (!row) return;
|
|
185
|
+
const payload = {};
|
|
186
|
+
const raw_event_payload = row[EVENT_PAYLOAD_COLUMN];
|
|
187
|
+
if (typeof raw_event_payload === "string") {
|
|
188
|
+
try {
|
|
189
|
+
const decoded_event_payload = JSON.parse(raw_event_payload);
|
|
190
|
+
if (decoded_event_payload && typeof decoded_event_payload === "object" && !Array.isArray(decoded_event_payload)) {
|
|
191
|
+
Object.assign(payload, decoded_event_payload);
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
for (const [key, raw_value] of Object.entries(row)) {
|
|
197
|
+
if (key === EVENT_PAYLOAD_COLUMN || !key.startsWith("event_")) continue;
|
|
198
|
+
if (raw_value === null || raw_value === void 0) continue;
|
|
199
|
+
if (typeof raw_value !== "string") {
|
|
200
|
+
payload[key] = raw_value;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
payload[key] = JSON.parse(raw_value);
|
|
205
|
+
} catch {
|
|
206
|
+
payload[key] = raw_value;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
await this.dispatchInboundPayload(payload);
|
|
210
|
+
}
|
|
211
|
+
async dispatchInboundPayload(payload) {
|
|
212
|
+
const event = import_base_event.BaseEvent.fromJSON(payload).eventReset();
|
|
213
|
+
this.inbound_bus.emit(event);
|
|
214
|
+
}
|
|
215
|
+
async ensureTableExists() {
|
|
216
|
+
if (!this.client) return;
|
|
217
|
+
await this.client.query(
|
|
218
|
+
`CREATE TABLE IF NOT EXISTS "${this.table}" ("event_id" TEXT PRIMARY KEY, "event_created_at" TEXT, "event_type" TEXT, "event_payload" TEXT)`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
async ensureBaseIndexes() {
|
|
222
|
+
if (!this.client) return;
|
|
223
|
+
const event_created_at_idx = indexName(this.table, "event_created_at_idx");
|
|
224
|
+
const event_type_idx = indexName(this.table, "event_type_idx");
|
|
225
|
+
await this.client.query(`CREATE INDEX IF NOT EXISTS "${event_created_at_idx}" ON "${this.table}" ("event_created_at")`);
|
|
226
|
+
await this.client.query(`CREATE INDEX IF NOT EXISTS "${event_type_idx}" ON "${this.table}" ("event_type")`);
|
|
227
|
+
}
|
|
228
|
+
async refreshColumnCache() {
|
|
229
|
+
if (!this.client) return;
|
|
230
|
+
const result = await this.client.query(
|
|
231
|
+
`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1`,
|
|
232
|
+
[this.table]
|
|
233
|
+
);
|
|
234
|
+
this.table_columns = new Set(result.rows.map((row) => row.column_name));
|
|
235
|
+
}
|
|
236
|
+
async ensureColumns(keys) {
|
|
237
|
+
if (!this.client) return;
|
|
238
|
+
for (const key of keys) {
|
|
239
|
+
validateIdentifier(key, "event field name");
|
|
240
|
+
if (key !== EVENT_PAYLOAD_COLUMN && !key.startsWith("event_")) {
|
|
241
|
+
throw new Error(`Invalid event field name for bridge column: ${JSON.stringify(key)}. Only event_* fields become columns`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const missing = keys.filter((key) => !this.table_columns.has(key));
|
|
245
|
+
for (const key of missing) {
|
|
246
|
+
await this.client.query(`ALTER TABLE "${this.table}" ADD COLUMN IF NOT EXISTS "${key}" TEXT`);
|
|
247
|
+
this.table_columns.add(key);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=bridge_postgres.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/bridge_postgres.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * PostgreSQL LISTEN/NOTIFY + flat-table bridge for forwarding events.\n */\nimport { BaseEvent } from './base_event.js'\nimport { EventBus } from './event_bus.js'\nimport { assertOptionalDependencyAvailable, importOptionalDependency, isNodeRuntime } from './optional_deps.js'\nimport type { EventClass, EventHandlerCallable, EventPattern, UntypedEventHandlerFunction } from './types.js'\n\nconst randomSuffix = (): string => Math.random().toString(36).slice(2, 10)\nconst IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/\nconst DEFAULT_POSTGRES_TABLE = 'abxbus_events'\nconst DEFAULT_POSTGRES_CHANNEL = 'abxbus_events'\nconst EVENT_PAYLOAD_COLUMN = 'event_payload'\n\nconst validateIdentifier = (value: string, label: string): string => {\n if (!IDENTIFIER_RE.test(value)) {\n throw new Error(`Invalid ${label}: ${JSON.stringify(value)}. Use only [A-Za-z0-9_] and start with a letter/_`)\n }\n return value\n}\n\nconst indexName = (table: string, suffix: string): string => validateIdentifier(`${table}_${suffix}`.slice(0, 63), 'index name')\n\nconst parseTableUrl = (table_url: string): { dsn: string; table: string } => {\n let parsed: URL\n try {\n parsed = new URL(table_url)\n } catch {\n throw new Error(\n 'PostgresEventBridge URL must include at least database in path, e.g. postgresql://user:pass@host:5432/dbname[/tablename]'\n )\n }\n\n const segments = parsed.pathname.split('/').filter(Boolean)\n if (segments.length < 1) {\n throw new Error(\n 'PostgresEventBridge URL must include at least database in path, e.g. postgresql://user:pass@host:5432/dbname[/tablename]'\n )\n }\n\n const db_name = segments[0]\n const table = segments.length >= 2 ? validateIdentifier(segments[1], 'table name') : DEFAULT_POSTGRES_TABLE\n const dsn_url = new URL(parsed.toString())\n dsn_url.pathname = `/${db_name}`\n return { dsn: dsn_url.toString(), table }\n}\n\nconst splitBridgePayload = (\n payload: Record<string, unknown>\n): { event_fields: Record<string, unknown>; event_payload: Record<string, unknown> } => {\n const event_fields: Record<string, unknown> = {}\n const event_payload: Record<string, unknown> = { ...payload }\n for (const [key, value] of Object.entries(payload)) {\n if (key.startsWith('event_')) {\n event_fields[key] = value\n }\n }\n return { event_fields, event_payload }\n}\n\nexport class PostgresEventBridge {\n readonly table_url: string\n readonly dsn: string\n readonly table: string\n readonly channel: string\n readonly name: string\n\n private readonly inbound_bus: EventBus\n private running: boolean\n private client: any | null\n private table_columns: Set<string>\n private notification_handler: ((msg: { channel: string; payload?: string }) => void) | null\n\n constructor(table_url: string, channel?: string, name?: string) {\n assertOptionalDependencyAvailable('PostgresEventBridge', 'pg')\n\n const parsed = parseTableUrl(table_url)\n this.table_url = table_url\n this.dsn = parsed.dsn\n this.table = parsed.table\n\n const derived_channel = channel ?? DEFAULT_POSTGRES_CHANNEL\n this.channel = validateIdentifier(derived_channel.slice(0, 63), 'channel name')\n this.name = name ?? `PostgresEventBridge_${randomSuffix()}`\n\n this.inbound_bus = new EventBus(this.name, { max_history_size: 0 })\n this.running = false\n this.client = null\n this.table_columns = new Set(['event_id', 'event_created_at', 'event_type', EVENT_PAYLOAD_COLUMN])\n this.notification_handler = null\n\n this.dispatch = this.dispatch.bind(this)\n this.emit = this.emit.bind(this)\n this.on = this.on.bind(this)\n }\n\n on<T extends BaseEvent>(event_pattern: EventClass<T>, handler: EventHandlerCallable<T>): void\n on<T extends BaseEvent>(event_pattern: string | '*', handler: UntypedEventHandlerFunction<T>): void\n on(event_pattern: EventPattern | '*', handler: EventHandlerCallable | UntypedEventHandlerFunction): void {\n this.ensureStarted()\n if (typeof event_pattern === 'string') {\n this.inbound_bus.on(event_pattern, handler as UntypedEventHandlerFunction<BaseEvent>)\n return\n }\n this.inbound_bus.on(event_pattern as EventClass<BaseEvent>, handler as EventHandlerCallable<BaseEvent>)\n }\n\n async emit<T extends BaseEvent>(event: T): Promise<void> {\n this.ensureStarted()\n if (!this.client) await this.start()\n\n const payload = event.toJSON() as Record<string, unknown>\n const { event_fields, event_payload } = splitBridgePayload(payload)\n const write_payload: Record<string, unknown> = { ...event_fields, [EVENT_PAYLOAD_COLUMN]: event_payload }\n const keys = Object.keys(write_payload).sort()\n await this.ensureColumns(keys)\n\n const columns_sql = keys.map((key) => `\"${key}\"`).join(', ')\n const placeholders_sql = keys.map((_, index) => `$${index + 1}`).join(', ')\n const values = keys.map((key) =>\n write_payload[key] === null || write_payload[key] === undefined ? null : JSON.stringify(write_payload[key])\n )\n\n const update_fields = keys.filter((key) => key !== 'event_id')\n let upsert_sql = `INSERT INTO \"${this.table}\" (${columns_sql}) VALUES (${placeholders_sql})`\n if (update_fields.length > 0) {\n const updates_sql = update_fields.map((key) => `\"${key}\" = EXCLUDED.\"${key}\"`).join(', ')\n upsert_sql += ` ON CONFLICT (\"event_id\") DO UPDATE SET ${updates_sql}`\n } else {\n upsert_sql += ' ON CONFLICT (\"event_id\") DO NOTHING'\n }\n\n await this.client.query(upsert_sql, values)\n await this.client.query('SELECT pg_notify($1, $2)', [this.channel, JSON.stringify(String(event.event_id))])\n }\n\n async dispatch<T extends BaseEvent>(event: T): Promise<void> {\n return this.emit(event)\n }\n\n async start(): Promise<void> {\n if (this.running) return\n if (!isNodeRuntime()) {\n throw new Error('PostgresEventBridge is only supported in Node.js runtimes')\n }\n\n const mod = await importOptionalDependency('PostgresEventBridge', 'pg')\n const Client = mod.Client ?? mod.default?.Client\n this.client = new Client({ connectionString: this.dsn })\n this.client.on('error', () => {})\n await this.client.connect()\n\n await this.ensureTableExists()\n await this.refreshColumnCache()\n await this.ensureColumns(['event_id', 'event_created_at', 'event_type', EVENT_PAYLOAD_COLUMN])\n await this.ensureBaseIndexes()\n\n this.notification_handler = (msg: { channel: string; payload?: string }) => {\n if (msg.channel !== this.channel || !msg.payload) return\n void this.dispatchByEventId(msg.payload).catch(() => {\n // Ignore transient shutdown races while closing connections.\n })\n }\n\n this.client.on('notification', this.notification_handler)\n await this.client.query(`LISTEN ${this.channel}`)\n this.running = true\n }\n\n async close(): Promise<void> {\n this.running = false\n if (this.client) {\n try {\n await this.client.query(`UNLISTEN ${this.channel}`)\n } catch {\n // ignore\n }\n if (this.notification_handler) {\n this.client.off('notification', this.notification_handler)\n this.notification_handler = null\n }\n await this.client.end()\n this.client = null\n }\n this.inbound_bus.destroy()\n }\n\n private ensureStarted(): void {\n if (this.running) return\n void this.start().catch((error: unknown) => {\n console.error('[abxbus] PostgresEventBridge failed to start', error)\n })\n }\n\n private async dispatchByEventId(event_id: string): Promise<void> {\n if (!this.running || !this.client) return\n const result = await this.client.query(`SELECT * FROM \"${this.table}\" WHERE \"event_id\" = $1`, [event_id])\n const row = result.rows?.[0] as Record<string, unknown> | undefined\n if (!row) return\n\n const payload: Record<string, unknown> = {}\n const raw_event_payload = row[EVENT_PAYLOAD_COLUMN]\n if (typeof raw_event_payload === 'string') {\n try {\n const decoded_event_payload = JSON.parse(raw_event_payload)\n if (decoded_event_payload && typeof decoded_event_payload === 'object' && !Array.isArray(decoded_event_payload)) {\n Object.assign(payload, decoded_event_payload as Record<string, unknown>)\n }\n } catch {\n // ignore malformed payload column\n }\n }\n\n for (const [key, raw_value] of Object.entries(row)) {\n if (key === EVENT_PAYLOAD_COLUMN || !key.startsWith('event_')) continue\n if (raw_value === null || raw_value === undefined) continue\n if (typeof raw_value !== 'string') {\n payload[key] = raw_value\n continue\n }\n try {\n payload[key] = JSON.parse(raw_value)\n } catch {\n payload[key] = raw_value\n }\n }\n\n await this.dispatchInboundPayload(payload)\n }\n\n private async dispatchInboundPayload(payload: unknown): Promise<void> {\n const event = BaseEvent.fromJSON(payload).eventReset()\n this.inbound_bus.emit(event)\n }\n\n private async ensureTableExists(): Promise<void> {\n if (!this.client) return\n await this.client.query(\n `CREATE TABLE IF NOT EXISTS \"${this.table}\" (\"event_id\" TEXT PRIMARY KEY, \"event_created_at\" TEXT, \"event_type\" TEXT, \"event_payload\" TEXT)`\n )\n }\n\n private async ensureBaseIndexes(): Promise<void> {\n if (!this.client) return\n\n const event_created_at_idx = indexName(this.table, 'event_created_at_idx')\n const event_type_idx = indexName(this.table, 'event_type_idx')\n\n await this.client.query(`CREATE INDEX IF NOT EXISTS \"${event_created_at_idx}\" ON \"${this.table}\" (\"event_created_at\")`)\n await this.client.query(`CREATE INDEX IF NOT EXISTS \"${event_type_idx}\" ON \"${this.table}\" (\"event_type\")`)\n }\n\n private async refreshColumnCache(): Promise<void> {\n if (!this.client) return\n const result = await this.client.query(\n `SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1`,\n [this.table]\n )\n this.table_columns = new Set((result.rows as Array<{ column_name: string }>).map((row) => row.column_name))\n }\n\n private async ensureColumns(keys: string[]): Promise<void> {\n if (!this.client) return\n for (const key of keys) {\n validateIdentifier(key, 'event field name')\n if (key !== EVENT_PAYLOAD_COLUMN && !key.startsWith('event_')) {\n throw new Error(`Invalid event field name for bridge column: ${JSON.stringify(key)}. Only event_* fields become columns`)\n }\n }\n\n const missing = keys.filter((key) => !this.table_columns.has(key))\n for (const key of missing) {\n await this.client.query(`ALTER TABLE \"${this.table}\" ADD COLUMN IF NOT EXISTS \"${key}\" TEXT`)\n this.table_columns.add(key)\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis pub/sub bridge for forwarding events between runtimes.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* // channel from URL path
|
|
6
|
+
* const bridge = new RedisEventBridge('redis://user:pass@localhost:6379/1/my_channel')
|
|
7
|
+
*
|
|
8
|
+
* // explicit channel override
|
|
9
|
+
* const bridge2 = new RedisEventBridge('redis://user:pass@localhost:6379/1', 'my_channel')
|
|
10
|
+
*
|
|
11
|
+
* URL format:
|
|
12
|
+
* redis://user:pass@host:6379/<db>/<optional_channel>
|
|
13
|
+
*/
|
|
14
|
+
import { BaseEvent } from './base_event.js';
|
|
15
|
+
import type { EventClass, EventHandlerCallable, UntypedEventHandlerFunction } from './types.js';
|
|
16
|
+
export declare class RedisEventBridge {
|
|
17
|
+
readonly url: string;
|
|
18
|
+
readonly channel: string;
|
|
19
|
+
readonly name: string;
|
|
20
|
+
private readonly inbound_bus;
|
|
21
|
+
private running;
|
|
22
|
+
private start_promise;
|
|
23
|
+
private redis_pub;
|
|
24
|
+
private redis_sub;
|
|
25
|
+
constructor(redis_url: string, channel?: string, name?: string);
|
|
26
|
+
on<T extends BaseEvent>(event_pattern: EventClass<T>, handler: EventHandlerCallable<T>): void;
|
|
27
|
+
on<T extends BaseEvent>(event_pattern: string | '*', handler: UntypedEventHandlerFunction<T>): void;
|
|
28
|
+
emit<T extends BaseEvent>(event: T): Promise<void>;
|
|
29
|
+
dispatch<T extends BaseEvent>(event: T): Promise<void>;
|
|
30
|
+
start(): Promise<void>;
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
private ensureStarted;
|
|
33
|
+
private dispatchInboundPayload;
|
|
34
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
var bridge_redis_exports = {};
|
|
20
|
+
__export(bridge_redis_exports, {
|
|
21
|
+
RedisEventBridge: () => RedisEventBridge
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(bridge_redis_exports);
|
|
24
|
+
var import_base_event = require("./base_event.js");
|
|
25
|
+
var import_event_bus = require("./event_bus.js");
|
|
26
|
+
var import_optional_deps = require("./optional_deps.js");
|
|
27
|
+
const randomSuffix = () => Math.random().toString(36).slice(2, 10);
|
|
28
|
+
const DEFAULT_REDIS_CHANNEL = "abxbus_events";
|
|
29
|
+
const DB_INIT_KEY = "__abxbus:bridge_init__";
|
|
30
|
+
const parseRedisUrl = (redis_url, channel) => {
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = new URL(redis_url);
|
|
34
|
+
} catch {
|
|
35
|
+
throw new Error(`RedisEventBridge URL must be a valid redis:// or rediss:// URL, got: ${redis_url}`);
|
|
36
|
+
}
|
|
37
|
+
const protocol = parsed.protocol.replace(/:$/, "").toLowerCase();
|
|
38
|
+
if (protocol !== "redis" && protocol !== "rediss") {
|
|
39
|
+
throw new Error(`RedisEventBridge URL must use redis:// or rediss://, got: ${redis_url}`);
|
|
40
|
+
}
|
|
41
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
42
|
+
if (segments.length > 2) {
|
|
43
|
+
throw new Error(`RedisEventBridge URL path must be /<db> or /<db>/<channel>, got: ${parsed.pathname || "/"}`);
|
|
44
|
+
}
|
|
45
|
+
let db_index = "0";
|
|
46
|
+
let channel_from_url;
|
|
47
|
+
if (segments.length > 0) {
|
|
48
|
+
db_index = segments[0];
|
|
49
|
+
if (!/^\d+$/.test(db_index)) {
|
|
50
|
+
throw new Error(`RedisEventBridge URL db path segment must be numeric, got: ${JSON.stringify(db_index)} in ${redis_url}`);
|
|
51
|
+
}
|
|
52
|
+
if (segments.length === 2) {
|
|
53
|
+
channel_from_url = segments[1];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const resolved_channel = channel ?? channel_from_url ?? DEFAULT_REDIS_CHANNEL;
|
|
57
|
+
if (!resolved_channel) {
|
|
58
|
+
throw new Error("RedisEventBridge channel must not be empty");
|
|
59
|
+
}
|
|
60
|
+
const normalized = new URL(parsed.toString());
|
|
61
|
+
normalized.pathname = `/${db_index}`;
|
|
62
|
+
return { url: normalized.toString(), channel: resolved_channel };
|
|
63
|
+
};
|
|
64
|
+
class RedisEventBridge {
|
|
65
|
+
url;
|
|
66
|
+
channel;
|
|
67
|
+
name;
|
|
68
|
+
inbound_bus;
|
|
69
|
+
running;
|
|
70
|
+
start_promise;
|
|
71
|
+
redis_pub;
|
|
72
|
+
redis_sub;
|
|
73
|
+
constructor(redis_url, channel, name) {
|
|
74
|
+
(0, import_optional_deps.assertOptionalDependencyAvailable)("RedisEventBridge", "ioredis");
|
|
75
|
+
const parsed = parseRedisUrl(redis_url, channel);
|
|
76
|
+
this.url = parsed.url;
|
|
77
|
+
this.channel = parsed.channel;
|
|
78
|
+
this.name = name ?? `RedisEventBridge_${randomSuffix()}`;
|
|
79
|
+
this.inbound_bus = new import_event_bus.EventBus(this.name, { max_history_size: 0 });
|
|
80
|
+
this.running = false;
|
|
81
|
+
this.start_promise = null;
|
|
82
|
+
this.redis_pub = null;
|
|
83
|
+
this.redis_sub = null;
|
|
84
|
+
this.dispatch = this.dispatch.bind(this);
|
|
85
|
+
this.emit = this.emit.bind(this);
|
|
86
|
+
this.on = this.on.bind(this);
|
|
87
|
+
}
|
|
88
|
+
on(event_pattern, handler) {
|
|
89
|
+
this.ensureStarted();
|
|
90
|
+
if (typeof event_pattern === "string") {
|
|
91
|
+
this.inbound_bus.on(event_pattern, handler);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.inbound_bus.on(event_pattern, handler);
|
|
95
|
+
}
|
|
96
|
+
async emit(event) {
|
|
97
|
+
this.ensureStarted();
|
|
98
|
+
if (!this.redis_pub) await this.start();
|
|
99
|
+
const payload = JSON.stringify(event.toJSON());
|
|
100
|
+
await this.redis_pub.publish(this.channel, payload);
|
|
101
|
+
}
|
|
102
|
+
async dispatch(event) {
|
|
103
|
+
return this.emit(event);
|
|
104
|
+
}
|
|
105
|
+
async start() {
|
|
106
|
+
if (this.running) return;
|
|
107
|
+
if (this.start_promise) {
|
|
108
|
+
await this.start_promise;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.start_promise = (async () => {
|
|
112
|
+
if (!(0, import_optional_deps.isNodeRuntime)()) {
|
|
113
|
+
throw new Error("RedisEventBridge is only supported in Node.js runtimes");
|
|
114
|
+
}
|
|
115
|
+
const mod = await (0, import_optional_deps.importOptionalDependency)("RedisEventBridge", "ioredis");
|
|
116
|
+
const Redis = mod.default ?? mod.Redis ?? mod;
|
|
117
|
+
const redis_pub = new Redis(this.url);
|
|
118
|
+
const redis_sub = new Redis(this.url);
|
|
119
|
+
redis_pub.on("error", () => {
|
|
120
|
+
});
|
|
121
|
+
redis_sub.on("error", () => {
|
|
122
|
+
});
|
|
123
|
+
await redis_pub.set(DB_INIT_KEY, "1", "EX", 60, "NX");
|
|
124
|
+
redis_sub.on("message", (channel_name, message) => {
|
|
125
|
+
if (channel_name !== this.channel) return;
|
|
126
|
+
try {
|
|
127
|
+
const payload = JSON.parse(message);
|
|
128
|
+
void this.dispatchInboundPayload(payload);
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
await redis_sub.subscribe(this.channel);
|
|
133
|
+
this.redis_pub = redis_pub;
|
|
134
|
+
this.redis_sub = redis_sub;
|
|
135
|
+
this.running = true;
|
|
136
|
+
})();
|
|
137
|
+
try {
|
|
138
|
+
await this.start_promise;
|
|
139
|
+
} finally {
|
|
140
|
+
this.start_promise = null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async close() {
|
|
144
|
+
if (this.start_promise) {
|
|
145
|
+
await this.start_promise.catch(() => {
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
this.running = false;
|
|
149
|
+
if (this.redis_sub) {
|
|
150
|
+
try {
|
|
151
|
+
await this.redis_sub.unsubscribe(this.channel);
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
await this.redis_sub.quit();
|
|
155
|
+
this.redis_sub = null;
|
|
156
|
+
}
|
|
157
|
+
if (this.redis_pub) {
|
|
158
|
+
await this.redis_pub.quit();
|
|
159
|
+
this.redis_pub = null;
|
|
160
|
+
}
|
|
161
|
+
this.inbound_bus.destroy();
|
|
162
|
+
}
|
|
163
|
+
ensureStarted() {
|
|
164
|
+
if (this.running) return;
|
|
165
|
+
if (this.start_promise) return;
|
|
166
|
+
void this.start().catch((error) => {
|
|
167
|
+
console.error("[abxbus] RedisEventBridge failed to start", error);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async dispatchInboundPayload(payload) {
|
|
171
|
+
const event = import_base_event.BaseEvent.fromJSON(payload).eventReset();
|
|
172
|
+
this.inbound_bus.emit(event);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=bridge_redis.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/bridge_redis.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Redis pub/sub bridge for forwarding events between runtimes.\n *\n * Usage:\n * // channel from URL path\n * const bridge = new RedisEventBridge('redis://user:pass@localhost:6379/1/my_channel')\n *\n * // explicit channel override\n * const bridge2 = new RedisEventBridge('redis://user:pass@localhost:6379/1', 'my_channel')\n *\n * URL format:\n * redis://user:pass@host:6379/<db>/<optional_channel>\n */\nimport { BaseEvent } from './base_event.js'\nimport { EventBus } from './event_bus.js'\nimport { assertOptionalDependencyAvailable, importOptionalDependency, isNodeRuntime } from './optional_deps.js'\nimport type { EventClass, EventHandlerCallable, EventPattern, UntypedEventHandlerFunction } from './types.js'\n\nconst randomSuffix = (): string => Math.random().toString(36).slice(2, 10)\nconst DEFAULT_REDIS_CHANNEL = 'abxbus_events'\nconst DB_INIT_KEY = '__abxbus:bridge_init__'\n\nconst parseRedisUrl = (redis_url: string, channel?: string): { url: string; channel: string } => {\n let parsed: URL\n try {\n parsed = new URL(redis_url)\n } catch {\n throw new Error(`RedisEventBridge URL must be a valid redis:// or rediss:// URL, got: ${redis_url}`)\n }\n\n const protocol = parsed.protocol.replace(/:$/, '').toLowerCase()\n if (protocol !== 'redis' && protocol !== 'rediss') {\n throw new Error(`RedisEventBridge URL must use redis:// or rediss://, got: ${redis_url}`)\n }\n\n const segments = parsed.pathname.split('/').filter(Boolean)\n if (segments.length > 2) {\n throw new Error(`RedisEventBridge URL path must be /<db> or /<db>/<channel>, got: ${parsed.pathname || '/'}`)\n }\n\n let db_index = '0'\n let channel_from_url: string | undefined\n\n if (segments.length > 0) {\n db_index = segments[0]\n if (!/^\\d+$/.test(db_index)) {\n throw new Error(`RedisEventBridge URL db path segment must be numeric, got: ${JSON.stringify(db_index)} in ${redis_url}`)\n }\n if (segments.length === 2) {\n channel_from_url = segments[1]\n }\n }\n\n const resolved_channel = channel ?? channel_from_url ?? DEFAULT_REDIS_CHANNEL\n if (!resolved_channel) {\n throw new Error('RedisEventBridge channel must not be empty')\n }\n\n const normalized = new URL(parsed.toString())\n normalized.pathname = `/${db_index}`\n return { url: normalized.toString(), channel: resolved_channel }\n}\n\nexport class RedisEventBridge {\n readonly url: string\n readonly channel: string\n readonly name: string\n\n private readonly inbound_bus: EventBus\n private running: boolean\n private start_promise: Promise<void> | null\n private redis_pub: any | null\n private redis_sub: any | null\n\n constructor(redis_url: string, channel?: string, name?: string) {\n assertOptionalDependencyAvailable('RedisEventBridge', 'ioredis')\n\n const parsed = parseRedisUrl(redis_url, channel)\n this.url = parsed.url\n this.channel = parsed.channel\n this.name = name ?? `RedisEventBridge_${randomSuffix()}`\n this.inbound_bus = new EventBus(this.name, { max_history_size: 0 })\n this.running = false\n this.start_promise = null\n this.redis_pub = null\n this.redis_sub = null\n\n this.dispatch = this.dispatch.bind(this)\n this.emit = this.emit.bind(this)\n this.on = this.on.bind(this)\n }\n\n on<T extends BaseEvent>(event_pattern: EventClass<T>, handler: EventHandlerCallable<T>): void\n on<T extends BaseEvent>(event_pattern: string | '*', handler: UntypedEventHandlerFunction<T>): void\n on(event_pattern: EventPattern | '*', handler: EventHandlerCallable | UntypedEventHandlerFunction): void {\n this.ensureStarted()\n if (typeof event_pattern === 'string') {\n this.inbound_bus.on(event_pattern, handler as UntypedEventHandlerFunction<BaseEvent>)\n return\n }\n this.inbound_bus.on(event_pattern as EventClass<BaseEvent>, handler as EventHandlerCallable<BaseEvent>)\n }\n\n async emit<T extends BaseEvent>(event: T): Promise<void> {\n this.ensureStarted()\n if (!this.redis_pub) await this.start()\n const payload = JSON.stringify(event.toJSON())\n await this.redis_pub.publish(this.channel, payload)\n }\n\n async dispatch<T extends BaseEvent>(event: T): Promise<void> {\n return this.emit(event)\n }\n\n async start(): Promise<void> {\n if (this.running) return\n if (this.start_promise) {\n await this.start_promise\n return\n }\n\n // `on(...)` auto-start and explicit `await start()` can happen back-to-back; use one in-flight\n // startup promise so we do not leak extra Redis clients.\n this.start_promise = (async () => {\n if (!isNodeRuntime()) {\n throw new Error('RedisEventBridge is only supported in Node.js runtimes')\n }\n\n const mod = await importOptionalDependency('RedisEventBridge', 'ioredis')\n const Redis = mod.default ?? mod.Redis ?? mod\n const redis_pub = new Redis(this.url)\n const redis_sub = new Redis(this.url)\n\n redis_pub.on('error', () => {})\n redis_sub.on('error', () => {})\n\n // Redis logical DBs are created lazily; writing a short-lived key initializes/validates the selected DB.\n await redis_pub.set(DB_INIT_KEY, '1', 'EX', 60, 'NX')\n redis_sub.on('message', (channel_name: string, message: string) => {\n if (channel_name !== this.channel) return\n try {\n const payload = JSON.parse(message)\n void this.dispatchInboundPayload(payload)\n } catch {\n // Ignore malformed payloads.\n }\n })\n await redis_sub.subscribe(this.channel)\n this.redis_pub = redis_pub\n this.redis_sub = redis_sub\n this.running = true\n })()\n\n try {\n await this.start_promise\n } finally {\n this.start_promise = null\n }\n }\n\n async close(): Promise<void> {\n if (this.start_promise) {\n await this.start_promise.catch(() => {})\n }\n this.running = false\n if (this.redis_sub) {\n try {\n await this.redis_sub.unsubscribe(this.channel)\n } catch {\n // ignore\n }\n await this.redis_sub.quit()\n this.redis_sub = null\n }\n if (this.redis_pub) {\n await this.redis_pub.quit()\n this.redis_pub = null\n }\n this.inbound_bus.destroy()\n }\n\n private ensureStarted(): void {\n if (this.running) return\n if (this.start_promise) return\n void this.start().catch((error: unknown) => {\n console.error('[abxbus] RedisEventBridge failed to start', error)\n })\n }\n\n private async dispatchInboundPayload(payload: unknown): Promise<void> {\n const event = BaseEvent.fromJSON(payload).eventReset()\n this.inbound_bus.emit(event)\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BaseEvent } from './base_event.js';
|
|
2
|
+
import type { EventClass, EventHandlerCallable, UntypedEventHandlerFunction } from './types.js';
|
|
3
|
+
export declare class SQLiteEventBridge {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly table: string;
|
|
6
|
+
readonly poll_interval: number;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
private readonly inbound_bus;
|
|
9
|
+
private running;
|
|
10
|
+
private last_seen_event_created_at;
|
|
11
|
+
private last_seen_event_id;
|
|
12
|
+
private listener_task;
|
|
13
|
+
private start_task;
|
|
14
|
+
private db;
|
|
15
|
+
private table_columns;
|
|
16
|
+
constructor(path: string, table?: string, poll_interval?: number, name?: string);
|
|
17
|
+
on<T extends BaseEvent>(event_pattern: EventClass<T>, handler: EventHandlerCallable<T>): void;
|
|
18
|
+
on<T extends BaseEvent>(event_pattern: string | '*', handler: UntypedEventHandlerFunction<T>): void;
|
|
19
|
+
emit<T extends BaseEvent>(event: T): Promise<void>;
|
|
20
|
+
dispatch<T extends BaseEvent>(event: T): Promise<void>;
|
|
21
|
+
start(): Promise<void>;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
private ensureStarted;
|
|
24
|
+
private listenLoop;
|
|
25
|
+
private dispatchInboundPayload;
|
|
26
|
+
private refreshColumnCache;
|
|
27
|
+
private ensureColumns;
|
|
28
|
+
private ensureBaseIndexes;
|
|
29
|
+
private setCursorToLatestRow;
|
|
30
|
+
}
|