@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.
- package/LICENSE +21 -0
- package/README.ko.md +216 -0
- package/README.md +216 -0
- package/dist/channel.d.ts +24 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +61 -0
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +29 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/module.d.ts +41 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +133 -0
- package/dist/service.d.ts +78 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +262 -0
- package/dist/status.d.ts +27 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +82 -0
- package/dist/tokens.d.ts +10 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +6 -0
- package/dist/types.d.ts +253 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/webhook.d.ts +19 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +200 -0
- package/package.json +54 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMhE,OAAO,KAAK,EAEV,yBAAyB,EACzB,oBAAoB,EAIrB,MAAM,YAAY,CAAC;AA2GpB,oFAAoF;AACpF,qBAAa,aAAa;IACxB;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,UAAU;IAIzD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,yBAAyB,GAAG,UAAU;CAGpE"}
|
package/dist/module.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { defineModule } from '@fluojs/runtime';
|
|
2
|
+
import { DiscordChannel } from './channel.js';
|
|
3
|
+
import { DiscordConfigurationError } from './errors.js';
|
|
4
|
+
import { DiscordService } from './service.js';
|
|
5
|
+
import { DISCORD, DISCORD_CHANNEL, DISCORD_OPTIONS } from './tokens.js';
|
|
6
|
+
function normalizeOptionalString(value) {
|
|
7
|
+
const trimmed = value?.trim();
|
|
8
|
+
return trimmed && trimmed.length > 0 ? trimmed : undefined;
|
|
9
|
+
}
|
|
10
|
+
function isTransportFactory(value) {
|
|
11
|
+
return typeof value === 'object' && value !== null && 'create' in value;
|
|
12
|
+
}
|
|
13
|
+
function normalizeDiscordModuleOptions(options) {
|
|
14
|
+
if (!options.transport) {
|
|
15
|
+
throw new DiscordConfigurationError('DiscordModule requires an explicit `transport` to be configured.');
|
|
16
|
+
}
|
|
17
|
+
const transport = options.transport;
|
|
18
|
+
const createTransport = isTransportFactory(transport) ? async () => transport.create() : async () => transport;
|
|
19
|
+
return {
|
|
20
|
+
defaultThreadId: normalizeOptionalString(options.defaultThreadId),
|
|
21
|
+
notifications: {
|
|
22
|
+
channel: normalizeOptionalString(options.notifications?.channel) ?? 'discord'
|
|
23
|
+
},
|
|
24
|
+
renderer: options.renderer,
|
|
25
|
+
transport: {
|
|
26
|
+
create: createTransport,
|
|
27
|
+
kind: isTransportFactory(transport) ? normalizeOptionalString(transport.kind) ?? 'custom-factory' : 'custom-instance',
|
|
28
|
+
ownsResources: isTransportFactory(transport) ? transport.ownsResources ?? true : false
|
|
29
|
+
},
|
|
30
|
+
verifyOnModuleInit: options.verifyOnModuleInit ?? false
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function createDiscordRuntimeProviders(optionsProvider) {
|
|
34
|
+
return [optionsProvider, DiscordService, DiscordChannel, {
|
|
35
|
+
inject: [DiscordService],
|
|
36
|
+
provide: DISCORD,
|
|
37
|
+
useFactory: service => ({
|
|
38
|
+
send: (message, options) => service.send(message, options),
|
|
39
|
+
sendMany: (messages, options) => service.sendMany(messages, options),
|
|
40
|
+
sendNotification: (notification, options) => service.sendNotification(notification, options)
|
|
41
|
+
})
|
|
42
|
+
}, {
|
|
43
|
+
inject: [DiscordChannel],
|
|
44
|
+
provide: DISCORD_CHANNEL,
|
|
45
|
+
useFactory: channel => channel
|
|
46
|
+
}];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates Discord providers for manual module composition.
|
|
51
|
+
*
|
|
52
|
+
* @param options Static Discord module options including explicit transport wiring.
|
|
53
|
+
* @returns Provider definitions equivalent to {@link DiscordModule.forRoot} wiring.
|
|
54
|
+
*/
|
|
55
|
+
function createDiscordProviders(options) {
|
|
56
|
+
return createDiscordRuntimeProviders({
|
|
57
|
+
provide: DISCORD_OPTIONS,
|
|
58
|
+
useValue: normalizeDiscordModuleOptions(options)
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function buildDiscordModule(options) {
|
|
62
|
+
class DiscordRootModuleDefinition {}
|
|
63
|
+
return defineModule(DiscordRootModuleDefinition, {
|
|
64
|
+
exports: [DiscordService, DiscordChannel, DISCORD, DISCORD_CHANNEL],
|
|
65
|
+
global: true,
|
|
66
|
+
providers: createDiscordProviders(options)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function buildDiscordModuleAsync(options) {
|
|
70
|
+
class DiscordAsyncModuleDefinition {}
|
|
71
|
+
const factory = options.useFactory;
|
|
72
|
+
let cachedResult;
|
|
73
|
+
const memoizedFactory = (...deps) => {
|
|
74
|
+
if (!cachedResult) {
|
|
75
|
+
cachedResult = Promise.resolve(factory(...deps)).then(resolved => normalizeDiscordModuleOptions(resolved));
|
|
76
|
+
}
|
|
77
|
+
return cachedResult;
|
|
78
|
+
};
|
|
79
|
+
return defineModule(DiscordAsyncModuleDefinition, {
|
|
80
|
+
exports: [DiscordService, DiscordChannel, DISCORD, DISCORD_CHANNEL],
|
|
81
|
+
global: true,
|
|
82
|
+
providers: createDiscordRuntimeProviders({
|
|
83
|
+
inject: options.inject,
|
|
84
|
+
provide: DISCORD_OPTIONS,
|
|
85
|
+
scope: 'singleton',
|
|
86
|
+
useFactory: (...deps) => memoizedFactory(...deps)
|
|
87
|
+
})
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Runtime module entrypoint for Discord delivery and notifications integration. */
|
|
92
|
+
export class DiscordModule {
|
|
93
|
+
/**
|
|
94
|
+
* Registers Discord providers using static options.
|
|
95
|
+
*
|
|
96
|
+
* @param options Static Discord module options including transport wiring and optional template rendering behavior.
|
|
97
|
+
* @returns A global module definition that exports {@link DiscordService}, {@link DiscordChannel}, and compatibility tokens.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* DiscordModule.forRoot({
|
|
102
|
+
* transport: createDiscordWebhookTransport({ webhookUrl: 'https://discord.com/api/webhooks/...' }),
|
|
103
|
+
* });
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
static forRoot(options) {
|
|
107
|
+
return buildDiscordModule(options);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Registers Discord providers from an async DI factory.
|
|
112
|
+
*
|
|
113
|
+
* @param options Async module options that resolve Discord transport and renderer configuration through DI.
|
|
114
|
+
* @returns A global module definition that memoizes async option resolution per module instance.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* DiscordModule.forRootAsync({
|
|
119
|
+
* inject: [ConfigService],
|
|
120
|
+
* useFactory: (config) => ({
|
|
121
|
+
* defaultThreadId: config.discord.threadId,
|
|
122
|
+
* transport: createDiscordWebhookTransport({
|
|
123
|
+
* fetch: config.runtime.fetch,
|
|
124
|
+
* webhookUrl: config.discord.webhookUrl,
|
|
125
|
+
* }),
|
|
126
|
+
* }),
|
|
127
|
+
* });
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
static forRootAsync(options) {
|
|
131
|
+
return buildDiscordModuleAsync(options);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { OnApplicationShutdown, OnModuleInit } from '@fluojs/runtime';
|
|
2
|
+
import type { Discord, DiscordMessage, DiscordNotificationDispatchRequest, DiscordSendBatchResult, DiscordSendManyOptions, DiscordSendOptions, DiscordSendResult, NormalizedDiscordModuleOptions } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Injectable Discord delivery service for standalone and notifications-backed usage.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* The service stays transport-agnostic at the shared package boundary, consumes only
|
|
8
|
+
* explicitly injected {@link DiscordTransport} contracts, and translates
|
|
9
|
+
* `@fluojs/notifications` envelopes into concrete Discord messages.
|
|
10
|
+
*/
|
|
11
|
+
export declare class DiscordService implements Discord, OnModuleInit, OnApplicationShutdown {
|
|
12
|
+
private readonly options;
|
|
13
|
+
private lifecycleState;
|
|
14
|
+
private resolvedTransport;
|
|
15
|
+
private transportPromise;
|
|
16
|
+
constructor(options: NormalizedDiscordModuleOptions);
|
|
17
|
+
onApplicationShutdown(): Promise<void>;
|
|
18
|
+
onModuleInit(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Creates a platform status snapshot for the active Discord transport wiring.
|
|
21
|
+
*
|
|
22
|
+
* @returns A structured snapshot describing lifecycle state, resource ownership, and notifications integration details.
|
|
23
|
+
*/
|
|
24
|
+
createPlatformStatusSnapshot(): import("./status.js").DiscordPlatformStatusSnapshot;
|
|
25
|
+
/**
|
|
26
|
+
* Sends one Discord message directly through the configured transport.
|
|
27
|
+
*
|
|
28
|
+
* @param message Caller-supplied Discord message with content, embeds, or component payloads.
|
|
29
|
+
* @param options Optional abort signal propagated to the transport.
|
|
30
|
+
* @returns A normalized delivery receipt describing the transport response.
|
|
31
|
+
* @throws {DiscordMessageValidationError} When the resolved message does not include Discord-visible content.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* await discord.send({
|
|
36
|
+
* content: 'Deploy finished successfully.',
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
send(message: DiscordMessage, options?: DiscordSendOptions): Promise<DiscordSendResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Sends multiple Discord messages in input order with optional tolerant failure handling.
|
|
43
|
+
*
|
|
44
|
+
* @param messages Ordered message list to deliver through the configured transport.
|
|
45
|
+
* @param options Optional tolerant batch controls such as `continueOnError`.
|
|
46
|
+
* @returns A batch summary containing successes and any captured failures.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const result = await discord.sendMany(messages, { continueOnError: true });
|
|
51
|
+
* console.log(result.succeeded, result.failed);
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
sendMany(messages: readonly DiscordMessage[], options?: DiscordSendManyOptions): Promise<DiscordSendBatchResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Converts one notifications foundation request into a concrete Discord delivery.
|
|
57
|
+
*
|
|
58
|
+
* @param notification Shared notification envelope interpreted by the Discord package.
|
|
59
|
+
* @param options Optional abort signal propagated to rendering and transport work.
|
|
60
|
+
* @returns A normalized delivery receipt for the resulting Discord message.
|
|
61
|
+
* @throws {DiscordMessageValidationError} When the notification cannot resolve one target thread or any Discord-visible content.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* await discord.sendNotification({
|
|
66
|
+
* channel: 'discord',
|
|
67
|
+
* payload: { content: 'Deploy finished successfully.' },
|
|
68
|
+
* recipients: ['release-thread-id'],
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
sendNotification(notification: DiscordNotificationDispatchRequest, options?: DiscordSendOptions): Promise<DiscordSendResult>;
|
|
73
|
+
private ensureTransport;
|
|
74
|
+
private normalizeMessage;
|
|
75
|
+
private resolveNotificationThreadId;
|
|
76
|
+
private renderNotification;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK3E,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,kCAAkC,EAClC,sBAAsB,EAEtB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EAIjB,8BAA8B,EAC/B,MAAM,YAAY,CAAC;AAqBpB;;;;;;;GAOG;AACH,qBACa,cAAe,YAAW,OAAO,EAAE,YAAY,EAAE,qBAAqB;IAKrE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,cAAc,CAAmF;IACzG,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,gBAAgB,CAAwC;gBAEnC,OAAO,EAAE,8BAA8B;IAE9D,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAetC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBnC;;;;OAIG;IACH,4BAA4B;IAW5B;;;;;;;;;;;;;;OAcG;IACG,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAuBjG;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA6B1H;;;;;;;;;;;;;;;;OAgBG;IACG,gBAAgB,CACpB,YAAY,EAAE,kCAAkC,EAChD,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;YA6Bf,eAAe;IAe7B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,2BAA2B;YAkBrB,kBAAkB;CAejC"}
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
let _initClass;
|
|
2
|
+
function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
|
|
3
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
4
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
5
|
+
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
6
|
+
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
7
|
+
import { Inject } from '@fluojs/core';
|
|
8
|
+
import { DiscordMessageValidationError } from './errors.js';
|
|
9
|
+
import { createDiscordPlatformStatusSnapshot } from './status.js';
|
|
10
|
+
import { DISCORD_OPTIONS } from './tokens.js';
|
|
11
|
+
function createAbortError() {
|
|
12
|
+
const error = new Error('Discord delivery was aborted.');
|
|
13
|
+
error.name = 'AbortError';
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
16
|
+
function normalizeOptionalString(value) {
|
|
17
|
+
const trimmed = value?.trim();
|
|
18
|
+
return trimmed && trimmed.length > 0 ? trimmed : undefined;
|
|
19
|
+
}
|
|
20
|
+
function assertMessageContent(message) {
|
|
21
|
+
if (!message.content && message.embeds.length === 0 && message.components.length === 0 && message.attachments.length === 0) {
|
|
22
|
+
throw new DiscordMessageValidationError('Discord messages require `content`, `embeds`, `components`, or `attachments` content.');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Injectable Discord delivery service for standalone and notifications-backed usage.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* The service stays transport-agnostic at the shared package boundary, consumes only
|
|
31
|
+
* explicitly injected {@link DiscordTransport} contracts, and translates
|
|
32
|
+
* `@fluojs/notifications` envelopes into concrete Discord messages.
|
|
33
|
+
*/
|
|
34
|
+
let _DiscordService;
|
|
35
|
+
class DiscordService {
|
|
36
|
+
static {
|
|
37
|
+
[_DiscordService, _initClass] = _applyDecs(this, [Inject(DISCORD_OPTIONS)], []).c;
|
|
38
|
+
}
|
|
39
|
+
lifecycleState = 'created';
|
|
40
|
+
resolvedTransport;
|
|
41
|
+
transportPromise;
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.options = options;
|
|
44
|
+
}
|
|
45
|
+
async onApplicationShutdown() {
|
|
46
|
+
this.lifecycleState = 'stopping';
|
|
47
|
+
try {
|
|
48
|
+
if (this.resolvedTransport && this.options.transport.ownsResources && this.resolvedTransport.close) {
|
|
49
|
+
await this.resolvedTransport.close();
|
|
50
|
+
}
|
|
51
|
+
this.lifecycleState = 'stopped';
|
|
52
|
+
} catch {
|
|
53
|
+
this.lifecycleState = 'failed';
|
|
54
|
+
throw new Error('Discord transport failed to close cleanly.');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async onModuleInit() {
|
|
58
|
+
this.lifecycleState = 'starting';
|
|
59
|
+
try {
|
|
60
|
+
const transport = await this.ensureTransport();
|
|
61
|
+
if (this.options.verifyOnModuleInit && transport.verify) {
|
|
62
|
+
await transport.verify();
|
|
63
|
+
}
|
|
64
|
+
this.lifecycleState = 'ready';
|
|
65
|
+
} catch {
|
|
66
|
+
this.lifecycleState = 'failed';
|
|
67
|
+
throw new Error('Discord transport failed to initialize.');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Creates a platform status snapshot for the active Discord transport wiring.
|
|
73
|
+
*
|
|
74
|
+
* @returns A structured snapshot describing lifecycle state, resource ownership, and notifications integration details.
|
|
75
|
+
*/
|
|
76
|
+
createPlatformStatusSnapshot() {
|
|
77
|
+
return createDiscordPlatformStatusSnapshot({
|
|
78
|
+
channelName: this.options.notifications.channel,
|
|
79
|
+
defaultThreadConfigured: this.options.defaultThreadId !== undefined,
|
|
80
|
+
lifecycleState: this.lifecycleState,
|
|
81
|
+
ownsTransportResources: this.options.transport.ownsResources,
|
|
82
|
+
transportKind: this.options.transport.kind,
|
|
83
|
+
verifiedOnModuleInit: this.options.verifyOnModuleInit
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Sends one Discord message directly through the configured transport.
|
|
89
|
+
*
|
|
90
|
+
* @param message Caller-supplied Discord message with content, embeds, or component payloads.
|
|
91
|
+
* @param options Optional abort signal propagated to the transport.
|
|
92
|
+
* @returns A normalized delivery receipt describing the transport response.
|
|
93
|
+
* @throws {DiscordMessageValidationError} When the resolved message does not include Discord-visible content.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* await discord.send({
|
|
98
|
+
* content: 'Deploy finished successfully.',
|
|
99
|
+
* });
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
async send(message, options = {}) {
|
|
103
|
+
if (options.signal?.aborted) {
|
|
104
|
+
throw createAbortError();
|
|
105
|
+
}
|
|
106
|
+
const transport = await this.ensureTransport();
|
|
107
|
+
const normalized = this.normalizeMessage(message);
|
|
108
|
+
assertMessageContent(normalized);
|
|
109
|
+
const result = await transport.send(normalized, options);
|
|
110
|
+
return {
|
|
111
|
+
channelId: result.channelId,
|
|
112
|
+
guildId: result.guildId,
|
|
113
|
+
messageId: result.messageId,
|
|
114
|
+
metadata: result.metadata,
|
|
115
|
+
ok: result.ok ?? true,
|
|
116
|
+
response: result.response,
|
|
117
|
+
statusCode: result.statusCode,
|
|
118
|
+
threadId: result.threadId ?? normalized.threadId,
|
|
119
|
+
warnings: result.warnings ?? []
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Sends multiple Discord messages in input order with optional tolerant failure handling.
|
|
125
|
+
*
|
|
126
|
+
* @param messages Ordered message list to deliver through the configured transport.
|
|
127
|
+
* @param options Optional tolerant batch controls such as `continueOnError`.
|
|
128
|
+
* @returns A batch summary containing successes and any captured failures.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* const result = await discord.sendMany(messages, { continueOnError: true });
|
|
133
|
+
* console.log(result.succeeded, result.failed);
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
async sendMany(messages, options = {}) {
|
|
137
|
+
const results = [];
|
|
138
|
+
const failures = [];
|
|
139
|
+
for (const message of messages) {
|
|
140
|
+
try {
|
|
141
|
+
results.push(await this.send(message, options));
|
|
142
|
+
} catch (error) {
|
|
143
|
+
const failure = {
|
|
144
|
+
error: error instanceof Error ? error : new Error('Discord delivery failed.'),
|
|
145
|
+
message
|
|
146
|
+
};
|
|
147
|
+
if (!(options.continueOnError ?? false)) {
|
|
148
|
+
throw failure.error;
|
|
149
|
+
}
|
|
150
|
+
failures.push(failure);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
failed: failures.length,
|
|
155
|
+
failures,
|
|
156
|
+
results,
|
|
157
|
+
succeeded: results.length
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Converts one notifications foundation request into a concrete Discord delivery.
|
|
163
|
+
*
|
|
164
|
+
* @param notification Shared notification envelope interpreted by the Discord package.
|
|
165
|
+
* @param options Optional abort signal propagated to rendering and transport work.
|
|
166
|
+
* @returns A normalized delivery receipt for the resulting Discord message.
|
|
167
|
+
* @throws {DiscordMessageValidationError} When the notification cannot resolve one target thread or any Discord-visible content.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* await discord.sendNotification({
|
|
172
|
+
* channel: 'discord',
|
|
173
|
+
* payload: { content: 'Deploy finished successfully.' },
|
|
174
|
+
* recipients: ['release-thread-id'],
|
|
175
|
+
* });
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
async sendNotification(notification, options = {}) {
|
|
179
|
+
const payload = notification.payload;
|
|
180
|
+
const rendered = await this.renderNotification(notification);
|
|
181
|
+
return this.send({
|
|
182
|
+
allowedMentions: payload.allowedMentions,
|
|
183
|
+
attachments: payload.attachments ?? [],
|
|
184
|
+
avatarUrl: payload.avatarUrl,
|
|
185
|
+
components: payload.components ?? rendered?.components,
|
|
186
|
+
content: payload.content ?? rendered?.content ?? notification.subject,
|
|
187
|
+
embeds: payload.embeds ?? rendered?.embeds,
|
|
188
|
+
flags: payload.flags,
|
|
189
|
+
metadata: {
|
|
190
|
+
...(payload.metadata ?? {}),
|
|
191
|
+
...(notification.metadata ?? {}),
|
|
192
|
+
...(notification.subject ? {
|
|
193
|
+
subject: notification.subject
|
|
194
|
+
} : {}),
|
|
195
|
+
...(notification.template ? {
|
|
196
|
+
template: notification.template
|
|
197
|
+
} : {})
|
|
198
|
+
},
|
|
199
|
+
poll: payload.poll,
|
|
200
|
+
threadId: this.resolveNotificationThreadId(notification),
|
|
201
|
+
threadName: payload.threadName,
|
|
202
|
+
tts: payload.tts,
|
|
203
|
+
username: payload.username
|
|
204
|
+
}, options);
|
|
205
|
+
}
|
|
206
|
+
async ensureTransport() {
|
|
207
|
+
if (this.resolvedTransport) {
|
|
208
|
+
return this.resolvedTransport;
|
|
209
|
+
}
|
|
210
|
+
if (!this.transportPromise) {
|
|
211
|
+
this.transportPromise = this.options.transport.create().then(transport => {
|
|
212
|
+
this.resolvedTransport = transport;
|
|
213
|
+
return transport;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return this.transportPromise;
|
|
217
|
+
}
|
|
218
|
+
normalizeMessage(message) {
|
|
219
|
+
return {
|
|
220
|
+
allowedMentions: message.allowedMentions,
|
|
221
|
+
attachments: message.attachments ?? [],
|
|
222
|
+
avatarUrl: normalizeOptionalString(message.avatarUrl),
|
|
223
|
+
components: message.components ?? [],
|
|
224
|
+
content: normalizeOptionalString(message.content),
|
|
225
|
+
embeds: message.embeds ?? [],
|
|
226
|
+
flags: message.flags,
|
|
227
|
+
metadata: message.metadata,
|
|
228
|
+
poll: message.poll,
|
|
229
|
+
threadId: normalizeOptionalString(message.threadId) ?? this.options.defaultThreadId,
|
|
230
|
+
threadName: normalizeOptionalString(message.threadName),
|
|
231
|
+
tts: message.tts,
|
|
232
|
+
username: normalizeOptionalString(message.username)
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
resolveNotificationThreadId(notification) {
|
|
236
|
+
const payloadThreadId = normalizeOptionalString(notification.payload.threadId);
|
|
237
|
+
if (payloadThreadId) {
|
|
238
|
+
return payloadThreadId;
|
|
239
|
+
}
|
|
240
|
+
const recipients = notification.recipients?.map(entry => entry.trim()).filter(entry => entry.length > 0) ?? [];
|
|
241
|
+
if (recipients.length > 1) {
|
|
242
|
+
throw new DiscordMessageValidationError('Discord notifications accept exactly one target thread per dispatch. Use `sendMany(...)` for fan-out delivery.');
|
|
243
|
+
}
|
|
244
|
+
return recipients[0] ?? this.options.defaultThreadId;
|
|
245
|
+
}
|
|
246
|
+
async renderNotification(notification) {
|
|
247
|
+
if (!notification.template || !this.options.renderer) {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
return this.options.renderer.render({
|
|
251
|
+
locale: notification.locale,
|
|
252
|
+
metadata: notification.metadata,
|
|
253
|
+
payload: notification.payload,
|
|
254
|
+
subject: notification.subject,
|
|
255
|
+
template: notification.template
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
static {
|
|
259
|
+
_initClass();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
export { _DiscordService as DiscordService };
|
package/dist/status.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PlatformHealthReport, PlatformReadinessReport, PlatformSnapshot } from '@fluojs/runtime';
|
|
2
|
+
/** Resolved Discord lifecycle state used for diagnostics and health checks. */
|
|
3
|
+
export type DiscordLifecycleState = 'created' | 'starting' | 'ready' | 'stopping' | 'stopped' | 'failed';
|
|
4
|
+
/** Input required to describe the package health/readiness contract. */
|
|
5
|
+
export interface DiscordStatusAdapterInput {
|
|
6
|
+
channelName: string;
|
|
7
|
+
defaultThreadConfigured: boolean;
|
|
8
|
+
lifecycleState: DiscordLifecycleState;
|
|
9
|
+
ownsTransportResources: boolean;
|
|
10
|
+
transportKind: string;
|
|
11
|
+
verifiedOnModuleInit: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** Structured snapshot returned by {@link createDiscordPlatformStatusSnapshot}. */
|
|
14
|
+
export interface DiscordPlatformStatusSnapshot {
|
|
15
|
+
details: Record<string, unknown>;
|
|
16
|
+
health: PlatformHealthReport;
|
|
17
|
+
ownership: PlatformSnapshot['ownership'];
|
|
18
|
+
readiness: PlatformReadinessReport;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Creates a health/readiness snapshot for the Discord delivery layer.
|
|
22
|
+
*
|
|
23
|
+
* @param input Lifecycle and ownership details derived from the active Discord module wiring.
|
|
24
|
+
* @returns A structured snapshot suitable for status endpoints and operational diagnostics.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createDiscordPlatformStatusSnapshot(input: DiscordStatusAdapterInput): DiscordPlatformStatusSnapshot;
|
|
27
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,+EAA+E;AAC/E,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEzG,wEAAwE;AACxE,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB,EAAE,OAAO,CAAC;IACjC,cAAc,EAAE,qBAAqB,CAAC;IACtC,sBAAsB,EAAE,OAAO,CAAC;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAED,mFAAmF;AACnF,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,SAAS,EAAE,uBAAuB,CAAC;CACpC;AA6DD;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,KAAK,EAAE,yBAAyB,GAAG,6BAA6B,CAiBnH"}
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Resolved Discord lifecycle state used for diagnostics and health checks. */
|
|
2
|
+
|
|
3
|
+
/** Input required to describe the package health/readiness contract. */
|
|
4
|
+
|
|
5
|
+
/** Structured snapshot returned by {@link createDiscordPlatformStatusSnapshot}. */
|
|
6
|
+
|
|
7
|
+
function createReadiness(input) {
|
|
8
|
+
if (input.lifecycleState === 'ready') {
|
|
9
|
+
return {
|
|
10
|
+
critical: true,
|
|
11
|
+
status: 'ready'
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (input.lifecycleState === 'starting') {
|
|
15
|
+
return {
|
|
16
|
+
critical: true,
|
|
17
|
+
reason: 'Discord transport is still starting.',
|
|
18
|
+
status: 'degraded'
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (input.lifecycleState === 'stopping' || input.lifecycleState === 'stopped') {
|
|
22
|
+
return {
|
|
23
|
+
critical: true,
|
|
24
|
+
reason: 'Discord transport is shutting down or already stopped.',
|
|
25
|
+
status: 'not-ready'
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (input.lifecycleState === 'failed') {
|
|
29
|
+
return {
|
|
30
|
+
critical: true,
|
|
31
|
+
reason: 'Discord transport failed to initialize.',
|
|
32
|
+
status: 'not-ready'
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
critical: true,
|
|
37
|
+
reason: 'Discord transport has not started yet.',
|
|
38
|
+
status: 'not-ready'
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function createHealth(input) {
|
|
42
|
+
if (input.lifecycleState === 'failed' || input.lifecycleState === 'stopped') {
|
|
43
|
+
return {
|
|
44
|
+
reason: 'Discord transport is unavailable.',
|
|
45
|
+
status: 'unhealthy'
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (input.lifecycleState === 'created' || input.lifecycleState === 'starting' || input.lifecycleState === 'stopping') {
|
|
49
|
+
return {
|
|
50
|
+
reason: 'Discord transport is transitioning lifecycle state.',
|
|
51
|
+
status: 'degraded'
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
status: 'healthy'
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates a health/readiness snapshot for the Discord delivery layer.
|
|
61
|
+
*
|
|
62
|
+
* @param input Lifecycle and ownership details derived from the active Discord module wiring.
|
|
63
|
+
* @returns A structured snapshot suitable for status endpoints and operational diagnostics.
|
|
64
|
+
*/
|
|
65
|
+
export function createDiscordPlatformStatusSnapshot(input) {
|
|
66
|
+
return {
|
|
67
|
+
details: {
|
|
68
|
+
channelName: input.channelName,
|
|
69
|
+
defaultThreadConfigured: input.defaultThreadConfigured,
|
|
70
|
+
dependencies: ['notifications.channel', 'discord.transport'],
|
|
71
|
+
lifecycleState: input.lifecycleState,
|
|
72
|
+
transportKind: input.transportKind,
|
|
73
|
+
verifiedOnModuleInit: input.verifiedOnModuleInit
|
|
74
|
+
},
|
|
75
|
+
health: createHealth(input),
|
|
76
|
+
ownership: {
|
|
77
|
+
externallyManaged: !input.ownsTransportResources,
|
|
78
|
+
ownsResources: input.ownsTransportResources
|
|
79
|
+
},
|
|
80
|
+
readiness: createReadiness(input)
|
|
81
|
+
};
|
|
82
|
+
}
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Token } from '@fluojs/core';
|
|
2
|
+
import type { NotificationChannel } from '@fluojs/notifications';
|
|
3
|
+
import type { Discord, DiscordNotificationDispatchRequest, NormalizedDiscordModuleOptions } from './types.js';
|
|
4
|
+
/** Compatibility token for the facade returned by {@link DiscordModule.forRoot}. */
|
|
5
|
+
export declare const DISCORD: Token<Discord>;
|
|
6
|
+
/** Injection token for the channel implementation consumed by `@fluojs/notifications`. */
|
|
7
|
+
export declare const DISCORD_CHANNEL: Token<NotificationChannel<DiscordNotificationDispatchRequest>>;
|
|
8
|
+
/** Injection token for normalized Discord module options consumed internally by providers. */
|
|
9
|
+
export declare const DISCORD_OPTIONS: Token<NormalizedDiscordModuleOptions>;
|
|
10
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,OAAO,KAAK,EAAE,OAAO,EAAE,kCAAkC,EAAE,8BAA8B,EAAE,MAAM,YAAY,CAAC;AAE9G,oFAAoF;AACpF,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,OAAO,CAA8B,CAAC;AAClE,0FAA0F;AAC1F,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,mBAAmB,CAAC,kCAAkC,CAAC,CAE1F,CAAC;AACF,8FAA8F;AAC9F,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,8BAA8B,CAAsC,CAAC"}
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Compatibility token for the facade returned by {@link DiscordModule.forRoot}. */
|
|
2
|
+
export const DISCORD = Symbol.for('fluo.discord');
|
|
3
|
+
/** Injection token for the channel implementation consumed by `@fluojs/notifications`. */
|
|
4
|
+
export const DISCORD_CHANNEL = Symbol.for('fluo.discord.channel');
|
|
5
|
+
/** Injection token for normalized Discord module options consumed internally by providers. */
|
|
6
|
+
export const DISCORD_OPTIONS = Symbol.for('fluo.discord.options');
|