@interactive-inc/flume 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -17
- package/dist/discord.d.ts +2 -2
- package/dist/discord.js +13 -4
- package/dist/github.d.ts +2 -2
- package/dist/github.js +9 -2
- package/dist/index.d.ts +9 -20
- package/dist/index.js +26 -9
- package/dist/{schedule-reconnect-DSxZJG3h.js → reconnector-BDoJ1xNX.js} +1 -27
- package/dist/safe-json-parse-BWlzGOLl.js +41 -0
- package/dist/slack.d.ts +2 -2
- package/dist/slack.js +13 -4
- package/dist/{types-tnOPBc1p.d.ts → types-Bm9uKUQz.d.ts} +18 -3
- package/package.json +1 -1
- package/dist/safe-json-parse-WCg_x1JS.js +0 -15
package/README.md
CHANGED
|
@@ -29,16 +29,18 @@ const onLog = (log) => console.log(`[${log.level}] ${log.source}/${log.action}:
|
|
|
29
29
|
const flume = new Flume({
|
|
30
30
|
sources: [
|
|
31
31
|
new FlumeDiscordSource({ token: process.env.DISCORD_BOT_TOKEN!, onLog, reconnect: true }),
|
|
32
|
-
new FlumeSlackSource({ appToken: process.env.SLACK_APP_TOKEN!, onLog, reconnect: true }),
|
|
32
|
+
new FlumeSlackSource({ appToken: process.env.SLACK_APP_TOKEN!, botToken: process.env.SLACK_BOT_TOKEN!, onLog, reconnect: true }),
|
|
33
33
|
new FlumeGitHubSource({ token: process.env.GITHUB_TOKEN!, onLog, pollInterval: 60 }),
|
|
34
34
|
],
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
const
|
|
37
|
+
const result = await flume.start((event) => {
|
|
38
38
|
console.log(event.source, event.type, event.meta)
|
|
39
39
|
})
|
|
40
40
|
|
|
41
|
-
if (
|
|
41
|
+
if (!result.ok) throw result.error
|
|
42
|
+
|
|
43
|
+
const running = flume.runningState()!
|
|
42
44
|
|
|
43
45
|
// later
|
|
44
46
|
await running.stop()
|
|
@@ -53,19 +55,21 @@ Flume ──start()──▶ FlumeRunning ──stop()──▶ FlumeStopped
|
|
|
53
55
|
(idle) (running) (terminal)
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
- `Flume.start(handler)` returns `
|
|
58
|
+
- `Flume.start(handler)` returns `FlumeStartResult` — a discriminated union `{ ok: true } | { ok: false; error: Error }`. On `ok: true`, the `FlumeRunning` instance is reachable via `flume.runningState()`. On partial failure (one source fails while another succeeds), the already-started sources are rolled back and `ok: false` is returned with per-source detail in `error.message`.
|
|
57
59
|
- `FlumeRunning.stop()` returns a `FlumeStopped` snapshot. `stop()` is idempotent and concurrent-safe.
|
|
58
60
|
- `FlumeStopped` exposes only `statuses()` — a frozen snapshot of each source's final state. No `start`, no `stop`, no leaking source references.
|
|
59
61
|
- An `AbortSignal` on `Flume` drives an automatic transition to `FlumeStopped`.
|
|
60
62
|
|
|
61
63
|
```ts
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
console.error(
|
|
64
|
+
const result = await flume.start(handler)
|
|
65
|
+
if (!result.ok) {
|
|
66
|
+
console.error(result.error.message)
|
|
65
67
|
// "Flume.start: 1 source(s) failed: slack: connect refused"
|
|
66
68
|
return
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
const running = flume.runningState()!
|
|
72
|
+
|
|
69
73
|
running.start() // type error — `start` is not on FlumeRunning
|
|
70
74
|
|
|
71
75
|
const stopped = await running.stop()
|
|
@@ -87,8 +91,8 @@ const source = new FlumeDiscordSource({
|
|
|
87
91
|
onLog: (log) => console.log(log),
|
|
88
92
|
})
|
|
89
93
|
|
|
90
|
-
const
|
|
91
|
-
if (
|
|
94
|
+
const result = await source.start((event) => { /* ... */ })
|
|
95
|
+
if (!result.ok) throw result.error
|
|
92
96
|
```
|
|
93
97
|
|
|
94
98
|
## Sub-entries
|
|
@@ -97,7 +101,7 @@ Each source has a dedicated entry — importing one does not pull the others int
|
|
|
97
101
|
|
|
98
102
|
| sub-entry | exports |
|
|
99
103
|
|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
100
|
-
| `@interactive-inc/flume` | `Flume`, `FlumeRunning`, `FlumeStopped`, `FlumeLogger`, `FlumeReconnector`, `
|
|
104
|
+
| `@interactive-inc/flume` | `Flume`, `FlumeRunning`, `FlumeStopped`, `FlumeLogger`, `FlumeReconnector`, `createFlumeDefaultDeps`, errors, types |
|
|
101
105
|
| `@interactive-inc/flume/discord` | `FlumeDiscordSource`, `FlumeDiscordGateway`, `FlumeDiscordGatewayIntents`, `FlumeDiscordHeartbeat`, `FlumeDiscordGatewaySession`, `parseDiscordGatewayMessage`, `extractDiscordMeta`, `FlumeGatewayMessageSchema` |
|
|
102
106
|
| `@interactive-inc/flume/slack` | `FlumeSlackSource`, `FlumeSlackSocketMode`, `FlumeSlackSeenCache`, `obtainSlackUrl`, `extractSlackMeta`, `FlumeSlackEnvelopeSchema`, `FlumeSlackConnectionResponseSchema` |
|
|
103
107
|
| `@interactive-inc/flume/github` | `FlumeGitHubSource`, `FlumeGitHubPoller`, `FlumeGitHubSeenCache`, `extractGitHubMeta`, `FlumeGitHubNotificationSchema` |
|
|
@@ -207,13 +211,16 @@ const flume = new Flume({
|
|
|
207
211
|
signal: controller.signal,
|
|
208
212
|
})
|
|
209
213
|
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
```ts
|
|
215
|
+
const result = await flume.start(handler)
|
|
216
|
+
if (!result.ok) throw result.error
|
|
217
|
+
|
|
218
|
+
const running = flume.runningState()!
|
|
212
219
|
|
|
213
220
|
controller.abort() // FlumeRunning auto-transitions to FlumeStopped
|
|
214
221
|
```
|
|
215
222
|
|
|
216
|
-
If the signal is already aborted at `Flume.start()` time, `start` returns
|
|
223
|
+
If the signal is already aborted at `Flume.start()` time, `start` returns `{ ok: false, error }` and no source is touched.
|
|
217
224
|
|
|
218
225
|
## Dependency injection
|
|
219
226
|
|
|
@@ -236,12 +243,12 @@ new FlumeDiscordSource({
|
|
|
236
243
|
|
|
237
244
|
- **Backpressure** — each source has its own `FlumeSerialQueue`. Handler invocations are awaited and run one at a time per source, so async handlers don't race and `stop()` drains in-flight events before transitioning state.
|
|
238
245
|
- **Duplicate suppression** — Slack envelopes are deduped by `envelope_id` (`FlumeSlackSeenCache`) to absorb ack retries. GitHub notifications are deduped by `id + updated_at` (`FlumeGitHubSeenCache`). Discord uses session resume so the Gateway does not re-emit dispatches.
|
|
239
|
-
- **Partial-failure rollback** — if any source fails during `Flume.start()`, the already-started sources are stopped and
|
|
246
|
+
- **Partial-failure rollback** — if any source fails during `Flume.start()`, the already-started sources are stopped and `{ ok: false, error }` is returned with per-source detail.
|
|
240
247
|
- **Idempotent stop** — `FlumeRunning.stop()` is safe to call concurrently; the first call wins and subsequent callers receive the same `FlumeStopped` snapshot.
|
|
241
248
|
|
|
242
249
|
## Errors
|
|
243
250
|
|
|
244
|
-
Flume does not throw on protocol/network failures.
|
|
251
|
+
Flume does not throw on protocol/network failures. `Source.start()` and `Flume.start()` return `FlumeStartResult` (`{ ok: true } | { ok: false; error: Error }`) — branch on `result.ok`. Protocol-layer helpers (`FlumeDiscordGateway.connect()`, `obtainSlackUrl()`, …) return `T | Error` and you check `instanceof`:
|
|
245
252
|
|
|
246
253
|
- `FlumeConnectionError` — WebSocket closed before ready
|
|
247
254
|
- `FlumeHttpError` — HTTP call returned an error payload (e.g. Slack `ok: false`)
|
|
@@ -254,7 +261,7 @@ Internal handler exceptions are caught and logged (never rethrown into the proto
|
|
|
254
261
|
| source | transport | auth |
|
|
255
262
|
|---------|----------------------------------|-----------------------------------------------|
|
|
256
263
|
| Discord | Gateway WebSocket v10 (JSON) | bot token |
|
|
257
|
-
| Slack | Socket Mode WebSocket | app token
|
|
264
|
+
| Slack | Socket Mode WebSocket | app token + bot token (both required) |
|
|
258
265
|
| GitHub | REST polling `/notifications` | personal access token |
|
|
259
266
|
|
|
260
267
|
GitHub also exposes `gh auth token` if you want to reuse the `gh` CLI's session:
|
|
@@ -272,7 +279,7 @@ const github = new FlumeGitHubSource({ token })
|
|
|
272
279
|
- `FlumeDiscordGateway` / `FlumeSlackSocketMode` / `FlumeGitHubPoller` — protocol layer
|
|
273
280
|
- `FlumeDiscordGatewaySession` — immutable session value object (id / seq / resume URL) carried across Discord reconnects
|
|
274
281
|
- `FlumeSlackSeenCache` / `FlumeGitHubSeenCache` — per-source duplicate suppression
|
|
275
|
-
- `FlumeReconnector`
|
|
282
|
+
- `FlumeReconnector` — exponential backoff with jitter (the internal `scheduleFlumeReconnect` helper is not exported; sources wire it themselves)
|
|
276
283
|
- `FlumeLogger` — structured log emitter (feeds `onLog`)
|
|
277
284
|
- `FlumeRuntimeDeps` — IO boundary port (`fetch`, `WebSocket`, `now`, `random`, timers)
|
|
278
285
|
- `extractDiscordMeta` / `extractSlackMeta` / `extractGitHubMeta` — pure functions that build `FlumeEvent.meta` from each protocol's payload shape
|
package/dist/discord.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as FlumeGatewayMessageSchema, C as FlumeStartResult, c as FlumeLogHandler, o as FlumeHandler, p as FlumeRuntimeDeps, r as FlumeGatewayMessage, t as FlumeDiscordSourceOptions, w as FlumeStatus } from "./types-Bm9uKUQz.js";
|
|
2
2
|
import { t as FlumeConnectionError } from "./connection-error-BOk97djj.js";
|
|
3
3
|
import { t as FlumeParseError } from "./parse-error-BAiCLRmk.js";
|
|
4
4
|
|
|
@@ -14,7 +14,7 @@ declare class FlumeDiscordSource {
|
|
|
14
14
|
private readonly deps;
|
|
15
15
|
private readonly queue;
|
|
16
16
|
constructor(options: FlumeDiscordSourceOptions);
|
|
17
|
-
start(handler: FlumeHandler): Promise<
|
|
17
|
+
start(handler: FlumeHandler): Promise<FlumeStartResult>;
|
|
18
18
|
stop(): Promise<void>;
|
|
19
19
|
status(): FlumeStatus;
|
|
20
20
|
private connectInternal;
|
package/dist/discord.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as createFlumeDefaultDeps, t as FlumeLogger } from "./logger-CpGB9WO_.js";
|
|
2
|
-
import {
|
|
2
|
+
import { i as FlumeConnectionError, n as resolveFlumeReconnectConfig, r as FlumeParseError, t as FlumeReconnector } from "./reconnector-BDoJ1xNX.js";
|
|
3
3
|
import { t as FlumeSerialQueue } from "./serial-queue-ExmlnpzQ.js";
|
|
4
|
-
import { n as isRecord, t as safeJsonParse } from "./safe-json-parse-
|
|
4
|
+
import { n as isRecord, r as scheduleFlumeReconnect, t as safeJsonParse } from "./safe-json-parse-BWlzGOLl.js";
|
|
5
5
|
import { z } from "zod/v4";
|
|
6
6
|
//#region lib/discord/extract-discord-meta.ts
|
|
7
7
|
function extractDiscordMeta(eventName, eventData) {
|
|
@@ -429,14 +429,22 @@ var FlumeDiscordSource = class {
|
|
|
429
429
|
});
|
|
430
430
|
}
|
|
431
431
|
async start(handler) {
|
|
432
|
-
if (this.options.signal?.aborted) return
|
|
432
|
+
if (this.options.signal?.aborted) return {
|
|
433
|
+
ok: false,
|
|
434
|
+
error: /* @__PURE__ */ new Error("Discord source: signal already aborted")
|
|
435
|
+
};
|
|
433
436
|
this.options.signal?.addEventListener("abort", () => this.stop(), { once: true });
|
|
434
437
|
this.handler = handler;
|
|
435
438
|
this.log.info({
|
|
436
439
|
action: "start",
|
|
437
440
|
message: "starting Discord source"
|
|
438
441
|
});
|
|
439
|
-
|
|
442
|
+
const result = await this.connectInternal();
|
|
443
|
+
if (result instanceof Error) return {
|
|
444
|
+
ok: false,
|
|
445
|
+
error: result
|
|
446
|
+
};
|
|
447
|
+
return { ok: true };
|
|
440
448
|
}
|
|
441
449
|
async stop() {
|
|
442
450
|
this.log.info({
|
|
@@ -480,6 +488,7 @@ var FlumeDiscordSource = class {
|
|
|
480
488
|
}
|
|
481
489
|
this.scheduleReconnect();
|
|
482
490
|
}
|
|
491
|
+
return null;
|
|
483
492
|
}
|
|
484
493
|
handleDispatch(eventName, eventData) {
|
|
485
494
|
const event = {
|
package/dist/github.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as FlumeStartResult, a as FlumeGitHubSourceOptions, c as FlumeLogHandler, i as FlumeGitHubNotification, k as FlumeGitHubNotificationSchema, o as FlumeHandler, p as FlumeRuntimeDeps, w as FlumeStatus } from "./types-Bm9uKUQz.js";
|
|
2
2
|
|
|
3
3
|
//#region lib/github/github-source.d.ts
|
|
4
4
|
declare class FlumeGitHubSource {
|
|
@@ -10,7 +10,7 @@ declare class FlumeGitHubSource {
|
|
|
10
10
|
private readonly deps;
|
|
11
11
|
private readonly queue;
|
|
12
12
|
constructor(options: FlumeGitHubSourceOptions);
|
|
13
|
-
start(handler: FlumeHandler): Promise<
|
|
13
|
+
start(handler: FlumeHandler): Promise<FlumeStartResult>;
|
|
14
14
|
stop(): Promise<void>;
|
|
15
15
|
status(): FlumeStatus;
|
|
16
16
|
private handleNotifications;
|
package/dist/github.js
CHANGED
|
@@ -229,7 +229,10 @@ var FlumeGitHubSource = class {
|
|
|
229
229
|
});
|
|
230
230
|
}
|
|
231
231
|
async start(handler) {
|
|
232
|
-
if (this.options.signal?.aborted) return
|
|
232
|
+
if (this.options.signal?.aborted) return {
|
|
233
|
+
ok: false,
|
|
234
|
+
error: /* @__PURE__ */ new Error("GitHub source: signal already aborted")
|
|
235
|
+
};
|
|
233
236
|
this.options.signal?.addEventListener("abort", () => this.stop(), { once: true });
|
|
234
237
|
this.log.info({
|
|
235
238
|
action: "start",
|
|
@@ -255,8 +258,12 @@ var FlumeGitHubSource = class {
|
|
|
255
258
|
error: err
|
|
256
259
|
});
|
|
257
260
|
this.setStatus("disconnected");
|
|
258
|
-
return
|
|
261
|
+
return {
|
|
262
|
+
ok: false,
|
|
263
|
+
error: err
|
|
264
|
+
};
|
|
259
265
|
}
|
|
266
|
+
return { ok: true };
|
|
260
267
|
}
|
|
261
268
|
async stop() {
|
|
262
269
|
this.log.info({
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as FlumeTimerHandle, S as FlumeStatusHandler, _ as FlumeSource, a as FlumeGitHubSourceOptions, b as FlumeSourceStatus, c as FlumeLogHandler, d as FlumeReconnectConfig, f as FlumeReconnectOptions, g as FlumeSlackSourceOptions, h as FlumeSlackEnvelope, i as FlumeGitHubNotification, l as FlumeLogInput, m as FlumeSlackConnectionResponse, n as FlumeEvent, o as FlumeHandler, p as FlumeRuntimeDeps, r as FlumeGatewayMessage, s as FlumeLog, t as FlumeDiscordSourceOptions, u as FlumeLogLevel, v as FlumeSourceName,
|
|
1
|
+
import { C as FlumeStartResult, E as FlumeTimerHandle, S as FlumeStartOk, T as FlumeStatusHandler, _ as FlumeSource, a as FlumeGitHubSourceOptions, b as FlumeSourceStatus, c as FlumeLogHandler, d as FlumeReconnectConfig, f as FlumeReconnectOptions, g as FlumeSlackSourceOptions, h as FlumeSlackEnvelope, i as FlumeGitHubNotification, l as FlumeLogInput, m as FlumeSlackConnectionResponse, n as FlumeEvent, o as FlumeHandler, p as FlumeRuntimeDeps, r as FlumeGatewayMessage, s as FlumeLog, t as FlumeDiscordSourceOptions, u as FlumeLogLevel, v as FlumeSourceName, w as FlumeStatus, x as FlumeStartErr, y as FlumeSourceOptions } from "./types-Bm9uKUQz.js";
|
|
2
2
|
import { t as FlumeConnectionError } from "./connection-error-BOk97djj.js";
|
|
3
3
|
import { t as FlumeParseError } from "./parse-error-BAiCLRmk.js";
|
|
4
4
|
import { t as FlumeHttpError } from "./http-error-K-Ym4lfK.js";
|
|
@@ -7,14 +7,14 @@ import { t as FlumeHttpError } from "./http-error-K-Ym4lfK.js";
|
|
|
7
7
|
declare function createFlumeDefaultDeps(): FlumeRuntimeDeps;
|
|
8
8
|
//#endregion
|
|
9
9
|
//#region lib/logger.d.ts
|
|
10
|
-
type Props$
|
|
10
|
+
type Props$4 = {
|
|
11
11
|
source: string;
|
|
12
12
|
handler?: FlumeLogHandler;
|
|
13
13
|
deps: Pick<FlumeRuntimeDeps, "now">;
|
|
14
14
|
};
|
|
15
15
|
declare class FlumeLogger {
|
|
16
16
|
private readonly props;
|
|
17
|
-
constructor(props: Props$
|
|
17
|
+
constructor(props: Props$4);
|
|
18
18
|
debug(entry: FlumeLogInput): void;
|
|
19
19
|
info(entry: FlumeLogInput): void;
|
|
20
20
|
warn(entry: FlumeLogInput): void;
|
|
@@ -26,7 +26,7 @@ declare class FlumeLogger {
|
|
|
26
26
|
declare function resolveFlumeReconnectConfig(input: boolean | FlumeReconnectOptions | undefined): FlumeReconnectConfig | null;
|
|
27
27
|
//#endregion
|
|
28
28
|
//#region lib/reconnector.d.ts
|
|
29
|
-
type Props$
|
|
29
|
+
type Props$3 = {
|
|
30
30
|
maxAttempts: number;
|
|
31
31
|
baseDelay: number;
|
|
32
32
|
maxDelay: number;
|
|
@@ -37,26 +37,13 @@ declare class FlumeReconnector {
|
|
|
37
37
|
attempt: number;
|
|
38
38
|
aborted: boolean;
|
|
39
39
|
private timer;
|
|
40
|
-
constructor(props: Props$
|
|
40
|
+
constructor(props: Props$3);
|
|
41
41
|
schedule(fn: () => void): number;
|
|
42
42
|
reset(): void;
|
|
43
43
|
cancel(): void;
|
|
44
44
|
private nextDelay;
|
|
45
45
|
}
|
|
46
46
|
//#endregion
|
|
47
|
-
//#region lib/schedule-reconnect.d.ts
|
|
48
|
-
type Props$3 = {
|
|
49
|
-
reconnector: FlumeReconnector | null;
|
|
50
|
-
log: FlumeLogger;
|
|
51
|
-
setStatus: (status: FlumeStatus) => void;
|
|
52
|
-
retry: () => void;
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* 接続が落ちた際の共通再接続スケジューラ。reconnector の状態を見て次回試行を予約し、
|
|
56
|
-
* 試行回数が尽きていれば disconnected に落とす
|
|
57
|
-
*/
|
|
58
|
-
declare function scheduleFlumeReconnect(props: Props$3): void;
|
|
59
|
-
//#endregion
|
|
60
47
|
//#region lib/flume-stopped.d.ts
|
|
61
48
|
type Props$2 = {
|
|
62
49
|
finalStatuses: ReadonlyArray<FlumeSourceStatus>;
|
|
@@ -100,7 +87,9 @@ declare class Flume {
|
|
|
100
87
|
private readonly props;
|
|
101
88
|
private consumed;
|
|
102
89
|
constructor(props: Props);
|
|
103
|
-
start(handler: FlumeHandler): Promise<
|
|
90
|
+
start(handler: FlumeHandler): Promise<FlumeStartResult>;
|
|
91
|
+
private running;
|
|
92
|
+
runningState(): FlumeRunning | null;
|
|
104
93
|
}
|
|
105
94
|
//#endregion
|
|
106
|
-
export { Flume, FlumeConnectionError, type FlumeDiscordSourceOptions, type FlumeEvent, type FlumeGatewayMessage, type FlumeGitHubNotification, type FlumeGitHubSourceOptions, type FlumeHandler, FlumeHttpError, type FlumeLog, type FlumeLogHandler, type FlumeLogInput, type FlumeLogLevel, FlumeLogger, FlumeParseError, type FlumeReconnectConfig, type FlumeReconnectOptions, FlumeReconnector, FlumeRunning, type FlumeRuntimeDeps, type FlumeSlackConnectionResponse, type FlumeSlackEnvelope, type FlumeSlackSourceOptions, type FlumeSource, type FlumeSourceName, type FlumeSourceOptions, type FlumeSourceStatus, type FlumeStatus, type FlumeStatusHandler, FlumeStopped, type FlumeTimerHandle, createFlumeDefaultDeps, resolveFlumeReconnectConfig
|
|
95
|
+
export { Flume, FlumeConnectionError, type FlumeDiscordSourceOptions, type FlumeEvent, type FlumeGatewayMessage, type FlumeGitHubNotification, type FlumeGitHubSourceOptions, type FlumeHandler, FlumeHttpError, type FlumeLog, type FlumeLogHandler, type FlumeLogInput, type FlumeLogLevel, FlumeLogger, FlumeParseError, type FlumeReconnectConfig, type FlumeReconnectOptions, FlumeReconnector, FlumeRunning, type FlumeRuntimeDeps, type FlumeSlackConnectionResponse, type FlumeSlackEnvelope, type FlumeSlackSourceOptions, type FlumeSource, type FlumeSourceName, type FlumeSourceOptions, type FlumeSourceStatus, type FlumeStartErr, type FlumeStartOk, type FlumeStartResult, type FlumeStatus, type FlumeStatusHandler, FlumeStopped, type FlumeTimerHandle, createFlumeDefaultDeps, resolveFlumeReconnectConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as createFlumeDefaultDeps, t as FlumeLogger } from "./logger-CpGB9WO_.js";
|
|
2
|
-
import {
|
|
2
|
+
import { i as FlumeConnectionError, n as resolveFlumeReconnectConfig, r as FlumeParseError, t as FlumeReconnector } from "./reconnector-BDoJ1xNX.js";
|
|
3
3
|
import { t as FlumeHttpError } from "./http-error-BtXonO-W.js";
|
|
4
4
|
//#region lib/flume-stopped.ts
|
|
5
5
|
/**
|
|
@@ -63,8 +63,14 @@ var Flume = class {
|
|
|
63
63
|
this.props = props;
|
|
64
64
|
}
|
|
65
65
|
async start(handler) {
|
|
66
|
-
if (this.consumed) return
|
|
67
|
-
|
|
66
|
+
if (this.consumed) return {
|
|
67
|
+
ok: false,
|
|
68
|
+
error: /* @__PURE__ */ new Error("Flume.start: already started")
|
|
69
|
+
};
|
|
70
|
+
if (this.props.signal?.aborted) return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: /* @__PURE__ */ new Error("Flume.start: signal already aborted")
|
|
73
|
+
};
|
|
68
74
|
this.consumed = true;
|
|
69
75
|
const settled = await Promise.allSettled(this.props.sources.map((source) => source.start(handler)));
|
|
70
76
|
const failures = [];
|
|
@@ -79,26 +85,37 @@ var Flume = class {
|
|
|
79
85
|
name: source.name,
|
|
80
86
|
error: reason instanceof Error ? reason : new Error(String(reason))
|
|
81
87
|
});
|
|
82
|
-
} else if (result.value
|
|
88
|
+
} else if (!result.value.ok) failures.push({
|
|
83
89
|
name: source.name,
|
|
84
|
-
error: result.value
|
|
90
|
+
error: result.value.error
|
|
85
91
|
});
|
|
86
92
|
else started.push(source);
|
|
87
93
|
}
|
|
88
94
|
if (failures.length > 0) {
|
|
89
95
|
await Promise.allSettled(started.map((source) => source.stop()));
|
|
90
96
|
const detail = failures.map((f) => `${f.name}: ${f.error.message}`).join("; ");
|
|
91
|
-
return
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
error: /* @__PURE__ */ new Error(`Flume.start: ${failures.length} source(s) failed: ${detail}`)
|
|
100
|
+
};
|
|
92
101
|
}
|
|
93
102
|
if (this.props.signal?.aborted) {
|
|
94
103
|
await Promise.allSettled(this.props.sources.map((source) => source.stop()));
|
|
95
|
-
return
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
error: /* @__PURE__ */ new Error("Flume.start: aborted during start")
|
|
107
|
+
};
|
|
96
108
|
}
|
|
97
|
-
|
|
109
|
+
this.running = new FlumeRunning({
|
|
98
110
|
sources: this.props.sources,
|
|
99
111
|
signal: this.props.signal
|
|
100
112
|
});
|
|
113
|
+
return { ok: true };
|
|
114
|
+
}
|
|
115
|
+
running = null;
|
|
116
|
+
runningState() {
|
|
117
|
+
return this.running;
|
|
101
118
|
}
|
|
102
119
|
};
|
|
103
120
|
//#endregion
|
|
104
|
-
export { Flume, FlumeConnectionError, FlumeHttpError, FlumeLogger, FlumeParseError, FlumeReconnector, FlumeRunning, FlumeStopped, createFlumeDefaultDeps, resolveFlumeReconnectConfig
|
|
121
|
+
export { Flume, FlumeConnectionError, FlumeHttpError, FlumeLogger, FlumeParseError, FlumeReconnector, FlumeRunning, FlumeStopped, createFlumeDefaultDeps, resolveFlumeReconnectConfig };
|
|
@@ -64,30 +64,4 @@ var FlumeReconnector = class {
|
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
66
|
//#endregion
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 接続が落ちた際の共通再接続スケジューラ。reconnector の状態を見て次回試行を予約し、
|
|
70
|
-
* 試行回数が尽きていれば disconnected に落とす
|
|
71
|
-
*/
|
|
72
|
-
function scheduleFlumeReconnect(props) {
|
|
73
|
-
if (!props.reconnector || props.reconnector.aborted) {
|
|
74
|
-
props.setStatus("disconnected");
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
props.setStatus("reconnecting");
|
|
78
|
-
const delay = props.reconnector.schedule(props.retry);
|
|
79
|
-
if (delay === -1) {
|
|
80
|
-
props.log.error({
|
|
81
|
-
action: "reconnect.exhausted",
|
|
82
|
-
message: `gave up after ${props.reconnector.attempt} attempts`
|
|
83
|
-
});
|
|
84
|
-
props.setStatus("disconnected");
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
props.log.info({
|
|
88
|
-
action: "reconnect.scheduled",
|
|
89
|
-
message: `next attempt in ${Math.round(delay)}ms`
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
//#endregion
|
|
93
|
-
export { FlumeConnectionError as a, FlumeParseError as i, FlumeReconnector as n, resolveFlumeReconnectConfig as r, scheduleFlumeReconnect as t };
|
|
67
|
+
export { FlumeConnectionError as i, resolveFlumeReconnectConfig as n, FlumeParseError as r, FlumeReconnector as t };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region lib/schedule-reconnect.ts
|
|
2
|
+
/**
|
|
3
|
+
* 接続が落ちた際の共通再接続スケジューラ。reconnector の状態を見て次回試行を予約し、
|
|
4
|
+
* 試行回数が尽きていれば disconnected に落とす
|
|
5
|
+
*/
|
|
6
|
+
function scheduleFlumeReconnect(props) {
|
|
7
|
+
if (!props.reconnector || props.reconnector.aborted) {
|
|
8
|
+
props.setStatus("disconnected");
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
props.setStatus("reconnecting");
|
|
12
|
+
const delay = props.reconnector.schedule(props.retry);
|
|
13
|
+
if (delay === -1) {
|
|
14
|
+
props.log.error({
|
|
15
|
+
action: "reconnect.exhausted",
|
|
16
|
+
message: `gave up after ${props.reconnector.attempt} attempts`
|
|
17
|
+
});
|
|
18
|
+
props.setStatus("disconnected");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
props.log.info({
|
|
22
|
+
action: "reconnect.scheduled",
|
|
23
|
+
message: `next attempt in ${Math.round(delay)}ms`
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region lib/utils/is-record.ts
|
|
28
|
+
function isRecord(value) {
|
|
29
|
+
return typeof value === "object" && value !== null;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region lib/utils/safe-json-parse.ts
|
|
33
|
+
function safeJsonParse(raw) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(raw);
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { isRecord as n, scheduleFlumeReconnect as r, safeJsonParse as t };
|
package/dist/slack.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as FlumeStartResult, D as FlumeSlackEnvelopeSchema, O as FlumeSlackConnectionResponseSchema, c as FlumeLogHandler, g as FlumeSlackSourceOptions, h as FlumeSlackEnvelope, o as FlumeHandler, p as FlumeRuntimeDeps, w as FlumeStatus } from "./types-Bm9uKUQz.js";
|
|
2
2
|
import { t as FlumeConnectionError } from "./connection-error-BOk97djj.js";
|
|
3
3
|
import { t as FlumeHttpError } from "./http-error-K-Ym4lfK.js";
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ declare class FlumeSlackSource {
|
|
|
15
15
|
private readonly queue;
|
|
16
16
|
private readonly seen;
|
|
17
17
|
constructor(options: FlumeSlackSourceOptions);
|
|
18
|
-
start(handler: FlumeHandler): Promise<
|
|
18
|
+
start(handler: FlumeHandler): Promise<FlumeStartResult>;
|
|
19
19
|
stop(): Promise<void>;
|
|
20
20
|
status(): FlumeStatus;
|
|
21
21
|
private connectInternal;
|
package/dist/slack.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as createFlumeDefaultDeps, t as FlumeLogger } from "./logger-CpGB9WO_.js";
|
|
2
|
-
import {
|
|
2
|
+
import { i as FlumeConnectionError, n as resolveFlumeReconnectConfig, r as FlumeParseError, t as FlumeReconnector } from "./reconnector-BDoJ1xNX.js";
|
|
3
3
|
import { t as FlumeHttpError } from "./http-error-BtXonO-W.js";
|
|
4
4
|
import { t as FlumeSerialQueue } from "./serial-queue-ExmlnpzQ.js";
|
|
5
|
-
import { n as isRecord, t as safeJsonParse } from "./safe-json-parse-
|
|
5
|
+
import { n as isRecord, r as scheduleFlumeReconnect, t as safeJsonParse } from "./safe-json-parse-BWlzGOLl.js";
|
|
6
6
|
import { t as safeFetch } from "./safe-fetch-30ZzOKHL.js";
|
|
7
7
|
import { z } from "zod/v4";
|
|
8
8
|
//#region lib/slack/extract-slack-meta.ts
|
|
@@ -325,14 +325,22 @@ var FlumeSlackSource = class {
|
|
|
325
325
|
});
|
|
326
326
|
}
|
|
327
327
|
async start(handler) {
|
|
328
|
-
if (this.options.signal?.aborted) return
|
|
328
|
+
if (this.options.signal?.aborted) return {
|
|
329
|
+
ok: false,
|
|
330
|
+
error: /* @__PURE__ */ new Error("Slack source: signal already aborted")
|
|
331
|
+
};
|
|
329
332
|
this.options.signal?.addEventListener("abort", () => this.stop(), { once: true });
|
|
330
333
|
this.handler = handler;
|
|
331
334
|
this.log.info({
|
|
332
335
|
action: "start",
|
|
333
336
|
message: "starting Slack source"
|
|
334
337
|
});
|
|
335
|
-
|
|
338
|
+
const result = await this.connectInternal();
|
|
339
|
+
if (result instanceof Error) return {
|
|
340
|
+
ok: false,
|
|
341
|
+
error: result
|
|
342
|
+
};
|
|
343
|
+
return { ok: true };
|
|
336
344
|
}
|
|
337
345
|
async stop() {
|
|
338
346
|
this.log.info({
|
|
@@ -385,6 +393,7 @@ var FlumeSlackSource = class {
|
|
|
385
393
|
}
|
|
386
394
|
this.scheduleReconnect();
|
|
387
395
|
}
|
|
396
|
+
return null;
|
|
388
397
|
}
|
|
389
398
|
handleMessage(envelope) {
|
|
390
399
|
if (this.seen.has(envelope.envelope_id)) {
|
|
@@ -62,9 +62,17 @@ type FlumeEvent = {
|
|
|
62
62
|
receivedAt: number;
|
|
63
63
|
};
|
|
64
64
|
type FlumeHandler = (event: FlumeEvent) => void | Promise<void>;
|
|
65
|
+
type FlumeStartOk = {
|
|
66
|
+
ok: true;
|
|
67
|
+
};
|
|
68
|
+
type FlumeStartErr = {
|
|
69
|
+
ok: false;
|
|
70
|
+
error: Error;
|
|
71
|
+
};
|
|
72
|
+
type FlumeStartResult = FlumeStartOk | FlumeStartErr;
|
|
65
73
|
type FlumeSource = {
|
|
66
74
|
readonly name: FlumeSourceName;
|
|
67
|
-
start(handler: FlumeHandler): Promise<
|
|
75
|
+
start(handler: FlumeHandler): Promise<FlumeStartResult>;
|
|
68
76
|
stop(): Promise<void>;
|
|
69
77
|
status(): FlumeStatus;
|
|
70
78
|
};
|
|
@@ -114,7 +122,14 @@ type FlumeDiscordSourceOptions = FlumeSourceOptions & {
|
|
|
114
122
|
};
|
|
115
123
|
type FlumeSlackSourceOptions = FlumeSourceOptions & {
|
|
116
124
|
appToken: string;
|
|
117
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Bot token (`xoxb-`). Required — used by the host (e.g. funnel) to call
|
|
127
|
+
* `auth.test` for self-detection and to post replies. Flume's Socket Mode
|
|
128
|
+
* transport only needs `appToken` to open the socket, but every realistic
|
|
129
|
+
* consumer needs the bot token too, so the type forces it to be present
|
|
130
|
+
* rather than leaving it optional and failing at runtime.
|
|
131
|
+
*/
|
|
132
|
+
botToken: string;
|
|
118
133
|
};
|
|
119
134
|
type FlumeGitHubSourceOptions = FlumeSourceOptions & {
|
|
120
135
|
token: string;
|
|
@@ -125,4 +140,4 @@ type FlumeSlackEnvelope = z.infer<typeof FlumeSlackEnvelopeSchema>;
|
|
|
125
140
|
type FlumeSlackConnectionResponse = z.infer<typeof FlumeSlackConnectionResponseSchema>;
|
|
126
141
|
type FlumeGitHubNotification = z.infer<typeof FlumeGitHubNotificationSchema>;
|
|
127
142
|
//#endregion
|
|
128
|
-
export {
|
|
143
|
+
export { FlumeGatewayMessageSchema as A, FlumeStartResult as C, FlumeSlackEnvelopeSchema as D, FlumeTimerHandle as E, FlumeSlackConnectionResponseSchema as O, FlumeStartOk as S, FlumeStatusHandler as T, FlumeSource as _, FlumeGitHubSourceOptions as a, FlumeSourceStatus as b, FlumeLogHandler as c, FlumeReconnectConfig as d, FlumeReconnectOptions as f, FlumeSlackSourceOptions as g, FlumeSlackEnvelope as h, FlumeGitHubNotification as i, FlumeGitHubNotificationSchema as k, FlumeLogInput as l, FlumeSlackConnectionResponse as m, FlumeEvent as n, FlumeHandler as o, FlumeRuntimeDeps as p, FlumeGatewayMessage as r, FlumeLog as s, FlumeDiscordSourceOptions as t, FlumeLogLevel as u, FlumeSourceName as v, FlumeStatus as w, FlumeStartErr as x, FlumeSourceOptions as y };
|
package/package.json
CHANGED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
//#region lib/utils/is-record.ts
|
|
2
|
-
function isRecord(value) {
|
|
3
|
-
return typeof value === "object" && value !== null;
|
|
4
|
-
}
|
|
5
|
-
//#endregion
|
|
6
|
-
//#region lib/utils/safe-json-parse.ts
|
|
7
|
-
function safeJsonParse(raw) {
|
|
8
|
-
try {
|
|
9
|
-
return JSON.parse(raw);
|
|
10
|
-
} catch {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
//#endregion
|
|
15
|
-
export { isRecord as n, safeJsonParse as t };
|