@interactive-inc/flume 0.1.0 → 0.2.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 +98 -46
- package/dist/connection-error-BOk97djj.d.ts +6 -0
- package/dist/discord.d.ts +111 -4
- package/dist/discord.js +537 -2
- package/dist/github.d.ts +50 -4
- package/dist/github.js +307 -2
- package/dist/http-error-BtXonO-W.js +12 -0
- package/dist/http-error-K-Ym4lfK.d.ts +11 -0
- package/dist/index.d.ts +48 -220
- package/dist/index.js +90 -52
- package/dist/logger-CpGB9WO_.js +49 -0
- package/dist/parse-error-BAiCLRmk.d.ts +6 -0
- package/dist/safe-fetch-30ZzOKHL.js +20 -0
- package/dist/safe-json-parse-WCg_x1JS.js +15 -0
- package/dist/{safe-json-parse-D8t_4Vm_.js → schedule-reconnect-DSxZJG3h.js} +24 -12
- package/dist/serial-queue-ExmlnpzQ.js +16 -0
- package/dist/slack.d.ts +65 -4
- package/dist/slack.js +443 -2
- package/dist/{types-BVQSU336.d.ts → types-tnOPBc1p.d.ts} +32 -16
- package/package.json +1 -1
- package/dist/discord-source-Q4JRIbNs.js +0 -525
- package/dist/github-source-D7Z1RUbe.js +0 -280
- package/dist/logger-B9E8zvgj.js +0 -69
- package/dist/slack-source-CszepStG.js +0 -400
package/dist/index.js
CHANGED
|
@@ -1,66 +1,104 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
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";
|
|
3
|
+
import { t as FlumeHttpError } from "./http-error-BtXonO-W.js";
|
|
4
|
+
//#region lib/flume-stopped.ts
|
|
5
|
+
/**
|
|
6
|
+
* 停止済みの終端状態。最終ステータスのスナップショットのみ観測できる
|
|
7
|
+
*/
|
|
8
|
+
var FlumeStopped = class {
|
|
9
|
+
props;
|
|
10
|
+
constructor(props) {
|
|
11
|
+
this.props = props;
|
|
12
|
+
Object.freeze(this);
|
|
13
|
+
}
|
|
14
|
+
statuses() {
|
|
15
|
+
return this.props.finalStatuses;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
19
18
|
//#endregion
|
|
20
|
-
//#region lib/flume.ts
|
|
19
|
+
//#region lib/flume-running.ts
|
|
21
20
|
/**
|
|
22
|
-
*
|
|
21
|
+
* 稼働中の Flume。stop() で FlumeStopped へ遷移する。signal が abort されると自動 stop
|
|
23
22
|
*/
|
|
24
|
-
var
|
|
23
|
+
var FlumeRunning = class {
|
|
25
24
|
props;
|
|
26
|
-
|
|
25
|
+
stopPromise = null;
|
|
26
|
+
onAbort;
|
|
27
27
|
constructor(props) {
|
|
28
28
|
this.props = props;
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
...props.deps
|
|
29
|
+
this.onAbort = () => {
|
|
30
|
+
this.stop();
|
|
32
31
|
};
|
|
32
|
+
if (props.signal) props.signal.addEventListener("abort", this.onAbort, { once: true });
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
reconnect: this.props.reconnect,
|
|
39
|
-
signal: this.props.signal,
|
|
40
|
-
deps: this.resolvedDeps,
|
|
41
|
-
...options
|
|
42
|
-
});
|
|
34
|
+
stop() {
|
|
35
|
+
if (this.stopPromise) return this.stopPromise;
|
|
36
|
+
this.stopPromise = this.runStop();
|
|
37
|
+
return this.stopPromise;
|
|
43
38
|
}
|
|
44
|
-
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
39
|
+
statuses() {
|
|
40
|
+
return this.props.sources.map((source) => ({
|
|
41
|
+
name: source.name,
|
|
42
|
+
status: source.status()
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
async runStop() {
|
|
46
|
+
await Promise.allSettled(this.props.sources.map((source) => source.stop()));
|
|
47
|
+
this.props.signal?.removeEventListener("abort", this.onAbort);
|
|
48
|
+
return new FlumeStopped({ finalStatuses: this.props.sources.map((source) => ({
|
|
49
|
+
name: source.name,
|
|
50
|
+
status: source.status()
|
|
51
|
+
})) });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region lib/flume.ts
|
|
56
|
+
/**
|
|
57
|
+
* 起動前の Flume。start() で FlumeRunning へ遷移する
|
|
58
|
+
*/
|
|
59
|
+
var Flume = class {
|
|
60
|
+
props;
|
|
61
|
+
consumed = false;
|
|
62
|
+
constructor(props) {
|
|
63
|
+
this.props = props;
|
|
53
64
|
}
|
|
54
|
-
|
|
55
|
-
return new
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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");
|
|
68
|
+
this.consumed = true;
|
|
69
|
+
const settled = await Promise.allSettled(this.props.sources.map((source) => source.start(handler)));
|
|
70
|
+
const failures = [];
|
|
71
|
+
const started = [];
|
|
72
|
+
for (let i = 0; i < settled.length; i++) {
|
|
73
|
+
const result = settled[i];
|
|
74
|
+
const source = this.props.sources[i];
|
|
75
|
+
if (result === void 0 || source === void 0) continue;
|
|
76
|
+
if (result.status === "rejected") {
|
|
77
|
+
const reason = result.reason;
|
|
78
|
+
failures.push({
|
|
79
|
+
name: source.name,
|
|
80
|
+
error: reason instanceof Error ? reason : new Error(String(reason))
|
|
81
|
+
});
|
|
82
|
+
} else if (result.value instanceof Error) failures.push({
|
|
83
|
+
name: source.name,
|
|
84
|
+
error: result.value
|
|
85
|
+
});
|
|
86
|
+
else started.push(source);
|
|
87
|
+
}
|
|
88
|
+
if (failures.length > 0) {
|
|
89
|
+
await Promise.allSettled(started.map((source) => source.stop()));
|
|
90
|
+
const detail = failures.map((f) => `${f.name}: ${f.error.message}`).join("; ");
|
|
91
|
+
return /* @__PURE__ */ new Error(`Flume.start: ${failures.length} source(s) failed: ${detail}`);
|
|
92
|
+
}
|
|
93
|
+
if (this.props.signal?.aborted) {
|
|
94
|
+
await Promise.allSettled(this.props.sources.map((source) => source.stop()));
|
|
95
|
+
return /* @__PURE__ */ new Error("Flume.start: aborted during start");
|
|
96
|
+
}
|
|
97
|
+
return new FlumeRunning({
|
|
98
|
+
sources: this.props.sources,
|
|
99
|
+
signal: this.props.signal
|
|
62
100
|
});
|
|
63
101
|
}
|
|
64
102
|
};
|
|
65
103
|
//#endregion
|
|
66
|
-
export { Flume, FlumeConnectionError,
|
|
104
|
+
export { Flume, FlumeConnectionError, FlumeHttpError, FlumeLogger, FlumeParseError, FlumeReconnector, FlumeRunning, FlumeStopped, createFlumeDefaultDeps, resolveFlumeReconnectConfig, scheduleFlumeReconnect };
|
|
@@ -0,0 +1,49 @@
|
|
|
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 };
|
|
@@ -0,0 +1,20 @@
|
|
|
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 };
|
|
@@ -0,0 +1,15 @@
|
|
|
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 };
|
|
@@ -64,18 +64,30 @@ var FlumeReconnector = class {
|
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
66
|
//#endregion
|
|
67
|
-
//#region lib/
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
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;
|
|
78
86
|
}
|
|
87
|
+
props.log.info({
|
|
88
|
+
action: "reconnect.scheduled",
|
|
89
|
+
message: `next attempt in ${Math.round(delay)}ms`
|
|
90
|
+
});
|
|
79
91
|
}
|
|
80
92
|
//#endregion
|
|
81
|
-
export {
|
|
93
|
+
export { FlumeConnectionError as a, FlumeParseError as i, FlumeReconnector as n, resolveFlumeReconnectConfig as r, scheduleFlumeReconnect as t };
|
|
@@ -0,0 +1,16 @@
|
|
|
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 };
|
package/dist/slack.d.ts
CHANGED
|
@@ -1,23 +1,84 @@
|
|
|
1
|
-
import { g as FlumeSlackSourceOptions, h as FlumeSlackEnvelope, o as FlumeHandler,
|
|
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";
|
|
2
|
+
import { t as FlumeConnectionError } from "./connection-error-BOk97djj.js";
|
|
3
|
+
import { t as FlumeHttpError } from "./http-error-K-Ym4lfK.js";
|
|
2
4
|
|
|
3
5
|
//#region lib/slack/slack-source.d.ts
|
|
4
6
|
declare class FlumeSlackSource {
|
|
5
7
|
private readonly options;
|
|
8
|
+
readonly name: "slack";
|
|
6
9
|
private socket;
|
|
7
10
|
private reconnector;
|
|
8
11
|
private handler;
|
|
9
12
|
private currentStatus;
|
|
10
13
|
private readonly log;
|
|
11
14
|
private readonly deps;
|
|
15
|
+
private readonly queue;
|
|
16
|
+
private readonly seen;
|
|
12
17
|
constructor(options: FlumeSlackSourceOptions);
|
|
13
|
-
start(handler: FlumeHandler): Promise<void>;
|
|
18
|
+
start(handler: FlumeHandler): Promise<void | Error>;
|
|
14
19
|
stop(): Promise<void>;
|
|
15
20
|
status(): FlumeStatus;
|
|
16
21
|
private connectInternal;
|
|
17
22
|
private handleMessage;
|
|
18
23
|
private scheduleReconnect;
|
|
19
24
|
private setStatus;
|
|
20
|
-
static extractMeta(envelope: FlumeSlackEnvelope): Record<string, string>;
|
|
21
25
|
}
|
|
22
26
|
//#endregion
|
|
23
|
-
|
|
27
|
+
//#region lib/slack/extract-slack-meta.d.ts
|
|
28
|
+
declare function extractSlackMeta(envelope: FlumeSlackEnvelope): Record<string, string>;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region lib/slack/slack-socket-mode.d.ts
|
|
31
|
+
type Deps = Pick<FlumeRuntimeDeps, "WebSocket" | "fetch" | "now">;
|
|
32
|
+
type Props$2 = {
|
|
33
|
+
appToken: string;
|
|
34
|
+
onMessage: (envelope: FlumeSlackEnvelope) => void;
|
|
35
|
+
onConnected: () => void;
|
|
36
|
+
onDisconnected: () => void;
|
|
37
|
+
onLog?: FlumeLogHandler;
|
|
38
|
+
deps: Deps;
|
|
39
|
+
};
|
|
40
|
+
declare class FlumeSlackSocketMode {
|
|
41
|
+
private readonly props;
|
|
42
|
+
private readonly log;
|
|
43
|
+
private ws;
|
|
44
|
+
stopped: boolean;
|
|
45
|
+
private pendingResolve;
|
|
46
|
+
private pendingResolved;
|
|
47
|
+
constructor(props: Props$2);
|
|
48
|
+
connect(): Promise<FlumeConnectionError | FlumeHttpError | null>;
|
|
49
|
+
disconnect(): void;
|
|
50
|
+
isConnected(): boolean;
|
|
51
|
+
private openSocket;
|
|
52
|
+
private completeConnect;
|
|
53
|
+
private onMessage;
|
|
54
|
+
private onClose;
|
|
55
|
+
private onError;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region lib/slack/slack-seen-cache.d.ts
|
|
59
|
+
type Props$1 = {
|
|
60
|
+
maxSize: number;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Slack envelope_id の LRU 風キャッシュ。Slack は ack 失敗時に同じ envelope を再送するため、
|
|
64
|
+
* source レイヤで handler への重複配送を防ぐ
|
|
65
|
+
*/
|
|
66
|
+
declare class FlumeSlackSeenCache {
|
|
67
|
+
private readonly props;
|
|
68
|
+
private seen;
|
|
69
|
+
constructor(props: Props$1);
|
|
70
|
+
has(envelopeId: string): boolean;
|
|
71
|
+
add(envelopeId: string): void;
|
|
72
|
+
trim(): void;
|
|
73
|
+
get size(): number;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region lib/slack/obtain-slack-url.d.ts
|
|
77
|
+
type Props = {
|
|
78
|
+
appToken: string;
|
|
79
|
+
onLog?: FlumeLogHandler;
|
|
80
|
+
deps: Pick<FlumeRuntimeDeps, "fetch" | "now">;
|
|
81
|
+
};
|
|
82
|
+
declare function obtainSlackUrl(props: Props): Promise<string | FlumeHttpError>;
|
|
83
|
+
//#endregion
|
|
84
|
+
export { FlumeSlackConnectionResponseSchema, FlumeSlackEnvelopeSchema, FlumeSlackSeenCache, FlumeSlackSocketMode, FlumeSlackSource, extractSlackMeta, obtainSlackUrl };
|