@orieljs/notifications 0.1.0-alpha.1 → 0.1.0-alpha.11

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/NOTICE CHANGED
@@ -7,3 +7,10 @@ This product includes software developed by the Oriel project
7
7
  Oriel is licensed under the Apache License, Version 2.0 (see LICENSE).
8
8
  The Oriel name and logo are not covered by that license; see TRADEMARKS.md
9
9
  (https://github.com/orieljs/framework/blob/main/TRADEMARKS.md).
10
+
11
+ @orieljs/support (and @orieljs/framework, which bundles it) includes transliteration data from
12
+ voku/portable-ascii (https://github.com/voku/portable-ascii), Copyright (C) 2019 Lars Moelleken, MIT License.
13
+
14
+ @orieljs/notifications (and @orieljs/framework, which bundles it) includes the HTML and CSS of
15
+ Laravel's default notification mail (laravel/framework, https://github.com/laravel/framework),
16
+ Copyright (c) Taylor Otwell, MIT License.
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @orieljs/notifications
2
+
3
+ Notifications for Oriel: short messages to your users by mail or stored in the database, as Laravel's notifications. It is part of `@orieljs/framework`, which every Oriel app already installs, and is re-exported from it. In an app, import it from `@orieljs/framework`; `@orieljs/notifications` is also published on its own.
4
+
5
+ ```bash
6
+ npm install @orieljs/framework
7
+ ```
8
+
9
+ ```ts title="app/Notifications/PostPublished.ts"
10
+ import { MailMessage, Notification, url } from '@orieljs/framework'
11
+ import type { Post } from '../Models/Post'
12
+
13
+ export class PostPublished extends Notification {
14
+ constructor(public post: Post) {
15
+ super()
16
+ }
17
+
18
+ via(): string[] {
19
+ return ['mail']
20
+ }
21
+
22
+ toMail(): MailMessage {
23
+ return new MailMessage().line(`"${this.post.title}" is published.`).action('View post', url(`/posts/${this.post.slug}`))
24
+ }
25
+ }
26
+ ```
27
+
28
+ `via()` names the channels (`mail`, `database`), and `toMail()` or `toDatabase()` builds the message for each. Send it with `await user.notify(new PostPublished(post))` (models get `notify()` from the `Notifiable` mixin), to several users with `await Notification.send(users, notification)`, or to an address with `await Notification.route('mail', address).notify(notification)`. A mail's links need full URLs: `url()` makes one from a path. A notification with `readonly shouldQueue = true` is sent by the queue worker.
29
+
30
+ Documentation: https://orieljs.vercel.app/docs/notifications
31
+
32
+ Status: early alpha. APIs will change.
33
+
34
+ ## License
35
+
36
+ Oriel is open-source software licensed under the Apache License 2.0 (Apache-2.0; see `LICENSE` and `NOTICE`). The Oriel name and logo are trademarks; see `TRADEMARKS.md`.
37
+
38
+ Website: https://orieljs.vercel.app
package/TRADEMARKS.md ADDED
@@ -0,0 +1,33 @@
1
+ # Oriel trademark policy
2
+
3
+ The Oriel code is open source under the Apache License 2.0. The **Oriel name and logo** are not: the license covers
4
+ the code only (section 6 of the license says so), and the name and logo stay with the project. This page explains
5
+ what you can and can't do with them. The goal is simple: people should always be able to tell what is the real Oriel
6
+ and what isn't.
7
+
8
+ ## You can, without asking
9
+
10
+ - Use, change, ship and sell the code, as the license allows.
11
+ - Say that your app or package is "built with Oriel", "for Oriel" or "compatible with Oriel".
12
+ - Name a package `oriel-<something>` or `<something>-for-oriel` when it works with Oriel, as long as it is clear that
13
+ it is not official (for example, don't publish it under the `@orieljs` npm scope).
14
+ - Use the name and logo to write about Oriel, teach it, or link to it, in articles, talks, videos and courses.
15
+ - Use the unmodified logo to point to the project, as it is (no recolouring, cropping or
16
+ distortion).
17
+
18
+ ## You can't, without written permission
19
+
20
+ - Call a fork, a modified copy or a competing product "Oriel", or anything likely to be confused with it.
21
+ - Use the name or logo as the name, logo or domain of your own product, company or service.
22
+ - Suggest that the Oriel project made, endorses or supports your work when it doesn't.
23
+ - Sell merchandise with the name or logo.
24
+
25
+ ## Forks
26
+
27
+ Forks are welcome. Please give yours a different name and logo, keep the `LICENSE` and `NOTICE` files, and say that
28
+ it is based on Oriel.
29
+
30
+ ## Questions
31
+
32
+ Open an issue at https://github.com/orieljs/framework or email meeeet.dev@gmail.com. If you're unsure whether a use
33
+ is fine, ask; the answer is usually yes.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { OrielError, MailRecipient, NotificationChannel, Notifiable as Notifiable$1, Notification as Notification$1, NotificationDispatcher, Container, Job } from '@orieljs/contracts';
1
+ import { OrielError, MailRecipient, Attachment, NotificationChannel, Notifiable, Notification as Notification$1, NotificationDispatcher, Container, Job } from '@orieljs/contracts';
2
2
  import * as _orieljs_support from '@orieljs/support';
3
3
  import { ServiceProvider } from '@orieljs/support';
4
4
 
@@ -51,38 +51,84 @@ declare class NotificationDispatcherUnavailableError extends Error implements Or
51
51
  constructor();
52
52
  }
53
53
 
54
+ /*!
55
+ * Laravel's default notification mail (the `notifications::email` view in the `mail::` markdown components with the
56
+ * `default` theme), as Laravel 12 sends it: the same HTML, with the theme's CSS inlined as CssToInlineStyles inlines
57
+ * it, and the same plain-text part. The lines are plain text (escaped), not Markdown.
58
+ *
59
+ * laravel/framework: Copyright (c) Taylor Otwell
60
+ *
61
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
62
+ * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
63
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
64
+ * permit persons to whom the Software is furnished to do so, subject to the following conditions:
65
+ *
66
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
67
+ * Software.
68
+ *
69
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
70
+ * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
71
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
72
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
73
+ */
74
+ type MailLevel = 'info' | 'success' | 'error';
75
+
54
76
  interface MailMessageAction {
55
77
  text: string;
56
78
  url: string;
57
79
  }
58
80
  /**
59
- * Laravel's Illuminate\Notifications\Messages\MailMessage: greeting/line/action/salutation, rendered to a default
60
- * HTML+text template with no View involved. line() appends to the intro before action() is called, and to the
61
- * outro afterwards — exactly like Laravel's SimpleMessage.
81
+ * Laravel's Illuminate\Notifications\Messages\MailMessage: greeting/line/action/salutation, rendered to Laravel's
82
+ * default notification mail (HTML and text) with no View involved. line() appends to the intro before action() is
83
+ * called, and to the outro afterwards — exactly like Laravel's SimpleMessage.
62
84
  */
63
85
  declare class MailMessage {
86
+ /** info, success or error: the colour of the button, and error() greets with "Whoops!". Laravel's `$level`. */
87
+ levelName: MailLevel;
64
88
  subjectLine?: string;
65
89
  greetingLine?: string;
66
90
  introLines: string[];
67
91
  actionData?: MailMessageAction;
68
92
  outroLines: string[];
69
- salutationLine: string;
93
+ /** Unset: "Regards," and config('app.name'), as Laravel's template. */
94
+ salutationLine?: string;
70
95
  toAddresses: MailRecipient[];
71
96
  ccAddresses: MailRecipient[];
72
97
  bccAddresses: MailRecipient[];
98
+ replyToAddresses: MailRecipient[];
73
99
  fromAddress?: MailRecipient;
100
+ attachments: Attachment[];
101
+ /** A green button: something went well. */
102
+ success(): this;
103
+ /** A red button, and "Whoops!" when there is no greeting: something went wrong. */
104
+ error(): this;
105
+ level(level: MailLevel): this;
74
106
  subject(subject: string): this;
75
107
  greeting(greeting: string): this;
76
108
  /** Before action(), appends to the intro; after action(), to the outro. */
77
109
  line(text: string): this;
78
110
  lines(lines: string[]): this;
111
+ /** line(), when the condition holds. */
112
+ lineIf(condition: boolean, text: string): this;
113
+ /** lines(), when the condition holds. */
114
+ linesIf(condition: boolean, lines: string[]): this;
79
115
  action(text: string, url: string): this;
80
116
  salutation(salutation: string): this;
81
117
  from(address: MailRecipient): this;
82
118
  to(recipients: MailRecipient | MailRecipient[]): this;
83
119
  cc(recipients: MailRecipient | MailRecipient[]): this;
84
120
  bcc(recipients: MailRecipient | MailRecipient[]): this;
85
- /** Renders the default notification mail template. No ViewFactory involved. */
121
+ replyTo(recipients: MailRecipient | MailRecipient[]): this;
122
+ /** Attaches a file by its path: `as` names it in the mail (its file name by default), `mime` gives its type. */
123
+ attach(path: string, options?: {
124
+ as?: string;
125
+ mime?: string;
126
+ }): this;
127
+ /** Attaches bytes or text under a file name. */
128
+ attachData(data: Uint8Array | string, name: string, options?: {
129
+ mime?: string;
130
+ }): this;
131
+ /** Renders Laravel's default notification mail, HTML and text. No ViewFactory involved. */
86
132
  render(): {
87
133
  html: string;
88
134
  text: string;
@@ -104,29 +150,17 @@ interface PlainNotificationMail {
104
150
  salutation?: string;
105
151
  }
106
152
  declare class MailNotificationChannel implements NotificationChannel {
107
- send(notifiable: Notifiable$1, notification: Notification$1): Promise<void>;
153
+ send(notifiable: Notifiable, notification: Notification$1): Promise<void>;
108
154
  }
109
155
 
110
156
  declare const NOTIFICATIONS_TABLE = "notifications";
111
- interface DatabaseNotificationRecord {
112
- readonly id: string;
113
- readonly type: string;
114
- readonly data: Record<string, unknown>;
115
- readonly createdAt: Date;
116
- readonly updatedAt: Date;
117
- readAt: Date | undefined;
118
- markAsRead(): Promise<void>;
119
- markAsUnread(): Promise<void>;
120
- }
121
157
  /**
122
- * Database writes Dates as zoneless UTC (`'2026-09-25 04:30:00'`) and hands them back as strings, so
123
- * `new Date()` would read such a string as local time; it is parsed as UTC here instead.
158
+ * Laravel's DatabaseChannel: one row in `notifications` per notifiable, with what toDatabase() (or toArray())
159
+ * returns as JSON. The row is keyed as `user.notifications()` reads it: `notifiable_type` is the model's morph class
160
+ * (its morph map alias, else its class name) and it goes to the model's own connection.
124
161
  */
125
- declare function parseStoredDate(value: unknown): Date;
126
- /** notifiable_type + notifiable_id together, never id alone. */
127
- declare function fetchNotifications(notifiableType: string, notifiableId: unknown, filter?: 'all' | 'read' | 'unread'): Promise<DatabaseNotificationRecord[]>;
128
162
  declare class DatabaseNotificationChannel implements NotificationChannel {
129
- send(notifiable: Notifiable$1, notification: Notification$1): Promise<void>;
163
+ send(notifiable: Notifiable, notification: Notification$1): Promise<void>;
130
164
  }
131
165
 
132
166
  declare const NOTIFICATIONS_TABLE_MIGRATION = "import { Migration, Schema } from '@orieljs/framework'\n\nexport default class extends Migration {\n async up() {\n await Schema.create('notifications', (t) => {\n t.uuid('id').primary()\n t.string('type')\n t.morphs('notifiable')\n t.text('data')\n t.timestamp('read_at').nullable()\n t.timestamps()\n })\n }\n\n async down() {\n await Schema.dropIfExists('notifications')\n }\n}\n";
@@ -144,7 +178,7 @@ declare const SHOULD_QUEUE: unique symbol;
144
178
  * survives Queue's serializer (which rejects functions in own fields) and is restored on the worker as this class
145
179
  * (registered as `'Notifications.AnonymousNotifiable'` in ClassRegistry).
146
180
  */
147
- declare class AnonymousNotifiable implements Notifiable$1 {
181
+ declare class AnonymousNotifiable implements Notifiable {
148
182
  routes: Record<string, unknown>;
149
183
  /** Adds a route; chains, so one on-demand notifiable can go to several channels. */
150
184
  route(channel: string, route: unknown): this;
@@ -158,7 +192,7 @@ declare class AnonymousNotifiable implements Notifiable$1 {
158
192
  * a Set, ...), as Laravel's `Notification::send($users, ...)` accepts. A notifiable is recognised by its
159
193
  * routeNotificationFor() method first, so an iterable model is still treated as one notifiable.
160
194
  */
161
- declare function toNotifiableList(notifiables: Notifiable$1 | Iterable<Notifiable$1>): Notifiable$1[];
195
+ declare function toNotifiableList(notifiables: Notifiable | Iterable<Notifiable>): Notifiable[];
162
196
  /** Resolves channels by name from a factory map — mirrors MailManager's driver resolution. */
163
197
  declare class Dispatcher implements NotificationDispatcher {
164
198
  private readonly container;
@@ -167,8 +201,8 @@ declare class Dispatcher implements NotificationDispatcher {
167
201
  constructor(container: Container);
168
202
  extend(channel: string, factory: () => NotificationChannel): void;
169
203
  private resolve;
170
- send(notifiables: Notifiable$1 | Iterable<Notifiable$1>, notification: Notification$1): Promise<void>;
171
- sendNow(notifiables: Notifiable$1 | Iterable<Notifiable$1>, notification: Notification$1, channels?: string[]): Promise<void>;
204
+ send(notifiables: Notifiable | Iterable<Notifiable>, notification: Notification$1): Promise<void>;
205
+ sendNow(notifiables: Notifiable | Iterable<Notifiable>, notification: Notification$1, channels?: string[]): Promise<void>;
172
206
  /** On-demand: an AnonymousNotifiable, whose notify() goes through the bound NotificationDispatcher. */
173
207
  route(channel: string, route: unknown): AnonymousNotifiable;
174
208
  }
@@ -181,10 +215,10 @@ declare class Dispatcher implements NotificationDispatcher {
181
215
  * re-decides queued-or-now on whatever job it's handed — actually enqueues it instead of running it inline; the
182
216
  * notification's own queueability (checked by Dispatcher, above) only decides whether to wrap it at all. Like
183
217
  * Laravel's SendQueuedNotifications, it forwards failed(), retryUntil(), middleware() and displayName() to the
184
- * notification.
218
+ * notification. Unlike Laravel's, it is unique when the notification is (Laravel queues it past the unique lock).
185
219
  */
186
220
  declare class SendQueuedNotifications implements Job {
187
- readonly notifiables: Notifiable$1[];
221
+ readonly notifiables: Notifiable[];
188
222
  readonly notification: Notification$1;
189
223
  readonly channels?: string[] | undefined;
190
224
  connection?: string;
@@ -195,7 +229,7 @@ declare class SendQueuedNotifications implements Job {
195
229
  timeout?: number;
196
230
  failOnTimeout?: boolean;
197
231
  deleteWhenMissingModels?: boolean;
198
- constructor(notifiables: Notifiable$1[], notification: Notification$1, channels?: string[] | undefined);
232
+ constructor(notifiables: Notifiable[], notification: Notification$1, channels?: string[] | undefined);
199
233
  handle(): Promise<void>;
200
234
  /** Called by Queue after the final failed attempt. */
201
235
  failed(error: unknown): Promise<void>;
@@ -207,19 +241,17 @@ declare class SendQueuedNotifications implements Job {
207
241
 
208
242
  type NotifiableConstructor = new (...args: any[]) => object;
209
243
  /**
210
- * Mirrors Laravel's Illuminate\Notifications\Notifiable trait: `class User extends Notifiable(Model) {}`. Custom
211
- * routing per channel follows Laravel's own resolution order: a `routeNotificationFor<Channel>()` method (Studly
212
- * name) wins if present, else 'mail' reads `email`/`getAttribute('email')`, 'database' returns the instance itself
213
- * (the database channel derives the table row from it directly), and anything else is unrouted (`undefined`).
244
+ * Laravel's RoutesNotifications trait: `notify()`, `notifyNow()` and `routeNotificationFor()`. Apps use
245
+ * `Notifiable` from '@orieljs/framework', which adds the database notifications relations. Custom routing per
246
+ * channel follows Laravel's order: a `routeNotificationFor<Channel>()` method (Studly name) wins if present, else
247
+ * 'mail' reads `email`/`getAttribute('email')`, 'database' returns the instance itself (the database channel derives
248
+ * the row from it), and anything else is unrouted (`undefined`).
214
249
  */
215
- declare function Notifiable<TBase extends NotifiableConstructor>(Base: TBase): {
250
+ declare function RoutesNotifications<TBase extends NotifiableConstructor>(Base: TBase): {
216
251
  new (...args: any[]): {
217
252
  routeNotificationFor(channel: string, notification: Notification$1): unknown;
218
253
  notify(notification: Notification$1): Promise<void>;
219
254
  notifyNow(notification: Notification$1, channels?: string[]): Promise<void>;
220
- notifications(): Promise<DatabaseNotificationRecord[]>;
221
- unreadNotifications(): Promise<DatabaseNotificationRecord[]>;
222
- readNotifications(): Promise<DatabaseNotificationRecord[]>;
223
255
  };
224
256
  } & TBase;
225
257
 
@@ -232,12 +264,12 @@ declare function Notifiable<TBase extends NotifiableConstructor>(Base: TBase): {
232
264
  */
233
265
  declare abstract class Notification implements Notification$1 {
234
266
  id?: string;
235
- abstract via(notifiable: Notifiable$1): string[];
267
+ abstract via(notifiable: Notifiable): string[];
236
268
  /** Queues it when the notification is queueable, otherwise sends now. Accepts one notifiable, an array or any iterable. */
237
- static send(notifiables: Notifiable$1 | Iterable<Notifiable$1>, notification: Notification$1): Promise<void>;
238
- static sendNow(notifiables: Notifiable$1 | Iterable<Notifiable$1>, notification: Notification$1, channels?: string[]): Promise<void>;
269
+ static send(notifiables: Notifiable | Iterable<Notifiable>, notification: Notification$1): Promise<void>;
270
+ static sendNow(notifiables: Notifiable | Iterable<Notifiable>, notification: Notification$1, channels?: string[]): Promise<void>;
239
271
  /** On-demand: `Notification.route('mail', 'a@b.c').notify(new InvoicePaid())`. */
240
- static route(channel: string, route: unknown): Notifiable$1 & {
272
+ static route(channel: string, route: unknown): Notifiable & {
241
273
  notify(notification: Notification$1): Promise<void>;
242
274
  };
243
275
  static extend(channel: string, factory: () => NotificationChannel): void;
@@ -257,4 +289,4 @@ declare class NotificationsServiceProvider extends ServiceProvider {
257
289
  boot(): void;
258
290
  }
259
291
 
260
- export { AnonymousNotifiable, DatabaseNotificationChannel, type DatabaseNotificationRecord, DatabaseUnavailableError, Dispatcher, MailMessage, type MailMessageAction, MailNotificationChannel, MailUnavailableError, NOTIFICATIONS_TABLE, NOTIFICATIONS_TABLE_MIGRATION, NotDatabaseNotifiableError, Notifiable, type NotifiableConstructor, Notification, NotificationDispatcherUnavailableError, NotificationFacade, NotificationQueueUnavailableError, NotificationsServiceProvider, type PlainNotificationMail, SHOULD_QUEUE, SendQueuedNotifications, UnknownNotificationChannelError, UnrenderableNotificationDataError, UnrenderableNotificationMailError, fetchNotifications, parseStoredDate, toNotifiableList };
292
+ export { AnonymousNotifiable, DatabaseNotificationChannel, DatabaseUnavailableError, Dispatcher, type MailLevel, MailMessage, type MailMessageAction, MailNotificationChannel, MailUnavailableError, NOTIFICATIONS_TABLE, NOTIFICATIONS_TABLE_MIGRATION, NotDatabaseNotifiableError, type NotifiableConstructor, Notification, NotificationDispatcherUnavailableError, NotificationFacade, NotificationQueueUnavailableError, NotificationsServiceProvider, type PlainNotificationMail, RoutesNotifications, SHOULD_QUEUE, SendQueuedNotifications, UnknownNotificationChannelError, UnrenderableNotificationDataError, UnrenderableNotificationMailError, toNotifiableList };
package/dist/index.js CHANGED
@@ -66,18 +66,214 @@ var NotificationDispatcherUnavailableError = class extends Error {
66
66
  }
67
67
  };
68
68
 
69
+ // MailMessage.ts
70
+ import { ConfigRepository } from "@orieljs/contracts";
71
+ import { currentContainer } from "@orieljs/container";
72
+
73
+ // mail/template.ts
74
+ var BASE = "box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; position: relative;";
75
+ var style = (rules = "") => `style="${BASE}${rules === "" ? "" : ` ${rules}`}"`;
76
+ var PREMAILER_FULL = "-premailer-cellpadding: 0; -premailer-cellspacing: 0; -premailer-width: 100%;";
77
+ var PREMAILER_570 = "-premailer-cellpadding: 0; -premailer-cellspacing: 0; -premailer-width: 570px;";
78
+ var PARAGRAPH = style("font-size: 16px; line-height: 1.5em; margin-top: 0; text-align: left;");
79
+ var BUTTON_COLORS = {
80
+ info: { name: "primary", color: "#2d3748" },
81
+ success: { name: "success", color: "#48bb78" },
82
+ error: { name: "error", color: "#e53e3e" }
83
+ };
84
+ var text = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
85
+ var attribute = (value) => text(value).replace(/"/g, "&quot;");
86
+ var displayable = (url) => url.replace(/mailto:|tel:/g, "");
87
+ var isUnsafeLink = (url) => {
88
+ const destination = url.replace(/^[\x00-\x20]+/, "").replace(/[\t\n\r]/g, "");
89
+ return /^javascript:|vbscript:|file:|data:/i.test(destination) && !/^data:image\/(?:png|gif|jpeg|webp)/i.test(destination);
90
+ };
91
+ var HEAD = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
92
+ <html xmlns="http://www.w3.org/1999/xhtml">
93
+ <head>
94
+ <title>{{title}}</title>
95
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
96
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
97
+ <meta name="color-scheme" content="light">
98
+ <meta name="supported-color-schemes" content="light">
99
+ <style>
100
+ @media only screen and (max-width: 600px) {
101
+ .inner-body {
102
+ width: 100% !important;
103
+ }
104
+
105
+ .footer {
106
+ width: 100% !important;
107
+ }
108
+ }
109
+
110
+ @media only screen and (max-width: 500px) {
111
+ .button {
112
+ width: 100% !important;
113
+ }
114
+ }
115
+ </style>
116
+
117
+ </head>`;
118
+ function button(label, url, level) {
119
+ const { name, color } = BUTTON_COLORS[level];
120
+ const look = `-webkit-text-size-adjust: none; border-radius: 4px; color: #fff; display: inline-block; overflow: hidden; text-decoration: none; background-color: ${color}; border-bottom: 8px solid ${color}; border-left: 18px solid ${color}; border-right: 18px solid ${color}; border-top: 8px solid ${color}; word-break: break-all;`;
121
+ return [
122
+ `<table class="action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation" ${style(`${PREMAILER_FULL} margin: 30px auto; padding: 0; text-align: center; width: 100%; float: unset;`)}>`,
123
+ "<tr>",
124
+ `<td align="center" ${style()}>`,
125
+ `<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation" ${style()}>`,
126
+ "<tr>",
127
+ `<td align="center" ${style()}>`,
128
+ `<table border="0" cellpadding="0" cellspacing="0" role="presentation" ${style()}>`,
129
+ "<tr>",
130
+ `<td ${style()}>`,
131
+ `<a href="${attribute(url)}" class="button button-${name}" target="_blank" rel="noopener" ${style(look)}>${text(label)}</a>`,
132
+ "</td>",
133
+ "</tr>",
134
+ "</table>",
135
+ "</td>",
136
+ "</tr>",
137
+ "</table>",
138
+ "</td>",
139
+ "</tr>",
140
+ "</table>"
141
+ ].join("\n");
142
+ }
143
+ function subcopy(label, url) {
144
+ const link = `<a ${isUnsafeLink(url) ? "" : `href="${attribute(url)}" `}${style("color: #3869d4; word-break: break-all;")}>${text(displayable(url))}</a>`;
145
+ return [
146
+ `<table class="subcopy" width="100%" cellpadding="0" cellspacing="0" role="presentation" ${style("border-top: 1px solid #e8e5ef; margin-top: 25px; padding-top: 25px;")}>`,
147
+ "<tr>",
148
+ `<td ${style()}>`,
149
+ `<p ${style("line-height: 1.5em; margin-top: 0; text-align: left; font-size: 14px;")}>If you're having trouble clicking the "${text(label)}" button, copy and paste the URL below`,
150
+ `into your web browser: <span class="break-all" ${style("word-break: break-all;")}>${link}</span></p>`,
151
+ "",
152
+ "</td>",
153
+ "</tr>",
154
+ "</table>"
155
+ ].join("\n");
156
+ }
157
+ function greetingOf(content) {
158
+ if (content.greeting !== void 0 && content.greeting !== "") return content.greeting;
159
+ return content.level === "error" ? "Whoops!" : "Hello!";
160
+ }
161
+ function hasAction(content) {
162
+ return content.actionText !== void 0 && content.actionUrl !== void 0;
163
+ }
164
+ function html(content) {
165
+ const salutation = content.salutation === void 0 || content.salutation === "" ? `Regards,<br>
166
+ ${text(content.appName)}` : text(content.salutation).replace(/\r?\n/g, "<br>\n");
167
+ const body = [
168
+ `<h1 ${style("color: #3d4852; font-size: 18px; font-weight: bold; margin-top: 0; text-align: left;")}>${text(greetingOf(content))}</h1>`,
169
+ ...content.introLines.map((line) => `<p ${PARAGRAPH}>${text(line)}</p>`),
170
+ ...hasAction(content) ? [button(content.actionText, content.actionUrl, content.level)] : [],
171
+ ...content.outroLines.map((line) => `<p ${PARAGRAPH}>${text(line)}</p>`),
172
+ `<p ${PARAGRAPH}>${salutation}</p>`
173
+ ].join("\n");
174
+ const after = hasAction(content) ? `${subcopy(content.actionText, content.actionUrl)}
175
+ ` : "\n";
176
+ return [
177
+ HEAD.replace("{{title}}", () => text(content.appName)),
178
+ `<body ${style("-webkit-text-size-adjust: none; background-color: #ffffff; color: #718096; height: 100%; line-height: 1.4; margin: 0; padding: 0; width: 100% !important;")}>`,
179
+ "",
180
+ `<table class="wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation" ${style(`${PREMAILER_FULL} background-color: #edf2f7; margin: 0; padding: 0; width: 100%;`)}>`,
181
+ "<tr>",
182
+ `<td align="center" ${style()}>`,
183
+ `<table class="content" width="100%" cellpadding="0" cellspacing="0" role="presentation" ${style(`${PREMAILER_FULL} margin: 0; padding: 0; width: 100%;`)}>`,
184
+ "<tr>",
185
+ `<td class="header" ${style("padding: 25px 0; text-align: center;")}>`,
186
+ `<a href="${attribute(content.appUrl)}" ${style("color: #3d4852; font-size: 19px; font-weight: bold; text-decoration: none; display: inline-block;")}>`,
187
+ text(content.appName),
188
+ "</a>",
189
+ "</td>",
190
+ "</tr>",
191
+ "",
192
+ "<!-- Email Body -->",
193
+ "<tr>",
194
+ `<td class="body" width="100%" cellpadding="0" cellspacing="0" ${style(`${PREMAILER_FULL} background-color: #edf2f7; border-bottom: 1px solid #edf2f7; border-top: 1px solid #edf2f7; margin: 0; padding: 0; width: 100%; border: hidden !important;`)}>`,
195
+ `<table class="inner-body" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation" ${style(`${PREMAILER_570} background-color: #ffffff; border-color: #e8e5ef; border-radius: 2px; border-width: 1px; box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015); margin: 0 auto; padding: 0; width: 570px;`)}>`,
196
+ "<!-- Body content -->",
197
+ "<tr>",
198
+ `<td class="content-cell" ${style("max-width: 100vw; padding: 32px;")}>`,
199
+ `${body}
200
+
201
+
202
+ ${after}</td>`,
203
+ "</tr>",
204
+ "</table>",
205
+ "</td>",
206
+ "</tr>",
207
+ "",
208
+ "<tr>",
209
+ `<td ${style()}>`,
210
+ `<table class="footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation" ${style(`${PREMAILER_570} margin: 0 auto; padding: 0; text-align: center; width: 570px;`)}>`,
211
+ "<tr>",
212
+ `<td class="content-cell" align="center" ${style("max-width: 100vw; padding: 32px;")}>`,
213
+ `<p ${style("line-height: 1.5em; margin-top: 0; color: #b0adc5; font-size: 12px; text-align: center;")}>\xA9 ${content.year} ${text(content.appName)}. All rights reserved.</p>`,
214
+ "",
215
+ "</td>",
216
+ "</tr>",
217
+ "</table>",
218
+ "</td>",
219
+ "</tr>",
220
+ "</table>",
221
+ "</td>",
222
+ "</tr>",
223
+ "</table>",
224
+ "</body>",
225
+ "</html>"
226
+ ].join("\n");
227
+ }
228
+ function plain(content) {
229
+ const parts = [`${content.appName}: ${content.appUrl}`, `# ${greetingOf(content)}`, ...content.introLines];
230
+ if (hasAction(content)) parts.push(`${content.actionText}: ${content.actionUrl}`);
231
+ parts.push(...content.outroLines);
232
+ parts.push(content.salutation === void 0 || content.salutation === "" ? `Regards,
233
+ ${content.appName}` : content.salutation);
234
+ if (hasAction(content)) {
235
+ parts.push(
236
+ `If you're having trouble clicking the "${content.actionText}" button, copy and paste the URL below
237
+ into your web browser: [${displayable(content.actionUrl)}](${content.actionUrl})`
238
+ );
239
+ }
240
+ parts.push(`\xA9 ${content.year} ${content.appName}. All rights reserved.`);
241
+ return `${parts.join("\n\n")}
242
+ `;
243
+ }
244
+ function renderNotificationMail(content) {
245
+ return { html: html(content), text: plain(content) };
246
+ }
247
+
69
248
  // MailMessage.ts
70
249
  var MailMessage = class {
250
+ /** info, success or error: the colour of the button, and error() greets with "Whoops!". Laravel's `$level`. */
251
+ levelName = "info";
71
252
  subjectLine;
72
253
  greetingLine;
73
254
  introLines = [];
74
255
  actionData;
75
256
  outroLines = [];
76
- salutationLine = "Regards,\nApplication";
257
+ /** Unset: "Regards," and config('app.name'), as Laravel's template. */
258
+ salutationLine;
77
259
  toAddresses = [];
78
260
  ccAddresses = [];
79
261
  bccAddresses = [];
262
+ replyToAddresses = [];
80
263
  fromAddress;
264
+ attachments = [];
265
+ /** A green button: something went well. */
266
+ success() {
267
+ return this.level("success");
268
+ }
269
+ /** A red button, and "Whoops!" when there is no greeting: something went wrong. */
270
+ error() {
271
+ return this.level("error");
272
+ }
273
+ level(level) {
274
+ this.levelName = level;
275
+ return this;
276
+ }
81
277
  subject(subject) {
82
278
  this.subjectLine = subject;
83
279
  return this;
@@ -87,17 +283,25 @@ var MailMessage = class {
87
283
  return this;
88
284
  }
89
285
  /** Before action(), appends to the intro; after action(), to the outro. */
90
- line(text) {
286
+ line(text2) {
91
287
  ;
92
- (this.actionData ? this.outroLines : this.introLines).push(text);
288
+ (this.actionData ? this.outroLines : this.introLines).push(text2);
93
289
  return this;
94
290
  }
95
291
  lines(lines) {
96
292
  for (const line of lines) this.line(line);
97
293
  return this;
98
294
  }
99
- action(text, url) {
100
- this.actionData = { text, url };
295
+ /** line(), when the condition holds. */
296
+ lineIf(condition, text2) {
297
+ return condition ? this.line(text2) : this;
298
+ }
299
+ /** lines(), when the condition holds. */
300
+ linesIf(condition, lines) {
301
+ return condition ? this.lines(lines) : this;
302
+ }
303
+ action(text2, url) {
304
+ this.actionData = { text: text2, url };
101
305
  return this;
102
306
  }
103
307
  salutation(salutation) {
@@ -120,38 +324,48 @@ var MailMessage = class {
120
324
  this.bccAddresses.push(...Array.isArray(recipients) ? recipients : [recipients]);
121
325
  return this;
122
326
  }
123
- /** Renders the default notification mail template. No ViewFactory involved. */
327
+ replyTo(recipients) {
328
+ this.replyToAddresses.push(...Array.isArray(recipients) ? recipients : [recipients]);
329
+ return this;
330
+ }
331
+ /** Attaches a file by its path: `as` names it in the mail (its file name by default), `mime` gives its type. */
332
+ attach(path, options = {}) {
333
+ this.attachments.push({ name: options.as ?? path.slice(Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + 1), path, ...options.mime === void 0 ? {} : { mime: options.mime } });
334
+ return this;
335
+ }
336
+ /** Attaches bytes or text under a file name. */
337
+ attachData(data, name, options = {}) {
338
+ this.attachments.push({ name, data, ...options.mime === void 0 ? {} : { mime: options.mime } });
339
+ return this;
340
+ }
341
+ /** Renders Laravel's default notification mail, HTML and text. No ViewFactory involved. */
124
342
  render() {
125
- const greeting = this.greetingLine ?? "Hello!";
126
- const html = [`<p>${escapeHtml(greeting)}</p>`];
127
- const text = [greeting];
128
- for (const line of this.introLines) {
129
- html.push(`<p>${escapeHtml(line)}</p>`);
130
- text.push(line);
131
- }
132
- if (this.actionData) {
133
- const { text: actionText, url } = this.actionData;
134
- html.push(`<p><a href="${escapeHtml(url)}" class="button" target="_blank">${escapeHtml(actionText)}</a></p>`);
135
- text.push(`${actionText}: ${url}`);
136
- }
137
- for (const line of this.outroLines) {
138
- html.push(`<p>${escapeHtml(line)}</p>`);
139
- text.push(line);
140
- }
141
- html.push(`<p>${escapeHtml(this.salutationLine).replace(/\n/g, "<br>")}</p>`);
142
- text.push(this.salutationLine);
143
- return { html: `<!doctype html><html><body>${html.join("\n")}</body></html>`, text: text.join("\n\n") };
343
+ return renderNotificationMail({
344
+ level: this.levelName,
345
+ greeting: this.greetingLine,
346
+ introLines: this.introLines,
347
+ actionText: this.actionData?.text,
348
+ actionUrl: this.actionData?.url,
349
+ outroLines: this.outroLines,
350
+ salutation: this.salutationLine,
351
+ appName: appConfig("app.name", "Oriel"),
352
+ appUrl: appConfig("app.url", "http://localhost"),
353
+ year: (/* @__PURE__ */ new Date()).getFullYear()
354
+ });
144
355
  }
145
356
  };
146
- function escapeHtml(value) {
147
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
357
+ function appConfig(key, fallback) {
358
+ const container = currentContainer();
359
+ const value = container?.bound(ConfigRepository) ? container.make(ConfigRepository).get(key) : void 0;
360
+ return typeof value === "string" && value !== "" ? value : fallback;
148
361
  }
149
362
 
150
363
  // channels/MailNotificationChannel.ts
151
364
  import {
152
365
  MailManager as MailManagerToken
153
366
  } from "@orieljs/contracts";
154
- import { currentContainer } from "@orieljs/container";
367
+ import { currentContainer as currentContainer2 } from "@orieljs/container";
368
+ import { Str } from "@orieljs/support";
155
369
  function isMailable(value) {
156
370
  return typeof value === "object" && value !== null && typeof value.envelope === "function" && typeof value.content === "function";
157
371
  }
@@ -170,19 +384,21 @@ function toMailMessage(result, notificationName) {
170
384
  if (result.salutation) message.salutation(result.salutation);
171
385
  return message;
172
386
  }
173
- function mailableFromMessage(message, fallbackTo) {
387
+ function mailableFromMessage(message, fallbackTo, notificationName) {
174
388
  return {
175
389
  envelope: async () => ({
176
- subject: message.subjectLine ?? "",
390
+ subject: message.subjectLine || Str.title(Str.snake(notificationName, " ")),
177
391
  to: message.toAddresses.length > 0 ? message.toAddresses : fallbackTo,
178
392
  cc: message.ccAddresses,
179
393
  bcc: message.bccAddresses,
394
+ replyTo: message.replyToAddresses,
180
395
  from: message.fromAddress
181
396
  }),
182
397
  content: async () => {
183
398
  const rendered = message.render();
184
399
  return { htmlString: rendered.html, textString: rendered.text };
185
- }
400
+ },
401
+ attachments: async () => message.attachments
186
402
  };
187
403
  }
188
404
  function normalizeRoute(route) {
@@ -195,10 +411,10 @@ var MailNotificationChannel = class {
195
411
  const result = await notification.toMail(notifiable);
196
412
  const to = normalizeRoute(notifiable.routeNotificationFor("mail", notification));
197
413
  if (to.length === 0 && !isMailable(result)) return;
198
- const mailable = isMailable(result) ? result : mailableFromMessage(toMailMessage(result, notification.constructor.name), to);
199
- const container = currentContainer();
414
+ const mailable = isMailable(result) ? result : mailableFromMessage(toMailMessage(result, notification.constructor.name), to, notification.constructor.name);
415
+ const container = currentContainer2();
200
416
  if (!container?.bound(MailManagerToken)) throw new MailUnavailableError();
201
- await container.make(MailManagerToken).send(mailable);
417
+ await container.make(MailManagerToken).sendNow(mailable);
202
418
  }
203
419
  };
204
420
 
@@ -207,49 +423,12 @@ import { randomUUID } from "crypto";
207
423
  import {
208
424
  DatabaseManager as DatabaseManagerToken
209
425
  } from "@orieljs/contracts";
210
- import { currentContainer as currentContainer2 } from "@orieljs/container";
426
+ import { currentContainer as currentContainer3 } from "@orieljs/container";
211
427
  var NOTIFICATIONS_TABLE = "notifications";
212
- var ZONELESS_TIMESTAMP = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/;
213
- function parseStoredDate(value) {
214
- if (value instanceof Date) return value;
215
- if (typeof value === "number") return new Date(value);
216
- const text = String(value);
217
- return ZONELESS_TIMESTAMP.test(text) ? /* @__PURE__ */ new Date(`${text.replace(" ", "T")}Z`) : new Date(text);
218
- }
219
- function wrapRow(row, db) {
220
- const record = {
221
- id: String(row.id),
222
- type: String(row.type),
223
- data: JSON.parse(String(row.data)),
224
- createdAt: parseStoredDate(row.created_at),
225
- updatedAt: parseStoredDate(row.updated_at),
226
- readAt: row.read_at ? parseStoredDate(row.read_at) : void 0,
227
- async markAsRead() {
228
- if (record.readAt) return;
229
- const now = /* @__PURE__ */ new Date();
230
- await db.table(NOTIFICATIONS_TABLE).where("id", record.id).update({ read_at: now, updated_at: now });
231
- record.readAt = now;
232
- },
233
- async markAsUnread() {
234
- if (!record.readAt) return;
235
- await db.table(NOTIFICATIONS_TABLE).where("id", record.id).update({ read_at: null, updated_at: /* @__PURE__ */ new Date() });
236
- record.readAt = void 0;
237
- }
238
- };
239
- return record;
240
- }
241
- function requireDatabase() {
242
- const container = currentContainer2();
428
+ function requireDatabase(connection) {
429
+ const container = currentContainer3();
243
430
  if (!container?.bound(DatabaseManagerToken)) throw new DatabaseUnavailableError();
244
- return container.make(DatabaseManagerToken).connection();
245
- }
246
- async function fetchNotifications(notifiableType, notifiableId, filter = "all") {
247
- const db = requireDatabase();
248
- let query = db.table(NOTIFICATIONS_TABLE).where("notifiable_type", notifiableType).where("notifiable_id", notifiableId).orderBy("created_at", "desc");
249
- if (filter === "unread") query = query.whereNull("read_at");
250
- if (filter === "read") query = query.whereNotNull("read_at");
251
- const rows = await query.get();
252
- return rows.all().map((row) => wrapRow(row, db));
431
+ return container.make(DatabaseManagerToken).connection(connection);
253
432
  }
254
433
  var DatabaseNotificationChannel = class {
255
434
  async send(notifiable, notification) {
@@ -259,12 +438,12 @@ var DatabaseNotificationChannel = class {
259
438
  const data = notification.toDatabase ? notification.toDatabase(notifiable) : notification.toArray(notifiable);
260
439
  const model = notifiable;
261
440
  if (typeof model.getKey !== "function") throw new NotDatabaseNotifiableError(notifiable.constructor.name);
262
- const db = requireDatabase();
441
+ const db = requireDatabase(typeof model.getConnectionName === "function" ? model.getConnectionName() : void 0);
263
442
  const now = /* @__PURE__ */ new Date();
264
443
  await db.table(NOTIFICATIONS_TABLE).insert({
265
444
  id: notification.id ?? randomUUID(),
266
445
  type: notification.constructor.name,
267
- notifiable_type: notifiable.constructor.name,
446
+ notifiable_type: typeof model.getMorphClass === "function" ? model.getMorphClass() : notifiable.constructor.name,
268
447
  notifiable_id: model.getKey(),
269
448
  data: JSON.stringify(data),
270
449
  read_at: null,
@@ -297,6 +476,20 @@ export default class extends Migration {
297
476
 
298
477
  // symbols.ts
299
478
  var SHOULD_QUEUE = /* @__PURE__ */ Symbol.for("oriel:bus.shouldQueue");
479
+ var UNIQUE = /* @__PURE__ */ Symbol.for("oriel:bus.unique");
480
+ function uniqueSpecOf(target, name) {
481
+ const members = target;
482
+ const declared = typeof members.uniqueId === "function" || members.uniqueFor !== void 0 || members.uniqueUntilProcessing === true || typeof members.uniqueVia === "function";
483
+ if (!declared) return void 0;
484
+ const id = typeof members.uniqueId === "function" ? members.uniqueId.call(target) : void 0;
485
+ const spec = { name, id: id === void 0 || id === null ? "" : String(id) };
486
+ const uniqueFor = typeof members.uniqueFor === "function" ? members.uniqueFor.call(target) : members.uniqueFor;
487
+ if (typeof uniqueFor === "number") spec.for = uniqueFor;
488
+ if (members.uniqueUntilProcessing === true) spec.untilProcessing = true;
489
+ const via = typeof members.uniqueVia === "function" ? members.uniqueVia.call(target) : void 0;
490
+ if (typeof via === "string") spec.via = via;
491
+ return spec;
492
+ }
300
493
 
301
494
  // Dispatcher.ts
302
495
  import { randomUUID as randomUUID2 } from "crypto";
@@ -304,26 +497,21 @@ import {
304
497
  BusDispatcher as BusDispatcherToken
305
498
  } from "@orieljs/contracts";
306
499
 
307
- // Notifiable.ts
500
+ // RoutesNotifications.ts
308
501
  import {
309
502
  NotificationDispatcher as NotificationDispatcherToken
310
503
  } from "@orieljs/contracts";
311
- import { currentContainer as currentContainer3 } from "@orieljs/container";
312
- import { Str } from "@orieljs/support";
504
+ import { currentContainer as currentContainer4 } from "@orieljs/container";
505
+ import { Str as Str2 } from "@orieljs/support";
313
506
  function resolveDispatcher() {
314
- const container = currentContainer3();
507
+ const container = currentContainer4();
315
508
  if (!container?.bound(NotificationDispatcherToken)) throw new NotificationDispatcherUnavailableError();
316
509
  return container.make(NotificationDispatcherToken);
317
510
  }
318
- function requireKey(instance) {
319
- const model = instance;
320
- if (typeof model.getKey !== "function") throw new NotDatabaseNotifiableError(instance.constructor.name);
321
- return model.getKey();
322
- }
323
- function Notifiable(Base) {
324
- return class NotifiableMixin extends Base {
511
+ function RoutesNotifications(Base) {
512
+ return class RoutesNotificationsMixin extends Base {
325
513
  routeNotificationFor(channel, notification) {
326
- const custom = this[`routeNotificationFor${Str.studly(channel)}`];
514
+ const custom = this[`routeNotificationFor${Str2.studly(channel)}`];
327
515
  if (typeof custom === "function") return custom.call(this, notification);
328
516
  if (channel === "mail") {
329
517
  const withAttrs = this;
@@ -338,15 +526,6 @@ function Notifiable(Base) {
338
526
  async notifyNow(notification, channels) {
339
527
  await resolveDispatcher().sendNow(this, notification, channels);
340
528
  }
341
- notifications() {
342
- return fetchNotifications(this.constructor.name, requireKey(this));
343
- }
344
- unreadNotifications() {
345
- return fetchNotifications(this.constructor.name, requireKey(this), "unread");
346
- }
347
- readNotifications() {
348
- return fetchNotifications(this.constructor.name, requireKey(this), "read");
349
- }
350
529
  };
351
530
  }
352
531
 
@@ -370,6 +549,7 @@ var AnonymousNotifiable = class {
370
549
  };
371
550
 
372
551
  // Jobs/SendQueuedNotifications.ts
552
+ import { createHash } from "crypto";
373
553
  import { NotificationDispatcher as NotificationDispatcherToken2 } from "@orieljs/contracts";
374
554
  import { inject } from "@orieljs/container";
375
555
  var QUEUE_OPTIONS = [
@@ -388,12 +568,25 @@ function copyShouldQueueFields(target, source) {
388
568
  if (fields[option] !== void 0) target[option] = fields[option];
389
569
  }
390
570
  }
571
+ function notifiableIdentity(notifiable, channel, notification) {
572
+ const model = notifiable;
573
+ if (typeof model.getKey === "function") return `${model.getMorphClass?.() ?? notifiable.constructor.name}:${String(model.getKey())}`;
574
+ const route = channel === void 0 ? void 0 : notifiable.routeNotificationFor(channel, notification);
575
+ if (route === void 0 || route === null) return notifiable.constructor.name;
576
+ return createHash("sha1").update(typeof route === "string" ? route : JSON.stringify(route)).digest("hex");
577
+ }
391
578
  var SendQueuedNotifications = class {
392
579
  constructor(notifiables, notification, channels) {
393
580
  this.notifiables = notifiables;
394
581
  this.notification = notification;
395
582
  this.channels = channels;
396
583
  copyShouldQueueFields(this, notification);
584
+ const unique = uniqueSpecOf(notification, notification.constructor.name);
585
+ if (unique) {
586
+ const recipients = notifiables.map((notifiable) => notifiableIdentity(notifiable, channels?.[0], notification));
587
+ unique.id = [unique.id, channels?.join(",") ?? "", recipients.join(",")].join(":");
588
+ this[UNIQUE] = unique;
589
+ }
397
590
  }
398
591
  notifiables;
399
592
  notification;
@@ -424,7 +617,7 @@ SendQueuedNotifications.prototype[SHOULD_QUEUE] = true;
424
617
  // Dispatcher.ts
425
618
  function isQueueableNotification(notification) {
426
619
  const record = notification;
427
- if (record[SHOULD_QUEUE] === true) return true;
620
+ if (record.shouldQueue === true || record[SHOULD_QUEUE] === true) return true;
428
621
  return record.connection !== void 0 || record.queue !== void 0 || record.delay !== void 0 || record.tries !== void 0 || record.backoff !== void 0 || record.timeout !== void 0 || typeof record.uniqueId === "function" || typeof record.retryUntil === "function";
429
622
  }
430
623
  function forNotifiable(notification) {
@@ -553,19 +746,37 @@ export {
553
746
  NOTIFICATIONS_TABLE,
554
747
  NOTIFICATIONS_TABLE_MIGRATION,
555
748
  NotDatabaseNotifiableError,
556
- Notifiable,
557
749
  Notification,
558
750
  NotificationDispatcherUnavailableError,
559
751
  NotificationFacade,
560
752
  NotificationQueueUnavailableError,
561
753
  NotificationsServiceProvider,
754
+ RoutesNotifications,
562
755
  SHOULD_QUEUE,
563
756
  SendQueuedNotifications,
564
757
  UnknownNotificationChannelError,
565
758
  UnrenderableNotificationDataError,
566
759
  UnrenderableNotificationMailError,
567
- fetchNotifications,
568
- parseStoredDate,
569
760
  toNotifiableList
570
761
  };
762
+ /*!
763
+ * Laravel's default notification mail (the `notifications::email` view in the `mail::` markdown components with the
764
+ * `default` theme), as Laravel 12 sends it: the same HTML, with the theme's CSS inlined as CssToInlineStyles inlines
765
+ * it, and the same plain-text part. The lines are plain text (escaped), not Markdown.
766
+ *
767
+ * laravel/framework: Copyright (c) Taylor Otwell
768
+ *
769
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
770
+ * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
771
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
772
+ * permit persons to whom the Software is furnished to do so, subject to the following conditions:
773
+ *
774
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
775
+ * Software.
776
+ *
777
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
778
+ * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
779
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
780
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
781
+ */
571
782
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../errors.ts","../MailMessage.ts","../channels/MailNotificationChannel.ts","../channels/DatabaseNotificationChannel.ts","../migrations.ts","../symbols.ts","../Dispatcher.ts","../Notifiable.ts","../AnonymousNotifiable.ts","../Jobs/SendQueuedNotifications.ts","../Notification.ts","../facade.ts","../NotificationsServiceProvider.ts"],"sourcesContent":["import type { OrielError } from '@orieljs/contracts'\n\nexport class UnrenderableNotificationMailError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'UnrenderableNotificationMailError'\n constructor(notification: string) {\n super(\n `[${notification}].toMail() must return a Mailable, a MailMessage, or ` +\n '{ subject?, greeting?, lines, actionText?, actionUrl?, outroLines?, salutation? }.',\n )\n }\n}\n\nexport class MailUnavailableError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'MailUnavailableError'\n constructor() {\n super(\"Sending a notification's mail requires the Mail component. Register MailManager first.\")\n }\n}\n\nexport class DatabaseUnavailableError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'DatabaseUnavailableError'\n constructor() {\n super('The database notification channel requires the Database component. Register DatabaseManager first.')\n }\n}\n\nexport class NotDatabaseNotifiableError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'NotDatabaseNotifiableError'\n constructor(notifiable: string) {\n super(`[${notifiable}] has no getKey() — the database channel and notification-listing methods need a model.`)\n }\n}\n\nexport class UnrenderableNotificationDataError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'UnrenderableNotificationDataError'\n constructor(notification: string) {\n super(`[${notification}] needs a toDatabase() or toArray() method to use the database channel.`)\n }\n}\n\nexport class UnknownNotificationChannelError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'UnknownNotificationChannelError'\n constructor(channel: string) {\n super(`Notification channel [${channel}] is not registered. Register it first with NotificationDispatcher.extend().`)\n }\n}\n\nexport class NotificationQueueUnavailableError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'NotificationQueueUnavailableError'\n constructor() {\n super('Queueing a notification requires the Bus component. Register BusDispatcher, or call sendNow() instead.')\n }\n}\n\nexport class NotificationDispatcherUnavailableError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'NotificationDispatcherUnavailableError'\n constructor() {\n super('notify()/notifyNow() require the Notifications component. Register NotificationDispatcher first.')\n }\n}\n","import type { MailRecipient } from '@orieljs/contracts'\n\nexport interface MailMessageAction {\n text: string\n url: string\n}\n\n/**\n * Laravel's Illuminate\\Notifications\\Messages\\MailMessage: greeting/line/action/salutation, rendered to a default\n * HTML+text template with no View involved. line() appends to the intro before action() is called, and to the\n * outro afterwards — exactly like Laravel's SimpleMessage.\n */\nexport class MailMessage {\n subjectLine?: string\n greetingLine?: string\n introLines: string[] = []\n actionData?: MailMessageAction\n outroLines: string[] = []\n salutationLine = 'Regards,\\nApplication'\n toAddresses: MailRecipient[] = []\n ccAddresses: MailRecipient[] = []\n bccAddresses: MailRecipient[] = []\n fromAddress?: MailRecipient\n\n subject(subject: string): this {\n this.subjectLine = subject\n return this\n }\n\n greeting(greeting: string): this {\n this.greetingLine = greeting\n return this\n }\n\n /** Before action(), appends to the intro; after action(), to the outro. */\n line(text: string): this {\n ;(this.actionData ? this.outroLines : this.introLines).push(text)\n return this\n }\n\n lines(lines: string[]): this {\n for (const line of lines) this.line(line)\n return this\n }\n\n action(text: string, url: string): this {\n this.actionData = { text, url }\n return this\n }\n\n salutation(salutation: string): this {\n this.salutationLine = salutation\n return this\n }\n\n from(address: MailRecipient): this {\n this.fromAddress = address\n return this\n }\n\n to(recipients: MailRecipient | MailRecipient[]): this {\n this.toAddresses.push(...(Array.isArray(recipients) ? recipients : [recipients]))\n return this\n }\n\n cc(recipients: MailRecipient | MailRecipient[]): this {\n this.ccAddresses.push(...(Array.isArray(recipients) ? recipients : [recipients]))\n return this\n }\n\n bcc(recipients: MailRecipient | MailRecipient[]): this {\n this.bccAddresses.push(...(Array.isArray(recipients) ? recipients : [recipients]))\n return this\n }\n\n /** Renders the default notification mail template. No ViewFactory involved. */\n render(): { html: string; text: string } {\n const greeting = this.greetingLine ?? 'Hello!'\n const html: string[] = [`<p>${escapeHtml(greeting)}</p>`]\n const text: string[] = [greeting]\n\n for (const line of this.introLines) {\n html.push(`<p>${escapeHtml(line)}</p>`)\n text.push(line)\n }\n if (this.actionData) {\n const { text: actionText, url } = this.actionData\n html.push(`<p><a href=\"${escapeHtml(url)}\" class=\"button\" target=\"_blank\">${escapeHtml(actionText)}</a></p>`)\n text.push(`${actionText}: ${url}`)\n }\n for (const line of this.outroLines) {\n html.push(`<p>${escapeHtml(line)}</p>`)\n text.push(line)\n }\n html.push(`<p>${escapeHtml(this.salutationLine).replace(/\\n/g, '<br>')}</p>`)\n text.push(this.salutationLine)\n\n return { html: `<!doctype html><html><body>${html.join('\\n')}</body></html>`, text: text.join('\\n\\n') }\n }\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\n","import {\n MailManager as MailManagerToken,\n type Mailable,\n type MailRecipient,\n type Notifiable,\n type Notification,\n type NotificationChannel,\n} from '@orieljs/contracts'\nimport { currentContainer } from '@orieljs/container'\nimport { MailUnavailableError, UnrenderableNotificationMailError } from '../errors'\nimport { MailMessage } from '../MailMessage'\n\n/**\n * What Auth's default ResetPassword notification's toMail() returns: plain data, since Auth can't import\n * Notifications. `{ subject, lines, actionText, actionUrl, outroLines }` exactly; `greeting`/`salutation` are\n * optional extensions any other notification's plain toMail() may also use.\n */\nexport interface PlainNotificationMail {\n subject?: string\n greeting?: string\n lines: string[]\n actionText?: string\n actionUrl?: string\n outroLines?: string[]\n salutation?: string\n}\n\nfunction isMailable(value: unknown): value is Mailable {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as Mailable).envelope === 'function' &&\n typeof (value as Mailable).content === 'function'\n )\n}\n\nfunction isPlainNotificationMail(value: unknown): value is PlainNotificationMail {\n return typeof value === 'object' && value !== null && Array.isArray((value as PlainNotificationMail).lines)\n}\n\nfunction toMailMessage(result: unknown, notificationName: string): MailMessage {\n if (result instanceof MailMessage) return result\n if (!isPlainNotificationMail(result)) throw new UnrenderableNotificationMailError(notificationName)\n const message = new MailMessage()\n if (result.subject) message.subject(result.subject)\n if (result.greeting) message.greeting(result.greeting)\n message.lines(result.lines)\n if (result.actionText && result.actionUrl) message.action(result.actionText, result.actionUrl)\n for (const line of result.outroLines ?? []) message.line(line)\n if (result.salutation) message.salutation(result.salutation)\n return message\n}\n\n/**\n * Wraps a MailMessage as a Mailable, so it can go through MailManager.send() without a View: the default template's\n * HTML goes in `htmlString` and its plain-text twin in `textString`.\n */\nfunction mailableFromMessage(message: MailMessage, fallbackTo: MailRecipient[]): Mailable {\n return {\n envelope: async () => ({\n subject: message.subjectLine ?? '',\n to: message.toAddresses.length > 0 ? message.toAddresses : fallbackTo,\n cc: message.ccAddresses,\n bcc: message.bccAddresses,\n from: message.fromAddress,\n }),\n content: async () => {\n const rendered = message.render()\n return { htmlString: rendered.html, textString: rendered.text }\n },\n }\n}\n\nfunction normalizeRoute(route: unknown): MailRecipient[] {\n if (route === undefined || route === null) return []\n return Array.isArray(route) ? (route as MailRecipient[]) : [route as MailRecipient]\n}\n\nexport class MailNotificationChannel implements NotificationChannel {\n async send(notifiable: Notifiable, notification: Notification): Promise<void> {\n if (!notification.toMail) throw new UnrenderableNotificationMailError(notification.constructor.name)\n const result = await notification.toMail(notifiable)\n const to = normalizeRoute(notifiable.routeNotificationFor('mail', notification))\n\n // Matches Laravel's MailChannel: with no route and no Mailable to supply its own recipients, there's nothing to send.\n if (to.length === 0 && !isMailable(result)) return\n\n const mailable = isMailable(result) ? result : mailableFromMessage(toMailMessage(result, notification.constructor.name), to)\n const container = currentContainer()\n if (!container?.bound(MailManagerToken)) throw new MailUnavailableError()\n // MailManager.send() is the only send path the contract offers, so a queueable Mailable returned from toMail()\n // is queued again from here (even when this notification is itself running on a queue worker).\n await container.make(MailManagerToken).send(mailable)\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport {\n DatabaseManager as DatabaseManagerToken,\n type DatabaseConnection,\n type ModelInstance,\n type Notifiable,\n type Notification,\n type NotificationChannel,\n type Row,\n} from '@orieljs/contracts'\nimport { currentContainer } from '@orieljs/container'\nimport { DatabaseUnavailableError, NotDatabaseNotifiableError, UnrenderableNotificationDataError } from '../errors'\n\nexport const NOTIFICATIONS_TABLE = 'notifications'\n\nexport interface DatabaseNotificationRecord {\n readonly id: string\n readonly type: string\n readonly data: Record<string, unknown>\n readonly createdAt: Date\n readonly updatedAt: Date\n readAt: Date | undefined\n markAsRead(): Promise<void>\n markAsUnread(): Promise<void>\n}\n\nconst ZONELESS_TIMESTAMP = /^\\d{4}-\\d{2}-\\d{2}[ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?$/\n\n/**\n * Database writes Dates as zoneless UTC (`'2026-09-25 04:30:00'`) and hands them back as strings, so\n * `new Date()` would read such a string as local time; it is parsed as UTC here instead.\n */\nexport function parseStoredDate(value: unknown): Date {\n if (value instanceof Date) return value\n if (typeof value === 'number') return new Date(value)\n const text = String(value)\n return ZONELESS_TIMESTAMP.test(text) ? new Date(`${text.replace(' ', 'T')}Z`) : new Date(text)\n}\n\nfunction wrapRow(row: Row, db: DatabaseConnection): DatabaseNotificationRecord {\n const record: DatabaseNotificationRecord = {\n id: String(row.id),\n type: String(row.type),\n data: JSON.parse(String(row.data)) as Record<string, unknown>,\n createdAt: parseStoredDate(row.created_at),\n updatedAt: parseStoredDate(row.updated_at),\n readAt: row.read_at ? parseStoredDate(row.read_at) : undefined,\n async markAsRead() {\n if (record.readAt) return\n const now = new Date()\n await db.table(NOTIFICATIONS_TABLE).where('id', record.id).update({ read_at: now, updated_at: now })\n record.readAt = now\n },\n async markAsUnread() {\n if (!record.readAt) return\n await db.table(NOTIFICATIONS_TABLE).where('id', record.id).update({ read_at: null, updated_at: new Date() })\n record.readAt = undefined\n },\n }\n return record\n}\n\nfunction requireDatabase(): DatabaseConnection {\n const container = currentContainer()\n if (!container?.bound(DatabaseManagerToken)) throw new DatabaseUnavailableError()\n return container.make(DatabaseManagerToken).connection()\n}\n\n/** notifiable_type + notifiable_id together, never id alone. */\nexport async function fetchNotifications(\n notifiableType: string,\n notifiableId: unknown,\n filter: 'all' | 'read' | 'unread' = 'all',\n): Promise<DatabaseNotificationRecord[]> {\n const db = requireDatabase()\n let query = db\n .table(NOTIFICATIONS_TABLE)\n .where('notifiable_type', notifiableType)\n .where('notifiable_id', notifiableId)\n .orderBy('created_at', 'desc')\n if (filter === 'unread') query = query.whereNull('read_at')\n if (filter === 'read') query = query.whereNotNull('read_at')\n const rows = await query.get()\n return rows.all().map((row) => wrapRow(row, db))\n}\n\nexport class DatabaseNotificationChannel implements NotificationChannel {\n async send(notifiable: Notifiable, notification: Notification): Promise<void> {\n if (!notification.toDatabase && !notification.toArray) {\n throw new UnrenderableNotificationDataError(notification.constructor.name)\n }\n const data = notification.toDatabase ? notification.toDatabase(notifiable) : notification.toArray!(notifiable)\n const model = notifiable as Partial<ModelInstance>\n if (typeof model.getKey !== 'function') throw new NotDatabaseNotifiableError(notifiable.constructor.name)\n\n const db = requireDatabase()\n const now = new Date()\n await db.table(NOTIFICATIONS_TABLE).insert({\n id: notification.id ?? randomUUID(),\n type: notification.constructor.name,\n notifiable_type: notifiable.constructor.name,\n notifiable_id: model.getKey(),\n data: JSON.stringify(data),\n read_at: null,\n created_at: now,\n updated_at: now,\n })\n }\n}\n","export const NOTIFICATIONS_TABLE_MIGRATION = `import { Migration, Schema } from '@orieljs/framework'\n\nexport default class extends Migration {\n async up() {\n await Schema.create('notifications', (t) => {\n t.uuid('id').primary()\n t.string('type')\n t.morphs('notifiable')\n t.text('data')\n t.timestamp('read_at').nullable()\n t.timestamps()\n })\n }\n\n async down() {\n await Schema.dropIfExists('notifications')\n }\n}\n`\n","/**\n * The Queue/Bus protocol's shared marker (the \"Queue/Bus protocol\" section of the Queue/Bus design notes) —\n * identical to Mail's `symbols.ts`. `Symbol.for` is a *global* registry key, so this is the literal same runtime\n * symbol Bus's `Queueable` base class and `isQueueable()` use, with no import needed (and none allowed).\n */\nexport const SHOULD_QUEUE: unique symbol = Symbol.for('oriel:bus.shouldQueue')\n","import { randomUUID } from 'node:crypto'\nimport {\n BusDispatcher as BusDispatcherToken,\n type Container,\n type Notifiable,\n type Notification,\n type NotificationChannel,\n type NotificationDispatcher as NotificationDispatcherContract,\n} from '@orieljs/contracts'\nimport { AnonymousNotifiable } from './AnonymousNotifiable'\nimport { NotificationQueueUnavailableError, UnknownNotificationChannelError } from './errors'\nimport { SendQueuedNotifications } from './Jobs/SendQueuedNotifications'\nimport { SHOULD_QUEUE } from './symbols'\n\n/**\n * Matches the Queue/Bus protocol's `isQueueable()` exactly: true when the notification carries\n * `SHOULD_QUEUE === true`, or has a defined value for any of ShouldQueue's fields, or declares a\n * `uniqueId()`/`retryUntil()` method (same rule as Mail's `isQueueableMailable`). `implements ShouldQueue` itself\n * leaves no runtime trace. Call sendNow() explicitly to bypass this for a notification with none of these.\n */\nfunction isQueueableNotification(notification: Notification): boolean {\n const record = notification as unknown as Record<PropertyKey, unknown>\n if (record[SHOULD_QUEUE] === true) return true\n return (\n record.connection !== undefined ||\n record.queue !== undefined ||\n record.delay !== undefined ||\n record.tries !== undefined ||\n record.backoff !== undefined ||\n record.timeout !== undefined ||\n typeof record.uniqueId === 'function' ||\n typeof record.retryUntil === 'function'\n )\n}\n\n/**\n * The notification one notifiable receives. With no id set, a shallow copy (same prototype, own fields copied)\n * carrying a fresh id, so every notifiable gets its own id (the database channel's primary key) and the caller's\n * instance is left untouched; a caller-preset id is kept as is. The copy keeps its class, so Queue's serializer\n * restores it as that class on the worker.\n */\nfunction forNotifiable(notification: Notification): Notification {\n if (notification.id) return notification\n const copy = Object.assign(Object.create(Object.getPrototypeOf(notification) as object) as Notification, notification)\n copy.id = randomUUID()\n return copy\n}\n\n/**\n * One notifiable, an array, or any other iterable of them (a Database `Collection` from `await User.where(...).get()`,\n * a Set, ...), as Laravel's `Notification::send($users, ...)` accepts. A notifiable is recognised by its\n * routeNotificationFor() method first, so an iterable model is still treated as one notifiable.\n */\nexport function toNotifiableList(notifiables: Notifiable | Iterable<Notifiable>): Notifiable[] {\n if (Array.isArray(notifiables)) return notifiables as Notifiable[]\n if (typeof (notifiables as Partial<Notifiable>).routeNotificationFor === 'function') return [notifiables as Notifiable]\n if (typeof notifiables === 'object' && notifiables !== null && Symbol.iterator in notifiables) {\n return [...(notifiables as Iterable<Notifiable>)]\n }\n return [notifiables as Notifiable]\n}\n\n/** Resolves channels by name from a factory map — mirrors MailManager's driver resolution. */\nexport class Dispatcher implements NotificationDispatcherContract {\n private readonly factories = new Map<string, () => NotificationChannel>()\n private readonly channels = new Map<string, NotificationChannel>()\n\n constructor(private readonly container: Container) {}\n\n extend(channel: string, factory: () => NotificationChannel): void {\n this.factories.set(channel, factory)\n this.channels.delete(channel)\n }\n\n private resolve(name: string): NotificationChannel {\n const cached = this.channels.get(name)\n if (cached) return cached\n const factory = this.factories.get(name)\n if (!factory) throw new UnknownNotificationChannelError(name)\n const channel = factory()\n this.channels.set(name, channel)\n return channel\n }\n\n async send(notifiables: Notifiable | Iterable<Notifiable>, notification: Notification): Promise<void> {\n const list = toNotifiableList(notifiables)\n if (list.length === 0) return\n if (isQueueableNotification(notification)) {\n if (!this.container.bound(BusDispatcherToken)) throw new NotificationQueueUnavailableError()\n const bus = this.container.make(BusDispatcherToken)\n // One job per notifiable per channel, as Laravel does, so a failing channel or recipient retries alone.\n for (const notifiable of list) {\n const own = forNotifiable(notification)\n for (const channel of notification.via(notifiable)) {\n await bus.dispatch(new SendQueuedNotifications([notifiable], own, [channel]))\n }\n }\n return\n }\n await this.sendNow(list, notification)\n }\n\n async sendNow(notifiables: Notifiable | Iterable<Notifiable>, notification: Notification, channels?: string[]): Promise<void> {\n const list = toNotifiableList(notifiables)\n for (const notifiable of list) {\n const own = forNotifiable(notification)\n const names = channels ?? notification.via(notifiable)\n for (const name of names) {\n // An on-demand notifiable has no model to store a row against, so it skips 'database' as in Laravel.\n if (name === 'database' && notifiable instanceof AnonymousNotifiable) continue\n await this.resolve(name).send(notifiable, own)\n }\n }\n }\n\n /** On-demand: an AnonymousNotifiable, whose notify() goes through the bound NotificationDispatcher. */\n route(channel: string, route: unknown): AnonymousNotifiable {\n return new AnonymousNotifiable().route(channel, route)\n }\n}\n","import {\n NotificationDispatcher as NotificationDispatcherToken,\n type ModelInstance,\n type Notifiable as NotifiableContract,\n type Notification,\n type NotificationDispatcher,\n} from '@orieljs/contracts'\nimport { currentContainer } from '@orieljs/container'\nimport { Str } from '@orieljs/support'\nimport { fetchNotifications, type DatabaseNotificationRecord } from './channels/DatabaseNotificationChannel'\nimport { NotDatabaseNotifiableError, NotificationDispatcherUnavailableError } from './errors'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type NotifiableConstructor = new (...args: any[]) => object\n\n/** The bound NotificationDispatcher, or a clear error when the Notifications component isn't registered. */\nexport function resolveDispatcher(): NotificationDispatcher {\n const container = currentContainer()\n if (!container?.bound(NotificationDispatcherToken)) throw new NotificationDispatcherUnavailableError()\n return container.make(NotificationDispatcherToken)\n}\n\nfunction requireKey(instance: object): unknown {\n const model = instance as Partial<ModelInstance>\n if (typeof model.getKey !== 'function') throw new NotDatabaseNotifiableError(instance.constructor.name)\n return model.getKey()\n}\n\n/**\n * Mirrors Laravel's Illuminate\\Notifications\\Notifiable trait: `class User extends Notifiable(Model) {}`. Custom\n * routing per channel follows Laravel's own resolution order: a `routeNotificationFor<Channel>()` method (Studly\n * name) wins if present, else 'mail' reads `email`/`getAttribute('email')`, 'database' returns the instance itself\n * (the database channel derives the table row from it directly), and anything else is unrouted (`undefined`).\n */\nexport function Notifiable<TBase extends NotifiableConstructor>(Base: TBase) {\n return class NotifiableMixin extends Base implements NotifiableContract {\n routeNotificationFor(channel: string, notification: Notification): unknown {\n const custom = (this as unknown as Record<string, unknown>)[`routeNotificationFor${Str.studly(channel)}`]\n if (typeof custom === 'function') return (custom as (n: Notification) => unknown).call(this, notification)\n if (channel === 'mail') {\n const withAttrs = this as unknown as { email?: string; getAttribute?: (key: string) => unknown }\n return typeof withAttrs.getAttribute === 'function' ? withAttrs.getAttribute('email') : withAttrs.email\n }\n if (channel === 'database') return this\n return undefined\n }\n\n async notify(notification: Notification): Promise<void> {\n await resolveDispatcher().send(this as unknown as NotifiableContract, notification)\n }\n\n async notifyNow(notification: Notification, channels?: string[]): Promise<void> {\n await resolveDispatcher().sendNow(this as unknown as NotifiableContract, notification, channels)\n }\n\n notifications(): Promise<DatabaseNotificationRecord[]> {\n return fetchNotifications(this.constructor.name, requireKey(this))\n }\n\n unreadNotifications(): Promise<DatabaseNotificationRecord[]> {\n return fetchNotifications(this.constructor.name, requireKey(this), 'unread')\n }\n\n readNotifications(): Promise<DatabaseNotificationRecord[]> {\n return fetchNotifications(this.constructor.name, requireKey(this), 'read')\n }\n }\n}\n","import type { Notifiable, Notification } from '@orieljs/contracts'\nimport { resolveDispatcher } from './Notifiable'\n\n/**\n * Returned by `Notification.route(channel, address)` for sending to an address with no model behind it. The\n * routes are an own plain record and every method sits on the prototype, so a queued on-demand notification\n * survives Queue's serializer (which rejects functions in own fields) and is restored on the worker as this class\n * (registered as `'Notifications.AnonymousNotifiable'` in ClassRegistry).\n */\nexport class AnonymousNotifiable implements Notifiable {\n routes: Record<string, unknown> = {}\n\n /** Adds a route; chains, so one on-demand notifiable can go to several channels. */\n route(channel: string, route: unknown): this {\n this.routes[channel] = route\n return this\n }\n\n routeNotificationFor(channel: string): unknown {\n return this.routes[channel]\n }\n\n async notify(notification: Notification): Promise<void> {\n await resolveDispatcher().send(this, notification)\n }\n\n async notifyNow(notification: Notification, channels?: string[]): Promise<void> {\n await resolveDispatcher().sendNow(this, notification, channels)\n }\n}\n","import { NotificationDispatcher as NotificationDispatcherToken, type Job, type Notifiable, type Notification } from '@orieljs/contracts'\nimport { inject } from '@orieljs/container'\nimport { SHOULD_QUEUE } from '../symbols'\n\n/** The ShouldQueue options copied from the notification onto the wrapper job. */\nconst QUEUE_OPTIONS = [\n 'connection',\n 'queue',\n 'delay',\n 'tries',\n 'backoff',\n 'timeout',\n 'failOnTimeout',\n 'deleteWhenMissingModels',\n] as const\n\n/** The job hooks Queue reads off a job instance, when the wrapped notification defines them. */\ninterface JobHooks {\n failed?: (error: unknown) => unknown\n retryUntil?: () => unknown\n middleware?: unknown[] | (() => unknown[])\n}\n\n/**\n * Copies the notification's own ShouldQueue fields onto the wrapper job (when set) — same reasoning as Mail's\n * SendQueuedMailable. Uses `declare` fields on the class (no runtime auto-initialization to `undefined`), so a\n * field is a genuinely own property only when the notification actually set it.\n */\nfunction copyShouldQueueFields(target: Record<PropertyKey, unknown>, source: object): void {\n const fields = source as Record<string, unknown>\n for (const option of QUEUE_OPTIONS) {\n if (fields[option] !== undefined) target[option] = fields[option]\n }\n}\n\n/**\n * Dispatched by Dispatcher.send() when the notification is queueable. Runs on the worker: calls sendNow()\n * directly (never send()), so it can't re-detect itself as queueable and loop.\n *\n * Carries `SHOULD_QUEUE` on its own prototype, per the Queue/Bus protocol, so `BusDispatcher.dispatch()` — which\n * re-decides queued-or-now on whatever job it's handed — actually enqueues it instead of running it inline; the\n * notification's own queueability (checked by Dispatcher, above) only decides whether to wrap it at all. Like\n * Laravel's SendQueuedNotifications, it forwards failed(), retryUntil(), middleware() and displayName() to the\n * notification.\n */\nexport class SendQueuedNotifications implements Job {\n declare connection?: string\n declare queue?: string\n declare delay?: number\n declare tries?: number\n declare backoff?: number | number[]\n declare timeout?: number\n declare failOnTimeout?: boolean\n declare deleteWhenMissingModels?: boolean\n\n constructor(\n readonly notifiables: Notifiable[],\n readonly notification: Notification,\n readonly channels?: string[],\n ) {\n copyShouldQueueFields(this as unknown as Record<PropertyKey, unknown>, notification)\n }\n\n async handle(): Promise<void> {\n await inject(NotificationDispatcherToken).sendNow(this.notifiables, this.notification, this.channels)\n }\n\n /** Called by Queue after the final failed attempt. */\n async failed(error: unknown): Promise<void> {\n const hooks = this.notification as unknown as JobHooks\n if (typeof hooks.failed === 'function') await hooks.failed.call(this.notification, error)\n }\n\n /** `failed_jobs` and `queue:failed` show the notification's class, not the wrapper's. */\n displayName(): string {\n return this.notification.constructor.name\n }\n\n retryUntil(): unknown {\n const hooks = this.notification as unknown as JobHooks\n return typeof hooks.retryUntil === 'function' ? hooks.retryUntil.call(this.notification) : undefined\n }\n\n middleware(): unknown[] {\n const declared = (this.notification as unknown as JobHooks).middleware\n return (typeof declared === 'function' ? declared.call(this.notification) : declared) ?? []\n }\n}\n;(SendQueuedNotifications.prototype as unknown as Record<PropertyKey, unknown>)[SHOULD_QUEUE] = true\n","import {\n NotificationDispatcher as NotificationDispatcherToken,\n type Notifiable,\n type Notification as NotificationContract,\n type NotificationChannel,\n type NotificationDispatcher,\n} from '@orieljs/contracts'\nimport { facade } from '@orieljs/support'\nimport { toNotifiableList } from './Dispatcher'\nimport { resolveDispatcher } from './Notifiable'\n\n/**\n * Mirrors Laravel's Illuminate\\Notifications\\Notification: override via(), and whichever toXxx() the channels\n * need.\n *\n * The statics are the `Notification` facade, so one export serves both `class InvoicePaid extends Notification`\n * and `Notification.send(users, new InvoicePaid())`.\n */\nexport abstract class Notification implements NotificationContract {\n id?: string\n abstract via(notifiable: Notifiable): string[]\n\n /** Queues it when the notification is queueable, otherwise sends now. Accepts one notifiable, an array or any iterable. */\n static send(notifiables: Notifiable | Iterable<Notifiable>, notification: NotificationContract): Promise<void> {\n return resolveDispatcher().send(toNotifiableList(notifiables), notification)\n }\n\n static sendNow(\n notifiables: Notifiable | Iterable<Notifiable>,\n notification: NotificationContract,\n channels?: string[],\n ): Promise<void> {\n return resolveDispatcher().sendNow(toNotifiableList(notifiables), notification, channels)\n }\n\n /** On-demand: `Notification.route('mail', 'a@b.c').notify(new InvoicePaid())`. */\n static route(channel: string, route: unknown): Notifiable & { notify(notification: NotificationContract): Promise<void> } {\n return resolveDispatcher().route(channel, route)\n }\n\n static extend(channel: string, factory: () => NotificationChannel): void {\n resolveDispatcher().extend(channel, factory)\n }\n\n /** Replace the dispatcher in the current container (what a NotificationFake uses). */\n static swap(fake: NotificationDispatcher): void {\n facade<NotificationDispatcher>(NotificationDispatcherToken).swap(fake)\n }\n}\n","import type { NotificationDispatcher } from '@orieljs/contracts'\nimport { NotificationDispatcher as NotificationDispatcherToken } from '@orieljs/contracts'\nimport { facade } from '@orieljs/support'\n\n/**\n * The plain facade over NotificationDispatcher. App code normally uses the `Notification` base class's statics\n * instead (one `Notification` export is both the base class and the facade); this stays exported for code that\n * wants the contract-typed proxy (getFacadeRoot(), swap()).\n */\nexport const NotificationFacade = facade<NotificationDispatcher>(NotificationDispatcherToken)\n","import { ClassRegistry as ClassRegistryToken, NotificationDispatcher as NotificationDispatcherToken } from '@orieljs/contracts'\nimport { ServiceProvider } from '@orieljs/support'\nimport { AnonymousNotifiable } from './AnonymousNotifiable'\nimport { DatabaseNotificationChannel } from './channels/DatabaseNotificationChannel'\nimport { MailNotificationChannel } from './channels/MailNotificationChannel'\nimport { Dispatcher } from './Dispatcher'\nimport { SendQueuedNotifications } from './Jobs/SendQueuedNotifications'\n\nexport class NotificationsServiceProvider extends ServiceProvider {\n override register(): void {\n this.app.singleton(NotificationDispatcherToken, (container) => new Dispatcher(container))\n // Namespaced names, registered in register() (not boot()) — the Queue/Bus protocol rule for wrapper classes.\n // ClassRegistry is safe to use here: Application's own constructor binds it before any provider registers.\n const registry = this.app.make(ClassRegistryToken)\n registry.register(SendQueuedNotifications, 'Notifications.SendQueuedNotifications')\n registry.register(AnonymousNotifiable, 'Notifications.AnonymousNotifiable')\n }\n\n override boot(): void {\n const dispatcher = this.app.make(NotificationDispatcherToken)\n dispatcher.extend('mail', () => new MailNotificationChannel())\n dispatcher.extend('database', () => new DatabaseNotificationChannel())\n }\n}\n"],"mappings":"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","names":["currentContainer","currentContainer","randomUUID","currentContainer","currentContainer","NotificationDispatcherToken","NotificationDispatcherToken","randomUUID","NotificationDispatcherToken","NotificationDispatcherToken","NotificationDispatcherToken","facade","NotificationDispatcherToken","NotificationDispatcherToken"]}
1
+ {"version":3,"sources":["../errors.ts","../MailMessage.ts","../mail/template.ts","../channels/MailNotificationChannel.ts","../channels/DatabaseNotificationChannel.ts","../migrations.ts","../symbols.ts","../Dispatcher.ts","../RoutesNotifications.ts","../AnonymousNotifiable.ts","../Jobs/SendQueuedNotifications.ts","../Notification.ts","../facade.ts","../NotificationsServiceProvider.ts"],"sourcesContent":["import type { OrielError } from '@orieljs/contracts'\n\nexport class UnrenderableNotificationMailError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'UnrenderableNotificationMailError'\n constructor(notification: string) {\n super(\n `[${notification}].toMail() must return a Mailable, a MailMessage, or ` +\n '{ subject?, greeting?, lines, actionText?, actionUrl?, outroLines?, salutation? }.',\n )\n }\n}\n\nexport class MailUnavailableError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'MailUnavailableError'\n constructor() {\n super(\"Sending a notification's mail requires the Mail component. Register MailManager first.\")\n }\n}\n\nexport class DatabaseUnavailableError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'DatabaseUnavailableError'\n constructor() {\n super('The database notification channel requires the Database component. Register DatabaseManager first.')\n }\n}\n\nexport class NotDatabaseNotifiableError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'NotDatabaseNotifiableError'\n constructor(notifiable: string) {\n super(`[${notifiable}] has no getKey() — the database channel and notification-listing methods need a model.`)\n }\n}\n\nexport class UnrenderableNotificationDataError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'UnrenderableNotificationDataError'\n constructor(notification: string) {\n super(`[${notification}] needs a toDatabase() or toArray() method to use the database channel.`)\n }\n}\n\nexport class UnknownNotificationChannelError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'UnknownNotificationChannelError'\n constructor(channel: string) {\n super(`Notification channel [${channel}] is not registered. Register it first with NotificationDispatcher.extend().`)\n }\n}\n\nexport class NotificationQueueUnavailableError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'NotificationQueueUnavailableError'\n constructor() {\n super('Queueing a notification requires the Bus component. Register BusDispatcher, or call sendNow() instead.')\n }\n}\n\nexport class NotificationDispatcherUnavailableError extends Error implements OrielError {\n readonly kind = 'Server' as const\n readonly status = 500\n override readonly name = 'NotificationDispatcherUnavailableError'\n constructor() {\n super('notify()/notifyNow() require the Notifications component. Register NotificationDispatcher first.')\n }\n}\n","import { type Attachment, ConfigRepository, type MailRecipient } from '@orieljs/contracts'\nimport { currentContainer } from '@orieljs/container'\nimport { type MailLevel, renderNotificationMail } from './mail/template'\n\nexport interface MailMessageAction {\n text: string\n url: string\n}\n\n/**\n * Laravel's Illuminate\\Notifications\\Messages\\MailMessage: greeting/line/action/salutation, rendered to Laravel's\n * default notification mail (HTML and text) with no View involved. line() appends to the intro before action() is\n * called, and to the outro afterwards — exactly like Laravel's SimpleMessage.\n */\nexport class MailMessage {\n /** info, success or error: the colour of the button, and error() greets with \"Whoops!\". Laravel's `$level`. */\n levelName: MailLevel = 'info'\n subjectLine?: string\n greetingLine?: string\n introLines: string[] = []\n actionData?: MailMessageAction\n outroLines: string[] = []\n /** Unset: \"Regards,\" and config('app.name'), as Laravel's template. */\n salutationLine?: string\n toAddresses: MailRecipient[] = []\n ccAddresses: MailRecipient[] = []\n bccAddresses: MailRecipient[] = []\n replyToAddresses: MailRecipient[] = []\n fromAddress?: MailRecipient\n attachments: Attachment[] = []\n\n /** A green button: something went well. */\n success(): this {\n return this.level('success')\n }\n\n /** A red button, and \"Whoops!\" when there is no greeting: something went wrong. */\n error(): this {\n return this.level('error')\n }\n\n level(level: MailLevel): this {\n this.levelName = level\n return this\n }\n\n subject(subject: string): this {\n this.subjectLine = subject\n return this\n }\n\n greeting(greeting: string): this {\n this.greetingLine = greeting\n return this\n }\n\n /** Before action(), appends to the intro; after action(), to the outro. */\n line(text: string): this {\n ;(this.actionData ? this.outroLines : this.introLines).push(text)\n return this\n }\n\n lines(lines: string[]): this {\n for (const line of lines) this.line(line)\n return this\n }\n\n /** line(), when the condition holds. */\n lineIf(condition: boolean, text: string): this {\n return condition ? this.line(text) : this\n }\n\n /** lines(), when the condition holds. */\n linesIf(condition: boolean, lines: string[]): this {\n return condition ? this.lines(lines) : this\n }\n\n action(text: string, url: string): this {\n this.actionData = { text, url }\n return this\n }\n\n salutation(salutation: string): this {\n this.salutationLine = salutation\n return this\n }\n\n from(address: MailRecipient): this {\n this.fromAddress = address\n return this\n }\n\n to(recipients: MailRecipient | MailRecipient[]): this {\n this.toAddresses.push(...(Array.isArray(recipients) ? recipients : [recipients]))\n return this\n }\n\n cc(recipients: MailRecipient | MailRecipient[]): this {\n this.ccAddresses.push(...(Array.isArray(recipients) ? recipients : [recipients]))\n return this\n }\n\n bcc(recipients: MailRecipient | MailRecipient[]): this {\n this.bccAddresses.push(...(Array.isArray(recipients) ? recipients : [recipients]))\n return this\n }\n\n replyTo(recipients: MailRecipient | MailRecipient[]): this {\n this.replyToAddresses.push(...(Array.isArray(recipients) ? recipients : [recipients]))\n return this\n }\n\n /** Attaches a file by its path: `as` names it in the mail (its file name by default), `mime` gives its type. */\n attach(path: string, options: { as?: string; mime?: string } = {}): this {\n this.attachments.push({ name: options.as ?? path.slice(Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\\\')) + 1), path, ...(options.mime === undefined ? {} : { mime: options.mime }) })\n return this\n }\n\n /** Attaches bytes or text under a file name. */\n attachData(data: Uint8Array | string, name: string, options: { mime?: string } = {}): this {\n this.attachments.push({ name, data, ...(options.mime === undefined ? {} : { mime: options.mime }) })\n return this\n }\n\n /** Renders Laravel's default notification mail, HTML and text. No ViewFactory involved. */\n render(): { html: string; text: string } {\n return renderNotificationMail({\n level: this.levelName,\n greeting: this.greetingLine,\n introLines: this.introLines,\n actionText: this.actionData?.text,\n actionUrl: this.actionData?.url,\n outroLines: this.outroLines,\n salutation: this.salutationLine,\n appName: appConfig('app.name', 'Oriel'),\n appUrl: appConfig('app.url', 'http://localhost'),\n year: new Date().getFullYear(),\n })\n }\n}\n\n/** A config value (`app.name`, `app.url`) when there is a container with config and the value is set, else `fallback`. */\nfunction appConfig(key: string, fallback: string): string {\n const container = currentContainer()\n const value = container?.bound(ConfigRepository) ? container.make(ConfigRepository).get<unknown>(key) : undefined\n return typeof value === 'string' && value !== '' ? value : fallback\n}\n","/*!\n * Laravel's default notification mail (the `notifications::email` view in the `mail::` markdown components with the\n * `default` theme), as Laravel 12 sends it: the same HTML, with the theme's CSS inlined as CssToInlineStyles inlines\n * it, and the same plain-text part. The lines are plain text (escaped), not Markdown.\n *\n * laravel/framework: Copyright (c) Taylor Otwell\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n * documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the\n * Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n\nexport type MailLevel = 'info' | 'success' | 'error'\n\nexport interface NotificationMailContent {\n level: MailLevel\n greeting: string | undefined\n introLines: readonly string[]\n actionText: string | undefined\n actionUrl: string | undefined\n outroLines: readonly string[]\n salutation: string | undefined\n appName: string\n appUrl: string\n year: number\n}\n\n/** The base rule of the theme (`body *`), which every element gets first. */\nconst BASE =\n \"box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; position: relative;\"\n\nconst style = (rules = ''): string => `style=\"${BASE}${rules === '' ? '' : ` ${rules}`}\"`\n\nconst PREMAILER_FULL = '-premailer-cellpadding: 0; -premailer-cellspacing: 0; -premailer-width: 100%;'\nconst PREMAILER_570 = '-premailer-cellpadding: 0; -premailer-cellspacing: 0; -premailer-width: 570px;'\nconst PARAGRAPH = style('font-size: 16px; line-height: 1.5em; margin-top: 0; text-align: left;')\n\n/** The theme's button colours: `.button-primary`, `.button-success`, `.button-error`. */\nconst BUTTON_COLORS: Record<MailLevel, { name: string; color: string }> = {\n info: { name: 'primary', color: '#2d3748' },\n success: { name: 'success', color: '#48bb78' },\n error: { name: 'error', color: '#e53e3e' },\n}\n\n/** Text content: &, < and > (quotes stay as written, as Laravel's output has them). */\nconst text = (value: string): string => value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')\n\n/** An attribute value: the text characters and the double quote. */\nconst attribute = (value: string): string => text(value).replace(/\"/g, '&quot;')\n\n/** Laravel's displayableActionUrl: the URL without a mailto: or tel: scheme. */\nconst displayable = (url: string): string => url.replace(/mailto:|tel:/g, '')\n\n/**\n * CommonMark's isLinkPotentiallyUnsafe, which drops the subcopy link's href (Laravel's Markdown sets\n * `allow_unsafe_links` off). It tests the destination CommonMark parsed, so the leading spaces and line break the\n * parser skips are skipped here too. A URL with other leading control characters or space, or a tab or line break\n * inside, is not a link destination at all (Laravel shows no link), and a browser would still run it, so those are\n * skipped as well rather than kept as a live href.\n */\nconst isUnsafeLink = (url: string): boolean => {\n const destination = url.replace(/^[\\x00-\\x20]+/, '').replace(/[\\t\\n\\r]/g, '')\n return /^javascript:|vbscript:|file:|data:/i.test(destination) && !/^data:image\\/(?:png|gif|jpeg|webp)/i.test(destination)\n}\n\nconst HEAD = `<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<title>{{title}}</title>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n<meta name=\"color-scheme\" content=\"light\">\n<meta name=\"supported-color-schemes\" content=\"light\">\n<style>\n@media only screen and (max-width: 600px) {\n.inner-body {\nwidth: 100% !important;\n}\n\n.footer {\nwidth: 100% !important;\n}\n}\n\n@media only screen and (max-width: 500px) {\n.button {\nwidth: 100% !important;\n}\n}\n</style>\n\n</head>`\n\nfunction button(label: string, url: string, level: MailLevel): string {\n const { name, color } = BUTTON_COLORS[level]\n const look = `-webkit-text-size-adjust: none; border-radius: 4px; color: #fff; display: inline-block; overflow: hidden; text-decoration: none; background-color: ${color}; border-bottom: 8px solid ${color}; border-left: 18px solid ${color}; border-right: 18px solid ${color}; border-top: 8px solid ${color}; word-break: break-all;`\n return [\n `<table class=\"action\" align=\"center\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" ${style(`${PREMAILER_FULL} margin: 30px auto; padding: 0; text-align: center; width: 100%; float: unset;`)}>`,\n '<tr>',\n `<td align=\"center\" ${style()}>`,\n `<table width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" ${style()}>`,\n '<tr>',\n `<td align=\"center\" ${style()}>`,\n `<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" ${style()}>`,\n '<tr>',\n `<td ${style()}>`,\n `<a href=\"${attribute(url)}\" class=\"button button-${name}\" target=\"_blank\" rel=\"noopener\" ${style(look)}>${text(label)}</a>`,\n '</td>',\n '</tr>',\n '</table>',\n '</td>',\n '</tr>',\n '</table>',\n '</td>',\n '</tr>',\n '</table>',\n ].join('\\n')\n}\n\nfunction subcopy(label: string, url: string): string {\n const link = `<a ${isUnsafeLink(url) ? '' : `href=\"${attribute(url)}\" `}${style('color: #3869d4; word-break: break-all;')}>${text(displayable(url))}</a>`\n return [\n `<table class=\"subcopy\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" ${style('border-top: 1px solid #e8e5ef; margin-top: 25px; padding-top: 25px;')}>`,\n '<tr>',\n `<td ${style()}>`,\n `<p ${style('line-height: 1.5em; margin-top: 0; text-align: left; font-size: 14px;')}>If you're having trouble clicking the \"${text(label)}\" button, copy and paste the URL below`,\n `into your web browser: <span class=\"break-all\" ${style('word-break: break-all;')}>${link}</span></p>`,\n '',\n '</td>',\n '</tr>',\n '</table>',\n ].join('\\n')\n}\n\nfunction greetingOf(content: NotificationMailContent): string {\n if (content.greeting !== undefined && content.greeting !== '') return content.greeting\n return content.level === 'error' ? 'Whoops!' : 'Hello!'\n}\n\nfunction hasAction(content: NotificationMailContent): content is NotificationMailContent & { actionText: string; actionUrl: string } {\n return content.actionText !== undefined && content.actionUrl !== undefined\n}\n\n/** The HTML part, byte for byte the markup Laravel 12 sends for the same message. */\nfunction html(content: NotificationMailContent): string {\n const salutation =\n content.salutation === undefined || content.salutation === ''\n ? `Regards,<br>\\n${text(content.appName)}`\n : text(content.salutation).replace(/\\r?\\n/g, '<br>\\n')\n const body = [\n `<h1 ${style('color: #3d4852; font-size: 18px; font-weight: bold; margin-top: 0; text-align: left;')}>${text(greetingOf(content))}</h1>`,\n ...content.introLines.map((line) => `<p ${PARAGRAPH}>${text(line)}</p>`),\n ...(hasAction(content) ? [button(content.actionText, content.actionUrl, content.level)] : []),\n ...content.outroLines.map((line) => `<p ${PARAGRAPH}>${text(line)}</p>`),\n `<p ${PARAGRAPH}>${salutation}</p>`,\n ].join('\\n')\n const after = hasAction(content) ? `${subcopy(content.actionText, content.actionUrl)}\\n` : '\\n'\n return [\n HEAD.replace('{{title}}', () => text(content.appName)),\n `<body ${style('-webkit-text-size-adjust: none; background-color: #ffffff; color: #718096; height: 100%; line-height: 1.4; margin: 0; padding: 0; width: 100% !important;')}>`,\n '',\n `<table class=\"wrapper\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" ${style(`${PREMAILER_FULL} background-color: #edf2f7; margin: 0; padding: 0; width: 100%;`)}>`,\n '<tr>',\n `<td align=\"center\" ${style()}>`,\n `<table class=\"content\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" ${style(`${PREMAILER_FULL} margin: 0; padding: 0; width: 100%;`)}>`,\n '<tr>',\n `<td class=\"header\" ${style('padding: 25px 0; text-align: center;')}>`,\n `<a href=\"${attribute(content.appUrl)}\" ${style('color: #3d4852; font-size: 19px; font-weight: bold; text-decoration: none; display: inline-block;')}>`,\n text(content.appName),\n '</a>',\n '</td>',\n '</tr>',\n '',\n '<!-- Email Body -->',\n '<tr>',\n `<td class=\"body\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" ${style(`${PREMAILER_FULL} background-color: #edf2f7; border-bottom: 1px solid #edf2f7; border-top: 1px solid #edf2f7; margin: 0; padding: 0; width: 100%; border: hidden !important;`)}>`,\n `<table class=\"inner-body\" align=\"center\" width=\"570\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" ${style(`${PREMAILER_570} background-color: #ffffff; border-color: #e8e5ef; border-radius: 2px; border-width: 1px; box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015); margin: 0 auto; padding: 0; width: 570px;`)}>`,\n '<!-- Body content -->',\n '<tr>',\n `<td class=\"content-cell\" ${style('max-width: 100vw; padding: 32px;')}>`,\n `${body}\\n\\n\\n${after}</td>`,\n '</tr>',\n '</table>',\n '</td>',\n '</tr>',\n '',\n '<tr>',\n `<td ${style()}>`,\n `<table class=\"footer\" align=\"center\" width=\"570\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" ${style(`${PREMAILER_570} margin: 0 auto; padding: 0; text-align: center; width: 570px;`)}>`,\n '<tr>',\n `<td class=\"content-cell\" align=\"center\" ${style('max-width: 100vw; padding: 32px;')}>`,\n `<p ${style('line-height: 1.5em; margin-top: 0; color: #b0adc5; font-size: 12px; text-align: center;')}>© ${content.year} ${text(content.appName)}. All rights reserved.</p>`,\n '',\n '</td>',\n '</tr>',\n '</table>',\n '</td>',\n '</tr>',\n '</table>',\n '</td>',\n '</tr>',\n '</table>',\n '</body>',\n '</html>',\n ].join('\\n')\n}\n\n/** The plain-text part, as Laravel's text components write it. */\nfunction plain(content: NotificationMailContent): string {\n const parts = [`${content.appName}: ${content.appUrl}`, `# ${greetingOf(content)}`, ...content.introLines]\n if (hasAction(content)) parts.push(`${content.actionText}: ${content.actionUrl}`)\n parts.push(...content.outroLines)\n parts.push(content.salutation === undefined || content.salutation === '' ? `Regards,\\n${content.appName}` : content.salutation)\n if (hasAction(content)) {\n parts.push(\n `If you're having trouble clicking the \"${content.actionText}\" button, copy and paste the URL below\\ninto your web browser: [${displayable(content.actionUrl)}](${content.actionUrl})`,\n )\n }\n parts.push(`© ${content.year} ${content.appName}. All rights reserved.`)\n return `${parts.join('\\n\\n')}\\n`\n}\n\nexport function renderNotificationMail(content: NotificationMailContent): { html: string; text: string } {\n return { html: html(content), text: plain(content) }\n}\n","import {\n MailManager as MailManagerToken,\n type Mailable,\n type MailRecipient,\n type Notifiable,\n type Notification,\n type NotificationChannel,\n} from '@orieljs/contracts'\nimport { currentContainer } from '@orieljs/container'\nimport { Str } from '@orieljs/support'\nimport { MailUnavailableError, UnrenderableNotificationMailError } from '../errors'\nimport { MailMessage } from '../MailMessage'\n\n/**\n * What Auth's default ResetPassword notification's toMail() returns: plain data, since Auth can't import\n * Notifications. `{ subject, lines, actionText, actionUrl, outroLines }` exactly; `greeting`/`salutation` are\n * optional extensions any other notification's plain toMail() may also use.\n */\nexport interface PlainNotificationMail {\n subject?: string\n greeting?: string\n lines: string[]\n actionText?: string\n actionUrl?: string\n outroLines?: string[]\n salutation?: string\n}\n\nfunction isMailable(value: unknown): value is Mailable {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as Mailable).envelope === 'function' &&\n typeof (value as Mailable).content === 'function'\n )\n}\n\nfunction isPlainNotificationMail(value: unknown): value is PlainNotificationMail {\n return typeof value === 'object' && value !== null && Array.isArray((value as PlainNotificationMail).lines)\n}\n\nfunction toMailMessage(result: unknown, notificationName: string): MailMessage {\n if (result instanceof MailMessage) return result\n if (!isPlainNotificationMail(result)) throw new UnrenderableNotificationMailError(notificationName)\n const message = new MailMessage()\n if (result.subject) message.subject(result.subject)\n if (result.greeting) message.greeting(result.greeting)\n message.lines(result.lines)\n if (result.actionText && result.actionUrl) message.action(result.actionText, result.actionUrl)\n for (const line of result.outroLines ?? []) message.line(line)\n if (result.salutation) message.salutation(result.salutation)\n return message\n}\n\n/**\n * Wraps a MailMessage as a Mailable, so it can go through MailManager.sendNow() without a View: the default template's\n * HTML goes in `htmlString` and its plain-text twin in `textString`. Without subject(), the subject is the\n * notification's class name in words (`InvoicePaid` → \"Invoice Paid\"), as Laravel's MailChannel names it.\n */\nfunction mailableFromMessage(message: MailMessage, fallbackTo: MailRecipient[], notificationName: string): Mailable {\n return {\n envelope: async () => ({\n subject: message.subjectLine || Str.title(Str.snake(notificationName, ' ')),\n to: message.toAddresses.length > 0 ? message.toAddresses : fallbackTo,\n cc: message.ccAddresses,\n bcc: message.bccAddresses,\n replyTo: message.replyToAddresses,\n from: message.fromAddress,\n }),\n content: async () => {\n const rendered = message.render()\n return { htmlString: rendered.html, textString: rendered.text }\n },\n attachments: async () => message.attachments,\n }\n}\n\nfunction normalizeRoute(route: unknown): MailRecipient[] {\n if (route === undefined || route === null) return []\n return Array.isArray(route) ? (route as MailRecipient[]) : [route as MailRecipient]\n}\n\nexport class MailNotificationChannel implements NotificationChannel {\n async send(notifiable: Notifiable, notification: Notification): Promise<void> {\n if (!notification.toMail) throw new UnrenderableNotificationMailError(notification.constructor.name)\n const result = await notification.toMail(notifiable)\n const to = normalizeRoute(notifiable.routeNotificationFor('mail', notification))\n\n // Matches Laravel's MailChannel: with no route and no Mailable to supply its own recipients, there's nothing to send.\n if (to.length === 0 && !isMailable(result)) return\n\n const mailable = isMailable(result) ? result : mailableFromMessage(toMailMessage(result, notification.constructor.name), to, notification.constructor.name)\n const container = currentContainer()\n if (!container?.bound(MailManagerToken)) throw new MailUnavailableError()\n // sendNow(), as Laravel's MailChannel sends: a queueable Mailable returned from toMail() is delivered here, not\n // queued a second time (a queued notification already runs on the worker).\n await container.make(MailManagerToken).sendNow(mailable)\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport {\n DatabaseManager as DatabaseManagerToken,\n type DatabaseConnection,\n type ModelInstance,\n type Notifiable,\n type Notification,\n type NotificationChannel,\n} from '@orieljs/contracts'\nimport { currentContainer } from '@orieljs/container'\nimport { DatabaseUnavailableError, NotDatabaseNotifiableError, UnrenderableNotificationDataError } from '../errors'\n\nexport const NOTIFICATIONS_TABLE = 'notifications'\n\n/** What the channel reads from a model notifiable: ModelInstance, plus Database's connection name when it has one. */\ntype StoredNotifiable = Partial<ModelInstance> & { getConnectionName?(): string | undefined }\n\nfunction requireDatabase(connection: string | undefined): DatabaseConnection {\n const container = currentContainer()\n if (!container?.bound(DatabaseManagerToken)) throw new DatabaseUnavailableError()\n return container.make(DatabaseManagerToken).connection(connection)\n}\n\n/**\n * Laravel's DatabaseChannel: one row in `notifications` per notifiable, with what toDatabase() (or toArray())\n * returns as JSON. The row is keyed as `user.notifications()` reads it: `notifiable_type` is the model's morph class\n * (its morph map alias, else its class name) and it goes to the model's own connection.\n */\nexport class DatabaseNotificationChannel implements NotificationChannel {\n async send(notifiable: Notifiable, notification: Notification): Promise<void> {\n if (!notification.toDatabase && !notification.toArray) {\n throw new UnrenderableNotificationDataError(notification.constructor.name)\n }\n const data = notification.toDatabase ? notification.toDatabase(notifiable) : notification.toArray!(notifiable)\n const model = notifiable as StoredNotifiable\n if (typeof model.getKey !== 'function') throw new NotDatabaseNotifiableError(notifiable.constructor.name)\n\n const db = requireDatabase(typeof model.getConnectionName === 'function' ? model.getConnectionName() : undefined)\n const now = new Date()\n await db.table(NOTIFICATIONS_TABLE).insert({\n id: notification.id ?? randomUUID(),\n type: notification.constructor.name,\n notifiable_type: typeof model.getMorphClass === 'function' ? model.getMorphClass() : notifiable.constructor.name,\n notifiable_id: model.getKey(),\n data: JSON.stringify(data),\n read_at: null,\n created_at: now,\n updated_at: now,\n })\n }\n}\n","export const NOTIFICATIONS_TABLE_MIGRATION = `import { Migration, Schema } from '@orieljs/framework'\n\nexport default class extends Migration {\n async up() {\n await Schema.create('notifications', (t) => {\n t.uuid('id').primary()\n t.string('type')\n t.morphs('notifiable')\n t.text('data')\n t.timestamp('read_at').nullable()\n t.timestamps()\n })\n }\n\n async down() {\n await Schema.dropIfExists('notifications')\n }\n}\n`\n","/**\n * The Queue/Bus protocol's shared marker (the \"Queue/Bus protocol\" section of the Queue/Bus design notes) —\n * identical to Mail's `symbols.ts`. `Symbol.for` is a *global* registry key, so this is the literal same runtime\n * symbol Bus's `Queueable` base class and `isQueueable()` use, with no import needed (and none allowed).\n */\nexport const SHOULD_QUEUE: unique symbol = Symbol.for('oriel:bus.shouldQueue')\n\n/**\n * Bus's UNIQUE key, the same `Symbol.for`: the uniqueness a SendQueuedNotifications carries for its notification,\n * read by Bus's isUnique() and UniqueLock. Symbol-keyed, so the payload never carries it.\n */\nexport const UNIQUE: unique symbol = Symbol.for('oriel:bus.unique')\n\n/** Bus's UniqueSpec, declared again here (Notifications can't import Bus): the lock is `oriel_unique_job:<name>:<id>`. */\nexport interface UniqueSpec {\n name: string\n id: string\n for?: number\n untilProcessing?: boolean\n via?: string\n}\n\ninterface UniqueMembers {\n uniqueId?: () => unknown\n uniqueFor?: number | (() => unknown)\n uniqueUntilProcessing?: boolean\n uniqueVia?: () => unknown\n}\n\n/**\n * Laravel's ShouldBeUnique read off a notification, by Bus's isUnique() rule (it defines uniqueId(), uniqueFor,\n * uniqueUntilProcessing or uniqueVia()), each value resolved now, at dispatch. undefined when it declares none.\n */\nexport function uniqueSpecOf(target: object, name: string): UniqueSpec | undefined {\n const members = target as UniqueMembers\n const declared =\n typeof members.uniqueId === 'function' ||\n members.uniqueFor !== undefined ||\n members.uniqueUntilProcessing === true ||\n typeof members.uniqueVia === 'function'\n if (!declared) return undefined\n const id = typeof members.uniqueId === 'function' ? members.uniqueId.call(target) : undefined\n const spec: UniqueSpec = { name, id: id === undefined || id === null ? '' : String(id) }\n const uniqueFor = typeof members.uniqueFor === 'function' ? members.uniqueFor.call(target) : members.uniqueFor\n if (typeof uniqueFor === 'number') spec.for = uniqueFor\n if (members.uniqueUntilProcessing === true) spec.untilProcessing = true\n const via = typeof members.uniqueVia === 'function' ? members.uniqueVia.call(target) : undefined\n if (typeof via === 'string') spec.via = via\n return spec\n}\n","import { randomUUID } from 'node:crypto'\nimport {\n BusDispatcher as BusDispatcherToken,\n type Container,\n type Notifiable,\n type Notification,\n type NotificationChannel,\n type NotificationDispatcher as NotificationDispatcherContract,\n} from '@orieljs/contracts'\nimport { AnonymousNotifiable } from './AnonymousNotifiable'\nimport { NotificationQueueUnavailableError, UnknownNotificationChannelError } from './errors'\nimport { SendQueuedNotifications } from './Jobs/SendQueuedNotifications'\nimport { SHOULD_QUEUE } from './symbols'\n\n/**\n * Matches the Queue/Bus protocol's `isQueueable()` exactly: true when the notification carries ShouldQueue's\n * `shouldQueue: true` or `SHOULD_QUEUE === true`, or has a defined value for any of ShouldQueue's fields, or\n * declares a `uniqueId()`/`retryUntil()` method (same rule as Mail's `isQueueableMailable`). Call sendNow() to send\n * a queueable notification right away.\n */\nfunction isQueueableNotification(notification: Notification): boolean {\n const record = notification as unknown as Record<PropertyKey, unknown>\n if (record.shouldQueue === true || record[SHOULD_QUEUE] === true) return true\n return (\n record.connection !== undefined ||\n record.queue !== undefined ||\n record.delay !== undefined ||\n record.tries !== undefined ||\n record.backoff !== undefined ||\n record.timeout !== undefined ||\n typeof record.uniqueId === 'function' ||\n typeof record.retryUntil === 'function'\n )\n}\n\n/**\n * The notification one notifiable receives. With no id set, a shallow copy (same prototype, own fields copied)\n * carrying a fresh id, so every notifiable gets its own id (the database channel's primary key) and the caller's\n * instance is left untouched; a caller-preset id is kept as is. The copy keeps its class, so Queue's serializer\n * restores it as that class on the worker.\n */\nfunction forNotifiable(notification: Notification): Notification {\n if (notification.id) return notification\n const copy = Object.assign(Object.create(Object.getPrototypeOf(notification) as object) as Notification, notification)\n copy.id = randomUUID()\n return copy\n}\n\n/**\n * One notifiable, an array, or any other iterable of them (a Database `Collection` from `await User.where(...).get()`,\n * a Set, ...), as Laravel's `Notification::send($users, ...)` accepts. A notifiable is recognised by its\n * routeNotificationFor() method first, so an iterable model is still treated as one notifiable.\n */\nexport function toNotifiableList(notifiables: Notifiable | Iterable<Notifiable>): Notifiable[] {\n if (Array.isArray(notifiables)) return notifiables as Notifiable[]\n if (typeof (notifiables as Partial<Notifiable>).routeNotificationFor === 'function') return [notifiables as Notifiable]\n if (typeof notifiables === 'object' && notifiables !== null && Symbol.iterator in notifiables) {\n return [...(notifiables as Iterable<Notifiable>)]\n }\n return [notifiables as Notifiable]\n}\n\n/** Resolves channels by name from a factory map — mirrors MailManager's driver resolution. */\nexport class Dispatcher implements NotificationDispatcherContract {\n private readonly factories = new Map<string, () => NotificationChannel>()\n private readonly channels = new Map<string, NotificationChannel>()\n\n constructor(private readonly container: Container) {}\n\n extend(channel: string, factory: () => NotificationChannel): void {\n this.factories.set(channel, factory)\n this.channels.delete(channel)\n }\n\n private resolve(name: string): NotificationChannel {\n const cached = this.channels.get(name)\n if (cached) return cached\n const factory = this.factories.get(name)\n if (!factory) throw new UnknownNotificationChannelError(name)\n const channel = factory()\n this.channels.set(name, channel)\n return channel\n }\n\n async send(notifiables: Notifiable | Iterable<Notifiable>, notification: Notification): Promise<void> {\n const list = toNotifiableList(notifiables)\n if (list.length === 0) return\n if (isQueueableNotification(notification)) {\n if (!this.container.bound(BusDispatcherToken)) throw new NotificationQueueUnavailableError()\n const bus = this.container.make(BusDispatcherToken)\n // One job per notifiable per channel, as Laravel does, so a failing channel or recipient retries alone.\n for (const notifiable of list) {\n const own = forNotifiable(notification)\n for (const channel of notification.via(notifiable)) {\n await bus.dispatch(new SendQueuedNotifications([notifiable], own, [channel]))\n }\n }\n return\n }\n await this.sendNow(list, notification)\n }\n\n async sendNow(notifiables: Notifiable | Iterable<Notifiable>, notification: Notification, channels?: string[]): Promise<void> {\n const list = toNotifiableList(notifiables)\n for (const notifiable of list) {\n const own = forNotifiable(notification)\n const names = channels ?? notification.via(notifiable)\n for (const name of names) {\n // An on-demand notifiable has no model to store a row against, so it skips 'database' as in Laravel.\n if (name === 'database' && notifiable instanceof AnonymousNotifiable) continue\n await this.resolve(name).send(notifiable, own)\n }\n }\n }\n\n /** On-demand: an AnonymousNotifiable, whose notify() goes through the bound NotificationDispatcher. */\n route(channel: string, route: unknown): AnonymousNotifiable {\n return new AnonymousNotifiable().route(channel, route)\n }\n}\n","import {\n NotificationDispatcher as NotificationDispatcherToken,\n type Notifiable as NotifiableContract,\n type Notification,\n type NotificationDispatcher,\n} from '@orieljs/contracts'\nimport { currentContainer } from '@orieljs/container'\nimport { Str } from '@orieljs/support'\nimport { NotificationDispatcherUnavailableError } from './errors'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type NotifiableConstructor = new (...args: any[]) => object\n\n/** The bound NotificationDispatcher, or a clear error when the Notifications component isn't registered. */\nexport function resolveDispatcher(): NotificationDispatcher {\n const container = currentContainer()\n if (!container?.bound(NotificationDispatcherToken)) throw new NotificationDispatcherUnavailableError()\n return container.make(NotificationDispatcherToken)\n}\n\n/**\n * Laravel's RoutesNotifications trait: `notify()`, `notifyNow()` and `routeNotificationFor()`. Apps use\n * `Notifiable` from '@orieljs/framework', which adds the database notifications relations. Custom routing per\n * channel follows Laravel's order: a `routeNotificationFor<Channel>()` method (Studly name) wins if present, else\n * 'mail' reads `email`/`getAttribute('email')`, 'database' returns the instance itself (the database channel derives\n * the row from it), and anything else is unrouted (`undefined`).\n */\nexport function RoutesNotifications<TBase extends NotifiableConstructor>(Base: TBase) {\n return class RoutesNotificationsMixin extends Base implements NotifiableContract {\n routeNotificationFor(channel: string, notification: Notification): unknown {\n const custom = (this as unknown as Record<string, unknown>)[`routeNotificationFor${Str.studly(channel)}`]\n if (typeof custom === 'function') return (custom as (n: Notification) => unknown).call(this, notification)\n if (channel === 'mail') {\n const withAttrs = this as unknown as { email?: string; getAttribute?: (key: string) => unknown }\n return typeof withAttrs.getAttribute === 'function' ? withAttrs.getAttribute('email') : withAttrs.email\n }\n if (channel === 'database') return this\n return undefined\n }\n\n async notify(notification: Notification): Promise<void> {\n await resolveDispatcher().send(this as unknown as NotifiableContract, notification)\n }\n\n async notifyNow(notification: Notification, channels?: string[]): Promise<void> {\n await resolveDispatcher().sendNow(this as unknown as NotifiableContract, notification, channels)\n }\n }\n}\n","import type { Notifiable, Notification } from '@orieljs/contracts'\nimport { resolveDispatcher } from './RoutesNotifications'\n\n/**\n * Returned by `Notification.route(channel, address)` for sending to an address with no model behind it. The\n * routes are an own plain record and every method sits on the prototype, so a queued on-demand notification\n * survives Queue's serializer (which rejects functions in own fields) and is restored on the worker as this class\n * (registered as `'Notifications.AnonymousNotifiable'` in ClassRegistry).\n */\nexport class AnonymousNotifiable implements Notifiable {\n routes: Record<string, unknown> = {}\n\n /** Adds a route; chains, so one on-demand notifiable can go to several channels. */\n route(channel: string, route: unknown): this {\n this.routes[channel] = route\n return this\n }\n\n routeNotificationFor(channel: string): unknown {\n return this.routes[channel]\n }\n\n async notify(notification: Notification): Promise<void> {\n await resolveDispatcher().send(this, notification)\n }\n\n async notifyNow(notification: Notification, channels?: string[]): Promise<void> {\n await resolveDispatcher().sendNow(this, notification, channels)\n }\n}\n","import { createHash } from 'node:crypto'\nimport { NotificationDispatcher as NotificationDispatcherToken, type Job, type Notifiable, type Notification } from '@orieljs/contracts'\nimport { inject } from '@orieljs/container'\nimport { SHOULD_QUEUE, UNIQUE, uniqueSpecOf } from '../symbols'\n\n/** The ShouldQueue options copied from the notification onto the wrapper job. */\nconst QUEUE_OPTIONS = [\n 'connection',\n 'queue',\n 'delay',\n 'tries',\n 'backoff',\n 'timeout',\n 'failOnTimeout',\n 'deleteWhenMissingModels',\n] as const\n\n/** The job hooks Queue reads off a job instance, when the wrapped notification defines them. */\ninterface JobHooks {\n failed?: (error: unknown) => unknown\n retryUntil?: () => unknown\n middleware?: unknown[] | (() => unknown[])\n}\n\n/**\n * Copies the notification's own ShouldQueue fields onto the wrapper job (when set) — same reasoning as Mail's\n * SendQueuedMailable. Uses `declare` fields on the class (no runtime auto-initialization to `undefined`), so a\n * field is a genuinely own property only when the notification actually set it.\n */\nfunction copyShouldQueueFields(target: Record<PropertyKey, unknown>, source: object): void {\n const fields = source as Record<string, unknown>\n for (const option of QUEUE_OPTIONS) {\n if (fields[option] !== undefined) target[option] = fields[option]\n }\n}\n\n/**\n * Who a notifiable is, for a unique notification's lock: a model by its morph class and key, anything else by a sha1\n * of its route for the channel (an on-demand notifiable's address: the lock key stays short and holds no address),\n * else by its class.\n */\nfunction notifiableIdentity(notifiable: Notifiable, channel: string | undefined, notification: Notification): string {\n const model = notifiable as Notifiable & { getKey?: () => unknown; getMorphClass?: () => string }\n if (typeof model.getKey === 'function') return `${model.getMorphClass?.() ?? notifiable.constructor.name}:${String(model.getKey())}`\n const route = channel === undefined ? undefined : notifiable.routeNotificationFor(channel, notification)\n if (route === undefined || route === null) return notifiable.constructor.name\n return createHash('sha1').update(typeof route === 'string' ? route : JSON.stringify(route)).digest('hex')\n}\n\n/**\n * Dispatched by Dispatcher.send() when the notification is queueable. Runs on the worker: calls sendNow()\n * directly (never send()), so it can't re-detect itself as queueable and loop.\n *\n * Carries `SHOULD_QUEUE` on its own prototype, per the Queue/Bus protocol, so `BusDispatcher.dispatch()` — which\n * re-decides queued-or-now on whatever job it's handed — actually enqueues it instead of running it inline; the\n * notification's own queueability (checked by Dispatcher, above) only decides whether to wrap it at all. Like\n * Laravel's SendQueuedNotifications, it forwards failed(), retryUntil(), middleware() and displayName() to the\n * notification. Unlike Laravel's, it is unique when the notification is (Laravel queues it past the unique lock).\n */\nexport class SendQueuedNotifications implements Job {\n declare connection?: string\n declare queue?: string\n declare delay?: number\n declare tries?: number\n declare backoff?: number | number[]\n declare timeout?: number\n declare failOnTimeout?: boolean\n declare deleteWhenMissingModels?: boolean\n\n constructor(\n readonly notifiables: Notifiable[],\n readonly notification: Notification,\n readonly channels?: string[],\n ) {\n copyShouldQueueFields(this as unknown as Record<PropertyKey, unknown>, notification)\n // A unique notification (uniqueId(), uniqueFor, ...) makes this job unique, under the notification's class. The\n // channel and the notifiable are part of its id: Dispatcher queues one job per notifiable per channel, and a\n // duplicate is the same notification to the same notifiable on the same channel.\n const unique = uniqueSpecOf(notification, notification.constructor.name)\n if (unique) {\n const recipients = notifiables.map((notifiable) => notifiableIdentity(notifiable, channels?.[0], notification))\n unique.id = [unique.id, channels?.join(',') ?? '', recipients.join(',')].join(':')\n ;(this as unknown as Record<PropertyKey, unknown>)[UNIQUE] = unique\n }\n }\n\n async handle(): Promise<void> {\n await inject(NotificationDispatcherToken).sendNow(this.notifiables, this.notification, this.channels)\n }\n\n /** Called by Queue after the final failed attempt. */\n async failed(error: unknown): Promise<void> {\n const hooks = this.notification as unknown as JobHooks\n if (typeof hooks.failed === 'function') await hooks.failed.call(this.notification, error)\n }\n\n /** `failed_jobs` and `queue:failed` show the notification's class, not the wrapper's. */\n displayName(): string {\n return this.notification.constructor.name\n }\n\n retryUntil(): unknown {\n const hooks = this.notification as unknown as JobHooks\n return typeof hooks.retryUntil === 'function' ? hooks.retryUntil.call(this.notification) : undefined\n }\n\n middleware(): unknown[] {\n const declared = (this.notification as unknown as JobHooks).middleware\n return (typeof declared === 'function' ? declared.call(this.notification) : declared) ?? []\n }\n}\n;(SendQueuedNotifications.prototype as unknown as Record<PropertyKey, unknown>)[SHOULD_QUEUE] = true\n","import {\n NotificationDispatcher as NotificationDispatcherToken,\n type Notifiable,\n type Notification as NotificationContract,\n type NotificationChannel,\n type NotificationDispatcher,\n} from '@orieljs/contracts'\nimport { facade } from '@orieljs/support'\nimport { toNotifiableList } from './Dispatcher'\nimport { resolveDispatcher } from './RoutesNotifications'\n\n/**\n * Mirrors Laravel's Illuminate\\Notifications\\Notification: override via(), and whichever toXxx() the channels\n * need.\n *\n * The statics are the `Notification` facade, so one export serves both `class InvoicePaid extends Notification`\n * and `Notification.send(users, new InvoicePaid())`.\n */\nexport abstract class Notification implements NotificationContract {\n id?: string\n abstract via(notifiable: Notifiable): string[]\n\n /** Queues it when the notification is queueable, otherwise sends now. Accepts one notifiable, an array or any iterable. */\n static send(notifiables: Notifiable | Iterable<Notifiable>, notification: NotificationContract): Promise<void> {\n return resolveDispatcher().send(toNotifiableList(notifiables), notification)\n }\n\n static sendNow(\n notifiables: Notifiable | Iterable<Notifiable>,\n notification: NotificationContract,\n channels?: string[],\n ): Promise<void> {\n return resolveDispatcher().sendNow(toNotifiableList(notifiables), notification, channels)\n }\n\n /** On-demand: `Notification.route('mail', 'a@b.c').notify(new InvoicePaid())`. */\n static route(channel: string, route: unknown): Notifiable & { notify(notification: NotificationContract): Promise<void> } {\n return resolveDispatcher().route(channel, route)\n }\n\n static extend(channel: string, factory: () => NotificationChannel): void {\n resolveDispatcher().extend(channel, factory)\n }\n\n /** Replace the dispatcher in the current container (what a NotificationFake uses). */\n static swap(fake: NotificationDispatcher): void {\n facade<NotificationDispatcher>(NotificationDispatcherToken).swap(fake)\n }\n}\n","import type { NotificationDispatcher } from '@orieljs/contracts'\nimport { NotificationDispatcher as NotificationDispatcherToken } from '@orieljs/contracts'\nimport { facade } from '@orieljs/support'\n\n/**\n * The plain facade over NotificationDispatcher. App code normally uses the `Notification` base class's statics\n * instead (one `Notification` export is both the base class and the facade); this stays exported for code that\n * wants the contract-typed proxy (getFacadeRoot(), swap()).\n */\nexport const NotificationFacade = facade<NotificationDispatcher>(NotificationDispatcherToken)\n","import { ClassRegistry as ClassRegistryToken, NotificationDispatcher as NotificationDispatcherToken } from '@orieljs/contracts'\nimport { ServiceProvider } from '@orieljs/support'\nimport { AnonymousNotifiable } from './AnonymousNotifiable'\nimport { DatabaseNotificationChannel } from './channels/DatabaseNotificationChannel'\nimport { MailNotificationChannel } from './channels/MailNotificationChannel'\nimport { Dispatcher } from './Dispatcher'\nimport { SendQueuedNotifications } from './Jobs/SendQueuedNotifications'\n\nexport class NotificationsServiceProvider extends ServiceProvider {\n override register(): void {\n this.app.singleton(NotificationDispatcherToken, (container) => new Dispatcher(container))\n // Namespaced names, registered in register() (not boot()) — the Queue/Bus protocol rule for wrapper classes.\n // ClassRegistry is safe to use here: Application's own constructor binds it before any provider registers.\n const registry = this.app.make(ClassRegistryToken)\n registry.register(SendQueuedNotifications, 'Notifications.SendQueuedNotifications')\n registry.register(AnonymousNotifiable, 'Notifications.AnonymousNotifiable')\n }\n\n override boot(): void {\n const dispatcher = this.app.make(NotificationDispatcherToken)\n dispatcher.extend('mail', () => new MailNotificationChannel())\n dispatcher.extend('database', () => new DatabaseNotificationChannel())\n }\n}\n"],"mappings":"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","names":["text","currentContainer","currentContainer","currentContainer","currentContainer","randomUUID","currentContainer","Str","currentContainer","Str","NotificationDispatcherToken","NotificationDispatcherToken","randomUUID","NotificationDispatcherToken","NotificationDispatcherToken","NotificationDispatcherToken","facade","NotificationDispatcherToken","NotificationDispatcherToken"]}
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@orieljs/notifications",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.11",
4
4
  "description": "The Oriel Notifications component.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
+ "homepage": "https://orieljs.vercel.app",
7
8
  "sideEffects": false,
8
9
  "repository": {
9
10
  "type": "git",
@@ -26,12 +27,13 @@
26
27
  "files": [
27
28
  "dist",
28
29
  "LICENSE",
29
- "NOTICE"
30
+ "NOTICE",
31
+ "TRADEMARKS.md"
30
32
  ],
31
33
  "dependencies": {
32
- "@orieljs/contracts": "0.1.0-alpha.1",
33
- "@orieljs/container": "0.1.0-alpha.1",
34
- "@orieljs/support": "0.1.0-alpha.1"
34
+ "@orieljs/support": "0.1.0-alpha.11",
35
+ "@orieljs/container": "0.1.0-alpha.11",
36
+ "@orieljs/contracts": "0.1.0-alpha.11"
35
37
  },
36
38
  "scripts": {
37
39
  "build": "tsup && node ../../../scripts/copy-license.mjs"