@guren/server 0.2.0-alpha.7 → 1.0.0-rc.9
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/dist/Application-DtWDHXr1.d.ts +2110 -0
- package/dist/BroadcastManager-AkIWUGJo.d.ts +466 -0
- package/dist/CacheManager-BkvHEOZX.d.ts +244 -0
- package/dist/ConsoleKernel-CqCVrdZs.d.ts +207 -0
- package/dist/EventManager-CmIoLt7r.d.ts +207 -0
- package/dist/Gate-CNkBYf8m.d.ts +268 -0
- package/dist/HealthManager-DUyMIzsZ.d.ts +141 -0
- package/dist/I18nManager-Dtgzsf5n.d.ts +270 -0
- package/dist/LogManager-7mxnkaPM.d.ts +256 -0
- package/dist/MailManager-DpMvYiP9.d.ts +292 -0
- package/dist/Scheduler-BstvSca7.d.ts +469 -0
- package/dist/StorageManager-oZTHqaza.d.ts +337 -0
- package/dist/api-token-JOif2CtG.d.ts +1792 -0
- package/dist/app-key-CsBfRC_Q.d.ts +214 -0
- package/dist/auth/index.d.ts +418 -0
- package/dist/auth/index.js +6742 -0
- package/dist/authorization/index.d.ts +129 -0
- package/dist/authorization/index.js +621 -0
- package/dist/broadcasting/index.d.ts +233 -0
- package/dist/broadcasting/index.js +907 -0
- package/dist/cache/index.d.ts +233 -0
- package/dist/cache/index.js +817 -0
- package/dist/encryption/index.d.ts +222 -0
- package/dist/encryption/index.js +602 -0
- package/dist/events/index.d.ts +155 -0
- package/dist/events/index.js +330 -0
- package/dist/health/index.d.ts +185 -0
- package/dist/health/index.js +379 -0
- package/dist/i18n/index.d.ts +101 -0
- package/dist/i18n/index.js +597 -0
- package/dist/index-9_Jzj5jo.d.ts +7 -0
- package/dist/index.d.ts +2628 -619
- package/dist/index.js +22229 -3116
- package/dist/lambda/index.d.ts +156 -0
- package/dist/lambda/index.js +91 -0
- package/dist/logging/index.d.ts +50 -0
- package/dist/logging/index.js +557 -0
- package/dist/mail/index.d.ts +288 -0
- package/dist/mail/index.js +695 -0
- package/dist/mcp/index.d.ts +139 -0
- package/dist/mcp/index.js +382 -0
- package/dist/notifications/index.d.ts +271 -0
- package/dist/notifications/index.js +741 -0
- package/dist/queue/index.d.ts +423 -0
- package/dist/queue/index.js +958 -0
- package/dist/runtime/index.d.ts +93 -0
- package/dist/runtime/index.js +834 -0
- package/dist/scheduling/index.d.ts +41 -0
- package/dist/scheduling/index.js +836 -0
- package/dist/storage/index.d.ts +196 -0
- package/dist/storage/index.js +832 -0
- package/dist/vite/index.js +203 -3
- package/package.json +93 -6
- package/dist/chunk-FK2XQSBF.js +0 -160
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
// src/notifications/Notification.ts
|
|
2
|
+
var Notification = class {
|
|
3
|
+
/**
|
|
4
|
+
* Whether this notification should be queued.
|
|
5
|
+
*/
|
|
6
|
+
static shouldQueue = false;
|
|
7
|
+
/**
|
|
8
|
+
* The queue name for this notification.
|
|
9
|
+
*/
|
|
10
|
+
static queue;
|
|
11
|
+
/**
|
|
12
|
+
* Delay in milliseconds before sending.
|
|
13
|
+
*/
|
|
14
|
+
static delay;
|
|
15
|
+
/**
|
|
16
|
+
* Unique notification ID.
|
|
17
|
+
*/
|
|
18
|
+
id;
|
|
19
|
+
/**
|
|
20
|
+
* When the notification was created.
|
|
21
|
+
*/
|
|
22
|
+
createdAt;
|
|
23
|
+
constructor() {
|
|
24
|
+
this.id = this.generateId();
|
|
25
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get the notification type (default: class name).
|
|
29
|
+
*/
|
|
30
|
+
get type() {
|
|
31
|
+
return this.constructor.name;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if the notification should be sent to the given notifiable.
|
|
35
|
+
* Override to add custom logic.
|
|
36
|
+
*/
|
|
37
|
+
shouldSend(_notifiable) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generate a unique notification ID.
|
|
42
|
+
*/
|
|
43
|
+
generateId() {
|
|
44
|
+
const timestamp = Date.now().toString(36);
|
|
45
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
46
|
+
return `notif_${timestamp}${random}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get static queue configuration.
|
|
50
|
+
*/
|
|
51
|
+
static getQueueConfig() {
|
|
52
|
+
return {
|
|
53
|
+
shouldQueue: this.shouldQueue,
|
|
54
|
+
queue: this.queue,
|
|
55
|
+
delay: this.delay
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/queue/Job.ts
|
|
61
|
+
import { randomBytes } from "crypto";
|
|
62
|
+
|
|
63
|
+
// src/container/Container.ts
|
|
64
|
+
var globalContainer = null;
|
|
65
|
+
function getContainer() {
|
|
66
|
+
if (!globalContainer) {
|
|
67
|
+
throw new Error("Container not initialized. Call setContainer() first.");
|
|
68
|
+
}
|
|
69
|
+
return globalContainer;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/queue/Job.ts
|
|
73
|
+
var globalDriver = null;
|
|
74
|
+
function generateJobId() {
|
|
75
|
+
return randomBytes(16).toString("hex");
|
|
76
|
+
}
|
|
77
|
+
var Job = class {
|
|
78
|
+
/**
|
|
79
|
+
* The queue this job should be dispatched to.
|
|
80
|
+
* @default 'default'
|
|
81
|
+
*/
|
|
82
|
+
static queue = "default";
|
|
83
|
+
/**
|
|
84
|
+
* Maximum number of times the job should be attempted.
|
|
85
|
+
* @default 3
|
|
86
|
+
*/
|
|
87
|
+
static maxAttempts = 3;
|
|
88
|
+
/**
|
|
89
|
+
* Backoff strategy for retries.
|
|
90
|
+
* - 'exponential': 2^attempt * 1000ms (1s, 2s, 4s, 8s, ...)
|
|
91
|
+
* - 'linear': attempt * 1000ms (1s, 2s, 3s, 4s, ...)
|
|
92
|
+
* - number: fixed delay in milliseconds
|
|
93
|
+
* @default 'exponential'
|
|
94
|
+
*/
|
|
95
|
+
static backoff = "exponential";
|
|
96
|
+
make(key) {
|
|
97
|
+
return getContainer().make(key);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Dispatch the job to the queue.
|
|
101
|
+
*
|
|
102
|
+
* @param payload - Job payload data
|
|
103
|
+
* @param options - Optional dispatch options
|
|
104
|
+
*/
|
|
105
|
+
static async dispatch(payload, options = {}) {
|
|
106
|
+
const driver = globalDriver;
|
|
107
|
+
if (!driver) {
|
|
108
|
+
throw new Error("Queue driver not configured. Call setQueueDriver() first.");
|
|
109
|
+
}
|
|
110
|
+
const jobId = generateJobId();
|
|
111
|
+
const now = /* @__PURE__ */ new Date();
|
|
112
|
+
const delay = options.delay ?? 0;
|
|
113
|
+
const job = {
|
|
114
|
+
id: jobId,
|
|
115
|
+
name: this.name,
|
|
116
|
+
payload,
|
|
117
|
+
queue: options.queue ?? this.queue,
|
|
118
|
+
attempts: 0,
|
|
119
|
+
maxAttempts: options.maxAttempts ?? this.maxAttempts,
|
|
120
|
+
availableAt: new Date(now.getTime() + delay),
|
|
121
|
+
createdAt: now,
|
|
122
|
+
reservedAt: null
|
|
123
|
+
};
|
|
124
|
+
await driver.push(job);
|
|
125
|
+
return jobId;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Dispatch the job after a delay.
|
|
129
|
+
*
|
|
130
|
+
* @param delayMs - Delay in milliseconds
|
|
131
|
+
* @param payload - Job payload data
|
|
132
|
+
* @param options - Optional dispatch options (delay is overridden)
|
|
133
|
+
*/
|
|
134
|
+
static async dispatchAfter(delayMs, payload, options = {}) {
|
|
135
|
+
return this.dispatch(payload, { ...options, delay: delayMs });
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Calculate the retry delay based on backoff strategy.
|
|
139
|
+
*/
|
|
140
|
+
static calculateRetryDelay(attempts) {
|
|
141
|
+
if (typeof this.backoff === "number") {
|
|
142
|
+
return this.backoff;
|
|
143
|
+
}
|
|
144
|
+
if (this.backoff === "linear") {
|
|
145
|
+
return attempts * 1e3;
|
|
146
|
+
}
|
|
147
|
+
return Math.pow(2, attempts) * 1e3;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
var jobRegistry = /* @__PURE__ */ new Map();
|
|
151
|
+
function registerJob(jobClass) {
|
|
152
|
+
jobRegistry.set(jobClass.name, jobClass);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/notifications/NotificationManager.ts
|
|
156
|
+
var NotificationManager = class {
|
|
157
|
+
channels = /* @__PURE__ */ new Map();
|
|
158
|
+
channelFactories = /* @__PURE__ */ new Map();
|
|
159
|
+
resolvedChannels = /* @__PURE__ */ new Map();
|
|
160
|
+
constructor(options = {}) {
|
|
161
|
+
if (options.channels) {
|
|
162
|
+
for (const [name, channel] of Object.entries(options.channels)) {
|
|
163
|
+
this.registerChannel(name, channel);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (options.channelFactories) {
|
|
167
|
+
for (const [name, factory] of Object.entries(options.channelFactories)) {
|
|
168
|
+
this.registerChannelFactory(name, factory);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Register a notification channel.
|
|
174
|
+
*/
|
|
175
|
+
registerChannel(name, channel) {
|
|
176
|
+
this.channels.set(name, channel);
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Register a notification channel factory.
|
|
181
|
+
*/
|
|
182
|
+
registerChannelFactory(name, factory) {
|
|
183
|
+
this.channelFactories.set(name, factory);
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get a notification channel by name.
|
|
188
|
+
*/
|
|
189
|
+
channel(name) {
|
|
190
|
+
const resolved = this.resolvedChannels.get(name);
|
|
191
|
+
if (resolved) {
|
|
192
|
+
return resolved;
|
|
193
|
+
}
|
|
194
|
+
const channel = this.channels.get(name);
|
|
195
|
+
if (channel) {
|
|
196
|
+
return channel;
|
|
197
|
+
}
|
|
198
|
+
const factory = this.channelFactories.get(name);
|
|
199
|
+
if (factory) {
|
|
200
|
+
const created = factory({});
|
|
201
|
+
this.resolvedChannels.set(name, created);
|
|
202
|
+
return created;
|
|
203
|
+
}
|
|
204
|
+
throw new Error(`Notification channel "${name}" not found`);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Check if a channel is registered.
|
|
208
|
+
*/
|
|
209
|
+
hasChannel(name) {
|
|
210
|
+
return this.channels.has(name) || this.channelFactories.has(name) || this.resolvedChannels.has(name);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get all registered channel names.
|
|
214
|
+
*/
|
|
215
|
+
getChannelNames() {
|
|
216
|
+
const names = /* @__PURE__ */ new Set([
|
|
217
|
+
...this.channels.keys(),
|
|
218
|
+
...this.channelFactories.keys(),
|
|
219
|
+
...this.resolvedChannels.keys()
|
|
220
|
+
]);
|
|
221
|
+
return Array.from(names);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Send a notification to a notifiable entity.
|
|
225
|
+
* Respects queue configuration on the notification class.
|
|
226
|
+
*/
|
|
227
|
+
async send(notifiable, notification) {
|
|
228
|
+
const NotificationClass = notification.constructor;
|
|
229
|
+
const queueConfig = NotificationClass.getQueueConfig?.() ?? {
|
|
230
|
+
shouldQueue: false
|
|
231
|
+
};
|
|
232
|
+
if (queueConfig.shouldQueue) {
|
|
233
|
+
await this.queue(notifiable, notification, queueConfig);
|
|
234
|
+
} else {
|
|
235
|
+
await this.sendNow(notifiable, notification);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Send a notification immediately (bypasses queue).
|
|
240
|
+
*/
|
|
241
|
+
async sendNow(notifiable, notification) {
|
|
242
|
+
const shouldSend = await notification.shouldSend(notifiable);
|
|
243
|
+
if (!shouldSend) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const viaChannels = notification.via(notifiable);
|
|
247
|
+
await Promise.all(
|
|
248
|
+
viaChannels.map(async (channelName) => {
|
|
249
|
+
try {
|
|
250
|
+
const channel = this.channel(channelName);
|
|
251
|
+
await channel.send(notifiable, notification);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error(
|
|
254
|
+
`Failed to send notification via ${channelName}:`,
|
|
255
|
+
error
|
|
256
|
+
);
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Queue a notification for later delivery.
|
|
264
|
+
*/
|
|
265
|
+
async queue(notifiable, notification, queueConfig) {
|
|
266
|
+
const job = this.createNotificationJob();
|
|
267
|
+
registerJob(job);
|
|
268
|
+
const payload = {
|
|
269
|
+
notifiableData: this.serializeNotifiable(notifiable),
|
|
270
|
+
notificationData: this.serializeNotification(notification),
|
|
271
|
+
notificationType: notification.type
|
|
272
|
+
};
|
|
273
|
+
if (queueConfig.delay) {
|
|
274
|
+
await job.dispatchAfter(queueConfig.delay, payload, {
|
|
275
|
+
queue: queueConfig.queue
|
|
276
|
+
});
|
|
277
|
+
} else {
|
|
278
|
+
await job.dispatch(payload, { queue: queueConfig.queue });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Send notification to multiple notifiables.
|
|
283
|
+
*/
|
|
284
|
+
async sendToMany(notifiables, notification) {
|
|
285
|
+
await Promise.all(
|
|
286
|
+
notifiables.map((notifiable) => this.send(notifiable, notification))
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Send notification immediately to multiple notifiables.
|
|
291
|
+
*/
|
|
292
|
+
async sendNowToMany(notifiables, notification) {
|
|
293
|
+
await Promise.all(
|
|
294
|
+
notifiables.map((notifiable) => this.sendNow(notifiable, notification))
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Serialize notifiable for queue storage.
|
|
299
|
+
*/
|
|
300
|
+
serializeNotifiable(notifiable) {
|
|
301
|
+
return {
|
|
302
|
+
type: notifiable.constructor?.name ?? "Unknown",
|
|
303
|
+
data: { ...notifiable }
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Serialize notification for queue storage.
|
|
308
|
+
*/
|
|
309
|
+
serializeNotification(notification) {
|
|
310
|
+
return {
|
|
311
|
+
type: notification.type,
|
|
312
|
+
id: notification.id,
|
|
313
|
+
data: { ...notification }
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Create the notification job class.
|
|
318
|
+
*/
|
|
319
|
+
createNotificationJob() {
|
|
320
|
+
const manager = this;
|
|
321
|
+
class BoundSendNotificationJob extends SendNotificationJob {
|
|
322
|
+
static notificationManager = manager;
|
|
323
|
+
}
|
|
324
|
+
return BoundSendNotificationJob;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
var SendNotificationJob = class extends Job {
|
|
328
|
+
static jobName = "SendNotificationJob";
|
|
329
|
+
static queue = "notifications";
|
|
330
|
+
static maxAttempts = 3;
|
|
331
|
+
// Will be set by NotificationManager
|
|
332
|
+
static notificationManager = null;
|
|
333
|
+
async handle(payload) {
|
|
334
|
+
const manager = this.constructor.notificationManager;
|
|
335
|
+
if (!manager) {
|
|
336
|
+
throw new Error("NotificationManager not set on SendNotificationJob");
|
|
337
|
+
}
|
|
338
|
+
const notifiable = {
|
|
339
|
+
...payload.notifiableData.data,
|
|
340
|
+
routeNotificationFor(channel) {
|
|
341
|
+
const key = `${channel}Route`;
|
|
342
|
+
const value = payload.notifiableData.data[key];
|
|
343
|
+
if (typeof value === "string") return value;
|
|
344
|
+
if (channel === "mail") {
|
|
345
|
+
const email = payload.notifiableData.data["email"];
|
|
346
|
+
return typeof email === "string" ? email : null;
|
|
347
|
+
}
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const notification = {
|
|
352
|
+
...payload.notificationData.data,
|
|
353
|
+
id: payload.notificationData.id,
|
|
354
|
+
type: payload.notificationType,
|
|
355
|
+
via: () => {
|
|
356
|
+
const viaChannels = payload.notificationData.data._viaChannels;
|
|
357
|
+
return viaChannels ?? [];
|
|
358
|
+
},
|
|
359
|
+
shouldSend: () => true,
|
|
360
|
+
toMail: payload.notificationData.data.toMail,
|
|
361
|
+
toDatabase: payload.notificationData.data.toDatabase,
|
|
362
|
+
toSlack: payload.notificationData.data.toSlack
|
|
363
|
+
};
|
|
364
|
+
await manager.sendNow(notifiable, notification);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
var globalNotificationManager = null;
|
|
368
|
+
function setNotificationManager(manager) {
|
|
369
|
+
globalNotificationManager = manager;
|
|
370
|
+
}
|
|
371
|
+
function getNotificationManager() {
|
|
372
|
+
if (!globalNotificationManager) {
|
|
373
|
+
throw new Error("NotificationManager not initialized. Call setNotificationManager() first.");
|
|
374
|
+
}
|
|
375
|
+
return globalNotificationManager;
|
|
376
|
+
}
|
|
377
|
+
function createNotificationManager(options) {
|
|
378
|
+
return new NotificationManager(options);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/notifications/channels/MailChannel.ts
|
|
382
|
+
function parseAddress(input) {
|
|
383
|
+
if (!input) {
|
|
384
|
+
return void 0;
|
|
385
|
+
}
|
|
386
|
+
if (typeof input !== "string") {
|
|
387
|
+
return input;
|
|
388
|
+
}
|
|
389
|
+
const match = input.match(/^(.+)\s*<(.+)>$/);
|
|
390
|
+
if (match) {
|
|
391
|
+
return { name: match[1].trim(), email: match[2].trim() };
|
|
392
|
+
}
|
|
393
|
+
return { email: input };
|
|
394
|
+
}
|
|
395
|
+
function parseAddressList(input) {
|
|
396
|
+
if (!input) {
|
|
397
|
+
return void 0;
|
|
398
|
+
}
|
|
399
|
+
const items = Array.isArray(input) ? input : [input];
|
|
400
|
+
const parsed = items.map((item) => parseAddress(item)).filter((item) => Boolean(item));
|
|
401
|
+
return parsed.length > 0 ? parsed : void 0;
|
|
402
|
+
}
|
|
403
|
+
var MailChannel = class {
|
|
404
|
+
constructor(mailManager, options = {}) {
|
|
405
|
+
this.mailManager = mailManager;
|
|
406
|
+
this.options = options;
|
|
407
|
+
}
|
|
408
|
+
name = "mail";
|
|
409
|
+
/**
|
|
410
|
+
* Send the notification via mail.
|
|
411
|
+
*/
|
|
412
|
+
async send(notifiable, notification) {
|
|
413
|
+
const message = notification.toMail?.(notifiable);
|
|
414
|
+
if (!message) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const to = notifiable.routeNotificationFor("mail");
|
|
418
|
+
if (!to) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const transport = this.mailManager.transport(this.options.transport);
|
|
422
|
+
const toAddresses = parseAddressList(to);
|
|
423
|
+
if (!toAddresses || toAddresses.length === 0) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
await transport.send({
|
|
427
|
+
to: toAddresses,
|
|
428
|
+
from: parseAddress(message.from ?? this.options.from),
|
|
429
|
+
replyTo: parseAddress(message.replyTo),
|
|
430
|
+
cc: parseAddressList(message.cc),
|
|
431
|
+
bcc: parseAddressList(message.bcc),
|
|
432
|
+
subject: message.subject,
|
|
433
|
+
html: message.html,
|
|
434
|
+
text: message.text,
|
|
435
|
+
attachments: message.attachments?.map((a) => ({
|
|
436
|
+
filename: a.filename,
|
|
437
|
+
content: a.content,
|
|
438
|
+
path: a.path,
|
|
439
|
+
contentType: a.contentType
|
|
440
|
+
}))
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// src/notifications/channels/DatabaseChannel.ts
|
|
446
|
+
var DatabaseChannel = class {
|
|
447
|
+
constructor(options = {}) {
|
|
448
|
+
this.options = options;
|
|
449
|
+
}
|
|
450
|
+
name = "database";
|
|
451
|
+
stored = [];
|
|
452
|
+
/**
|
|
453
|
+
* Send the notification to the database.
|
|
454
|
+
*/
|
|
455
|
+
async send(notifiable, notification) {
|
|
456
|
+
const data = notification.toDatabase?.(notifiable);
|
|
457
|
+
if (!data) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const record = {
|
|
461
|
+
id: notification.id,
|
|
462
|
+
type: notification.type,
|
|
463
|
+
notifiableId: this.getNotifiableId(notifiable),
|
|
464
|
+
notifiableType: this.getNotifiableType(notifiable),
|
|
465
|
+
data,
|
|
466
|
+
readAt: null,
|
|
467
|
+
createdAt: notification.createdAt
|
|
468
|
+
};
|
|
469
|
+
if (this.options.store) {
|
|
470
|
+
await this.options.store(notifiable, record);
|
|
471
|
+
} else {
|
|
472
|
+
this.stored.push(record);
|
|
473
|
+
if (notifiable.notifications) {
|
|
474
|
+
notifiable.notifications.push(record);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Get the notifiable ID.
|
|
480
|
+
*/
|
|
481
|
+
getNotifiableId(notifiable) {
|
|
482
|
+
const withId = notifiable;
|
|
483
|
+
if (withId.id !== void 0) {
|
|
484
|
+
return withId.id;
|
|
485
|
+
}
|
|
486
|
+
return String(notifiable);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Get the notifiable type.
|
|
490
|
+
*/
|
|
491
|
+
getNotifiableType(notifiable) {
|
|
492
|
+
return notifiable.constructor?.name ?? "Unknown";
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Get all stored notifications (for testing).
|
|
496
|
+
*/
|
|
497
|
+
getStored() {
|
|
498
|
+
return [...this.stored];
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Get stored notifications for a notifiable.
|
|
502
|
+
*/
|
|
503
|
+
getStoredFor(notifiable) {
|
|
504
|
+
const id = this.getNotifiableId(notifiable);
|
|
505
|
+
const type = this.getNotifiableType(notifiable);
|
|
506
|
+
return this.stored.filter(
|
|
507
|
+
(n) => n.notifiableId === id && n.notifiableType === type
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Clear stored notifications (for testing).
|
|
512
|
+
*/
|
|
513
|
+
clear() {
|
|
514
|
+
this.stored = [];
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// src/notifications/channels/SlackChannel.ts
|
|
519
|
+
var SlackChannel = class {
|
|
520
|
+
constructor(webhookUrl, options = {}) {
|
|
521
|
+
this.webhookUrl = webhookUrl;
|
|
522
|
+
this.options = options;
|
|
523
|
+
}
|
|
524
|
+
name = "slack";
|
|
525
|
+
/**
|
|
526
|
+
* Send the notification to Slack.
|
|
527
|
+
*/
|
|
528
|
+
async send(notifiable, notification) {
|
|
529
|
+
const message = notification.toSlack?.(notifiable);
|
|
530
|
+
if (!message) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const webhookUrl = notifiable.routeNotificationFor("slack") ?? this.webhookUrl;
|
|
534
|
+
if (!webhookUrl) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const payload = this.buildPayload(message);
|
|
538
|
+
const response = await fetch(webhookUrl, {
|
|
539
|
+
method: "POST",
|
|
540
|
+
headers: {
|
|
541
|
+
"Content-Type": "application/json"
|
|
542
|
+
},
|
|
543
|
+
body: JSON.stringify(payload)
|
|
544
|
+
});
|
|
545
|
+
if (!response.ok) {
|
|
546
|
+
const text = await response.text();
|
|
547
|
+
throw new Error(`Slack webhook failed: ${response.status} ${text}`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Build the Slack webhook payload.
|
|
552
|
+
*/
|
|
553
|
+
buildPayload(message) {
|
|
554
|
+
const payload = {};
|
|
555
|
+
if (message.text) {
|
|
556
|
+
payload.text = message.text;
|
|
557
|
+
}
|
|
558
|
+
if (message.blocks && message.blocks.length > 0) {
|
|
559
|
+
payload.blocks = message.blocks;
|
|
560
|
+
}
|
|
561
|
+
if (message.attachments && message.attachments.length > 0) {
|
|
562
|
+
payload.attachments = message.attachments;
|
|
563
|
+
}
|
|
564
|
+
if (message.channel) {
|
|
565
|
+
payload.channel = message.channel;
|
|
566
|
+
}
|
|
567
|
+
if (message.username ?? this.options.username) {
|
|
568
|
+
payload.username = message.username ?? this.options.username;
|
|
569
|
+
}
|
|
570
|
+
if (message.icon_emoji ?? this.options.iconEmoji) {
|
|
571
|
+
payload.icon_emoji = message.icon_emoji ?? this.options.iconEmoji;
|
|
572
|
+
}
|
|
573
|
+
if (message.icon_url ?? this.options.iconUrl) {
|
|
574
|
+
payload.icon_url = message.icon_url ?? this.options.iconUrl;
|
|
575
|
+
}
|
|
576
|
+
return payload;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// src/notifications/channels/MemoryChannel.ts
|
|
581
|
+
var MemoryChannel = class {
|
|
582
|
+
name = "memory";
|
|
583
|
+
/**
|
|
584
|
+
* All sent notifications.
|
|
585
|
+
*/
|
|
586
|
+
sent = [];
|
|
587
|
+
/**
|
|
588
|
+
* Send (store) the notification.
|
|
589
|
+
*/
|
|
590
|
+
async send(notifiable, notification) {
|
|
591
|
+
this.sent.push({
|
|
592
|
+
notifiable,
|
|
593
|
+
notification,
|
|
594
|
+
channels: notification.via(notifiable),
|
|
595
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Assert that a notification was sent to a notifiable.
|
|
600
|
+
*/
|
|
601
|
+
assertSentTo(notifiable, notificationType) {
|
|
602
|
+
const found = this.sent.some((record) => {
|
|
603
|
+
const matchesNotifiable = record.notifiable === notifiable;
|
|
604
|
+
const matchesType = notificationType ? record.notification.type === notificationType : true;
|
|
605
|
+
return matchesNotifiable && matchesType;
|
|
606
|
+
});
|
|
607
|
+
if (!found) {
|
|
608
|
+
const typeMsg = notificationType ? ` of type "${notificationType}"` : "";
|
|
609
|
+
throw new Error(`Expected notification${typeMsg} to be sent to notifiable`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Assert that a notification was not sent to a notifiable.
|
|
614
|
+
*/
|
|
615
|
+
assertNotSentTo(notifiable, notificationType) {
|
|
616
|
+
const found = this.sent.some((record) => {
|
|
617
|
+
const matchesNotifiable = record.notifiable === notifiable;
|
|
618
|
+
const matchesType = notificationType ? record.notification.type === notificationType : true;
|
|
619
|
+
return matchesNotifiable && matchesType;
|
|
620
|
+
});
|
|
621
|
+
if (found) {
|
|
622
|
+
const typeMsg = notificationType ? ` of type "${notificationType}"` : "";
|
|
623
|
+
throw new Error(
|
|
624
|
+
`Expected notification${typeMsg} not to be sent to notifiable`
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Assert the total count of sent notifications.
|
|
630
|
+
*/
|
|
631
|
+
assertCount(count) {
|
|
632
|
+
if (this.sent.length !== count) {
|
|
633
|
+
throw new Error(
|
|
634
|
+
`Expected ${count} notifications to be sent, but ${this.sent.length} were sent`
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Assert that a notification type was sent.
|
|
640
|
+
*/
|
|
641
|
+
assertSent(notificationType) {
|
|
642
|
+
const found = this.sent.some(
|
|
643
|
+
(record) => record.notification.type === notificationType
|
|
644
|
+
);
|
|
645
|
+
if (!found) {
|
|
646
|
+
throw new Error(
|
|
647
|
+
`Expected notification of type "${notificationType}" to be sent`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Assert that a notification type was not sent.
|
|
653
|
+
*/
|
|
654
|
+
assertNotSent(notificationType) {
|
|
655
|
+
const found = this.sent.some(
|
|
656
|
+
(record) => record.notification.type === notificationType
|
|
657
|
+
);
|
|
658
|
+
if (found) {
|
|
659
|
+
throw new Error(
|
|
660
|
+
`Expected notification of type "${notificationType}" not to be sent`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Assert that no notifications were sent.
|
|
666
|
+
*/
|
|
667
|
+
assertNothingSent() {
|
|
668
|
+
if (this.sent.length > 0) {
|
|
669
|
+
throw new Error(
|
|
670
|
+
`Expected no notifications to be sent, but ${this.sent.length} were sent`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Get notifications sent to a specific notifiable.
|
|
676
|
+
*/
|
|
677
|
+
getSentTo(notifiable) {
|
|
678
|
+
return this.sent.filter((record) => record.notifiable === notifiable);
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Get notifications of a specific type.
|
|
682
|
+
*/
|
|
683
|
+
getSentOfType(notificationType) {
|
|
684
|
+
return this.sent.filter(
|
|
685
|
+
(record) => record.notification.type === notificationType
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Check if a notification was sent to a notifiable.
|
|
690
|
+
*/
|
|
691
|
+
hasSentTo(notifiable, notificationType) {
|
|
692
|
+
return this.sent.some((record) => {
|
|
693
|
+
const matchesNotifiable = record.notifiable === notifiable;
|
|
694
|
+
const matchesType = notificationType ? record.notification.type === notificationType : true;
|
|
695
|
+
return matchesNotifiable && matchesType;
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Check if a notification type was sent.
|
|
700
|
+
*/
|
|
701
|
+
hasSent(notificationType) {
|
|
702
|
+
return this.sent.some(
|
|
703
|
+
(record) => record.notification.type === notificationType
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Get the count of sent notifications.
|
|
708
|
+
*/
|
|
709
|
+
count() {
|
|
710
|
+
return this.sent.length;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Get the last sent notification.
|
|
714
|
+
*/
|
|
715
|
+
last() {
|
|
716
|
+
return this.sent[this.sent.length - 1];
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Get the first sent notification.
|
|
720
|
+
*/
|
|
721
|
+
first() {
|
|
722
|
+
return this.sent[0];
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Clear all sent notifications.
|
|
726
|
+
*/
|
|
727
|
+
clear() {
|
|
728
|
+
this.sent = [];
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
export {
|
|
732
|
+
DatabaseChannel,
|
|
733
|
+
MailChannel,
|
|
734
|
+
MemoryChannel,
|
|
735
|
+
Notification,
|
|
736
|
+
NotificationManager,
|
|
737
|
+
SlackChannel,
|
|
738
|
+
createNotificationManager,
|
|
739
|
+
getNotificationManager,
|
|
740
|
+
setNotificationManager
|
|
741
|
+
};
|