@fluojs/discord 1.0.0-beta.1

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.
@@ -0,0 +1,253 @@
1
+ import type { AsyncModuleOptions, MaybePromise } from '@fluojs/core';
2
+ import type { NotificationDispatchRequest } from '@fluojs/notifications';
3
+ /** Opaque Discord embed object forwarded to one transport implementation. */
4
+ export type DiscordEmbed = Readonly<Record<string, unknown>>;
5
+ /** Opaque Discord component object forwarded to one transport implementation. */
6
+ export type DiscordComponent = Readonly<Record<string, unknown>>;
7
+ /** Opaque Discord attachment object forwarded to one transport implementation. */
8
+ export type DiscordAttachment = Readonly<Record<string, unknown>>;
9
+ /** Opaque Discord poll object forwarded to one transport implementation. */
10
+ export type DiscordPoll = Readonly<Record<string, unknown>>;
11
+ /** Opaque Discord allowed-mentions object forwarded to one transport implementation. */
12
+ export type DiscordAllowedMentions = Readonly<Record<string, unknown>>;
13
+ /** Caller-supplied Discord message shape used for standalone delivery. */
14
+ export interface DiscordMessage {
15
+ allowedMentions?: DiscordAllowedMentions;
16
+ attachments?: readonly DiscordAttachment[];
17
+ avatarUrl?: string;
18
+ components?: readonly DiscordComponent[];
19
+ content?: string;
20
+ embeds?: readonly DiscordEmbed[];
21
+ flags?: number;
22
+ metadata?: Record<string, unknown>;
23
+ poll?: DiscordPoll;
24
+ threadId?: string;
25
+ threadName?: string;
26
+ tts?: boolean;
27
+ username?: string;
28
+ }
29
+ /** Normalized Discord message passed to one transport implementation. */
30
+ export interface NormalizedDiscordMessage {
31
+ allowedMentions?: DiscordAllowedMentions;
32
+ attachments: readonly DiscordAttachment[];
33
+ avatarUrl?: string;
34
+ components: readonly DiscordComponent[];
35
+ content?: string;
36
+ embeds: readonly DiscordEmbed[];
37
+ flags?: number;
38
+ metadata?: Record<string, unknown>;
39
+ poll?: DiscordPoll;
40
+ threadId?: string;
41
+ threadName?: string;
42
+ tts?: boolean;
43
+ username?: string;
44
+ }
45
+ /** Context object forwarded to transport implementations per delivery attempt. */
46
+ export interface DiscordTransportContext {
47
+ signal?: AbortSignal;
48
+ }
49
+ /** Provider-specific receipt returned by one Discord transport. */
50
+ export interface DiscordTransportReceipt {
51
+ channelId?: string;
52
+ guildId?: string;
53
+ messageId?: string;
54
+ metadata?: Record<string, unknown>;
55
+ ok?: boolean;
56
+ response?: string;
57
+ statusCode?: number;
58
+ threadId?: string;
59
+ warnings?: readonly string[];
60
+ }
61
+ /** Transport contract implemented by runtime-specific or provider-specific Discord adapters. */
62
+ export interface DiscordTransport {
63
+ /**
64
+ * Sends one normalized Discord message.
65
+ *
66
+ * @param message Normalized message with resolved defaults and one target webhook/thread route.
67
+ * @param context Optional abort context propagated from the caller.
68
+ * @returns Provider-specific receipt details normalized for the Fluo Discord contract.
69
+ */
70
+ send(message: NormalizedDiscordMessage, context: DiscordTransportContext): Promise<DiscordTransportReceipt>;
71
+ /**
72
+ * Verifies transport readiness during bootstrap when configured.
73
+ *
74
+ * @returns A promise that resolves when the transport is ready for delivery.
75
+ */
76
+ verify?(): MaybePromise<void>;
77
+ /**
78
+ * Closes underlying transport resources during application shutdown.
79
+ *
80
+ * @returns A promise that resolves when resource cleanup completes.
81
+ */
82
+ close?(): MaybePromise<void>;
83
+ }
84
+ /** Factory used to construct a transport lazily during module bootstrap. */
85
+ export interface DiscordTransportFactory {
86
+ /**
87
+ * Creates the transport instance used by {@link DiscordService}.
88
+ *
89
+ * @returns The transport implementation that will own Discord delivery.
90
+ */
91
+ create(): MaybePromise<DiscordTransport>;
92
+ /**
93
+ * Stable diagnostic label describing the injected transport kind.
94
+ *
95
+ * @remarks
96
+ * This value is surfaced through platform status snapshots so applications can
97
+ * tell which adapter is currently wired without the package hard-coding a
98
+ * provider-specific runtime dependency.
99
+ */
100
+ kind?: string;
101
+ /**
102
+ * Declares whether the factory-created transport owns resources that the package should close.
103
+ *
104
+ * @remarks
105
+ * Factories default to `true` because they typically allocate the transport instance.
106
+ * Directly injected transport instances default to `false` because the caller owns them.
107
+ */
108
+ ownsResources?: boolean;
109
+ }
110
+ /** Minimal fetch-compatible response contract used by the built-in webhook transport helper. */
111
+ export interface DiscordFetchResponse {
112
+ ok: boolean;
113
+ status: number;
114
+ statusText?: string;
115
+ text(): MaybePromise<string>;
116
+ }
117
+ /** Minimal fetch-compatible function signature used by the built-in webhook transport helper. */
118
+ export type DiscordFetchLike = (input: string, init?: {
119
+ body?: string;
120
+ headers?: Readonly<Record<string, string>>;
121
+ method?: string;
122
+ signal?: AbortSignal;
123
+ }) => MaybePromise<DiscordFetchResponse>;
124
+ /** Options accepted by {@link createDiscordWebhookTransport}. */
125
+ export interface DiscordWebhookTransportOptions {
126
+ fetch?: DiscordFetchLike;
127
+ wait?: boolean;
128
+ webhookUrl: string;
129
+ }
130
+ /** Template render input used for `NotificationDispatchRequest.template` integration. */
131
+ export interface DiscordTemplateRenderInput<TPayload extends DiscordNotificationPayload = DiscordNotificationPayload> {
132
+ locale?: string;
133
+ metadata?: Record<string, unknown>;
134
+ payload: TPayload;
135
+ subject?: string;
136
+ template: string;
137
+ }
138
+ /** Render result returned by an optional Discord template renderer. */
139
+ export interface DiscordTemplateRenderResult {
140
+ components?: readonly DiscordComponent[];
141
+ content?: string;
142
+ embeds?: readonly DiscordEmbed[];
143
+ }
144
+ /** Optional renderer used to turn notification templates into concrete Discord content. */
145
+ export interface DiscordTemplateRenderer {
146
+ /**
147
+ * Renders one notification template into Discord content and/or embed fragments.
148
+ *
149
+ * @typeParam TPayload Payload shape carried by the notification request.
150
+ * @param input Template render input including the template key and opaque payload.
151
+ * @returns Rendered content or embed fragments that are merged with explicit payload overrides.
152
+ */
153
+ render<TPayload extends DiscordNotificationPayload = DiscordNotificationPayload>(input: DiscordTemplateRenderInput<TPayload>): MaybePromise<DiscordTemplateRenderResult>;
154
+ }
155
+ /** Notification payload understood by {@link DiscordChannel} and {@link DiscordService.sendNotification}. */
156
+ export interface DiscordNotificationPayload extends Record<string, unknown> {
157
+ allowedMentions?: DiscordAllowedMentions;
158
+ attachments?: readonly DiscordAttachment[];
159
+ avatarUrl?: string;
160
+ components?: readonly DiscordComponent[];
161
+ content?: string;
162
+ embeds?: readonly DiscordEmbed[];
163
+ flags?: number;
164
+ metadata?: Record<string, unknown>;
165
+ poll?: DiscordPoll;
166
+ threadId?: string;
167
+ threadName?: string;
168
+ tts?: boolean;
169
+ username?: string;
170
+ }
171
+ /** Shared notification request subtype consumed by the Discord channel implementation. */
172
+ export interface DiscordNotificationDispatchRequest extends NotificationDispatchRequest<DiscordNotificationPayload> {
173
+ channel: string;
174
+ }
175
+ /** Caller-visible result returned by standalone and notification-backed Discord delivery. */
176
+ export interface DiscordSendResult extends DiscordTransportReceipt {
177
+ ok: boolean;
178
+ warnings: readonly string[];
179
+ }
180
+ /** Failure entry returned by tolerant batch delivery. */
181
+ export interface DiscordSendFailure {
182
+ error: Error;
183
+ message: DiscordMessage;
184
+ }
185
+ /** Summary returned by {@link DiscordService.sendMany}. */
186
+ export interface DiscordSendBatchResult {
187
+ failed: number;
188
+ failures: readonly DiscordSendFailure[];
189
+ results: readonly DiscordSendResult[];
190
+ succeeded: number;
191
+ }
192
+ /** Additional send controls applied to one Discord delivery attempt. */
193
+ export interface DiscordSendOptions {
194
+ signal?: AbortSignal;
195
+ }
196
+ /** Additional controls applied to one batch send operation. */
197
+ export interface DiscordSendManyOptions extends DiscordSendOptions {
198
+ continueOnError?: boolean;
199
+ }
200
+ /** Module options accepted by {@link DiscordModule.forRoot} and `forRootAsync`. */
201
+ export interface DiscordModuleOptions {
202
+ defaultThreadId?: string;
203
+ notifications?: {
204
+ channel?: string;
205
+ };
206
+ renderer?: DiscordTemplateRenderer;
207
+ transport: DiscordTransport | DiscordTransportFactory;
208
+ verifyOnModuleInit?: boolean;
209
+ }
210
+ /** Async registration options for Discord modules that derive config through DI. */
211
+ export type DiscordAsyncModuleOptions = AsyncModuleOptions<DiscordModuleOptions>;
212
+ /** Normalized module options resolved once during module registration. */
213
+ export interface NormalizedDiscordModuleOptions {
214
+ defaultThreadId?: string;
215
+ notifications: {
216
+ channel: string;
217
+ };
218
+ renderer?: DiscordTemplateRenderer;
219
+ transport: {
220
+ create: () => Promise<DiscordTransport>;
221
+ kind: string;
222
+ ownsResources: boolean;
223
+ };
224
+ verifyOnModuleInit: boolean;
225
+ }
226
+ /** Discord facade exposed to application code and the compatibility token. */
227
+ export interface Discord {
228
+ /**
229
+ * Sends one Discord message directly through the configured transport.
230
+ *
231
+ * @param message Caller-supplied Discord message with content, embeds, or component payloads.
232
+ * @param options Optional abort signal propagated to the transport.
233
+ * @returns A normalized delivery receipt describing the transport response.
234
+ */
235
+ send(message: DiscordMessage, options?: DiscordSendOptions): Promise<DiscordSendResult>;
236
+ /**
237
+ * Sends multiple Discord messages in input order with optional tolerant failure handling.
238
+ *
239
+ * @param messages Ordered message list to deliver through the configured transport.
240
+ * @param options Optional tolerant batch controls such as `continueOnError`.
241
+ * @returns A batch summary containing successes and any captured failures.
242
+ */
243
+ sendMany(messages: readonly DiscordMessage[], options?: DiscordSendManyOptions): Promise<DiscordSendBatchResult>;
244
+ /**
245
+ * Converts one notifications foundation request into a concrete Discord delivery.
246
+ *
247
+ * @param notification Shared notification envelope interpreted by the Discord package.
248
+ * @param options Optional abort signal propagated to rendering and transport work.
249
+ * @returns A normalized delivery receipt for the resulting Discord message.
250
+ */
251
+ sendNotification(notification: DiscordNotificationDispatchRequest, options?: DiscordSendOptions): Promise<DiscordSendResult>;
252
+ }
253
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { DiscordTransport, DiscordWebhookTransportOptions } from './types.js';
2
+ /**
3
+ * Creates a webhook-first Discord transport backed by an explicit fetch-compatible boundary.
4
+ *
5
+ * @param options Webhook URL plus an optional injected fetch implementation for portable runtimes.
6
+ * @returns A Discord transport that posts JSON payloads to one Discord webhook endpoint.
7
+ * @throws {DiscordConfigurationError} When the webhook url is empty or no fetch implementation is available.
8
+ * @throws {DiscordTransportError} When Discord responds with a non-success HTTP status.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const transport = createDiscordWebhookTransport({
13
+ * fetch: runtime.fetch,
14
+ * webhookUrl: 'https://discord.com/api/webhooks/123/abc',
15
+ * });
16
+ * ```
17
+ */
18
+ export declare function createDiscordWebhookTransport(options: DiscordWebhookTransportOptions): DiscordTransport;
19
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,gBAAgB,EAEhB,8BAA8B,EAE/B,MAAM,YAAY,CAAC;AAiIpB;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,8BAA8B,GAAG,gBAAgB,CAyEvG"}
@@ -0,0 +1,200 @@
1
+ import { DiscordConfigurationError, DiscordTransportError } from './errors.js';
2
+ const DEFAULT_RETRY_ATTEMPTS = 3;
3
+ const DEFAULT_RETRY_DELAY_MS = 250;
4
+ function normalizeOptionalString(value) {
5
+ const trimmed = value?.trim();
6
+ return trimmed && trimmed.length > 0 ? trimmed : undefined;
7
+ }
8
+ function getStringField(body, key) {
9
+ const value = body?.[key];
10
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
11
+ }
12
+ function createWebhookPayload(message) {
13
+ return {
14
+ ...(message.allowedMentions ? {
15
+ allowed_mentions: message.allowedMentions
16
+ } : {}),
17
+ ...(message.attachments.length > 0 ? {
18
+ attachments: message.attachments
19
+ } : {}),
20
+ ...(message.avatarUrl ? {
21
+ avatar_url: message.avatarUrl
22
+ } : {}),
23
+ ...(message.components.length > 0 ? {
24
+ components: message.components
25
+ } : {}),
26
+ ...(message.content ? {
27
+ content: message.content
28
+ } : {}),
29
+ ...(message.embeds.length > 0 ? {
30
+ embeds: message.embeds
31
+ } : {}),
32
+ ...(typeof message.flags === 'number' ? {
33
+ flags: message.flags
34
+ } : {}),
35
+ ...(message.poll ? {
36
+ poll: message.poll
37
+ } : {}),
38
+ ...(message.threadName ? {
39
+ thread_name: message.threadName
40
+ } : {}),
41
+ ...(typeof message.tts === 'boolean' ? {
42
+ tts: message.tts
43
+ } : {}),
44
+ ...(message.username ? {
45
+ username: message.username
46
+ } : {})
47
+ };
48
+ }
49
+ function resolveFetch(fetchLike) {
50
+ if (fetchLike) {
51
+ return fetchLike;
52
+ }
53
+ if (typeof globalThis.fetch !== 'function') {
54
+ throw new DiscordConfigurationError('Discord webhook transport requires an explicit fetch implementation when `globalThis.fetch` is unavailable.');
55
+ }
56
+ return (input, init) => globalThis.fetch(input, init);
57
+ }
58
+ function parseWebhookUrl(webhookUrl) {
59
+ try {
60
+ return new URL(webhookUrl);
61
+ } catch {
62
+ throw new DiscordConfigurationError('Discord webhook transport requires a valid absolute `webhookUrl`.');
63
+ }
64
+ }
65
+ function resolveWebhookUrl(webhookUrl, message, wait) {
66
+ const url = new URL(webhookUrl.toString());
67
+ url.searchParams.set('wait', String(wait));
68
+ if (message.threadId) {
69
+ url.searchParams.set('thread_id', message.threadId);
70
+ }
71
+ return url.toString();
72
+ }
73
+ async function readResponseBody(response) {
74
+ try {
75
+ return await response.text();
76
+ } catch {
77
+ return '';
78
+ }
79
+ }
80
+ function createStatusFailureMessage(response, attempt) {
81
+ return `Discord webhook delivery failed with status ${response.status}${response.statusText ? ` ${response.statusText}` : ''} after ${String(attempt)} attempt(s). Upstream response body was omitted from the caller-visible error.`;
82
+ }
83
+ function createTransportFailureMessage(attempt) {
84
+ return `Discord webhook delivery failed after ${String(attempt)} attempt(s). Upstream response details were omitted from the caller-visible error.`;
85
+ }
86
+ function isAbortError(error) {
87
+ return error instanceof Error && error.name === 'AbortError';
88
+ }
89
+ function isTransientStatus(status) {
90
+ return status === 408 || status === 429 || status >= 500 && status <= 599;
91
+ }
92
+ async function waitForRetry(delayMs, signal) {
93
+ if (delayMs <= 0) {
94
+ return;
95
+ }
96
+ await new Promise((resolve, reject) => {
97
+ const timer = setTimeout(() => {
98
+ signal?.removeEventListener('abort', onAbort);
99
+ resolve();
100
+ }, delayMs);
101
+ function onAbort() {
102
+ clearTimeout(timer);
103
+ reject(signal?.reason ?? new DOMException('The operation was aborted.', 'AbortError'));
104
+ }
105
+ if (signal) {
106
+ signal.addEventListener('abort', onAbort, {
107
+ once: true
108
+ });
109
+ }
110
+ });
111
+ }
112
+ function parseJsonRecord(body) {
113
+ if (!body) {
114
+ return undefined;
115
+ }
116
+ try {
117
+ const parsed = JSON.parse(body);
118
+ return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? parsed : undefined;
119
+ } catch {
120
+ return undefined;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Creates a webhook-first Discord transport backed by an explicit fetch-compatible boundary.
126
+ *
127
+ * @param options Webhook URL plus an optional injected fetch implementation for portable runtimes.
128
+ * @returns A Discord transport that posts JSON payloads to one Discord webhook endpoint.
129
+ * @throws {DiscordConfigurationError} When the webhook url is empty or no fetch implementation is available.
130
+ * @throws {DiscordTransportError} When Discord responds with a non-success HTTP status.
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * const transport = createDiscordWebhookTransport({
135
+ * fetch: runtime.fetch,
136
+ * webhookUrl: 'https://discord.com/api/webhooks/123/abc',
137
+ * });
138
+ * ```
139
+ */
140
+ export function createDiscordWebhookTransport(options) {
141
+ const webhookUrl = normalizeOptionalString(options.webhookUrl);
142
+ if (!webhookUrl) {
143
+ throw new DiscordConfigurationError('Discord webhook transport requires a non-empty `webhookUrl`.');
144
+ }
145
+ const parsedWebhookUrl = parseWebhookUrl(webhookUrl);
146
+ const fetchLike = resolveFetch(options.fetch);
147
+ const wait = options.wait ?? true;
148
+ return {
149
+ async send(message, context) {
150
+ for (let attempt = 1; attempt <= DEFAULT_RETRY_ATTEMPTS; attempt += 1) {
151
+ try {
152
+ const response = await fetchLike(resolveWebhookUrl(parsedWebhookUrl, message, wait), {
153
+ body: JSON.stringify(createWebhookPayload(message)),
154
+ headers: {
155
+ 'content-type': 'application/json; charset=utf-8'
156
+ },
157
+ method: 'POST',
158
+ signal: context.signal
159
+ });
160
+ const body = await readResponseBody(response);
161
+ if (!response.ok) {
162
+ if (attempt < DEFAULT_RETRY_ATTEMPTS && isTransientStatus(response.status)) {
163
+ await waitForRetry(DEFAULT_RETRY_DELAY_MS * 2 ** (attempt - 1), context.signal);
164
+ continue;
165
+ }
166
+ throw new DiscordTransportError(createStatusFailureMessage(response, attempt));
167
+ }
168
+ const parsed = parseJsonRecord(body);
169
+ const warnings = [];
170
+ if (body && !parsed) {
171
+ warnings.push('Discord webhook returned a non-JSON success body.');
172
+ }
173
+ return {
174
+ channelId: getStringField(parsed, 'channel_id'),
175
+ guildId: getStringField(parsed, 'guild_id'),
176
+ messageId: getStringField(parsed, 'id'),
177
+ ok: true,
178
+ response: body || undefined,
179
+ statusCode: response.status,
180
+ threadId: getStringField(parsed, 'thread_id') ?? message.threadId,
181
+ warnings
182
+ };
183
+ } catch (error) {
184
+ if (isAbortError(error) || context.signal?.aborted) {
185
+ throw error;
186
+ }
187
+ if (attempt < DEFAULT_RETRY_ATTEMPTS) {
188
+ await waitForRetry(DEFAULT_RETRY_DELAY_MS * 2 ** (attempt - 1), context.signal);
189
+ continue;
190
+ }
191
+ if (error instanceof DiscordTransportError) {
192
+ throw error;
193
+ }
194
+ throw new DiscordTransportError(createTransportFailureMessage(attempt));
195
+ }
196
+ }
197
+ throw new DiscordTransportError(createTransportFailureMessage(DEFAULT_RETRY_ATTEMPTS));
198
+ }
199
+ };
200
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@fluojs/discord",
3
+ "description": "Webhook-first, transport-agnostic Discord delivery core for Fluo with notifications integration.",
4
+ "keywords": [
5
+ "fluo",
6
+ "discord",
7
+ "webhook",
8
+ "notifications",
9
+ "portable",
10
+ "fetch"
11
+ ],
12
+ "version": "1.0.0-beta.1",
13
+ "private": false,
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/fluojs/fluo.git",
18
+ "directory": "packages/discord"
19
+ },
20
+ "engines": {
21
+ "node": ">=20.0.0"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "type": "module",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ }
32
+ },
33
+ "main": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "dependencies": {
39
+ "@fluojs/core": "^1.0.0-beta.1",
40
+ "@fluojs/runtime": "^1.0.0-beta.1",
41
+ "@fluojs/di": "^1.0.0-beta.1",
42
+ "@fluojs/notifications": "^1.0.0-beta.1"
43
+ },
44
+ "devDependencies": {
45
+ "vitest": "^3.2.4"
46
+ },
47
+ "scripts": {
48
+ "prebuild": "node ../../tooling/scripts/clean-dist.mjs",
49
+ "build": "pnpm exec babel src --extensions .ts --ignore 'src/**/*.test.ts' --out-dir dist --config-file ../../tooling/babel/babel.config.cjs && pnpm exec tsc -p tsconfig.build.json",
50
+ "typecheck": "pnpm exec tsc -p tsconfig.json --noEmit",
51
+ "test": "pnpm exec vitest run -c vitest.config.ts",
52
+ "test:watch": "pnpm exec vitest -c vitest.config.ts"
53
+ }
54
+ }