@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/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 requestedPublicationError = await this.publishLifecycleEventBestEffort('notification.dispatch.requested', notification, options);
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(notification.channel);
62
+ this.requireChannel(dispatchNotification.channel);
57
63
  } catch (error) {
58
- await this.publishFailureLifecycleEvent(notification, options, error, requestedPublicationError);
64
+ await this.publishFailureLifecycleEvent(dispatchNotification, options, error, requestedPublicationError);
59
65
  throw error;
60
66
  }
61
- const job = this.createQueueJob(notification);
67
+ const job = this.createQueueJob(dispatchNotification);
62
68
  try {
63
- const deliveryId = await this.requireQueueAdapter().enqueue(job);
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: notification.channel,
66
- deliveryId: this.normalizeDeliveryId(deliveryId, notification),
75
+ channel: dispatchNotification.channel,
76
+ deliveryId,
67
77
  queued: true,
68
78
  status: 'queued'
69
79
  };
70
- await this.publishLifecycleEventBestEffort('notification.dispatch.queued', notification, options, result.deliveryId);
80
+ await this.publishLifecycleEventBestEffort('notification.dispatch.queued', dispatchNotification, options, result.deliveryId);
71
81
  return result;
72
82
  } catch (error) {
73
- await this.publishFailureLifecycleEvent(notification, options, error, requestedPublicationError);
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(notification.channel);
89
+ channel = this.requireChannel(dispatchNotification.channel);
80
90
  } catch (error) {
81
- await this.publishFailureLifecycleEvent(notification, options, error, requestedPublicationError);
91
+ await this.publishFailureLifecycleEvent(dispatchNotification, options, error, requestedPublicationError);
82
92
  throw error;
83
93
  }
84
94
  try {
85
- const delivery = await channel.send(notification, {
95
+ const delivery = await channel.send(dispatchNotification, {
86
96
  signal: options.signal
87
97
  });
88
98
  const result = {
89
- channel: notification.channel,
90
- deliveryId: this.normalizeDeliveryId(delivery.externalId, notification),
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', notification, options, result.deliveryId);
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(notification, options, error, requestedPublicationError);
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(notifications, options);
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(notifications, options, error, requestedPublicationErrors);
140
+ await this.publishFailureLifecycleEvents(dispatchNotifications, options, error, requestedPublicationErrors);
129
141
  throw error;
130
142
  }
131
143
  try {
132
- for (const notification of notifications) {
144
+ for (const notification of dispatchNotifications) {
133
145
  this.requireChannel(notification.channel);
134
146
  }
135
147
  } catch (error) {
136
- await this.publishFailureLifecycleEvents(notifications, options, error, requestedPublicationErrors);
148
+ await this.publishFailureLifecycleEvents(dispatchNotifications, options, error, requestedPublicationErrors);
137
149
  throw error;
138
150
  }
139
- const jobs = notifications.map(notification => this.createQueueJob(notification));
151
+ const jobs = dispatchNotifications.map(notification => this.createQueueJob(notification));
140
152
  if (!queue.enqueueMany) {
141
- return this.dispatchManyThroughSequentialQueueFallback(notifications, jobs, options, requestedPublicationErrors);
153
+ return this.dispatchManyThroughSequentialQueueFallback(dispatchNotifications, jobs, options, requestedPublicationErrors);
142
154
  }
143
- let ids;
155
+ const admittedJobCount = jobs.length;
156
+ let results;
144
157
  try {
145
- ids = validateQueueBatchDeliveryIds(await queue.enqueueMany(jobs), jobs.length);
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(notifications, options, error, requestedPublicationErrors);
175
+ await this.publishFailureLifecycleEvents(dispatchNotifications, options, error, requestedPublicationErrors);
148
176
  throw error;
149
177
  }
150
- const results = notifications.map((notification, index) => ({
151
- channel: notification.channel,
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 notifications) {
192
+ for (const notification of dispatchNotifications) {
171
193
  try {
172
- results.push(await this.dispatch(notification, options));
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
- const deliveryId = this.normalizeDeliveryId(await queue.enqueue(job), notification);
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 createQueueBatchResultIntegrityError(`expected ${expectedCount} queue ids but received a non-array result`);
396
+ throw createQueueResultIntegrityError('enqueueMany', `expected ${expectedCount} queue ids but received a non-array result`);
368
397
  }
369
- if (value.length !== expectedCount) {
370
- throw createQueueBatchResultIntegrityError(`expected ${expectedCount} queue ids but received ${value.length}`);
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 < value.length; index += 1) {
374
- if (!Object.hasOwn(value, index)) {
375
- throw createQueueBatchResultIntegrityError(`queue id at index ${index} must be present`);
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[index];
415
+ const entry = descriptor.value;
378
416
  if (typeof entry !== 'string' || entry.length === 0) {
379
- throw createQueueBatchResultIntegrityError(`queue id at index ${index} must be a non-empty string`);
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 createQueueBatchResultIntegrityError(message) {
386
- const error = new Error(`Notifications queue adapter returned an invalid enqueueMany() result: ${message}.`);
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 = 0x811c9dc5;
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 = Math.imul(hash, 0x01000193) >>> 0;
443
+ hash ^= BigInt(input.charCodeAt(index));
444
+ hash = BigInt.asUintN(64, hash * 0x100000001b3n);
400
445
  }
401
- return hash.toString(36).padStart(7, '0');
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 stableStringify(value) {
404
- if (value === null || typeof value !== 'object') {
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
- return Number.isNaN(value.getTime()) ? 'Date:Invalid' : `Date:${JSON.stringify(value.toISOString())}`;
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
- return `URL:${JSON.stringify(value.href)}`;
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
- return `URLSearchParams:${JSON.stringify(value.toString())}`;
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
- return `RegExp:${JSON.stringify(value.source)}/${value.flags}`;
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]) => `[${stableStringify(key)},${stableStringify(entry)}]`).sort();
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 => stableStringify(entry)).sort();
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
- return `[${value.map(entry => stableStringify(entry)).join(',')}]`;
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.localeCompare(right));
433
- return `${objectTag}{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(',')}}`;
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"}