@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
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import { r as FlumeGitHubNotificationSchema, t as FlumeLogger } from "./logger-B9E8zvgj.js";
|
|
2
|
-
//#region lib/github/github-seen-cache.ts
|
|
3
|
-
var FlumeGitHubSeenCache = class {
|
|
4
|
-
props;
|
|
5
|
-
seen = /* @__PURE__ */ new Map();
|
|
6
|
-
constructor(props) {
|
|
7
|
-
this.props = props;
|
|
8
|
-
}
|
|
9
|
-
has(id, updatedAt) {
|
|
10
|
-
return this.seen.get(id) === updatedAt;
|
|
11
|
-
}
|
|
12
|
-
add(id, updatedAt) {
|
|
13
|
-
this.seen.set(id, updatedAt);
|
|
14
|
-
}
|
|
15
|
-
trim() {
|
|
16
|
-
if (this.seen.size <= this.props.maxSize) return;
|
|
17
|
-
const entries = [...this.seen.entries()];
|
|
18
|
-
this.seen = new Map(entries.slice(entries.length - this.props.maxSize));
|
|
19
|
-
}
|
|
20
|
-
get size() {
|
|
21
|
-
return this.seen.size;
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region lib/github/github-poller.ts
|
|
26
|
-
var FlumeGitHubPoller = class {
|
|
27
|
-
props;
|
|
28
|
-
log;
|
|
29
|
-
cache = new FlumeGitHubSeenCache({ maxSize: 5e3 });
|
|
30
|
-
timer = null;
|
|
31
|
-
since = null;
|
|
32
|
-
bootstrapped = false;
|
|
33
|
-
stopped = false;
|
|
34
|
-
consecutiveErrors = 0;
|
|
35
|
-
constructor(props) {
|
|
36
|
-
this.props = props;
|
|
37
|
-
this.log = new FlumeLogger({
|
|
38
|
-
source: "github.poller",
|
|
39
|
-
handler: props.onLog,
|
|
40
|
-
deps: props.deps
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
async start() {
|
|
44
|
-
this.stopped = false;
|
|
45
|
-
this.log.info({
|
|
46
|
-
action: "start",
|
|
47
|
-
message: `polling every ${this.props.interval}s`
|
|
48
|
-
});
|
|
49
|
-
await this.poll();
|
|
50
|
-
this.timer = this.props.deps.setInterval(() => {
|
|
51
|
-
this.poll().catch((err) => {
|
|
52
|
-
this.log.error({
|
|
53
|
-
action: "poll.unhandled",
|
|
54
|
-
message: "unexpected error in poll loop",
|
|
55
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
}, this.props.interval * 1e3);
|
|
59
|
-
}
|
|
60
|
-
stop() {
|
|
61
|
-
this.log.info({
|
|
62
|
-
action: "stop",
|
|
63
|
-
message: "stopping poller"
|
|
64
|
-
});
|
|
65
|
-
this.stopped = true;
|
|
66
|
-
if (this.timer !== null) {
|
|
67
|
-
this.props.deps.clearInterval(this.timer);
|
|
68
|
-
this.timer = null;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
async poll() {
|
|
72
|
-
const params = new URLSearchParams({ all: "false" });
|
|
73
|
-
if (this.since) params.set("since", this.since);
|
|
74
|
-
const url = `https://api.github.com/notifications?${params}`;
|
|
75
|
-
this.log.debug({
|
|
76
|
-
action: "http.request",
|
|
77
|
-
message: `GET ${url}`
|
|
78
|
-
});
|
|
79
|
-
const response = await this.safeFetch(url);
|
|
80
|
-
if (response instanceof Error) return;
|
|
81
|
-
this.log.debug({
|
|
82
|
-
action: "http.response",
|
|
83
|
-
message: `GET ${response.status}`,
|
|
84
|
-
detail: {
|
|
85
|
-
status: response.status,
|
|
86
|
-
url
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
if (!response.ok) {
|
|
90
|
-
this.consecutiveErrors++;
|
|
91
|
-
this.log.error({
|
|
92
|
-
action: "http.error",
|
|
93
|
-
message: `HTTP ${response.status} (consecutive=${this.consecutiveErrors})`
|
|
94
|
-
});
|
|
95
|
-
if (this.consecutiveErrors >= 3) this.props.onDisconnected(`HTTP ${response.status}`);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
this.consecutiveErrors = 0;
|
|
99
|
-
const body = await response.json();
|
|
100
|
-
if (!Array.isArray(body)) {
|
|
101
|
-
this.log.warn({
|
|
102
|
-
action: "http.body",
|
|
103
|
-
message: "response body is not an array, dropping",
|
|
104
|
-
detail: { bodyType: typeof body }
|
|
105
|
-
});
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
this.processNotifications(body);
|
|
109
|
-
}
|
|
110
|
-
processNotifications(raw) {
|
|
111
|
-
let dropped = 0;
|
|
112
|
-
const notifications = raw.flatMap((item) => {
|
|
113
|
-
const parsed = FlumeGitHubNotificationSchema.safeParse(item);
|
|
114
|
-
if (!parsed.success) {
|
|
115
|
-
dropped++;
|
|
116
|
-
this.log.warn({
|
|
117
|
-
action: "parse.skip",
|
|
118
|
-
message: "notification did not match schema",
|
|
119
|
-
detail: { issues: parsed.error.issues.map((i) => ({
|
|
120
|
-
path: i.path,
|
|
121
|
-
message: i.message
|
|
122
|
-
})) }
|
|
123
|
-
});
|
|
124
|
-
return [];
|
|
125
|
-
}
|
|
126
|
-
return [parsed.data];
|
|
127
|
-
});
|
|
128
|
-
if (dropped > 0) this.log.warn({
|
|
129
|
-
action: "parse.summary",
|
|
130
|
-
message: `${dropped}/${raw.length} notifications dropped by schema`
|
|
131
|
-
});
|
|
132
|
-
if (!this.bootstrapped) {
|
|
133
|
-
this.bootstrapped = true;
|
|
134
|
-
for (const notification of notifications) this.cache.add(notification.id, notification.updated_at);
|
|
135
|
-
this.since = new Date(this.props.deps.now()).toISOString();
|
|
136
|
-
this.log.info({
|
|
137
|
-
action: "bootstrap",
|
|
138
|
-
message: `seeded ${notifications.length} existing notifications`
|
|
139
|
-
});
|
|
140
|
-
this.props.onConnected();
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
const fresh = [];
|
|
144
|
-
for (const notification of notifications) {
|
|
145
|
-
if (this.cache.has(notification.id, notification.updated_at)) continue;
|
|
146
|
-
this.cache.add(notification.id, notification.updated_at);
|
|
147
|
-
fresh.push(notification);
|
|
148
|
-
}
|
|
149
|
-
this.cache.trim();
|
|
150
|
-
this.since = new Date(this.props.deps.now()).toISOString();
|
|
151
|
-
if (fresh.length > 0) {
|
|
152
|
-
this.log.info({
|
|
153
|
-
action: "poll.fresh",
|
|
154
|
-
message: `${fresh.length} new notifications`
|
|
155
|
-
});
|
|
156
|
-
this.props.onNotifications(fresh);
|
|
157
|
-
} else this.log.debug({
|
|
158
|
-
action: "poll.idle",
|
|
159
|
-
message: "0 new notifications"
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
async safeFetch(url) {
|
|
163
|
-
try {
|
|
164
|
-
return await this.props.deps.fetch(url, { headers: {
|
|
165
|
-
Authorization: `Bearer ${this.props.token}`,
|
|
166
|
-
Accept: "application/vnd.github+json",
|
|
167
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
168
|
-
} });
|
|
169
|
-
} catch (error) {
|
|
170
|
-
this.consecutiveErrors++;
|
|
171
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
172
|
-
this.log.error({
|
|
173
|
-
action: "http.error",
|
|
174
|
-
message: `network error (consecutive=${this.consecutiveErrors})`,
|
|
175
|
-
error: err
|
|
176
|
-
});
|
|
177
|
-
if (this.consecutiveErrors >= 3) this.props.onDisconnected("network error");
|
|
178
|
-
return err;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
//#endregion
|
|
183
|
-
//#region lib/github/github-source.ts
|
|
184
|
-
var FlumeGitHubSource = class FlumeGitHubSource {
|
|
185
|
-
options;
|
|
186
|
-
poller = null;
|
|
187
|
-
currentStatus = "disconnected";
|
|
188
|
-
log;
|
|
189
|
-
deps;
|
|
190
|
-
constructor(options) {
|
|
191
|
-
this.options = options;
|
|
192
|
-
this.deps = options.deps;
|
|
193
|
-
this.log = new FlumeLogger({
|
|
194
|
-
source: "github",
|
|
195
|
-
handler: options.onLog,
|
|
196
|
-
deps: this.deps
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
async start(handler) {
|
|
200
|
-
if (this.options.signal?.aborted) return;
|
|
201
|
-
this.options.signal?.addEventListener("abort", () => this.stop(), { once: true });
|
|
202
|
-
this.log.info({
|
|
203
|
-
action: "start",
|
|
204
|
-
message: "starting GitHub source"
|
|
205
|
-
});
|
|
206
|
-
this.setStatus("connecting");
|
|
207
|
-
this.poller = new FlumeGitHubPoller({
|
|
208
|
-
token: this.options.token,
|
|
209
|
-
interval: this.options.pollInterval ?? 60,
|
|
210
|
-
onLog: this.options.onLog,
|
|
211
|
-
deps: this.deps,
|
|
212
|
-
onNotifications: (notifications) => this.handleNotifications(handler, notifications),
|
|
213
|
-
onConnected: () => this.setStatus("connected"),
|
|
214
|
-
onDisconnected: (detail) => this.setStatus("disconnected", detail)
|
|
215
|
-
});
|
|
216
|
-
try {
|
|
217
|
-
await this.poller.start();
|
|
218
|
-
} catch (error) {
|
|
219
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
220
|
-
this.log.error({
|
|
221
|
-
action: "start.failed",
|
|
222
|
-
message: err.message,
|
|
223
|
-
error: err
|
|
224
|
-
});
|
|
225
|
-
this.setStatus("disconnected");
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
async stop() {
|
|
229
|
-
this.log.info({
|
|
230
|
-
action: "stop",
|
|
231
|
-
message: "stopping GitHub source"
|
|
232
|
-
});
|
|
233
|
-
this.poller?.stop();
|
|
234
|
-
this.poller = null;
|
|
235
|
-
this.setStatus("disconnected");
|
|
236
|
-
}
|
|
237
|
-
status() {
|
|
238
|
-
return this.currentStatus;
|
|
239
|
-
}
|
|
240
|
-
handleNotifications(handler, notifications) {
|
|
241
|
-
for (const notification of notifications) {
|
|
242
|
-
const event = {
|
|
243
|
-
source: "github",
|
|
244
|
-
type: "notification",
|
|
245
|
-
data: notification,
|
|
246
|
-
meta: FlumeGitHubSource.extractMeta(notification),
|
|
247
|
-
receivedAt: this.deps.now()
|
|
248
|
-
};
|
|
249
|
-
try {
|
|
250
|
-
handler(event);
|
|
251
|
-
} catch (err) {
|
|
252
|
-
this.log.error({
|
|
253
|
-
action: "handler.error",
|
|
254
|
-
message: "user handler threw",
|
|
255
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
setStatus(next, detail) {
|
|
261
|
-
if (this.currentStatus === next) return;
|
|
262
|
-
this.log.info({
|
|
263
|
-
action: "status",
|
|
264
|
-
message: `${this.currentStatus} → ${next}${detail ? ` (${detail})` : ""}`
|
|
265
|
-
});
|
|
266
|
-
this.currentStatus = next;
|
|
267
|
-
this.options.onStatus?.(next, detail);
|
|
268
|
-
}
|
|
269
|
-
static extractMeta(notification) {
|
|
270
|
-
return {
|
|
271
|
-
event_type: "notification",
|
|
272
|
-
reason: notification.reason,
|
|
273
|
-
subject_type: notification.subject.type,
|
|
274
|
-
repository: notification.repository.full_name,
|
|
275
|
-
thread_id: notification.id
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
//#endregion
|
|
280
|
-
export { FlumeGitHubPoller as n, FlumeGitHubSeenCache as r, FlumeGitHubSource as t };
|
package/dist/logger-B9E8zvgj.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { z } from "zod/v4";
|
|
2
|
-
//#region lib/schema.ts
|
|
3
|
-
const FlumeGatewayMessageSchema = z.object({
|
|
4
|
-
op: z.number(),
|
|
5
|
-
d: z.record(z.string(), z.unknown()).nullable(),
|
|
6
|
-
s: z.number().nullable(),
|
|
7
|
-
t: z.string().nullable()
|
|
8
|
-
});
|
|
9
|
-
const FlumeSlackEnvelopeSchema = z.object({
|
|
10
|
-
envelope_id: z.string(),
|
|
11
|
-
type: z.string(),
|
|
12
|
-
payload: z.record(z.string(), z.unknown()),
|
|
13
|
-
accepts_response_payload: z.boolean().optional(),
|
|
14
|
-
retry_attempt: z.number().optional(),
|
|
15
|
-
retry_reason: z.string().optional()
|
|
16
|
-
});
|
|
17
|
-
const FlumeSlackConnectionResponseSchema = z.object({
|
|
18
|
-
ok: z.boolean(),
|
|
19
|
-
url: z.string().optional(),
|
|
20
|
-
error: z.string().optional()
|
|
21
|
-
});
|
|
22
|
-
const FlumeGitHubNotificationSchema = z.object({
|
|
23
|
-
id: z.string(),
|
|
24
|
-
reason: z.string(),
|
|
25
|
-
unread: z.boolean(),
|
|
26
|
-
updated_at: z.string(),
|
|
27
|
-
subject: z.object({
|
|
28
|
-
title: z.string(),
|
|
29
|
-
url: z.string().nullable(),
|
|
30
|
-
type: z.string()
|
|
31
|
-
}),
|
|
32
|
-
repository: z.object({ full_name: z.string() })
|
|
33
|
-
});
|
|
34
|
-
//#endregion
|
|
35
|
-
//#region lib/logger.ts
|
|
36
|
-
var FlumeLogger = class {
|
|
37
|
-
props;
|
|
38
|
-
constructor(props) {
|
|
39
|
-
this.props = props;
|
|
40
|
-
Object.freeze(this);
|
|
41
|
-
}
|
|
42
|
-
debug(entry) {
|
|
43
|
-
this.emit("debug", entry);
|
|
44
|
-
}
|
|
45
|
-
info(entry) {
|
|
46
|
-
this.emit("info", entry);
|
|
47
|
-
}
|
|
48
|
-
warn(entry) {
|
|
49
|
-
this.emit("warn", entry);
|
|
50
|
-
}
|
|
51
|
-
error(entry) {
|
|
52
|
-
this.emit("error", entry);
|
|
53
|
-
}
|
|
54
|
-
emit(level, input) {
|
|
55
|
-
if (!this.props.handler) return;
|
|
56
|
-
const log = {
|
|
57
|
-
level,
|
|
58
|
-
source: this.props.source,
|
|
59
|
-
action: input.action,
|
|
60
|
-
message: input.message,
|
|
61
|
-
timestamp: this.props.deps.now(),
|
|
62
|
-
error: input.error,
|
|
63
|
-
detail: input.detail
|
|
64
|
-
};
|
|
65
|
-
this.props.handler(log);
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
//#endregion
|
|
69
|
-
export { FlumeSlackEnvelopeSchema as a, FlumeSlackConnectionResponseSchema as i, FlumeGatewayMessageSchema as n, FlumeGitHubNotificationSchema as r, FlumeLogger as t };
|