@12-apps/notifications 4.4.0 → 4.5.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/dist/chunk-WU6QJLSZ.js +94 -0
- package/dist/chunk-WU6QJLSZ.js.map +1 -0
- package/dist/chunk-YE24MDS6.js +1022 -0
- package/dist/chunk-YE24MDS6.js.map +1 -0
- package/dist/create-api-notifications-CgBdjfyF.d.ts +909 -0
- package/dist/create-web-notifications-Du3hTs7P.d.ts +354 -0
- package/dist/{generators-FATT537X.d.ts → generators-B9xt3sRh.d.ts} +1 -1
- package/dist/hono/index.d.ts +5 -5
- package/dist/index.d.ts +3 -3
- package/dist/jobs-DhDjrAX5.d.ts +74 -0
- package/dist/manifest/index.d.ts +20 -6
- package/dist/manifest/index.js +2 -1
- package/dist/manifest/index.js.map +1 -1
- package/dist/manifest/server.d.ts +22 -30
- package/dist/manifest/server.js +11 -2
- package/dist/manifest/server.js.map +1 -1
- package/dist/manifest/web.d.ts +51 -0
- package/dist/manifest/web.js +15 -0
- package/dist/manifest/web.js.map +1 -0
- package/dist/react/index.d.ts +6 -353
- package/dist/react/index.js +20 -1008
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.d.ts +79 -902
- package/dist/server/index.js +13 -2
- package/dist/{types-yq_o4N01.d.ts → types-CXLAG3UU.d.ts} +1 -1
- package/dist/web-push/index.d.ts +2 -2
- package/dist/{web-push-KLY6UMRT.d.ts → web-push-Cs14Wp9u.d.ts} +2 -2
- package/dist/{wire-SDUtscGu.d.ts → wire-5IRin4zH.d.ts} +1 -1
- package/package.json +10 -3
- package/src/manifest/index.ts +20 -6
- package/src/manifest/server.ts +8 -0
- package/src/manifest/web.ts +46 -0
- package/src/server/index.ts +20 -0
- package/src/server/jobs.ts +118 -0
- package/src/server/wire-notify-port.ts +125 -0
- package/dist/chunk-F5ANWJCY.js +0 -1
- package/dist/chunk-F5ANWJCY.js.map +0 -1
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { JSX, ComponentType } from 'react';
|
|
2
|
+
import { L as ListNotificationsResult, a as ChannelRow, I as InboxNotification, N as NotificationMessages } from './wire-5IRin4zH.js';
|
|
3
|
+
import { b as NotificationChannel } from './types-CXLAG3UU.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* How the notification screens reach their data (12-15) — the report-builder
|
|
7
|
+
* transport doctrine: this is the ONLY way the surface performs I/O, so a
|
|
8
|
+
* caller supplying one has substituted the entire backend without stubbing a
|
|
9
|
+
* global. The default is same-origin `fetch` riding the browser's cookies.
|
|
10
|
+
*/
|
|
11
|
+
/** A write outcome the screens branch on — never a thrown mutation. */
|
|
12
|
+
type NotificationsResult<T> = {
|
|
13
|
+
ok: true;
|
|
14
|
+
data: T;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
error: string;
|
|
18
|
+
};
|
|
19
|
+
/** A failed read, carrying the status the screens branch on (401 = signed out). */
|
|
20
|
+
declare class NotificationsHttpError extends Error {
|
|
21
|
+
readonly status: number;
|
|
22
|
+
constructor(status: number, message: string);
|
|
23
|
+
}
|
|
24
|
+
interface NotificationsTransport {
|
|
25
|
+
/** A read. Returns the payload INSIDE the `{ data }` envelope. */
|
|
26
|
+
get<T>(path: string): Promise<T>;
|
|
27
|
+
/** A write. Returns a {@link NotificationsResult} rather than rejecting. */
|
|
28
|
+
send<T>(path: string, method: string, body?: unknown): Promise<NotificationsResult<T>>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* @param fallbackError What a failed write says when the server sent no
|
|
32
|
+
* sentence of its own — REQUIRED, the host's words. `createWebNotifications`
|
|
33
|
+
* already passes its (equally required) `messages.operationFailed`; only a
|
|
34
|
+
* host constructing the transport directly writes it here. The old default
|
|
35
|
+
* was one application's Portuguese, and the only string in this package the
|
|
36
|
+
* required-messages port did not cover.
|
|
37
|
+
*/
|
|
38
|
+
declare function httpNotificationsTransport(fallbackError: string): NotificationsTransport;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The wire client, bound to one mount (12-15).
|
|
42
|
+
*
|
|
43
|
+
* Every path this package's screens can call, in one place — which is what
|
|
44
|
+
* makes the api half's route table and the web half's URLs one contract instead
|
|
45
|
+
* of two lists that drift.
|
|
46
|
+
*/
|
|
47
|
+
/** `GET <mount>/notification-preferences` and the PUT's answer. */
|
|
48
|
+
interface PreferencesPayload {
|
|
49
|
+
preferences: Record<string, ChannelRow>;
|
|
50
|
+
availability: Record<NotificationChannel, boolean>;
|
|
51
|
+
/** The host's taxonomy, so the screen renders it without being told twice. */
|
|
52
|
+
categories: string[];
|
|
53
|
+
}
|
|
54
|
+
/** `GET <mount>/push-subscriptions`. */
|
|
55
|
+
interface PushRegistrationPayload {
|
|
56
|
+
/** null = web push is not configured on this deployment. */
|
|
57
|
+
vapidPublicKey: string | null;
|
|
58
|
+
count: number;
|
|
59
|
+
/**
|
|
60
|
+
* Whether the endpoint asked about is still registered to the caller. Present
|
|
61
|
+
* only when one was passed — see {@link NotificationsApiClient.getPushRegistration}.
|
|
62
|
+
*/
|
|
63
|
+
registered?: boolean;
|
|
64
|
+
}
|
|
65
|
+
interface NotificationsApiClient {
|
|
66
|
+
listNotifications(input: {
|
|
67
|
+
cursor?: string | null;
|
|
68
|
+
limit?: number;
|
|
69
|
+
filter?: 'all' | 'unread';
|
|
70
|
+
}): Promise<ListNotificationsResult>;
|
|
71
|
+
unreadCount(): Promise<number>;
|
|
72
|
+
markRead(ids: readonly string[]): Promise<NotificationsResult<{
|
|
73
|
+
updated: number;
|
|
74
|
+
}>>;
|
|
75
|
+
markAllRead(): Promise<NotificationsResult<{
|
|
76
|
+
updated: number;
|
|
77
|
+
}>>;
|
|
78
|
+
remove(ids: readonly string[]): Promise<NotificationsResult<{
|
|
79
|
+
deleted: number;
|
|
80
|
+
}>>;
|
|
81
|
+
getPreferences(): Promise<PreferencesPayload>;
|
|
82
|
+
savePreference(category: string, channel: NotificationChannel, enabled: boolean): Promise<NotificationsResult<PreferencesPayload>>;
|
|
83
|
+
/**
|
|
84
|
+
* The deployment's VAPID key and the caller's device count — and, when an
|
|
85
|
+
* `endpoint` is passed, whether the SERVER still has that exact subscription
|
|
86
|
+
* under the caller's id. The browser holding a subscription object is not
|
|
87
|
+
* evidence of that: a re-own or a 404/410 prune drops the row and leaves the
|
|
88
|
+
* browser's object in place.
|
|
89
|
+
*/
|
|
90
|
+
getPushRegistration(input?: {
|
|
91
|
+
endpoint?: string;
|
|
92
|
+
}): Promise<PushRegistrationPayload>;
|
|
93
|
+
savePushSubscription(input: {
|
|
94
|
+
endpoint: string;
|
|
95
|
+
keys: {
|
|
96
|
+
p256dh: string;
|
|
97
|
+
auth: string;
|
|
98
|
+
};
|
|
99
|
+
}): Promise<NotificationsResult<{
|
|
100
|
+
count: number;
|
|
101
|
+
}>>;
|
|
102
|
+
removePushSubscription(endpoint: string): Promise<NotificationsResult<{
|
|
103
|
+
count: number;
|
|
104
|
+
}>>;
|
|
105
|
+
}
|
|
106
|
+
declare function createNotificationsApiClient(apiBase: string, transport: NotificationsTransport): NotificationsApiClient;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The inbox's client state, as ONE store shared by the bell and the panel.
|
|
110
|
+
*
|
|
111
|
+
* They have to share it: marking a row read in the panel must move the badge in
|
|
112
|
+
* the same tick, and an arrival must add a row to the list AND to the count.
|
|
113
|
+
* the origin got that for free from a react-query cache the host had already
|
|
114
|
+
* mounted; a published package cannot assume one — a query client is a host
|
|
115
|
+
* decision, and requiring a particular one (or a particular version of one) is
|
|
116
|
+
* the kind of dependency that keeps a package out of a host that made the other
|
|
117
|
+
* choice. So the sharing is explicit and dependency-free: one subscribable
|
|
118
|
+
* store, read through `useSyncExternalStore`.
|
|
119
|
+
*
|
|
120
|
+
* Optimistic on every write, with invalidate-on-error: the badge and the list
|
|
121
|
+
* update instantly, and a failed write refetches the server truth rather than
|
|
122
|
+
* leaving the screen asserting something the database does not say.
|
|
123
|
+
*/
|
|
124
|
+
declare const PAGE_SIZE = 20;
|
|
125
|
+
/** The badge's poll while nothing is pushing to us. */
|
|
126
|
+
declare const BADGE_POLL_MS = 60000;
|
|
127
|
+
/**
|
|
128
|
+
* The badge's interval while a realtime connection is live.
|
|
129
|
+
*
|
|
130
|
+
* Five minutes, not "never": this is the reconcile that catches an event the bus
|
|
131
|
+
* dropped, and it costs one COUNT per open tab per five minutes. Deliberately
|
|
132
|
+
* far slower than an operational screen's — a bell badge is ambient, and the
|
|
133
|
+
* arrival that matters is pushed within milliseconds anyway. The poll does NOT
|
|
134
|
+
* stop, which is the standing contract: a dropped event must cost latency and
|
|
135
|
+
* never correctness.
|
|
136
|
+
*/
|
|
137
|
+
declare const BADGE_RECONCILE_MS = 300000;
|
|
138
|
+
type InboxListStatus = 'idle' | 'pending' | 'ready' | 'error';
|
|
139
|
+
interface InboxState {
|
|
140
|
+
unread: number;
|
|
141
|
+
items: InboxNotification[];
|
|
142
|
+
status: InboxListStatus;
|
|
143
|
+
/** A cursor means there is another page. */
|
|
144
|
+
nextCursor: string | null;
|
|
145
|
+
loadingMore: boolean;
|
|
146
|
+
}
|
|
147
|
+
interface InboxStore {
|
|
148
|
+
getState(): InboxState;
|
|
149
|
+
subscribe(listener: () => void): () => void;
|
|
150
|
+
/** Load the first page (idempotent while one is in flight). */
|
|
151
|
+
open(): void;
|
|
152
|
+
/** Refetch the badge count. */
|
|
153
|
+
refreshBadge(): void;
|
|
154
|
+
/** Refetch both — what a realtime hint or a failed write triggers. */
|
|
155
|
+
invalidate(): void;
|
|
156
|
+
loadMore(): void;
|
|
157
|
+
markRead(ids: readonly string[]): void;
|
|
158
|
+
markAllRead(): void;
|
|
159
|
+
remove(id: string): void;
|
|
160
|
+
}
|
|
161
|
+
declare function createInboxStore(api: NotificationsApiClient): InboxStore;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The two hooks the bell and the panel use, and the realtime seam between them.
|
|
165
|
+
*
|
|
166
|
+
* A host that has a message bus passes `subscribe`; one that has not passes
|
|
167
|
+
* nothing and keeps the 60 s poll. The bell ships in this package and mounts in
|
|
168
|
+
* whatever embeds it, so it must not require the host to have adopted anything.
|
|
169
|
+
*/
|
|
170
|
+
/**
|
|
171
|
+
* How the surface learns an inbox changed without asking.
|
|
172
|
+
*
|
|
173
|
+
* Called once per mounted bell with a callback that means only "ask again" — no
|
|
174
|
+
* payload, so the number on screen is always one the server just gave us.
|
|
175
|
+
* Returns its own teardown. A host wires this to whatever it already has.
|
|
176
|
+
*/
|
|
177
|
+
type NotificationsSubscribe = (onHint: () => void) => () => void;
|
|
178
|
+
/**
|
|
179
|
+
* The same wiring, as a HOOK — for a host whose realtime connection lives in
|
|
180
|
+
* React context rather than in a module.
|
|
181
|
+
*
|
|
182
|
+
* `subscribe` above is supplied at FACTORY time, which is module scope, and a
|
|
183
|
+
* context-bound connection cannot be reached from there: the provider holding
|
|
184
|
+
* it is inside the tree. A host in that shape (a `<UserRealtimeProvider>` and a
|
|
185
|
+
* `useUserTopics` hook, which is the common one) had no way to pass anything at
|
|
186
|
+
* all, and the badge simply never heard an event.
|
|
187
|
+
*
|
|
188
|
+
* So this is the second door, and it is the one `@12-apps/app-shell` already
|
|
189
|
+
* uses for the same problem — its consent dialog takes a `useSignal` hook for
|
|
190
|
+
* exactly this reason. Two packages solving one problem two ways is how an
|
|
191
|
+
* adopter ends up believing the feature is unavailable to it.
|
|
192
|
+
*
|
|
193
|
+
* Called during render, so it may use context and hooks freely. Pass one or
|
|
194
|
+
* the other; passing both runs both, which is a host's business.
|
|
195
|
+
*/
|
|
196
|
+
type NotificationsSignalHook = (onHint: () => void) => void;
|
|
197
|
+
declare function useInboxState(store: InboxStore): InboxState;
|
|
198
|
+
/**
|
|
199
|
+
* The bell badge number: pushed while a subscription is live, polled otherwise.
|
|
200
|
+
*
|
|
201
|
+
* `enabled` gates the poll AND the subscription. A signed-out header still
|
|
202
|
+
* mounts the bell, and there is nothing for it to hear.
|
|
203
|
+
*/
|
|
204
|
+
declare function useUnreadCount(store: InboxStore, options?: {
|
|
205
|
+
enabled?: boolean;
|
|
206
|
+
subscribe?: NotificationsSubscribe;
|
|
207
|
+
useSignal?: NotificationsSignalHook;
|
|
208
|
+
}): number;
|
|
209
|
+
/** The panel's list — only fetches while the panel is open. */
|
|
210
|
+
declare function useInboxList(store: InboxStore, open: boolean): InboxState;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Bare bell trigger with the live unread badge — for hosts that do not already
|
|
214
|
+
* have a styled icon-button slot. A host with its own trigger chrome uses
|
|
215
|
+
* `useUnreadCount` + `Panel` directly.
|
|
216
|
+
*/
|
|
217
|
+
|
|
218
|
+
interface BellButtonProps {
|
|
219
|
+
onClick: () => void;
|
|
220
|
+
/** Signed-out hosts still mount the bell; `false` silences it. */
|
|
221
|
+
enabled?: boolean;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The notification-centre slide-over: newest-first list with unread styling,
|
|
226
|
+
* per-item open (marks read + deep-links), soft delete, mark-all, empty /
|
|
227
|
+
* loading / error states and a "load more" cursor pager.
|
|
228
|
+
*
|
|
229
|
+
* Rendering is app-agnostic — the host passes `onNavigate` (its router's
|
|
230
|
+
* navigate) for deep links. Without one a link is simply not followed, which is
|
|
231
|
+
* what lets the panel mount in a host that has no router at all.
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
interface NotificationsPanelProps {
|
|
235
|
+
open: boolean;
|
|
236
|
+
onClose: () => void;
|
|
237
|
+
/** Navigate to a notification's in-app link (the host's router). */
|
|
238
|
+
onNavigate?: (link: string) => void;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* The per-BROWSER Web Push enable step, which sits above the preference matrix
|
|
243
|
+
* because a preference alone cannot reach a device that never subscribed.
|
|
244
|
+
*/
|
|
245
|
+
|
|
246
|
+
/** The panel's copy for a host whose platform blocks browser-level push. */
|
|
247
|
+
interface WebPushPlatformHint {
|
|
248
|
+
title: string;
|
|
249
|
+
body: string;
|
|
250
|
+
}
|
|
251
|
+
interface WebPushSetupConfig {
|
|
252
|
+
/**
|
|
253
|
+
* The host's service-worker path. Path-routed SPAs each control their own
|
|
254
|
+
* scope, and the file itself is the host's.
|
|
255
|
+
*/
|
|
256
|
+
swPath?: string;
|
|
257
|
+
/**
|
|
258
|
+
* Whether THIS platform must be installed to the home screen before a
|
|
259
|
+
* subscription can exist at all.
|
|
260
|
+
*
|
|
261
|
+
* iOS is the case: Safari has no browser-level Web Push, so "Ativar" there
|
|
262
|
+
* asks no permission, creates no subscription, and fails with nothing a user
|
|
263
|
+
* could act on. The check is a config seam rather than a dependency because
|
|
264
|
+
* "is this an installable iOS browser" is a question a host's PWA layer
|
|
265
|
+
* already answers (the origin passes
|
|
266
|
+
* `() => isIosInstallable() && !isStandalone()` from `@12-apps/pwa`).
|
|
267
|
+
*/
|
|
268
|
+
needsInstallFirst?: () => boolean;
|
|
269
|
+
/** What to say instead of the button when the check above is true. */
|
|
270
|
+
installHint?: WebPushPlatformHint;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* The notification-preferences screen: the category × channel matrix over
|
|
275
|
+
* `GET/PUT <mount>/notification-preferences`.
|
|
276
|
+
*
|
|
277
|
+
* Toggles auto-save (optimistic, per change); channels that cannot reach the
|
|
278
|
+
* user right now (no phone on file / channel not declared) render disabled with
|
|
279
|
+
* a hint. Web Push additionally carries the per-BROWSER enable step, since a
|
|
280
|
+
* preference alone cannot reach a device that never subscribed.
|
|
281
|
+
*/
|
|
282
|
+
|
|
283
|
+
interface PreferencesScreenProps {
|
|
284
|
+
/** Rendered under the lead paragraph — a "back to account" link, typically. */
|
|
285
|
+
footer?: JSX.Element;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* The one thing this package exposes to a FRONTEND host (12-15).
|
|
290
|
+
*
|
|
291
|
+
* Everything the notification centre IS — the bell with its live badge, the
|
|
292
|
+
* slide-over inbox with its optimistic mark-read / delete / mark-all and its
|
|
293
|
+
* cursor pager, the preferences matrix with its availability hints and the
|
|
294
|
+
* per-browser push enable step, and every wire call between them — lives inside
|
|
295
|
+
* this package. The host names where the API is mounted, and that is the whole
|
|
296
|
+
* wiring.
|
|
297
|
+
*
|
|
298
|
+
* `page` is the standalone surface (the preferences screen), which is the one
|
|
299
|
+
* thing a host routes to. The bell and the panel are a PAIR a host drops into
|
|
300
|
+
* its own chrome, and they share one store, so a read in the panel moves the
|
|
301
|
+
* badge in the same tick.
|
|
302
|
+
*/
|
|
303
|
+
interface NotificationsWebConfig {
|
|
304
|
+
/** The account mount the routes live under, e.g. `/api/account`. */
|
|
305
|
+
apiBase: string;
|
|
306
|
+
/** How the surface reaches its data. Default: same-origin fetch. */
|
|
307
|
+
transport?: NotificationsTransport;
|
|
308
|
+
/** User-facing copy overrides (pt-BR product copy by default). */
|
|
309
|
+
messages: NotificationMessages;
|
|
310
|
+
/**
|
|
311
|
+
* How the surface learns an inbox changed without asking — the host's message
|
|
312
|
+
* bus. Without it the badge keeps its 60 s poll, which is the standing
|
|
313
|
+
* contract rather than a fallback: a dropped event must cost latency, never
|
|
314
|
+
* correctness.
|
|
315
|
+
*/
|
|
316
|
+
subscribe?: NotificationsSubscribe;
|
|
317
|
+
/**
|
|
318
|
+
* The same wiring as a HOOK, for a host whose realtime connection lives in
|
|
319
|
+
* React context — see `NotificationsSignalHook`. `subscribe` is read at
|
|
320
|
+
* factory time, which such a host cannot reach.
|
|
321
|
+
*/
|
|
322
|
+
useSignal?: NotificationsSignalHook;
|
|
323
|
+
/** The browser push enable step's host seams (SW path, platform hint). */
|
|
324
|
+
webPush?: WebPushSetupConfig;
|
|
325
|
+
}
|
|
326
|
+
interface WebNotifications {
|
|
327
|
+
/** The routed surface: the preferences screen. */
|
|
328
|
+
page: ComponentType<PreferencesScreenProps>;
|
|
329
|
+
/** The bell, already bound to the shared store. */
|
|
330
|
+
BellButton: ComponentType<BellButtonProps>;
|
|
331
|
+
/** The inbox slide-over, sharing that store. */
|
|
332
|
+
Panel: ComponentType<NotificationsPanelProps>;
|
|
333
|
+
/**
|
|
334
|
+
* Bell + panel as ONE element, for a host that just wants the feature in its
|
|
335
|
+
* header and does not want to own the open/closed state.
|
|
336
|
+
*/
|
|
337
|
+
BellWithPanel: ComponentType<{
|
|
338
|
+
enabled?: boolean;
|
|
339
|
+
onNavigate?: (link: string) => void;
|
|
340
|
+
}>;
|
|
341
|
+
/** The badge number, for a host with its own trigger chrome. */
|
|
342
|
+
useUnreadCount: (options?: {
|
|
343
|
+
enabled?: boolean;
|
|
344
|
+
}) => number;
|
|
345
|
+
/** The shared client state, for host glue. */
|
|
346
|
+
store: InboxStore;
|
|
347
|
+
/** The bound wire client. */
|
|
348
|
+
api: NotificationsApiClient;
|
|
349
|
+
/** The copy in force, so a host's own chrome can reuse a sentence. */
|
|
350
|
+
messages: NotificationMessages;
|
|
351
|
+
}
|
|
352
|
+
declare function createWebNotifications(config: NotificationsWebConfig): WebNotifications;
|
|
353
|
+
|
|
354
|
+
export { BADGE_POLL_MS as B, type InboxListStatus as I, type NotificationsApiClient as N, PAGE_SIZE as P, type WebNotifications as W, BADGE_RECONCILE_MS as a, type BellButtonProps as b, createWebNotifications as c, type InboxState as d, type InboxStore as e, NotificationsHttpError as f, type NotificationsPanelProps as g, type NotificationsResult as h, type NotificationsSignalHook as i, type NotificationsSubscribe as j, type NotificationsTransport as k, type NotificationsWebConfig as l, type PreferencesPayload as m, type PreferencesScreenProps as n, type PushRegistrationPayload as o, type WebPushPlatformHint as p, type WebPushSetupConfig as q, createInboxStore as r, createNotificationsApiClient as s, httpNotificationsTransport as t, useInboxList as u, useInboxState as v, useUnreadCount as w };
|
package/dist/hono/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Hono, Context } from 'hono';
|
|
2
|
-
import { ApiNotifications, NotificationsServerConfig, NotificationsActor } from '../
|
|
3
|
-
import '../generators-
|
|
4
|
-
import '../types-
|
|
5
|
-
import '../wire-
|
|
6
|
-
import '../web-push-
|
|
2
|
+
import { A as ApiNotifications, N as NotificationsServerConfig, a as NotificationsActor } from '../create-api-notifications-CgBdjfyF.js';
|
|
3
|
+
import '../generators-B9xt3sRh.js';
|
|
4
|
+
import '../types-CXLAG3UU.js';
|
|
5
|
+
import '../wire-5IRin4zH.js';
|
|
6
|
+
import '../web-push-Cs14Wp9u.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* `@12-apps/notifications/hono` — the account notification endpoints as a
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { D as DeliveryStatus,
|
|
2
|
-
export { N as NotificationGeneratorRegistry, c as createGeneratorRegistry } from './generators-
|
|
3
|
-
export { C as ChannelMatrix, a as ChannelRow, D as DEFAULT_CHANNEL_ROW, I as InboxNotification, L as ListNotificationsResult, N as NotificationMessages, b as NotificationRow, c as NotificationWireMessages, d as defaultChannelMatrix, e as enabledChannelsOf, i as inboxWire, m as mergeChoices, f as mergeStoredRow, g as messagesOf } from './wire-
|
|
1
|
+
export { D as DeliveryStatus, N as NOTIFICATION_CHANNELS, a as NotificationCategory, b as NotificationChannel, c as NotificationContent, d as NotificationEvent, e as NotificationGenerator, f as NotificationLogger, g as NotificationRecipient, h as NotificationTaxonomy, i as NotificationTransport, T as TransportRecipient, t as taxonomyOf } from './types-CXLAG3UU.js';
|
|
2
|
+
export { N as NotificationGeneratorRegistry, c as createGeneratorRegistry } from './generators-B9xt3sRh.js';
|
|
3
|
+
export { C as ChannelMatrix, a as ChannelRow, D as DEFAULT_CHANNEL_ROW, I as InboxNotification, L as ListNotificationsResult, N as NotificationMessages, b as NotificationRow, c as NotificationWireMessages, d as defaultChannelMatrix, e as enabledChannelsOf, i as inboxWire, m as mergeChoices, f as mergeStoredRow, g as messagesOf } from './wire-5IRin4zH.js';
|
|
4
4
|
|
|
5
5
|
/** Thrown by `notify` when no generator is registered for the event type. */
|
|
6
6
|
declare class UnknownNotificationTypeError extends Error {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { WireJobBlueprint } from '@12-apps/wiring';
|
|
2
|
+
import { A as ApiNotifications } from './create-api-notifications-CgBdjfyF.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The two background jobs getting a message out actually needs.
|
|
6
|
+
*
|
|
7
|
+
* ## Why they are declared here and not left to the host
|
|
8
|
+
*
|
|
9
|
+
* `notify()` commits the inbox record and one QUEUED delivery per channel in a
|
|
10
|
+
* transaction, and that row IS the durable record of the send. Everything after
|
|
11
|
+
* it is a decision about WHEN the provider call happens — and every one of
|
|
12
|
+
* those decisions is this package's knowledge, not a host's:
|
|
13
|
+
*
|
|
14
|
+
* - the fast path retries only INFRASTRUCTURE faults, because a provider
|
|
15
|
+
* rejecting one channel does not throw here (the row goes FAILED and the
|
|
16
|
+
* sweep owns the retry), so three spaced attempts is about surviving a
|
|
17
|
+
* restart rather than chasing a provider;
|
|
18
|
+
* - the sweep's five-minute cadence is the resolution at which "my customer
|
|
19
|
+
* never got the e-mail" stops being an incident;
|
|
20
|
+
* - the sweep runs single-flight, because overlapping passes are duplicate
|
|
21
|
+
* billed provider calls;
|
|
22
|
+
* - and the sweep is what makes the system UNSTUCK-ABLE: it re-dispatches
|
|
23
|
+
* FAILED rows and stale claims whose dispatching process died mid-flight.
|
|
24
|
+
* Before it existed a provider blip left a delivery FAILED forever.
|
|
25
|
+
*
|
|
26
|
+
* A host asked to restate all of that is a host that can get it wrong — and,
|
|
27
|
+
* far more likely, a host that never schedules the sweep at all and quietly
|
|
28
|
+
* has no retry. That is the `paymentsJobBlueprints()` incident exactly: a
|
|
29
|
+
* mechanism a host must remember to schedule is a mechanism most hosts do not
|
|
30
|
+
* have. The origin host DID write both jobs, correctly, by hand — cadence,
|
|
31
|
+
* lease ttl, attempts and concurrency restated in its own `lib/jobs` — which
|
|
32
|
+
* is the drift this declaration ends rather than a gap it fills.
|
|
33
|
+
*
|
|
34
|
+
* What stays the host's: whether to run them at all, on which queue runtime,
|
|
35
|
+
* and the lease implementation. A host with no worker declines the `jobs`
|
|
36
|
+
* capability in writing and the wiring report says so.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* What the host closes over at bind time — the mounted api's two send paths,
|
|
41
|
+
* and nothing else. Deliberately a `Pick` of the real thing rather than a
|
|
42
|
+
* parallel interface: a reshape of either method stops compiling here.
|
|
43
|
+
*/
|
|
44
|
+
type NotificationsJobDeps = Pick<ApiNotifications, 'dispatchDeliveries' | 'drainPending'>;
|
|
45
|
+
/**
|
|
46
|
+
* The single-flight queue name — the same string `@12-apps/jobs` exports as
|
|
47
|
+
* `SWEEP_QUEUE`, stated as a literal because this package does not depend on
|
|
48
|
+
* the job library (the payments-backend precedent, for the same reason).
|
|
49
|
+
*/
|
|
50
|
+
declare const NOTIFICATIONS_SWEEP_QUEUE = "sweeps";
|
|
51
|
+
/** Five minutes — see the header on why this number is the package's. */
|
|
52
|
+
declare const NOTIFICATIONS_DRAIN_CRON = "*/5 * * * *";
|
|
53
|
+
/**
|
|
54
|
+
* Comfortably longer than a drain pass, and under the cadence's own patience:
|
|
55
|
+
* every delivery a pass re-dispatches is a provider call.
|
|
56
|
+
*/
|
|
57
|
+
declare const NOTIFICATIONS_DRAIN_LEASE_MS: number;
|
|
58
|
+
/**
|
|
59
|
+
* The jobs contribution. `namespace` is prepended once at bind time, so these
|
|
60
|
+
* arrive at a runner as `notifications.dispatch` and `notifications.drain` —
|
|
61
|
+
* the wire names the origin host already uses, so adopting is a deletion
|
|
62
|
+
* rather than a rename.
|
|
63
|
+
*/
|
|
64
|
+
declare const NOTIFICATIONS_JOBS: {
|
|
65
|
+
readonly namespace: "notifications";
|
|
66
|
+
readonly blueprints: {
|
|
67
|
+
readonly dispatch: WireJobBlueprint<{
|
|
68
|
+
notificationId: string;
|
|
69
|
+
}, NotificationsJobDeps>;
|
|
70
|
+
readonly drain: WireJobBlueprint<void, NotificationsJobDeps>;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export { type NotificationsJobDeps as N, NOTIFICATIONS_DRAIN_CRON as a, NOTIFICATIONS_DRAIN_LEASE_MS as b, NOTIFICATIONS_JOBS as c, NOTIFICATIONS_SWEEP_QUEUE as d };
|
package/dist/manifest/index.d.ts
CHANGED
|
@@ -14,11 +14,24 @@
|
|
|
14
14
|
* (`config.transports`, `config.drivers`), so what would be declared here
|
|
15
15
|
* is a seam the host already fills — and declaring it would oblige an
|
|
16
16
|
* adopter to bind a mailer this package never owns.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
*
|
|
18
|
+
* ON THE `jobs` DECLARATION. The dispatch fast path and the retry sweep are
|
|
19
|
+
* this package's own cadence decisions — attempts, backoff, the five-minute
|
|
20
|
+
* tick, the single-flight lease — and they were host code in every adopting
|
|
21
|
+
* host, restated by hand from this package's docstrings. That is the
|
|
22
|
+
* `paymentsJobBlueprints()` incident's shape exactly: a mechanism a host must
|
|
23
|
+
* remember to schedule is a mechanism most hosts silently do not have, and the
|
|
24
|
+
* one host that DID write them wrote them correctly only because someone read
|
|
25
|
+
* the source. `./server`'s `NOTIFICATIONS_JOBS` declares both; a host with no
|
|
26
|
+
* worker declines the capability in writing and the report says so.
|
|
27
|
+
*
|
|
28
|
+
* ON THE `web` INVENTORY, which this manifest used to narrow away. The reason
|
|
29
|
+
* given — that listing it would oblige every SERVER host to answer for a React
|
|
30
|
+
* surface it never mounts — is not how the consumer behaves: a capability
|
|
31
|
+
* declared for the OTHER runtime is reported `out-of-scope`, and only an
|
|
32
|
+
* applicable, unanswered one is `unbound`. So the narrowing protected nothing
|
|
33
|
+
* and hid Bell, Panel and Preferences from every adopting host, which is why
|
|
34
|
+
* the origin host hand-duplicated two of the three.
|
|
22
35
|
*
|
|
23
36
|
* ON THE `db` DECLARATION. The origin host already composes
|
|
24
37
|
* `prisma/notifications.prisma` into its schema — but by STRUCTURAL
|
|
@@ -47,7 +60,8 @@ declare const notificationsManifest: {
|
|
|
47
60
|
readonly observability: {
|
|
48
61
|
readonly namespace: "notifications";
|
|
49
62
|
};
|
|
50
|
-
readonly server: readonly ["http"];
|
|
63
|
+
readonly server: readonly ["http", "jobs"];
|
|
64
|
+
readonly web: readonly ["surface"];
|
|
51
65
|
};
|
|
52
66
|
|
|
53
67
|
export { notificationsManifest };
|
package/dist/manifest/index.js
CHANGED
|
@@ -10,7 +10,8 @@ var notificationsManifest = {
|
|
|
10
10
|
* exhausts its attempts files under `notifications`, not nowhere.
|
|
11
11
|
*/
|
|
12
12
|
observability: { namespace: "notifications" },
|
|
13
|
-
server: ["http"]
|
|
13
|
+
server: ["http", "jobs"],
|
|
14
|
+
web: ["surface"]
|
|
14
15
|
};
|
|
15
16
|
export {
|
|
16
17
|
notificationsManifest
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/manifest/index.ts"],"sourcesContent":["/**\n * `@12-apps/notifications/manifest` — the SHARED wiring manifest.\n *\n * Identity, the Prisma contribution (the four owned models) and the runtime\n * inventory: `http` on the server. Three narrowings are deliberate:\n *\n * - **No `notifications` blueprints.** This package is the notification\n * MECHANISM — the inbox, the preference matrix, the transports, the retry\n * sweep — not an author of alerts. The blueprints belong to whichever\n * package raises the alert (`@12-apps/product-research`'s budget warning\n * ships as a factory over host copy for exactly this reason), and this one\n * takes them through the `generators` seam at mount.\n * - **No `email` capability.** The transports are host-supplied config\n * (`config.transports`, `config.drivers`), so what would be declared here\n * is a seam the host already fills — and declaring it would oblige an\n * adopter to bind a mailer this package never owns.\n *
|
|
1
|
+
{"version":3,"sources":["../../src/manifest/index.ts"],"sourcesContent":["/**\n * `@12-apps/notifications/manifest` — the SHARED wiring manifest.\n *\n * Identity, the Prisma contribution (the four owned models) and the runtime\n * inventory: `http` on the server. Three narrowings are deliberate:\n *\n * - **No `notifications` blueprints.** This package is the notification\n * MECHANISM — the inbox, the preference matrix, the transports, the retry\n * sweep — not an author of alerts. The blueprints belong to whichever\n * package raises the alert (`@12-apps/product-research`'s budget warning\n * ships as a factory over host copy for exactly this reason), and this one\n * takes them through the `generators` seam at mount.\n * - **No `email` capability.** The transports are host-supplied config\n * (`config.transports`, `config.drivers`), so what would be declared here\n * is a seam the host already fills — and declaring it would oblige an\n * adopter to bind a mailer this package never owns.\n *\n * ON THE `jobs` DECLARATION. The dispatch fast path and the retry sweep are\n * this package's own cadence decisions — attempts, backoff, the five-minute\n * tick, the single-flight lease — and they were host code in every adopting\n * host, restated by hand from this package's docstrings. That is the\n * `paymentsJobBlueprints()` incident's shape exactly: a mechanism a host must\n * remember to schedule is a mechanism most hosts silently do not have, and the\n * one host that DID write them wrote them correctly only because someone read\n * the source. `./server`'s `NOTIFICATIONS_JOBS` declares both; a host with no\n * worker declines the capability in writing and the report says so.\n *\n * ON THE `web` INVENTORY, which this manifest used to narrow away. The reason\n * given — that listing it would oblige every SERVER host to answer for a React\n * surface it never mounts — is not how the consumer behaves: a capability\n * declared for the OTHER runtime is reported `out-of-scope`, and only an\n * applicable, unanswered one is `unbound`. So the narrowing protected nothing\n * and hid Bell, Panel and Preferences from every adopting host, which is why\n * the origin host hand-duplicated two of the three.\n *\n * ON THE `db` DECLARATION. The origin host already composes\n * `prisma/notifications.prisma` into its schema — but by STRUCTURAL\n * DISCOVERY, the assembler's fallback for a package that declares nothing.\n * That fallback is why the gap was invisible: four tables reached a host's\n * database with no declaration behind them, and the contract's whole claim\n * is that a package's models arrive because it said so. Declaring changes no\n * host behaviour (the assembler reads the declaration where it used to scan)\n * and closes the one case where composition was happening by accident.\n *\n * `@12-apps/wiring` is a TYPE-ONLY devDependency (the report-builder move):\n * the manifest is a plain `satisfies`-checked value, and the producer\n * factories' runtime assertions run in this package's own test suite.\n */\n\nimport type { PackageManifest } from '@12-apps/wiring';\n\nexport const notificationsManifest = {\n name: '@12-apps/notifications',\n contract: 1,\n db: { partial: 'prisma/notifications.prisma', migrations: 'prisma/migrations' },\n /**\n * Mandatory for runtime manifests since wiring 1.3.0: a delivery that\n * exhausts its attempts files under `notifications`, not nowhere.\n */\n observability: { namespace: 'notifications' },\n server: ['http', 'jobs'],\n web: ['surface'],\n} as const satisfies PackageManifest;\n"],"mappings":";;;AAmDO,IAAM,wBAAwB;AAAA,EACnC,MAAM;AAAA,EACN,UAAU;AAAA,EACV,IAAI,EAAE,SAAS,+BAA+B,YAAY,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAK9E,eAAe,EAAE,WAAW,gBAAgB;AAAA,EAC5C,QAAQ,CAAC,QAAQ,MAAM;AAAA,EACvB,KAAK,CAAC,SAAS;AACjB;","names":[]}
|
|
@@ -1,34 +1,11 @@
|
|
|
1
|
+
import * as _12_apps_wiring from '@12-apps/wiring';
|
|
1
2
|
import { WireRequest } from '@12-apps/wiring';
|
|
2
|
-
import { NotificationsServerConfig, ApiNotifications, NotificationsRoute } from '../
|
|
3
|
-
import '../
|
|
4
|
-
import '../
|
|
5
|
-
import '../
|
|
6
|
-
import '../
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* `@12-apps/notifications/manifest/server` — the server capabilities.
|
|
10
|
-
*
|
|
11
|
-
* `http.create` wraps `createApiNotifications` in a WIRE VIEW, and the reason
|
|
12
|
-
* is one field. `NotificationsRequest` carries `headers` — the contract's
|
|
13
|
-
* `WireRequest` does not, because headers are the adapter's business
|
|
14
|
-
* everywhere else — and exactly one descriptor reads it: push-subscribe takes
|
|
15
|
-
* `user-agent` as the DEVICE HINT it labels a subscription with. Without the
|
|
16
|
-
* view the field would simply be absent at runtime while still type-checking,
|
|
17
|
-
* and every saved device would come back unnamed: a silent quality loss, the
|
|
18
|
-
* failure mode the wiring contract exists to convert into a loud one.
|
|
19
|
-
*
|
|
20
|
-
* So the view derives `headers` from the raw request the contract already
|
|
21
|
-
* carries for the handlers `params`/`query`/`body` cannot serve. A host whose
|
|
22
|
-
* adapter leaves `request` unset still gets a working surface — every route
|
|
23
|
-
* answers, the subscription saves — with an unnamed device, which is the
|
|
24
|
-
* honest degradation for a hint. `@12-apps/notifications/hono` populates it.
|
|
25
|
-
*
|
|
26
|
-
* Everything else rides beside the mapped routes on the aggregate unchanged:
|
|
27
|
-
* `notify`, `notifyByPermission`, `dispatchDeliveries`, `drainPending`, the
|
|
28
|
-
* three stores, `registerGenerator` and the transports registry. A host still
|
|
29
|
-
* calls those directly — being mounted does not make the emit front door stop
|
|
30
|
-
* being a library.
|
|
31
|
-
*/
|
|
3
|
+
import { N as NotificationsServerConfig, A as ApiNotifications, b as NotificationsRoute } from '../create-api-notifications-CgBdjfyF.js';
|
|
4
|
+
import { N as NotificationsJobDeps } from '../jobs-DhDjrAX5.js';
|
|
5
|
+
import '../generators-B9xt3sRh.js';
|
|
6
|
+
import '../types-CXLAG3UU.js';
|
|
7
|
+
import '../wire-5IRin4zH.js';
|
|
8
|
+
import '../web-push-Cs14Wp9u.js';
|
|
32
9
|
|
|
33
10
|
/** One `NotificationsRoute` as the wiring contract reads it. */
|
|
34
11
|
declare function asWireRoute(route: NotificationsRoute): {
|
|
@@ -48,6 +25,21 @@ declare const notificationsServerManifest: {
|
|
|
48
25
|
readonly http: {
|
|
49
26
|
readonly create: typeof createWireApiNotifications;
|
|
50
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* The dispatch fast path and the retry sweep, with their cadence. The host
|
|
30
|
+
* binds `{ dispatchDeliveries, drainPending }` off its own mount — the two
|
|
31
|
+
* methods the aggregate already hands it — and deletes the hand-rolled
|
|
32
|
+
* copies. See `../server/jobs` for why the numbers are the package's.
|
|
33
|
+
*/
|
|
34
|
+
readonly jobs: {
|
|
35
|
+
readonly namespace: "notifications";
|
|
36
|
+
readonly blueprints: {
|
|
37
|
+
readonly dispatch: _12_apps_wiring.WireJobBlueprint<{
|
|
38
|
+
notificationId: string;
|
|
39
|
+
}, NotificationsJobDeps>;
|
|
40
|
+
readonly drain: _12_apps_wiring.WireJobBlueprint<void, NotificationsJobDeps>;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
51
43
|
};
|
|
52
44
|
|
|
53
45
|
export { createWireApiNotifications, notificationsServerManifest };
|
package/dist/manifest/server.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
NOTIFICATIONS_JOBS
|
|
3
|
+
} from "../chunk-WU6QJLSZ.js";
|
|
2
4
|
import {
|
|
3
5
|
createApiNotifications
|
|
4
6
|
} from "../chunk-ORXJH3VM.js";
|
|
@@ -39,7 +41,14 @@ function createWireApiNotifications(config) {
|
|
|
39
41
|
__name(createWireApiNotifications, "createWireApiNotifications");
|
|
40
42
|
var notificationsServerManifest = {
|
|
41
43
|
name: "@12-apps/notifications",
|
|
42
|
-
http: { create: createWireApiNotifications }
|
|
44
|
+
http: { create: createWireApiNotifications },
|
|
45
|
+
/**
|
|
46
|
+
* The dispatch fast path and the retry sweep, with their cadence. The host
|
|
47
|
+
* binds `{ dispatchDeliveries, drainPending }` off its own mount — the two
|
|
48
|
+
* methods the aggregate already hands it — and deletes the hand-rolled
|
|
49
|
+
* copies. See `../server/jobs` for why the numbers are the package's.
|
|
50
|
+
*/
|
|
51
|
+
jobs: NOTIFICATIONS_JOBS
|
|
43
52
|
};
|
|
44
53
|
export {
|
|
45
54
|
createWireApiNotifications,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/manifest/server.ts"],"sourcesContent":["/**\n * `@12-apps/notifications/manifest/server` — the server capabilities.\n *\n * `http.create` wraps `createApiNotifications` in a WIRE VIEW, and the reason\n * is one field. `NotificationsRequest` carries `headers` — the contract's\n * `WireRequest` does not, because headers are the adapter's business\n * everywhere else — and exactly one descriptor reads it: push-subscribe takes\n * `user-agent` as the DEVICE HINT it labels a subscription with. Without the\n * view the field would simply be absent at runtime while still type-checking,\n * and every saved device would come back unnamed: a silent quality loss, the\n * failure mode the wiring contract exists to convert into a loud one.\n *\n * So the view derives `headers` from the raw request the contract already\n * carries for the handlers `params`/`query`/`body` cannot serve. A host whose\n * adapter leaves `request` unset still gets a working surface — every route\n * answers, the subscription saves — with an unnamed device, which is the\n * honest degradation for a hint. `@12-apps/notifications/hono` populates it.\n *\n * Everything else rides beside the mapped routes on the aggregate unchanged:\n * `notify`, `notifyByPermission`, `dispatchDeliveries`, `drainPending`, the\n * three stores, `registerGenerator` and the transports registry. A host still\n * calls those directly — being mounted does not make the emit front door stop\n * being a library.\n */\n\nimport type { AnyServerManifest, WireRequest } from '@12-apps/wiring';\n\nimport {\n createApiNotifications,\n type ApiNotifications,\n type NotificationsRoute,\n type NotificationsServerConfig,\n} from '../server';\n\n/** The header names this surface reads — the device hint, and nothing else. */\nconst READ_HEADERS = ['user-agent'] as const;\n\n/** The headers the package expects, taken off the raw request when there is one. */\nfunction headersOf(request: WireRequest<never>): Record<string, string | undefined> {\n const raw = request.request;\n if (!raw) return {};\n return Object.fromEntries(\n READ_HEADERS.map((name) => [name, raw.headers.get(name) ?? undefined]),\n );\n}\n\n/** One `NotificationsRoute` as the wiring contract reads it. */\nfunction asWireRoute(route: NotificationsRoute): {\n method: NotificationsRoute['method'];\n path: string;\n handle(request: WireRequest<never>): Promise<{ status: number; body: unknown }>;\n} {\n return {\n method: route.method,\n path: route.path,\n handle: (request) =>\n route.handle({\n actor: request.actor,\n params: request.params,\n query: request.query,\n body: request.body,\n headers: headersOf(request),\n }),\n };\n}\n\n/** `createApiNotifications`, its routes re-shaped for the aggregate. */\nexport function createWireApiNotifications(\n config: NotificationsServerConfig,\n): Omit<ApiNotifications, 'routes'> & { routes: ReturnType<typeof asWireRoute>[] } {\n const api = createApiNotifications(config);\n return { ...api, routes: api.routes.map(asWireRoute) };\n}\n\nexport const notificationsServerManifest = {\n name: '@12-apps/notifications',\n http: { create: createWireApiNotifications },\n} as const satisfies AnyServerManifest;\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/manifest/server.ts"],"sourcesContent":["/**\n * `@12-apps/notifications/manifest/server` — the server capabilities.\n *\n * `http.create` wraps `createApiNotifications` in a WIRE VIEW, and the reason\n * is one field. `NotificationsRequest` carries `headers` — the contract's\n * `WireRequest` does not, because headers are the adapter's business\n * everywhere else — and exactly one descriptor reads it: push-subscribe takes\n * `user-agent` as the DEVICE HINT it labels a subscription with. Without the\n * view the field would simply be absent at runtime while still type-checking,\n * and every saved device would come back unnamed: a silent quality loss, the\n * failure mode the wiring contract exists to convert into a loud one.\n *\n * So the view derives `headers` from the raw request the contract already\n * carries for the handlers `params`/`query`/`body` cannot serve. A host whose\n * adapter leaves `request` unset still gets a working surface — every route\n * answers, the subscription saves — with an unnamed device, which is the\n * honest degradation for a hint. `@12-apps/notifications/hono` populates it.\n *\n * Everything else rides beside the mapped routes on the aggregate unchanged:\n * `notify`, `notifyByPermission`, `dispatchDeliveries`, `drainPending`, the\n * three stores, `registerGenerator` and the transports registry. A host still\n * calls those directly — being mounted does not make the emit front door stop\n * being a library.\n */\n\nimport type { AnyServerManifest, WireRequest } from '@12-apps/wiring';\n\nimport {\n createApiNotifications,\n NOTIFICATIONS_JOBS,\n type ApiNotifications,\n type NotificationsRoute,\n type NotificationsServerConfig,\n} from '../server';\n\n/** The header names this surface reads — the device hint, and nothing else. */\nconst READ_HEADERS = ['user-agent'] as const;\n\n/** The headers the package expects, taken off the raw request when there is one. */\nfunction headersOf(request: WireRequest<never>): Record<string, string | undefined> {\n const raw = request.request;\n if (!raw) return {};\n return Object.fromEntries(\n READ_HEADERS.map((name) => [name, raw.headers.get(name) ?? undefined]),\n );\n}\n\n/** One `NotificationsRoute` as the wiring contract reads it. */\nfunction asWireRoute(route: NotificationsRoute): {\n method: NotificationsRoute['method'];\n path: string;\n handle(request: WireRequest<never>): Promise<{ status: number; body: unknown }>;\n} {\n return {\n method: route.method,\n path: route.path,\n handle: (request) =>\n route.handle({\n actor: request.actor,\n params: request.params,\n query: request.query,\n body: request.body,\n headers: headersOf(request),\n }),\n };\n}\n\n/** `createApiNotifications`, its routes re-shaped for the aggregate. */\nexport function createWireApiNotifications(\n config: NotificationsServerConfig,\n): Omit<ApiNotifications, 'routes'> & { routes: ReturnType<typeof asWireRoute>[] } {\n const api = createApiNotifications(config);\n return { ...api, routes: api.routes.map(asWireRoute) };\n}\n\nexport const notificationsServerManifest = {\n name: '@12-apps/notifications',\n http: { create: createWireApiNotifications },\n /**\n * The dispatch fast path and the retry sweep, with their cadence. The host\n * binds `{ dispatchDeliveries, drainPending }` off its own mount — the two\n * methods the aggregate already hands it — and deletes the hand-rolled\n * copies. See `../server/jobs` for why the numbers are the package's.\n */\n jobs: NOTIFICATIONS_JOBS,\n} as const satisfies AnyServerManifest;\n"],"mappings":";;;;;;;;;;;;;AAoCA,IAAM,eAAe,CAAC,YAAY;AAGlC,SAAS,UAAU,SAAiE;AAClF,QAAM,MAAM,QAAQ;AACpB,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,SAAO,OAAO;AAAA,IACZ,aAAa,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,QAAQ,IAAI,IAAI,KAAK,MAAS,CAAC;AAAA,EACvE;AACF;AANS;AAST,SAAS,YAAY,OAInB;AACA,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,MAAM,MAAM;AAAA,IACZ,QAAQ,wBAAC,YACP,MAAM,OAAO;AAAA,MACX,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,MAChB,OAAO,QAAQ;AAAA,MACf,MAAM,QAAQ;AAAA,MACd,SAAS,UAAU,OAAO;AAAA,IAC5B,CAAC,GAPK;AAAA,EAQV;AACF;AAjBS;AAoBF,SAAS,2BACd,QACiF;AACjF,QAAM,MAAM,uBAAuB,MAAM;AACzC,SAAO,EAAE,GAAG,KAAK,QAAQ,IAAI,OAAO,IAAI,WAAW,EAAE;AACvD;AALgB;AAOT,IAAM,8BAA8B;AAAA,EACzC,MAAM;AAAA,EACN,MAAM,EAAE,QAAQ,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO3C,MAAM;AACR;","names":[]}
|