@interactive-inc/flume 0.4.0 → 0.9.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 +232 -95
- package/dist/discord.d.ts +5 -13
- package/dist/discord.js +48 -108
- package/dist/flume-source.d.ts +269 -0
- package/dist/flume-source.js +344 -0
- package/dist/github.d.ts +4 -12
- package/dist/github.js +33 -88
- package/dist/index.d.ts +133 -42
- package/dist/index.js +340 -59
- package/dist/is-record.js +6 -0
- package/dist/parse-error.d.ts +9 -0
- package/dist/safe-json-parse.js +11 -0
- package/dist/{safe-read-text-DgrJ4Uhl.js → safe-read-text.js} +2 -2
- package/dist/{safe-stringify-BWS-uXZP.js → safe-stringify.js} +7 -28
- package/dist/slack.d.ts +5 -13
- package/dist/slack.js +65 -118
- package/dist/time.d.ts +45 -0
- package/dist/time.js +319 -0
- package/package.json +7 -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
- /package/dist/{connection-error-HUO3PC3G.js → connection-error.js} +0 -0
- /package/dist/{http-error-CPSKoSie.js → http-error.js} +0 -0
package/dist/discord.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { t as FlumeConnectionError } from "./connection-error
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { a as FlumeStartError, c as safeNormalizeError, i as safeNow, l as safeErrorMessage, n as safeInvokeCallback, o as FlumeParseError, r as FlumeLogger, s as attempt, t as FlumeSource } from "./flume-source.js";
|
|
2
|
+
import { t as FlumeConnectionError } from "./connection-error.js";
|
|
3
|
+
import { i as safeRandom, n as scheduleFlumeReconnect, r as FlumeReconnector, t as safeStringify } from "./safe-stringify.js";
|
|
4
|
+
import { t as isRecord } from "./is-record.js";
|
|
5
|
+
import { t as safeJsonParse } from "./safe-json-parse.js";
|
|
5
6
|
import { z } from "zod/v4";
|
|
6
7
|
//#region lib/discord/discord-gateway-session.ts
|
|
7
8
|
var FlumeDiscordGatewaySession = class FlumeDiscordGatewaySession {
|
|
@@ -647,89 +648,38 @@ function flumeExtractDiscordMeta(eventName, eventData) {
|
|
|
647
648
|
//#endregion
|
|
648
649
|
//#region lib/discord/discord-source.ts
|
|
649
650
|
const DEFAULT_INTENTS = FlumeDiscordGatewayIntents.Guilds | FlumeDiscordGatewayIntents.GuildMessages | FlumeDiscordGatewayIntents.DirectMessages;
|
|
650
|
-
var FlumeDiscordSource = class {
|
|
651
|
+
var FlumeDiscordSource = class extends FlumeSource {
|
|
651
652
|
options;
|
|
652
653
|
name = "discord";
|
|
653
654
|
gateway = null;
|
|
654
655
|
reconnector = null;
|
|
655
|
-
handler = null;
|
|
656
|
-
log;
|
|
657
|
-
deps;
|
|
658
|
-
queue = new FlumeSerialQueue();
|
|
659
|
-
signals;
|
|
660
|
-
statusEmitter;
|
|
661
|
-
onSignalAbort = () => {
|
|
662
|
-
safeInvokeCallback({
|
|
663
|
-
fn: () => this.stop(),
|
|
664
|
-
onError: (error) => {
|
|
665
|
-
this.log.error({
|
|
666
|
-
action: "signal.abort.stop.failed",
|
|
667
|
-
message: safeErrorMessage({ error }),
|
|
668
|
-
error
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
});
|
|
672
|
-
};
|
|
673
656
|
constructor(options) {
|
|
657
|
+
super();
|
|
674
658
|
this.options = options;
|
|
675
|
-
this.deps = options.deps ?? createFlumeDefaultDeps();
|
|
676
|
-
this.log = new FlumeLogger({
|
|
677
|
-
source: "discord",
|
|
678
|
-
handler: options.onLog,
|
|
679
|
-
deps: this.deps
|
|
680
|
-
});
|
|
681
|
-
this.signals = new FlumeSignalRegistry({
|
|
682
|
-
log: this.log,
|
|
683
|
-
onAbort: this.onSignalAbort
|
|
684
|
-
});
|
|
685
|
-
this.statusEmitter = new FlumeStatusEmitter({
|
|
686
|
-
log: this.log,
|
|
687
|
-
onStatus: options.onStatus
|
|
688
|
-
});
|
|
689
|
-
const rc = resolveFlumeReconnectConfig(options.reconnect);
|
|
690
|
-
if (rc) this.reconnector = new FlumeReconnector({
|
|
691
|
-
...rc,
|
|
692
|
-
log: this.log,
|
|
693
|
-
deps: this.deps
|
|
694
|
-
});
|
|
695
659
|
}
|
|
696
|
-
async
|
|
697
|
-
if (this.
|
|
698
|
-
if (!this.
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
this.log.info({
|
|
703
|
-
action: "source.start",
|
|
704
|
-
message: "starting Discord source"
|
|
660
|
+
async connect(ctx) {
|
|
661
|
+
if (!this.hasWebSocket(ctx)) return new FlumeStartError("Discord source: deps.WebSocket is null (no WebSocket runtime available)");
|
|
662
|
+
if (ctx.reconnect && !this.reconnector) this.reconnector = new FlumeReconnector({
|
|
663
|
+
...ctx.reconnect,
|
|
664
|
+
log: ctx.log,
|
|
665
|
+
deps: ctx.deps
|
|
705
666
|
});
|
|
706
|
-
return await this.connectInternal();
|
|
667
|
+
return await this.connectInternal(ctx);
|
|
707
668
|
}
|
|
708
|
-
|
|
709
|
-
this.
|
|
710
|
-
this.log.info({
|
|
711
|
-
action: "source.stop",
|
|
712
|
-
message: "stopping Discord source"
|
|
713
|
-
});
|
|
714
|
-
if (this.reconnector && !this.reconnector.aborted) this.log.debug({
|
|
669
|
+
disconnect() {
|
|
670
|
+
if (this.reconnector && !this.reconnector.aborted) this.context?.log.debug({
|
|
715
671
|
action: "reconnect.cancel",
|
|
716
672
|
message: "aborting reconnector"
|
|
717
673
|
});
|
|
718
674
|
this.reconnector?.cancel();
|
|
719
675
|
this.gateway?.disconnect();
|
|
720
|
-
await this.queue.drain();
|
|
721
676
|
this.gateway = null;
|
|
722
|
-
this.handler = null;
|
|
723
|
-
this.statusEmitter.set("disconnected");
|
|
724
677
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
hasWebSocket() {
|
|
729
|
-
const result = attempt(() => Boolean(this.deps.WebSocket));
|
|
678
|
+
hasWebSocket(ctx) {
|
|
679
|
+
const result = attempt(() => Boolean(ctx.deps.WebSocket));
|
|
730
680
|
if (result instanceof Error) {
|
|
731
681
|
const error = safeNormalizeError({ value: result });
|
|
732
|
-
|
|
682
|
+
ctx.log.error({
|
|
733
683
|
action: "deps.web-socket.read.error",
|
|
734
684
|
message: safeErrorMessage({ error }),
|
|
735
685
|
error
|
|
@@ -738,55 +688,45 @@ var FlumeDiscordSource = class {
|
|
|
738
688
|
}
|
|
739
689
|
return result;
|
|
740
690
|
}
|
|
741
|
-
async connectInternal(resumeUrl) {
|
|
742
|
-
this.
|
|
691
|
+
async connectInternal(ctx, resumeUrl) {
|
|
692
|
+
this.setStatus("connecting");
|
|
743
693
|
this.gateway = new FlumeDiscordGateway({
|
|
744
694
|
token: this.options.token,
|
|
745
695
|
intents: this.options.intents ?? DEFAULT_INTENTS,
|
|
746
|
-
onLog:
|
|
747
|
-
deps:
|
|
748
|
-
onDispatch: (eventName, eventData) => this.
|
|
749
|
-
onStatus: (status) => this.handleGatewayStatus(status)
|
|
696
|
+
onLog: ctx.log.handler,
|
|
697
|
+
deps: ctx.deps,
|
|
698
|
+
onDispatch: (eventName, eventData) => this.dispatch(ctx, eventName, eventData),
|
|
699
|
+
onStatus: (status) => this.handleGatewayStatus(ctx, status)
|
|
750
700
|
});
|
|
751
701
|
const error = await this.gateway.connect(resumeUrl);
|
|
752
702
|
if (error instanceof FlumeConnectionError) {
|
|
753
|
-
|
|
703
|
+
ctx.log.error({
|
|
754
704
|
action: "connect.failed",
|
|
755
705
|
message: safeErrorMessage({ error }),
|
|
756
706
|
error
|
|
757
707
|
});
|
|
758
708
|
if (this.gateway.isStopped || !this.reconnector || this.reconnector.aborted) {
|
|
759
|
-
this.
|
|
709
|
+
this.setStatus("disconnected");
|
|
760
710
|
return error;
|
|
761
711
|
}
|
|
762
|
-
this.scheduleReconnect();
|
|
712
|
+
this.scheduleReconnect(ctx);
|
|
763
713
|
}
|
|
764
714
|
return null;
|
|
765
715
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
data: eventData,
|
|
774
|
-
meta: this.safeExtractMeta(eventName, eventData),
|
|
775
|
-
receivedAt: safeNow({ deps: this.deps })
|
|
776
|
-
};
|
|
777
|
-
const r = await attempt(() => Promise.resolve(handler(event)));
|
|
778
|
-
if (r instanceof Error) this.log.error({
|
|
779
|
-
action: "handler.error",
|
|
780
|
-
message: safeErrorMessage({ error: r }),
|
|
781
|
-
error: r
|
|
782
|
-
});
|
|
716
|
+
dispatch(ctx, eventName, eventData) {
|
|
717
|
+
this.emit({
|
|
718
|
+
source: "discord",
|
|
719
|
+
type: eventName,
|
|
720
|
+
data: eventData,
|
|
721
|
+
meta: this.safeExtractMeta(ctx, eventName, eventData),
|
|
722
|
+
receivedAt: safeNow({ deps: ctx.deps })
|
|
783
723
|
});
|
|
784
724
|
}
|
|
785
|
-
safeExtractMeta(eventName, eventData) {
|
|
725
|
+
safeExtractMeta(ctx, eventName, eventData) {
|
|
786
726
|
const result = attempt(() => flumeExtractDiscordMeta(eventName, eventData));
|
|
787
727
|
if (result instanceof Error) {
|
|
788
728
|
const error = safeNormalizeError({ value: result });
|
|
789
|
-
|
|
729
|
+
ctx.log.warn({
|
|
790
730
|
action: "meta.extract.error",
|
|
791
731
|
message: safeErrorMessage({ error }),
|
|
792
732
|
error,
|
|
@@ -796,37 +736,37 @@ var FlumeDiscordSource = class {
|
|
|
796
736
|
}
|
|
797
737
|
return result;
|
|
798
738
|
}
|
|
799
|
-
handleGatewayStatus(status) {
|
|
739
|
+
handleGatewayStatus(ctx, status) {
|
|
800
740
|
if (status === "connected") {
|
|
801
|
-
if (this.reconnector && this.reconnector.attempt > 0)
|
|
741
|
+
if (this.reconnector && this.reconnector.attempt > 0) ctx.log.info({
|
|
802
742
|
action: "reconnect.reset",
|
|
803
743
|
message: `cleared ${this.reconnector.attempt} attempts`
|
|
804
744
|
});
|
|
805
745
|
this.reconnector?.reset();
|
|
806
|
-
this.
|
|
746
|
+
this.setStatus("connected");
|
|
807
747
|
return;
|
|
808
748
|
}
|
|
809
749
|
if (this.gateway?.isStopped) {
|
|
810
|
-
this.
|
|
750
|
+
this.setStatus("disconnected");
|
|
811
751
|
return;
|
|
812
752
|
}
|
|
813
|
-
this.scheduleReconnect();
|
|
753
|
+
this.scheduleReconnect(ctx);
|
|
814
754
|
}
|
|
815
|
-
scheduleReconnect() {
|
|
755
|
+
scheduleReconnect(ctx) {
|
|
816
756
|
const url = this.gateway?.session.resumeUrl ?? void 0;
|
|
817
757
|
scheduleFlumeReconnect({
|
|
818
758
|
reconnector: this.reconnector,
|
|
819
|
-
log:
|
|
820
|
-
setStatus: (status) => this.
|
|
759
|
+
log: ctx.log,
|
|
760
|
+
setStatus: (status) => this.setStatus(status),
|
|
821
761
|
retry: () => {
|
|
822
|
-
this.connectInternal(url).catch((err) => {
|
|
762
|
+
this.connectInternal(ctx, url).catch((err) => {
|
|
823
763
|
const error = safeNormalizeError({ value: err });
|
|
824
|
-
|
|
764
|
+
ctx.log.error({
|
|
825
765
|
action: "reconnect.unhandled",
|
|
826
766
|
message: safeErrorMessage({ error }),
|
|
827
767
|
error
|
|
828
768
|
});
|
|
829
|
-
this.
|
|
769
|
+
this.setStatus("disconnected");
|
|
830
770
|
});
|
|
831
771
|
}
|
|
832
772
|
});
|
|
@@ -0,0 +1,269 @@
|
|
|
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" | "time";
|
|
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 FlumeTimeEvent = {
|
|
100
|
+
source: "time";
|
|
101
|
+
type: string;
|
|
102
|
+
data: Record<string, unknown>;
|
|
103
|
+
meta: Record<string, string>;
|
|
104
|
+
receivedAt: number;
|
|
105
|
+
};
|
|
106
|
+
type FlumeEvent = FlumeDiscordEvent | FlumeSlackEvent | FlumeGitHubEvent | FlumeTimeEvent;
|
|
107
|
+
type FlumeEventHandler = (event: FlumeEvent) => void | Promise<void>;
|
|
108
|
+
type FlumeStreamItem = {
|
|
109
|
+
kind: "event";
|
|
110
|
+
event: FlumeEvent;
|
|
111
|
+
} | {
|
|
112
|
+
kind: "log";
|
|
113
|
+
log: FlumeLog;
|
|
114
|
+
};
|
|
115
|
+
type FlumeStreamHandler = (item: FlumeStreamItem) => void;
|
|
116
|
+
type FlumeStreamOverflow = "drop-oldest" | "drop-newest";
|
|
117
|
+
type FlumeStreamOptions = {
|
|
118
|
+
/** バッファ上限 (既定 1000)。consumer が遅れて溢れたら onOverflow に従う */buffer?: number; /** バッファ溢れ時の方針 (既定 "drop-oldest") */
|
|
119
|
+
onOverflow?: FlumeStreamOverflow;
|
|
120
|
+
};
|
|
121
|
+
type FlumeStatus = "disconnected" | "connecting" | "connected" | "reconnecting";
|
|
122
|
+
type FlumeSourceStatus = {
|
|
123
|
+
/**
|
|
124
|
+
* 多くは `FlumeSourceName` のいずれかだが、`source.name` getter が throw する
|
|
125
|
+
* 第三者 FlumeSource 実装に備えて `string` まで広げてある (fallback で `"?"`)
|
|
126
|
+
*/
|
|
127
|
+
source: string;
|
|
128
|
+
status: FlumeStatus;
|
|
129
|
+
};
|
|
130
|
+
type FlumeLogLevel = "debug" | "info" | "warn" | "error";
|
|
131
|
+
type FlumeLog = {
|
|
132
|
+
level: FlumeLogLevel;
|
|
133
|
+
source: string;
|
|
134
|
+
action: string;
|
|
135
|
+
message: string;
|
|
136
|
+
error?: Error;
|
|
137
|
+
detail?: Record<string, unknown>;
|
|
138
|
+
timestamp: number;
|
|
139
|
+
};
|
|
140
|
+
type FlumeLogHandler = (log: FlumeLog) => void;
|
|
141
|
+
/** error レベルのログだけを受け取る (Sentry など error 専用の送信先向け) */
|
|
142
|
+
type FlumeErrorHandler = (log: FlumeLog) => void;
|
|
143
|
+
type FlumeLogInput = {
|
|
144
|
+
action: string;
|
|
145
|
+
message: string;
|
|
146
|
+
error?: Error;
|
|
147
|
+
detail?: Record<string, unknown>;
|
|
148
|
+
};
|
|
149
|
+
type FlumeReconnectOptions = {
|
|
150
|
+
maxAttempts?: number;
|
|
151
|
+
baseDelay?: number;
|
|
152
|
+
maxDelay?: number;
|
|
153
|
+
};
|
|
154
|
+
type FlumeReconnectConfig = {
|
|
155
|
+
maxAttempts: number;
|
|
156
|
+
baseDelay: number;
|
|
157
|
+
maxDelay: number;
|
|
158
|
+
};
|
|
159
|
+
type FlumeSourceLocalStatusHandler = (status: FlumeStatus, detail?: string) => void;
|
|
160
|
+
type FlumeSourceStartContext = {
|
|
161
|
+
onEvent: FlumeEventHandler;
|
|
162
|
+
log: FlumeLogger;
|
|
163
|
+
deps: FlumeRuntimeDeps; /** Source 内部の status 遷移ブリッジ。Flume 公開 API に status callback は無く、遷移は log に出る */
|
|
164
|
+
onStatus?: FlumeSourceLocalStatusHandler;
|
|
165
|
+
reconnect: FlumeReconnectConfig | null;
|
|
166
|
+
/**
|
|
167
|
+
* Flume.start() に渡された signal をそのまま転送する。
|
|
168
|
+
* source 実装が自前で `fetch(url, { signal })` / `setTimeout` cancel / WS close を
|
|
169
|
+
* host abort 経由で発火させたい時に使う (Flume 自身は最外殻で runClose を駆動するので
|
|
170
|
+
* source は signal を無視しても動作的には停止する — 自然な伝播パスが欲しい場合のみ)。
|
|
171
|
+
* Flume.options.signal が未設定なら省略される。
|
|
172
|
+
*/
|
|
173
|
+
signal?: AbortSignal;
|
|
174
|
+
};
|
|
175
|
+
type FlumeDiscordSourceOptions = {
|
|
176
|
+
token: string;
|
|
177
|
+
intents?: number;
|
|
178
|
+
};
|
|
179
|
+
type FlumeSlackSourceOptions = {
|
|
180
|
+
appToken: string;
|
|
181
|
+
/**
|
|
182
|
+
* Bot token (`xoxb-`). Slack Socket Mode (受信) には不要だが、ホスト側 (返信や
|
|
183
|
+
* `auth.test` での self 検出) が必ず使うため型で保持を強制する
|
|
184
|
+
*/
|
|
185
|
+
botToken: string;
|
|
186
|
+
};
|
|
187
|
+
type FlumeGitHubSourceOptions = {
|
|
188
|
+
token: string;
|
|
189
|
+
pollInterval?: number;
|
|
190
|
+
};
|
|
191
|
+
type FlumeTimeTick = {
|
|
192
|
+
/** cron がマッチした壁時計時刻 (epoch ms)。setTimeout の発火実時刻ではなく予定時刻 */firedAt: number;
|
|
193
|
+
cron: string;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* tick ごとに emit するイベントの上書き内容。全フィールド optional。
|
|
197
|
+
* 省略フィールドは既定値 (type: "tick" / data: tick 内容 / meta: { cron }) になる
|
|
198
|
+
*/
|
|
199
|
+
type FlumeTimeMessage = {
|
|
200
|
+
type?: string;
|
|
201
|
+
data?: Record<string, unknown>;
|
|
202
|
+
meta?: Record<string, string>;
|
|
203
|
+
};
|
|
204
|
+
type FlumeTimeSourceOptions = {
|
|
205
|
+
/** 5 フィールド cron 式 (minute hour day-of-month month day-of-week)。壁時計 (local time) 基準 */cron: string;
|
|
206
|
+
message?: (tick: FlumeTimeTick) => FlumeTimeMessage;
|
|
207
|
+
};
|
|
208
|
+
type FlumeGatewayMessage = z.infer<typeof FlumeGatewayMessageSchema>;
|
|
209
|
+
type FlumeSlackEnvelope = z.infer<typeof FlumeSlackEnvelopeSchema>;
|
|
210
|
+
type FlumeSlackConnectionResponse = z.infer<typeof FlumeSlackConnectionResponseSchema>;
|
|
211
|
+
type FlumeGitHubNotification = z.infer<typeof FlumeGitHubNotificationSchema>;
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region lib/flume-source.d.ts
|
|
214
|
+
/**
|
|
215
|
+
* 全 Source の基底クラス。protocol 固有のロジック (`connect` / `disconnect`) のみ
|
|
216
|
+
* subclass に実装させ、queue / status / handler 安全呼び出しといった共通の
|
|
217
|
+
* cross-cutting concern は base が引き受ける。Flume 側で全 source に注入される
|
|
218
|
+
* `FlumeSourceStartContext` (handler / log / deps / onStatus / reconnect) を
|
|
219
|
+
* `start()` で受け取り、subclass の `connect(ctx)` に手渡す。
|
|
220
|
+
*
|
|
221
|
+
* subclass のテンプレート:
|
|
222
|
+
*
|
|
223
|
+
* ```ts
|
|
224
|
+
* export class MySource extends FlumeSource {
|
|
225
|
+
* readonly name = "my-source"
|
|
226
|
+
*
|
|
227
|
+
* constructor(private readonly options: { apiKey: string }) {
|
|
228
|
+
* super()
|
|
229
|
+
* }
|
|
230
|
+
*
|
|
231
|
+
* protected async connect(ctx: FlumeSourceStartContext): Promise<Error | null> {
|
|
232
|
+
* // 接続して onEvent で this.emit({...}) / 状態遷移で this.setStatus(...)
|
|
233
|
+
* return null
|
|
234
|
+
* }
|
|
235
|
+
*
|
|
236
|
+
* protected disconnect(): void { ... }
|
|
237
|
+
* }
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
declare abstract class FlumeSource {
|
|
241
|
+
abstract readonly name: string;
|
|
242
|
+
private consumed;
|
|
243
|
+
private stopped;
|
|
244
|
+
private ctx;
|
|
245
|
+
private statusEmitter;
|
|
246
|
+
private readonly queue;
|
|
247
|
+
start(ctx: FlumeSourceStartContext): Promise<Error | null>;
|
|
248
|
+
stop(): Promise<void>;
|
|
249
|
+
status(): FlumeStatus;
|
|
250
|
+
/**
|
|
251
|
+
* subclass が受信した protocol イベントを `FlumeEvent` として handler へ流す。
|
|
252
|
+
* handler の throw / async reject は queue 内で catch + log し、後続を止めない
|
|
253
|
+
*/
|
|
254
|
+
protected emit(event: FlumeEvent): void;
|
|
255
|
+
/**
|
|
256
|
+
* subclass が protocol 状態遷移をユーザーに通知する。同一 (status, detail) の連続は冪等
|
|
257
|
+
*/
|
|
258
|
+
protected setStatus(status: FlumeStatus, detail?: string): void;
|
|
259
|
+
/** subclass が現在の status を読みたい場合 */
|
|
260
|
+
protected get currentStatus(): FlumeStatus;
|
|
261
|
+
/** subclass が start ctx を再参照したい場合 (stop 後は null) */
|
|
262
|
+
protected get context(): FlumeSourceStartContext | null;
|
|
263
|
+
/** protocol 接続。subclass 実装 */
|
|
264
|
+
protected abstract connect(ctx: FlumeSourceStartContext): Promise<Error | null>;
|
|
265
|
+
/** protocol 切断。subclass 実装。base が `stop()` 内で必ず呼ぶ */
|
|
266
|
+
protected abstract disconnect(): Promise<void> | void;
|
|
267
|
+
}
|
|
268
|
+
//#endregion
|
|
269
|
+
export { FlumeStreamOverflow as A, FlumeSourceName as C, FlumeStreamHandler as D, FlumeStatus as E, FlumeTimerHandle as F, FlumeLogger as I, FlumeTimeMessage as M, FlumeTimeSourceOptions as N, FlumeStreamItem as O, FlumeTimeTick as P, FlumeSourceLocalStatusHandler as S, FlumeSourceStatus as T, FlumeRuntimeDeps as _, FlumeEvent as a, FlumeSlackEvent as b, FlumeGitHubEvent as c, FlumeLog as d, FlumeLogHandler as f, FlumeReconnectOptions as g, FlumeReconnectConfig as h, FlumeErrorHandler as i, FlumeTimeEvent as j, FlumeStreamOptions as k, FlumeGitHubNotification as l, FlumeLogLevel as m, FlumeDiscordEvent as n, FlumeEventHandler as o, FlumeLogInput as p, FlumeDiscordSourceOptions as r, FlumeGatewayMessage as s, FlumeSource as t, FlumeGitHubSourceOptions as u, FlumeSlackConnectionResponse as v, FlumeSourceStartContext as w, FlumeSlackSourceOptions as x, FlumeSlackEnvelope as y };
|