@voltro/plugin-notifications 0.26.0 → 0.28.0
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/CHANGELOG.md +532 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +146 -140
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -160,6 +160,49 @@ export declare const inboxTable: Table<"_voltro_notification_inbox", FieldDefini
|
|
|
160
160
|
* midnight, e.g. 22:00→08:00). An empty window (start === end) is never active. */
|
|
161
161
|
export declare const inQuietHours: (qh: QuietHours, at: Date) => boolean;
|
|
162
162
|
|
|
163
|
+
/**
|
|
164
|
+
* WHO an inbox belongs to.
|
|
165
|
+
*
|
|
166
|
+
* `subject.id` is the framework's answer and it is not always the app's. A
|
|
167
|
+
* reporter's addressing unit is the EMPLOYEE: a shift change, an absence
|
|
168
|
+
* request or a task reminder is addressed to a person, and a person does not
|
|
169
|
+
* necessarily have an auth user. Their numbers, on 14 670 rows:
|
|
170
|
+
*
|
|
171
|
+
* employee WITH a userId 4 677 representable
|
|
172
|
+
* employee WITHOUT one 668 NOT — 4 people, 516 of those rows read,
|
|
173
|
+
* most recent 2026-06-19
|
|
174
|
+
*
|
|
175
|
+
* The 668 are live traffic, read by someone, and unaddressable under a
|
|
176
|
+
* subject-only model. Worse is what comes after a migration: every producer
|
|
177
|
+
* would resolve employee → userId and silently deliver NOTHING for anyone
|
|
178
|
+
* without an auth user — the exact failure this plugin's own docstring warns
|
|
179
|
+
* about, one level up and structural rather than accidental.
|
|
180
|
+
*
|
|
181
|
+
* So the app supplies it, the same seam `auth.resolveScopes` already offers for
|
|
182
|
+
* this shape. Absent keeps today's behaviour exactly.
|
|
183
|
+
*
|
|
184
|
+
* **It is ASYNC, and the sync-only first version was the defect.** The seam was
|
|
185
|
+
* built for an app whose addressing unit is its own, and an app that has one
|
|
186
|
+
* keeps it in a TABLE — the three examples in the option's own docstring
|
|
187
|
+
* (employee, member, contact) are all rows. So the resolver reads the store,
|
|
188
|
+
* so it returns a promise, so the call written in our docstring did not
|
|
189
|
+
* typecheck for the only readers it was written for. A resolver that happens to
|
|
190
|
+
* be sync still satisfies the type; nothing about that case changed.
|
|
191
|
+
*
|
|
192
|
+
* NOT cached here on purpose. A per-connection cache is the obvious next step
|
|
193
|
+
* and it is the app's to make, not ours: the first call decides the answer for
|
|
194
|
+
* the life of the connection, so a member created a second after connect
|
|
195
|
+
* resolves to the fallback until reconnect. An app that knows its own
|
|
196
|
+
* invalidation can memoise inside the resolver; the framework cannot know it.
|
|
197
|
+
*/
|
|
198
|
+
export declare const makeSubjectId: (resolve: NotificationsPluginOptions["resolveSubjectId"]) => (ctx: {
|
|
199
|
+
request: {
|
|
200
|
+
subject: {
|
|
201
|
+
id?: string | null;
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
}) => Promise<string>;
|
|
205
|
+
|
|
163
206
|
export declare const memoryNotificationStore: () => NotificationStore;
|
|
164
207
|
|
|
165
208
|
/** The minute-of-day (0–1439) `at` falls on in the IANA `tz`. Uses `Intl` so no
|
|
@@ -200,6 +243,34 @@ export declare interface NotificationServiceShape {
|
|
|
200
243
|
export declare const notificationsPlugin: (options?: NotificationsPluginOptions) => VoltroPlugin;
|
|
201
244
|
|
|
202
245
|
export declare interface NotificationsPluginOptions {
|
|
246
|
+
/**
|
|
247
|
+
* Resolve WHO the caller's inbox belongs to, when that is not `subject.id`.
|
|
248
|
+
*
|
|
249
|
+
* The addressing unit is the app's, not the framework's. If yours is an
|
|
250
|
+
* employee, a member or a contact — something that need not have an auth user
|
|
251
|
+
* — return its id here and the whole surface (`inbox`, `unreadCount`,
|
|
252
|
+
* `markRead`, `archive`, preferences, quiet hours) follows.
|
|
253
|
+
*
|
|
254
|
+
* notificationsPlugin({ resolveSubjectId: (ctx) => resolveCallerEmployeeId(ctx) })
|
|
255
|
+
*
|
|
256
|
+
* **May be async, and usually has to be.** An app that HAS its own addressing
|
|
257
|
+
* unit almost always stores it in a table — an employee, a member, a contact
|
|
258
|
+
* are rows, not token claims. If the mapping were in the token there would be
|
|
259
|
+
* no need for this seam at all: `subject.id` would already be the right id.
|
|
260
|
+
* The sync-only signature this shipped with made the call in the line above —
|
|
261
|
+
* our own docstring — fail to typecheck for exactly the apps it was built for.
|
|
262
|
+
*
|
|
263
|
+
* Absent keeps `subject.id`, so nothing changes for an app whose units line
|
|
264
|
+
* up. Returning `undefined` for one caller falls back the same way rather
|
|
265
|
+
* than failing the read.
|
|
266
|
+
*/
|
|
267
|
+
readonly resolveSubjectId?: (ctx: {
|
|
268
|
+
readonly request: {
|
|
269
|
+
readonly subject: {
|
|
270
|
+
readonly id?: string | null;
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
}) => string | undefined | Promise<string | undefined>;
|
|
203
274
|
/** Delivery channels. The in-app inbox channel is added automatically unless
|
|
204
275
|
* you pass your own `inApp` channel. Default `[consoleChannel()]` + in-app. */
|
|
205
276
|
readonly channels?: ReadonlyArray<Channel>;
|