@doist/comms-sdk 0.2.0 → 0.2.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 (33) hide show
  1. package/dist/cjs/clients/add-comment-helper.js +1 -2
  2. package/dist/cjs/clients/base-client.js +8 -0
  3. package/dist/cjs/clients/channels-client.js +19 -8
  4. package/dist/cjs/clients/comments-client.js +24 -5
  5. package/dist/cjs/clients/conversation-messages-client.js +15 -4
  6. package/dist/cjs/clients/conversations-client.js +23 -12
  7. package/dist/cjs/clients/inbox-client.js +15 -2
  8. package/dist/cjs/clients/threads-client.js +27 -8
  9. package/dist/cjs/clients/workspaces-client.js +9 -1
  10. package/dist/cjs/types/entities.js +119 -98
  11. package/dist/cjs/utils/url-helpers.js +3 -1
  12. package/dist/esm/clients/add-comment-helper.js +1 -2
  13. package/dist/esm/clients/base-client.js +8 -0
  14. package/dist/esm/clients/channels-client.js +20 -9
  15. package/dist/esm/clients/comments-client.js +25 -6
  16. package/dist/esm/clients/conversation-messages-client.js +16 -5
  17. package/dist/esm/clients/conversations-client.js +24 -13
  18. package/dist/esm/clients/inbox-client.js +15 -2
  19. package/dist/esm/clients/threads-client.js +28 -9
  20. package/dist/esm/clients/workspaces-client.js +10 -2
  21. package/dist/esm/types/entities.js +111 -97
  22. package/dist/esm/utils/url-helpers.js +3 -1
  23. package/dist/types/clients/add-comment-helper.d.ts +3 -1
  24. package/dist/types/clients/base-client.d.ts +6 -0
  25. package/dist/types/clients/channels-client.d.ts +3 -0
  26. package/dist/types/clients/comments-client.d.ts +4 -0
  27. package/dist/types/clients/conversation-messages-client.d.ts +3 -0
  28. package/dist/types/clients/conversations-client.d.ts +3 -0
  29. package/dist/types/clients/inbox-client.d.ts +382 -0
  30. package/dist/types/clients/threads-client.d.ts +4 -0
  31. package/dist/types/clients/workspaces-client.d.ts +2 -0
  32. package/dist/types/types/entities.d.ts +1654 -126
  33. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { ENDPOINT_CONVERSATIONS } from '../consts/endpoints.js';
3
3
  import { request } from '../transport/http-client.js';
4
- import { ConversationSchema, StatusOkSchema, UnreadConversationSchema, } from '../types/entities.js';
4
+ import { ConversationSchema, createConversationSchema, StatusOkSchema, UnreadConversationSchema, } from '../types/entities.js';
5
5
  import { resolveCreateId } from '../utils/uuidv7.js';
6
6
  import { BaseClient } from './base-client.js';
7
7
  export const ConversationListSchema = z.array(ConversationSchema);
@@ -16,6 +16,17 @@ const GetUnreadResponseSchema = z.object({
16
16
  * already-assigned `id` and your generated one is silently dropped.
17
17
  */
18
18
  export class ConversationsClient extends BaseClient {
19
+ constructor() {
20
+ super(...arguments);
21
+ this.linkBaseUrl = this.getLinkBaseUrl();
22
+ // Reuse the shared singletons when no custom base is configured.
23
+ this.conversationSchema = this.linkBaseUrl
24
+ ? createConversationSchema(this.linkBaseUrl)
25
+ : ConversationSchema;
26
+ this.conversationListSchema = this.linkBaseUrl
27
+ ? z.array(this.conversationSchema)
28
+ : ConversationListSchema;
29
+ }
19
30
  /**
20
31
  * Gets all conversations for a workspace.
21
32
  *
@@ -38,7 +49,7 @@ export class ConversationsClient extends BaseClient {
38
49
  apiToken: this.apiToken,
39
50
  payload: args,
40
51
  customFetch: this.customFetch,
41
- }).then((response) => ConversationListSchema.parse(response.data));
52
+ }).then((response) => this.conversationListSchema.parse(response.data));
42
53
  }
43
54
  /**
44
55
  * Gets a single conversation object by id.
@@ -47,7 +58,7 @@ export class ConversationsClient extends BaseClient {
47
58
  * @returns The conversation object.
48
59
  */
49
60
  getConversation(id) {
50
- return this.simple('GET', 'getone', { id }, ConversationSchema);
61
+ return this.simple('GET', 'getone', { id }, this.conversationSchema);
51
62
  }
52
63
  /**
53
64
  * Gets an existing 1:1 / group conversation with `userIds`, or creates a
@@ -68,7 +79,7 @@ export class ConversationsClient extends BaseClient {
68
79
  * ```
69
80
  */
70
81
  getOrCreateConversation(args) {
71
- return this.simple('GET', 'get_or_create', { ...args, id: resolveCreateId(args.id) }, ConversationSchema);
82
+ return this.simple('GET', 'get_or_create', { ...args, id: resolveCreateId(args.id) }, this.conversationSchema);
72
83
  }
73
84
  /**
74
85
  * Updates a conversation's title.
@@ -91,7 +102,7 @@ export class ConversationsClient extends BaseClient {
91
102
  const params = { id: args.id, title: args.title };
92
103
  if (args.archived !== undefined)
93
104
  params.archived = args.archived;
94
- return this.simple('POST', 'update', params, ConversationSchema);
105
+ return this.simple('POST', 'update', params, this.conversationSchema);
95
106
  }
96
107
  /**
97
108
  * Archives a conversation.
@@ -100,7 +111,7 @@ export class ConversationsClient extends BaseClient {
100
111
  * @returns The updated conversation object.
101
112
  */
102
113
  archiveConversation(id) {
103
- return this.simple('GET', 'archive', { id }, ConversationSchema);
114
+ return this.simple('GET', 'archive', { id }, this.conversationSchema);
104
115
  }
105
116
  /**
106
117
  * Unarchives a conversation.
@@ -109,7 +120,7 @@ export class ConversationsClient extends BaseClient {
109
120
  * @returns The updated conversation object.
110
121
  */
111
122
  unarchiveConversation(id) {
112
- return this.simple('GET', 'unarchive', { id }, ConversationSchema);
123
+ return this.simple('GET', 'unarchive', { id }, this.conversationSchema);
113
124
  }
114
125
  /**
115
126
  * Adds a user to a conversation.
@@ -120,7 +131,7 @@ export class ConversationsClient extends BaseClient {
120
131
  * @returns The updated conversation object.
121
132
  */
122
133
  addUser(args) {
123
- return this.simple('POST', 'add_user', { ...args }, ConversationSchema);
134
+ return this.simple('POST', 'add_user', { ...args }, this.conversationSchema);
124
135
  }
125
136
  /**
126
137
  * Adds multiple users to a conversation.
@@ -136,7 +147,7 @@ export class ConversationsClient extends BaseClient {
136
147
  * ```
137
148
  */
138
149
  addUsers(args) {
139
- return this.simple('POST', 'add_users', { ...args }, ConversationSchema);
150
+ return this.simple('POST', 'add_users', { ...args }, this.conversationSchema);
140
151
  }
141
152
  /**
142
153
  * Removes a user from a conversation.
@@ -147,7 +158,7 @@ export class ConversationsClient extends BaseClient {
147
158
  * @returns The updated conversation object.
148
159
  */
149
160
  removeUser(args) {
150
- return this.simple('POST', 'remove_user', { ...args }, ConversationSchema);
161
+ return this.simple('POST', 'remove_user', { ...args }, this.conversationSchema);
151
162
  }
152
163
  /**
153
164
  * Removes multiple users from a conversation.
@@ -158,7 +169,7 @@ export class ConversationsClient extends BaseClient {
158
169
  * @returns The updated conversation object.
159
170
  */
160
171
  removeUsers(args) {
161
- return this.simple('POST', 'remove_users', { ...args }, ConversationSchema);
172
+ return this.simple('POST', 'remove_users', { ...args }, this.conversationSchema);
162
173
  }
163
174
  /**
164
175
  * Marks a conversation as read.
@@ -215,7 +226,7 @@ export class ConversationsClient extends BaseClient {
215
226
  * ```
216
227
  */
217
228
  muteConversation(args) {
218
- return this.simple('GET', 'mute', { ...args }, ConversationSchema);
229
+ return this.simple('GET', 'mute', { ...args }, this.conversationSchema);
219
230
  }
220
231
  /**
221
232
  * Unmutes a conversation.
@@ -224,7 +235,7 @@ export class ConversationsClient extends BaseClient {
224
235
  * @returns The updated conversation object.
225
236
  */
226
237
  unmuteConversation(id) {
227
- return this.simple('GET', 'unmute', { id }, ConversationSchema);
238
+ return this.simple('GET', 'unmute', { id }, this.conversationSchema);
228
239
  }
229
240
  simple(httpMethod, suffix, params, schema) {
230
241
  return request({
@@ -1,9 +1,22 @@
1
+ import { z } from 'zod';
1
2
  import { ENDPOINT_INBOX } from '../consts/endpoints.js';
2
3
  import { request } from '../transport/http-client.js';
3
- import { InboxThreadSchema } from '../types/entities.js';
4
+ import { createInboxThreadSchema, InboxThreadSchema } from '../types/entities.js';
4
5
  import { BaseClient } from './base-client.js';
6
+ export const InboxThreadListSchema = z.array(InboxThreadSchema);
5
7
  /** Client for `/api/v1/inbox/`. */
6
8
  export class InboxClient extends BaseClient {
9
+ constructor() {
10
+ super(...arguments);
11
+ this.linkBaseUrl = this.getLinkBaseUrl();
12
+ // Reuse the shared singletons when no custom base is configured.
13
+ this.inboxThreadSchema = this.linkBaseUrl
14
+ ? createInboxThreadSchema(this.linkBaseUrl)
15
+ : InboxThreadSchema;
16
+ this.inboxThreadListSchema = this.linkBaseUrl
17
+ ? z.array(this.inboxThreadSchema)
18
+ : InboxThreadListSchema;
19
+ }
7
20
  /**
8
21
  * Gets inbox items (threads).
9
22
  *
@@ -49,7 +62,7 @@ export class InboxClient extends BaseClient {
49
62
  apiToken: this.apiToken,
50
63
  payload: params,
51
64
  customFetch: this.customFetch,
52
- }).then((response) => response.data.map((thread) => InboxThreadSchema.parse(thread)));
65
+ }).then((response) => this.inboxThreadListSchema.parse(response.data));
53
66
  }
54
67
  /**
55
68
  * Gets unread count for inbox.
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { ENDPOINT_THREADS } from '../consts/endpoints.js';
3
3
  import { request } from '../transport/http-client.js';
4
- import { StatusOkSchema, ThreadSchema, UnreadThreadSchema, } from '../types/entities.js';
4
+ import { CommentSchema, createCommentSchema, createThreadSchema, StatusOkSchema, ThreadSchema, UnreadThreadSchema, } from '../types/entities.js';
5
5
  import { resolveCreateId } from '../utils/uuidv7.js';
6
6
  import { addCommentRequest } from './add-comment-helper.js';
7
7
  import { BaseClient } from './base-client.js';
@@ -16,6 +16,20 @@ const GetUnreadResponseSchema = z.object({
16
16
  * `createThread` when the caller doesn't supply one.
17
17
  */
18
18
  export class ThreadsClient extends BaseClient {
19
+ constructor() {
20
+ super(...arguments);
21
+ this.linkBaseUrl = this.getLinkBaseUrl();
22
+ // Reuse the shared singletons when no custom base is configured.
23
+ this.threadSchema = this.linkBaseUrl
24
+ ? createThreadSchema(this.linkBaseUrl)
25
+ : ThreadSchema;
26
+ this.threadListSchema = this.linkBaseUrl
27
+ ? z.array(this.threadSchema)
28
+ : ThreadListSchema;
29
+ this.commentSchema = this.linkBaseUrl
30
+ ? createCommentSchema(this.linkBaseUrl)
31
+ : CommentSchema;
32
+ }
19
33
  /**
20
34
  * Gets threads. At least one of `channelId` / `workspaceId` is required.
21
35
  * `newerThan` / `olderThan` (`Date`) are converted to the
@@ -58,7 +72,7 @@ export class ThreadsClient extends BaseClient {
58
72
  apiToken: this.apiToken,
59
73
  payload: params,
60
74
  customFetch: this.customFetch,
61
- }).then((response) => ThreadListSchema.parse(response.data));
75
+ }).then((response) => this.threadListSchema.parse(response.data));
62
76
  }
63
77
  /**
64
78
  * Gets a single thread object by id.
@@ -67,7 +81,7 @@ export class ThreadsClient extends BaseClient {
67
81
  * @returns The thread object.
68
82
  */
69
83
  getThread(id) {
70
- return this.simple('GET', 'getone', { id }, ThreadSchema);
84
+ return this.simple('GET', 'getone', { id }, this.threadSchema);
71
85
  }
72
86
  /**
73
87
  * Creates a new thread in a channel. `id` is auto-generated if not supplied.
@@ -90,7 +104,7 @@ export class ThreadsClient extends BaseClient {
90
104
  * ```
91
105
  */
92
106
  createThread(args) {
93
- return this.simple('POST', 'add', { ...args, id: resolveCreateId(args.id) }, ThreadSchema);
107
+ return this.simple('POST', 'add', { ...args, id: resolveCreateId(args.id) }, this.threadSchema);
94
108
  }
95
109
  /**
96
110
  * Partial update of an existing thread.
@@ -102,7 +116,7 @@ export class ThreadsClient extends BaseClient {
102
116
  * @returns The updated thread object.
103
117
  */
104
118
  updateThread(args) {
105
- return this.simple('POST', 'update', { ...args }, ThreadSchema);
119
+ return this.simple('POST', 'update', { ...args }, this.threadSchema);
106
120
  }
107
121
  /**
108
122
  * Permanently deletes a thread.
@@ -153,7 +167,7 @@ export class ThreadsClient extends BaseClient {
153
167
  * @returns The updated thread object.
154
168
  */
155
169
  moveToChannel(args) {
156
- return this.simple('GET', 'move_to_channel', { ...args }, ThreadSchema);
170
+ return this.simple('GET', 'move_to_channel', { ...args }, this.threadSchema);
157
171
  }
158
172
  /**
159
173
  * Marks a thread as read.
@@ -241,7 +255,7 @@ export class ThreadsClient extends BaseClient {
241
255
  * ```
242
256
  */
243
257
  muteThread(args) {
244
- return this.simple('GET', 'mute', { ...args }, ThreadSchema);
258
+ return this.simple('GET', 'mute', { ...args }, this.threadSchema);
245
259
  }
246
260
  /**
247
261
  * Unmutes a thread.
@@ -251,7 +265,7 @@ export class ThreadsClient extends BaseClient {
251
265
  * @returns The updated thread object.
252
266
  */
253
267
  unmuteThread(id) {
254
- return this.simple('GET', 'unmute', { id }, ThreadSchema);
268
+ return this.simple('GET', 'unmute', { id }, this.threadSchema);
255
269
  }
256
270
  /**
257
271
  * Closes a thread by adding a comment with a close action.
@@ -311,7 +325,12 @@ export class ThreadsClient extends BaseClient {
311
325
  }
312
326
  addCommentWithAction(args, threadAction) {
313
327
  const { id, ...rest } = args;
314
- return addCommentRequest({ baseUri: this.getBaseUri(), apiToken: this.apiToken, customFetch: this.customFetch }, { threadId: id, ...rest }, { threadAction });
328
+ return addCommentRequest({
329
+ baseUri: this.getBaseUri(),
330
+ apiToken: this.apiToken,
331
+ customFetch: this.customFetch,
332
+ schema: this.commentSchema,
333
+ }, { threadId: id, ...rest }, { threadAction });
315
334
  }
316
335
  simple(httpMethod, suffix, params, schema) {
317
336
  return request({
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { ENDPOINT_WORKSPACES } from '../consts/endpoints.js';
3
3
  import { request } from '../transport/http-client.js';
4
- import { ChannelSchema, WorkspaceSchema } from '../types/entities.js';
4
+ import { ChannelSchema, createChannelSchema, WorkspaceSchema, } from '../types/entities.js';
5
5
  import { BaseClient } from './base-client.js';
6
6
  export const ChannelListSchema = z.array(ChannelSchema);
7
7
  /**
@@ -9,6 +9,14 @@ export const ChannelListSchema = z.array(ChannelSchema);
9
9
  * currently rejects any `color` other than `1` on add/update.
10
10
  */
11
11
  export class WorkspacesClient extends BaseClient {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.linkBaseUrl = this.getLinkBaseUrl();
15
+ // Reuse the shared singleton when no custom base is configured.
16
+ this.channelListSchema = this.linkBaseUrl
17
+ ? z.array(createChannelSchema(this.linkBaseUrl))
18
+ : ChannelListSchema;
19
+ }
12
20
  /**
13
21
  * Gets all the user's workspaces.
14
22
  *
@@ -157,6 +165,6 @@ export class WorkspacesClient extends BaseClient {
157
165
  apiToken: this.apiToken,
158
166
  payload: { id },
159
167
  customFetch: this.customFetch,
160
- }).then((response) => ChannelListSchema.parse(response.data));
168
+ }).then((response) => this.channelListSchema.parse(response.data));
161
169
  }
162
170
  }
@@ -97,8 +97,7 @@ export const WorkspaceSchema = z.object({
97
97
  .optional(),
98
98
  plan: z.string().nullable().optional(),
99
99
  });
100
- export const ChannelSchema = z
101
- .object({
100
+ export const ChannelObjectSchema = z.object({
102
101
  id: z.string(),
103
102
  name: z.string(),
104
103
  description: z.string().nullable().optional(),
@@ -116,15 +115,17 @@ export const ChannelSchema = z
116
115
  icon: z.number().nullable().optional(),
117
116
  version: z.number(),
118
117
  filters: z.record(z.string(), z.string()).nullable().optional(),
119
- })
120
- .transform((data) => ({
121
- ...data,
122
- url: getFullCommsURL({ workspaceId: data.workspaceId, channelId: data.id }),
123
- }));
118
+ });
119
+ export function createChannelSchema(linkBaseUrl) {
120
+ return ChannelObjectSchema.transform((data) => ({
121
+ ...data,
122
+ url: getFullCommsURL({ workspaceId: data.workspaceId, channelId: data.id }, linkBaseUrl),
123
+ }));
124
+ }
125
+ export const ChannelSchema = createChannelSchema();
124
126
  // Thread entity from API. `pinned` (boolean) and `pinnedTs` (epoch ms or
125
127
  // null) are both surfaced — `pinned` is kept for Zapier/webhook clients.
126
- export const ThreadSchema = z
127
- .object({
128
+ export const ThreadObjectSchema = z.object({
128
129
  id: z.string(),
129
130
  title: z.string(),
130
131
  content: z.string(),
@@ -188,15 +189,18 @@ export const ThreadSchema = z
188
189
  })
189
190
  .nullable()
190
191
  .optional(),
191
- })
192
- .transform((data) => ({
193
- ...data,
194
- url: getFullCommsURL({
195
- workspaceId: data.workspaceId,
196
- channelId: data.channelId,
197
- threadId: data.id,
198
- }),
199
- }));
192
+ });
193
+ export function createThreadSchema(linkBaseUrl) {
194
+ return ThreadObjectSchema.transform((data) => ({
195
+ ...data,
196
+ url: getFullCommsURL({
197
+ workspaceId: data.workspaceId,
198
+ channelId: data.channelId,
199
+ threadId: data.id,
200
+ }, linkBaseUrl),
201
+ }));
202
+ }
203
+ export const ThreadSchema = createThreadSchema();
200
204
  // Group entity from API. The broadcast markers `EVERYONE` and
201
205
  // `EVERYONE_IN_THREAD` can appear in place of a real `id` in group-bearing
202
206
  // fields on threads/comments/channels.
@@ -209,8 +213,7 @@ export const GroupSchema = z.object({
209
213
  version: z.number(),
210
214
  });
211
215
  // Conversation entity from API.
212
- export const ConversationSchema = z
213
- .object({
216
+ export const ConversationObjectSchema = z.object({
214
217
  id: z.string(),
215
218
  workspaceId: z.number(),
216
219
  userIds: z.array(z.number()),
@@ -247,13 +250,15 @@ export const ConversationSchema = z
247
250
  })
248
251
  .nullable()
249
252
  .optional(),
250
- })
251
- .transform((data) => ({
252
- ...data,
253
- url: getFullCommsURL({ workspaceId: data.workspaceId, conversationId: data.id }),
254
- }));
255
- export const CommentSchema = z
256
- .object({
253
+ });
254
+ export function createConversationSchema(linkBaseUrl) {
255
+ return ConversationObjectSchema.transform((data) => ({
256
+ ...data,
257
+ url: getFullCommsURL({ workspaceId: data.workspaceId, conversationId: data.id }, linkBaseUrl),
258
+ }));
259
+ }
260
+ export const ConversationSchema = createConversationSchema();
261
+ export const CommentObjectSchema = z.object({
257
262
  id: z.string(),
258
263
  content: z.string(),
259
264
  creator: z.number(),
@@ -277,16 +282,19 @@ export const CommentSchema = z
277
282
  deletedBy: z.number().nullable().optional(),
278
283
  version: z.number().nullable().optional(),
279
284
  actions: z.array(z.unknown()).nullable().optional(),
280
- })
281
- .transform((data) => ({
282
- ...data,
283
- url: getFullCommsURL({
284
- workspaceId: data.workspaceId,
285
- channelId: data.channelId,
286
- threadId: data.threadId,
287
- commentId: data.id,
288
- }),
289
- }));
285
+ });
286
+ export function createCommentSchema(linkBaseUrl) {
287
+ return CommentObjectSchema.transform((data) => ({
288
+ ...data,
289
+ url: getFullCommsURL({
290
+ workspaceId: data.workspaceId,
291
+ channelId: data.channelId,
292
+ threadId: data.threadId,
293
+ commentId: data.id,
294
+ }, linkBaseUrl),
295
+ }));
296
+ }
297
+ export const CommentSchema = createCommentSchema();
290
298
  export const WorkspaceUserSchema = BaseUserSchema.extend({
291
299
  email: z.string().nullable().optional(),
292
300
  userType: z.enum(USER_TYPES),
@@ -299,8 +307,7 @@ export const WorkspaceUserSchema = BaseUserSchema.extend({
299
307
  // (coerced to a string post-parse) because the backend currently emits
300
308
  // either shape depending on the endpoint; the URL/reaction helpers accept
301
309
  // both.
302
- export const ConversationMessageSchema = z
303
- .object({
310
+ export const ConversationMessageObjectSchema = z.object({
304
311
  id: z.union([z.string(), z.number()]).transform(String),
305
312
  content: z.string(),
306
313
  creator: z.number(),
@@ -317,65 +324,72 @@ export const ConversationMessageSchema = z
317
324
  directMentions: z.array(z.number()).nullable().optional(),
318
325
  version: z.number().nullable().optional(),
319
326
  workspaceId: z.number(),
320
- })
321
- .transform((data) => ({
322
- ...data,
323
- url: getFullCommsURL({
324
- workspaceId: data.workspaceId,
325
- conversationId: data.conversationId,
326
- messageId: data.id,
327
- }),
328
- }));
327
+ });
328
+ export function createConversationMessageSchema(linkBaseUrl) {
329
+ return ConversationMessageObjectSchema.transform((data) => ({
330
+ ...data,
331
+ url: getFullCommsURL({
332
+ workspaceId: data.workspaceId,
333
+ conversationId: data.conversationId,
334
+ messageId: data.id,
335
+ }, linkBaseUrl),
336
+ }));
337
+ }
338
+ export const ConversationMessageSchema = createConversationMessageSchema();
329
339
  // InboxThread entity from API - returns full Thread objects with additional inbox metadata.
330
- export const InboxThreadSchema = z
331
- .object({
332
- id: z.string(),
333
- title: z.string(),
334
- content: z.string(),
335
- creator: z.number(),
336
- creatorName: z.string().nullable().optional(),
337
- channelId: z.string(),
338
- workspaceId: z.number(),
339
- actions: z.array(z.unknown()).nullable().optional(),
340
- attachments: z.array(AttachmentSchema).nullable().optional(),
341
- commentCount: z.number(),
342
- directGroupMentions: z.array(z.string()).nullable().optional(),
343
- directMentions: z.array(z.number()).nullable().optional(),
344
- groups: z.array(z.string()).nullable().optional(),
345
- lastEdited: z.date().nullable().optional(),
346
- lastObjIndex: z.number().nullable().optional(),
347
- lastUpdated: z.date(),
348
- mutedUntil: z.date().nullable().optional(),
349
- participants: z.array(z.number()).nullable().optional(),
350
- // Backend wire shape only includes `pinned_ts` (epoch ms or null);
351
- // derive `pinned` from `pinnedTs != null` if you need a bool.
352
- pinned: z.boolean().optional(),
353
- pinnedTs: z.number().int().nullable().optional(),
354
- posted: z.date(),
355
- reactions: z.record(z.string(), z.array(z.number())).nullable().optional(),
356
- recipients: z.array(z.number()).nullable().optional(),
357
- snippet: z.string(),
358
- snippetCreator: z.number(),
359
- snippetMaskAvatarUrl: z.string().nullable().optional(),
360
- snippetMaskPoster: z.string().nullable().optional(),
361
- systemMessage: SystemMessageSchema,
362
- isArchived: z.boolean(),
363
- inInbox: z.boolean(),
364
- isSaved: z.boolean().nullable().optional(),
365
- closed: z.boolean(),
366
- responders: z.array(z.number()).nullable().optional(),
367
- lastComment: CommentSchema.nullable().optional(),
368
- toEmails: z.array(z.string()).nullable().optional(),
369
- version: z.number().nullable().optional(),
370
- })
371
- .transform((data) => ({
372
- ...data,
373
- url: getFullCommsURL({
374
- workspaceId: data.workspaceId,
375
- channelId: data.channelId,
376
- threadId: data.id,
377
- }),
378
- }));
340
+ export function createInboxThreadObjectSchema(linkBaseUrl) {
341
+ return z.object({
342
+ id: z.string(),
343
+ title: z.string(),
344
+ content: z.string(),
345
+ creator: z.number(),
346
+ creatorName: z.string().nullable().optional(),
347
+ channelId: z.string(),
348
+ workspaceId: z.number(),
349
+ actions: z.array(z.unknown()).nullable().optional(),
350
+ attachments: z.array(AttachmentSchema).nullable().optional(),
351
+ commentCount: z.number(),
352
+ directGroupMentions: z.array(z.string()).nullable().optional(),
353
+ directMentions: z.array(z.number()).nullable().optional(),
354
+ groups: z.array(z.string()).nullable().optional(),
355
+ lastEdited: z.date().nullable().optional(),
356
+ lastObjIndex: z.number().nullable().optional(),
357
+ lastUpdated: z.date(),
358
+ mutedUntil: z.date().nullable().optional(),
359
+ participants: z.array(z.number()).nullable().optional(),
360
+ // Backend wire shape only includes `pinned_ts` (epoch ms or null);
361
+ // derive `pinned` from `pinnedTs != null` if you need a bool.
362
+ pinned: z.boolean().optional(),
363
+ pinnedTs: z.number().int().nullable().optional(),
364
+ posted: z.date(),
365
+ reactions: z.record(z.string(), z.array(z.number())).nullable().optional(),
366
+ recipients: z.array(z.number()).nullable().optional(),
367
+ snippet: z.string(),
368
+ snippetCreator: z.number(),
369
+ snippetMaskAvatarUrl: z.string().nullable().optional(),
370
+ snippetMaskPoster: z.string().nullable().optional(),
371
+ systemMessage: SystemMessageSchema,
372
+ isArchived: z.boolean(),
373
+ inInbox: z.boolean(),
374
+ isSaved: z.boolean().nullable().optional(),
375
+ closed: z.boolean(),
376
+ responders: z.array(z.number()).nullable().optional(),
377
+ lastComment: createCommentSchema(linkBaseUrl).nullable().optional(),
378
+ toEmails: z.array(z.string()).nullable().optional(),
379
+ version: z.number().nullable().optional(),
380
+ });
381
+ }
382
+ export function createInboxThreadSchema(linkBaseUrl) {
383
+ return createInboxThreadObjectSchema(linkBaseUrl).transform((data) => ({
384
+ ...data,
385
+ url: getFullCommsURL({
386
+ workspaceId: data.workspaceId,
387
+ channelId: data.channelId,
388
+ threadId: data.id,
389
+ }, linkBaseUrl),
390
+ }));
391
+ }
392
+ export const InboxThreadSchema = createInboxThreadSchema();
379
393
  // UnreadThread entity from API - simplified thread reference.
380
394
  export const UnreadThreadSchema = z.object({
381
395
  threadId: z.string(),
@@ -45,7 +45,9 @@ export function getCommsURL(params) {
45
45
  * @param baseUrl - Optional base URL (defaults to 'https://comms.todoist.com')
46
46
  */
47
47
  export function getFullCommsURL(params, baseUrl = COMMS_BASE_URL) {
48
- return `${baseUrl}${getCommsURL(params)}`;
48
+ // Strip a trailing slash so links don't double up — `getCommsURL` paths start with '/'.
49
+ const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
50
+ return `${normalizedBase}${getCommsURL(params)}`;
49
51
  }
50
52
  /** Returns the URL for a thread in a channel. */
51
53
  export function getThreadURL(params) {
@@ -1,10 +1,12 @@
1
- import { type Comment } from '../types/entities.js';
1
+ import { type Comment, type createCommentSchema } from '../types/entities.js';
2
2
  import type { CustomFetch } from '../types/http.js';
3
3
  import type { CreateCommentArgs, ThreadAction } from '../types/requests.js';
4
4
  type ClientContext = {
5
5
  baseUri: string;
6
6
  apiToken: string;
7
7
  customFetch?: CustomFetch;
8
+ /** Per-client Comment schema, base-bound for the returned comment's web `url`. */
9
+ schema: ReturnType<typeof createCommentSchema>;
8
10
  };
9
11
  /**
10
12
  * Internal helper that powers `comments.createComment`,
@@ -25,4 +25,10 @@ export declare class BaseClient {
25
25
  * slash so relative paths resolve cleanly through `URL`.
26
26
  */
27
27
  protected getBaseUri(): string;
28
+ /**
29
+ * Base URL for entity web links, or `undefined` to use getFullCommsURL's
30
+ * default web app. Trailing-slash normalization happens in
31
+ * `getFullCommsURL`, so the configured value is returned verbatim.
32
+ */
33
+ protected getLinkBaseUrl(): string | undefined;
28
34
  }
@@ -64,6 +64,9 @@ export declare const ChannelListSchema: z.ZodArray<z.ZodPipe<z.ZodObject<{
64
64
  * to keep an optimistic-UI ID stable through the round-trip.
65
65
  */
66
66
  export declare class ChannelsClient extends BaseClient {
67
+ private readonly linkBaseUrl;
68
+ private readonly channelSchema;
69
+ private readonly channelListSchema;
67
70
  /**
68
71
  * Gets all channels for a given workspace.
69
72
  *
@@ -134,6 +134,10 @@ export declare const CommentListSchema: z.ZodArray<z.ZodPipe<z.ZodObject<{
134
134
  * on `createComment` when the caller doesn't supply one.
135
135
  */
136
136
  export declare class CommentsClient extends BaseClient {
137
+ private readonly linkBaseUrl;
138
+ private readonly commentSchema;
139
+ private readonly commentListSchema;
140
+ private readonly wrappedCommentSchema;
137
141
  /**
138
142
  * Gets all comments for a thread. `newerThan` / `olderThan` (`Date`) are
139
143
  * converted to `newer_than_ts` / `older_than_ts` epoch seconds on the
@@ -113,6 +113,9 @@ export declare const ConversationMessageListSchema: z.ZodArray<z.ZodPipe<z.ZodOb
113
113
  * message `id` on `createMessage` when the caller doesn't supply one.
114
114
  */
115
115
  export declare class ConversationMessagesClient extends BaseClient {
116
+ private readonly linkBaseUrl;
117
+ private readonly messageSchema;
118
+ private readonly messageListSchema;
116
119
  /**
117
120
  * Gets all messages in a conversation.
118
121
  *
@@ -163,6 +163,9 @@ export declare const ConversationListSchema: z.ZodArray<z.ZodPipe<z.ZodObject<{
163
163
  * already-assigned `id` and your generated one is silently dropped.
164
164
  */
165
165
  export declare class ConversationsClient extends BaseClient {
166
+ private readonly linkBaseUrl;
167
+ private readonly conversationSchema;
168
+ private readonly conversationListSchema;
166
169
  /**
167
170
  * Gets all conversations for a workspace.
168
171
  *