unified-notification-core 0.1.0 → 0.1.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/CHANGELOG.md +24 -0
- package/README.md +46 -5
- package/dist/core.d.ts +1 -1
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +303 -147
- package/dist/core.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +15 -1
- package/dist/errors.js.map +1 -1
- package/dist/keys.d.ts +10 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +71 -0
- package/dist/keys.js.map +1 -0
- package/dist/preferences.d.ts.map +1 -1
- package/dist/preferences.js +13 -4
- package/dist/preferences.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/adoption-notes.md +18 -0
- package/docs/architecture.md +43 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
- Made delivery claims unique per row and attempt, with durable attempt counts
|
|
6
|
+
incremented before adapter invocation and exhausted stale claims closed.
|
|
7
|
+
- Made cancellation races truthful: current-claim provider success is `sent`,
|
|
8
|
+
failure remains `canceled`, and stale callbacks report durable state without
|
|
9
|
+
mutating it.
|
|
10
|
+
- Restricted requeueing to failed deliveries on active notifications and made
|
|
11
|
+
attempt-budget/reset semantics explicit.
|
|
12
|
+
- Normalized recipient/topic/channel keys across publish, preference APIs, the
|
|
13
|
+
public resolver, and configured defaults; canonical default collisions now
|
|
14
|
+
fail fast. Oversized delivery error codes are rejected before persistence.
|
|
15
|
+
- Preserved `0.1.0` whitespace preference rows without a migration, with
|
|
16
|
+
deterministic opt-out-wins conflict handling and atomic consolidation on
|
|
17
|
+
writes/clears.
|
|
18
|
+
- Matched legacy SQL key normalization to the complete ECMAScript trim set,
|
|
19
|
+
batched preference reads into exact plus one legacy fallback query, and added
|
|
20
|
+
the corresponding functional recipient index to the schema factory.
|
|
21
|
+
- Hardened adapters, defaults, attempt limits, channel payloads, and inbox keys
|
|
22
|
+
against prototype-name collisions and runtime-invalid values.
|
|
23
|
+
- Prevented provider dispatch through transaction-scoped cores. Transactional
|
|
24
|
+
`publish()` remains supported, while `publishAndDispatch()` and `dispatchDue()`
|
|
25
|
+
fail before publication, claiming, or adapter invocation.
|
|
26
|
+
|
|
3
27
|
## 0.1.0
|
|
4
28
|
|
|
5
29
|
- Established the UNC (Unified Notification Core) package identity.
|
package/README.md
CHANGED
|
@@ -100,6 +100,12 @@ createNotificationSchema({ tablePrefix: "app_notification_" });
|
|
|
100
100
|
Run the application's normal Drizzle schema workflow after exporting the
|
|
101
101
|
tables. The package never connects to or mutates a database by itself.
|
|
102
102
|
|
|
103
|
+
Upgrading an existing `0.1.0` database to `0.1.1` adds a functional recipient
|
|
104
|
+
preference index used by legacy-key fallback reads. Generate and apply that
|
|
105
|
+
schema diff through the application's normal reviewed migration workflow. UNC
|
|
106
|
+
does not create the index at runtime; correctness is preserved before the index
|
|
107
|
+
is applied, but legacy fallback reads can be slower.
|
|
108
|
+
|
|
103
109
|
## Define channels
|
|
104
110
|
|
|
105
111
|
Channel names are plain text, not a PostgreSQL enum. Adding a future channel
|
|
@@ -144,6 +150,12 @@ const result = await notificationCore.publishAndDispatch({
|
|
|
144
150
|
`publish()` only persists. `publishAndDispatch()` persists and immediately
|
|
145
151
|
attempts due deliveries. Both use the same durable outbox records.
|
|
146
152
|
|
|
153
|
+
A core constructed with a Drizzle `PgTransaction` is publish-only. This lets an
|
|
154
|
+
application commit its own row and `publish()` atomically. Do not call
|
|
155
|
+
`publishAndDispatch()` or `dispatchDue()` on that core: both fail before writing
|
|
156
|
+
or invoking an adapter. After the outer transaction commits, dispatch with a
|
|
157
|
+
core constructed from the top-level `PgDatabase`.
|
|
158
|
+
|
|
147
159
|
For scheduled delivery or retries, call `dispatchDue()` from infrastructure the
|
|
148
160
|
application already owns:
|
|
149
161
|
|
|
@@ -159,6 +171,9 @@ This can run from a cron handler, queue consumer, scheduled function, or an
|
|
|
159
171
|
application-owned interval. The package starts no timer and exposes no port.
|
|
160
172
|
Concurrent workers claim rows with `FOR UPDATE SKIP LOCKED`; abandoned
|
|
161
173
|
`processing` rows become eligible again after the configured lock timeout.
|
|
174
|
+
`workerId` is diagnostic (maximum 154 characters). Every claimed delivery gets
|
|
175
|
+
a separate claim token, so two attempts from the same worker cannot finalize
|
|
176
|
+
each other's rows.
|
|
162
177
|
|
|
163
178
|
## Preferences
|
|
164
179
|
|
|
@@ -215,6 +230,23 @@ const notificationCore = createNotificationCore<AppChannel>({
|
|
|
215
230
|
});
|
|
216
231
|
```
|
|
217
232
|
|
|
233
|
+
Recipient, topic, and channel keys are trimmed at the public boundary, including
|
|
234
|
+
`resolvePreference()` and configured defaults. Keys that become empty are
|
|
235
|
+
rejected. Configuration also fails fast when two raw default keys normalize to
|
|
236
|
+
the same topic or channel, rather than silently choosing one.
|
|
237
|
+
|
|
238
|
+
Reads remain compatible with `0.1.0` preference rows whose stored keys contain
|
|
239
|
+
surrounding whitespace. If multiple stored rows collapse to one canonical
|
|
240
|
+
override, opt-out wins: any `enabled: false` row disables that topic/channel.
|
|
241
|
+
Writing or clearing that override atomically consolidates its legacy duplicates.
|
|
242
|
+
Channel-keyed configuration is copied into own-key registries, so names such as
|
|
243
|
+
`toString`, `constructor`, and `__proto__` have no inherited behavior and work
|
|
244
|
+
only when explicitly configured.
|
|
245
|
+
|
|
246
|
+
Batch publication loads preferences in one exact indexed query plus at most one
|
|
247
|
+
legacy canonical fallback query for the whole recipient batch. It does not scan
|
|
248
|
+
preferences once per recipient.
|
|
249
|
+
|
|
218
250
|
Use `preferencePolicy: "ignore"` only for notifications that legally or
|
|
219
251
|
operationally must be delivered regardless of opt-out:
|
|
220
252
|
|
|
@@ -257,7 +289,8 @@ const unread = await notificationCore.unreadCount(userId);
|
|
|
257
289
|
```
|
|
258
290
|
|
|
259
291
|
Inbox mutations always require the recipient id as well as the notification id,
|
|
260
|
-
so a route can scope changes to its authenticated user.
|
|
292
|
+
so a route can scope changes to its authenticated user. Inbox recipient ids use
|
|
293
|
+
the same trim-and-reject-empty normalization as publication and preferences.
|
|
261
294
|
|
|
262
295
|
## Retry behavior
|
|
263
296
|
|
|
@@ -277,7 +310,11 @@ throw new NotificationDeliveryError(
|
|
|
277
310
|
Unknown errors are retryable. The default backoff is 1 minute, 5 minutes,
|
|
278
311
|
15 minutes, 1 hour, then 6 hours. Configure `retryDelayMs` and
|
|
279
312
|
`defaultMaxAttempts` for application policy. A failed delivery can be explicitly
|
|
280
|
-
requeued with `requeueDelivery()`.
|
|
313
|
+
requeued with `requeueDelivery()`. Attempts are durably incremented when a row
|
|
314
|
+
is claimed, before the adapter runs. `requeueDelivery()` preserves that counter
|
|
315
|
+
by default and therefore requires remaining attempt capacity; pass a larger
|
|
316
|
+
`maxAttempts`, or set `resetAttempts: true` to start a new attempt budget.
|
|
317
|
+
Only a `failed` delivery whose notification is not canceled can be requeued.
|
|
281
318
|
|
|
282
319
|
If a provider callback already has its own durable queue, returning after a
|
|
283
320
|
successful enqueue is correct. UNC then tracks acceptance by that
|
|
@@ -289,8 +326,11 @@ An `idempotencyKey` is unique per recipient. Publishing the same key again
|
|
|
289
326
|
returns the original notification and deliveries with `created: false`.
|
|
290
327
|
|
|
291
328
|
`scheduledFor` delays all selected channels. `cancel(notificationId)` cancels
|
|
292
|
-
pending/retrying/processing deliveries.
|
|
293
|
-
|
|
329
|
+
pending/retrying/processing deliveries. A provider callback already in flight
|
|
330
|
+
cannot be recalled: success from its current claim is persisted as `sent`
|
|
331
|
+
(`sent` is immutable audit history), while failure stays `canceled` and is not
|
|
332
|
+
retried. A stale callback never overwrites the state written by a newer claim,
|
|
333
|
+
and its `DispatchResult` reports the durable state it observed.
|
|
294
334
|
|
|
295
335
|
## Package guarantees
|
|
296
336
|
|
|
@@ -300,7 +340,8 @@ history.
|
|
|
300
340
|
- No background timers or service runtime.
|
|
301
341
|
- No assumption that recipient ids are UUIDs.
|
|
302
342
|
- No assumption that every notification is in-app.
|
|
303
|
-
- Provider callbacks are invoked
|
|
343
|
+
- Provider callbacks are invoked only by a top-level-database core, after the
|
|
344
|
+
claim transaction commits.
|
|
304
345
|
- Preferences disabled at publication are recorded as `skipped` deliveries for
|
|
305
346
|
auditability.
|
|
306
347
|
|
package/dist/core.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type PgDatabase } from "drizzle-orm/pg-core";
|
|
2
2
|
import { type NotificationSchema } from "./schema.js";
|
|
3
3
|
import { PREFERENCE_WILDCARD, type DeliveryAdapters, type DispatchResult, type NotificationLogger, type InboxCursor, type InboxPage, type JsonObject, type PreferenceDefaults, type PreferenceOverride, type PublishInput, type PublishedNotification, type ResolvedPreference } from "./types.js";
|
|
4
4
|
type AnyPgDatabase = PgDatabase<any, any, any>;
|
package/dist/core.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
|