@interactive-inc/flume 0.2.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.
- package/README.md +60 -39
- package/dist/connection-error-HUO3PC3G.js +16 -0
- package/dist/discord.d.ts +10 -85
- package/dist/discord.js +474 -175
- package/dist/github.d.ts +9 -48
- package/dist/github.js +391 -128
- package/dist/{http-error-BtXonO-W.js → http-error-CPSKoSie.js} +1 -1
- package/dist/index.d.ts +76 -45
- package/dist/index.js +225 -40
- package/dist/safe-invoke-callback-EpWXwfwp.js +170 -0
- package/dist/safe-read-text-DgrJ4Uhl.js +20 -0
- package/dist/safe-stringify-BWS-uXZP.js +172 -0
- package/dist/serial-queue-B9LoBc64.js +162 -0
- package/dist/slack.d.ts +10 -62
- package/dist/slack.js +377 -131
- package/dist/{types-tnOPBc1p.d.ts → types-D-tO-Mh2.d.ts} +36 -10
- package/package.json +21 -18
- package/dist/connection-error-BOk97djj.d.ts +0 -6
- package/dist/http-error-K-Ym4lfK.d.ts +0 -11
- package/dist/logger-CpGB9WO_.js +0 -49
- package/dist/parse-error-BAiCLRmk.d.ts +0 -6
- package/dist/safe-fetch-30ZzOKHL.js +0 -20
- package/dist/safe-json-parse-WCg_x1JS.js +0 -15
- package/dist/schedule-reconnect-DSxZJG3h.js +0 -93
- package/dist/serial-queue-ExmlnpzQ.js +0 -16
|
@@ -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.
|
|
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 =
|
|
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,22 +54,44 @@ type FlumeRuntimeDeps = {
|
|
|
54
54
|
clearInterval(id: FlumeTimerHandle): void;
|
|
55
55
|
};
|
|
56
56
|
type FlumeSourceName = "discord" | "slack" | "github";
|
|
57
|
-
type
|
|
58
|
-
source:
|
|
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 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;
|
|
77
|
+
};
|
|
78
|
+
type FlumeEvent = FlumeDiscordEvent | FlumeSlackEvent | FlumeGitHubEvent;
|
|
64
79
|
type FlumeHandler = (event: FlumeEvent) => void | Promise<void>;
|
|
80
|
+
type FlumeSourceStartOptions = {
|
|
81
|
+
signal?: AbortSignal;
|
|
82
|
+
};
|
|
65
83
|
type FlumeSource = {
|
|
66
84
|
readonly name: FlumeSourceName;
|
|
67
|
-
start(handler: FlumeHandler): Promise<
|
|
85
|
+
start(handler: FlumeHandler, options?: FlumeSourceStartOptions): Promise<Error | null>;
|
|
68
86
|
stop(): Promise<void>;
|
|
69
87
|
status(): FlumeStatus;
|
|
70
88
|
};
|
|
71
89
|
type FlumeSourceStatus = {
|
|
72
|
-
|
|
90
|
+
/**
|
|
91
|
+
* 多くは `FlumeSourceName` のいずれかだが、`source.name` getter が throw する
|
|
92
|
+
* 第三者 `FlumeSource` 実装に備えて `string` まで広げてある (fallback で `"?"`)
|
|
93
|
+
*/
|
|
94
|
+
source: string;
|
|
73
95
|
status: FlumeStatus;
|
|
74
96
|
};
|
|
75
97
|
type FlumeStatus = "disconnected" | "connecting" | "connected" | "reconnecting";
|
|
@@ -114,7 +136,11 @@ type FlumeDiscordSourceOptions = FlumeSourceOptions & {
|
|
|
114
136
|
};
|
|
115
137
|
type FlumeSlackSourceOptions = FlumeSourceOptions & {
|
|
116
138
|
appToken: string;
|
|
117
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Bot token (`xoxb-`). Slack の Socket Mode (受信) には不要だが、ホスト側 (返信や
|
|
141
|
+
* `auth.test` での self 検出) が必ず使うため型で保持を強制する
|
|
142
|
+
*/
|
|
143
|
+
botToken: string;
|
|
118
144
|
};
|
|
119
145
|
type FlumeGitHubSourceOptions = FlumeSourceOptions & {
|
|
120
146
|
token: string;
|
|
@@ -125,4 +151,4 @@ type FlumeSlackEnvelope = z.infer<typeof FlumeSlackEnvelopeSchema>;
|
|
|
125
151
|
type FlumeSlackConnectionResponse = z.infer<typeof FlumeSlackConnectionResponseSchema>;
|
|
126
152
|
type FlumeGitHubNotification = z.infer<typeof FlumeGitHubNotificationSchema>;
|
|
127
153
|
//#endregion
|
|
128
|
-
export {
|
|
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
|
+
"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
|
-
"
|
|
7
|
+
"event-source",
|
|
8
|
+
"gateway",
|
|
8
9
|
"github",
|
|
10
|
+
"listener",
|
|
9
11
|
"notifications",
|
|
10
|
-
"
|
|
12
|
+
"slack",
|
|
11
13
|
"socket-mode",
|
|
12
|
-
"
|
|
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
|
-
"
|
|
23
|
-
|
|
24
|
-
"
|
|
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
|
-
"
|
|
75
|
-
"
|
|
77
|
+
"engines": {
|
|
78
|
+
"node": ">=22"
|
|
76
79
|
}
|
|
77
80
|
}
|
package/dist/logger-CpGB9WO_.js
DELETED
|
@@ -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,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,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 };
|
|
@@ -1,93 +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
|
-
//#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 };
|
|
@@ -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 };
|