@doist/comms-sdk 0.3.0 → 0.4.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/dist/cjs/clients/attachments-client.js +71 -0
- package/dist/cjs/clients/threads-client.js +1 -0
- package/dist/cjs/comms-api.js +2 -0
- package/dist/cjs/consts/endpoints.js +2 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/types/entities.js +8 -5
- package/dist/cjs/types/requests.js +3 -2
- package/dist/cjs/utils/multipart-upload.js +144 -0
- package/dist/esm/clients/attachments-client.js +67 -0
- package/dist/esm/clients/threads-client.js +1 -0
- package/dist/esm/comms-api.js +2 -0
- package/dist/esm/consts/endpoints.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/types/entities.js +7 -4
- package/dist/esm/types/requests.js +4 -3
- package/dist/esm/utils/multipart-upload.js +140 -0
- package/dist/types/clients/attachments-client.d.ts +39 -0
- package/dist/types/clients/threads-client.d.ts +1 -0
- package/dist/types/comms-api.d.ts +2 -0
- package/dist/types/consts/endpoints.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/types/entities.d.ts +19 -0
- package/dist/types/types/requests.d.ts +42 -2
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/multipart-upload.d.ts +58 -0
- package/package.json +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AttachmentsClient = void 0;
|
|
4
|
+
const endpoints_1 = require("../consts/endpoints");
|
|
5
|
+
const entities_1 = require("../types/entities");
|
|
6
|
+
const multipart_upload_1 = require("../utils/multipart-upload");
|
|
7
|
+
const uuidv7_1 = require("../utils/uuidv7");
|
|
8
|
+
const base_client_1 = require("./base-client");
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the `attachment_id`: validate a caller-supplied value (throwing an
|
|
11
|
+
* {@link UuidV7Error} that names `attachmentId` rather than the generic `id`) or
|
|
12
|
+
* mint a fresh one. Wraps {@link resolveCreateId} so the error contract matches the
|
|
13
|
+
* client's actual parameter.
|
|
14
|
+
*/
|
|
15
|
+
function resolveAttachmentId(attachmentId) {
|
|
16
|
+
if (attachmentId !== undefined && !(0, uuidv7_1.isValidUuidV7Base58)(attachmentId)) {
|
|
17
|
+
throw new uuidv7_1.UuidV7Error(`invalid attachmentId ${JSON.stringify(attachmentId)} — use generateId() or omit \`attachmentId\` and let the SDK mint one.`);
|
|
18
|
+
}
|
|
19
|
+
return (0, uuidv7_1.resolveCreateId)(attachmentId);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Client for uploading file attachments to Comms.
|
|
23
|
+
*
|
|
24
|
+
* Attachments are uploaded independently, then referenced by passing the returned
|
|
25
|
+
* {@link Attachment} into the `attachments` array of `comments.createComment`,
|
|
26
|
+
* `conversationMessages.createMessage`, and similar calls.
|
|
27
|
+
*/
|
|
28
|
+
class AttachmentsClient extends base_client_1.BaseClient {
|
|
29
|
+
/**
|
|
30
|
+
* Uploads a file and returns the created {@link Attachment}.
|
|
31
|
+
*
|
|
32
|
+
* Mirrors the canonical multipart upload: `POST /attachments/upload` with the `file`
|
|
33
|
+
* binary plus `file_name`, `file_size`, `attachment_id`, and `underlying_type` form
|
|
34
|
+
* fields.
|
|
35
|
+
*
|
|
36
|
+
* @param args - The file to upload and optional metadata.
|
|
37
|
+
* @returns The created attachment, ready to attach to a comment or message.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* import { readFile } from 'node:fs/promises'
|
|
42
|
+
*
|
|
43
|
+
* const attachment = await api.attachments.upload({
|
|
44
|
+
* file: await readFile('./diagram.png'),
|
|
45
|
+
* fileName: 'diagram.png',
|
|
46
|
+
* })
|
|
47
|
+
*
|
|
48
|
+
* await api.comments.createComment({
|
|
49
|
+
* threadId: '7YpL3oZ4kZ9vP7Q1tR2sX3z',
|
|
50
|
+
* content: 'See attached',
|
|
51
|
+
* attachments: [attachment],
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
async upload(args) {
|
|
56
|
+
const data = await (0, multipart_upload_1.uploadMultipartFile)({
|
|
57
|
+
baseUrl: this.getBaseUri(),
|
|
58
|
+
authToken: this.apiToken,
|
|
59
|
+
endpoint: `${endpoints_1.ENDPOINT_ATTACHMENTS}/upload`,
|
|
60
|
+
file: args.file,
|
|
61
|
+
fileName: args.fileName,
|
|
62
|
+
contentType: args.contentType,
|
|
63
|
+
additionalFields: {
|
|
64
|
+
attachment_id: resolveAttachmentId(args.attachmentId),
|
|
65
|
+
},
|
|
66
|
+
customFetch: this.customFetch,
|
|
67
|
+
});
|
|
68
|
+
return entities_1.AttachmentSchema.parse(data);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.AttachmentsClient = AttachmentsClient;
|
|
@@ -95,6 +95,7 @@ class ThreadsClient extends base_client_1.BaseClient {
|
|
|
95
95
|
* @param args.content - The thread content.
|
|
96
96
|
* @param args.recipients - Optional array of user IDs to notify.
|
|
97
97
|
* @param args.groups - Optional array of custom group IDs to notify.
|
|
98
|
+
* @param args.attachments - Optional array of {@link Attachment}s (from `attachments.upload`).
|
|
98
99
|
* @returns The created thread object.
|
|
99
100
|
*
|
|
100
101
|
* @example
|
package/dist/cjs/comms-api.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CommsApi = void 0;
|
|
4
|
+
const attachments_client_1 = require("./clients/attachments-client");
|
|
4
5
|
const channels_client_1 = require("./clients/channels-client");
|
|
5
6
|
const comments_client_1 = require("./clients/comments-client");
|
|
6
7
|
const conversation_messages_client_1 = require("./clients/conversation-messages-client");
|
|
@@ -51,6 +52,7 @@ class CommsApi {
|
|
|
51
52
|
this.inbox = new inbox_client_1.InboxClient(clientConfig);
|
|
52
53
|
this.reactions = new reactions_client_1.ReactionsClient(clientConfig);
|
|
53
54
|
this.search = new search_client_1.SearchClient(clientConfig);
|
|
55
|
+
this.attachments = new attachments_client_1.AttachmentsClient(clientConfig);
|
|
54
56
|
}
|
|
55
57
|
/**
|
|
56
58
|
* 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_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_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';
|
|
@@ -30,3 +30,4 @@ exports.ENDPOINT_INBOX = 'inbox';
|
|
|
30
30
|
exports.ENDPOINT_REACTIONS = 'reactions';
|
|
31
31
|
exports.ENDPOINT_SEARCH = 'search';
|
|
32
32
|
exports.ENDPOINT_CONVERSATION_MESSAGES = 'conversation_messages';
|
|
33
|
+
exports.ENDPOINT_ATTACHMENTS = 'attachments';
|
package/dist/cjs/index.js
CHANGED
|
@@ -14,8 +14,10 @@ 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 = void 0;
|
|
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;
|
|
18
18
|
__exportStar(require("./authentication"), exports);
|
|
19
|
+
var attachments_client_1 = require("./clients/attachments-client");
|
|
20
|
+
Object.defineProperty(exports, "AttachmentsClient", { enumerable: true, get: function () { return attachments_client_1.AttachmentsClient; } });
|
|
19
21
|
var channels_client_1 = require("./clients/channels-client");
|
|
20
22
|
Object.defineProperty(exports, "ChannelsClient", { enumerable: true, get: function () { return channels_client_1.ChannelsClient; } });
|
|
21
23
|
var comments_client_1 = require("./clients/comments-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.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.StatusOkSchema = exports.SystemMessageSchema = void 0;
|
|
4
4
|
exports.createChannelSchema = createChannelSchema;
|
|
5
5
|
exports.createThreadSchema = createThreadSchema;
|
|
6
6
|
exports.createConversationSchema = createConversationSchema;
|
|
@@ -33,8 +33,7 @@ exports.StatusOkSchema = zod_1.z.object({ status: zod_1.z.literal('ok') });
|
|
|
33
33
|
// the attachment kind (file vs image vs link preview vs unfurled GIF).
|
|
34
34
|
// Loose: unknown keys from the backend pass through rather than being
|
|
35
35
|
// stripped, so newly-added or off-spec fields stay accessible to callers.
|
|
36
|
-
|
|
37
|
-
.object({
|
|
36
|
+
const attachmentShape = {
|
|
38
37
|
attachmentId: zod_1.z.string(),
|
|
39
38
|
urlType: zod_1.z.string(),
|
|
40
39
|
title: zod_1.z.string().nullable().optional(),
|
|
@@ -51,8 +50,12 @@ exports.AttachmentSchema = zod_1.z
|
|
|
51
50
|
video: zod_1.z.string().nullable().optional(),
|
|
52
51
|
videoType: zod_1.z.string().nullable().optional(),
|
|
53
52
|
videoAutoPlay: zod_1.z.boolean().nullable().optional(),
|
|
54
|
-
}
|
|
55
|
-
|
|
53
|
+
};
|
|
54
|
+
exports.AttachmentSchema = zod_1.z.object(attachmentShape).loose();
|
|
55
|
+
// Request-side attachment schema. Unlike the loose response `AttachmentSchema`,
|
|
56
|
+
// this strips unknown keys so caller typos / off-spec fields are dropped instead
|
|
57
|
+
// of being forwarded on the wire. Use this for `*Args` request schemas.
|
|
58
|
+
exports.RequestAttachmentSchema = zod_1.z.object(attachmentShape);
|
|
56
59
|
// Fields shared by `User` and `WorkspaceUser`. Profile fields
|
|
57
60
|
// (`fullName`, `imageId`, avatar URLs) are sourced from Todoist-ID.
|
|
58
61
|
exports.BaseUserSchema = zod_1.z.object({
|
|
@@ -42,6 +42,7 @@ exports.CreateThreadArgsSchema = zod_1.z.object({
|
|
|
42
42
|
id: zod_1.z.string().optional(),
|
|
43
43
|
recipients: zod_1.z.array(zod_1.z.number()).nullable().optional(),
|
|
44
44
|
groups: zod_1.z.array(zod_1.z.string()).nullable().optional(),
|
|
45
|
+
attachments: zod_1.z.array(entities_1.RequestAttachmentSchema).nullable().optional(),
|
|
45
46
|
});
|
|
46
47
|
exports.UpdateThreadArgsSchema = zod_1.z.object({
|
|
47
48
|
id: zod_1.z.string(),
|
|
@@ -52,7 +53,7 @@ exports.CreateCommentArgsSchema = zod_1.z.object({
|
|
|
52
53
|
threadId: zod_1.z.string(),
|
|
53
54
|
content: zod_1.z.string(),
|
|
54
55
|
id: zod_1.z.string().optional(),
|
|
55
|
-
attachments: zod_1.z.array(entities_1.
|
|
56
|
+
attachments: zod_1.z.array(entities_1.RequestAttachmentSchema).nullable().optional(),
|
|
56
57
|
actions: zod_1.z.unknown().nullable().optional(),
|
|
57
58
|
recipients: zod_1.z.array(zod_1.z.number()).nullable().optional(),
|
|
58
59
|
groups: zod_1.z.array(zod_1.z.string()).nullable().optional(),
|
|
@@ -76,7 +77,7 @@ exports.CreateMessageArgsSchema = zod_1.z
|
|
|
76
77
|
threadId: zod_1.z.string().nullable().optional(),
|
|
77
78
|
content: zod_1.z.string(),
|
|
78
79
|
id: zod_1.z.string().optional(),
|
|
79
|
-
attachments: zod_1.z.array(entities_1.
|
|
80
|
+
attachments: zod_1.z.array(entities_1.RequestAttachmentSchema).nullable().optional(),
|
|
80
81
|
})
|
|
81
82
|
.refine((data) => {
|
|
82
83
|
return ((data.conversationId && !data.threadId) || (!data.conversationId && data.threadId));
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getContentTypeFromFileName = getContentTypeFromFileName;
|
|
4
|
+
exports.uploadMultipartFile = uploadMultipartFile;
|
|
5
|
+
const fetch_with_retry_1 = require("../transport/fetch-with-retry");
|
|
6
|
+
/**
|
|
7
|
+
* Determine a content-type from a filename extension. Falls back to
|
|
8
|
+
* `application/octet-stream` for unknown extensions.
|
|
9
|
+
*/
|
|
10
|
+
function getContentTypeFromFileName(fileName) {
|
|
11
|
+
const extension = fileName.toLowerCase().split('.').pop();
|
|
12
|
+
return extension ? (MIME_BY_EXTENSION[extension] ?? DEFAULT_MIME_TYPE) : DEFAULT_MIME_TYPE;
|
|
13
|
+
}
|
|
14
|
+
const DEFAULT_MIME_TYPE = 'application/octet-stream';
|
|
15
|
+
/**
|
|
16
|
+
* Extension → MIME map covering the formats commonly attached to messages.
|
|
17
|
+
* Anything not listed falls back to {@link DEFAULT_MIME_TYPE}; callers can
|
|
18
|
+
* always override via the `contentType` argument.
|
|
19
|
+
*/
|
|
20
|
+
const MIME_BY_EXTENSION = {
|
|
21
|
+
// Images
|
|
22
|
+
png: 'image/png',
|
|
23
|
+
jpg: 'image/jpeg',
|
|
24
|
+
jpeg: 'image/jpeg',
|
|
25
|
+
gif: 'image/gif',
|
|
26
|
+
webp: 'image/webp',
|
|
27
|
+
svg: 'image/svg+xml',
|
|
28
|
+
bmp: 'image/bmp',
|
|
29
|
+
ico: 'image/x-icon',
|
|
30
|
+
tif: 'image/tiff',
|
|
31
|
+
tiff: 'image/tiff',
|
|
32
|
+
heic: 'image/heic',
|
|
33
|
+
heif: 'image/heif',
|
|
34
|
+
avif: 'image/avif',
|
|
35
|
+
// Documents / text
|
|
36
|
+
pdf: 'application/pdf',
|
|
37
|
+
txt: 'text/plain',
|
|
38
|
+
md: 'text/markdown',
|
|
39
|
+
csv: 'text/csv',
|
|
40
|
+
json: 'application/json',
|
|
41
|
+
xml: 'application/xml',
|
|
42
|
+
html: 'text/html',
|
|
43
|
+
htm: 'text/html',
|
|
44
|
+
rtf: 'application/rtf',
|
|
45
|
+
doc: 'application/msword',
|
|
46
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
47
|
+
xls: 'application/vnd.ms-excel',
|
|
48
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
49
|
+
ppt: 'application/vnd.ms-powerpoint',
|
|
50
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
51
|
+
// Archives
|
|
52
|
+
zip: 'application/zip',
|
|
53
|
+
gz: 'application/gzip',
|
|
54
|
+
tar: 'application/x-tar',
|
|
55
|
+
'7z': 'application/x-7z-compressed',
|
|
56
|
+
rar: 'application/vnd.rar',
|
|
57
|
+
// Audio
|
|
58
|
+
mp3: 'audio/mpeg',
|
|
59
|
+
wav: 'audio/wav',
|
|
60
|
+
ogg: 'audio/ogg',
|
|
61
|
+
m4a: 'audio/mp4',
|
|
62
|
+
flac: 'audio/flac',
|
|
63
|
+
aac: 'audio/aac',
|
|
64
|
+
// Video
|
|
65
|
+
mp4: 'video/mp4',
|
|
66
|
+
m4v: 'video/mp4',
|
|
67
|
+
mov: 'video/quicktime',
|
|
68
|
+
avi: 'video/x-msvideo',
|
|
69
|
+
webm: 'video/webm',
|
|
70
|
+
mkv: 'video/x-matroska',
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Normalise a supported {@link UploadFile} into a `Blob` plus a resolved file name and
|
|
74
|
+
* content type, so uploads use the cross-platform global `FormData`/`Blob` body that
|
|
75
|
+
* `undici` and browsers both accept natively.
|
|
76
|
+
*/
|
|
77
|
+
function toBlob(file, fileName, contentType) {
|
|
78
|
+
if (file instanceof Blob) {
|
|
79
|
+
// Duck-type the name rather than `instanceof File`: `File` isn't a global in
|
|
80
|
+
// Node 18, so a `node:buffer` `File` (which carries a real `name`) would
|
|
81
|
+
// otherwise fall back to `'upload'` and lose its inferred file name.
|
|
82
|
+
const blobName = file.name;
|
|
83
|
+
const name = fileName || (typeof blobName === 'string' ? blobName : undefined) || 'upload';
|
|
84
|
+
const type = contentType || file.type || getContentTypeFromFileName(name);
|
|
85
|
+
// Re-wrap only when stamping a type the Blob doesn't already carry.
|
|
86
|
+
const blob = file.type === type ? file : new Blob([file], { type });
|
|
87
|
+
return { blob, fileName: name, contentType: type };
|
|
88
|
+
}
|
|
89
|
+
if (file instanceof Uint8Array) {
|
|
90
|
+
if (!fileName) {
|
|
91
|
+
throw new Error('fileName is required when uploading raw bytes');
|
|
92
|
+
}
|
|
93
|
+
const type = contentType || getContentTypeFromFileName(fileName);
|
|
94
|
+
// `Blob` accepts any `ArrayBufferView`; the cast satisfies the stricter lib
|
|
95
|
+
// `BlobPart` type (which pins the backing buffer to `ArrayBuffer`).
|
|
96
|
+
return { blob: new Blob([file], { type }), fileName, contentType: type };
|
|
97
|
+
}
|
|
98
|
+
throw new Error('Unsupported file type for upload: expected a Blob or Uint8Array');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Upload a file using `multipart/form-data`.
|
|
102
|
+
*
|
|
103
|
+
* Builds the request body with the global `FormData`/`Blob` so it works unchanged in the
|
|
104
|
+
* browser and in Node.js (via `undici`). The `file` part is sent alongside `file_name`,
|
|
105
|
+
* `file_size`, and `underlying_type` fields (the canonical Comms upload shape); any
|
|
106
|
+
* `additionalFields` are merged in and override the derived values. Authentication uses
|
|
107
|
+
* `Authorization: Bearer`, matching every other Comms SDK client, and `Content-Type` is
|
|
108
|
+
* intentionally left unset so the runtime adds the correct multipart boundary.
|
|
109
|
+
*
|
|
110
|
+
* The response is JSON-parsed and camel-cased by {@link fetchWithRetry}; callers validate
|
|
111
|
+
* the returned shape with the appropriate schema.
|
|
112
|
+
*/
|
|
113
|
+
async function uploadMultipartFile(args) {
|
|
114
|
+
const { baseUrl, authToken, endpoint, file, fileName, contentType, additionalFields, maxRetries = 0, requestId, customFetch, } = args;
|
|
115
|
+
const { blob, fileName: resolvedFileName, contentType: resolvedType, } = toBlob(file, fileName, contentType);
|
|
116
|
+
const fields = {
|
|
117
|
+
file_name: resolvedFileName,
|
|
118
|
+
file_size: blob.size,
|
|
119
|
+
underlying_type: resolvedType,
|
|
120
|
+
...additionalFields,
|
|
121
|
+
};
|
|
122
|
+
const form = new FormData();
|
|
123
|
+
form.append('file', blob, resolvedFileName);
|
|
124
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
125
|
+
if (value !== undefined && value !== null) {
|
|
126
|
+
form.append(key, String(value));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const headers = {
|
|
130
|
+
Authorization: `Bearer ${authToken}`,
|
|
131
|
+
};
|
|
132
|
+
if (requestId) {
|
|
133
|
+
headers['X-Request-Id'] = requestId;
|
|
134
|
+
}
|
|
135
|
+
const url = new URL(endpoint, baseUrl).toString();
|
|
136
|
+
const response = await (0, fetch_with_retry_1.fetchWithRetry)(url, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers,
|
|
139
|
+
body: form,
|
|
140
|
+
// Don't set Content-Type — the runtime adds the multipart boundary.
|
|
141
|
+
timeout: 30000,
|
|
142
|
+
}, maxRetries, customFetch);
|
|
143
|
+
return response.data;
|
|
144
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ENDPOINT_ATTACHMENTS } from '../consts/endpoints.js';
|
|
2
|
+
import { AttachmentSchema } from '../types/entities.js';
|
|
3
|
+
import { uploadMultipartFile } from '../utils/multipart-upload.js';
|
|
4
|
+
import { isValidUuidV7Base58, resolveCreateId, UuidV7Error } from '../utils/uuidv7.js';
|
|
5
|
+
import { BaseClient } from './base-client.js';
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the `attachment_id`: validate a caller-supplied value (throwing an
|
|
8
|
+
* {@link UuidV7Error} that names `attachmentId` rather than the generic `id`) or
|
|
9
|
+
* mint a fresh one. Wraps {@link resolveCreateId} so the error contract matches the
|
|
10
|
+
* client's actual parameter.
|
|
11
|
+
*/
|
|
12
|
+
function resolveAttachmentId(attachmentId) {
|
|
13
|
+
if (attachmentId !== undefined && !isValidUuidV7Base58(attachmentId)) {
|
|
14
|
+
throw new UuidV7Error(`invalid attachmentId ${JSON.stringify(attachmentId)} — use generateId() or omit \`attachmentId\` and let the SDK mint one.`);
|
|
15
|
+
}
|
|
16
|
+
return resolveCreateId(attachmentId);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Client for uploading file attachments to Comms.
|
|
20
|
+
*
|
|
21
|
+
* Attachments are uploaded independently, then referenced by passing the returned
|
|
22
|
+
* {@link Attachment} into the `attachments` array of `comments.createComment`,
|
|
23
|
+
* `conversationMessages.createMessage`, and similar calls.
|
|
24
|
+
*/
|
|
25
|
+
export class AttachmentsClient extends BaseClient {
|
|
26
|
+
/**
|
|
27
|
+
* Uploads a file and returns the created {@link Attachment}.
|
|
28
|
+
*
|
|
29
|
+
* Mirrors the canonical multipart upload: `POST /attachments/upload` with the `file`
|
|
30
|
+
* binary plus `file_name`, `file_size`, `attachment_id`, and `underlying_type` form
|
|
31
|
+
* fields.
|
|
32
|
+
*
|
|
33
|
+
* @param args - The file to upload and optional metadata.
|
|
34
|
+
* @returns The created attachment, ready to attach to a comment or message.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import { readFile } from 'node:fs/promises'
|
|
39
|
+
*
|
|
40
|
+
* const attachment = await api.attachments.upload({
|
|
41
|
+
* file: await readFile('./diagram.png'),
|
|
42
|
+
* fileName: 'diagram.png',
|
|
43
|
+
* })
|
|
44
|
+
*
|
|
45
|
+
* await api.comments.createComment({
|
|
46
|
+
* threadId: '7YpL3oZ4kZ9vP7Q1tR2sX3z',
|
|
47
|
+
* content: 'See attached',
|
|
48
|
+
* attachments: [attachment],
|
|
49
|
+
* })
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
async upload(args) {
|
|
53
|
+
const data = await uploadMultipartFile({
|
|
54
|
+
baseUrl: this.getBaseUri(),
|
|
55
|
+
authToken: this.apiToken,
|
|
56
|
+
endpoint: `${ENDPOINT_ATTACHMENTS}/upload`,
|
|
57
|
+
file: args.file,
|
|
58
|
+
fileName: args.fileName,
|
|
59
|
+
contentType: args.contentType,
|
|
60
|
+
additionalFields: {
|
|
61
|
+
attachment_id: resolveAttachmentId(args.attachmentId),
|
|
62
|
+
},
|
|
63
|
+
customFetch: this.customFetch,
|
|
64
|
+
});
|
|
65
|
+
return AttachmentSchema.parse(data);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -92,6 +92,7 @@ export class ThreadsClient extends BaseClient {
|
|
|
92
92
|
* @param args.content - The thread content.
|
|
93
93
|
* @param args.recipients - Optional array of user IDs to notify.
|
|
94
94
|
* @param args.groups - Optional array of custom group IDs to notify.
|
|
95
|
+
* @param args.attachments - Optional array of {@link Attachment}s (from `attachments.upload`).
|
|
95
96
|
* @returns The created thread object.
|
|
96
97
|
*
|
|
97
98
|
* @example
|
package/dist/esm/comms-api.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AttachmentsClient } from './clients/attachments-client.js';
|
|
1
2
|
import { ChannelsClient } from './clients/channels-client.js';
|
|
2
3
|
import { CommentsClient } from './clients/comments-client.js';
|
|
3
4
|
import { ConversationMessagesClient } from './clients/conversation-messages-client.js';
|
|
@@ -48,6 +49,7 @@ export class CommsApi {
|
|
|
48
49
|
this.inbox = new InboxClient(clientConfig);
|
|
49
50
|
this.reactions = new ReactionsClient(clientConfig);
|
|
50
51
|
this.search = new SearchClient(clientConfig);
|
|
52
|
+
this.attachments = new AttachmentsClient(clientConfig);
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
53
55
|
* Drains the SDK's process-global connection pool. CLIs and scripts
|
package/dist/esm/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './authentication.js';
|
|
2
|
+
export { AttachmentsClient } from './clients/attachments-client.js';
|
|
2
3
|
export { ChannelsClient } from './clients/channels-client.js';
|
|
3
4
|
export { CommentsClient } from './clients/comments-client.js';
|
|
4
5
|
export { ConversationMessagesClient } from './clients/conversation-messages-client.js';
|
|
@@ -23,8 +23,7 @@ export const StatusOkSchema = z.object({ status: z.literal('ok') });
|
|
|
23
23
|
// the attachment kind (file vs image vs link preview vs unfurled GIF).
|
|
24
24
|
// Loose: unknown keys from the backend pass through rather than being
|
|
25
25
|
// stripped, so newly-added or off-spec fields stay accessible to callers.
|
|
26
|
-
|
|
27
|
-
.object({
|
|
26
|
+
const attachmentShape = {
|
|
28
27
|
attachmentId: z.string(),
|
|
29
28
|
urlType: z.string(),
|
|
30
29
|
title: z.string().nullable().optional(),
|
|
@@ -41,8 +40,12 @@ export const AttachmentSchema = z
|
|
|
41
40
|
video: z.string().nullable().optional(),
|
|
42
41
|
videoType: z.string().nullable().optional(),
|
|
43
42
|
videoAutoPlay: z.boolean().nullable().optional(),
|
|
44
|
-
}
|
|
45
|
-
|
|
43
|
+
};
|
|
44
|
+
export const AttachmentSchema = z.object(attachmentShape).loose();
|
|
45
|
+
// Request-side attachment schema. Unlike the loose response `AttachmentSchema`,
|
|
46
|
+
// this strips unknown keys so caller typos / off-spec fields are dropped instead
|
|
47
|
+
// of being forwarded on the wire. Use this for `*Args` request schemas.
|
|
48
|
+
export const RequestAttachmentSchema = z.object(attachmentShape);
|
|
46
49
|
// Fields shared by `User` and `WorkspaceUser`. Profile fields
|
|
47
50
|
// (`fullName`, `imageId`, avatar URLs) are sourced from Todoist-ID.
|
|
48
51
|
export const BaseUserSchema = z.object({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { RequestAttachmentSchema } from './entities.js';
|
|
3
3
|
import { NOTIFY_AUDIENCES } from './enums.js';
|
|
4
4
|
/**
|
|
5
5
|
* Create-side requests require an `id` on the wire. The SDK clients accept
|
|
@@ -39,6 +39,7 @@ export const CreateThreadArgsSchema = z.object({
|
|
|
39
39
|
id: z.string().optional(),
|
|
40
40
|
recipients: z.array(z.number()).nullable().optional(),
|
|
41
41
|
groups: z.array(z.string()).nullable().optional(),
|
|
42
|
+
attachments: z.array(RequestAttachmentSchema).nullable().optional(),
|
|
42
43
|
});
|
|
43
44
|
export const UpdateThreadArgsSchema = z.object({
|
|
44
45
|
id: z.string(),
|
|
@@ -49,7 +50,7 @@ export const CreateCommentArgsSchema = z.object({
|
|
|
49
50
|
threadId: z.string(),
|
|
50
51
|
content: z.string(),
|
|
51
52
|
id: z.string().optional(),
|
|
52
|
-
attachments: z.array(
|
|
53
|
+
attachments: z.array(RequestAttachmentSchema).nullable().optional(),
|
|
53
54
|
actions: z.unknown().nullable().optional(),
|
|
54
55
|
recipients: z.array(z.number()).nullable().optional(),
|
|
55
56
|
groups: z.array(z.string()).nullable().optional(),
|
|
@@ -73,7 +74,7 @@ export const CreateMessageArgsSchema = z
|
|
|
73
74
|
threadId: z.string().nullable().optional(),
|
|
74
75
|
content: z.string(),
|
|
75
76
|
id: z.string().optional(),
|
|
76
|
-
attachments: z.array(
|
|
77
|
+
attachments: z.array(RequestAttachmentSchema).nullable().optional(),
|
|
77
78
|
})
|
|
78
79
|
.refine((data) => {
|
|
79
80
|
return ((data.conversationId && !data.threadId) || (!data.conversationId && data.threadId));
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { fetchWithRetry } from '../transport/fetch-with-retry.js';
|
|
2
|
+
/**
|
|
3
|
+
* Determine a content-type from a filename extension. Falls back to
|
|
4
|
+
* `application/octet-stream` for unknown extensions.
|
|
5
|
+
*/
|
|
6
|
+
export function getContentTypeFromFileName(fileName) {
|
|
7
|
+
const extension = fileName.toLowerCase().split('.').pop();
|
|
8
|
+
return extension ? (MIME_BY_EXTENSION[extension] ?? DEFAULT_MIME_TYPE) : DEFAULT_MIME_TYPE;
|
|
9
|
+
}
|
|
10
|
+
const DEFAULT_MIME_TYPE = 'application/octet-stream';
|
|
11
|
+
/**
|
|
12
|
+
* Extension → MIME map covering the formats commonly attached to messages.
|
|
13
|
+
* Anything not listed falls back to {@link DEFAULT_MIME_TYPE}; callers can
|
|
14
|
+
* always override via the `contentType` argument.
|
|
15
|
+
*/
|
|
16
|
+
const MIME_BY_EXTENSION = {
|
|
17
|
+
// Images
|
|
18
|
+
png: 'image/png',
|
|
19
|
+
jpg: 'image/jpeg',
|
|
20
|
+
jpeg: 'image/jpeg',
|
|
21
|
+
gif: 'image/gif',
|
|
22
|
+
webp: 'image/webp',
|
|
23
|
+
svg: 'image/svg+xml',
|
|
24
|
+
bmp: 'image/bmp',
|
|
25
|
+
ico: 'image/x-icon',
|
|
26
|
+
tif: 'image/tiff',
|
|
27
|
+
tiff: 'image/tiff',
|
|
28
|
+
heic: 'image/heic',
|
|
29
|
+
heif: 'image/heif',
|
|
30
|
+
avif: 'image/avif',
|
|
31
|
+
// Documents / text
|
|
32
|
+
pdf: 'application/pdf',
|
|
33
|
+
txt: 'text/plain',
|
|
34
|
+
md: 'text/markdown',
|
|
35
|
+
csv: 'text/csv',
|
|
36
|
+
json: 'application/json',
|
|
37
|
+
xml: 'application/xml',
|
|
38
|
+
html: 'text/html',
|
|
39
|
+
htm: 'text/html',
|
|
40
|
+
rtf: 'application/rtf',
|
|
41
|
+
doc: 'application/msword',
|
|
42
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
43
|
+
xls: 'application/vnd.ms-excel',
|
|
44
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
45
|
+
ppt: 'application/vnd.ms-powerpoint',
|
|
46
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
47
|
+
// Archives
|
|
48
|
+
zip: 'application/zip',
|
|
49
|
+
gz: 'application/gzip',
|
|
50
|
+
tar: 'application/x-tar',
|
|
51
|
+
'7z': 'application/x-7z-compressed',
|
|
52
|
+
rar: 'application/vnd.rar',
|
|
53
|
+
// Audio
|
|
54
|
+
mp3: 'audio/mpeg',
|
|
55
|
+
wav: 'audio/wav',
|
|
56
|
+
ogg: 'audio/ogg',
|
|
57
|
+
m4a: 'audio/mp4',
|
|
58
|
+
flac: 'audio/flac',
|
|
59
|
+
aac: 'audio/aac',
|
|
60
|
+
// Video
|
|
61
|
+
mp4: 'video/mp4',
|
|
62
|
+
m4v: 'video/mp4',
|
|
63
|
+
mov: 'video/quicktime',
|
|
64
|
+
avi: 'video/x-msvideo',
|
|
65
|
+
webm: 'video/webm',
|
|
66
|
+
mkv: 'video/x-matroska',
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Normalise a supported {@link UploadFile} into a `Blob` plus a resolved file name and
|
|
70
|
+
* content type, so uploads use the cross-platform global `FormData`/`Blob` body that
|
|
71
|
+
* `undici` and browsers both accept natively.
|
|
72
|
+
*/
|
|
73
|
+
function toBlob(file, fileName, contentType) {
|
|
74
|
+
if (file instanceof Blob) {
|
|
75
|
+
// Duck-type the name rather than `instanceof File`: `File` isn't a global in
|
|
76
|
+
// Node 18, so a `node:buffer` `File` (which carries a real `name`) would
|
|
77
|
+
// otherwise fall back to `'upload'` and lose its inferred file name.
|
|
78
|
+
const blobName = file.name;
|
|
79
|
+
const name = fileName || (typeof blobName === 'string' ? blobName : undefined) || 'upload';
|
|
80
|
+
const type = contentType || file.type || getContentTypeFromFileName(name);
|
|
81
|
+
// Re-wrap only when stamping a type the Blob doesn't already carry.
|
|
82
|
+
const blob = file.type === type ? file : new Blob([file], { type });
|
|
83
|
+
return { blob, fileName: name, contentType: type };
|
|
84
|
+
}
|
|
85
|
+
if (file instanceof Uint8Array) {
|
|
86
|
+
if (!fileName) {
|
|
87
|
+
throw new Error('fileName is required when uploading raw bytes');
|
|
88
|
+
}
|
|
89
|
+
const type = contentType || getContentTypeFromFileName(fileName);
|
|
90
|
+
// `Blob` accepts any `ArrayBufferView`; the cast satisfies the stricter lib
|
|
91
|
+
// `BlobPart` type (which pins the backing buffer to `ArrayBuffer`).
|
|
92
|
+
return { blob: new Blob([file], { type }), fileName, contentType: type };
|
|
93
|
+
}
|
|
94
|
+
throw new Error('Unsupported file type for upload: expected a Blob or Uint8Array');
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Upload a file using `multipart/form-data`.
|
|
98
|
+
*
|
|
99
|
+
* Builds the request body with the global `FormData`/`Blob` so it works unchanged in the
|
|
100
|
+
* browser and in Node.js (via `undici`). The `file` part is sent alongside `file_name`,
|
|
101
|
+
* `file_size`, and `underlying_type` fields (the canonical Comms upload shape); any
|
|
102
|
+
* `additionalFields` are merged in and override the derived values. Authentication uses
|
|
103
|
+
* `Authorization: Bearer`, matching every other Comms SDK client, and `Content-Type` is
|
|
104
|
+
* intentionally left unset so the runtime adds the correct multipart boundary.
|
|
105
|
+
*
|
|
106
|
+
* The response is JSON-parsed and camel-cased by {@link fetchWithRetry}; callers validate
|
|
107
|
+
* the returned shape with the appropriate schema.
|
|
108
|
+
*/
|
|
109
|
+
export async function uploadMultipartFile(args) {
|
|
110
|
+
const { baseUrl, authToken, endpoint, file, fileName, contentType, additionalFields, maxRetries = 0, requestId, customFetch, } = args;
|
|
111
|
+
const { blob, fileName: resolvedFileName, contentType: resolvedType, } = toBlob(file, fileName, contentType);
|
|
112
|
+
const fields = {
|
|
113
|
+
file_name: resolvedFileName,
|
|
114
|
+
file_size: blob.size,
|
|
115
|
+
underlying_type: resolvedType,
|
|
116
|
+
...additionalFields,
|
|
117
|
+
};
|
|
118
|
+
const form = new FormData();
|
|
119
|
+
form.append('file', blob, resolvedFileName);
|
|
120
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
121
|
+
if (value !== undefined && value !== null) {
|
|
122
|
+
form.append(key, String(value));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const headers = {
|
|
126
|
+
Authorization: `Bearer ${authToken}`,
|
|
127
|
+
};
|
|
128
|
+
if (requestId) {
|
|
129
|
+
headers['X-Request-Id'] = requestId;
|
|
130
|
+
}
|
|
131
|
+
const url = new URL(endpoint, baseUrl).toString();
|
|
132
|
+
const response = await fetchWithRetry(url, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers,
|
|
135
|
+
body: form,
|
|
136
|
+
// Don't set Content-Type — the runtime adds the multipart boundary.
|
|
137
|
+
timeout: 30000,
|
|
138
|
+
}, maxRetries, customFetch);
|
|
139
|
+
return response.data;
|
|
140
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type Attachment } from '../types/entities.js';
|
|
2
|
+
import type { UploadAttachmentArgs } from '../types/requests.js';
|
|
3
|
+
import { BaseClient } from './base-client.js';
|
|
4
|
+
/**
|
|
5
|
+
* Client for uploading file attachments to Comms.
|
|
6
|
+
*
|
|
7
|
+
* Attachments are uploaded independently, then referenced by passing the returned
|
|
8
|
+
* {@link Attachment} into the `attachments` array of `comments.createComment`,
|
|
9
|
+
* `conversationMessages.createMessage`, and similar calls.
|
|
10
|
+
*/
|
|
11
|
+
export declare class AttachmentsClient extends BaseClient {
|
|
12
|
+
/**
|
|
13
|
+
* Uploads a file and returns the created {@link Attachment}.
|
|
14
|
+
*
|
|
15
|
+
* Mirrors the canonical multipart upload: `POST /attachments/upload` with the `file`
|
|
16
|
+
* binary plus `file_name`, `file_size`, `attachment_id`, and `underlying_type` form
|
|
17
|
+
* fields.
|
|
18
|
+
*
|
|
19
|
+
* @param args - The file to upload and optional metadata.
|
|
20
|
+
* @returns The created attachment, ready to attach to a comment or message.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import { readFile } from 'node:fs/promises'
|
|
25
|
+
*
|
|
26
|
+
* const attachment = await api.attachments.upload({
|
|
27
|
+
* file: await readFile('./diagram.png'),
|
|
28
|
+
* fileName: 'diagram.png',
|
|
29
|
+
* })
|
|
30
|
+
*
|
|
31
|
+
* await api.comments.createComment({
|
|
32
|
+
* threadId: '7YpL3oZ4kZ9vP7Q1tR2sX3z',
|
|
33
|
+
* content: 'See attached',
|
|
34
|
+
* attachments: [attachment],
|
|
35
|
+
* })
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
upload(args: UploadAttachmentArgs): Promise<Attachment>;
|
|
39
|
+
}
|
|
@@ -336,6 +336,7 @@ export declare class ThreadsClient extends BaseClient {
|
|
|
336
336
|
* @param args.content - The thread content.
|
|
337
337
|
* @param args.recipients - Optional array of user IDs to notify.
|
|
338
338
|
* @param args.groups - Optional array of custom group IDs to notify.
|
|
339
|
+
* @param args.attachments - Optional array of {@link Attachment}s (from `attachments.upload`).
|
|
339
340
|
* @returns The created thread object.
|
|
340
341
|
*
|
|
341
342
|
* @example
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AttachmentsClient } from './clients/attachments-client.js';
|
|
1
2
|
import { ChannelsClient } from './clients/channels-client.js';
|
|
2
3
|
import { CommentsClient } from './clients/comments-client.js';
|
|
3
4
|
import { ConversationMessagesClient } from './clients/conversation-messages-client.js';
|
|
@@ -44,6 +45,7 @@ export declare class CommsApi {
|
|
|
44
45
|
inbox: InboxClient;
|
|
45
46
|
reactions: ReactionsClient;
|
|
46
47
|
search: SearchClient;
|
|
48
|
+
attachments: AttachmentsClient;
|
|
47
49
|
/**
|
|
48
50
|
* Creates a new Comms API client.
|
|
49
51
|
*
|
|
@@ -22,3 +22,4 @@ export declare const ENDPOINT_INBOX = "inbox";
|
|
|
22
22
|
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
|
+
export declare const ENDPOINT_ATTACHMENTS = "attachments";
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './authentication.js';
|
|
2
|
+
export { AttachmentsClient } from './clients/attachments-client.js';
|
|
2
3
|
export { ChannelsClient } from './clients/channels-client.js';
|
|
3
4
|
export { CommentsClient } from './clients/comments-client.js';
|
|
4
5
|
export { ConversationMessagesClient } from './clients/conversation-messages-client.js';
|
|
@@ -30,6 +30,25 @@ export declare const AttachmentSchema: z.ZodObject<{
|
|
|
30
30
|
videoAutoPlay: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
31
31
|
}, z.core.$loose>;
|
|
32
32
|
export type Attachment = z.infer<typeof AttachmentSchema>;
|
|
33
|
+
export declare const RequestAttachmentSchema: z.ZodObject<{
|
|
34
|
+
attachmentId: z.ZodString;
|
|
35
|
+
urlType: z.ZodString;
|
|
36
|
+
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
37
|
+
url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
38
|
+
fileName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
39
|
+
fileSize: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
40
|
+
underlyingType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
41
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
42
|
+
image: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
43
|
+
imageWidth: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
44
|
+
imageHeight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
45
|
+
duration: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
46
|
+
uploadState: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
47
|
+
video: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
48
|
+
videoType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
49
|
+
videoAutoPlay: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
export type RequestAttachment = z.infer<typeof RequestAttachmentSchema>;
|
|
33
52
|
export declare const BaseUserSchema: z.ZodObject<{
|
|
34
53
|
id: z.ZodNumber;
|
|
35
54
|
fullName: z.ZodString;
|
|
@@ -40,6 +40,24 @@ export declare const CreateThreadArgsSchema: z.ZodObject<{
|
|
|
40
40
|
id: z.ZodOptional<z.ZodString>;
|
|
41
41
|
recipients: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber>>>;
|
|
42
42
|
groups: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
|
|
43
|
+
attachments: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
44
|
+
attachmentId: z.ZodString;
|
|
45
|
+
urlType: z.ZodString;
|
|
46
|
+
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
47
|
+
url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
48
|
+
fileName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
49
|
+
fileSize: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
50
|
+
underlyingType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
51
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
52
|
+
image: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
53
|
+
imageWidth: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
54
|
+
imageHeight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
55
|
+
duration: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
56
|
+
uploadState: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
57
|
+
video: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
58
|
+
videoType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
59
|
+
videoAutoPlay: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
60
|
+
}, z.core.$strip>>>>;
|
|
43
61
|
}, z.core.$strip>;
|
|
44
62
|
export type CreateThreadArgs = z.infer<typeof CreateThreadArgsSchema>;
|
|
45
63
|
export declare const UpdateThreadArgsSchema: z.ZodObject<{
|
|
@@ -69,7 +87,7 @@ export declare const CreateCommentArgsSchema: z.ZodObject<{
|
|
|
69
87
|
video: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
70
88
|
videoType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
71
89
|
videoAutoPlay: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
72
|
-
}, z.core.$
|
|
90
|
+
}, z.core.$strip>>>>;
|
|
73
91
|
actions: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
|
|
74
92
|
recipients: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodNumber>>>;
|
|
75
93
|
groups: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
|
|
@@ -115,7 +133,7 @@ export declare const CreateMessageArgsSchema: z.ZodObject<{
|
|
|
115
133
|
video: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
116
134
|
videoType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
117
135
|
videoAutoPlay: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
118
|
-
}, z.core.$
|
|
136
|
+
}, z.core.$strip>>>>;
|
|
119
137
|
}, z.core.$strip>;
|
|
120
138
|
export type CreateMessageArgs = z.infer<typeof CreateMessageArgsSchema>;
|
|
121
139
|
export declare const GetChannelsArgsSchema: z.ZodObject<{
|
|
@@ -368,4 +386,26 @@ export type GetUserLocalTimeArgs = {
|
|
|
368
386
|
workspaceId: number;
|
|
369
387
|
userId: number;
|
|
370
388
|
};
|
|
389
|
+
type UploadAttachmentCommonArgs = {
|
|
390
|
+
/** MIME type. Defaults to the `Blob`'s type or one inferred from the file extension. */
|
|
391
|
+
contentType?: string;
|
|
392
|
+
/** Attachment ID to use. A random ID is generated when omitted. */
|
|
393
|
+
attachmentId?: string;
|
|
394
|
+
};
|
|
395
|
+
/**
|
|
396
|
+
* Arguments for `attachments.upload`. A discriminated union on `file` enforces, at
|
|
397
|
+
* compile time, that raw `Uint8Array` bytes are accompanied by a `fileName` (there is
|
|
398
|
+
* no name to infer), while a `Blob`/`File` may omit it.
|
|
399
|
+
*/
|
|
400
|
+
export type UploadAttachmentArgs = UploadAttachmentCommonArgs & ({
|
|
401
|
+
/** The file to upload — a `Blob`/`File` (browser, or any runtime with a global `Blob`). */
|
|
402
|
+
file: Blob;
|
|
403
|
+
/** File name. Inferred from the `File.name` when omitted. */
|
|
404
|
+
fileName?: string;
|
|
405
|
+
} | {
|
|
406
|
+
/** Raw bytes to upload. A Node `Buffer` is a `Uint8Array`, so `await readFile(path)` works directly. */
|
|
407
|
+
file: Uint8Array;
|
|
408
|
+
/** File name. Required for raw bytes — there is no name to infer. */
|
|
409
|
+
fileName: string;
|
|
410
|
+
});
|
|
371
411
|
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { CustomFetch } from '../types/http.js';
|
|
2
|
+
/**
|
|
3
|
+
* File content accepted by the upload helpers.
|
|
4
|
+
*
|
|
5
|
+
* - `Blob`/`File` — browser, or any runtime with a global `Blob` (Node 18+).
|
|
6
|
+
* - `Uint8Array` — raw bytes (requires `fileName`). A Node `Buffer` is a `Uint8Array`,
|
|
7
|
+
* so reading a file with `fs` and passing the result works without conversion.
|
|
8
|
+
*
|
|
9
|
+
* Both are universal types, so the published `.d.ts` stays free of Node-only globals
|
|
10
|
+
* (`Buffer`, `NodeJS.*`) and the helper pulls in no Node built-ins — keeping the browser
|
|
11
|
+
* bundle clean.
|
|
12
|
+
*/
|
|
13
|
+
export type UploadFile = Blob | Uint8Array;
|
|
14
|
+
type UploadMultipartFileArgs = {
|
|
15
|
+
/** Base API URI with trailing slash, e.g. `https://comms.todoist.com/api/v1/`. */
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
/** API token used for `Authorization: Bearer`. */
|
|
18
|
+
authToken: string;
|
|
19
|
+
/** Relative endpoint path, e.g. `attachments/upload`. */
|
|
20
|
+
endpoint: string;
|
|
21
|
+
/** File content to upload. */
|
|
22
|
+
file: UploadFile;
|
|
23
|
+
/** File name. Required for raw `Uint8Array` bytes; inferred from a `File`. */
|
|
24
|
+
fileName?: string;
|
|
25
|
+
/** MIME type. Defaults to the `Blob`'s type or one inferred from the file extension. */
|
|
26
|
+
contentType?: string;
|
|
27
|
+
/** Extra multipart fields to send alongside the file metadata fields. */
|
|
28
|
+
additionalFields?: Record<string, string | number | boolean | undefined | null>;
|
|
29
|
+
/**
|
|
30
|
+
* Network-error retry count. Defaults to `0` — unlike JSON requests, a retry here
|
|
31
|
+
* resends the entire file body, so large uploads aren't retried automatically.
|
|
32
|
+
*/
|
|
33
|
+
maxRetries?: number;
|
|
34
|
+
/** Optional request ID for tracing. */
|
|
35
|
+
requestId?: string;
|
|
36
|
+
/** Optional custom fetch implementation. */
|
|
37
|
+
customFetch?: CustomFetch;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Determine a content-type from a filename extension. Falls back to
|
|
41
|
+
* `application/octet-stream` for unknown extensions.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getContentTypeFromFileName(fileName: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Upload a file using `multipart/form-data`.
|
|
46
|
+
*
|
|
47
|
+
* Builds the request body with the global `FormData`/`Blob` so it works unchanged in the
|
|
48
|
+
* browser and in Node.js (via `undici`). The `file` part is sent alongside `file_name`,
|
|
49
|
+
* `file_size`, and `underlying_type` fields (the canonical Comms upload shape); any
|
|
50
|
+
* `additionalFields` are merged in and override the derived values. Authentication uses
|
|
51
|
+
* `Authorization: Bearer`, matching every other Comms SDK client, and `Content-Type` is
|
|
52
|
+
* intentionally left unset so the runtime adds the correct multipart boundary.
|
|
53
|
+
*
|
|
54
|
+
* The response is JSON-parsed and camel-cased by {@link fetchWithRetry}; callers validate
|
|
55
|
+
* the returned shape with the appropriate schema.
|
|
56
|
+
*/
|
|
57
|
+
export declare function uploadMultipartFile<T>(args: UploadMultipartFileArgs): Promise<T>;
|
|
58
|
+
export {};
|