@holo-js/notifications 0.1.3

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/index.mjs ADDED
@@ -0,0 +1,842 @@
1
+ import {
2
+ createAnonymousNotificationTarget,
3
+ defineNotification,
4
+ isNotificationDefinition,
5
+ normalizeNotificationDefinition,
6
+ notificationsInternals
7
+ } from "./chunk-INGM2JYY.mjs";
8
+
9
+ // src/runtime.ts
10
+ import { randomUUID } from "crypto";
11
+ import { holoNotificationsDefaults } from "@holo-js/config";
12
+
13
+ // src/registry.ts
14
+ function normalizeChannelName(value) {
15
+ const normalized = value.trim();
16
+ if (!normalized) {
17
+ throw new Error("[@holo-js/notifications] Notification channel names must be non-empty strings.");
18
+ }
19
+ return normalized;
20
+ }
21
+ function getRegistry() {
22
+ const runtime = globalThis;
23
+ runtime.__holoNotificationChannelRegistry__ ??= /* @__PURE__ */ new Map();
24
+ return runtime.__holoNotificationChannelRegistry__;
25
+ }
26
+ function registerNotificationChannel(name, channel, options = {}) {
27
+ const normalizedName = normalizeChannelName(name);
28
+ if (typeof channel?.send !== "function") {
29
+ throw new Error(`[@holo-js/notifications] Notification channel "${normalizedName}" must define send().`);
30
+ }
31
+ const registry = getRegistry();
32
+ if (registry.has(normalizedName) && options.replaceExisting !== true) {
33
+ throw new Error(`[@holo-js/notifications] Notification channel "${normalizedName}" is already registered.`);
34
+ }
35
+ registry.set(normalizedName, Object.freeze({
36
+ name: normalizedName,
37
+ channel
38
+ }));
39
+ }
40
+ function getRegisteredNotificationChannel(name) {
41
+ return getRegistry().get(normalizeChannelName(name));
42
+ }
43
+ function listRegisteredNotificationChannels() {
44
+ return Object.freeze([...getRegistry().values()]);
45
+ }
46
+ function resetNotificationChannelRegistry() {
47
+ getRegistry().clear();
48
+ }
49
+ var notificationRegistryInternals = {
50
+ getRegistry,
51
+ normalizeChannelName
52
+ };
53
+
54
+ // src/runtime.ts
55
+ var HOLO_NOTIFICATIONS_DELIVER_JOB = "holo.notifications.deliver";
56
+ function normalizeOptionalString(value, label) {
57
+ const normalized = value.trim();
58
+ if (!normalized) {
59
+ throw new Error(`[@holo-js/notifications] ${label} must be a non-empty string.`);
60
+ }
61
+ return normalized;
62
+ }
63
+ function normalizeDelayValue(value, label) {
64
+ if (typeof value === "number") {
65
+ if (!Number.isFinite(value) || value < 0) {
66
+ throw new Error(`[@holo-js/notifications] ${label} must be a finite number greater than or equal to 0.`);
67
+ }
68
+ return value;
69
+ }
70
+ if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
71
+ throw new Error(`[@holo-js/notifications] ${label} dates must be valid Date instances.`);
72
+ }
73
+ return value;
74
+ }
75
+ function isObject(value) {
76
+ return !!value && typeof value === "object" && !Array.isArray(value);
77
+ }
78
+ function isAnonymousTarget(value) {
79
+ return isObject(value) && value.anonymous === true && isObject(value.routes);
80
+ }
81
+ function getRuntimeBindings() {
82
+ return getRuntimeState().bindings ?? {};
83
+ }
84
+ function getRuntimeState() {
85
+ const runtime = globalThis;
86
+ runtime.__holoNotificationsRuntime__ ??= {};
87
+ return runtime.__holoNotificationsRuntime__;
88
+ }
89
+ function getDispatchHandler() {
90
+ const bindings = getRuntimeBindings();
91
+ return bindings.dispatch ?? dispatchNotifications;
92
+ }
93
+ function dynamicImport(specifier) {
94
+ return import(specifier);
95
+ }
96
+ async function loadQueueModule() {
97
+ const override = getRuntimeState().loadQueueModule;
98
+ if (override) {
99
+ try {
100
+ return await override();
101
+ } catch (error) {
102
+ if (error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND") {
103
+ throw new Error("[@holo-js/notifications] Queued or delayed notifications require @holo-js/queue to be installed.");
104
+ }
105
+ throw error;
106
+ }
107
+ }
108
+ try {
109
+ return await dynamicImport("@holo-js/queue");
110
+ } catch (error) {
111
+ if (error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND") {
112
+ throw new Error("[@holo-js/notifications] Queued or delayed notifications require @holo-js/queue to be installed.");
113
+ }
114
+ throw error;
115
+ }
116
+ }
117
+ async function loadDbModule() {
118
+ const override = getRuntimeState().loadDbModule;
119
+ if (override) {
120
+ try {
121
+ return await override();
122
+ } catch (error) {
123
+ if (error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND") {
124
+ return null;
125
+ }
126
+ throw error;
127
+ }
128
+ }
129
+ try {
130
+ return await dynamicImport("@holo-js/db");
131
+ } catch (error) {
132
+ if (error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND") {
133
+ return null;
134
+ }
135
+ throw error;
136
+ }
137
+ }
138
+ function createNotificationContext(anonymous) {
139
+ return Object.freeze({ anonymous });
140
+ }
141
+ function createBuildContext(channel, anonymous) {
142
+ return Object.freeze({
143
+ channel,
144
+ anonymous
145
+ });
146
+ }
147
+ function normalizeEmailRouteFromValue(value) {
148
+ if (typeof value === "string") {
149
+ const email = value.trim();
150
+ if (!email) {
151
+ throw new Error("[@holo-js/notifications] Email routes must be non-empty strings.");
152
+ }
153
+ return email;
154
+ }
155
+ if (!isObject(value) || typeof value.email !== "string" || !value.email.trim()) {
156
+ throw new Error("[@holo-js/notifications] Email routes must be a string or an object with a non-empty email.");
157
+ }
158
+ return Object.freeze({
159
+ email: value.email.trim(),
160
+ ...typeof value.name === "string" && value.name.trim() ? { name: value.name.trim() } : {}
161
+ });
162
+ }
163
+ function resolveEmailRouteFromNotifiable(notifiable) {
164
+ if (!isObject(notifiable) || typeof notifiable.email !== "string" || !notifiable.email.trim()) {
165
+ throw new Error("[@holo-js/notifications] Email notifications require a notifiable with a non-empty email.");
166
+ }
167
+ return Object.freeze({
168
+ email: notifiable.email.trim(),
169
+ ...typeof notifiable.name === "string" && notifiable.name.trim() ? { name: notifiable.name.trim() } : {}
170
+ });
171
+ }
172
+ function normalizeDatabaseRouteFromValue(value) {
173
+ if (!isObject(value) || typeof value.id !== "string" && typeof value.id !== "number" || typeof value.type !== "string" || !value.type.trim()) {
174
+ throw new Error("[@holo-js/notifications] Database routes must include a string or numeric id and a non-empty type.");
175
+ }
176
+ return Object.freeze({
177
+ id: value.id,
178
+ type: value.type.trim()
179
+ });
180
+ }
181
+ function resolveDatabaseRouteFromNotifiable(notifiable) {
182
+ if (!isObject(notifiable) || typeof notifiable.id !== "string" && typeof notifiable.id !== "number") {
183
+ throw new Error("[@holo-js/notifications] Database notifications require a notifiable with a string or numeric id.");
184
+ }
185
+ const explicitType = typeof notifiable.type === "string" && notifiable.type.trim() ? notifiable.type.trim() : void 0;
186
+ if (explicitType) {
187
+ return Object.freeze({
188
+ id: notifiable.id,
189
+ type: explicitType
190
+ });
191
+ }
192
+ const constructorName = isObject(notifiable) && "constructor" in notifiable && typeof notifiable.constructor === "function" && typeof notifiable.constructor.name === "string" ? notifiable.constructor.name.trim() : "";
193
+ if (!constructorName || constructorName === "Object") {
194
+ throw new Error(
195
+ "[@holo-js/notifications] Database notifications require a notifiable.type or a non-plain-object constructor name."
196
+ );
197
+ }
198
+ return Object.freeze({
199
+ id: notifiable.id,
200
+ type: constructorName
201
+ });
202
+ }
203
+ function normalizeBroadcastRouteFromValue(value) {
204
+ if (typeof value === "string") {
205
+ const channel = value.trim();
206
+ if (!channel) {
207
+ throw new Error("[@holo-js/notifications] Broadcast routes must be non-empty strings.");
208
+ }
209
+ return channel;
210
+ }
211
+ if (Array.isArray(value)) {
212
+ const channels = value.map((entry, index) => {
213
+ if (typeof entry !== "string" || !entry.trim()) {
214
+ throw new Error(`[@holo-js/notifications] Broadcast route entry at index ${index} must be a non-empty string.`);
215
+ }
216
+ return entry.trim();
217
+ });
218
+ if (channels.length === 0) {
219
+ throw new Error("[@holo-js/notifications] Broadcast routes must include at least one channel.");
220
+ }
221
+ return Object.freeze(channels);
222
+ }
223
+ if (!isObject(value) || !Array.isArray(value.channels)) {
224
+ throw new Error("[@holo-js/notifications] Broadcast routes must be a string, string array, or object with channels.");
225
+ }
226
+ return Object.freeze({
227
+ channels: normalizeBroadcastRouteFromValue(value.channels)
228
+ });
229
+ }
230
+ function resolveBroadcastRouteFromNotifiable(notifiable) {
231
+ if (!isObject(notifiable)) {
232
+ throw new Error(
233
+ "[@holo-js/notifications] Broadcast notifications require an anonymous route or a routeNotificationForBroadcast() method."
234
+ );
235
+ }
236
+ if (typeof notifiable.routeNotificationForBroadcast === "function") {
237
+ return normalizeBroadcastRouteFromValue(notifiable.routeNotificationForBroadcast());
238
+ }
239
+ if (typeof notifiable.broadcastChannels === "function") {
240
+ return normalizeBroadcastRouteFromValue(notifiable.broadcastChannels());
241
+ }
242
+ if ("broadcastChannels" in notifiable) {
243
+ return normalizeBroadcastRouteFromValue(notifiable.broadcastChannels);
244
+ }
245
+ throw new Error(
246
+ "[@holo-js/notifications] Broadcast notifications require an anonymous route or a routeNotificationForBroadcast() method."
247
+ );
248
+ }
249
+ function normalizeNotificationRecord(route, payload, notificationType) {
250
+ const now = /* @__PURE__ */ new Date();
251
+ return Object.freeze({
252
+ id: randomUUID(),
253
+ type: notificationType,
254
+ notifiableType: route.type,
255
+ notifiableId: route.id,
256
+ data: payload.data,
257
+ readAt: null,
258
+ createdAt: now,
259
+ updatedAt: now
260
+ });
261
+ }
262
+ function requireMailer(bindings) {
263
+ if (!bindings.mailer) {
264
+ throw new Error("[@holo-js/notifications] Email notifications require a configured mailer runtime.");
265
+ }
266
+ return bindings.mailer;
267
+ }
268
+ function requireBroadcaster(bindings) {
269
+ if (!bindings.broadcaster) {
270
+ throw new Error("[@holo-js/notifications] Broadcast notifications require a configured broadcaster runtime.");
271
+ }
272
+ return bindings.broadcaster;
273
+ }
274
+ function requireStore(bindings) {
275
+ if (!bindings.store) {
276
+ throw new Error("[@holo-js/notifications] Database notifications require a configured notification store runtime.");
277
+ }
278
+ return bindings.store;
279
+ }
280
+ function normalizeNotificationRecordIds(ids) {
281
+ const normalized = ids.map((value, index) => {
282
+ if (typeof value !== "string" || !value.trim()) {
283
+ throw new Error(`[@holo-js/notifications] Notification id at index ${index} must be a non-empty string.`);
284
+ }
285
+ return value.trim();
286
+ });
287
+ return Object.freeze([...new Set(normalized)]);
288
+ }
289
+ var builtInChannels = Object.freeze({
290
+ email: Object.freeze({
291
+ resolveRoute: resolveEmailRouteFromNotifiable,
292
+ async send(input) {
293
+ await requireMailer(getRuntimeBindings()).send(
294
+ input.payload,
295
+ input
296
+ );
297
+ }
298
+ }),
299
+ database: Object.freeze({
300
+ resolveRoute: resolveDatabaseRouteFromNotifiable,
301
+ async send(input) {
302
+ const route = input.route;
303
+ if (!route) {
304
+ throw new Error("[@holo-js/notifications] Database notifications require a resolved route.");
305
+ }
306
+ await requireStore(getRuntimeBindings()).create(
307
+ normalizeNotificationRecord(
308
+ route,
309
+ input.payload,
310
+ input.notificationType
311
+ )
312
+ );
313
+ }
314
+ }),
315
+ broadcast: Object.freeze({
316
+ resolveRoute: resolveBroadcastRouteFromNotifiable,
317
+ async send(input) {
318
+ await requireBroadcaster(getRuntimeBindings()).send(
319
+ input.payload,
320
+ input
321
+ );
322
+ }
323
+ })
324
+ });
325
+ function getNotificationChannel(name) {
326
+ if (name in builtInChannels) {
327
+ return builtInChannels[name];
328
+ }
329
+ return getRegisteredNotificationChannel(name)?.channel;
330
+ }
331
+ function resolveTargets(target) {
332
+ if (target.kind === "anonymous") {
333
+ const anonymous = target.value;
334
+ if (!isAnonymousTarget(anonymous)) {
335
+ throw new Error("[@holo-js/notifications] Anonymous notification targets must be created through notifyUsing().");
336
+ }
337
+ return Object.freeze([Object.freeze({
338
+ index: 0,
339
+ anonymous: true,
340
+ notifiable: anonymous,
341
+ routes: anonymous.routes
342
+ })]);
343
+ }
344
+ if (target.kind === "many") {
345
+ if (!Array.isArray(target.value)) {
346
+ throw new Error("[@holo-js/notifications] Multi-target notification dispatch requires an array target.");
347
+ }
348
+ return Object.freeze(target.value.map((notifiable, index) => Object.freeze({
349
+ index,
350
+ anonymous: false,
351
+ notifiable
352
+ })));
353
+ }
354
+ return Object.freeze([Object.freeze({
355
+ index: 0,
356
+ anonymous: false,
357
+ notifiable: target.value
358
+ })]);
359
+ }
360
+ function resolveChannels(notification, target) {
361
+ const channels = notification.via(target.notifiable, createNotificationContext(target.anonymous));
362
+ if (!Array.isArray(channels)) {
363
+ throw new Error("[@holo-js/notifications] Notification via() must return an array of channel names.");
364
+ }
365
+ return Object.freeze(channels.map((channel, index) => {
366
+ if (typeof channel !== "string") {
367
+ throw new Error(`[@holo-js/notifications] Notification channel at index ${index} must be a string.`);
368
+ }
369
+ const normalized = normalizeOptionalString(channel, "Notification channel");
370
+ if (!getNotificationChannel(normalized)) {
371
+ throw new Error(`[@holo-js/notifications] Notification channel "${normalized}" is not registered.`);
372
+ }
373
+ return normalized;
374
+ }));
375
+ }
376
+ function resolvePayload(notification, channel, target) {
377
+ const factory = notification.build[channel];
378
+ if (typeof factory !== "function") {
379
+ throw new Error(
380
+ `[@holo-js/notifications] Notification channel "${channel}" is listed in via() but has no build.${channel}() payload factory.`
381
+ );
382
+ }
383
+ return factory(target.notifiable, createBuildContext(channel, target.anonymous));
384
+ }
385
+ function resolveNotificationQueueOptions(notification, target, channel) {
386
+ if (typeof notification.queue === "function") {
387
+ return notification.queue(
388
+ target.notifiable,
389
+ channel,
390
+ createNotificationContext(target.anonymous)
391
+ );
392
+ }
393
+ return notification.queue ?? false;
394
+ }
395
+ function resolveNotificationDelay(notification, target, channel) {
396
+ if (typeof notification.delay === "function") {
397
+ return notification.delay(
398
+ target.notifiable,
399
+ channel,
400
+ createNotificationContext(target.anonymous)
401
+ );
402
+ }
403
+ if (typeof notification.delay === "undefined") {
404
+ return void 0;
405
+ }
406
+ if (typeof notification.delay === "number" || notification.delay instanceof Date) {
407
+ return notification.delay;
408
+ }
409
+ return notification.delay[channel];
410
+ }
411
+ function resolveRoute(channel, target) {
412
+ if (target.anonymous) {
413
+ if (!(channel in (target.routes ?? {}))) {
414
+ throw new Error(`[@holo-js/notifications] Anonymous notifications must define a route for channel "${channel}".`);
415
+ }
416
+ const route = target.routes?.[channel];
417
+ if (channel === "email") {
418
+ return normalizeEmailRouteFromValue(route);
419
+ }
420
+ if (channel === "database") {
421
+ return normalizeDatabaseRouteFromValue(route);
422
+ }
423
+ if (channel === "broadcast") {
424
+ return normalizeBroadcastRouteFromValue(route);
425
+ }
426
+ return route;
427
+ }
428
+ const registered = getNotificationChannel(channel);
429
+ if (!registered) {
430
+ throw new Error(`[@holo-js/notifications] Notification channel "${channel}" is not registered.`);
431
+ }
432
+ if ("resolveRoute" in registered && typeof registered.resolveRoute === "function") {
433
+ return registered.resolveRoute(target.notifiable);
434
+ }
435
+ if (isObject(registered) && typeof registered.validateRoute === "function") {
436
+ const routedNotifiable = isObject(target.notifiable) && typeof target.notifiable.routeNotificationFor === "function" ? target.notifiable.routeNotificationFor(channel) : void 0;
437
+ if (typeof routedNotifiable === "undefined") {
438
+ return void 0;
439
+ }
440
+ return registered.validateRoute(routedNotifiable);
441
+ }
442
+ if (isObject(target.notifiable) && typeof target.notifiable.routeNotificationFor === "function") {
443
+ return target.notifiable.routeNotificationFor(channel);
444
+ }
445
+ return void 0;
446
+ }
447
+ function resolveChannelSendContext(notification, channel, target) {
448
+ const payload = resolvePayload(notification, channel, target);
449
+ return Object.freeze({
450
+ channel,
451
+ anonymous: target.anonymous,
452
+ notifiable: target.notifiable,
453
+ route: resolveRoute(channel, target),
454
+ notificationType: notification.type,
455
+ payload,
456
+ targetIndex: target.index
457
+ });
458
+ }
459
+ function resolveChannelDispatchPlan(notification, target, channel, options) {
460
+ const notificationQueue = resolveNotificationQueueOptions(notification, target, channel);
461
+ const notificationQueueOptions = notificationQueue && notificationQueue !== true ? notificationQueue : void 0;
462
+ const config = getRuntimeBindings().config ?? holoNotificationsDefaults;
463
+ const resolvedDelay = options.delayByChannel?.[channel] ?? options.delay ?? resolveNotificationDelay(notification, target, channel);
464
+ const resolvedConnection = options.connection ?? notificationQueueOptions?.connection ?? config.queue.connection;
465
+ const resolvedQueue = options.queue ?? notificationQueueOptions?.queue ?? config.queue.queue;
466
+ const afterCommit = options.afterCommit ?? notificationQueueOptions?.afterCommit ?? config.queue.afterCommit;
467
+ const queued = notificationQueue === true || !!notificationQueueOptions || typeof resolvedDelay !== "undefined" || typeof resolvedConnection !== "undefined" || typeof resolvedQueue !== "undefined";
468
+ return Object.freeze({
469
+ channel,
470
+ queued,
471
+ connection: queued ? resolvedConnection : void 0,
472
+ queue: queued ? resolvedQueue : void 0,
473
+ delay: queued ? resolvedDelay : void 0,
474
+ afterCommit
475
+ });
476
+ }
477
+ async function deliverResolvedNotificationChannel(context) {
478
+ const definition = getNotificationChannel(context.channel);
479
+ if (!definition) {
480
+ throw new Error(`[@holo-js/notifications] Notification channel "${context.channel}" is not registered.`);
481
+ }
482
+ const routeValidated = isObject(definition) && typeof definition.validateRoute === "function" && typeof context.route !== "undefined" ? definition.validateRoute(context.route) : context.route;
483
+ const runtimeContext = typeof routeValidated === "undefined" ? context : Object.freeze({
484
+ ...context,
485
+ route: routeValidated
486
+ });
487
+ return await definition.send(runtimeContext);
488
+ }
489
+ function createQueuedDeliveryPayload(context) {
490
+ return Object.freeze({
491
+ channel: context.channel,
492
+ anonymous: context.anonymous,
493
+ notifiable: context.notifiable,
494
+ ...typeof context.route === "undefined" ? {} : { route: context.route },
495
+ ...typeof context.notificationType === "undefined" ? {} : { notificationType: context.notificationType },
496
+ payload: context.payload,
497
+ targetIndex: context.targetIndex
498
+ });
499
+ }
500
+ async function runQueuedNotificationDelivery(payload) {
501
+ return await deliverResolvedNotificationChannel(Object.freeze({
502
+ channel: payload.channel,
503
+ anonymous: payload.anonymous,
504
+ notifiable: payload.notifiable,
505
+ route: payload.route,
506
+ notificationType: payload.notificationType,
507
+ payload: payload.payload,
508
+ targetIndex: payload.targetIndex
509
+ }));
510
+ }
511
+ async function ensureNotificationsQueueJobRegistered(queueModule) {
512
+ const resolvedQueueModule = queueModule ?? await loadQueueModule();
513
+ if (resolvedQueueModule.getRegisteredQueueJob(HOLO_NOTIFICATIONS_DELIVER_JOB)) {
514
+ return resolvedQueueModule;
515
+ }
516
+ resolvedQueueModule.registerQueueJob(
517
+ resolvedQueueModule.defineJob({
518
+ async handle(payload) {
519
+ return await runQueuedNotificationDelivery(payload);
520
+ }
521
+ }),
522
+ { name: HOLO_NOTIFICATIONS_DELIVER_JOB }
523
+ );
524
+ return resolvedQueueModule;
525
+ }
526
+ async function dispatchQueuedNotificationChannel(context, plan) {
527
+ const queueModule = await ensureNotificationsQueueJobRegistered();
528
+ let pending = queueModule.dispatch(
529
+ HOLO_NOTIFICATIONS_DELIVER_JOB,
530
+ createQueuedDeliveryPayload(context)
531
+ );
532
+ if (typeof plan.connection !== "undefined") {
533
+ pending = pending.onConnection(plan.connection);
534
+ }
535
+ if (typeof plan.queue !== "undefined") {
536
+ pending = pending.onQueue(plan.queue);
537
+ }
538
+ if (typeof plan.delay !== "undefined") {
539
+ pending = pending.delay(plan.delay);
540
+ }
541
+ await pending.dispatch();
542
+ }
543
+ async function deferDispatchUntilCommit(input, targets, notification) {
544
+ const dbModule = await loadDbModule();
545
+ const active = dbModule?.connectionAsyncContext.getActive()?.connection;
546
+ if (!active || active.getScope().kind === "root") {
547
+ return null;
548
+ }
549
+ const channels = [];
550
+ for (const target of targets) {
551
+ for (const channel of resolveChannels(notification, target)) {
552
+ const plan = resolveChannelDispatchPlan(notification, target, channel, input.options);
553
+ channels.push(Object.freeze({
554
+ channel,
555
+ targetIndex: target.index,
556
+ queued: plan.queued,
557
+ deferred: true,
558
+ success: true
559
+ }));
560
+ }
561
+ }
562
+ active.afterCommit(async () => {
563
+ await dispatchNotifications(input, { allowAfterCommitDeferral: false });
564
+ });
565
+ return Object.freeze({
566
+ totalTargets: targets.length,
567
+ channels: Object.freeze(channels),
568
+ deferred: true
569
+ });
570
+ }
571
+ function shouldDeferDispatchAfterCommit(notification, targets, options) {
572
+ if (options.afterCommit) {
573
+ return true;
574
+ }
575
+ return targets.some((target) => resolveChannels(notification, target).some((channel) => {
576
+ return resolveChannelDispatchPlan(notification, target, channel, options).afterCommit;
577
+ }));
578
+ }
579
+ async function dispatchNotifications(input, execution = {}) {
580
+ const notification = normalizeNotificationDefinition(input.notification);
581
+ const targets = resolveTargets(input.target);
582
+ if (execution.allowAfterCommitDeferral !== false && shouldDeferDispatchAfterCommit(notification, targets, input.options)) {
583
+ const deferredResult = await deferDispatchUntilCommit(input, targets, notification);
584
+ if (deferredResult) {
585
+ return deferredResult;
586
+ }
587
+ }
588
+ const results = [];
589
+ for (const target of targets) {
590
+ const channels = resolveChannels(notification, target);
591
+ for (const channel of channels) {
592
+ try {
593
+ const context = resolveChannelSendContext(notification, channel, target);
594
+ const plan = resolveChannelDispatchPlan(notification, target, channel, input.options);
595
+ const result = plan.queued ? await dispatchQueuedNotificationChannel(context, plan) : await deliverResolvedNotificationChannel(context);
596
+ results.push(Object.freeze({
597
+ channel,
598
+ targetIndex: target.index,
599
+ queued: plan.queued,
600
+ success: true,
601
+ ...typeof result === "undefined" ? {} : { result }
602
+ }));
603
+ } catch (error) {
604
+ results.push(Object.freeze({
605
+ channel,
606
+ targetIndex: target.index,
607
+ queued: false,
608
+ success: false,
609
+ error
610
+ }));
611
+ }
612
+ }
613
+ }
614
+ return Object.freeze({
615
+ totalTargets: targets.length,
616
+ channels: Object.freeze(results)
617
+ });
618
+ }
619
+ var PendingDispatch = class {
620
+ constructor(target, notification, options = {}) {
621
+ this.target = target;
622
+ this.notification = notification;
623
+ this.options = options;
624
+ }
625
+ #promise;
626
+ onConnection(name) {
627
+ this.options.connection = normalizeOptionalString(name, "Notification queue connection");
628
+ return this;
629
+ }
630
+ onQueue(name) {
631
+ this.options.queue = normalizeOptionalString(name, "Notification queue name");
632
+ return this;
633
+ }
634
+ delay(value) {
635
+ this.options.delay = normalizeDelayValue(value, "Notification delay");
636
+ return this;
637
+ }
638
+ delayFor(channel, value) {
639
+ const normalizedChannel = normalizeOptionalString(channel, "Notification channel");
640
+ this.options.delayByChannel ??= {};
641
+ this.options.delayByChannel[normalizedChannel] = normalizeDelayValue(value, `Notification delay for channel "${normalizedChannel}"`);
642
+ return this;
643
+ }
644
+ afterCommit() {
645
+ this.options.afterCommit = true;
646
+ return this;
647
+ }
648
+ then(onfulfilled, onrejected) {
649
+ return this.#execute().then(onfulfilled, onrejected);
650
+ }
651
+ catch(onrejected) {
652
+ return this.#execute().catch(onrejected);
653
+ }
654
+ finally(onfinally) {
655
+ return this.#execute().finally(onfinally ?? void 0);
656
+ }
657
+ #execute() {
658
+ if (!this.#promise) {
659
+ try {
660
+ this.#promise = getDispatchHandler()({
661
+ target: typeof this.target === "function" ? this.target() : this.target,
662
+ notification: this.notification,
663
+ options: Object.freeze({
664
+ connection: this.options.connection,
665
+ queue: this.options.queue,
666
+ delay: this.options.delay,
667
+ delayByChannel: this.options.delayByChannel ? Object.freeze({ ...this.options.delayByChannel }) : void 0,
668
+ afterCommit: this.options.afterCommit
669
+ })
670
+ });
671
+ } catch (error) {
672
+ this.#promise = Promise.reject(error);
673
+ }
674
+ }
675
+ return this.#promise;
676
+ }
677
+ };
678
+ var AnonymousNotificationBuilder = class _AnonymousNotificationBuilder {
679
+ target;
680
+ constructor(routes = {}) {
681
+ this.target = createAnonymousNotificationTarget(routes);
682
+ }
683
+ channel(channel, route) {
684
+ const normalizedChannel = normalizeOptionalString(channel, "Notification channel");
685
+ return new _AnonymousNotificationBuilder({
686
+ ...this.target.routes,
687
+ [normalizedChannel]: route
688
+ });
689
+ }
690
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- must accept any NotificationDefinition variant
691
+ notify(notification) {
692
+ return new PendingDispatch({
693
+ kind: "anonymous",
694
+ value: this.target
695
+ }, notification);
696
+ }
697
+ };
698
+ function configureNotificationsRuntime(bindings) {
699
+ getRuntimeState().bindings = bindings;
700
+ }
701
+ function getNotificationsRuntimeBindings() {
702
+ return getRuntimeBindings();
703
+ }
704
+ function resetNotificationsRuntime() {
705
+ const state = getRuntimeState();
706
+ state.bindings = void 0;
707
+ state.loadQueueModule = void 0;
708
+ state.loadDbModule = void 0;
709
+ }
710
+ function notify(notifiable, notification) {
711
+ return new PendingDispatch({
712
+ kind: "notifiable",
713
+ value: notifiable
714
+ }, notification);
715
+ }
716
+ function notifyMany(notifiables, notification) {
717
+ return new PendingDispatch(() => ({
718
+ kind: "many",
719
+ value: Object.freeze([...notifiables])
720
+ }), notification);
721
+ }
722
+ function notifyUsing() {
723
+ return new AnonymousNotificationBuilder();
724
+ }
725
+ async function listNotifications(notifiable) {
726
+ return requireStore(getRuntimeBindings()).list(resolveDatabaseRouteFromNotifiable(notifiable));
727
+ }
728
+ async function unreadNotifications(notifiable) {
729
+ return requireStore(getRuntimeBindings()).unread(resolveDatabaseRouteFromNotifiable(notifiable));
730
+ }
731
+ async function markNotificationsAsRead(ids) {
732
+ return requireStore(getRuntimeBindings()).markAsRead(normalizeNotificationRecordIds(ids));
733
+ }
734
+ async function markNotificationsAsUnread(ids) {
735
+ return requireStore(getRuntimeBindings()).markAsUnread(normalizeNotificationRecordIds(ids));
736
+ }
737
+ async function deleteNotifications(ids) {
738
+ return requireStore(getRuntimeBindings()).delete(normalizeNotificationRecordIds(ids));
739
+ }
740
+ function getNotificationsRuntime() {
741
+ return Object.freeze({
742
+ notify,
743
+ notifyMany,
744
+ notifyUsing,
745
+ listNotifications,
746
+ unreadNotifications,
747
+ markNotificationsAsRead,
748
+ markNotificationsAsUnread,
749
+ deleteNotifications
750
+ });
751
+ }
752
+ var notificationsRuntimeInternals = {
753
+ HOLO_NOTIFICATIONS_DELIVER_JOB,
754
+ AnonymousNotificationBuilder,
755
+ PendingDispatch,
756
+ builtInChannels,
757
+ createBuildContext,
758
+ createNotificationContext,
759
+ createQueuedDeliveryPayload,
760
+ deferDispatchUntilCommit,
761
+ deliverResolvedNotificationChannel,
762
+ dispatchNotifications,
763
+ dispatchQueuedNotificationChannel,
764
+ ensureNotificationsQueueJobRegistered,
765
+ getDispatchHandler,
766
+ getRuntimeBindings,
767
+ getRuntimeState,
768
+ getNotificationChannel,
769
+ isAnonymousTarget,
770
+ isObject,
771
+ loadDbModule,
772
+ loadQueueModule,
773
+ normalizeBroadcastRouteFromValue,
774
+ normalizeDatabaseRouteFromValue,
775
+ normalizeDelayValue,
776
+ normalizeEmailRouteFromValue,
777
+ normalizeNotificationRecord,
778
+ normalizeNotificationRecordIds,
779
+ normalizeOptionalString,
780
+ resolveBroadcastRouteFromNotifiable,
781
+ resolveChannelDispatchPlan,
782
+ resolveChannels,
783
+ resolveDatabaseRouteFromNotifiable,
784
+ resolveEmailRouteFromNotifiable,
785
+ resolveNotificationDelay,
786
+ resolveNotificationQueueOptions,
787
+ resolvePayload,
788
+ resolveRoute,
789
+ runQueuedNotificationDelivery,
790
+ resolveTargets,
791
+ setDbModuleLoader(loader) {
792
+ getRuntimeState().loadDbModule = loader;
793
+ },
794
+ setQueueModuleLoader(loader) {
795
+ getRuntimeState().loadQueueModule = loader;
796
+ }
797
+ };
798
+
799
+ // src/index.ts
800
+ import {
801
+ defineNotificationsConfig as defineBaseNotificationsConfig
802
+ } from "@holo-js/config";
803
+ function defineNotificationsConfig(config) {
804
+ return defineBaseNotificationsConfig(config);
805
+ }
806
+ var notifications = Object.freeze({
807
+ deleteNotifications,
808
+ listNotifications,
809
+ markNotificationsAsRead,
810
+ markNotificationsAsUnread,
811
+ notify,
812
+ notifyMany,
813
+ notifyUsing,
814
+ unreadNotifications
815
+ });
816
+ var src_default = notifications;
817
+ export {
818
+ configureNotificationsRuntime,
819
+ src_default as default,
820
+ defineNotification,
821
+ defineNotificationsConfig,
822
+ deleteNotifications,
823
+ getNotificationsRuntime,
824
+ getNotificationsRuntimeBindings,
825
+ getRegisteredNotificationChannel,
826
+ isNotificationDefinition,
827
+ listNotifications,
828
+ listRegisteredNotificationChannels,
829
+ markNotificationsAsRead,
830
+ markNotificationsAsUnread,
831
+ normalizeNotificationDefinition,
832
+ notificationRegistryInternals,
833
+ notificationsInternals,
834
+ notificationsRuntimeInternals,
835
+ notify,
836
+ notifyMany,
837
+ notifyUsing,
838
+ registerNotificationChannel,
839
+ resetNotificationChannelRegistry,
840
+ resetNotificationsRuntime,
841
+ unreadNotifications
842
+ };