abxbus 2.4.29 → 2.4.31
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 +22 -1
- package/dist/cjs/BaseEvent.d.ts +4 -1
- package/dist/cjs/BaseEvent.js.map +2 -2
- package/dist/cjs/EventBus.d.ts +5 -1
- package/dist/cjs/EventBus.js +20 -7
- package/dist/cjs/EventBus.js.map +2 -2
- package/dist/cjs/EventHistory.d.ts +5 -0
- package/dist/cjs/EventHistory.js +32 -5
- package/dist/cjs/EventHistory.js.map +2 -2
- package/dist/cjs/TachyonEventBridge.d.ts +25 -0
- package/dist/cjs/TachyonEventBridge.js +427 -0
- package/dist/cjs/TachyonEventBridge.js.map +7 -0
- package/dist/cjs/base_event.d.ts +2 -2
- package/dist/cjs/bridges.d.ts +1 -0
- package/dist/cjs/bridges.js +3 -1
- package/dist/cjs/bridges.js.map +2 -2
- package/dist/cjs/event_handler.d.ts +0 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/types.d.ts +3 -0
- package/dist/cjs/types.js.map +2 -2
- package/dist/esm/BaseEvent.js.map +2 -2
- package/dist/esm/EventBus.js +20 -7
- package/dist/esm/EventBus.js.map +2 -2
- package/dist/esm/EventHistory.js +32 -5
- package/dist/esm/EventHistory.js.map +2 -2
- package/dist/esm/TachyonEventBridge.js +406 -0
- package/dist/esm/TachyonEventBridge.js.map +7 -0
- package/dist/esm/bridges.js +3 -1
- package/dist/esm/bridges.js.map +2 -2
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/types.js.map +2 -2
- package/dist/types/BaseEvent.d.ts +4 -1
- package/dist/types/EventBus.d.ts +5 -1
- package/dist/types/EventHistory.d.ts +5 -0
- package/dist/types/TachyonEventBridge.d.ts +25 -0
- package/dist/types/base_event.d.ts +2 -2
- package/dist/types/bridges.d.ts +1 -0
- package/dist/types/event_handler.d.ts +0 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/types.d.ts +3 -0
- package/package.json +26 -20
- package/src/BaseEvent.ts +2 -3
- package/src/EventBus.ts +38 -10
- package/src/EventHistory.ts +47 -4
- package/src/TachyonEventBridge.ts +498 -0
- package/src/bridges.ts +1 -0
- package/src/index.ts +10 -2
- package/src/types.ts +2 -0
- package/dist/cjs/bridge_ipc.d.ts +0 -45
- package/dist/cjs/middleware_otel_tracing.d.ts +0 -49
- package/dist/types/bridge_ipc.d.ts +0 -45
- package/dist/types/middleware_otel_tracing.d.ts +0 -49
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { existsSync, symlinkSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { Worker } from "node:worker_threads";
|
|
5
|
+
import { BaseEvent } from "./BaseEvent.js";
|
|
6
|
+
import { EventBus } from "./EventBus.js";
|
|
7
|
+
import { assertOptionalDependencyAvailable, isNodeRuntime } from "./optional_deps.js";
|
|
8
|
+
const randomSuffix = () => Math.random().toString(36).slice(2, 10);
|
|
9
|
+
const DEFAULT_TACHYON_CAPACITY = 1 << 20;
|
|
10
|
+
const TACHYON_CONNECT_TIMEOUT_MS = 5e3;
|
|
11
|
+
const TACHYON_LISTEN_TIMEOUT_MS = 5e3;
|
|
12
|
+
const TACHYON_SHUTDOWN_TYPE_ID = 57005;
|
|
13
|
+
const TACHYON_DATA_TYPE_ID = 1;
|
|
14
|
+
const requireForTachyon = createRequire(import.meta.url);
|
|
15
|
+
const ensureTachyonNativeLayout = () => {
|
|
16
|
+
try {
|
|
17
|
+
const package_json = requireForTachyon.resolve("@tachyon-ipc/core/package.json");
|
|
18
|
+
const core_dir = dirname(package_json);
|
|
19
|
+
const expected_build_dir = join(dirname(core_dir), "build");
|
|
20
|
+
const actual_build_dir = join(core_dir, "build");
|
|
21
|
+
if (!existsSync(expected_build_dir) && existsSync(actual_build_dir)) {
|
|
22
|
+
symlinkSync(actual_build_dir, expected_build_dir, "dir");
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const TACHYON_NATIVE_LAYOUT_FIX = `
|
|
28
|
+
const ensureTachyonNativeLayout = () => {
|
|
29
|
+
try {
|
|
30
|
+
const fs = require('node:fs')
|
|
31
|
+
const path = require('node:path')
|
|
32
|
+
const packageJson = require.resolve('@tachyon-ipc/core/package.json')
|
|
33
|
+
const coreDir = path.dirname(packageJson)
|
|
34
|
+
const expectedBuildDir = path.join(path.dirname(coreDir), 'build')
|
|
35
|
+
const actualBuildDir = path.join(coreDir, 'build')
|
|
36
|
+
if (!fs.existsSync(expectedBuildDir) && fs.existsSync(actualBuildDir)) {
|
|
37
|
+
fs.symlinkSync(actualBuildDir, expectedBuildDir, 'dir')
|
|
38
|
+
}
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
ensureTachyonNativeLayout()
|
|
42
|
+
`;
|
|
43
|
+
const TACHYON_LISTENER_WORKER_CODE = `
|
|
44
|
+
const { parentPort, workerData } = require('node:worker_threads')
|
|
45
|
+
${TACHYON_NATIVE_LAYOUT_FIX}
|
|
46
|
+
|
|
47
|
+
const SHUTDOWN_TYPE_ID = ${TACHYON_SHUTDOWN_TYPE_ID}
|
|
48
|
+
|
|
49
|
+
const probeListenerAlive = (path) => new Promise((resolve) => {
|
|
50
|
+
const net = require('node:net')
|
|
51
|
+
const sock = net.createConnection(path)
|
|
52
|
+
const settle = (alive) => {
|
|
53
|
+
try { sock.destroy() } catch {}
|
|
54
|
+
resolve(alive)
|
|
55
|
+
}
|
|
56
|
+
sock.setTimeout(50, () => settle(false))
|
|
57
|
+
sock.once('connect', () => settle(true))
|
|
58
|
+
sock.once('error', () => settle(false))
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const main = async () => {
|
|
62
|
+
const { Bus } = await import('@tachyon-ipc/core')
|
|
63
|
+
const fs = require('node:fs')
|
|
64
|
+
const { path, capacity } = workerData
|
|
65
|
+
let bus
|
|
66
|
+
try {
|
|
67
|
+
bus = Bus.listen(path, capacity)
|
|
68
|
+
} catch (firstErr) {
|
|
69
|
+
// The bind failed because the path is in use. If a live listener owns it, propagate
|
|
70
|
+
// the error so the user can resolve the conflict; if it's a stale socket from a
|
|
71
|
+
// previous crash, unlink it and retry exactly once. Refuse to unlink anything that
|
|
72
|
+
// isn't a unix socket \u2014 we have no business deleting an unrelated regular file.
|
|
73
|
+
const alive = await probeListenerAlive(path)
|
|
74
|
+
if (alive || !fs.existsSync(path)) throw firstErr
|
|
75
|
+
let st
|
|
76
|
+
try { st = fs.statSync(path) } catch { throw firstErr }
|
|
77
|
+
if (!st.isSocket()) throw firstErr
|
|
78
|
+
try { fs.unlinkSync(path) } catch {}
|
|
79
|
+
bus = Bus.listen(path, capacity)
|
|
80
|
+
}
|
|
81
|
+
parentPort.postMessage({ type: 'ready' })
|
|
82
|
+
while (true) {
|
|
83
|
+
let msg
|
|
84
|
+
try {
|
|
85
|
+
msg = bus.recv()
|
|
86
|
+
} catch (err) {
|
|
87
|
+
parentPort.postMessage({ type: 'error', message: err && err.message ? err.message : String(err) })
|
|
88
|
+
break
|
|
89
|
+
}
|
|
90
|
+
if (msg.typeId === SHUTDOWN_TYPE_ID) break
|
|
91
|
+
parentPort.postMessage({ type: 'message', data: msg.data, typeId: msg.typeId })
|
|
92
|
+
}
|
|
93
|
+
try { bus.close && bus.close() } catch {}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main().catch((err) => {
|
|
97
|
+
if (parentPort) {
|
|
98
|
+
parentPort.postMessage({ type: 'error', message: err && err.message ? err.message : String(err) })
|
|
99
|
+
}
|
|
100
|
+
}).finally(() => process.exit(0))
|
|
101
|
+
`;
|
|
102
|
+
const TACHYON_SENDER_WORKER_CODE = `
|
|
103
|
+
const { parentPort, workerData } = require('node:worker_threads')
|
|
104
|
+
${TACHYON_NATIVE_LAYOUT_FIX}
|
|
105
|
+
|
|
106
|
+
const SHUTDOWN_TYPE_ID = ${TACHYON_SHUTDOWN_TYPE_ID}
|
|
107
|
+
const DATA_TYPE_ID = ${TACHYON_DATA_TYPE_ID}
|
|
108
|
+
|
|
109
|
+
const main = async () => {
|
|
110
|
+
const { Bus } = await import('@tachyon-ipc/core')
|
|
111
|
+
const { path, connect_timeout_ms } = workerData
|
|
112
|
+
let bus = null
|
|
113
|
+
let last_err = null
|
|
114
|
+
const deadline = Date.now() + connect_timeout_ms
|
|
115
|
+
while (Date.now() < deadline) {
|
|
116
|
+
try {
|
|
117
|
+
bus = Bus.connect(path)
|
|
118
|
+
break
|
|
119
|
+
} catch (err) {
|
|
120
|
+
last_err = err
|
|
121
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!bus) {
|
|
125
|
+
parentPort.postMessage({ type: 'error', message: 'TachyonEventBridge sender failed to connect: ' + (last_err && last_err.message ? last_err.message : String(last_err)) })
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
parentPort.postMessage({ type: 'ready' })
|
|
129
|
+
parentPort.on('message', (msg) => {
|
|
130
|
+
if (!msg) return
|
|
131
|
+
if (msg.type === 'send') {
|
|
132
|
+
try {
|
|
133
|
+
bus.send(Buffer.from(msg.payload), DATA_TYPE_ID)
|
|
134
|
+
parentPort.postMessage({ type: 'sent', id: msg.id })
|
|
135
|
+
} catch (err) {
|
|
136
|
+
parentPort.postMessage({ type: 'send_error', id: msg.id, message: err && err.message ? err.message : String(err) })
|
|
137
|
+
}
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
if (msg.type === 'close') {
|
|
141
|
+
try { bus.send(Buffer.alloc(0), SHUTDOWN_TYPE_ID) } catch {}
|
|
142
|
+
try { bus.close && bus.close() } catch {}
|
|
143
|
+
process.exit(0)
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
main().catch((err) => {
|
|
149
|
+
if (parentPort) {
|
|
150
|
+
parentPort.postMessage({ type: 'error', message: err && err.message ? err.message : String(err) })
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
`;
|
|
154
|
+
class TachyonEventBridge {
|
|
155
|
+
path;
|
|
156
|
+
capacity;
|
|
157
|
+
name;
|
|
158
|
+
inbound_bus;
|
|
159
|
+
listener_worker;
|
|
160
|
+
// Sticky: `listener_worker` may be cleared mid-session (graceful exit, retry path),
|
|
161
|
+
// but the socket on disk is still ours to unlink in close().
|
|
162
|
+
acted_as_listener;
|
|
163
|
+
listener_startup_error;
|
|
164
|
+
sender_worker;
|
|
165
|
+
sender_ready_promise;
|
|
166
|
+
send_seq;
|
|
167
|
+
pending_sends;
|
|
168
|
+
closed;
|
|
169
|
+
constructor(path, capacity = DEFAULT_TACHYON_CAPACITY, name) {
|
|
170
|
+
if (!path) throw new Error("TachyonEventBridge path must not be empty");
|
|
171
|
+
if (capacity <= 0 || (capacity & capacity - 1) !== 0) {
|
|
172
|
+
throw new Error(`TachyonEventBridge capacity must be a positive power of two, got: ${capacity}`);
|
|
173
|
+
}
|
|
174
|
+
assertOptionalDependencyAvailable("TachyonEventBridge", "@tachyon-ipc/core");
|
|
175
|
+
ensureTachyonNativeLayout();
|
|
176
|
+
this.path = path;
|
|
177
|
+
this.capacity = capacity;
|
|
178
|
+
this.name = name ?? `TachyonEventBridge_${randomSuffix()}`;
|
|
179
|
+
this.inbound_bus = new EventBus(this.name, { max_history_size: 0 });
|
|
180
|
+
this.listener_worker = null;
|
|
181
|
+
this.acted_as_listener = false;
|
|
182
|
+
this.listener_startup_error = null;
|
|
183
|
+
this.sender_worker = null;
|
|
184
|
+
this.sender_ready_promise = null;
|
|
185
|
+
this.send_seq = 0;
|
|
186
|
+
this.pending_sends = /* @__PURE__ */ new Map();
|
|
187
|
+
this.closed = false;
|
|
188
|
+
this.dispatch = this.dispatch.bind(this);
|
|
189
|
+
this.emit = this.emit.bind(this);
|
|
190
|
+
this.on = this.on.bind(this);
|
|
191
|
+
}
|
|
192
|
+
on(event_pattern, handler) {
|
|
193
|
+
this.ensureListenerStarted();
|
|
194
|
+
if (typeof event_pattern === "string") {
|
|
195
|
+
this.inbound_bus.on(event_pattern, handler);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
this.inbound_bus.on(event_pattern, handler);
|
|
199
|
+
}
|
|
200
|
+
async emit(event) {
|
|
201
|
+
if (this.closed) throw new Error("TachyonEventBridge is closed");
|
|
202
|
+
await this.ensureSenderConnected();
|
|
203
|
+
if (this.closed || !this.sender_worker) {
|
|
204
|
+
throw new Error("TachyonEventBridge is closed");
|
|
205
|
+
}
|
|
206
|
+
const payload = Buffer.from(JSON.stringify(event.toJSON()));
|
|
207
|
+
const id = ++this.send_seq;
|
|
208
|
+
await new Promise((resolve, reject) => {
|
|
209
|
+
this.pending_sends.set(id, { resolve, reject });
|
|
210
|
+
this.sender_worker.postMessage({ type: "send", id, payload });
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async dispatch(event) {
|
|
214
|
+
return this.emit(event);
|
|
215
|
+
}
|
|
216
|
+
async start() {
|
|
217
|
+
const worker = this.listener_worker;
|
|
218
|
+
if (!worker) return;
|
|
219
|
+
const deadline = Date.now() + TACHYON_LISTEN_TIMEOUT_MS;
|
|
220
|
+
let path_first_seen_at = null;
|
|
221
|
+
while (Date.now() < deadline) {
|
|
222
|
+
if (this.listener_startup_error) throw this.listener_startup_error;
|
|
223
|
+
if (this.acted_as_listener) return;
|
|
224
|
+
if (existsSync(this.path)) {
|
|
225
|
+
if (path_first_seen_at === null) {
|
|
226
|
+
path_first_seen_at = Date.now();
|
|
227
|
+
} else if (Date.now() - path_first_seen_at >= 200) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
232
|
+
}
|
|
233
|
+
if (this.listener_startup_error) throw this.listener_startup_error;
|
|
234
|
+
worker.terminate().catch(() => {
|
|
235
|
+
});
|
|
236
|
+
if (this.listener_worker === worker) this.listener_worker = null;
|
|
237
|
+
throw new Error(`TachyonEventBridge listener did not bind socket ${this.path} within ${TACHYON_LISTEN_TIMEOUT_MS}ms`);
|
|
238
|
+
}
|
|
239
|
+
async close() {
|
|
240
|
+
this.closed = true;
|
|
241
|
+
if (this.sender_worker) {
|
|
242
|
+
const sender_exited = new Promise((resolve) => {
|
|
243
|
+
this.sender_worker.once("exit", () => resolve());
|
|
244
|
+
});
|
|
245
|
+
try {
|
|
246
|
+
this.sender_worker.postMessage({ type: "close" });
|
|
247
|
+
} catch {
|
|
248
|
+
}
|
|
249
|
+
await Promise.race([sender_exited, new Promise((resolve) => setTimeout(resolve, 1e3))]);
|
|
250
|
+
try {
|
|
251
|
+
await this.sender_worker.terminate();
|
|
252
|
+
} catch {
|
|
253
|
+
}
|
|
254
|
+
this.sender_worker = null;
|
|
255
|
+
}
|
|
256
|
+
if (this.listener_worker) {
|
|
257
|
+
const listener = this.listener_worker;
|
|
258
|
+
this.listener_worker = null;
|
|
259
|
+
const listener_exited = new Promise((resolve) => {
|
|
260
|
+
listener.once("exit", () => resolve());
|
|
261
|
+
});
|
|
262
|
+
await Promise.race([listener_exited, new Promise((resolve) => setTimeout(resolve, 1e3))]);
|
|
263
|
+
const terminate_promise = listener.terminate().catch(() => void 0);
|
|
264
|
+
const terminate_settled = await Promise.race([
|
|
265
|
+
terminate_promise.then(() => true),
|
|
266
|
+
new Promise((resolve) => setTimeout(() => resolve(false), 500))
|
|
267
|
+
]);
|
|
268
|
+
if (!terminate_settled) {
|
|
269
|
+
try {
|
|
270
|
+
listener.unref();
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
for (const pending of this.pending_sends.values()) {
|
|
276
|
+
pending.reject(new Error("TachyonEventBridge closed"));
|
|
277
|
+
}
|
|
278
|
+
this.pending_sends.clear();
|
|
279
|
+
if (this.acted_as_listener && existsSync(this.path)) {
|
|
280
|
+
try {
|
|
281
|
+
unlinkSync(this.path);
|
|
282
|
+
} catch {
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
this.inbound_bus.destroy();
|
|
286
|
+
}
|
|
287
|
+
ensureListenerStarted() {
|
|
288
|
+
if (this.closed) throw new Error("TachyonEventBridge is closed");
|
|
289
|
+
if (this.listener_worker) return;
|
|
290
|
+
if (!isNodeRuntime()) {
|
|
291
|
+
throw new Error("TachyonEventBridge is only supported in Node.js runtimes");
|
|
292
|
+
}
|
|
293
|
+
const worker = new Worker(TACHYON_LISTENER_WORKER_CODE, {
|
|
294
|
+
eval: true,
|
|
295
|
+
workerData: { path: this.path, capacity: this.capacity }
|
|
296
|
+
});
|
|
297
|
+
this.listener_startup_error = null;
|
|
298
|
+
worker.on("message", (msg) => {
|
|
299
|
+
if (msg.type === "ready") {
|
|
300
|
+
this.acted_as_listener = true;
|
|
301
|
+
} else if (msg.type === "message" && msg.data) {
|
|
302
|
+
try {
|
|
303
|
+
const text = Buffer.from(msg.data).toString("utf-8");
|
|
304
|
+
const payload = JSON.parse(text);
|
|
305
|
+
const event = BaseEvent.fromJSON(payload).eventReset();
|
|
306
|
+
this.inbound_bus.emit(event);
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
} else if (msg.type === "error") {
|
|
310
|
+
this.listener_startup_error = new Error(msg.message ?? "TachyonEventBridge listener error");
|
|
311
|
+
console.error("[abxbus] TachyonEventBridge listener error:", msg.message);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
worker.on("error", (err) => {
|
|
315
|
+
this.listener_startup_error = err instanceof Error ? err : new Error(String(err));
|
|
316
|
+
console.error("[abxbus] TachyonEventBridge listener worker crashed:", err);
|
|
317
|
+
});
|
|
318
|
+
worker.on("exit", () => {
|
|
319
|
+
if (this.listener_worker === worker) this.listener_worker = null;
|
|
320
|
+
if (!this.acted_as_listener && !this.listener_startup_error && !this.closed) {
|
|
321
|
+
this.listener_startup_error = new Error("TachyonEventBridge listener worker exited before binding");
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
this.listener_worker = worker;
|
|
325
|
+
}
|
|
326
|
+
async ensureSenderConnected() {
|
|
327
|
+
if (this.sender_ready_promise) {
|
|
328
|
+
await this.sender_ready_promise;
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (!isNodeRuntime()) {
|
|
332
|
+
throw new Error("TachyonEventBridge is only supported in Node.js runtimes");
|
|
333
|
+
}
|
|
334
|
+
const promise = new Promise((resolve, reject) => {
|
|
335
|
+
const worker = new Worker(TACHYON_SENDER_WORKER_CODE, {
|
|
336
|
+
eval: true,
|
|
337
|
+
workerData: { path: this.path, connect_timeout_ms: TACHYON_CONNECT_TIMEOUT_MS }
|
|
338
|
+
});
|
|
339
|
+
let resolved = false;
|
|
340
|
+
worker.on("message", (msg) => {
|
|
341
|
+
if (msg.type === "ready") {
|
|
342
|
+
resolved = true;
|
|
343
|
+
resolve();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (msg.type === "sent" && typeof msg.id === "number") {
|
|
347
|
+
const pending = this.pending_sends.get(msg.id);
|
|
348
|
+
if (pending) {
|
|
349
|
+
this.pending_sends.delete(msg.id);
|
|
350
|
+
pending.resolve();
|
|
351
|
+
}
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (msg.type === "send_error" && typeof msg.id === "number") {
|
|
355
|
+
const pending = this.pending_sends.get(msg.id);
|
|
356
|
+
if (pending) {
|
|
357
|
+
this.pending_sends.delete(msg.id);
|
|
358
|
+
pending.reject(new Error(msg.message ?? "TachyonEventBridge send failed"));
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (msg.type === "error") {
|
|
363
|
+
const err = new Error(msg.message ?? "TachyonEventBridge sender error");
|
|
364
|
+
if (!resolved) {
|
|
365
|
+
reject(err);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
console.error("[abxbus] TachyonEventBridge sender error:", err);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
worker.on("error", (err) => {
|
|
372
|
+
if (!resolved) reject(err instanceof Error ? err : new Error(String(err)));
|
|
373
|
+
else console.error("[abxbus] TachyonEventBridge sender worker crashed:", err);
|
|
374
|
+
});
|
|
375
|
+
worker.on("exit", (code) => {
|
|
376
|
+
if (code === 0 && this.closed) return;
|
|
377
|
+
const err = new Error(`TachyonEventBridge sender worker exited with code ${code}`);
|
|
378
|
+
for (const pending of this.pending_sends.values()) pending.reject(err);
|
|
379
|
+
this.pending_sends.clear();
|
|
380
|
+
if (this.sender_worker === worker) {
|
|
381
|
+
this.sender_worker = null;
|
|
382
|
+
this.sender_ready_promise = null;
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
this.sender_worker = worker;
|
|
386
|
+
});
|
|
387
|
+
this.sender_ready_promise = promise;
|
|
388
|
+
try {
|
|
389
|
+
await promise;
|
|
390
|
+
} catch (err) {
|
|
391
|
+
this.sender_ready_promise = null;
|
|
392
|
+
if (this.sender_worker) {
|
|
393
|
+
try {
|
|
394
|
+
await this.sender_worker.terminate();
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
this.sender_worker = null;
|
|
398
|
+
}
|
|
399
|
+
throw err;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
export {
|
|
404
|
+
TachyonEventBridge
|
|
405
|
+
};
|
|
406
|
+
//# sourceMappingURL=TachyonEventBridge.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/TachyonEventBridge.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Tachyon SPSC IPC bridge for forwarding events between runtimes.\n *\n * Tachyon is a same-machine shared-memory ring buffer with single-producer/\n * single-consumer semantics. Each bridge instance plays exactly one role per\n * session (sender XOR listener) \u2014 the role is committed on the first call to\n * `emit()` (sender) or `on()` (listener).\n *\n * Usage:\n * const bridge = new TachyonEventBridge('/tmp/abxbus.sock')\n *\n * // listener side (creates the SHM arena, must exist before sender connects)\n * bridge.on('SomeEvent', handler)\n *\n * // sender side (separate process or instance)\n * const sender = new TachyonEventBridge('/tmp/abxbus.sock')\n * await sender.emit(event)\n */\nimport { existsSync, symlinkSync, unlinkSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { dirname, join } from 'node:path'\nimport { Worker } from 'node:worker_threads'\n\nimport { BaseEvent } from './BaseEvent.js'\nimport { EventBus } from './EventBus.js'\nimport { assertOptionalDependencyAvailable, 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_TACHYON_CAPACITY = 1 << 20\nconst TACHYON_CONNECT_TIMEOUT_MS = 5000\nconst TACHYON_LISTEN_TIMEOUT_MS = 5000\n// Tachyon recv() blocks on a futex that worker.terminate() cannot preempt; the\n// producer signals graceful shutdown by emitting one final message with this\n// reserved type id, which lets the consumer break out of its recv loop.\nconst TACHYON_SHUTDOWN_TYPE_ID = 0xdead\nconst TACHYON_DATA_TYPE_ID = 1\nconst requireForTachyon = createRequire(import.meta.url)\n\nconst ensureTachyonNativeLayout = (): void => {\n try {\n const package_json = requireForTachyon.resolve('@tachyon-ipc/core/package.json')\n const core_dir = dirname(package_json)\n const expected_build_dir = join(dirname(core_dir), 'build')\n const actual_build_dir = join(core_dir, 'build')\n if (!existsSync(expected_build_dir) && existsSync(actual_build_dir)) {\n symlinkSync(actual_build_dir, expected_build_dir, 'dir')\n }\n } catch {\n // Optional dependency availability and import errors are reported by the caller.\n }\n}\n\nconst TACHYON_NATIVE_LAYOUT_FIX = `\nconst ensureTachyonNativeLayout = () => {\n try {\n const fs = require('node:fs')\n const path = require('node:path')\n const packageJson = require.resolve('@tachyon-ipc/core/package.json')\n const coreDir = path.dirname(packageJson)\n const expectedBuildDir = path.join(path.dirname(coreDir), 'build')\n const actualBuildDir = path.join(coreDir, 'build')\n if (!fs.existsSync(expectedBuildDir) && fs.existsSync(actualBuildDir)) {\n fs.symlinkSync(actualBuildDir, expectedBuildDir, 'dir')\n }\n } catch {}\n}\nensureTachyonNativeLayout()\n`\n\nconst TACHYON_LISTENER_WORKER_CODE = `\nconst { parentPort, workerData } = require('node:worker_threads')\n${TACHYON_NATIVE_LAYOUT_FIX}\n\nconst SHUTDOWN_TYPE_ID = ${TACHYON_SHUTDOWN_TYPE_ID}\n\nconst probeListenerAlive = (path) => new Promise((resolve) => {\n const net = require('node:net')\n const sock = net.createConnection(path)\n const settle = (alive) => {\n try { sock.destroy() } catch {}\n resolve(alive)\n }\n sock.setTimeout(50, () => settle(false))\n sock.once('connect', () => settle(true))\n sock.once('error', () => settle(false))\n})\n\nconst main = async () => {\n const { Bus } = await import('@tachyon-ipc/core')\n const fs = require('node:fs')\n const { path, capacity } = workerData\n let bus\n try {\n bus = Bus.listen(path, capacity)\n } catch (firstErr) {\n // The bind failed because the path is in use. If a live listener owns it, propagate\n // the error so the user can resolve the conflict; if it's a stale socket from a\n // previous crash, unlink it and retry exactly once. Refuse to unlink anything that\n // isn't a unix socket \u2014 we have no business deleting an unrelated regular file.\n const alive = await probeListenerAlive(path)\n if (alive || !fs.existsSync(path)) throw firstErr\n let st\n try { st = fs.statSync(path) } catch { throw firstErr }\n if (!st.isSocket()) throw firstErr\n try { fs.unlinkSync(path) } catch {}\n bus = Bus.listen(path, capacity)\n }\n parentPort.postMessage({ type: 'ready' })\n while (true) {\n let msg\n try {\n msg = bus.recv()\n } catch (err) {\n parentPort.postMessage({ type: 'error', message: err && err.message ? err.message : String(err) })\n break\n }\n if (msg.typeId === SHUTDOWN_TYPE_ID) break\n parentPort.postMessage({ type: 'message', data: msg.data, typeId: msg.typeId })\n }\n try { bus.close && bus.close() } catch {}\n}\n\nmain().catch((err) => {\n if (parentPort) {\n parentPort.postMessage({ type: 'error', message: err && err.message ? err.message : String(err) })\n }\n}).finally(() => process.exit(0))\n`\n\nconst TACHYON_SENDER_WORKER_CODE = `\nconst { parentPort, workerData } = require('node:worker_threads')\n${TACHYON_NATIVE_LAYOUT_FIX}\n\nconst SHUTDOWN_TYPE_ID = ${TACHYON_SHUTDOWN_TYPE_ID}\nconst DATA_TYPE_ID = ${TACHYON_DATA_TYPE_ID}\n\nconst main = async () => {\n const { Bus } = await import('@tachyon-ipc/core')\n const { path, connect_timeout_ms } = workerData\n let bus = null\n let last_err = null\n const deadline = Date.now() + connect_timeout_ms\n while (Date.now() < deadline) {\n try {\n bus = Bus.connect(path)\n break\n } catch (err) {\n last_err = err\n await new Promise((resolve) => setTimeout(resolve, 10))\n }\n }\n if (!bus) {\n parentPort.postMessage({ type: 'error', message: 'TachyonEventBridge sender failed to connect: ' + (last_err && last_err.message ? last_err.message : String(last_err)) })\n return\n }\n parentPort.postMessage({ type: 'ready' })\n parentPort.on('message', (msg) => {\n if (!msg) return\n if (msg.type === 'send') {\n try {\n bus.send(Buffer.from(msg.payload), DATA_TYPE_ID)\n parentPort.postMessage({ type: 'sent', id: msg.id })\n } catch (err) {\n parentPort.postMessage({ type: 'send_error', id: msg.id, message: err && err.message ? err.message : String(err) })\n }\n return\n }\n if (msg.type === 'close') {\n try { bus.send(Buffer.alloc(0), SHUTDOWN_TYPE_ID) } catch {}\n try { bus.close && bus.close() } catch {}\n process.exit(0)\n }\n })\n}\n\nmain().catch((err) => {\n if (parentPort) {\n parentPort.postMessage({ type: 'error', message: err && err.message ? err.message : String(err) })\n }\n})\n`\n\ntype SendResolver = { resolve: () => void; reject: (err: Error) => void }\n\nexport class TachyonEventBridge {\n readonly path: string\n readonly capacity: number\n readonly name: string\n\n private readonly inbound_bus: EventBus\n private listener_worker: Worker | null\n // Sticky: `listener_worker` may be cleared mid-session (graceful exit, retry path),\n // but the socket on disk is still ours to unlink in close().\n private acted_as_listener: boolean\n private listener_startup_error: Error | null\n private sender_worker: Worker | null\n private sender_ready_promise: Promise<void> | null\n private send_seq: number\n private pending_sends: Map<number, SendResolver>\n private closed: boolean\n\n constructor(path: string, capacity: number = DEFAULT_TACHYON_CAPACITY, name?: string) {\n if (!path) throw new Error('TachyonEventBridge path must not be empty')\n if (capacity <= 0 || (capacity & (capacity - 1)) !== 0) {\n throw new Error(`TachyonEventBridge capacity must be a positive power of two, got: ${capacity}`)\n }\n assertOptionalDependencyAvailable('TachyonEventBridge', '@tachyon-ipc/core')\n ensureTachyonNativeLayout()\n\n this.path = path\n this.capacity = capacity\n this.name = name ?? `TachyonEventBridge_${randomSuffix()}`\n this.inbound_bus = new EventBus(this.name, { max_history_size: 0 })\n this.listener_worker = null\n this.acted_as_listener = false\n this.listener_startup_error = null\n this.sender_worker = null\n this.sender_ready_promise = null\n this.send_seq = 0\n this.pending_sends = new Map()\n this.closed = false\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 // Fail fast before spawning a sender worker / connecting to the listener so a\n // post-close emit() doesn't leak an extra worker for an instance that is going away.\n if (this.closed) throw new Error('TachyonEventBridge is closed')\n await this.ensureSenderConnected()\n if (this.closed || !this.sender_worker) {\n throw new Error('TachyonEventBridge is closed')\n }\n const payload = Buffer.from(JSON.stringify(event.toJSON()))\n const id = ++this.send_seq\n await new Promise<void>((resolve, reject) => {\n this.pending_sends.set(id, { resolve, reject })\n this.sender_worker!.postMessage({ type: 'send', id, payload })\n })\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 // Role is committed lazily on first on() / emit(). For listener-side bridges,\n // await the underlying socket bind so callers that need fail-fast readiness\n // (peers about to connect, tests writing a ready_path file) can rely on it.\n const worker = this.listener_worker\n if (!worker) return\n const deadline = Date.now() + TACHYON_LISTEN_TIMEOUT_MS\n let path_first_seen_at: number | null = null\n while (Date.now() < deadline) {\n if (this.listener_startup_error) throw this.listener_startup_error\n // acted_as_listener is set when the worker posts 'ready' \u2014 the only signal that\n // *we* (not some other process) actually own the socket file at `path`.\n if (this.acted_as_listener) return\n if (existsSync(this.path)) {\n if (path_first_seen_at === null) {\n path_first_seen_at = Date.now()\n } else if (Date.now() - path_first_seen_at >= 200) {\n // The path exists, but it might belong to another process. After a brief\n // grace window with no startup error and no ready signal, assume our worker\n // bound it (it would otherwise have raised EADDRINUSE almost immediately).\n return\n }\n }\n await new Promise((resolve) => setTimeout(resolve, 5))\n }\n if (this.listener_startup_error) throw this.listener_startup_error\n // Tear down the worker that never bound so a later on() can spawn a fresh one.\n // Use .catch() so a terminate() rejection doesn't bubble up as an unhandled\n // promise rejection (which crashes Node by default in newer versions).\n worker.terminate().catch(() => {\n /* ignore */\n })\n if (this.listener_worker === worker) this.listener_worker = null\n throw new Error(`TachyonEventBridge listener did not bind socket ${this.path} within ${TACHYON_LISTEN_TIMEOUT_MS}ms`)\n }\n\n async close(): Promise<void> {\n this.closed = true\n if (this.sender_worker) {\n const sender_exited = new Promise<void>((resolve) => {\n this.sender_worker!.once('exit', () => resolve())\n })\n try {\n this.sender_worker.postMessage({ type: 'close' })\n } catch {\n // worker may already be gone\n }\n // The sender flushes a SHUTDOWN_TYPE_ID sentinel and self-exits in response\n // to `close`; await that natural exit before terminating to avoid orphaning.\n await Promise.race([sender_exited, new Promise((resolve) => setTimeout(resolve, 1000))])\n try {\n await this.sender_worker.terminate()\n } catch {\n // ignore\n }\n this.sender_worker = null\n }\n if (this.listener_worker) {\n const listener = this.listener_worker\n this.listener_worker = null\n // The listener exits naturally once it consumes the shutdown sentinel.\n const listener_exited = new Promise<void>((resolve) => {\n listener.once('exit', () => resolve())\n })\n await Promise.race([listener_exited, new Promise((resolve) => setTimeout(resolve, 1000))])\n // worker.terminate() cannot preempt a futex-blocked Tachyon recv() (see the\n // SHUTDOWN_TYPE_ID comment above), so a listener-only instance that never\n // received a sentinel would hang forever if we awaited it. Fire-and-forget the\n // terminate and bound how long we'll wait for it to come back; if it doesn't,\n // unref the worker so the parent Node process can still exit (the worker is\n // daemon-mode and dies with the process either way).\n const terminate_promise = listener.terminate().catch(() => undefined)\n const terminate_settled = await Promise.race([\n terminate_promise.then(() => true),\n new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 500)),\n ])\n if (!terminate_settled) {\n try {\n listener.unref()\n } catch {\n // ignore\n }\n }\n }\n for (const pending of this.pending_sends.values()) {\n pending.reject(new Error('TachyonEventBridge closed'))\n }\n this.pending_sends.clear()\n // Only the side that bound the socket (the listener) owns the path on disk.\n // Sender-only instances must leave it alone so other senders/listeners can keep using it.\n if (this.acted_as_listener && existsSync(this.path)) {\n try {\n unlinkSync(this.path)\n } catch {\n // ignore\n }\n }\n this.inbound_bus.destroy()\n }\n\n private ensureListenerStarted(): void {\n if (this.closed) throw new Error('TachyonEventBridge is closed')\n if (this.listener_worker) return\n if (!isNodeRuntime()) {\n throw new Error('TachyonEventBridge is only supported in Node.js runtimes')\n }\n // The worker probes the path before unlinking \u2014 only stale sockets (no live\n // listener) get cleared. This avoids clobbering a listener owned by another\n // process when two bridges race on the same path.\n const worker = new Worker(TACHYON_LISTENER_WORKER_CODE, {\n eval: true,\n workerData: { path: this.path, capacity: this.capacity },\n })\n this.listener_startup_error = null\n worker.on('message', (msg: { type: string; data?: Uint8Array; typeId?: number; message?: string }) => {\n if (msg.type === 'ready') {\n // Bus.listen returning means *this* worker completed the bind+handshake; only\n // now can we safely claim socket ownership for close()'s unlink path.\n this.acted_as_listener = true\n } else if (msg.type === 'message' && msg.data) {\n try {\n const text = Buffer.from(msg.data).toString('utf-8')\n const payload = JSON.parse(text)\n const event = BaseEvent.fromJSON(payload).eventReset()\n this.inbound_bus.emit(event)\n } catch {\n // ignore malformed payloads\n }\n } else if (msg.type === 'error') {\n // Surface the failure so a concurrent start() can fail fast instead of\n // hanging until the bind-wait deadline.\n this.listener_startup_error = new Error(msg.message ?? 'TachyonEventBridge listener error')\n console.error('[abxbus] TachyonEventBridge listener error:', msg.message)\n }\n })\n worker.on('error', (err: unknown) => {\n this.listener_startup_error = err instanceof Error ? err : new Error(String(err))\n console.error('[abxbus] TachyonEventBridge listener worker crashed:', err)\n })\n // Drop the cached reference if the worker dies (crash, init failure, or natural exit\n // after consuming a shutdown sentinel) so a subsequent on() call can spin up a fresh one.\n worker.on('exit', () => {\n if (this.listener_worker === worker) this.listener_worker = null\n // Only flag a startup error if we never confirmed a bind AND the bridge isn't\n // shutting down; a normal shutdown exit (close(), or recv loop after consuming a\n // sentinel) shouldn't poison future start() calls.\n if (!this.acted_as_listener && !this.listener_startup_error && !this.closed) {\n this.listener_startup_error = new Error('TachyonEventBridge listener worker exited before binding')\n }\n })\n this.listener_worker = worker\n // on() returns immediately so the Node event loop isn't frozen on startup; peers\n // that try Bus.connect before this worker finishes binding are covered by the\n // sender-side connect retry loop (see TACHYON_CONNECT_TIMEOUT_MS) and by the\n // worker's 'ready' message which flips acted_as_listener once bind completes.\n }\n\n private async ensureSenderConnected(): Promise<void> {\n if (this.sender_ready_promise) {\n await this.sender_ready_promise\n return\n }\n if (!isNodeRuntime()) {\n throw new Error('TachyonEventBridge is only supported in Node.js runtimes')\n }\n const promise = new Promise<void>((resolve, reject) => {\n const worker = new Worker(TACHYON_SENDER_WORKER_CODE, {\n eval: true,\n workerData: { path: this.path, connect_timeout_ms: TACHYON_CONNECT_TIMEOUT_MS },\n })\n let resolved = false\n worker.on('message', (msg: { type: string; id?: number; message?: string }) => {\n if (msg.type === 'ready') {\n resolved = true\n resolve()\n return\n }\n if (msg.type === 'sent' && typeof msg.id === 'number') {\n const pending = this.pending_sends.get(msg.id)\n if (pending) {\n this.pending_sends.delete(msg.id)\n pending.resolve()\n }\n return\n }\n if (msg.type === 'send_error' && typeof msg.id === 'number') {\n const pending = this.pending_sends.get(msg.id)\n if (pending) {\n this.pending_sends.delete(msg.id)\n pending.reject(new Error(msg.message ?? 'TachyonEventBridge send failed'))\n }\n return\n }\n if (msg.type === 'error') {\n const err = new Error(msg.message ?? 'TachyonEventBridge sender error')\n if (!resolved) {\n reject(err)\n return\n }\n console.error('[abxbus] TachyonEventBridge sender error:', err)\n }\n })\n worker.on('error', (err) => {\n if (!resolved) reject(err instanceof Error ? err : new Error(String(err)))\n else console.error('[abxbus] TachyonEventBridge sender worker crashed:', err)\n })\n worker.on('exit', (code) => {\n // The sender worker exits with code 0 only when responding to our `close`\n // message; any other exit means the worker died unexpectedly and pending\n // sends would otherwise hang forever.\n if (code === 0 && this.closed) return\n const err = new Error(`TachyonEventBridge sender worker exited with code ${code}`)\n for (const pending of this.pending_sends.values()) pending.reject(err)\n this.pending_sends.clear()\n if (this.sender_worker === worker) {\n this.sender_worker = null\n this.sender_ready_promise = null\n }\n })\n this.sender_worker = worker\n })\n this.sender_ready_promise = promise\n try {\n await promise\n } catch (err) {\n // Allow a later emit() to retry once the listener becomes available.\n this.sender_ready_promise = null\n if (this.sender_worker) {\n try {\n await this.sender_worker.terminate()\n } catch {\n /* ignore */\n }\n this.sender_worker = null\n }\n throw err\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/esm/bridges.js
CHANGED
|
@@ -6,6 +6,7 @@ import { SQLiteEventBridge } from "./SQLiteEventBridge.js";
|
|
|
6
6
|
import { NATSEventBridge } from "./NATSEventBridge.js";
|
|
7
7
|
import { RedisEventBridge } from "./RedisEventBridge.js";
|
|
8
8
|
import { PostgresEventBridge } from "./PostgresEventBridge.js";
|
|
9
|
+
import { TachyonEventBridge } from "./TachyonEventBridge.js";
|
|
9
10
|
export {
|
|
10
11
|
EventBridge,
|
|
11
12
|
HTTPEventBridge,
|
|
@@ -14,6 +15,7 @@ export {
|
|
|
14
15
|
PostgresEventBridge,
|
|
15
16
|
RedisEventBridge,
|
|
16
17
|
SQLiteEventBridge,
|
|
17
|
-
SocketEventBridge
|
|
18
|
+
SocketEventBridge,
|
|
19
|
+
TachyonEventBridge
|
|
18
20
|
};
|
|
19
21
|
//# sourceMappingURL=bridges.js.map
|
package/dist/esm/bridges.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/bridges.ts"],
|
|
4
|
-
"sourcesContent": ["export { EventBridge } from './EventBridge.js'\nexport { HTTPEventBridge } from './HTTPEventBridge.js'\nexport type { HTTPEventBridgeOptions } from './HTTPEventBridge.js'\nexport { SocketEventBridge } from './SocketEventBridge.js'\nexport { JSONLEventBridge } from './JSONLEventBridge.js'\nexport { SQLiteEventBridge } from './SQLiteEventBridge.js'\nexport { NATSEventBridge } from './NATSEventBridge.js'\nexport { RedisEventBridge } from './RedisEventBridge.js'\nexport { PostgresEventBridge } from './PostgresEventBridge.js'\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAEhC,SAAS,yBAAyB;AAClC,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,2BAA2B;",
|
|
4
|
+
"sourcesContent": ["export { EventBridge } from './EventBridge.js'\nexport { HTTPEventBridge } from './HTTPEventBridge.js'\nexport type { HTTPEventBridgeOptions } from './HTTPEventBridge.js'\nexport { SocketEventBridge } from './SocketEventBridge.js'\nexport { JSONLEventBridge } from './JSONLEventBridge.js'\nexport { SQLiteEventBridge } from './SQLiteEventBridge.js'\nexport { NATSEventBridge } from './NATSEventBridge.js'\nexport { RedisEventBridge } from './RedisEventBridge.js'\nexport { PostgresEventBridge } from './PostgresEventBridge.js'\nexport { TachyonEventBridge } from './TachyonEventBridge.js'\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAEhC,SAAS,yBAAyB;AAClC,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,2BAA2B;AACpC,SAAS,0BAA0B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["export { BaseEvent, BaseEventSchema } from './BaseEvent.js'\nexport { EventHistory } from './EventHistory.js'\nexport type { EventHistoryFindOptions, EventHistoryTrimOptions } from './EventHistory.js'\nexport { EventResult } from './EventResult.js'\nexport { EventBus } from './EventBus.js'\nexport type { EventBusJSON, EventBusOptions } from './EventBus.js'\nexport { EventBridge } from './EventBridge.js'\nexport { HTTPEventBridge } from './HTTPEventBridge.js'\nexport type { HTTPEventBridgeOptions } from './HTTPEventBridge.js'\nexport { SocketEventBridge } from './SocketEventBridge.js'\nexport { JSONLEventBridge } from './JSONLEventBridge.js'\nexport { SQLiteEventBridge } from './SQLiteEventBridge.js'\nexport type { EventBusMiddleware, EventBusMiddlewareCtor, EventBusMiddlewareInput } from './EventBusMiddleware.js'\nexport { monotonicDatetime } from './helpers.js'\nexport {\n EventHandlerTimeoutError,\n EventHandlerCancelledError,\n EventHandlerAbortedError,\n EventHandlerResultSchemaError,\n} from './EventHandler.js'\nexport type {\n EventConcurrencyMode,\n EventHandlerConcurrencyMode,\n EventHandlerCompletionMode,\n EventBusInterfaceForLockManager,\n} from './LockManager.js'\nexport type {
|
|
5
|
-
"mappings": "AAAA,SAAS,WAAW,uBAAuB;AAC3C,SAAS,oBAAoB;AAE7B,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AAEzB,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAEhC,SAAS,yBAAyB;AAClC,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAElC,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["export { BaseEvent, BaseEventSchema } from './BaseEvent.js'\nexport { EventHistory } from './EventHistory.js'\nexport type { EventHistoryFilterOptions, EventHistoryFindOptions, EventHistoryTrimOptions } from './EventHistory.js'\nexport { EventResult } from './EventResult.js'\nexport { EventBus } from './EventBus.js'\nexport type { EventBusJSON, EventBusOptions } from './EventBus.js'\nexport { EventBridge } from './EventBridge.js'\nexport { HTTPEventBridge } from './HTTPEventBridge.js'\nexport type { HTTPEventBridgeOptions } from './HTTPEventBridge.js'\nexport { SocketEventBridge } from './SocketEventBridge.js'\nexport { JSONLEventBridge } from './JSONLEventBridge.js'\nexport { SQLiteEventBridge } from './SQLiteEventBridge.js'\nexport type { EventBusMiddleware, EventBusMiddlewareCtor, EventBusMiddlewareInput } from './EventBusMiddleware.js'\nexport { monotonicDatetime } from './helpers.js'\nexport {\n EventHandlerTimeoutError,\n EventHandlerCancelledError,\n EventHandlerAbortedError,\n EventHandlerResultSchemaError,\n} from './EventHandler.js'\nexport type {\n EventConcurrencyMode,\n EventHandlerConcurrencyMode,\n EventHandlerCompletionMode,\n EventBusInterfaceForLockManager,\n} from './LockManager.js'\nexport type {\n EventClass,\n EventHandlerCallable as EventHandler,\n EventPattern,\n EventStatus,\n FilterOptions,\n FindOptions,\n FindWindow,\n} from './types.js'\nexport { retry, clearSemaphoreRegistry, RetryTimeoutError, SemaphoreTimeoutError } from './retry.js'\nexport type { RetryOptions } from './retry.js'\nexport { events_suck } from './events_suck.js'\nexport type { EventsSuckClient, EventsSuckClientClass, GeneratedEvents } from './events_suck.js'\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,WAAW,uBAAuB;AAC3C,SAAS,oBAAoB;AAE7B,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AAEzB,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAEhC,SAAS,yBAAyB;AAClC,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAElC,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAgBP,SAAS,OAAO,wBAAwB,mBAAmB,6BAA6B;AAExF,SAAS,mBAAmB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/esm/types.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/types.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport type { BaseEvent } from './BaseEvent.js'\n\nexport type EventStatus = 'pending' | 'started' | 'completed'\n\nexport type EventClass<T extends BaseEvent = BaseEvent> = { event_type?: string } & (new (...args: any[]) => T)\n\nexport type EventPattern<T extends BaseEvent = BaseEvent> = string | EventClass<T>\n\nexport type EventWithResultSchema<TResult> = BaseEvent & { __event_result_type__?: TResult }\n\nexport type EventResultType<TEvent extends BaseEvent> = TEvent extends { __event_result_type__?: infer TResult } ? TResult : unknown\n\nexport type EventResultTypeConstructor = StringConstructor | NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor\n\nexport type EventResultTypeInput = z.ZodTypeAny | EventResultTypeConstructor | unknown\n\nexport type EventHandlerReturn<T extends BaseEvent = BaseEvent> = EventResultType<T> | BaseEvent | null | void\n\nexport type EventHandlerCallable<T extends BaseEvent = BaseEvent> = (event: T) => EventHandlerReturn<T> | Promise<EventHandlerReturn<T>>\n\n// For string and wildcard subscriptions we cannot reliably infer which event\n// type will arrive, so return type checking intentionally degrades to unknown.\nexport type UntypedEventHandlerFunction<T extends BaseEvent = BaseEvent> = (\n event: T\n) => EventHandlerReturn<T> | unknown | Promise<EventHandlerReturn<T> | unknown>\n\nexport type FindWindow = boolean | number\n\ntype FindReservedOptionKeys = 'past' | 'future' | 'child_of'\n\ntype EventFilterFields<T extends BaseEvent> = {\n [K in keyof T as string extends K\n ? never\n : number extends K\n ? never\n : symbol extends K\n ? never\n : K extends FindReservedOptionKeys\n ? never\n : T[K] extends (...args: any[]) => any\n ? never\n : K]?: T[K]\n}\n\nexport type FindOptions<T extends BaseEvent = BaseEvent> = {\n past?: FindWindow\n future?: FindWindow\n child_of?: BaseEvent | null\n} & EventFilterFields<T> &\n Record<string, unknown>\n\nexport const normalizeEventPattern = (event_pattern: EventPattern | '*'): string | '*' => {\n if (event_pattern === '*') {\n return '*'\n }\n if (typeof event_pattern === 'string') {\n return event_pattern\n }\n const event_type = (event_pattern as { event_type?: unknown }).event_type\n if (typeof event_type === 'string' && event_type.length > 0 && event_type !== 'BaseEvent') {\n return event_type\n }\n const class_name = (event_pattern as { name?: unknown }).name\n if (typeof class_name === 'string' && class_name.length > 0 && class_name !== 'BaseEvent') {\n return class_name\n }\n let preview: string\n try {\n const encoded = JSON.stringify(event_pattern)\n preview = typeof encoded === 'string' ? encoded.slice(0, 30) : String(event_pattern).slice(0, 30)\n } catch {\n preview = String(event_pattern).slice(0, 30)\n }\n throw new Error('bus.on(match_pattern, ...) must be a string event type, \"*\", or a BaseEvent class, got: ' + preview)\n}\n\nexport const isZodSchema = (value: unknown): value is z.ZodTypeAny => !!value && typeof (value as z.ZodTypeAny).safeParse === 'function'\n\nexport const eventResultTypeFromConstructor = (value: unknown): z.ZodTypeAny | undefined => {\n if (value === String) {\n return z.string()\n }\n if (value === Number) {\n return z.number()\n }\n if (value === Boolean) {\n return z.boolean()\n }\n if (value === Array) {\n return z.array(z.unknown())\n }\n if (value === Object) {\n return z.record(z.string(), z.unknown())\n }\n return undefined\n}\n\nexport const extractZodShape = (raw: Record<string, unknown>): z.ZodRawShape => {\n const shape: Record<string, z.ZodTypeAny> = {}\n for (const [key, value] of Object.entries(raw)) {\n if (key === 'event_result_type') continue\n if (isZodSchema(value)) shape[key] = value\n }\n return shape as z.ZodRawShape\n}\n\nexport const toJsonSchema = (schema: unknown): unknown => {\n if (!schema || !isZodSchema(schema)) return schema\n const zod_any = z as unknown as { toJSONSchema: (input: z.ZodTypeAny) => unknown }\n // Cross-language roundtrips preserve core structural types; constraint keywords may not roundtrip exactly.\n return zod_any.toJSONSchema(schema)\n}\n\nexport const fromJsonSchema = (schema: unknown): z.ZodTypeAny => {\n const zod_any = z as unknown as { fromJSONSchema: (input: unknown) => z.ZodTypeAny }\n return zod_any.fromJSONSchema(schema)\n}\n\nexport const normalizeEventResultType = (value: EventResultTypeInput): z.ZodTypeAny | undefined => {\n if (value === undefined || value === null) {\n return undefined\n }\n if (isZodSchema(value)) {\n return value\n }\n const constructor_schema = eventResultTypeFromConstructor(value)\n if (constructor_schema) {\n return constructor_schema\n }\n return fromJsonSchema(value)\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { BaseEvent } from './BaseEvent.js'\n\nexport type EventStatus = 'pending' | 'started' | 'completed'\n\nexport type EventClass<T extends BaseEvent = BaseEvent> = { event_type?: string } & (new (...args: any[]) => T)\n\nexport type EventPattern<T extends BaseEvent = BaseEvent> = string | EventClass<T>\n\nexport type EventWithResultSchema<TResult> = BaseEvent & { __event_result_type__?: TResult }\n\nexport type EventResultType<TEvent extends BaseEvent> = TEvent extends { __event_result_type__?: infer TResult } ? TResult : unknown\n\nexport type EventResultTypeConstructor = StringConstructor | NumberConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor\n\nexport type EventResultTypeInput = z.ZodTypeAny | EventResultTypeConstructor | unknown\n\nexport type EventHandlerReturn<T extends BaseEvent = BaseEvent> = EventResultType<T> | BaseEvent | null | void\n\nexport type EventHandlerCallable<T extends BaseEvent = BaseEvent> = (event: T) => EventHandlerReturn<T> | Promise<EventHandlerReturn<T>>\n\n// For string and wildcard subscriptions we cannot reliably infer which event\n// type will arrive, so return type checking intentionally degrades to unknown.\nexport type UntypedEventHandlerFunction<T extends BaseEvent = BaseEvent> = (\n event: T\n) => EventHandlerReturn<T> | unknown | Promise<EventHandlerReturn<T> | unknown>\n\nexport type FindWindow = boolean | number\n\ntype FindReservedOptionKeys = 'past' | 'future' | 'child_of'\n\ntype EventFilterFields<T extends BaseEvent> = {\n [K in keyof T as string extends K\n ? never\n : number extends K\n ? never\n : symbol extends K\n ? never\n : K extends FindReservedOptionKeys\n ? never\n : T[K] extends (...args: any[]) => any\n ? never\n : K]?: T[K]\n}\n\nexport type FindOptions<T extends BaseEvent = BaseEvent> = {\n past?: FindWindow\n future?: FindWindow\n child_of?: BaseEvent | null\n} & EventFilterFields<T> &\n Record<string, unknown>\n\nexport type FilterOptions<T extends BaseEvent = BaseEvent> = FindOptions<T> & { limit?: number | null }\n\nexport const normalizeEventPattern = (event_pattern: EventPattern | '*'): string | '*' => {\n if (event_pattern === '*') {\n return '*'\n }\n if (typeof event_pattern === 'string') {\n return event_pattern\n }\n const event_type = (event_pattern as { event_type?: unknown }).event_type\n if (typeof event_type === 'string' && event_type.length > 0 && event_type !== 'BaseEvent') {\n return event_type\n }\n const class_name = (event_pattern as { name?: unknown }).name\n if (typeof class_name === 'string' && class_name.length > 0 && class_name !== 'BaseEvent') {\n return class_name\n }\n let preview: string\n try {\n const encoded = JSON.stringify(event_pattern)\n preview = typeof encoded === 'string' ? encoded.slice(0, 30) : String(event_pattern).slice(0, 30)\n } catch {\n preview = String(event_pattern).slice(0, 30)\n }\n throw new Error('bus.on(match_pattern, ...) must be a string event type, \"*\", or a BaseEvent class, got: ' + preview)\n}\n\nexport const isZodSchema = (value: unknown): value is z.ZodTypeAny => !!value && typeof (value as z.ZodTypeAny).safeParse === 'function'\n\nexport const eventResultTypeFromConstructor = (value: unknown): z.ZodTypeAny | undefined => {\n if (value === String) {\n return z.string()\n }\n if (value === Number) {\n return z.number()\n }\n if (value === Boolean) {\n return z.boolean()\n }\n if (value === Array) {\n return z.array(z.unknown())\n }\n if (value === Object) {\n return z.record(z.string(), z.unknown())\n }\n return undefined\n}\n\nexport const extractZodShape = (raw: Record<string, unknown>): z.ZodRawShape => {\n const shape: Record<string, z.ZodTypeAny> = {}\n for (const [key, value] of Object.entries(raw)) {\n if (key === 'event_result_type') continue\n if (isZodSchema(value)) shape[key] = value\n }\n return shape as z.ZodRawShape\n}\n\nexport const toJsonSchema = (schema: unknown): unknown => {\n if (!schema || !isZodSchema(schema)) return schema\n const zod_any = z as unknown as { toJSONSchema: (input: z.ZodTypeAny) => unknown }\n // Cross-language roundtrips preserve core structural types; constraint keywords may not roundtrip exactly.\n return zod_any.toJSONSchema(schema)\n}\n\nexport const fromJsonSchema = (schema: unknown): z.ZodTypeAny => {\n const zod_any = z as unknown as { fromJSONSchema: (input: unknown) => z.ZodTypeAny }\n return zod_any.fromJSONSchema(schema)\n}\n\nexport const normalizeEventResultType = (value: EventResultTypeInput): z.ZodTypeAny | undefined => {\n if (value === undefined || value === null) {\n return undefined\n }\n if (isZodSchema(value)) {\n return value\n }\n const constructor_schema = eventResultTypeFromConstructor(value)\n if (constructor_schema) {\n return constructor_schema\n }\n return fromJsonSchema(value)\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAsDX,MAAM,wBAAwB,CAAC,kBAAoD;AACxF,MAAI,kBAAkB,KAAK;AACzB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,kBAAkB,UAAU;AACrC,WAAO;AAAA,EACT;AACA,QAAM,aAAc,cAA2C;AAC/D,MAAI,OAAO,eAAe,YAAY,WAAW,SAAS,KAAK,eAAe,aAAa;AACzF,WAAO;AAAA,EACT;AACA,QAAM,aAAc,cAAqC;AACzD,MAAI,OAAO,eAAe,YAAY,WAAW,SAAS,KAAK,eAAe,aAAa;AACzF,WAAO;AAAA,EACT;AACA,MAAI;AACJ,MAAI;AACF,UAAM,UAAU,KAAK,UAAU,aAAa;AAC5C,cAAU,OAAO,YAAY,WAAW,QAAQ,MAAM,GAAG,EAAE,IAAI,OAAO,aAAa,EAAE,MAAM,GAAG,EAAE;AAAA,EAClG,QAAQ;AACN,cAAU,OAAO,aAAa,EAAE,MAAM,GAAG,EAAE;AAAA,EAC7C;AACA,QAAM,IAAI,MAAM,6FAA6F,OAAO;AACtH;AAEO,MAAM,cAAc,CAAC,UAA0C,CAAC,CAAC,SAAS,OAAQ,MAAuB,cAAc;AAEvH,MAAM,iCAAiC,CAAC,UAA6C;AAC1F,MAAI,UAAU,QAAQ;AACpB,WAAO,EAAE,OAAO;AAAA,EAClB;AACA,MAAI,UAAU,QAAQ;AACpB,WAAO,EAAE,OAAO;AAAA,EAClB;AACA,MAAI,UAAU,SAAS;AACrB,WAAO,EAAE,QAAQ;AAAA,EACnB;AACA,MAAI,UAAU,OAAO;AACnB,WAAO,EAAE,MAAM,EAAE,QAAQ,CAAC;AAAA,EAC5B;AACA,MAAI,UAAU,QAAQ;AACpB,WAAO,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AAAA,EACzC;AACA,SAAO;AACT;AAEO,MAAM,kBAAkB,CAAC,QAAgD;AAC9E,QAAM,QAAsC,CAAC;AAC7C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,QAAI,QAAQ,oBAAqB;AACjC,QAAI,YAAY,KAAK,EAAG,OAAM,GAAG,IAAI;AAAA,EACvC;AACA,SAAO;AACT;AAEO,MAAM,eAAe,CAAC,WAA6B;AACxD,MAAI,CAAC,UAAU,CAAC,YAAY,MAAM,EAAG,QAAO;AAC5C,QAAM,UAAU;AAEhB,SAAO,QAAQ,aAAa,MAAM;AACpC;AAEO,MAAM,iBAAiB,CAAC,WAAkC;AAC/D,QAAM,UAAU;AAChB,SAAO,QAAQ,eAAe,MAAM;AACtC;AAEO,MAAM,2BAA2B,CAAC,UAA0D;AACjG,MAAI,UAAU,UAAa,UAAU,MAAM;AACzC,WAAO;AAAA,EACT;AACA,MAAI,YAAY,KAAK,GAAG;AACtB,WAAO;AAAA,EACT;AACA,QAAM,qBAAqB,+BAA+B,KAAK;AAC/D,MAAI,oBAAoB;AACtB,WAAO;AAAA,EACT;AACA,SAAO,eAAe,KAAK;AAC7B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -44,7 +44,10 @@ export declare const BaseEventSchema: z.ZodObject<{
|
|
|
44
44
|
}, z.core.$loose>;
|
|
45
45
|
export type BaseEventData = z.infer<typeof BaseEventSchema>;
|
|
46
46
|
export type BaseEventJSON = BaseEventData & Record<string, unknown>;
|
|
47
|
-
type
|
|
47
|
+
type BaseEventFieldName = 'event_id' | 'event_created_at' | 'event_type' | 'event_version' | 'event_timeout' | 'event_slow_timeout' | 'event_handler_timeout' | 'event_handler_slow_timeout' | 'event_blocks_parent_completion' | 'event_parent_id' | 'event_path' | 'event_result_type' | 'event_emitted_by_handler_id' | 'event_pending_bus_count' | 'event_status' | 'event_started_at' | 'event_completed_at' | 'event_results' | 'event_concurrency' | 'event_handler_concurrency' | 'event_handler_completion';
|
|
48
|
+
type BaseEventFields = {
|
|
49
|
+
[K in BaseEventFieldName]: BaseEventData[K];
|
|
50
|
+
};
|
|
48
51
|
export type BaseEventInit<TFields extends Record<string, unknown>> = TFields & Partial<BaseEventFields>;
|
|
49
52
|
type BaseEventSchemaShape = typeof BaseEventSchema.shape;
|
|
50
53
|
export type EventSchema<TShape extends z.ZodRawShape> = z.ZodObject<BaseEventSchemaShape & TShape>;
|
package/dist/types/EventBus.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { EventResult } from './EventResult.js';
|
|
|
4
4
|
import { AsyncLock, type EventConcurrencyMode, type EventHandlerConcurrencyMode, type EventHandlerCompletionMode, LockManager } from './LockManager.js';
|
|
5
5
|
import { EventHandler, type EphemeralFindEventHandler, type EventHandlerJSON } from './EventHandler.js';
|
|
6
6
|
import type { EventBusMiddleware, EventBusMiddlewareInput } from './EventBusMiddleware.js';
|
|
7
|
-
import type { EventClass, EventHandlerCallable, EventPattern, FindOptions, UntypedEventHandlerFunction } from './types.js';
|
|
7
|
+
import type { EventClass, EventHandlerCallable, EventPattern, FilterOptions, FindOptions, UntypedEventHandlerFunction } from './types.js';
|
|
8
8
|
export type EventBusOptions = {
|
|
9
9
|
id?: string;
|
|
10
10
|
max_history_size?: number | null;
|
|
@@ -104,6 +104,10 @@ export declare class EventBus {
|
|
|
104
104
|
find(event_pattern: '*', where: (event: BaseEvent) => boolean, options?: FindOptions<BaseEvent>): Promise<BaseEvent | null>;
|
|
105
105
|
find<T extends BaseEvent>(event_pattern: EventPattern<T>, options?: FindOptions<T>): Promise<T | null>;
|
|
106
106
|
find<T extends BaseEvent>(event_pattern: EventPattern<T>, where: (event: T) => boolean, options?: FindOptions<T>): Promise<T | null>;
|
|
107
|
+
filter(event_pattern: '*', options?: FilterOptions<BaseEvent>): Promise<BaseEvent[]>;
|
|
108
|
+
filter(event_pattern: '*', where: (event: BaseEvent) => boolean, options?: FilterOptions<BaseEvent>): Promise<BaseEvent[]>;
|
|
109
|
+
filter<T extends BaseEvent>(event_pattern: EventPattern<T>, options?: FilterOptions<T>): Promise<T[]>;
|
|
110
|
+
filter<T extends BaseEvent>(event_pattern: EventPattern<T>, where: (event: T) => boolean, options?: FilterOptions<T>): Promise<T[]>;
|
|
107
111
|
private _waitForFutureMatch;
|
|
108
112
|
waitUntilIdle(timeout?: number | null): Promise<boolean>;
|
|
109
113
|
isIdle(): boolean;
|
|
@@ -7,6 +7,9 @@ export type EventHistoryFindOptions = {
|
|
|
7
7
|
event_is_child_of?: (event: BaseEvent, ancestor: BaseEvent) => boolean;
|
|
8
8
|
wait_for_future_match?: (event_pattern: string | '*', matches: (event: BaseEvent) => boolean, future: FindWindow) => Promise<BaseEvent | null>;
|
|
9
9
|
} & Record<string, unknown>;
|
|
10
|
+
export type EventHistoryFilterOptions = EventHistoryFindOptions & {
|
|
11
|
+
limit?: number | null;
|
|
12
|
+
};
|
|
10
13
|
export type EventHistoryTrimOptions<TEvent extends BaseEvent = BaseEvent> = {
|
|
11
14
|
is_event_complete?: (event: TEvent) => boolean;
|
|
12
15
|
on_remove?: (event: TEvent) => void;
|
|
@@ -40,6 +43,8 @@ export declare class EventHistory<TEvent extends BaseEvent = BaseEvent> implemen
|
|
|
40
43
|
static normalizeEventPattern(event_pattern: EventPattern | '*'): string | '*';
|
|
41
44
|
find(event_pattern: '*', where?: (event: TEvent) => boolean, options?: EventHistoryFindOptions): Promise<TEvent | null>;
|
|
42
45
|
find<TMatch extends TEvent>(event_pattern: EventPattern<TMatch>, where?: (event: TMatch) => boolean, options?: EventHistoryFindOptions): Promise<TMatch | null>;
|
|
46
|
+
filter(event_pattern: '*', where?: (event: TEvent) => boolean, options?: EventHistoryFilterOptions): Promise<TEvent[]>;
|
|
47
|
+
filter<TMatch extends TEvent>(event_pattern: EventPattern<TMatch>, where?: (event: TMatch) => boolean, options?: EventHistoryFilterOptions): Promise<TMatch[]>;
|
|
43
48
|
trimEventHistory(options?: EventHistoryTrimOptions<TEvent>): number;
|
|
44
49
|
private eventIsChildOf;
|
|
45
50
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BaseEvent } from './BaseEvent.js';
|
|
2
|
+
import type { EventClass, EventHandlerCallable, UntypedEventHandlerFunction } from './types.js';
|
|
3
|
+
export declare class TachyonEventBridge {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly capacity: number;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
private readonly inbound_bus;
|
|
8
|
+
private listener_worker;
|
|
9
|
+
private acted_as_listener;
|
|
10
|
+
private listener_startup_error;
|
|
11
|
+
private sender_worker;
|
|
12
|
+
private sender_ready_promise;
|
|
13
|
+
private send_seq;
|
|
14
|
+
private pending_sends;
|
|
15
|
+
private closed;
|
|
16
|
+
constructor(path: string, capacity?: 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 ensureListenerStarted;
|
|
24
|
+
private ensureSenderConnected;
|
|
25
|
+
}
|