@interactive-inc/flume 0.10.0 → 0.11.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 +93 -32
- package/dist/discord.d.ts +15 -1
- package/dist/discord.js +304 -35
- package/dist/flume-source.d.ts +58 -14
- package/dist/flume-source.js +142 -87
- package/dist/github.d.ts +5 -1
- package/dist/github.js +217 -69
- package/dist/index.d.ts +223 -46
- package/dist/index.js +466 -149
- package/dist/parse-error.d.ts +9 -0
- package/dist/safe-json-parse.js +1 -1
- package/dist/safe-read-text.js +22 -5
- package/dist/safe-stringify.js +34 -134
- package/dist/schedule-reconnect.js +153 -0
- package/dist/slack.d.ts +8 -1
- package/dist/slack.js +230 -34
- package/dist/time.d.ts +81 -2
- package/dist/time.js +625 -2
- package/package.json +11 -4
- package/dist/connection-error.js +0 -16
- package/dist/http-error.js +0 -12
- package/dist/parse-cron.d.ts +0 -55
- package/dist/time-source.js +0 -427
package/dist/flume-source.d.ts
CHANGED
|
@@ -24,9 +24,9 @@ declare class FlumeLogger {
|
|
|
24
24
|
//#region lib/discord/discord-gateway-message-schema.d.ts
|
|
25
25
|
declare const FlumeGatewayMessageSchema: z.ZodObject<{
|
|
26
26
|
op: z.ZodNumber;
|
|
27
|
-
d: z.ZodUnknown
|
|
28
|
-
s: z.ZodNullable<z.ZodNumber
|
|
29
|
-
t: z.ZodNullable<z.ZodString
|
|
27
|
+
d: z.ZodOptional<z.ZodUnknown>;
|
|
28
|
+
s: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
29
|
+
t: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
30
30
|
}, z.core.$strip>;
|
|
31
31
|
//#endregion
|
|
32
32
|
//#region lib/github/github-notification-schema.d.ts
|
|
@@ -103,7 +103,15 @@ type FlumeTimeEvent = {
|
|
|
103
103
|
meta: Record<string, string>;
|
|
104
104
|
receivedAt: number;
|
|
105
105
|
};
|
|
106
|
-
type
|
|
106
|
+
type FlumeCustomEvent = {
|
|
107
|
+
source: "custom";
|
|
108
|
+
sourceName: string;
|
|
109
|
+
type: string;
|
|
110
|
+
data: Record<string, unknown>;
|
|
111
|
+
meta: Record<string, string>;
|
|
112
|
+
receivedAt: number;
|
|
113
|
+
};
|
|
114
|
+
type FlumeEvent = FlumeDiscordEvent | FlumeSlackEvent | FlumeGitHubEvent | FlumeTimeEvent | FlumeCustomEvent;
|
|
107
115
|
type FlumeEventHandler = (event: FlumeEvent) => void | Promise<void>;
|
|
108
116
|
type FlumeStreamItem = {
|
|
109
117
|
kind: "event";
|
|
@@ -112,7 +120,7 @@ type FlumeStreamItem = {
|
|
|
112
120
|
kind: "log";
|
|
113
121
|
log: FlumeLog;
|
|
114
122
|
};
|
|
115
|
-
type FlumeStreamHandler = (item: FlumeStreamItem) =>
|
|
123
|
+
type FlumeStreamHandler = (item: FlumeStreamItem) => unknown | Promise<unknown>;
|
|
116
124
|
/**
|
|
117
125
|
* `FlumeConfluence` が onEvent に渡す item。Flume 単体の `FlumeStreamItem` に
|
|
118
126
|
* `groupId` (`add(id, ...)` で渡したグループ識別子) をスタンプしたもの。
|
|
@@ -121,7 +129,7 @@ type FlumeStreamHandler = (item: FlumeStreamItem) => void;
|
|
|
121
129
|
type FlumeConfluenceItem = FlumeStreamItem & {
|
|
122
130
|
readonly groupId: string;
|
|
123
131
|
};
|
|
124
|
-
type FlumeConfluenceItemHandler = (item: FlumeConfluenceItem) =>
|
|
132
|
+
type FlumeConfluenceItemHandler = (item: FlumeConfluenceItem) => unknown | Promise<unknown>;
|
|
125
133
|
type FlumeStreamOverflow = "drop-oldest" | "drop-newest";
|
|
126
134
|
type FlumeStreamOptions = {
|
|
127
135
|
/** バッファ上限 (既定 1000)。consumer が遅れて溢れたら onOverflow に従う */buffer?: number; /** バッファ溢れ時の方針 (既定 "drop-oldest") */
|
|
@@ -173,17 +181,28 @@ type FlumeSourceStartContext = {
|
|
|
173
181
|
onStatus?: FlumeSourceLocalStatusHandler;
|
|
174
182
|
reconnect: FlumeReconnectConfig | null;
|
|
175
183
|
/**
|
|
176
|
-
* Flume
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* source
|
|
180
|
-
* Flume.options.signal が未設定なら省略される。
|
|
184
|
+
* Flume に渡された signal をそのまま転送する。
|
|
185
|
+
* connect 中の abort は `FlumeSource` 基底クラスが購読して `stop()` を発火する
|
|
186
|
+
* (進行中の接続を中断して `Flume.open()` を解放する)。connect 完了後の abort は
|
|
187
|
+
* FlumeRunning が runClose を駆動する。source 実装が自前で `fetch(url, { signal })`
|
|
188
|
+
* などへ伝播させたい場合にも使える。Flume.options.signal が未設定なら省略される。
|
|
181
189
|
*/
|
|
182
190
|
signal?: AbortSignal;
|
|
183
191
|
};
|
|
184
192
|
type FlumeDiscordSourceOptions = {
|
|
185
193
|
token: string;
|
|
194
|
+
/**
|
|
195
|
+
* Gateway intent ビットフラグ。既定は Guilds | GuildMessages | DirectMessages。
|
|
196
|
+
* message の `content` 本文が必要な場合は privileged intent の `MessageContent` を
|
|
197
|
+
* Developer Portal で有効化した上で明示的に足す (未承認のまま足すと close 4014 で終端する)
|
|
198
|
+
*/
|
|
186
199
|
intents?: number;
|
|
200
|
+
/**
|
|
201
|
+
* WebSocket open から READY/RESUMED までの上限 (ms)。既定 30_000。
|
|
202
|
+
* HELLO が来ない half-open socket で `connect()` (ひいては `Flume.open()`) が
|
|
203
|
+
* 永久にハングするのを防ぐ
|
|
204
|
+
*/
|
|
205
|
+
handshakeTimeoutMs?: number;
|
|
187
206
|
};
|
|
188
207
|
type FlumeSlackSourceOptions = {
|
|
189
208
|
appToken: string;
|
|
@@ -198,6 +217,12 @@ type FlumeSlackSourceOptions = {
|
|
|
198
217
|
* inbound frame.
|
|
199
218
|
*/
|
|
200
219
|
idleTimeoutMs?: number | null;
|
|
220
|
+
/**
|
|
221
|
+
* WebSocket open から hello 受信までの上限 (ms)。既定 30_000。
|
|
222
|
+
* hello が来ない half-open socket で `connect()` (ひいては `Flume.open()`) が
|
|
223
|
+
* 永久にハングするのを防ぐ
|
|
224
|
+
*/
|
|
225
|
+
handshakeTimeoutMs?: number;
|
|
201
226
|
};
|
|
202
227
|
type FlumeGitHubSourceOptions = {
|
|
203
228
|
token: string;
|
|
@@ -219,7 +244,9 @@ type FlumeTimeMessage = {
|
|
|
219
244
|
/**
|
|
220
245
|
* 起動 / 終了をまたいだ状態を 1 つ載せる純粋な DI ポート。flume 内部で fs / db / network を
|
|
221
246
|
* 触らないように、I/O の場所と方式は host が決める。load の失敗は null 復帰扱い、save の
|
|
222
|
-
* 失敗は best-effort (source 側で log するが throw しない)
|
|
247
|
+
* 失敗は best-effort (source 側で log するが throw しない)。Time source は save を直列実行し、
|
|
248
|
+
* stop 時に queued save の完了を待つ。persister 内から Flume の終了を await しない。
|
|
249
|
+
* stop は load の待機を解除するが、host 側で開始した read IO 自体は中断しない。
|
|
223
250
|
*/
|
|
224
251
|
type FlumeStatePersister<S> = {
|
|
225
252
|
load(): Promise<S | null>;
|
|
@@ -267,6 +294,10 @@ type FlumeGitHubNotification = z.infer<typeof FlumeGitHubNotificationSchema>;
|
|
|
267
294
|
* `FlumeSourceStartContext` (handler / log / deps / onStatus / reconnect) を
|
|
268
295
|
* `start()` で受け取り、subclass の `connect(ctx)` に手渡す。
|
|
269
296
|
*
|
|
297
|
+
* `ctx.signal` の購読も base が行う: connect 中に abort されたら `stop()` を発火して
|
|
298
|
+
* 進行中の接続を中断する (subclass の `disconnect()` が pending な connect を解決する契約)。
|
|
299
|
+
* connect 完了後の abort は Flume / FlumeRunning が runClose 経由で駆動する。
|
|
300
|
+
*
|
|
270
301
|
* subclass のテンプレート:
|
|
271
302
|
*
|
|
272
303
|
* ```ts
|
|
@@ -290,11 +321,19 @@ declare abstract class FlumeSource {
|
|
|
290
321
|
abstract readonly name: string;
|
|
291
322
|
private consumed;
|
|
292
323
|
private stopped;
|
|
324
|
+
private stopPromise;
|
|
293
325
|
private ctx;
|
|
294
326
|
private statusEmitter;
|
|
327
|
+
private abortHandler;
|
|
295
328
|
private readonly queue;
|
|
296
329
|
start(ctx: FlumeSourceStartContext): Promise<Error | null>;
|
|
297
|
-
|
|
330
|
+
/**
|
|
331
|
+
* 冪等。`disconnect()` の throw は捕捉して `Error` として返す (公開境界から reject しない)。
|
|
332
|
+
* Flume.runClose / Flume.rollback は戻り値の Error を `flume.close.failed` /
|
|
333
|
+
* `flume.rollback.failed` として firehose に流す
|
|
334
|
+
*/
|
|
335
|
+
stop(): Promise<Error | null>;
|
|
336
|
+
private runStop;
|
|
298
337
|
status(): FlumeStatus;
|
|
299
338
|
/**
|
|
300
339
|
* subclass が受信した protocol イベントを `FlumeEvent` として handler へ流す。
|
|
@@ -305,14 +344,19 @@ declare abstract class FlumeSource {
|
|
|
305
344
|
* subclass が protocol 状態遷移をユーザーに通知する。同一 (status, detail) の連続は冪等
|
|
306
345
|
*/
|
|
307
346
|
protected setStatus(status: FlumeStatus, detail?: string): void;
|
|
347
|
+
/** await をまたぐ接続処理が、停止後に新しいリソースを作らないための guard。 */
|
|
348
|
+
protected get isStopped(): boolean;
|
|
308
349
|
/** subclass が現在の status を読みたい場合 */
|
|
309
350
|
protected get currentStatus(): FlumeStatus;
|
|
310
351
|
/** subclass が start ctx を再参照したい場合 (stop 後は null) */
|
|
311
352
|
protected get context(): FlumeSourceStartContext | null;
|
|
353
|
+
private isSignalAborted;
|
|
354
|
+
private attachAbortListener;
|
|
355
|
+
private detachAbortListener;
|
|
312
356
|
/** protocol 接続。subclass 実装 */
|
|
313
357
|
protected abstract connect(ctx: FlumeSourceStartContext): Promise<Error | null>;
|
|
314
358
|
/** protocol 切断。subclass 実装。base が `stop()` 内で必ず呼ぶ */
|
|
315
359
|
protected abstract disconnect(): Promise<void> | void;
|
|
316
360
|
}
|
|
317
361
|
//#endregion
|
|
318
|
-
export {
|
|
362
|
+
export { FlumeStatePersister as A, FlumeTimeTick as B, FlumeSlackEnvelope as C, FlumeSourceName as D, FlumeSourceLocalStatusHandler as E, FlumeStreamOverflow as F, FlumeLogger as H, FlumeTimeEvent as I, FlumeTimeMessage as L, FlumeStreamHandler as M, FlumeStreamItem as N, FlumeSourceStartContext as O, FlumeStreamOptions as P, FlumeTimeSourceOptions as R, FlumeSlackConnectionResponse as S, FlumeSlackSourceOptions as T, FlumeTimerHandle as V, FlumeLogInput as _, FlumeCustomEvent as a, FlumeReconnectOptions as b, FlumeErrorHandler as c, FlumeGatewayMessage as d, FlumeGitHubEvent as f, FlumeLogHandler as g, FlumeLog as h, FlumeConfluenceItemHandler as i, FlumeStatus as j, FlumeSourceStatus as k, FlumeEvent as l, FlumeGitHubSourceOptions as m, FlumeCatchupPolicy as n, FlumeDiscordEvent as o, FlumeGitHubNotification as p, FlumeConfluenceItem as r, FlumeDiscordSourceOptions as s, FlumeSource as t, FlumeEventHandler as u, FlumeLogLevel as v, FlumeSlackEvent as w, FlumeRuntimeDeps as x, FlumeReconnectConfig as y, FlumeTimeSourceState as z };
|
package/dist/flume-source.js
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* 任意の値から人が読めるメッセージ文字列を取り出す。
|
|
4
4
|
* `Error.message` getter / `Symbol.toPrimitive` / `toString` / `valueOf` が throw しても固定文字列に fallback。
|
|
5
|
-
*
|
|
5
|
+
* `instanceof` 自体が throw する値 (revoked Proxy 等) にも耐える。自身は決して throw しない
|
|
6
6
|
*/
|
|
7
7
|
function safeErrorMessage(props) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
try {
|
|
9
|
+
if (props.error instanceof Error) {
|
|
10
|
+
const message = props.error.message;
|
|
11
|
+
if (typeof message === "string") return message;
|
|
12
|
+
return "<non-string error message>";
|
|
13
|
+
}
|
|
12
14
|
} catch {
|
|
13
15
|
return "<unreadable error message>";
|
|
14
16
|
}
|
|
@@ -23,10 +25,13 @@ function safeErrorMessage(props) {
|
|
|
23
25
|
/**
|
|
24
26
|
* 任意の値を `Error` インスタンスへ正規化する。すでに Error ならそのまま返し、
|
|
25
27
|
* それ以外は `safeErrorMessage` で安全な文字列化を経由して new Error する。
|
|
26
|
-
*
|
|
28
|
+
* `instanceof` 自体が throw する値 (revoked Proxy 等) や Error コンストラクタが throw する
|
|
29
|
+
* 病的環境でも fallback を返し、決して throw しない
|
|
27
30
|
*/
|
|
28
31
|
function safeNormalizeError(props) {
|
|
29
|
-
|
|
32
|
+
try {
|
|
33
|
+
if (props.value instanceof Error) return props.value;
|
|
34
|
+
} catch {}
|
|
30
35
|
const message = safeErrorMessage({ error: props.value });
|
|
31
36
|
try {
|
|
32
37
|
return new Error(message);
|
|
@@ -43,10 +48,16 @@ function safeNormalizeError(props) {
|
|
|
43
48
|
}
|
|
44
49
|
//#endregion
|
|
45
50
|
//#region lib/utils/attempt.ts
|
|
51
|
+
function isThenable(value) {
|
|
52
|
+
if (typeof value !== "object" && typeof value !== "function") return false;
|
|
53
|
+
if (value === null) return false;
|
|
54
|
+
return "then" in value && typeof value.then === "function";
|
|
55
|
+
}
|
|
46
56
|
function attempt(fn) {
|
|
47
57
|
try {
|
|
48
58
|
const result = fn();
|
|
49
59
|
if (result instanceof Promise) return result.catch((err) => safeNormalizeError({ value: err }));
|
|
60
|
+
if (isThenable(result)) return Promise.resolve(result).then((value) => value, (err) => safeNormalizeError({ value: err }));
|
|
50
61
|
return result;
|
|
51
62
|
} catch (err) {
|
|
52
63
|
return safeNormalizeError({ value: err });
|
|
@@ -71,16 +82,44 @@ var FlumeStartError = class extends Error {
|
|
|
71
82
|
}
|
|
72
83
|
};
|
|
73
84
|
//#endregion
|
|
85
|
+
//#region lib/errors/source-reuse-error.ts
|
|
86
|
+
/** 起動を取得する前の拒否。Flume は他の起動処理が所有する Source を rollback しない。 */
|
|
87
|
+
var FlumeSourceReuseError = class extends FlumeStartError {};
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region lib/utils/safe-invoke-callback.ts
|
|
90
|
+
/**
|
|
91
|
+
* fire-and-forget でユーザーコールバックを呼び出す。sync throw と async reject のどちらも
|
|
92
|
+
* `onError(Error)` に正規化して通知。`onError` 自身が throw しても外に漏らさない。
|
|
93
|
+
* 戻り値を持たない fire-and-forget 専用のため log/出力先には依存しない (caller が onError で決める)
|
|
94
|
+
*/
|
|
95
|
+
function safeInvokeCallback(props) {
|
|
96
|
+
try {
|
|
97
|
+
Promise.resolve(props.fn()).catch((err) => {
|
|
98
|
+
try {
|
|
99
|
+
props.onError(safeNormalizeError({ value: err }));
|
|
100
|
+
} catch {}
|
|
101
|
+
}).catch(() => {});
|
|
102
|
+
} catch (err) {
|
|
103
|
+
try {
|
|
104
|
+
props.onError(safeNormalizeError({ value: err }));
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
74
109
|
//#region lib/utils/safe-now.ts
|
|
75
110
|
/**
|
|
76
|
-
* `deps.now()` を保護する。throw /
|
|
111
|
+
* `deps.now()` を保護する。throw / 非数値 / 非有限値が返った場合は `Date.now()` へ
|
|
112
|
+
* フォールバックする (0 を返すと epoch 1970 が TTL / cron / レート計算へ伝播するため)。
|
|
113
|
+
* `Date.now` 自体まで壊れている病的環境でのみ 0 を返す。
|
|
77
114
|
* IO 境界のため呼び出し側はこの戻り値を信頼できる
|
|
78
115
|
*/
|
|
79
116
|
function safeNow(props) {
|
|
80
117
|
try {
|
|
81
118
|
const value = props.deps.now();
|
|
82
|
-
if (typeof value
|
|
83
|
-
|
|
119
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
120
|
+
} catch {}
|
|
121
|
+
try {
|
|
122
|
+
return Date.now();
|
|
84
123
|
} catch {
|
|
85
124
|
return 0;
|
|
86
125
|
}
|
|
@@ -130,31 +169,38 @@ var FlumeLogger = class FlumeLogger {
|
|
|
130
169
|
error: input.error,
|
|
131
170
|
detail: input.detail
|
|
132
171
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
172
|
+
safeInvokeCallback({
|
|
173
|
+
fn: () => handler(log),
|
|
174
|
+
onError: () => {}
|
|
175
|
+
});
|
|
136
176
|
}
|
|
137
177
|
};
|
|
138
178
|
//#endregion
|
|
139
|
-
//#region lib/utils/
|
|
179
|
+
//#region lib/utils/serial-queue.ts
|
|
140
180
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
181
|
+
* 投入順を保ったまま task を直列実行する。各 task は前の完了を待ってから走る。
|
|
182
|
+
* task が throw しても後続には伝播しない (キュー自体は止まらない)。
|
|
183
|
+
* drain() は待機中に追加された task も含めてキューが空になるまで待つ
|
|
144
184
|
*/
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
185
|
+
var FlumeSerialQueue = class {
|
|
186
|
+
chain = Promise.resolve();
|
|
187
|
+
add(task) {
|
|
188
|
+
const completion = this.chain.then(async () => {
|
|
148
189
|
try {
|
|
149
|
-
|
|
190
|
+
await task();
|
|
150
191
|
} catch {}
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
props.onError(safeNormalizeError({ value: err }));
|
|
155
|
-
} catch {}
|
|
192
|
+
});
|
|
193
|
+
this.chain = completion;
|
|
194
|
+
return completion;
|
|
156
195
|
}
|
|
157
|
-
|
|
196
|
+
async drain() {
|
|
197
|
+
while (true) {
|
|
198
|
+
const current = this.chain;
|
|
199
|
+
await current;
|
|
200
|
+
if (this.chain === current) return;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
158
204
|
//#endregion
|
|
159
205
|
//#region lib/source-helpers/flume-status-emitter.ts
|
|
160
206
|
/**
|
|
@@ -203,55 +249,6 @@ var FlumeStatusEmitter = class {
|
|
|
203
249
|
}
|
|
204
250
|
};
|
|
205
251
|
//#endregion
|
|
206
|
-
//#region lib/utils/serial-queue.ts
|
|
207
|
-
/**
|
|
208
|
-
* 投入順を保ったまま task を直列実行する。各 task は前の完了を待ってから走る。
|
|
209
|
-
* task が throw しても後続には伝播しない (キュー自体は止まらない)。
|
|
210
|
-
* maxDepth を超えた場合は新規 task を drop し onOverflow に通知。
|
|
211
|
-
* cancel() 後の add() は no-op となり drain() は即時 resolve する
|
|
212
|
-
*/
|
|
213
|
-
var FlumeSerialQueue = class {
|
|
214
|
-
props;
|
|
215
|
-
chain = Promise.resolve();
|
|
216
|
-
depth = 0;
|
|
217
|
-
cancelled = false;
|
|
218
|
-
constructor(props = {}) {
|
|
219
|
-
this.props = props;
|
|
220
|
-
}
|
|
221
|
-
add(task) {
|
|
222
|
-
if (this.cancelled) return;
|
|
223
|
-
if (this.props.maxDepth !== void 0 && this.depth >= this.props.maxDepth) {
|
|
224
|
-
this.props.onOverflow?.({
|
|
225
|
-
dropped: 1,
|
|
226
|
-
depth: this.depth
|
|
227
|
-
});
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
this.depth++;
|
|
231
|
-
this.chain = this.chain.then(async () => {
|
|
232
|
-
try {
|
|
233
|
-
await task();
|
|
234
|
-
} catch {} finally {
|
|
235
|
-
this.depth--;
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
async drain() {
|
|
240
|
-
await this.chain;
|
|
241
|
-
}
|
|
242
|
-
cancel() {
|
|
243
|
-
this.cancelled = true;
|
|
244
|
-
this.depth = 0;
|
|
245
|
-
this.chain = Promise.resolve();
|
|
246
|
-
}
|
|
247
|
-
size() {
|
|
248
|
-
return this.depth;
|
|
249
|
-
}
|
|
250
|
-
isCancelled() {
|
|
251
|
-
return this.cancelled;
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
//#endregion
|
|
255
252
|
//#region lib/flume-source.ts
|
|
256
253
|
/**
|
|
257
254
|
* 全 Source の基底クラス。protocol 固有のロジック (`connect` / `disconnect`) のみ
|
|
@@ -260,6 +257,10 @@ var FlumeSerialQueue = class {
|
|
|
260
257
|
* `FlumeSourceStartContext` (handler / log / deps / onStatus / reconnect) を
|
|
261
258
|
* `start()` で受け取り、subclass の `connect(ctx)` に手渡す。
|
|
262
259
|
*
|
|
260
|
+
* `ctx.signal` の購読も base が行う: connect 中に abort されたら `stop()` を発火して
|
|
261
|
+
* 進行中の接続を中断する (subclass の `disconnect()` が pending な connect を解決する契約)。
|
|
262
|
+
* connect 完了後の abort は Flume / FlumeRunning が runClose 経由で駆動する。
|
|
263
|
+
*
|
|
263
264
|
* subclass のテンプレート:
|
|
264
265
|
*
|
|
265
266
|
* ```ts
|
|
@@ -282,29 +283,45 @@ var FlumeSerialQueue = class {
|
|
|
282
283
|
var FlumeSource = class {
|
|
283
284
|
consumed = false;
|
|
284
285
|
stopped = false;
|
|
286
|
+
stopPromise = null;
|
|
285
287
|
ctx = null;
|
|
286
288
|
statusEmitter = null;
|
|
289
|
+
abortHandler = null;
|
|
287
290
|
queue = new FlumeSerialQueue();
|
|
288
291
|
async start(ctx) {
|
|
289
|
-
if (this.consumed) return new
|
|
292
|
+
if (this.consumed) return new FlumeSourceReuseError("Source already started");
|
|
293
|
+
if (this.stopped) return new FlumeSourceReuseError("Source already stopped");
|
|
290
294
|
this.consumed = true;
|
|
291
295
|
this.ctx = ctx;
|
|
292
296
|
this.statusEmitter = new FlumeStatusEmitter({
|
|
293
297
|
log: ctx.log,
|
|
294
298
|
onStatus: ctx.onStatus
|
|
295
299
|
});
|
|
296
|
-
return
|
|
300
|
+
if (this.isSignalAborted(ctx)) return new FlumeStartError(`${this.name}: aborted before connect`);
|
|
301
|
+
this.attachAbortListener(ctx);
|
|
302
|
+
const result = await attempt(async () => await this.connect(ctx));
|
|
303
|
+
this.detachAbortListener(ctx);
|
|
304
|
+
return result instanceof Error ? safeNormalizeError({ value: result }) : result;
|
|
297
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* 冪等。`disconnect()` の throw は捕捉して `Error` として返す (公開境界から reject しない)。
|
|
308
|
+
* Flume.runClose / Flume.rollback は戻り値の Error を `flume.close.failed` /
|
|
309
|
+
* `flume.rollback.failed` として firehose に流す
|
|
310
|
+
*/
|
|
298
311
|
async stop() {
|
|
299
|
-
if (this.
|
|
312
|
+
if (this.stopPromise !== null) return this.stopPromise;
|
|
300
313
|
this.stopped = true;
|
|
301
|
-
|
|
314
|
+
this.stopPromise = this.runStop();
|
|
315
|
+
return this.stopPromise;
|
|
316
|
+
}
|
|
317
|
+
async runStop() {
|
|
318
|
+
const disconnectResult = await attempt(async () => {
|
|
302
319
|
await this.disconnect();
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
320
|
+
});
|
|
321
|
+
await this.queue.drain();
|
|
322
|
+
this.statusEmitter?.set("disconnected");
|
|
323
|
+
this.ctx = null;
|
|
324
|
+
return disconnectResult instanceof Error ? disconnectResult : null;
|
|
308
325
|
}
|
|
309
326
|
status() {
|
|
310
327
|
return this.statusEmitter?.value ?? "disconnected";
|
|
@@ -315,7 +332,7 @@ var FlumeSource = class {
|
|
|
315
332
|
*/
|
|
316
333
|
emit(event) {
|
|
317
334
|
const ctx = this.ctx;
|
|
318
|
-
if (!ctx) return;
|
|
335
|
+
if (!ctx || this.stopped) return;
|
|
319
336
|
this.queue.add(async () => {
|
|
320
337
|
const result = await attempt(() => Promise.resolve(ctx.onEvent(event)));
|
|
321
338
|
if (result instanceof Error) ctx.log.error({
|
|
@@ -329,8 +346,13 @@ var FlumeSource = class {
|
|
|
329
346
|
* subclass が protocol 状態遷移をユーザーに通知する。同一 (status, detail) の連続は冪等
|
|
330
347
|
*/
|
|
331
348
|
setStatus(status, detail) {
|
|
349
|
+
if (this.stopped) return;
|
|
332
350
|
this.statusEmitter?.set(status, detail);
|
|
333
351
|
}
|
|
352
|
+
/** await をまたぐ接続処理が、停止後に新しいリソースを作らないための guard。 */
|
|
353
|
+
get isStopped() {
|
|
354
|
+
return this.stopped;
|
|
355
|
+
}
|
|
334
356
|
/** subclass が現在の status を読みたい場合 */
|
|
335
357
|
get currentStatus() {
|
|
336
358
|
return this.statusEmitter?.value ?? "disconnected";
|
|
@@ -339,6 +361,39 @@ var FlumeSource = class {
|
|
|
339
361
|
get context() {
|
|
340
362
|
return this.ctx;
|
|
341
363
|
}
|
|
364
|
+
isSignalAborted(ctx) {
|
|
365
|
+
const signal = ctx.signal;
|
|
366
|
+
if (!signal) return false;
|
|
367
|
+
const result = attempt(() => signal.aborted === true);
|
|
368
|
+
return result instanceof Error ? true : result;
|
|
369
|
+
}
|
|
370
|
+
attachAbortListener(ctx) {
|
|
371
|
+
const signal = ctx.signal;
|
|
372
|
+
if (!signal) return;
|
|
373
|
+
const handler = () => {
|
|
374
|
+
attempt(async () => {
|
|
375
|
+
await this.stop();
|
|
376
|
+
});
|
|
377
|
+
};
|
|
378
|
+
const result = attempt(() => signal.addEventListener("abort", handler, { once: true }));
|
|
379
|
+
if (result instanceof Error) {
|
|
380
|
+
ctx.log.warn({
|
|
381
|
+
action: "signal.addListener.failed",
|
|
382
|
+
message: safeErrorMessage({ error: result }),
|
|
383
|
+
error: result
|
|
384
|
+
});
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
this.abortHandler = handler;
|
|
388
|
+
}
|
|
389
|
+
detachAbortListener(ctx) {
|
|
390
|
+
const handler = this.abortHandler;
|
|
391
|
+
if (!handler) return;
|
|
392
|
+
this.abortHandler = null;
|
|
393
|
+
const signal = ctx.signal;
|
|
394
|
+
if (!signal) return;
|
|
395
|
+
attempt(() => signal.removeEventListener("abort", handler));
|
|
396
|
+
}
|
|
342
397
|
};
|
|
343
398
|
//#endregion
|
|
344
|
-
export {
|
|
399
|
+
export { safeInvokeCallback as a, FlumeParseError as c, safeErrorMessage as d, safeNow as i, attempt as l, FlumeSerialQueue as n, FlumeSourceReuseError as o, FlumeLogger as r, FlumeStartError as s, FlumeSource as t, safeNormalizeError as u };
|
package/dist/github.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { O as FlumeSourceStartContext, m as FlumeGitHubSourceOptions, p as FlumeGitHubNotification, t as FlumeSource } from "./flume-source.js";
|
|
2
2
|
|
|
3
3
|
//#region lib/github/github-source.d.ts
|
|
4
4
|
declare class FlumeGitHubSource extends FlumeSource {
|
|
@@ -8,6 +8,10 @@ declare class FlumeGitHubSource extends FlumeSource {
|
|
|
8
8
|
constructor(options: FlumeGitHubSourceOptions);
|
|
9
9
|
protected connect(ctx: FlumeSourceStartContext): Promise<Error | null>;
|
|
10
10
|
protected disconnect(): void;
|
|
11
|
+
/**
|
|
12
|
+
* pollInterval が非数値・非有限・0 以下の場合は既定値へフォールバックする
|
|
13
|
+
*/
|
|
14
|
+
private getPollIntervalSec;
|
|
11
15
|
private handleNotifications;
|
|
12
16
|
private safeExtractMeta;
|
|
13
17
|
}
|