@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.
@@ -1,170 +0,0 @@
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/deps.ts
57
- const wsCandidate = attempt(() => globalThis.WebSocket);
58
- const cachedWebSocket = wsCandidate instanceof Error || typeof wsCandidate !== "function" ? null : wsCandidate;
59
- /**
60
- * platform 既定の IO を束ねた `FlumeRuntimeDeps`。
61
- * `FlumeTimerHandle` は不透明型 (`unknown`) のため、setTimeout / clearTimeout の戻り値・引数を
62
- * platform 型と橋渡しする際に境界で `as unknown as` を使う (IO 境界の最終手段)
63
- */
64
- function createFlumeDefaultDeps() {
65
- return {
66
- fetch: (url, init) => globalThis.fetch(url, init),
67
- WebSocket: cachedWebSocket,
68
- now: () => Date.now(),
69
- random: () => Math.random(),
70
- setTimeout: (fn, ms) => globalThis.setTimeout(fn, ms),
71
- clearTimeout: (id) => globalThis.clearTimeout(id),
72
- setInterval: (fn, ms) => globalThis.setInterval(fn, ms),
73
- clearInterval: (id) => globalThis.clearInterval(id)
74
- };
75
- }
76
- //#endregion
77
- //#region lib/errors/parse-error.ts
78
- var FlumeParseError = class extends Error {
79
- constructor(message, options) {
80
- super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
81
- this.name = "FlumeParseError";
82
- Object.freeze(this);
83
- }
84
- };
85
- //#endregion
86
- //#region lib/errors/start-error.ts
87
- var FlumeStartError = class extends Error {
88
- constructor(message, options) {
89
- super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
90
- this.name = "FlumeStartError";
91
- Object.freeze(this);
92
- }
93
- };
94
- //#endregion
95
- //#region lib/utils/safe-now.ts
96
- /**
97
- * `deps.now()` を保護する。throw / 非数値が返った場合は 0 を返す。
98
- * IO 境界のため呼び出し側はこの戻り値を信頼できる
99
- */
100
- function safeNow(props) {
101
- try {
102
- const value = props.deps.now();
103
- if (typeof value !== "number" || !Number.isFinite(value)) return 0;
104
- return value;
105
- } catch {
106
- return 0;
107
- }
108
- }
109
- //#endregion
110
- //#region lib/logger.ts
111
- /**
112
- * 構造化ログを onLog に流す。handler が throw / reject してもループは継続する
113
- */
114
- var FlumeLogger = class {
115
- props;
116
- constructor(props) {
117
- this.props = props;
118
- Object.freeze(this);
119
- }
120
- debug(entry) {
121
- this.emit("debug", entry);
122
- }
123
- info(entry) {
124
- this.emit("info", entry);
125
- }
126
- warn(entry) {
127
- this.emit("warn", entry);
128
- }
129
- error(entry) {
130
- this.emit("error", entry);
131
- }
132
- emit(level, input) {
133
- const handler = this.props.handler;
134
- if (!handler) return;
135
- const log = {
136
- level,
137
- source: this.props.source,
138
- action: input.action,
139
- message: input.message,
140
- timestamp: safeNow({ deps: this.props.deps }),
141
- error: input.error,
142
- detail: input.detail
143
- };
144
- try {
145
- Promise.resolve(handler(log)).catch(() => {});
146
- } catch {}
147
- }
148
- };
149
- //#endregion
150
- //#region lib/utils/safe-invoke-callback.ts
151
- /**
152
- * fire-and-forget でユーザーコールバックを呼び出す。sync throw と async reject のどちらも
153
- * `onError(Error)` に正規化して通知。`onError` 自身が throw しても外に漏らさない。
154
- * 戻り値を持たない fire-and-forget 専用のため log/出力先には依存しない (caller が onError で決める)
155
- */
156
- function safeInvokeCallback(props) {
157
- try {
158
- Promise.resolve(props.fn()).catch((err) => {
159
- try {
160
- props.onError(safeNormalizeError({ value: err }));
161
- } catch {}
162
- }).catch(() => {});
163
- } catch (err) {
164
- try {
165
- props.onError(safeNormalizeError({ value: err }));
166
- } catch {}
167
- }
168
- }
169
- //#endregion
170
- export { FlumeParseError as a, safeNormalizeError as c, FlumeStartError as i, safeErrorMessage as l, FlumeLogger as n, createFlumeDefaultDeps as o, safeNow as r, attempt as s, safeInvokeCallback as t };
@@ -1,162 +0,0 @@
1
- import { a as FlumeParseError, c as safeNormalizeError, l as safeErrorMessage, s as attempt, t as safeInvokeCallback } from "./safe-invoke-callback-EpWXwfwp.js";
2
- //#region lib/utils/safe-json-parse.ts
3
- function safeJsonParse(raw) {
4
- try {
5
- return JSON.parse(raw);
6
- } catch (error) {
7
- return new FlumeParseError(error instanceof Error ? `invalid JSON: ${error.message}` : "invalid JSON", { cause: error });
8
- }
9
- }
10
- //#endregion
11
- //#region lib/source-helpers/flume-signal-registry.ts
12
- /**
13
- * Source 群に共通する AbortSignal の登録・解除・abort 判定を集約する。
14
- * 異常な polyfill / poisoned getter / 凍結 signal を吸収するため境界呼び出しは全て try/catch で
15
- * 包み、失敗時はログに流して握り潰す
16
- */
17
- var FlumeSignalRegistry = class {
18
- props;
19
- signals = [];
20
- constructor(props) {
21
- this.props = props;
22
- }
23
- isAnyAborted(extra) {
24
- if (this.isAborted(extra)) return true;
25
- for (const signal of this.signals) if (this.isAborted(signal)) return true;
26
- return false;
27
- }
28
- register(signal) {
29
- if (!signal) return;
30
- const result = attempt(() => signal.addEventListener("abort", this.props.onAbort, { once: true }));
31
- if (result instanceof Error) {
32
- const error = safeNormalizeError({ value: result });
33
- this.props.log.error({
34
- action: "signal.addListener.failed",
35
- message: safeErrorMessage({ error }),
36
- error
37
- });
38
- return;
39
- }
40
- this.signals.push(signal);
41
- }
42
- unregisterAll() {
43
- for (const signal of this.signals) {
44
- const result = attempt(() => signal.removeEventListener("abort", this.props.onAbort));
45
- if (result instanceof Error) {
46
- const error = safeNormalizeError({ value: result });
47
- this.props.log.error({
48
- action: "signal.removeListener.failed",
49
- message: safeErrorMessage({ error }),
50
- error
51
- });
52
- }
53
- }
54
- this.signals.length = 0;
55
- }
56
- get size() {
57
- return this.signals.length;
58
- }
59
- isAborted(signal) {
60
- if (!signal) return false;
61
- const result = attempt(() => signal.aborted === true);
62
- return result instanceof Error ? true : result;
63
- }
64
- };
65
- //#endregion
66
- //#region lib/source-helpers/flume-status-emitter.ts
67
- /**
68
- * Source の `currentStatus` と `onStatus` 通知を集約する。
69
- * 同一 (status, detail) の連続遷移は冪等に握り潰し、ユーザーコールバックは `safeInvokeCallback`
70
- * 経由で例外を隔離する
71
- */
72
- var FlumeStatusEmitter = class {
73
- props;
74
- currentStatus = "disconnected";
75
- currentDetail = null;
76
- constructor(props) {
77
- this.props = props;
78
- }
79
- get value() {
80
- return this.currentStatus;
81
- }
82
- set(next, detail) {
83
- const normalizedDetail = detail ?? null;
84
- if (this.currentStatus === next && this.currentDetail === normalizedDetail) return;
85
- const prev = this.currentStatus;
86
- const suffix = detail ? ` (${detail})` : "";
87
- this.props.log.info({
88
- action: "status",
89
- message: `${prev} → ${next}${suffix}`,
90
- detail: {
91
- from: prev,
92
- to: next,
93
- reason: normalizedDetail
94
- }
95
- });
96
- this.currentStatus = next;
97
- this.currentDetail = normalizedDetail;
98
- const onStatus = this.props.onStatus;
99
- if (!onStatus) return;
100
- safeInvokeCallback({
101
- fn: detail !== void 0 ? () => onStatus(next, detail) : () => onStatus(next),
102
- onError: (error) => {
103
- this.props.log.error({
104
- action: "onStatus.error",
105
- message: safeErrorMessage({ error }),
106
- error
107
- });
108
- }
109
- });
110
- }
111
- };
112
- //#endregion
113
- //#region lib/utils/serial-queue.ts
114
- /**
115
- * 投入順を保ったまま task を直列実行する。各 task は前の完了を待ってから走る。
116
- * task が throw しても後続には伝播しない (キュー自体は止まらない)。
117
- * maxDepth を超えた場合は新規 task を drop し onOverflow に通知。
118
- * cancel() 後の add() は no-op となり drain() は即時 resolve する
119
- */
120
- var FlumeSerialQueue = class {
121
- props;
122
- chain = Promise.resolve();
123
- depth = 0;
124
- cancelled = false;
125
- constructor(props = {}) {
126
- this.props = props;
127
- }
128
- add(task) {
129
- if (this.cancelled) return;
130
- if (this.props.maxDepth !== void 0 && this.depth >= this.props.maxDepth) {
131
- this.props.onOverflow?.({
132
- dropped: 1,
133
- depth: this.depth
134
- });
135
- return;
136
- }
137
- this.depth++;
138
- this.chain = this.chain.then(async () => {
139
- try {
140
- await task();
141
- } catch {} finally {
142
- this.depth--;
143
- }
144
- });
145
- }
146
- async drain() {
147
- await this.chain;
148
- }
149
- cancel() {
150
- this.cancelled = true;
151
- this.depth = 0;
152
- this.chain = Promise.resolve();
153
- }
154
- size() {
155
- return this.depth;
156
- }
157
- isCancelled() {
158
- return this.cancelled;
159
- }
160
- };
161
- //#endregion
162
- export { safeJsonParse as i, FlumeStatusEmitter as n, FlumeSignalRegistry as r, FlumeSerialQueue as t };
@@ -1,154 +0,0 @@
1
- import { z } from "zod/v4";
2
-
3
- //#region lib/discord/discord-gateway-message-schema.d.ts
4
- declare const FlumeGatewayMessageSchema: z.ZodObject<{
5
- op: z.ZodNumber;
6
- d: z.ZodUnknown;
7
- s: z.ZodNullable<z.ZodNumber>;
8
- t: z.ZodNullable<z.ZodString>;
9
- }, z.core.$strip>;
10
- //#endregion
11
- //#region lib/github/github-notification-schema.d.ts
12
- declare const FlumeGitHubNotificationSchema: z.ZodObject<{
13
- id: z.ZodString;
14
- reason: z.ZodString;
15
- unread: z.ZodBoolean;
16
- updated_at: z.ZodString;
17
- subject: z.ZodObject<{
18
- title: z.ZodString;
19
- url: z.ZodNullable<z.ZodString>;
20
- type: z.ZodString;
21
- }, z.core.$strip>;
22
- repository: z.ZodObject<{
23
- full_name: z.ZodString;
24
- }, z.core.$strip>;
25
- }, z.core.$strip>;
26
- //#endregion
27
- //#region lib/slack/slack-connection-response-schema.d.ts
28
- declare const FlumeSlackConnectionResponseSchema: z.ZodObject<{
29
- ok: z.ZodBoolean;
30
- url: z.ZodOptional<z.ZodString>;
31
- error: z.ZodOptional<z.ZodString>;
32
- }, z.core.$strip>;
33
- //#endregion
34
- //#region lib/slack/slack-envelope-schema.d.ts
35
- declare const FlumeSlackEnvelopeSchema: z.ZodObject<{
36
- envelope_id: z.ZodString;
37
- type: z.ZodString;
38
- payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
39
- accepts_response_payload: z.ZodOptional<z.ZodBoolean>;
40
- retry_attempt: z.ZodOptional<z.ZodNumber>;
41
- retry_reason: z.ZodOptional<z.ZodString>;
42
- }, z.core.$strip>;
43
- //#endregion
44
- //#region lib/types.d.ts
45
- type FlumeTimerHandle = unknown;
46
- type FlumeRuntimeDeps = {
47
- fetch(url: string | URL, init?: RequestInit): Promise<Response>;
48
- WebSocket: (new (url: string | URL) => WebSocket) | null;
49
- now(): number;
50
- random(): number;
51
- setTimeout(fn: () => void, ms: number): FlumeTimerHandle;
52
- clearTimeout(id: FlumeTimerHandle): void;
53
- setInterval(fn: () => void, ms: number): FlumeTimerHandle;
54
- clearInterval(id: FlumeTimerHandle): void;
55
- };
56
- type FlumeSourceName = "discord" | "slack" | "github";
57
- type FlumeDiscordEvent = {
58
- source: "discord";
59
- type: string;
60
- data: Record<string, unknown>;
61
- meta: Record<string, string>;
62
- receivedAt: number;
63
- };
64
- type FlumeSlackEvent = {
65
- source: "slack";
66
- type: string;
67
- data: Record<string, unknown>;
68
- meta: Record<string, string>;
69
- receivedAt: number;
70
- };
71
- type FlumeGitHubEvent = {
72
- source: "github";
73
- type: "notification";
74
- data: FlumeGitHubNotification;
75
- meta: Record<string, string>;
76
- receivedAt: number;
77
- };
78
- type FlumeEvent = FlumeDiscordEvent | FlumeSlackEvent | FlumeGitHubEvent;
79
- type FlumeHandler = (event: FlumeEvent) => void | Promise<void>;
80
- type FlumeSourceStartOptions = {
81
- signal?: AbortSignal;
82
- };
83
- type FlumeSource = {
84
- readonly name: FlumeSourceName;
85
- start(handler: FlumeHandler, options?: FlumeSourceStartOptions): Promise<Error | null>;
86
- stop(): Promise<void>;
87
- status(): FlumeStatus;
88
- };
89
- type FlumeSourceStatus = {
90
- /**
91
- * 多くは `FlumeSourceName` のいずれかだが、`source.name` getter が throw する
92
- * 第三者 `FlumeSource` 実装に備えて `string` まで広げてある (fallback で `"?"`)
93
- */
94
- source: string;
95
- status: FlumeStatus;
96
- };
97
- type FlumeStatus = "disconnected" | "connecting" | "connected" | "reconnecting";
98
- type FlumeStatusHandler = (status: FlumeStatus, detail?: string) => void;
99
- type FlumeLogLevel = "debug" | "info" | "warn" | "error";
100
- type FlumeLog = {
101
- level: FlumeLogLevel;
102
- source: string;
103
- action: string;
104
- message: string;
105
- error?: Error;
106
- detail?: Record<string, unknown>;
107
- timestamp: number;
108
- };
109
- type FlumeLogHandler = (log: FlumeLog) => void;
110
- type FlumeLogInput = {
111
- action: string;
112
- message: string;
113
- error?: Error;
114
- detail?: Record<string, unknown>;
115
- };
116
- type FlumeReconnectOptions = {
117
- maxAttempts?: number;
118
- baseDelay?: number;
119
- maxDelay?: number;
120
- };
121
- type FlumeReconnectConfig = {
122
- maxAttempts: number;
123
- baseDelay: number;
124
- maxDelay: number;
125
- };
126
- type FlumeSourceOptions = {
127
- reconnect?: boolean | FlumeReconnectOptions;
128
- onStatus?: FlumeStatusHandler;
129
- onLog?: FlumeLogHandler;
130
- signal?: AbortSignal;
131
- deps?: FlumeRuntimeDeps;
132
- };
133
- type FlumeDiscordSourceOptions = FlumeSourceOptions & {
134
- token: string;
135
- intents?: number;
136
- };
137
- type FlumeSlackSourceOptions = FlumeSourceOptions & {
138
- appToken: string;
139
- /**
140
- * Bot token (`xoxb-`). Slack の Socket Mode (受信) には不要だが、ホスト側 (返信や
141
- * `auth.test` での self 検出) が必ず使うため型で保持を強制する
142
- */
143
- botToken: string;
144
- };
145
- type FlumeGitHubSourceOptions = FlumeSourceOptions & {
146
- token: string;
147
- pollInterval?: number;
148
- };
149
- type FlumeGatewayMessage = z.infer<typeof FlumeGatewayMessageSchema>;
150
- type FlumeSlackEnvelope = z.infer<typeof FlumeSlackEnvelopeSchema>;
151
- type FlumeSlackConnectionResponse = z.infer<typeof FlumeSlackConnectionResponseSchema>;
152
- type FlumeGitHubNotification = z.infer<typeof FlumeGitHubNotificationSchema>;
153
- //#endregion
154
- export { FlumeSourceStartOptions as C, FlumeTimerHandle as D, FlumeStatusHandler as E, FlumeSourceOptions as S, FlumeStatus as T, FlumeSlackEnvelope as _, FlumeGitHubEvent as a, FlumeSource as b, FlumeHandler as c, FlumeLogInput as d, FlumeLogLevel as f, FlumeSlackConnectionResponse as g, FlumeRuntimeDeps as h, FlumeGatewayMessage as i, FlumeLog as l, FlumeReconnectOptions as m, FlumeDiscordSourceOptions as n, FlumeGitHubNotification as o, FlumeReconnectConfig as p, FlumeEvent as r, FlumeGitHubSourceOptions as s, FlumeDiscordEvent as t, FlumeLogHandler as u, FlumeSlackEvent as v, FlumeSourceStatus as w, FlumeSourceName as x, FlumeSlackSourceOptions as y };
File without changes