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,235 @@
|
|
|
1
|
+
import { BaseEvent } from "./base_event.js";
|
|
2
|
+
import { EventBus } from "./event_bus.js";
|
|
3
|
+
import { isNodeRuntime } from "./optional_deps.js";
|
|
4
|
+
const randomSuffix = () => Math.random().toString(36).slice(2, 10);
|
|
5
|
+
const IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
6
|
+
const EVENT_PAYLOAD_COLUMN = "event_payload";
|
|
7
|
+
const validateIdentifier = (value, label) => {
|
|
8
|
+
if (!IDENTIFIER_RE.test(value)) {
|
|
9
|
+
throw new Error(`Invalid ${label}: ${JSON.stringify(value)}. Use only [A-Za-z0-9_] and start with a letter/_`);
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
};
|
|
13
|
+
const loadNodeSqlite = async () => {
|
|
14
|
+
const dynamic_import = Function("module_name", "return import(module_name)");
|
|
15
|
+
try {
|
|
16
|
+
return await dynamic_import("node:sqlite");
|
|
17
|
+
} catch {
|
|
18
|
+
throw new Error('SQLiteEventBridge requires Node.js with built-in "node:sqlite" support (Node 22+).');
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const splitBridgePayload = (payload) => {
|
|
22
|
+
const event_fields = {};
|
|
23
|
+
const event_payload = { ...payload };
|
|
24
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
25
|
+
if (key.startsWith("event_")) {
|
|
26
|
+
event_fields[key] = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { event_fields, event_payload };
|
|
30
|
+
};
|
|
31
|
+
class SQLiteEventBridge {
|
|
32
|
+
path;
|
|
33
|
+
table;
|
|
34
|
+
poll_interval;
|
|
35
|
+
name;
|
|
36
|
+
inbound_bus;
|
|
37
|
+
running;
|
|
38
|
+
last_seen_event_created_at;
|
|
39
|
+
last_seen_event_id;
|
|
40
|
+
listener_task;
|
|
41
|
+
start_task;
|
|
42
|
+
db;
|
|
43
|
+
table_columns;
|
|
44
|
+
constructor(path, table = "abxbus_events", poll_interval = 0.25, name) {
|
|
45
|
+
this.path = path;
|
|
46
|
+
this.table = validateIdentifier(table, "table name");
|
|
47
|
+
this.poll_interval = poll_interval;
|
|
48
|
+
this.name = name ?? `SQLiteEventBridge_${randomSuffix()}`;
|
|
49
|
+
this.inbound_bus = new EventBus(this.name, { max_history_size: 0 });
|
|
50
|
+
this.running = false;
|
|
51
|
+
this.last_seen_event_created_at = "";
|
|
52
|
+
this.last_seen_event_id = "";
|
|
53
|
+
this.listener_task = null;
|
|
54
|
+
this.start_task = null;
|
|
55
|
+
this.db = null;
|
|
56
|
+
this.table_columns = /* @__PURE__ */ new Set(["event_id", "event_created_at", "event_type", EVENT_PAYLOAD_COLUMN]);
|
|
57
|
+
this.dispatch = this.dispatch.bind(this);
|
|
58
|
+
this.emit = this.emit.bind(this);
|
|
59
|
+
this.on = this.on.bind(this);
|
|
60
|
+
}
|
|
61
|
+
on(event_pattern, handler) {
|
|
62
|
+
this.ensureStarted();
|
|
63
|
+
if (typeof event_pattern === "string") {
|
|
64
|
+
this.inbound_bus.on(event_pattern, handler);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.inbound_bus.on(event_pattern, handler);
|
|
68
|
+
}
|
|
69
|
+
async emit(event) {
|
|
70
|
+
this.ensureStarted();
|
|
71
|
+
if (!this.running) {
|
|
72
|
+
await this.start();
|
|
73
|
+
}
|
|
74
|
+
if (!this.db) {
|
|
75
|
+
throw new Error("SQLiteEventBridge database not initialized");
|
|
76
|
+
}
|
|
77
|
+
const payload = event.toJSON();
|
|
78
|
+
const { event_fields, event_payload } = splitBridgePayload(payload);
|
|
79
|
+
const write_payload = { ...event_fields, [EVENT_PAYLOAD_COLUMN]: event_payload };
|
|
80
|
+
const payload_keys = Object.keys(write_payload).sort();
|
|
81
|
+
this.ensureColumns(payload_keys);
|
|
82
|
+
const columns_sql = payload_keys.map((key) => `"${key}"`).join(", ");
|
|
83
|
+
const placeholders_sql = payload_keys.map((key) => key === EVENT_PAYLOAD_COLUMN ? "json(?)" : "?").join(", ");
|
|
84
|
+
const values = payload_keys.map(
|
|
85
|
+
(key) => write_payload[key] === null || write_payload[key] === void 0 ? null : JSON.stringify(write_payload[key])
|
|
86
|
+
);
|
|
87
|
+
const update_fields = payload_keys.filter((key) => key !== "event_id");
|
|
88
|
+
let upsert_sql = `INSERT INTO "${this.table}" (${columns_sql}) VALUES (${placeholders_sql})`;
|
|
89
|
+
if (update_fields.length > 0) {
|
|
90
|
+
const updates_sql = update_fields.map((key) => `"${key}" = excluded."${key}"`).join(", ");
|
|
91
|
+
upsert_sql += ` ON CONFLICT("event_id") DO UPDATE SET ${updates_sql}`;
|
|
92
|
+
} else {
|
|
93
|
+
upsert_sql += ' ON CONFLICT("event_id") DO NOTHING';
|
|
94
|
+
}
|
|
95
|
+
this.db.prepare(upsert_sql).run(...values);
|
|
96
|
+
}
|
|
97
|
+
async dispatch(event) {
|
|
98
|
+
return this.emit(event);
|
|
99
|
+
}
|
|
100
|
+
async start() {
|
|
101
|
+
if (this.running) return;
|
|
102
|
+
if (this.start_task) {
|
|
103
|
+
await this.start_task;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.start_task = (async () => {
|
|
107
|
+
if (!isNodeRuntime()) {
|
|
108
|
+
throw new Error("SQLiteEventBridge is only supported in Node.js runtimes");
|
|
109
|
+
}
|
|
110
|
+
const mod = await loadNodeSqlite();
|
|
111
|
+
const Database = mod.DatabaseSync ?? mod.default?.DatabaseSync;
|
|
112
|
+
if (typeof Database !== "function") {
|
|
113
|
+
throw new Error("SQLiteEventBridge could not load DatabaseSync from node:sqlite. Please use Node.js 22+.");
|
|
114
|
+
}
|
|
115
|
+
this.db = new Database(this.path);
|
|
116
|
+
this.db.exec("PRAGMA journal_mode = WAL");
|
|
117
|
+
this.db.prepare(
|
|
118
|
+
`CREATE TABLE IF NOT EXISTS "${this.table}" ("event_id" TEXT PRIMARY KEY, "event_created_at" TEXT, "event_type" TEXT, "event_payload" JSON)`
|
|
119
|
+
).run();
|
|
120
|
+
this.refreshColumnCache();
|
|
121
|
+
this.ensureColumns(["event_id", "event_created_at", "event_type", EVENT_PAYLOAD_COLUMN]);
|
|
122
|
+
this.ensureBaseIndexes();
|
|
123
|
+
this.setCursorToLatestRow();
|
|
124
|
+
this.running = true;
|
|
125
|
+
this.listener_task = this.listenLoop();
|
|
126
|
+
})();
|
|
127
|
+
try {
|
|
128
|
+
await this.start_task;
|
|
129
|
+
} finally {
|
|
130
|
+
this.start_task = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async close() {
|
|
134
|
+
await Promise.allSettled(this.start_task ? [this.start_task] : []);
|
|
135
|
+
this.running = false;
|
|
136
|
+
await Promise.allSettled(this.listener_task ? [this.listener_task] : []);
|
|
137
|
+
this.listener_task = null;
|
|
138
|
+
if (this.db) {
|
|
139
|
+
this.db.close();
|
|
140
|
+
this.db = null;
|
|
141
|
+
}
|
|
142
|
+
this.inbound_bus.destroy();
|
|
143
|
+
}
|
|
144
|
+
ensureStarted() {
|
|
145
|
+
if (this.running || this.listener_task || this.start_task) return;
|
|
146
|
+
void this.start().catch((error) => {
|
|
147
|
+
console.error("[abxbus] SQLiteEventBridge failed to start", error);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async listenLoop() {
|
|
151
|
+
while (this.running) {
|
|
152
|
+
try {
|
|
153
|
+
if (this.db) {
|
|
154
|
+
const rows = this.db.prepare(
|
|
155
|
+
`SELECT * FROM "${this.table}" WHERE COALESCE("event_created_at", '') > ? OR (COALESCE("event_created_at", '') = ? AND COALESCE("event_id", '') > ?) ORDER BY COALESCE("event_created_at", '') ASC, COALESCE("event_id", '') ASC`
|
|
156
|
+
).all(this.last_seen_event_created_at, this.last_seen_event_created_at, this.last_seen_event_id);
|
|
157
|
+
for (const row of rows) {
|
|
158
|
+
this.last_seen_event_created_at = String(row.event_created_at ?? "");
|
|
159
|
+
this.last_seen_event_id = String(row.event_id ?? "");
|
|
160
|
+
const raw_payload_blob = row[EVENT_PAYLOAD_COLUMN];
|
|
161
|
+
const payload = {};
|
|
162
|
+
if (typeof raw_payload_blob === "string") {
|
|
163
|
+
try {
|
|
164
|
+
const decoded_event_payload = JSON.parse(raw_payload_blob);
|
|
165
|
+
if (decoded_event_payload && typeof decoded_event_payload === "object" && !Array.isArray(decoded_event_payload)) {
|
|
166
|
+
Object.assign(payload, decoded_event_payload);
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
for (const [key, raw_value] of Object.entries(row)) {
|
|
172
|
+
if (key === EVENT_PAYLOAD_COLUMN || !key.startsWith("event_")) continue;
|
|
173
|
+
if (raw_value === null || raw_value === void 0) continue;
|
|
174
|
+
if (typeof raw_value !== "string") {
|
|
175
|
+
payload[key] = raw_value;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
payload[key] = JSON.parse(raw_value);
|
|
180
|
+
} catch {
|
|
181
|
+
payload[key] = raw_value;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
await this.dispatchInboundPayload(payload);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
await new Promise((resolve) => setTimeout(resolve, Math.max(1, this.poll_interval * 1e3)));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async dispatchInboundPayload(payload) {
|
|
193
|
+
const event = BaseEvent.fromJSON(payload).eventReset();
|
|
194
|
+
this.inbound_bus.emit(event);
|
|
195
|
+
}
|
|
196
|
+
refreshColumnCache() {
|
|
197
|
+
if (!this.db) return;
|
|
198
|
+
const rows = this.db.prepare(`PRAGMA table_info("${this.table}")`).all();
|
|
199
|
+
this.table_columns = new Set(rows.map((row) => String(row.name)));
|
|
200
|
+
}
|
|
201
|
+
ensureColumns(keys) {
|
|
202
|
+
if (!this.db) return;
|
|
203
|
+
for (const key of keys) {
|
|
204
|
+
validateIdentifier(key, "event field name");
|
|
205
|
+
if (key !== EVENT_PAYLOAD_COLUMN && !key.startsWith("event_")) {
|
|
206
|
+
throw new Error(`Invalid event field name for bridge column: ${JSON.stringify(key)}. Only event_* fields become columns`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const missing_columns = keys.filter((key) => !this.table_columns.has(key));
|
|
210
|
+
for (const key of missing_columns) {
|
|
211
|
+
const column_type = key === EVENT_PAYLOAD_COLUMN ? "JSON" : "TEXT";
|
|
212
|
+
this.db.prepare(`ALTER TABLE "${this.table}" ADD COLUMN "${key}" ${column_type}`).run();
|
|
213
|
+
this.table_columns.add(key);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
ensureBaseIndexes() {
|
|
217
|
+
if (!this.db) return;
|
|
218
|
+
const event_created_at_index = `${this.table}_event_created_at_idx`;
|
|
219
|
+
const event_type_index = `${this.table}_event_type_idx`;
|
|
220
|
+
this.db.prepare(`CREATE INDEX IF NOT EXISTS "${event_created_at_index}" ON "${this.table}" ("event_created_at")`).run();
|
|
221
|
+
this.db.prepare(`CREATE INDEX IF NOT EXISTS "${event_type_index}" ON "${this.table}" ("event_type")`).run();
|
|
222
|
+
}
|
|
223
|
+
setCursorToLatestRow() {
|
|
224
|
+
if (!this.db) return;
|
|
225
|
+
const row = this.db.prepare(
|
|
226
|
+
`SELECT COALESCE("event_created_at", '') AS event_created_at, COALESCE("event_id", '') AS event_id FROM "${this.table}" ORDER BY COALESCE("event_created_at", '') DESC, COALESCE("event_id", '') DESC LIMIT 1`
|
|
227
|
+
).get();
|
|
228
|
+
this.last_seen_event_created_at = String(row?.event_created_at ?? "");
|
|
229
|
+
this.last_seen_event_id = String(row?.event_id ?? "");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
export {
|
|
233
|
+
SQLiteEventBridge
|
|
234
|
+
};
|
|
235
|
+
//# sourceMappingURL=bridge_sqlite.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/bridge_sqlite.ts"],
|
|
4
|
+
"sourcesContent": ["import { BaseEvent } from './base_event.js'\nimport { EventBus } from './event_bus.js'\nimport { 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 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 loadNodeSqlite = async (): Promise<any> => {\n const dynamic_import = Function('module_name', 'return import(module_name)') as (module_name: string) => Promise<unknown>\n try {\n return (await dynamic_import('node:sqlite')) as any\n } catch {\n throw new Error('SQLiteEventBridge requires Node.js with built-in \"node:sqlite\" support (Node 22+).')\n }\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 SQLiteEventBridge {\n readonly path: string\n readonly table: string\n readonly poll_interval: number\n readonly name: string\n\n private readonly inbound_bus: EventBus\n private running: boolean\n private last_seen_event_created_at: string\n private last_seen_event_id: string\n private listener_task: Promise<void> | null\n private start_task: Promise<void> | null\n private db: any | null\n private table_columns: Set<string>\n\n constructor(path: string, table: string = 'abxbus_events', poll_interval: number = 0.25, name?: string) {\n this.path = path\n this.table = validateIdentifier(table, 'table name')\n this.poll_interval = poll_interval\n this.name = name ?? `SQLiteEventBridge_${randomSuffix()}`\n this.inbound_bus = new EventBus(this.name, { max_history_size: 0 })\n this.running = false\n this.last_seen_event_created_at = ''\n this.last_seen_event_id = ''\n this.listener_task = null\n this.start_task = null\n this.db = null\n this.table_columns = new Set(['event_id', 'event_created_at', 'event_type', EVENT_PAYLOAD_COLUMN])\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.running) {\n await this.start()\n }\n if (!this.db) {\n throw new Error('SQLiteEventBridge database not initialized')\n }\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 payload_keys = Object.keys(write_payload).sort()\n this.ensureColumns(payload_keys)\n\n const columns_sql = payload_keys.map((key) => `\"${key}\"`).join(', ')\n const placeholders_sql = payload_keys.map((key) => (key === EVENT_PAYLOAD_COLUMN ? 'json(?)' : '?')).join(', ')\n const values = payload_keys.map((key) =>\n write_payload[key] === null || write_payload[key] === undefined ? null : JSON.stringify(write_payload[key])\n )\n\n const update_fields = payload_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 this.db.prepare(upsert_sql).run(...values)\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_task) {\n await this.start_task\n return\n }\n\n this.start_task = (async (): Promise<void> => {\n if (!isNodeRuntime()) {\n throw new Error('SQLiteEventBridge is only supported in Node.js runtimes')\n }\n\n const mod = await loadNodeSqlite()\n const Database = mod.DatabaseSync ?? mod.default?.DatabaseSync\n if (typeof Database !== 'function') {\n throw new Error('SQLiteEventBridge could not load DatabaseSync from node:sqlite. Please use Node.js 22+.')\n }\n this.db = new Database(this.path)\n this.db.exec('PRAGMA journal_mode = WAL')\n this.db\n .prepare(\n `CREATE TABLE IF NOT EXISTS \"${this.table}\" (\"event_id\" TEXT PRIMARY KEY, \"event_created_at\" TEXT, \"event_type\" TEXT, \"event_payload\" JSON)`\n )\n .run()\n\n this.refreshColumnCache()\n this.ensureColumns(['event_id', 'event_created_at', 'event_type', EVENT_PAYLOAD_COLUMN])\n this.ensureBaseIndexes()\n this.setCursorToLatestRow()\n\n this.running = true\n this.listener_task = this.listenLoop()\n })()\n\n try {\n await this.start_task\n } finally {\n this.start_task = null\n }\n }\n\n async close(): Promise<void> {\n await Promise.allSettled(this.start_task ? [this.start_task] : [])\n this.running = false\n await Promise.allSettled(this.listener_task ? [this.listener_task] : [])\n this.listener_task = null\n\n if (this.db) {\n this.db.close()\n this.db = null\n }\n\n this.inbound_bus.destroy()\n }\n\n private ensureStarted(): void {\n if (this.running || this.listener_task || this.start_task) return\n void this.start().catch((error: unknown) => {\n console.error('[abxbus] SQLiteEventBridge failed to start', error)\n })\n }\n\n private async listenLoop(): Promise<void> {\n while (this.running) {\n try {\n if (this.db) {\n const rows = this.db\n .prepare(\n `SELECT * FROM \"${this.table}\" WHERE COALESCE(\"event_created_at\", '') > ? OR (COALESCE(\"event_created_at\", '') = ? AND COALESCE(\"event_id\", '') > ?) ORDER BY COALESCE(\"event_created_at\", '') ASC, COALESCE(\"event_id\", '') ASC`\n )\n .all(this.last_seen_event_created_at, this.last_seen_event_created_at, this.last_seen_event_id) as Array<\n Record<string, unknown>\n >\n\n for (const row of rows) {\n this.last_seen_event_created_at = String(row.event_created_at ?? '')\n this.last_seen_event_id = String(row.event_id ?? '')\n\n const raw_payload_blob = row[EVENT_PAYLOAD_COLUMN]\n const payload: Record<string, unknown> = {}\n if (typeof raw_payload_blob === 'string') {\n try {\n const decoded_event_payload = JSON.parse(raw_payload_blob)\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\n if (typeof raw_value !== 'string') {\n payload[key] = raw_value\n continue\n }\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 } catch {\n // Keep polling on transient errors.\n }\n await new Promise((resolve) => setTimeout(resolve, Math.max(1, this.poll_interval * 1000)))\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 private refreshColumnCache(): void {\n if (!this.db) return\n const rows = this.db.prepare(`PRAGMA table_info(\"${this.table}\")`).all() as Array<{ name: string }>\n this.table_columns = new Set(rows.map((row) => String(row.name)))\n }\n\n private ensureColumns(keys: string[]): void {\n if (!this.db) return\n\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_columns = keys.filter((key) => !this.table_columns.has(key))\n for (const key of missing_columns) {\n const column_type = key === EVENT_PAYLOAD_COLUMN ? 'JSON' : 'TEXT'\n this.db.prepare(`ALTER TABLE \"${this.table}\" ADD COLUMN \"${key}\" ${column_type}`).run()\n this.table_columns.add(key)\n }\n }\n\n private ensureBaseIndexes(): void {\n if (!this.db) return\n\n const event_created_at_index = `${this.table}_event_created_at_idx`\n const event_type_index = `${this.table}_event_type_idx`\n\n this.db.prepare(`CREATE INDEX IF NOT EXISTS \"${event_created_at_index}\" ON \"${this.table}\" (\"event_created_at\")`).run()\n this.db.prepare(`CREATE INDEX IF NOT EXISTS \"${event_type_index}\" ON \"${this.table}\" (\"event_type\")`).run()\n }\n\n private setCursorToLatestRow(): void {\n if (!this.db) return\n\n const row = this.db\n .prepare(\n `SELECT COALESCE(\"event_created_at\", '') AS event_created_at, COALESCE(\"event_id\", '') AS event_id FROM \"${this.table}\" ORDER BY COALESCE(\"event_created_at\", '') DESC, COALESCE(\"event_id\", '') DESC LIMIT 1`\n )\n .get() as { event_created_at?: string; event_id?: string } | undefined\n\n this.last_seen_event_created_at = String(row?.event_created_at ?? '')\n this.last_seen_event_id = String(row?.event_id ?? '')\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { BaseEvent } from "./base_event.js";
|
|
2
|
+
import { EventBus } from "./event_bus.js";
|
|
3
|
+
const isNodeRuntime = () => {
|
|
4
|
+
const maybe_process = globalThis.process;
|
|
5
|
+
return typeof maybe_process?.versions?.node === "string";
|
|
6
|
+
};
|
|
7
|
+
const isBrowserRuntime = () => !isNodeRuntime() && typeof globalThis.window !== "undefined";
|
|
8
|
+
const randomSuffix = () => Math.random().toString(36).slice(2, 10);
|
|
9
|
+
const UNIX_SOCKET_MAX_PATH_CHARS = 90;
|
|
10
|
+
const parseEndpoint = (raw_endpoint) => {
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = new URL(raw_endpoint);
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error(`Invalid endpoint URL: ${raw_endpoint}`);
|
|
16
|
+
}
|
|
17
|
+
const protocol = parsed.protocol.replace(/:$/, "").toLowerCase();
|
|
18
|
+
if (protocol !== "unix" && protocol !== "http" && protocol !== "https") {
|
|
19
|
+
throw new Error(`Unsupported endpoint scheme: ${raw_endpoint}`);
|
|
20
|
+
}
|
|
21
|
+
if (protocol === "unix") {
|
|
22
|
+
const socket_path = decodeURIComponent(parsed.pathname || "");
|
|
23
|
+
if (!socket_path) {
|
|
24
|
+
throw new Error(`Invalid unix endpoint (missing socket path): ${raw_endpoint}`);
|
|
25
|
+
}
|
|
26
|
+
const socket_path_len = new TextEncoder().encode(socket_path).length;
|
|
27
|
+
if (socket_path_len > UNIX_SOCKET_MAX_PATH_CHARS) {
|
|
28
|
+
throw new Error(`Unix socket path is too long (${socket_path_len} chars), max is ${UNIX_SOCKET_MAX_PATH_CHARS}: ${socket_path}`);
|
|
29
|
+
}
|
|
30
|
+
return { raw: raw_endpoint, scheme: "unix", path: socket_path };
|
|
31
|
+
}
|
|
32
|
+
if (!parsed.hostname) {
|
|
33
|
+
throw new Error(`Invalid HTTP endpoint (missing hostname): ${raw_endpoint}`);
|
|
34
|
+
}
|
|
35
|
+
const default_port = protocol === "https" ? 443 : 80;
|
|
36
|
+
return {
|
|
37
|
+
raw: raw_endpoint,
|
|
38
|
+
scheme: protocol,
|
|
39
|
+
host: parsed.hostname,
|
|
40
|
+
port: parsed.port ? Number(parsed.port) : default_port,
|
|
41
|
+
path: `${parsed.pathname || "/"}${parsed.search || ""}`
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const importNodeModule = async (specifier) => {
|
|
45
|
+
const dynamic_import = Function("module_name", "return import(module_name)");
|
|
46
|
+
return dynamic_import(specifier);
|
|
47
|
+
};
|
|
48
|
+
class _EventBridge {
|
|
49
|
+
send_to;
|
|
50
|
+
listen_on;
|
|
51
|
+
name;
|
|
52
|
+
inbound_bus;
|
|
53
|
+
start_promise;
|
|
54
|
+
node_server;
|
|
55
|
+
constructor(send_to, listen_on, name) {
|
|
56
|
+
this.send_to = send_to ? parseEndpoint(send_to) : null;
|
|
57
|
+
this.listen_on = listen_on ? parseEndpoint(listen_on) : null;
|
|
58
|
+
this.name = name ?? `EventBridge_${randomSuffix()}`;
|
|
59
|
+
this.inbound_bus = new EventBus(this.name, { max_history_size: 0 });
|
|
60
|
+
this.start_promise = null;
|
|
61
|
+
this.node_server = null;
|
|
62
|
+
if (this.listen_on && isBrowserRuntime()) {
|
|
63
|
+
throw new Error(`${this.constructor.name} listen_on is not supported in browser runtimes`);
|
|
64
|
+
}
|
|
65
|
+
this.dispatch = this.dispatch.bind(this);
|
|
66
|
+
this.emit = this.emit.bind(this);
|
|
67
|
+
this.on = this.on.bind(this);
|
|
68
|
+
}
|
|
69
|
+
on(event_pattern, handler) {
|
|
70
|
+
this.ensureListenerStarted();
|
|
71
|
+
if (typeof event_pattern === "string") {
|
|
72
|
+
this.inbound_bus.on(event_pattern, handler);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.inbound_bus.on(event_pattern, handler);
|
|
76
|
+
}
|
|
77
|
+
async emit(event) {
|
|
78
|
+
if (!this.send_to) {
|
|
79
|
+
throw new Error(`${this.constructor.name}.emit() requires send_to`);
|
|
80
|
+
}
|
|
81
|
+
const payload = event.toJSON();
|
|
82
|
+
if (this.send_to.scheme === "unix") {
|
|
83
|
+
await this.sendUnix(this.send_to, payload);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
await this.sendHttp(this.send_to, payload);
|
|
87
|
+
}
|
|
88
|
+
async dispatch(event) {
|
|
89
|
+
return this.emit(event);
|
|
90
|
+
}
|
|
91
|
+
async start() {
|
|
92
|
+
if (!this.listen_on) return;
|
|
93
|
+
if (this.node_server) return;
|
|
94
|
+
if (this.start_promise) {
|
|
95
|
+
await this.start_promise;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!isNodeRuntime()) {
|
|
99
|
+
throw new Error(`${this.constructor.name} listen_on is only supported in Node.js runtimes`);
|
|
100
|
+
}
|
|
101
|
+
const launch = (async () => {
|
|
102
|
+
const endpoint = this.listen_on;
|
|
103
|
+
if (!endpoint) return;
|
|
104
|
+
if (endpoint.scheme === "unix") {
|
|
105
|
+
await this.startUnixListener(endpoint);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (endpoint.scheme !== "http") {
|
|
109
|
+
throw new Error(`listen_on only supports unix:// or http:// endpoints, got: ${endpoint.raw}`);
|
|
110
|
+
}
|
|
111
|
+
await this.startHttpListener(endpoint);
|
|
112
|
+
})();
|
|
113
|
+
this.start_promise = launch;
|
|
114
|
+
try {
|
|
115
|
+
await launch;
|
|
116
|
+
} finally {
|
|
117
|
+
if (this.start_promise === launch) {
|
|
118
|
+
this.start_promise = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async close() {
|
|
123
|
+
if (this.start_promise) {
|
|
124
|
+
await Promise.allSettled([this.start_promise]);
|
|
125
|
+
this.start_promise = null;
|
|
126
|
+
}
|
|
127
|
+
if (this.node_server) {
|
|
128
|
+
const server = this.node_server;
|
|
129
|
+
await new Promise((resolve) => {
|
|
130
|
+
server.close(() => resolve());
|
|
131
|
+
});
|
|
132
|
+
this.node_server = null;
|
|
133
|
+
}
|
|
134
|
+
this.inbound_bus.destroy();
|
|
135
|
+
}
|
|
136
|
+
ensureListenerStarted() {
|
|
137
|
+
if (!this.listen_on || this.node_server || this.start_promise) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
void this.start().catch((error) => {
|
|
141
|
+
console.error("[abxbus] EventBridge failed to start listener", error);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async handleIncomingPayload(payload) {
|
|
145
|
+
const event = BaseEvent.fromJSON(payload).eventReset();
|
|
146
|
+
this.inbound_bus.emit(event);
|
|
147
|
+
}
|
|
148
|
+
async sendHttp(endpoint, payload) {
|
|
149
|
+
const response = await fetch(endpoint.raw, {
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers: { "content-type": "application/json" },
|
|
152
|
+
body: JSON.stringify(payload)
|
|
153
|
+
});
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
throw new Error(`IPC HTTP send failed with status ${response.status}: ${endpoint.raw}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async sendUnix(endpoint, payload) {
|
|
159
|
+
if (!isNodeRuntime()) {
|
|
160
|
+
throw new Error("unix:// send_to is only supported in Node.js runtimes");
|
|
161
|
+
}
|
|
162
|
+
const socket_path = endpoint.path;
|
|
163
|
+
if (!socket_path) {
|
|
164
|
+
throw new Error(`Invalid unix endpoint: ${endpoint.raw}`);
|
|
165
|
+
}
|
|
166
|
+
const node_net = await importNodeModule("node:net");
|
|
167
|
+
await new Promise((resolve, reject) => {
|
|
168
|
+
const socket = node_net.createConnection(socket_path, () => {
|
|
169
|
+
socket.end(`${JSON.stringify(payload)}
|
|
170
|
+
`);
|
|
171
|
+
});
|
|
172
|
+
socket.on("error", (error) => reject(error));
|
|
173
|
+
socket.on("close", () => resolve());
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async startHttpListener(endpoint) {
|
|
177
|
+
const node_http = await importNodeModule("node:http");
|
|
178
|
+
const expected_path = endpoint.path || "/";
|
|
179
|
+
this.node_server = node_http.createServer((req, res) => {
|
|
180
|
+
const method = (req.method || "").toUpperCase();
|
|
181
|
+
const request_url = String(req.url || "/");
|
|
182
|
+
if (method !== "POST") {
|
|
183
|
+
res.statusCode = 405;
|
|
184
|
+
res.end("method not allowed");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (request_url !== expected_path) {
|
|
188
|
+
res.statusCode = 404;
|
|
189
|
+
res.end("not found");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
let body = "";
|
|
193
|
+
req.setEncoding("utf8");
|
|
194
|
+
req.on("data", (chunk) => {
|
|
195
|
+
body += chunk;
|
|
196
|
+
});
|
|
197
|
+
req.on("end", () => {
|
|
198
|
+
let parsed_payload;
|
|
199
|
+
try {
|
|
200
|
+
parsed_payload = JSON.parse(body);
|
|
201
|
+
} catch {
|
|
202
|
+
res.statusCode = 400;
|
|
203
|
+
res.end("invalid json");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
void this.handleIncomingPayload(parsed_payload).then(() => {
|
|
207
|
+
res.statusCode = 202;
|
|
208
|
+
res.end("accepted");
|
|
209
|
+
}).catch((error) => {
|
|
210
|
+
res.statusCode = 500;
|
|
211
|
+
res.end("failed to process event");
|
|
212
|
+
console.error("[abxbus] EventBridge HTTP listener error", error);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
await new Promise((resolve, reject) => {
|
|
217
|
+
this.node_server.once("error", (error) => reject(error));
|
|
218
|
+
this.node_server.listen(endpoint.port, endpoint.host, () => resolve());
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async startUnixListener(endpoint) {
|
|
222
|
+
const socket_path = endpoint.path;
|
|
223
|
+
if (!socket_path) {
|
|
224
|
+
throw new Error(`Invalid unix endpoint: ${endpoint.raw}`);
|
|
225
|
+
}
|
|
226
|
+
const node_net = await importNodeModule("node:net");
|
|
227
|
+
const node_fs = await importNodeModule("node:fs");
|
|
228
|
+
try {
|
|
229
|
+
await node_fs.promises.unlink(socket_path);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
const code = error.code;
|
|
232
|
+
if (code !== "ENOENT") {
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
this.node_server = node_net.createServer((socket) => {
|
|
237
|
+
let buffer = "";
|
|
238
|
+
socket.setEncoding("utf8");
|
|
239
|
+
socket.on("data", (chunk) => {
|
|
240
|
+
buffer += chunk;
|
|
241
|
+
while (true) {
|
|
242
|
+
const newline_index = buffer.indexOf("\n");
|
|
243
|
+
if (newline_index < 0) break;
|
|
244
|
+
const line = buffer.slice(0, newline_index).trim();
|
|
245
|
+
buffer = buffer.slice(newline_index + 1);
|
|
246
|
+
if (!line) continue;
|
|
247
|
+
try {
|
|
248
|
+
const parsed_payload = JSON.parse(line);
|
|
249
|
+
void this.handleIncomingPayload(parsed_payload);
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
socket.on("end", () => {
|
|
255
|
+
const remainder = buffer.trim();
|
|
256
|
+
if (!remainder) return;
|
|
257
|
+
try {
|
|
258
|
+
const parsed_payload = JSON.parse(remainder);
|
|
259
|
+
void this.handleIncomingPayload(parsed_payload);
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
await new Promise((resolve, reject) => {
|
|
265
|
+
this.node_server.once("error", (error) => reject(error));
|
|
266
|
+
this.node_server.listen(socket_path, () => resolve());
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
class HTTPEventBridge extends _EventBridge {
|
|
271
|
+
constructor(send_to_or_options, listen_on, name) {
|
|
272
|
+
const options = typeof send_to_or_options === "object" ? send_to_or_options ?? {} : { send_to: send_to_or_options ?? void 0, listen_on: listen_on ?? void 0, name };
|
|
273
|
+
if (options.send_to && parseEndpoint(options.send_to).scheme === "unix") {
|
|
274
|
+
throw new Error("HTTPEventBridge send_to must be http:// or https://");
|
|
275
|
+
}
|
|
276
|
+
if (options.listen_on && parseEndpoint(options.listen_on).scheme !== "http") {
|
|
277
|
+
throw new Error("HTTPEventBridge listen_on must be http://");
|
|
278
|
+
}
|
|
279
|
+
super(options.send_to, options.listen_on, options.name ?? `HTTPEventBridge_${randomSuffix()}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
class SocketEventBridge extends _EventBridge {
|
|
283
|
+
constructor(path, name) {
|
|
284
|
+
const normalized = path ? path.startsWith("unix://") ? path.slice(7) : path : null;
|
|
285
|
+
if (normalized === "") {
|
|
286
|
+
throw new Error("SocketEventBridge path must not be empty");
|
|
287
|
+
}
|
|
288
|
+
const endpoint = normalized ? `unix://${normalized}` : null;
|
|
289
|
+
super(endpoint, endpoint, name ?? `SocketEventBridge_${randomSuffix()}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
import { NATSEventBridge } from "./bridge_nats.js";
|
|
293
|
+
import { RedisEventBridge } from "./bridge_redis.js";
|
|
294
|
+
import { PostgresEventBridge } from "./bridge_postgres.js";
|
|
295
|
+
import { JSONLEventBridge } from "./bridge_jsonl.js";
|
|
296
|
+
import { SQLiteEventBridge } from "./bridge_sqlite.js";
|
|
297
|
+
export {
|
|
298
|
+
HTTPEventBridge,
|
|
299
|
+
JSONLEventBridge,
|
|
300
|
+
NATSEventBridge,
|
|
301
|
+
PostgresEventBridge,
|
|
302
|
+
RedisEventBridge,
|
|
303
|
+
SQLiteEventBridge,
|
|
304
|
+
SocketEventBridge
|
|
305
|
+
};
|
|
306
|
+
//# sourceMappingURL=bridges.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/bridges.ts"],
|
|
4
|
+
"sourcesContent": ["import { BaseEvent } from './base_event.js'\nimport { EventBus } from './event_bus.js'\nimport type { EventClass, EventHandlerCallable, EventPattern, UntypedEventHandlerFunction } from './types.js'\n\ntype EndpointScheme = 'unix' | 'http' | 'https'\n\ntype ParsedEndpoint = {\n raw: string\n scheme: EndpointScheme\n host?: string\n port?: number\n path?: string\n}\n\nexport type HTTPEventBridgeOptions = {\n send_to?: string | null\n listen_on?: string | null\n name?: string\n}\n\nconst isNodeRuntime = (): boolean => {\n const maybe_process = (globalThis as { process?: { versions?: { node?: string } } }).process\n return typeof maybe_process?.versions?.node === 'string'\n}\n\nconst isBrowserRuntime = (): boolean => !isNodeRuntime() && typeof globalThis.window !== 'undefined'\n\nconst randomSuffix = (): string => Math.random().toString(36).slice(2, 10)\nconst UNIX_SOCKET_MAX_PATH_CHARS = 90\n\nconst parseEndpoint = (raw_endpoint: string): ParsedEndpoint => {\n let parsed: URL\n try {\n parsed = new URL(raw_endpoint)\n } catch {\n throw new Error(`Invalid endpoint URL: ${raw_endpoint}`)\n }\n\n const protocol = parsed.protocol.replace(/:$/, '').toLowerCase()\n if (protocol !== 'unix' && protocol !== 'http' && protocol !== 'https') {\n throw new Error(`Unsupported endpoint scheme: ${raw_endpoint}`)\n }\n\n if (protocol === 'unix') {\n const socket_path = decodeURIComponent(parsed.pathname || '')\n if (!socket_path) {\n throw new Error(`Invalid unix endpoint (missing socket path): ${raw_endpoint}`)\n }\n const socket_path_len = new TextEncoder().encode(socket_path).length\n if (socket_path_len > UNIX_SOCKET_MAX_PATH_CHARS) {\n throw new Error(`Unix socket path is too long (${socket_path_len} chars), max is ${UNIX_SOCKET_MAX_PATH_CHARS}: ${socket_path}`)\n }\n return { raw: raw_endpoint, scheme: 'unix', path: socket_path }\n }\n\n if (!parsed.hostname) {\n throw new Error(`Invalid HTTP endpoint (missing hostname): ${raw_endpoint}`)\n }\n\n const default_port = protocol === 'https' ? 443 : 80\n return {\n raw: raw_endpoint,\n scheme: protocol,\n host: parsed.hostname,\n port: parsed.port ? Number(parsed.port) : default_port,\n path: `${parsed.pathname || '/'}${parsed.search || ''}`,\n }\n}\n\nconst importNodeModule = async (specifier: string): Promise<any> => {\n const dynamic_import = Function('module_name', 'return import(module_name)') as (module_name: string) => Promise<unknown>\n return dynamic_import(specifier) as Promise<any>\n}\n\nclass _EventBridge {\n readonly send_to: ParsedEndpoint | null\n readonly listen_on: ParsedEndpoint | null\n readonly name: string\n\n protected readonly inbound_bus: EventBus\n private start_promise: Promise<void> | null\n private node_server: any | null\n\n constructor(send_to?: string | null, listen_on?: string | null, name?: string) {\n this.send_to = send_to ? parseEndpoint(send_to) : null\n this.listen_on = listen_on ? parseEndpoint(listen_on) : null\n this.name = name ?? `EventBridge_${randomSuffix()}`\n this.inbound_bus = new EventBus(this.name, { max_history_size: 0 })\n this.start_promise = null\n this.node_server = null\n\n if (this.listen_on && isBrowserRuntime()) {\n throw new Error(`${this.constructor.name} listen_on is not supported in browser runtimes`)\n }\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.ensureListenerStarted()\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 if (!this.send_to) {\n throw new Error(`${this.constructor.name}.emit() requires send_to`)\n }\n\n const payload = event.toJSON()\n\n if (this.send_to.scheme === 'unix') {\n await this.sendUnix(this.send_to, payload)\n return\n }\n\n await this.sendHttp(this.send_to, 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.listen_on) return\n if (this.node_server) return\n if (this.start_promise) {\n await this.start_promise\n return\n }\n\n if (!isNodeRuntime()) {\n throw new Error(`${this.constructor.name} listen_on is only supported in Node.js runtimes`)\n }\n\n const launch = (async () => {\n const endpoint = this.listen_on\n if (!endpoint) return\n\n if (endpoint.scheme === 'unix') {\n await this.startUnixListener(endpoint)\n return\n }\n\n if (endpoint.scheme !== 'http') {\n throw new Error(`listen_on only supports unix:// or http:// endpoints, got: ${endpoint.raw}`)\n }\n\n await this.startHttpListener(endpoint)\n })()\n this.start_promise = launch\n\n try {\n await launch\n } finally {\n if (this.start_promise === launch) {\n this.start_promise = null\n }\n }\n }\n\n async close(): Promise<void> {\n if (this.start_promise) {\n await Promise.allSettled([this.start_promise])\n this.start_promise = null\n }\n\n if (this.node_server) {\n const server = this.node_server\n await new Promise<void>((resolve) => {\n server.close(() => resolve())\n })\n this.node_server = null\n }\n\n this.inbound_bus.destroy()\n }\n\n private ensureListenerStarted(): void {\n if (!this.listen_on || this.node_server || this.start_promise) {\n return\n }\n void this.start().catch((error: unknown) => {\n console.error('[abxbus] EventBridge failed to start listener', error)\n })\n }\n\n private async handleIncomingPayload(payload: unknown): Promise<void> {\n const event = BaseEvent.fromJSON(payload).eventReset()\n this.inbound_bus.emit(event)\n }\n\n private async sendHttp(endpoint: ParsedEndpoint, payload: unknown): Promise<void> {\n const response = await fetch(endpoint.raw, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!response.ok) {\n throw new Error(`IPC HTTP send failed with status ${response.status}: ${endpoint.raw}`)\n }\n }\n\n private async sendUnix(endpoint: ParsedEndpoint, payload: unknown): Promise<void> {\n if (!isNodeRuntime()) {\n throw new Error('unix:// send_to is only supported in Node.js runtimes')\n }\n\n const socket_path = endpoint.path\n if (!socket_path) {\n throw new Error(`Invalid unix endpoint: ${endpoint.raw}`)\n }\n\n const node_net = await importNodeModule('node:net')\n await new Promise<void>((resolve, reject) => {\n const socket = node_net.createConnection(socket_path, () => {\n socket.end(`${JSON.stringify(payload)}\\n`)\n })\n socket.on('error', (error: unknown) => reject(error))\n socket.on('close', () => resolve())\n })\n }\n\n private async startHttpListener(endpoint: ParsedEndpoint): Promise<void> {\n const node_http = await importNodeModule('node:http')\n const expected_path = endpoint.path || '/'\n\n this.node_server = node_http.createServer((req: any, res: any) => {\n const method = (req.method || '').toUpperCase()\n const request_url = String(req.url || '/')\n\n if (method !== 'POST') {\n res.statusCode = 405\n res.end('method not allowed')\n return\n }\n if (request_url !== expected_path) {\n res.statusCode = 404\n res.end('not found')\n return\n }\n\n let body = ''\n req.setEncoding('utf8')\n req.on('data', (chunk: string) => {\n body += chunk\n })\n req.on('end', () => {\n let parsed_payload: unknown\n try {\n parsed_payload = JSON.parse(body)\n } catch {\n res.statusCode = 400\n res.end('invalid json')\n return\n }\n\n void this.handleIncomingPayload(parsed_payload)\n .then(() => {\n res.statusCode = 202\n res.end('accepted')\n })\n .catch((error: unknown) => {\n res.statusCode = 500\n res.end('failed to process event')\n console.error('[abxbus] EventBridge HTTP listener error', error)\n })\n })\n })\n\n await new Promise<void>((resolve, reject) => {\n this.node_server.once('error', (error: unknown) => reject(error))\n this.node_server.listen(endpoint.port, endpoint.host, () => resolve())\n })\n }\n\n private async startUnixListener(endpoint: ParsedEndpoint): Promise<void> {\n const socket_path = endpoint.path\n if (!socket_path) {\n throw new Error(`Invalid unix endpoint: ${endpoint.raw}`)\n }\n\n const node_net = await importNodeModule('node:net')\n const node_fs = await importNodeModule('node:fs')\n\n try {\n await node_fs.promises.unlink(socket_path)\n } catch (error: unknown) {\n const code = (error as { code?: string }).code\n if (code !== 'ENOENT') {\n throw error\n }\n }\n\n this.node_server = node_net.createServer((socket: any) => {\n let buffer = ''\n socket.setEncoding('utf8')\n socket.on('data', (chunk: string) => {\n buffer += chunk\n while (true) {\n const newline_index = buffer.indexOf('\\n')\n if (newline_index < 0) break\n const line = buffer.slice(0, newline_index).trim()\n buffer = buffer.slice(newline_index + 1)\n if (!line) continue\n try {\n const parsed_payload = JSON.parse(line)\n void this.handleIncomingPayload(parsed_payload)\n } catch {\n // Ignore malformed lines and continue reading next frames.\n }\n }\n })\n socket.on('end', () => {\n const remainder = buffer.trim()\n if (!remainder) return\n try {\n const parsed_payload = JSON.parse(remainder)\n void this.handleIncomingPayload(parsed_payload)\n } catch {\n // Ignore malformed trailing frame.\n }\n })\n })\n\n await new Promise<void>((resolve, reject) => {\n this.node_server.once('error', (error: unknown) => reject(error))\n this.node_server.listen(socket_path, () => resolve())\n })\n }\n}\n\nexport class HTTPEventBridge extends _EventBridge {\n constructor(send_to?: string | null, listen_on?: string | null, name?: string)\n constructor(options?: HTTPEventBridgeOptions)\n constructor(send_to_or_options?: string | null | HTTPEventBridgeOptions, listen_on?: string | null, name?: string) {\n const options: HTTPEventBridgeOptions =\n typeof send_to_or_options === 'object'\n ? (send_to_or_options ?? {})\n : { send_to: send_to_or_options ?? undefined, listen_on: listen_on ?? undefined, name }\n\n if (options.send_to && parseEndpoint(options.send_to).scheme === 'unix') {\n throw new Error('HTTPEventBridge send_to must be http:// or https://')\n }\n if (options.listen_on && parseEndpoint(options.listen_on).scheme !== 'http') {\n throw new Error('HTTPEventBridge listen_on must be http://')\n }\n\n super(options.send_to, options.listen_on, options.name ?? `HTTPEventBridge_${randomSuffix()}`)\n }\n}\n\nexport class SocketEventBridge extends _EventBridge {\n constructor(path?: string | null, name?: string) {\n const normalized = path ? (path.startsWith('unix://') ? path.slice(7) : path) : null\n if (normalized === '') {\n throw new Error('SocketEventBridge path must not be empty')\n }\n\n const endpoint = normalized ? `unix://${normalized}` : null\n super(endpoint, endpoint, name ?? `SocketEventBridge_${randomSuffix()}`)\n }\n}\n\nexport { NATSEventBridge } from './bridge_nats.js'\nexport { RedisEventBridge } from './bridge_redis.js'\nexport { PostgresEventBridge } from './bridge_postgres.js'\nexport { JSONLEventBridge } from './bridge_jsonl.js'\nexport { SQLiteEventBridge } from './bridge_sqlite.js'\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|