@doist/todoist-api-typescript 6.5.0 → 6.5.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.
@@ -1,12 +1,39 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
5
35
  Object.defineProperty(exports, "__esModule", { value: true });
6
36
  exports.uploadMultipartFile = uploadMultipartFile;
7
- const form_data_1 = __importDefault(require("form-data"));
8
- const fs_1 = require("fs");
9
- const path_1 = require("path");
10
37
  const fetch_with_retry_1 = require("./fetch-with-retry");
11
38
  /**
12
39
  * Helper function to determine content-type from filename extension.
@@ -37,11 +64,13 @@ function getContentTypeFromFileName(fileName) {
37
64
  * This is a shared utility for uploading files to Todoist endpoints that require
38
65
  * multipart/form-data content type (e.g., file uploads, workspace logo uploads).
39
66
  *
67
+ * Supports both browser (Blob/File) and Node.js (Buffer/ReadableStream/path) environments.
68
+ *
40
69
  * @param baseUrl - The base API URL (e.g., https://api.todoist.com/api/v1/)
41
70
  * @param authToken - The authentication token
42
71
  * @param endpoint - The relative endpoint path (e.g., 'uploads', 'workspaces/logo')
43
- * @param file - The file content (Buffer, ReadableStream, or file system path)
44
- * @param fileName - Optional file name (required for Buffer/Stream, optional for paths)
72
+ * @param file - The file content (Blob/File for browser, or Buffer/ReadableStream/path for Node)
73
+ * @param fileName - Optional file name (required for Buffer/Stream, optional for paths and File objects)
45
74
  * @param additionalFields - Additional form fields to include (e.g., project_id, workspace_id)
46
75
  * @param requestId - Optional request ID for idempotency
47
76
  * @returns The response data from the server
@@ -72,52 +101,74 @@ function getContentTypeFromFileName(fileName) {
72
101
  */
73
102
  async function uploadMultipartFile(args) {
74
103
  const { baseUrl, authToken, endpoint, file, fileName, additionalFields, requestId, customFetch, } = args;
75
- const form = new form_data_1.default();
76
- // Determine file type and add to form data
77
- if (typeof file === 'string') {
78
- // File path - create read stream
79
- const filePath = file;
80
- const resolvedFileName = fileName || (0, path_1.basename)(filePath);
81
- form.append('file', (0, fs_1.createReadStream)(filePath), resolvedFileName);
104
+ // Build the full URL
105
+ const url = `${baseUrl}${endpoint}`;
106
+ let body;
107
+ const headers = {
108
+ Authorization: `Bearer ${authToken}`,
109
+ };
110
+ if (requestId) {
111
+ headers['X-Request-Id'] = requestId;
82
112
  }
83
- else if (Buffer.isBuffer(file)) {
84
- // Buffer - require fileName
85
- if (!fileName) {
86
- throw new Error('fileName is required when uploading from a Buffer');
113
+ if (file instanceof Blob) {
114
+ // Browser path: use native FormData
115
+ const form = new globalThis.FormData();
116
+ const resolvedFileName = fileName || (file instanceof File ? file.name : undefined) || 'upload';
117
+ form.append('file', file, resolvedFileName);
118
+ for (const [key, value] of Object.entries(additionalFields)) {
119
+ if (value !== undefined && value !== null) {
120
+ form.append(key, value.toString());
121
+ }
87
122
  }
88
- // Detect content-type from filename extension
89
- const contentType = getContentTypeFromFileName(fileName);
90
- form.append('file', file, {
91
- filename: fileName,
92
- contentType: contentType,
93
- });
123
+ // Don't set Content-Type let fetch set it with the correct multipart boundary
124
+ body = form;
94
125
  }
95
126
  else {
96
- // Stream - require fileName
97
- if (!fileName) {
98
- throw new Error('fileName is required when uploading from a stream');
127
+ // Node path: dynamically import Node-only modules
128
+ const [FormDataModule, fsModule, pathModule] = await Promise.all([
129
+ Promise.resolve().then(() => __importStar(require('form-data'))),
130
+ Promise.resolve().then(() => __importStar(require('fs'))),
131
+ Promise.resolve().then(() => __importStar(require('path'))),
132
+ ]);
133
+ const FormData = FormDataModule.default;
134
+ const form = new FormData();
135
+ if (typeof file === 'string') {
136
+ // File path - create read stream
137
+ const resolvedFileName = fileName || pathModule.basename(file);
138
+ form.append('file', fsModule.createReadStream(file), resolvedFileName);
99
139
  }
100
- form.append('file', file, fileName);
101
- }
102
- // Add additional fields to the form
103
- for (const [key, value] of Object.entries(additionalFields)) {
104
- if (value !== undefined && value !== null) {
105
- form.append(key, value.toString());
140
+ else if (Buffer.isBuffer(file)) {
141
+ // Buffer - require fileName
142
+ if (!fileName) {
143
+ throw new Error('fileName is required when uploading from a Buffer');
144
+ }
145
+ const contentType = getContentTypeFromFileName(fileName);
146
+ form.append('file', file, {
147
+ filename: fileName,
148
+ contentType: contentType,
149
+ });
106
150
  }
107
- }
108
- // Build the full URL
109
- const url = `${baseUrl}${endpoint}`;
110
- // Prepare headers
111
- const headers = Object.assign({ Authorization: `Bearer ${authToken}` }, form.getHeaders());
112
- if (requestId) {
113
- headers['X-Request-Id'] = requestId;
151
+ else {
152
+ // Stream - require fileName
153
+ if (!fileName) {
154
+ throw new Error('fileName is required when uploading from a stream');
155
+ }
156
+ form.append('file', file, fileName);
157
+ }
158
+ for (const [key, value] of Object.entries(additionalFields)) {
159
+ if (value !== undefined && value !== null) {
160
+ form.append(key, value.toString());
161
+ }
162
+ }
163
+ Object.assign(headers, form.getHeaders());
164
+ body = form;
114
165
  }
115
166
  // Make the request using fetch
116
167
  const response = await (0, fetch_with_retry_1.fetchWithRetry)({
117
168
  url,
118
169
  options: {
119
170
  method: 'POST',
120
- body: form, // FormData from 'form-data' package is compatible with fetch
171
+ body,
121
172
  headers,
122
173
  timeout: 30000, // 30 second timeout for file uploads
123
174
  },
@@ -1,6 +1,3 @@
1
- import FormData from 'form-data';
2
- import { createReadStream } from 'fs';
3
- import { basename } from 'path';
4
1
  import { fetchWithRetry } from './fetch-with-retry.js';
5
2
  /**
6
3
  * Helper function to determine content-type from filename extension.
@@ -31,11 +28,13 @@ function getContentTypeFromFileName(fileName) {
31
28
  * This is a shared utility for uploading files to Todoist endpoints that require
32
29
  * multipart/form-data content type (e.g., file uploads, workspace logo uploads).
33
30
  *
31
+ * Supports both browser (Blob/File) and Node.js (Buffer/ReadableStream/path) environments.
32
+ *
34
33
  * @param baseUrl - The base API URL (e.g., https://api.todoist.com/api/v1/)
35
34
  * @param authToken - The authentication token
36
35
  * @param endpoint - The relative endpoint path (e.g., 'uploads', 'workspaces/logo')
37
- * @param file - The file content (Buffer, ReadableStream, or file system path)
38
- * @param fileName - Optional file name (required for Buffer/Stream, optional for paths)
36
+ * @param file - The file content (Blob/File for browser, or Buffer/ReadableStream/path for Node)
37
+ * @param fileName - Optional file name (required for Buffer/Stream, optional for paths and File objects)
39
38
  * @param additionalFields - Additional form fields to include (e.g., project_id, workspace_id)
40
39
  * @param requestId - Optional request ID for idempotency
41
40
  * @returns The response data from the server
@@ -66,52 +65,74 @@ function getContentTypeFromFileName(fileName) {
66
65
  */
67
66
  export async function uploadMultipartFile(args) {
68
67
  const { baseUrl, authToken, endpoint, file, fileName, additionalFields, requestId, customFetch, } = args;
69
- const form = new FormData();
70
- // Determine file type and add to form data
71
- if (typeof file === 'string') {
72
- // File path - create read stream
73
- const filePath = file;
74
- const resolvedFileName = fileName || basename(filePath);
75
- form.append('file', createReadStream(filePath), resolvedFileName);
68
+ // Build the full URL
69
+ const url = `${baseUrl}${endpoint}`;
70
+ let body;
71
+ const headers = {
72
+ Authorization: `Bearer ${authToken}`,
73
+ };
74
+ if (requestId) {
75
+ headers['X-Request-Id'] = requestId;
76
76
  }
77
- else if (Buffer.isBuffer(file)) {
78
- // Buffer - require fileName
79
- if (!fileName) {
80
- throw new Error('fileName is required when uploading from a Buffer');
77
+ if (file instanceof Blob) {
78
+ // Browser path: use native FormData
79
+ const form = new globalThis.FormData();
80
+ const resolvedFileName = fileName || (file instanceof File ? file.name : undefined) || 'upload';
81
+ form.append('file', file, resolvedFileName);
82
+ for (const [key, value] of Object.entries(additionalFields)) {
83
+ if (value !== undefined && value !== null) {
84
+ form.append(key, value.toString());
85
+ }
81
86
  }
82
- // Detect content-type from filename extension
83
- const contentType = getContentTypeFromFileName(fileName);
84
- form.append('file', file, {
85
- filename: fileName,
86
- contentType: contentType,
87
- });
87
+ // Don't set Content-Type let fetch set it with the correct multipart boundary
88
+ body = form;
88
89
  }
89
90
  else {
90
- // Stream - require fileName
91
- if (!fileName) {
92
- throw new Error('fileName is required when uploading from a stream');
91
+ // Node path: dynamically import Node-only modules
92
+ const [FormDataModule, fsModule, pathModule] = await Promise.all([
93
+ import('form-data'),
94
+ import('fs'),
95
+ import('path'),
96
+ ]);
97
+ const FormData = FormDataModule.default;
98
+ const form = new FormData();
99
+ if (typeof file === 'string') {
100
+ // File path - create read stream
101
+ const resolvedFileName = fileName || pathModule.basename(file);
102
+ form.append('file', fsModule.createReadStream(file), resolvedFileName);
93
103
  }
94
- form.append('file', file, fileName);
95
- }
96
- // Add additional fields to the form
97
- for (const [key, value] of Object.entries(additionalFields)) {
98
- if (value !== undefined && value !== null) {
99
- form.append(key, value.toString());
104
+ else if (Buffer.isBuffer(file)) {
105
+ // Buffer - require fileName
106
+ if (!fileName) {
107
+ throw new Error('fileName is required when uploading from a Buffer');
108
+ }
109
+ const contentType = getContentTypeFromFileName(fileName);
110
+ form.append('file', file, {
111
+ filename: fileName,
112
+ contentType: contentType,
113
+ });
100
114
  }
101
- }
102
- // Build the full URL
103
- const url = `${baseUrl}${endpoint}`;
104
- // Prepare headers
105
- const headers = Object.assign({ Authorization: `Bearer ${authToken}` }, form.getHeaders());
106
- if (requestId) {
107
- headers['X-Request-Id'] = requestId;
115
+ else {
116
+ // Stream - require fileName
117
+ if (!fileName) {
118
+ throw new Error('fileName is required when uploading from a stream');
119
+ }
120
+ form.append('file', file, fileName);
121
+ }
122
+ for (const [key, value] of Object.entries(additionalFields)) {
123
+ if (value !== undefined && value !== null) {
124
+ form.append(key, value.toString());
125
+ }
126
+ }
127
+ Object.assign(headers, form.getHeaders());
128
+ body = form;
108
129
  }
109
130
  // Make the request using fetch
110
131
  const response = await fetchWithRetry({
111
132
  url,
112
133
  options: {
113
134
  method: 'POST',
114
- body: form, // FormData from 'form-data' package is compatible with fetch
135
+ body,
115
136
  headers,
116
137
  timeout: 30000, // 30 second timeout for file uploads
117
138
  },
@@ -511,10 +511,10 @@ export type UploadFileArgs = {
511
511
  * - ReadableStream: File content as a stream (requires fileName)
512
512
  * - string: Path to a file on the filesystem (fileName is optional, will be inferred from path)
513
513
  */
514
- file: Buffer | NodeJS.ReadableStream | string;
514
+ file: Buffer | NodeJS.ReadableStream | string | Blob;
515
515
  /**
516
516
  * The name of the file. Required for Buffer and Stream inputs.
517
- * Optional for file path strings (will be inferred from the path if not provided).
517
+ * Optional for file path strings and File objects (will be inferred if not provided).
518
518
  */
519
519
  fileName?: string;
520
520
  /**
@@ -609,7 +609,7 @@ export type WorkspaceLogoArgs = {
609
609
  /**
610
610
  * The image file to upload (Buffer, Stream, or file path).
611
611
  */
612
- file?: Buffer | NodeJS.ReadableStream | string;
612
+ file?: Buffer | NodeJS.ReadableStream | string | Blob;
613
613
  /**
614
614
  * The file name (required for Buffer/Stream uploads).
615
615
  */
@@ -3,7 +3,7 @@ type UploadMultipartFileArgs = {
3
3
  baseUrl: string;
4
4
  authToken: string;
5
5
  endpoint: string;
6
- file: Buffer | NodeJS.ReadableStream | string;
6
+ file: Buffer | NodeJS.ReadableStream | string | Blob;
7
7
  fileName?: string;
8
8
  additionalFields: Record<string, string | number | boolean>;
9
9
  requestId?: string;
@@ -15,11 +15,13 @@ type UploadMultipartFileArgs = {
15
15
  * This is a shared utility for uploading files to Todoist endpoints that require
16
16
  * multipart/form-data content type (e.g., file uploads, workspace logo uploads).
17
17
  *
18
+ * Supports both browser (Blob/File) and Node.js (Buffer/ReadableStream/path) environments.
19
+ *
18
20
  * @param baseUrl - The base API URL (e.g., https://api.todoist.com/api/v1/)
19
21
  * @param authToken - The authentication token
20
22
  * @param endpoint - The relative endpoint path (e.g., 'uploads', 'workspaces/logo')
21
- * @param file - The file content (Buffer, ReadableStream, or file system path)
22
- * @param fileName - Optional file name (required for Buffer/Stream, optional for paths)
23
+ * @param file - The file content (Blob/File for browser, or Buffer/ReadableStream/path for Node)
24
+ * @param fileName - Optional file name (required for Buffer/Stream, optional for paths and File objects)
23
25
  * @param additionalFields - Additional form fields to include (e.g., project_id, workspace_id)
24
26
  * @param requestId - Optional request ID for idempotency
25
27
  * @returns The response data from the server
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/todoist-api-typescript",
3
- "version": "6.5.0",
3
+ "version": "6.5.1",
4
4
  "description": "A typescript wrapper for the Todoist REST API.",
5
5
  "author": "Doist developers",
6
6
  "repository": "https://github.com/Doist/todoist-api-typescript",
@@ -44,11 +44,11 @@
44
44
  "dependencies": {
45
45
  "camelcase": "6.3.0",
46
46
  "emoji-regex": "10.6.0",
47
- "form-data": "4.0.4",
47
+ "form-data": "4.0.5",
48
48
  "ts-custom-error": "^3.2.0",
49
49
  "undici": "^7.16.0",
50
50
  "uuid": "11.1.0",
51
- "zod": "4.1.12"
51
+ "zod": "4.3.6"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@doist/eslint-config": "12.0.0",
@@ -63,15 +63,15 @@
63
63
  "eslint-plugin-prettier": "5.1.3",
64
64
  "husky": "9.1.7",
65
65
  "jest": "30.1.3",
66
- "lint-staged": "16.1.6",
67
- "msw": "2.11.6",
66
+ "lint-staged": "16.2.7",
67
+ "msw": "2.12.10",
68
68
  "npm-run-all2": "8.0.4",
69
69
  "obsidian": "^1.10.2-1",
70
70
  "prettier": "3.3.2",
71
- "rimraf": "6.0.1",
72
- "ts-jest": "29.4.5",
71
+ "rimraf": "6.1.2",
72
+ "ts-jest": "29.4.6",
73
73
  "ts-node": "10.9.2",
74
- "type-fest": "^4.12.0",
74
+ "type-fest": "^5.0.0",
75
75
  "typescript": "5.9.3"
76
76
  },
77
77
  "peerDependencies": {