@doist/todoist-sdk 15.3.1 → 15.4.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/dist/esm/consts/endpoints.js +29 -27
- package/dist/esm/transport/http-client.js +13 -4
- package/dist/esm/types/activity/types.js +20 -0
- package/dist/esm/utils/multipart-upload.js +15 -2
- package/dist/esm/utils/request-helpers.js +30 -4
- package/dist/types/transport/http-client.d.ts +7 -0
- package/dist/types/types/activity/types.d.ts +12 -5
- package/dist/types/utils/multipart-upload.d.ts +2 -0
- package/dist/types/utils/request-helpers.d.ts +23 -3
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { encodePathSegment } from '../utils/request-helpers.js';
|
|
1
2
|
const BASE_URI = 'https://api.todoist.com';
|
|
2
3
|
const TODOIST_URI = 'https://todoist.com';
|
|
3
4
|
export const TODOIST_WEB_URI = 'https://app.todoist.com/app';
|
|
@@ -94,48 +95,48 @@ export const ENDPOINT_PRICES = 'prices';
|
|
|
94
95
|
export const ENDPOINT_PRICING = 'pricing';
|
|
95
96
|
// Workspace invitation actions (require invite_code parameter)
|
|
96
97
|
export function getWorkspaceInvitationAcceptEndpoint(inviteCode) {
|
|
97
|
-
return `workspaces/invitations/${inviteCode}/accept`;
|
|
98
|
+
return `workspaces/invitations/${encodePathSegment(inviteCode)}/accept`;
|
|
98
99
|
}
|
|
99
100
|
export function getWorkspaceInvitationRejectEndpoint(inviteCode) {
|
|
100
|
-
return `workspaces/invitations/${inviteCode}/reject`;
|
|
101
|
+
return `workspaces/invitations/${encodePathSegment(inviteCode)}/reject`;
|
|
101
102
|
}
|
|
102
103
|
// Insights endpoints
|
|
103
104
|
export function getProjectInsightsActivityStatsEndpoint(projectId) {
|
|
104
|
-
return `projects/${projectId}/insights/activity_stats`;
|
|
105
|
+
return `projects/${encodePathSegment(projectId)}/insights/activity_stats`;
|
|
105
106
|
}
|
|
106
107
|
export function getProjectInsightsHealthEndpoint(projectId) {
|
|
107
|
-
return `projects/${projectId}/insights/health`;
|
|
108
|
+
return `projects/${encodePathSegment(projectId)}/insights/health`;
|
|
108
109
|
}
|
|
109
110
|
export function getProjectInsightsHealthContextEndpoint(projectId) {
|
|
110
|
-
return `projects/${projectId}/insights/health/context`;
|
|
111
|
+
return `projects/${encodePathSegment(projectId)}/insights/health/context`;
|
|
111
112
|
}
|
|
112
113
|
export function getProjectInsightsProgressEndpoint(projectId) {
|
|
113
|
-
return `projects/${projectId}/insights/progress`;
|
|
114
|
+
return `projects/${encodePathSegment(projectId)}/insights/progress`;
|
|
114
115
|
}
|
|
115
116
|
export function getProjectInsightsHealthAnalyzeEndpoint(projectId) {
|
|
116
|
-
return `projects/${projectId}/insights/health/analyze`;
|
|
117
|
+
return `projects/${encodePathSegment(projectId)}/insights/health/analyze`;
|
|
117
118
|
}
|
|
118
119
|
export function getWorkspaceInsightsEndpoint(workspaceId) {
|
|
119
|
-
return `workspaces/${workspaceId}/insights`;
|
|
120
|
+
return `workspaces/${encodePathSegment(workspaceId)}/insights`;
|
|
120
121
|
}
|
|
121
122
|
// Workspace members
|
|
122
123
|
export const ENDPOINT_WORKSPACE_MEMBERS = 'workspaces/members';
|
|
123
124
|
// Workspace user management (require workspace_id and/or user_id parameters)
|
|
124
125
|
export function getWorkspaceUserTasksEndpoint(workspaceId, userId) {
|
|
125
|
-
return `workspaces/${workspaceId}/users/${userId}/tasks`;
|
|
126
|
+
return `workspaces/${encodePathSegment(workspaceId)}/users/${encodePathSegment(userId)}/tasks`;
|
|
126
127
|
}
|
|
127
128
|
export function getWorkspaceInviteUsersEndpoint(workspaceId) {
|
|
128
|
-
return `workspaces/${workspaceId}/users/invite`;
|
|
129
|
+
return `workspaces/${encodePathSegment(workspaceId)}/users/invite`;
|
|
129
130
|
}
|
|
130
131
|
export function getWorkspaceUserEndpoint(workspaceId, userId) {
|
|
131
|
-
return `workspaces/${workspaceId}/users/${userId}`;
|
|
132
|
+
return `workspaces/${encodePathSegment(workspaceId)}/users/${encodePathSegment(userId)}`;
|
|
132
133
|
}
|
|
133
134
|
// Workspace projects (require workspace_id parameter)
|
|
134
135
|
export function getWorkspaceActiveProjectsEndpoint(workspaceId) {
|
|
135
|
-
return `workspaces/${workspaceId}/projects/active`;
|
|
136
|
+
return `workspaces/${encodePathSegment(workspaceId)}/projects/active`;
|
|
136
137
|
}
|
|
137
138
|
export function getWorkspaceArchivedProjectsEndpoint(workspaceId) {
|
|
138
|
-
return `workspaces/${workspaceId}/projects/archived`;
|
|
139
|
+
return `workspaces/${encodePathSegment(workspaceId)}/projects/archived`;
|
|
139
140
|
}
|
|
140
141
|
// App management endpoints (dev:app_console scope)
|
|
141
142
|
export const ENDPOINT_REST_APPS = 'apps';
|
|
@@ -147,44 +148,45 @@ export const ENDPOINT_REST_USER_AUTHORIZATIONS_DELETE = 'user/authorizations/del
|
|
|
147
148
|
/** Available app icon sizes (small 60x60, medium 300x300, large 600x600). */
|
|
148
149
|
export const APP_ICON_SIZES = ['small', 'medium', 'large'];
|
|
149
150
|
export function getAppEndpoint(appId) {
|
|
150
|
-
return `apps/${appId}`;
|
|
151
|
+
return `apps/${encodePathSegment(appId)}`;
|
|
151
152
|
}
|
|
152
153
|
export function getAppSecretsEndpoint(appId) {
|
|
153
|
-
return `apps/${appId}/secrets`;
|
|
154
|
+
return `apps/${encodePathSegment(appId)}/secrets`;
|
|
154
155
|
}
|
|
155
156
|
export function getAppClientSecretEndpoint(appId) {
|
|
156
|
-
return `apps/${appId}/client_secret`;
|
|
157
|
+
return `apps/${encodePathSegment(appId)}/client_secret`;
|
|
157
158
|
}
|
|
158
159
|
export function getAppTokensEndpoint(appId) {
|
|
159
|
-
return `apps/${appId}/tokens`;
|
|
160
|
+
return `apps/${encodePathSegment(appId)}/tokens`;
|
|
160
161
|
}
|
|
161
162
|
export function getAppTestTokenEndpoint(appId) {
|
|
162
|
-
return `apps/${appId}/test_token`;
|
|
163
|
+
return `apps/${encodePathSegment(appId)}/test_token`;
|
|
163
164
|
}
|
|
164
165
|
export function getAppDistributionTokenEndpoint(appId) {
|
|
165
|
-
return `apps/${appId}/distribution_token`;
|
|
166
|
+
return `apps/${encodePathSegment(appId)}/distribution_token`;
|
|
166
167
|
}
|
|
167
168
|
export function getAppVerificationTokenEndpoint(appId) {
|
|
168
|
-
return `apps/${appId}/verification_token`;
|
|
169
|
+
return `apps/${encodePathSegment(appId)}/verification_token`;
|
|
169
170
|
}
|
|
170
171
|
export function getAppWebhookEndpoint(appId) {
|
|
171
|
-
return `apps/${appId}/webhook`;
|
|
172
|
+
return `apps/${encodePathSegment(appId)}/webhook`;
|
|
172
173
|
}
|
|
173
174
|
export function getAppIconEndpoint(appId, size) {
|
|
174
|
-
|
|
175
|
+
const base = `apps/${encodePathSegment(appId)}/icon`;
|
|
176
|
+
return size === 'medium' ? base : `${base}/${size}`;
|
|
175
177
|
}
|
|
176
178
|
export function getAppInstallationEndpoint(installationId) {
|
|
177
|
-
return `apps/installations/${installationId}`;
|
|
179
|
+
return `apps/installations/${encodePathSegment(installationId)}`;
|
|
178
180
|
}
|
|
179
181
|
export function getAppByDistributionTokenEndpoint(distributionToken) {
|
|
180
|
-
return `apps/by_distribution_token/${distributionToken}`;
|
|
182
|
+
return `apps/by_distribution_token/${encodePathSegment(distributionToken)}`;
|
|
181
183
|
}
|
|
182
184
|
export function getUiExtensionEndpoint(uiExtensionId) {
|
|
183
|
-
return `apps/ui_extensions/${uiExtensionId}`;
|
|
185
|
+
return `apps/ui_extensions/${encodePathSegment(uiExtensionId)}`;
|
|
184
186
|
}
|
|
185
187
|
export function getUiExtensionIconEndpoint(uiExtensionId) {
|
|
186
|
-
return `apps/ui_extensions/${uiExtensionId}/icon`;
|
|
188
|
+
return `apps/ui_extensions/${encodePathSegment(uiExtensionId)}/icon`;
|
|
187
189
|
}
|
|
188
190
|
export function getUiExtensionsByIntegrationEndpoint(integrationId) {
|
|
189
|
-
return `apps/ui_extensions/integration/${integrationId}`;
|
|
191
|
+
return `apps/ui_extensions/integration/${encodePathSegment(integrationId)}`;
|
|
190
192
|
}
|
|
@@ -43,6 +43,18 @@ function getTodoistRequestError(args) {
|
|
|
43
43
|
}
|
|
44
44
|
return requestError;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Normalises anything thrown while making an API request into the error a
|
|
48
|
+
* caller should see: a `TodoistRequestError` (carrying the HTTP status and
|
|
49
|
+
* response body when there is one) reported against `originalStack`, or a
|
|
50
|
+
* generic `Error` when the thrown value isn't an `Error` at all.
|
|
51
|
+
*/
|
|
52
|
+
export function toTodoistRequestError(error, originalStack) {
|
|
53
|
+
if (!(error instanceof Error)) {
|
|
54
|
+
return new Error('An unknown error occurred during the request');
|
|
55
|
+
}
|
|
56
|
+
return getTodoistRequestError({ error, originalStack });
|
|
57
|
+
}
|
|
46
58
|
function getRequestConfiguration(args) {
|
|
47
59
|
const { baseURL, apiToken, requestId, customHeaders } = args;
|
|
48
60
|
const authHeader = apiToken ? { Authorization: getAuthHeader(apiToken) } : undefined;
|
|
@@ -121,9 +133,6 @@ export async function request(args) {
|
|
|
121
133
|
return Object.assign(Object.assign({}, response), { data: convertedData });
|
|
122
134
|
}
|
|
123
135
|
catch (error) {
|
|
124
|
-
|
|
125
|
-
throw new Error('An unknown error occurred during the request');
|
|
126
|
-
}
|
|
127
|
-
throw getTodoistRequestError({ error, originalStack });
|
|
136
|
+
throw toTodoistRequestError(error, originalStack);
|
|
128
137
|
}
|
|
129
138
|
}
|
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Known activity object types, using modern names (`task`, `comment`) rather
|
|
4
|
+
* than legacy API names (`item`, `note`).
|
|
5
|
+
*/
|
|
6
|
+
export const ACTIVITY_OBJECT_TYPES = ['task', 'comment', 'project'];
|
|
7
|
+
/**
|
|
8
|
+
* Known activity event types.
|
|
9
|
+
*/
|
|
10
|
+
export const ACTIVITY_EVENT_TYPES = [
|
|
11
|
+
'added',
|
|
12
|
+
'updated',
|
|
13
|
+
'deleted',
|
|
14
|
+
'completed',
|
|
15
|
+
'uncompleted',
|
|
16
|
+
'archived',
|
|
17
|
+
'unarchived',
|
|
18
|
+
'shared',
|
|
19
|
+
'left',
|
|
20
|
+
'moved',
|
|
21
|
+
];
|
|
2
22
|
/**
|
|
3
23
|
* Flexible object containing event-specific data.
|
|
4
24
|
* Uses z.record to accept any properties for forward compatibility.
|
|
@@ -20,6 +20,7 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
|
|
|
20
20
|
};
|
|
21
21
|
import { v4 as uuidv4 } from 'uuid';
|
|
22
22
|
import { fetchWithRetry } from '../transport/fetch-with-retry.js';
|
|
23
|
+
import { toTodoistRequestError } from '../transport/http-client.js';
|
|
23
24
|
import { camelCaseKeys } from './case-conversion.js';
|
|
24
25
|
/**
|
|
25
26
|
* Helper function to determine content-type from filename extension.
|
|
@@ -170,6 +171,8 @@ function buildMultipartBody(args) {
|
|
|
170
171
|
* @param additionalFields - Additional form fields to include (e.g., project_id, workspace_id)
|
|
171
172
|
* @param requestId - Optional request ID for idempotency
|
|
172
173
|
* @returns The response data from the server
|
|
174
|
+
* @throws TodoistRequestError if the upload fails, carrying the HTTP status and
|
|
175
|
+
* the server's error body like any other API request.
|
|
173
176
|
*
|
|
174
177
|
* @example
|
|
175
178
|
* ```typescript
|
|
@@ -197,6 +200,8 @@ function buildMultipartBody(args) {
|
|
|
197
200
|
*/
|
|
198
201
|
export async function uploadMultipartFile(args) {
|
|
199
202
|
const { baseUrl, authToken, endpoint, file, fileName, additionalFields, requestId, customFetch, } = args;
|
|
203
|
+
// Capture original stack for better error reporting
|
|
204
|
+
const originalStack = new Error();
|
|
200
205
|
// Build the full URL
|
|
201
206
|
const url = `${baseUrl}${endpoint}`;
|
|
202
207
|
let body;
|
|
@@ -251,7 +256,15 @@ export async function uploadMultipartFile(args) {
|
|
|
251
256
|
headers['Content-Type'] = multipart.contentType;
|
|
252
257
|
body = multipart.body;
|
|
253
258
|
// Make the request using fetch
|
|
254
|
-
|
|
255
|
-
|
|
259
|
+
let response;
|
|
260
|
+
try {
|
|
261
|
+
response = await fetchWithRetry(Object.assign(Object.assign({ url, options: Object.assign({ method: 'POST', body,
|
|
262
|
+
headers, timeout: 30000 }, (multipart.isStreamed ? { duplex: 'half' } : {})) }, (multipart.isStreamed ? { retryConfig: { retries: 0 } } : {})), { customFetch }));
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
// `fetchWithRetry` throws a plain `Error` for non-2xx responses; convert
|
|
266
|
+
// it so callers get the same error type as every other API request.
|
|
267
|
+
throw toTodoistRequestError(error, originalStack);
|
|
268
|
+
}
|
|
256
269
|
return camelCaseKeys(response.data);
|
|
257
270
|
}
|
|
@@ -1,10 +1,36 @@
|
|
|
1
|
+
import { TodoistArgumentError } from '../types/errors.js';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
3
|
+
* Encodes a caller-supplied value (an ID, a name) for use as one URL path segment.
|
|
4
|
+
*
|
|
5
|
+
* Without this, a value such as `../projects/123` would be joined into the path
|
|
6
|
+
* as-is and the URL would resolve to a different endpoint. `encodeURIComponent`
|
|
7
|
+
* escapes `/`, `?`, `#` and `%`, but leaves `.` alone, so the dot segments `.`
|
|
8
|
+
* and `..` (and the empty segment) are rejected outright. `,` is kept literal,
|
|
9
|
+
* as it is a valid path character and joins lists of IDs.
|
|
10
|
+
*
|
|
11
|
+
* @param segment The value to encode.
|
|
12
|
+
* @returns The encoded segment.
|
|
13
|
+
* @throws {TodoistArgumentError} When the value is empty, `.` or `..`.
|
|
14
|
+
*/
|
|
15
|
+
export function encodePathSegment(segment) {
|
|
16
|
+
if (segment === '' || segment === '.' || segment === '..') {
|
|
17
|
+
throw new TodoistArgumentError(`Invalid path segment: "${segment}"`);
|
|
18
|
+
}
|
|
19
|
+
return encodeURIComponent(segment).replace(/%2C/gi, ',');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Joins an endpoint with path segments using `/` separator.
|
|
23
|
+
*
|
|
24
|
+
* The endpoint is used as-is (it may contain `/`); every following segment is
|
|
25
|
+
* encoded with {@link encodePathSegment}, so an ID can never address another
|
|
26
|
+
* resource.
|
|
27
|
+
*
|
|
28
|
+
* @param endpoint The base endpoint, e.g. `tasks`.
|
|
29
|
+
* @param segments Path segments to append, e.g. an ID and an action.
|
|
4
30
|
* @returns A joined path.
|
|
5
31
|
*/
|
|
6
|
-
export function generatePath(...segments) {
|
|
7
|
-
return segments.join('/');
|
|
32
|
+
export function generatePath(endpoint, ...segments) {
|
|
33
|
+
return [endpoint, ...segments.map(encodePathSegment)].join('/');
|
|
8
34
|
}
|
|
9
35
|
/**
|
|
10
36
|
* Returns `fn(value)` spread into an object if `value` is defined, otherwise an empty object.
|
|
@@ -11,6 +11,13 @@ type RequestArgs = {
|
|
|
11
11
|
customFetch?: CustomFetch;
|
|
12
12
|
};
|
|
13
13
|
export declare function paramsSerializer(params: Record<string, unknown>): string;
|
|
14
|
+
/**
|
|
15
|
+
* Normalises anything thrown while making an API request into the error a
|
|
16
|
+
* caller should see: a `TodoistRequestError` (carrying the HTTP status and
|
|
17
|
+
* response body when there is one) reported against `originalStack`, or a
|
|
18
|
+
* generic `Error` when the thrown value isn't an `Error` at all.
|
|
19
|
+
*/
|
|
20
|
+
export declare function toTodoistRequestError(error: unknown, originalStack?: Error): Error;
|
|
14
21
|
export declare function isSuccess(response: HttpResponse): boolean;
|
|
15
22
|
export declare function request<T>(args: RequestArgs): Promise<HttpResponse<T>>;
|
|
16
23
|
export {};
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Known activity object types, using modern names (`task`, `comment`) rather
|
|
4
|
+
* than legacy API names (`item`, `note`).
|
|
5
|
+
*/
|
|
6
|
+
export declare const ACTIVITY_OBJECT_TYPES: readonly ["task", "comment", "project"];
|
|
7
|
+
/**
|
|
8
|
+
* Known activity event types.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ACTIVITY_EVENT_TYPES: readonly ["added", "updated", "deleted", "completed", "uncompleted", "archived", "unarchived", "shared", "left", "moved"];
|
|
2
11
|
/**
|
|
3
12
|
* Type hints for known object types. Accepts any string for forward compatibility.
|
|
4
13
|
*/
|
|
5
|
-
export type ActivityObjectType =
|
|
14
|
+
export type ActivityObjectType = (typeof ACTIVITY_OBJECT_TYPES)[number] | (string & Record<string, never>);
|
|
6
15
|
/**
|
|
7
16
|
* Type hints for known event types. Accepts any string for forward compatibility.
|
|
8
17
|
*/
|
|
9
|
-
export type ActivityEventType =
|
|
10
|
-
type ModernActivityObjectType = 'task' | 'comment' | 'project' | (string & Record<string, never>);
|
|
18
|
+
export type ActivityEventType = (typeof ACTIVITY_EVENT_TYPES)[number] | (string & Record<string, never>);
|
|
11
19
|
/**
|
|
12
20
|
* Combined object:event filter string for the `objectEventTypes` parameter of `getActivityLogs`.
|
|
13
21
|
*
|
|
@@ -21,7 +29,7 @@ type ModernActivityObjectType = 'task' | 'comment' | 'project' | (string & Recor
|
|
|
21
29
|
* to be passed for forward compatibility, while still providing autocomplete for
|
|
22
30
|
* the known combinations above.
|
|
23
31
|
*/
|
|
24
|
-
export type ActivityObjectEventType = `${
|
|
32
|
+
export type ActivityObjectEventType = `${ActivityObjectType}:${ActivityEventType}` | `${ActivityObjectType}:` | `:${ActivityEventType}` | (string & Record<string, never>);
|
|
25
33
|
/**
|
|
26
34
|
* Flexible object containing event-specific data.
|
|
27
35
|
* Uses z.record to accept any properties for forward compatibility.
|
|
@@ -46,4 +54,3 @@ export declare const ActivityEventSchema: z.ZodObject<{
|
|
|
46
54
|
* Represents an activity log event in Todoist.
|
|
47
55
|
*/
|
|
48
56
|
export type ActivityEvent = z.infer<typeof ActivityEventSchema>;
|
|
49
|
-
export {};
|
|
@@ -25,6 +25,8 @@ type UploadMultipartFileArgs = {
|
|
|
25
25
|
* @param additionalFields - Additional form fields to include (e.g., project_id, workspace_id)
|
|
26
26
|
* @param requestId - Optional request ID for idempotency
|
|
27
27
|
* @returns The response data from the server
|
|
28
|
+
* @throws TodoistRequestError if the upload fails, carrying the HTTP status and
|
|
29
|
+
* the server's error body like any other API request.
|
|
28
30
|
*
|
|
29
31
|
* @example
|
|
30
32
|
* ```typescript
|
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Encodes a caller-supplied value (an ID, a name) for use as one URL path segment.
|
|
3
|
+
*
|
|
4
|
+
* Without this, a value such as `../projects/123` would be joined into the path
|
|
5
|
+
* as-is and the URL would resolve to a different endpoint. `encodeURIComponent`
|
|
6
|
+
* escapes `/`, `?`, `#` and `%`, but leaves `.` alone, so the dot segments `.`
|
|
7
|
+
* and `..` (and the empty segment) are rejected outright. `,` is kept literal,
|
|
8
|
+
* as it is a valid path character and joins lists of IDs.
|
|
9
|
+
*
|
|
10
|
+
* @param segment The value to encode.
|
|
11
|
+
* @returns The encoded segment.
|
|
12
|
+
* @throws {TodoistArgumentError} When the value is empty, `.` or `..`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function encodePathSegment(segment: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Joins an endpoint with path segments using `/` separator.
|
|
17
|
+
*
|
|
18
|
+
* The endpoint is used as-is (it may contain `/`); every following segment is
|
|
19
|
+
* encoded with {@link encodePathSegment}, so an ID can never address another
|
|
20
|
+
* resource.
|
|
21
|
+
*
|
|
22
|
+
* @param endpoint The base endpoint, e.g. `tasks`.
|
|
23
|
+
* @param segments Path segments to append, e.g. an ID and an action.
|
|
4
24
|
* @returns A joined path.
|
|
5
25
|
*/
|
|
6
|
-
export declare function generatePath(...segments: string[]): string;
|
|
26
|
+
export declare function generatePath(endpoint: string, ...segments: string[]): string;
|
|
7
27
|
/**
|
|
8
28
|
* Returns `fn(value)` spread into an object if `value` is defined, otherwise an empty object.
|
|
9
29
|
*
|
package/package.json
CHANGED