@clipboard-health/notifications 0.1.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 +66 -0
- package/package.json +29 -0
- package/src/index.d.ts +5 -0
- package/src/index.js +9 -0
- package/src/index.js.map +1 -0
- package/src/lib/chunk.d.ts +9 -0
- package/src/lib/chunk.js +21 -0
- package/src/lib/chunk.js.map +1 -0
- package/src/lib/chunkRecipients.d.ts +7 -0
- package/src/lib/chunkRecipients.js +18 -0
- package/src/lib/chunkRecipients.js.map +1 -0
- package/src/lib/createDeterministicHash.d.ts +17 -0
- package/src/lib/createDeterministicHash.js +25 -0
- package/src/lib/createDeterministicHash.js.map +1 -0
- package/src/lib/errorsInResult.d.ts +9 -0
- package/src/lib/errorsInResult.js +17 -0
- package/src/lib/errorsInResult.js.map +1 -0
- package/src/lib/internal/createTriggerLogParams.d.ts +3 -0
- package/src/lib/internal/createTriggerLogParams.js +14 -0
- package/src/lib/internal/createTriggerLogParams.js.map +1 -0
- package/src/lib/internal/createTriggerTraceOptions.d.ts +3 -0
- package/src/lib/internal/createTriggerTraceOptions.js +19 -0
- package/src/lib/internal/createTriggerTraceOptions.js.map +1 -0
- package/src/lib/internal/formatPhoneNumber.d.ts +9 -0
- package/src/lib/internal/formatPhoneNumber.js +16 -0
- package/src/lib/internal/formatPhoneNumber.js.map +1 -0
- package/src/lib/internal/idempotentKnock.d.ts +12 -0
- package/src/lib/internal/idempotentKnock.js +22 -0
- package/src/lib/internal/idempotentKnock.js.map +1 -0
- package/src/lib/internal/redact.d.ts +4 -0
- package/src/lib/internal/redact.js +38 -0
- package/src/lib/internal/redact.js.map +1 -0
- package/src/lib/internal/toKnockBody.d.ts +3 -0
- package/src/lib/internal/toKnockBody.js +34 -0
- package/src/lib/internal/toKnockBody.js.map +1 -0
- package/src/lib/notificationClient.d.ts +109 -0
- package/src/lib/notificationClient.js +307 -0
- package/src/lib/notificationClient.js.map +1 -0
- package/src/lib/types.d.ts +174 -0
- package/src/lib/types.js +5 -0
- package/src/lib/types.js.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @clipboard-health/notifications <!-- omit from toc -->
|
|
2
|
+
|
|
3
|
+
Send notifications through third-party providers.
|
|
4
|
+
|
|
5
|
+
## Table of contents <!-- omit from toc -->
|
|
6
|
+
|
|
7
|
+
- [Install](#install)
|
|
8
|
+
- [Usage](#usage)
|
|
9
|
+
- [`NotificationClient`](#notificationclient)
|
|
10
|
+
- [Local development commands](#local-development-commands)
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @clipboard-health/notifications
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
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
|
+
```
|
|
61
|
+
|
|
62
|
+
</embedex>
|
|
63
|
+
|
|
64
|
+
## Local development commands
|
|
65
|
+
|
|
66
|
+
See [`package.json`](./package.json) `scripts` for a list of commands.
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clipboard-health/notifications",
|
|
3
|
+
"description": "Send notifications through third-party providers.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"bugs": "https://github.com/ClipboardHealth/core-utils/issues",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@clipboard-health/phone-number": "0.2.0",
|
|
8
|
+
"@clipboard-health/util-ts": "3.12.0",
|
|
9
|
+
"@knocklabs/node": "1.16.0",
|
|
10
|
+
"tslib": "2.8.1"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@clipboard-health/testing-core": "0.21.0"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"main": "./src/index.js",
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"directory": "packages/notifications",
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/ClipboardHealth/core-utils.git"
|
|
25
|
+
},
|
|
26
|
+
"type": "commonjs",
|
|
27
|
+
"typings": "./src/index.d.ts",
|
|
28
|
+
"types": "./src/index.d.ts"
|
|
29
|
+
}
|
package/src/index.d.ts
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./lib/chunkRecipients"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./lib/createDeterministicHash"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./lib/errorsInResult"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./lib/notificationClient"), exports);
|
|
8
|
+
tslib_1.__exportStar(require("./lib/types"), exports);
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/notifications/src/index.ts"],"names":[],"mappings":";;;AAAA,gEAAsC;AACtC,wEAA8C;AAC9C,+DAAqC;AACrC,mEAAyC;AACzC,sDAA4B"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates an `array` of elements split into groups the length of `size`. If `array` can't be split
|
|
3
|
+
* evenly, the final chunk will be the remaining elements.
|
|
4
|
+
*
|
|
5
|
+
* @param array - The array to chunk.
|
|
6
|
+
* @param size - The length of each chunk.
|
|
7
|
+
* @returns the new 2D array of chunks.
|
|
8
|
+
*/
|
|
9
|
+
export declare function chunk<T>(array: T[], size: number): T[][];
|
package/src/lib/chunk.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.chunk = chunk;
|
|
4
|
+
/**
|
|
5
|
+
* Creates an `array` of elements split into groups the length of `size`. If `array` can't be split
|
|
6
|
+
* evenly, the final chunk will be the remaining elements.
|
|
7
|
+
*
|
|
8
|
+
* @param array - The array to chunk.
|
|
9
|
+
* @param size - The length of each chunk.
|
|
10
|
+
* @returns the new 2D array of chunks.
|
|
11
|
+
*/
|
|
12
|
+
function chunk(array, size) {
|
|
13
|
+
if (!Number.isInteger(size) || size <= 0) {
|
|
14
|
+
throw new RangeError("size must be a positive integer");
|
|
15
|
+
}
|
|
16
|
+
if (array.length === 0) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
return Array.from({ length: Math.ceil(array.length / size) }, (_, index) => array.slice(index * size, (index + 1) * size));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=chunk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/chunk.ts"],"names":[],"mappings":";;AAQA,sBAYC;AApBD;;;;;;;GAOG;AACH,SAAgB,KAAK,CAAI,KAAU,EAAE,IAAY;IAC/C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,UAAU,CAAC,iCAAiC,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CACzE,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAC9C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.chunkRecipients = chunkRecipients;
|
|
4
|
+
const chunk_1 = require("./chunk");
|
|
5
|
+
const notificationClient_1 = require("./notificationClient");
|
|
6
|
+
function chunkRecipients(params) {
|
|
7
|
+
const { recipientIds, idempotencyKey } = params;
|
|
8
|
+
if (recipientIds.length === 0) {
|
|
9
|
+
return [{ idempotencyKey, recipientIds: [] }];
|
|
10
|
+
}
|
|
11
|
+
const idChunks = (0, chunk_1.chunk)(recipientIds, notificationClient_1.MAXIMUM_RECIPIENTS_COUNT);
|
|
12
|
+
const singleChunk = idChunks.length === 1;
|
|
13
|
+
return idChunks.map((ids, index) => ({
|
|
14
|
+
idempotencyKey: singleChunk ? idempotencyKey : `${idempotencyKey}-chunk-${index + 1}`,
|
|
15
|
+
recipientIds: ids,
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=chunkRecipients.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunkRecipients.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/chunkRecipients.ts"],"names":[],"mappings":";;AAGA,0CAgBC;AAnBD,mCAAgC;AAChC,6DAAgE;AAEhE,SAAgB,eAAe,CAAC,MAG/B;IACC,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC;IAEhD,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,EAAE,cAAc,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,QAAQ,GAAG,IAAA,aAAK,EAAC,YAAY,EAAE,6CAAwB,CAAC,CAAC;IAC/D,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC;IAC1C,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACnC,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,cAAc,UAAU,KAAK,GAAG,CAAC,EAAE;QACrF,YAAY,EAAE,GAAG;KAClB,CAAC,CAAC,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a deterministic hash from `items`.
|
|
3
|
+
*
|
|
4
|
+
* The function sorts the strings, then generates a SHA-256 hash truncated to the specified number
|
|
5
|
+
* of characters.
|
|
6
|
+
*
|
|
7
|
+
* @param params.items - Array of strings or objects with id property to hash.
|
|
8
|
+
* @param params.characters - Number of characters in the resulting hash (default: 32).
|
|
9
|
+
*
|
|
10
|
+
* @returns A hash string of the specified length.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createDeterministicHash(params: {
|
|
13
|
+
items: string[] | Array<{
|
|
14
|
+
id: string;
|
|
15
|
+
}>;
|
|
16
|
+
characters?: number;
|
|
17
|
+
}): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDeterministicHash = createDeterministicHash;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
/**
|
|
6
|
+
* Creates a deterministic hash from `items`.
|
|
7
|
+
*
|
|
8
|
+
* The function sorts the strings, then generates a SHA-256 hash truncated to the specified number
|
|
9
|
+
* of characters.
|
|
10
|
+
*
|
|
11
|
+
* @param params.items - Array of strings or objects with id property to hash.
|
|
12
|
+
* @param params.characters - Number of characters in the resulting hash (default: 32).
|
|
13
|
+
*
|
|
14
|
+
* @returns A hash string of the specified length.
|
|
15
|
+
*/
|
|
16
|
+
function createDeterministicHash(params) {
|
|
17
|
+
const { items, characters = 32 } = params;
|
|
18
|
+
const hash = (0, node_crypto_1.createHash)("sha256")
|
|
19
|
+
// Unicode code-points for deterministic, locale-independent sorting.
|
|
20
|
+
.update(JSON.stringify(items.map((item) => (typeof item === "string" ? item : item.id)).sort()))
|
|
21
|
+
.digest("hex");
|
|
22
|
+
const length = Math.max(1, Math.min(characters, hash.length));
|
|
23
|
+
return hash.slice(0, length);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=createDeterministicHash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createDeterministicHash.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/createDeterministicHash.ts"],"names":[],"mappings":";;AAaA,0DAaC;AA1BD,6CAAyC;AAEzC;;;;;;;;;;GAUG;AACH,SAAgB,uBAAuB,CAAC,MAGvC;IACC,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,EAAE,EAAE,GAAG,MAAM,CAAC;IAE1C,MAAM,IAAI,GAAG,IAAA,wBAAU,EAAC,QAAQ,CAAC;QAC/B,qEAAqE;SACpE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;SAC/F,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9D,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ErrorCode, type FailureResult, type ServiceResult } from "@clipboard-health/util-ts";
|
|
2
|
+
import { type TriggerResponse } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Checks if a result contains specific error codes and is a failure result.
|
|
5
|
+
*
|
|
6
|
+
* @param result - ServiceResult containing an error or T.
|
|
7
|
+
* @param errorCodes - Array of error codes to check for. If empty, any error will match.
|
|
8
|
+
*/
|
|
9
|
+
export declare function errorsInResult<T = TriggerResponse>(result: ServiceResult<T>, errorCodes?: ErrorCode[]): result is FailureResult;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.errorsInResult = errorsInResult;
|
|
4
|
+
const util_ts_1 = require("@clipboard-health/util-ts");
|
|
5
|
+
/**
|
|
6
|
+
* Checks if a result contains specific error codes and is a failure result.
|
|
7
|
+
*
|
|
8
|
+
* @param result - ServiceResult containing an error or T.
|
|
9
|
+
* @param errorCodes - Array of error codes to check for. If empty, any error will match.
|
|
10
|
+
*/
|
|
11
|
+
function errorsInResult(result, errorCodes = []) {
|
|
12
|
+
if ((0, util_ts_1.isSuccess)(result)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return (errorCodes.length === 0 || result.error.issues.some((issue) => errorCodes.includes(issue.code)));
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=errorsInResult.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorsInResult.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/errorsInResult.ts"],"names":[],"mappings":";;AAeA,wCAWC;AA1BD,uDAKmC;AAInC;;;;;GAKG;AACH,SAAgB,cAAc,CAC5B,MAAwB,EACxB,aAA0B,EAAE;IAE5B,IAAI,IAAA,mBAAS,EAAC,MAAM,CAAC,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,CACL,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAChG,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createTriggerLogParams = createTriggerLogParams;
|
|
4
|
+
function createTriggerLogParams(params) {
|
|
5
|
+
const { attempt, destination, idempotencyKey, key, traceName } = params;
|
|
6
|
+
return {
|
|
7
|
+
attempt,
|
|
8
|
+
destination,
|
|
9
|
+
idempotencyKey,
|
|
10
|
+
key,
|
|
11
|
+
traceName,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=createTriggerLogParams.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createTriggerTraceOptions = createTriggerTraceOptions;
|
|
4
|
+
function createTriggerTraceOptions(params) {
|
|
5
|
+
const { key, attempt, destination } = params;
|
|
6
|
+
return {
|
|
7
|
+
resource: `notification.${key}`,
|
|
8
|
+
// Don't include high cardinality tags like expiresAt and idempotencyKey.
|
|
9
|
+
tags: {
|
|
10
|
+
"span.kind": "producer",
|
|
11
|
+
component: "customer-notifications",
|
|
12
|
+
"messaging.system": "knock.app",
|
|
13
|
+
"messaging.operation": "publish",
|
|
14
|
+
"messaging.destination": destination,
|
|
15
|
+
"notification.attempt": attempt.toString(),
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=createTriggerTraceOptions.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a phone number to E.164 format.
|
|
3
|
+
*
|
|
4
|
+
* If the phone number cannot be formatted (invalid format, missing country code, etc.),
|
|
5
|
+
* returns the original phone number unchanged.
|
|
6
|
+
*/
|
|
7
|
+
export declare function formatPhoneNumber(params: {
|
|
8
|
+
phoneNumber: string;
|
|
9
|
+
}): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatPhoneNumber = formatPhoneNumber;
|
|
4
|
+
const phone_number_1 = require("@clipboard-health/phone-number");
|
|
5
|
+
const util_ts_1 = require("@clipboard-health/util-ts");
|
|
6
|
+
/**
|
|
7
|
+
* Formats a phone number to E.164 format.
|
|
8
|
+
*
|
|
9
|
+
* If the phone number cannot be formatted (invalid format, missing country code, etc.),
|
|
10
|
+
* returns the original phone number unchanged.
|
|
11
|
+
*/
|
|
12
|
+
function formatPhoneNumber(params) {
|
|
13
|
+
const { phoneNumber } = params;
|
|
14
|
+
return (0, util_ts_1.pipe)((0, phone_number_1.formatPhoneNumber)({ phoneNumber, format: "E.164" }), util_ts_1.either.getOrElse(() => phoneNumber));
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=formatPhoneNumber.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatPhoneNumber.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/formatPhoneNumber.ts"],"names":[],"mappings":";;AASA,8CAOC;AAhBD,iEAAyF;AACzF,uDAA8D;AAE9D;;;;;GAKG;AACH,SAAgB,iBAAiB,CAAC,MAA+B;IAC/D,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAE/B,OAAO,IAAA,cAAI,EACT,IAAA,gCAAkB,EAAC,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EACpD,gBAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,CAC/B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Logger } from "@clipboard-health/util-ts";
|
|
2
|
+
import { Knock } from "@knocklabs/node";
|
|
3
|
+
export declare class IdempotentKnock extends Knock {
|
|
4
|
+
logLevel: "warn";
|
|
5
|
+
maxRetries: number;
|
|
6
|
+
timeout: number;
|
|
7
|
+
protected idempotencyHeader: string;
|
|
8
|
+
constructor(params: {
|
|
9
|
+
apiKey: string;
|
|
10
|
+
logger: Logger;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IdempotentKnock = void 0;
|
|
4
|
+
const node_1 = require("@knocklabs/node");
|
|
5
|
+
class IdempotentKnock extends node_1.Knock {
|
|
6
|
+
logLevel = "warn"; // Knock's default.
|
|
7
|
+
maxRetries = 1; // Knock's default is 2, but we rely on background job retries.
|
|
8
|
+
timeout = 60_000; // Knock's default.
|
|
9
|
+
idempotencyHeader = "Idempotency-Key";
|
|
10
|
+
constructor(params) {
|
|
11
|
+
const { apiKey, logger } = params;
|
|
12
|
+
super({
|
|
13
|
+
apiKey,
|
|
14
|
+
logger: {
|
|
15
|
+
...logger,
|
|
16
|
+
debug: logger.info,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.IdempotentKnock = IdempotentKnock;
|
|
22
|
+
//# sourceMappingURL=idempotentKnock.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.redact = redact;
|
|
4
|
+
function redact(params) {
|
|
5
|
+
const { data, keysToRedact } = params;
|
|
6
|
+
if (!data) {
|
|
7
|
+
return data;
|
|
8
|
+
}
|
|
9
|
+
const redactedObject = {};
|
|
10
|
+
for (const [key, value] of Object.entries(data)) {
|
|
11
|
+
redactedObject[key] = redactValue({ keysToRedact, key, value });
|
|
12
|
+
}
|
|
13
|
+
return redactedObject;
|
|
14
|
+
}
|
|
15
|
+
function redactValue(params) {
|
|
16
|
+
const { value, key, keysToRedact } = params;
|
|
17
|
+
if (key && keysToRedact.includes(key)) {
|
|
18
|
+
return "[REDACTED]";
|
|
19
|
+
}
|
|
20
|
+
if (value === null || value === undefined) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
return value.map((aValue) => redactValue({ keysToRedact, value: aValue }));
|
|
25
|
+
}
|
|
26
|
+
if (isPlainObject(value)) {
|
|
27
|
+
const redactedObject = {};
|
|
28
|
+
for (const [oKey, oValue] of Object.entries(value)) {
|
|
29
|
+
redactedObject[oKey] = redactValue({ keysToRedact, key: oKey, value: oValue });
|
|
30
|
+
}
|
|
31
|
+
return redactedObject;
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
function isPlainObject(value) {
|
|
36
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/redact.ts"],"names":[],"mappings":";;AAAA,wBAgBC;AAhBD,SAAgB,MAAM,CAAC,MAGtB;IACC,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC;IAEtC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,cAAc,GAA4B,EAAE,CAAC;IACnD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,cAAc,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,SAAS,WAAW,CAAC,MAAgE;IACnF,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC;IAE5C,IAAI,GAAG,IAAI,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,cAAc,GAA4B,EAAE,CAAC;QACnD,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACnD,cAAc,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACjF,CAAC;QAED,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,iBAAiB,CAAC;AACrE,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toKnockBody = toKnockBody;
|
|
4
|
+
const formatPhoneNumber_1 = require("./formatPhoneNumber");
|
|
5
|
+
function toKnockBody(body) {
|
|
6
|
+
const { actor, cancellationKey, recipients, workplaceId, ...rest } = body;
|
|
7
|
+
return {
|
|
8
|
+
...(actor ? { actor: toKnockRecipient(actor) } : {}),
|
|
9
|
+
...(cancellationKey ? { cancellation_key: cancellationKey } : {}),
|
|
10
|
+
...(workplaceId ? { tenant: workplaceId } : {}),
|
|
11
|
+
recipients: recipients.map(toKnockRecipient),
|
|
12
|
+
...rest,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function toKnockRecipient(recipient) {
|
|
16
|
+
if (typeof recipient === "string") {
|
|
17
|
+
return recipient;
|
|
18
|
+
}
|
|
19
|
+
return toKnockInlineIdentifyUserRequest(recipient);
|
|
20
|
+
}
|
|
21
|
+
function toKnockInlineIdentifyUserRequest(recipient) {
|
|
22
|
+
const { channelData, createdAt, email, name, phoneNumber, timeZone, userId, ...rest } = recipient;
|
|
23
|
+
return {
|
|
24
|
+
id: userId,
|
|
25
|
+
...(channelData ? { channel_data: channelData } : {}),
|
|
26
|
+
...(createdAt ? { created_at: createdAt.toISOString() } : {}),
|
|
27
|
+
...(email ? { email } : {}),
|
|
28
|
+
...(name ? { name } : {}),
|
|
29
|
+
...(phoneNumber ? { phone_number: (0, formatPhoneNumber_1.formatPhoneNumber)({ phoneNumber }) } : {}),
|
|
30
|
+
...(timeZone ? { timezone: timeZone } : {}),
|
|
31
|
+
...rest,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=toKnockBody.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toKnockBody.js","sourceRoot":"","sources":["../../../../../../packages/notifications/src/lib/internal/toKnockBody.ts"],"names":[],"mappings":";;AAKA,kCAUC;AAZD,2DAAwD;AAExD,SAAgB,WAAW,CAAC,IAAiB;IAC3C,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IAE1E,OAAO;QACL,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,gBAAgB,CAAC;QAC5C,GAAG,IAAI;KACR,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,SAA2B;IACnD,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,gCAAgC,CAAC,SAAS,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,gCAAgC,CACvC,SAAoC;IAEpC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,SAAS,CAAC;IAElG,OAAO;QACL,EAAE,EAAE,MAAM;QACV,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;QAC3C,GAAG,IAAI;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type Logger, type ServiceResult } from "@clipboard-health/util-ts";
|
|
2
|
+
import { IdempotentKnock } from "./internal/idempotentKnock";
|
|
3
|
+
import type { AppendPushTokenRequest, AppendPushTokenResponse, Tracer, TriggerRequest, TriggerResponse, UpsertWorkplaceRequest, UpsertWorkplaceResponse } from "./types";
|
|
4
|
+
export declare const MAXIMUM_RECIPIENTS_COUNT = 1000;
|
|
5
|
+
export declare const ERROR_CODES: {
|
|
6
|
+
readonly recipientCountBelowMinimum: "recipientCountBelowMinimum";
|
|
7
|
+
readonly recipientCountAboveMaximum: "recipientCountAboveMaximum";
|
|
8
|
+
readonly expired: "expired";
|
|
9
|
+
readonly unknown: "unknown";
|
|
10
|
+
};
|
|
11
|
+
export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
|
12
|
+
/**
|
|
13
|
+
* Client for sending notifications through third-party providers.
|
|
14
|
+
*/
|
|
15
|
+
export declare class NotificationClient {
|
|
16
|
+
private readonly logger;
|
|
17
|
+
private readonly provider;
|
|
18
|
+
private readonly tracer;
|
|
19
|
+
/**
|
|
20
|
+
* Creates a new NotificationClient instance.
|
|
21
|
+
*
|
|
22
|
+
* @param params.apiKey - API key for the third-party provider.
|
|
23
|
+
* @param params.logger - Logger instance for structured logging.
|
|
24
|
+
* @param params.tracer - Tracer instance for distributed tracing.
|
|
25
|
+
*/
|
|
26
|
+
constructor(params: {
|
|
27
|
+
logger: Logger;
|
|
28
|
+
tracer: Tracer;
|
|
29
|
+
} & ({
|
|
30
|
+
provider?: never;
|
|
31
|
+
apiKey: string;
|
|
32
|
+
} | {
|
|
33
|
+
provider: IdempotentKnock;
|
|
34
|
+
apiKey?: never;
|
|
35
|
+
}));
|
|
36
|
+
/**
|
|
37
|
+
* Triggers a notification through third-party providers.
|
|
38
|
+
*
|
|
39
|
+
* This method handles:
|
|
40
|
+
* - Stale notifications prevention through expiration checks.
|
|
41
|
+
* - Logging with sensitive data redaction.
|
|
42
|
+
* - Distributed tracing with notification metadata.
|
|
43
|
+
* - Idempotency to prevent duplicate notifications.
|
|
44
|
+
* - Comprehensive error handling and logging.
|
|
45
|
+
*
|
|
46
|
+
* @returns Promise resolving to either an error or successful response.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* <embedex source="packages/notifications/examples/notificationClient.ts">
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { NotificationClient, type Span } from "@clipboard-health/notifications";
|
|
53
|
+
* import { isSuccess } from "@clipboard-health/util-ts";
|
|
54
|
+
*
|
|
55
|
+
* const client = new NotificationClient({
|
|
56
|
+
* apiKey: "test-api-key",
|
|
57
|
+
* logger: {
|
|
58
|
+
* info: console.log,
|
|
59
|
+
* warn: console.warn,
|
|
60
|
+
* error: console.error,
|
|
61
|
+
* } as const,
|
|
62
|
+
* tracer: {
|
|
63
|
+
* trace: <T>(_name: string, _options: unknown, fun: (span?: Span | undefined) => T): T => fun(),
|
|
64
|
+
* },
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* async function triggerNotification(job: { attemptsCount: number }) {
|
|
68
|
+
* const result = await client.trigger({
|
|
69
|
+
* attempt: (job?.attemptsCount ?? 0) + 1,
|
|
70
|
+
* body: {
|
|
71
|
+
* recipients: ["user-1"],
|
|
72
|
+
* data: { favoriteColor: "blue", secret: "2" },
|
|
73
|
+
* },
|
|
74
|
+
* expiresAt: new Date(Date.now() + 300_000), // 5 minutes
|
|
75
|
+
* idempotencyKey: "welcome-user-4",
|
|
76
|
+
* key: "welcome-email",
|
|
77
|
+
* keysToRedact: ["secret"],
|
|
78
|
+
* });
|
|
79
|
+
*
|
|
80
|
+
* if (isSuccess(result)) {
|
|
81
|
+
* console.log("Notification sent:", result.value.id);
|
|
82
|
+
* }
|
|
83
|
+
* }
|
|
84
|
+
*
|
|
85
|
+
* // eslint-disable-next-line unicorn/prefer-top-level-await
|
|
86
|
+
* void triggerNotification({ attemptsCount: 0 });
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* </embedex>
|
|
90
|
+
*/
|
|
91
|
+
trigger(params: TriggerRequest): Promise<ServiceResult<TriggerResponse>>;
|
|
92
|
+
/**
|
|
93
|
+
* Append to a user's push tokens.
|
|
94
|
+
*
|
|
95
|
+
* @returns Promise resolving to either an error or successful response.
|
|
96
|
+
*/
|
|
97
|
+
appendPushToken(params: AppendPushTokenRequest): Promise<ServiceResult<AppendPushTokenResponse>>;
|
|
98
|
+
/**
|
|
99
|
+
* Updates or creates a workplace (tenant) in Knock.
|
|
100
|
+
*
|
|
101
|
+
* @returns Promise resolving to either an error or successful response.
|
|
102
|
+
*/
|
|
103
|
+
upsertWorkplace(params: UpsertWorkplaceRequest): Promise<ServiceResult<UpsertWorkplaceResponse>>;
|
|
104
|
+
private validateTriggerRequest;
|
|
105
|
+
private getExistingTokens;
|
|
106
|
+
private logTriggerRequest;
|
|
107
|
+
private logTriggerResponse;
|
|
108
|
+
private createAndLogError;
|
|
109
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NotificationClient = exports.ERROR_CODES = exports.MAXIMUM_RECIPIENTS_COUNT = void 0;
|
|
4
|
+
const util_ts_1 = require("@clipboard-health/util-ts");
|
|
5
|
+
const createTriggerLogParams_1 = require("./internal/createTriggerLogParams");
|
|
6
|
+
const createTriggerTraceOptions_1 = require("./internal/createTriggerTraceOptions");
|
|
7
|
+
const formatPhoneNumber_1 = require("./internal/formatPhoneNumber");
|
|
8
|
+
const idempotentKnock_1 = require("./internal/idempotentKnock");
|
|
9
|
+
const redact_1 = require("./internal/redact");
|
|
10
|
+
const toKnockBody_1 = require("./internal/toKnockBody");
|
|
11
|
+
const LOG_PARAMS = {
|
|
12
|
+
trigger: {
|
|
13
|
+
traceName: "notifications.trigger",
|
|
14
|
+
destination: "knock.workflows.trigger",
|
|
15
|
+
},
|
|
16
|
+
appendPushToken: {
|
|
17
|
+
traceName: "notifications.appendPushToken",
|
|
18
|
+
destination: "knock.users.setChannelData",
|
|
19
|
+
},
|
|
20
|
+
upsertWorkplace: {
|
|
21
|
+
traceName: "notifications.upsertWorkplace",
|
|
22
|
+
destination: "knock.tenants.set",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
exports.MAXIMUM_RECIPIENTS_COUNT = 1000;
|
|
26
|
+
exports.ERROR_CODES = {
|
|
27
|
+
recipientCountBelowMinimum: "recipientCountBelowMinimum",
|
|
28
|
+
recipientCountAboveMaximum: "recipientCountAboveMaximum",
|
|
29
|
+
expired: "expired",
|
|
30
|
+
unknown: "unknown",
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Client for sending notifications through third-party providers.
|
|
34
|
+
*/
|
|
35
|
+
class NotificationClient {
|
|
36
|
+
logger;
|
|
37
|
+
provider;
|
|
38
|
+
tracer;
|
|
39
|
+
/**
|
|
40
|
+
* Creates a new NotificationClient instance.
|
|
41
|
+
*
|
|
42
|
+
* @param params.apiKey - API key for the third-party provider.
|
|
43
|
+
* @param params.logger - Logger instance for structured logging.
|
|
44
|
+
* @param params.tracer - Tracer instance for distributed tracing.
|
|
45
|
+
*/
|
|
46
|
+
constructor(params) {
|
|
47
|
+
const { logger, tracer } = params;
|
|
48
|
+
this.logger = logger;
|
|
49
|
+
this.tracer = tracer;
|
|
50
|
+
this.provider =
|
|
51
|
+
"provider" in params
|
|
52
|
+
? // eslint-disable-next-line unicorn/consistent-destructuring
|
|
53
|
+
params.provider
|
|
54
|
+
: // eslint-disable-next-line unicorn/consistent-destructuring
|
|
55
|
+
new idempotentKnock_1.IdempotentKnock({ apiKey: params.apiKey, logger });
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Triggers a notification through third-party providers.
|
|
59
|
+
*
|
|
60
|
+
* This method handles:
|
|
61
|
+
* - Stale notifications prevention through expiration checks.
|
|
62
|
+
* - Logging with sensitive data redaction.
|
|
63
|
+
* - Distributed tracing with notification metadata.
|
|
64
|
+
* - Idempotency to prevent duplicate notifications.
|
|
65
|
+
* - Comprehensive error handling and logging.
|
|
66
|
+
*
|
|
67
|
+
* @returns Promise resolving to either an error or successful response.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* <embedex source="packages/notifications/examples/notificationClient.ts">
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { NotificationClient, type Span } from "@clipboard-health/notifications";
|
|
74
|
+
* import { isSuccess } from "@clipboard-health/util-ts";
|
|
75
|
+
*
|
|
76
|
+
* const client = new NotificationClient({
|
|
77
|
+
* apiKey: "test-api-key",
|
|
78
|
+
* logger: {
|
|
79
|
+
* info: console.log,
|
|
80
|
+
* warn: console.warn,
|
|
81
|
+
* error: console.error,
|
|
82
|
+
* } as const,
|
|
83
|
+
* tracer: {
|
|
84
|
+
* trace: <T>(_name: string, _options: unknown, fun: (span?: Span | undefined) => T): T => fun(),
|
|
85
|
+
* },
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* async function triggerNotification(job: { attemptsCount: number }) {
|
|
89
|
+
* const result = await client.trigger({
|
|
90
|
+
* attempt: (job?.attemptsCount ?? 0) + 1,
|
|
91
|
+
* body: {
|
|
92
|
+
* recipients: ["user-1"],
|
|
93
|
+
* data: { favoriteColor: "blue", secret: "2" },
|
|
94
|
+
* },
|
|
95
|
+
* expiresAt: new Date(Date.now() + 300_000), // 5 minutes
|
|
96
|
+
* idempotencyKey: "welcome-user-4",
|
|
97
|
+
* key: "welcome-email",
|
|
98
|
+
* keysToRedact: ["secret"],
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* if (isSuccess(result)) {
|
|
102
|
+
* console.log("Notification sent:", result.value.id);
|
|
103
|
+
* }
|
|
104
|
+
* }
|
|
105
|
+
*
|
|
106
|
+
* // eslint-disable-next-line unicorn/prefer-top-level-await
|
|
107
|
+
* void triggerNotification({ attemptsCount: 0 });
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* </embedex>
|
|
111
|
+
*/
|
|
112
|
+
async trigger(params) {
|
|
113
|
+
const logParams = (0, createTriggerLogParams_1.createTriggerLogParams)({ ...params, ...LOG_PARAMS.trigger });
|
|
114
|
+
return await this.tracer.trace(logParams.traceName, (0, createTriggerTraceOptions_1.createTriggerTraceOptions)(logParams), async (span) => {
|
|
115
|
+
const validated = this.validateTriggerRequest({ ...params, span, logParams });
|
|
116
|
+
if ((0, util_ts_1.isFailure)(validated)) {
|
|
117
|
+
return validated;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const { key, body, idempotencyKey, keysToRedact = [] } = validated.value;
|
|
121
|
+
this.logTriggerRequest({ logParams, body, keysToRedact });
|
|
122
|
+
const response = await this.provider.workflows.trigger(key, (0, toKnockBody_1.toKnockBody)(body), {
|
|
123
|
+
idempotencyKey,
|
|
124
|
+
});
|
|
125
|
+
const id = response.workflow_run_id;
|
|
126
|
+
this.logTriggerResponse({ span, response, id, logParams });
|
|
127
|
+
return (0, util_ts_1.success)({ id });
|
|
128
|
+
}
|
|
129
|
+
catch (maybeError) {
|
|
130
|
+
const error = (0, util_ts_1.toError)(maybeError);
|
|
131
|
+
return this.createAndLogError({
|
|
132
|
+
notificationError: {
|
|
133
|
+
code: exports.ERROR_CODES.unknown,
|
|
134
|
+
message: error.message,
|
|
135
|
+
},
|
|
136
|
+
span,
|
|
137
|
+
logFunction: this.logger.error,
|
|
138
|
+
logParams,
|
|
139
|
+
metadata: { error },
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Append to a user's push tokens.
|
|
146
|
+
*
|
|
147
|
+
* @returns Promise resolving to either an error or successful response.
|
|
148
|
+
*/
|
|
149
|
+
async appendPushToken(params) {
|
|
150
|
+
const { channelId, userId, token } = params;
|
|
151
|
+
const logParams = { ...LOG_PARAMS.appendPushToken, userId, channelId };
|
|
152
|
+
try {
|
|
153
|
+
// Don't log the push token, it is sensitive.
|
|
154
|
+
this.logger.info(`${logParams.traceName} request`, logParams);
|
|
155
|
+
const existingTokens = await this.getExistingTokens({ userId, channelId, logParams });
|
|
156
|
+
this.logger.info(`${logParams.traceName} existing tokens`, {
|
|
157
|
+
...logParams,
|
|
158
|
+
existingTokenCount: existingTokens.length,
|
|
159
|
+
});
|
|
160
|
+
const response = await this.provider.users.setChannelData(userId, channelId, {
|
|
161
|
+
data: { tokens: [...new Set([...existingTokens, token])] },
|
|
162
|
+
});
|
|
163
|
+
this.logger.info(`${logParams.traceName} response`, {
|
|
164
|
+
...logParams,
|
|
165
|
+
// Don't log the actual response; push tokens are sensitive.
|
|
166
|
+
response: { tokenCount: "tokens" in response.data ? response.data.tokens.length : 0 },
|
|
167
|
+
});
|
|
168
|
+
return (0, util_ts_1.success)({ success: true });
|
|
169
|
+
}
|
|
170
|
+
catch (maybeError) {
|
|
171
|
+
const error = (0, util_ts_1.toError)(maybeError);
|
|
172
|
+
return this.createAndLogError({
|
|
173
|
+
notificationError: {
|
|
174
|
+
code: exports.ERROR_CODES.unknown,
|
|
175
|
+
message: error.message,
|
|
176
|
+
},
|
|
177
|
+
logFunction: this.logger.error,
|
|
178
|
+
logParams,
|
|
179
|
+
metadata: { error },
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Updates or creates a workplace (tenant) in Knock.
|
|
185
|
+
*
|
|
186
|
+
* @returns Promise resolving to either an error or successful response.
|
|
187
|
+
*/
|
|
188
|
+
async upsertWorkplace(params) {
|
|
189
|
+
const { workplaceId, ...body } = params;
|
|
190
|
+
const logParams = { ...LOG_PARAMS.upsertWorkplace, workplaceId, ...body };
|
|
191
|
+
try {
|
|
192
|
+
this.logger.info(`${logParams.traceName} request`, logParams);
|
|
193
|
+
body.phoneNumber &&= (0, formatPhoneNumber_1.formatPhoneNumber)({ phoneNumber: body.phoneNumber });
|
|
194
|
+
const response = await this.provider.tenants.set(workplaceId, body);
|
|
195
|
+
this.logger.info(`${logParams.traceName} response`, {
|
|
196
|
+
...logParams,
|
|
197
|
+
response: { workplaceId: response.id, name: response.name },
|
|
198
|
+
});
|
|
199
|
+
return (0, util_ts_1.success)({
|
|
200
|
+
workplaceId: response.id,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
catch (maybeError) {
|
|
204
|
+
const error = (0, util_ts_1.toError)(maybeError);
|
|
205
|
+
return this.createAndLogError({
|
|
206
|
+
notificationError: {
|
|
207
|
+
code: exports.ERROR_CODES.unknown,
|
|
208
|
+
message: error.message,
|
|
209
|
+
},
|
|
210
|
+
logFunction: this.logger.error,
|
|
211
|
+
logParams,
|
|
212
|
+
metadata: { error },
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
validateTriggerRequest(params) {
|
|
217
|
+
const { body, expiresAt, span, logParams } = params;
|
|
218
|
+
if (body.recipients.length <= 0) {
|
|
219
|
+
return this.createAndLogError({
|
|
220
|
+
notificationError: {
|
|
221
|
+
code: exports.ERROR_CODES.recipientCountBelowMinimum,
|
|
222
|
+
message: `Got ${body.recipients.length} recipients; must be > 0.`,
|
|
223
|
+
},
|
|
224
|
+
span,
|
|
225
|
+
logParams,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (body.recipients.length > exports.MAXIMUM_RECIPIENTS_COUNT) {
|
|
229
|
+
const recipientsCount = body.recipients.length;
|
|
230
|
+
return this.createAndLogError({
|
|
231
|
+
notificationError: {
|
|
232
|
+
code: exports.ERROR_CODES.recipientCountAboveMaximum,
|
|
233
|
+
message: `Got ${recipientsCount} recipients; must be <= ${exports.MAXIMUM_RECIPIENTS_COUNT}.`,
|
|
234
|
+
},
|
|
235
|
+
span,
|
|
236
|
+
logParams,
|
|
237
|
+
metadata: { recipientsCount },
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
const now = new Date();
|
|
241
|
+
if (now > expiresAt) {
|
|
242
|
+
return this.createAndLogError({
|
|
243
|
+
notificationError: {
|
|
244
|
+
code: exports.ERROR_CODES.expired,
|
|
245
|
+
message: `Got ${now.toISOString()}; notification expires at ${expiresAt.toISOString()}.`,
|
|
246
|
+
},
|
|
247
|
+
span,
|
|
248
|
+
logParams,
|
|
249
|
+
metadata: { currentTime: now.toISOString(), expiresAt: expiresAt.toISOString() },
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return (0, util_ts_1.success)(params);
|
|
253
|
+
}
|
|
254
|
+
async getExistingTokens(params) {
|
|
255
|
+
const { userId, channelId, logParams } = params;
|
|
256
|
+
// If existing tokens, use them; otherwise, start with empty array
|
|
257
|
+
try {
|
|
258
|
+
const response = await this.provider.users.getChannelData(userId, channelId);
|
|
259
|
+
return "tokens" in response.data ? response.data.tokens : [];
|
|
260
|
+
}
|
|
261
|
+
catch (maybeError) {
|
|
262
|
+
const error = (0, util_ts_1.toError)(maybeError);
|
|
263
|
+
if ("status" in error && error.status === 404) {
|
|
264
|
+
this.logger.info(`${logParams.traceName} no existing channel data`, logParams);
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
logTriggerRequest(params) {
|
|
271
|
+
const { logParams, body, keysToRedact } = params;
|
|
272
|
+
this.logger.info(`${logParams.traceName} request`, {
|
|
273
|
+
...logParams,
|
|
274
|
+
redactedBody: {
|
|
275
|
+
...body,
|
|
276
|
+
// Don't log potentially sensitive recipient data.
|
|
277
|
+
recipients: body.recipients.length,
|
|
278
|
+
data: (0, redact_1.redact)({ data: body.data ?? undefined, keysToRedact }),
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
logTriggerResponse(params) {
|
|
283
|
+
const { span, response, id, logParams } = params;
|
|
284
|
+
span?.addTags({
|
|
285
|
+
"response.id": id,
|
|
286
|
+
success: true,
|
|
287
|
+
});
|
|
288
|
+
this.logger.info(`${logParams.traceName} response`, { ...logParams, id, response });
|
|
289
|
+
return id;
|
|
290
|
+
}
|
|
291
|
+
createAndLogError(params) {
|
|
292
|
+
const { logParams, notificationError, span, metadata, logFunction = this.logger.warn } = params;
|
|
293
|
+
const { code, message } = notificationError;
|
|
294
|
+
span?.addTags({
|
|
295
|
+
error: true,
|
|
296
|
+
"error.type": code,
|
|
297
|
+
"error.message": message,
|
|
298
|
+
});
|
|
299
|
+
logFunction(`${logParams.traceName} [${code}] ${message}`, {
|
|
300
|
+
...logParams,
|
|
301
|
+
...metadata,
|
|
302
|
+
});
|
|
303
|
+
return (0, util_ts_1.failure)(new util_ts_1.ServiceError({ issues: [{ code, message }] }));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
exports.NotificationClient = NotificationClient;
|
|
307
|
+
//# sourceMappingURL=notificationClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notificationClient.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/notificationClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
export type Tags = Record<string, unknown>;
|
|
2
|
+
export declare const MOBILE_PLATFORMS: readonly ["android", "ios"];
|
|
3
|
+
export type MobilePlatform = (typeof MOBILE_PLATFORMS)[number];
|
|
4
|
+
export interface TraceOptions {
|
|
5
|
+
resource?: string;
|
|
6
|
+
tags?: Tags;
|
|
7
|
+
}
|
|
8
|
+
export interface LogParams {
|
|
9
|
+
traceName: string;
|
|
10
|
+
destination: string;
|
|
11
|
+
}
|
|
12
|
+
export interface Span {
|
|
13
|
+
addTags: (tags: Tags) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Tracer interface for distributed tracing operations.
|
|
17
|
+
*/
|
|
18
|
+
export interface Tracer {
|
|
19
|
+
trace<T>(name: string, options: TraceOptions, fun: (span?: Span) => T): T;
|
|
20
|
+
}
|
|
21
|
+
export interface PushChannelData {
|
|
22
|
+
/**
|
|
23
|
+
* A list of push channel tokens.
|
|
24
|
+
*/
|
|
25
|
+
tokens: string[];
|
|
26
|
+
}
|
|
27
|
+
export type InlineChannelDataRequest = Record<string, PushChannelData>;
|
|
28
|
+
/**
|
|
29
|
+
* Parameters to upsert and inline-identify a user, ensuring they exist before notifying them.
|
|
30
|
+
*/
|
|
31
|
+
export interface InlineIdentifyUserRequest {
|
|
32
|
+
/**
|
|
33
|
+
* The user ID.
|
|
34
|
+
*/
|
|
35
|
+
userId: string;
|
|
36
|
+
/**
|
|
37
|
+
* The user's channel data.
|
|
38
|
+
*/
|
|
39
|
+
channelData?: InlineChannelDataRequest;
|
|
40
|
+
/**
|
|
41
|
+
* The user's creation date.
|
|
42
|
+
*/
|
|
43
|
+
createdAt?: Date | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The user's email address.
|
|
46
|
+
*/
|
|
47
|
+
email?: string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* The user's display name.
|
|
50
|
+
*/
|
|
51
|
+
name?: string | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* The user's phone number.
|
|
54
|
+
*/
|
|
55
|
+
phoneNumber?: string | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The user's [timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for
|
|
58
|
+
* scheduled notifications.
|
|
59
|
+
*/
|
|
60
|
+
timeZone?: string | undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Specifies a recipient in a request. This can either be a user ID or an inline user request.
|
|
64
|
+
*/
|
|
65
|
+
export type RecipientRequest = string | InlineIdentifyUserRequest;
|
|
66
|
+
export interface TriggerBody {
|
|
67
|
+
/**
|
|
68
|
+
* The recipients to trigger the workflow for. Limited to 1,000 recipients.
|
|
69
|
+
*/
|
|
70
|
+
recipients: RecipientRequest[];
|
|
71
|
+
/**
|
|
72
|
+
* The trigger actor.
|
|
73
|
+
*/
|
|
74
|
+
actor?: RecipientRequest;
|
|
75
|
+
/**
|
|
76
|
+
* An optional key used to reference a specific trigger request when issuing a cancellation
|
|
77
|
+
* request. You must provide it while triggering in order to enable subsequent cancellation and it
|
|
78
|
+
* should be unique across trigger requests to avoid unintentional cancellations.
|
|
79
|
+
*/
|
|
80
|
+
cancellationKey?: string;
|
|
81
|
+
/**
|
|
82
|
+
* An optional map of data to pass into the trigger execution. Limited to 1024 bytes for each
|
|
83
|
+
* string and 10MB overall.
|
|
84
|
+
*/
|
|
85
|
+
data?: Record<string, unknown>;
|
|
86
|
+
/**
|
|
87
|
+
* The associated workplace ID.
|
|
88
|
+
*/
|
|
89
|
+
workplaceId?: string;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Request parameters for triggering a notification.
|
|
93
|
+
*/
|
|
94
|
+
export interface TriggerRequest {
|
|
95
|
+
/** Notification key. */
|
|
96
|
+
key: string;
|
|
97
|
+
/** Notification payload. */
|
|
98
|
+
body: TriggerBody;
|
|
99
|
+
/**
|
|
100
|
+
* Key to prevent duplicate requests if provider supports it. It's important it is deterministic
|
|
101
|
+
* and remains the same across any retry logic.
|
|
102
|
+
*/
|
|
103
|
+
idempotencyKey: string;
|
|
104
|
+
/** Array of data keys to redact in logs for privacy. */
|
|
105
|
+
keysToRedact?: string[];
|
|
106
|
+
/** Expiration timestamp after which the request is dropped. */
|
|
107
|
+
expiresAt: Date;
|
|
108
|
+
/** Attempt number for tracking. */
|
|
109
|
+
attempt: number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Response from triggering a notification.
|
|
113
|
+
*/
|
|
114
|
+
export interface TriggerResponse {
|
|
115
|
+
/** Third-party provider's unique identifier. */
|
|
116
|
+
id: string;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Request parameters for appending a push token.
|
|
120
|
+
*/
|
|
121
|
+
export interface AppendPushTokenRequest {
|
|
122
|
+
/** The channel ID. */
|
|
123
|
+
channelId: string;
|
|
124
|
+
/** The user ID. */
|
|
125
|
+
userId: string;
|
|
126
|
+
/** The push token to append. */
|
|
127
|
+
token: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Response from appending a push token.
|
|
131
|
+
*/
|
|
132
|
+
export interface AppendPushTokenResponse {
|
|
133
|
+
/** Whether the push token was appended successfully. */
|
|
134
|
+
success: boolean;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Request parameters for workplace upsert.
|
|
138
|
+
*/
|
|
139
|
+
export interface UpsertWorkplaceRequest {
|
|
140
|
+
/**
|
|
141
|
+
* The workplace's unique identifier.
|
|
142
|
+
*/
|
|
143
|
+
workplaceId: string;
|
|
144
|
+
/**
|
|
145
|
+
* The workplace's creation date.
|
|
146
|
+
*/
|
|
147
|
+
createdAt?: Date | undefined;
|
|
148
|
+
/**
|
|
149
|
+
* The workplace's email address.
|
|
150
|
+
*/
|
|
151
|
+
email?: string | undefined;
|
|
152
|
+
/**
|
|
153
|
+
* The workplace's display name.
|
|
154
|
+
*/
|
|
155
|
+
name?: string | undefined;
|
|
156
|
+
/**
|
|
157
|
+
* The workplace's phone number.
|
|
158
|
+
*/
|
|
159
|
+
phoneNumber?: string | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* The workplace's [timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for
|
|
162
|
+
* scheduled notifications.
|
|
163
|
+
*/
|
|
164
|
+
timeZone?: string | undefined;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Response after workplace upsert.
|
|
168
|
+
*/
|
|
169
|
+
export interface UpsertWorkplaceResponse {
|
|
170
|
+
/**
|
|
171
|
+
* The workplace's unique identifier.
|
|
172
|
+
*/
|
|
173
|
+
workplaceId: string;
|
|
174
|
+
}
|
package/src/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../packages/notifications/src/lib/types.ts"],"names":[],"mappings":";;;AAEa,QAAA,gBAAgB,GAAG,CAAC,SAAS,EAAE,KAAK,CAAU,CAAC"}
|