@doist/comms-sdk 0.1.0-alpha.1

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 (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/dist/cjs/authentication.js +211 -0
  4. package/dist/cjs/clients/add-comment-helper.js +53 -0
  5. package/dist/cjs/clients/base-client.js +27 -0
  6. package/dist/cjs/clients/channels-client.js +83 -0
  7. package/dist/cjs/clients/comments-client.js +93 -0
  8. package/dist/cjs/clients/conversation-messages-client.js +87 -0
  9. package/dist/cjs/clients/conversations-client.js +103 -0
  10. package/dist/cjs/clients/groups-client.js +71 -0
  11. package/dist/cjs/clients/inbox-client.js +98 -0
  12. package/dist/cjs/clients/reactions-client.js +59 -0
  13. package/dist/cjs/clients/search-client.js +88 -0
  14. package/dist/cjs/clients/threads-client.js +135 -0
  15. package/dist/cjs/clients/users-client.js +199 -0
  16. package/dist/cjs/clients/workspace-users-client.js +140 -0
  17. package/dist/cjs/clients/workspaces-client.js +93 -0
  18. package/dist/cjs/comms-api.js +65 -0
  19. package/dist/cjs/consts/endpoints.js +27 -0
  20. package/dist/cjs/index.js +48 -0
  21. package/dist/cjs/package.json +1 -0
  22. package/dist/cjs/testUtils/msw-handlers.js +51 -0
  23. package/dist/cjs/testUtils/msw-setup.js +21 -0
  24. package/dist/cjs/testUtils/obsidian-fetch-adapter.js +53 -0
  25. package/dist/cjs/testUtils/test-defaults.js +102 -0
  26. package/dist/cjs/transport/fetch-with-retry.js +136 -0
  27. package/dist/cjs/transport/http-client.js +56 -0
  28. package/dist/cjs/transport/http-dispatcher.js +143 -0
  29. package/dist/cjs/types/entities.js +411 -0
  30. package/dist/cjs/types/enums.js +37 -0
  31. package/dist/cjs/types/errors.js +12 -0
  32. package/dist/cjs/types/http.js +4 -0
  33. package/dist/cjs/types/index.js +21 -0
  34. package/dist/cjs/types/requests.js +117 -0
  35. package/dist/cjs/utils/case-conversion.js +54 -0
  36. package/dist/cjs/utils/index.js +19 -0
  37. package/dist/cjs/utils/timestamp-conversion.js +49 -0
  38. package/dist/cjs/utils/url-helpers.js +131 -0
  39. package/dist/cjs/utils/uuidv7.js +174 -0
  40. package/dist/esm/authentication.js +203 -0
  41. package/dist/esm/clients/add-comment-helper.js +50 -0
  42. package/dist/esm/clients/base-client.js +23 -0
  43. package/dist/esm/clients/channels-client.js +79 -0
  44. package/dist/esm/clients/comments-client.js +89 -0
  45. package/dist/esm/clients/conversation-messages-client.js +83 -0
  46. package/dist/esm/clients/conversations-client.js +99 -0
  47. package/dist/esm/clients/groups-client.js +67 -0
  48. package/dist/esm/clients/inbox-client.js +94 -0
  49. package/dist/esm/clients/reactions-client.js +55 -0
  50. package/dist/esm/clients/search-client.js +84 -0
  51. package/dist/esm/clients/threads-client.js +131 -0
  52. package/dist/esm/clients/users-client.js +195 -0
  53. package/dist/esm/clients/workspace-users-client.js +136 -0
  54. package/dist/esm/clients/workspaces-client.js +89 -0
  55. package/dist/esm/comms-api.js +61 -0
  56. package/dist/esm/consts/endpoints.js +23 -0
  57. package/dist/esm/index.js +17 -0
  58. package/dist/esm/testUtils/msw-handlers.js +45 -0
  59. package/dist/esm/testUtils/msw-setup.js +18 -0
  60. package/dist/esm/testUtils/obsidian-fetch-adapter.js +50 -0
  61. package/dist/esm/testUtils/test-defaults.js +99 -0
  62. package/dist/esm/transport/fetch-with-retry.js +133 -0
  63. package/dist/esm/transport/http-client.js +51 -0
  64. package/dist/esm/transport/http-dispatcher.js +104 -0
  65. package/dist/esm/types/entities.js +408 -0
  66. package/dist/esm/types/enums.js +34 -0
  67. package/dist/esm/types/errors.js +8 -0
  68. package/dist/esm/types/http.js +1 -0
  69. package/dist/esm/types/index.js +5 -0
  70. package/dist/esm/types/requests.js +114 -0
  71. package/dist/esm/utils/case-conversion.js +47 -0
  72. package/dist/esm/utils/index.js +3 -0
  73. package/dist/esm/utils/timestamp-conversion.js +45 -0
  74. package/dist/esm/utils/url-helpers.js +112 -0
  75. package/dist/esm/utils/uuidv7.js +163 -0
  76. package/dist/types/authentication.d.ts +160 -0
  77. package/dist/types/clients/add-comment-helper.d.ts +12 -0
  78. package/dist/types/clients/base-client.d.ts +24 -0
  79. package/dist/types/clients/channels-client.d.ts +91 -0
  80. package/dist/types/clients/comments-client.d.ts +157 -0
  81. package/dist/types/clients/conversation-messages-client.d.ts +127 -0
  82. package/dist/types/clients/conversations-client.d.ts +206 -0
  83. package/dist/types/clients/groups-client.d.ts +55 -0
  84. package/dist/types/clients/inbox-client.d.ts +20 -0
  85. package/dist/types/clients/reactions-client.d.ts +19 -0
  86. package/dist/types/clients/search-client.d.ts +20 -0
  87. package/dist/types/clients/threads-client.d.ts +344 -0
  88. package/dist/types/clients/users-client.d.ts +123 -0
  89. package/dist/types/clients/workspace-users-client.d.ts +47 -0
  90. package/dist/types/clients/workspaces-client.d.ts +79 -0
  91. package/dist/types/comms-api.d.ts +59 -0
  92. package/dist/types/consts/endpoints.d.ts +19 -0
  93. package/dist/types/index.d.ts +18 -0
  94. package/dist/types/testUtils/msw-handlers.d.ts +28 -0
  95. package/dist/types/testUtils/msw-setup.d.ts +1 -0
  96. package/dist/types/testUtils/obsidian-fetch-adapter.d.ts +29 -0
  97. package/dist/types/testUtils/test-defaults.d.ts +16 -0
  98. package/dist/types/transport/fetch-with-retry.d.ts +4 -0
  99. package/dist/types/transport/http-client.d.ts +13 -0
  100. package/dist/types/transport/http-dispatcher.d.ts +10 -0
  101. package/dist/types/types/entities.d.ts +1288 -0
  102. package/dist/types/types/enums.d.ts +55 -0
  103. package/dist/types/types/errors.d.ts +6 -0
  104. package/dist/types/types/http.d.ts +54 -0
  105. package/dist/types/types/index.d.ts +5 -0
  106. package/dist/types/types/requests.d.ts +385 -0
  107. package/dist/types/utils/case-conversion.d.ts +8 -0
  108. package/dist/types/utils/index.d.ts +3 -0
  109. package/dist/types/utils/timestamp-conversion.d.ts +13 -0
  110. package/dist/types/utils/url-helpers.d.ts +88 -0
  111. package/dist/types/utils/uuidv7.d.ts +40 -0
  112. package/package.json +93 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Doist
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # Comms SDK (TypeScript)
2
+
3
+ The official TypeScript SDK for the Comms REST API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @doist/comms-sdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { CommsApi } from '@doist/comms-sdk'
15
+
16
+ const api = new CommsApi('YOUR_API_TOKEN')
17
+
18
+ api.users
19
+ .getSessionUser()
20
+ .then((user) => console.log(user))
21
+ .catch((error) => console.log(error))
22
+ ```
23
+
24
+ By default the SDK targets production at `https://comms.todoist.com`.
25
+ Pass a `baseUrl` option to point at a different deployment — staging
26
+ lives at `https://comms.staging.todoist.com`:
27
+
28
+ ```typescript
29
+ const api = new CommsApi('YOUR_API_TOKEN', {
30
+ baseUrl: 'https://comms.staging.todoist.com',
31
+ })
32
+ ```
33
+
34
+ ### Creating entities
35
+
36
+ Channel / thread / comment / conversation / message / group IDs are
37
+ opaque base58-encoded UUIDv7 strings; `workspaceId` and `userId` are
38
+ numeric.
39
+
40
+ Creation endpoints (`createChannel`, `createThread`, `createComment`,
41
+ `getOrCreateConversation`, `createMessage`, `createGroup`) accept an
42
+ optional `id`. **A caller-supplied `id` must be a base58-encoded
43
+ UUIDv7** — anything else fails fast with a `UuidV7Error` before the
44
+ request leaves the SDK. Either mint your own with `generateId()` (handy
45
+ for optimistic UI — the ID survives the round-trip unchanged) or omit
46
+ `id` and let the SDK mint one:
47
+
48
+ ```typescript
49
+ import { CommsApi, generateId } from '@doist/comms-sdk'
50
+
51
+ const api = new CommsApi('YOUR_API_TOKEN')
52
+
53
+ // Option 1: let the SDK mint an ID
54
+ const channel = await api.channels.createChannel({
55
+ workspaceId: 1,
56
+ name: 'Engineering',
57
+ })
58
+
59
+ // Option 2: mint the ID yourself (must be a base58 UUIDv7 from generateId)
60
+ const id = generateId()
61
+ const sameChannel = await api.channels.createChannel({
62
+ workspaceId: 1,
63
+ name: 'Engineering',
64
+ id,
65
+ })
66
+ ```
67
+
68
+ ### Broadcast group markers
69
+
70
+ Use the string constants `EVERYONE` / `EVERYONE_IN_THREAD` when
71
+ populating `groups[]` / `directGroupMentions[]` directly, or pass
72
+ `notifyAudience` to `createComment` / `closeThread` / `reopenThread`
73
+ and let the SDK encode it for you:
74
+
75
+ ```typescript
76
+ await api.comments.createComment({
77
+ threadId,
78
+ content: 'Heads up everyone',
79
+ notifyAudience: 'channel', // encoded as EVERYONE
80
+ })
81
+ ```
82
+
83
+ ### OAuth 2.0
84
+
85
+ ```typescript
86
+ import { getAuthorizationUrl, getAuthToken, CommsApi } from '@doist/comms-sdk'
87
+
88
+ const authUrl = getAuthorizationUrl(
89
+ 'your-client-id',
90
+ ['user:read', 'channels:read'],
91
+ 'state-parameter',
92
+ 'https://yourapp.com/callback',
93
+ )
94
+
95
+ const tokenResponse = await getAuthToken({
96
+ clientId: 'your-client-id',
97
+ clientSecret: 'your-client-secret',
98
+ code: 'authorization-code',
99
+ redirectUri: 'https://yourapp.com/callback',
100
+ })
101
+
102
+ const api = new CommsApi(tokenResponse.accessToken)
103
+ const user = await api.users.getSessionUser()
104
+ ```
105
+
106
+ ### Short-lived processes (CLIs, scripts)
107
+
108
+ On Node, the SDK keeps a connection pool alive across requests so HTTP/2
109
+ multiplexing and TLS reuse actually work. Long-running processes don't
110
+ need to think about it. **Short-lived processes should `await api.close()`
111
+ before exit**, otherwise Node's event loop waits ~4 seconds for idle
112
+ sockets to time out:
113
+
114
+ ```typescript
115
+ const api = new CommsApi('YOUR_API_TOKEN')
116
+ try {
117
+ await api.users.getSessionUser()
118
+ } finally {
119
+ await api.close()
120
+ }
121
+ ```
122
+
123
+ `api.close()` drains the process-global pool, so it also covers code
124
+ paths that only use the standalone OAuth helpers (`getAuthToken`,
125
+ `revokeAuthToken`, `registerClient`). Those flows can also import
126
+ `closeDefaultDispatcher` directly.
127
+
128
+ ## Development
129
+
130
+ - `npm install`
131
+ - `npm test` — Vitest
132
+ - `npm run type-check` — TypeScript
133
+ - `npm run check` — oxlint + oxfmt
134
+ - `npm run build` — emit CJS + ESM + d.ts
135
+
136
+ ## Releases
137
+
138
+ The package follows semantic versioning; releases publish to npm via the
139
+ GitHub workflow.
140
+
141
+ ## Feedback
142
+
143
+ Open issues at https://github.com/Doist/comms-sdk-typescript.
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TOKEN_ENDPOINT_AUTH_METHODS = exports.COMMS_SCOPES = void 0;
4
+ exports.getAuthStateParameter = getAuthStateParameter;
5
+ exports.getAuthorizationUrl = getAuthorizationUrl;
6
+ exports.getAuthToken = getAuthToken;
7
+ exports.revokeAuthToken = revokeAuthToken;
8
+ exports.registerClient = registerClient;
9
+ const uuid_1 = require("uuid");
10
+ const http_client_1 = require("./transport/http-client");
11
+ const errors_1 = require("./types/errors");
12
+ /**
13
+ * OAuth scopes for the Comms API.
14
+ *
15
+ * @remarks
16
+ * Request only the scopes your application needs:
17
+ *
18
+ * **User Scopes:**
19
+ * - `user:read` - Access user's personal settings
20
+ * - `user:write` - Access and update user's personal settings
21
+ *
22
+ * **Workspace Scopes:**
23
+ * - `workspaces:read` - Access teams the user is part of
24
+ * - `workspaces:write` - Access and update teams the user is part of
25
+ *
26
+ * **Channel Scopes:**
27
+ * - `channels:read` - Access channels
28
+ * - `channels:write` - Access and update channels
29
+ * - `channels:remove` - Access, update, and delete channels
30
+ *
31
+ * **Thread Scopes:**
32
+ * - `threads:read` - Access threads
33
+ * - `threads:write` - Access and update threads
34
+ * - `threads:remove` - Access, update, and delete threads
35
+ *
36
+ * **Comment Scopes:**
37
+ * - `comments:read` - Access comments
38
+ * - `comments:write` - Access and update comments
39
+ * - `comments:remove` - Access, update, and delete comments
40
+ *
41
+ * **Group Scopes:**
42
+ * - `groups:read` - Access groups
43
+ * - `groups:write` - Access and update groups
44
+ * - `groups:remove` - Access, update, and delete groups
45
+ *
46
+ * **Message Scopes:**
47
+ * - `messages:read` - Access messages
48
+ * - `messages:write` - Access and update messages
49
+ * - `messages:remove` - Access, update, and delete messages
50
+ *
51
+ * **Reaction Scopes:**
52
+ * - `reactions:read` - Access reactions
53
+ * - `reactions:write` - Access and update reactions
54
+ * - `reactions:remove` - Access, update, and delete reactions
55
+ *
56
+ * **Search Scopes:**
57
+ * - `search:read` - Search
58
+ *
59
+ * **Attachment Scopes:**
60
+ * - `attachments:read` - Access attachments
61
+ * - `attachments:write` - Access and update attachments
62
+ *
63
+ * **Notification Scopes:**
64
+ * - `notifications:read` - Read user's notifications settings
65
+ * - `notifications:write` - Read and update user's notifications settings
66
+ */
67
+ exports.COMMS_SCOPES = [
68
+ 'user:read',
69
+ 'user:write',
70
+ 'workspaces:read',
71
+ 'workspaces:write',
72
+ 'channels:read',
73
+ 'channels:write',
74
+ 'channels:remove',
75
+ 'threads:read',
76
+ 'threads:write',
77
+ 'threads:remove',
78
+ 'comments:read',
79
+ 'comments:write',
80
+ 'comments:remove',
81
+ 'groups:read',
82
+ 'groups:write',
83
+ 'groups:remove',
84
+ 'messages:read',
85
+ 'messages:write',
86
+ 'messages:remove',
87
+ 'reactions:read',
88
+ 'reactions:write',
89
+ 'reactions:remove',
90
+ 'search:read',
91
+ 'attachments:read',
92
+ 'attachments:write',
93
+ 'notifications:read',
94
+ 'notifications:write',
95
+ ];
96
+ /** Supported token endpoint authentication methods for dynamic client registration. */
97
+ exports.TOKEN_ENDPOINT_AUTH_METHODS = [
98
+ 'client_secret_post',
99
+ 'client_secret_basic',
100
+ 'none',
101
+ ];
102
+ function getAuthStateParameter() {
103
+ return (0, uuid_1.v4)();
104
+ }
105
+ /**
106
+ * Generates the authorization URL for the OAuth2 flow.
107
+ *
108
+ * The `clientId` can be either a traditional client ID string (e.g. from
109
+ * {@link registerClient}) or an HTTPS URL pointing to a client metadata document,
110
+ * as defined in {@link https://drafts.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/ RFC draft-ietf-oauth-client-id-metadata-document}.
111
+ */
112
+ function getAuthorizationUrl(clientId, scopes, state, redirectUri, baseUrl) {
113
+ if (!scopes?.length) {
114
+ throw new Error('At least one scope value is required.');
115
+ }
116
+ const authBaseUrl = baseUrl ? `${baseUrl}/oauth` : 'https://comms.todoist.com/oauth';
117
+ const scope = scopes.join(' ');
118
+ const params = new URLSearchParams({
119
+ client_id: clientId,
120
+ response_type: 'code',
121
+ scope,
122
+ state,
123
+ });
124
+ if (redirectUri) {
125
+ params.append('redirect_uri', redirectUri);
126
+ }
127
+ return `${authBaseUrl}/authorize?${params.toString()}`;
128
+ }
129
+ async function getAuthToken(args, options) {
130
+ const tokenUrl = options?.baseUrl
131
+ ? `${options.baseUrl}/oauth/token`
132
+ : 'https://comms.todoist.com/oauth/token';
133
+ const payload = {
134
+ clientId: args.clientId,
135
+ clientSecret: args.clientSecret,
136
+ code: args.code,
137
+ grantType: 'authorization_code',
138
+ ...(args.redirectUri && { redirectUri: args.redirectUri }),
139
+ };
140
+ const response = await (0, http_client_1.request)({
141
+ httpMethod: 'POST',
142
+ baseUri: tokenUrl,
143
+ relativePath: '',
144
+ payload,
145
+ customFetch: options?.customFetch,
146
+ });
147
+ if (!(0, http_client_1.isSuccess)(response) || !response.data?.accessToken) {
148
+ throw new errors_1.CommsRequestError('Authentication token exchange failed.', response.status, response.data);
149
+ }
150
+ return response.data;
151
+ }
152
+ async function revokeAuthToken(args, options) {
153
+ const revokeUrl = options?.baseUrl
154
+ ? `${options.baseUrl}/oauth/revoke`
155
+ : 'https://comms.todoist.com/oauth/revoke';
156
+ const response = await (0, http_client_1.request)({
157
+ httpMethod: 'POST',
158
+ baseUri: revokeUrl,
159
+ relativePath: '',
160
+ payload: {
161
+ clientId: args.clientId,
162
+ clientSecret: args.clientSecret,
163
+ token: args.accessToken,
164
+ },
165
+ customFetch: options?.customFetch,
166
+ });
167
+ return (0, http_client_1.isSuccess)(response);
168
+ }
169
+ /**
170
+ * Registers a new OAuth client via Dynamic Client Registration (RFC 7591).
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * const client = await registerClient({
175
+ * redirectUris: ['https://example.com/callback'],
176
+ * clientName: 'My App',
177
+ * scope: ['user:read', 'channels:read'],
178
+ * })
179
+ * // Use client.clientId and client.clientSecret for OAuth flows
180
+ * ```
181
+ *
182
+ * @returns The registered client details
183
+ * @throws {@link CommsRequestError} If the registration fails
184
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7591 RFC 7591}
185
+ */
186
+ async function registerClient(args, options) {
187
+ const registerUrl = options?.baseUrl
188
+ ? `${options.baseUrl}/oauth/register`
189
+ : 'https://comms.todoist.com/oauth/register';
190
+ const response = await (0, http_client_1.request)({
191
+ httpMethod: 'POST',
192
+ baseUri: registerUrl,
193
+ relativePath: '',
194
+ payload: { ...args, scope: args.scope?.join(' ') },
195
+ customFetch: options?.customFetch,
196
+ });
197
+ if (!(0, http_client_1.isSuccess)(response) || !response.data?.clientId) {
198
+ throw new errors_1.CommsRequestError('Dynamic client registration failed.', response.status, response.data);
199
+ }
200
+ const { clientIdIssuedAt, clientSecretExpiresAt, scope, ...rest } = response.data;
201
+ return {
202
+ ...rest,
203
+ scope: scope ? scope.split(' ') : undefined,
204
+ clientIdIssuedAt: clientIdIssuedAt !== undefined ? new Date(clientIdIssuedAt * 1000) : undefined,
205
+ clientSecretExpiresAt: clientSecretExpiresAt === undefined
206
+ ? undefined
207
+ : clientSecretExpiresAt === 0
208
+ ? null
209
+ : new Date(clientSecretExpiresAt * 1000),
210
+ };
211
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addCommentRequest = addCommentRequest;
4
+ const endpoints_1 = require("../consts/endpoints");
5
+ const http_client_1 = require("../transport/http-client");
6
+ const entities_1 = require("../types/entities");
7
+ const enums_1 = require("../types/enums");
8
+ const uuidv7_1 = require("../utils/uuidv7");
9
+ const SENTINEL_GROUP_IDS = new Set(Object.values(enums_1.NOTIFY_AUDIENCE_GROUP_IDS));
10
+ function isNotifyAudience(value) {
11
+ return typeof value === 'string' && enums_1.NOTIFY_AUDIENCES.includes(value);
12
+ }
13
+ function collectMarkerOffenses(field, values) {
14
+ if (!values)
15
+ return null;
16
+ const offending = values.filter((id) => SENTINEL_GROUP_IDS.has(id));
17
+ return offending.length > 0 ? { field, offending } : null;
18
+ }
19
+ function applyNotifyAudience(params) {
20
+ const offenses = [
21
+ collectMarkerOffenses('groups', params.groups ?? undefined),
22
+ collectMarkerOffenses('directGroupMentions', params.directGroupMentions ?? undefined),
23
+ ].filter((o) => o !== null);
24
+ if (offenses.length > 0) {
25
+ const details = offenses
26
+ .map(({ field, offending }) => `\`${field}\` contains ${offending.join(', ')}`)
27
+ .join('; ');
28
+ throw new Error(`Reserved broadcast marker IDs found: ${details}. Pass these via \`notifyAudience\` on createComment / closeThread / reopenThread (e.g. \`notifyAudience: 'channel'\` for EVERYONE) instead of populating \`groups\` / \`directGroupMentions\` directly.`);
29
+ }
30
+ if (params.notifyAudience == null)
31
+ return params;
32
+ if (!isNotifyAudience(params.notifyAudience)) {
33
+ throw new Error(`Invalid \`notifyAudience\` value "${String(params.notifyAudience)}". Expected one of: ${enums_1.NOTIFY_AUDIENCES.join(', ')}.`);
34
+ }
35
+ const sentinel = enums_1.NOTIFY_AUDIENCE_GROUP_IDS[params.notifyAudience];
36
+ const { notifyAudience: _stripped, groups, ...rest } = params;
37
+ return { ...rest, groups: [...(groups ?? []), sentinel] };
38
+ }
39
+ function addCommentRequest(context, params, options) {
40
+ const normalized = applyNotifyAudience(params);
41
+ const withId = { ...normalized, id: (0, uuidv7_1.resolveCreateId)(normalized.id) };
42
+ const payload = options?.threadAction
43
+ ? { ...withId, threadAction: options.threadAction }
44
+ : withId;
45
+ return (0, http_client_1.request)({
46
+ httpMethod: 'POST',
47
+ baseUri: context.baseUri,
48
+ relativePath: `${endpoints_1.ENDPOINT_COMMENTS}/add`,
49
+ apiToken: context.apiToken,
50
+ payload,
51
+ customFetch: context.customFetch,
52
+ }).then((response) => entities_1.CommentSchema.parse(response.data));
53
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseClient = void 0;
4
+ const endpoints_1 = require("../consts/endpoints");
5
+ /**
6
+ * Base class for every Comms API client. Centralizes URL handling and
7
+ * config so individual clients stay focused on their endpoints.
8
+ */
9
+ class BaseClient {
10
+ constructor(config) {
11
+ this.apiToken = config.apiToken;
12
+ this.baseUrl = config.baseUrl;
13
+ this.customFetch = config.customFetch;
14
+ }
15
+ /**
16
+ * Returns the base URI for an API request, with a guaranteed trailing
17
+ * slash so relative paths resolve cleanly through `URL`.
18
+ */
19
+ getBaseUri() {
20
+ if (this.baseUrl) {
21
+ const normalizedBaseUrl = this.baseUrl.endsWith('/') ? this.baseUrl : `${this.baseUrl}/`;
22
+ return `${normalizedBaseUrl}api/v1/`;
23
+ }
24
+ return (0, endpoints_1.getCommsBaseUri)();
25
+ }
26
+ }
27
+ exports.BaseClient = BaseClient;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChannelsClient = exports.ChannelListSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const endpoints_1 = require("../consts/endpoints");
6
+ const http_client_1 = require("../transport/http-client");
7
+ const entities_1 = require("../types/entities");
8
+ const uuidv7_1 = require("../utils/uuidv7");
9
+ const base_client_1 = require("./base-client");
10
+ exports.ChannelListSchema = zod_1.z.array(entities_1.ChannelSchema);
11
+ /**
12
+ * Client for `/api/v1/channels/`. The SDK auto-generates an `id` on
13
+ * `createChannel` when the caller doesn't supply one — pass your own `id`
14
+ * to keep an optimistic-UI ID stable through the round-trip.
15
+ */
16
+ class ChannelsClient extends base_client_1.BaseClient {
17
+ /** Lists channels in a workspace. */
18
+ getChannels(args) {
19
+ return (0, http_client_1.request)({
20
+ httpMethod: 'GET',
21
+ baseUri: this.getBaseUri(),
22
+ relativePath: `${endpoints_1.ENDPOINT_CHANNELS}/get`,
23
+ apiToken: this.apiToken,
24
+ payload: args,
25
+ customFetch: this.customFetch,
26
+ }).then((response) => exports.ChannelListSchema.parse(response.data));
27
+ }
28
+ /** Fetches a single channel by ID. */
29
+ getChannel(id) {
30
+ return this.simple('GET', 'getone', { id }, entities_1.ChannelSchema);
31
+ }
32
+ /** Creates a new channel. `id` is auto-generated if not supplied. */
33
+ createChannel(args) {
34
+ return this.simple('POST', 'add', { ...args, id: (0, uuidv7_1.resolveCreateId)(args.id) }, entities_1.ChannelSchema);
35
+ }
36
+ /** Partial update of an existing channel. */
37
+ updateChannel(args) {
38
+ return this.simple('POST', 'update', { ...args }, entities_1.ChannelSchema);
39
+ }
40
+ /** Updates the channel's view filter (`only_open` / `all` / `only_closed`). */
41
+ updateFilters(args) {
42
+ return this.simple('POST', 'update_filters', { ...args }, entities_1.StatusOkSchema);
43
+ }
44
+ /** Permanently deletes a channel. */
45
+ deleteChannel(id) {
46
+ return this.simple('POST', 'remove', { id }, entities_1.StatusOkSchema);
47
+ }
48
+ archiveChannel(id) {
49
+ return this.simple('POST', 'archive', { id }, entities_1.StatusOkSchema);
50
+ }
51
+ unarchiveChannel(id) {
52
+ return this.simple('POST', 'unarchive', { id }, entities_1.StatusOkSchema);
53
+ }
54
+ favoriteChannel(id) {
55
+ return this.simple('POST', 'favorite', { id }, entities_1.StatusOkSchema);
56
+ }
57
+ unfavoriteChannel(id) {
58
+ return this.simple('POST', 'unfavorite', { id }, entities_1.StatusOkSchema);
59
+ }
60
+ addUser(args) {
61
+ return this.simple('POST', 'add_user', { ...args }, entities_1.ChannelSchema);
62
+ }
63
+ addUsers(args) {
64
+ return this.simple('POST', 'add_users', { ...args }, entities_1.ChannelSchema);
65
+ }
66
+ removeUser(args) {
67
+ return this.simple('POST', 'remove_user', { ...args }, entities_1.ChannelSchema);
68
+ }
69
+ removeUsers(args) {
70
+ return this.simple('POST', 'remove_users', { ...args }, entities_1.ChannelSchema);
71
+ }
72
+ simple(httpMethod, suffix, params, schema) {
73
+ return (0, http_client_1.request)({
74
+ httpMethod,
75
+ baseUri: this.getBaseUri(),
76
+ relativePath: `${endpoints_1.ENDPOINT_CHANNELS}/${suffix}`,
77
+ apiToken: this.apiToken,
78
+ payload: params,
79
+ customFetch: this.customFetch,
80
+ }).then((response) => schema.parse(response.data));
81
+ }
82
+ }
83
+ exports.ChannelsClient = ChannelsClient;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CommentsClient = exports.CommentListSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const endpoints_1 = require("../consts/endpoints");
6
+ const http_client_1 = require("../transport/http-client");
7
+ const entities_1 = require("../types/entities");
8
+ const add_comment_helper_1 = require("./add-comment-helper");
9
+ const base_client_1 = require("./base-client");
10
+ exports.CommentListSchema = zod_1.z.array(entities_1.CommentSchema);
11
+ /**
12
+ * Client for `/api/v1/comments/`. The SDK auto-generates the comment `id`
13
+ * on `createComment` when the caller doesn't supply one.
14
+ */
15
+ class CommentsClient extends base_client_1.BaseClient {
16
+ /**
17
+ * Lists comments in a thread. `newerThan` / `olderThan` (`Date`) are
18
+ * converted to `newer_than_ts` / `older_than_ts` epoch seconds on the
19
+ * wire.
20
+ */
21
+ getComments(args) {
22
+ const params = { threadId: args.threadId };
23
+ const newerThan = args.newerThan ?? args.from;
24
+ if (newerThan)
25
+ params.newerThanTs = Math.floor(newerThan.getTime() / 1000);
26
+ if (args.olderThan)
27
+ params.olderThanTs = Math.floor(args.olderThan.getTime() / 1000);
28
+ if (args.limit)
29
+ params.limit = args.limit;
30
+ return (0, http_client_1.request)({
31
+ httpMethod: 'GET',
32
+ baseUri: this.getBaseUri(),
33
+ relativePath: `${endpoints_1.ENDPOINT_COMMENTS}/get`,
34
+ apiToken: this.apiToken,
35
+ payload: params,
36
+ customFetch: this.customFetch,
37
+ }).then((response) => exports.CommentListSchema.parse(response.data));
38
+ }
39
+ /** Fetches a single comment by ID. The API wraps it in `{comment: ...}`. */
40
+ getComment(id) {
41
+ const wrappedSchema = zod_1.z.object({ comment: entities_1.CommentSchema }).transform((data) => data.comment);
42
+ return (0, http_client_1.request)({
43
+ httpMethod: 'GET',
44
+ baseUri: this.getBaseUri(),
45
+ relativePath: `${endpoints_1.ENDPOINT_COMMENTS}/getone`,
46
+ apiToken: this.apiToken,
47
+ payload: { id },
48
+ customFetch: this.customFetch,
49
+ }).then((response) => wrappedSchema.parse(response.data));
50
+ }
51
+ /**
52
+ * Creates a new comment. `id` is auto-generated if not supplied.
53
+ */
54
+ createComment(args) {
55
+ return (0, add_comment_helper_1.addCommentRequest)({ baseUri: this.getBaseUri(), apiToken: this.apiToken, customFetch: this.customFetch }, args);
56
+ }
57
+ /** Updates a comment. */
58
+ updateComment(args) {
59
+ return (0, http_client_1.request)({
60
+ httpMethod: 'POST',
61
+ baseUri: this.getBaseUri(),
62
+ relativePath: `${endpoints_1.ENDPOINT_COMMENTS}/update`,
63
+ apiToken: this.apiToken,
64
+ payload: { ...args },
65
+ customFetch: this.customFetch,
66
+ }).then((response) => entities_1.CommentSchema.parse(response.data));
67
+ }
68
+ /** Permanently deletes a comment. */
69
+ deleteComment(id) {
70
+ return (0, http_client_1.request)({
71
+ httpMethod: 'POST',
72
+ baseUri: this.getBaseUri(),
73
+ relativePath: `${endpoints_1.ENDPOINT_COMMENTS}/remove`,
74
+ apiToken: this.apiToken,
75
+ payload: { id },
76
+ customFetch: this.customFetch,
77
+ }).then((response) => entities_1.StatusOkSchema.parse(response.data));
78
+ }
79
+ /**
80
+ * Marks the user's read position in a thread. Comment IDs are strings.
81
+ */
82
+ markPosition(args) {
83
+ return (0, http_client_1.request)({
84
+ httpMethod: 'POST',
85
+ baseUri: this.getBaseUri(),
86
+ relativePath: `${endpoints_1.ENDPOINT_COMMENTS}/mark_position`,
87
+ apiToken: this.apiToken,
88
+ payload: { threadId: args.threadId, commentId: args.commentId },
89
+ customFetch: this.customFetch,
90
+ }).then((response) => entities_1.StatusOkSchema.parse(response.data));
91
+ }
92
+ }
93
+ exports.CommentsClient = CommentsClient;