@interactive-inc/flume 0.3.0 → 0.4.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.
@@ -3,7 +3,7 @@ import { z } from "zod/v4";
3
3
  //#region lib/discord/discord-gateway-message-schema.d.ts
4
4
  declare const FlumeGatewayMessageSchema: z.ZodObject<{
5
5
  op: z.ZodNumber;
6
- d: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
6
+ d: z.ZodUnknown;
7
7
  s: z.ZodNullable<z.ZodNumber>;
8
8
  t: z.ZodNullable<z.ZodString>;
9
9
  }, z.core.$strip>;
@@ -42,10 +42,10 @@ declare const FlumeSlackEnvelopeSchema: z.ZodObject<{
42
42
  }, z.core.$strip>;
43
43
  //#endregion
44
44
  //#region lib/types.d.ts
45
- type FlumeTimerHandle = ReturnType<typeof setTimeout>;
45
+ type FlumeTimerHandle = unknown;
46
46
  type FlumeRuntimeDeps = {
47
47
  fetch(url: string | URL, init?: RequestInit): Promise<Response>;
48
- WebSocket: new (url: string | URL) => WebSocket;
48
+ WebSocket: (new (url: string | URL) => WebSocket) | null;
49
49
  now(): number;
50
50
  random(): number;
51
51
  setTimeout(fn: () => void, ms: number): FlumeTimerHandle;
@@ -54,30 +54,44 @@ type FlumeRuntimeDeps = {
54
54
  clearInterval(id: FlumeTimerHandle): void;
55
55
  };
56
56
  type FlumeSourceName = "discord" | "slack" | "github";
57
- type FlumeEvent = {
58
- source: FlumeSourceName;
57
+ type FlumeDiscordEvent = {
58
+ source: "discord";
59
59
  type: string;
60
- data: unknown;
60
+ data: Record<string, unknown>;
61
61
  meta: Record<string, string>;
62
62
  receivedAt: number;
63
63
  };
64
- type FlumeHandler = (event: FlumeEvent) => void | Promise<void>;
65
- type FlumeStartOk = {
66
- ok: true;
64
+ type FlumeSlackEvent = {
65
+ source: "slack";
66
+ type: string;
67
+ data: Record<string, unknown>;
68
+ meta: Record<string, string>;
69
+ receivedAt: number;
70
+ };
71
+ type FlumeGitHubEvent = {
72
+ source: "github";
73
+ type: "notification";
74
+ data: FlumeGitHubNotification;
75
+ meta: Record<string, string>;
76
+ receivedAt: number;
67
77
  };
68
- type FlumeStartErr = {
69
- ok: false;
70
- error: Error;
78
+ type FlumeEvent = FlumeDiscordEvent | FlumeSlackEvent | FlumeGitHubEvent;
79
+ type FlumeHandler = (event: FlumeEvent) => void | Promise<void>;
80
+ type FlumeSourceStartOptions = {
81
+ signal?: AbortSignal;
71
82
  };
72
- type FlumeStartResult = FlumeStartOk | FlumeStartErr;
73
83
  type FlumeSource = {
74
84
  readonly name: FlumeSourceName;
75
- start(handler: FlumeHandler): Promise<FlumeStartResult>;
85
+ start(handler: FlumeHandler, options?: FlumeSourceStartOptions): Promise<Error | null>;
76
86
  stop(): Promise<void>;
77
87
  status(): FlumeStatus;
78
88
  };
79
89
  type FlumeSourceStatus = {
80
- name: FlumeSourceName;
90
+ /**
91
+ * 多くは `FlumeSourceName` のいずれかだが、`source.name` getter が throw する
92
+ * 第三者 `FlumeSource` 実装に備えて `string` まで広げてある (fallback で `"?"`)
93
+ */
94
+ source: string;
81
95
  status: FlumeStatus;
82
96
  };
83
97
  type FlumeStatus = "disconnected" | "connecting" | "connected" | "reconnecting";
@@ -123,11 +137,8 @@ type FlumeDiscordSourceOptions = FlumeSourceOptions & {
123
137
  type FlumeSlackSourceOptions = FlumeSourceOptions & {
124
138
  appToken: string;
125
139
  /**
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.
140
+ * Bot token (`xoxb-`). Slack Socket Mode (受信) には不要だが、ホスト側 (返信や
141
+ * `auth.test` での self 検出) が必ず使うため型で保持を強制する
131
142
  */
132
143
  botToken: string;
133
144
  };
@@ -140,4 +151,4 @@ type FlumeSlackEnvelope = z.infer<typeof FlumeSlackEnvelopeSchema>;
140
151
  type FlumeSlackConnectionResponse = z.infer<typeof FlumeSlackConnectionResponseSchema>;
141
152
  type FlumeGitHubNotification = z.infer<typeof FlumeGitHubNotificationSchema>;
142
153
  //#endregion
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 };
154
+ export { FlumeSourceStartOptions as C, FlumeTimerHandle as D, FlumeStatusHandler as E, FlumeSourceOptions as S, FlumeStatus as T, FlumeSlackEnvelope as _, FlumeGitHubEvent as a, FlumeSource as b, FlumeHandler as c, FlumeLogInput as d, FlumeLogLevel as f, FlumeSlackConnectionResponse as g, FlumeRuntimeDeps as h, FlumeGatewayMessage as i, FlumeLog as l, FlumeReconnectOptions as m, FlumeDiscordSourceOptions as n, FlumeGitHubNotification as o, FlumeReconnectConfig as p, FlumeEvent as r, FlumeGitHubSourceOptions as s, FlumeDiscordEvent as t, FlumeLogHandler as u, FlumeSlackEvent as v, FlumeSourceStatus as w, FlumeSourceName as x, FlumeSlackSourceOptions as y };
package/package.json CHANGED
@@ -1,33 +1,38 @@
1
1
  {
2
2
  "name": "@interactive-inc/flume",
3
- "version": "0.3.0",
3
+ "version": "0.4.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",
7
- "slack",
7
+ "event-source",
8
+ "gateway",
8
9
  "github",
10
+ "listener",
9
11
  "notifications",
10
- "webhook",
12
+ "slack",
11
13
  "socket-mode",
12
- "gateway",
13
- "event-source",
14
- "listener"
14
+ "webhook"
15
15
  ],
16
+ "homepage": "https://github.com/interactive-inc/open-flume#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/interactive-inc/open-flume/issues"
19
+ },
16
20
  "license": "MIT",
17
21
  "author": "interactive-inc",
18
22
  "repository": {
19
23
  "type": "git",
20
24
  "url": "git+https://github.com/interactive-inc/open-flume.git"
21
25
  },
22
- "homepage": "https://github.com/interactive-inc/open-flume#readme",
23
- "bugs": {
24
- "url": "https://github.com/interactive-inc/open-flume/issues"
25
- },
26
+ "files": [
27
+ "dist/**/*",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
26
31
  "type": "module",
32
+ "sideEffects": false,
27
33
  "main": "./dist/index.js",
28
34
  "module": "./dist/index.js",
29
35
  "types": "./dist/index.d.ts",
30
- "sideEffects": false,
31
36
  "exports": {
32
37
  ".": {
33
38
  "types": "./dist/index.d.ts",
@@ -51,11 +56,6 @@
51
56
  },
52
57
  "./package.json": "./package.json"
53
58
  },
54
- "files": [
55
- "dist/**/*",
56
- "README.md",
57
- "LICENSE"
58
- ],
59
59
  "publishConfig": {
60
60
  "access": "public"
61
61
  },
@@ -67,11 +67,14 @@
67
67
  "lint": "vp lint",
68
68
  "fmt": "vp fmt"
69
69
  },
70
+ "dependencies": {
71
+ "zod": "^4.4.3"
72
+ },
70
73
  "devDependencies": {
71
74
  "vite-plus": "^0.1.21",
72
75
  "vitest": "^4.1.9"
73
76
  },
74
- "dependencies": {
75
- "zod": "^4.4.3"
77
+ "engines": {
78
+ "node": ">=22"
76
79
  }
77
80
  }
@@ -1,6 +0,0 @@
1
- //#region lib/errors/connection-error.d.ts
2
- declare class FlumeConnectionError extends Error {
3
- constructor(message: string);
4
- }
5
- //#endregion
6
- export { FlumeConnectionError as t };
@@ -1,11 +0,0 @@
1
- //#region lib/errors/http-error.d.ts
2
- type Props = {
3
- message: string;
4
- status: number;
5
- };
6
- declare class FlumeHttpError extends Error {
7
- readonly status: number;
8
- constructor(props: Props);
9
- }
10
- //#endregion
11
- export { FlumeHttpError as t };
@@ -1,49 +0,0 @@
1
- //#region lib/deps.ts
2
- function createFlumeDefaultDeps() {
3
- return {
4
- fetch: (url, init) => globalThis.fetch(url, init),
5
- WebSocket: globalThis.WebSocket,
6
- now: () => Date.now(),
7
- random: () => Math.random(),
8
- setTimeout: (fn, ms) => globalThis.setTimeout(fn, ms),
9
- clearTimeout: (id) => globalThis.clearTimeout(id),
10
- setInterval: (fn, ms) => globalThis.setInterval(fn, ms),
11
- clearInterval: (id) => globalThis.clearInterval(id)
12
- };
13
- }
14
- //#endregion
15
- //#region lib/logger.ts
16
- var FlumeLogger = class {
17
- props;
18
- constructor(props) {
19
- this.props = props;
20
- Object.freeze(this);
21
- }
22
- debug(entry) {
23
- this.emit("debug", entry);
24
- }
25
- info(entry) {
26
- this.emit("info", entry);
27
- }
28
- warn(entry) {
29
- this.emit("warn", entry);
30
- }
31
- error(entry) {
32
- this.emit("error", entry);
33
- }
34
- emit(level, input) {
35
- if (!this.props.handler) return;
36
- const log = {
37
- level,
38
- source: this.props.source,
39
- action: input.action,
40
- message: input.message,
41
- timestamp: this.props.deps.now(),
42
- error: input.error,
43
- detail: input.detail
44
- };
45
- this.props.handler(log);
46
- }
47
- };
48
- //#endregion
49
- export { createFlumeDefaultDeps as n, FlumeLogger as t };
@@ -1,6 +0,0 @@
1
- //#region lib/errors/parse-error.d.ts
2
- declare class FlumeParseError extends Error {
3
- constructor(message: string);
4
- }
5
- //#endregion
6
- export { FlumeParseError as t };
@@ -1,67 +0,0 @@
1
- //#region lib/errors/connection-error.ts
2
- var FlumeConnectionError = class extends Error {
3
- constructor(message) {
4
- super(message);
5
- this.name = "FlumeConnectionError";
6
- Object.freeze(this);
7
- }
8
- };
9
- //#endregion
10
- //#region lib/errors/parse-error.ts
11
- var FlumeParseError = class extends Error {
12
- constructor(message) {
13
- super(message);
14
- this.name = "FlumeParseError";
15
- Object.freeze(this);
16
- }
17
- };
18
- //#endregion
19
- //#region lib/reconnect-config.ts
20
- const DEFAULTS = {
21
- maxAttempts: Infinity,
22
- baseDelay: 1e3,
23
- maxDelay: 3e4
24
- };
25
- function resolveFlumeReconnectConfig(input) {
26
- if (input === false || input === void 0) return null;
27
- if (input === true) return { ...DEFAULTS };
28
- return {
29
- ...DEFAULTS,
30
- ...input
31
- };
32
- }
33
- //#endregion
34
- //#region lib/reconnector.ts
35
- var FlumeReconnector = class {
36
- props;
37
- attempt = 0;
38
- aborted = false;
39
- timer = null;
40
- constructor(props) {
41
- this.props = props;
42
- }
43
- schedule(fn) {
44
- if (this.aborted) return 0;
45
- if (this.attempt >= this.props.maxAttempts) return -1;
46
- const delay = this.nextDelay();
47
- this.timer = this.props.deps.setTimeout(fn, delay);
48
- return delay;
49
- }
50
- reset() {
51
- this.attempt = 0;
52
- }
53
- cancel() {
54
- this.aborted = true;
55
- if (this.timer !== null) {
56
- this.props.deps.clearTimeout(this.timer);
57
- this.timer = null;
58
- }
59
- }
60
- nextDelay() {
61
- const jitter = Math.min(this.props.baseDelay * 2 ** this.attempt, this.props.maxDelay) * (.5 + this.props.deps.random() * .5);
62
- this.attempt++;
63
- return jitter;
64
- }
65
- };
66
- //#endregion
67
- export { FlumeConnectionError as i, resolveFlumeReconnectConfig as n, FlumeParseError as r, FlumeReconnector as t };
@@ -1,20 +0,0 @@
1
- //#region lib/utils/safe-fetch.ts
2
- /**
3
- * deps.fetch を try/catch で包み、ネットワーク例外を Error として返す。
4
- * HTTP ステータスの解釈や追加の副作用 (リトライ計数等) は呼び出し側の責務
5
- */
6
- async function safeFetch(props) {
7
- try {
8
- return await props.fetch(props.url, props.init);
9
- } catch (error) {
10
- const err = error instanceof Error ? error : new Error(String(error));
11
- props.log.error({
12
- action: "http.error",
13
- message: `network error: ${err.message}`,
14
- error: err
15
- });
16
- return err;
17
- }
18
- }
19
- //#endregion
20
- export { safeFetch as t };
@@ -1,41 +0,0 @@
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 };
@@ -1,16 +0,0 @@
1
- //#region lib/utils/serial-queue.ts
2
- /**
3
- * 投入順を保ったまま task を直列実行する。各 task は前の完了を待ってから走る。
4
- * task が throw しても後続には伝播しない (キュー自体は止まらない)
5
- */
6
- var FlumeSerialQueue = class {
7
- chain = Promise.resolve();
8
- add(task) {
9
- this.chain = this.chain.then(task).catch(() => {});
10
- }
11
- async drain() {
12
- await this.chain;
13
- }
14
- };
15
- //#endregion
16
- export { FlumeSerialQueue as t };