@holo-js/notifications 0.2.5 → 0.3.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.
@@ -1,5 +1,4 @@
1
1
  // src/contracts.ts
2
- import { defineNotificationsConfig } from "@holo-js/config";
3
2
  var HOLO_NOTIFICATION_DEFINITION_MARKER = /* @__PURE__ */ Symbol.for("holo-js.notifications.definition");
4
3
  var BUILT_IN_NOTIFICATION_CHANNELS = ["email", "database", "broadcast"];
5
4
  function isObject(value) {
@@ -117,7 +116,6 @@ var notificationsInternals = {
117
116
  };
118
117
 
119
118
  export {
120
- defineNotificationsConfig,
121
119
  isNotificationDefinition,
122
120
  normalizeNotificationDefinition,
123
121
  defineNotification,
@@ -0,0 +1,45 @@
1
+ // src/config.ts
2
+ import { registerConfigNormalizer } from "@holo-js/config/registry";
3
+ var DEFAULT_NOTIFICATIONS_TABLE = "notifications";
4
+ var holoNotificationsDefaults = Object.freeze({
5
+ table: DEFAULT_NOTIFICATIONS_TABLE,
6
+ queue: Object.freeze({
7
+ connection: void 0,
8
+ queue: void 0,
9
+ afterCommit: false
10
+ })
11
+ });
12
+ function normalizeOptionalString(value, label) {
13
+ if (typeof value === "undefined") {
14
+ return void 0;
15
+ }
16
+ const normalized = value.trim();
17
+ if (!normalized) {
18
+ throw new Error(`[@holo-js/notifications] ${label} must be a non-empty string when provided.`);
19
+ }
20
+ return normalized;
21
+ }
22
+ function defineNotificationsConfig(config) {
23
+ return Object.freeze({ ...config });
24
+ }
25
+ function normalizeNotificationsConfig(config = {}) {
26
+ return Object.freeze({
27
+ table: normalizeOptionalString(config.table, "Notifications table") ?? DEFAULT_NOTIFICATIONS_TABLE,
28
+ queue: Object.freeze({
29
+ connection: normalizeOptionalString(config.queue?.connection, "Notifications queue connection"),
30
+ queue: normalizeOptionalString(config.queue?.queue, "Notifications queue name"),
31
+ afterCommit: config.queue?.afterCommit === true
32
+ })
33
+ });
34
+ }
35
+ registerConfigNormalizer({
36
+ name: "notifications",
37
+ normalize: normalizeNotificationsConfig
38
+ });
39
+
40
+ export {
41
+ DEFAULT_NOTIFICATIONS_TABLE,
42
+ holoNotificationsDefaults,
43
+ defineNotificationsConfig,
44
+ normalizeNotificationsConfig
45
+ };
@@ -0,0 +1,27 @@
1
+ interface HoloNotificationsConfig {
2
+ readonly table?: string;
3
+ readonly queue?: {
4
+ readonly connection?: string;
5
+ readonly queue?: string;
6
+ readonly afterCommit?: boolean;
7
+ };
8
+ }
9
+ declare module '@holo-js/config' {
10
+ interface HoloConfigRegistry {
11
+ notifications: NormalizedHoloNotificationsConfig;
12
+ }
13
+ }
14
+ interface NormalizedHoloNotificationsConfig {
15
+ readonly table: string;
16
+ readonly queue: {
17
+ readonly connection?: string;
18
+ readonly queue?: string;
19
+ readonly afterCommit: boolean;
20
+ };
21
+ }
22
+ declare const DEFAULT_NOTIFICATIONS_TABLE = "notifications";
23
+ declare const holoNotificationsDefaults: Readonly<NormalizedHoloNotificationsConfig>;
24
+ declare function defineNotificationsConfig<const TConfig extends HoloNotificationsConfig>(config: TConfig): Readonly<TConfig>;
25
+ declare function normalizeNotificationsConfig(config?: HoloNotificationsConfig): NormalizedHoloNotificationsConfig;
26
+
27
+ export { DEFAULT_NOTIFICATIONS_TABLE, type HoloNotificationsConfig, type NormalizedHoloNotificationsConfig, defineNotificationsConfig, holoNotificationsDefaults, normalizeNotificationsConfig };
@@ -0,0 +1,12 @@
1
+ import {
2
+ DEFAULT_NOTIFICATIONS_TABLE,
3
+ defineNotificationsConfig,
4
+ holoNotificationsDefaults,
5
+ normalizeNotificationsConfig
6
+ } from "./chunk-ZZH7Q6TC.mjs";
7
+ export {
8
+ DEFAULT_NOTIFICATIONS_TABLE,
9
+ defineNotificationsConfig,
10
+ holoNotificationsDefaults,
11
+ normalizeNotificationsConfig
12
+ };
@@ -1,5 +1,4 @@
1
- import { NormalizedHoloNotificationsConfig } from '@holo-js/config';
2
- export { defineNotificationsConfig } from '@holo-js/config';
1
+ import { NormalizedHoloNotificationsConfig } from './config.js';
3
2
 
4
3
  type NotificationJsonPrimitive = string | number | boolean | null;
5
4
  type NotificationJsonValue = NotificationJsonPrimitive | readonly NotificationJsonValue[] | {
@@ -179,6 +178,7 @@ interface NotificationDispatchInput<TNotification extends NotificationDefinition
179
178
  }
180
179
  interface NotificationRuntimeBindings {
181
180
  readonly config?: NormalizedHoloNotificationsConfig;
181
+ readonly deferAfterCommit?: (callback: () => Promise<void>) => boolean;
182
182
  readonly mailer?: NotificationMailSender;
183
183
  readonly broadcaster?: NotificationBroadcaster;
184
184
  readonly store?: NotificationStore;
@@ -1,15 +1,13 @@
1
1
  import {
2
2
  createAnonymousNotificationTarget,
3
3
  defineNotification,
4
- defineNotificationsConfig,
5
4
  isNotificationDefinition,
6
5
  normalizeNotificationDefinition,
7
6
  notificationsInternals
8
- } from "./chunk-INGM2JYY.mjs";
7
+ } from "./chunk-4KUFKBEW.mjs";
9
8
  export {
10
9
  createAnonymousNotificationTarget,
11
10
  defineNotification,
12
- defineNotificationsConfig,
13
11
  isNotificationDefinition,
14
12
  normalizeNotificationDefinition,
15
13
  notificationsInternals
package/dist/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
- import { NotificationChannel, NotificationBuildContext, AnonymousNotificationTarget, NotificationBroadcastRoute, NotificationDatabaseRoute, NotificationEmailRoute, NotificationDatabaseMessage, NotificationRecord, NotificationRuntimeBindings, InferNotificationNotifiable, NotificationDefinition, PendingNotificationDispatch, NotificationDispatchResult, PendingAnonymousNotification, NotificationChannelName, NotificationRouteFor, NotificationDispatchTarget, NotificationDelayValue, NotificationSendContext, NotificationDispatchInput, NotificationDispatchOptions, NotificationQueueOptions, RegisteredNotificationChannel, RegisterNotificationChannelOptions } from './contracts.js';
1
+ import { NotificationDelayValue, NotificationChannel, NotificationBuildContext, AnonymousNotificationTarget, NotificationBroadcastRoute, NotificationDatabaseRoute, NotificationEmailRoute, NotificationDatabaseMessage, NotificationRecord, NotificationRuntimeBindings, InferNotificationNotifiable, NotificationDefinition, PendingNotificationDispatch, NotificationDispatchResult, PendingAnonymousNotification, NotificationChannelName, NotificationRouteFor, NotificationDispatchTarget, NotificationSendContext, NotificationDispatchInput, NotificationDispatchOptions, NotificationQueueOptions, RegisteredNotificationChannel, RegisterNotificationChannelOptions } from './contracts.js';
2
2
  export { BuiltInNotificationChannelRegistry, HoloNotificationChannelRegistry, InferNotificationChannels, NotificationBroadcastMessage, NotificationBroadcaster, NotificationBuildFactories, NotificationChannelDispatchResult, NotificationContext, NotificationDelayResolver, NotificationJsonValue, NotificationMailMessage, NotificationMailSender, NotificationPayloadFor, NotificationQueueResolver, NotificationResultFor, NotificationStore, defineNotification, isNotificationDefinition, normalizeNotificationDefinition, notificationsInternals } from './contracts.js';
3
- import { HoloNotificationsConfig } from '@holo-js/config';
4
- export { HoloNotificationsConfig, NormalizedHoloNotificationsConfig } from '@holo-js/config';
3
+ export { HoloNotificationsConfig, NormalizedHoloNotificationsConfig, defineNotificationsConfig, holoNotificationsDefaults, normalizeNotificationsConfig } from './config.js';
4
+
5
+ declare function normalizeOptionalNotificationString(value: string, label: string): string;
6
+ declare function normalizeOptionalNotificationString(value: string | undefined, label: string): string | undefined;
7
+ declare function normalizeNotificationDelay(value: NotificationDelayValue, label: string): NotificationDelayValue;
5
8
 
6
9
  type RouteResolver = (notifiable: unknown) => unknown;
7
10
  type BuiltInChannelDefinition = NotificationChannel & {
@@ -22,19 +25,20 @@ declare function resolveBroadcastRouteFromNotifiable(notifiable: unknown): Notif
22
25
  declare function normalizeNotificationRecord(route: NotificationDatabaseRoute, payload: NotificationDatabaseMessage, notificationType: string | undefined): NotificationRecord;
23
26
  declare function normalizeNotificationRecordIds(ids: readonly string[]): readonly string[];
24
27
 
25
- declare function normalizeOptionalString(value: string, label: string): string;
26
- declare function normalizeOptionalString(value: string | undefined, label: string): string | undefined;
27
- declare function normalizeDelayValue(value: NotificationDelayValue, label: string): NotificationDelayValue;
28
28
  declare function getRuntimeBindings(): NotificationRuntimeBindings;
29
29
  type RuntimeState = {
30
30
  bindings?: NotificationRuntimeBindings;
31
+ projectRoot?: string;
32
+ pluginNames?: readonly string[];
31
33
  loadQueueModule?: () => Promise<QueueModule>;
32
- loadDbModule?: () => Promise<DbModule | null>;
34
+ };
35
+ type ResolvedTargetChannels = {
36
+ readonly target: ResolvedTarget;
37
+ readonly channels: readonly string[];
33
38
  };
34
39
  declare function getRuntimeState(): RuntimeState;
35
40
  declare function getDispatchHandler(): typeof dispatchNotifications;
36
41
  declare function loadQueueModule(): Promise<QueueModule>;
37
- declare function loadDbModule(): Promise<DbModule | null>;
38
42
  type MutableDispatchOptions = {
39
43
  connection?: string;
40
44
  queue?: string;
@@ -76,18 +80,6 @@ type QueueModule = {
76
80
  name: string;
77
81
  }): void;
78
82
  };
79
- type DbModule = {
80
- connectionAsyncContext: {
81
- getActive(): {
82
- connection: {
83
- getScope(): {
84
- kind: string;
85
- };
86
- afterCommit(callback: () => Promise<void>): void;
87
- };
88
- } | undefined;
89
- };
90
- };
91
83
  type QueuedNotificationDeliveryPayload = Readonly<{
92
84
  readonly channel: string;
93
85
  readonly anonymous: boolean;
@@ -110,7 +102,7 @@ declare function createQueuedDeliveryPayload(context: NotificationSendContext):
110
102
  declare function runQueuedNotificationDelivery(payload: QueuedNotificationDeliveryPayload): Promise<unknown>;
111
103
  declare function ensureNotificationsQueueJobRegistered(queueModule?: QueueModule): Promise<QueueModule>;
112
104
  declare function dispatchQueuedNotificationChannel(context: NotificationSendContext, plan: ResolvedChannelPlan): Promise<void>;
113
- declare function deferDispatchUntilCommit(input: NotificationDispatchInput, targets: readonly ResolvedTarget[], notification: NotificationDefinition): Promise<NotificationDispatchResult | null>;
105
+ declare function deferDispatchUntilCommit(input: NotificationDispatchInput, notification: NotificationDefinition, targetChannels: readonly ResolvedTargetChannels[]): Promise<NotificationDispatchResult | null>;
114
106
  declare function dispatchNotifications(input: NotificationDispatchInput, execution?: DispatchExecutionOptions): Promise<NotificationDispatchResult>;
115
107
  declare class PendingDispatch<TResult = NotificationDispatchResult> implements PendingNotificationDispatch<TResult> {
116
108
  #private;
@@ -164,7 +156,7 @@ declare const notificationsRuntimeInternals: {
164
156
  HOLO_NOTIFICATIONS_DELIVER_JOB: string;
165
157
  AnonymousNotificationBuilder: typeof AnonymousNotificationBuilder;
166
158
  PendingDispatch: typeof PendingDispatch;
167
- builtInChannels: Readonly<Record<"email" | "database" | "broadcast", BuiltInChannelDefinition>>;
159
+ builtInChannels: Readonly<Record<"database" | "email" | "broadcast", BuiltInChannelDefinition>>;
168
160
  createBuildContext: typeof createBuildContext;
169
161
  createNotificationContext: typeof createNotificationContext;
170
162
  createQueuedDeliveryPayload: typeof createQueuedDeliveryPayload;
@@ -179,15 +171,14 @@ declare const notificationsRuntimeInternals: {
179
171
  getNotificationChannel: typeof getNotificationChannel;
180
172
  isAnonymousTarget: typeof isAnonymousTarget;
181
173
  isObject: typeof isObject;
182
- loadDbModule: typeof loadDbModule;
183
174
  loadQueueModule: typeof loadQueueModule;
184
175
  normalizeBroadcastRouteFromValue: typeof normalizeBroadcastRouteFromValue;
185
176
  normalizeDatabaseRouteFromValue: typeof normalizeDatabaseRouteFromValue;
186
- normalizeDelayValue: typeof normalizeDelayValue;
177
+ normalizeDelayValue: typeof normalizeNotificationDelay;
187
178
  normalizeEmailRouteFromValue: typeof normalizeEmailRouteFromValue;
188
179
  normalizeNotificationRecord: typeof normalizeNotificationRecord;
189
180
  normalizeNotificationRecordIds: typeof normalizeNotificationRecordIds;
190
- normalizeOptionalString: typeof normalizeOptionalString;
181
+ normalizeOptionalString: typeof normalizeOptionalNotificationString;
191
182
  resolveBroadcastRouteFromNotifiable: typeof resolveBroadcastRouteFromNotifiable;
192
183
  resolveChannelDispatchPlan: typeof resolveChannelDispatchPlan;
193
184
  resolveChannels: typeof resolveChannels;
@@ -199,7 +190,6 @@ declare const notificationsRuntimeInternals: {
199
190
  resolveRoute: typeof resolveRoute;
200
191
  runQueuedNotificationDelivery: typeof runQueuedNotificationDelivery;
201
192
  resolveTargets: typeof resolveTargets;
202
- setDbModuleLoader(loader: (() => Promise<DbModule | null>) | undefined): void;
203
193
  setQueueModuleLoader(loader: (() => Promise<QueueModule>) | undefined): void;
204
194
  };
205
195
 
@@ -214,7 +204,9 @@ declare const notificationRegistryInternals: {
214
204
  normalizeChannelName: typeof normalizeChannelName;
215
205
  };
216
206
 
217
- declare function defineNotificationsConfig<const TConfig extends HoloNotificationsConfig>(config: TConfig): Readonly<TConfig>;
207
+ declare function loadNotificationPluginChannels(projectRoot?: string, pluginNames?: readonly string[]): Promise<void>;
208
+ declare function resetNotificationPluginChannels(): void;
209
+
218
210
  declare const notifications: Readonly<{
219
211
  deleteNotifications: typeof deleteNotifications;
220
212
  listNotifications: typeof listNotifications;
@@ -226,4 +218,4 @@ declare const notifications: Readonly<{
226
218
  unreadNotifications: typeof unreadNotifications;
227
219
  }>;
228
220
 
229
- export { AnonymousNotificationTarget, InferNotificationNotifiable, NotificationBroadcastRoute, NotificationBuildContext, NotificationChannel, NotificationChannelName, NotificationDatabaseMessage, NotificationDatabaseRoute, NotificationDefinition, NotificationDelayValue, NotificationDispatchInput, NotificationDispatchOptions, NotificationDispatchResult, NotificationDispatchTarget, NotificationEmailRoute, NotificationQueueOptions, NotificationRecord, NotificationRouteFor, NotificationRuntimeBindings, NotificationSendContext, PendingAnonymousNotification, PendingNotificationDispatch, RegisterNotificationChannelOptions, RegisteredNotificationChannel, configureNotificationsRuntime, notifications as default, defineNotificationsConfig, deleteNotifications, getNotificationsRuntime, getNotificationsRuntimeBindings, getRegisteredNotificationChannel, listNotifications, listRegisteredNotificationChannels, markNotificationsAsRead, markNotificationsAsUnread, notificationRegistryInternals, notificationsRuntimeInternals, notify, notifyMany, notifyUsing, registerNotificationChannel, resetNotificationChannelRegistry, resetNotificationsRuntime, unreadNotifications };
221
+ export { AnonymousNotificationTarget, InferNotificationNotifiable, NotificationBroadcastRoute, NotificationBuildContext, NotificationChannel, NotificationChannelName, NotificationDatabaseMessage, NotificationDatabaseRoute, NotificationDefinition, NotificationDelayValue, NotificationDispatchInput, NotificationDispatchOptions, NotificationDispatchResult, NotificationDispatchTarget, NotificationEmailRoute, NotificationQueueOptions, NotificationRecord, NotificationRouteFor, NotificationRuntimeBindings, NotificationSendContext, PendingAnonymousNotification, PendingNotificationDispatch, RegisterNotificationChannelOptions, RegisteredNotificationChannel, configureNotificationsRuntime, notifications as default, deleteNotifications, getNotificationsRuntime, getNotificationsRuntimeBindings, getRegisteredNotificationChannel, listNotifications, listRegisteredNotificationChannels, loadNotificationPluginChannels, markNotificationsAsRead, markNotificationsAsUnread, notificationRegistryInternals, notificationsRuntimeInternals, notify, notifyMany, notifyUsing, registerNotificationChannel, resetNotificationChannelRegistry, resetNotificationPluginChannels, resetNotificationsRuntime, unreadNotifications };
package/dist/index.mjs CHANGED
@@ -1,13 +1,15 @@
1
+ import {
2
+ defineNotificationsConfig,
3
+ holoNotificationsDefaults,
4
+ normalizeNotificationsConfig
5
+ } from "./chunk-ZZH7Q6TC.mjs";
1
6
  import {
2
7
  createAnonymousNotificationTarget,
3
8
  defineNotification,
4
9
  isNotificationDefinition,
5
10
  normalizeNotificationDefinition,
6
11
  notificationsInternals
7
- } from "./chunk-INGM2JYY.mjs";
8
-
9
- // src/runtime.ts
10
- import { holoNotificationsDefaults } from "@holo-js/config";
12
+ } from "./chunk-4KUFKBEW.mjs";
11
13
 
12
14
  // src/registry.ts
13
15
  function normalizeChannelName(value) {
@@ -39,6 +41,9 @@ function registerNotificationChannel(name, channel, options = {}) {
39
41
  function getRegisteredNotificationChannel(name) {
40
42
  return getRegistry().get(normalizeChannelName(name));
41
43
  }
44
+ function unregisterNotificationChannel(name) {
45
+ return getRegistry().delete(normalizeChannelName(name));
46
+ }
42
47
  function listRegisteredNotificationChannels() {
43
48
  return Object.freeze([...getRegistry().values()]);
44
49
  }
@@ -50,6 +55,105 @@ var notificationRegistryInternals = {
50
55
  normalizeChannelName
51
56
  };
52
57
 
58
+ // src/plugins.ts
59
+ import {
60
+ loadHoloPluginContributionModules,
61
+ loadHoloPluginDefinitions
62
+ } from "@holo-js/kernel";
63
+ var loadedProjectRoots = /* @__PURE__ */ new Set();
64
+ var registeredPluginChannels = /* @__PURE__ */ new Map();
65
+ function isRecord(value) {
66
+ return !!value && typeof value === "object" && !Array.isArray(value);
67
+ }
68
+ function resolveNotificationChannel(moduleValue, packageName, channelName) {
69
+ const candidate = isRecord(moduleValue) && typeof moduleValue.default !== "undefined" ? moduleValue.default : isRecord(moduleValue) && typeof moduleValue.channel !== "undefined" ? moduleValue.channel : moduleValue;
70
+ if (!isRecord(candidate) || typeof candidate.send !== "function") {
71
+ throw new Error(`[@holo-js/notifications] Plugin ${packageName} notification channel "${channelName}" must export send().`);
72
+ }
73
+ return {
74
+ send: candidate.send,
75
+ ...typeof candidate.validateRoute === "function" ? { validateRoute: candidate.validateRoute } : {}
76
+ };
77
+ }
78
+ async function loadNotificationPluginChannels(projectRoot = process.cwd(), pluginNames = []) {
79
+ if (loadedProjectRoots.has(projectRoot)) {
80
+ return;
81
+ }
82
+ const plugins = await loadHoloPluginDefinitions(projectRoot, pluginNames);
83
+ const contributions = await loadHoloPluginContributionModules(
84
+ projectRoot,
85
+ plugins,
86
+ "notifications",
87
+ "channels"
88
+ );
89
+ for (const contribution of contributions) {
90
+ const previous = getRegisteredNotificationChannel(contribution.name);
91
+ const channel = resolveNotificationChannel(contribution.module, contribution.plugin.packageName, contribution.name);
92
+ registerNotificationChannel(
93
+ contribution.name,
94
+ channel,
95
+ { replaceExisting: true }
96
+ );
97
+ const registered = getRegisteredNotificationChannel(contribution.name);
98
+ if (registered) {
99
+ const existingPluginChannel = registeredPluginChannels.get(registered.name);
100
+ registeredPluginChannels.set(registered.name, {
101
+ channel,
102
+ previous: existingPluginChannel ? existingPluginChannel.previous : previous?.channel
103
+ });
104
+ }
105
+ }
106
+ loadedProjectRoots.add(projectRoot);
107
+ }
108
+ function resetNotificationPluginChannels() {
109
+ for (const [channelName, registered] of registeredPluginChannels) {
110
+ if (getRegisteredNotificationChannel(channelName)?.channel !== registered.channel) {
111
+ continue;
112
+ }
113
+ unregisterNotificationChannel(channelName);
114
+ if (registered.previous) {
115
+ registerNotificationChannel(channelName, registered.previous, { replaceExisting: true });
116
+ }
117
+ }
118
+ loadedProjectRoots.clear();
119
+ registeredPluginChannels.clear();
120
+ }
121
+
122
+ // src/queueOptions.ts
123
+ function normalizeOptionalNotificationString(value, label) {
124
+ if (typeof value === "undefined") return void 0;
125
+ const normalized = value.trim();
126
+ if (!normalized) throw new Error(`[@holo-js/notifications] ${label} must be a non-empty string.`);
127
+ return normalized;
128
+ }
129
+ function normalizeNotificationDelay(value, label) {
130
+ if (typeof value === "number") {
131
+ if (!Number.isFinite(value) || value < 0) {
132
+ throw new Error(`[@holo-js/notifications] ${label} must be a finite number greater than or equal to 0.`);
133
+ }
134
+ return value;
135
+ }
136
+ if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
137
+ throw new Error(`[@holo-js/notifications] ${label} dates must be valid Date instances.`);
138
+ }
139
+ return value;
140
+ }
141
+ function normalizeOptionalBoolean(value, label) {
142
+ if (typeof value === "undefined") return void 0;
143
+ if (typeof value !== "boolean") throw new Error(`[@holo-js/notifications] ${label} must be a boolean.`);
144
+ return value;
145
+ }
146
+ function normalizeNotificationQueueOptions(value) {
147
+ if (typeof value === "undefined") return void 0;
148
+ const afterCommit = normalizeOptionalBoolean(value.afterCommit, "Notification queue afterCommit");
149
+ return Object.freeze({
150
+ connection: normalizeOptionalNotificationString(value.connection, "Notification queue connection"),
151
+ queue: normalizeOptionalNotificationString(value.queue, "Notification queue name"),
152
+ ...typeof value.delay === "undefined" ? {} : { delay: normalizeNotificationDelay(value.delay, "Notification queue delay") },
153
+ ...typeof afterCommit === "undefined" ? {} : { afterCommit }
154
+ });
155
+ }
156
+
53
157
  // src/runtime-channels.ts
54
158
  import { randomUUID } from "crypto";
55
159
  function isObject(value) {
@@ -250,48 +354,9 @@ function createBuiltInChannels(getBindings) {
250
354
 
251
355
  // src/runtime.ts
252
356
  var HOLO_NOTIFICATIONS_DELIVER_JOB = "holo.notifications.deliver";
253
- function normalizeOptionalString(value, label) {
254
- if (typeof value === "undefined") {
255
- return void 0;
256
- }
257
- const normalized = value.trim();
258
- if (!normalized) {
259
- throw new Error(`[@holo-js/notifications] ${label} must be a non-empty string.`);
260
- }
261
- return normalized;
262
- }
263
- function normalizeDelayValue(value, label) {
264
- if (typeof value === "number") {
265
- if (!Number.isFinite(value) || value < 0) {
266
- throw new Error(`[@holo-js/notifications] ${label} must be a finite number greater than or equal to 0.`);
267
- }
268
- return value;
269
- }
270
- if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
271
- throw new Error(`[@holo-js/notifications] ${label} dates must be valid Date instances.`);
272
- }
273
- return value;
274
- }
275
- function normalizeOptionalBoolean(value, label) {
276
- if (typeof value === "undefined") {
277
- return void 0;
278
- }
279
- if (typeof value !== "boolean") {
280
- throw new Error(`[@holo-js/notifications] ${label} must be a boolean.`);
281
- }
282
- return value;
283
- }
284
- function normalizeQueueOptions(value) {
285
- if (typeof value === "undefined") {
286
- return void 0;
287
- }
288
- return Object.freeze({
289
- connection: normalizeOptionalString(value.connection, "Notification queue connection"),
290
- queue: normalizeOptionalString(value.queue, "Notification queue name"),
291
- ...typeof value.delay === "undefined" ? {} : { delay: normalizeDelayValue(value.delay, "Notification queue delay") },
292
- ...typeof normalizeOptionalBoolean(value.afterCommit, "Notification queue afterCommit") === "undefined" ? {} : { afterCommit: value.afterCommit }
293
- });
294
- }
357
+ var normalizeOptionalString = normalizeOptionalNotificationString;
358
+ var normalizeDelayValue = normalizeNotificationDelay;
359
+ var normalizeQueueOptions = normalizeNotificationQueueOptions;
295
360
  function getRuntimeBindings() {
296
361
  return getRuntimeState().bindings ?? {};
297
362
  }
@@ -328,27 +393,6 @@ async function loadQueueModule() {
328
393
  throw error;
329
394
  }
330
395
  }
331
- async function loadDbModule() {
332
- const override = getRuntimeState().loadDbModule;
333
- if (override) {
334
- try {
335
- return await override();
336
- } catch (error) {
337
- if (error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND") {
338
- return null;
339
- }
340
- throw error;
341
- }
342
- }
343
- try {
344
- return await dynamicImport("@holo-js/db");
345
- } catch (error) {
346
- if (error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND") {
347
- return null;
348
- }
349
- throw error;
350
- }
351
- }
352
396
  var builtInChannels = createBuiltInChannels(getRuntimeBindings);
353
397
  function getNotificationChannel(name) {
354
398
  const registered = getRegisteredNotificationChannel(name)?.channel;
@@ -389,7 +433,15 @@ function resolveTargets(target) {
389
433
  notifiable: target.value
390
434
  })]);
391
435
  }
392
- function resolveChannels(notification, target) {
436
+ function resolveRegisteredChannels(channels) {
437
+ return Object.freeze(channels.map((channel) => {
438
+ if (!getNotificationChannel(channel)) {
439
+ throw new Error(`[@holo-js/notifications] Notification channel "${channel}" is not registered.`);
440
+ }
441
+ return channel;
442
+ }));
443
+ }
444
+ function resolveDeclaredChannels(notification, target) {
393
445
  const channels = notification.via(target.notifiable, createNotificationContext(target.anonymous));
394
446
  if (!Array.isArray(channels)) {
395
447
  throw new Error("[@holo-js/notifications] Notification via() must return an array of channel names.");
@@ -398,13 +450,18 @@ function resolveChannels(notification, target) {
398
450
  if (typeof channel !== "string") {
399
451
  throw new Error(`[@holo-js/notifications] Notification channel at index ${index} must be a string.`);
400
452
  }
401
- const normalized = normalizeOptionalString(channel, "Notification channel");
402
- if (!getNotificationChannel(normalized)) {
403
- throw new Error(`[@holo-js/notifications] Notification channel "${normalized}" is not registered.`);
404
- }
405
- return normalized;
453
+ return normalizeOptionalString(channel, "Notification channel");
406
454
  }));
407
455
  }
456
+ function resolveChannels(notification, target) {
457
+ return resolveRegisteredChannels(resolveDeclaredChannels(notification, target));
458
+ }
459
+ function resolveTargetChannels(notification, targets) {
460
+ return Object.freeze(targets.map((target) => Object.freeze({
461
+ target,
462
+ channels: resolveDeclaredChannels(notification, target)
463
+ })));
464
+ }
408
465
  function resolvePayload(notification, channel, target) {
409
466
  const factory = notification.build[channel];
410
467
  if (typeof factory !== "function") {
@@ -536,6 +593,10 @@ function createQueuedDeliveryPayload(context) {
536
593
  });
537
594
  }
538
595
  async function runQueuedNotificationDelivery(payload) {
596
+ if (!getNotificationChannel(payload.channel)) {
597
+ const state = getRuntimeState();
598
+ await loadNotificationPluginChannels(state.projectRoot, state.pluginNames);
599
+ }
539
600
  return await deliverResolvedNotificationChannel(Object.freeze({
540
601
  channel: payload.channel,
541
602
  anonymous: payload.anonymous,
@@ -578,15 +639,14 @@ async function dispatchQueuedNotificationChannel(context, plan) {
578
639
  }
579
640
  await pending.dispatch();
580
641
  }
581
- async function deferDispatchUntilCommit(input, targets, notification) {
582
- const dbModule = await loadDbModule();
583
- const active = dbModule?.connectionAsyncContext.getActive()?.connection;
584
- if (!active || active.getScope().kind === "root") {
642
+ async function deferDispatchUntilCommit(input, notification, targetChannels) {
643
+ const deferAfterCommit = getRuntimeBindings().deferAfterCommit;
644
+ if (!deferAfterCommit) {
585
645
  return null;
586
646
  }
587
647
  const channels = [];
588
- for (const target of targets) {
589
- for (const channel of resolveChannels(notification, target)) {
648
+ for (const { target, channels: resolvedChannels } of targetChannels) {
649
+ for (const channel of resolvedChannels) {
590
650
  const plan = resolveChannelDispatchPlan(notification, target, channel, input.options);
591
651
  channels.push(Object.freeze({
592
652
  channel,
@@ -597,20 +657,23 @@ async function deferDispatchUntilCommit(input, targets, notification) {
597
657
  }));
598
658
  }
599
659
  }
600
- active.afterCommit(async () => {
660
+ const deferred = deferAfterCommit(async () => {
601
661
  await dispatchNotifications(input, { allowAfterCommitDeferral: false });
602
662
  });
663
+ if (!deferred) {
664
+ return null;
665
+ }
603
666
  return Object.freeze({
604
- totalTargets: targets.length,
667
+ totalTargets: targetChannels.length,
605
668
  channels: Object.freeze(channels),
606
669
  deferred: true
607
670
  });
608
671
  }
609
- function shouldDeferDispatchAfterCommit(notification, targets, options) {
672
+ function shouldDeferDispatchAfterCommit(notification, targetChannels, options) {
610
673
  if (options.afterCommit) {
611
674
  return true;
612
675
  }
613
- return targets.some((target) => resolveChannels(notification, target).some((channel) => {
676
+ return targetChannels.some(({ target, channels }) => channels.some((channel) => {
614
677
  try {
615
678
  return resolveChannelDispatchPlan(notification, target, channel, options).afterCommit;
616
679
  } catch {
@@ -621,15 +684,20 @@ function shouldDeferDispatchAfterCommit(notification, targets, options) {
621
684
  async function dispatchNotifications(input, execution = {}) {
622
685
  const notification = normalizeNotificationDefinition(input.notification);
623
686
  const targets = resolveTargets(input.target);
624
- if (execution.allowAfterCommitDeferral !== false && shouldDeferDispatchAfterCommit(notification, targets, input.options)) {
625
- const deferredResult = await deferDispatchUntilCommit(input, targets, notification);
687
+ const declaredTargetChannels = resolveTargetChannels(notification, targets);
688
+ if (hasUnresolvedNotificationChannels(declaredTargetChannels)) {
689
+ const state = getRuntimeState();
690
+ await loadNotificationPluginChannels(state.projectRoot, state.pluginNames);
691
+ }
692
+ const targetChannels = resolveRegisteredTargetChannels(declaredTargetChannels);
693
+ if (execution.allowAfterCommitDeferral !== false && shouldDeferDispatchAfterCommit(notification, targetChannels, input.options)) {
694
+ const deferredResult = await deferDispatchUntilCommit(input, notification, targetChannels);
626
695
  if (deferredResult) {
627
696
  return deferredResult;
628
697
  }
629
698
  }
630
699
  const results = [];
631
- for (const target of targets) {
632
- const channels = resolveChannels(notification, target);
700
+ for (const { target, channels } of targetChannels) {
633
701
  for (const channel of channels) {
634
702
  try {
635
703
  const context = resolveChannelSendContext(notification, channel, target);
@@ -658,6 +726,15 @@ async function dispatchNotifications(input, execution = {}) {
658
726
  channels: Object.freeze(results)
659
727
  });
660
728
  }
729
+ function hasUnresolvedNotificationChannels(targetChannels) {
730
+ return targetChannels.some(({ channels }) => channels.some((channel) => !getNotificationChannel(channel)));
731
+ }
732
+ function resolveRegisteredTargetChannels(targetChannels) {
733
+ return Object.freeze(targetChannels.map(({ target, channels }) => Object.freeze({
734
+ target,
735
+ channels: resolveRegisteredChannels(channels)
736
+ })));
737
+ }
661
738
  var PendingDispatch = class {
662
739
  constructor(target, notification, options = {}) {
663
740
  this.target = target;
@@ -738,7 +815,18 @@ var AnonymousNotificationBuilder = class _AnonymousNotificationBuilder {
738
815
  }
739
816
  };
740
817
  function configureNotificationsRuntime(bindings) {
741
- getRuntimeState().bindings = bindings;
818
+ const state = getRuntimeState();
819
+ if (!bindings) {
820
+ state.bindings = void 0;
821
+ state.projectRoot = void 0;
822
+ state.pluginNames = void 0;
823
+ return;
824
+ }
825
+ const { projectRoot, plugins, ...runtimeBindings } = bindings;
826
+ state.bindings = runtimeBindings;
827
+ const normalizedProjectRoot = typeof projectRoot === "string" ? projectRoot.trim() : "";
828
+ state.projectRoot = normalizedProjectRoot ? normalizedProjectRoot : void 0;
829
+ state.pluginNames = Object.freeze([...new Set((plugins ?? []).map((plugin) => plugin.trim()).filter((plugin) => plugin.length > 0))]);
742
830
  }
743
831
  function getNotificationsRuntimeBindings() {
744
832
  return getRuntimeBindings();
@@ -746,8 +834,10 @@ function getNotificationsRuntimeBindings() {
746
834
  function resetNotificationsRuntime() {
747
835
  const state = getRuntimeState();
748
836
  state.bindings = void 0;
837
+ state.projectRoot = void 0;
838
+ state.pluginNames = void 0;
749
839
  state.loadQueueModule = void 0;
750
- state.loadDbModule = void 0;
840
+ resetNotificationPluginChannels();
751
841
  }
752
842
  function notify(notifiable, notification) {
753
843
  return new PendingDispatch({
@@ -810,7 +900,6 @@ var notificationsRuntimeInternals = {
810
900
  getNotificationChannel,
811
901
  isAnonymousTarget,
812
902
  isObject,
813
- loadDbModule,
814
903
  loadQueueModule,
815
904
  normalizeBroadcastRouteFromValue,
816
905
  normalizeDatabaseRouteFromValue,
@@ -830,21 +919,12 @@ var notificationsRuntimeInternals = {
830
919
  resolveRoute,
831
920
  runQueuedNotificationDelivery,
832
921
  resolveTargets,
833
- setDbModuleLoader(loader) {
834
- getRuntimeState().loadDbModule = loader;
835
- },
836
922
  setQueueModuleLoader(loader) {
837
923
  getRuntimeState().loadQueueModule = loader;
838
924
  }
839
925
  };
840
926
 
841
927
  // src/index.ts
842
- import {
843
- defineNotificationsConfig as defineBaseNotificationsConfig
844
- } from "@holo-js/config";
845
- function defineNotificationsConfig(config) {
846
- return defineBaseNotificationsConfig(config);
847
- }
848
928
  var notifications = Object.freeze({
849
929
  deleteNotifications,
850
930
  listNotifications,
@@ -865,12 +945,15 @@ export {
865
945
  getNotificationsRuntime,
866
946
  getNotificationsRuntimeBindings,
867
947
  getRegisteredNotificationChannel,
948
+ holoNotificationsDefaults,
868
949
  isNotificationDefinition,
869
950
  listNotifications,
870
951
  listRegisteredNotificationChannels,
952
+ loadNotificationPluginChannels,
871
953
  markNotificationsAsRead,
872
954
  markNotificationsAsUnread,
873
955
  normalizeNotificationDefinition,
956
+ normalizeNotificationsConfig,
874
957
  notificationRegistryInternals,
875
958
  notificationsInternals,
876
959
  notificationsRuntimeInternals,
@@ -879,6 +962,7 @@ export {
879
962
  notifyUsing,
880
963
  registerNotificationChannel,
881
964
  resetNotificationChannelRegistry,
965
+ resetNotificationPluginChannels,
882
966
  resetNotificationsRuntime,
883
967
  unreadNotifications
884
968
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holo-js/notifications",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "Holo-JS Framework - notification contracts, channels, and fluent dispatch helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -10,6 +10,11 @@
10
10
  "import": "./dist/index.mjs",
11
11
  "default": "./dist/index.mjs"
12
12
  },
13
+ "./config": {
14
+ "types": "./dist/config.d.ts",
15
+ "import": "./dist/config.mjs",
16
+ "default": "./dist/config.mjs"
17
+ },
13
18
  "./contracts": {
14
19
  "types": "./dist/contracts.d.ts",
15
20
  "import": "./dist/contracts.mjs",
@@ -28,16 +33,13 @@
28
33
  "test": "vitest --run"
29
34
  },
30
35
  "dependencies": {
31
- "@holo-js/config": "^0.2.5"
36
+ "@holo-js/config": "^0.3.0",
37
+ "@holo-js/kernel": "^0.3.0"
32
38
  },
33
39
  "peerDependencies": {
34
- "@holo-js/db": "^0.2.5",
35
- "@holo-js/queue": "^0.2.5"
40
+ "@holo-js/queue": "^0.3.0"
36
41
  },
37
42
  "peerDependenciesMeta": {
38
- "@holo-js/db": {
39
- "optional": true
40
- },
41
43
  "@holo-js/queue": {
42
44
  "optional": true
43
45
  }