@doist/todoist-api-typescript 5.8.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.
- package/README.md +1 -1
- package/dist/cjs/authentication.js +158 -0
- package/dist/cjs/consts/endpoints.js +74 -0
- package/dist/{index.js → cjs/index.js} +1 -1
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/rest-client.js +124 -0
- package/dist/{testUtils → cjs/test-utils}/asserts.js +1 -1
- package/dist/{testUtils → cjs/test-utils}/mocks.js +8 -4
- package/dist/cjs/test-utils/msw-setup.js +27 -0
- package/dist/{testUtils/testDefaults.js → cjs/test-utils/test-defaults.js} +41 -52
- package/dist/cjs/todoist-api.js +1235 -0
- package/dist/{types → cjs/types}/entities.js +78 -31
- package/dist/cjs/types/errors.js +22 -0
- package/dist/cjs/types/http.js +22 -0
- package/dist/cjs/utils/case-conversion.js +69 -0
- package/dist/{utils → cjs/utils}/colors.js +3 -3
- package/dist/cjs/utils/fetch-with-retry.js +150 -0
- package/dist/{utils → cjs/utils}/index.js +4 -4
- package/dist/cjs/utils/multipart-upload.js +126 -0
- package/dist/{utils → cjs/utils}/processing-helpers.js +3 -3
- package/dist/{utils → cjs/utils}/sanitization.js +17 -28
- package/dist/{utils/urlHelpers.js → cjs/utils/url-helpers.js} +15 -15
- package/dist/{utils → cjs/utils}/validators.js +28 -1
- package/dist/esm/authentication.js +151 -0
- package/dist/esm/consts/endpoints.js +65 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/rest-client.js +119 -0
- package/dist/esm/test-utils/asserts.js +8 -0
- package/dist/esm/test-utils/mocks.js +10 -0
- package/dist/esm/test-utils/msw-setup.js +22 -0
- package/dist/esm/test-utils/test-defaults.js +198 -0
- package/dist/esm/todoist-api.js +1231 -0
- package/dist/esm/types/entities.js +366 -0
- package/dist/esm/types/errors.js +18 -0
- package/dist/esm/types/http.js +18 -0
- package/dist/esm/types/index.js +3 -0
- package/dist/esm/types/requests.js +1 -0
- package/dist/esm/types/sync.js +1 -0
- package/dist/esm/utils/activity-helpers.js +36 -0
- package/dist/esm/utils/case-conversion.js +61 -0
- package/dist/esm/utils/colors.js +215 -0
- package/dist/esm/utils/fetch-with-retry.js +147 -0
- package/dist/esm/utils/index.js +3 -0
- package/dist/esm/utils/multipart-upload.js +120 -0
- package/dist/esm/utils/processing-helpers.js +12 -0
- package/dist/esm/utils/sanitization.js +112 -0
- package/dist/esm/utils/url-helpers.js +68 -0
- package/dist/esm/utils/validators.js +97 -0
- package/dist/{authentication.d.ts → types/authentication.d.ts} +6 -1
- package/dist/{consts → types/consts}/endpoints.d.ts +11 -0
- package/dist/types/index.d.ts +4 -3
- package/dist/types/rest-client.d.ts +15 -0
- package/dist/types/test-utils/msw-setup.d.ts +3 -0
- package/dist/{TodoistApi.d.ts → types/todoist-api.d.ts} +91 -2
- package/dist/types/{entities.d.ts → types/entities.d.ts} +119 -0
- package/dist/types/types/http.d.ts +68 -0
- package/dist/types/types/index.d.ts +3 -0
- package/dist/types/{requests.d.ts → types/requests.d.ts} +137 -0
- package/dist/types/utils/case-conversion.d.ts +12 -0
- package/dist/types/utils/fetch-with-retry.d.ts +11 -0
- package/dist/types/utils/index.d.ts +3 -0
- package/dist/types/utils/multipart-upload.d.ts +50 -0
- package/dist/{utils → types/utils}/validators.d.ts +7 -1
- package/package.json +24 -8
- package/dist/TodoistApi.js +0 -1209
- package/dist/authentication.js +0 -199
- package/dist/consts/endpoints.js +0 -50
- package/dist/index.d.ts +0 -4
- package/dist/restClient.d.ts +0 -5
- package/dist/restClient.js +0 -170
- package/dist/types/errors.js +0 -39
- package/dist/types/http.d.ts +0 -1
- package/dist/types/http.js +0 -2
- package/dist/utils/index.d.ts +0 -3
- /package/dist/{types → cjs/types}/index.js +0 -0
- /package/dist/{types → cjs/types}/requests.js +0 -0
- /package/dist/{types → cjs/types}/sync.js +0 -0
- /package/dist/{utils → cjs/utils}/activity-helpers.js +0 -0
- /package/dist/{testUtils → types/test-utils}/asserts.d.ts +0 -0
- /package/dist/{testUtils → types/test-utils}/mocks.d.ts +0 -0
- /package/dist/{testUtils/testDefaults.d.ts → types/test-utils/test-defaults.d.ts} +0 -0
- /package/dist/types/{errors.d.ts → types/errors.d.ts} +0 -0
- /package/dist/types/{sync.d.ts → types/sync.d.ts} +0 -0
- /package/dist/{utils → types/utils}/activity-helpers.d.ts +0 -0
- /package/dist/{utils → types/utils}/colors.d.ts +0 -0
- /package/dist/{utils → types/utils}/processing-helpers.d.ts +0 -0
- /package/dist/{utils → types/utils}/sanitization.d.ts +0 -0
- /package/dist/{utils/urlHelpers.d.ts → types/utils/url-helpers.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)
|
|
@@ -22,9 +11,9 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
22
11
|
return t;
|
|
23
12
|
};
|
|
24
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
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
|
-
|
|
27
|
-
|
|
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;
|
|
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(
|
|
75
|
-
return
|
|
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(
|
|
106
|
-
return
|
|
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(
|
|
120
|
-
return
|
|
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(
|
|
137
|
-
return
|
|
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(
|
|
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(
|
|
184
|
-
|
|
185
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
222
|
+
const CompletedItemSchema = zod_1.z.object({
|
|
234
223
|
id: zod_1.z.string(),
|
|
235
224
|
completed: zod_1.z.number(),
|
|
236
225
|
});
|
|
237
|
-
|
|
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
|
-
|
|
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(
|
|
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(),
|
|
@@ -320,3 +309,61 @@ exports.ActivityEventSchema = zod_1.z
|
|
|
320
309
|
extraData: exports.ActivityEventExtraDataSchema,
|
|
321
310
|
})
|
|
322
311
|
.catchall(zod_1.z.any());
|
|
312
|
+
/**
|
|
313
|
+
* Available workspace roles.
|
|
314
|
+
*/
|
|
315
|
+
exports.WORKSPACE_ROLES = ['ADMIN', 'MEMBER', 'GUEST'];
|
|
316
|
+
exports.WorkspaceRoleSchema = zod_1.z.enum(exports.WORKSPACE_ROLES);
|
|
317
|
+
exports.WorkspaceUserSchema = zod_1.z.object({
|
|
318
|
+
userId: zod_1.z.string(),
|
|
319
|
+
workspaceId: zod_1.z.string(),
|
|
320
|
+
userEmail: zod_1.z.string(),
|
|
321
|
+
fullName: zod_1.z.string(),
|
|
322
|
+
timezone: zod_1.z.string(),
|
|
323
|
+
role: exports.WorkspaceRoleSchema,
|
|
324
|
+
imageId: zod_1.z.string().nullable(),
|
|
325
|
+
isDeleted: zod_1.z.boolean().default(false),
|
|
326
|
+
});
|
|
327
|
+
exports.WorkspaceInvitationSchema = zod_1.z.object({
|
|
328
|
+
id: zod_1.z.string().default('0'),
|
|
329
|
+
inviterId: zod_1.z.string(),
|
|
330
|
+
userEmail: zod_1.z.string(),
|
|
331
|
+
workspaceId: zod_1.z.string(),
|
|
332
|
+
role: exports.WorkspaceRoleSchema,
|
|
333
|
+
isExistingUser: zod_1.z.boolean(),
|
|
334
|
+
});
|
|
335
|
+
exports.PlanPriceSchema = zod_1.z.object({
|
|
336
|
+
currency: zod_1.z.string(),
|
|
337
|
+
amount: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]),
|
|
338
|
+
interval: zod_1.z.string().optional(),
|
|
339
|
+
});
|
|
340
|
+
exports.FormattedPriceListingSchema = zod_1.z.object({
|
|
341
|
+
currency: zod_1.z.string().optional(),
|
|
342
|
+
amount: zod_1.z.number().optional(),
|
|
343
|
+
interval: zod_1.z.string().optional(),
|
|
344
|
+
formatted: zod_1.z.string().optional(),
|
|
345
|
+
});
|
|
346
|
+
exports.WorkspacePlanDetailsSchema = zod_1.z.object({
|
|
347
|
+
currentMemberCount: zod_1.z.number(),
|
|
348
|
+
currentPlan: zod_1.z.enum(['Business', 'Starter']),
|
|
349
|
+
currentPlanStatus: zod_1.z.enum(['Active', 'Downgraded', 'Cancelled', 'NeverSubscribed']),
|
|
350
|
+
downgradeAt: zod_1.z.string().nullable(),
|
|
351
|
+
currentActiveProjects: zod_1.z.number(),
|
|
352
|
+
maximumActiveProjects: zod_1.z.number(),
|
|
353
|
+
priceList: zod_1.z.array(exports.FormattedPriceListingSchema),
|
|
354
|
+
workspaceId: zod_1.z.number(),
|
|
355
|
+
isTrialing: zod_1.z.boolean(),
|
|
356
|
+
trialEndsAt: zod_1.z.string().nullable(),
|
|
357
|
+
cancelAtPeriodEnd: zod_1.z.boolean(),
|
|
358
|
+
hasTrialed: zod_1.z.boolean(),
|
|
359
|
+
planPrice: exports.PlanPriceSchema.nullable(),
|
|
360
|
+
hasBillingPortal: zod_1.z.boolean(),
|
|
361
|
+
hasBillingPortalSwitchToAnnual: zod_1.z.boolean(),
|
|
362
|
+
});
|
|
363
|
+
exports.JoinWorkspaceResultSchema = zod_1.z.object({
|
|
364
|
+
custom_sorting_applied: zod_1.z.boolean(),
|
|
365
|
+
project_sort_preference: zod_1.z.string(),
|
|
366
|
+
role: exports.WorkspaceRoleSchema,
|
|
367
|
+
user_id: zod_1.z.string(),
|
|
368
|
+
workspace_id: zod_1.z.string(),
|
|
369
|
+
});
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -17,7 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.getSectionUrl = exports.getProjectUrl = exports.getTaskUrl = void 0;
|
|
18
18
|
__exportStar(require("./colors"), exports);
|
|
19
19
|
__exportStar(require("./sanitization"), exports);
|
|
20
|
-
var
|
|
21
|
-
Object.defineProperty(exports, "getTaskUrl", { enumerable: true, get: function () { return
|
|
22
|
-
Object.defineProperty(exports, "getProjectUrl", { enumerable: true, get: function () { return
|
|
23
|
-
Object.defineProperty(exports, "getSectionUrl", { enumerable: true, get: function () { return
|
|
20
|
+
var url_helpers_1 = require("./url-helpers");
|
|
21
|
+
Object.defineProperty(exports, "getTaskUrl", { enumerable: true, get: function () { return url_helpers_1.getTaskUrl; } });
|
|
22
|
+
Object.defineProperty(exports, "getProjectUrl", { enumerable: true, get: function () { return url_helpers_1.getProjectUrl; } });
|
|
23
|
+
Object.defineProperty(exports, "getSectionUrl", { enumerable: true, get: function () { return url_helpers_1.getSectionUrl; } });
|
|
@@ -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
|
-
|
|
8
|
-
|
|
7
|
+
const camelcase_1 = __importDefault(require("camelcase"));
|
|
8
|
+
const emoji_regex_1 = __importDefault(require("emoji-regex"));
|
|
9
9
|
function isEmojiKey(key) {
|
|
10
|
-
|
|
10
|
+
const regex = (0, emoji_regex_1.default)();
|
|
11
11
|
return regex.test(key);
|
|
12
12
|
}
|
|
13
13
|
function customCamelCase(input) {
|