@arkstack/notifications 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @arkstack/notifications
2
2
 
3
+ [![@arkstack/notifications](https://img.shields.io/npm/dt/@arkstack/notifications?style=flat-square&label=@arkstack/notifications&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F@arkstack/notifications)](https://www.npmjs.com/package/@arkstack/notifications)
4
+
3
5
  Framework-agnostic notification module for Arkstack and Nodejs, providing support for multi-channel notification delivery.
4
6
 
5
7
  ```ts
@@ -30,7 +32,7 @@ export default () => ({
30
32
  drivers: {
31
33
  mail: {
32
34
  transport: 'smtp',
33
- from: env('SMTP_FROM_ADDRESS', 'no-reply@example.com'),
35
+ from: env('MAIL_FROM_ADDRESS', 'no-reply@example.com'),
34
36
  },
35
37
  sms: {
36
38
  transport: 'africastalking',
@@ -42,8 +44,8 @@ export default () => ({
42
44
  },
43
45
  transports: {
44
46
  smtp: {
45
- host: env('SMTP_HOST', 'localhost'),
46
- port: env('SMTP_PORT', 1025),
47
+ host: env('MAIL_HOST', 'localhost'),
48
+ port: env('MAIL_PORT', 1025),
47
49
  },
48
50
  africastalking: {
49
51
  username: env('AFRICASTALKING_USERNAME', 'sandbox'),
package/dist/index.d.ts CHANGED
@@ -1,12 +1,14 @@
1
- import * as _$arkormx from "arkormx";
2
- import { Model } from "arkormx";
1
+ import { DotPath, DotPathValue, MergedConfig } from "@arkstack/common";
3
2
  import { Transporter } from "nodemailer";
3
+ import { Model } from "@arkstack/database";
4
+ import { User } from "@app/models/User";
5
+ import { User as User$1 } from "@arkstack/auth";
4
6
  import * as _$twilio_lib_rest_api_v2010_account_message0 from "twilio/lib/rest/api/v2010/account/message.js";
7
+ import * as _$arkormx from "arkormx";
5
8
 
6
9
  //#region src/Contracts/UserNotification.d.ts
7
10
  declare abstract class UserNotification extends Model {
8
- id: number | string;
9
- userId: number | string;
11
+ [key: string]: any;
10
12
  type: DbNotificationType | null;
11
13
  title: string;
12
14
  description: string;
@@ -29,13 +31,8 @@ type MailRecipient = string | MailRecipientAddress | Array<string | MailRecipien
29
31
  type NotificationData = Record<string, unknown>;
30
32
  type SmsDriverName = 'africastalking' | 'twilio';
31
33
  type NotificationChannel = 'mail' | 'sms' | 'db';
32
- type NotificationUser = {
33
- id: string | number;
34
- email?: string | null;
35
- phone?: string | null;
36
- };
37
34
  type MailDriverOptions = {
38
- transport?: string;
35
+ transport?: 'africastalking' | 'twilio' | 'file' | 'smtp';
39
36
  host?: string;
40
37
  port?: number;
41
38
  secure?: boolean;
@@ -43,6 +40,7 @@ type MailDriverOptions = {
43
40
  pass?: string;
44
41
  from?: string;
45
42
  testAddress?: string;
43
+ directory?: string;
46
44
  };
47
45
  type SmsDriverOptions = {
48
46
  transport?: SmsDriverName;
@@ -73,6 +71,64 @@ type NotificationDriverMap = {
73
71
  sms: DriverResult;
74
72
  db: UserNotification;
75
73
  };
74
+ interface NotificationConfig {
75
+ default_driver: 'mail' | 'sms' | 'db';
76
+ drivers: {
77
+ mail: {
78
+ transport: 'smtp' | 'file';
79
+ from: string;
80
+ test_address: string;
81
+ };
82
+ sms: {
83
+ transport: 'africastalking' | 'twillio';
84
+ from: string;
85
+ };
86
+ db: {
87
+ table: string;
88
+ };
89
+ };
90
+ transports: {
91
+ smtp: {
92
+ host: string;
93
+ port: number;
94
+ secure: boolean;
95
+ auth: {
96
+ user: string;
97
+ pass: string;
98
+ };
99
+ user?: string;
100
+ pass?: string;
101
+ test_address?: string;
102
+ } | {
103
+ host: string;
104
+ port: number;
105
+ secure: boolean;
106
+ auth?: {
107
+ user: string;
108
+ pass: string;
109
+ };
110
+ user: string;
111
+ pass: string;
112
+ test_address?: string;
113
+ };
114
+ file: {
115
+ directory: string;
116
+ from?: string;
117
+ test_address?: string;
118
+ };
119
+ africastalking: {
120
+ username: string;
121
+ apiKey: string;
122
+ senderId: string;
123
+ };
124
+ twilio: {
125
+ accountSid: string;
126
+ authToken: string;
127
+ from: string;
128
+ };
129
+ };
130
+ }
131
+ type MergedTransportConfig = MergedConfig<NotificationConfig['transports'][NonNullable<MailDriverOptions['transport']>]>;
76
132
  //#endregion
77
133
  //#region src/Contracts/NotificationContract.d.ts
78
134
  declare abstract class NotificationContract<TResult = DriverResult> {
@@ -93,12 +149,12 @@ declare class DbNotification extends NotificationContract<UserNotification> {
93
149
  private payload;
94
150
  from(_from: string): this;
95
151
  subject(subject: string): this;
96
- recipient(recipient: NotificationRecipient | NotificationUser): this;
152
+ recipient(recipient: NotificationRecipient | User): this;
97
153
  type(type: DbNotificationPayload['type']): this;
98
154
  action(text?: string | null, link?: string | null): this;
99
155
  meta(meta?: NotificationData | null): this;
100
- create(user: NotificationUser, payload: DbNotificationPayload): Promise<UserNotification & _$arkormx.Model<any, any>>;
101
- send(message: string, subject?: string, _recipient?: NotificationRecipient, data?: NotificationData): Promise<UserNotification & _$arkormx.Model<any, any>>;
156
+ create(user: User, payload: DbNotificationPayload): Promise<UserNotification>;
157
+ send(message: string, subject?: string, _recipient?: NotificationRecipient, data?: NotificationData): Promise<UserNotification>;
102
158
  }
103
159
  //#endregion
104
160
  //#region src/drivers/MailNotification.d.ts
@@ -107,12 +163,64 @@ declare class MailNotification extends NotificationContract {
107
163
  private fromAddress?;
108
164
  private subjectLine?;
109
165
  private recipients?;
110
- static defaultHtmlTemplate: string;
166
+ private ViewName;
167
+ private htmlTemplate?;
168
+ private textTemplate?;
169
+ private fileDirectory?;
111
170
  constructor(options?: MailDriverOptions);
112
171
  from(from: string): this;
172
+ /**
173
+ * The email subject
174
+ *
175
+ * @param subject
176
+ * @returns
177
+ */
113
178
  subject(subject: string): this;
179
+ /**
180
+ * Set email the notification recipeint
181
+ *
182
+ * @param recipient string or array of email addresses
183
+ * @returns
184
+ */
114
185
  recipient(recipient: MailRecipient): this;
186
+ /**
187
+ * Set email the notification view name
188
+ *
189
+ * @param view view name
190
+ * @returns
191
+ */
192
+ view(view: string): this;
193
+ /**
194
+ * Set email the notification html template
195
+ *
196
+ * @param content view name
197
+ * @returns
198
+ */
199
+ html(content: string): this;
200
+ /**
201
+ * Set email the notification text template
202
+ *
203
+ * @param content view name
204
+ * @returns
205
+ */
206
+ text(content: string): this;
207
+ /**
208
+ * Prepare a notification to be sent.
209
+ *
210
+ * @param user The recipient user(s) for the notification.
211
+ */
212
+ prepare(user?: null | string | string[] | User$1, data?: Record<string, any>): this;
213
+ /**
214
+ * Send a notification to the specified recipient(s) with the given message.
215
+ *
216
+ * @param message The message content to be sent to the recipient(s).
217
+ * @param subject The message subject to be sent to the recipient(s).
218
+ * @param recipient An array of recipient identifiers
219
+ * @param data Additioal context data
220
+ * @returns
221
+ */
115
222
  send(message: string, subject?: string, recipient?: MailRecipient, data?: NotificationData): Promise<any>;
223
+ private storeFileMail;
116
224
  private resolveRecipients;
117
225
  private normalizeRecipient;
118
226
  }
@@ -163,26 +271,25 @@ declare class Notification<D extends keyof DriverMap = keyof DriverMap> {
163
271
  static sms(options?: SmsDriverOptions): SmsNotification;
164
272
  static db(): DbNotification;
165
273
  static channel(channel?: NotificationChannel | 'email', options?: MailDriverOptions | SmsDriverOptions): MailNotification | SmsNotification | DbNotification;
166
- prepare(recipient?: null | MailRecipient | NotificationRecipient | NotificationUser, data?: NotificationData): DriverMap[D];
274
+ prepare(recipient?: null | MailRecipient | NotificationRecipient | User, data?: NotificationData): DriverMap[D];
167
275
  private static createDriver;
168
276
  }
169
277
  //#endregion
170
278
  //#region src/UserNotificationCenter.d.ts
171
279
  declare class UserNotificationCenter {
172
280
  private static getModel;
173
- static create(user: NotificationUser, payload: DbNotificationPayload): Promise<UserNotification & _$arkormx.Model<any, any>>;
174
- static forUser(user: NotificationUser): Promise<_$arkormx.ArkormCollection<UserNotification & _$arkormx.Model<any, any>, (UserNotification & _$arkormx.Model<any, any>)[]>>;
175
- static unreadForUser(user: NotificationUser): Promise<_$arkormx.ArkormCollection<UserNotification & _$arkormx.Model<any, any>, (UserNotification & _$arkormx.Model<any, any>)[]>>;
281
+ static create(user: User, payload: DbNotificationPayload): Promise<UserNotification>;
282
+ static forUser(user: User): Promise<_$arkormx.ArkormCollection<UserNotification, UserNotification[]>>;
283
+ static unreadForUser(user: User): Promise<_$arkormx.ArkormCollection<UserNotification, UserNotification[]>>;
176
284
  static markRead(notification: UserNotification | UserNotification['id']): Promise<void>;
177
- static markAllRead(user: NotificationUser): Promise<void>;
285
+ static markAllRead(user: User): Promise<void>;
178
286
  static delete(notification: UserNotification | UserNotification['id']): Promise<void>;
179
287
  }
180
288
  //#endregion
181
289
  //#region src/config.d.ts
182
- declare const notificationConfig: <T = unknown>(key: string, defaultValue: T) => T;
290
+ declare const configure: <T extends DotPath<NotificationConfig>>(key: T, defaultValue: unknown) => DotPathValue<NotificationConfig, T>;
183
291
  //#endregion
184
292
  //#region src/utils/template.d.ts
185
293
  declare const interpolate: (value: string, data?: NotificationData) => string;
186
294
  //#endregion
187
- export { AfricasTalkingSmsDriver, DbNotification, DbNotificationPayload, DbNotificationType, DriverResult, MailDriverOptions, MailNotification, MailRecipient, MailRecipientAddress, Notification, NotificationChannel, NotificationContract, NotificationData, NotificationDriverMap, NotificationRecipient, NotificationUser, SmsDriverName, SmsDriverOptions, SmsNotification, TwilioSmsDriver, UserNotification, UserNotificationCenter, interpolate, notificationConfig };
188
- //# sourceMappingURL=index.d.ts.map
295
+ export { AfricasTalkingSmsDriver, DbNotification, DbNotificationPayload, DbNotificationType, DriverResult, MailDriverOptions, MailNotification, MailRecipient, MailRecipientAddress, MergedTransportConfig, Notification, NotificationChannel, NotificationConfig, NotificationContract, NotificationData, NotificationDriverMap, NotificationRecipient, SmsDriverName, SmsDriverOptions, SmsNotification, TwilioSmsDriver, UserNotification, UserNotificationCenter, configure, interpolate };
package/dist/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import { config, env, getModel } from "@arkstack/common";
2
- import { Model } from "arkormx";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
3
4
  import nodemailer from "nodemailer";
5
+ import { Arkstack } from "@arkstack/contract";
4
6
  import africastalking from "africastalking";
5
7
  import twilio from "twilio";
8
+ import { Model } from "@arkstack/database";
6
9
  //#region src/Contracts/NotificationContract.ts
7
10
  var NotificationContract = class {
8
11
  contextData = {};
@@ -19,12 +22,6 @@ var NotificationContract = class {
19
22
  }
20
23
  };
21
24
  //#endregion
22
- //#region src/Contracts/UserNotification.ts
23
- var UserNotification = class extends Model {
24
- static table = "user_notifications";
25
- casts = { meta: "json" };
26
- };
27
- //#endregion
28
25
  //#region src/UserNotificationCenter.ts
29
26
  var UserNotificationCenter = class {
30
27
  static async getModel() {
@@ -52,10 +49,10 @@ var UserNotificationCenter = class {
52
49
  }
53
50
  static async markRead(notification) {
54
51
  const Model = await this.getModel();
55
- const id = notification instanceof UserNotification ? notification.id : notification;
52
+ const id = typeof notification === "object" ? notification.id : notification;
56
53
  const readAt = /* @__PURE__ */ new Date();
57
54
  await Model.query().where({ id }).update({ readAt });
58
- if (notification instanceof UserNotification) notification.readAt = readAt;
55
+ if (typeof notification === "object") notification.readAt = readAt;
59
56
  }
60
57
  static async markAllRead(user) {
61
58
  await (await this.getModel()).query().where({
@@ -65,7 +62,7 @@ var UserNotificationCenter = class {
65
62
  }
66
63
  static async delete(notification) {
67
64
  const Model = await this.getModel();
68
- const id = notification instanceof UserNotification ? notification.id : notification;
65
+ const id = typeof notification === "object" ? notification.id : notification;
69
66
  await Model.query().where({ id }).delete();
70
67
  }
71
68
  };
@@ -128,7 +125,7 @@ var DbNotification = class extends NotificationContract {
128
125
  };
129
126
  //#endregion
130
127
  //#region src/config.ts
131
- const notificationConfig = (key, defaultValue) => {
128
+ const configure = (key, defaultValue) => {
132
129
  try {
133
130
  return config(`notifications.${key}`, defaultValue);
134
131
  } catch {
@@ -142,32 +139,30 @@ var MailNotification = class extends NotificationContract {
142
139
  fromAddress;
143
140
  subjectLine;
144
141
  recipients;
145
- static defaultHtmlTemplate = [
146
- "<!doctype html>",
147
- "<html>",
148
- "<body style=\"font-family:Arial,sans-serif;line-height:1.5;color:#111827\">",
149
- "<h2>{subject}</h2>",
150
- "<div>{message}</div>",
151
- "<p style=\"color:#6b7280;font-size:12px\">&copy; {year} {app_name}</p>",
152
- "</body>",
153
- "</html>"
154
- ].join("");
142
+ ViewName = "~arkstack/notifications.mail";
143
+ htmlTemplate;
144
+ textTemplate;
145
+ fileDirectory;
155
146
  constructor(options = {}) {
156
147
  super();
157
- const driverConfig = notificationConfig("drivers.mail", {});
158
- const transportConfig = notificationConfig(`transports.${options.transport ?? driverConfig.transport ?? "smtp"}`, {});
159
- const legacyMailConfig = notificationConfig("mail", {});
160
- const smtpConfig = {
161
- ...notificationConfig("smtp", {}),
162
- ...legacyMailConfig.smtp ?? {},
163
- ...transportConfig
164
- };
165
- const host = options.host ?? smtpConfig.host ?? env("SMTP_HOST", "localhost");
166
- const port = options.port ?? smtpConfig.port ?? env("SMTP_PORT", 1025);
167
- const secure = options.secure ?? smtpConfig.secure ?? env("SMTP_SECURE", false);
168
- const user = options.user ?? smtpConfig.auth?.user ?? smtpConfig.user ?? env("SMTP_USERNAME", "");
169
- const pass = options.pass ?? smtpConfig.auth?.pass ?? smtpConfig.pass ?? env("SMTP_PASSWORD", "");
170
- this.fromAddress = options.from ?? driverConfig.from ?? legacyMailConfig.from ?? smtpConfig.from ?? env("SMTP_FROM_ADDRESS", "no-reply@example.com");
148
+ const driver = configure("drivers.mail", {});
149
+ const trpt = driver.transport ?? "smtp";
150
+ const transport = configure(`transports.${trpt}`, {});
151
+ if (trpt === "file") {
152
+ this.fileDirectory = options.directory ?? transport.directory ?? env("MAIL_FILE_PATH", join(Arkstack.rootDir(), "./storage/framework/mails"));
153
+ this.fromAddress = options.from ?? driver.from ?? transport.from ?? env("MAIL_FROM_ADDRESS", "no-reply@example.com");
154
+ this.driver = nodemailer.createTransport({
155
+ streamTransport: true,
156
+ buffer: true
157
+ });
158
+ return;
159
+ }
160
+ const host = options.host ?? transport.host ?? env("MAIL_HOST", "localhost");
161
+ const port = options.port ?? transport.port ?? env("MAIL_PORT", 1025);
162
+ const secure = options.secure ?? transport.secure ?? env("MAIL_SECURE", false);
163
+ const user = options.user ?? transport.auth?.user ?? transport.user ?? env("MAIL_USERNAME", "");
164
+ const pass = options.pass ?? transport.auth?.pass ?? transport.pass ?? env("MAIL_PASSWORD", "");
165
+ this.fromAddress = options.from ?? driver.from ?? transport.from ?? env("MAIL_FROM_ADDRESS", "no-reply@example.com");
171
166
  this.driver = nodemailer.createTransport({
172
167
  host,
173
168
  port: Number(port),
@@ -182,41 +177,138 @@ var MailNotification = class extends NotificationContract {
182
177
  this.fromAddress = from;
183
178
  return this;
184
179
  }
180
+ /**
181
+ * The email subject
182
+ *
183
+ * @param subject
184
+ * @returns
185
+ */
185
186
  subject(subject) {
186
187
  this.subjectLine = subject;
187
188
  return this;
188
189
  }
190
+ /**
191
+ * Set email the notification recipeint
192
+ *
193
+ * @param recipient string or array of email addresses
194
+ * @returns
195
+ */
189
196
  recipient(recipient) {
190
197
  this.recipients = recipient;
191
198
  return this;
192
199
  }
200
+ /**
201
+ * Set email the notification view name
202
+ *
203
+ * @param view view name
204
+ * @returns
205
+ */
206
+ view(view) {
207
+ this.ViewName = view;
208
+ return this;
209
+ }
210
+ /**
211
+ * Set email the notification html template
212
+ *
213
+ * @param content view name
214
+ * @returns
215
+ */
216
+ html(content) {
217
+ this.htmlTemplate = content;
218
+ return this;
219
+ }
220
+ /**
221
+ * Set email the notification text template
222
+ *
223
+ * @param content view name
224
+ * @returns
225
+ */
226
+ text(content) {
227
+ this.textTemplate = content;
228
+ return this;
229
+ }
230
+ /**
231
+ * Prepare a notification to be sent.
232
+ *
233
+ * @param user The recipient user(s) for the notification.
234
+ */
235
+ prepare(user, data = {}) {
236
+ this.data(data);
237
+ if (user && typeof user === "object" && !Array.isArray(user)) user = user.email;
238
+ if (user) this.recipient(user);
239
+ return this;
240
+ }
241
+ /**
242
+ * Send a notification to the specified recipient(s) with the given message.
243
+ *
244
+ * @param message The message content to be sent to the recipient(s).
245
+ * @param subject The message subject to be sent to the recipient(s).
246
+ * @param recipient An array of recipient identifiers
247
+ * @param data Additioal context data
248
+ * @returns
249
+ */
193
250
  async send(message, subject, recipient, data) {
194
251
  const mergedData = {
195
- app_name: env("APP_NAME", "Arkstack"),
252
+ app_name: config("app.name", "Arkstack"),
196
253
  ...this.mergeData(data)
197
254
  };
198
255
  const resolvedSubject = interpolate(subject ?? this.subjectLine ?? "", mergedData);
199
256
  const resolvedMessage = interpolate(message, mergedData);
200
257
  const to = this.resolveRecipients(recipient);
201
258
  if (to.length < 1) throw new Error("No recipient provided for mail notification");
202
- return await this.driver.sendMail({
259
+ const templateData = {
260
+ ...mergedData,
261
+ message: resolvedMessage,
262
+ subject: resolvedSubject
263
+ };
264
+ const textMessage = resolvedMessage.replace(/<\/?[^>]+(>|$)/g, "");
265
+ const payload = {
203
266
  to,
204
267
  subject: resolvedSubject,
205
268
  from: this.fromAddress,
206
- text: resolvedMessage.replace(/<\/?[^>]+(>|$)/g, ""),
207
- html: interpolate(await globalThis.view("~arkstack/notifications.mail", {
208
- ...mergedData,
209
- message: resolvedMessage,
210
- subject: resolvedSubject
211
- }))
212
- });
269
+ text: interpolate(this.textTemplate ?? textMessage, {
270
+ ...templateData,
271
+ message: textMessage
272
+ }),
273
+ html: this.htmlTemplate ? interpolate(this.htmlTemplate, templateData) : await globalThis.view(this.ViewName, templateData)
274
+ };
275
+ const result = await this.driver.sendMail(payload);
276
+ if (this.fileDirectory) await this.storeFileMail(payload, result);
277
+ return result;
278
+ }
279
+ async storeFileMail(payload, result) {
280
+ const now = /* @__PURE__ */ new Date();
281
+ const date = now.toISOString().slice(0, 10);
282
+ const directory = join(this.fileDirectory, date);
283
+ const id = String(result.messageId || now.getTime()).replace(/[^a-zA-Z0-9_.-]/g, "-");
284
+ const path = join(directory, now.getTime() + "-" + id + ".json");
285
+ const message = result.message;
286
+ await mkdir(directory, { recursive: true });
287
+ await writeFile(path, JSON.stringify({
288
+ id: result.messageId,
289
+ date: now.toISOString(),
290
+ envelope: result.envelope,
291
+ accepted: result.accepted,
292
+ rejected: result.rejected,
293
+ pending: result.pending,
294
+ response: result.response,
295
+ message: {
296
+ from: payload.from,
297
+ to: payload.to,
298
+ subject: payload.subject,
299
+ text: payload.text,
300
+ html: payload.html,
301
+ raw: Buffer.isBuffer(message) ? message.toString("utf8") : String(message ?? "")
302
+ }
303
+ }, null, 2), "utf8");
304
+ return path;
213
305
  }
214
306
  resolveRecipients(recipient) {
215
307
  const recipients = recipient ?? this.recipients;
216
308
  const resolved = (Array.isArray(recipients) ? [...recipients] : recipients ? [recipients] : []).flatMap((recipient) => this.normalizeRecipient(recipient));
217
- const driverConfig = notificationConfig("drivers.mail", {});
218
- const transportConfig = notificationConfig(`transports.${driverConfig.transport ?? "smtp"}`, {});
219
- const testAddress = driverConfig.testAddress ?? driverConfig.test_address ?? transportConfig.testAddress ?? transportConfig.test_address ?? env("SMTP_TEST_ADDRESS");
309
+ const driver = configure("drivers.mail", {});
310
+ const transport = configure(`transports.${driver.transport ?? "smtp"}`, {});
311
+ const testAddress = driver.test_address ?? driver.test_address ?? transport.test_address ?? transport.test_address ?? env("MAIL_TEST_ADDRESS");
220
312
  if (env("NODE_ENV") !== "production" && testAddress) resolved.push(testAddress);
221
313
  return resolved;
222
314
  }
@@ -286,22 +378,19 @@ var SmsNotification = class extends NotificationContract {
286
378
  fromValue;
287
379
  constructor(options = {}) {
288
380
  super();
289
- const driverConfig = notificationConfig("drivers.sms", {});
381
+ const driverConfig = configure("drivers.sms", {});
290
382
  const transport = options.transport ?? driverConfig.transport ?? "twilio";
291
- const transportConfig = notificationConfig(`transports.${transport}`, {});
292
- const legacySmsConfig = notificationConfig("sms", {});
293
- const from = options.from ?? driverConfig.from ?? legacySmsConfig.from;
383
+ const transportConfig = configure(`transports.${transport}`, {});
384
+ const from = options.from ?? driverConfig.from;
294
385
  this.fromValue = from;
295
386
  this.driver = transport === "twilio" ? new TwilioSmsDriver({
296
- ...legacySmsConfig.twilio,
297
387
  ...transportConfig,
298
388
  ...options.twilio,
299
- from: options.twilio?.from ?? transportConfig.from ?? legacySmsConfig.twilio?.from ?? from
389
+ from: options.twilio?.from ?? transportConfig.from ?? from
300
390
  }) : new AfricasTalkingSmsDriver({
301
- ...legacySmsConfig.africastalking,
302
391
  ...transportConfig,
303
392
  ...options.africastalking,
304
- senderId: options.africastalking?.senderId ?? transportConfig.senderId ?? legacySmsConfig.africastalking?.senderId ?? from
393
+ senderId: options.africastalking?.senderId ?? transportConfig.senderId ?? from
305
394
  });
306
395
  }
307
396
  from(from) {
@@ -345,7 +434,7 @@ var Notification = class Notification {
345
434
  return new DbNotification();
346
435
  }
347
436
  static channel(channel, options) {
348
- return Notification.createDriver(channel ?? notificationConfig("default_driver", "mail"), options);
437
+ return Notification.createDriver(channel ?? configure("default_driver", "mail"), options);
349
438
  }
350
439
  prepare(recipient, data = {}) {
351
440
  this.driver.data(data);
@@ -371,6 +460,10 @@ var Notification = class Notification {
371
460
  }
372
461
  };
373
462
  //#endregion
374
- export { AfricasTalkingSmsDriver, DbNotification, MailNotification, Notification, NotificationContract, SmsNotification, TwilioSmsDriver, UserNotification, UserNotificationCenter, interpolate, notificationConfig };
375
-
376
- //# sourceMappingURL=index.js.map
463
+ //#region src/Contracts/UserNotification.ts
464
+ var UserNotification = class extends Model {
465
+ static table = "user_notifications";
466
+ casts = { meta: "json" };
467
+ };
468
+ //#endregion
469
+ export { AfricasTalkingSmsDriver, DbNotification, MailNotification, Notification, NotificationContract, SmsNotification, TwilioSmsDriver, UserNotification, UserNotificationCenter, configure, interpolate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkstack/notifications",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "description": "Framework-agnostic notification module for Arkstack and Nodejs, providing support for multi-channel notification delivery.",
6
6
  "homepage": "https://arkstack.toneflix.net/guide/notifications",
@@ -20,7 +20,8 @@
20
20
  "arkstack"
21
21
  ],
22
22
  "files": [
23
- "dist"
23
+ "dist",
24
+ "resources"
24
25
  ],
25
26
  "publishConfig": {
26
27
  "access": "public"
@@ -33,10 +34,11 @@
33
34
  "africastalking": "^0.8.0",
34
35
  "nodemailer": "^8.0.7",
35
36
  "twilio": "^6.0.0",
36
- "@arkstack/common": "^0.5.1"
37
+ "@arkstack/common": "^0.5.3"
37
38
  },
38
39
  "peerDependencies": {
39
- "@arkstack/database": "^0.5.1"
40
+ "@arkstack/database": "^0.5.3",
41
+ "@arkstack/contract": "^0.5.3"
40
42
  },
41
43
  "devDependencies": {
42
44
  "@types/nodemailer": "^7.0.11"
@@ -0,0 +1,10 @@
1
+ <!doctype html>
2
+ <html>
3
+
4
+ <body style="font-family:Arial,sans-serif;line-height:1.5;color:#111827">
5
+ <h2>{{{ subject }}}</h2>
6
+ <div>{{{ message }}}</div>
7
+ <p style="color:#6b7280;font-size:12px">&copy; {{ year }} {{ app_name }}</p>
8
+ </body>
9
+
10
+ </html>
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/Contracts/NotificationContract.ts","../src/Contracts/UserNotification.ts","../src/UserNotificationCenter.ts","../src/utils/template.ts","../src/drivers/DbNotification.ts","../src/config.ts","../src/drivers/MailNotification.ts","../src/drivers/sms/AfricasTalkingSmsDriver.ts","../src/drivers/sms/TwilioSmsDriver.ts","../src/drivers/SmsNotification.ts","../src/Notification.ts"],"sourcesContent":["import type { DriverResult, NotificationData, NotificationRecipient } from '../types'\n\nexport abstract class NotificationContract<TResult = DriverResult> {\n protected contextData: NotificationData = {}\n\n abstract send (\n message: string,\n subject?: string,\n recipient?: NotificationRecipient,\n data?: NotificationData\n ): Promise<TResult>\n\n abstract from (from: string): this\n abstract subject (subject: string): this\n abstract recipient (recipient: NotificationRecipient): this\n\n data (data: NotificationData): this {\n this.contextData = data\n\n return this\n }\n\n protected mergeData (data?: NotificationData) {\n return {\n ...this.contextData,\n ...data,\n year: new Date().getFullYear(),\n }\n }\n}\n","import { Model } from 'arkormx'\n\nimport type { DbNotificationType, NotificationData } from '../types'\n\nexport abstract class UserNotification extends Model {\n declare id: number | string\n declare userId: number | string\n declare type: DbNotificationType | null\n declare title: string\n declare description: string\n declare actionText: string | null\n declare actionLink: string | null\n declare meta: NotificationData | null\n declare readAt: Date | null\n declare createdAt: Date\n declare updatedAt: Date\n\n protected static table?: string | undefined = 'user_notifications'\n\n protected casts = {\n meta: 'json',\n } as const\n}\n","import { getModel } from '@arkstack/common'\n\nimport { UserNotification } from './Contracts/UserNotification'\nimport type { DbNotificationPayload, NotificationUser } from './types'\n\nexport class UserNotificationCenter {\n private static async getModel () {\n return await getModel<typeof UserNotification>('UserNotification')\n }\n\n static async create (user: NotificationUser, payload: DbNotificationPayload) {\n const Model = await this.getModel()\n\n return await Model.query().create({\n userId: user.id,\n type: payload.type ?? null,\n title: payload.title,\n description: payload.description,\n actionText: payload.actionText ?? null,\n actionLink: payload.actionLink ?? null,\n meta: payload.meta ?? null,\n })\n }\n\n static async forUser (user: NotificationUser) {\n const Model = await this.getModel()\n\n return await Model.query().where({ userId: user.id }).get()\n }\n\n static async unreadForUser (user: NotificationUser) {\n const Model = await this.getModel()\n\n return await Model.query().where({ userId: user.id, readAt: null }).get()\n }\n\n static async markRead (notification: UserNotification | UserNotification['id']) {\n const Model = await this.getModel()\n const id = notification instanceof UserNotification ? notification.id : notification\n const readAt = new Date()\n\n await Model.query().where({ id }).update({ readAt })\n\n if (notification instanceof UserNotification) {\n notification.readAt = readAt\n }\n }\n\n static async markAllRead (user: NotificationUser) {\n const Model = await this.getModel()\n\n await Model.query().where({ userId: user.id, readAt: null }).update({ readAt: new Date() })\n }\n\n static async delete (notification: UserNotification | UserNotification['id']) {\n const Model = await this.getModel()\n const id = notification instanceof UserNotification ? notification.id : notification\n\n await Model.query().where({ id }).delete()\n }\n}\n","import type { NotificationData } from '../types'\n\nexport const interpolate = (value: string, data: NotificationData = {}) => {\n return value.replace(/\\{([^{}]+)\\}/g, (match, key) => {\n const replacement = data[key.trim()]\n\n return replacement === undefined || replacement === null ? match : String(replacement)\n })\n}\n","import type { DbNotificationPayload, NotificationData, NotificationRecipient, NotificationUser } from '../types'\n\nimport { NotificationContract } from '../Contracts/NotificationContract'\nimport { UserNotification } from '../Contracts/UserNotification'\nimport { UserNotificationCenter } from '../UserNotificationCenter'\nimport { getModel } from '@arkstack/common'\nimport { interpolate } from '../utils/template'\n\nexport class DbNotification extends NotificationContract<UserNotification> {\n private user?: NotificationUser\n private payload: Partial<DbNotificationPayload> = {}\n\n from (_from: string): this {\n return this\n }\n\n subject (subject: string): this {\n this.payload.title = subject\n\n return this\n }\n\n recipient (recipient: NotificationRecipient | NotificationUser): this {\n if (typeof recipient === 'object' && !Array.isArray(recipient) && 'id' in recipient) {\n this.user = recipient\n\n return this\n }\n\n throw new Error('Database notifications require a user recipient')\n }\n\n type (type: DbNotificationPayload['type']): this {\n this.payload.type = type\n\n return this\n }\n\n action (text?: string | null, link?: string | null): this {\n this.payload.actionText = text\n this.payload.actionLink = link\n\n return this\n }\n\n meta (meta?: NotificationData | null): this {\n this.payload.meta = meta\n\n return this\n }\n\n async create (user: NotificationUser, payload: DbNotificationPayload) {\n await getModel<typeof UserNotification>('UserNotification')\n\n return await UserNotificationCenter.create(user, payload)\n }\n\n async send (\n message: string,\n subject?: string,\n _recipient?: NotificationRecipient,\n data?: NotificationData\n ) {\n if (!this.user) {\n throw new Error('No user recipient provided for database notification')\n }\n\n const mergedData = this.mergeData(data)\n\n return await this.create(this.user, {\n type: this.payload.type ?? null,\n title: interpolate(subject ?? this.payload.title ?? '', mergedData),\n description: interpolate(message, mergedData),\n actionText: this.payload.actionText ?? null,\n actionLink: this.payload.actionLink ?? null,\n meta: this.payload.meta ?? null,\n })\n }\n}\n","import { config } from '@arkstack/common'\n\nexport const notificationConfig = <T = unknown> (key: string, defaultValue: T): T => {\n try {\n return config(`notifications.${key}`, defaultValue) as T\n } catch {\n return defaultValue\n }\n}\n","import { env } from '@arkstack/common'\nimport nodemailer, { type Transporter } from 'nodemailer'\n\nimport { NotificationContract } from '../Contracts/NotificationContract'\nimport { interpolate } from '../utils/template'\nimport { notificationConfig } from '../config'\nimport type { MailDriverOptions, MailRecipient, MailRecipientAddress, NotificationData } from '../types'\n\nexport class MailNotification extends NotificationContract {\n driver: Transporter\n private fromAddress?: string\n private subjectLine?: string\n private recipients?: MailRecipient\n static defaultHtmlTemplate = [\n '<!doctype html>',\n '<html>',\n '<body style=\"font-family:Arial,sans-serif;line-height:1.5;color:#111827\">',\n '<h2>{subject}</h2>',\n '<div>{message}</div>',\n '<p style=\"color:#6b7280;font-size:12px\">&copy; {year} {app_name}</p>',\n '</body>',\n '</html>',\n ].join('')\n\n constructor(options: MailDriverOptions = {}) {\n super()\n\n const driverConfig = notificationConfig<Record<string, any>>('drivers.mail', {})\n const transport = options.transport ?? driverConfig.transport ?? 'smtp'\n const transportConfig = notificationConfig<Record<string, any>>(`transports.${transport}`, {})\n const legacyMailConfig = notificationConfig<Record<string, any>>('mail', {})\n const legacySmtpConfig = notificationConfig<Record<string, any>>('smtp', {})\n const smtpConfig = {\n ...legacySmtpConfig,\n ...(legacyMailConfig.smtp ?? {}),\n ...transportConfig,\n }\n const host = options.host ?? smtpConfig.host ?? env('SMTP_HOST', 'localhost')\n const port = options.port ?? smtpConfig.port ?? env('SMTP_PORT', 1025)\n const secure = options.secure ?? smtpConfig.secure ?? env('SMTP_SECURE', false)\n const user = options.user ?? smtpConfig.auth?.user ?? smtpConfig.user ?? env('SMTP_USERNAME', '')\n const pass = options.pass ?? smtpConfig.auth?.pass ?? smtpConfig.pass ?? env('SMTP_PASSWORD', '')\n\n this.fromAddress = options.from ?? driverConfig.from ?? legacyMailConfig.from ?? smtpConfig.from ?? env('SMTP_FROM_ADDRESS', 'no-reply@example.com')\n this.driver = nodemailer.createTransport({\n host,\n port: Number(port),\n secure: Boolean(secure),\n auth: user || pass ? { user, pass } : undefined,\n })\n }\n\n from (from: string): this {\n this.fromAddress = from\n\n return this\n }\n\n subject (subject: string): this {\n this.subjectLine = subject\n\n return this\n }\n\n recipient (recipient: MailRecipient): this {\n this.recipients = recipient\n\n return this\n }\n\n async send (\n message: string,\n subject?: string,\n recipient?: MailRecipient,\n data?: NotificationData\n ) {\n const mergedData = {\n app_name: env('APP_NAME', 'Arkstack'),\n ...this.mergeData(data),\n }\n const resolvedSubject = interpolate(subject ?? this.subjectLine ?? '', mergedData)\n const resolvedMessage = interpolate(message, mergedData)\n const to = this.resolveRecipients(recipient)\n\n if (to.length < 1) {\n throw new Error('No recipient provided for mail notification')\n }\n\n return await this.driver.sendMail({\n to: to as never,\n subject: resolvedSubject,\n from: this.fromAddress,\n text: resolvedMessage.replace(/<\\/?[^>]+(>|$)/g, ''),\n html: interpolate(await globalThis.view('~arkstack/notifications.mail', {\n ...mergedData,\n message: resolvedMessage,\n subject: resolvedSubject,\n })),\n })\n }\n\n private resolveRecipients (recipient?: MailRecipient) {\n const recipients = recipient ?? this.recipients\n const resolved = (Array.isArray(recipients)\n ? [...recipients]\n : recipients\n ? [recipients]\n : []\n ).flatMap(recipient => this.normalizeRecipient(recipient) as unknown)\n\n const driverConfig = notificationConfig<Record<string, any>>('drivers.mail', {})\n const transport = driverConfig.transport ?? 'smtp'\n const transportConfig = notificationConfig<Record<string, any>>(`transports.${transport}`, {})\n const testAddress = driverConfig.testAddress\n ?? driverConfig.test_address\n ?? transportConfig.testAddress\n ?? transportConfig.test_address\n ?? env<string | undefined>('SMTP_TEST_ADDRESS')\n\n if (env('NODE_ENV') !== 'production' && testAddress) {\n resolved.push(testAddress)\n }\n\n return resolved\n }\n\n private normalizeRecipient (recipient: string | MailRecipientAddress) {\n if (typeof recipient === 'string') {\n return [recipient]\n }\n\n return Object.entries(recipient).map(([address, name]) => ({ address, name }))\n }\n}\n","import { env } from '@arkstack/common'\nimport africastalking from 'africastalking'\n\nimport { interpolate } from '../../utils/template'\nimport type { NotificationData, NotificationRecipient, SmsDriverOptions } from '../../types'\n\nexport class AfricasTalkingSmsDriver {\n private driver: {\n send: (payload: { to: string; from?: string; message: string }) => Promise<unknown>\n sendBulk: (payload: { to: string[]; from?: string; message: string }) => Promise<unknown>\n }\n private senderId?: string\n\n constructor(options: SmsDriverOptions['africastalking'] = {}) {\n const username = options.username ?? env('AFRICASTALKING_USERNAME', 'sandbox')\n const apiKey = options.apiKey ?? env('AFRICASTALKING_API_KEY', 'sandbox')\n\n this.senderId = options.senderId ?? env('AFRICASTALKING_SENDER_ID', env('SMS_FROM', 'Arkstack'))\n this.driver = (africastalking as (config: { username: string; apiKey: string }) => {\n SMS: AfricasTalkingSmsDriver['driver']\n })({ username, apiKey }).SMS\n }\n\n async send (message: string, recipient: NotificationRecipient, data: NotificationData = {}) {\n const recipients = Array.isArray(recipient) ? recipient : [recipient]\n const resolvedMessage = interpolate(message, data)\n\n if (recipients.length > 1) {\n return await this.driver.sendBulk({\n to: recipients,\n from: this.senderId,\n message: resolvedMessage,\n })\n }\n\n return await this.driver.send({\n to: recipients[0],\n from: this.senderId,\n message: resolvedMessage,\n })\n }\n}\n","import type { NotificationData, NotificationRecipient, SmsDriverOptions } from '../../types'\n\nimport { env } from '@arkstack/common'\nimport { interpolate } from '../../utils/template'\nimport twilio from 'twilio'\n\nexport class TwilioSmsDriver {\n private client: ReturnType<typeof twilio>\n private fromNumber: string\n\n constructor(options: SmsDriverOptions['twilio'] = {}) {\n const accountSid = options.accountSid ?? env('TWILIO_ACCOUNT_SID', '')\n const authToken = options.authToken ?? env('TWILIO_AUTH_TOKEN', '')\n\n this.fromNumber = options.from ?? env('TWILIO_FROM', env('SMS_FROM', ''))\n this.client = twilio(accountSid, authToken)\n }\n\n async send (message: string, recipient: NotificationRecipient, data: NotificationData = {}) {\n const recipients = Array.isArray(recipient) ? recipient : [recipient]\n const resolvedMessage = interpolate(message, data)\n\n return await Promise.all(recipients.map(async to => await this.client.messages.create({\n body: resolvedMessage,\n from: this.fromNumber,\n to,\n })))\n }\n}\n","import type { NotificationData, NotificationRecipient, SmsDriverOptions } from '../types'\n\nimport { AfricasTalkingSmsDriver } from './sms/AfricasTalkingSmsDriver'\nimport { NotificationContract } from '../Contracts/NotificationContract'\nimport { TwilioSmsDriver } from './sms/TwilioSmsDriver'\nimport { env } from '@arkstack/common'\nimport { notificationConfig } from '../config'\n\ntype SmsProvider = AfricasTalkingSmsDriver | TwilioSmsDriver\n\nexport class SmsNotification extends NotificationContract {\n driver: SmsProvider\n private recipients?: NotificationRecipient\n private fromValue?: string\n\n constructor(options: SmsDriverOptions = {}) {\n super()\n\n const driverConfig = notificationConfig<SmsDriverOptions>('drivers.sms', {})\n const transport = options.transport ?? driverConfig.transport ?? 'twilio'\n const transportConfig = notificationConfig<Record<string, any>>(`transports.${transport}`, {})\n const legacySmsConfig = notificationConfig<SmsDriverOptions>('sms', {})\n const from = options.from ?? driverConfig.from ?? legacySmsConfig.from\n\n this.fromValue = from\n this.driver = transport === 'twilio'\n ? new TwilioSmsDriver({\n ...legacySmsConfig.twilio,\n ...transportConfig,\n ...options.twilio,\n from: options.twilio?.from ?? transportConfig.from ?? legacySmsConfig.twilio?.from ?? from,\n })\n : new AfricasTalkingSmsDriver({\n ...legacySmsConfig.africastalking,\n ...transportConfig,\n ...options.africastalking,\n senderId: options.africastalking?.senderId ?? transportConfig.senderId ?? legacySmsConfig.africastalking?.senderId ?? from,\n })\n }\n\n from (from: string): this {\n this.fromValue = from\n\n return this\n }\n\n subject (_subject: string): this {\n return this\n }\n\n recipient (recipient: NotificationRecipient): this {\n this.recipients = recipient\n\n return this\n }\n\n async send (\n message: string,\n _subject?: string,\n recipient?: NotificationRecipient,\n data?: NotificationData\n ) {\n const resolvedRecipient = recipient ?? this.recipients\n\n if (!resolvedRecipient) {\n throw new Error('No recipient provided for SMS notification')\n }\n\n return await this.driver.send(message, resolvedRecipient, {\n app_name: env('APP_NAME', 'Arkstack'),\n from: this.fromValue,\n ...this.mergeData(data),\n })\n }\n}\n","import type { MailDriverOptions, MailRecipient, NotificationChannel, NotificationData, NotificationRecipient, NotificationUser, SmsDriverOptions } from './types'\n\nimport { DbNotification } from './drivers/DbNotification'\nimport { DriverMap } from './Contracts/Maps'\nimport { MailNotification } from './drivers/MailNotification'\nimport { SmsNotification } from './drivers/SmsNotification'\nimport { notificationConfig } from './config'\n\nexport class Notification<D extends keyof DriverMap = keyof DriverMap> {\n private driver: DriverMap[D]\n\n constructor(driver: D, options: MailDriverOptions | SmsDriverOptions = {}) {\n this.driver = Notification.createDriver(driver, options) as DriverMap[D]\n }\n\n static mail (options?: MailDriverOptions) {\n return new MailNotification(options)\n }\n\n static email (options?: MailDriverOptions) {\n return this.mail(options)\n }\n\n static sms (options?: SmsDriverOptions) {\n return new SmsNotification(options)\n }\n\n static db () {\n return new DbNotification()\n }\n\n static channel (\n channel?: NotificationChannel | 'email',\n options?: MailDriverOptions | SmsDriverOptions\n ) {\n return Notification.createDriver(channel ?? notificationConfig<NotificationChannel>('default_driver', 'mail'), options)\n }\n\n prepare (\n recipient?: null | MailRecipient | NotificationRecipient | NotificationUser,\n data: NotificationData = {}\n ) {\n this.driver.data(data)\n\n if (recipient && typeof recipient === 'object' && !Array.isArray(recipient) && 'id' in recipient) {\n if (this.driver instanceof MailNotification) {\n if (recipient.email) {\n this.driver.recipient(recipient.email)\n }\n } else if (this.driver instanceof SmsNotification) {\n if (recipient.phone) {\n this.driver.recipient(recipient.phone)\n }\n } else {\n this.driver.recipient(recipient as never)\n }\n\n return this.driver\n }\n\n if (recipient) {\n this.driver.recipient(recipient as NotificationRecipient)\n }\n\n return this.driver\n }\n\n private static createDriver (\n driver: NotificationChannel | 'email',\n options: MailDriverOptions | SmsDriverOptions = {}\n ) {\n switch (driver) {\n case 'mail':\n case 'email':\n return new MailNotification(options as MailDriverOptions)\n case 'sms':\n return new SmsNotification(options as SmsDriverOptions)\n case 'db':\n return new DbNotification()\n default:\n throw new Error(`Unsupported notification driver: ${driver}`)\n }\n }\n}\n"],"mappings":"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"}