@voltro/plugin-notifications 0.51.0 → 0.53.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.
@@ -5,7 +5,7 @@ property of its respective copyright holders and is used under the terms of
5
5
  its license. This file is provided for attribution; it grants no rights in
6
6
  @voltro/plugin-notifications itself, which is proprietary (see LICENSE).
7
7
 
8
- Generated from the resolved runtime dependency closure (6 packages).
8
+ Generated from the resolved runtime dependency closure (12 packages).
9
9
 
10
10
  ---
11
11
 
@@ -37,6 +37,146 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37
37
  SOFTWARE.
38
38
  ```
39
39
 
40
+ ## @tanstack/devtools-event-client@0.4.4
41
+
42
+ License: MIT
43
+
44
+ ```
45
+ MIT License
46
+
47
+ Copyright (c) 2025 TanStack
48
+
49
+ Permission is hereby granted, free of charge, to any person obtaining a copy
50
+ of this software and associated documentation files (the "Software"), to deal
51
+ in the Software without restriction, including without limitation the rights
52
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
53
+ copies of the Software, and to permit persons to whom the Software is
54
+ furnished to do so, subject to the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be included in all
57
+ copies or substantial portions of the Software.
58
+
59
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
60
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
61
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
62
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
63
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
64
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
65
+ SOFTWARE.
66
+ ```
67
+
68
+ ## @tanstack/form-core@1.33.5
69
+
70
+ License: MIT
71
+
72
+ ```
73
+ MIT License
74
+
75
+ Copyright (c) 2021-present Tanner Linsley
76
+
77
+ Permission is hereby granted, free of charge, to any person obtaining a copy
78
+ of this software and associated documentation files (the "Software"), to deal
79
+ in the Software without restriction, including without limitation the rights
80
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
81
+ copies of the Software, and to permit persons to whom the Software is
82
+ furnished to do so, subject to the following conditions:
83
+
84
+ The above copyright notice and this permission notice shall be included in all
85
+ copies or substantial portions of the Software.
86
+
87
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
88
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
89
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
90
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
91
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
92
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
93
+ SOFTWARE.
94
+ ```
95
+
96
+ ## @tanstack/pacer-lite@0.1.1
97
+
98
+ License: MIT
99
+
100
+ ```
101
+ MIT License
102
+
103
+ Copyright (c) 2025 Tanner Linsley
104
+
105
+ Permission is hereby granted, free of charge, to any person obtaining a copy
106
+ of this software and associated documentation files (the "Software"), to deal
107
+ in the Software without restriction, including without limitation the rights
108
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
109
+ copies of the Software, and to permit persons to whom the Software is
110
+ furnished to do so, subject to the following conditions:
111
+
112
+ The above copyright notice and this permission notice shall be included in all
113
+ copies or substantial portions of the Software.
114
+
115
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
116
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
117
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
118
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
119
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
120
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
121
+ SOFTWARE.
122
+ ```
123
+
124
+ ## @tanstack/react-store@0.11.1
125
+
126
+ License: MIT
127
+
128
+ ```
129
+ MIT License
130
+
131
+ Copyright (c) 2021 Tanner Linsley
132
+
133
+ Permission is hereby granted, free of charge, to any person obtaining a copy
134
+ of this software and associated documentation files (the "Software"), to deal
135
+ in the Software without restriction, including without limitation the rights
136
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
137
+ copies of the Software, and to permit persons to whom the Software is
138
+ furnished to do so, subject to the following conditions:
139
+
140
+ The above copyright notice and this permission notice shall be included in all
141
+ copies or substantial portions of the Software.
142
+
143
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
144
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
145
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
146
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
147
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
148
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
149
+ SOFTWARE.
150
+ ```
151
+
152
+ ## @tanstack/store@0.11.1
153
+
154
+ License: MIT
155
+
156
+ ```
157
+ MIT License
158
+
159
+ Copyright (c) 2021 Tanner Linsley
160
+
161
+ Permission is hereby granted, free of charge, to any person obtaining a copy
162
+ of this software and associated documentation files (the "Software"), to deal
163
+ in the Software without restriction, including without limitation the rights
164
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
165
+ copies of the Software, and to permit persons to whom the Software is
166
+ furnished to do so, subject to the following conditions:
167
+
168
+ The above copyright notice and this permission notice shall be included in all
169
+ copies or substantial portions of the Software.
170
+
171
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
172
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
173
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
174
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
175
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
176
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
177
+ SOFTWARE.
178
+ ```
179
+
40
180
  ## jose@6.2.8
41
181
 
42
182
  License: MIT
@@ -329,6 +469,34 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
329
469
  SOFTWARE.
330
470
  ```
331
471
 
472
+ ## use-sync-external-store@1.6.0
473
+
474
+ License: MIT
475
+
476
+ ```
477
+ MIT License
478
+
479
+ Copyright (c) Meta Platforms, Inc. and affiliates.
480
+
481
+ Permission is hereby granted, free of charge, to any person obtaining a copy
482
+ of this software and associated documentation files (the "Software"), to deal
483
+ in the Software without restriction, including without limitation the rights
484
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
485
+ copies of the Software, and to permit persons to whom the Software is
486
+ furnished to do so, subject to the following conditions:
487
+
488
+ The above copyright notice and this permission notice shall be included in all
489
+ copies or substantial portions of the Software.
490
+
491
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
492
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
493
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
494
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
495
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
496
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
497
+ SOFTWARE.
498
+ ```
499
+
332
500
  ## uuid@11.1.1
333
501
 
334
502
  License: MIT
package/dist/index.d.ts CHANGED
@@ -24,10 +24,17 @@ export declare const buildNotificationService: (deps: {
24
24
  }) => NotificationServiceShape;
25
25
 
26
26
  /** A delivery channel. `id` is the channel key prefs target (e.g. 'email',
27
- * 'slack', 'sms', 'push', 'inApp'). */
27
+ * 'slack', 'sms', 'push', 'inApp').
28
+ *
29
+ * A MULTI-ENDPOINT channel (web push, mobile push) additionally implements
30
+ * `deliverDetailed`: per-endpoint outcomes, each endpoint isolated — one dead
31
+ * registration never aborts the others, and the delivery log says per
32
+ * endpoint what happened (the per-CHANNEL record the old push loop wrote
33
+ * could not). The service prefers it over `deliver` when present. */
28
34
  export declare interface Channel {
29
35
  readonly id: string;
30
36
  readonly deliver: (msg: ChannelMessage) => Promise<void>;
37
+ readonly deliverDetailed?: (msg: ChannelMessage) => Promise<ReadonlyArray<EndpointDelivery>>;
31
38
  }
32
39
 
33
40
  /** A built notification to deliver on one channel. */
@@ -65,6 +72,19 @@ export declare const customChannel: (id: string, deliver: (msg: ChannelMessage)
65
72
  */
66
73
  export declare const dataStoreNotificationStore: (store: DataStore) => NotificationStore;
67
74
 
75
+ export declare const dataStorePushSubscriptionStore: (store: DataStore) => PushSubscriptionStore;
76
+
77
+ /**
78
+ * The receiver side — what a browser does with the body above. Shipped (not
79
+ * test-only) because the mock-push-endpoint test IS the product's proof: a test
80
+ * that asserts "some bytes arrived" proves transport, this proves the bytes
81
+ * decrypt with the subscriber's keys and nothing else.
82
+ */
83
+ export declare const decryptWebPushPayload: (body: Buffer, receiver: {
84
+ readonly uaPrivate: Buffer;
85
+ readonly authSecret: Buffer;
86
+ }) => Buffer;
87
+
68
88
  export declare const deliveriesTable: Table<"_voltro_notification_deliveries", FieldDefinitions<{
69
89
  readonly id: ColumnBuilder<string, "id", boolean>;
70
90
  readonly recipient: ColumnBuilder<string, "text", boolean>;
@@ -73,9 +93,14 @@ export declare const deliveriesTable: Table<"_voltro_notification_deliveries", F
73
93
  readonly status: ColumnBuilder<string, "text", boolean>;
74
94
  readonly error: ColumnBuilder<string | null, "text", boolean>;
75
95
  readonly at: ColumnBuilder<Date, "timestamp", true>;
76
- }>, true, "byDeliveriesAt">;
77
-
78
- /** Per-channel delivery outcome (for the delivery log). */
96
+ readonly endpoint: ColumnBuilder<string | null, "text", boolean>;
97
+ readonly clickToken: ColumnBuilder<string | null, "text", boolean>;
98
+ readonly clickedAt: ColumnBuilder<Date | null, "timestamp", boolean>;
99
+ }>, true, "byDeliveriesAt" | "byDeliveriesClickToken">;
100
+
101
+ /** Per-channel delivery outcome (for the delivery log). A multi-endpoint
102
+ * channel writes ONE record per endpoint (`endpoint` set); single-target
103
+ * channels write one per channel as before. */
79
104
  export declare interface DeliveryRecord {
80
105
  readonly id: string;
81
106
  readonly to: string;
@@ -84,6 +109,13 @@ export declare interface DeliveryRecord {
84
109
  readonly status: 'sent' | 'failed' | 'skipped';
85
110
  readonly error?: string;
86
111
  readonly at: string;
112
+ /** The device token / push endpoint this record is about, on multi-endpoint
113
+ * channels. Absent on single-target channels. */
114
+ readonly endpoint?: string | null;
115
+ /** Click-report capability (web push). Never sent to a dashboard reader. */
116
+ readonly clickToken?: string | null;
117
+ /** Set when the recipient clicked the delivered notification. */
118
+ readonly clickedAt?: string | null;
87
119
  }
88
120
 
89
121
  /** Email channel — the app supplies the sender (compose with
@@ -95,6 +127,39 @@ export declare const emailChannel: (send: (msg: {
95
127
  text: string;
96
128
  }) => Promise<void>) => Channel;
97
129
 
130
+ /** Test seam: fixed ephemeral key + salt reproduce RFC 8291 Appendix A. */
131
+ declare interface EncryptOverrides {
132
+ readonly asPrivate?: Buffer;
133
+ readonly salt?: Buffer;
134
+ }
135
+
136
+ /**
137
+ * Encrypt one payload for one subscription — the complete `aes128gcm` body
138
+ * (header ‖ ciphertext) a push service accepts, per RFC 8291. Single record
139
+ * (web-push payloads are capped ~4 KB by every push service, so multi-record
140
+ * framing buys nothing).
141
+ */
142
+ export declare const encryptWebPushPayload: (keys: PushSubscriptionKeys, payload: Buffer, overrides?: EncryptOverrides) => Buffer;
143
+
144
+ /** One endpoint's (device token's / browser subscription's) outcome inside a
145
+ * multi-endpoint channel delivery. `pruned` marks a registration the channel
146
+ * itself removed (the push service said it is gone). */
147
+ declare interface EndpointDelivery {
148
+ readonly endpoint: string;
149
+ readonly status: 'sent' | 'failed';
150
+ readonly error?: string;
151
+ readonly pruned?: boolean;
152
+ /** Capability for the notification-click report — stored on the delivery
153
+ * record, carried in the push payload, matched constant-time. */
154
+ readonly clickToken?: string;
155
+ }
156
+
157
+ export declare const endpointHashOf: (endpoint: string) => string;
158
+
159
+ /** Mint a fresh P-256 private scalar, base64url — what the dev secret mint and
160
+ * `voltro secret generate` produce for `VOLTRO_VAPID_PRIVATE_KEY`. */
161
+ export declare const generateVapidPrivateKey: () => string;
162
+
98
163
  /** One notification held for later delivery — a digest window rollup or a
99
164
  * quiet-hours deferral. `flushAt` is the ISO instant it becomes deliverable
100
165
  * (the window boundary / the digest flush tick). `kind` marks WHY it was held
@@ -205,6 +270,8 @@ export declare const makeSubjectId: (resolve: NotificationsPluginOptions["resolv
205
270
 
206
271
  export declare const memoryNotificationStore: () => NotificationStore;
207
272
 
273
+ export declare const memoryPushSubscriptionStore: () => PushSubscriptionStore;
274
+
208
275
  /** The minute-of-day (0–1439) `at` falls on in the IANA `tz`. Uses `Intl` so no
209
276
  * date-math library is needed; an unknown zone falls back to UTC. */
210
277
  export declare const minuteOfDayInZone: (at: Date, tz: string) => number;
@@ -374,6 +441,10 @@ export declare interface NotificationStore {
374
441
  readonly listDeliveries: (opts?: {
375
442
  limit?: number;
376
443
  }) => Promise<ReadonlyArray<DeliveryRecord>>;
444
+ /** Stamp `clickedAt` on the delivery record carrying this click token.
445
+ * Constant-time token match happens in the caller; this is the lookup +
446
+ * write. Returns false when no record carries the token. */
447
+ readonly markClicked: (clickToken: string) => Promise<boolean>;
377
448
  readonly subscribeTopic: (sub: TopicSubscription) => Promise<void>;
378
449
  readonly unsubscribeTopic: (topic: string, subjectId: string) => Promise<void>;
379
450
  readonly listTopicSubscribers: (topic: string) => Promise<ReadonlyArray<TopicSubscription>>;
@@ -418,6 +489,10 @@ export declare const pushChannel: (opts: {
418
489
  readonly id?: string;
419
490
  readonly tokensFor: (subjectId: string) => Promise<ReadonlyArray<string>>;
420
491
  readonly transport: (payload: PushPayload) => Promise<void>;
492
+ /** Called when the transport threw `PushTokenRejected` for one token — the
493
+ * app's prune hook (this channel's registry is the APP's table, so the app
494
+ * deletes; web push, whose registry is the plugin's own, prunes itself). */
495
+ readonly onTokenRejected?: (token: string, reason: string) => Promise<void>;
421
496
  }) => Channel;
422
497
 
423
498
  /** The provider-agnostic push payload `pushChannel` builds per device token —
@@ -432,6 +507,54 @@ export declare interface PushPayload {
432
507
  readonly data: Record<string, unknown>;
433
508
  }
434
509
 
510
+ /** A browser push subscription's crypto material, as `PushSubscription.toJSON()`
511
+ * hands it to the page: base64url `p256dh` (the UA public key) + `auth`. */
512
+ export declare interface PushSubscriptionKeys {
513
+ readonly p256dh: string;
514
+ readonly auth: string;
515
+ }
516
+
517
+ export declare interface PushSubscriptionRow {
518
+ readonly subjectId: string;
519
+ readonly endpoint: string;
520
+ readonly endpointHash: string;
521
+ readonly p256dh: string;
522
+ readonly auth: string;
523
+ readonly ua: string | null;
524
+ readonly tenantId: string | null;
525
+ }
526
+
527
+ export declare const pushSubscriptionsTable: Table<"_voltro_notification_push_subscriptions", FieldDefinitions<{
528
+ readonly id: ColumnBuilder<string, "id", boolean>;
529
+ readonly subjectId: ColumnBuilder<string, "text", boolean>;
530
+ /** sha256 hex of `endpoint` — the unique key (endpoint URLs can exceed the
531
+ * unique-index byte ceiling on mssql/mysql, so the hash carries the
532
+ * constraint and the URL rides as plain text). */
533
+ readonly endpointHash: ColumnBuilder<string, "text", boolean>;
534
+ readonly endpoint: ColumnBuilder<string, "text", boolean>;
535
+ /** Browser subscription crypto material (`PushSubscription.toJSON().keys`). */
536
+ readonly p256dh: ColumnBuilder<string, "text", boolean>;
537
+ readonly auth: ColumnBuilder<string, "text", boolean>;
538
+ readonly ua: ColumnBuilder<string | null, "text", boolean>;
539
+ readonly createdAt: ColumnBuilder<Date, "timestamp", true>;
540
+ readonly tenantId: ColumnBuilder<string | null, "text", boolean>;
541
+ }>, true, "byPushSubject">;
542
+
543
+ export declare interface PushSubscriptionStore {
544
+ /** Idempotent per endpoint: a re-subscribe (same browser, possibly a new
545
+ * logged-in subject) takes the row over — the endpoint belongs to the
546
+ * browser profile, and the latest authenticated subject is its owner. */
547
+ readonly upsert: (row: PushSubscriptionRow) => Promise<void>;
548
+ readonly listForSubject: (subjectId: string) => Promise<ReadonlyArray<PushSubscriptionRow>>;
549
+ /** Unsubscribe — scoped to the CALLING subject so one subject cannot detach
550
+ * another's browser. */
551
+ readonly removeForSubject: (endpointHash: string, subjectId: string) => Promise<boolean>;
552
+ /** Prune — unconditional by hash: the push service said the endpoint is gone,
553
+ * whoever it belonged to. */
554
+ readonly removeByHash: (endpointHash: string) => Promise<void>;
555
+ readonly countForSubject: (subjectId: string) => Promise<number>;
556
+ }
557
+
435
558
  /** A push transport rejected a device token (unregistered / invalid / expired —
436
559
  * the APNs `Unregistered` / FCM `UNREGISTERED` class). Thrown by `pushChannel`'s
437
560
  * `deliver` so the fan-out records the delivery `failed` with the token that was
@@ -478,6 +601,9 @@ export declare const quietHoursTable: Table<"_voltro_notification_quiet_hours",
478
601
  readonly policy: ColumnBuilder<string, "text", true>;
479
602
  }>, true, never>;
480
603
 
604
+ /** Test seam. */
605
+ export declare const resetWebPushStoreForTest: () => void;
606
+
481
607
  /**
482
608
  * Resolve which channel ids to deliver on. Order: caller-requested `channels`
483
609
  * (intersected with configured) ELSE all configured; then drop any the
@@ -509,6 +635,9 @@ export declare interface SendResult {
509
635
  readonly held?: 'digest' | 'quiet-held' | 'quiet-drop';
510
636
  }
511
637
 
638
+ /** POST one encrypted payload to one subscription's push service. */
639
+ export declare const sendWebPush: (target: WebPushTarget, payload: Buffer, options: WebPushSendOptions) => Promise<WebPushOutcome>;
640
+
512
641
  /** SMS via a generic REST sender (Twilio etc.) — app supplies the send fn. */
513
642
  export declare const smsChannel: (send: (to: string, body: string) => Promise<void>) => Channel;
514
643
 
@@ -527,6 +656,19 @@ export declare const topicSubscriptionsTable: Table<"_voltro_notification_topic_
527
656
  readonly tenantId: ColumnBuilder<string | null, "text", boolean>;
528
657
  }>, true, "byTopic">;
529
658
 
659
+ /**
660
+ * The `Authorization: vapid t=<jwt>, k=<pub>` header value for one push-service
661
+ * origin (RFC 8292). `aud` is the ENDPOINT's origin (scheme+host), `exp` 12h
662
+ * out (the spec ceiling is 24h), `sub` the operator contact when configured —
663
+ * push services use it to reach whoever is sending, so it is worth setting.
664
+ */
665
+ export declare const vapidAuthorization: (endpoint: string, privateKeyB64url: string, contact?: string, nowMs?: number) => string;
666
+
667
+ /** The browser-facing application server key (65-byte uncompressed point,
668
+ * base64url) derived from the private scalar. Throws on a malformed key —
669
+ * a wrong VAPID key must fail the boot, not the first delivery. */
670
+ export declare const vapidPublicKeyFor: (privateKeyB64url: string) => string;
671
+
530
672
  /** POST the message to an incoming webhook (Slack / Discord / Teams / generic).
531
673
  * `format` shapes the JSON body; default Slack-style `{ text }`. */
532
674
  export declare const webhookChannel: (opts: {
@@ -536,4 +678,60 @@ export declare const webhookChannel: (opts: {
536
678
  readonly headers?: Record<string, string>;
537
679
  }) => Channel;
538
680
 
681
+ /**
682
+ * The Web Push channel. Delivery is per endpoint and isolated — the fan-out
683
+ * records one `EndpointDelivery` per registered endpoint, prunes on `gone`,
684
+ * and never lets one endpoint's failure abort another's send.
685
+ */
686
+ export declare const webPushChannel: (options?: WebPushChannelOptions) => Channel;
687
+
688
+ export declare interface WebPushChannelOptions {
689
+ /** Override the channel id (prefs key). Default `webPush`. */
690
+ readonly id?: string;
691
+ /** The VAPID private scalar. Default: `VOLTRO_VAPID_PRIVATE_KEY` from the
692
+ * environment, read at delivery time (env files load after config). */
693
+ readonly privateKey?: string;
694
+ /** VAPID `sub` contact (`mailto:`/https) push services may use to reach the
695
+ * operator. Strongly recommended for production. */
696
+ readonly contact?: string;
697
+ /** Push-service retention (TTL header), seconds. Default 24h. */
698
+ readonly ttl?: number;
699
+ /** Test seam — injected into `sendWebPush`. */
700
+ readonly fetchImpl?: typeof fetch;
701
+ }
702
+
703
+ export declare type WebPushOutcome = {
704
+ readonly kind: 'delivered';
705
+ readonly status: number;
706
+ }
707
+ /** 404/410 — the subscription no longer exists at the push service. The ONE
708
+ * signal a registry may prune on. */
709
+ | {
710
+ readonly kind: 'gone';
711
+ readonly status: number;
712
+ } | {
713
+ readonly kind: 'failed';
714
+ readonly status: number;
715
+ readonly detail: string;
716
+ };
717
+
718
+ /** The browser-facing application server key for the configured private key. */
719
+ export declare const webPushPublicKey: (explicit?: string) => string;
720
+
721
+ export declare interface WebPushSendOptions {
722
+ readonly privateKey: string;
723
+ /** VAPID `sub` claim — `mailto:` or https URL the push service can reach the
724
+ * operator at. */
725
+ readonly contact?: string;
726
+ /** Push-service retention for an undeliverable message, seconds. Default 24h. */
727
+ readonly ttl?: number;
728
+ /** Injectable for the mock-endpoint tests. */
729
+ readonly fetchImpl?: typeof fetch;
730
+ }
731
+
732
+ export declare interface WebPushTarget {
733
+ readonly endpoint: string;
734
+ readonly keys: PushSubscriptionKeys;
735
+ }
736
+
539
737
  export { }