@doist/todoist-api-typescript 7.4.0 → 7.5.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 +2 -0
- package/dist/cjs/authentication.js +4 -4
- package/dist/cjs/consts/endpoints.js +11 -1
- package/dist/cjs/test-utils/test-defaults.js +33 -1
- package/dist/cjs/todoist-api.js +528 -84
- package/dist/cjs/{utils → transport}/fetch-with-retry.js +23 -71
- package/dist/cjs/{rest-client.js → transport/http-client.js} +5 -5
- package/dist/cjs/transport/http-dispatcher.js +72 -0
- package/dist/cjs/types/entities.js +9 -1
- package/dist/cjs/types/errors.js +9 -1
- package/dist/cjs/types/http.js +3 -1
- package/dist/cjs/types/requests.js +3 -0
- package/dist/cjs/types/sync/commands/shared.js +2 -8
- package/dist/cjs/types/sync/resources/reminders.js +2 -0
- package/dist/cjs/utils/multipart-upload.js +1 -1
- package/dist/esm/authentication.js +1 -1
- package/dist/esm/consts/endpoints.js +9 -0
- package/dist/esm/test-utils/test-defaults.js +32 -0
- package/dist/esm/todoist-api.js +461 -17
- package/dist/esm/{utils → transport}/fetch-with-retry.js +23 -38
- package/dist/esm/{rest-client.js → transport/http-client.js} +5 -5
- package/dist/esm/transport/http-dispatcher.js +35 -0
- package/dist/esm/types/entities.js +8 -0
- package/dist/esm/types/errors.js +7 -0
- package/dist/esm/types/http.js +3 -1
- package/dist/esm/types/requests.js +2 -1
- package/dist/esm/types/sync/commands/shared.js +1 -8
- package/dist/esm/types/sync/resources/reminders.js +2 -0
- package/dist/esm/utils/multipart-upload.js +1 -1
- package/dist/types/consts/endpoints.d.ts +9 -0
- package/dist/types/test-utils/test-defaults.d.ts +5 -1
- package/dist/types/todoist-api.d.ts +144 -3
- package/dist/types/{utils → transport}/fetch-with-retry.d.ts +1 -1
- package/dist/types/{rest-client.d.ts → transport/http-client.d.ts} +1 -1
- package/dist/types/transport/http-dispatcher.d.ts +3 -0
- package/dist/types/types/entities.d.ts +4 -0
- package/dist/types/types/errors.d.ts +4 -0
- package/dist/types/types/requests.d.ts +163 -1
- package/dist/types/types/sync/commands/projects.d.ts +2 -2
- package/dist/types/types/sync/commands/shared.d.ts +1 -4
- package/dist/types/types/sync/resources/reminders.d.ts +4 -0
- package/dist/types/utils/validators.d.ts +4 -0
- package/package.json +4 -4
package/dist/esm/todoist-api.js
CHANGED
|
@@ -9,9 +9,12 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
12
|
+
import { DueDateSchema, } from './types/entities.js';
|
|
13
|
+
import { LOCATION_TRIGGERS } from './types/sync/resources/reminders.js';
|
|
14
|
+
import { REMINDER_DELIVERY_SERVICES, } from './types/requests.js';
|
|
15
|
+
import { request, isSuccess } from './transport/http-client.js';
|
|
16
|
+
import { getSyncBaseUri, ENDPOINT_REST_TASKS, ENDPOINT_REST_TASKS_FILTER, ENDPOINT_REST_TASKS_COMPLETED_BY_COMPLETION_DATE, ENDPOINT_REST_TASKS_COMPLETED_BY_DUE_DATE, ENDPOINT_REST_TASKS_COMPLETED_SEARCH, ENDPOINT_REST_PROJECTS, ENDPOINT_REST_PROJECTS_SEARCH, ENDPOINT_SYNC_QUICK_ADD, ENDPOINT_REST_TASK_CLOSE, ENDPOINT_REST_TASK_REOPEN, ENDPOINT_REST_TASK_MOVE, ENDPOINT_REST_LABELS, ENDPOINT_REST_LABELS_SEARCH, ENDPOINT_REST_PROJECT_COLLABORATORS, ENDPOINT_REST_SECTIONS, ENDPOINT_REST_SECTIONS_SEARCH, ENDPOINT_REST_COMMENTS, ENDPOINT_REST_LOCATION_REMINDERS, ENDPOINT_REST_REMINDERS, ENDPOINT_REST_LABELS_SHARED, ENDPOINT_REST_LABELS_SHARED_RENAME, ENDPOINT_REST_LABELS_SHARED_REMOVE, ENDPOINT_SYNC, PROJECT_ARCHIVE, PROJECT_UNARCHIVE, ENDPOINT_REST_PROJECTS_MOVE_TO_WORKSPACE, ENDPOINT_REST_PROJECTS_MOVE_TO_PERSONAL, ENDPOINT_REST_PROJECTS_ARCHIVED, ENDPOINT_REST_PROJECTS_ARCHIVED_COUNT, ENDPOINT_REST_PROJECTS_PERMISSIONS, ENDPOINT_REST_PROJECT_FULL, ENDPOINT_REST_PROJECT_JOIN, SECTION_ARCHIVE, SECTION_UNARCHIVE, ENDPOINT_REST_USER, ENDPOINT_REST_PRODUCTIVITY, ENDPOINT_REST_ACTIVITIES, ENDPOINT_REST_UPLOADS, ENDPOINT_REST_WORKSPACES, ENDPOINT_WORKSPACE_INVITATIONS, ENDPOINT_WORKSPACE_INVITATIONS_ALL, ENDPOINT_WORKSPACE_INVITATIONS_DELETE, getWorkspaceInvitationAcceptEndpoint, getWorkspaceInvitationRejectEndpoint, ENDPOINT_WORKSPACE_JOIN, ENDPOINT_WORKSPACE_LOGO, ENDPOINT_WORKSPACE_PLAN_DETAILS, ENDPOINT_WORKSPACE_USERS, getWorkspaceActiveProjectsEndpoint, getWorkspaceArchivedProjectsEndpoint, } from './consts/endpoints.js';
|
|
17
|
+
import { validateAttachment, validateComment, validateCommentArray, validateCurrentUser, validateLabel, validateLabelArray, validateProject, validateProjectArray, validateSection, validateSectionArray, validateTask, validateTaskArray, validateUserArray, validateProductivityStats, validateReminder, validateActivityEventArray, validateWorkspaceUserArray, validateWorkspaceInvitation, validateWorkspaceInvitationArray, validateWorkspacePlanDetails, validateJoinWorkspaceResult, validateWorkspace, validateWorkspaceArray, } from './utils/validators.js';
|
|
15
18
|
import { formatDateToYYYYMMDD } from './utils/url-helpers.js';
|
|
16
19
|
import { uploadMultipartFile } from './utils/multipart-upload.js';
|
|
17
20
|
import { normalizeObjectEventTypeForApi, denormalizeObjectTypeFromApi, } from './utils/activity-helpers.js';
|
|
@@ -19,7 +22,7 @@ import { processTaskContent } from './utils/uncompletable-helpers.js';
|
|
|
19
22
|
import { z } from 'zod';
|
|
20
23
|
import { v4 as uuidv4 } from 'uuid';
|
|
21
24
|
import { DATE_FORMAT_TO_API, TIME_FORMAT_TO_API, DAY_OF_WEEK_TO_API, } from './types/sync/index.js';
|
|
22
|
-
import { TodoistRequestError } from './types/index.js';
|
|
25
|
+
import { TodoistArgumentError, TodoistRequestError } from './types/index.js';
|
|
23
26
|
const MAX_COMMAND_COUNT = 100;
|
|
24
27
|
/**
|
|
25
28
|
* Joins path segments using `/` separator.
|
|
@@ -80,6 +83,56 @@ function headersToRecord(headers) {
|
|
|
80
83
|
});
|
|
81
84
|
return result;
|
|
82
85
|
}
|
|
86
|
+
const ReminderDeliveryServiceSchema = z.enum(REMINDER_DELIVERY_SERVICES);
|
|
87
|
+
const ReminderIdSchema = z.string();
|
|
88
|
+
const ReminderDueDateSchema = DueDateSchema.pick({
|
|
89
|
+
date: true,
|
|
90
|
+
string: true,
|
|
91
|
+
timezone: true,
|
|
92
|
+
lang: true,
|
|
93
|
+
isRecurring: true,
|
|
94
|
+
})
|
|
95
|
+
.partial()
|
|
96
|
+
.strict();
|
|
97
|
+
const UpdateRelativeReminderArgsSchema = z
|
|
98
|
+
.object({
|
|
99
|
+
reminderType: z.literal('relative'),
|
|
100
|
+
minuteOffset: z.number().int().optional(),
|
|
101
|
+
notifyUid: z.string().optional(),
|
|
102
|
+
service: ReminderDeliveryServiceSchema.optional(),
|
|
103
|
+
isUrgent: z.boolean().optional(),
|
|
104
|
+
})
|
|
105
|
+
.strict();
|
|
106
|
+
const UpdateAbsoluteReminderArgsSchema = z
|
|
107
|
+
.object({
|
|
108
|
+
reminderType: z.literal('absolute'),
|
|
109
|
+
due: ReminderDueDateSchema.optional(),
|
|
110
|
+
notifyUid: z.string().optional(),
|
|
111
|
+
service: ReminderDeliveryServiceSchema.optional(),
|
|
112
|
+
isUrgent: z.boolean().optional(),
|
|
113
|
+
})
|
|
114
|
+
.strict();
|
|
115
|
+
const UpdateLocationReminderArgsSchema = z
|
|
116
|
+
.object({
|
|
117
|
+
notifyUid: z.string().optional(),
|
|
118
|
+
name: z.string().optional(),
|
|
119
|
+
locLat: z.string().optional(),
|
|
120
|
+
locLong: z.string().optional(),
|
|
121
|
+
locTrigger: z.enum(LOCATION_TRIGGERS).optional(),
|
|
122
|
+
radius: z.number().int().optional(),
|
|
123
|
+
})
|
|
124
|
+
.strict()
|
|
125
|
+
.refine((args) => Object.values(args).some((value) => value !== undefined), {
|
|
126
|
+
message: 'At least one reminder field must be provided to updateLocationReminder',
|
|
127
|
+
});
|
|
128
|
+
const UpdateReminderArgsSchema = z
|
|
129
|
+
.discriminatedUnion('reminderType', [
|
|
130
|
+
UpdateRelativeReminderArgsSchema,
|
|
131
|
+
UpdateAbsoluteReminderArgsSchema,
|
|
132
|
+
])
|
|
133
|
+
.refine((args) => Object.entries(args).some(([key, value]) => key !== 'reminderType' && value !== undefined), {
|
|
134
|
+
message: 'At least one reminder field must be provided to updateReminder',
|
|
135
|
+
});
|
|
83
136
|
export class TodoistApi {
|
|
84
137
|
constructor(
|
|
85
138
|
/**
|
|
@@ -683,6 +736,83 @@ export class TodoistApi {
|
|
|
683
736
|
});
|
|
684
737
|
return validateProject(response.data.project);
|
|
685
738
|
}
|
|
739
|
+
/**
|
|
740
|
+
* Counts the number of archived projects.
|
|
741
|
+
*
|
|
742
|
+
* @param args - Optional parameters to filter the count.
|
|
743
|
+
* @returns A promise that resolves to the count of archived projects.
|
|
744
|
+
*/
|
|
745
|
+
async getArchivedProjectsCount(args = {}) {
|
|
746
|
+
const { data } = await request({
|
|
747
|
+
httpMethod: 'GET',
|
|
748
|
+
baseUri: this.syncApiBase,
|
|
749
|
+
relativePath: ENDPOINT_REST_PROJECTS_ARCHIVED_COUNT,
|
|
750
|
+
apiToken: this.authToken,
|
|
751
|
+
customFetch: this.customFetch,
|
|
752
|
+
payload: args,
|
|
753
|
+
});
|
|
754
|
+
return data;
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Retrieves the role-to-action permission mappings for projects.
|
|
758
|
+
*
|
|
759
|
+
* @returns A promise that resolves to the permission mappings.
|
|
760
|
+
*/
|
|
761
|
+
async getProjectPermissions() {
|
|
762
|
+
const { data } = await request({
|
|
763
|
+
httpMethod: 'GET',
|
|
764
|
+
baseUri: this.syncApiBase,
|
|
765
|
+
relativePath: ENDPOINT_REST_PROJECTS_PERMISSIONS,
|
|
766
|
+
apiToken: this.authToken,
|
|
767
|
+
customFetch: this.customFetch,
|
|
768
|
+
});
|
|
769
|
+
return data;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Retrieves full project data including tasks, sections, collaborators, and notes.
|
|
773
|
+
*
|
|
774
|
+
* @param id - The unique identifier of the project.
|
|
775
|
+
* @param args - Optional parameters.
|
|
776
|
+
* @returns A promise that resolves to the full project data.
|
|
777
|
+
*/
|
|
778
|
+
async getFullProject(id, args = {}) {
|
|
779
|
+
z.string().parse(id);
|
|
780
|
+
const { data } = await request({
|
|
781
|
+
httpMethod: 'GET',
|
|
782
|
+
baseUri: this.syncApiBase,
|
|
783
|
+
relativePath: generatePath(ENDPOINT_REST_PROJECTS, id, ENDPOINT_REST_PROJECT_FULL),
|
|
784
|
+
apiToken: this.authToken,
|
|
785
|
+
customFetch: this.customFetch,
|
|
786
|
+
payload: args,
|
|
787
|
+
});
|
|
788
|
+
return {
|
|
789
|
+
project: data.project ? validateProject(data.project) : null,
|
|
790
|
+
commentsCount: data.commentsCount,
|
|
791
|
+
tasks: validateTaskArray(data.tasks),
|
|
792
|
+
sections: validateSectionArray(data.sections),
|
|
793
|
+
collaborators: validateUserArray(data.collaborators),
|
|
794
|
+
notes: validateCommentArray(data.notes),
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Joins a shared project by its ID.
|
|
799
|
+
*
|
|
800
|
+
* @param id - The unique identifier of the project to join.
|
|
801
|
+
* @param requestId - Optional custom identifier for the request.
|
|
802
|
+
* @returns A promise that resolves to the joined project.
|
|
803
|
+
*/
|
|
804
|
+
async joinProject(id, requestId) {
|
|
805
|
+
z.string().parse(id);
|
|
806
|
+
const response = await request({
|
|
807
|
+
httpMethod: 'POST',
|
|
808
|
+
baseUri: this.syncApiBase,
|
|
809
|
+
relativePath: generatePath(ENDPOINT_REST_PROJECTS, id, ENDPOINT_REST_PROJECT_JOIN),
|
|
810
|
+
apiToken: this.authToken,
|
|
811
|
+
customFetch: this.customFetch,
|
|
812
|
+
requestId: requestId,
|
|
813
|
+
});
|
|
814
|
+
return validateProject(response.data);
|
|
815
|
+
}
|
|
686
816
|
/**
|
|
687
817
|
* Retrieves a list of collaborators for a specific project.
|
|
688
818
|
*
|
|
@@ -822,6 +952,44 @@ export class TodoistApi {
|
|
|
822
952
|
});
|
|
823
953
|
return isSuccess(response);
|
|
824
954
|
}
|
|
955
|
+
/**
|
|
956
|
+
* Archives a section by its ID.
|
|
957
|
+
*
|
|
958
|
+
* @param id - The unique identifier of the section to archive.
|
|
959
|
+
* @param requestId - Optional custom identifier for the request.
|
|
960
|
+
* @returns A promise that resolves to the updated section.
|
|
961
|
+
*/
|
|
962
|
+
async archiveSection(id, requestId) {
|
|
963
|
+
z.string().parse(id);
|
|
964
|
+
const response = await request({
|
|
965
|
+
httpMethod: 'POST',
|
|
966
|
+
baseUri: this.syncApiBase,
|
|
967
|
+
relativePath: generatePath(ENDPOINT_REST_SECTIONS, id, SECTION_ARCHIVE),
|
|
968
|
+
apiToken: this.authToken,
|
|
969
|
+
customFetch: this.customFetch,
|
|
970
|
+
requestId: requestId,
|
|
971
|
+
});
|
|
972
|
+
return validateSection(response.data);
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Unarchives a section by its ID.
|
|
976
|
+
*
|
|
977
|
+
* @param id - The unique identifier of the section to unarchive.
|
|
978
|
+
* @param requestId - Optional custom identifier for the request.
|
|
979
|
+
* @returns A promise that resolves to the updated section.
|
|
980
|
+
*/
|
|
981
|
+
async unarchiveSection(id, requestId) {
|
|
982
|
+
z.string().parse(id);
|
|
983
|
+
const response = await request({
|
|
984
|
+
httpMethod: 'POST',
|
|
985
|
+
baseUri: this.syncApiBase,
|
|
986
|
+
relativePath: generatePath(ENDPOINT_REST_SECTIONS, id, SECTION_UNARCHIVE),
|
|
987
|
+
apiToken: this.authToken,
|
|
988
|
+
customFetch: this.customFetch,
|
|
989
|
+
requestId: requestId,
|
|
990
|
+
});
|
|
991
|
+
return validateSection(response.data);
|
|
992
|
+
}
|
|
825
993
|
/**
|
|
826
994
|
* Retrieves a label by its ID.
|
|
827
995
|
*
|
|
@@ -1085,6 +1253,208 @@ export class TodoistApi {
|
|
|
1085
1253
|
});
|
|
1086
1254
|
return isSuccess(response);
|
|
1087
1255
|
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Retrieves a time-based reminder by its ID.
|
|
1258
|
+
*
|
|
1259
|
+
* @param id - The unique identifier of the reminder to retrieve.
|
|
1260
|
+
* @returns A promise that resolves to the requested reminder.
|
|
1261
|
+
*/
|
|
1262
|
+
async getReminder(id) {
|
|
1263
|
+
ReminderIdSchema.parse(id);
|
|
1264
|
+
try {
|
|
1265
|
+
const response = await request({
|
|
1266
|
+
httpMethod: 'GET',
|
|
1267
|
+
baseUri: this.syncApiBase,
|
|
1268
|
+
relativePath: generatePath(ENDPOINT_REST_REMINDERS, id),
|
|
1269
|
+
apiToken: this.authToken,
|
|
1270
|
+
customFetch: this.customFetch,
|
|
1271
|
+
});
|
|
1272
|
+
return validateReminder(response.data);
|
|
1273
|
+
}
|
|
1274
|
+
catch (error) {
|
|
1275
|
+
if (!(error instanceof TodoistRequestError) || error.httpStatusCode !== 404) {
|
|
1276
|
+
throw error;
|
|
1277
|
+
}
|
|
1278
|
+
throw new TodoistArgumentError(`Reminder ${id} was not found on the time-based reminder endpoint. If this is a location reminder, use getLocationReminder instead.`);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Retrieves a location reminder by its ID.
|
|
1283
|
+
*
|
|
1284
|
+
* @param id - The unique identifier of the location reminder to retrieve.
|
|
1285
|
+
* @returns A promise that resolves to the requested reminder.
|
|
1286
|
+
*/
|
|
1287
|
+
async getLocationReminder(id) {
|
|
1288
|
+
ReminderIdSchema.parse(id);
|
|
1289
|
+
try {
|
|
1290
|
+
const response = await request({
|
|
1291
|
+
httpMethod: 'GET',
|
|
1292
|
+
baseUri: this.syncApiBase,
|
|
1293
|
+
relativePath: generatePath(ENDPOINT_REST_LOCATION_REMINDERS, id),
|
|
1294
|
+
apiToken: this.authToken,
|
|
1295
|
+
customFetch: this.customFetch,
|
|
1296
|
+
});
|
|
1297
|
+
return validateReminder(response.data);
|
|
1298
|
+
}
|
|
1299
|
+
catch (error) {
|
|
1300
|
+
if (!(error instanceof TodoistRequestError) || error.httpStatusCode !== 404) {
|
|
1301
|
+
throw error;
|
|
1302
|
+
}
|
|
1303
|
+
throw new TodoistArgumentError(`Location reminder ${id} was not found on the location reminder endpoint. If this is a time-based reminder, use getReminder instead.`);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Creates a time-based reminder for a task.
|
|
1308
|
+
*
|
|
1309
|
+
* @param args - Reminder creation parameters for relative or absolute reminders.
|
|
1310
|
+
* @param requestId - Optional custom identifier for the request.
|
|
1311
|
+
* @returns A promise that resolves to the created reminder.
|
|
1312
|
+
*/
|
|
1313
|
+
async addReminder(args, requestId) {
|
|
1314
|
+
const response = await request({
|
|
1315
|
+
httpMethod: 'POST',
|
|
1316
|
+
baseUri: this.syncApiBase,
|
|
1317
|
+
relativePath: ENDPOINT_REST_REMINDERS,
|
|
1318
|
+
apiToken: this.authToken,
|
|
1319
|
+
customFetch: this.customFetch,
|
|
1320
|
+
payload: args,
|
|
1321
|
+
requestId: requestId,
|
|
1322
|
+
});
|
|
1323
|
+
return validateReminder(response.data);
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Creates a location reminder for a task.
|
|
1327
|
+
*
|
|
1328
|
+
* @param args - Location reminder creation parameters.
|
|
1329
|
+
* @param requestId - Optional custom identifier for the request.
|
|
1330
|
+
* @returns A promise that resolves to the created reminder.
|
|
1331
|
+
*/
|
|
1332
|
+
async addLocationReminder(args, requestId) {
|
|
1333
|
+
const response = await request({
|
|
1334
|
+
httpMethod: 'POST',
|
|
1335
|
+
baseUri: this.syncApiBase,
|
|
1336
|
+
relativePath: ENDPOINT_REST_LOCATION_REMINDERS,
|
|
1337
|
+
apiToken: this.authToken,
|
|
1338
|
+
customFetch: this.customFetch,
|
|
1339
|
+
payload: Object.assign(Object.assign({}, args), { reminderType: 'location' }),
|
|
1340
|
+
requestId: requestId,
|
|
1341
|
+
});
|
|
1342
|
+
return validateReminder(response.data);
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Updates an existing time-based reminder.
|
|
1346
|
+
*
|
|
1347
|
+
* @param id - The unique identifier of the reminder to update.
|
|
1348
|
+
* @param args - Reminder update parameters.
|
|
1349
|
+
* @param requestId - Optional custom identifier for the request.
|
|
1350
|
+
* @returns A promise that resolves to the updated reminder.
|
|
1351
|
+
*/
|
|
1352
|
+
async updateReminder(id, args, requestId) {
|
|
1353
|
+
ReminderIdSchema.parse(id);
|
|
1354
|
+
const payload = UpdateReminderArgsSchema.parse(args);
|
|
1355
|
+
try {
|
|
1356
|
+
const response = await request({
|
|
1357
|
+
httpMethod: 'POST',
|
|
1358
|
+
baseUri: this.syncApiBase,
|
|
1359
|
+
relativePath: generatePath(ENDPOINT_REST_REMINDERS, id),
|
|
1360
|
+
apiToken: this.authToken,
|
|
1361
|
+
customFetch: this.customFetch,
|
|
1362
|
+
payload,
|
|
1363
|
+
requestId: requestId,
|
|
1364
|
+
});
|
|
1365
|
+
return validateReminder(response.data);
|
|
1366
|
+
}
|
|
1367
|
+
catch (error) {
|
|
1368
|
+
if (!(error instanceof TodoistRequestError) || error.httpStatusCode !== 404) {
|
|
1369
|
+
throw error;
|
|
1370
|
+
}
|
|
1371
|
+
throw new TodoistArgumentError(`Reminder ${id} was not found on the time-based reminder endpoint. If this is a location reminder, use updateLocationReminder instead.`);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Updates an existing location reminder.
|
|
1376
|
+
*
|
|
1377
|
+
* @param id - The unique identifier of the location reminder to update.
|
|
1378
|
+
* @param args - Location reminder update parameters.
|
|
1379
|
+
* @param requestId - Optional custom identifier for the request.
|
|
1380
|
+
* @returns A promise that resolves to the updated reminder.
|
|
1381
|
+
*/
|
|
1382
|
+
async updateLocationReminder(id, args, requestId) {
|
|
1383
|
+
ReminderIdSchema.parse(id);
|
|
1384
|
+
const payload = UpdateLocationReminderArgsSchema.parse(args);
|
|
1385
|
+
try {
|
|
1386
|
+
const response = await request({
|
|
1387
|
+
httpMethod: 'POST',
|
|
1388
|
+
baseUri: this.syncApiBase,
|
|
1389
|
+
relativePath: generatePath(ENDPOINT_REST_LOCATION_REMINDERS, id),
|
|
1390
|
+
apiToken: this.authToken,
|
|
1391
|
+
customFetch: this.customFetch,
|
|
1392
|
+
payload,
|
|
1393
|
+
requestId: requestId,
|
|
1394
|
+
});
|
|
1395
|
+
return validateReminder(response.data);
|
|
1396
|
+
}
|
|
1397
|
+
catch (error) {
|
|
1398
|
+
if (!(error instanceof TodoistRequestError) || error.httpStatusCode !== 404) {
|
|
1399
|
+
throw error;
|
|
1400
|
+
}
|
|
1401
|
+
throw new TodoistArgumentError(`Location reminder ${id} was not found on the location reminder endpoint. If this is a time-based reminder, use updateReminder instead.`);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Deletes a time-based reminder by its ID.
|
|
1406
|
+
*
|
|
1407
|
+
* @param id - The unique identifier of the reminder to delete.
|
|
1408
|
+
* @param requestId - Optional custom identifier for the request.
|
|
1409
|
+
* @returns A promise that resolves to `true` if successful.
|
|
1410
|
+
*/
|
|
1411
|
+
async deleteReminder(id, requestId) {
|
|
1412
|
+
ReminderIdSchema.parse(id);
|
|
1413
|
+
try {
|
|
1414
|
+
const response = await request({
|
|
1415
|
+
httpMethod: 'DELETE',
|
|
1416
|
+
baseUri: this.syncApiBase,
|
|
1417
|
+
relativePath: generatePath(ENDPOINT_REST_REMINDERS, id),
|
|
1418
|
+
apiToken: this.authToken,
|
|
1419
|
+
customFetch: this.customFetch,
|
|
1420
|
+
requestId: requestId,
|
|
1421
|
+
});
|
|
1422
|
+
return isSuccess(response);
|
|
1423
|
+
}
|
|
1424
|
+
catch (error) {
|
|
1425
|
+
if (!(error instanceof TodoistRequestError) || error.httpStatusCode !== 404) {
|
|
1426
|
+
throw error;
|
|
1427
|
+
}
|
|
1428
|
+
throw new TodoistArgumentError(`Reminder ${id} was not found on the time-based reminder endpoint. If this is a location reminder, use deleteLocationReminder instead.`);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Deletes a location reminder by its ID.
|
|
1433
|
+
*
|
|
1434
|
+
* @param id - The unique identifier of the location reminder to delete.
|
|
1435
|
+
* @param requestId - Optional custom identifier for the request.
|
|
1436
|
+
* @returns A promise that resolves to `true` if successful.
|
|
1437
|
+
*/
|
|
1438
|
+
async deleteLocationReminder(id, requestId) {
|
|
1439
|
+
ReminderIdSchema.parse(id);
|
|
1440
|
+
try {
|
|
1441
|
+
const response = await request({
|
|
1442
|
+
httpMethod: 'DELETE',
|
|
1443
|
+
baseUri: this.syncApiBase,
|
|
1444
|
+
relativePath: generatePath(ENDPOINT_REST_LOCATION_REMINDERS, id),
|
|
1445
|
+
apiToken: this.authToken,
|
|
1446
|
+
customFetch: this.customFetch,
|
|
1447
|
+
requestId: requestId,
|
|
1448
|
+
});
|
|
1449
|
+
return isSuccess(response);
|
|
1450
|
+
}
|
|
1451
|
+
catch (error) {
|
|
1452
|
+
if (!(error instanceof TodoistRequestError) || error.httpStatusCode !== 404) {
|
|
1453
|
+
throw error;
|
|
1454
|
+
}
|
|
1455
|
+
throw new TodoistArgumentError(`Location reminder ${id} was not found on the location reminder endpoint. If this is a time-based reminder, use deleteReminder instead.`);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1088
1458
|
/**
|
|
1089
1459
|
* Retrieves productivity stats for the authenticated user.
|
|
1090
1460
|
*
|
|
@@ -1520,8 +1890,6 @@ export class TodoistApi {
|
|
|
1520
1890
|
/**
|
|
1521
1891
|
* Retrieves all workspaces for the authenticated user.
|
|
1522
1892
|
*
|
|
1523
|
-
* Uses the Sync API internally to fetch workspace data.
|
|
1524
|
-
*
|
|
1525
1893
|
* @param requestId - Optional custom identifier for the request.
|
|
1526
1894
|
* @returns A promise that resolves to an array of workspaces.
|
|
1527
1895
|
*
|
|
@@ -1534,17 +1902,93 @@ export class TodoistApi {
|
|
|
1534
1902
|
* ```
|
|
1535
1903
|
*/
|
|
1536
1904
|
async getWorkspaces(requestId) {
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1905
|
+
const response = await request({
|
|
1906
|
+
httpMethod: 'GET',
|
|
1907
|
+
baseUri: this.syncApiBase,
|
|
1908
|
+
relativePath: ENDPOINT_REST_WORKSPACES,
|
|
1909
|
+
apiToken: this.authToken,
|
|
1910
|
+
customFetch: this.customFetch,
|
|
1911
|
+
requestId: requestId,
|
|
1912
|
+
});
|
|
1913
|
+
return validateWorkspaceArray(response.data);
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Retrieves a workspace by its ID.
|
|
1917
|
+
*
|
|
1918
|
+
* @param id - The unique identifier of the workspace.
|
|
1919
|
+
* @param requestId - Optional custom identifier for the request.
|
|
1920
|
+
* @returns A promise that resolves to the requested workspace.
|
|
1921
|
+
*/
|
|
1922
|
+
async getWorkspace(id, requestId) {
|
|
1923
|
+
z.string().parse(id);
|
|
1924
|
+
const response = await request({
|
|
1925
|
+
httpMethod: 'GET',
|
|
1926
|
+
baseUri: this.syncApiBase,
|
|
1927
|
+
relativePath: generatePath(ENDPOINT_REST_WORKSPACES, id),
|
|
1928
|
+
apiToken: this.authToken,
|
|
1929
|
+
customFetch: this.customFetch,
|
|
1930
|
+
requestId: requestId,
|
|
1931
|
+
});
|
|
1932
|
+
return validateWorkspace(response.data);
|
|
1933
|
+
}
|
|
1934
|
+
/**
|
|
1935
|
+
* Creates a new workspace.
|
|
1936
|
+
*
|
|
1937
|
+
* @param args - The arguments for creating the workspace.
|
|
1938
|
+
* @param requestId - Optional custom identifier for the request.
|
|
1939
|
+
* @returns A promise that resolves to the created workspace.
|
|
1940
|
+
*/
|
|
1941
|
+
async addWorkspace(args, requestId) {
|
|
1942
|
+
const response = await request({
|
|
1943
|
+
httpMethod: 'POST',
|
|
1944
|
+
baseUri: this.syncApiBase,
|
|
1945
|
+
relativePath: ENDPOINT_REST_WORKSPACES,
|
|
1946
|
+
apiToken: this.authToken,
|
|
1947
|
+
customFetch: this.customFetch,
|
|
1948
|
+
payload: args,
|
|
1949
|
+
requestId: requestId,
|
|
1950
|
+
});
|
|
1951
|
+
return validateWorkspace(response.data);
|
|
1952
|
+
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Updates an existing workspace.
|
|
1955
|
+
*
|
|
1956
|
+
* @param id - The unique identifier of the workspace to update.
|
|
1957
|
+
* @param args - The arguments for updating the workspace.
|
|
1958
|
+
* @param requestId - Optional custom identifier for the request.
|
|
1959
|
+
* @returns A promise that resolves to the updated workspace.
|
|
1960
|
+
*/
|
|
1961
|
+
async updateWorkspace(id, args, requestId) {
|
|
1962
|
+
z.string().parse(id);
|
|
1963
|
+
const response = await request({
|
|
1964
|
+
httpMethod: 'POST',
|
|
1965
|
+
baseUri: this.syncApiBase,
|
|
1966
|
+
relativePath: generatePath(ENDPOINT_REST_WORKSPACES, id),
|
|
1967
|
+
apiToken: this.authToken,
|
|
1968
|
+
customFetch: this.customFetch,
|
|
1969
|
+
payload: args,
|
|
1970
|
+
requestId: requestId,
|
|
1971
|
+
});
|
|
1972
|
+
return validateWorkspace(response.data);
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Deletes a workspace by its ID.
|
|
1976
|
+
*
|
|
1977
|
+
* @param id - The unique identifier of the workspace to delete.
|
|
1978
|
+
* @param requestId - Optional custom identifier for the request.
|
|
1979
|
+
* @returns A promise that resolves to `true` if successful.
|
|
1980
|
+
*/
|
|
1981
|
+
async deleteWorkspace(id, requestId) {
|
|
1982
|
+
z.string().parse(id);
|
|
1983
|
+
const response = await request({
|
|
1984
|
+
httpMethod: 'DELETE',
|
|
1985
|
+
baseUri: this.syncApiBase,
|
|
1986
|
+
relativePath: generatePath(ENDPOINT_REST_WORKSPACES, id),
|
|
1987
|
+
apiToken: this.authToken,
|
|
1988
|
+
customFetch: this.customFetch,
|
|
1989
|
+
requestId: requestId,
|
|
1990
|
+
});
|
|
1991
|
+
return isSuccess(response);
|
|
1548
1992
|
}
|
|
1549
1993
|
/**
|
|
1550
1994
|
* Gets active projects in a workspace with pagination.
|
|
@@ -10,6 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { isNetworkError } from '../types/http.js';
|
|
13
|
+
import { getDefaultDispatcher } from './http-dispatcher.js';
|
|
13
14
|
/**
|
|
14
15
|
* Default retry configuration matching the original axios-retry behavior
|
|
15
16
|
*/
|
|
@@ -22,32 +23,11 @@ const DEFAULT_RETRY_CONFIG = {
|
|
|
22
23
|
return retryNumber === 1 ? 0 : 500;
|
|
23
24
|
},
|
|
24
25
|
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Gets the HTTP agent for Node.js environments or undefined for browser environments.
|
|
32
|
-
* Uses dynamic import to avoid loading undici in browser environments.
|
|
33
|
-
*/
|
|
34
|
-
async function getHttpAgent() {
|
|
35
|
-
var _a;
|
|
36
|
-
if (httpAgent === null) {
|
|
37
|
-
if (typeof process !== 'undefined' && ((_a = process.versions) === null || _a === void 0 ? void 0 : _a.node)) {
|
|
38
|
-
// We're in Node.js - dynamically import undici
|
|
39
|
-
const { Agent } = await import('undici');
|
|
40
|
-
httpAgent = new Agent({
|
|
41
|
-
keepAliveTimeout: 1, // Close connections after 1ms of idle time
|
|
42
|
-
keepAliveMaxTimeout: 1, // Maximum time to keep connections alive
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
// We're in browser - no agent needed
|
|
47
|
-
httpAgent = undefined;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return httpAgent;
|
|
26
|
+
const TIMEOUT_ERROR_NAME = 'TimeoutError';
|
|
27
|
+
function createTimeoutError(timeoutMs) {
|
|
28
|
+
const error = new Error(`Request timeout after ${timeoutMs}ms`);
|
|
29
|
+
error.name = TIMEOUT_ERROR_NAME;
|
|
30
|
+
return error;
|
|
51
31
|
}
|
|
52
32
|
/**
|
|
53
33
|
* Converts Headers object to a plain object
|
|
@@ -67,10 +47,14 @@ function createTimeoutSignal(timeoutMs, existingSignal) {
|
|
|
67
47
|
const controller = new AbortController();
|
|
68
48
|
// Timeout logic
|
|
69
49
|
const timeoutId = setTimeout(() => {
|
|
70
|
-
controller.abort(
|
|
50
|
+
controller.abort(createTimeoutError(timeoutMs));
|
|
71
51
|
}, timeoutMs);
|
|
52
|
+
let abortHandler;
|
|
72
53
|
function clear() {
|
|
73
54
|
clearTimeout(timeoutId);
|
|
55
|
+
if (existingSignal && abortHandler) {
|
|
56
|
+
existingSignal.removeEventListener('abort', abortHandler);
|
|
57
|
+
}
|
|
74
58
|
}
|
|
75
59
|
// If there's an existing signal, forward its abort
|
|
76
60
|
if (existingSignal) {
|
|
@@ -79,10 +63,11 @@ function createTimeoutSignal(timeoutMs, existingSignal) {
|
|
|
79
63
|
controller.abort(existingSignal.reason);
|
|
80
64
|
}
|
|
81
65
|
else {
|
|
82
|
-
|
|
66
|
+
abortHandler = () => {
|
|
83
67
|
clearTimeout(timeoutId);
|
|
84
68
|
controller.abort(existingSignal.reason);
|
|
85
|
-
}
|
|
69
|
+
};
|
|
70
|
+
existingSignal.addEventListener('abort', abortHandler, { once: true });
|
|
86
71
|
}
|
|
87
72
|
}
|
|
88
73
|
// Clean up timeout when request completes
|
|
@@ -131,9 +116,13 @@ export async function fetchWithRetry(args) {
|
|
|
131
116
|
fetchResponse = await customFetch(url, Object.assign(Object.assign({}, fetchOptions), { signal: requestSignal, timeout }));
|
|
132
117
|
}
|
|
133
118
|
else {
|
|
134
|
-
const
|
|
119
|
+
const dispatcher = await getDefaultDispatcher();
|
|
120
|
+
const nativeFetchOptions = Object.assign(Object.assign({}, fetchOptions), { signal: requestSignal });
|
|
121
|
+
if (dispatcher !== undefined) {
|
|
135
122
|
// @ts-expect-error - dispatcher is a valid option for Node.js fetch but not in the TS types
|
|
136
|
-
dispatcher
|
|
123
|
+
nativeFetchOptions.dispatcher = dispatcher;
|
|
124
|
+
}
|
|
125
|
+
const nativeResponse = await fetch(url, nativeFetchOptions);
|
|
137
126
|
fetchResponse = convertResponseToCustomFetch(nativeResponse);
|
|
138
127
|
}
|
|
139
128
|
// Check if the response is successful
|
|
@@ -188,6 +177,9 @@ export async function fetchWithRetry(args) {
|
|
|
188
177
|
};
|
|
189
178
|
}
|
|
190
179
|
catch (error) {
|
|
180
|
+
if (clearTimeoutFn) {
|
|
181
|
+
clearTimeoutFn();
|
|
182
|
+
}
|
|
191
183
|
lastError = error;
|
|
192
184
|
// Check if this error should trigger a retry
|
|
193
185
|
const shouldRetry = attempt < config.retries && config.retryCondition(lastError);
|
|
@@ -197,9 +189,6 @@ export async function fetchWithRetry(args) {
|
|
|
197
189
|
const networkError = lastError;
|
|
198
190
|
networkError.isNetworkError = true;
|
|
199
191
|
}
|
|
200
|
-
if (clearTimeoutFn) {
|
|
201
|
-
clearTimeoutFn();
|
|
202
|
-
}
|
|
203
192
|
throw lastError;
|
|
204
193
|
}
|
|
205
194
|
// Wait before retrying
|
|
@@ -207,10 +196,6 @@ export async function fetchWithRetry(args) {
|
|
|
207
196
|
if (delay > 0) {
|
|
208
197
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
209
198
|
}
|
|
210
|
-
// Retry path – ensure this attempt's timeout is cleared before looping
|
|
211
|
-
if (clearTimeoutFn) {
|
|
212
|
-
clearTimeoutFn();
|
|
213
|
-
}
|
|
214
199
|
}
|
|
215
200
|
}
|
|
216
201
|
// This should never be reached, but just in case
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { TodoistRequestError } from './types/errors.js';
|
|
2
|
-
import { isNetworkError, isHttpError } from './types/http.js';
|
|
3
1
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
-
import { API_BASE_URI } from '
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { API_BASE_URI } from '../consts/endpoints.js';
|
|
3
|
+
import { TodoistRequestError } from '../types/errors.js';
|
|
4
|
+
import { isHttpError, isNetworkError } from '../types/http.js';
|
|
5
|
+
import { camelCaseKeys, snakeCaseKeys } from '../utils/case-conversion.js';
|
|
6
|
+
import { fetchWithRetry } from './fetch-with-retry.js';
|
|
7
7
|
export function paramsSerializer(params) {
|
|
8
8
|
const qs = new URLSearchParams();
|
|
9
9
|
Object.keys(params).forEach((key) => {
|