@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 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 running = await flume.start((event) => {
37
+ const result = await flume.start((event) => {
38
38
  console.log(event.source, event.type, event.meta)
39
39
  })
40
40
 
41
- if (running instanceof Error) throw running
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 `FlumeRunning | Error`. On partial failure (one source fails while another succeeds), the already-started sources are rolled back and an `Error` is returned with per-source detail.
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 running = await flume.start(handler)
63
- if (running instanceof Error) {
64
- console.error(running.message)
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 error = await source.start((event) => { /* ... */ })
91
- if (error instanceof Error) throw error
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`, `scheduleFlumeReconnect`, `createFlumeDefaultDeps`, errors, types |
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
- const running = await flume.start(handler)
211
- if (running instanceof Error) throw running
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 an `Error` and no source is touched.
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 an `Error` is returned with per-source detail.
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. Connection methods return `T | Error` and you check `instanceof`:
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 (`botToken` optional, for future) |
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` + `scheduleFlumeReconnect` — exponential backoff with jitter + shared reconnect scheduler
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 { D as FlumeGatewayMessageSchema, c as FlumeLogHandler, o as FlumeHandler, p as FlumeRuntimeDeps, r as FlumeGatewayMessage, t as FlumeDiscordSourceOptions, x as FlumeStatus } from "./types-tnOPBc1p.js";
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<void | Error>;
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 { a as FlumeConnectionError, i as FlumeParseError, n as FlumeReconnector, r as resolveFlumeReconnectConfig, t as scheduleFlumeReconnect } from "./schedule-reconnect-DSxZJG3h.js";
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-WCg_x1JS.js";
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 /* @__PURE__ */ new Error("Discord source: signal already aborted");
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
- return this.connectInternal();
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 { E as FlumeGitHubNotificationSchema, a as FlumeGitHubSourceOptions, c as FlumeLogHandler, i as FlumeGitHubNotification, o as FlumeHandler, p as FlumeRuntimeDeps, x as FlumeStatus } from "./types-tnOPBc1p.js";
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<void | Error>;
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 /* @__PURE__ */ new Error("GitHub source: signal already aborted");
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 err;
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, x as FlumeStatus, y as FlumeSourceOptions } from "./types-tnOPBc1p.js";
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$5 = {
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$5);
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$4 = {
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$4);
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<FlumeRunning | Error>;
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, scheduleFlumeReconnect };
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 { a as FlumeConnectionError, i as FlumeParseError, n as FlumeReconnector, r as resolveFlumeReconnectConfig, t as scheduleFlumeReconnect } from "./schedule-reconnect-DSxZJG3h.js";
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 /* @__PURE__ */ new Error("Flume.start: already started");
67
- if (this.props.signal?.aborted) return /* @__PURE__ */ new Error("Flume.start: signal already aborted");
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 instanceof Error) failures.push({
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 /* @__PURE__ */ new Error(`Flume.start: ${failures.length} source(s) failed: ${detail}`);
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 /* @__PURE__ */ new Error("Flume.start: aborted during start");
104
+ return {
105
+ ok: false,
106
+ error: /* @__PURE__ */ new Error("Flume.start: aborted during start")
107
+ };
96
108
  }
97
- return new FlumeRunning({
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, scheduleFlumeReconnect };
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
- //#region lib/schedule-reconnect.ts
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 { T as FlumeSlackConnectionResponseSchema, c as FlumeLogHandler, g as FlumeSlackSourceOptions, h as FlumeSlackEnvelope, o as FlumeHandler, p as FlumeRuntimeDeps, w as FlumeSlackEnvelopeSchema, x as FlumeStatus } from "./types-tnOPBc1p.js";
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<void | Error>;
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 { a as FlumeConnectionError, i as FlumeParseError, n as FlumeReconnector, r as resolveFlumeReconnectConfig, t as scheduleFlumeReconnect } from "./schedule-reconnect-DSxZJG3h.js";
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-WCg_x1JS.js";
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 /* @__PURE__ */ new Error("Slack source: signal already aborted");
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
- return this.connectInternal();
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<void | Error>;
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
- botToken?: string;
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 { FlumeTimerHandle as C, FlumeGatewayMessageSchema as D, FlumeGitHubNotificationSchema as E, FlumeStatusHandler as S, FlumeSlackConnectionResponseSchema 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, 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, FlumeSlackEnvelopeSchema as w, FlumeStatus as x, FlumeSourceOptions as y };
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@interactive-inc/flume",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Unified notification listener for Discord, Slack, and GitHub. Raw WebSocket + fetch + Zod. No SDK dependencies.",
5
5
  "keywords": [
6
6
  "discord",
@@ -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 };