@stone-js/notifications 0.8.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +318 -0
- package/dist/NoticeRegistry.d.ts +44 -0
- package/dist/NotificationManager.d.ts +49 -0
- package/dist/NotificationServiceProvider.d.ts +40 -0
- package/dist/Notifier.d.ts +260 -0
- package/dist/channels/InAppChannel.d.ts +43 -0
- package/dist/channels/LogChannel.d.ts +43 -0
- package/dist/channels/SmtpChannel.d.ts +46 -0
- package/dist/constants.d.ts +6 -0
- package/dist/declarations.d.ts +345 -0
- package/dist/decorators/Notice.d.ts +50 -0
- package/dist/decorators/NotificationChannel.d.ts +34 -0
- package/dist/decorators/Notifications.d.ts +26 -0
- package/dist/decorators/constants.d.ts +14 -0
- package/dist/defineNotice.d.ts +24 -0
- package/dist/errors/NotificationError.d.ts +12 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +1481 -0
- package/dist/jobs/DeliverNotification.d.ts +30 -0
- package/dist/middleware/NoticeSubscriptionsMiddleware.d.ts +25 -0
- package/dist/options/NotificationsBlueprint.d.ts +37 -0
- package/dist/render.d.ts +36 -0
- package/package.json +114 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Stone Foundation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Stone.js - Notifications
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://www.npmjs.com/package/@stone-js/notifications)
|
|
5
|
+
[](https://www.npmjs.com/package/@stone-js/notifications)
|
|
6
|
+

|
|
7
|
+
[](https://github.com/stone-foundation/stone-js-framework/actions/workflows/ci.yml)
|
|
8
|
+
[](https://github.com/stone-foundation/stone-js-framework/actions/workflows/release.yml)
|
|
9
|
+
[](https://sonarcloud.io/summary/new_code?id=stone-foundation_stone-js-framework)
|
|
10
|
+
[](https://sonarcloud.io/summary/new_code?id=stone-foundation_stone-js-framework)
|
|
11
|
+
[](https://github.com/stone-foundation/stone-js-framework/blob/main/SECURITY.md)
|
|
12
|
+
[](https://github.com/stone-foundation/stone-js-framework/security/code-scanning)
|
|
13
|
+
[](https://github.com/stone-foundation/stone-js-framework/network/updates)
|
|
14
|
+
[](https://conventionalcommits.org)
|
|
15
|
+
|
|
16
|
+
**Notifications for Stone.js.** One declaration reaches a person wherever they are: the mailbox, the
|
|
17
|
+
phone, and the tab they already have open. Delivered out of band, in their own language.
|
|
18
|
+
|
|
19
|
+
**Nobody has to call it.** A module emits what happened; the notice that named that event decides
|
|
20
|
+
who learns about it, through which channels, and in what words. The emitting module imports nothing
|
|
21
|
+
and is never reopened when a channel is added.
|
|
22
|
+
|
|
23
|
+
**Decide now, deliver later.** A request resolves who the person is and hands the delivery to a
|
|
24
|
+
queue. Reaching a mail provider takes as long as it takes, and a request that waits for one is a
|
|
25
|
+
request that times out on the endpoint the user is watching.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @stone-js/notifications
|
|
33
|
+
|
|
34
|
+
# optional, and each earns its keep:
|
|
35
|
+
npm install @stone-js/queue # deliver out of band rather than in the request
|
|
36
|
+
npm install @stone-js/realtime # reach the tab someone already has open
|
|
37
|
+
npm install @stone-js/i18n # write in the recipient's own language
|
|
38
|
+
npm install nodemailer # the SMTP channel (9.0.1 or later)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> Peer dependency: `@stone-js/core`. Everything else is optional, and the module degrades to
|
|
42
|
+
> something honest without each of them.
|
|
43
|
+
|
|
44
|
+
## Enable it
|
|
45
|
+
|
|
46
|
+
Declarative:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { StoneApp } from '@stone-js/core'
|
|
50
|
+
import { Notifications } from '@stone-js/notifications'
|
|
51
|
+
|
|
52
|
+
@Notifications({
|
|
53
|
+
default: ['smtp', 'in-app'],
|
|
54
|
+
channels: [{ name: 'smtp', driver: 'smtp', from: 'App <no-reply@example.test>' }]
|
|
55
|
+
})
|
|
56
|
+
@StoneApp({ name: 'app' })
|
|
57
|
+
export class Application {}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Imperative, through `stone.notifications`:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { defineConfig, defineStoneApp } from '@stone-js/core'
|
|
64
|
+
import { notificationsBlueprint } from '@stone-js/notifications'
|
|
65
|
+
|
|
66
|
+
export const App = defineStoneApp({ name: 'app' }, [notificationsBlueprint])
|
|
67
|
+
|
|
68
|
+
export const AppConfig = defineConfig((blueprint) => blueprint.set('stone.notifications', {
|
|
69
|
+
default: ['smtp', 'in-app'],
|
|
70
|
+
channels: [{ name: 'smtp', driver: 'smtp', from: 'App <no-reply@example.test>' }],
|
|
71
|
+
recipients: async (id) => await accounts.contactFor(id)
|
|
72
|
+
}))
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
With nothing configured, notifications go to the log and say so on first use. Reaching real people
|
|
76
|
+
is a decision, so it is written down: a default that quietly sent real mail would send it from the
|
|
77
|
+
first test run.
|
|
78
|
+
|
|
79
|
+
## A notice: what someone receives
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
@Notice({
|
|
83
|
+
name: 'guardianship.consent_needed',
|
|
84
|
+
on: 'identity.guardian.invited.v1',
|
|
85
|
+
channels: ['smtp', 'in-app']
|
|
86
|
+
})
|
|
87
|
+
export class ConsentNeeded {
|
|
88
|
+
constructor ({ i18n }) { this.i18n = i18n }
|
|
89
|
+
|
|
90
|
+
// Who learns about it. Required when the notice reacts to an event: the event carries the
|
|
91
|
+
// account, and only the notice knows which field that is.
|
|
92
|
+
recipients (event) { return event.guardianId }
|
|
93
|
+
|
|
94
|
+
// What it says, per channel. Asked once per recipient, so a name in the body is rendered once.
|
|
95
|
+
notify (event, { locale, recipient }) {
|
|
96
|
+
return {
|
|
97
|
+
smtp: {
|
|
98
|
+
subject: this.i18n.t('consent.subject', { lng: locale }),
|
|
99
|
+
body: this.i18n.t('consent.body', { lng: locale, child: event.childHandle })
|
|
100
|
+
},
|
|
101
|
+
'in-app': { body: this.i18n.t('consent.short', { lng: locale }) }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Optional: the same fact, told once, however many times it arrives.
|
|
106
|
+
dedupe (event) { return event.eventId }
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**The decorator carries metadata; the class carries content.** There is no `content` option, and
|
|
111
|
+
that is deliberate: text in a decorator is text that cannot be translated, formatted, or read off
|
|
112
|
+
the event. The class is built through the container, so it asks for whatever it needs.
|
|
113
|
+
|
|
114
|
+
The imperative form says the same thing:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
blueprint.set('stone.notifications.notices', [
|
|
118
|
+
defineNotice(ConsentNeeded, { name: 'guardianship.consent_needed', on: 'identity.guardian.invited.v1' })
|
|
119
|
+
])
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Nobody calls the notifier
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
identity emits -> identity.guardian.invited.v1 -> the notice that named it
|
|
126
|
+
renders, chooses its channels, delivers
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The notice subscribes through the light key router, the same one `@stone-js/event-bus` routes domain
|
|
130
|
+
events through, so `identity` imports nothing. Add a channel later and only the notice changes.
|
|
131
|
+
|
|
132
|
+
**A body per channel matters.** A text message is not an email: one has a subject and room to
|
|
133
|
+
explain, the other has a hundred and sixty characters. A channel the notice says nothing about falls
|
|
134
|
+
back to the declared template rather than sending an empty body.
|
|
135
|
+
|
|
136
|
+
**The nature of the message decides its channels**, not the caller. A service inviting a guardian has
|
|
137
|
+
no reason to know whether that goes by mail or by text.
|
|
138
|
+
|
|
139
|
+
## Tell someone something
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
export class GuardianshipService {
|
|
143
|
+
constructor (private readonly notifier) {}
|
|
144
|
+
|
|
145
|
+
async invite (guardianId: string, child: string) {
|
|
146
|
+
await this.notifier.notify(guardianId, 'guardianship.consent_needed', { child })
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
A **template key**, never a rendered body. What is not copied does not have to be erased, and a
|
|
152
|
+
message queued before a translation was fixed goes out fixed.
|
|
153
|
+
|
|
154
|
+
The recipient can be the person, or their id when a directory can resolve one:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
await notifier.notify({ id: 'u1', email: 'a@example.test', locale: 'fr' }, 'welcome')
|
|
158
|
+
await notifier.notify(['u1', 'u2'], 'edition.opened', { edition: 'Spring' })
|
|
159
|
+
await notifier.notify(user, 'welcome', {}, { channels: ['in-app'], inline: true })
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## What this module decides, and what it does not
|
|
163
|
+
|
|
164
|
+
It decides **who learns what, through which channel, and in which language**.
|
|
165
|
+
|
|
166
|
+
It never decides **whether** to send. Consent, preferences, quiet hours and audiences are yours,
|
|
167
|
+
because the rules that matter there are about your own people: a framework imposing them would be
|
|
168
|
+
wrong for the first application whose rules differ. Make that call, then call here.
|
|
169
|
+
|
|
170
|
+
## Channels
|
|
171
|
+
|
|
172
|
+
| Channel | What it is |
|
|
173
|
+
|---|---|
|
|
174
|
+
| `log` | The zero-config default. Writes the message where you write everything else and **reaches nobody**. Right in development and in tests, and not a channel anywhere else. |
|
|
175
|
+
| `in-app` | Broadcasts on the recipient's own realtime channel, so an open tab receives it. Needs `@stone-js/realtime`. |
|
|
176
|
+
| `smtp` | Email over SMTP, through `nodemailer`. SMTP rather than a provider's API, so one channel reaches all of them without this package choosing a vendor for you. |
|
|
177
|
+
|
|
178
|
+
`sms` and `push` are not shipped, deliberately: a channel that picked a vendor would be wrong for
|
|
179
|
+
everyone who chose a different one. Register yours, and it is a channel like any other:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
channels: [{ name: 'sms', factory: () => new TwilioChannel(twilio) }]
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Or as a class the container builds, which is how it gets its dependencies:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
@NotificationChannel('sms')
|
|
189
|
+
export class TwilioChannel {
|
|
190
|
+
readonly name = 'sms'
|
|
191
|
+
|
|
192
|
+
constructor ({ twilio }) { this.twilio = twilio }
|
|
193
|
+
|
|
194
|
+
async send (message, recipient) {
|
|
195
|
+
if (recipient.phone === undefined) {
|
|
196
|
+
return { status: 'unreachable', retryable: false, reason: 'No phone number.' }
|
|
197
|
+
}
|
|
198
|
+
await this.twilio.messages.create({ to: recipient.phone, body: message.body })
|
|
199
|
+
return { status: 'sent' }
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### The port a channel implements
|
|
205
|
+
|
|
206
|
+
`send(message, recipient)` **returns** an outcome and does not throw, for everything it can foresee.
|
|
207
|
+
The outcome says whether another attempt could work: a provider being down is retryable, an address
|
|
208
|
+
that does not exist never will be, and retrying that forever is how a queue fills with work that
|
|
209
|
+
cannot succeed.
|
|
210
|
+
|
|
211
|
+
A channel that throws anyway is treated as retryable, because a throw is an adapter bug rather than
|
|
212
|
+
a verdict.
|
|
213
|
+
|
|
214
|
+
## The language a message is written in
|
|
215
|
+
|
|
216
|
+
The **recipient's**, never the request's. A French-speaking guardian invited by an English-speaking
|
|
217
|
+
member of staff reads French.
|
|
218
|
+
|
|
219
|
+
Keys are looked up in `@stone-js/i18n` under `<key>.subject` and `<key>.body`, in that person's
|
|
220
|
+
locale. Without a catalogue, declare templates in configuration:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
templates: {
|
|
224
|
+
'guardianship.consent_needed': {
|
|
225
|
+
subject: 'Your consent is needed',
|
|
226
|
+
body: 'Please confirm for {{ child }}.'
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
A missing translation renders its **key**, never an empty string: an empty subject looks like a
|
|
232
|
+
broken mail client and gets ignored for months, while `guardianship.consent_needed` is visibly ours
|
|
233
|
+
and gets reported the same day.
|
|
234
|
+
|
|
235
|
+
## Delivery is out of band
|
|
236
|
+
|
|
237
|
+
With `@stone-js/queue` enabled, `notify()` resolves the recipient, decides the channels and dispatches
|
|
238
|
+
a job. A worker performs the delivery, running the same code the inline path runs, so a retry means
|
|
239
|
+
exactly what the first attempt meant.
|
|
240
|
+
|
|
241
|
+
Without a queue, delivery happens in the request, which is right for development and says so when it
|
|
242
|
+
was not what you asked for.
|
|
243
|
+
|
|
244
|
+
`notify()` never throws at its caller. A notification is almost always a side effect of something
|
|
245
|
+
that already succeeded, and failing that operation because a mail provider was down would undo work
|
|
246
|
+
that was correct.
|
|
247
|
+
|
|
248
|
+
## Configuration
|
|
249
|
+
|
|
250
|
+
| Key | Meaning |
|
|
251
|
+
|---|---|
|
|
252
|
+
| `channels` | The channels this application configures. |
|
|
253
|
+
| `default` | The channels a notification uses when it names none. Defaults to `['log']`. |
|
|
254
|
+
| `recipients` | How to turn an id into a person. The one thing this module cannot ship. |
|
|
255
|
+
| `templates` | Templates, for an application with no translation catalogue. |
|
|
256
|
+
| `dispatch` | `queue` or `inline`. Defaults to `queue` when a queue is enabled. |
|
|
257
|
+
| `queue` / `attempts` | Which queue, and how many retries. |
|
|
258
|
+
| `notices` | Notices declared in configuration rather than with `@Notice`. Both are read. |
|
|
259
|
+
| `dedupe` | `{ ttl, store }`. Keys live in `@stone-js/cache`, so this module stores nothing of its own. |
|
|
260
|
+
| `announce` | Emit `notification.delivered` and `notification.failed` on the bus. On when a bus is enabled. |
|
|
261
|
+
|
|
262
|
+
## The same message twice
|
|
263
|
+
|
|
264
|
+
The most common production failure of any notification system: a queue is at-least-once, a retry
|
|
265
|
+
half succeeded, or two events describe one fact. Name the occurrence and the repeat is dropped:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
await notifier.notify(user, 'welcome', {}, { dedupe: `welcome:${user.id}` })
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
A notice states its own through `dedupe(event)`. Keys are claimed atomically in the cache store the
|
|
272
|
+
application already chose. Without the cache module, deduplication does not happen and says so once:
|
|
273
|
+
sending twice in silence is the failure it exists to prevent.
|
|
274
|
+
|
|
275
|
+
## Who received what
|
|
276
|
+
|
|
277
|
+
This module keeps no delivery ledger, because the answer to "why did they never receive it" belongs
|
|
278
|
+
in whatever the application already queries. It announces instead:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
@BusHandler()
|
|
282
|
+
export class NotificationLedger {
|
|
283
|
+
@OnBusEvent('notification.failed')
|
|
284
|
+
onFailed (event) { /* ... write your own row */ }
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The event carries the notice, the channel, the outcome, whether it is worth retrying, and the
|
|
289
|
+
recipient's **id**, never their address: an address in an event is an address in every log that
|
|
290
|
+
event passes through.
|
|
291
|
+
|
|
292
|
+
## Seeing it before sending it
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
const previewed = await notifier.preview(guardian, 'guardianship.consent_needed', { child: 'Lea' })
|
|
296
|
+
// [{ recipient, channel: 'smtp', message: { subject, body, locale } }, ...]
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Exactly what delivery would render, per channel, with nothing sent. For the screen that shows a
|
|
300
|
+
member of staff what a guardian is about to receive, and for a test that checks a notice without a
|
|
301
|
+
channel.
|
|
302
|
+
|
|
303
|
+
## Later rather than now
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
await notifier.notify(user, 'trial.ending', {}, { delay: 86_400 })
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Deferred by the queue, because a timer held in a process a cold start can end is not a reminder.
|
|
310
|
+
|
|
311
|
+
## Documentation
|
|
312
|
+
|
|
313
|
+
See the [official documentation](https://stonejs.dev/docs/extensions/notifications) for the full
|
|
314
|
+
guide.
|
|
315
|
+
|
|
316
|
+
## License
|
|
317
|
+
|
|
318
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { IBlueprint, IContainer } from '@stone-js/core';
|
|
2
|
+
import { NoticeDeclaration, NoticeInstance } from './declarations.js';
|
|
3
|
+
/**
|
|
4
|
+
* Finds the notice a name or an event refers to, and builds it.
|
|
5
|
+
*
|
|
6
|
+
* Built for one event, like everything else in the container: it is a lookup over what the blueprint
|
|
7
|
+
* declared, and holds nothing between events. A notice class is built through the container, so it
|
|
8
|
+
* gets its services, and it is the container that decides whether that instance is shared.
|
|
9
|
+
*/
|
|
10
|
+
export declare class NoticeRegistry {
|
|
11
|
+
private readonly blueprint;
|
|
12
|
+
private readonly container?;
|
|
13
|
+
/**
|
|
14
|
+
* @param dependencies - Auto-wired services.
|
|
15
|
+
*/
|
|
16
|
+
constructor({ blueprint, container }: {
|
|
17
|
+
blueprint: IBlueprint;
|
|
18
|
+
container?: IContainer;
|
|
19
|
+
});
|
|
20
|
+
/** Every notice this application declared, from `@Notice` and from configuration alike. */
|
|
21
|
+
all(): NoticeDeclaration[];
|
|
22
|
+
/**
|
|
23
|
+
* What was declared under this name.
|
|
24
|
+
*
|
|
25
|
+
* @param name - The notice's name.
|
|
26
|
+
* @returns The declaration, or nothing.
|
|
27
|
+
*/
|
|
28
|
+
declaration(name: string): NoticeDeclaration | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* What reacts to this event.
|
|
31
|
+
*
|
|
32
|
+
* @param event - The domain event's name.
|
|
33
|
+
* @returns The declaration, or nothing.
|
|
34
|
+
*/
|
|
35
|
+
forEvent(event: string): NoticeDeclaration | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* The notice itself, built.
|
|
38
|
+
*
|
|
39
|
+
* @param declaration - What was declared.
|
|
40
|
+
* @returns The notice.
|
|
41
|
+
* @throws {NotificationConfigurationError} When it cannot be built, or does not answer `notify`.
|
|
42
|
+
*/
|
|
43
|
+
build(declaration: NoticeDeclaration): NoticeInstance;
|
|
44
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { NotificationChannel } from './declarations.js';
|
|
2
|
+
/**
|
|
3
|
+
* Holds the configured channels and hands one out by name.
|
|
4
|
+
*
|
|
5
|
+
* The same shape every driver-based module in the framework uses, and the same lifetime: built for
|
|
6
|
+
* one event, like the container it belongs to. It is a registry of factories, so rebuilding it costs
|
|
7
|
+
* nothing. Nothing here holds state between events; a channel that needs a connection holds it
|
|
8
|
+
* itself, because a connection is a resource and the channel is the boundary that owns it.
|
|
9
|
+
*/
|
|
10
|
+
export declare class NotificationManager {
|
|
11
|
+
private static current?;
|
|
12
|
+
private readonly channels;
|
|
13
|
+
private readonly factories;
|
|
14
|
+
/**
|
|
15
|
+
* @returns A manager.
|
|
16
|
+
*/
|
|
17
|
+
static create(): NotificationManager;
|
|
18
|
+
/** Publish the manager, so code outside the container can reach it. */
|
|
19
|
+
static setInstance(manager?: NotificationManager): void;
|
|
20
|
+
/** The published manager, if there is one. */
|
|
21
|
+
static getInstance(): NotificationManager | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Register a built channel.
|
|
24
|
+
*
|
|
25
|
+
* @param channel - The channel.
|
|
26
|
+
* @returns This manager.
|
|
27
|
+
*/
|
|
28
|
+
register(channel: NotificationChannel): this;
|
|
29
|
+
/**
|
|
30
|
+
* Register a channel to be built on first use.
|
|
31
|
+
*
|
|
32
|
+
* @param name - The name notifications refer to it by.
|
|
33
|
+
* @param factory - How to build it.
|
|
34
|
+
* @returns This manager.
|
|
35
|
+
*/
|
|
36
|
+
registerFactory(name: string, factory: () => NotificationChannel): this;
|
|
37
|
+
/** Whether a channel is registered under this name. */
|
|
38
|
+
has(name: string): boolean;
|
|
39
|
+
/** The names of every registered channel. */
|
|
40
|
+
names(): string[];
|
|
41
|
+
/**
|
|
42
|
+
* The channel a notification named.
|
|
43
|
+
*
|
|
44
|
+
* @param name - The channel's name.
|
|
45
|
+
* @returns The channel.
|
|
46
|
+
* @throws {NotificationConfigurationError} When nothing is registered under that name.
|
|
47
|
+
*/
|
|
48
|
+
channel(name: string): NotificationChannel;
|
|
49
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { IContainer, IServiceProvider, Promiseable } from '@stone-js/core';
|
|
2
|
+
/**
|
|
3
|
+
* Binds the channels and the notifier.
|
|
4
|
+
*
|
|
5
|
+
* Everything is built for this event, like the rest of the container. A channel holding a connection
|
|
6
|
+
* holds it itself, because a connection is a resource and the channel is the boundary that owns it;
|
|
7
|
+
* nothing here keeps state between events.
|
|
8
|
+
*/
|
|
9
|
+
export declare class NotificationServiceProvider implements IServiceProvider {
|
|
10
|
+
private readonly container;
|
|
11
|
+
constructor(container: IContainer);
|
|
12
|
+
register(): Promiseable<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Register one configured channel, lazily: a transport is built when first used, not at boot, so an
|
|
15
|
+
* application configured for production does not need a mail server to start locally.
|
|
16
|
+
*
|
|
17
|
+
* @param manager - The registry to register into.
|
|
18
|
+
* @param config - What the application declared.
|
|
19
|
+
*/
|
|
20
|
+
private registerChannel;
|
|
21
|
+
/**
|
|
22
|
+
* A declared channel class, built.
|
|
23
|
+
*
|
|
24
|
+
* @param config - What the application declared.
|
|
25
|
+
* @returns The channel.
|
|
26
|
+
* @throws {NotificationConfigurationError} When the class cannot be built.
|
|
27
|
+
*/
|
|
28
|
+
private build;
|
|
29
|
+
/**
|
|
30
|
+
* The builder for a driver this package ships.
|
|
31
|
+
*
|
|
32
|
+
* @param driver - The driver's name.
|
|
33
|
+
* @returns The factory, or nothing when the name is not one of ours.
|
|
34
|
+
*/
|
|
35
|
+
private driverFor;
|
|
36
|
+
/** The realtime broadcaster, when one is bound. */
|
|
37
|
+
private broadcaster;
|
|
38
|
+
/** The logger, when one is bound. */
|
|
39
|
+
private logger;
|
|
40
|
+
}
|