@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.
- package/dist/{chunk-INGM2JYY.mjs → chunk-4KUFKBEW.mjs} +0 -2
- package/dist/chunk-ZZH7Q6TC.mjs +45 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.mjs +12 -0
- package/dist/contracts.d.ts +2 -2
- package/dist/contracts.mjs +1 -3
- package/dist/index.d.ts +20 -28
- package/dist/index.mjs +183 -99
- package/package.json +9 -7
|
@@ -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
|
+
};
|
package/dist/config.d.ts
ADDED
|
@@ -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 };
|
package/dist/config.mjs
ADDED
|
@@ -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
|
+
};
|
package/dist/contracts.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { NormalizedHoloNotificationsConfig } from '
|
|
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;
|
package/dist/contracts.mjs
CHANGED
|
@@ -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-
|
|
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,
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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,
|
|
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<"
|
|
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
|
|
177
|
+
normalizeDelayValue: typeof normalizeNotificationDelay;
|
|
187
178
|
normalizeEmailRouteFromValue: typeof normalizeEmailRouteFromValue;
|
|
188
179
|
normalizeNotificationRecord: typeof normalizeNotificationRecord;
|
|
189
180
|
normalizeNotificationRecordIds: typeof normalizeNotificationRecordIds;
|
|
190
|
-
normalizeOptionalString: typeof
|
|
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
|
|
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,
|
|
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-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
582
|
-
const
|
|
583
|
-
|
|
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
|
|
589
|
-
for (const channel of
|
|
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
|
-
|
|
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:
|
|
667
|
+
totalTargets: targetChannels.length,
|
|
605
668
|
channels: Object.freeze(channels),
|
|
606
669
|
deferred: true
|
|
607
670
|
});
|
|
608
671
|
}
|
|
609
|
-
function shouldDeferDispatchAfterCommit(notification,
|
|
672
|
+
function shouldDeferDispatchAfterCommit(notification, targetChannels, options) {
|
|
610
673
|
if (options.afterCommit) {
|
|
611
674
|
return true;
|
|
612
675
|
}
|
|
613
|
-
return
|
|
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
|
-
|
|
625
|
-
|
|
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
|
|
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()
|
|
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
|
-
|
|
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.
|
|
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.
|
|
36
|
+
"@holo-js/config": "^0.3.0",
|
|
37
|
+
"@holo-js/kernel": "^0.3.0"
|
|
32
38
|
},
|
|
33
39
|
"peerDependencies": {
|
|
34
|
-
"@holo-js/
|
|
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
|
}
|