@interactive-inc/flume 0.3.0 → 0.6.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.
@@ -0,0 +1,157 @@
1
+ import { l as safeErrorMessage, n as safeInvokeCallback, s as attempt } from "./flume-source-DUvt9aJt.js";
2
+ import { t as FlumeConnectionError } from "./connection-error-HUO3PC3G.js";
3
+ //#region lib/utils/safe-random.ts
4
+ /**
5
+ * `deps.random()` を保護する。throw / 範囲外値 / 非数値が返った場合は 0.5 を返す。
6
+ * 0 以上 1 未満 (Math.random と同等) の値のみそのまま透過
7
+ */
8
+ function safeRandom(props) {
9
+ try {
10
+ const value = props.deps.random();
11
+ if (typeof value !== "number" || !Number.isFinite(value)) return .5;
12
+ if (value < 0 || value >= 1) return .5;
13
+ return value;
14
+ } catch {
15
+ return .5;
16
+ }
17
+ }
18
+ //#endregion
19
+ //#region lib/reconnector.ts
20
+ /**
21
+ * 指数バックオフ + ジッタ付きの再接続スケジューラ。
22
+ * `schedule()` の戻り値: 正の delay = 予約成功 / -1 = 試行上限到達 / 0 = cancel 済み or 内部 timer 拒否。
23
+ * setTimeout コールバック内のユーザー fn が throw / reject しても reconnect ループは止めない
24
+ */
25
+ var FlumeReconnector = class {
26
+ props;
27
+ currentAttempt = 0;
28
+ isAborted = false;
29
+ timer = null;
30
+ constructor(props) {
31
+ this.props = props;
32
+ }
33
+ get attempt() {
34
+ return this.currentAttempt;
35
+ }
36
+ get aborted() {
37
+ return this.isAborted;
38
+ }
39
+ schedule(fn) {
40
+ if (this.isAborted) return 0;
41
+ if (this.currentAttempt >= this.props.maxAttempts) return -1;
42
+ this.clearTimer();
43
+ const delay = this.nextDelay();
44
+ const timerResult = attempt(() => this.props.deps.setTimeout(() => this.runRetry(fn), delay));
45
+ if (timerResult instanceof Error) {
46
+ this.props.log.error({
47
+ action: "reconnect.timer.schedule.error",
48
+ message: safeErrorMessage({ error: timerResult }),
49
+ error: timerResult
50
+ });
51
+ this.timer = null;
52
+ return 0;
53
+ }
54
+ this.timer = timerResult;
55
+ return delay;
56
+ }
57
+ reset() {
58
+ this.currentAttempt = 0;
59
+ }
60
+ cancel() {
61
+ this.isAborted = true;
62
+ this.clearTimer();
63
+ }
64
+ runRetry(fn) {
65
+ this.timer = null;
66
+ safeInvokeCallback({
67
+ fn,
68
+ onError: (error) => {
69
+ this.props.log.error({
70
+ action: "reconnect.timer.error",
71
+ message: safeErrorMessage({ error }),
72
+ error
73
+ });
74
+ }
75
+ });
76
+ }
77
+ clearTimer() {
78
+ if (this.timer === null) return;
79
+ const handle = this.timer;
80
+ const result = attempt(() => this.props.deps.clearTimeout(handle));
81
+ if (result instanceof Error) this.props.log.error({
82
+ action: "reconnect.timer.clear.error",
83
+ message: safeErrorMessage({ error: result }),
84
+ error: result
85
+ });
86
+ this.timer = null;
87
+ }
88
+ nextDelay() {
89
+ const jitter = Math.min(this.props.baseDelay * 2 ** this.currentAttempt, this.props.maxDelay) * (.5 + safeRandom({ deps: this.props.deps }) * .5);
90
+ this.currentAttempt++;
91
+ return jitter;
92
+ }
93
+ };
94
+ //#endregion
95
+ //#region lib/schedule-reconnect.ts
96
+ /**
97
+ * 接続が落ちた際の共通再接続スケジューラ。
98
+ * 再接続の設定状況 (無効 / 中止 / 試行尽き) を見極めてからステータス遷移する。
99
+ * - reconnector が無ければ reconnect.disabled を info ログし disconnected へ
100
+ * - cancel 済みなら reconnect.aborted を info ログし disconnected へ
101
+ * - schedule() が -1 を返したら reconnect.exhausted を error ログし disconnected へ
102
+ * - それ以外は reconnecting へ遷移し reconnect.scheduled を info ログ
103
+ */
104
+ function scheduleFlumeReconnect(props) {
105
+ if (!props.reconnector) {
106
+ props.log.info({
107
+ action: "reconnect.disabled",
108
+ message: "reconnect is disabled, staying disconnected"
109
+ });
110
+ props.setStatus("disconnected");
111
+ return;
112
+ }
113
+ if (props.reconnector.aborted) {
114
+ props.log.info({
115
+ action: "reconnect.aborted",
116
+ message: "reconnector cancelled, staying disconnected"
117
+ });
118
+ props.setStatus("disconnected");
119
+ return;
120
+ }
121
+ const delay = props.reconnector.schedule(props.retry);
122
+ if (delay === -1) {
123
+ const error = new FlumeConnectionError(`reconnect exhausted after ${props.reconnector.attempt} attempts`);
124
+ props.log.error({
125
+ action: "reconnect.exhausted",
126
+ message: safeErrorMessage({ error }),
127
+ error
128
+ });
129
+ props.setStatus("disconnected");
130
+ return;
131
+ }
132
+ props.setStatus("reconnecting");
133
+ props.log.info({
134
+ action: "reconnect.scheduled",
135
+ message: `next attempt in ${Math.round(delay)}ms`,
136
+ detail: {
137
+ attempt: props.reconnector.attempt,
138
+ delayMs: Math.round(delay)
139
+ }
140
+ });
141
+ }
142
+ //#endregion
143
+ //#region lib/utils/is-record.ts
144
+ function isRecord(value) {
145
+ return typeof value === "object" && value !== null;
146
+ }
147
+ //#endregion
148
+ //#region lib/utils/safe-stringify.ts
149
+ /**
150
+ * `JSON.stringify` を `string | Error` に変換するだけのラッパ。
151
+ * cyclic / BigInt / throwing toJSON など標準が throw するケースを Error として返す
152
+ */
153
+ function safeStringify(value) {
154
+ return attempt(() => JSON.stringify(value));
155
+ }
156
+ //#endregion
157
+ export { safeRandom as a, FlumeReconnector as i, isRecord as n, scheduleFlumeReconnect as r, safeStringify as t };
package/dist/slack.d.ts CHANGED
@@ -1,84 +1,24 @@
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
- import { t as FlumeConnectionError } from "./connection-error-BOk97djj.js";
3
- import { t as FlumeHttpError } from "./http-error-K-Ym4lfK.js";
1
+ import { C as FlumeSourceStartContext, b as FlumeSlackSourceOptions, t as FlumeSource, v as FlumeSlackEnvelope } from "./flume-source-DuUFPhSe.js";
4
2
 
5
3
  //#region lib/slack/slack-source.d.ts
6
- declare class FlumeSlackSource {
4
+ declare class FlumeSlackSource extends FlumeSource {
7
5
  private readonly options;
8
6
  readonly name: "slack";
9
7
  private socket;
10
8
  private reconnector;
11
- private handler;
12
- private currentStatus;
13
- private readonly log;
14
- private readonly deps;
15
- private readonly queue;
16
- private readonly seen;
9
+ private internalController;
10
+ private seen;
17
11
  constructor(options: FlumeSlackSourceOptions);
18
- start(handler: FlumeHandler): Promise<FlumeStartResult>;
19
- stop(): Promise<void>;
20
- status(): FlumeStatus;
12
+ protected connect(ctx: FlumeSourceStartContext): Promise<Error | null>;
13
+ protected disconnect(): void;
14
+ private hasWebSocket;
21
15
  private connectInternal;
22
16
  private handleMessage;
17
+ private safeExtractMeta;
23
18
  private scheduleReconnect;
24
- private setStatus;
25
19
  }
26
20
  //#endregion
27
21
  //#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>;
22
+ declare function flumeExtractSlackMeta(envelope: FlumeSlackEnvelope): Record<string, string>;
83
23
  //#endregion
84
- export { FlumeSlackConnectionResponseSchema, FlumeSlackEnvelopeSchema, FlumeSlackSeenCache, FlumeSlackSocketMode, FlumeSlackSource, extractSlackMeta, obtainSlackUrl };
24
+ export { FlumeSlackSource, flumeExtractSlackMeta };