@clipboard-health/notifications 0.5.1 → 0.7.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.md +133 -43
- package/package.json +5 -5
- package/src/index.d.ts +2 -2
- package/src/index.js +2 -2
- package/src/index.js.map +1 -1
- package/src/lib/idempotencyKey.d.ts +46 -0
- package/src/lib/idempotencyKey.js +32 -0
- package/src/lib/idempotencyKey.js.map +1 -0
- package/src/lib/internal/chunkRecipients.d.ts +18 -0
- package/src/lib/internal/chunkRecipients.js +28 -0
- package/src/lib/internal/chunkRecipients.js.map +1 -0
- package/src/lib/internal/createDeterministicHash.d.ts +13 -0
- package/src/lib/internal/createDeterministicHash.js +23 -0
- package/src/lib/internal/createDeterministicHash.js.map +1 -0
- package/src/lib/internal/createTriggerLogParams.d.ts +1 -1
- package/src/lib/internal/createTriggerLogParams.js +2 -2
- package/src/lib/internal/createTriggerLogParams.js.map +1 -1
- package/src/lib/internal/createTriggerTraceOptions.js +6 -3
- package/src/lib/internal/createTriggerTraceOptions.js.map +1 -1
- package/src/lib/internal/idempotencyKeyDoNotImportOutsideNotificationsLibrary.d.ts +35 -0
- package/src/lib/internal/idempotencyKeyDoNotImportOutsideNotificationsLibrary.js +43 -0
- package/src/lib/internal/idempotencyKeyDoNotImportOutsideNotificationsLibrary.js.map +1 -0
- package/src/lib/internal/idempotentKnock.d.ts +5 -0
- package/src/lib/internal/idempotentKnock.js +5 -0
- package/src/lib/internal/idempotentKnock.js.map +1 -1
- package/src/lib/internal/toInlineIdentifyUserRequest.d.ts +3 -0
- package/src/lib/internal/toInlineIdentifyUserRequest.js +12 -0
- package/src/lib/internal/toInlineIdentifyUserRequest.js.map +1 -0
- package/src/lib/internal/toInlineIdentifyUserRequestWithoutUserId.d.ts +3 -0
- package/src/lib/internal/toInlineIdentifyUserRequestWithoutUserId.js +18 -0
- package/src/lib/internal/toInlineIdentifyUserRequestWithoutUserId.js.map +1 -0
- package/src/lib/internal/toTenantSetRequest.d.ts +3 -0
- package/src/lib/internal/toTenantSetRequest.js +9 -0
- package/src/lib/internal/toTenantSetRequest.js.map +1 -0
- package/src/lib/internal/toTriggerBody.d.ts +3 -0
- package/src/lib/internal/toTriggerBody.js +21 -0
- package/src/lib/internal/toTriggerBody.js.map +1 -0
- package/src/lib/notificationClient.d.ts +31 -38
- package/src/lib/notificationClient.js +39 -49
- package/src/lib/notificationClient.js.map +1 -1
- package/src/lib/notificationJobEnqueuer.d.ts +96 -0
- package/src/lib/notificationJobEnqueuer.js +101 -0
- package/src/lib/notificationJobEnqueuer.js.map +1 -0
- package/src/lib/types.d.ts +17 -20
- package/src/lib/types.js.map +1 -1
- package/src/lib/chunkRecipients.d.ts +0 -7
- package/src/lib/chunkRecipients.js +0 -18
- package/src/lib/chunkRecipients.js.map +0 -1
- package/src/lib/createIdempotencyKey.d.ts +0 -17
- package/src/lib/createIdempotencyKey.js +0 -28
- package/src/lib/createIdempotencyKey.js.map +0 -1
- package/src/lib/internal/toKnockBody.d.ts +0 -3
- package/src/lib/internal/toKnockBody.js +0 -35
- package/src/lib/internal/toKnockBody.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,7 +6,6 @@ Send notifications through third-party providers.
|
|
|
6
6
|
|
|
7
7
|
- [Install](#install)
|
|
8
8
|
- [Usage](#usage)
|
|
9
|
-
- [`NotificationClient`](#notificationclient)
|
|
10
9
|
- [Local development commands](#local-development-commands)
|
|
11
10
|
|
|
12
11
|
## Install
|
|
@@ -17,49 +16,140 @@ npm install @clipboard-health/notifications
|
|
|
17
16
|
|
|
18
17
|
## Usage
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<embedex source="packages/notifications/examples/
|
|
23
|
-
|
|
24
|
-
```ts
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
19
|
+
1. Export a `NotificationJobEnqueuer` instance:
|
|
20
|
+
|
|
21
|
+
<embedex source="packages/notifications/examples/notificationJobEnqueuer.ts">
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { NotificationJobEnqueuer } from "@clipboard-health/notifications";
|
|
25
|
+
|
|
26
|
+
import { BackgroundJobsService } from "./setup";
|
|
27
|
+
|
|
28
|
+
// Provide this in your microservice.
|
|
29
|
+
export const notificationJobEnqueuer = new NotificationJobEnqueuer({
|
|
30
|
+
adapter: new BackgroundJobsService(),
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
</embedex>
|
|
35
|
+
|
|
36
|
+
1. Enqueue your job:
|
|
37
|
+
|
|
38
|
+
<embedex source="packages/notifications/examples/enqueueNotificationJob.ts">
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { IdempotencyKey } from "@clipboard-health/notifications";
|
|
42
|
+
|
|
43
|
+
import { ExampleNotificationJob } from "./exampleNotification.job";
|
|
44
|
+
import { notificationJobEnqueuer } from "./notificationJobEnqueuer";
|
|
45
|
+
|
|
46
|
+
async function enqueueNotificationJob() {
|
|
47
|
+
await notificationJobEnqueuer.enqueueOneOrMore(ExampleNotificationJob, {
|
|
48
|
+
// Set expiresAt at enqueue-time so it remains stable across job retries.
|
|
49
|
+
expiresAt: minutesFromNow(60),
|
|
50
|
+
// Set idempotencyKey at enqueue-time so it remains stable across job retries.
|
|
51
|
+
idempotencyKey: new IdempotencyKey({
|
|
52
|
+
resourceId: "event-123",
|
|
53
|
+
}),
|
|
54
|
+
// Set recipients at enqueue-time so they respect our notification provider's limits.
|
|
55
|
+
recipients: ["user-1"],
|
|
56
|
+
|
|
57
|
+
workflowKey: "event-starting-reminder",
|
|
58
|
+
|
|
59
|
+
// Any additional enqueue-time data passed to the job:
|
|
60
|
+
workplaceId: "workplace-123",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// eslint-disable-next-line unicorn/prefer-top-level-await
|
|
65
|
+
void enqueueNotificationJob();
|
|
66
|
+
|
|
67
|
+
function minutesFromNow(minutes: number) {
|
|
68
|
+
return new Date(Date.now() + minutes * 60_000);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
</embedex>
|
|
73
|
+
|
|
74
|
+
1. Implement your job, which should be minimal, calling off to a service to send the actual notification:
|
|
75
|
+
|
|
76
|
+
<embedex source="packages/notifications/examples/exampleNotification.job.ts">
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { type BaseHandler } from "@clipboard-health/background-jobs-adapter";
|
|
80
|
+
import {
|
|
81
|
+
errorsInResult,
|
|
82
|
+
type NotificationEnqueueData,
|
|
83
|
+
type NotificationJobData,
|
|
84
|
+
RETRYABLE_ERRORS,
|
|
85
|
+
} from "@clipboard-health/notifications";
|
|
86
|
+
import { toError } from "@clipboard-health/util-ts";
|
|
87
|
+
|
|
88
|
+
import { type ExampleNotificationService } from "./exampleNotification.service";
|
|
89
|
+
|
|
90
|
+
interface ExampleNotificationData {
|
|
91
|
+
workplaceId: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type ExampleNotificationEnqueueData = NotificationEnqueueData & ExampleNotificationData;
|
|
95
|
+
export type ExampleNotificationJobData = NotificationJobData & ExampleNotificationData;
|
|
96
|
+
|
|
97
|
+
export class ExampleNotificationJob implements BaseHandler<ExampleNotificationJobData> {
|
|
98
|
+
constructor(private readonly service: ExampleNotificationService) {}
|
|
99
|
+
|
|
100
|
+
async perform(data: ExampleNotificationJobData, job: { attemptsCount: number }) {
|
|
101
|
+
const result = await this.service.sendNotification({
|
|
102
|
+
...data,
|
|
103
|
+
// Include the job's attempts count for debugging, this is called `retryAttempts` in `background-jobs-postgres`.
|
|
104
|
+
attempt: job.attemptsCount + 1,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (errorsInResult(result, RETRYABLE_ERRORS)) {
|
|
108
|
+
throw toError(result.error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
</embedex>
|
|
115
|
+
|
|
116
|
+
1. Trigger the job in your service:
|
|
117
|
+
|
|
118
|
+
<embedex source="packages/notifications/examples/exampleNotification.service.ts">
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { type NotificationClient } from "@clipboard-health/notifications";
|
|
122
|
+
|
|
123
|
+
import { type ExampleNotificationJobData } from "./exampleNotification.job";
|
|
124
|
+
|
|
125
|
+
type ExampleNotificationDo = ExampleNotificationJobData & { attempt: number };
|
|
126
|
+
|
|
127
|
+
export class ExampleNotificationService {
|
|
128
|
+
constructor(private readonly client: NotificationClient) {}
|
|
129
|
+
|
|
130
|
+
async sendNotification(params: ExampleNotificationDo) {
|
|
131
|
+
const { attempt, expiresAt, idempotencyKey, recipients, workflowKey, workplaceId } = params;
|
|
132
|
+
|
|
133
|
+
// Assume this comes from a database and, for example, are used as template variables...
|
|
134
|
+
const data = { favoriteColor: "blue", secret: "2" };
|
|
135
|
+
|
|
136
|
+
return await this.client.trigger({
|
|
137
|
+
attempt,
|
|
138
|
+
body: {
|
|
139
|
+
recipients,
|
|
140
|
+
data,
|
|
141
|
+
workplaceId,
|
|
142
|
+
},
|
|
143
|
+
expiresAt,
|
|
144
|
+
idempotencyKey,
|
|
145
|
+
workflowKey,
|
|
146
|
+
keysToRedact: ["secret"],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
61
151
|
|
|
62
|
-
</embedex>
|
|
152
|
+
</embedex>
|
|
63
153
|
|
|
64
154
|
## Local development commands
|
|
65
155
|
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clipboard-health/notifications",
|
|
3
3
|
"description": "Send notifications through third-party providers.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"bugs": "https://github.com/ClipboardHealth/core-utils/issues",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@clipboard-health/
|
|
8
|
-
"@clipboard-health/
|
|
7
|
+
"@clipboard-health/background-jobs-adapter": "0.2.0",
|
|
8
|
+
"@clipboard-health/phone-number": "0.6.0",
|
|
9
|
+
"@clipboard-health/util-ts": "3.15.0",
|
|
9
10
|
"@knocklabs/node": "1.16.0",
|
|
10
|
-
"fast-json-stable-stringify": "2.1.0",
|
|
11
11
|
"tslib": "2.8.1"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@clipboard-health/testing-core": "0.
|
|
14
|
+
"@clipboard-health/testing-core": "0.24.0"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [],
|
|
17
17
|
"license": "MIT",
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * from "./lib/chunkRecipients";
|
|
2
|
-
export * from "./lib/createIdempotencyKey";
|
|
3
1
|
export * from "./lib/errorsInResult";
|
|
2
|
+
export * from "./lib/idempotencyKey";
|
|
4
3
|
export * from "./lib/notificationClient";
|
|
4
|
+
export * from "./lib/notificationJobEnqueuer";
|
|
5
5
|
export * from "./lib/types";
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
|
-
tslib_1.__exportStar(require("./lib/chunkRecipients"), exports);
|
|
5
|
-
tslib_1.__exportStar(require("./lib/createIdempotencyKey"), exports);
|
|
6
4
|
tslib_1.__exportStar(require("./lib/errorsInResult"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./lib/idempotencyKey"), exports);
|
|
7
6
|
tslib_1.__exportStar(require("./lib/notificationClient"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./lib/notificationJobEnqueuer"), exports);
|
|
8
8
|
tslib_1.__exportStar(require("./lib/types"), exports);
|
|
9
9
|
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/notifications/src/index.ts"],"names":[],"mappings":";;;AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/notifications/src/index.ts"],"names":[],"mappings":";;;AAAA,+DAAqC;AACrC,+DAAqC;AACrC,mEAAyC;AACzC,wEAA8C;AAC9C,sDAA4B"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface IdempotencyKeyParams {
|
|
2
|
+
/**
|
|
3
|
+
* Prefer `resourceId` over `eventOccurredAt`; it's harder to misuse.
|
|
4
|
+
*
|
|
5
|
+
* If an event triggered your workflow and it doesn't have a unique ID, you may decide to use its
|
|
6
|
+
* occurrence timestamp. For example, if you have a daily CRON job, use the date it ran.
|
|
7
|
+
*
|
|
8
|
+
* Take care when using `Date.now()` or `new Date()`. These change each time your job runs, which
|
|
9
|
+
* means the idempotency key will always be different and doesn't prevent duplicate notifications.
|
|
10
|
+
*/
|
|
11
|
+
eventOccurredAt?: Date | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* If a resource triggered your workflow, include its unique ID.
|
|
14
|
+
*
|
|
15
|
+
* @note `recipients` (and `workplaceId` if it exists) are included in the idempotency key
|
|
16
|
+
* automatically from the trigger body.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* 1. For a "meeting starts in one hour" notification, set resourceId to the meeting ID.
|
|
20
|
+
* 2. For a payout notification, set resourceId to the payment ID.
|
|
21
|
+
*/
|
|
22
|
+
resourceId?: string | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Idempotency keys prevent duplicate notifications. They should be deterministic and remain the
|
|
26
|
+
* same across retry logic.
|
|
27
|
+
*
|
|
28
|
+
* If you retry a request with the same idempotency key within 24 hours, the client returns the same
|
|
29
|
+
* response as the original request.
|
|
30
|
+
*
|
|
31
|
+
* @note `recipients` (and `workplaceId` if it exists) are included in the idempotency key
|
|
32
|
+
* automatically from the trigger body.
|
|
33
|
+
*
|
|
34
|
+
* We provide this class because idempotency keys can be difficult to use correctly. If the key
|
|
35
|
+
* changes on each retry (e.g., Date.now() or uuid.v4()), it won't prevent duplicate notifications.
|
|
36
|
+
* Conversely, if you don't provide enough information, you prevent recipients from receiving
|
|
37
|
+
* notifications they otherwise should have. For example, if you use the trigger key and the
|
|
38
|
+
* recipient's ID as the idempotency key, but it's possible the recipient could receive the same
|
|
39
|
+
* notification multiple times within the idempotency key's validity window, the recipient will only
|
|
40
|
+
* receive the first notification.
|
|
41
|
+
*/
|
|
42
|
+
export declare class IdempotencyKey {
|
|
43
|
+
protected readonly eventOccurredAt: IdempotencyKeyParams["eventOccurredAt"];
|
|
44
|
+
protected readonly resourceId: IdempotencyKeyParams["resourceId"];
|
|
45
|
+
constructor(params: IdempotencyKeyParams);
|
|
46
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IdempotencyKey = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Idempotency keys prevent duplicate notifications. They should be deterministic and remain the
|
|
6
|
+
* same across retry logic.
|
|
7
|
+
*
|
|
8
|
+
* If you retry a request with the same idempotency key within 24 hours, the client returns the same
|
|
9
|
+
* response as the original request.
|
|
10
|
+
*
|
|
11
|
+
* @note `recipients` (and `workplaceId` if it exists) are included in the idempotency key
|
|
12
|
+
* automatically from the trigger body.
|
|
13
|
+
*
|
|
14
|
+
* We provide this class because idempotency keys can be difficult to use correctly. If the key
|
|
15
|
+
* changes on each retry (e.g., Date.now() or uuid.v4()), it won't prevent duplicate notifications.
|
|
16
|
+
* Conversely, if you don't provide enough information, you prevent recipients from receiving
|
|
17
|
+
* notifications they otherwise should have. For example, if you use the trigger key and the
|
|
18
|
+
* recipient's ID as the idempotency key, but it's possible the recipient could receive the same
|
|
19
|
+
* notification multiple times within the idempotency key's validity window, the recipient will only
|
|
20
|
+
* receive the first notification.
|
|
21
|
+
*/
|
|
22
|
+
class IdempotencyKey {
|
|
23
|
+
eventOccurredAt;
|
|
24
|
+
resourceId;
|
|
25
|
+
constructor(params) {
|
|
26
|
+
const { eventOccurredAt, resourceId } = params;
|
|
27
|
+
this.eventOccurredAt = eventOccurredAt;
|
|
28
|
+
this.resourceId = resourceId;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.IdempotencyKey = IdempotencyKey;
|
|
32
|
+
//# sourceMappingURL=idempotencyKey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotencyKey.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/idempotencyKey.ts"],"names":[],"mappings":";;;AAyBA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAa,cAAc;IACN,eAAe,CAA0C;IACzD,UAAU,CAAqC;IAElE,YAAmB,MAA4B;QAC7C,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;QAE/C,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AAVD,wCAUC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chunks recipients into groups that don't exceed the maximum recipients count,
|
|
3
|
+
* assigning a unique chunk number to each group.
|
|
4
|
+
*
|
|
5
|
+
* This function is used to split large recipient lists into smaller batches that
|
|
6
|
+
* comply with notification provider limits. Each chunk is numbered sequentially
|
|
7
|
+
* starting from 1.
|
|
8
|
+
*
|
|
9
|
+
* @param params - The chunking parameters
|
|
10
|
+
* @param params.recipients - Array of recipient IDs to chunk
|
|
11
|
+
* @returns Array of chunks, each containing a chunk number and recipients array
|
|
12
|
+
*/
|
|
13
|
+
export declare function chunkRecipients(params: {
|
|
14
|
+
recipients: string[];
|
|
15
|
+
}): Array<{
|
|
16
|
+
number: number;
|
|
17
|
+
recipients: string[];
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.chunkRecipients = chunkRecipients;
|
|
4
|
+
const util_ts_1 = require("@clipboard-health/util-ts");
|
|
5
|
+
const notificationClient_1 = require("../notificationClient");
|
|
6
|
+
/**
|
|
7
|
+
* Chunks recipients into groups that don't exceed the maximum recipients count,
|
|
8
|
+
* assigning a unique chunk number to each group.
|
|
9
|
+
*
|
|
10
|
+
* This function is used to split large recipient lists into smaller batches that
|
|
11
|
+
* comply with notification provider limits. Each chunk is numbered sequentially
|
|
12
|
+
* starting from 1.
|
|
13
|
+
*
|
|
14
|
+
* @param params - The chunking parameters
|
|
15
|
+
* @param params.recipients - Array of recipient IDs to chunk
|
|
16
|
+
* @returns Array of chunks, each containing a chunk number and recipients array
|
|
17
|
+
*/
|
|
18
|
+
function chunkRecipients(params) {
|
|
19
|
+
const { recipients } = params;
|
|
20
|
+
if (recipients.length === 0) {
|
|
21
|
+
return [{ number: 1, recipients: [] }];
|
|
22
|
+
}
|
|
23
|
+
return (0, util_ts_1.chunk)(recipients, notificationClient_1.MAXIMUM_RECIPIENTS_COUNT).map((recipientsChunk, index) => ({
|
|
24
|
+
number: index + 1,
|
|
25
|
+
recipients: recipientsChunk,
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=chunkRecipients.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunkRecipients.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/chunkRecipients.ts"],"names":[],"mappings":";;AAgBA,0CAaC;AA7BD,uDAAkD;AAElD,8DAAiE;AAEjE;;;;;;;;;;;GAWG;AACH,SAAgB,eAAe,CAAC,MAE/B;IACC,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IAE9B,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,IAAA,eAAK,EAAC,UAAU,EAAE,6CAAwB,CAAC,CAAC,GAAG,CAAC,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAClF,MAAM,EAAE,KAAK,GAAG,CAAC;QACjB,UAAU,EAAE,eAAe;KAC5B,CAAC,CAAC,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a deterministic hash.
|
|
3
|
+
*
|
|
4
|
+
* Normalizes `value` using stable JSON serialization for non-string values and generates
|
|
5
|
+
* a SHA-256 hash.
|
|
6
|
+
*
|
|
7
|
+
* Note: Non-JSON-serializable values (undefined, Symbol, functions, circular refs) may be
|
|
8
|
+
* dropped or cause errors during serialization.
|
|
9
|
+
*
|
|
10
|
+
* @param value - Value to hash (string, string[], object, etc.).
|
|
11
|
+
* @returns A hex string representing the hash.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createDeterministicHash(value: unknown): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDeterministicHash = createDeterministicHash;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const util_ts_1 = require("@clipboard-health/util-ts");
|
|
6
|
+
/**
|
|
7
|
+
* Creates a deterministic hash.
|
|
8
|
+
*
|
|
9
|
+
* Normalizes `value` using stable JSON serialization for non-string values and generates
|
|
10
|
+
* a SHA-256 hash.
|
|
11
|
+
*
|
|
12
|
+
* Note: Non-JSON-serializable values (undefined, Symbol, functions, circular refs) may be
|
|
13
|
+
* dropped or cause errors during serialization.
|
|
14
|
+
*
|
|
15
|
+
* @param value - Value to hash (string, string[], object, etc.).
|
|
16
|
+
* @returns A hex string representing the hash.
|
|
17
|
+
*/
|
|
18
|
+
function createDeterministicHash(value) {
|
|
19
|
+
return (0, node_crypto_1.createHash)("sha256")
|
|
20
|
+
.update(typeof value === "string" ? value : (0, util_ts_1.stringify)(value))
|
|
21
|
+
.digest("hex");
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=createDeterministicHash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createDeterministicHash.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/createDeterministicHash.ts"],"names":[],"mappings":";;AAgBA,0DAIC;AApBD,6CAAyC;AAEzC,uDAAsD;AAEtD;;;;;;;;;;;GAWG;AACH,SAAgB,uBAAuB,CAAC,KAAc;IACpD,OAAO,IAAA,wBAAU,EAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAA,mBAAS,EAAC,KAAK,CAAC,CAAC;SAC5D,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { LogParams, TriggerRequest } from "../types";
|
|
2
|
-
export type TriggerLogContext = LogParams & Pick<TriggerRequest, "attempt" | "idempotencyKey" | "
|
|
2
|
+
export type TriggerLogContext = LogParams & Pick<TriggerRequest, "attempt" | "idempotencyKey" | "workflowKey">;
|
|
3
3
|
export declare function createTriggerLogParams(params: TriggerRequest & LogParams): TriggerLogContext;
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createTriggerLogParams = createTriggerLogParams;
|
|
4
4
|
function createTriggerLogParams(params) {
|
|
5
|
-
const { attempt, destination, idempotencyKey, key, traceName } = params;
|
|
5
|
+
const { attempt, destination, idempotencyKey, key, workflowKey, traceName } = params;
|
|
6
6
|
return {
|
|
7
7
|
attempt,
|
|
8
8
|
destination,
|
|
9
9
|
idempotencyKey,
|
|
10
|
-
key,
|
|
10
|
+
workflowKey: /* istanbul ignore next */ workflowKey ?? key,
|
|
11
11
|
traceName,
|
|
12
12
|
};
|
|
13
13
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createTriggerLogParams.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/createTriggerLogParams.ts"],"names":[],"mappings":";;AAKA,wDAUC;AAVD,SAAgB,sBAAsB,CAAC,MAAkC;IACvE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"createTriggerLogParams.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/createTriggerLogParams.ts"],"names":[],"mappings":";;AAKA,wDAUC;AAVD,SAAgB,sBAAsB,CAAC,MAAkC;IACvE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAErF,OAAO;QACL,OAAO;QACP,WAAW;QACX,cAAc;QACd,WAAW,EAAE,0BAA0B,CAAC,WAAW,IAAI,GAAG;QAC1D,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createTriggerTraceOptions = createTriggerTraceOptions;
|
|
4
4
|
function createTriggerTraceOptions(params) {
|
|
5
|
-
const {
|
|
5
|
+
const { workflowKey, attempt, destination } = params;
|
|
6
6
|
return {
|
|
7
|
-
resource: `notification.${
|
|
8
|
-
|
|
7
|
+
resource: `notification.${workflowKey}`,
|
|
8
|
+
/**
|
|
9
|
+
* Don't include high cardinality tags like expiresAt and idempotencyKey to reduce Datadog
|
|
10
|
+
* costs.
|
|
11
|
+
*/
|
|
9
12
|
tags: {
|
|
10
13
|
"span.kind": "producer",
|
|
11
14
|
component: "customer-notifications",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createTriggerTraceOptions.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/createTriggerTraceOptions.ts"],"names":[],"mappings":";;AAGA,
|
|
1
|
+
{"version":3,"file":"createTriggerTraceOptions.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/createTriggerTraceOptions.ts"],"names":[],"mappings":";;AAGA,8DAkBC;AAlBD,SAAgB,yBAAyB,CAAC,MAAyB;IACjE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAErD,OAAO;QACL,QAAQ,EAAE,gBAAgB,WAAW,EAAE;QACvC;;;WAGG;QACH,IAAI,EAAE;YACJ,WAAW,EAAE,UAAU;YACvB,SAAS,EAAE,wBAAwB;YACnC,kBAAkB,EAAE,WAAW;YAC/B,qBAAqB,EAAE,SAAS;YAChC,uBAAuB,EAAE,WAAW;YACpC,sBAAsB,EAAE,OAAO,CAAC,QAAQ,EAAE;SAC3C;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { IdempotencyKey, type IdempotencyKeyParams } from "../idempotencyKey";
|
|
2
|
+
interface IdempotencyKeyDoNotImportOutsideNotificationsLibraryParams extends IdempotencyKeyParams {
|
|
3
|
+
/**
|
|
4
|
+
* The recipient chunk number.
|
|
5
|
+
*/
|
|
6
|
+
chunk: number;
|
|
7
|
+
/**
|
|
8
|
+
* The recipients in the chunk; maximum of MAXIMUM_RECIPIENTS_COUNT.
|
|
9
|
+
*/
|
|
10
|
+
recipients: string[];
|
|
11
|
+
/**
|
|
12
|
+
* The workflow key.
|
|
13
|
+
*/
|
|
14
|
+
workflowKey: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Idempotency keys prevent duplicate notifications. `NotificationClient.trigger` should be called
|
|
18
|
+
* after properly enqueuing a job using `NotificationJobEnqueuer.enqueueOneOrMore` to help ensure
|
|
19
|
+
* we're following best practices so customers don't receive duplicate or stale notifications.
|
|
20
|
+
*
|
|
21
|
+
* Yes, you could import this class into your service and call `NotificationClient.trigger`
|
|
22
|
+
* directly. We're using the honor system in hopes that enforcement is unnecessary.
|
|
23
|
+
*
|
|
24
|
+
* @see {@link NotificationJobEnqueuer.enqueueOneOrMore}.
|
|
25
|
+
*/
|
|
26
|
+
export declare class IdempotencyKeyDoNotImportOutsideNotificationsLibrary extends IdempotencyKey {
|
|
27
|
+
private readonly chunk;
|
|
28
|
+
private readonly recipients;
|
|
29
|
+
private readonly workflowKey;
|
|
30
|
+
constructor(params: IdempotencyKeyDoNotImportOutsideNotificationsLibraryParams);
|
|
31
|
+
toHash(params: {
|
|
32
|
+
workplaceId?: string | undefined;
|
|
33
|
+
}): string;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IdempotencyKeyDoNotImportOutsideNotificationsLibrary = void 0;
|
|
4
|
+
const util_ts_1 = require("@clipboard-health/util-ts");
|
|
5
|
+
const idempotencyKey_1 = require("../idempotencyKey");
|
|
6
|
+
const createDeterministicHash_1 = require("./createDeterministicHash");
|
|
7
|
+
/**
|
|
8
|
+
* Idempotency keys prevent duplicate notifications. `NotificationClient.trigger` should be called
|
|
9
|
+
* after properly enqueuing a job using `NotificationJobEnqueuer.enqueueOneOrMore` to help ensure
|
|
10
|
+
* we're following best practices so customers don't receive duplicate or stale notifications.
|
|
11
|
+
*
|
|
12
|
+
* Yes, you could import this class into your service and call `NotificationClient.trigger`
|
|
13
|
+
* directly. We're using the honor system in hopes that enforcement is unnecessary.
|
|
14
|
+
*
|
|
15
|
+
* @see {@link NotificationJobEnqueuer.enqueueOneOrMore}.
|
|
16
|
+
*/
|
|
17
|
+
class IdempotencyKeyDoNotImportOutsideNotificationsLibrary extends idempotencyKey_1.IdempotencyKey {
|
|
18
|
+
chunk;
|
|
19
|
+
recipients;
|
|
20
|
+
workflowKey;
|
|
21
|
+
constructor(params) {
|
|
22
|
+
const { chunk, recipients, workflowKey, ...rest } = params;
|
|
23
|
+
super(rest);
|
|
24
|
+
this.chunk = chunk;
|
|
25
|
+
this.recipients = recipients;
|
|
26
|
+
this.workflowKey = workflowKey;
|
|
27
|
+
}
|
|
28
|
+
toHash(params) {
|
|
29
|
+
const { workplaceId } = params;
|
|
30
|
+
return (0, createDeterministicHash_1.createDeterministicHash)([
|
|
31
|
+
this.workflowKey,
|
|
32
|
+
this.chunk,
|
|
33
|
+
this.resourceId,
|
|
34
|
+
this.eventOccurredAt?.toISOString(),
|
|
35
|
+
this.recipients.join(","),
|
|
36
|
+
workplaceId,
|
|
37
|
+
]
|
|
38
|
+
.filter(util_ts_1.isDefined)
|
|
39
|
+
.join(","));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.IdempotencyKeyDoNotImportOutsideNotificationsLibrary = IdempotencyKeyDoNotImportOutsideNotificationsLibrary;
|
|
43
|
+
//# sourceMappingURL=idempotencyKeyDoNotImportOutsideNotificationsLibrary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotencyKeyDoNotImportOutsideNotificationsLibrary.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/idempotencyKeyDoNotImportOutsideNotificationsLibrary.ts"],"names":[],"mappings":";;;AAAA,uDAAsD;AAEtD,sDAA8E;AAK9E,uEAAoE;AAmBpE;;;;;;;;;GASG;AACH,MAAa,oDAAqD,SAAQ,+BAAc;IACrE,KAAK,CAAsE;IAC3E,UAAU,CAA2E;IACrF,WAAW,CAA4E;IAExG,YAAY,MAAkE;QAC5E,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC;QAE3D,KAAK,CAAC,IAAI,CAAC,CAAC;QACZ,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;IAED,MAAM,CAAC,MAA4C;QACjD,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;QAE/B,OAAO,IAAA,iDAAuB,EAC5B;YACE,IAAI,CAAC,WAAW;YAChB,IAAI,CAAC,KAAK;YACV,IAAI,CAAC,UAAU;YACf,IAAI,CAAC,eAAe,EAAE,WAAW,EAAE;YACnC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;YACzB,WAAW;SACZ;aACE,MAAM,CAAC,mBAAS,CAAC;aACjB,IAAI,CAAC,GAAG,CAAC,CACb,CAAC;IACJ,CAAC;CACF;AA9BD,oHA8BC"}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { type Logger } from "@clipboard-health/util-ts";
|
|
2
2
|
import { Knock } from "@knocklabs/node";
|
|
3
|
+
/**
|
|
4
|
+
* Extended Knock client with custom configuration for idempotent operations.
|
|
5
|
+
*
|
|
6
|
+
* @see {@link https://docs.knock.app/api-reference/overview/idempotent-requests}
|
|
7
|
+
*/
|
|
3
8
|
export declare class IdempotentKnock extends Knock {
|
|
4
9
|
logLevel: "warn";
|
|
5
10
|
maxRetries: number;
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.IdempotentKnock = void 0;
|
|
4
4
|
const node_1 = require("@knocklabs/node");
|
|
5
|
+
/**
|
|
6
|
+
* Extended Knock client with custom configuration for idempotent operations.
|
|
7
|
+
*
|
|
8
|
+
* @see {@link https://docs.knock.app/api-reference/overview/idempotent-requests}
|
|
9
|
+
*/
|
|
5
10
|
class IdempotentKnock extends node_1.Knock {
|
|
6
11
|
logLevel = "warn"; // Knock's default.
|
|
7
12
|
maxRetries = 1; // Knock's default is 2, but we rely on background job retries.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idempotentKnock.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/idempotentKnock.ts"],"names":[],"mappings":";;;AACA,0CAAwC;AAExC,MAAa,eAAgB,SAAQ,YAAK;IACxB,QAAQ,GAAG,MAAe,CAAC,CAAC,mBAAmB;IAC/C,UAAU,GAAG,CAAC,CAAC,CAAC,+DAA+D;IAC/E,OAAO,GAAG,MAAM,CAAC,CAAC,mBAAmB;IAElC,iBAAiB,GAAG,iBAAiB,CAAC;IAEzD,YAAY,MAA0C;QACpD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;QAElC,KAAK,CAAC;YACJ,MAAM;YACN,MAAM,EAAE;gBACN,GAAG,MAAM;gBACT,KAAK,EAAE,MAAM,CAAC,IAAI;aACnB;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAlBD,0CAkBC"}
|
|
1
|
+
{"version":3,"file":"idempotentKnock.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/idempotentKnock.ts"],"names":[],"mappings":";;;AACA,0CAAwC;AAExC;;;;GAIG;AACH,MAAa,eAAgB,SAAQ,YAAK;IACxB,QAAQ,GAAG,MAAe,CAAC,CAAC,mBAAmB;IAC/C,UAAU,GAAG,CAAC,CAAC,CAAC,+DAA+D;IAC/E,OAAO,GAAG,MAAM,CAAC,CAAC,mBAAmB;IAElC,iBAAiB,GAAG,iBAAiB,CAAC;IAEzD,YAAY,MAA0C;QACpD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;QAElC,KAAK,CAAC;YACJ,MAAM;YACN,MAAM,EAAE;gBACN,GAAG,MAAM;gBACT,KAAK,EAAE,MAAM,CAAC,IAAI;aACnB;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAlBD,0CAkBC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toInlineIdentifyUserRequest = toInlineIdentifyUserRequest;
|
|
4
|
+
const toInlineIdentifyUserRequestWithoutUserId_1 = require("./toInlineIdentifyUserRequestWithoutUserId");
|
|
5
|
+
function toInlineIdentifyUserRequest(recipient) {
|
|
6
|
+
const { userId, ...rest } = recipient;
|
|
7
|
+
return {
|
|
8
|
+
...(0, toInlineIdentifyUserRequestWithoutUserId_1.toInlineIdentifyUserRequestWithoutUserId)(rest),
|
|
9
|
+
id: userId,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=toInlineIdentifyUserRequest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toInlineIdentifyUserRequest.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/toInlineIdentifyUserRequest.ts"],"names":[],"mappings":";;AAKA,kEASC;AAXD,yGAAsG;AAEtG,SAAgB,2BAA2B,CACzC,SAAoC;IAEpC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,SAAS,CAAC;IAEtC,OAAO;QACL,GAAG,IAAA,mFAAwC,EAAC,IAAI,CAAC;QACjD,EAAE,EAAE,MAAM;KACX,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type Knock } from "@knocklabs/node";
|
|
2
|
+
import type { InlineIdentifyUserRequest } from "../types";
|
|
3
|
+
export declare function toInlineIdentifyUserRequestWithoutUserId(recipient: Omit<InlineIdentifyUserRequest, "userId">): Omit<Knock.Users.InlineIdentifyUserRequest, "id">;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toInlineIdentifyUserRequestWithoutUserId = toInlineIdentifyUserRequestWithoutUserId;
|
|
4
|
+
const formatPhoneNumber_1 = require("./formatPhoneNumber");
|
|
5
|
+
function toInlineIdentifyUserRequestWithoutUserId(recipient) {
|
|
6
|
+
const { channelData, createdAt, email, name, phoneNumber, timeZone, customProperties, ...rest } = recipient;
|
|
7
|
+
return {
|
|
8
|
+
...rest,
|
|
9
|
+
...customProperties,
|
|
10
|
+
...(channelData ? { channel_data: channelData } : {}),
|
|
11
|
+
...(createdAt ? { created_at: createdAt.toISOString() } : {}),
|
|
12
|
+
...(email ? { email } : {}),
|
|
13
|
+
...(name ? { name } : {}),
|
|
14
|
+
...(phoneNumber ? { phone_number: (0, formatPhoneNumber_1.formatPhoneNumber)({ phoneNumber }) } : {}),
|
|
15
|
+
...(timeZone ? { timezone: timeZone } : {}),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=toInlineIdentifyUserRequestWithoutUserId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toInlineIdentifyUserRequestWithoutUserId.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/toInlineIdentifyUserRequestWithoutUserId.ts"],"names":[],"mappings":";;AAKA,4FAgBC;AAlBD,2DAAwD;AAExD,SAAgB,wCAAwC,CACtD,SAAoD;IAEpD,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,gBAAgB,EAAE,GAAG,IAAI,EAAE,GAC7F,SAAS,CAAC;IAEZ,OAAO;QACL,GAAG,IAAI;QACP,GAAG,gBAAgB;QACnB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAA,qCAAiB,EAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5C,CAAC;AACJ,CAAC"}
|