@doist/comms-sdk 2.0.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/esm/utils/url-helpers.js +104 -16
  2. package/dist/types/utils/url-helpers.d.ts +41 -0
  3. package/package.json +5 -10
  4. package/dist/cjs/authentication.js +0 -256
  5. package/dist/cjs/clients/add-comment-helper.js +0 -43
  6. package/dist/cjs/clients/attachments-client.js +0 -71
  7. package/dist/cjs/clients/base-client.js +0 -33
  8. package/dist/cjs/clients/channels-client.js +0 -211
  9. package/dist/cjs/clients/comments-client.js +0 -175
  10. package/dist/cjs/clients/conversation-messages-client.js +0 -169
  11. package/dist/cjs/clients/conversations-client.js +0 -283
  12. package/dist/cjs/clients/groups-client.js +0 -172
  13. package/dist/cjs/clients/hooks-client.js +0 -52
  14. package/dist/cjs/clients/inbox-client.js +0 -184
  15. package/dist/cjs/clients/notify-audience.js +0 -45
  16. package/dist/cjs/clients/reactions-client.js +0 -97
  17. package/dist/cjs/clients/search-client.js +0 -138
  18. package/dist/cjs/clients/threads-client.js +0 -364
  19. package/dist/cjs/clients/users-client.js +0 -326
  20. package/dist/cjs/clients/workspace-users-client.js +0 -252
  21. package/dist/cjs/clients/workspaces-client.js +0 -174
  22. package/dist/cjs/comms-api.js +0 -70
  23. package/dist/cjs/consts/endpoints.js +0 -34
  24. package/dist/cjs/index.js +0 -53
  25. package/dist/cjs/migration.js +0 -96
  26. package/dist/cjs/package.json +0 -1
  27. package/dist/cjs/testUtils/msw-handlers.js +0 -51
  28. package/dist/cjs/testUtils/msw-setup.js +0 -35
  29. package/dist/cjs/testUtils/obsidian-fetch-adapter.js +0 -53
  30. package/dist/cjs/testUtils/test-defaults.js +0 -106
  31. package/dist/cjs/transport/fetch-with-retry.js +0 -146
  32. package/dist/cjs/transport/http-client.js +0 -56
  33. package/dist/cjs/transport/http-dispatcher.js +0 -190
  34. package/dist/cjs/types/api-version.js +0 -8
  35. package/dist/cjs/types/entities.js +0 -496
  36. package/dist/cjs/types/enums.js +0 -37
  37. package/dist/cjs/types/errors.js +0 -12
  38. package/dist/cjs/types/http.js +0 -4
  39. package/dist/cjs/types/index.js +0 -22
  40. package/dist/cjs/types/requests.js +0 -183
  41. package/dist/cjs/utils/case-conversion.js +0 -54
  42. package/dist/cjs/utils/index.js +0 -19
  43. package/dist/cjs/utils/multipart-upload.js +0 -202
  44. package/dist/cjs/utils/timestamp-conversion.js +0 -58
  45. package/dist/cjs/utils/url-helpers.js +0 -133
  46. package/dist/cjs/utils/uuidv7.js +0 -182
@@ -1,183 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.THREAD_ACTIONS = exports.UnsubscribeHookArgsSchema = exports.SubscribeHookArgsSchema = exports.SubscribeMessageHookArgsSchema = exports.SubscribeNonMessageHookArgsSchema = exports.SubscribeHookArgsBaseSchema = exports.HookTargetUrlSchema = exports.HookEventSchema = exports.MessageHookEventSchema = exports.NonMessageHookEventSchema = exports.HOOK_EVENTS = exports.MESSAGE_HOOK_EVENTS = exports.NON_MESSAGE_HOOK_EVENTS = exports.ARCHIVE_FILTER_VALUES = exports.GetOrCreateConversationArgsSchema = exports.GetConversationsArgsSchema = exports.GetCommentsArgsSchema = exports.GetThreadsArgsSchema = exports.GetChannelsArgsSchema = exports.CreateMessageArgsSchema = exports.CreateConversationArgsSchema = exports.UpdateCommentArgsSchema = exports.CreateCommentArgsSchema = exports.UpdateThreadArgsSchema = exports.CreateThreadArgsSchema = exports.UpdateChannelArgsSchema = exports.CreateChannelArgsSchema = void 0;
4
- const zod_1 = require("zod");
5
- const entities_1 = require("./entities");
6
- const enums_1 = require("./enums");
7
- /**
8
- * Create-side requests require an `id` on the wire. The SDK clients accept
9
- * `id` as optional and auto-generate one via
10
- * {@link import('../utils/uuidv7').generateId} when the caller doesn't pass
11
- * one — callers can still mint their own ID locally (e.g. for optimistic
12
- * UI) and have it stick.
13
- */
14
- exports.CreateChannelArgsSchema = zod_1.z.object({
15
- workspaceId: zod_1.z.number(),
16
- name: zod_1.z.string(),
17
- id: zod_1.z.string().optional(),
18
- userIds: zod_1.z.array(zod_1.z.number()).nullable().optional(),
19
- color: zod_1.z.number().nullable().optional(),
20
- public: zod_1.z.boolean().nullable().optional(),
21
- description: zod_1.z.string().nullable().optional(),
22
- defaultGroups: zod_1.z.array(zod_1.z.string()).nullable().optional(),
23
- defaultRecipients: zod_1.z.array(zod_1.z.number()).nullable().optional(),
24
- isFavorited: zod_1.z.boolean().nullable().optional(),
25
- icon: zod_1.z.number().nullable().optional(),
26
- });
27
- exports.UpdateChannelArgsSchema = zod_1.z.object({
28
- id: zod_1.z.string(),
29
- name: zod_1.z.string(),
30
- color: zod_1.z.number().nullable().optional(),
31
- public: zod_1.z.boolean().nullable().optional(),
32
- description: zod_1.z.string().nullable().optional(),
33
- defaultGroups: zod_1.z.array(zod_1.z.string()).nullable().optional(),
34
- defaultRecipients: zod_1.z.array(zod_1.z.number()).nullable().optional(),
35
- isFavorited: zod_1.z.boolean().nullable().optional(),
36
- icon: zod_1.z.number().nullable().optional(),
37
- });
38
- exports.CreateThreadArgsSchema = zod_1.z.object({
39
- channelId: zod_1.z.string(),
40
- content: zod_1.z.string(),
41
- title: zod_1.z.string().nullable().optional(),
42
- id: zod_1.z.string().optional(),
43
- recipients: zod_1.z.array(zod_1.z.number()).nullable().optional(),
44
- groups: zod_1.z.array(zod_1.z.string()).nullable().optional(),
45
- notifyAudience: zod_1.z.enum(enums_1.NOTIFY_AUDIENCES).nullable().optional(),
46
- attachments: zod_1.z.array(entities_1.RequestAttachmentSchema).nullable().optional(),
47
- });
48
- exports.UpdateThreadArgsSchema = zod_1.z.object({
49
- id: zod_1.z.string(),
50
- title: zod_1.z.string().nullable().optional(),
51
- content: zod_1.z.string().nullable().optional(),
52
- });
53
- exports.CreateCommentArgsSchema = zod_1.z.object({
54
- threadId: zod_1.z.string(),
55
- content: zod_1.z.string(),
56
- id: zod_1.z.string().optional(),
57
- attachments: zod_1.z.array(entities_1.RequestAttachmentSchema).nullable().optional(),
58
- actions: zod_1.z.unknown().nullable().optional(),
59
- recipients: zod_1.z.array(zod_1.z.number()).nullable().optional(),
60
- groups: zod_1.z.array(zod_1.z.string()).nullable().optional(),
61
- directMentions: zod_1.z.array(zod_1.z.number()).nullable().optional(),
62
- directGroupMentions: zod_1.z.array(zod_1.z.string()).nullable().optional(),
63
- notifyAudience: zod_1.z.enum(enums_1.NOTIFY_AUDIENCES).nullable().optional(),
64
- });
65
- exports.UpdateCommentArgsSchema = zod_1.z.object({
66
- id: zod_1.z.string(),
67
- content: zod_1.z.string(),
68
- });
69
- exports.CreateConversationArgsSchema = zod_1.z.object({
70
- workspaceId: zod_1.z.number(),
71
- recipients: zod_1.z.array(zod_1.z.number()),
72
- id: zod_1.z.string().optional(),
73
- title: zod_1.z.string().nullable().optional(),
74
- });
75
- exports.CreateMessageArgsSchema = zod_1.z
76
- .object({
77
- conversationId: zod_1.z.string().nullable().optional(),
78
- threadId: zod_1.z.string().nullable().optional(),
79
- content: zod_1.z.string(),
80
- id: zod_1.z.string().optional(),
81
- attachments: zod_1.z.array(entities_1.RequestAttachmentSchema).nullable().optional(),
82
- })
83
- .refine((data) => {
84
- return ((data.conversationId && !data.threadId) || (!data.conversationId && data.threadId));
85
- }, {
86
- message: 'Exactly one of conversationId or threadId must be provided',
87
- });
88
- exports.GetChannelsArgsSchema = zod_1.z.object({
89
- workspaceId: zod_1.z.number(),
90
- archived: zod_1.z.boolean().nullable().optional(),
91
- });
92
- exports.GetThreadsArgsSchema = zod_1.z.object({
93
- workspaceId: zod_1.z.number(),
94
- channelId: zod_1.z.string().nullable().optional(),
95
- archived: zod_1.z.boolean().nullable().optional(),
96
- newerThan: zod_1.z.date().nullable().optional(),
97
- olderThan: zod_1.z.date().nullable().optional(),
98
- limit: zod_1.z.number().nullable().optional(),
99
- });
100
- exports.GetCommentsArgsSchema = zod_1.z.object({
101
- threadId: zod_1.z.string(),
102
- newerThan: zod_1.z.date().nullable().optional(),
103
- olderThan: zod_1.z.date().nullable().optional(),
104
- limit: zod_1.z.number().nullable().optional(),
105
- });
106
- exports.GetConversationsArgsSchema = zod_1.z.object({
107
- workspaceId: zod_1.z.number(),
108
- archived: zod_1.z.boolean().nullable().optional(),
109
- olderThan: zod_1.z.date().nullable().optional(),
110
- beforeId: zod_1.z.string().nullable().optional(),
111
- limit: zod_1.z.number().nullable().optional(),
112
- });
113
- exports.GetOrCreateConversationArgsSchema = zod_1.z.object({
114
- workspaceId: zod_1.z.number(),
115
- userIds: zod_1.z.array(zod_1.z.number()),
116
- id: zod_1.z.string().optional(),
117
- });
118
- // Inbox
119
- exports.ARCHIVE_FILTER_VALUES = ['active', 'archived', 'all'];
120
- // Hooks
121
- exports.NON_MESSAGE_HOOK_EVENTS = [
122
- 'workspace_added',
123
- 'workspace_updated',
124
- 'workspace_deleted',
125
- 'workspace_user_added',
126
- 'workspace_user_updated',
127
- 'workspace_user_removed',
128
- 'channel_added',
129
- 'channel_updated',
130
- 'channel_deleted',
131
- 'channel_user_added',
132
- 'channel_user_removed',
133
- 'thread_added',
134
- 'thread_updated',
135
- 'thread_deleted',
136
- 'comment_added',
137
- 'comment_updated',
138
- 'comment_deleted',
139
- 'group_added',
140
- 'group_updated',
141
- 'group_deleted',
142
- 'group_user_added',
143
- 'group_user_removed',
144
- ];
145
- exports.MESSAGE_HOOK_EVENTS = ['message_added', 'message_updated'];
146
- exports.HOOK_EVENTS = [...exports.NON_MESSAGE_HOOK_EVENTS, ...exports.MESSAGE_HOOK_EVENTS];
147
- exports.NonMessageHookEventSchema = zod_1.z.enum(exports.NON_MESSAGE_HOOK_EVENTS);
148
- exports.MessageHookEventSchema = zod_1.z.enum(exports.MESSAGE_HOOK_EVENTS);
149
- exports.HookEventSchema = zod_1.z.enum(exports.HOOK_EVENTS);
150
- exports.HookTargetUrlSchema = zod_1.z
151
- .url()
152
- .max(150)
153
- .refine((value) => {
154
- try {
155
- return new URL(value).protocol === 'https:';
156
- }
157
- catch {
158
- return false;
159
- }
160
- }, { message: 'URL must use HTTPS' });
161
- exports.SubscribeHookArgsBaseSchema = zod_1.z.object({
162
- targetUrl: exports.HookTargetUrlSchema,
163
- workspaceId: zod_1.z.number().nullable().optional(),
164
- channelId: zod_1.z.string().nullable().optional(),
165
- threadId: zod_1.z.string().nullable().optional(),
166
- });
167
- exports.SubscribeNonMessageHookArgsSchema = exports.SubscribeHookArgsBaseSchema.extend({
168
- event: exports.NonMessageHookEventSchema,
169
- conversationId: zod_1.z.never().optional(),
170
- });
171
- exports.SubscribeMessageHookArgsSchema = exports.SubscribeHookArgsBaseSchema.extend({
172
- event: exports.MessageHookEventSchema,
173
- conversationId: zod_1.z.string().nullable().optional(),
174
- });
175
- exports.SubscribeHookArgsSchema = zod_1.z.union([
176
- exports.SubscribeNonMessageHookArgsSchema,
177
- exports.SubscribeMessageHookArgsSchema,
178
- ]);
179
- exports.UnsubscribeHookArgsSchema = zod_1.z.object({
180
- targetUrl: exports.HookTargetUrlSchema,
181
- });
182
- // Threads
183
- exports.THREAD_ACTIONS = ['close', 'reopen'];
@@ -1,54 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.camelCaseKeys = camelCaseKeys;
7
- exports.snakeCaseKeys = snakeCaseKeys;
8
- const camelcase_1 = __importDefault(require("camelcase"));
9
- function isObject(value) {
10
- return typeof value === 'object' && value !== null && !Array.isArray(value);
11
- }
12
- function isArray(value) {
13
- return Array.isArray(value);
14
- }
15
- function toSnakeCase(str) {
16
- return str
17
- .replace(/([A-Z])/g, '_$1')
18
- .toLowerCase()
19
- .replace(/^_/, '');
20
- }
21
- /**
22
- * @internal
23
- */
24
- function camelCaseKeys(obj) {
25
- if (isArray(obj)) {
26
- return obj.map(camelCaseKeys);
27
- }
28
- if (isObject(obj)) {
29
- const result = {};
30
- for (const [key, value] of Object.entries(obj)) {
31
- const camelKey = (0, camelcase_1.default)(key);
32
- result[camelKey] = camelCaseKeys(value);
33
- }
34
- return result;
35
- }
36
- return obj;
37
- }
38
- /**
39
- * @internal
40
- */
41
- function snakeCaseKeys(obj) {
42
- if (isArray(obj)) {
43
- return obj.map(snakeCaseKeys);
44
- }
45
- if (isObject(obj)) {
46
- const result = {};
47
- for (const [key, value] of Object.entries(obj)) {
48
- const snakeKey = toSnakeCase(key);
49
- result[snakeKey] = snakeCaseKeys(value);
50
- }
51
- return result;
52
- }
53
- return obj;
54
- }
@@ -1,19 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./case-conversion"), exports);
18
- __exportStar(require("./url-helpers"), exports);
19
- __exportStar(require("./uuidv7"), exports);
@@ -1,202 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getContentTypeFromFileName = getContentTypeFromFileName;
4
- exports.uploadMultipartFile = uploadMultipartFile;
5
- const uuid_1 = require("uuid");
6
- const fetch_with_retry_1 = require("../transport/fetch-with-retry");
7
- /**
8
- * Determine a content-type from a filename extension. Falls back to
9
- * `application/octet-stream` for unknown extensions.
10
- */
11
- function getContentTypeFromFileName(fileName) {
12
- const extension = fileName.toLowerCase().split('.').pop();
13
- return extension ? (MIME_BY_EXTENSION[extension] ?? DEFAULT_MIME_TYPE) : DEFAULT_MIME_TYPE;
14
- }
15
- const DEFAULT_MIME_TYPE = 'application/octet-stream';
16
- /**
17
- * Extension → MIME map covering the formats commonly attached to messages.
18
- * Anything not listed falls back to {@link DEFAULT_MIME_TYPE}; callers can
19
- * always override via the `contentType` argument.
20
- */
21
- const MIME_BY_EXTENSION = {
22
- // Images
23
- png: 'image/png',
24
- jpg: 'image/jpeg',
25
- jpeg: 'image/jpeg',
26
- gif: 'image/gif',
27
- webp: 'image/webp',
28
- svg: 'image/svg+xml',
29
- bmp: 'image/bmp',
30
- ico: 'image/x-icon',
31
- tif: 'image/tiff',
32
- tiff: 'image/tiff',
33
- heic: 'image/heic',
34
- heif: 'image/heif',
35
- avif: 'image/avif',
36
- // Documents / text
37
- pdf: 'application/pdf',
38
- txt: 'text/plain',
39
- md: 'text/markdown',
40
- csv: 'text/csv',
41
- json: 'application/json',
42
- xml: 'application/xml',
43
- html: 'text/html',
44
- htm: 'text/html',
45
- rtf: 'application/rtf',
46
- doc: 'application/msword',
47
- docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
48
- xls: 'application/vnd.ms-excel',
49
- xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
50
- ppt: 'application/vnd.ms-powerpoint',
51
- pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
52
- // Archives
53
- zip: 'application/zip',
54
- gz: 'application/gzip',
55
- tar: 'application/x-tar',
56
- '7z': 'application/x-7z-compressed',
57
- rar: 'application/vnd.rar',
58
- // Audio
59
- mp3: 'audio/mpeg',
60
- wav: 'audio/wav',
61
- ogg: 'audio/ogg',
62
- m4a: 'audio/mp4',
63
- flac: 'audio/flac',
64
- aac: 'audio/aac',
65
- // Video
66
- mp4: 'video/mp4',
67
- m4v: 'video/mp4',
68
- mov: 'video/quicktime',
69
- avi: 'video/x-msvideo',
70
- webm: 'video/webm',
71
- mkv: 'video/x-matroska',
72
- };
73
- /**
74
- * Normalise a supported {@link UploadFile} into a `Blob` plus a resolved file name and
75
- * content type, ready to be encoded into the multipart body.
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
- * Escapes a value for use inside a quoted `Content-Disposition` parameter, where a raw
102
- * quote would end the value early and a raw CR or LF would end the header. A lone CR
103
- * needs escaping too, not just CRLF pairs.
104
- *
105
- * This is the same escaping the platform's own `FormData` encoder applies, so servers
106
- * see exactly what they would from any browser.
107
- */
108
- function escapeDispositionValue(value) {
109
- return value.replace(/"/g, '%22').replace(/\r/g, '%0D').replace(/\n/g, '%0A');
110
- }
111
- /**
112
- * Rejects a media type that could not be safely written into a part header.
113
- *
114
- * `contentType` is public caller input and reaches the header verbatim. A value
115
- * carrying a CR or LF would end the header early and let the caller forge one
116
- * of its own — `Blob` normalises the `type` it stores, but the supplied value
117
- * is what gets interpolated. Reject rather than silently rewrite, so a caller
118
- * passing something unusable finds out instead of having it altered.
119
- */
120
- function assertHeaderSafeContentType(contentType) {
121
- if (!/^[\x20-\x7e]*$/.test(contentType)) {
122
- throw new Error('contentType must contain only printable ASCII characters, without CR or LF');
123
- }
124
- }
125
- /**
126
- * Encodes a `multipart/form-data` body as a `Blob`, together with the `Content-Type`
127
- * that describes it.
128
- *
129
- * Deliberately avoids `FormData`. A `FormData` body is only encoded by the `fetch` that
130
- * owns that `FormData` class — undici brands its own and checks it with a plain
131
- * `instanceof` — and the SDK dispatches through undici's own `fetch` to keep the request
132
- * client on the same undici as the dispatcher. A global `FormData` handed to it is
133
- * stringified to the literal `"[object FormData]"`, so the upload silently carries no
134
- * file. `Blob` has no such brand and works with whichever `fetch` sends the request,
135
- * including a caller-supplied `customFetch`.
136
- */
137
- function buildMultipartBody(args) {
138
- const { blob, fileName, contentType, fields } = args;
139
- assertHeaderSafeContentType(contentType);
140
- const boundary = `----comms-sdk-${(0, uuid_1.v4)()}`;
141
- const parts = [
142
- `--${boundary}\r\n` +
143
- `Content-Disposition: form-data; name="file"; filename="${escapeDispositionValue(fileName)}"\r\n` +
144
- `Content-Type: ${contentType}\r\n\r\n`,
145
- blob,
146
- '\r\n',
147
- ];
148
- for (const [key, value] of Object.entries(fields)) {
149
- if (value !== undefined && value !== null) {
150
- parts.push(`--${boundary}\r\n` +
151
- `Content-Disposition: form-data; name="${escapeDispositionValue(key)}"\r\n\r\n` +
152
- `${String(value)}\r\n`);
153
- }
154
- }
155
- parts.push(`--${boundary}--\r\n`);
156
- return { body: new Blob(parts), contentType: `multipart/form-data; boundary=${boundary}` };
157
- }
158
- /**
159
- * Upload a file using `multipart/form-data`.
160
- *
161
- * Encodes the request body as a `Blob` so it works unchanged in the browser and in
162
- * Node.js, whichever `fetch` ends up sending it. The `file` part is sent alongside `file_name`,
163
- * `file_size`, and `underlying_type` fields (the canonical Comms upload shape); any
164
- * `additionalFields` are merged in and override the derived values. Authentication uses
165
- * `Authorization: Bearer`, matching every other Comms SDK client.
166
- *
167
- * The response is JSON-parsed and camel-cased by {@link fetchWithRetry}; callers validate
168
- * the returned shape with the appropriate schema.
169
- */
170
- async function uploadMultipartFile(args) {
171
- const { baseUrl, authToken, endpoint, file, fileName, contentType, additionalFields, maxRetries = 0, requestId, customFetch, } = args;
172
- const { blob, fileName: resolvedFileName, contentType: resolvedType, } = toBlob(file, fileName, contentType);
173
- const fields = {
174
- file_name: resolvedFileName,
175
- file_size: blob.size,
176
- underlying_type: resolvedType,
177
- ...additionalFields,
178
- };
179
- const multipart = buildMultipartBody({
180
- blob,
181
- fileName: resolvedFileName,
182
- contentType: resolvedType,
183
- fields,
184
- });
185
- const headers = {
186
- Authorization: `Bearer ${authToken}`,
187
- // The boundary is ours, so this has to be declared explicitly — `fetch`
188
- // only fills it in for a `FormData` body.
189
- 'Content-Type': multipart.contentType,
190
- };
191
- if (requestId) {
192
- headers['X-Request-Id'] = requestId;
193
- }
194
- const url = new URL(endpoint, baseUrl).toString();
195
- const response = await (0, fetch_with_retry_1.fetchWithRetry)(url, {
196
- method: 'POST',
197
- headers,
198
- body: multipart.body,
199
- timeout: 30000,
200
- }, maxRetries, customFetch);
201
- return response.data;
202
- }
@@ -1,58 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.timestampToDate = timestampToDate;
4
- exports.transformTimestamps = transformTimestamps;
5
- /**
6
- * Converts a Unix timestamp (in seconds) to a Date object.
7
- * @param timestamp - Unix timestamp in seconds
8
- * @returns Date object
9
- */
10
- function timestampToDate(timestamp) {
11
- return new Date(timestamp * 1000);
12
- }
13
- const timestampFieldTargetOverrides = new Map([['pinnedTs', 'pinnedDate']]);
14
- /**
15
- * Recursively transforms timestamp fields (ending in 'Ts') in an object to Date objects.
16
- * Also renames converted fields by removing the 'Ts' suffix.
17
- * @param obj - The object to transform
18
- * @returns The transformed object with Date fields
19
- */
20
- function transformTimestamps(obj) {
21
- if (obj === null || obj === undefined) {
22
- return obj;
23
- }
24
- if (Array.isArray(obj)) {
25
- return obj.map((item) => transformTimestamps(item));
26
- }
27
- if (typeof obj === 'object') {
28
- const result = {};
29
- for (const [key, value] of Object.entries(obj)) {
30
- // Check if the key ends with 'Ts'
31
- if (key.endsWith('Ts')) {
32
- const overriddenTargetKey = timestampFieldTargetOverrides.get(key);
33
- if (typeof value === 'number') {
34
- // Remove 'Ts' suffix and convert to Date
35
- const newKey = key.slice(0, -2);
36
- // If the base key already exists in the original object, use *Date suffix
37
- // to avoid overwriting it (e.g. posted + postedTs → posted + postedDate)
38
- const targetKey = overriddenTargetKey ??
39
- (newKey in obj ? `${newKey}Date` : newKey);
40
- result[targetKey] = timestampToDate(value);
41
- continue;
42
- }
43
- if (overriddenTargetKey) {
44
- result[overriddenTargetKey] = value;
45
- continue;
46
- }
47
- }
48
- if (typeof value === 'object' && value !== null) {
49
- // Recursively transform nested objects
50
- result[key] = transformTimestamps(value);
51
- continue;
52
- }
53
- result[key] = value;
54
- }
55
- return result;
56
- }
57
- return obj;
58
- }
@@ -1,133 +0,0 @@
1
- "use strict";
2
- /**
3
- * Helper functions for creating Comms permalinks (`https://comms.todoist.com/...`).
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getCommsURL = getCommsURL;
7
- exports.getFullCommsURL = getFullCommsURL;
8
- exports.getThreadURL = getThreadURL;
9
- exports.getChannelURL = getChannelURL;
10
- exports.getConversationURL = getConversationURL;
11
- exports.getMessageURL = getMessageURL;
12
- exports.getCommentURL = getCommentURL;
13
- exports.getThreadsRootURL = getThreadsRootURL;
14
- exports.getInboxURL = getInboxURL;
15
- exports.getMessagesRootURL = getMessagesRootURL;
16
- exports.getUserProfileURL = getUserProfileURL;
17
- exports.getSavedThreadsRootURL = getSavedThreadsRootURL;
18
- exports.getSavedThreadURL = getSavedThreadURL;
19
- exports.getSearchRootURL = getSearchRootURL;
20
- exports.getSearchQueryURL = getSearchQueryURL;
21
- exports.getSettingsURL = getSettingsURL;
22
- exports.getTeamMembersRootURL = getTeamMembersRootURL;
23
- const COMMS_BASE_URL = 'https://comms.todoist.com';
24
- /**
25
- * Builds a relative Comms URL based on the provided parameters
26
- * @param params - URL parameters including workspace, channel, conversation, thread, etc.
27
- * @returns A relative URL path
28
- * @example
29
- * getCommsURL({ workspaceId: 1, channelId: '7Yp...', threadId: '7Yq...' })
30
- * // returns '/1/ch/7Yp.../t/7Yq.../'
31
- */
32
- function getCommsURL(params) {
33
- const { workspaceId, channelId, conversationId, threadId, commentId, messageId, userId } = params;
34
- let url = `/${workspaceId}/`;
35
- if (channelId) {
36
- url += `ch/${channelId}/`;
37
- if (threadId) {
38
- url += `t/${threadId}/`;
39
- if (commentId) {
40
- url += `c/${commentId}`;
41
- }
42
- }
43
- }
44
- else if (threadId) {
45
- url += `inbox/t/${threadId}/`;
46
- if (commentId) {
47
- url += `c/${commentId}`;
48
- }
49
- }
50
- else if (conversationId) {
51
- url += `msg/${conversationId}/`;
52
- if (messageId) {
53
- url += `m/${messageId}`;
54
- }
55
- }
56
- else if (userId) {
57
- url += `people/u/${userId}`;
58
- }
59
- return url;
60
- }
61
- /**
62
- * Builds a full Comms URL (with protocol and hostname) based on the provided parameters
63
- * @param params - URL parameters including workspace, channel, conversation, thread, etc.
64
- * @param baseUrl - Optional base URL (defaults to 'https://comms.todoist.com')
65
- */
66
- function getFullCommsURL(params, baseUrl = COMMS_BASE_URL) {
67
- // Strip a trailing slash so links don't double up — `getCommsURL` paths start with '/'.
68
- const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
69
- return `${normalizedBase}${getCommsURL(params)}`;
70
- }
71
- /** Returns the URL for a thread in a channel. */
72
- function getThreadURL(params) {
73
- return getCommsURL(params);
74
- }
75
- /** Returns the URL for a channel. */
76
- function getChannelURL(params) {
77
- return getCommsURL(params);
78
- }
79
- /** Returns the URL for a conversation. */
80
- function getConversationURL(params) {
81
- return getCommsURL(params);
82
- }
83
- /** Returns the URL for a specific message in a conversation. */
84
- function getMessageURL(params) {
85
- return getCommsURL(params);
86
- }
87
- /** Returns the URL for a comment in a thread. */
88
- function getCommentURL(params) {
89
- return getCommsURL(params);
90
- }
91
- /** Returns the URL for the threads root (channels view). */
92
- function getThreadsRootURL(workspaceId) {
93
- return `/${workspaceId}/ch`;
94
- }
95
- /** Returns the URL for the inbox. */
96
- function getInboxURL(workspaceId, tab) {
97
- const tabParam = tab ? `/${tab}` : '';
98
- return `/${workspaceId}/inbox${tabParam}`;
99
- }
100
- /** Returns the URL for the messages/conversations root. */
101
- function getMessagesRootURL(workspaceId) {
102
- return `/${workspaceId}/msg`;
103
- }
104
- /** Returns the URL for a user profile. */
105
- function getUserProfileURL(params) {
106
- return `/${params.workspaceId}/people/u/${params.userId}`;
107
- }
108
- /** Returns the URL for the saved threads view. */
109
- function getSavedThreadsRootURL(workspaceId) {
110
- return `/${workspaceId}/saved`;
111
- }
112
- /** Returns the URL for a saved thread. */
113
- function getSavedThreadURL(params) {
114
- return `/${params.workspaceId}/saved/t/${params.threadId}`;
115
- }
116
- /** Returns the URL for the search root. */
117
- function getSearchRootURL(workspaceId) {
118
- return `/${workspaceId}/search`;
119
- }
120
- /** Returns the URL for a search with a query. */
121
- function getSearchQueryURL(params) {
122
- return `/${params.workspaceId}/search?q=${decodeURIComponent(params.query)}`;
123
- }
124
- /** Returns the URL for settings. */
125
- function getSettingsURL(params) {
126
- return params.initialLocation
127
- ? `/${params.workspaceId}/settings/${params.initialLocation}`
128
- : `/${params.workspaceId}/settings`;
129
- }
130
- /** Returns the URL for the team members root. */
131
- function getTeamMembersRootURL(workspaceId) {
132
- return `/${workspaceId}/people/u`;
133
- }