@fluojs/notifications 1.0.2 → 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 +43 -8
- package/README.md +43 -8
- 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 +195 -68
- 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 -6
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,73 +388,174 @@ 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`);
|
|
368
397
|
}
|
|
369
|
-
|
|
370
|
-
|
|
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`);
|
|
402
|
+
}
|
|
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`);
|
|
411
|
+
}
|
|
412
|
+
if (!isOwnDataPropertyDescriptor(descriptor)) {
|
|
413
|
+
throw createQueueResultIntegrityError('enqueueMany', `queue id at index ${index} must be an own data property`);
|
|
376
414
|
}
|
|
377
|
-
const entry = value
|
|
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);
|
|
422
|
+
}
|
|
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;
|
|
384
431
|
}
|
|
385
|
-
function
|
|
386
|
-
|
|
387
|
-
error.name = 'NotificationQueueResultIntegrityError';
|
|
388
|
-
return error;
|
|
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 =
|
|
396
|
-
const input = stableStringify(notification);
|
|
440
|
+
let hash = 0xcbf29ce484222325n;
|
|
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');
|
|
447
|
+
}
|
|
448
|
+
function createStableStringifyContext() {
|
|
449
|
+
return {
|
|
450
|
+
nextReferenceId: 0,
|
|
451
|
+
seen: new WeakMap()
|
|
452
|
+
};
|
|
402
453
|
}
|
|
403
|
-
function
|
|
404
|
-
|
|
454
|
+
function enterStableObject(value, context) {
|
|
455
|
+
const existingReferenceId = context.seen.get(value);
|
|
456
|
+
if (existingReferenceId !== undefined) {
|
|
457
|
+
return existingReferenceId;
|
|
458
|
+
}
|
|
459
|
+
context.nextReferenceId += 1;
|
|
460
|
+
context.seen.set(value, context.nextReferenceId);
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
function createCollectionSortContext(parent) {
|
|
464
|
+
const context = createStableStringifyContext();
|
|
465
|
+
context.nextReferenceId = 1;
|
|
466
|
+
context.seen.set(parent, 1);
|
|
467
|
+
return context;
|
|
468
|
+
}
|
|
469
|
+
function stableCollectionSortKey(value, parent) {
|
|
470
|
+
return stableStringify(value, createCollectionSortContext(parent));
|
|
471
|
+
}
|
|
472
|
+
function compareStableString(left, right) {
|
|
473
|
+
if (left < right) {
|
|
474
|
+
return -1;
|
|
475
|
+
}
|
|
476
|
+
if (left > right) {
|
|
477
|
+
return 1;
|
|
478
|
+
}
|
|
479
|
+
return 0;
|
|
480
|
+
}
|
|
481
|
+
function stableStringify(value, context) {
|
|
482
|
+
if (value === null) {
|
|
483
|
+
return 'null';
|
|
484
|
+
}
|
|
485
|
+
if (typeof value !== 'object') {
|
|
486
|
+
if (typeof value === 'bigint') {
|
|
487
|
+
return `BigInt:${value.toString()}`;
|
|
488
|
+
}
|
|
405
489
|
return JSON.stringify(value) ?? String(value);
|
|
406
490
|
}
|
|
491
|
+
const circularReferenceId = enterStableObject(value, context);
|
|
492
|
+
if (circularReferenceId !== undefined) {
|
|
493
|
+
return `Circular:${circularReferenceId}`;
|
|
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
|
+
}
|
|
407
505
|
if (value instanceof Date) {
|
|
408
|
-
|
|
506
|
+
const serialized = Number.isNaN(value.getTime()) ? 'Date:Invalid' : `Date:${JSON.stringify(value.toISOString())}`;
|
|
507
|
+
context.seen.delete(value);
|
|
508
|
+
return serialized;
|
|
409
509
|
}
|
|
410
510
|
if (value instanceof URL) {
|
|
411
|
-
|
|
511
|
+
const serialized = `URL:${JSON.stringify(value.href)}`;
|
|
512
|
+
context.seen.delete(value);
|
|
513
|
+
return serialized;
|
|
412
514
|
}
|
|
413
515
|
if (value instanceof URLSearchParams) {
|
|
414
|
-
|
|
516
|
+
const serialized = `URLSearchParams:${JSON.stringify(value.toString())}`;
|
|
517
|
+
context.seen.delete(value);
|
|
518
|
+
return serialized;
|
|
415
519
|
}
|
|
416
520
|
if (value instanceof RegExp) {
|
|
417
|
-
|
|
521
|
+
const serialized = `RegExp:${JSON.stringify(value.source)}/${value.flags}`;
|
|
522
|
+
context.seen.delete(value);
|
|
523
|
+
return serialized;
|
|
418
524
|
}
|
|
419
525
|
if (value instanceof Map) {
|
|
420
|
-
const entries = Array.from(value.entries()).map(([key, entry]) =>
|
|
526
|
+
const entries = Array.from(value.entries()).map(([key, entry]) => ({
|
|
527
|
+
entry,
|
|
528
|
+
key,
|
|
529
|
+
sortKey: `[${stableCollectionSortKey(key, value)},${stableCollectionSortKey(entry, value)}]`
|
|
530
|
+
})).sort((left, right) => compareStableString(left.sortKey, right.sortKey)).map(({
|
|
531
|
+
key,
|
|
532
|
+
entry
|
|
533
|
+
}) => `[${stableStringify(key, context)},${stableStringify(entry, context)}]`);
|
|
534
|
+
context.seen.delete(value);
|
|
421
535
|
return `Map:{${entries.join(',')}}`;
|
|
422
536
|
}
|
|
423
537
|
if (value instanceof Set) {
|
|
424
|
-
const entries = Array.from(value.values()).map(entry =>
|
|
538
|
+
const entries = Array.from(value.values()).map(entry => ({
|
|
539
|
+
entry,
|
|
540
|
+
sortKey: stableCollectionSortKey(entry, value)
|
|
541
|
+
})).sort((left, right) => compareStableString(left.sortKey, right.sortKey)).map(({
|
|
542
|
+
entry
|
|
543
|
+
}) => stableStringify(entry, context));
|
|
544
|
+
context.seen.delete(value);
|
|
425
545
|
return `Set:[${entries.join(',')}]`;
|
|
426
546
|
}
|
|
427
547
|
if (Array.isArray(value)) {
|
|
428
|
-
|
|
548
|
+
const serialized = `[${value.map(entry => stableStringify(entry, context)).join(',')}]`;
|
|
549
|
+
context.seen.delete(value);
|
|
550
|
+
return serialized;
|
|
429
551
|
}
|
|
430
552
|
const prototype = Object.getPrototypeOf(value);
|
|
431
553
|
const objectTag = prototype && prototype !== Object.prototype ? `${prototype.constructor?.name ?? 'Object'}:` : '';
|
|
432
|
-
const entries = Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => left
|
|
433
|
-
|
|
554
|
+
const entries = Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => compareStableString(left, right));
|
|
555
|
+
const serialized = `${objectTag}{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry, context)}`).join(',')}}`;
|
|
556
|
+
context.seen.delete(value);
|
|
557
|
+
return serialized;
|
|
558
|
+
}
|
|
559
|
+
function stableByteArray(bytes) {
|
|
560
|
+
return `[${Array.from(bytes).join(',')}]`;
|
|
434
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"}
|