@orieljs/notifications 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/NOTICE ADDED
@@ -0,0 +1,9 @@
1
+ Oriel
2
+ Copyright 2026 meeeet-dev and Oriel contributors
3
+
4
+ This product includes software developed by the Oriel project
5
+ (https://orieljs.vercel.app, https://github.com/orieljs).
6
+
7
+ Oriel is licensed under the Apache License, Version 2.0 (see LICENSE).
8
+ The Oriel name and logo are not covered by that license; see TRADEMARKS.md
9
+ (https://github.com/orieljs/framework/blob/main/TRADEMARKS.md).
@@ -0,0 +1,260 @@
1
+ import { OrielError, MailRecipient, NotificationChannel, Notifiable as Notifiable$1, Notification as Notification$1, NotificationDispatcher, Container, Job } from '@orieljs/contracts';
2
+ import * as _orieljs_support from '@orieljs/support';
3
+ import { ServiceProvider } from '@orieljs/support';
4
+
5
+ declare class UnrenderableNotificationMailError extends Error implements OrielError {
6
+ readonly kind: "Server";
7
+ readonly status = 500;
8
+ readonly name = "UnrenderableNotificationMailError";
9
+ constructor(notification: string);
10
+ }
11
+ declare class MailUnavailableError extends Error implements OrielError {
12
+ readonly kind: "Server";
13
+ readonly status = 500;
14
+ readonly name = "MailUnavailableError";
15
+ constructor();
16
+ }
17
+ declare class DatabaseUnavailableError extends Error implements OrielError {
18
+ readonly kind: "Server";
19
+ readonly status = 500;
20
+ readonly name = "DatabaseUnavailableError";
21
+ constructor();
22
+ }
23
+ declare class NotDatabaseNotifiableError extends Error implements OrielError {
24
+ readonly kind: "Server";
25
+ readonly status = 500;
26
+ readonly name = "NotDatabaseNotifiableError";
27
+ constructor(notifiable: string);
28
+ }
29
+ declare class UnrenderableNotificationDataError extends Error implements OrielError {
30
+ readonly kind: "Server";
31
+ readonly status = 500;
32
+ readonly name = "UnrenderableNotificationDataError";
33
+ constructor(notification: string);
34
+ }
35
+ declare class UnknownNotificationChannelError extends Error implements OrielError {
36
+ readonly kind: "Server";
37
+ readonly status = 500;
38
+ readonly name = "UnknownNotificationChannelError";
39
+ constructor(channel: string);
40
+ }
41
+ declare class NotificationQueueUnavailableError extends Error implements OrielError {
42
+ readonly kind: "Server";
43
+ readonly status = 500;
44
+ readonly name = "NotificationQueueUnavailableError";
45
+ constructor();
46
+ }
47
+ declare class NotificationDispatcherUnavailableError extends Error implements OrielError {
48
+ readonly kind: "Server";
49
+ readonly status = 500;
50
+ readonly name = "NotificationDispatcherUnavailableError";
51
+ constructor();
52
+ }
53
+
54
+ interface MailMessageAction {
55
+ text: string;
56
+ url: string;
57
+ }
58
+ /**
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.
62
+ */
63
+ declare class MailMessage {
64
+ subjectLine?: string;
65
+ greetingLine?: string;
66
+ introLines: string[];
67
+ actionData?: MailMessageAction;
68
+ outroLines: string[];
69
+ salutationLine: string;
70
+ toAddresses: MailRecipient[];
71
+ ccAddresses: MailRecipient[];
72
+ bccAddresses: MailRecipient[];
73
+ fromAddress?: MailRecipient;
74
+ subject(subject: string): this;
75
+ greeting(greeting: string): this;
76
+ /** Before action(), appends to the intro; after action(), to the outro. */
77
+ line(text: string): this;
78
+ lines(lines: string[]): this;
79
+ action(text: string, url: string): this;
80
+ salutation(salutation: string): this;
81
+ from(address: MailRecipient): this;
82
+ to(recipients: MailRecipient | MailRecipient[]): this;
83
+ cc(recipients: MailRecipient | MailRecipient[]): this;
84
+ bcc(recipients: MailRecipient | MailRecipient[]): this;
85
+ /** Renders the default notification mail template. No ViewFactory involved. */
86
+ render(): {
87
+ html: string;
88
+ text: string;
89
+ };
90
+ }
91
+
92
+ /**
93
+ * What Auth's default ResetPassword notification's toMail() returns: plain data, since Auth can't import
94
+ * Notifications. `{ subject, lines, actionText, actionUrl, outroLines }` exactly; `greeting`/`salutation` are
95
+ * optional extensions any other notification's plain toMail() may also use.
96
+ */
97
+ interface PlainNotificationMail {
98
+ subject?: string;
99
+ greeting?: string;
100
+ lines: string[];
101
+ actionText?: string;
102
+ actionUrl?: string;
103
+ outroLines?: string[];
104
+ salutation?: string;
105
+ }
106
+ declare class MailNotificationChannel implements NotificationChannel {
107
+ send(notifiable: Notifiable$1, notification: Notification$1): Promise<void>;
108
+ }
109
+
110
+ 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
+ /**
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.
124
+ */
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
+ declare class DatabaseNotificationChannel implements NotificationChannel {
129
+ send(notifiable: Notifiable$1, notification: Notification$1): Promise<void>;
130
+ }
131
+
132
+ 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";
133
+
134
+ /**
135
+ * The Queue/Bus protocol's shared marker (the "Queue/Bus protocol" section of the Queue/Bus design notes) —
136
+ * identical to Mail's `symbols.ts`. `Symbol.for` is a *global* registry key, so this is the literal same runtime
137
+ * symbol Bus's `Queueable` base class and `isQueueable()` use, with no import needed (and none allowed).
138
+ */
139
+ declare const SHOULD_QUEUE: unique symbol;
140
+
141
+ /**
142
+ * Returned by `Notification.route(channel, address)` for sending to an address with no model behind it. The
143
+ * routes are an own plain record and every method sits on the prototype, so a queued on-demand notification
144
+ * survives Queue's serializer (which rejects functions in own fields) and is restored on the worker as this class
145
+ * (registered as `'Notifications.AnonymousNotifiable'` in ClassRegistry).
146
+ */
147
+ declare class AnonymousNotifiable implements Notifiable$1 {
148
+ routes: Record<string, unknown>;
149
+ /** Adds a route; chains, so one on-demand notifiable can go to several channels. */
150
+ route(channel: string, route: unknown): this;
151
+ routeNotificationFor(channel: string): unknown;
152
+ notify(notification: Notification$1): Promise<void>;
153
+ notifyNow(notification: Notification$1, channels?: string[]): Promise<void>;
154
+ }
155
+
156
+ /**
157
+ * One notifiable, an array, or any other iterable of them (a Database `Collection` from `await User.where(...).get()`,
158
+ * a Set, ...), as Laravel's `Notification::send($users, ...)` accepts. A notifiable is recognised by its
159
+ * routeNotificationFor() method first, so an iterable model is still treated as one notifiable.
160
+ */
161
+ declare function toNotifiableList(notifiables: Notifiable$1 | Iterable<Notifiable$1>): Notifiable$1[];
162
+ /** Resolves channels by name from a factory map — mirrors MailManager's driver resolution. */
163
+ declare class Dispatcher implements NotificationDispatcher {
164
+ private readonly container;
165
+ private readonly factories;
166
+ private readonly channels;
167
+ constructor(container: Container);
168
+ extend(channel: string, factory: () => NotificationChannel): void;
169
+ 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>;
172
+ /** On-demand: an AnonymousNotifiable, whose notify() goes through the bound NotificationDispatcher. */
173
+ route(channel: string, route: unknown): AnonymousNotifiable;
174
+ }
175
+
176
+ /**
177
+ * Dispatched by Dispatcher.send() when the notification is queueable. Runs on the worker: calls sendNow()
178
+ * directly (never send()), so it can't re-detect itself as queueable and loop.
179
+ *
180
+ * Carries `SHOULD_QUEUE` on its own prototype, per the Queue/Bus protocol, so `BusDispatcher.dispatch()` — which
181
+ * re-decides queued-or-now on whatever job it's handed — actually enqueues it instead of running it inline; the
182
+ * notification's own queueability (checked by Dispatcher, above) only decides whether to wrap it at all. Like
183
+ * Laravel's SendQueuedNotifications, it forwards failed(), retryUntil(), middleware() and displayName() to the
184
+ * notification.
185
+ */
186
+ declare class SendQueuedNotifications implements Job {
187
+ readonly notifiables: Notifiable$1[];
188
+ readonly notification: Notification$1;
189
+ readonly channels?: string[] | undefined;
190
+ connection?: string;
191
+ queue?: string;
192
+ delay?: number;
193
+ tries?: number;
194
+ backoff?: number | number[];
195
+ timeout?: number;
196
+ failOnTimeout?: boolean;
197
+ deleteWhenMissingModels?: boolean;
198
+ constructor(notifiables: Notifiable$1[], notification: Notification$1, channels?: string[] | undefined);
199
+ handle(): Promise<void>;
200
+ /** Called by Queue after the final failed attempt. */
201
+ failed(error: unknown): Promise<void>;
202
+ /** `failed_jobs` and `queue:failed` show the notification's class, not the wrapper's. */
203
+ displayName(): string;
204
+ retryUntil(): unknown;
205
+ middleware(): unknown[];
206
+ }
207
+
208
+ type NotifiableConstructor = new (...args: any[]) => object;
209
+ /**
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`).
214
+ */
215
+ declare function Notifiable<TBase extends NotifiableConstructor>(Base: TBase): {
216
+ new (...args: any[]): {
217
+ routeNotificationFor(channel: string, notification: Notification$1): unknown;
218
+ notify(notification: Notification$1): Promise<void>;
219
+ notifyNow(notification: Notification$1, channels?: string[]): Promise<void>;
220
+ notifications(): Promise<DatabaseNotificationRecord[]>;
221
+ unreadNotifications(): Promise<DatabaseNotificationRecord[]>;
222
+ readNotifications(): Promise<DatabaseNotificationRecord[]>;
223
+ };
224
+ } & TBase;
225
+
226
+ /**
227
+ * Mirrors Laravel's Illuminate\Notifications\Notification: override via(), and whichever toXxx() the channels
228
+ * need.
229
+ *
230
+ * The statics are the `Notification` facade, so one export serves both `class InvoicePaid extends Notification`
231
+ * and `Notification.send(users, new InvoicePaid())`.
232
+ */
233
+ declare abstract class Notification implements Notification$1 {
234
+ id?: string;
235
+ abstract via(notifiable: Notifiable$1): string[];
236
+ /** 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>;
239
+ /** On-demand: `Notification.route('mail', 'a@b.c').notify(new InvoicePaid())`. */
240
+ static route(channel: string, route: unknown): Notifiable$1 & {
241
+ notify(notification: Notification$1): Promise<void>;
242
+ };
243
+ static extend(channel: string, factory: () => NotificationChannel): void;
244
+ /** Replace the dispatcher in the current container (what a NotificationFake uses). */
245
+ static swap(fake: NotificationDispatcher): void;
246
+ }
247
+
248
+ /**
249
+ * The plain facade over NotificationDispatcher. App code normally uses the `Notification` base class's statics
250
+ * instead (one `Notification` export is both the base class and the facade); this stays exported for code that
251
+ * wants the contract-typed proxy (getFacadeRoot(), swap()).
252
+ */
253
+ declare const NotificationFacade: _orieljs_support.Facade<NotificationDispatcher, object>;
254
+
255
+ declare class NotificationsServiceProvider extends ServiceProvider {
256
+ register(): void;
257
+ boot(): void;
258
+ }
259
+
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 };
package/dist/index.js ADDED
@@ -0,0 +1,571 @@
1
+ // errors.ts
2
+ var UnrenderableNotificationMailError = class extends Error {
3
+ kind = "Server";
4
+ status = 500;
5
+ name = "UnrenderableNotificationMailError";
6
+ constructor(notification) {
7
+ super(
8
+ `[${notification}].toMail() must return a Mailable, a MailMessage, or { subject?, greeting?, lines, actionText?, actionUrl?, outroLines?, salutation? }.`
9
+ );
10
+ }
11
+ };
12
+ var MailUnavailableError = class extends Error {
13
+ kind = "Server";
14
+ status = 500;
15
+ name = "MailUnavailableError";
16
+ constructor() {
17
+ super("Sending a notification's mail requires the Mail component. Register MailManager first.");
18
+ }
19
+ };
20
+ var DatabaseUnavailableError = class extends Error {
21
+ kind = "Server";
22
+ status = 500;
23
+ name = "DatabaseUnavailableError";
24
+ constructor() {
25
+ super("The database notification channel requires the Database component. Register DatabaseManager first.");
26
+ }
27
+ };
28
+ var NotDatabaseNotifiableError = class extends Error {
29
+ kind = "Server";
30
+ status = 500;
31
+ name = "NotDatabaseNotifiableError";
32
+ constructor(notifiable) {
33
+ super(`[${notifiable}] has no getKey() \u2014 the database channel and notification-listing methods need a model.`);
34
+ }
35
+ };
36
+ var UnrenderableNotificationDataError = class extends Error {
37
+ kind = "Server";
38
+ status = 500;
39
+ name = "UnrenderableNotificationDataError";
40
+ constructor(notification) {
41
+ super(`[${notification}] needs a toDatabase() or toArray() method to use the database channel.`);
42
+ }
43
+ };
44
+ var UnknownNotificationChannelError = class extends Error {
45
+ kind = "Server";
46
+ status = 500;
47
+ name = "UnknownNotificationChannelError";
48
+ constructor(channel) {
49
+ super(`Notification channel [${channel}] is not registered. Register it first with NotificationDispatcher.extend().`);
50
+ }
51
+ };
52
+ var NotificationQueueUnavailableError = class extends Error {
53
+ kind = "Server";
54
+ status = 500;
55
+ name = "NotificationQueueUnavailableError";
56
+ constructor() {
57
+ super("Queueing a notification requires the Bus component. Register BusDispatcher, or call sendNow() instead.");
58
+ }
59
+ };
60
+ var NotificationDispatcherUnavailableError = class extends Error {
61
+ kind = "Server";
62
+ status = 500;
63
+ name = "NotificationDispatcherUnavailableError";
64
+ constructor() {
65
+ super("notify()/notifyNow() require the Notifications component. Register NotificationDispatcher first.");
66
+ }
67
+ };
68
+
69
+ // MailMessage.ts
70
+ var MailMessage = class {
71
+ subjectLine;
72
+ greetingLine;
73
+ introLines = [];
74
+ actionData;
75
+ outroLines = [];
76
+ salutationLine = "Regards,\nApplication";
77
+ toAddresses = [];
78
+ ccAddresses = [];
79
+ bccAddresses = [];
80
+ fromAddress;
81
+ subject(subject) {
82
+ this.subjectLine = subject;
83
+ return this;
84
+ }
85
+ greeting(greeting) {
86
+ this.greetingLine = greeting;
87
+ return this;
88
+ }
89
+ /** Before action(), appends to the intro; after action(), to the outro. */
90
+ line(text) {
91
+ ;
92
+ (this.actionData ? this.outroLines : this.introLines).push(text);
93
+ return this;
94
+ }
95
+ lines(lines) {
96
+ for (const line of lines) this.line(line);
97
+ return this;
98
+ }
99
+ action(text, url) {
100
+ this.actionData = { text, url };
101
+ return this;
102
+ }
103
+ salutation(salutation) {
104
+ this.salutationLine = salutation;
105
+ return this;
106
+ }
107
+ from(address) {
108
+ this.fromAddress = address;
109
+ return this;
110
+ }
111
+ to(recipients) {
112
+ this.toAddresses.push(...Array.isArray(recipients) ? recipients : [recipients]);
113
+ return this;
114
+ }
115
+ cc(recipients) {
116
+ this.ccAddresses.push(...Array.isArray(recipients) ? recipients : [recipients]);
117
+ return this;
118
+ }
119
+ bcc(recipients) {
120
+ this.bccAddresses.push(...Array.isArray(recipients) ? recipients : [recipients]);
121
+ return this;
122
+ }
123
+ /** Renders the default notification mail template. No ViewFactory involved. */
124
+ 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") };
144
+ }
145
+ };
146
+ function escapeHtml(value) {
147
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
148
+ }
149
+
150
+ // channels/MailNotificationChannel.ts
151
+ import {
152
+ MailManager as MailManagerToken
153
+ } from "@orieljs/contracts";
154
+ import { currentContainer } from "@orieljs/container";
155
+ function isMailable(value) {
156
+ return typeof value === "object" && value !== null && typeof value.envelope === "function" && typeof value.content === "function";
157
+ }
158
+ function isPlainNotificationMail(value) {
159
+ return typeof value === "object" && value !== null && Array.isArray(value.lines);
160
+ }
161
+ function toMailMessage(result, notificationName) {
162
+ if (result instanceof MailMessage) return result;
163
+ if (!isPlainNotificationMail(result)) throw new UnrenderableNotificationMailError(notificationName);
164
+ const message = new MailMessage();
165
+ if (result.subject) message.subject(result.subject);
166
+ if (result.greeting) message.greeting(result.greeting);
167
+ message.lines(result.lines);
168
+ if (result.actionText && result.actionUrl) message.action(result.actionText, result.actionUrl);
169
+ for (const line of result.outroLines ?? []) message.line(line);
170
+ if (result.salutation) message.salutation(result.salutation);
171
+ return message;
172
+ }
173
+ function mailableFromMessage(message, fallbackTo) {
174
+ return {
175
+ envelope: async () => ({
176
+ subject: message.subjectLine ?? "",
177
+ to: message.toAddresses.length > 0 ? message.toAddresses : fallbackTo,
178
+ cc: message.ccAddresses,
179
+ bcc: message.bccAddresses,
180
+ from: message.fromAddress
181
+ }),
182
+ content: async () => {
183
+ const rendered = message.render();
184
+ return { htmlString: rendered.html, textString: rendered.text };
185
+ }
186
+ };
187
+ }
188
+ function normalizeRoute(route) {
189
+ if (route === void 0 || route === null) return [];
190
+ return Array.isArray(route) ? route : [route];
191
+ }
192
+ var MailNotificationChannel = class {
193
+ async send(notifiable, notification) {
194
+ if (!notification.toMail) throw new UnrenderableNotificationMailError(notification.constructor.name);
195
+ const result = await notification.toMail(notifiable);
196
+ const to = normalizeRoute(notifiable.routeNotificationFor("mail", notification));
197
+ if (to.length === 0 && !isMailable(result)) return;
198
+ const mailable = isMailable(result) ? result : mailableFromMessage(toMailMessage(result, notification.constructor.name), to);
199
+ const container = currentContainer();
200
+ if (!container?.bound(MailManagerToken)) throw new MailUnavailableError();
201
+ await container.make(MailManagerToken).send(mailable);
202
+ }
203
+ };
204
+
205
+ // channels/DatabaseNotificationChannel.ts
206
+ import { randomUUID } from "crypto";
207
+ import {
208
+ DatabaseManager as DatabaseManagerToken
209
+ } from "@orieljs/contracts";
210
+ import { currentContainer as currentContainer2 } from "@orieljs/container";
211
+ 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();
243
+ 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));
253
+ }
254
+ var DatabaseNotificationChannel = class {
255
+ async send(notifiable, notification) {
256
+ if (!notification.toDatabase && !notification.toArray) {
257
+ throw new UnrenderableNotificationDataError(notification.constructor.name);
258
+ }
259
+ const data = notification.toDatabase ? notification.toDatabase(notifiable) : notification.toArray(notifiable);
260
+ const model = notifiable;
261
+ if (typeof model.getKey !== "function") throw new NotDatabaseNotifiableError(notifiable.constructor.name);
262
+ const db = requireDatabase();
263
+ const now = /* @__PURE__ */ new Date();
264
+ await db.table(NOTIFICATIONS_TABLE).insert({
265
+ id: notification.id ?? randomUUID(),
266
+ type: notification.constructor.name,
267
+ notifiable_type: notifiable.constructor.name,
268
+ notifiable_id: model.getKey(),
269
+ data: JSON.stringify(data),
270
+ read_at: null,
271
+ created_at: now,
272
+ updated_at: now
273
+ });
274
+ }
275
+ };
276
+
277
+ // migrations.ts
278
+ var NOTIFICATIONS_TABLE_MIGRATION = `import { Migration, Schema } from '@orieljs/framework'
279
+
280
+ export default class extends Migration {
281
+ async up() {
282
+ await Schema.create('notifications', (t) => {
283
+ t.uuid('id').primary()
284
+ t.string('type')
285
+ t.morphs('notifiable')
286
+ t.text('data')
287
+ t.timestamp('read_at').nullable()
288
+ t.timestamps()
289
+ })
290
+ }
291
+
292
+ async down() {
293
+ await Schema.dropIfExists('notifications')
294
+ }
295
+ }
296
+ `;
297
+
298
+ // symbols.ts
299
+ var SHOULD_QUEUE = /* @__PURE__ */ Symbol.for("oriel:bus.shouldQueue");
300
+
301
+ // Dispatcher.ts
302
+ import { randomUUID as randomUUID2 } from "crypto";
303
+ import {
304
+ BusDispatcher as BusDispatcherToken
305
+ } from "@orieljs/contracts";
306
+
307
+ // Notifiable.ts
308
+ import {
309
+ NotificationDispatcher as NotificationDispatcherToken
310
+ } from "@orieljs/contracts";
311
+ import { currentContainer as currentContainer3 } from "@orieljs/container";
312
+ import { Str } from "@orieljs/support";
313
+ function resolveDispatcher() {
314
+ const container = currentContainer3();
315
+ if (!container?.bound(NotificationDispatcherToken)) throw new NotificationDispatcherUnavailableError();
316
+ return container.make(NotificationDispatcherToken);
317
+ }
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 {
325
+ routeNotificationFor(channel, notification) {
326
+ const custom = this[`routeNotificationFor${Str.studly(channel)}`];
327
+ if (typeof custom === "function") return custom.call(this, notification);
328
+ if (channel === "mail") {
329
+ const withAttrs = this;
330
+ return typeof withAttrs.getAttribute === "function" ? withAttrs.getAttribute("email") : withAttrs.email;
331
+ }
332
+ if (channel === "database") return this;
333
+ return void 0;
334
+ }
335
+ async notify(notification) {
336
+ await resolveDispatcher().send(this, notification);
337
+ }
338
+ async notifyNow(notification, channels) {
339
+ await resolveDispatcher().sendNow(this, notification, channels);
340
+ }
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
+ };
351
+ }
352
+
353
+ // AnonymousNotifiable.ts
354
+ var AnonymousNotifiable = class {
355
+ routes = {};
356
+ /** Adds a route; chains, so one on-demand notifiable can go to several channels. */
357
+ route(channel, route) {
358
+ this.routes[channel] = route;
359
+ return this;
360
+ }
361
+ routeNotificationFor(channel) {
362
+ return this.routes[channel];
363
+ }
364
+ async notify(notification) {
365
+ await resolveDispatcher().send(this, notification);
366
+ }
367
+ async notifyNow(notification, channels) {
368
+ await resolveDispatcher().sendNow(this, notification, channels);
369
+ }
370
+ };
371
+
372
+ // Jobs/SendQueuedNotifications.ts
373
+ import { NotificationDispatcher as NotificationDispatcherToken2 } from "@orieljs/contracts";
374
+ import { inject } from "@orieljs/container";
375
+ var QUEUE_OPTIONS = [
376
+ "connection",
377
+ "queue",
378
+ "delay",
379
+ "tries",
380
+ "backoff",
381
+ "timeout",
382
+ "failOnTimeout",
383
+ "deleteWhenMissingModels"
384
+ ];
385
+ function copyShouldQueueFields(target, source) {
386
+ const fields = source;
387
+ for (const option of QUEUE_OPTIONS) {
388
+ if (fields[option] !== void 0) target[option] = fields[option];
389
+ }
390
+ }
391
+ var SendQueuedNotifications = class {
392
+ constructor(notifiables, notification, channels) {
393
+ this.notifiables = notifiables;
394
+ this.notification = notification;
395
+ this.channels = channels;
396
+ copyShouldQueueFields(this, notification);
397
+ }
398
+ notifiables;
399
+ notification;
400
+ channels;
401
+ async handle() {
402
+ await inject(NotificationDispatcherToken2).sendNow(this.notifiables, this.notification, this.channels);
403
+ }
404
+ /** Called by Queue after the final failed attempt. */
405
+ async failed(error) {
406
+ const hooks = this.notification;
407
+ if (typeof hooks.failed === "function") await hooks.failed.call(this.notification, error);
408
+ }
409
+ /** `failed_jobs` and `queue:failed` show the notification's class, not the wrapper's. */
410
+ displayName() {
411
+ return this.notification.constructor.name;
412
+ }
413
+ retryUntil() {
414
+ const hooks = this.notification;
415
+ return typeof hooks.retryUntil === "function" ? hooks.retryUntil.call(this.notification) : void 0;
416
+ }
417
+ middleware() {
418
+ const declared = this.notification.middleware;
419
+ return (typeof declared === "function" ? declared.call(this.notification) : declared) ?? [];
420
+ }
421
+ };
422
+ SendQueuedNotifications.prototype[SHOULD_QUEUE] = true;
423
+
424
+ // Dispatcher.ts
425
+ function isQueueableNotification(notification) {
426
+ const record = notification;
427
+ if (record[SHOULD_QUEUE] === true) return true;
428
+ 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
+ }
430
+ function forNotifiable(notification) {
431
+ if (notification.id) return notification;
432
+ const copy = Object.assign(Object.create(Object.getPrototypeOf(notification)), notification);
433
+ copy.id = randomUUID2();
434
+ return copy;
435
+ }
436
+ function toNotifiableList(notifiables) {
437
+ if (Array.isArray(notifiables)) return notifiables;
438
+ if (typeof notifiables.routeNotificationFor === "function") return [notifiables];
439
+ if (typeof notifiables === "object" && notifiables !== null && Symbol.iterator in notifiables) {
440
+ return [...notifiables];
441
+ }
442
+ return [notifiables];
443
+ }
444
+ var Dispatcher = class {
445
+ constructor(container) {
446
+ this.container = container;
447
+ }
448
+ container;
449
+ factories = /* @__PURE__ */ new Map();
450
+ channels = /* @__PURE__ */ new Map();
451
+ extend(channel, factory) {
452
+ this.factories.set(channel, factory);
453
+ this.channels.delete(channel);
454
+ }
455
+ resolve(name) {
456
+ const cached = this.channels.get(name);
457
+ if (cached) return cached;
458
+ const factory = this.factories.get(name);
459
+ if (!factory) throw new UnknownNotificationChannelError(name);
460
+ const channel = factory();
461
+ this.channels.set(name, channel);
462
+ return channel;
463
+ }
464
+ async send(notifiables, notification) {
465
+ const list = toNotifiableList(notifiables);
466
+ if (list.length === 0) return;
467
+ if (isQueueableNotification(notification)) {
468
+ if (!this.container.bound(BusDispatcherToken)) throw new NotificationQueueUnavailableError();
469
+ const bus = this.container.make(BusDispatcherToken);
470
+ for (const notifiable of list) {
471
+ const own = forNotifiable(notification);
472
+ for (const channel of notification.via(notifiable)) {
473
+ await bus.dispatch(new SendQueuedNotifications([notifiable], own, [channel]));
474
+ }
475
+ }
476
+ return;
477
+ }
478
+ await this.sendNow(list, notification);
479
+ }
480
+ async sendNow(notifiables, notification, channels) {
481
+ const list = toNotifiableList(notifiables);
482
+ for (const notifiable of list) {
483
+ const own = forNotifiable(notification);
484
+ const names = channels ?? notification.via(notifiable);
485
+ for (const name of names) {
486
+ if (name === "database" && notifiable instanceof AnonymousNotifiable) continue;
487
+ await this.resolve(name).send(notifiable, own);
488
+ }
489
+ }
490
+ }
491
+ /** On-demand: an AnonymousNotifiable, whose notify() goes through the bound NotificationDispatcher. */
492
+ route(channel, route) {
493
+ return new AnonymousNotifiable().route(channel, route);
494
+ }
495
+ };
496
+
497
+ // Notification.ts
498
+ import {
499
+ NotificationDispatcher as NotificationDispatcherToken3
500
+ } from "@orieljs/contracts";
501
+ import { facade } from "@orieljs/support";
502
+ var Notification = class {
503
+ id;
504
+ /** Queues it when the notification is queueable, otherwise sends now. Accepts one notifiable, an array or any iterable. */
505
+ static send(notifiables, notification) {
506
+ return resolveDispatcher().send(toNotifiableList(notifiables), notification);
507
+ }
508
+ static sendNow(notifiables, notification, channels) {
509
+ return resolveDispatcher().sendNow(toNotifiableList(notifiables), notification, channels);
510
+ }
511
+ /** On-demand: `Notification.route('mail', 'a@b.c').notify(new InvoicePaid())`. */
512
+ static route(channel, route) {
513
+ return resolveDispatcher().route(channel, route);
514
+ }
515
+ static extend(channel, factory) {
516
+ resolveDispatcher().extend(channel, factory);
517
+ }
518
+ /** Replace the dispatcher in the current container (what a NotificationFake uses). */
519
+ static swap(fake) {
520
+ facade(NotificationDispatcherToken3).swap(fake);
521
+ }
522
+ };
523
+
524
+ // facade.ts
525
+ import { NotificationDispatcher as NotificationDispatcherToken4 } from "@orieljs/contracts";
526
+ import { facade as facade2 } from "@orieljs/support";
527
+ var NotificationFacade = facade2(NotificationDispatcherToken4);
528
+
529
+ // NotificationsServiceProvider.ts
530
+ import { ClassRegistry as ClassRegistryToken, NotificationDispatcher as NotificationDispatcherToken5 } from "@orieljs/contracts";
531
+ import { ServiceProvider } from "@orieljs/support";
532
+ var NotificationsServiceProvider = class extends ServiceProvider {
533
+ register() {
534
+ this.app.singleton(NotificationDispatcherToken5, (container) => new Dispatcher(container));
535
+ const registry = this.app.make(ClassRegistryToken);
536
+ registry.register(SendQueuedNotifications, "Notifications.SendQueuedNotifications");
537
+ registry.register(AnonymousNotifiable, "Notifications.AnonymousNotifiable");
538
+ }
539
+ boot() {
540
+ const dispatcher = this.app.make(NotificationDispatcherToken5);
541
+ dispatcher.extend("mail", () => new MailNotificationChannel());
542
+ dispatcher.extend("database", () => new DatabaseNotificationChannel());
543
+ }
544
+ };
545
+ export {
546
+ AnonymousNotifiable,
547
+ DatabaseNotificationChannel,
548
+ DatabaseUnavailableError,
549
+ Dispatcher,
550
+ MailMessage,
551
+ MailNotificationChannel,
552
+ MailUnavailableError,
553
+ NOTIFICATIONS_TABLE,
554
+ NOTIFICATIONS_TABLE_MIGRATION,
555
+ NotDatabaseNotifiableError,
556
+ Notifiable,
557
+ Notification,
558
+ NotificationDispatcherUnavailableError,
559
+ NotificationFacade,
560
+ NotificationQueueUnavailableError,
561
+ NotificationsServiceProvider,
562
+ SHOULD_QUEUE,
563
+ SendQueuedNotifications,
564
+ UnknownNotificationChannelError,
565
+ UnrenderableNotificationDataError,
566
+ UnrenderableNotificationMailError,
567
+ fetchNotifications,
568
+ parseStoredDate,
569
+ toNotifiableList
570
+ };
571
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@orieljs/notifications",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "The Oriel Notifications component.",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "sideEffects": false,
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/orieljs/framework.git",
11
+ "directory": "src/Oriel/Notifications"
12
+ },
13
+ "engines": {
14
+ "node": ">=22"
15
+ },
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ }
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "LICENSE",
29
+ "NOTICE"
30
+ ],
31
+ "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"
35
+ },
36
+ "scripts": {
37
+ "build": "tsup && node ../../../scripts/copy-license.mjs"
38
+ }
39
+ }