@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.
Files changed (54) hide show
  1. package/README.md +133 -43
  2. package/package.json +5 -5
  3. package/src/index.d.ts +2 -2
  4. package/src/index.js +2 -2
  5. package/src/index.js.map +1 -1
  6. package/src/lib/idempotencyKey.d.ts +46 -0
  7. package/src/lib/idempotencyKey.js +32 -0
  8. package/src/lib/idempotencyKey.js.map +1 -0
  9. package/src/lib/internal/chunkRecipients.d.ts +18 -0
  10. package/src/lib/internal/chunkRecipients.js +28 -0
  11. package/src/lib/internal/chunkRecipients.js.map +1 -0
  12. package/src/lib/internal/createDeterministicHash.d.ts +13 -0
  13. package/src/lib/internal/createDeterministicHash.js +23 -0
  14. package/src/lib/internal/createDeterministicHash.js.map +1 -0
  15. package/src/lib/internal/createTriggerLogParams.d.ts +1 -1
  16. package/src/lib/internal/createTriggerLogParams.js +2 -2
  17. package/src/lib/internal/createTriggerLogParams.js.map +1 -1
  18. package/src/lib/internal/createTriggerTraceOptions.js +6 -3
  19. package/src/lib/internal/createTriggerTraceOptions.js.map +1 -1
  20. package/src/lib/internal/idempotencyKeyDoNotImportOutsideNotificationsLibrary.d.ts +35 -0
  21. package/src/lib/internal/idempotencyKeyDoNotImportOutsideNotificationsLibrary.js +43 -0
  22. package/src/lib/internal/idempotencyKeyDoNotImportOutsideNotificationsLibrary.js.map +1 -0
  23. package/src/lib/internal/idempotentKnock.d.ts +5 -0
  24. package/src/lib/internal/idempotentKnock.js +5 -0
  25. package/src/lib/internal/idempotentKnock.js.map +1 -1
  26. package/src/lib/internal/toInlineIdentifyUserRequest.d.ts +3 -0
  27. package/src/lib/internal/toInlineIdentifyUserRequest.js +12 -0
  28. package/src/lib/internal/toInlineIdentifyUserRequest.js.map +1 -0
  29. package/src/lib/internal/toInlineIdentifyUserRequestWithoutUserId.d.ts +3 -0
  30. package/src/lib/internal/toInlineIdentifyUserRequestWithoutUserId.js +18 -0
  31. package/src/lib/internal/toInlineIdentifyUserRequestWithoutUserId.js.map +1 -0
  32. package/src/lib/internal/toTenantSetRequest.d.ts +3 -0
  33. package/src/lib/internal/toTenantSetRequest.js +9 -0
  34. package/src/lib/internal/toTenantSetRequest.js.map +1 -0
  35. package/src/lib/internal/toTriggerBody.d.ts +3 -0
  36. package/src/lib/internal/toTriggerBody.js +21 -0
  37. package/src/lib/internal/toTriggerBody.js.map +1 -0
  38. package/src/lib/notificationClient.d.ts +31 -38
  39. package/src/lib/notificationClient.js +39 -49
  40. package/src/lib/notificationClient.js.map +1 -1
  41. package/src/lib/notificationJobEnqueuer.d.ts +96 -0
  42. package/src/lib/notificationJobEnqueuer.js +101 -0
  43. package/src/lib/notificationJobEnqueuer.js.map +1 -0
  44. package/src/lib/types.d.ts +17 -20
  45. package/src/lib/types.js.map +1 -1
  46. package/src/lib/chunkRecipients.d.ts +0 -7
  47. package/src/lib/chunkRecipients.js +0 -18
  48. package/src/lib/chunkRecipients.js.map +0 -1
  49. package/src/lib/createIdempotencyKey.d.ts +0 -17
  50. package/src/lib/createIdempotencyKey.js +0 -28
  51. package/src/lib/createIdempotencyKey.js.map +0 -1
  52. package/src/lib/internal/toKnockBody.d.ts +0 -3
  53. package/src/lib/internal/toKnockBody.js +0 -35
  54. 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
- ### `NotificationClient`
21
-
22
- <embedex source="packages/notifications/examples/notificationClient.ts">
23
-
24
- ```ts
25
- import { NotificationClient, type Span } from "@clipboard-health/notifications";
26
- import { isSuccess } from "@clipboard-health/util-ts";
27
-
28
- const client = new NotificationClient({
29
- apiKey: "test-api-key",
30
- logger: {
31
- info: console.log,
32
- warn: console.warn,
33
- error: console.error,
34
- } as const,
35
- tracer: {
36
- trace: <T>(_name: string, _options: unknown, fun: (span?: Span | undefined) => T): T => fun(),
37
- },
38
- });
39
-
40
- async function triggerNotification(job: { attemptsCount: number }) {
41
- const result = await client.trigger({
42
- attempt: (job?.attemptsCount ?? 0) + 1,
43
- body: {
44
- recipients: ["user-1"],
45
- data: { favoriteColor: "blue", secret: "2" },
46
- },
47
- expiresAt: new Date(Date.now() + 300_000), // 5 minutes
48
- idempotencyKey: "welcome-user-4",
49
- key: "welcome-email",
50
- keysToRedact: ["secret"],
51
- });
52
-
53
- if (isSuccess(result)) {
54
- console.log("Notification sent:", result.value.id);
55
- }
56
- }
57
-
58
- // eslint-disable-next-line unicorn/prefer-top-level-await
59
- void triggerNotification({ attemptsCount: 0 });
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.5.1",
4
+ "version": "0.7.0",
5
5
  "bugs": "https://github.com/ClipboardHealth/core-utils/issues",
6
6
  "dependencies": {
7
- "@clipboard-health/phone-number": "0.4.1",
8
- "@clipboard-health/util-ts": "3.13.1",
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.22.1"
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,gEAAsC;AACtC,qEAA2C;AAC3C,+DAAqC;AACrC,mEAAyC;AACzC,sDAA4B"}
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" | "key">;
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;IAExE,OAAO;QACL,OAAO;QACP,WAAW;QACX,cAAc;QACd,GAAG;QACH,SAAS;KACV,CAAC;AACJ,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 { key, attempt, destination } = params;
5
+ const { workflowKey, attempt, destination } = params;
6
6
  return {
7
- resource: `notification.${key}`,
8
- // Don't include high cardinality tags like expiresAt and idempotencyKey.
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,8DAeC;AAfD,SAAgB,yBAAyB,CAAC,MAAyB;IACjE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAE7C,OAAO;QACL,QAAQ,EAAE,gBAAgB,GAAG,EAAE;QAC/B,yEAAyE;QACzE,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"}
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,3 @@
1
+ import { type Knock } from "@knocklabs/node";
2
+ import { type InlineIdentifyUserRequest } from "../types";
3
+ export declare function toInlineIdentifyUserRequest(recipient: InlineIdentifyUserRequest): Knock.Users.InlineIdentifyUserRequest;
@@ -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"}