@doist/todoist-api-typescript 6.1.11 → 6.2.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/cjs/test-utils/test-defaults.js +2 -0
- package/dist/cjs/todoist-api.js +11 -3
- package/dist/cjs/types/entities.js +5 -1
- package/dist/cjs/utils/fetch-with-retry.js +22 -3
- package/dist/cjs/utils/uncompletable-helpers.js +60 -0
- package/dist/esm/test-utils/test-defaults.js +2 -0
- package/dist/esm/todoist-api.js +11 -3
- package/dist/esm/types/entities.js +5 -1
- package/dist/esm/utils/fetch-with-retry.js +22 -3
- package/dist/esm/utils/uncompletable-helpers.js +54 -0
- package/dist/types/test-utils/test-defaults.d.ts +1 -0
- package/dist/types/types/entities.d.ts +3 -0
- package/dist/types/types/requests.d.ts +3 -0
- package/dist/types/utils/uncompletable-helpers.d.ts +30 -0
- package/package.json +1 -1
|
@@ -79,6 +79,7 @@ exports.DEFAULT_TASK = {
|
|
|
79
79
|
noteCount: DEFAULT_NOTE_COUNT,
|
|
80
80
|
dayOrder: exports.DEFAULT_ORDER,
|
|
81
81
|
isCollapsed: DEFAULT_IS_COLLAPSED,
|
|
82
|
+
isUncompletable: false,
|
|
82
83
|
url: DEFAULT_TASK_URL,
|
|
83
84
|
};
|
|
84
85
|
exports.INVALID_TASK = Object.assign(Object.assign({}, exports.DEFAULT_TASK), { due: '2020-01-31' });
|
|
@@ -106,6 +107,7 @@ exports.TASK_WITH_OPTIONALS_AS_NULL = {
|
|
|
106
107
|
description: exports.DEFAULT_TASK_DESCRIPTION,
|
|
107
108
|
dayOrder: exports.DEFAULT_ORDER,
|
|
108
109
|
isCollapsed: DEFAULT_IS_COLLAPSED,
|
|
110
|
+
isUncompletable: false,
|
|
109
111
|
noteCount: DEFAULT_NOTE_COUNT,
|
|
110
112
|
url: DEFAULT_TASK_URL,
|
|
111
113
|
};
|
package/dist/cjs/todoist-api.js
CHANGED
|
@@ -7,6 +7,7 @@ const validators_1 = require("./utils/validators");
|
|
|
7
7
|
const url_helpers_1 = require("./utils/url-helpers");
|
|
8
8
|
const multipart_upload_1 = require("./utils/multipart-upload");
|
|
9
9
|
const activity_helpers_1 = require("./utils/activity-helpers");
|
|
10
|
+
const uncompletable_helpers_1 = require("./utils/uncompletable-helpers");
|
|
10
11
|
const zod_1 = require("zod");
|
|
11
12
|
const uuid_1 = require("uuid");
|
|
12
13
|
const types_1 = require("./types");
|
|
@@ -185,13 +186,15 @@ class TodoistApi {
|
|
|
185
186
|
* @returns A promise that resolves to the created task.
|
|
186
187
|
*/
|
|
187
188
|
async addTask(args, requestId) {
|
|
189
|
+
// Process content based on isUncompletable flag
|
|
190
|
+
const processedArgs = Object.assign(Object.assign({}, args), { content: (0, uncompletable_helpers_1.processTaskContent)(args.content, args.isUncompletable) });
|
|
188
191
|
const response = await (0, rest_client_1.request)({
|
|
189
192
|
httpMethod: 'POST',
|
|
190
193
|
baseUri: this.syncApiBase,
|
|
191
194
|
relativePath: endpoints_1.ENDPOINT_REST_TASKS,
|
|
192
195
|
apiToken: this.authToken,
|
|
193
196
|
customFetch: this.customFetch,
|
|
194
|
-
payload:
|
|
197
|
+
payload: processedArgs,
|
|
195
198
|
requestId: requestId,
|
|
196
199
|
});
|
|
197
200
|
return (0, validators_1.validateTask)(response.data);
|
|
@@ -203,13 +206,15 @@ class TodoistApi {
|
|
|
203
206
|
* @returns A promise that resolves to the created task.
|
|
204
207
|
*/
|
|
205
208
|
async quickAddTask(args) {
|
|
209
|
+
// Process text based on isUncompletable flag
|
|
210
|
+
const processedArgs = Object.assign(Object.assign({}, args), { text: (0, uncompletable_helpers_1.processTaskContent)(args.text, args.isUncompletable) });
|
|
206
211
|
const response = await (0, rest_client_1.request)({
|
|
207
212
|
httpMethod: 'POST',
|
|
208
213
|
baseUri: this.syncApiBase,
|
|
209
214
|
relativePath: endpoints_1.ENDPOINT_SYNC_QUICK_ADD,
|
|
210
215
|
apiToken: this.authToken,
|
|
211
216
|
customFetch: this.customFetch,
|
|
212
|
-
payload:
|
|
217
|
+
payload: processedArgs,
|
|
213
218
|
});
|
|
214
219
|
return (0, validators_1.validateTask)(response.data);
|
|
215
220
|
}
|
|
@@ -223,13 +228,16 @@ class TodoistApi {
|
|
|
223
228
|
*/
|
|
224
229
|
async updateTask(id, args, requestId) {
|
|
225
230
|
zod_1.z.string().parse(id);
|
|
231
|
+
// Process content if both content and isUncompletable are provided
|
|
232
|
+
const processedArgs = args.content && args.isUncompletable !== undefined
|
|
233
|
+
? Object.assign(Object.assign({}, args), { content: (0, uncompletable_helpers_1.processTaskContent)(args.content, args.isUncompletable) }) : args;
|
|
226
234
|
const response = await (0, rest_client_1.request)({
|
|
227
235
|
httpMethod: 'POST',
|
|
228
236
|
baseUri: this.syncApiBase,
|
|
229
237
|
relativePath: generatePath(endpoints_1.ENDPOINT_REST_TASKS, id),
|
|
230
238
|
apiToken: this.authToken,
|
|
231
239
|
customFetch: this.customFetch,
|
|
232
|
-
payload:
|
|
240
|
+
payload: processedArgs,
|
|
233
241
|
requestId: requestId,
|
|
234
242
|
});
|
|
235
243
|
return (0, validators_1.validateTask)(response.data);
|
|
@@ -14,6 +14,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
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
15
|
const zod_1 = require("zod");
|
|
16
16
|
const url_helpers_1 = require("../utils/url-helpers");
|
|
17
|
+
const uncompletable_helpers_1 = require("../utils/uncompletable-helpers");
|
|
17
18
|
exports.DueDateSchema = zod_1.z
|
|
18
19
|
.object({
|
|
19
20
|
isRecurring: zod_1.z.boolean(),
|
|
@@ -59,9 +60,12 @@ exports.TaskSchema = zod_1.z
|
|
|
59
60
|
noteCount: zod_1.z.number().int(),
|
|
60
61
|
dayOrder: zod_1.z.number().int(),
|
|
61
62
|
isCollapsed: zod_1.z.boolean(),
|
|
63
|
+
isUncompletable: zod_1.z.boolean().default(false),
|
|
62
64
|
})
|
|
63
65
|
.transform((data) => {
|
|
64
|
-
|
|
66
|
+
// Auto-detect uncompletable status from content prefix
|
|
67
|
+
const isUncompletable = (0, uncompletable_helpers_1.hasUncompletablePrefix)(data.content);
|
|
68
|
+
return Object.assign(Object.assign({}, data), { isUncompletable, url: (0, url_helpers_1.getTaskUrl)(data.id, data.content) });
|
|
65
69
|
});
|
|
66
70
|
/**
|
|
67
71
|
* Base schema for all project types in Todoist.
|
|
@@ -45,7 +45,8 @@ function headersToObject(headers) {
|
|
|
45
45
|
return result;
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
|
-
* Creates an AbortSignal that
|
|
48
|
+
* Creates an AbortSignal that aborts after timeoutMs. Returns the signal and a
|
|
49
|
+
* clear function to cancel the timeout early.
|
|
49
50
|
*/
|
|
50
51
|
function createTimeoutSignal(timeoutMs, existingSignal) {
|
|
51
52
|
const controller = new AbortController();
|
|
@@ -53,6 +54,9 @@ function createTimeoutSignal(timeoutMs, existingSignal) {
|
|
|
53
54
|
const timeoutId = setTimeout(() => {
|
|
54
55
|
controller.abort(new Error(`Request timeout after ${timeoutMs}ms`));
|
|
55
56
|
}, timeoutMs);
|
|
57
|
+
function clear() {
|
|
58
|
+
clearTimeout(timeoutId);
|
|
59
|
+
}
|
|
56
60
|
// If there's an existing signal, forward its abort
|
|
57
61
|
if (existingSignal) {
|
|
58
62
|
if (existingSignal.aborted) {
|
|
@@ -70,7 +74,7 @@ function createTimeoutSignal(timeoutMs, existingSignal) {
|
|
|
70
74
|
controller.signal.addEventListener('abort', () => {
|
|
71
75
|
clearTimeout(timeoutId);
|
|
72
76
|
});
|
|
73
|
-
return controller.signal;
|
|
77
|
+
return { signal: controller.signal, clear };
|
|
74
78
|
}
|
|
75
79
|
/**
|
|
76
80
|
* Converts native fetch Response to CustomFetchResponse for consistency
|
|
@@ -96,11 +100,15 @@ async function fetchWithRetry(args) {
|
|
|
96
100
|
const { timeout, signal: userSignal } = options, fetchOptions = __rest(options, ["timeout", "signal"]);
|
|
97
101
|
let lastError;
|
|
98
102
|
for (let attempt = 0; attempt <= config.retries; attempt++) {
|
|
103
|
+
// Timeout clear function for this attempt (hoisted for catch scope)
|
|
104
|
+
let clearTimeoutFn;
|
|
99
105
|
try {
|
|
100
106
|
// Set up timeout and signal handling
|
|
101
107
|
let requestSignal = userSignal || undefined;
|
|
102
108
|
if (timeout && timeout > 0) {
|
|
103
|
-
|
|
109
|
+
const timeoutResult = createTimeoutSignal(timeout, requestSignal);
|
|
110
|
+
requestSignal = timeoutResult.signal;
|
|
111
|
+
clearTimeoutFn = timeoutResult.clear;
|
|
104
112
|
}
|
|
105
113
|
// Use custom fetch or native fetch
|
|
106
114
|
let fetchResponse;
|
|
@@ -153,6 +161,10 @@ async function fetchWithRetry(args) {
|
|
|
153
161
|
// If JSON parsing fails, return the raw text
|
|
154
162
|
data = responseText;
|
|
155
163
|
}
|
|
164
|
+
// Success – clear pending timeout (if any) so Node can exit promptly
|
|
165
|
+
if (clearTimeoutFn) {
|
|
166
|
+
clearTimeoutFn();
|
|
167
|
+
}
|
|
156
168
|
return {
|
|
157
169
|
data,
|
|
158
170
|
status: fetchResponse.status,
|
|
@@ -170,6 +182,9 @@ async function fetchWithRetry(args) {
|
|
|
170
182
|
const networkError = lastError;
|
|
171
183
|
networkError.isNetworkError = true;
|
|
172
184
|
}
|
|
185
|
+
if (clearTimeoutFn) {
|
|
186
|
+
clearTimeoutFn();
|
|
187
|
+
}
|
|
173
188
|
throw lastError;
|
|
174
189
|
}
|
|
175
190
|
// Wait before retrying
|
|
@@ -177,6 +192,10 @@ async function fetchWithRetry(args) {
|
|
|
177
192
|
if (delay > 0) {
|
|
178
193
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
179
194
|
}
|
|
195
|
+
// Retry path – ensure this attempt's timeout is cleared before looping
|
|
196
|
+
if (clearTimeoutFn) {
|
|
197
|
+
clearTimeoutFn();
|
|
198
|
+
}
|
|
180
199
|
}
|
|
181
200
|
}
|
|
182
201
|
// This should never be reached, but just in case
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addUncompletablePrefix = addUncompletablePrefix;
|
|
4
|
+
exports.removeUncompletablePrefix = removeUncompletablePrefix;
|
|
5
|
+
exports.hasUncompletablePrefix = hasUncompletablePrefix;
|
|
6
|
+
exports.processTaskContent = processTaskContent;
|
|
7
|
+
const UNCOMPLETABLE_PREFIX = '* ';
|
|
8
|
+
/**
|
|
9
|
+
* Adds the uncompletable prefix (* ) to task content if not already present
|
|
10
|
+
* @param content - The task content
|
|
11
|
+
* @returns Content with uncompletable prefix added
|
|
12
|
+
*/
|
|
13
|
+
function addUncompletablePrefix(content) {
|
|
14
|
+
if (content.startsWith(UNCOMPLETABLE_PREFIX)) {
|
|
15
|
+
return content;
|
|
16
|
+
}
|
|
17
|
+
return UNCOMPLETABLE_PREFIX + content;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Removes the uncompletable prefix (* ) from task content if present
|
|
21
|
+
* @param content - The task content
|
|
22
|
+
* @returns Content with uncompletable prefix removed
|
|
23
|
+
*/
|
|
24
|
+
function removeUncompletablePrefix(content) {
|
|
25
|
+
if (content.startsWith(UNCOMPLETABLE_PREFIX)) {
|
|
26
|
+
return content.slice(UNCOMPLETABLE_PREFIX.length);
|
|
27
|
+
}
|
|
28
|
+
return content;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Checks if task content has the uncompletable prefix (* )
|
|
32
|
+
* @param content - The task content
|
|
33
|
+
* @returns True if content starts with uncompletable prefix
|
|
34
|
+
*/
|
|
35
|
+
function hasUncompletablePrefix(content) {
|
|
36
|
+
return content.startsWith(UNCOMPLETABLE_PREFIX);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Processes task content based on isUncompletable flag, with content prefix taking precedence
|
|
40
|
+
* @param content - The original task content
|
|
41
|
+
* @param isUncompletable - Optional flag to make task uncompletable
|
|
42
|
+
* @returns Processed content
|
|
43
|
+
*
|
|
44
|
+
* Logic:
|
|
45
|
+
* - If content already has * prefix, task is uncompletable regardless of flag
|
|
46
|
+
* - If content doesn't have * prefix and isUncompletable is true, add the prefix
|
|
47
|
+
* - If isUncompletable is undefined or false (and no prefix), leave content unchanged
|
|
48
|
+
*/
|
|
49
|
+
function processTaskContent(content, isUncompletable) {
|
|
50
|
+
// Content prefix takes precedence - if already has prefix, keep it
|
|
51
|
+
if (hasUncompletablePrefix(content)) {
|
|
52
|
+
return content;
|
|
53
|
+
}
|
|
54
|
+
// If content doesn't have prefix and user wants uncompletable, add it
|
|
55
|
+
if (isUncompletable === true) {
|
|
56
|
+
return addUncompletablePrefix(content);
|
|
57
|
+
}
|
|
58
|
+
// Otherwise, leave content unchanged
|
|
59
|
+
return content;
|
|
60
|
+
}
|
|
@@ -76,6 +76,7 @@ export const DEFAULT_TASK = {
|
|
|
76
76
|
noteCount: DEFAULT_NOTE_COUNT,
|
|
77
77
|
dayOrder: DEFAULT_ORDER,
|
|
78
78
|
isCollapsed: DEFAULT_IS_COLLAPSED,
|
|
79
|
+
isUncompletable: false,
|
|
79
80
|
url: DEFAULT_TASK_URL,
|
|
80
81
|
};
|
|
81
82
|
export const INVALID_TASK = Object.assign(Object.assign({}, DEFAULT_TASK), { due: '2020-01-31' });
|
|
@@ -103,6 +104,7 @@ export const TASK_WITH_OPTIONALS_AS_NULL = {
|
|
|
103
104
|
description: DEFAULT_TASK_DESCRIPTION,
|
|
104
105
|
dayOrder: DEFAULT_ORDER,
|
|
105
106
|
isCollapsed: DEFAULT_IS_COLLAPSED,
|
|
107
|
+
isUncompletable: false,
|
|
106
108
|
noteCount: DEFAULT_NOTE_COUNT,
|
|
107
109
|
url: DEFAULT_TASK_URL,
|
|
108
110
|
};
|
package/dist/esm/todoist-api.js
CHANGED
|
@@ -4,6 +4,7 @@ import { validateAttachment, validateComment, validateCommentArray, validateCurr
|
|
|
4
4
|
import { formatDateToYYYYMMDD } from './utils/url-helpers.js';
|
|
5
5
|
import { uploadMultipartFile } from './utils/multipart-upload.js';
|
|
6
6
|
import { normalizeObjectTypeForApi, denormalizeObjectTypeFromApi } from './utils/activity-helpers.js';
|
|
7
|
+
import { processTaskContent } from './utils/uncompletable-helpers.js';
|
|
7
8
|
import { z } from 'zod';
|
|
8
9
|
import { v4 as uuidv4 } from 'uuid';
|
|
9
10
|
import { TodoistRequestError } from './types/index.js';
|
|
@@ -182,13 +183,15 @@ export class TodoistApi {
|
|
|
182
183
|
* @returns A promise that resolves to the created task.
|
|
183
184
|
*/
|
|
184
185
|
async addTask(args, requestId) {
|
|
186
|
+
// Process content based on isUncompletable flag
|
|
187
|
+
const processedArgs = Object.assign(Object.assign({}, args), { content: processTaskContent(args.content, args.isUncompletable) });
|
|
185
188
|
const response = await request({
|
|
186
189
|
httpMethod: 'POST',
|
|
187
190
|
baseUri: this.syncApiBase,
|
|
188
191
|
relativePath: ENDPOINT_REST_TASKS,
|
|
189
192
|
apiToken: this.authToken,
|
|
190
193
|
customFetch: this.customFetch,
|
|
191
|
-
payload:
|
|
194
|
+
payload: processedArgs,
|
|
192
195
|
requestId: requestId,
|
|
193
196
|
});
|
|
194
197
|
return validateTask(response.data);
|
|
@@ -200,13 +203,15 @@ export class TodoistApi {
|
|
|
200
203
|
* @returns A promise that resolves to the created task.
|
|
201
204
|
*/
|
|
202
205
|
async quickAddTask(args) {
|
|
206
|
+
// Process text based on isUncompletable flag
|
|
207
|
+
const processedArgs = Object.assign(Object.assign({}, args), { text: processTaskContent(args.text, args.isUncompletable) });
|
|
203
208
|
const response = await request({
|
|
204
209
|
httpMethod: 'POST',
|
|
205
210
|
baseUri: this.syncApiBase,
|
|
206
211
|
relativePath: ENDPOINT_SYNC_QUICK_ADD,
|
|
207
212
|
apiToken: this.authToken,
|
|
208
213
|
customFetch: this.customFetch,
|
|
209
|
-
payload:
|
|
214
|
+
payload: processedArgs,
|
|
210
215
|
});
|
|
211
216
|
return validateTask(response.data);
|
|
212
217
|
}
|
|
@@ -220,13 +225,16 @@ export class TodoistApi {
|
|
|
220
225
|
*/
|
|
221
226
|
async updateTask(id, args, requestId) {
|
|
222
227
|
z.string().parse(id);
|
|
228
|
+
// Process content if both content and isUncompletable are provided
|
|
229
|
+
const processedArgs = args.content && args.isUncompletable !== undefined
|
|
230
|
+
? Object.assign(Object.assign({}, args), { content: processTaskContent(args.content, args.isUncompletable) }) : args;
|
|
223
231
|
const response = await request({
|
|
224
232
|
httpMethod: 'POST',
|
|
225
233
|
baseUri: this.syncApiBase,
|
|
226
234
|
relativePath: generatePath(ENDPOINT_REST_TASKS, id),
|
|
227
235
|
apiToken: this.authToken,
|
|
228
236
|
customFetch: this.customFetch,
|
|
229
|
-
payload:
|
|
237
|
+
payload: processedArgs,
|
|
230
238
|
requestId: requestId,
|
|
231
239
|
});
|
|
232
240
|
return validateTask(response.data);
|
|
@@ -11,6 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { z } from 'zod';
|
|
13
13
|
import { getProjectUrl, getTaskUrl, getSectionUrl } from '../utils/url-helpers.js';
|
|
14
|
+
import { hasUncompletablePrefix } from '../utils/uncompletable-helpers.js';
|
|
14
15
|
export const DueDateSchema = z
|
|
15
16
|
.object({
|
|
16
17
|
isRecurring: z.boolean(),
|
|
@@ -56,9 +57,12 @@ export const TaskSchema = z
|
|
|
56
57
|
noteCount: z.number().int(),
|
|
57
58
|
dayOrder: z.number().int(),
|
|
58
59
|
isCollapsed: z.boolean(),
|
|
60
|
+
isUncompletable: z.boolean().default(false),
|
|
59
61
|
})
|
|
60
62
|
.transform((data) => {
|
|
61
|
-
|
|
63
|
+
// Auto-detect uncompletable status from content prefix
|
|
64
|
+
const isUncompletable = hasUncompletablePrefix(data.content);
|
|
65
|
+
return Object.assign(Object.assign({}, data), { isUncompletable, url: getTaskUrl(data.id, data.content) });
|
|
62
66
|
});
|
|
63
67
|
/**
|
|
64
68
|
* Base schema for all project types in Todoist.
|
|
@@ -42,7 +42,8 @@ function headersToObject(headers) {
|
|
|
42
42
|
return result;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* Creates an AbortSignal that
|
|
45
|
+
* Creates an AbortSignal that aborts after timeoutMs. Returns the signal and a
|
|
46
|
+
* clear function to cancel the timeout early.
|
|
46
47
|
*/
|
|
47
48
|
function createTimeoutSignal(timeoutMs, existingSignal) {
|
|
48
49
|
const controller = new AbortController();
|
|
@@ -50,6 +51,9 @@ function createTimeoutSignal(timeoutMs, existingSignal) {
|
|
|
50
51
|
const timeoutId = setTimeout(() => {
|
|
51
52
|
controller.abort(new Error(`Request timeout after ${timeoutMs}ms`));
|
|
52
53
|
}, timeoutMs);
|
|
54
|
+
function clear() {
|
|
55
|
+
clearTimeout(timeoutId);
|
|
56
|
+
}
|
|
53
57
|
// If there's an existing signal, forward its abort
|
|
54
58
|
if (existingSignal) {
|
|
55
59
|
if (existingSignal.aborted) {
|
|
@@ -67,7 +71,7 @@ function createTimeoutSignal(timeoutMs, existingSignal) {
|
|
|
67
71
|
controller.signal.addEventListener('abort', () => {
|
|
68
72
|
clearTimeout(timeoutId);
|
|
69
73
|
});
|
|
70
|
-
return controller.signal;
|
|
74
|
+
return { signal: controller.signal, clear };
|
|
71
75
|
}
|
|
72
76
|
/**
|
|
73
77
|
* Converts native fetch Response to CustomFetchResponse for consistency
|
|
@@ -93,11 +97,15 @@ export async function fetchWithRetry(args) {
|
|
|
93
97
|
const { timeout, signal: userSignal } = options, fetchOptions = __rest(options, ["timeout", "signal"]);
|
|
94
98
|
let lastError;
|
|
95
99
|
for (let attempt = 0; attempt <= config.retries; attempt++) {
|
|
100
|
+
// Timeout clear function for this attempt (hoisted for catch scope)
|
|
101
|
+
let clearTimeoutFn;
|
|
96
102
|
try {
|
|
97
103
|
// Set up timeout and signal handling
|
|
98
104
|
let requestSignal = userSignal || undefined;
|
|
99
105
|
if (timeout && timeout > 0) {
|
|
100
|
-
|
|
106
|
+
const timeoutResult = createTimeoutSignal(timeout, requestSignal);
|
|
107
|
+
requestSignal = timeoutResult.signal;
|
|
108
|
+
clearTimeoutFn = timeoutResult.clear;
|
|
101
109
|
}
|
|
102
110
|
// Use custom fetch or native fetch
|
|
103
111
|
let fetchResponse;
|
|
@@ -150,6 +158,10 @@ export async function fetchWithRetry(args) {
|
|
|
150
158
|
// If JSON parsing fails, return the raw text
|
|
151
159
|
data = responseText;
|
|
152
160
|
}
|
|
161
|
+
// Success – clear pending timeout (if any) so Node can exit promptly
|
|
162
|
+
if (clearTimeoutFn) {
|
|
163
|
+
clearTimeoutFn();
|
|
164
|
+
}
|
|
153
165
|
return {
|
|
154
166
|
data,
|
|
155
167
|
status: fetchResponse.status,
|
|
@@ -167,6 +179,9 @@ export async function fetchWithRetry(args) {
|
|
|
167
179
|
const networkError = lastError;
|
|
168
180
|
networkError.isNetworkError = true;
|
|
169
181
|
}
|
|
182
|
+
if (clearTimeoutFn) {
|
|
183
|
+
clearTimeoutFn();
|
|
184
|
+
}
|
|
170
185
|
throw lastError;
|
|
171
186
|
}
|
|
172
187
|
// Wait before retrying
|
|
@@ -174,6 +189,10 @@ export async function fetchWithRetry(args) {
|
|
|
174
189
|
if (delay > 0) {
|
|
175
190
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
176
191
|
}
|
|
192
|
+
// Retry path – ensure this attempt's timeout is cleared before looping
|
|
193
|
+
if (clearTimeoutFn) {
|
|
194
|
+
clearTimeoutFn();
|
|
195
|
+
}
|
|
177
196
|
}
|
|
178
197
|
}
|
|
179
198
|
// This should never be reached, but just in case
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const UNCOMPLETABLE_PREFIX = '* ';
|
|
2
|
+
/**
|
|
3
|
+
* Adds the uncompletable prefix (* ) to task content if not already present
|
|
4
|
+
* @param content - The task content
|
|
5
|
+
* @returns Content with uncompletable prefix added
|
|
6
|
+
*/
|
|
7
|
+
export function addUncompletablePrefix(content) {
|
|
8
|
+
if (content.startsWith(UNCOMPLETABLE_PREFIX)) {
|
|
9
|
+
return content;
|
|
10
|
+
}
|
|
11
|
+
return UNCOMPLETABLE_PREFIX + content;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Removes the uncompletable prefix (* ) from task content if present
|
|
15
|
+
* @param content - The task content
|
|
16
|
+
* @returns Content with uncompletable prefix removed
|
|
17
|
+
*/
|
|
18
|
+
export function removeUncompletablePrefix(content) {
|
|
19
|
+
if (content.startsWith(UNCOMPLETABLE_PREFIX)) {
|
|
20
|
+
return content.slice(UNCOMPLETABLE_PREFIX.length);
|
|
21
|
+
}
|
|
22
|
+
return content;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Checks if task content has the uncompletable prefix (* )
|
|
26
|
+
* @param content - The task content
|
|
27
|
+
* @returns True if content starts with uncompletable prefix
|
|
28
|
+
*/
|
|
29
|
+
export function hasUncompletablePrefix(content) {
|
|
30
|
+
return content.startsWith(UNCOMPLETABLE_PREFIX);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Processes task content based on isUncompletable flag, with content prefix taking precedence
|
|
34
|
+
* @param content - The original task content
|
|
35
|
+
* @param isUncompletable - Optional flag to make task uncompletable
|
|
36
|
+
* @returns Processed content
|
|
37
|
+
*
|
|
38
|
+
* Logic:
|
|
39
|
+
* - If content already has * prefix, task is uncompletable regardless of flag
|
|
40
|
+
* - If content doesn't have * prefix and isUncompletable is true, add the prefix
|
|
41
|
+
* - If isUncompletable is undefined or false (and no prefix), leave content unchanged
|
|
42
|
+
*/
|
|
43
|
+
export function processTaskContent(content, isUncompletable) {
|
|
44
|
+
// Content prefix takes precedence - if already has prefix, keep it
|
|
45
|
+
if (hasUncompletablePrefix(content)) {
|
|
46
|
+
return content;
|
|
47
|
+
}
|
|
48
|
+
// If content doesn't have prefix and user wants uncompletable, add it
|
|
49
|
+
if (isUncompletable === true) {
|
|
50
|
+
return addUncompletablePrefix(content);
|
|
51
|
+
}
|
|
52
|
+
// Otherwise, leave content unchanged
|
|
53
|
+
return content;
|
|
54
|
+
}
|
|
@@ -73,7 +73,9 @@ export declare const TaskSchema: z.ZodPipe<z.ZodObject<{
|
|
|
73
73
|
noteCount: z.ZodNumber;
|
|
74
74
|
dayOrder: z.ZodNumber;
|
|
75
75
|
isCollapsed: z.ZodBoolean;
|
|
76
|
+
isUncompletable: z.ZodDefault<z.ZodBoolean>;
|
|
76
77
|
}, z.core.$strip>, z.ZodTransform<{
|
|
78
|
+
isUncompletable: boolean;
|
|
77
79
|
url: string;
|
|
78
80
|
id: string;
|
|
79
81
|
userId: string;
|
|
@@ -150,6 +152,7 @@ export declare const TaskSchema: z.ZodPipe<z.ZodObject<{
|
|
|
150
152
|
noteCount: number;
|
|
151
153
|
dayOrder: number;
|
|
152
154
|
isCollapsed: boolean;
|
|
155
|
+
isUncompletable: boolean;
|
|
153
156
|
}>>;
|
|
154
157
|
/**
|
|
155
158
|
* Represents a task in Todoist.
|
|
@@ -18,6 +18,7 @@ export type AddTaskArgs = {
|
|
|
18
18
|
dueLang?: string;
|
|
19
19
|
deadlineLang?: string;
|
|
20
20
|
deadlineDate?: string;
|
|
21
|
+
isUncompletable?: boolean;
|
|
21
22
|
} & RequireOneOrNone<{
|
|
22
23
|
dueDate?: string;
|
|
23
24
|
dueDatetime?: string;
|
|
@@ -118,6 +119,7 @@ export type UpdateTaskArgs = {
|
|
|
118
119
|
assigneeId?: string | null;
|
|
119
120
|
deadlineDate?: string | null;
|
|
120
121
|
deadlineLang?: string | null;
|
|
122
|
+
isUncompletable?: boolean;
|
|
121
123
|
} & RequireOneOrNone<{
|
|
122
124
|
dueDate?: string;
|
|
123
125
|
dueDatetime?: string;
|
|
@@ -135,6 +137,7 @@ export type QuickAddTaskArgs = {
|
|
|
135
137
|
reminder?: string;
|
|
136
138
|
autoReminder?: boolean;
|
|
137
139
|
meta?: boolean;
|
|
140
|
+
isUncompletable?: boolean;
|
|
138
141
|
};
|
|
139
142
|
/**
|
|
140
143
|
* Response from quick adding a task.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds the uncompletable prefix (* ) to task content if not already present
|
|
3
|
+
* @param content - The task content
|
|
4
|
+
* @returns Content with uncompletable prefix added
|
|
5
|
+
*/
|
|
6
|
+
export declare function addUncompletablePrefix(content: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Removes the uncompletable prefix (* ) from task content if present
|
|
9
|
+
* @param content - The task content
|
|
10
|
+
* @returns Content with uncompletable prefix removed
|
|
11
|
+
*/
|
|
12
|
+
export declare function removeUncompletablePrefix(content: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Checks if task content has the uncompletable prefix (* )
|
|
15
|
+
* @param content - The task content
|
|
16
|
+
* @returns True if content starts with uncompletable prefix
|
|
17
|
+
*/
|
|
18
|
+
export declare function hasUncompletablePrefix(content: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Processes task content based on isUncompletable flag, with content prefix taking precedence
|
|
21
|
+
* @param content - The original task content
|
|
22
|
+
* @param isUncompletable - Optional flag to make task uncompletable
|
|
23
|
+
* @returns Processed content
|
|
24
|
+
*
|
|
25
|
+
* Logic:
|
|
26
|
+
* - If content already has * prefix, task is uncompletable regardless of flag
|
|
27
|
+
* - If content doesn't have * prefix and isUncompletable is true, add the prefix
|
|
28
|
+
* - If isUncompletable is undefined or false (and no prefix), leave content unchanged
|
|
29
|
+
*/
|
|
30
|
+
export declare function processTaskContent(content: string, isUncompletable?: boolean): string;
|
package/package.json
CHANGED