@doist/todoist-api-typescript 5.9.0 → 6.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 (85) hide show
  1. package/dist/cjs/authentication.js +158 -0
  2. package/dist/{consts → cjs/consts}/endpoints.js +10 -12
  3. package/dist/cjs/package.json +1 -0
  4. package/dist/cjs/rest-client.js +124 -0
  5. package/dist/{test-utils → cjs/test-utils}/asserts.js +1 -1
  6. package/dist/{test-utils → cjs/test-utils}/mocks.js +8 -4
  7. package/dist/cjs/test-utils/msw-setup.js +27 -0
  8. package/dist/{test-utils → cjs/test-utils}/test-defaults.js +41 -52
  9. package/dist/cjs/todoist-api.js +1235 -0
  10. package/dist/{types → cjs/types}/entities.js +19 -30
  11. package/dist/cjs/types/errors.js +22 -0
  12. package/dist/cjs/types/http.js +22 -0
  13. package/dist/cjs/utils/case-conversion.js +69 -0
  14. package/dist/{utils → cjs/utils}/colors.js +3 -3
  15. package/dist/cjs/utils/fetch-with-retry.js +150 -0
  16. package/dist/cjs/utils/multipart-upload.js +126 -0
  17. package/dist/{utils → cjs/utils}/processing-helpers.js +3 -3
  18. package/dist/{utils → cjs/utils}/sanitization.js +17 -28
  19. package/dist/{utils → cjs/utils}/url-helpers.js +15 -15
  20. package/dist/{utils → cjs/utils}/validators.js +2 -2
  21. package/dist/esm/authentication.js +151 -0
  22. package/dist/esm/consts/endpoints.js +65 -0
  23. package/dist/esm/index.js +4 -0
  24. package/dist/esm/rest-client.js +119 -0
  25. package/dist/esm/test-utils/asserts.js +8 -0
  26. package/dist/esm/test-utils/mocks.js +10 -0
  27. package/dist/esm/test-utils/msw-setup.js +22 -0
  28. package/dist/esm/test-utils/test-defaults.js +198 -0
  29. package/dist/esm/todoist-api.js +1231 -0
  30. package/dist/esm/types/entities.js +366 -0
  31. package/dist/esm/types/errors.js +18 -0
  32. package/dist/esm/types/http.js +18 -0
  33. package/dist/esm/types/index.js +3 -0
  34. package/dist/esm/types/requests.js +1 -0
  35. package/dist/esm/types/sync.js +1 -0
  36. package/dist/esm/utils/activity-helpers.js +36 -0
  37. package/dist/esm/utils/case-conversion.js +61 -0
  38. package/dist/esm/utils/colors.js +215 -0
  39. package/dist/esm/utils/fetch-with-retry.js +147 -0
  40. package/dist/esm/utils/index.js +3 -0
  41. package/dist/esm/utils/multipart-upload.js +120 -0
  42. package/dist/esm/utils/processing-helpers.js +12 -0
  43. package/dist/esm/utils/sanitization.js +112 -0
  44. package/dist/esm/utils/url-helpers.js +68 -0
  45. package/dist/esm/utils/validators.js +97 -0
  46. package/dist/{authentication.d.ts → types/authentication.d.ts} +6 -1
  47. package/dist/types/index.d.ts +4 -3
  48. package/dist/{rest-client.d.ts → types/rest-client.d.ts} +3 -4
  49. package/dist/types/test-utils/msw-setup.d.ts +3 -0
  50. package/dist/types/types/http.d.ts +68 -0
  51. package/dist/types/types/index.d.ts +3 -0
  52. package/dist/types/utils/case-conversion.d.ts +12 -0
  53. package/dist/types/utils/fetch-with-retry.d.ts +11 -0
  54. package/package.json +24 -8
  55. package/dist/authentication.js +0 -220
  56. package/dist/index.d.ts +0 -4
  57. package/dist/rest-client.js +0 -178
  58. package/dist/todoist-api.js +0 -1845
  59. package/dist/types/errors.js +0 -39
  60. package/dist/types/http.d.ts +0 -1
  61. package/dist/types/http.js +0 -2
  62. package/dist/utils/multipart-upload.js +0 -171
  63. /package/dist/{index.js → cjs/index.js} +0 -0
  64. /package/dist/{types → cjs/types}/index.js +0 -0
  65. /package/dist/{types → cjs/types}/requests.js +0 -0
  66. /package/dist/{types → cjs/types}/sync.js +0 -0
  67. /package/dist/{utils → cjs/utils}/activity-helpers.js +0 -0
  68. /package/dist/{utils → cjs/utils}/index.js +0 -0
  69. /package/dist/{consts → types/consts}/endpoints.d.ts +0 -0
  70. /package/dist/{test-utils → types/test-utils}/asserts.d.ts +0 -0
  71. /package/dist/{test-utils → types/test-utils}/mocks.d.ts +0 -0
  72. /package/dist/{test-utils → types/test-utils}/test-defaults.d.ts +0 -0
  73. /package/dist/{todoist-api.d.ts → types/todoist-api.d.ts} +0 -0
  74. /package/dist/types/{entities.d.ts → types/entities.d.ts} +0 -0
  75. /package/dist/types/{errors.d.ts → types/errors.d.ts} +0 -0
  76. /package/dist/types/{requests.d.ts → types/requests.d.ts} +0 -0
  77. /package/dist/types/{sync.d.ts → types/sync.d.ts} +0 -0
  78. /package/dist/{utils → types/utils}/activity-helpers.d.ts +0 -0
  79. /package/dist/{utils → types/utils}/colors.d.ts +0 -0
  80. /package/dist/{utils → types/utils}/index.d.ts +0 -0
  81. /package/dist/{utils → types/utils}/multipart-upload.d.ts +0 -0
  82. /package/dist/{utils → types/utils}/processing-helpers.d.ts +0 -0
  83. /package/dist/{utils → types/utils}/sanitization.d.ts +0 -0
  84. /package/dist/{utils → types/utils}/url-helpers.d.ts +0 -0
  85. /package/dist/{utils → types/utils}/validators.d.ts +0 -0
@@ -1,15 +1,4 @@
1
1
  "use strict";
2
- var __assign = (this && this.__assign) || function () {
3
- __assign = Object.assign || function(t) {
4
- for (var s, i = 1, n = arguments.length; i < n; i++) {
5
- s = arguments[i];
6
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
- t[p] = s[p];
8
- }
9
- return t;
10
- };
11
- return __assign.apply(this, arguments);
12
- };
13
2
  var __rest = (this && this.__rest) || function (s, e) {
14
3
  var t = {};
15
4
  for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
@@ -23,8 +12,8 @@ var __rest = (this && this.__rest) || function (s, e) {
23
12
  };
24
13
  Object.defineProperty(exports, "__esModule", { value: true });
25
14
  exports.JoinWorkspaceResultSchema = exports.WorkspacePlanDetailsSchema = exports.FormattedPriceListingSchema = exports.PlanPriceSchema = exports.WorkspaceInvitationSchema = exports.WorkspaceUserSchema = exports.WorkspaceRoleSchema = exports.WORKSPACE_ROLES = exports.ActivityEventSchema = exports.ActivityEventExtraDataSchema = exports.ColorSchema = exports.ProductivityStatsSchema = exports.CurrentUserSchema = exports.TimezoneInfoSchema = exports.UserSchema = exports.CommentSchema = exports.RawCommentSchema = exports.AttachmentSchema = exports.LabelSchema = exports.SectionSchema = exports.WorkspaceProjectSchema = exports.PersonalProjectSchema = exports.BaseProjectSchema = exports.TaskSchema = exports.DeadlineSchema = exports.DurationSchema = exports.DueDateSchema = void 0;
26
- var zod_1 = require("zod");
27
- var url_helpers_1 = require("../utils/url-helpers");
15
+ const zod_1 = require("zod");
16
+ const url_helpers_1 = require("../utils/url-helpers");
28
17
  exports.DueDateSchema = zod_1.z
29
18
  .object({
30
19
  isRecurring: zod_1.z.boolean(),
@@ -71,8 +60,8 @@ exports.TaskSchema = zod_1.z
71
60
  dayOrder: zod_1.z.number().int(),
72
61
  isCollapsed: zod_1.z.boolean(),
73
62
  })
74
- .transform(function (data) {
75
- return __assign(__assign({}, data), { url: (0, url_helpers_1.getTaskUrl)(data.id, data.content) });
63
+ .transform((data) => {
64
+ return Object.assign(Object.assign({}, data), { url: (0, url_helpers_1.getTaskUrl)(data.id, data.content) });
76
65
  });
77
66
  /**
78
67
  * Base schema for all project types in Todoist.
@@ -102,8 +91,8 @@ exports.BaseProjectSchema = zod_1.z.object({
102
91
  exports.PersonalProjectSchema = exports.BaseProjectSchema.extend({
103
92
  parentId: zod_1.z.string().nullable(),
104
93
  inboxProject: zod_1.z.boolean().optional().default(false),
105
- }).transform(function (data) {
106
- return __assign(__assign({}, data), { url: (0, url_helpers_1.getProjectUrl)(data.id, data.name) });
94
+ }).transform((data) => {
95
+ return Object.assign(Object.assign({}, data), { url: (0, url_helpers_1.getProjectUrl)(data.id, data.name) });
107
96
  });
108
97
  /**
109
98
  * Schema for workspace projects in Todoist.
@@ -116,8 +105,8 @@ exports.WorkspaceProjectSchema = exports.BaseProjectSchema.extend({
116
105
  role: zod_1.z.string().nullable(),
117
106
  status: zod_1.z.string(),
118
107
  workspaceId: zod_1.z.string(),
119
- }).transform(function (data) {
120
- return __assign(__assign({}, data), { url: (0, url_helpers_1.getProjectUrl)(data.id, data.name) });
108
+ }).transform((data) => {
109
+ return Object.assign(Object.assign({}, data), { url: (0, url_helpers_1.getProjectUrl)(data.id, data.name) });
121
110
  });
122
111
  exports.SectionSchema = zod_1.z
123
112
  .object({
@@ -133,8 +122,8 @@ exports.SectionSchema = zod_1.z
133
122
  isDeleted: zod_1.z.boolean(),
134
123
  isCollapsed: zod_1.z.boolean(),
135
124
  })
136
- .transform(function (data) {
137
- return __assign(__assign({}, data), { url: (0, url_helpers_1.getSectionUrl)(data.id, data.name) });
125
+ .transform((data) => {
126
+ return Object.assign(Object.assign({}, data), { url: (0, url_helpers_1.getSectionUrl)(data.id, data.name) });
138
127
  });
139
128
  exports.LabelSchema = zod_1.z.object({
140
129
  id: zod_1.z.string(),
@@ -173,16 +162,16 @@ exports.RawCommentSchema = zod_1.z
173
162
  reactions: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string())).nullable(),
174
163
  isDeleted: zod_1.z.boolean(),
175
164
  })
176
- .refine(function (data) {
165
+ .refine((data) => {
177
166
  // At least one of itemId or projectId must be present
178
167
  return ((data.itemId !== undefined && data.projectId === undefined) ||
179
168
  (data.itemId === undefined && data.projectId !== undefined));
180
169
  }, {
181
170
  message: 'Exactly one of itemId or projectId must be provided',
182
171
  });
183
- exports.CommentSchema = exports.RawCommentSchema.transform(function (data) {
184
- var itemId = data.itemId, rest = __rest(data, ["itemId"]);
185
- return __assign(__assign({}, rest), {
172
+ exports.CommentSchema = exports.RawCommentSchema.transform((data) => {
173
+ const { itemId } = data, rest = __rest(data, ["itemId"]);
174
+ return Object.assign(Object.assign({}, rest), {
186
175
  // Map itemId to taskId for backwards compatibility
187
176
  taskId: itemId });
188
177
  });
@@ -225,20 +214,20 @@ exports.CurrentUserSchema = zod_1.z.object({
225
214
  daysOff: zod_1.z.array(zod_1.z.number().int()),
226
215
  weekendStartDay: zod_1.z.number().int(),
227
216
  });
228
- var StreakSchema = zod_1.z.object({
217
+ const StreakSchema = zod_1.z.object({
229
218
  count: zod_1.z.number(),
230
219
  start: zod_1.z.string(),
231
220
  end: zod_1.z.string(),
232
221
  });
233
- var CompletedItemSchema = zod_1.z.object({
222
+ const CompletedItemSchema = zod_1.z.object({
234
223
  id: zod_1.z.string(),
235
224
  completed: zod_1.z.number(),
236
225
  });
237
- var ItemsWithDateSchema = zod_1.z.object({
226
+ const ItemsWithDateSchema = zod_1.z.object({
238
227
  items: zod_1.z.array(CompletedItemSchema),
239
228
  totalCompleted: zod_1.z.number(),
240
229
  });
241
- var KarmaUpdateSchema = zod_1.z.object({
230
+ const KarmaUpdateSchema = zod_1.z.object({
242
231
  time: zod_1.z.string(),
243
232
  newKarma: zod_1.z.number(),
244
233
  positiveKarma: zod_1.z.number(),
@@ -312,7 +301,7 @@ exports.ActivityEventSchema = zod_1.z
312
301
  eventDate: zod_1.z.string(),
313
302
  id: zod_1.z
314
303
  .union([zod_1.z.string(), zod_1.z.number()])
315
- .transform(function (val) { var _a; return (_a = val === null || val === void 0 ? void 0 : val.toString()) !== null && _a !== void 0 ? _a : null; })
304
+ .transform((val) => { var _a; return (_a = val === null || val === void 0 ? void 0 : val.toString()) !== null && _a !== void 0 ? _a : null; })
316
305
  .nullable(),
317
306
  parentProjectId: zod_1.z.string().nullable(),
318
307
  parentItemId: zod_1.z.string().nullable(),
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TodoistRequestError = void 0;
4
+ const ts_custom_error_1 = require("ts-custom-error");
5
+ const authenticationErrorCodes = [401, 403];
6
+ class TodoistRequestError extends ts_custom_error_1.CustomError {
7
+ // eslint-disable-next-line max-params
8
+ constructor(message, httpStatusCode, responseData) {
9
+ super(message);
10
+ this.message = message;
11
+ this.httpStatusCode = httpStatusCode;
12
+ this.responseData = responseData;
13
+ this.isAuthenticationError = () => {
14
+ if (!this.httpStatusCode) {
15
+ return false;
16
+ }
17
+ return authenticationErrorCodes.includes(this.httpStatusCode);
18
+ };
19
+ Object.defineProperty(this, 'name', { value: 'TodoistRequestError' });
20
+ }
21
+ }
22
+ exports.TodoistRequestError = TodoistRequestError;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isNetworkError = isNetworkError;
4
+ exports.isHttpError = isHttpError;
5
+ /**
6
+ * Type guard to check if an error is a network error
7
+ */
8
+ function isNetworkError(error) {
9
+ // Network errors in fetch are typically TypeError with specific messages
10
+ return ((error instanceof TypeError &&
11
+ (error.message.includes('fetch') ||
12
+ error.message.includes('network') ||
13
+ error.message.includes('Failed to fetch') ||
14
+ error.message.includes('NetworkError'))) ||
15
+ error.isNetworkError === true);
16
+ }
17
+ /**
18
+ * Type guard to check if an error is an HTTP error
19
+ */
20
+ function isHttpError(error) {
21
+ return 'status' in error && typeof error.status === 'number';
22
+ }
@@ -0,0 +1,69 @@
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.customCamelCase = customCamelCase;
7
+ exports.camelCaseKeys = camelCaseKeys;
8
+ exports.snakeCaseKeys = snakeCaseKeys;
9
+ const camelcase_1 = __importDefault(require("camelcase"));
10
+ const emoji_regex_1 = __importDefault(require("emoji-regex"));
11
+ /**
12
+ * Checks if a string is a solitary emoji
13
+ */
14
+ function isEmojiKey(input) {
15
+ const regex = (0, emoji_regex_1.default)();
16
+ const match = input.match(regex);
17
+ return match !== null && match.join('') === input;
18
+ }
19
+ /**
20
+ * Custom camelCase function that preserves emoji strings
21
+ */
22
+ function customCamelCase(input) {
23
+ // If the value is a solitary emoji string, return the key as-is
24
+ if (isEmojiKey(input)) {
25
+ return input;
26
+ }
27
+ return (0, camelcase_1.default)(input);
28
+ }
29
+ /**
30
+ * Converts object keys from snake_case to camelCase recursively
31
+ */
32
+ function camelCaseKeys(obj) {
33
+ if (obj === null || obj === undefined) {
34
+ return obj;
35
+ }
36
+ if (Array.isArray(obj)) {
37
+ return obj.map((item) => camelCaseKeys(item));
38
+ }
39
+ if (typeof obj === 'object' && obj.constructor === Object) {
40
+ const converted = {};
41
+ for (const [key, value] of Object.entries(obj)) {
42
+ const camelKey = customCamelCase(key);
43
+ converted[camelKey] = camelCaseKeys(value);
44
+ }
45
+ return converted;
46
+ }
47
+ return obj;
48
+ }
49
+ /**
50
+ * Converts object keys from camelCase to snake_case recursively
51
+ */
52
+ function snakeCaseKeys(obj) {
53
+ if (obj === null || obj === undefined) {
54
+ return obj;
55
+ }
56
+ if (Array.isArray(obj)) {
57
+ return obj.map((item) => snakeCaseKeys(item));
58
+ }
59
+ if (typeof obj === 'object' && obj.constructor === Object) {
60
+ const converted = {};
61
+ for (const [key, value] of Object.entries(obj)) {
62
+ // Convert camelCase to snake_case
63
+ const snakeKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
64
+ converted[snakeKey] = snakeCaseKeys(value);
65
+ }
66
+ return converted;
67
+ }
68
+ return obj;
69
+ }
@@ -192,7 +192,7 @@ exports.defaultColor = exports.charcoal;
192
192
  * @deprecated Use {@link getColorByKey} instead
193
193
  */
194
194
  function getColorById(colorId) {
195
- var color = exports.colors.find(function (color) { return color.id === colorId; });
195
+ const color = exports.colors.find((color) => color.id === colorId);
196
196
  return color !== null && color !== void 0 ? color : exports.defaultColor;
197
197
  }
198
198
  /**
@@ -200,7 +200,7 @@ function getColorById(colorId) {
200
200
  * @deprecated Use {@link getColorByKey} instead
201
201
  */
202
202
  function getColorByName(colorName) {
203
- var color = exports.colors.find(function (color) { return color.name === colorName; });
203
+ const color = exports.colors.find((color) => color.name === colorName);
204
204
  return color !== null && color !== void 0 ? color : exports.defaultColor;
205
205
  }
206
206
  /**
@@ -216,6 +216,6 @@ function getColorByName(colorName) {
216
216
  * ```
217
217
  */
218
218
  function getColorByKey(colorKey) {
219
- var color = exports.colors.find(function (color) { return color.key === colorKey; });
219
+ const color = exports.colors.find((color) => color.key === colorKey);
220
220
  return color !== null && color !== void 0 ? color : exports.defaultColor;
221
221
  }
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.fetchWithRetry = fetchWithRetry;
15
+ const http_1 = require("../types/http");
16
+ /**
17
+ * Default retry configuration matching the original axios-retry behavior
18
+ */
19
+ const DEFAULT_RETRY_CONFIG = {
20
+ retries: 3,
21
+ retryCondition: http_1.isNetworkError,
22
+ retryDelay: (retryNumber) => {
23
+ // First retry: immediate (0ms delay)
24
+ // Subsequent retries: 500ms delay
25
+ return retryNumber === 1 ? 0 : 500;
26
+ },
27
+ };
28
+ /**
29
+ * Converts Headers object to a plain object
30
+ */
31
+ function headersToObject(headers) {
32
+ const result = {};
33
+ headers.forEach((value, key) => {
34
+ result[key] = value;
35
+ });
36
+ return result;
37
+ }
38
+ /**
39
+ * Creates an AbortSignal that times out after the specified duration
40
+ */
41
+ function createTimeoutSignal(timeoutMs, existingSignal) {
42
+ const controller = new AbortController();
43
+ // Timeout logic
44
+ const timeoutId = setTimeout(() => {
45
+ controller.abort(new Error(`Request timeout after ${timeoutMs}ms`));
46
+ }, timeoutMs);
47
+ // If there's an existing signal, forward its abort
48
+ if (existingSignal) {
49
+ if (existingSignal.aborted) {
50
+ clearTimeout(timeoutId);
51
+ controller.abort(existingSignal.reason);
52
+ }
53
+ else {
54
+ existingSignal.addEventListener('abort', () => {
55
+ clearTimeout(timeoutId);
56
+ controller.abort(existingSignal.reason);
57
+ }, { once: true });
58
+ }
59
+ }
60
+ // Clean up timeout when request completes
61
+ controller.signal.addEventListener('abort', () => {
62
+ clearTimeout(timeoutId);
63
+ });
64
+ return controller.signal;
65
+ }
66
+ /**
67
+ * Performs a fetch request with retry logic and timeout support
68
+ */
69
+ async function fetchWithRetry(args) {
70
+ const { url, options = {}, retryConfig = {} } = args;
71
+ const config = Object.assign(Object.assign({}, DEFAULT_RETRY_CONFIG), retryConfig);
72
+ const { timeout, signal: userSignal } = options, fetchOptions = __rest(options, ["timeout", "signal"]);
73
+ let lastError;
74
+ for (let attempt = 0; attempt <= config.retries; attempt++) {
75
+ try {
76
+ // Set up timeout and signal handling
77
+ let requestSignal = userSignal || undefined;
78
+ if (timeout && timeout > 0) {
79
+ requestSignal = createTimeoutSignal(timeout, requestSignal);
80
+ }
81
+ const response = await fetch(url, Object.assign(Object.assign({}, fetchOptions), { signal: requestSignal }));
82
+ // Check if the response is successful
83
+ if (!response.ok) {
84
+ const errorMessage = `HTTP ${response.status}: ${response.statusText}`;
85
+ const error = new Error(errorMessage);
86
+ error.status = response.status;
87
+ error.statusText = response.statusText;
88
+ error.response = {
89
+ data: undefined, // Will be set below if we can parse the response
90
+ status: response.status,
91
+ statusText: response.statusText,
92
+ headers: headersToObject(response.headers),
93
+ };
94
+ // Try to get response body for error details
95
+ try {
96
+ const responseText = await response.text();
97
+ let responseData;
98
+ try {
99
+ responseData = responseText ? JSON.parse(responseText) : undefined;
100
+ }
101
+ catch (_a) {
102
+ responseData = responseText;
103
+ }
104
+ error.data = responseData;
105
+ error.response.data = responseData;
106
+ }
107
+ catch (_b) {
108
+ // If we can't read the response body, that's OK
109
+ }
110
+ throw error;
111
+ }
112
+ // Parse response
113
+ const responseText = await response.text();
114
+ let data;
115
+ try {
116
+ data = responseText ? JSON.parse(responseText) : undefined;
117
+ }
118
+ catch (_c) {
119
+ // If JSON parsing fails, return the raw text
120
+ data = responseText;
121
+ }
122
+ return {
123
+ data,
124
+ status: response.status,
125
+ statusText: response.statusText,
126
+ headers: headersToObject(response.headers),
127
+ };
128
+ }
129
+ catch (error) {
130
+ lastError = error;
131
+ // Check if this error should trigger a retry
132
+ const shouldRetry = attempt < config.retries && config.retryCondition(lastError);
133
+ if (!shouldRetry) {
134
+ // Add network error flag for network errors
135
+ if ((0, http_1.isNetworkError)(lastError)) {
136
+ const networkError = lastError;
137
+ networkError.isNetworkError = true;
138
+ }
139
+ throw lastError;
140
+ }
141
+ // Wait before retrying
142
+ const delay = config.retryDelay(attempt + 1);
143
+ if (delay > 0) {
144
+ await new Promise((resolve) => setTimeout(resolve, delay));
145
+ }
146
+ }
147
+ }
148
+ // This should never be reached, but just in case
149
+ throw lastError || new Error('Request failed after retries');
150
+ }
@@ -0,0 +1,126 @@
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.uploadMultipartFile = uploadMultipartFile;
7
+ const form_data_1 = __importDefault(require("form-data"));
8
+ const fs_1 = require("fs");
9
+ const path_1 = require("path");
10
+ const fetch_with_retry_1 = require("./fetch-with-retry");
11
+ /**
12
+ * Helper function to determine content-type from filename extension.
13
+ * @param fileName - The filename to analyze
14
+ * @returns The appropriate MIME type
15
+ */
16
+ function getContentTypeFromFileName(fileName) {
17
+ const extension = fileName.toLowerCase().split('.').pop();
18
+ switch (extension) {
19
+ case 'png':
20
+ return 'image/png';
21
+ case 'jpg':
22
+ case 'jpeg':
23
+ return 'image/jpeg';
24
+ case 'gif':
25
+ return 'image/gif';
26
+ case 'webp':
27
+ return 'image/webp';
28
+ case 'svg':
29
+ return 'image/svg+xml';
30
+ default:
31
+ return 'application/octet-stream';
32
+ }
33
+ }
34
+ /**
35
+ * Uploads a file using multipart/form-data.
36
+ *
37
+ * This is a shared utility for uploading files to Todoist endpoints that require
38
+ * multipart/form-data content type (e.g., file uploads, workspace logo uploads).
39
+ *
40
+ * @param baseUrl - The base API URL (e.g., https://api.todoist.com/api/v1/)
41
+ * @param authToken - The authentication token
42
+ * @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)
45
+ * @param additionalFields - Additional form fields to include (e.g., project_id, workspace_id)
46
+ * @param requestId - Optional request ID for idempotency
47
+ * @returns The response data from the server
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * // Upload from a file path
52
+ * const result = await uploadMultipartFile(
53
+ * 'https://api.todoist.com/api/v1/',
54
+ * 'my-token',
55
+ * 'uploads',
56
+ * '/path/to/file.pdf',
57
+ * undefined,
58
+ * { project_id: '12345' }
59
+ * )
60
+ *
61
+ * // Upload from a Buffer
62
+ * const buffer = Buffer.from('file content')
63
+ * const result = await uploadMultipartFile(
64
+ * 'https://api.todoist.com/api/v1/',
65
+ * 'my-token',
66
+ * 'uploads',
67
+ * buffer,
68
+ * 'document.pdf',
69
+ * { project_id: '12345' }
70
+ * )
71
+ * ```
72
+ */
73
+ async function uploadMultipartFile(args) {
74
+ const { baseUrl, authToken, endpoint, file, fileName, additionalFields, requestId } = 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);
82
+ }
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');
87
+ }
88
+ // Detect content-type from filename extension
89
+ const contentType = getContentTypeFromFileName(fileName);
90
+ form.append('file', file, {
91
+ filename: fileName,
92
+ contentType: contentType,
93
+ });
94
+ }
95
+ else {
96
+ // Stream - require fileName
97
+ if (!fileName) {
98
+ throw new Error('fileName is required when uploading from a stream');
99
+ }
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());
106
+ }
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;
114
+ }
115
+ // Make the request using fetch
116
+ const response = await (0, fetch_with_retry_1.fetchWithRetry)({
117
+ url,
118
+ options: {
119
+ method: 'POST',
120
+ body: form, // FormData from 'form-data' package is compatible with fetch
121
+ headers,
122
+ timeout: 30000, // 30 second timeout for file uploads
123
+ },
124
+ });
125
+ return response.data;
126
+ }
@@ -4,10 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.customCamelCase = customCamelCase;
7
- var camelcase_1 = __importDefault(require("camelcase"));
8
- var emoji_regex_1 = __importDefault(require("emoji-regex"));
7
+ const camelcase_1 = __importDefault(require("camelcase"));
8
+ const emoji_regex_1 = __importDefault(require("emoji-regex"));
9
9
  function isEmojiKey(key) {
10
- var regex = (0, emoji_regex_1.default)();
10
+ const regex = (0, emoji_regex_1.default)();
11
11
  return regex.test(key);
12
12
  }
13
13
  function customCamelCase(input) {
@@ -1,36 +1,25 @@
1
1
  "use strict";
2
- var __assign = (this && this.__assign) || function () {
3
- __assign = Object.assign || function(t) {
4
- for (var s, i = 1, n = arguments.length; i < n; i++) {
5
- s = arguments[i];
6
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
- t[p] = s[p];
8
- }
9
- return t;
10
- };
11
- return __assign.apply(this, arguments);
12
- };
13
2
  Object.defineProperty(exports, "__esModule", { value: true });
14
3
  exports.getSanitizedContent = getSanitizedContent;
15
4
  exports.getSanitizedTasks = getSanitizedTasks;
16
- var BOLD_FORMAT = /(^|[\s!?,;>:]+)(?:\*\*|__|!!)(.+?)(\*\*|__|!!)(?=$|[\s!?,;><:]+)/gi;
17
- var ITALIC_FORMAT = /(^|[\s!?,;>:]+)(?:\*|_|!)(.+?)(\*|_|!)(?=$|[\s!?,;><:]+)/gi;
18
- var BOLD_ITALIC_FORMAT = /(^|[\s!?,;>:]+)(?:\*\*\*|___|!!!)(.+?)(\*\*\*|___|!!!)(?=$|[\s!?,;><:]+)/gi;
19
- var CODE_BLOCK_FORMAT = /```([\s\S]*?)```/gi;
20
- var CODE_INLINE_FORMAT = /`([^`]+)`/gi;
21
- var TODOIST_LINK = /((?:(?:onenote:)?[\w-]+):\/\/[^\s]+)\s+[[(]([^)]+)[\])]/gi;
22
- var MARKDOWN_LINK = /\[(.+?)\]\((.+?)\)/gi;
23
- var GMAIL_LINK = /\[\[gmail=(.+?),\s*(.+?)\]\]/gi;
24
- var OUTLOOK_LINK = /\[\[outlook=(.+?),\s*(.+?)\]\]/gi;
25
- var THUNDERBIRD_LINK = /\[\[thunderbird\n(.+)\n(.+)\n\s*\]\]/gi;
26
- var FAKE_SECTION_PREFIX = '* ';
27
- var FAKE_SECTION_SUFFIX = ':';
5
+ const BOLD_FORMAT = /(^|[\s!?,;>:]+)(?:\*\*|__|!!)(.+?)(\*\*|__|!!)(?=$|[\s!?,;><:]+)/gi;
6
+ const ITALIC_FORMAT = /(^|[\s!?,;>:]+)(?:\*|_|!)(.+?)(\*|_|!)(?=$|[\s!?,;><:]+)/gi;
7
+ const BOLD_ITALIC_FORMAT = /(^|[\s!?,;>:]+)(?:\*\*\*|___|!!!)(.+?)(\*\*\*|___|!!!)(?=$|[\s!?,;><:]+)/gi;
8
+ const CODE_BLOCK_FORMAT = /```([\s\S]*?)```/gi;
9
+ const CODE_INLINE_FORMAT = /`([^`]+)`/gi;
10
+ const TODOIST_LINK = /((?:(?:onenote:)?[\w-]+):\/\/[^\s]+)\s+[[(]([^)]+)[\])]/gi;
11
+ const MARKDOWN_LINK = /\[(.+?)\]\((.+?)\)/gi;
12
+ const GMAIL_LINK = /\[\[gmail=(.+?),\s*(.+?)\]\]/gi;
13
+ const OUTLOOK_LINK = /\[\[outlook=(.+?),\s*(.+?)\]\]/gi;
14
+ const THUNDERBIRD_LINK = /\[\[thunderbird\n(.+)\n(.+)\n\s*\]\]/gi;
15
+ const FAKE_SECTION_PREFIX = '* ';
16
+ const FAKE_SECTION_SUFFIX = ':';
28
17
  function removeStyleFormatting(input) {
29
18
  if (!input.includes('!') && !input.includes('*') && !input.includes('_')) {
30
19
  return input;
31
20
  }
32
21
  function removeMarkdown(match, prefix, text) {
33
- return "".concat(prefix).concat(text);
22
+ return `${prefix}${text}`;
34
23
  }
35
24
  input = input.replace(BOLD_ITALIC_FORMAT, removeMarkdown);
36
25
  input = input.replace(BOLD_FORMAT, removeMarkdown);
@@ -74,13 +63,13 @@ function removeTodoistLinks(input) {
74
63
  }
75
64
  function removeAppLinks(input) {
76
65
  if (input.includes('gmail')) {
77
- input = input.replace(GMAIL_LINK, function (match, id, text) { return text; });
66
+ input = input.replace(GMAIL_LINK, (match, id, text) => text);
78
67
  }
79
68
  if (input.includes('outlook')) {
80
- input = input.replace(OUTLOOK_LINK, function (match, id, text) { return text; });
69
+ input = input.replace(OUTLOOK_LINK, (match, id, text) => text);
81
70
  }
82
71
  if (input.includes('thunderbird')) {
83
- input = input.replace(THUNDERBIRD_LINK, function (match, text) { return text; });
72
+ input = input.replace(THUNDERBIRD_LINK, (match, text) => text);
84
73
  }
85
74
  return input;
86
75
  }
@@ -123,5 +112,5 @@ function getSanitizedContent(input) {
123
112
  * @returns Array of tasks with added sanitizedContent property
124
113
  */
125
114
  function getSanitizedTasks(tasks) {
126
- return tasks.map(function (task) { return (__assign(__assign({}, task), { sanitizedContent: getSanitizedContent(task.content) })); });
115
+ return tasks.map((task) => (Object.assign(Object.assign({}, task), { sanitizedContent: getSanitizedContent(task.content) })));
127
116
  }