@doist/todoist-sdk 15.5.0 → 15.7.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 +22 -0
- package/dist/esm/clients/activity-client.js +1 -0
- package/dist/esm/clients/app-client.js +25 -0
- package/dist/esm/clients/backup-client.js +5 -1
- package/dist/esm/clients/base-client.js +3 -1
- package/dist/esm/clients/billing-client.js +12 -0
- package/dist/esm/clients/comment-client.js +5 -0
- package/dist/esm/clients/email-client.js +2 -0
- package/dist/esm/clients/folder-client.js +5 -0
- package/dist/esm/clients/id-mapping-client.js +2 -0
- package/dist/esm/clients/insights-client.js +6 -0
- package/dist/esm/clients/label-client.js +9 -0
- package/dist/esm/clients/productivity-client.js +1 -0
- package/dist/esm/clients/project-client.js +16 -0
- package/dist/esm/clients/reminder-client.js +10 -0
- package/dist/esm/clients/section-client.js +8 -0
- package/dist/esm/clients/task-client.js +14 -0
- package/dist/esm/clients/template-client.js +5 -0
- package/dist/esm/clients/ui-extension-client.js +7 -0
- package/dist/esm/clients/upload-client.js +2 -0
- package/dist/esm/clients/workspace-client.js +22 -0
- package/dist/esm/todoist-api.js +4 -0
- package/dist/esm/transport/http-client.js +3 -1
- package/dist/esm/transport/sync-request.js +2 -1
- package/dist/esm/utils/colors.js +1 -2
- package/dist/esm/utils/headers.js +29 -0
- package/dist/esm/utils/multipart-upload.js +7 -10
- package/dist/types/clients/base-client.d.ts +3 -2
- package/dist/types/todoist-api.d.ts +9 -1
- package/dist/types/transport/http-client.d.ts +2 -2
- package/dist/types/transport/sync-request.d.ts +3 -2
- package/dist/types/types/http.d.ts +6 -0
- package/dist/types/utils/colors.d.ts +1 -2
- package/dist/types/utils/headers.d.ts +13 -0
- package/dist/types/utils/multipart-upload.d.ts +3 -1
- package/package.json +2 -2
|
@@ -3,6 +3,7 @@ import { API_BASE_URI } from '../consts/endpoints.js';
|
|
|
3
3
|
import { TodoistRequestError } from '../types/errors.js';
|
|
4
4
|
import { isHttpError, isNetworkError } from '../types/http.js';
|
|
5
5
|
import { camelCaseKeys, snakeCaseKeys } from '../utils/case-conversion.js';
|
|
6
|
+
import { mergeHeaders, resolveCustomHeaders } from '../utils/headers.js';
|
|
6
7
|
import { fetchWithRetry } from './fetch-with-retry.js';
|
|
7
8
|
export function paramsSerializer(params) {
|
|
8
9
|
const qs = new URLSearchParams();
|
|
@@ -59,7 +60,8 @@ function getRequestConfiguration(args) {
|
|
|
59
60
|
const { baseURL, apiToken, requestId, customHeaders } = args;
|
|
60
61
|
const authHeader = apiToken ? { Authorization: getAuthHeader(apiToken) } : undefined;
|
|
61
62
|
const requestIdHeader = requestId ? { 'X-Request-Id': requestId } : undefined;
|
|
62
|
-
|
|
63
|
+
// Custom headers sit below auth and request id so they can never replace them.
|
|
64
|
+
const headers = mergeHeaders(defaultHeaders, resolveCustomHeaders(customHeaders), authHeader, requestIdHeader);
|
|
63
65
|
return { baseURL, headers };
|
|
64
66
|
}
|
|
65
67
|
function getHttpClientConfig(args) {
|
|
@@ -62,7 +62,7 @@ function preprocessSyncCommands(commands) {
|
|
|
62
62
|
/**
|
|
63
63
|
* Makes a request to the Sync API and handles error checking.
|
|
64
64
|
*
|
|
65
|
-
* @param ctx - HTTP identity (auth token, sync API base URI, optional custom fetch).
|
|
65
|
+
* @param ctx - HTTP identity (auth token, sync API base URI, optional custom fetch and headers).
|
|
66
66
|
* @param syncRequest - The sync request payload.
|
|
67
67
|
* @param options - Optional request identifier and commands flag.
|
|
68
68
|
* @returns The sync response data.
|
|
@@ -79,6 +79,7 @@ export async function performSyncRequest(ctx, syncRequest, options = {}) {
|
|
|
79
79
|
relativePath: ENDPOINT_SYNC,
|
|
80
80
|
apiToken: ctx.authToken,
|
|
81
81
|
customFetch: ctx.customFetch,
|
|
82
|
+
customHeaders: ctx.customHeaders,
|
|
82
83
|
payload: processedRequest,
|
|
83
84
|
requestId: requestId,
|
|
84
85
|
hasSyncCommands: hasSyncCommands,
|
package/dist/esm/utils/colors.js
CHANGED
|
@@ -101,8 +101,7 @@ export const taupe = {
|
|
|
101
101
|
};
|
|
102
102
|
/**
|
|
103
103
|
* Every color, in the order the apps show them. The keys mirror `TodoistColor` from
|
|
104
|
-
* `@doist/sdk-kmp/shared-todoist-all
|
|
105
|
-
* the SDK's own.
|
|
104
|
+
* `@doist/sdk-kmp/shared-todoist-all`; the display names and hex values are the SDK's own.
|
|
106
105
|
*/
|
|
107
106
|
export const colors = [
|
|
108
107
|
berryRed,
|
|
@@ -11,3 +11,32 @@ export function headersToRecord(headers) {
|
|
|
11
11
|
});
|
|
12
12
|
return result;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Merges header records left to right. HTTP header names are case-insensitive,
|
|
16
|
+
* so a later record replaces any earlier header with the same name whatever
|
|
17
|
+
* its case. Without this, a caller's `authorization` would sit alongside the
|
|
18
|
+
* SDK's `Authorization` and `fetch` would join the two into one value.
|
|
19
|
+
*/
|
|
20
|
+
export function mergeHeaders(...sources) {
|
|
21
|
+
const merged = {};
|
|
22
|
+
for (const source of sources) {
|
|
23
|
+
if (!source)
|
|
24
|
+
continue;
|
|
25
|
+
for (const [name, value] of Object.entries(source)) {
|
|
26
|
+
const lowerName = name.toLowerCase();
|
|
27
|
+
for (const existingName of Object.keys(merged)) {
|
|
28
|
+
if (existingName.toLowerCase() === lowerName)
|
|
29
|
+
delete merged[existingName];
|
|
30
|
+
}
|
|
31
|
+
merged[name] = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return merged;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Turns the `headers` option into the record to send with one request, calling
|
|
38
|
+
* it when it is a function so per-request values are fresh.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveCustomHeaders(headers) {
|
|
41
|
+
return typeof headers === 'function' ? headers() : headers;
|
|
42
|
+
}
|
|
@@ -22,6 +22,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
22
22
|
import { fetchWithRetry } from '../transport/fetch-with-retry.js';
|
|
23
23
|
import { toTodoistRequestError } from '../transport/http-client.js';
|
|
24
24
|
import { camelCaseKeys } from './case-conversion.js';
|
|
25
|
+
import { mergeHeaders, resolveCustomHeaders } from './headers.js';
|
|
25
26
|
/**
|
|
26
27
|
* Helper function to determine content-type from filename extension.
|
|
27
28
|
* @param fileName - The filename to analyze
|
|
@@ -199,18 +200,12 @@ function buildMultipartBody(args) {
|
|
|
199
200
|
* ```
|
|
200
201
|
*/
|
|
201
202
|
export async function uploadMultipartFile(args) {
|
|
202
|
-
const { baseUrl, authToken, endpoint, file, fileName, additionalFields, requestId, customFetch, } = args;
|
|
203
|
+
const { baseUrl, authToken, endpoint, file, fileName, additionalFields, requestId, customHeaders, customFetch, } = args;
|
|
203
204
|
// Capture original stack for better error reporting
|
|
204
205
|
const originalStack = new Error();
|
|
205
206
|
// Build the full URL
|
|
206
207
|
const url = `${baseUrl}${endpoint}`;
|
|
207
208
|
let body;
|
|
208
|
-
const headers = {
|
|
209
|
-
Authorization: `Bearer ${authToken}`,
|
|
210
|
-
};
|
|
211
|
-
if (requestId) {
|
|
212
|
-
headers['X-Request-Id'] = requestId;
|
|
213
|
-
}
|
|
214
209
|
// Resolve the file to something every `fetch` accepts as a body part: a
|
|
215
210
|
// Blob where the size is known up front, or a Node stream that gets piped
|
|
216
211
|
// through untouched.
|
|
@@ -251,9 +246,11 @@ export async function uploadMultipartFile(args) {
|
|
|
251
246
|
getContentTypeFromFileName(resolvedFileName),
|
|
252
247
|
additionalFields,
|
|
253
248
|
});
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
249
|
+
// Custom headers come first so they can never replace the SDK's own.
|
|
250
|
+
const headers = mergeHeaders(resolveCustomHeaders(customHeaders), Object.assign(Object.assign({ Authorization: `Bearer ${authToken}` }, (requestId ? { 'X-Request-Id': requestId } : {})), {
|
|
251
|
+
// The boundary is ours, so Content-Type has to be set explicitly —
|
|
252
|
+
// `fetch` only fills it in for a `FormData` body.
|
|
253
|
+
'Content-Type': multipart.contentType }));
|
|
257
254
|
body = multipart.body;
|
|
258
255
|
// Make the request using fetch
|
|
259
256
|
let response;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { SyncRequestContext } from '../transport/sync-request.js';
|
|
2
|
-
import type { CustomFetch } from '../types/http.js';
|
|
2
|
+
import type { CustomFetch, CustomHeaders } from '../types/http.js';
|
|
3
3
|
/**
|
|
4
4
|
* Internal base class for every domain sub-client (tasks, projects, …).
|
|
5
5
|
*
|
|
6
6
|
* Not exported from the package — sub-clients are an implementation detail
|
|
7
|
-
* of `TodoistApi`. Holds the
|
|
7
|
+
* of `TodoistApi`. Holds the pieces of HTTP identity every sub-client
|
|
8
8
|
* needs and exposes a typed accessor to feed the Sync API helper.
|
|
9
9
|
*
|
|
10
10
|
* The constructor takes a {@link SyncRequestContext} — the same record
|
|
@@ -17,6 +17,7 @@ export declare abstract class BaseClient {
|
|
|
17
17
|
protected readonly syncApiBase: string;
|
|
18
18
|
protected readonly apiRootBase: string;
|
|
19
19
|
protected readonly customFetch?: CustomFetch;
|
|
20
|
+
protected readonly customHeaders?: CustomHeaders;
|
|
20
21
|
constructor(context: SyncRequestContext);
|
|
21
22
|
protected get syncContext(): SyncRequestContext;
|
|
22
23
|
}
|
|
@@ -6,7 +6,7 @@ import type { BillingPortalUrlResponse, CancelPlanArgs, CheckoutSessionResponse,
|
|
|
6
6
|
import { Attachment, Comment, AddCommentArgs, UpdateCommentArgs, GetProjectCommentsArgs, GetTaskCommentsArgs, GetCommentsResponse } from './types/comments/index.js';
|
|
7
7
|
import { GetOrCreateEmailArgs, GetOrCreateEmailResponse, DisableEmailArgs } from './types/emails/index.js';
|
|
8
8
|
import { GetFoldersArgs, GetFoldersResponse, AddFolderArgs, UpdateFolderArgs } from './types/folders/index.js';
|
|
9
|
-
import { CustomFetch, FileResponse } from './types/http.js';
|
|
9
|
+
import { CustomFetch, CustomHeaders, FileResponse } from './types/http.js';
|
|
10
10
|
import { IdMapping, MovedId, GetIdMappingsArgs, GetMovedIdsArgs } from './types/id-mappings/index.js';
|
|
11
11
|
import { ProjectActivityStats, ProjectHealth, ProjectHealthContext, ProjectProgress, WorkspaceInsights, GetProjectActivityStatsArgs, GetWorkspaceInsightsArgs } from './types/insights/index.js';
|
|
12
12
|
import { Label, AddLabelArgs, UpdateLabelArgs, GetLabelsArgs, SearchLabelsArgs, GetLabelsResponse, GetSharedLabelsArgs, GetSharedLabelsResponse, RenameSharedLabelArgs, RemoveSharedLabelArgs } from './types/labels/index.js';
|
|
@@ -55,11 +55,19 @@ export type TodoistApiOptions = {
|
|
|
55
55
|
* Optional custom fetch function for alternative HTTP clients (e.g., Obsidian's requestUrl, React Native fetch)
|
|
56
56
|
*/
|
|
57
57
|
customFetch?: CustomFetch;
|
|
58
|
+
/**
|
|
59
|
+
* Optional headers sent with every API request, e.g. `{ 'Doist-Platform': 'cli' }` for client
|
|
60
|
+
* attribution. Pass a function instead to compute them per request, e.g. a fresh request ID;
|
|
61
|
+
* it is called once per request and retries reuse its result. Cannot replace `Authorization`
|
|
62
|
+
* or `X-Request-Id`. Not sent on attachment downloads, which redirect to a third-party CDN.
|
|
63
|
+
*/
|
|
64
|
+
headers?: CustomHeaders;
|
|
58
65
|
};
|
|
59
66
|
export declare class TodoistApi {
|
|
60
67
|
private authToken;
|
|
61
68
|
private syncApiBase;
|
|
62
69
|
private customFetch?;
|
|
70
|
+
private customHeaders?;
|
|
63
71
|
private readonly taskClient;
|
|
64
72
|
private readonly projectClient;
|
|
65
73
|
private readonly sectionClient;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CustomFetch, HttpMethod, HttpResponse } from '../types/http.js';
|
|
1
|
+
import type { CustomFetch, CustomHeaders, HttpMethod, HttpResponse } from '../types/http.js';
|
|
2
2
|
type RequestArgs = {
|
|
3
3
|
httpMethod: HttpMethod;
|
|
4
4
|
baseUri: string;
|
|
@@ -7,7 +7,7 @@ type RequestArgs = {
|
|
|
7
7
|
payload?: Record<string, unknown>;
|
|
8
8
|
requestId?: string;
|
|
9
9
|
hasSyncCommands?: boolean;
|
|
10
|
-
customHeaders?:
|
|
10
|
+
customHeaders?: CustomHeaders;
|
|
11
11
|
customFetch?: CustomFetch;
|
|
12
12
|
};
|
|
13
13
|
export declare function paramsSerializer(params: Record<string, unknown>): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CustomFetch } from '../types/http.js';
|
|
1
|
+
import type { CustomFetch, CustomHeaders } from '../types/http.js';
|
|
2
2
|
import { type SyncRequest, type SyncResponse } from '../types/sync/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Shared context needed to issue Sync API requests.
|
|
@@ -11,11 +11,12 @@ export type SyncRequestContext = {
|
|
|
11
11
|
authToken: string;
|
|
12
12
|
syncApiBase: string;
|
|
13
13
|
customFetch?: CustomFetch;
|
|
14
|
+
customHeaders?: CustomHeaders;
|
|
14
15
|
};
|
|
15
16
|
/**
|
|
16
17
|
* Makes a request to the Sync API and handles error checking.
|
|
17
18
|
*
|
|
18
|
-
* @param ctx - HTTP identity (auth token, sync API base URI, optional custom fetch).
|
|
19
|
+
* @param ctx - HTTP identity (auth token, sync API base URI, optional custom fetch and headers).
|
|
19
20
|
* @param syncRequest - The sync request payload.
|
|
20
21
|
* @param options - Optional request identifier and commands flag.
|
|
21
22
|
* @returns The sync response data.
|
|
@@ -83,6 +83,12 @@ export type CustomFetchResponse = {
|
|
|
83
83
|
json(): Promise<unknown>;
|
|
84
84
|
arrayBuffer?: () => Promise<ArrayBuffer>;
|
|
85
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* Extra headers sent with every API request: either a fixed record, or a function
|
|
88
|
+
* the SDK calls once per request (retries reuse its result) for values that change
|
|
89
|
+
* between requests, such as a per-request ID or the command currently running.
|
|
90
|
+
*/
|
|
91
|
+
export type CustomHeaders = Record<string, string> | (() => Record<string, string>);
|
|
86
92
|
/**
|
|
87
93
|
* Custom fetch function type for alternative HTTP clients
|
|
88
94
|
*/
|
|
@@ -101,8 +101,7 @@ export declare const taupe: {
|
|
|
101
101
|
};
|
|
102
102
|
/**
|
|
103
103
|
* Every color, in the order the apps show them. The keys mirror `TodoistColor` from
|
|
104
|
-
* `@doist/sdk-kmp/shared-todoist-all
|
|
105
|
-
* the SDK's own.
|
|
104
|
+
* `@doist/sdk-kmp/shared-todoist-all`; the display names and hex values are the SDK's own.
|
|
106
105
|
*/
|
|
107
106
|
export declare const colors: readonly [{
|
|
108
107
|
readonly key: "berry_red";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CustomHeaders } from '../types/http.js';
|
|
1
2
|
/**
|
|
2
3
|
* Converts a native Fetch `Headers` object into a plain `Record<string, string>`.
|
|
3
4
|
* Used when wrapping a native `fetch` response into the SDK's `FileResponse`
|
|
@@ -5,3 +6,15 @@
|
|
|
5
6
|
* `CustomFetchResponse`).
|
|
6
7
|
*/
|
|
7
8
|
export declare function headersToRecord(headers: Headers): Record<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* Merges header records left to right. HTTP header names are case-insensitive,
|
|
11
|
+
* so a later record replaces any earlier header with the same name whatever
|
|
12
|
+
* its case. Without this, a caller's `authorization` would sit alongside the
|
|
13
|
+
* SDK's `Authorization` and `fetch` would join the two into one value.
|
|
14
|
+
*/
|
|
15
|
+
export declare function mergeHeaders(...sources: (Record<string, string> | undefined)[]): Record<string, string>;
|
|
16
|
+
/**
|
|
17
|
+
* Turns the `headers` option into the record to send with one request, calling
|
|
18
|
+
* it when it is a function so per-request values are fresh.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveCustomHeaders(headers: CustomHeaders | undefined): Record<string, string> | undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CustomFetch } from '../types/http.js';
|
|
1
|
+
import type { CustomFetch, CustomHeaders } from '../types/http.js';
|
|
2
2
|
type UploadMultipartFileArgs = {
|
|
3
3
|
baseUrl: string;
|
|
4
4
|
authToken: string;
|
|
@@ -7,6 +7,8 @@ type UploadMultipartFileArgs = {
|
|
|
7
7
|
fileName?: string;
|
|
8
8
|
additionalFields: Record<string, string | number | boolean>;
|
|
9
9
|
requestId?: string;
|
|
10
|
+
/** Extra headers sent with the upload. Cannot replace `Authorization`, `X-Request-Id` or `Content-Type`. */
|
|
11
|
+
customHeaders?: CustomHeaders;
|
|
10
12
|
customFetch?: CustomFetch;
|
|
11
13
|
};
|
|
12
14
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doist/todoist-sdk",
|
|
3
|
-
"version": "15.
|
|
3
|
+
"version": "15.7.0",
|
|
4
4
|
"description": "The official TypeScript SDK for the Todoist REST API.",
|
|
5
5
|
"author": "Doist developers",
|
|
6
6
|
"repository": "https://github.com/Doist/todoist-sdk-typescript",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"msw": "2.13.2",
|
|
66
66
|
"npm-run-all2": "8.0.4",
|
|
67
67
|
"obsidian": "^1.10.2-1",
|
|
68
|
-
"oxfmt": "0.
|
|
68
|
+
"oxfmt": "0.73.0",
|
|
69
69
|
"oxlint": "1.57.0",
|
|
70
70
|
"rimraf": "6.1.3",
|
|
71
71
|
"semantic-release": "25.0.3",
|