@interactive-inc/flume 0.4.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.
- package/README.md +129 -86
- package/dist/discord.d.ts +5 -13
- package/dist/discord.js +46 -107
- package/dist/flume-source-DUvt9aJt.js +341 -0
- package/dist/flume-source-DuUFPhSe.d.ts +228 -0
- package/dist/github.d.ts +4 -12
- package/dist/github.js +24 -84
- package/dist/index.d.ts +26 -38
- package/dist/index.js +92 -18
- package/dist/safe-json-parse-CfJjt-RY.js +11 -0
- package/dist/{safe-read-text-DgrJ4Uhl.js → safe-read-text-JQd_5vbd.js} +1 -1
- package/dist/{safe-stringify-BWS-uXZP.js → safe-stringify-DbWQw9qe.js} +2 -17
- package/dist/slack.d.ts +5 -13
- package/dist/slack.js +56 -114
- package/package.json +2 -1
- package/dist/safe-invoke-callback-EpWXwfwp.js +0 -170
- package/dist/serial-queue-B9LoBc64.js +0 -162
- package/dist/types-D-tO-Mh2.d.ts +0 -154
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
//#region lib/utils/safe-error-message.ts
|
|
2
|
+
/**
|
|
3
|
+
* 任意の値から人が読めるメッセージ文字列を取り出す。
|
|
4
|
+
* `Error.message` getter / `Symbol.toPrimitive` / `toString` / `valueOf` が throw しても固定文字列に fallback。
|
|
5
|
+
* 自身は決して throw しない
|
|
6
|
+
*/
|
|
7
|
+
function safeErrorMessage(props) {
|
|
8
|
+
if (props.error instanceof Error) try {
|
|
9
|
+
const message = props.error.message;
|
|
10
|
+
if (typeof message === "string") return message;
|
|
11
|
+
return "<non-string error message>";
|
|
12
|
+
} catch {
|
|
13
|
+
return "<unreadable error message>";
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
return String(props.error);
|
|
17
|
+
} catch {
|
|
18
|
+
return "<unprintable error>";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region lib/utils/safe-normalize-error.ts
|
|
23
|
+
/**
|
|
24
|
+
* 任意の値を `Error` インスタンスへ正規化する。すでに Error ならそのまま返し、
|
|
25
|
+
* それ以外は `safeErrorMessage` で安全な文字列化を経由して new Error する。
|
|
26
|
+
* Error コンストラクタ自体が throw する病的環境でも fallback を返し、決して throw しない
|
|
27
|
+
*/
|
|
28
|
+
function safeNormalizeError(props) {
|
|
29
|
+
if (props.value instanceof Error) return props.value;
|
|
30
|
+
const message = safeErrorMessage({ error: props.value });
|
|
31
|
+
try {
|
|
32
|
+
return new Error(message);
|
|
33
|
+
} catch {
|
|
34
|
+
try {
|
|
35
|
+
return /* @__PURE__ */ new Error("unknown error");
|
|
36
|
+
} catch {
|
|
37
|
+
const fallback = Object.create(Error.prototype);
|
|
38
|
+
fallback.name = "Error";
|
|
39
|
+
fallback.message = "unknown error";
|
|
40
|
+
return fallback;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region lib/utils/attempt.ts
|
|
46
|
+
function attempt(fn) {
|
|
47
|
+
try {
|
|
48
|
+
const result = fn();
|
|
49
|
+
if (result instanceof Promise) return result.catch((err) => safeNormalizeError({ value: err }));
|
|
50
|
+
return result;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
return safeNormalizeError({ value: err });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region lib/errors/parse-error.ts
|
|
57
|
+
var FlumeParseError = class extends Error {
|
|
58
|
+
constructor(message, options) {
|
|
59
|
+
super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
|
|
60
|
+
this.name = "FlumeParseError";
|
|
61
|
+
Object.freeze(this);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region lib/errors/start-error.ts
|
|
66
|
+
var FlumeStartError = class extends Error {
|
|
67
|
+
constructor(message, options) {
|
|
68
|
+
super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
|
|
69
|
+
this.name = "FlumeStartError";
|
|
70
|
+
Object.freeze(this);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region lib/utils/safe-now.ts
|
|
75
|
+
/**
|
|
76
|
+
* `deps.now()` を保護する。throw / 非数値が返った場合は 0 を返す。
|
|
77
|
+
* IO 境界のため呼び出し側はこの戻り値を信頼できる
|
|
78
|
+
*/
|
|
79
|
+
function safeNow(props) {
|
|
80
|
+
try {
|
|
81
|
+
const value = props.deps.now();
|
|
82
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return 0;
|
|
83
|
+
return value;
|
|
84
|
+
} catch {
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region lib/logger.ts
|
|
90
|
+
/**
|
|
91
|
+
* 構造化ログを onLog に流す。handler が throw / reject してもループは継続する
|
|
92
|
+
*/
|
|
93
|
+
var FlumeLogger = class FlumeLogger {
|
|
94
|
+
props;
|
|
95
|
+
constructor(props) {
|
|
96
|
+
this.props = props;
|
|
97
|
+
Object.freeze(this);
|
|
98
|
+
}
|
|
99
|
+
debug(entry) {
|
|
100
|
+
this.emit("debug", entry);
|
|
101
|
+
}
|
|
102
|
+
info(entry) {
|
|
103
|
+
this.emit("info", entry);
|
|
104
|
+
}
|
|
105
|
+
warn(entry) {
|
|
106
|
+
this.emit("warn", entry);
|
|
107
|
+
}
|
|
108
|
+
error(entry) {
|
|
109
|
+
this.emit("error", entry);
|
|
110
|
+
}
|
|
111
|
+
get handler() {
|
|
112
|
+
return this.props.handler;
|
|
113
|
+
}
|
|
114
|
+
child(source) {
|
|
115
|
+
return new FlumeLogger({
|
|
116
|
+
source,
|
|
117
|
+
handler: this.props.handler,
|
|
118
|
+
deps: this.props.deps
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
emit(level, input) {
|
|
122
|
+
const handler = this.props.handler;
|
|
123
|
+
if (!handler) return;
|
|
124
|
+
const log = {
|
|
125
|
+
level,
|
|
126
|
+
source: this.props.source,
|
|
127
|
+
action: input.action,
|
|
128
|
+
message: input.message,
|
|
129
|
+
timestamp: safeNow({ deps: this.props.deps }),
|
|
130
|
+
error: input.error,
|
|
131
|
+
detail: input.detail
|
|
132
|
+
};
|
|
133
|
+
try {
|
|
134
|
+
Promise.resolve(handler(log)).catch(() => {});
|
|
135
|
+
} catch {}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region lib/utils/safe-invoke-callback.ts
|
|
140
|
+
/**
|
|
141
|
+
* fire-and-forget でユーザーコールバックを呼び出す。sync throw と async reject のどちらも
|
|
142
|
+
* `onError(Error)` に正規化して通知。`onError` 自身が throw しても外に漏らさない。
|
|
143
|
+
* 戻り値を持たない fire-and-forget 専用のため log/出力先には依存しない (caller が onError で決める)
|
|
144
|
+
*/
|
|
145
|
+
function safeInvokeCallback(props) {
|
|
146
|
+
try {
|
|
147
|
+
Promise.resolve(props.fn()).catch((err) => {
|
|
148
|
+
try {
|
|
149
|
+
props.onError(safeNormalizeError({ value: err }));
|
|
150
|
+
} catch {}
|
|
151
|
+
}).catch(() => {});
|
|
152
|
+
} catch (err) {
|
|
153
|
+
try {
|
|
154
|
+
props.onError(safeNormalizeError({ value: err }));
|
|
155
|
+
} catch {}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region lib/source-helpers/flume-status-emitter.ts
|
|
160
|
+
/**
|
|
161
|
+
* Source の `currentStatus` と `onStatus` 通知を集約する。
|
|
162
|
+
* 同一 (status, detail) の連続遷移は冪等に握り潰し、ユーザーコールバックは `safeInvokeCallback`
|
|
163
|
+
* 経由で例外を隔離する
|
|
164
|
+
*/
|
|
165
|
+
var FlumeStatusEmitter = class {
|
|
166
|
+
props;
|
|
167
|
+
currentStatus = "disconnected";
|
|
168
|
+
currentDetail = null;
|
|
169
|
+
constructor(props) {
|
|
170
|
+
this.props = props;
|
|
171
|
+
}
|
|
172
|
+
get value() {
|
|
173
|
+
return this.currentStatus;
|
|
174
|
+
}
|
|
175
|
+
set(next, detail) {
|
|
176
|
+
const normalizedDetail = detail ?? null;
|
|
177
|
+
if (this.currentStatus === next && this.currentDetail === normalizedDetail) return;
|
|
178
|
+
const prev = this.currentStatus;
|
|
179
|
+
const suffix = detail ? ` (${detail})` : "";
|
|
180
|
+
this.props.log.info({
|
|
181
|
+
action: "status",
|
|
182
|
+
message: `${prev} → ${next}${suffix}`,
|
|
183
|
+
detail: {
|
|
184
|
+
from: prev,
|
|
185
|
+
to: next,
|
|
186
|
+
reason: normalizedDetail
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
this.currentStatus = next;
|
|
190
|
+
this.currentDetail = normalizedDetail;
|
|
191
|
+
const onStatus = this.props.onStatus;
|
|
192
|
+
if (!onStatus) return;
|
|
193
|
+
safeInvokeCallback({
|
|
194
|
+
fn: detail !== void 0 ? () => onStatus(next, detail) : () => onStatus(next),
|
|
195
|
+
onError: (error) => {
|
|
196
|
+
this.props.log.error({
|
|
197
|
+
action: "onStatus.error",
|
|
198
|
+
message: safeErrorMessage({ error }),
|
|
199
|
+
error
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
//#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
|
+
//#region lib/flume-source.ts
|
|
256
|
+
/**
|
|
257
|
+
* 全 Source の基底クラス。protocol 固有のロジック (`connect` / `disconnect`) のみ
|
|
258
|
+
* subclass に実装させ、queue / status / handler 安全呼び出しといった共通の
|
|
259
|
+
* cross-cutting concern は base が引き受ける。Flume 側で全 source に注入される
|
|
260
|
+
* `FlumeSourceStartContext` (handler / log / deps / onStatus / reconnect) を
|
|
261
|
+
* `start()` で受け取り、subclass の `connect(ctx)` に手渡す。
|
|
262
|
+
*
|
|
263
|
+
* subclass のテンプレート:
|
|
264
|
+
*
|
|
265
|
+
* ```ts
|
|
266
|
+
* export class MySource extends FlumeSource {
|
|
267
|
+
* readonly name = "my-source"
|
|
268
|
+
*
|
|
269
|
+
* constructor(private readonly options: { apiKey: string }) {
|
|
270
|
+
* super()
|
|
271
|
+
* }
|
|
272
|
+
*
|
|
273
|
+
* protected async connect(ctx: FlumeSourceStartContext): Promise<Error | null> {
|
|
274
|
+
* // 接続して onEvent で this.emit({...}) / 状態遷移で this.setStatus(...)
|
|
275
|
+
* return null
|
|
276
|
+
* }
|
|
277
|
+
*
|
|
278
|
+
* protected disconnect(): void { ... }
|
|
279
|
+
* }
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
var FlumeSource = class {
|
|
283
|
+
consumed = false;
|
|
284
|
+
stopped = false;
|
|
285
|
+
ctx = null;
|
|
286
|
+
statusEmitter = null;
|
|
287
|
+
queue = new FlumeSerialQueue();
|
|
288
|
+
async start(ctx) {
|
|
289
|
+
if (this.consumed) return new FlumeStartError(`${this.name}: already started`);
|
|
290
|
+
this.consumed = true;
|
|
291
|
+
this.ctx = ctx;
|
|
292
|
+
this.statusEmitter = new FlumeStatusEmitter({
|
|
293
|
+
log: ctx.log,
|
|
294
|
+
onStatus: ctx.onStatus
|
|
295
|
+
});
|
|
296
|
+
return await this.connect(ctx);
|
|
297
|
+
}
|
|
298
|
+
async stop() {
|
|
299
|
+
if (this.stopped) return;
|
|
300
|
+
this.stopped = true;
|
|
301
|
+
await this.disconnect();
|
|
302
|
+
await this.queue.drain();
|
|
303
|
+
this.statusEmitter?.set("disconnected");
|
|
304
|
+
this.ctx = null;
|
|
305
|
+
}
|
|
306
|
+
status() {
|
|
307
|
+
return this.statusEmitter?.value ?? "disconnected";
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* subclass が受信した protocol イベントを `FlumeEvent` として handler へ流す。
|
|
311
|
+
* handler の throw / async reject は queue 内で catch + log し、後続を止めない
|
|
312
|
+
*/
|
|
313
|
+
emit(event) {
|
|
314
|
+
const ctx = this.ctx;
|
|
315
|
+
if (!ctx) return;
|
|
316
|
+
this.queue.add(async () => {
|
|
317
|
+
const result = await attempt(() => Promise.resolve(ctx.onEvent(event)));
|
|
318
|
+
if (result instanceof Error) ctx.log.error({
|
|
319
|
+
action: "onEvent.error",
|
|
320
|
+
message: safeErrorMessage({ error: result }),
|
|
321
|
+
error: result
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* subclass が protocol 状態遷移をユーザーに通知する。同一 (status, detail) の連続は冪等
|
|
327
|
+
*/
|
|
328
|
+
setStatus(status, detail) {
|
|
329
|
+
this.statusEmitter?.set(status, detail);
|
|
330
|
+
}
|
|
331
|
+
/** subclass が現在の status を読みたい場合 */
|
|
332
|
+
get currentStatus() {
|
|
333
|
+
return this.statusEmitter?.value ?? "disconnected";
|
|
334
|
+
}
|
|
335
|
+
/** subclass が start ctx を再参照したい場合 (stop 後は null) */
|
|
336
|
+
get context() {
|
|
337
|
+
return this.ctx;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
//#endregion
|
|
341
|
+
export { FlumeStartError as a, safeNormalizeError as c, safeNow as i, safeErrorMessage as l, safeInvokeCallback as n, FlumeParseError as o, FlumeLogger as r, attempt as s, FlumeSource as t };
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
|
|
3
|
+
//#region lib/logger.d.ts
|
|
4
|
+
type Props = {
|
|
5
|
+
source: string;
|
|
6
|
+
handler?: FlumeLogHandler;
|
|
7
|
+
deps: Pick<FlumeRuntimeDeps, "now">;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* 構造化ログを onLog に流す。handler が throw / reject してもループは継続する
|
|
11
|
+
*/
|
|
12
|
+
declare class FlumeLogger {
|
|
13
|
+
private readonly props;
|
|
14
|
+
constructor(props: Props);
|
|
15
|
+
debug(entry: FlumeLogInput): void;
|
|
16
|
+
info(entry: FlumeLogInput): void;
|
|
17
|
+
warn(entry: FlumeLogInput): void;
|
|
18
|
+
error(entry: FlumeLogInput): void;
|
|
19
|
+
get handler(): FlumeLogHandler | undefined;
|
|
20
|
+
child(source: string): FlumeLogger;
|
|
21
|
+
private emit;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region lib/discord/discord-gateway-message-schema.d.ts
|
|
25
|
+
declare const FlumeGatewayMessageSchema: z.ZodObject<{
|
|
26
|
+
op: z.ZodNumber;
|
|
27
|
+
d: z.ZodUnknown;
|
|
28
|
+
s: z.ZodNullable<z.ZodNumber>;
|
|
29
|
+
t: z.ZodNullable<z.ZodString>;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region lib/github/github-notification-schema.d.ts
|
|
33
|
+
declare const FlumeGitHubNotificationSchema: z.ZodObject<{
|
|
34
|
+
id: z.ZodString;
|
|
35
|
+
reason: z.ZodString;
|
|
36
|
+
unread: z.ZodBoolean;
|
|
37
|
+
updated_at: z.ZodString;
|
|
38
|
+
subject: z.ZodObject<{
|
|
39
|
+
title: z.ZodString;
|
|
40
|
+
url: z.ZodNullable<z.ZodString>;
|
|
41
|
+
type: z.ZodString;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
repository: z.ZodObject<{
|
|
44
|
+
full_name: z.ZodString;
|
|
45
|
+
}, z.core.$strip>;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region lib/slack/slack-connection-response-schema.d.ts
|
|
49
|
+
declare const FlumeSlackConnectionResponseSchema: z.ZodObject<{
|
|
50
|
+
ok: z.ZodBoolean;
|
|
51
|
+
url: z.ZodOptional<z.ZodString>;
|
|
52
|
+
error: z.ZodOptional<z.ZodString>;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region lib/slack/slack-envelope-schema.d.ts
|
|
56
|
+
declare const FlumeSlackEnvelopeSchema: z.ZodObject<{
|
|
57
|
+
envelope_id: z.ZodString;
|
|
58
|
+
type: z.ZodString;
|
|
59
|
+
payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
60
|
+
accepts_response_payload: z.ZodOptional<z.ZodBoolean>;
|
|
61
|
+
retry_attempt: z.ZodOptional<z.ZodNumber>;
|
|
62
|
+
retry_reason: z.ZodOptional<z.ZodString>;
|
|
63
|
+
}, z.core.$strip>;
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region lib/types.d.ts
|
|
66
|
+
type FlumeTimerHandle = unknown;
|
|
67
|
+
type FlumeRuntimeDeps = {
|
|
68
|
+
fetch(url: string | URL, init?: RequestInit): Promise<Response>;
|
|
69
|
+
WebSocket: (new (url: string | URL) => WebSocket) | null;
|
|
70
|
+
now(): number;
|
|
71
|
+
random(): number;
|
|
72
|
+
setTimeout(fn: () => void, ms: number): FlumeTimerHandle;
|
|
73
|
+
clearTimeout(id: FlumeTimerHandle): void;
|
|
74
|
+
setInterval(fn: () => void, ms: number): FlumeTimerHandle;
|
|
75
|
+
clearInterval(id: FlumeTimerHandle): void;
|
|
76
|
+
};
|
|
77
|
+
type FlumeSourceName = "discord" | "slack" | "github";
|
|
78
|
+
type FlumeDiscordEvent = {
|
|
79
|
+
source: "discord";
|
|
80
|
+
type: string;
|
|
81
|
+
data: Record<string, unknown>;
|
|
82
|
+
meta: Record<string, string>;
|
|
83
|
+
receivedAt: number;
|
|
84
|
+
};
|
|
85
|
+
type FlumeSlackEvent = {
|
|
86
|
+
source: "slack";
|
|
87
|
+
type: string;
|
|
88
|
+
data: Record<string, unknown>;
|
|
89
|
+
meta: Record<string, string>;
|
|
90
|
+
receivedAt: number;
|
|
91
|
+
};
|
|
92
|
+
type FlumeGitHubEvent = {
|
|
93
|
+
source: "github";
|
|
94
|
+
type: "notification";
|
|
95
|
+
data: FlumeGitHubNotification;
|
|
96
|
+
meta: Record<string, string>;
|
|
97
|
+
receivedAt: number;
|
|
98
|
+
};
|
|
99
|
+
type FlumeEvent = FlumeDiscordEvent | FlumeSlackEvent | FlumeGitHubEvent;
|
|
100
|
+
type FlumeEventHandler = (event: FlumeEvent) => void | Promise<void>;
|
|
101
|
+
type FlumeStatus = "disconnected" | "connecting" | "connected" | "reconnecting";
|
|
102
|
+
type FlumeStatusEvent = {
|
|
103
|
+
source: string;
|
|
104
|
+
status: FlumeStatus;
|
|
105
|
+
detail?: string;
|
|
106
|
+
};
|
|
107
|
+
type FlumeStatusHandler = (event: FlumeStatusEvent) => void;
|
|
108
|
+
type FlumeSourceStatus = {
|
|
109
|
+
/**
|
|
110
|
+
* 多くは `FlumeSourceName` のいずれかだが、`source.name` getter が throw する
|
|
111
|
+
* 第三者 FlumeSource 実装に備えて `string` まで広げてある (fallback で `"?"`)
|
|
112
|
+
*/
|
|
113
|
+
source: string;
|
|
114
|
+
status: FlumeStatus;
|
|
115
|
+
};
|
|
116
|
+
type FlumeLogLevel = "debug" | "info" | "warn" | "error";
|
|
117
|
+
type FlumeLog = {
|
|
118
|
+
level: FlumeLogLevel;
|
|
119
|
+
source: string;
|
|
120
|
+
action: string;
|
|
121
|
+
message: string;
|
|
122
|
+
error?: Error;
|
|
123
|
+
detail?: Record<string, unknown>;
|
|
124
|
+
timestamp: number;
|
|
125
|
+
};
|
|
126
|
+
type FlumeLogHandler = (log: FlumeLog) => void;
|
|
127
|
+
type FlumeLogInput = {
|
|
128
|
+
action: string;
|
|
129
|
+
message: string;
|
|
130
|
+
error?: Error;
|
|
131
|
+
detail?: Record<string, unknown>;
|
|
132
|
+
};
|
|
133
|
+
type FlumeReconnectOptions = {
|
|
134
|
+
maxAttempts?: number;
|
|
135
|
+
baseDelay?: number;
|
|
136
|
+
maxDelay?: number;
|
|
137
|
+
};
|
|
138
|
+
type FlumeReconnectConfig = {
|
|
139
|
+
maxAttempts: number;
|
|
140
|
+
baseDelay: number;
|
|
141
|
+
maxDelay: number;
|
|
142
|
+
};
|
|
143
|
+
type FlumeSourceLocalStatusHandler = (status: FlumeStatus, detail?: string) => void;
|
|
144
|
+
type FlumeSourceStartContext = {
|
|
145
|
+
onEvent: FlumeEventHandler;
|
|
146
|
+
log: FlumeLogger;
|
|
147
|
+
deps: FlumeRuntimeDeps;
|
|
148
|
+
onStatus: FlumeSourceLocalStatusHandler;
|
|
149
|
+
reconnect: FlumeReconnectConfig | null;
|
|
150
|
+
};
|
|
151
|
+
type FlumeDiscordSourceOptions = {
|
|
152
|
+
token: string;
|
|
153
|
+
intents?: number;
|
|
154
|
+
};
|
|
155
|
+
type FlumeSlackSourceOptions = {
|
|
156
|
+
appToken: string;
|
|
157
|
+
/**
|
|
158
|
+
* Bot token (`xoxb-`). Slack Socket Mode (受信) には不要だが、ホスト側 (返信や
|
|
159
|
+
* `auth.test` での self 検出) が必ず使うため型で保持を強制する
|
|
160
|
+
*/
|
|
161
|
+
botToken: string;
|
|
162
|
+
};
|
|
163
|
+
type FlumeGitHubSourceOptions = {
|
|
164
|
+
token: string;
|
|
165
|
+
pollInterval?: number;
|
|
166
|
+
};
|
|
167
|
+
type FlumeGatewayMessage = z.infer<typeof FlumeGatewayMessageSchema>;
|
|
168
|
+
type FlumeSlackEnvelope = z.infer<typeof FlumeSlackEnvelopeSchema>;
|
|
169
|
+
type FlumeSlackConnectionResponse = z.infer<typeof FlumeSlackConnectionResponseSchema>;
|
|
170
|
+
type FlumeGitHubNotification = z.infer<typeof FlumeGitHubNotificationSchema>;
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region lib/flume-source.d.ts
|
|
173
|
+
/**
|
|
174
|
+
* 全 Source の基底クラス。protocol 固有のロジック (`connect` / `disconnect`) のみ
|
|
175
|
+
* subclass に実装させ、queue / status / handler 安全呼び出しといった共通の
|
|
176
|
+
* cross-cutting concern は base が引き受ける。Flume 側で全 source に注入される
|
|
177
|
+
* `FlumeSourceStartContext` (handler / log / deps / onStatus / reconnect) を
|
|
178
|
+
* `start()` で受け取り、subclass の `connect(ctx)` に手渡す。
|
|
179
|
+
*
|
|
180
|
+
* subclass のテンプレート:
|
|
181
|
+
*
|
|
182
|
+
* ```ts
|
|
183
|
+
* export class MySource extends FlumeSource {
|
|
184
|
+
* readonly name = "my-source"
|
|
185
|
+
*
|
|
186
|
+
* constructor(private readonly options: { apiKey: string }) {
|
|
187
|
+
* super()
|
|
188
|
+
* }
|
|
189
|
+
*
|
|
190
|
+
* protected async connect(ctx: FlumeSourceStartContext): Promise<Error | null> {
|
|
191
|
+
* // 接続して onEvent で this.emit({...}) / 状態遷移で this.setStatus(...)
|
|
192
|
+
* return null
|
|
193
|
+
* }
|
|
194
|
+
*
|
|
195
|
+
* protected disconnect(): void { ... }
|
|
196
|
+
* }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
declare abstract class FlumeSource {
|
|
200
|
+
abstract readonly name: string;
|
|
201
|
+
private consumed;
|
|
202
|
+
private stopped;
|
|
203
|
+
private ctx;
|
|
204
|
+
private statusEmitter;
|
|
205
|
+
private readonly queue;
|
|
206
|
+
start(ctx: FlumeSourceStartContext): Promise<Error | null>;
|
|
207
|
+
stop(): Promise<void>;
|
|
208
|
+
status(): FlumeStatus;
|
|
209
|
+
/**
|
|
210
|
+
* subclass が受信した protocol イベントを `FlumeEvent` として handler へ流す。
|
|
211
|
+
* handler の throw / async reject は queue 内で catch + log し、後続を止めない
|
|
212
|
+
*/
|
|
213
|
+
protected emit(event: FlumeEvent): void;
|
|
214
|
+
/**
|
|
215
|
+
* subclass が protocol 状態遷移をユーザーに通知する。同一 (status, detail) の連続は冪等
|
|
216
|
+
*/
|
|
217
|
+
protected setStatus(status: FlumeStatus, detail?: string): void;
|
|
218
|
+
/** subclass が現在の status を読みたい場合 */
|
|
219
|
+
protected get currentStatus(): FlumeStatus;
|
|
220
|
+
/** subclass が start ctx を再参照したい場合 (stop 後は null) */
|
|
221
|
+
protected get context(): FlumeSourceStartContext | null;
|
|
222
|
+
/** protocol 接続。subclass 実装 */
|
|
223
|
+
protected abstract connect(ctx: FlumeSourceStartContext): Promise<Error | null>;
|
|
224
|
+
/** protocol 切断。subclass 実装。base が `stop()` 内で必ず呼ぶ */
|
|
225
|
+
protected abstract disconnect(): Promise<void> | void;
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
export { FlumeSourceStartContext as C, FlumeStatusHandler as D, FlumeStatusEvent as E, FlumeTimerHandle as O, FlumeSourceName as S, FlumeStatus as T, FlumeSlackConnectionResponse as _, FlumeEventHandler as a, FlumeSlackSourceOptions as b, FlumeGitHubNotification as c, FlumeLogHandler as d, FlumeLogInput as f, FlumeRuntimeDeps as g, FlumeReconnectOptions as h, FlumeEvent as i, FlumeLogger as k, FlumeGitHubSourceOptions as l, FlumeReconnectConfig as m, FlumeDiscordEvent as n, FlumeGatewayMessage as o, FlumeLogLevel as p, FlumeDiscordSourceOptions as r, FlumeGitHubEvent as s, FlumeSource as t, FlumeLog as u, FlumeSlackEnvelope as v, FlumeSourceStatus as w, FlumeSourceLocalStatusHandler as x, FlumeSlackEvent as y };
|
package/dist/github.d.ts
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { C as FlumeSourceStartContext, c as FlumeGitHubNotification, l as FlumeGitHubSourceOptions, t as FlumeSource } from "./flume-source-DuUFPhSe.js";
|
|
2
2
|
|
|
3
3
|
//#region lib/github/github-source.d.ts
|
|
4
|
-
declare class FlumeGitHubSource {
|
|
4
|
+
declare class FlumeGitHubSource extends FlumeSource {
|
|
5
5
|
private readonly options;
|
|
6
6
|
readonly name: "github";
|
|
7
7
|
private poller;
|
|
8
|
-
private handler;
|
|
9
|
-
private readonly log;
|
|
10
|
-
private readonly deps;
|
|
11
|
-
private readonly queue;
|
|
12
|
-
private readonly signals;
|
|
13
|
-
private readonly statusEmitter;
|
|
14
|
-
private readonly onSignalAbort;
|
|
15
8
|
constructor(options: FlumeGitHubSourceOptions);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
status(): FlumeStatus;
|
|
9
|
+
protected connect(ctx: FlumeSourceStartContext): Promise<Error | null>;
|
|
10
|
+
protected disconnect(): void;
|
|
19
11
|
private handleNotifications;
|
|
20
12
|
private safeExtractMeta;
|
|
21
13
|
}
|