@doist/comms-sdk 0.5.1 → 0.6.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 CHANGED
@@ -121,6 +121,31 @@ const refreshed = await refreshAuthToken({
121
121
  const api = new CommsApi(refreshed.accessToken)
122
122
  ```
123
123
 
124
+ ### REST hooks
125
+
126
+ REST hooks require a Todoist OAuth access token with Todoist Comms audience. Subscribe
127
+ with an HTTPS target URL up to 150 characters and one of the exported
128
+ `HOOK_EVENTS`; the backend will POST webhook payloads to that URL when
129
+ matching events happen:
130
+
131
+ ```typescript
132
+ import { CommsApi, HOOK_EVENTS } from '@doist/comms-sdk'
133
+
134
+ const api = new CommsApi('TODOIST_OAUTH_ACCESS_TOKEN')
135
+
136
+ const subscription = await api.hooks.subscribe({
137
+ targetUrl: 'https://example.com/comms-hook',
138
+ event: 'thread_added',
139
+ workspaceId: 1,
140
+ })
141
+
142
+ console.log(subscription.id, HOOK_EVENTS)
143
+
144
+ await api.hooks.unsubscribe({
145
+ targetUrl: 'https://example.com/comms-hook',
146
+ })
147
+ ```
148
+
124
149
  ### Short-lived processes (CLIs, scripts)
125
150
 
126
151
  On Node, the SDK keeps a connection pool alive across requests so HTTP/2
@@ -106,7 +106,11 @@ class GroupsClient extends base_client_1.BaseClient {
106
106
  * @param args.userId - The user ID to add.
107
107
  */
108
108
  addUser(args) {
109
- return this.simple('POST', 'add_user', { ...args }, entities_1.StatusOkSchema);
109
+ // The backend only exposes the plural `add_users` endpoint (routed via
110
+ // Todoist); there is no `add_user`. Delegate to `addUsers` so the wire
111
+ // contract lives in one place.
112
+ const { userId, ...rest } = args;
113
+ return this.addUsers({ ...rest, userIds: [userId] });
110
114
  }
111
115
  /**
112
116
  * Adds multiple users to a group.
@@ -137,7 +141,11 @@ class GroupsClient extends base_client_1.BaseClient {
137
141
  * @param args.userId - The user ID to remove.
138
142
  */
139
143
  removeUser(args) {
140
- return this.simple('POST', 'remove_user', { ...args }, entities_1.StatusOkSchema);
144
+ // The backend only exposes the plural `remove_users` endpoint (routed
145
+ // via Todoist); there is no `remove_user`. Delegate to `removeUsers` so
146
+ // the wire contract lives in one place.
147
+ const { userId, ...rest } = args;
148
+ return this.removeUsers({ ...rest, userIds: [userId] });
141
149
  }
142
150
  /**
143
151
  * Removes multiple users from a group.
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HooksClient = void 0;
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 base_client_1 = require("./base-client");
8
+ /**
9
+ * Client for `/api/v1/hooks/`. REST hooks require Todoist OAuth bearer tokens
10
+ * that include Comms audience; session tokens are rejected by the backend.
11
+ */
12
+ class HooksClient extends base_client_1.BaseClient {
13
+ /**
14
+ * Subscribes the current OAuth app to a REST hook event.
15
+ *
16
+ * @param args - The hook subscription parameters.
17
+ * @param args.targetUrl - HTTPS endpoint, up to 150 characters, that will receive webhook POSTs.
18
+ * @param args.event - Event name to subscribe to.
19
+ * @param args.workspaceId - Optional workspace filter.
20
+ * @param args.channelId - Optional channel filter.
21
+ * @param args.threadId - Optional thread filter.
22
+ * @param args.conversationId - Optional conversation filter for message events.
23
+ * @returns The `{ status: 'ok', id }` response for the subscription.
24
+ */
25
+ subscribe(args) {
26
+ return (0, http_client_1.request)({
27
+ httpMethod: 'POST',
28
+ baseUri: this.getBaseUri(),
29
+ relativePath: `${endpoints_1.ENDPOINT_HOOKS}/subscribe`,
30
+ apiToken: this.apiToken,
31
+ payload: args,
32
+ customFetch: this.customFetch,
33
+ }).then((response) => entities_1.HookSubscribeResponseSchema.parse(response.data));
34
+ }
35
+ /**
36
+ * Unsubscribes all hooks with the target URL for the current OAuth app.
37
+ *
38
+ * @param args - The hook unsubscribe parameters.
39
+ * @param args.targetUrl - Target URL to remove.
40
+ */
41
+ unsubscribe(args) {
42
+ return (0, http_client_1.request)({
43
+ httpMethod: 'POST',
44
+ baseUri: this.getBaseUri(),
45
+ relativePath: `${endpoints_1.ENDPOINT_HOOKS}/unsubscribe`,
46
+ apiToken: this.apiToken,
47
+ payload: args,
48
+ customFetch: this.customFetch,
49
+ }).then((response) => entities_1.StatusOkSchema.parse(response.data));
50
+ }
51
+ }
52
+ exports.HooksClient = HooksClient;
@@ -181,7 +181,8 @@ class ThreadsClient extends base_client_1.BaseClient {
181
181
  * @param args.objIndex - The index of the last known read message.
182
182
  */
183
183
  markRead(args) {
184
- return this.simple('POST', 'mark_read', { ...args }, entities_1.StatusOkSchema);
184
+ const { id, ...rest } = args;
185
+ return this.simple('POST', 'mark_read', { threadId: id, ...rest }, entities_1.StatusOkSchema);
185
186
  }
186
187
  /**
187
188
  * Marks a thread as unread.
@@ -191,7 +192,8 @@ class ThreadsClient extends base_client_1.BaseClient {
191
192
  * @param args.objIndex - The index of the last unread message. Use -1 to mark the whole thread as unread.
192
193
  */
193
194
  markUnread(args) {
194
- return this.simple('POST', 'mark_unread', { ...args }, entities_1.StatusOkSchema);
195
+ const { id, ...rest } = args;
196
+ return this.simple('POST', 'mark_unread', { threadId: id, ...rest }, entities_1.StatusOkSchema);
195
197
  }
196
198
  /**
197
199
  * Marks a thread as unread for others. Useful to notify others about thread changes.
@@ -7,6 +7,7 @@ const comments_client_1 = require("./clients/comments-client");
7
7
  const conversation_messages_client_1 = require("./clients/conversation-messages-client");
8
8
  const conversations_client_1 = require("./clients/conversations-client");
9
9
  const groups_client_1 = require("./clients/groups-client");
10
+ const hooks_client_1 = require("./clients/hooks-client");
10
11
  const inbox_client_1 = require("./clients/inbox-client");
11
12
  const reactions_client_1 = require("./clients/reactions-client");
12
13
  const search_client_1 = require("./clients/search-client");
@@ -53,6 +54,7 @@ class CommsApi {
53
54
  this.reactions = new reactions_client_1.ReactionsClient(clientConfig);
54
55
  this.search = new search_client_1.SearchClient(clientConfig);
55
56
  this.attachments = new attachments_client_1.AttachmentsClient(clientConfig);
57
+ this.hooks = new hooks_client_1.HooksClient(clientConfig);
56
58
  }
57
59
  /**
58
60
  * Drains the SDK's process-global connection pool. CLIs and scripts
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ENDPOINT_ATTACHMENTS = exports.ENDPOINT_CONVERSATION_MESSAGES = exports.ENDPOINT_SEARCH = exports.ENDPOINT_REACTIONS = exports.ENDPOINT_INBOX = exports.ENDPOINT_NOTIFICATIONS = exports.ENDPOINT_COMMENTS = exports.ENDPOINT_CONVERSATIONS = exports.ENDPOINT_GROUPS = exports.ENDPOINT_THREADS = exports.ENDPOINT_CHANNELS = exports.ENDPOINT_WORKSPACES = exports.ENDPOINT_USERS = void 0;
3
+ exports.ENDPOINT_HOOKS = exports.ENDPOINT_ATTACHMENTS = exports.ENDPOINT_CONVERSATION_MESSAGES = exports.ENDPOINT_SEARCH = exports.ENDPOINT_REACTIONS = exports.ENDPOINT_INBOX = exports.ENDPOINT_NOTIFICATIONS = exports.ENDPOINT_COMMENTS = exports.ENDPOINT_CONVERSATIONS = exports.ENDPOINT_GROUPS = exports.ENDPOINT_THREADS = exports.ENDPOINT_CHANNELS = exports.ENDPOINT_WORKSPACES = exports.ENDPOINT_USERS = void 0;
4
4
  exports.getCommsBaseUri = getCommsBaseUri;
5
5
  const api_version_1 = require("../types/api-version");
6
6
  const BASE_URI = 'https://comms.todoist.com';
@@ -31,3 +31,4 @@ exports.ENDPOINT_REACTIONS = 'reactions';
31
31
  exports.ENDPOINT_SEARCH = 'search';
32
32
  exports.ENDPOINT_CONVERSATION_MESSAGES = 'conversation_messages';
33
33
  exports.ENDPOINT_ATTACHMENTS = 'attachments';
34
+ exports.ENDPOINT_HOOKS = 'hooks';
package/dist/cjs/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.closeDefaultDispatcher = exports.CommsApi = exports.WorkspacesClient = exports.WorkspaceUsersClient = exports.UsersClient = exports.ThreadsClient = exports.SearchClient = exports.ReactionsClient = exports.InboxClient = exports.GroupsClient = exports.ConversationsClient = exports.ConversationMessagesClient = exports.CommentsClient = exports.ChannelsClient = exports.AttachmentsClient = void 0;
17
+ exports.closeDefaultDispatcher = exports.CommsApi = exports.WorkspacesClient = exports.WorkspaceUsersClient = exports.UsersClient = exports.ThreadsClient = exports.SearchClient = exports.ReactionsClient = exports.InboxClient = exports.HooksClient = exports.GroupsClient = exports.ConversationsClient = exports.ConversationMessagesClient = exports.CommentsClient = exports.ChannelsClient = exports.AttachmentsClient = void 0;
18
18
  __exportStar(require("./authentication"), exports);
19
19
  var attachments_client_1 = require("./clients/attachments-client");
20
20
  Object.defineProperty(exports, "AttachmentsClient", { enumerable: true, get: function () { return attachments_client_1.AttachmentsClient; } });
@@ -28,6 +28,8 @@ var conversations_client_1 = require("./clients/conversations-client");
28
28
  Object.defineProperty(exports, "ConversationsClient", { enumerable: true, get: function () { return conversations_client_1.ConversationsClient; } });
29
29
  var groups_client_1 = require("./clients/groups-client");
30
30
  Object.defineProperty(exports, "GroupsClient", { enumerable: true, get: function () { return groups_client_1.GroupsClient; } });
31
+ var hooks_client_1 = require("./clients/hooks-client");
32
+ Object.defineProperty(exports, "HooksClient", { enumerable: true, get: function () { return hooks_client_1.HooksClient; } });
31
33
  var inbox_client_1 = require("./clients/inbox-client");
32
34
  Object.defineProperty(exports, "InboxClient", { enumerable: true, get: function () { return inbox_client_1.InboxClient; } });
33
35
  var reactions_client_1 = require("./clients/reactions-client");
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SearchResultSchema = exports.SEARCH_RESULT_TYPES = exports.UnreadConversationSchema = exports.UnreadThreadSchema = exports.InboxThreadSchema = exports.ConversationMessageSchema = exports.ConversationMessageObjectSchema = exports.WorkspaceUserSchema = exports.CommentSchema = exports.CommentObjectSchema = exports.ConversationSchema = exports.ConversationObjectSchema = exports.GroupSchema = exports.ThreadSchema = exports.ThreadObjectSchema = exports.ChannelSchema = exports.ChannelObjectSchema = exports.WorkspaceSchema = exports.UserSchema = exports.BaseUserSchema = exports.RequestAttachmentSchema = exports.AttachmentSchema = exports.StatusOkSchema = exports.SystemMessageSchema = void 0;
3
+ exports.SearchResultSchema = exports.SEARCH_RESULT_TYPES = exports.UnreadConversationSchema = exports.UnreadThreadSchema = exports.InboxThreadSchema = exports.ConversationMessageSchema = exports.ConversationMessageObjectSchema = exports.WorkspaceUserSchema = exports.CommentSchema = exports.CommentObjectSchema = exports.ConversationSchema = exports.ConversationObjectSchema = exports.GroupSchema = exports.ThreadSchema = exports.ThreadObjectSchema = exports.ChannelSchema = exports.ChannelObjectSchema = exports.WorkspaceSchema = exports.UserSchema = exports.BaseUserSchema = exports.RequestAttachmentSchema = exports.AttachmentSchema = exports.HookSubscribeResponseSchema = exports.StatusOkSchema = exports.SystemMessageSchema = void 0;
4
4
  exports.createChannelSchema = createChannelSchema;
5
5
  exports.createThreadSchema = createThreadSchema;
6
6
  exports.createConversationSchema = createConversationSchema;
@@ -27,6 +27,9 @@ exports.SystemMessageSchema = zod_1.z.union([zod_1.z.string(), zod_1.z.unknown()
27
27
  * mark-read / mark-all-read / mute / clear-unread / etc.
28
28
  */
29
29
  exports.StatusOkSchema = zod_1.z.object({ status: zod_1.z.literal('ok') });
30
+ exports.HookSubscribeResponseSchema = exports.StatusOkSchema.extend({
31
+ id: zod_1.z.number().int(),
32
+ });
30
33
  // Attachment entity from API. Mirrors the canonical backend shape produced
31
34
  // by `unify_attachments` / `validate_file_attachment_json`. Only
32
35
  // `attachmentId` and `urlType` are guaranteed; everything else depends on
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.THREAD_ACTIONS = exports.ARCHIVE_FILTER_VALUES = exports.GetOrCreateConversationArgsSchema = exports.GetConversationsArgsSchema = exports.GetCommentsArgsSchema = exports.GetThreadsArgsSchema = exports.GetChannelsArgsSchema = exports.CreateMessageArgsSchema = exports.CreateConversationArgsSchema = exports.UpdateCommentArgsSchema = exports.CreateCommentArgsSchema = exports.UpdateThreadArgsSchema = exports.CreateThreadArgsSchema = exports.UpdateChannelArgsSchema = exports.CreateChannelArgsSchema = void 0;
3
+ exports.THREAD_ACTIONS = exports.UnsubscribeHookArgsSchema = exports.SubscribeHookArgsSchema = exports.SubscribeMessageHookArgsSchema = exports.SubscribeNonMessageHookArgsSchema = exports.SubscribeHookArgsBaseSchema = exports.HookTargetUrlSchema = exports.HookEventSchema = exports.MessageHookEventSchema = exports.NonMessageHookEventSchema = exports.HOOK_EVENTS = exports.MESSAGE_HOOK_EVENTS = exports.NON_MESSAGE_HOOK_EVENTS = exports.ARCHIVE_FILTER_VALUES = exports.GetOrCreateConversationArgsSchema = exports.GetConversationsArgsSchema = exports.GetCommentsArgsSchema = exports.GetThreadsArgsSchema = exports.GetChannelsArgsSchema = exports.CreateMessageArgsSchema = exports.CreateConversationArgsSchema = exports.UpdateCommentArgsSchema = exports.CreateCommentArgsSchema = exports.UpdateThreadArgsSchema = exports.CreateThreadArgsSchema = exports.UpdateChannelArgsSchema = exports.CreateChannelArgsSchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const entities_1 = require("./entities");
6
6
  const enums_1 = require("./enums");
@@ -113,5 +113,67 @@ exports.GetOrCreateConversationArgsSchema = zod_1.z.object({
113
113
  });
114
114
  // Inbox
115
115
  exports.ARCHIVE_FILTER_VALUES = ['active', 'archived', 'all'];
116
+ // Hooks
117
+ exports.NON_MESSAGE_HOOK_EVENTS = [
118
+ 'workspace_added',
119
+ 'workspace_updated',
120
+ 'workspace_deleted',
121
+ 'workspace_user_added',
122
+ 'workspace_user_updated',
123
+ 'workspace_user_removed',
124
+ 'channel_added',
125
+ 'channel_updated',
126
+ 'channel_deleted',
127
+ 'channel_user_added',
128
+ 'channel_user_removed',
129
+ 'thread_added',
130
+ 'thread_updated',
131
+ 'thread_deleted',
132
+ 'comment_added',
133
+ 'comment_updated',
134
+ 'comment_deleted',
135
+ 'group_added',
136
+ 'group_updated',
137
+ 'group_deleted',
138
+ 'group_user_added',
139
+ 'group_user_removed',
140
+ ];
141
+ exports.MESSAGE_HOOK_EVENTS = ['message_added', 'message_updated'];
142
+ exports.HOOK_EVENTS = [...exports.NON_MESSAGE_HOOK_EVENTS, ...exports.MESSAGE_HOOK_EVENTS];
143
+ exports.NonMessageHookEventSchema = zod_1.z.enum(exports.NON_MESSAGE_HOOK_EVENTS);
144
+ exports.MessageHookEventSchema = zod_1.z.enum(exports.MESSAGE_HOOK_EVENTS);
145
+ exports.HookEventSchema = zod_1.z.enum(exports.HOOK_EVENTS);
146
+ exports.HookTargetUrlSchema = zod_1.z
147
+ .url()
148
+ .max(150)
149
+ .refine((value) => {
150
+ try {
151
+ return new URL(value).protocol === 'https:';
152
+ }
153
+ catch {
154
+ return false;
155
+ }
156
+ }, { message: 'URL must use HTTPS' });
157
+ exports.SubscribeHookArgsBaseSchema = zod_1.z.object({
158
+ targetUrl: exports.HookTargetUrlSchema,
159
+ workspaceId: zod_1.z.number().nullable().optional(),
160
+ channelId: zod_1.z.string().nullable().optional(),
161
+ threadId: zod_1.z.string().nullable().optional(),
162
+ });
163
+ exports.SubscribeNonMessageHookArgsSchema = exports.SubscribeHookArgsBaseSchema.extend({
164
+ event: exports.NonMessageHookEventSchema,
165
+ conversationId: zod_1.z.never().optional(),
166
+ });
167
+ exports.SubscribeMessageHookArgsSchema = exports.SubscribeHookArgsBaseSchema.extend({
168
+ event: exports.MessageHookEventSchema,
169
+ conversationId: zod_1.z.string().nullable().optional(),
170
+ });
171
+ exports.SubscribeHookArgsSchema = zod_1.z.union([
172
+ exports.SubscribeNonMessageHookArgsSchema,
173
+ exports.SubscribeMessageHookArgsSchema,
174
+ ]);
175
+ exports.UnsubscribeHookArgsSchema = zod_1.z.object({
176
+ targetUrl: exports.HookTargetUrlSchema,
177
+ });
116
178
  // Threads
117
179
  exports.THREAD_ACTIONS = ['close', 'reopen'];
@@ -103,7 +103,11 @@ export class GroupsClient extends BaseClient {
103
103
  * @param args.userId - The user ID to add.
104
104
  */
105
105
  addUser(args) {
106
- return this.simple('POST', 'add_user', { ...args }, StatusOkSchema);
106
+ // The backend only exposes the plural `add_users` endpoint (routed via
107
+ // Todoist); there is no `add_user`. Delegate to `addUsers` so the wire
108
+ // contract lives in one place.
109
+ const { userId, ...rest } = args;
110
+ return this.addUsers({ ...rest, userIds: [userId] });
107
111
  }
108
112
  /**
109
113
  * Adds multiple users to a group.
@@ -134,7 +138,11 @@ export class GroupsClient extends BaseClient {
134
138
  * @param args.userId - The user ID to remove.
135
139
  */
136
140
  removeUser(args) {
137
- return this.simple('POST', 'remove_user', { ...args }, StatusOkSchema);
141
+ // The backend only exposes the plural `remove_users` endpoint (routed
142
+ // via Todoist); there is no `remove_user`. Delegate to `removeUsers` so
143
+ // the wire contract lives in one place.
144
+ const { userId, ...rest } = args;
145
+ return this.removeUsers({ ...rest, userIds: [userId] });
138
146
  }
139
147
  /**
140
148
  * Removes multiple users from a group.
@@ -0,0 +1,48 @@
1
+ import { ENDPOINT_HOOKS } from '../consts/endpoints.js';
2
+ import { request } from '../transport/http-client.js';
3
+ import { HookSubscribeResponseSchema, StatusOkSchema, } from '../types/entities.js';
4
+ import { BaseClient } from './base-client.js';
5
+ /**
6
+ * Client for `/api/v1/hooks/`. REST hooks require Todoist OAuth bearer tokens
7
+ * that include Comms audience; session tokens are rejected by the backend.
8
+ */
9
+ export class HooksClient extends BaseClient {
10
+ /**
11
+ * Subscribes the current OAuth app to a REST hook event.
12
+ *
13
+ * @param args - The hook subscription parameters.
14
+ * @param args.targetUrl - HTTPS endpoint, up to 150 characters, that will receive webhook POSTs.
15
+ * @param args.event - Event name to subscribe to.
16
+ * @param args.workspaceId - Optional workspace filter.
17
+ * @param args.channelId - Optional channel filter.
18
+ * @param args.threadId - Optional thread filter.
19
+ * @param args.conversationId - Optional conversation filter for message events.
20
+ * @returns The `{ status: 'ok', id }` response for the subscription.
21
+ */
22
+ subscribe(args) {
23
+ return request({
24
+ httpMethod: 'POST',
25
+ baseUri: this.getBaseUri(),
26
+ relativePath: `${ENDPOINT_HOOKS}/subscribe`,
27
+ apiToken: this.apiToken,
28
+ payload: args,
29
+ customFetch: this.customFetch,
30
+ }).then((response) => HookSubscribeResponseSchema.parse(response.data));
31
+ }
32
+ /**
33
+ * Unsubscribes all hooks with the target URL for the current OAuth app.
34
+ *
35
+ * @param args - The hook unsubscribe parameters.
36
+ * @param args.targetUrl - Target URL to remove.
37
+ */
38
+ unsubscribe(args) {
39
+ return request({
40
+ httpMethod: 'POST',
41
+ baseUri: this.getBaseUri(),
42
+ relativePath: `${ENDPOINT_HOOKS}/unsubscribe`,
43
+ apiToken: this.apiToken,
44
+ payload: args,
45
+ customFetch: this.customFetch,
46
+ }).then((response) => StatusOkSchema.parse(response.data));
47
+ }
48
+ }
@@ -178,7 +178,8 @@ export class ThreadsClient extends BaseClient {
178
178
  * @param args.objIndex - The index of the last known read message.
179
179
  */
180
180
  markRead(args) {
181
- return this.simple('POST', 'mark_read', { ...args }, StatusOkSchema);
181
+ const { id, ...rest } = args;
182
+ return this.simple('POST', 'mark_read', { threadId: id, ...rest }, StatusOkSchema);
182
183
  }
183
184
  /**
184
185
  * Marks a thread as unread.
@@ -188,7 +189,8 @@ export class ThreadsClient extends BaseClient {
188
189
  * @param args.objIndex - The index of the last unread message. Use -1 to mark the whole thread as unread.
189
190
  */
190
191
  markUnread(args) {
191
- return this.simple('POST', 'mark_unread', { ...args }, StatusOkSchema);
192
+ const { id, ...rest } = args;
193
+ return this.simple('POST', 'mark_unread', { threadId: id, ...rest }, StatusOkSchema);
192
194
  }
193
195
  /**
194
196
  * Marks a thread as unread for others. Useful to notify others about thread changes.
@@ -4,6 +4,7 @@ import { CommentsClient } from './clients/comments-client.js';
4
4
  import { ConversationMessagesClient } from './clients/conversation-messages-client.js';
5
5
  import { ConversationsClient } from './clients/conversations-client.js';
6
6
  import { GroupsClient } from './clients/groups-client.js';
7
+ import { HooksClient } from './clients/hooks-client.js';
7
8
  import { InboxClient } from './clients/inbox-client.js';
8
9
  import { ReactionsClient } from './clients/reactions-client.js';
9
10
  import { SearchClient } from './clients/search-client.js';
@@ -50,6 +51,7 @@ export class CommsApi {
50
51
  this.reactions = new ReactionsClient(clientConfig);
51
52
  this.search = new SearchClient(clientConfig);
52
53
  this.attachments = new AttachmentsClient(clientConfig);
54
+ this.hooks = new HooksClient(clientConfig);
53
55
  }
54
56
  /**
55
57
  * Drains the SDK's process-global connection pool. CLIs and scripts
@@ -27,3 +27,4 @@ export const ENDPOINT_REACTIONS = 'reactions';
27
27
  export const ENDPOINT_SEARCH = 'search';
28
28
  export const ENDPOINT_CONVERSATION_MESSAGES = 'conversation_messages';
29
29
  export const ENDPOINT_ATTACHMENTS = 'attachments';
30
+ export const ENDPOINT_HOOKS = 'hooks';
package/dist/esm/index.js CHANGED
@@ -5,6 +5,7 @@ export { CommentsClient } from './clients/comments-client.js';
5
5
  export { ConversationMessagesClient } from './clients/conversation-messages-client.js';
6
6
  export { ConversationsClient } from './clients/conversations-client.js';
7
7
  export { GroupsClient } from './clients/groups-client.js';
8
+ export { HooksClient } from './clients/hooks-client.js';
8
9
  export { InboxClient } from './clients/inbox-client.js';
9
10
  export { ReactionsClient } from './clients/reactions-client.js';
10
11
  export { SearchClient } from './clients/search-client.js';
@@ -17,6 +17,9 @@ export const SystemMessageSchema = z.union([z.string(), z.unknown()]).nullable()
17
17
  * mark-read / mark-all-read / mute / clear-unread / etc.
18
18
  */
19
19
  export const StatusOkSchema = z.object({ status: z.literal('ok') });
20
+ export const HookSubscribeResponseSchema = StatusOkSchema.extend({
21
+ id: z.number().int(),
22
+ });
20
23
  // Attachment entity from API. Mirrors the canonical backend shape produced
21
24
  // by `unify_attachments` / `validate_file_attachment_json`. Only
22
25
  // `attachmentId` and `urlType` are guaranteed; everything else depends on
@@ -110,5 +110,67 @@ export const GetOrCreateConversationArgsSchema = z.object({
110
110
  });
111
111
  // Inbox
112
112
  export const ARCHIVE_FILTER_VALUES = ['active', 'archived', 'all'];
113
+ // Hooks
114
+ export const NON_MESSAGE_HOOK_EVENTS = [
115
+ 'workspace_added',
116
+ 'workspace_updated',
117
+ 'workspace_deleted',
118
+ 'workspace_user_added',
119
+ 'workspace_user_updated',
120
+ 'workspace_user_removed',
121
+ 'channel_added',
122
+ 'channel_updated',
123
+ 'channel_deleted',
124
+ 'channel_user_added',
125
+ 'channel_user_removed',
126
+ 'thread_added',
127
+ 'thread_updated',
128
+ 'thread_deleted',
129
+ 'comment_added',
130
+ 'comment_updated',
131
+ 'comment_deleted',
132
+ 'group_added',
133
+ 'group_updated',
134
+ 'group_deleted',
135
+ 'group_user_added',
136
+ 'group_user_removed',
137
+ ];
138
+ export const MESSAGE_HOOK_EVENTS = ['message_added', 'message_updated'];
139
+ export const HOOK_EVENTS = [...NON_MESSAGE_HOOK_EVENTS, ...MESSAGE_HOOK_EVENTS];
140
+ export const NonMessageHookEventSchema = z.enum(NON_MESSAGE_HOOK_EVENTS);
141
+ export const MessageHookEventSchema = z.enum(MESSAGE_HOOK_EVENTS);
142
+ export const HookEventSchema = z.enum(HOOK_EVENTS);
143
+ export const HookTargetUrlSchema = z
144
+ .url()
145
+ .max(150)
146
+ .refine((value) => {
147
+ try {
148
+ return new URL(value).protocol === 'https:';
149
+ }
150
+ catch {
151
+ return false;
152
+ }
153
+ }, { message: 'URL must use HTTPS' });
154
+ export const SubscribeHookArgsBaseSchema = z.object({
155
+ targetUrl: HookTargetUrlSchema,
156
+ workspaceId: z.number().nullable().optional(),
157
+ channelId: z.string().nullable().optional(),
158
+ threadId: z.string().nullable().optional(),
159
+ });
160
+ export const SubscribeNonMessageHookArgsSchema = SubscribeHookArgsBaseSchema.extend({
161
+ event: NonMessageHookEventSchema,
162
+ conversationId: z.never().optional(),
163
+ });
164
+ export const SubscribeMessageHookArgsSchema = SubscribeHookArgsBaseSchema.extend({
165
+ event: MessageHookEventSchema,
166
+ conversationId: z.string().nullable().optional(),
167
+ });
168
+ export const SubscribeHookArgsSchema = z.union([
169
+ SubscribeNonMessageHookArgsSchema,
170
+ SubscribeMessageHookArgsSchema,
171
+ ]);
172
+ export const UnsubscribeHookArgsSchema = z.object({
173
+ targetUrl: HookTargetUrlSchema,
174
+ });
113
175
  // Threads
114
176
  export const THREAD_ACTIONS = ['close', 'reopen'];
@@ -0,0 +1,29 @@
1
+ import { type HookSubscribeResponse, type StatusOk } from '../types/entities.js';
2
+ import type { SubscribeHookArgs, UnsubscribeHookArgs } from '../types/requests.js';
3
+ import { BaseClient } from './base-client.js';
4
+ /**
5
+ * Client for `/api/v1/hooks/`. REST hooks require Todoist OAuth bearer tokens
6
+ * that include Comms audience; session tokens are rejected by the backend.
7
+ */
8
+ export declare class HooksClient extends BaseClient {
9
+ /**
10
+ * Subscribes the current OAuth app to a REST hook event.
11
+ *
12
+ * @param args - The hook subscription parameters.
13
+ * @param args.targetUrl - HTTPS endpoint, up to 150 characters, that will receive webhook POSTs.
14
+ * @param args.event - Event name to subscribe to.
15
+ * @param args.workspaceId - Optional workspace filter.
16
+ * @param args.channelId - Optional channel filter.
17
+ * @param args.threadId - Optional thread filter.
18
+ * @param args.conversationId - Optional conversation filter for message events.
19
+ * @returns The `{ status: 'ok', id }` response for the subscription.
20
+ */
21
+ subscribe(args: SubscribeHookArgs): Promise<HookSubscribeResponse>;
22
+ /**
23
+ * Unsubscribes all hooks with the target URL for the current OAuth app.
24
+ *
25
+ * @param args - The hook unsubscribe parameters.
26
+ * @param args.targetUrl - Target URL to remove.
27
+ */
28
+ unsubscribe(args: UnsubscribeHookArgs): Promise<StatusOk>;
29
+ }
@@ -4,6 +4,7 @@ import { CommentsClient } from './clients/comments-client.js';
4
4
  import { ConversationMessagesClient } from './clients/conversation-messages-client.js';
5
5
  import { ConversationsClient } from './clients/conversations-client.js';
6
6
  import { GroupsClient } from './clients/groups-client.js';
7
+ import { HooksClient } from './clients/hooks-client.js';
7
8
  import { InboxClient } from './clients/inbox-client.js';
8
9
  import { ReactionsClient } from './clients/reactions-client.js';
9
10
  import { SearchClient } from './clients/search-client.js';
@@ -46,6 +47,7 @@ export declare class CommsApi {
46
47
  reactions: ReactionsClient;
47
48
  search: SearchClient;
48
49
  attachments: AttachmentsClient;
50
+ hooks: HooksClient;
49
51
  /**
50
52
  * Creates a new Comms API client.
51
53
  *
@@ -23,3 +23,4 @@ export declare const ENDPOINT_REACTIONS = "reactions";
23
23
  export declare const ENDPOINT_SEARCH = "search";
24
24
  export declare const ENDPOINT_CONVERSATION_MESSAGES = "conversation_messages";
25
25
  export declare const ENDPOINT_ATTACHMENTS = "attachments";
26
+ export declare const ENDPOINT_HOOKS = "hooks";
@@ -5,6 +5,7 @@ export { CommentsClient } from './clients/comments-client.js';
5
5
  export { ConversationMessagesClient } from './clients/conversation-messages-client.js';
6
6
  export { ConversationsClient } from './clients/conversations-client.js';
7
7
  export { GroupsClient } from './clients/groups-client.js';
8
+ export { HooksClient } from './clients/hooks-client.js';
8
9
  export { InboxClient } from './clients/inbox-client.js';
9
10
  export { ReactionsClient } from './clients/reactions-client.js';
10
11
  export { SearchClient } from './clients/search-client.js';
@@ -11,6 +11,11 @@ export declare const StatusOkSchema: z.ZodObject<{
11
11
  status: z.ZodLiteral<"ok">;
12
12
  }, z.core.$strip>;
13
13
  export type StatusOk = z.infer<typeof StatusOkSchema>;
14
+ export declare const HookSubscribeResponseSchema: z.ZodObject<{
15
+ status: z.ZodLiteral<"ok">;
16
+ id: z.ZodNumber;
17
+ }, z.core.$strip>;
18
+ export type HookSubscribeResponse = z.infer<typeof HookSubscribeResponseSchema>;
14
19
  export declare const AttachmentSchema: z.ZodObject<{
15
20
  attachmentId: z.ZodString;
16
21
  urlType: z.ZodString;
@@ -264,6 +264,161 @@ export type GetReactionsArgs = {
264
264
  commentId?: string;
265
265
  messageId?: string;
266
266
  };
267
+ export declare const NON_MESSAGE_HOOK_EVENTS: readonly ["workspace_added", "workspace_updated", "workspace_deleted", "workspace_user_added", "workspace_user_updated", "workspace_user_removed", "channel_added", "channel_updated", "channel_deleted", "channel_user_added", "channel_user_removed", "thread_added", "thread_updated", "thread_deleted", "comment_added", "comment_updated", "comment_deleted", "group_added", "group_updated", "group_deleted", "group_user_added", "group_user_removed"];
268
+ export type NonMessageHookEvent = (typeof NON_MESSAGE_HOOK_EVENTS)[number];
269
+ export declare const MESSAGE_HOOK_EVENTS: readonly ["message_added", "message_updated"];
270
+ export type MessageHookEvent = (typeof MESSAGE_HOOK_EVENTS)[number];
271
+ export declare const HOOK_EVENTS: readonly ["workspace_added", "workspace_updated", "workspace_deleted", "workspace_user_added", "workspace_user_updated", "workspace_user_removed", "channel_added", "channel_updated", "channel_deleted", "channel_user_added", "channel_user_removed", "thread_added", "thread_updated", "thread_deleted", "comment_added", "comment_updated", "comment_deleted", "group_added", "group_updated", "group_deleted", "group_user_added", "group_user_removed", "message_added", "message_updated"];
272
+ export type HookEvent = (typeof HOOK_EVENTS)[number];
273
+ export declare const NonMessageHookEventSchema: z.ZodEnum<{
274
+ workspace_added: "workspace_added";
275
+ workspace_updated: "workspace_updated";
276
+ workspace_deleted: "workspace_deleted";
277
+ workspace_user_added: "workspace_user_added";
278
+ workspace_user_updated: "workspace_user_updated";
279
+ workspace_user_removed: "workspace_user_removed";
280
+ channel_added: "channel_added";
281
+ channel_updated: "channel_updated";
282
+ channel_deleted: "channel_deleted";
283
+ channel_user_added: "channel_user_added";
284
+ channel_user_removed: "channel_user_removed";
285
+ thread_added: "thread_added";
286
+ thread_updated: "thread_updated";
287
+ thread_deleted: "thread_deleted";
288
+ comment_added: "comment_added";
289
+ comment_updated: "comment_updated";
290
+ comment_deleted: "comment_deleted";
291
+ group_added: "group_added";
292
+ group_updated: "group_updated";
293
+ group_deleted: "group_deleted";
294
+ group_user_added: "group_user_added";
295
+ group_user_removed: "group_user_removed";
296
+ }>;
297
+ export declare const MessageHookEventSchema: z.ZodEnum<{
298
+ message_added: "message_added";
299
+ message_updated: "message_updated";
300
+ }>;
301
+ export declare const HookEventSchema: z.ZodEnum<{
302
+ workspace_added: "workspace_added";
303
+ workspace_updated: "workspace_updated";
304
+ workspace_deleted: "workspace_deleted";
305
+ workspace_user_added: "workspace_user_added";
306
+ workspace_user_updated: "workspace_user_updated";
307
+ workspace_user_removed: "workspace_user_removed";
308
+ channel_added: "channel_added";
309
+ channel_updated: "channel_updated";
310
+ channel_deleted: "channel_deleted";
311
+ channel_user_added: "channel_user_added";
312
+ channel_user_removed: "channel_user_removed";
313
+ thread_added: "thread_added";
314
+ thread_updated: "thread_updated";
315
+ thread_deleted: "thread_deleted";
316
+ comment_added: "comment_added";
317
+ comment_updated: "comment_updated";
318
+ comment_deleted: "comment_deleted";
319
+ group_added: "group_added";
320
+ group_updated: "group_updated";
321
+ group_deleted: "group_deleted";
322
+ group_user_added: "group_user_added";
323
+ group_user_removed: "group_user_removed";
324
+ message_added: "message_added";
325
+ message_updated: "message_updated";
326
+ }>;
327
+ export declare const HookTargetUrlSchema: z.ZodURL;
328
+ export declare const SubscribeHookArgsBaseSchema: z.ZodObject<{
329
+ targetUrl: z.ZodURL;
330
+ workspaceId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
331
+ channelId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
332
+ threadId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
333
+ }, z.core.$strip>;
334
+ export declare const SubscribeNonMessageHookArgsSchema: z.ZodObject<{
335
+ targetUrl: z.ZodURL;
336
+ workspaceId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
337
+ channelId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
338
+ threadId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
339
+ event: z.ZodEnum<{
340
+ workspace_added: "workspace_added";
341
+ workspace_updated: "workspace_updated";
342
+ workspace_deleted: "workspace_deleted";
343
+ workspace_user_added: "workspace_user_added";
344
+ workspace_user_updated: "workspace_user_updated";
345
+ workspace_user_removed: "workspace_user_removed";
346
+ channel_added: "channel_added";
347
+ channel_updated: "channel_updated";
348
+ channel_deleted: "channel_deleted";
349
+ channel_user_added: "channel_user_added";
350
+ channel_user_removed: "channel_user_removed";
351
+ thread_added: "thread_added";
352
+ thread_updated: "thread_updated";
353
+ thread_deleted: "thread_deleted";
354
+ comment_added: "comment_added";
355
+ comment_updated: "comment_updated";
356
+ comment_deleted: "comment_deleted";
357
+ group_added: "group_added";
358
+ group_updated: "group_updated";
359
+ group_deleted: "group_deleted";
360
+ group_user_added: "group_user_added";
361
+ group_user_removed: "group_user_removed";
362
+ }>;
363
+ conversationId: z.ZodOptional<z.ZodNever>;
364
+ }, z.core.$strip>;
365
+ export declare const SubscribeMessageHookArgsSchema: z.ZodObject<{
366
+ targetUrl: z.ZodURL;
367
+ workspaceId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
368
+ channelId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
369
+ threadId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
370
+ event: z.ZodEnum<{
371
+ message_added: "message_added";
372
+ message_updated: "message_updated";
373
+ }>;
374
+ conversationId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
375
+ }, z.core.$strip>;
376
+ export declare const SubscribeHookArgsSchema: z.ZodUnion<readonly [z.ZodObject<{
377
+ targetUrl: z.ZodURL;
378
+ workspaceId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
379
+ channelId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
380
+ threadId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
381
+ event: z.ZodEnum<{
382
+ workspace_added: "workspace_added";
383
+ workspace_updated: "workspace_updated";
384
+ workspace_deleted: "workspace_deleted";
385
+ workspace_user_added: "workspace_user_added";
386
+ workspace_user_updated: "workspace_user_updated";
387
+ workspace_user_removed: "workspace_user_removed";
388
+ channel_added: "channel_added";
389
+ channel_updated: "channel_updated";
390
+ channel_deleted: "channel_deleted";
391
+ channel_user_added: "channel_user_added";
392
+ channel_user_removed: "channel_user_removed";
393
+ thread_added: "thread_added";
394
+ thread_updated: "thread_updated";
395
+ thread_deleted: "thread_deleted";
396
+ comment_added: "comment_added";
397
+ comment_updated: "comment_updated";
398
+ comment_deleted: "comment_deleted";
399
+ group_added: "group_added";
400
+ group_updated: "group_updated";
401
+ group_deleted: "group_deleted";
402
+ group_user_added: "group_user_added";
403
+ group_user_removed: "group_user_removed";
404
+ }>;
405
+ conversationId: z.ZodOptional<z.ZodNever>;
406
+ }, z.core.$strip>, z.ZodObject<{
407
+ targetUrl: z.ZodURL;
408
+ workspaceId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
409
+ channelId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
410
+ threadId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
411
+ event: z.ZodEnum<{
412
+ message_added: "message_added";
413
+ message_updated: "message_updated";
414
+ }>;
415
+ conversationId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
416
+ }, z.core.$strip>]>;
417
+ export type SubscribeHookArgs = z.infer<typeof SubscribeHookArgsSchema>;
418
+ export declare const UnsubscribeHookArgsSchema: z.ZodObject<{
419
+ targetUrl: z.ZodURL;
420
+ }, z.core.$strip>;
421
+ export type UnsubscribeHookArgs = z.infer<typeof UnsubscribeHookArgsSchema>;
267
422
  export type AddChannelUserArgs = {
268
423
  id: string;
269
424
  userId: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/comms-sdk",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "A TypeScript wrapper for the Comms REST API.",
5
5
  "author": "Doist developers",
6
6
  "homepage": "https://doist.github.io/comms-sdk-typescript/",