@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.
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/dist/cjs/authentication.js +211 -0
- package/dist/cjs/clients/add-comment-helper.js +53 -0
- package/dist/cjs/clients/base-client.js +27 -0
- package/dist/cjs/clients/channels-client.js +83 -0
- package/dist/cjs/clients/comments-client.js +93 -0
- package/dist/cjs/clients/conversation-messages-client.js +87 -0
- package/dist/cjs/clients/conversations-client.js +103 -0
- package/dist/cjs/clients/groups-client.js +71 -0
- package/dist/cjs/clients/inbox-client.js +98 -0
- package/dist/cjs/clients/reactions-client.js +59 -0
- package/dist/cjs/clients/search-client.js +88 -0
- package/dist/cjs/clients/threads-client.js +135 -0
- package/dist/cjs/clients/users-client.js +199 -0
- package/dist/cjs/clients/workspace-users-client.js +140 -0
- package/dist/cjs/clients/workspaces-client.js +93 -0
- package/dist/cjs/comms-api.js +65 -0
- package/dist/cjs/consts/endpoints.js +27 -0
- package/dist/cjs/index.js +48 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/testUtils/msw-handlers.js +51 -0
- package/dist/cjs/testUtils/msw-setup.js +21 -0
- package/dist/cjs/testUtils/obsidian-fetch-adapter.js +53 -0
- package/dist/cjs/testUtils/test-defaults.js +102 -0
- package/dist/cjs/transport/fetch-with-retry.js +136 -0
- package/dist/cjs/transport/http-client.js +56 -0
- package/dist/cjs/transport/http-dispatcher.js +143 -0
- package/dist/cjs/types/entities.js +411 -0
- package/dist/cjs/types/enums.js +37 -0
- package/dist/cjs/types/errors.js +12 -0
- package/dist/cjs/types/http.js +4 -0
- package/dist/cjs/types/index.js +21 -0
- package/dist/cjs/types/requests.js +117 -0
- package/dist/cjs/utils/case-conversion.js +54 -0
- package/dist/cjs/utils/index.js +19 -0
- package/dist/cjs/utils/timestamp-conversion.js +49 -0
- package/dist/cjs/utils/url-helpers.js +131 -0
- package/dist/cjs/utils/uuidv7.js +174 -0
- package/dist/esm/authentication.js +203 -0
- package/dist/esm/clients/add-comment-helper.js +50 -0
- package/dist/esm/clients/base-client.js +23 -0
- package/dist/esm/clients/channels-client.js +79 -0
- package/dist/esm/clients/comments-client.js +89 -0
- package/dist/esm/clients/conversation-messages-client.js +83 -0
- package/dist/esm/clients/conversations-client.js +99 -0
- package/dist/esm/clients/groups-client.js +67 -0
- package/dist/esm/clients/inbox-client.js +94 -0
- package/dist/esm/clients/reactions-client.js +55 -0
- package/dist/esm/clients/search-client.js +84 -0
- package/dist/esm/clients/threads-client.js +131 -0
- package/dist/esm/clients/users-client.js +195 -0
- package/dist/esm/clients/workspace-users-client.js +136 -0
- package/dist/esm/clients/workspaces-client.js +89 -0
- package/dist/esm/comms-api.js +61 -0
- package/dist/esm/consts/endpoints.js +23 -0
- package/dist/esm/index.js +17 -0
- package/dist/esm/testUtils/msw-handlers.js +45 -0
- package/dist/esm/testUtils/msw-setup.js +18 -0
- package/dist/esm/testUtils/obsidian-fetch-adapter.js +50 -0
- package/dist/esm/testUtils/test-defaults.js +99 -0
- package/dist/esm/transport/fetch-with-retry.js +133 -0
- package/dist/esm/transport/http-client.js +51 -0
- package/dist/esm/transport/http-dispatcher.js +104 -0
- package/dist/esm/types/entities.js +408 -0
- package/dist/esm/types/enums.js +34 -0
- package/dist/esm/types/errors.js +8 -0
- package/dist/esm/types/http.js +1 -0
- package/dist/esm/types/index.js +5 -0
- package/dist/esm/types/requests.js +114 -0
- package/dist/esm/utils/case-conversion.js +47 -0
- package/dist/esm/utils/index.js +3 -0
- package/dist/esm/utils/timestamp-conversion.js +45 -0
- package/dist/esm/utils/url-helpers.js +112 -0
- package/dist/esm/utils/uuidv7.js +163 -0
- package/dist/types/authentication.d.ts +160 -0
- package/dist/types/clients/add-comment-helper.d.ts +12 -0
- package/dist/types/clients/base-client.d.ts +24 -0
- package/dist/types/clients/channels-client.d.ts +91 -0
- package/dist/types/clients/comments-client.d.ts +157 -0
- package/dist/types/clients/conversation-messages-client.d.ts +127 -0
- package/dist/types/clients/conversations-client.d.ts +206 -0
- package/dist/types/clients/groups-client.d.ts +55 -0
- package/dist/types/clients/inbox-client.d.ts +20 -0
- package/dist/types/clients/reactions-client.d.ts +19 -0
- package/dist/types/clients/search-client.d.ts +20 -0
- package/dist/types/clients/threads-client.d.ts +344 -0
- package/dist/types/clients/users-client.d.ts +123 -0
- package/dist/types/clients/workspace-users-client.d.ts +47 -0
- package/dist/types/clients/workspaces-client.d.ts +79 -0
- package/dist/types/comms-api.d.ts +59 -0
- package/dist/types/consts/endpoints.d.ts +19 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/testUtils/msw-handlers.d.ts +28 -0
- package/dist/types/testUtils/msw-setup.d.ts +1 -0
- package/dist/types/testUtils/obsidian-fetch-adapter.d.ts +29 -0
- package/dist/types/testUtils/test-defaults.d.ts +16 -0
- package/dist/types/transport/fetch-with-retry.d.ts +4 -0
- package/dist/types/transport/http-client.d.ts +13 -0
- package/dist/types/transport/http-dispatcher.d.ts +10 -0
- package/dist/types/types/entities.d.ts +1288 -0
- package/dist/types/types/enums.d.ts +55 -0
- package/dist/types/types/errors.d.ts +6 -0
- package/dist/types/types/http.d.ts +54 -0
- package/dist/types/types/index.d.ts +5 -0
- package/dist/types/types/requests.d.ts +385 -0
- package/dist/types/utils/case-conversion.d.ts +8 -0
- package/dist/types/utils/index.d.ts +3 -0
- package/dist/types/utils/timestamp-conversion.d.ts +13 -0
- package/dist/types/utils/url-helpers.d.ts +88 -0
- package/dist/types/utils/uuidv7.d.ts +40 -0
- package/package.json +93 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getFullCommsURL } from '../utils/url-helpers.js';
|
|
3
|
+
import { USER_TYPES } from './enums.js';
|
|
4
|
+
// EVERYONE / EVERYONE_IN_THREAD / GROUP_ID_MARKERS / GroupId / GroupIdMarker
|
|
5
|
+
// are defined in `./enums` (re-exported through `./index`) to keep this
|
|
6
|
+
// module from depending back on `./enums` for them. They're available
|
|
7
|
+
// from the same public surface (`@doist/comms-sdk`) either way.
|
|
8
|
+
// Reusable schema for system messages that can be either a string or an
|
|
9
|
+
// object. Nullable — the backend returns `null` when there is no system
|
|
10
|
+
// message.
|
|
11
|
+
export const SystemMessageSchema = z.union([z.string(), z.unknown()]).nullable().optional();
|
|
12
|
+
/**
|
|
13
|
+
* Shared `{ status: "ok" }` response shape. Pinned to the literal `'ok'`
|
|
14
|
+
* so a regression on the backend (e.g. a status code change) surfaces as
|
|
15
|
+
* a parse error here instead of being silently typed away. Most write
|
|
16
|
+
* endpoints that don't return an entity use this — archive / unarchive /
|
|
17
|
+
* mark-read / mark-all-read / mute / clear-unread / etc.
|
|
18
|
+
*/
|
|
19
|
+
export const StatusOkSchema = z.object({ status: z.literal('ok') });
|
|
20
|
+
// Attachment entity from API. Mirrors the canonical backend shape produced
|
|
21
|
+
// by `unify_attachments` / `validate_file_attachment_json`. Only
|
|
22
|
+
// `attachmentId` and `urlType` are guaranteed; everything else depends on
|
|
23
|
+
// the attachment kind (file vs image vs link preview vs unfurled GIF).
|
|
24
|
+
// Loose: unknown keys from the backend pass through rather than being
|
|
25
|
+
// stripped, so newly-added or off-spec fields stay accessible to callers.
|
|
26
|
+
export const AttachmentSchema = z
|
|
27
|
+
.object({
|
|
28
|
+
attachmentId: z.string(),
|
|
29
|
+
urlType: z.string(),
|
|
30
|
+
title: z.string().nullable().optional(),
|
|
31
|
+
url: z.string().nullable().optional(),
|
|
32
|
+
fileName: z.string().nullable().optional(),
|
|
33
|
+
fileSize: z.number().int().nonnegative().nullable().optional(),
|
|
34
|
+
underlyingType: z.string().nullable().optional(),
|
|
35
|
+
description: z.string().nullable().optional(),
|
|
36
|
+
image: z.string().nullable().optional(),
|
|
37
|
+
imageWidth: z.number().int().nonnegative().nullable().optional(),
|
|
38
|
+
imageHeight: z.number().int().nonnegative().nullable().optional(),
|
|
39
|
+
duration: z.string().nullable().optional(),
|
|
40
|
+
uploadState: z.string().nullable().optional(),
|
|
41
|
+
video: z.string().nullable().optional(),
|
|
42
|
+
videoType: z.string().nullable().optional(),
|
|
43
|
+
videoAutoPlay: z.boolean().nullable().optional(),
|
|
44
|
+
})
|
|
45
|
+
.loose();
|
|
46
|
+
// Fields shared by `User` and `WorkspaceUser`. Profile fields
|
|
47
|
+
// (`fullName`, `imageId`, avatar URLs) are sourced from Todoist-ID.
|
|
48
|
+
export const BaseUserSchema = z.object({
|
|
49
|
+
id: z.number(),
|
|
50
|
+
fullName: z.string(),
|
|
51
|
+
shortName: z.string(),
|
|
52
|
+
firstName: z.string().nullable().optional(),
|
|
53
|
+
timezone: z.string(),
|
|
54
|
+
removed: z.boolean(),
|
|
55
|
+
imageId: z.string().nullable().optional(),
|
|
56
|
+
avatarUrls: z
|
|
57
|
+
.object({
|
|
58
|
+
s35: z.string(),
|
|
59
|
+
s60: z.string(),
|
|
60
|
+
s195: z.string(),
|
|
61
|
+
s640: z.string(),
|
|
62
|
+
})
|
|
63
|
+
.nullable()
|
|
64
|
+
.optional(),
|
|
65
|
+
restricted: z.boolean().nullable().optional(),
|
|
66
|
+
setupPending: z.boolean().nullable().optional(),
|
|
67
|
+
});
|
|
68
|
+
// User entity from API.
|
|
69
|
+
export const UserSchema = BaseUserSchema.extend({
|
|
70
|
+
email: z.email(),
|
|
71
|
+
clientId: z.string().nullable().optional(),
|
|
72
|
+
cometChannel: z.string().nullable().optional(),
|
|
73
|
+
lang: z.string(),
|
|
74
|
+
cometServer: z.string().nullable().optional(),
|
|
75
|
+
token: z.string().nullable().optional(),
|
|
76
|
+
scheduledBanners: z.array(z.string()).nullable().optional(),
|
|
77
|
+
});
|
|
78
|
+
// Workspace entity from API. `default_conversation` is a base58 UUIDv7
|
|
79
|
+
// string. `avatar_id` is kept (unlike the User rename to `image_id`,
|
|
80
|
+
// which is a User-only change per Comms_API_changes.md PR #125).
|
|
81
|
+
// `plan` is intentionally `z.string()` — see {@link WORKSPACE_PLANS}.
|
|
82
|
+
export const WorkspaceSchema = z.object({
|
|
83
|
+
id: z.number(),
|
|
84
|
+
name: z.string(),
|
|
85
|
+
defaultConversation: z.string().nullable().optional(),
|
|
86
|
+
creator: z.number(),
|
|
87
|
+
created: z.date(),
|
|
88
|
+
avatarId: z.string().nullable().optional(),
|
|
89
|
+
avatarUrls: z
|
|
90
|
+
.object({
|
|
91
|
+
s35: z.string(),
|
|
92
|
+
s60: z.string(),
|
|
93
|
+
s195: z.string(),
|
|
94
|
+
s640: z.string(),
|
|
95
|
+
})
|
|
96
|
+
.nullable()
|
|
97
|
+
.optional(),
|
|
98
|
+
plan: z.string().nullable().optional(),
|
|
99
|
+
});
|
|
100
|
+
export const ChannelSchema = z
|
|
101
|
+
.object({
|
|
102
|
+
id: z.string(),
|
|
103
|
+
name: z.string(),
|
|
104
|
+
description: z.string().nullable().optional(),
|
|
105
|
+
creator: z.number(),
|
|
106
|
+
userIds: z.array(z.number()).nullable().optional(),
|
|
107
|
+
color: z.number().nullable().optional(),
|
|
108
|
+
public: z.boolean(),
|
|
109
|
+
workspaceId: z.number(),
|
|
110
|
+
archived: z.boolean(),
|
|
111
|
+
created: z.date(),
|
|
112
|
+
useDefaultRecipients: z.boolean().nullable().optional(),
|
|
113
|
+
defaultGroups: z.array(z.string()).nullable().optional(),
|
|
114
|
+
defaultRecipients: z.array(z.number()).nullable().optional(),
|
|
115
|
+
isFavorited: z.boolean().nullable().optional(),
|
|
116
|
+
icon: z.number().nullable().optional(),
|
|
117
|
+
version: z.number(),
|
|
118
|
+
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
|
+
}));
|
|
124
|
+
// Thread entity from API. `pinned` (boolean) and `pinnedTs` (epoch ms or
|
|
125
|
+
// null) are both surfaced — `pinned` is kept for Zapier/webhook clients.
|
|
126
|
+
export const ThreadSchema = z
|
|
127
|
+
.object({
|
|
128
|
+
id: z.string(),
|
|
129
|
+
title: z.string(),
|
|
130
|
+
content: z.string(),
|
|
131
|
+
creator: z.number(),
|
|
132
|
+
creatorName: z.string().nullable().optional(),
|
|
133
|
+
channelId: z.string(),
|
|
134
|
+
workspaceId: z.number(),
|
|
135
|
+
actions: z.array(z.unknown()).nullable().optional(),
|
|
136
|
+
attachments: z.array(AttachmentSchema).nullable().optional(),
|
|
137
|
+
commentCount: z.number(),
|
|
138
|
+
closed: z.boolean().nullable().optional(),
|
|
139
|
+
directGroupMentions: z.array(z.string()).nullable().optional(),
|
|
140
|
+
directMentions: z.array(z.number()).nullable().optional(),
|
|
141
|
+
groups: z.array(z.string()).nullable().optional(),
|
|
142
|
+
lastEdited: z.date().nullable().optional(),
|
|
143
|
+
lastObjIndex: z.number().nullable().optional(),
|
|
144
|
+
lastUpdated: z.date(),
|
|
145
|
+
mutedUntil: z.date().nullable().optional(),
|
|
146
|
+
participants: z.array(z.number()).nullable().optional(),
|
|
147
|
+
// Backend wire shape only includes `pinned_ts` (epoch ms or null);
|
|
148
|
+
// derive `pinned` from `pinnedTs != null` if you need a bool.
|
|
149
|
+
pinned: z.boolean().optional(),
|
|
150
|
+
pinnedTs: z.number().int().nullable().optional(),
|
|
151
|
+
posted: z.date(),
|
|
152
|
+
reactions: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
153
|
+
recipients: z.array(z.number()).nullable().optional(),
|
|
154
|
+
responders: z.array(z.number()).nullable().optional(),
|
|
155
|
+
snippet: z.string(),
|
|
156
|
+
snippetCreator: z.number(),
|
|
157
|
+
snippetMaskAvatarUrl: z.string().nullable().optional(),
|
|
158
|
+
snippetMaskPoster: z.string().nullable().optional(),
|
|
159
|
+
systemMessage: SystemMessageSchema,
|
|
160
|
+
toEmails: z.array(z.string()).nullable().optional(),
|
|
161
|
+
isArchived: z.boolean(),
|
|
162
|
+
isSaved: z.boolean().nullable().optional(),
|
|
163
|
+
inInbox: z.boolean().nullable().optional(),
|
|
164
|
+
lastComment: z
|
|
165
|
+
.object({
|
|
166
|
+
id: z.string(),
|
|
167
|
+
content: z.string(),
|
|
168
|
+
creator: z.number(),
|
|
169
|
+
creatorName: z.string(),
|
|
170
|
+
threadId: z.string(),
|
|
171
|
+
channelId: z.string(),
|
|
172
|
+
posted: z.date(),
|
|
173
|
+
systemMessage: SystemMessageSchema,
|
|
174
|
+
attachments: z.array(AttachmentSchema).nullable().optional(),
|
|
175
|
+
reactions: z.record(z.string(), z.array(z.number())).nullable().optional(),
|
|
176
|
+
actions: z.array(z.unknown()).nullable().optional(),
|
|
177
|
+
objIndex: z.number(),
|
|
178
|
+
lastEdited: z.date().nullable().optional(),
|
|
179
|
+
deleted: z.boolean(),
|
|
180
|
+
deletedBy: z.number().nullable().optional(),
|
|
181
|
+
directGroupMentions: z.array(z.string()).nullable().optional(),
|
|
182
|
+
directMentions: z.array(z.number()).nullable().optional(),
|
|
183
|
+
groups: z.array(z.string()).nullable().optional(),
|
|
184
|
+
recipients: z.array(z.number()).nullable().optional(),
|
|
185
|
+
toEmails: z.array(z.string()).nullable().optional(),
|
|
186
|
+
version: z.number(),
|
|
187
|
+
workspaceId: z.number(),
|
|
188
|
+
})
|
|
189
|
+
.nullable()
|
|
190
|
+
.optional(),
|
|
191
|
+
})
|
|
192
|
+
.transform((data) => ({
|
|
193
|
+
...data,
|
|
194
|
+
url: getFullCommsURL({
|
|
195
|
+
workspaceId: data.workspaceId,
|
|
196
|
+
channelId: data.channelId,
|
|
197
|
+
threadId: data.id,
|
|
198
|
+
}),
|
|
199
|
+
}));
|
|
200
|
+
// Group entity from API. The broadcast markers `EVERYONE` and
|
|
201
|
+
// `EVERYONE_IN_THREAD` can appear in place of a real `id` in group-bearing
|
|
202
|
+
// fields on threads/comments/channels.
|
|
203
|
+
export const GroupSchema = z.object({
|
|
204
|
+
id: z.string(),
|
|
205
|
+
name: z.string(),
|
|
206
|
+
description: z.string().nullable().optional(),
|
|
207
|
+
workspaceId: z.number(),
|
|
208
|
+
userIds: z.array(z.number()),
|
|
209
|
+
version: z.number(),
|
|
210
|
+
});
|
|
211
|
+
// Conversation entity from API.
|
|
212
|
+
export const ConversationSchema = z
|
|
213
|
+
.object({
|
|
214
|
+
id: z.string(),
|
|
215
|
+
workspaceId: z.number(),
|
|
216
|
+
userIds: z.array(z.number()),
|
|
217
|
+
messageCount: z.number().nullable().optional(),
|
|
218
|
+
lastObjIndex: z.number(),
|
|
219
|
+
snippet: z.string(),
|
|
220
|
+
snippetCreators: z.array(z.number()),
|
|
221
|
+
lastActive: z.date(),
|
|
222
|
+
mutedUntil: z.date().nullable().optional(),
|
|
223
|
+
archived: z.boolean(),
|
|
224
|
+
created: z.date(),
|
|
225
|
+
creator: z.number(),
|
|
226
|
+
title: z.string().nullable().optional(),
|
|
227
|
+
private: z.boolean().nullable().optional(),
|
|
228
|
+
lastMessage: z
|
|
229
|
+
.object({
|
|
230
|
+
// `id` may be string or number depending on the endpoint; coerced.
|
|
231
|
+
id: z.union([z.string(), z.number()]).transform(String),
|
|
232
|
+
content: z.string(),
|
|
233
|
+
creator: z.number(),
|
|
234
|
+
conversationId: z.string(),
|
|
235
|
+
posted: z.date(),
|
|
236
|
+
systemMessage: SystemMessageSchema,
|
|
237
|
+
attachments: z.array(AttachmentSchema).nullable().optional(),
|
|
238
|
+
reactions: z.record(z.string(), z.array(z.number())).nullable().optional(),
|
|
239
|
+
actions: z.array(z.unknown()).nullable().optional(),
|
|
240
|
+
objIndex: z.number().nullable().optional(),
|
|
241
|
+
lastEdited: z.date().nullable().optional(),
|
|
242
|
+
deleted: z.boolean().nullable().optional(),
|
|
243
|
+
directGroupMentions: z.array(z.string()).nullable().optional(),
|
|
244
|
+
directMentions: z.array(z.number()).nullable().optional(),
|
|
245
|
+
version: z.number().nullable().optional(),
|
|
246
|
+
workspaceId: z.number().nullable().optional(),
|
|
247
|
+
})
|
|
248
|
+
.nullable()
|
|
249
|
+
.optional(),
|
|
250
|
+
})
|
|
251
|
+
.transform((data) => ({
|
|
252
|
+
...data,
|
|
253
|
+
url: getFullCommsURL({ workspaceId: data.workspaceId, conversationId: data.id }),
|
|
254
|
+
}));
|
|
255
|
+
export const CommentSchema = z
|
|
256
|
+
.object({
|
|
257
|
+
id: z.string(),
|
|
258
|
+
content: z.string(),
|
|
259
|
+
creator: z.number(),
|
|
260
|
+
threadId: z.string(),
|
|
261
|
+
workspaceId: z.number(),
|
|
262
|
+
conversationId: z.string().nullable().optional(),
|
|
263
|
+
posted: z.date(),
|
|
264
|
+
lastEdited: z.date().nullable().optional(),
|
|
265
|
+
directMentions: z.array(z.number()).nullable().optional(),
|
|
266
|
+
directGroupMentions: z.array(z.string()).nullable().optional(),
|
|
267
|
+
systemMessage: SystemMessageSchema,
|
|
268
|
+
attachments: z.array(AttachmentSchema).nullable().optional(),
|
|
269
|
+
reactions: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
270
|
+
objIndex: z.number().nullable().optional(),
|
|
271
|
+
creatorName: z.string().nullable().optional(),
|
|
272
|
+
channelId: z.string(),
|
|
273
|
+
recipients: z.array(z.number()).nullable().optional(),
|
|
274
|
+
groups: z.array(z.string()).nullable().optional(),
|
|
275
|
+
toEmails: z.array(z.string()).nullable().optional(),
|
|
276
|
+
deleted: z.boolean().nullable().optional(),
|
|
277
|
+
deletedBy: z.number().nullable().optional(),
|
|
278
|
+
version: z.number().nullable().optional(),
|
|
279
|
+
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
|
+
}));
|
|
290
|
+
export const WorkspaceUserSchema = BaseUserSchema.extend({
|
|
291
|
+
email: z.string().nullable().optional(),
|
|
292
|
+
userType: z.enum(USER_TYPES),
|
|
293
|
+
dateFormat: z.string().nullable().optional(),
|
|
294
|
+
theme: z.number().int().nullable().optional(),
|
|
295
|
+
timeFormat: z.string().nullable().optional(),
|
|
296
|
+
version: z.number(),
|
|
297
|
+
});
|
|
298
|
+
// ConversationMessage entity from API. `id` is widened to `string | number`
|
|
299
|
+
// (coerced to a string post-parse) because the backend currently emits
|
|
300
|
+
// either shape depending on the endpoint; the URL/reaction helpers accept
|
|
301
|
+
// both.
|
|
302
|
+
export const ConversationMessageSchema = z
|
|
303
|
+
.object({
|
|
304
|
+
id: z.union([z.string(), z.number()]).transform(String),
|
|
305
|
+
content: z.string(),
|
|
306
|
+
creator: z.number(),
|
|
307
|
+
conversationId: z.string(),
|
|
308
|
+
posted: z.date(),
|
|
309
|
+
systemMessage: SystemMessageSchema,
|
|
310
|
+
attachments: z.array(AttachmentSchema).nullable().optional(),
|
|
311
|
+
reactions: z.record(z.string(), z.array(z.number())).nullable().optional(),
|
|
312
|
+
actions: z.array(z.unknown()).nullable().optional(),
|
|
313
|
+
objIndex: z.number().nullable().optional(),
|
|
314
|
+
lastEdited: z.date().nullable().optional(),
|
|
315
|
+
isDeleted: z.boolean().nullable().optional(),
|
|
316
|
+
directGroupMentions: z.array(z.string()).nullable().optional(),
|
|
317
|
+
directMentions: z.array(z.number()).nullable().optional(),
|
|
318
|
+
version: z.number().nullable().optional(),
|
|
319
|
+
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
|
+
}));
|
|
329
|
+
// 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
|
+
}));
|
|
379
|
+
// UnreadThread entity from API - simplified thread reference.
|
|
380
|
+
export const UnreadThreadSchema = z.object({
|
|
381
|
+
threadId: z.string(),
|
|
382
|
+
channelId: z.string(),
|
|
383
|
+
objIndex: z.number(),
|
|
384
|
+
directMention: z.boolean(),
|
|
385
|
+
});
|
|
386
|
+
// UnreadConversation entity from API - simplified conversation reference.
|
|
387
|
+
export const UnreadConversationSchema = z.object({
|
|
388
|
+
conversationId: z.string(),
|
|
389
|
+
objIndex: z.number(),
|
|
390
|
+
directMention: z.boolean(),
|
|
391
|
+
});
|
|
392
|
+
// SearchResult entity from API.
|
|
393
|
+
export const SEARCH_RESULT_TYPES = ['thread', 'comment', 'message', 'conversation'];
|
|
394
|
+
export const SearchResultSchema = z.object({
|
|
395
|
+
id: z.string(),
|
|
396
|
+
type: z.enum(SEARCH_RESULT_TYPES),
|
|
397
|
+
snippet: z.string(),
|
|
398
|
+
snippetCreatorId: z.number(),
|
|
399
|
+
snippetLastUpdated: z.date(),
|
|
400
|
+
threadId: z.string().nullable().optional(),
|
|
401
|
+
conversationId: z.string().nullable().optional(),
|
|
402
|
+
commentId: z.string().nullable().optional(),
|
|
403
|
+
channelId: z.string().nullable().optional(),
|
|
404
|
+
channelName: z.string().nullable().optional(),
|
|
405
|
+
channelColor: z.number().nullable().optional(),
|
|
406
|
+
title: z.string().nullable().optional(),
|
|
407
|
+
closed: z.boolean().nullable().optional(),
|
|
408
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker constants for the two "broadcast" group recipients. These appear
|
|
3
|
+
* in group-bearing fields (`Thread.groups`, `Comment.groups`,
|
|
4
|
+
* `Channel.defaultGroups`, `directGroupMentions`, etc.) in place of a
|
|
5
|
+
* real group ID and tell the backend to notify "everyone in the channel"
|
|
6
|
+
* or "everyone in the thread" respectively. Input and output are
|
|
7
|
+
* symmetric. Defined here (and not in `./entities`) so that `enums`
|
|
8
|
+
* stays leaf-level — `entities` imports from `enums`, never the reverse.
|
|
9
|
+
*/
|
|
10
|
+
export const EVERYONE = 'EVERYONE';
|
|
11
|
+
export const EVERYONE_IN_THREAD = 'EVERYONE_IN_THREAD';
|
|
12
|
+
/** Union of the two broadcast group markers. */
|
|
13
|
+
export const GROUP_ID_MARKERS = [EVERYONE, EVERYONE_IN_THREAD];
|
|
14
|
+
// User types for workspace users
|
|
15
|
+
export const USER_TYPES = ['USER', 'GUEST', 'ADMIN'];
|
|
16
|
+
// Workspace plans. The known values today are `'free'`, `'unlimited'`,
|
|
17
|
+
// and `'business'`, but the backend can introduce new plan names without
|
|
18
|
+
// coordinating a SDK release — so the schema accepts any string and the
|
|
19
|
+
// const array stays as a hint for autocomplete only.
|
|
20
|
+
export const WORKSPACE_PLANS = ['free', 'unlimited', 'business'];
|
|
21
|
+
// Audiences that comment-creating endpoints can target alongside (or instead
|
|
22
|
+
// of) individual `recipients` / custom `groups`.
|
|
23
|
+
export const NOTIFY_AUDIENCES = ['channel', 'thread'];
|
|
24
|
+
/**
|
|
25
|
+
* Internal mapping from {@link NotifyAudience} to the broadcast marker IDs
|
|
26
|
+
* (`EVERYONE` / `EVERYONE_IN_THREAD`) that comment- and thread-creation
|
|
27
|
+
* endpoints use on the wire. SDK consumers should use {@link NotifyAudience}
|
|
28
|
+
* via `notifyAudience` on the request args rather than passing these IDs
|
|
29
|
+
* directly.
|
|
30
|
+
*/
|
|
31
|
+
export const NOTIFY_AUDIENCE_GROUP_IDS = {
|
|
32
|
+
channel: EVERYONE,
|
|
33
|
+
thread: EVERYONE_IN_THREAD,
|
|
34
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const HTTP_METHODS = ['POST', 'GET', 'PUT', 'PATCH', 'DELETE'];
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AttachmentSchema } from './entities.js';
|
|
3
|
+
import { NOTIFY_AUDIENCES } from './enums.js';
|
|
4
|
+
/**
|
|
5
|
+
* Create-side requests require an `id` on the wire. The SDK clients accept
|
|
6
|
+
* `id` as optional and auto-generate one via
|
|
7
|
+
* {@link import('../utils/uuidv7').generateId} when the caller doesn't pass
|
|
8
|
+
* one — callers can still mint their own ID locally (e.g. for optimistic
|
|
9
|
+
* UI) and have it stick.
|
|
10
|
+
*/
|
|
11
|
+
export const CreateChannelArgsSchema = z.object({
|
|
12
|
+
workspaceId: z.number(),
|
|
13
|
+
name: z.string(),
|
|
14
|
+
id: z.string().optional(),
|
|
15
|
+
userIds: z.array(z.number()).nullable().optional(),
|
|
16
|
+
color: z.number().nullable().optional(),
|
|
17
|
+
public: z.boolean().nullable().optional(),
|
|
18
|
+
description: z.string().nullable().optional(),
|
|
19
|
+
defaultGroups: z.array(z.string()).nullable().optional(),
|
|
20
|
+
defaultRecipients: z.array(z.number()).nullable().optional(),
|
|
21
|
+
isFavorited: z.boolean().nullable().optional(),
|
|
22
|
+
icon: z.number().nullable().optional(),
|
|
23
|
+
});
|
|
24
|
+
export const UpdateChannelArgsSchema = z.object({
|
|
25
|
+
id: z.string(),
|
|
26
|
+
name: z.string(),
|
|
27
|
+
color: z.number().nullable().optional(),
|
|
28
|
+
public: z.boolean().nullable().optional(),
|
|
29
|
+
description: z.string().nullable().optional(),
|
|
30
|
+
defaultGroups: z.array(z.string()).nullable().optional(),
|
|
31
|
+
defaultRecipients: z.array(z.number()).nullable().optional(),
|
|
32
|
+
isFavorited: z.boolean().nullable().optional(),
|
|
33
|
+
icon: z.number().nullable().optional(),
|
|
34
|
+
});
|
|
35
|
+
export const CreateThreadArgsSchema = z.object({
|
|
36
|
+
channelId: z.string(),
|
|
37
|
+
content: z.string(),
|
|
38
|
+
title: z.string().nullable().optional(),
|
|
39
|
+
id: z.string().optional(),
|
|
40
|
+
recipients: z.array(z.number()).nullable().optional(),
|
|
41
|
+
groups: z.array(z.string()).nullable().optional(),
|
|
42
|
+
});
|
|
43
|
+
export const UpdateThreadArgsSchema = z.object({
|
|
44
|
+
id: z.string(),
|
|
45
|
+
title: z.string().nullable().optional(),
|
|
46
|
+
content: z.string().nullable().optional(),
|
|
47
|
+
});
|
|
48
|
+
export const CreateCommentArgsSchema = z.object({
|
|
49
|
+
threadId: z.string(),
|
|
50
|
+
content: z.string(),
|
|
51
|
+
id: z.string().optional(),
|
|
52
|
+
attachments: z.array(AttachmentSchema).nullable().optional(),
|
|
53
|
+
actions: z.unknown().nullable().optional(),
|
|
54
|
+
recipients: z.array(z.number()).nullable().optional(),
|
|
55
|
+
groups: z.array(z.string()).nullable().optional(),
|
|
56
|
+
directMentions: z.array(z.number()).nullable().optional(),
|
|
57
|
+
directGroupMentions: z.array(z.string()).nullable().optional(),
|
|
58
|
+
notifyAudience: z.enum(NOTIFY_AUDIENCES).nullable().optional(),
|
|
59
|
+
});
|
|
60
|
+
export const UpdateCommentArgsSchema = z.object({
|
|
61
|
+
id: z.string(),
|
|
62
|
+
content: z.string(),
|
|
63
|
+
});
|
|
64
|
+
export const CreateConversationArgsSchema = z.object({
|
|
65
|
+
workspaceId: z.number(),
|
|
66
|
+
recipients: z.array(z.number()),
|
|
67
|
+
id: z.string().optional(),
|
|
68
|
+
title: z.string().nullable().optional(),
|
|
69
|
+
});
|
|
70
|
+
export const CreateMessageArgsSchema = z
|
|
71
|
+
.object({
|
|
72
|
+
conversationId: z.string().nullable().optional(),
|
|
73
|
+
threadId: z.string().nullable().optional(),
|
|
74
|
+
content: z.string(),
|
|
75
|
+
id: z.string().optional(),
|
|
76
|
+
attachments: z.array(AttachmentSchema).nullable().optional(),
|
|
77
|
+
})
|
|
78
|
+
.refine((data) => {
|
|
79
|
+
return ((data.conversationId && !data.threadId) || (!data.conversationId && data.threadId));
|
|
80
|
+
}, {
|
|
81
|
+
message: 'Exactly one of conversationId or threadId must be provided',
|
|
82
|
+
});
|
|
83
|
+
export const GetChannelsArgsSchema = z.object({
|
|
84
|
+
workspaceId: z.number(),
|
|
85
|
+
archived: z.boolean().nullable().optional(),
|
|
86
|
+
});
|
|
87
|
+
export const GetThreadsArgsSchema = z.object({
|
|
88
|
+
workspaceId: z.number(),
|
|
89
|
+
channelId: z.string().nullable().optional(),
|
|
90
|
+
archived: z.boolean().nullable().optional(),
|
|
91
|
+
newerThan: z.date().nullable().optional(),
|
|
92
|
+
olderThan: z.date().nullable().optional(),
|
|
93
|
+
limit: z.number().nullable().optional(),
|
|
94
|
+
});
|
|
95
|
+
export const GetCommentsArgsSchema = z.object({
|
|
96
|
+
threadId: z.string(),
|
|
97
|
+
from: z.date().nullable().optional(),
|
|
98
|
+
newerThan: z.date().nullable().optional(),
|
|
99
|
+
olderThan: z.date().nullable().optional(),
|
|
100
|
+
limit: z.number().nullable().optional(),
|
|
101
|
+
});
|
|
102
|
+
export const GetConversationsArgsSchema = z.object({
|
|
103
|
+
workspaceId: z.number(),
|
|
104
|
+
archived: z.boolean().nullable().optional(),
|
|
105
|
+
});
|
|
106
|
+
export const GetOrCreateConversationArgsSchema = z.object({
|
|
107
|
+
workspaceId: z.number(),
|
|
108
|
+
userIds: z.array(z.number()),
|
|
109
|
+
id: z.string().optional(),
|
|
110
|
+
});
|
|
111
|
+
// Inbox
|
|
112
|
+
export const ARCHIVE_FILTER_VALUES = ['active', 'archived', 'all'];
|
|
113
|
+
// Threads
|
|
114
|
+
export const THREAD_ACTIONS = ['close', 'reopen'];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import camelcase from 'camelcase';
|
|
2
|
+
function isObject(value) {
|
|
3
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function isArray(value) {
|
|
6
|
+
return Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function toSnakeCase(str) {
|
|
9
|
+
return str
|
|
10
|
+
.replace(/([A-Z])/g, '_$1')
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.replace(/^_/, '');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export function camelCaseKeys(obj) {
|
|
18
|
+
if (isArray(obj)) {
|
|
19
|
+
return obj.map(camelCaseKeys);
|
|
20
|
+
}
|
|
21
|
+
if (isObject(obj)) {
|
|
22
|
+
const result = {};
|
|
23
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
24
|
+
const camelKey = camelcase(key);
|
|
25
|
+
result[camelKey] = camelCaseKeys(value);
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
return obj;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export function snakeCaseKeys(obj) {
|
|
35
|
+
if (isArray(obj)) {
|
|
36
|
+
return obj.map(snakeCaseKeys);
|
|
37
|
+
}
|
|
38
|
+
if (isObject(obj)) {
|
|
39
|
+
const result = {};
|
|
40
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
41
|
+
const snakeKey = toSnakeCase(key);
|
|
42
|
+
result[snakeKey] = snakeCaseKeys(value);
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
return obj;
|
|
47
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a Unix timestamp (in seconds) to a Date object.
|
|
3
|
+
* @param timestamp - Unix timestamp in seconds
|
|
4
|
+
* @returns Date object
|
|
5
|
+
*/
|
|
6
|
+
export function timestampToDate(timestamp) {
|
|
7
|
+
return new Date(timestamp * 1000);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Recursively transforms all timestamp fields (ending in 'Ts') in an object to Date objects.
|
|
11
|
+
* Also renames the fields by removing the 'Ts' suffix.
|
|
12
|
+
* @param obj - The object to transform
|
|
13
|
+
* @returns The transformed object with Date fields
|
|
14
|
+
*/
|
|
15
|
+
export function transformTimestamps(obj) {
|
|
16
|
+
if (obj === null || obj === undefined) {
|
|
17
|
+
return obj;
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(obj)) {
|
|
20
|
+
return obj.map((item) => transformTimestamps(item));
|
|
21
|
+
}
|
|
22
|
+
if (typeof obj === 'object') {
|
|
23
|
+
const result = {};
|
|
24
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
25
|
+
// Check if the key ends with 'Ts' and the value is a number
|
|
26
|
+
if (key.endsWith('Ts') && typeof value === 'number') {
|
|
27
|
+
// Remove 'Ts' suffix and convert to Date
|
|
28
|
+
const newKey = key.slice(0, -2);
|
|
29
|
+
// If the base key already exists in the original object, use *Date suffix
|
|
30
|
+
// to avoid overwriting it (e.g. pinned + pinnedTs → pinned + pinnedDate)
|
|
31
|
+
const targetKey = newKey in obj ? `${newKey}Date` : newKey;
|
|
32
|
+
result[targetKey] = timestampToDate(value);
|
|
33
|
+
}
|
|
34
|
+
else if (typeof value === 'object' && value !== null) {
|
|
35
|
+
// Recursively transform nested objects
|
|
36
|
+
result[key] = transformTimestamps(value);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
result[key] = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
return obj;
|
|
45
|
+
}
|