@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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DeliveryPayload, Notifier } from '../Notifier.js';
|
|
2
|
+
import { DeliveryOutcome } from '../declarations.js';
|
|
3
|
+
/**
|
|
4
|
+
* The worker side of a notification: it performs the delivery the request decided on.
|
|
5
|
+
*
|
|
6
|
+
* It runs the same code the inline path runs, which is what makes a retry mean exactly what the first
|
|
7
|
+
* attempt meant. The payload carries the template key and its params rather than a rendered body, so
|
|
8
|
+
* a message queued before a translation was fixed goes out fixed.
|
|
9
|
+
*
|
|
10
|
+
* **It throws when, and only when, another attempt could work.** That is the whole contract with the
|
|
11
|
+
* queue: a permanent failure that threw would be retried until the attempts ran out, filling the
|
|
12
|
+
* queue with work that cannot succeed, and an unreachable recipient would look like an outage.
|
|
13
|
+
*/
|
|
14
|
+
export declare class DeliverNotification {
|
|
15
|
+
private readonly notifier;
|
|
16
|
+
/**
|
|
17
|
+
* @param dependencies - Auto-wired services.
|
|
18
|
+
*/
|
|
19
|
+
constructor({ notifier }: {
|
|
20
|
+
notifier: Notifier;
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Deliver one notification.
|
|
24
|
+
*
|
|
25
|
+
* @param payload - Who, what, where, in which language.
|
|
26
|
+
* @returns What each channel answered.
|
|
27
|
+
* @throws {Error} When at least one channel failed in a way another attempt could fix.
|
|
28
|
+
*/
|
|
29
|
+
handle(payload: DeliveryPayload): Promise<DeliveryOutcome[]>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BlueprintContext, IBlueprint, NextMiddleware, type MetaMiddleware } from '@stone-js/core';
|
|
2
|
+
/**
|
|
3
|
+
* Build-phase middleware: subscribes every notice that named a domain event.
|
|
4
|
+
*
|
|
5
|
+
* **This is what makes calling the notifier optional.** A notice declaring `on` becomes a handler of
|
|
6
|
+
* the light key router, the same one `@stone-js/event-bus` routes incoming domain events through. So
|
|
7
|
+
* a module emits what happened, and the notice says who learns about it: the emitting module imports
|
|
8
|
+
* nothing, and is never reopened when a channel is added.
|
|
9
|
+
*
|
|
10
|
+
* A middleware rather than a static entry, because the notices are only known once everything has
|
|
11
|
+
* been collected: `@Notice` contributes them, and so does configuration, and both are read here.
|
|
12
|
+
*
|
|
13
|
+
* The entry is a plain object of the shape the key router already accepts, so nothing here depends on
|
|
14
|
+
* `@stone-js/router`. The handler is the **factory** form, which receives the container, so the
|
|
15
|
+
* closure carries only the notice's name and the notifier is resolved for the event that arrives.
|
|
16
|
+
*
|
|
17
|
+
* @param context - The blueprint context.
|
|
18
|
+
* @param next - The next blueprint middleware.
|
|
19
|
+
* @returns The blueprint.
|
|
20
|
+
*/
|
|
21
|
+
export declare const NoticeSubscriptionsMiddleware: (context: BlueprintContext<IBlueprint>, next: NextMiddleware<BlueprintContext<IBlueprint>, IBlueprint>) => Promise<IBlueprint>;
|
|
22
|
+
/**
|
|
23
|
+
* Meta blueprint middleware for the notice subscriptions.
|
|
24
|
+
*/
|
|
25
|
+
export declare const MetaNoticeSubscriptionsMiddleware: MetaMiddleware<BlueprintContext<IBlueprint>, IBlueprint>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NotificationsConfig } from '../declarations.js';
|
|
2
|
+
import { AppConfig, StoneBlueprint } from '@stone-js/core';
|
|
3
|
+
/** Application config augmented with the notifications bucket. */
|
|
4
|
+
export interface NotificationsAppConfig extends Partial<AppConfig> {
|
|
5
|
+
notifications: NotificationsConfig;
|
|
6
|
+
/** Contributed so a worker finds the delivery job. Ignored when the queue module is absent. */
|
|
7
|
+
queue?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
/** Blueprint for the notifications module. */
|
|
10
|
+
export interface NotificationsBlueprint extends StoneBlueprint {
|
|
11
|
+
stone: NotificationsAppConfig;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Opt-in blueprint: register it to reach people.
|
|
15
|
+
*
|
|
16
|
+
* The imperative half of the pair; `@Notifications()` is the declarative one. It binds the notifier
|
|
17
|
+
* and the channels, and contributes the delivery job so a worker performs what a request decided.
|
|
18
|
+
*
|
|
19
|
+
* The job is declared on `stone.queue.handlers`, the array the worker scans, so nothing here depends
|
|
20
|
+
* on the queue package. An application with no queue simply never has a worker to read it, and
|
|
21
|
+
* delivery happens in the request instead.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { defineConfig, defineStoneApp } from '@stone-js/core'
|
|
26
|
+
* import { notificationsBlueprint } from '@stone-js/notifications'
|
|
27
|
+
*
|
|
28
|
+
* export const App = defineStoneApp({ name: 'app' }, [notificationsBlueprint])
|
|
29
|
+
*
|
|
30
|
+
* export const AppConfig = defineConfig((blueprint) => blueprint.set('stone.notifications', {
|
|
31
|
+
* default: ['smtp', 'in-app'],
|
|
32
|
+
* channels: [{ name: 'smtp', driver: 'smtp', from: 'Noowow <no-reply@example.test>' }],
|
|
33
|
+
* recipients: async (id) => await accounts.contactFor(id)
|
|
34
|
+
* }))
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare const notificationsBlueprint: NotificationsBlueprint;
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { RenderedNotification, TemplateInput } from './declarations.js';
|
|
2
|
+
/** The shape this module needs from a translator, duck-typed so i18n is never imported. */
|
|
3
|
+
export interface TranslatorLike {
|
|
4
|
+
t: (key: string, options?: Record<string, unknown>) => string;
|
|
5
|
+
}
|
|
6
|
+
/** What rendering is given, so it can be tested without an application around it. */
|
|
7
|
+
export interface RenderContext {
|
|
8
|
+
/** The template key. */
|
|
9
|
+
template: string;
|
|
10
|
+
/** What to render it with. */
|
|
11
|
+
params: Record<string, unknown>;
|
|
12
|
+
/** The language the **recipient** reads. */
|
|
13
|
+
locale: string;
|
|
14
|
+
/** Templates the application declared. */
|
|
15
|
+
templates?: Record<string, TemplateInput>;
|
|
16
|
+
/** The catalogue, when `@stone-js/i18n` is enabled. */
|
|
17
|
+
translator?: TranslatorLike;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A template key and its params, become text in the recipient's language.
|
|
21
|
+
*
|
|
22
|
+
* **The locale is the recipient's, never the request's.** A French-speaking guardian invited by an
|
|
23
|
+
* English-speaking member of staff reads French. Getting that backwards is invisible in every test
|
|
24
|
+
* written by one person in one language, and obvious to the person who receives it.
|
|
25
|
+
*
|
|
26
|
+
* Three sources, in order: a template the application declared outright, the translation catalogue,
|
|
27
|
+
* and failing both the key itself.
|
|
28
|
+
*
|
|
29
|
+
* That last fallback is the point. A missing translation renders its **key**, never an empty string:
|
|
30
|
+
* an empty subject looks like a broken mail client and gets ignored for months, while
|
|
31
|
+
* `guardianship.consent_needed` is visibly ours and gets reported the same day.
|
|
32
|
+
*
|
|
33
|
+
* @param context - What to render, and what to render it from.
|
|
34
|
+
* @returns The rendered notification.
|
|
35
|
+
*/
|
|
36
|
+
export declare function render(context: RenderContext): RenderedNotification;
|
package/package.json
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stone-js/notifications",
|
|
3
|
+
"version": "0.8.17",
|
|
4
|
+
"description": "Notifications for Stone.js: one declaration reaches a person wherever they are, delivered out of band.",
|
|
5
|
+
"author": "Mr. Stone <evensstone@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/stone-foundation/stone-js-framework.git",
|
|
10
|
+
"directory": "stone-js-notifications"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://stonejs.dev",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/stone-foundation/stone-js-framework/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"StoneJS",
|
|
18
|
+
"notifications",
|
|
19
|
+
"email",
|
|
20
|
+
"sms",
|
|
21
|
+
"push",
|
|
22
|
+
"in-app"
|
|
23
|
+
],
|
|
24
|
+
"files": [
|
|
25
|
+
"/dist"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.17.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"nodemailer": "^9.0.1",
|
|
41
|
+
"@stone-js/core": "0.8.17",
|
|
42
|
+
"@stone-js/queue": "0.8.17",
|
|
43
|
+
"@stone-js/realtime": "0.8.17",
|
|
44
|
+
"@stone-js/i18n": "0.8.17"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@stone-js/config": "0.8.17"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@commitlint/cli": "^19.8.1",
|
|
51
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
52
|
+
"@rollup/plugin-commonjs": "^28.0.6",
|
|
53
|
+
"@rollup/plugin-multi-entry": "^6.0.1",
|
|
54
|
+
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
55
|
+
"@rollup/plugin-typescript": "^12.1.4",
|
|
56
|
+
"@types/node": "^24.0.7",
|
|
57
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
58
|
+
"husky": "^9.1.7",
|
|
59
|
+
"nodemailer": "^9.0.5",
|
|
60
|
+
"rimraf": "^6.0.1",
|
|
61
|
+
"rollup": "^4.44.1",
|
|
62
|
+
"rollup-plugin-node-externals": "^8.0.1",
|
|
63
|
+
"ts-standard": "^12.0.2",
|
|
64
|
+
"tslib": "^2.8.1",
|
|
65
|
+
"typedoc": "^0.28.6",
|
|
66
|
+
"typedoc-plugin-markdown": "^4.7.0",
|
|
67
|
+
"typescript": "^5.6.3",
|
|
68
|
+
"vitest": "^3.2.4",
|
|
69
|
+
"@stone-js/core": "0.8.17",
|
|
70
|
+
"@stone-js/http-core": "0.8.17",
|
|
71
|
+
"@stone-js/i18n": "0.8.17",
|
|
72
|
+
"@stone-js/queue": "0.8.17",
|
|
73
|
+
"@stone-js/realtime": "0.8.17",
|
|
74
|
+
"@stone-js/router": "0.8.17"
|
|
75
|
+
},
|
|
76
|
+
"ts-standard": {
|
|
77
|
+
"globals": [
|
|
78
|
+
"it",
|
|
79
|
+
"test",
|
|
80
|
+
"vi",
|
|
81
|
+
"expect",
|
|
82
|
+
"describe",
|
|
83
|
+
"beforeEach",
|
|
84
|
+
"afterEach"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"peerDependenciesMeta": {
|
|
88
|
+
"@stone-js/queue": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"@stone-js/realtime": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
94
|
+
"@stone-js/i18n": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"nodemailer": {
|
|
98
|
+
"optional": true
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"scripts": {
|
|
102
|
+
"lint": "ts-standard src",
|
|
103
|
+
"lint:fix": "ts-standard --fix src tests",
|
|
104
|
+
"predoc": "rimraf docs",
|
|
105
|
+
"doc": "typedoc",
|
|
106
|
+
"clean": "rimraf dist",
|
|
107
|
+
"build": "rollup -c",
|
|
108
|
+
"test": "vitest run",
|
|
109
|
+
"test:cvg": "npm run test -- --coverage",
|
|
110
|
+
"test:text": "npm run test:cvg -- --coverage.reporter=text",
|
|
111
|
+
"test:html": "npm run test:cvg -- --coverage.reporter=html",
|
|
112
|
+
"test:clover": "npm run test:cvg -- --coverage.reporter=clover"
|
|
113
|
+
}
|
|
114
|
+
}
|