@fluojs/notifications 1.0.3 → 2.0.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/README.ko.md +40 -5
- package/README.md +40 -5
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/module.d.ts +4 -2
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +9 -11
- package/dist/service.d.ts +3 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +115 -57
- package/dist/snapshots.d.ts +28 -0
- package/dist/snapshots.d.ts.map +1 -0
- package/dist/snapshots.js +240 -0
- package/dist/status.d.ts +32 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +37 -13
- package/dist/types.d.ts +91 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/service.js
CHANGED
|
@@ -5,7 +5,8 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
|
|
|
5
5
|
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
6
6
|
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
7
7
|
import { Inject } from '@fluojs/core';
|
|
8
|
-
import { NotificationChannelNotFoundError, NotificationQueueNotConfiguredError } from './errors.js';
|
|
8
|
+
import { NotificationChannelNotFoundError, NotificationQueueNotConfiguredError, NotificationQueueResultIntegrityError } from './errors.js';
|
|
9
|
+
import { createNotificationDispatchSnapshot, createNotificationLifecycleEventSnapshot } from './snapshots.js';
|
|
9
10
|
import { createNotificationsPlatformStatusSnapshot } from './status.js';
|
|
10
11
|
import { NOTIFICATION_CHANNELS, NOTIFICATIONS_OPTIONS } from './tokens.js';
|
|
11
12
|
let _NotificationsService;
|
|
@@ -38,6 +39,7 @@ class NotificationsService {
|
|
|
38
39
|
* @returns A normalized dispatch result describing direct vs queued delivery.
|
|
39
40
|
* @throws {NotificationChannelNotFoundError} When no registered channel matches `notification.channel`.
|
|
40
41
|
* @throws {NotificationQueueNotConfiguredError} When queue delivery is requested without a queue adapter.
|
|
42
|
+
* @throws {NotificationQueueResultIntegrityError} When a queue adapter returns an invalid delivery identifier.
|
|
41
43
|
*
|
|
42
44
|
* @example
|
|
43
45
|
* ```ts
|
|
@@ -50,52 +52,60 @@ class NotificationsService {
|
|
|
50
52
|
* ```
|
|
51
53
|
*/
|
|
52
54
|
async dispatch(notification, options = {}) {
|
|
53
|
-
const
|
|
55
|
+
const dispatchNotification = createNotificationDispatchSnapshot(notification);
|
|
56
|
+
return this.dispatchAdmitted(dispatchNotification, options);
|
|
57
|
+
}
|
|
58
|
+
async dispatchAdmitted(dispatchNotification, options) {
|
|
59
|
+
const requestedPublicationError = await this.publishLifecycleEventBestEffort('notification.dispatch.requested', dispatchNotification, options);
|
|
54
60
|
if (this.shouldQueueSingleDispatch(options)) {
|
|
55
61
|
try {
|
|
56
|
-
this.requireChannel(
|
|
62
|
+
this.requireChannel(dispatchNotification.channel);
|
|
57
63
|
} catch (error) {
|
|
58
|
-
await this.publishFailureLifecycleEvent(
|
|
64
|
+
await this.publishFailureLifecycleEvent(dispatchNotification, options, error, requestedPublicationError);
|
|
59
65
|
throw error;
|
|
60
66
|
}
|
|
61
|
-
const job = this.createQueueJob(
|
|
67
|
+
const job = this.createQueueJob(dispatchNotification);
|
|
62
68
|
try {
|
|
63
|
-
const
|
|
69
|
+
const queue = this.requireQueueAdapter();
|
|
70
|
+
throwIfAborted(options.signal);
|
|
71
|
+
const deliveryId = validateQueueDeliveryId(await queue.enqueue(job, {
|
|
72
|
+
signal: options.signal
|
|
73
|
+
}));
|
|
64
74
|
const result = {
|
|
65
|
-
channel:
|
|
66
|
-
deliveryId
|
|
75
|
+
channel: dispatchNotification.channel,
|
|
76
|
+
deliveryId,
|
|
67
77
|
queued: true,
|
|
68
78
|
status: 'queued'
|
|
69
79
|
};
|
|
70
|
-
await this.publishLifecycleEventBestEffort('notification.dispatch.queued',
|
|
80
|
+
await this.publishLifecycleEventBestEffort('notification.dispatch.queued', dispatchNotification, options, result.deliveryId);
|
|
71
81
|
return result;
|
|
72
82
|
} catch (error) {
|
|
73
|
-
await this.publishFailureLifecycleEvent(
|
|
83
|
+
await this.publishFailureLifecycleEvent(dispatchNotification, options, error, requestedPublicationError);
|
|
74
84
|
throw error;
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
87
|
let channel;
|
|
78
88
|
try {
|
|
79
|
-
channel = this.requireChannel(
|
|
89
|
+
channel = this.requireChannel(dispatchNotification.channel);
|
|
80
90
|
} catch (error) {
|
|
81
|
-
await this.publishFailureLifecycleEvent(
|
|
91
|
+
await this.publishFailureLifecycleEvent(dispatchNotification, options, error, requestedPublicationError);
|
|
82
92
|
throw error;
|
|
83
93
|
}
|
|
84
94
|
try {
|
|
85
|
-
const delivery = await channel.send(
|
|
95
|
+
const delivery = await channel.send(dispatchNotification, {
|
|
86
96
|
signal: options.signal
|
|
87
97
|
});
|
|
88
98
|
const result = {
|
|
89
|
-
channel:
|
|
90
|
-
deliveryId: this.normalizeDeliveryId(delivery.externalId,
|
|
99
|
+
channel: dispatchNotification.channel,
|
|
100
|
+
deliveryId: this.normalizeDeliveryId(delivery.externalId, dispatchNotification),
|
|
91
101
|
metadata: delivery.metadata,
|
|
92
102
|
queued: delivery.status === 'queued',
|
|
93
103
|
status: delivery.status ?? 'delivered'
|
|
94
104
|
};
|
|
95
|
-
await this.publishLifecycleEventBestEffort(result.queued ? 'notification.dispatch.queued' : 'notification.dispatch.delivered',
|
|
105
|
+
await this.publishLifecycleEventBestEffort(result.queued ? 'notification.dispatch.queued' : 'notification.dispatch.delivered', dispatchNotification, options, result.deliveryId);
|
|
96
106
|
return result;
|
|
97
107
|
} catch (error) {
|
|
98
|
-
await this.publishFailureLifecycleEvent(
|
|
108
|
+
await this.publishFailureLifecycleEvent(dispatchNotification, options, error, requestedPublicationError);
|
|
99
109
|
throw error;
|
|
100
110
|
}
|
|
101
111
|
}
|
|
@@ -108,6 +118,7 @@ class NotificationsService {
|
|
|
108
118
|
* @param options Optional queue preference and tolerant error-handling controls.
|
|
109
119
|
* @returns A batch summary containing successes and captured failures.
|
|
110
120
|
* @throws {NotificationQueueNotConfiguredError} When queue-backed bulk delivery is requested without a queue adapter.
|
|
121
|
+
* @throws {NotificationQueueResultIntegrityError} When a queue adapter returns invalid delivery identifiers.
|
|
111
122
|
*/
|
|
112
123
|
async dispatchMany(notifications, options = {}) {
|
|
113
124
|
if (notifications.length === 0) {
|
|
@@ -119,42 +130,53 @@ class NotificationsService {
|
|
|
119
130
|
succeeded: 0
|
|
120
131
|
};
|
|
121
132
|
}
|
|
133
|
+
const dispatchNotifications = notifications.map(notification => createNotificationDispatchSnapshot(notification));
|
|
122
134
|
if (this.shouldQueue(notifications.length, options)) {
|
|
123
|
-
const requestedPublicationErrors = await this.publishRequestedLifecycleEvents(
|
|
135
|
+
const requestedPublicationErrors = await this.publishRequestedLifecycleEvents(dispatchNotifications, options);
|
|
124
136
|
let queue;
|
|
125
137
|
try {
|
|
126
138
|
queue = this.requireQueueAdapter();
|
|
127
139
|
} catch (error) {
|
|
128
|
-
await this.publishFailureLifecycleEvents(
|
|
140
|
+
await this.publishFailureLifecycleEvents(dispatchNotifications, options, error, requestedPublicationErrors);
|
|
129
141
|
throw error;
|
|
130
142
|
}
|
|
131
143
|
try {
|
|
132
|
-
for (const notification of
|
|
144
|
+
for (const notification of dispatchNotifications) {
|
|
133
145
|
this.requireChannel(notification.channel);
|
|
134
146
|
}
|
|
135
147
|
} catch (error) {
|
|
136
|
-
await this.publishFailureLifecycleEvents(
|
|
148
|
+
await this.publishFailureLifecycleEvents(dispatchNotifications, options, error, requestedPublicationErrors);
|
|
137
149
|
throw error;
|
|
138
150
|
}
|
|
139
|
-
const jobs =
|
|
151
|
+
const jobs = dispatchNotifications.map(notification => this.createQueueJob(notification));
|
|
140
152
|
if (!queue.enqueueMany) {
|
|
141
|
-
return this.dispatchManyThroughSequentialQueueFallback(
|
|
153
|
+
return this.dispatchManyThroughSequentialQueueFallback(dispatchNotifications, jobs, options, requestedPublicationErrors);
|
|
142
154
|
}
|
|
143
|
-
|
|
155
|
+
const admittedJobCount = jobs.length;
|
|
156
|
+
let results;
|
|
144
157
|
try {
|
|
145
|
-
|
|
158
|
+
throwIfAborted(options.signal);
|
|
159
|
+
const ids = validateQueueBatchDeliveryIds(await queue.enqueueMany(jobs, {
|
|
160
|
+
signal: options.signal
|
|
161
|
+
}), admittedJobCount);
|
|
162
|
+
results = dispatchNotifications.map((notification, index) => {
|
|
163
|
+
const deliveryId = ids[index];
|
|
164
|
+
if (deliveryId === undefined) {
|
|
165
|
+
throw createQueueResultIntegrityError('enqueueMany', `queue id at index ${index} must be present`);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
channel: notification.channel,
|
|
169
|
+
deliveryId,
|
|
170
|
+
queued: true,
|
|
171
|
+
status: 'queued'
|
|
172
|
+
};
|
|
173
|
+
});
|
|
146
174
|
} catch (error) {
|
|
147
|
-
await this.publishFailureLifecycleEvents(
|
|
175
|
+
await this.publishFailureLifecycleEvents(dispatchNotifications, options, error, requestedPublicationErrors);
|
|
148
176
|
throw error;
|
|
149
177
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
deliveryId: this.normalizeDeliveryId(ids[index], notification),
|
|
153
|
-
queued: true,
|
|
154
|
-
status: 'queued'
|
|
155
|
-
}));
|
|
156
|
-
for (let index = 0; index < notifications.length; index += 1) {
|
|
157
|
-
const notification = notifications[index];
|
|
178
|
+
for (let index = 0; index < dispatchNotifications.length; index += 1) {
|
|
179
|
+
const notification = dispatchNotifications[index];
|
|
158
180
|
await this.publishLifecycleEventBestEffort('notification.dispatch.queued', notification, options, results[index]?.deliveryId);
|
|
159
181
|
}
|
|
160
182
|
return {
|
|
@@ -167,9 +189,9 @@ class NotificationsService {
|
|
|
167
189
|
}
|
|
168
190
|
const results = [];
|
|
169
191
|
const failures = [];
|
|
170
|
-
for (const notification of
|
|
192
|
+
for (const notification of dispatchNotifications) {
|
|
171
193
|
try {
|
|
172
|
-
results.push(await this.
|
|
194
|
+
results.push(await this.dispatchAdmitted(notification, options));
|
|
173
195
|
} catch (error) {
|
|
174
196
|
const failure = {
|
|
175
197
|
error: error instanceof Error ? error : new Error('Notification dispatch failed.'),
|
|
@@ -199,6 +221,7 @@ class NotificationsService {
|
|
|
199
221
|
return createNotificationsPlatformStatusSnapshot({
|
|
200
222
|
bulkQueueThreshold: this.options.queue?.bulkThreshold ?? 0,
|
|
201
223
|
channelsRegistered: this.channelsByName.size,
|
|
224
|
+
eventPublicationEnabled: this.options.events?.publishLifecycleEvents ?? false,
|
|
202
225
|
eventPublisherConfigured: this.options.events !== undefined,
|
|
203
226
|
queueConfigured: this.options.queue !== undefined
|
|
204
227
|
});
|
|
@@ -261,7 +284,7 @@ class NotificationsService {
|
|
|
261
284
|
if (!this.options.events || !this.shouldPublishLifecycleEvents(options)) {
|
|
262
285
|
return;
|
|
263
286
|
}
|
|
264
|
-
const event = {
|
|
287
|
+
const event = createNotificationLifecycleEventSnapshot({
|
|
265
288
|
channel: notification.channel,
|
|
266
289
|
deliveryId,
|
|
267
290
|
error: error instanceof Error ? {
|
|
@@ -271,7 +294,7 @@ class NotificationsService {
|
|
|
271
294
|
name,
|
|
272
295
|
notification,
|
|
273
296
|
occurredAt: new Date().toISOString()
|
|
274
|
-
};
|
|
297
|
+
});
|
|
275
298
|
await this.options.events.publisher.publish(event);
|
|
276
299
|
}
|
|
277
300
|
async publishLifecycleEventBestEffort(name, notification, options, deliveryId, error) {
|
|
@@ -323,7 +346,10 @@ class NotificationsService {
|
|
|
323
346
|
continue;
|
|
324
347
|
}
|
|
325
348
|
try {
|
|
326
|
-
|
|
349
|
+
throwIfAborted(options.signal);
|
|
350
|
+
const deliveryId = validateQueueDeliveryId(await queue.enqueue(job, {
|
|
351
|
+
signal: options.signal
|
|
352
|
+
}));
|
|
327
353
|
const result = {
|
|
328
354
|
channel: notification.channel,
|
|
329
355
|
deliveryId,
|
|
@@ -362,43 +388,62 @@ class NotificationsService {
|
|
|
362
388
|
}
|
|
363
389
|
}
|
|
364
390
|
export { _NotificationsService as NotificationsService };
|
|
391
|
+
function throwIfAborted(signal) {
|
|
392
|
+
signal?.throwIfAborted();
|
|
393
|
+
}
|
|
365
394
|
function validateQueueBatchDeliveryIds(value, expectedCount) {
|
|
366
395
|
if (!Array.isArray(value)) {
|
|
367
|
-
throw
|
|
396
|
+
throw createQueueResultIntegrityError('enqueueMany', `expected ${expectedCount} queue ids but received a non-array result`);
|
|
397
|
+
}
|
|
398
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
399
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, 'length');
|
|
400
|
+
if (!isOwnDataPropertyDescriptor(lengthDescriptor)) {
|
|
401
|
+
throw createQueueResultIntegrityError('enqueueMany', `expected ${expectedCount} queue ids but received an invalid length descriptor`);
|
|
368
402
|
}
|
|
369
|
-
if (value
|
|
370
|
-
throw
|
|
403
|
+
if (lengthDescriptor.value !== expectedCount) {
|
|
404
|
+
throw createQueueResultIntegrityError('enqueueMany', `expected ${expectedCount} queue ids but received ${String(lengthDescriptor.value)}`);
|
|
371
405
|
}
|
|
372
406
|
const ids = [];
|
|
373
|
-
for (let index = 0; index <
|
|
374
|
-
|
|
375
|
-
|
|
407
|
+
for (let index = 0; index < expectedCount; index += 1) {
|
|
408
|
+
const descriptor = descriptors[String(index)];
|
|
409
|
+
if (!descriptor) {
|
|
410
|
+
throw createQueueResultIntegrityError('enqueueMany', `queue id at index ${index} must be present`);
|
|
376
411
|
}
|
|
377
|
-
|
|
412
|
+
if (!isOwnDataPropertyDescriptor(descriptor)) {
|
|
413
|
+
throw createQueueResultIntegrityError('enqueueMany', `queue id at index ${index} must be an own data property`);
|
|
414
|
+
}
|
|
415
|
+
const entry = descriptor.value;
|
|
378
416
|
if (typeof entry !== 'string' || entry.length === 0) {
|
|
379
|
-
throw
|
|
417
|
+
throw createQueueResultIntegrityError('enqueueMany', `queue id at index ${index} must be a non-empty string`);
|
|
380
418
|
}
|
|
381
419
|
ids.push(entry);
|
|
382
420
|
}
|
|
383
|
-
return ids;
|
|
421
|
+
return Object.freeze(ids);
|
|
384
422
|
}
|
|
385
|
-
function
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
423
|
+
function isOwnDataPropertyDescriptor(descriptor) {
|
|
424
|
+
return descriptor !== undefined && Object.hasOwn(descriptor, 'value') && !Object.hasOwn(descriptor, 'get') && !Object.hasOwn(descriptor, 'set');
|
|
425
|
+
}
|
|
426
|
+
function validateQueueDeliveryId(value) {
|
|
427
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
428
|
+
throw createQueueResultIntegrityError('enqueue', 'queue id must be a non-empty string');
|
|
429
|
+
}
|
|
430
|
+
return value;
|
|
431
|
+
}
|
|
432
|
+
function createQueueResultIntegrityError(operation, message) {
|
|
433
|
+
return new NotificationQueueResultIntegrityError(operation, message);
|
|
389
434
|
}
|
|
390
435
|
function createLifecyclePublicationFailureError(dispatchError, ...publicationErrors) {
|
|
391
436
|
const primaryMessage = dispatchError instanceof Error ? dispatchError.message : 'Notification dispatch failed.';
|
|
392
437
|
return new AggregateError([dispatchError, ...publicationErrors], `Notification dispatch failed, and failed lifecycle event publication also failed: ${primaryMessage}`);
|
|
393
438
|
}
|
|
394
439
|
function stableNotificationHash(notification) {
|
|
395
|
-
let hash =
|
|
440
|
+
let hash = 0xcbf29ce484222325n;
|
|
396
441
|
const input = stableStringify(notification, createStableStringifyContext());
|
|
397
442
|
for (let index = 0; index < input.length; index += 1) {
|
|
398
|
-
hash ^= input.charCodeAt(index);
|
|
399
|
-
hash =
|
|
443
|
+
hash ^= BigInt(input.charCodeAt(index));
|
|
444
|
+
hash = BigInt.asUintN(64, hash * 0x100000001b3n);
|
|
400
445
|
}
|
|
401
|
-
return hash.toString(36).padStart(
|
|
446
|
+
return hash.toString(36).padStart(13, '0');
|
|
402
447
|
}
|
|
403
448
|
function createStableStringifyContext() {
|
|
404
449
|
return {
|
|
@@ -447,6 +492,16 @@ function stableStringify(value, context) {
|
|
|
447
492
|
if (circularReferenceId !== undefined) {
|
|
448
493
|
return `Circular:${circularReferenceId}`;
|
|
449
494
|
}
|
|
495
|
+
if (value instanceof ArrayBuffer) {
|
|
496
|
+
const serialized = `ArrayBuffer:{byteLength:${value.byteLength},bytes:${stableByteArray(new Uint8Array(value))}}`;
|
|
497
|
+
context.seen.delete(value);
|
|
498
|
+
return serialized;
|
|
499
|
+
}
|
|
500
|
+
if (ArrayBuffer.isView(value)) {
|
|
501
|
+
const serialized = `ArrayBufferView:{view:${JSON.stringify(Object.prototype.toString.call(value).slice(8, -1))},byteOffset:${value.byteOffset},byteLength:${value.byteLength},bytes:${stableByteArray(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))}}`;
|
|
502
|
+
context.seen.delete(value);
|
|
503
|
+
return serialized;
|
|
504
|
+
}
|
|
450
505
|
if (value instanceof Date) {
|
|
451
506
|
const serialized = Number.isNaN(value.getTime()) ? 'Date:Invalid' : `Date:${JSON.stringify(value.toISOString())}`;
|
|
452
507
|
context.seen.delete(value);
|
|
@@ -496,8 +551,11 @@ function stableStringify(value, context) {
|
|
|
496
551
|
}
|
|
497
552
|
const prototype = Object.getPrototypeOf(value);
|
|
498
553
|
const objectTag = prototype && prototype !== Object.prototype ? `${prototype.constructor?.name ?? 'Object'}:` : '';
|
|
499
|
-
const entries = Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => left
|
|
554
|
+
const entries = Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => compareStableString(left, right));
|
|
500
555
|
const serialized = `${objectTag}{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry, context)}`).join(',')}}`;
|
|
501
556
|
context.seen.delete(value);
|
|
502
557
|
return serialized;
|
|
558
|
+
}
|
|
559
|
+
function stableByteArray(bytes) {
|
|
560
|
+
return `[${Array.from(bytes).join(',')}]`;
|
|
503
561
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { NotificationDispatchRequest, NotificationLifecycleEvent } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Copies and freezes one notification envelope at dispatch admission.
|
|
4
|
+
*
|
|
5
|
+
* @param notification Notification envelope supplied by the caller.
|
|
6
|
+
* @returns The immutable dispatch snapshot.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function createNotificationDispatchSnapshot<TRequest extends NotificationDispatchRequest>(notification: TRequest): TRequest;
|
|
10
|
+
/**
|
|
11
|
+
* Copies and freezes one lifecycle event before publication.
|
|
12
|
+
*
|
|
13
|
+
* @param event Lifecycle event details captured by the dispatch service.
|
|
14
|
+
* @returns The immutable lifecycle event snapshot.
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare function createNotificationLifecycleEventSnapshot<TRequest extends NotificationDispatchRequest>(event: {
|
|
18
|
+
channel: string;
|
|
19
|
+
deliveryId?: string;
|
|
20
|
+
error?: {
|
|
21
|
+
message: string;
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
name: NotificationLifecycleEvent<TRequest>['name'];
|
|
25
|
+
notification: TRequest;
|
|
26
|
+
occurredAt: string;
|
|
27
|
+
}): NotificationLifecycleEvent<TRequest>;
|
|
28
|
+
//# sourceMappingURL=snapshots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshots.d.ts","sourceRoot":"","sources":["../src/snapshots.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAC3B,0BAA0B,EAU3B,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,wBAAgB,kCAAkC,CAAC,QAAQ,SAAS,2BAA2B,EAC7F,YAAY,EAAE,QAAQ,GACrB,QAAQ,CAEV;AAED;;;;;;GAMG;AACH,wBAAgB,wCAAwC,CAAC,QAAQ,SAAS,2BAA2B,EACnG,KAAK,EAAE;IACL,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,IAAI,EAAE,0BAA0B,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;IACnD,YAAY,EAAE,QAAQ,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB,GACA,0BAA0B,CAAC,QAAQ,CAAC,CAItC"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copies and freezes one notification envelope at dispatch admission.
|
|
3
|
+
*
|
|
4
|
+
* @param notification Notification envelope supplied by the caller.
|
|
5
|
+
* @returns The immutable dispatch snapshot.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export function createNotificationDispatchSnapshot(notification) {
|
|
9
|
+
return freezeSnapshot(cloneSnapshot(notification, new Map()));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Copies and freezes one lifecycle event before publication.
|
|
14
|
+
*
|
|
15
|
+
* @param event Lifecycle event details captured by the dispatch service.
|
|
16
|
+
* @returns The immutable lifecycle event snapshot.
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export function createNotificationLifecycleEventSnapshot(event) {
|
|
20
|
+
return freezeSnapshot(createLifecycleSnapshot(event, new Map()));
|
|
21
|
+
}
|
|
22
|
+
function cloneSnapshot(value, seen) {
|
|
23
|
+
if (value === null || typeof value !== 'object') {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
const existing = seen.get(value);
|
|
27
|
+
if (existing) {
|
|
28
|
+
return existing;
|
|
29
|
+
}
|
|
30
|
+
if (value instanceof Date) {
|
|
31
|
+
const clone = new Date(value.getTime());
|
|
32
|
+
seen.set(value, clone);
|
|
33
|
+
return clone;
|
|
34
|
+
}
|
|
35
|
+
if (value instanceof URL) {
|
|
36
|
+
const clone = new URL(value.href);
|
|
37
|
+
seen.set(value, clone);
|
|
38
|
+
return clone;
|
|
39
|
+
}
|
|
40
|
+
if (value instanceof URLSearchParams) {
|
|
41
|
+
const clone = new URLSearchParams(value);
|
|
42
|
+
seen.set(value, clone);
|
|
43
|
+
return clone;
|
|
44
|
+
}
|
|
45
|
+
if (value instanceof RegExp) {
|
|
46
|
+
const clone = new RegExp(value.source, value.flags);
|
|
47
|
+
clone.lastIndex = value.lastIndex;
|
|
48
|
+
seen.set(value, clone);
|
|
49
|
+
return clone;
|
|
50
|
+
}
|
|
51
|
+
if (value instanceof ArrayBuffer) {
|
|
52
|
+
const clone = value.slice(0);
|
|
53
|
+
seen.set(value, clone);
|
|
54
|
+
return clone;
|
|
55
|
+
}
|
|
56
|
+
if (ArrayBuffer.isView(value)) {
|
|
57
|
+
if (!(value.buffer instanceof ArrayBuffer)) {
|
|
58
|
+
throw new TypeError('Notification snapshots only support ArrayBuffer-backed views.');
|
|
59
|
+
}
|
|
60
|
+
const buffer = cloneSnapshot(value.buffer, seen);
|
|
61
|
+
const clone = cloneArrayBufferView(value, buffer);
|
|
62
|
+
seen.set(value, clone);
|
|
63
|
+
return clone;
|
|
64
|
+
}
|
|
65
|
+
if (value instanceof Map) {
|
|
66
|
+
const clone = new Map();
|
|
67
|
+
seen.set(value, clone);
|
|
68
|
+
for (const [key, entry] of value) {
|
|
69
|
+
clone.set(cloneSnapshot(key, seen), cloneSnapshot(entry, seen));
|
|
70
|
+
}
|
|
71
|
+
return clone;
|
|
72
|
+
}
|
|
73
|
+
if (value instanceof Set) {
|
|
74
|
+
const clone = new Set();
|
|
75
|
+
seen.set(value, clone);
|
|
76
|
+
for (const entry of value) {
|
|
77
|
+
clone.add(cloneSnapshot(entry, seen));
|
|
78
|
+
}
|
|
79
|
+
return clone;
|
|
80
|
+
}
|
|
81
|
+
assertSnapshotObjectIsDataOnly(value);
|
|
82
|
+
const clone = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
|
|
83
|
+
seen.set(value, clone);
|
|
84
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
85
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
86
|
+
if (!descriptor) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if ('value' in descriptor) {
|
|
90
|
+
descriptor.value = cloneSnapshot(descriptor.value, seen);
|
|
91
|
+
}
|
|
92
|
+
Object.defineProperty(clone, key, descriptor);
|
|
93
|
+
}
|
|
94
|
+
return clone;
|
|
95
|
+
}
|
|
96
|
+
function createLifecycleSnapshot(value, seen) {
|
|
97
|
+
if (value === null || typeof value !== 'object') {
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
const existing = seen.get(value);
|
|
101
|
+
if (existing) {
|
|
102
|
+
return existing;
|
|
103
|
+
}
|
|
104
|
+
if (value instanceof Date) {
|
|
105
|
+
const snapshot = {
|
|
106
|
+
epochMilliseconds: Number.isNaN(value.getTime()) ? null : value.getTime(),
|
|
107
|
+
kind: 'Date'
|
|
108
|
+
};
|
|
109
|
+
seen.set(value, snapshot);
|
|
110
|
+
return snapshot;
|
|
111
|
+
}
|
|
112
|
+
if (value instanceof URL) {
|
|
113
|
+
const snapshot = {
|
|
114
|
+
href: value.href,
|
|
115
|
+
kind: 'URL'
|
|
116
|
+
};
|
|
117
|
+
seen.set(value, snapshot);
|
|
118
|
+
return snapshot;
|
|
119
|
+
}
|
|
120
|
+
if (value instanceof URLSearchParams) {
|
|
121
|
+
const snapshot = {
|
|
122
|
+
kind: 'URLSearchParams',
|
|
123
|
+
query: value.toString()
|
|
124
|
+
};
|
|
125
|
+
seen.set(value, snapshot);
|
|
126
|
+
return snapshot;
|
|
127
|
+
}
|
|
128
|
+
if (value instanceof RegExp) {
|
|
129
|
+
const snapshot = {
|
|
130
|
+
flags: value.flags,
|
|
131
|
+
kind: 'RegExp',
|
|
132
|
+
lastIndex: value.lastIndex,
|
|
133
|
+
source: value.source
|
|
134
|
+
};
|
|
135
|
+
seen.set(value, snapshot);
|
|
136
|
+
return snapshot;
|
|
137
|
+
}
|
|
138
|
+
if (value instanceof ArrayBuffer) {
|
|
139
|
+
const snapshot = {
|
|
140
|
+
byteLength: value.byteLength,
|
|
141
|
+
bytes: Array.from(new Uint8Array(value)),
|
|
142
|
+
kind: 'ArrayBuffer'
|
|
143
|
+
};
|
|
144
|
+
seen.set(value, snapshot);
|
|
145
|
+
return snapshot;
|
|
146
|
+
}
|
|
147
|
+
if (ArrayBuffer.isView(value)) {
|
|
148
|
+
const snapshot = {
|
|
149
|
+
byteLength: value.byteLength,
|
|
150
|
+
byteOffset: value.byteOffset,
|
|
151
|
+
bytes: Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)),
|
|
152
|
+
kind: 'ArrayBufferView',
|
|
153
|
+
view: Object.prototype.toString.call(value).slice(8, -1)
|
|
154
|
+
};
|
|
155
|
+
seen.set(value, snapshot);
|
|
156
|
+
return snapshot;
|
|
157
|
+
}
|
|
158
|
+
if (value instanceof Map) {
|
|
159
|
+
const snapshot = {
|
|
160
|
+
entries: [],
|
|
161
|
+
kind: 'Map'
|
|
162
|
+
};
|
|
163
|
+
seen.set(value, snapshot);
|
|
164
|
+
for (const [key, entry] of value) {
|
|
165
|
+
snapshot.entries.push([createLifecycleSnapshot(key, seen), createLifecycleSnapshot(entry, seen)]);
|
|
166
|
+
}
|
|
167
|
+
return snapshot;
|
|
168
|
+
}
|
|
169
|
+
if (value instanceof Set) {
|
|
170
|
+
const snapshot = {
|
|
171
|
+
kind: 'Set',
|
|
172
|
+
values: []
|
|
173
|
+
};
|
|
174
|
+
seen.set(value, snapshot);
|
|
175
|
+
for (const entry of value) {
|
|
176
|
+
snapshot.values.push(createLifecycleSnapshot(entry, seen));
|
|
177
|
+
}
|
|
178
|
+
return snapshot;
|
|
179
|
+
}
|
|
180
|
+
assertSnapshotObjectIsDataOnly(value);
|
|
181
|
+
const snapshot = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
|
|
182
|
+
seen.set(value, snapshot);
|
|
183
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
184
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
185
|
+
if (!descriptor) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if ('value' in descriptor) {
|
|
189
|
+
descriptor.value = createLifecycleSnapshot(descriptor.value, seen);
|
|
190
|
+
}
|
|
191
|
+
Object.defineProperty(snapshot, key, descriptor);
|
|
192
|
+
}
|
|
193
|
+
return snapshot;
|
|
194
|
+
}
|
|
195
|
+
function freezeSnapshot(value, seen = new WeakSet()) {
|
|
196
|
+
if (value === null || typeof value !== 'object' || seen.has(value)) {
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
seen.add(value);
|
|
200
|
+
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
if (value instanceof Map) {
|
|
204
|
+
for (const [key, entry] of value) {
|
|
205
|
+
freezeSnapshot(key, seen);
|
|
206
|
+
freezeSnapshot(entry, seen);
|
|
207
|
+
}
|
|
208
|
+
} else if (value instanceof Set) {
|
|
209
|
+
for (const entry of value) {
|
|
210
|
+
freezeSnapshot(entry, seen);
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
214
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
215
|
+
if (descriptor && 'value' in descriptor) {
|
|
216
|
+
freezeSnapshot(descriptor.value, seen);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return Object.freeze(value);
|
|
221
|
+
}
|
|
222
|
+
function cloneArrayBufferView(value, buffer) {
|
|
223
|
+
if (value instanceof DataView) {
|
|
224
|
+
return new DataView(buffer, value.byteOffset, value.byteLength);
|
|
225
|
+
}
|
|
226
|
+
const typedArray = value;
|
|
227
|
+
return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
|
|
228
|
+
}
|
|
229
|
+
function assertSnapshotObjectIsDataOnly(value) {
|
|
230
|
+
const prototype = Object.getPrototypeOf(value);
|
|
231
|
+
if (Array.isArray(value) && prototype !== Array.prototype || !Array.isArray(value) && prototype !== null && prototype !== Object.prototype) {
|
|
232
|
+
throw new TypeError('Notification snapshots only support data properties on plain objects.');
|
|
233
|
+
}
|
|
234
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
235
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
236
|
+
if (descriptor && !('value' in descriptor)) {
|
|
237
|
+
throw new TypeError('Notification snapshots only support data properties on plain objects.');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
package/dist/status.d.ts
CHANGED
|
@@ -5,20 +5,50 @@ export type NotificationsOperationMode = 'direct-only' | 'direct-with-events' |
|
|
|
5
5
|
export interface NotificationsStatusAdapterInput {
|
|
6
6
|
bulkQueueThreshold: number;
|
|
7
7
|
channelsRegistered: number;
|
|
8
|
+
/**
|
|
9
|
+
* Whether lifecycle publication is enabled for the configured publisher. Defaults to
|
|
10
|
+
* `eventPublisherConfigured` when omitted so callers that only know about configuration keep
|
|
11
|
+
* their previous diagnostics.
|
|
12
|
+
*/
|
|
13
|
+
eventPublicationEnabled?: boolean;
|
|
8
14
|
eventPublisherConfigured: boolean;
|
|
9
15
|
queueConfigured: boolean;
|
|
10
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Typed diagnostics published under {@link NotificationsPlatformStatusSnapshot.details}.
|
|
19
|
+
*
|
|
20
|
+
* The index signature keeps the shape assignable to `Record<string, unknown>` consumers while the
|
|
21
|
+
* named members give typed access to the documented diagnostics.
|
|
22
|
+
*/
|
|
23
|
+
export interface NotificationsStatusDetails {
|
|
24
|
+
bulkQueueThreshold: number;
|
|
25
|
+
channelsRegistered: number;
|
|
26
|
+
dependencies: readonly string[];
|
|
27
|
+
/** Whether a configured publisher is actually publishing lifecycle events. */
|
|
28
|
+
eventPublicationEnabled: boolean;
|
|
29
|
+
/** Whether an event publisher is wired, regardless of publication enablement. */
|
|
30
|
+
eventPublisherConfigured: boolean;
|
|
31
|
+
operationMode: NotificationsOperationMode;
|
|
32
|
+
queueConfigured: boolean;
|
|
33
|
+
[detail: string]: unknown;
|
|
34
|
+
}
|
|
11
35
|
/** Structured snapshot returned by {@link createNotificationsPlatformStatusSnapshot}. */
|
|
12
36
|
export interface NotificationsPlatformStatusSnapshot {
|
|
13
37
|
readiness: PlatformReadinessReport;
|
|
14
38
|
health: PlatformHealthReport;
|
|
15
39
|
ownership: PlatformSnapshot['ownership'];
|
|
16
|
-
details:
|
|
40
|
+
details: NotificationsStatusDetails;
|
|
17
41
|
}
|
|
18
42
|
/**
|
|
19
43
|
* Creates a health/readiness snapshot for the notifications orchestration layer.
|
|
20
44
|
*
|
|
21
|
-
*
|
|
45
|
+
* Publisher configuration and lifecycle publication enablement are reported separately:
|
|
46
|
+
* `details.eventPublisherConfigured` records the wiring while `details.eventPublicationEnabled`
|
|
47
|
+
* records whether events are actually published. Operation mode, active dependencies, and external
|
|
48
|
+
* ownership are derived from enablement so a configured-but-disabled publisher is never reported as
|
|
49
|
+
* an active event-backed runtime.
|
|
50
|
+
*
|
|
51
|
+
* @param input Registered-channel and optional-integration state derived from the active module wiring.
|
|
22
52
|
* @returns A structured snapshot suitable for status endpoints and operational diagnostics.
|
|
23
53
|
*/
|
|
24
54
|
export declare function createNotificationsPlatformStatusSnapshot(input: NotificationsStatusAdapterInput): NotificationsPlatformStatusSnapshot;
|
package/dist/status.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,+DAA+D;AAC/D,MAAM,MAAM,0BAA0B,GAClC,aAAa,GACb,oBAAoB,GACpB,cAAc,GACd,0BAA0B,GAC1B,cAAc,CAAC;AAEnB,wEAAwE;AACxE,MAAM,WAAW,+BAA+B;IAC9C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,wBAAwB,EAAE,OAAO,CAAC;IAClC,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,yFAAyF;AACzF,MAAM,WAAW,mCAAmC;IAClD,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEvG,+DAA+D;AAC/D,MAAM,MAAM,0BAA0B,GAClC,aAAa,GACb,oBAAoB,GACpB,cAAc,GACd,0BAA0B,GAC1B,cAAc,CAAC;AAEnB,wEAAwE;AACxE,MAAM,WAAW,+BAA+B;IAC9C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,wBAAwB,EAAE,OAAO,CAAC;IAClC,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,8EAA8E;IAC9E,uBAAuB,EAAE,OAAO,CAAC;IACjC,iFAAiF;IACjF,wBAAwB,EAAE,OAAO,CAAC;IAClC,aAAa,EAAE,0BAA0B,CAAC;IAC1C,eAAe,EAAE,OAAO,CAAC;IACzB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B;AAED,yFAAyF;AACzF,MAAM,WAAW,mCAAmC;IAClD,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACzC,OAAO,EAAE,0BAA0B,CAAC;CACrC;AA4ED;;;;;;;;;;;GAWG;AACH,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,+BAA+B,GACrC,mCAAmC,CAuBrC"}
|