@doist/todoist-sdk 14.0.2 → 14.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.
Files changed (32) hide show
  1. package/README.md +46 -1
  2. package/dist/cjs/clients/view-options-client.js +25 -0
  3. package/dist/cjs/index.js +2 -1
  4. package/dist/cjs/todoist-api.js +30 -0
  5. package/dist/cjs/transport/http-dispatcher.js +25 -0
  6. package/dist/cjs/types/sync/resources/view-options.js +13 -4
  7. package/dist/cjs/utils/index.js +2 -0
  8. package/dist/cjs/utils/multipart-upload.js +171 -57
  9. package/dist/cjs/utils/task-sorting.js +281 -0
  10. package/dist/cjs/utils/view-options.js +19 -0
  11. package/dist/esm/clients/view-options-client.js +21 -0
  12. package/dist/esm/index.js +1 -1
  13. package/dist/esm/todoist-api.js +30 -0
  14. package/dist/esm/transport/http-dispatcher.js +25 -0
  15. package/dist/esm/types/sync/resources/view-options.js +12 -3
  16. package/dist/esm/utils/index.js +2 -0
  17. package/dist/esm/utils/multipart-upload.js +171 -57
  18. package/dist/esm/utils/task-sorting.js +277 -0
  19. package/dist/esm/utils/view-options.js +15 -0
  20. package/dist/types/clients/view-options-client.d.ts +8 -0
  21. package/dist/types/index.d.ts +2 -1
  22. package/dist/types/todoist-api.d.ts +24 -1
  23. package/dist/types/transport/http-dispatcher.d.ts +26 -1
  24. package/dist/types/types/sync/commands/project-view-options.d.ts +1 -4
  25. package/dist/types/types/sync/commands/view-options.d.ts +2 -1
  26. package/dist/types/types/sync/resources/view-options.d.ts +37 -7
  27. package/dist/types/types/sync/response.d.ts +24 -5
  28. package/dist/types/utils/index.d.ts +2 -0
  29. package/dist/types/utils/task-sorting.d.ts +38 -0
  30. package/dist/types/utils/validators.d.ts +24 -6
  31. package/dist/types/utils/view-options.d.ts +11 -0
  32. package/package.json +2 -3
@@ -0,0 +1,281 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_TASK_ORDERS = void 0;
4
+ exports.sortTasks = sortTasks;
5
+ /** Todoist's default task-ordering hierarchies. */
6
+ exports.DEFAULT_TASK_ORDERS = ['PRIORITY_FIRST', 'DATE_FIRST'];
7
+ const DEFAULT_TIMEZONE = 'UTC';
8
+ const DEFAULT_LOCALE = 'en';
9
+ const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
10
+ const DATE_TIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?$/;
11
+ const EXPLICIT_TIMEZONE_PATTERN = /(?:Z|[+-]\d{2}:?\d{2})$/i;
12
+ const DATE_TIME_FORMATTERS = new Map();
13
+ function compareNumber(a, b) {
14
+ if (a === b)
15
+ return 0;
16
+ return a < b ? -1 : 1;
17
+ }
18
+ function isValidDateParts(year, month, day) {
19
+ const date = new Date(0);
20
+ date.setUTCHours(0, 0, 0, 0);
21
+ date.setUTCFullYear(year, month - 1, day);
22
+ return (date.getUTCFullYear() === year &&
23
+ date.getUTCMonth() === month - 1 &&
24
+ date.getUTCDate() === day);
25
+ }
26
+ function dateSortValue({ year, month, day, hour = 0, minute = 0, second = 0, millisecond = 0, isAllDay = false, }) {
27
+ if (!isValidDateParts(year, month, day) ||
28
+ hour < 0 ||
29
+ hour > 23 ||
30
+ minute < 0 ||
31
+ minute > 59 ||
32
+ second < 0 ||
33
+ second > 59 ||
34
+ millisecond < 0 ||
35
+ millisecond > 999) {
36
+ return null;
37
+ }
38
+ return {
39
+ day: year * 10000 + month * 100 + day,
40
+ kind: isAllDay ? 0 : 1,
41
+ time: ((hour * 60 + minute) * 60 + second) * 1000 + millisecond,
42
+ };
43
+ }
44
+ function parseDateOnly(value) {
45
+ const match = DATE_ONLY_PATTERN.exec(value);
46
+ if (!match)
47
+ return null;
48
+ return dateSortValue({
49
+ year: Number(match[1]),
50
+ month: Number(match[2]),
51
+ day: Number(match[3]),
52
+ isAllDay: true,
53
+ });
54
+ }
55
+ function parseDateTimeComponents(value) {
56
+ var _a, _b;
57
+ const match = DATE_TIME_PATTERN.exec(value);
58
+ if (!match)
59
+ return null;
60
+ const milliseconds = Number(((_a = match[7]) !== null && _a !== void 0 ? _a : '').padEnd(3, '0').slice(0, 3));
61
+ const components = {
62
+ year: Number(match[1]),
63
+ month: Number(match[2]),
64
+ day: Number(match[3]),
65
+ hour: Number(match[4]),
66
+ minute: Number(match[5]),
67
+ second: Number((_b = match[6]) !== null && _b !== void 0 ? _b : 0),
68
+ millisecond: milliseconds,
69
+ };
70
+ return dateSortValue(components) === null ? null : components;
71
+ }
72
+ function createDateTimeFormatter(timezone, fallback) {
73
+ const cached = DATE_TIME_FORMATTERS.get(timezone);
74
+ if (cached)
75
+ return cached;
76
+ try {
77
+ const formatter = new Intl.DateTimeFormat('en-US', {
78
+ timeZone: timezone,
79
+ calendar: 'iso8601',
80
+ numberingSystem: 'latn',
81
+ year: 'numeric',
82
+ month: '2-digit',
83
+ day: '2-digit',
84
+ hour: '2-digit',
85
+ minute: '2-digit',
86
+ second: '2-digit',
87
+ hourCycle: 'h23',
88
+ });
89
+ DATE_TIME_FORMATTERS.set(timezone, formatter);
90
+ return formatter;
91
+ }
92
+ catch (_a) {
93
+ return fallback !== null && fallback !== void 0 ? fallback : createDateTimeFormatter(DEFAULT_TIMEZONE);
94
+ }
95
+ }
96
+ function componentsAt(timestamp, formatter) {
97
+ const parts = formatter.formatToParts(new Date(timestamp));
98
+ function part(type) {
99
+ var _a;
100
+ return Number((_a = parts.find((entry) => entry.type === type)) === null || _a === void 0 ? void 0 : _a.value);
101
+ }
102
+ return {
103
+ year: part('year'),
104
+ month: part('month'),
105
+ day: part('day'),
106
+ hour: part('hour'),
107
+ minute: part('minute'),
108
+ second: part('second'),
109
+ millisecond: new Date(timestamp).getUTCMilliseconds(),
110
+ };
111
+ }
112
+ function componentsAsUtcTimestamp({ year, month, day, hour = 0, minute = 0, second = 0, millisecond = 0, }) {
113
+ const date = new Date(0);
114
+ date.setUTCFullYear(year, month - 1, day);
115
+ date.setUTCHours(hour, minute, second, millisecond);
116
+ return date.getTime();
117
+ }
118
+ /** Resolves a zoned wall clock to an instant, including daylight-saving offsets. */
119
+ function timestampForWallClock(components, formatter) {
120
+ const target = componentsAsUtcTimestamp(components);
121
+ let timestamp = target;
122
+ for (let attempt = 0; attempt < 4; attempt += 1) {
123
+ const adjustment = target - componentsAsUtcTimestamp(componentsAt(timestamp, formatter));
124
+ if (adjustment === 0)
125
+ return timestamp;
126
+ timestamp += adjustment;
127
+ }
128
+ return null;
129
+ }
130
+ function dateSortValueAt(timestamp, formatter) {
131
+ const value = dateSortValue(componentsAt(timestamp, formatter));
132
+ return value ? Object.assign(Object.assign({}, value), { time: timestamp }) : null;
133
+ }
134
+ function parseFixedDateTime(value, formatter) {
135
+ if (parseDateTimeComponents(value.replace(EXPLICIT_TIMEZONE_PATTERN, '')) === null)
136
+ return null;
137
+ const timestamp = Date.parse(value);
138
+ return Number.isNaN(timestamp) ? null : dateSortValueAt(timestamp, formatter);
139
+ }
140
+ function parseZonedFloatingDateTime(value, sourceFormatter, accountFormatter) {
141
+ const components = parseDateTimeComponents(value);
142
+ if (!components)
143
+ return null;
144
+ const timestamp = timestampForWallClock(components, sourceFormatter);
145
+ return timestamp === null ? null : dateSortValueAt(timestamp, accountFormatter);
146
+ }
147
+ function parseTaskDate(value, formatter, timezone) {
148
+ var _a;
149
+ return ((_a = parseDateOnly(value)) !== null && _a !== void 0 ? _a : (EXPLICIT_TIMEZONE_PATTERN.test(value)
150
+ ? parseFixedDateTime(value, formatter)
151
+ : parseZonedFloatingDateTime(value, timezone ? createDateTimeFormatter(timezone, formatter) : formatter, formatter)));
152
+ }
153
+ function compareNullable(a, b, compare) {
154
+ if (a === null || b === null) {
155
+ if (a === b)
156
+ return 0;
157
+ return a === null ? 1 : -1;
158
+ }
159
+ return compare(a, b);
160
+ }
161
+ function compareDateValues(left, right) {
162
+ return (compareNumber(left.day, right.day) ||
163
+ compareNumber(left.kind, right.kind) ||
164
+ compareNumber(left.time, right.time));
165
+ }
166
+ function compareDate(a, b) {
167
+ return compareNullable(a, b, compareDateValues);
168
+ }
169
+ function compareOptionalNumber(a, b) {
170
+ return compareNullable(a, b, compareNumber);
171
+ }
172
+ function compareOptionalText(a, b, collator) {
173
+ return compareNullable(a, b, (left, right) => collator.compare(left, right));
174
+ }
175
+ function prepareTasks(tasks, context, { formatter, resolveAssignee }) {
176
+ return tasks.map((task, index) => {
177
+ var _a, _b, _c, _d, _e, _f, _g;
178
+ const due = task.due
179
+ ? parseTaskDate((_a = task.due.datetime) !== null && _a !== void 0 ? _a : task.due.date, formatter, task.due.timezone)
180
+ : null;
181
+ const deadline = task.deadline ? parseTaskDate(task.deadline.date, formatter) : null;
182
+ return {
183
+ task,
184
+ index,
185
+ due,
186
+ deadline,
187
+ schedule: due !== null && due !== void 0 ? due : deadline,
188
+ added: task.addedAt && !Number.isNaN(task.addedAt.getTime())
189
+ ? task.addedAt.getTime()
190
+ : null,
191
+ project: (_c = (_b = context.projectOrder) === null || _b === void 0 ? void 0 : _b.get(task.projectId)) !== null && _c !== void 0 ? _c : null,
192
+ workspace: (_e = (_d = context.workspaceOrder) === null || _d === void 0 ? void 0 : _d.get(task.projectId)) !== null && _e !== void 0 ? _e : null,
193
+ assignee: resolveAssignee ? ((_g = (_f = context.assigneeName) === null || _f === void 0 ? void 0 : _f.call(context, task)) !== null && _g !== void 0 ? _g : null) : null,
194
+ };
195
+ });
196
+ }
197
+ function comparePriorityFirst(a, b) {
198
+ const common = compareNumber(b.task.priority, a.task.priority) ||
199
+ compareDate(a.due, b.due) ||
200
+ compareDate(a.deadline, b.deadline);
201
+ if (common !== 0)
202
+ return common;
203
+ const project = compareOptionalNumber(a.project, b.project);
204
+ if (project !== 0)
205
+ return project;
206
+ if (a.task.projectId === b.task.projectId) {
207
+ return compareNumber(a.task.childOrder, b.task.childOrder);
208
+ }
209
+ return 0;
210
+ }
211
+ function compareDateFirst(a, b) {
212
+ return (compareDate(a.schedule, b.schedule) ||
213
+ compareNumber(b.task.priority, a.task.priority) ||
214
+ compareDate(a.deadline, b.deadline) ||
215
+ compareNumber(a.task.dayOrder, b.task.dayOrder) ||
216
+ compareOptionalNumber(a.added, b.added));
217
+ }
218
+ function compareDefault(a, b, defaultOrder) {
219
+ return defaultOrder === 'DATE_FIRST' ? compareDateFirst(a, b) : comparePriorityFirst(a, b);
220
+ }
221
+ function comparePrimary({ sortedBy, a, b, collator, }) {
222
+ switch (sortedBy) {
223
+ case 'ALPHABETICALLY':
224
+ return collator.compare(a.task.content, b.task.content);
225
+ case 'ASSIGNEE':
226
+ return compareOptionalText(a.assignee, b.assignee, collator);
227
+ case 'DUE_DATE':
228
+ return compareDate(a.due, b.due);
229
+ case 'DEADLINE':
230
+ return compareDate(a.deadline, b.deadline);
231
+ case 'ADDED_DATE':
232
+ return compareOptionalNumber(a.added, b.added);
233
+ case 'PRIORITY':
234
+ return compareNumber(a.task.priority, b.task.priority);
235
+ case 'PROJECT':
236
+ return compareOptionalNumber(a.project, b.project);
237
+ case 'WORKSPACE':
238
+ return compareOptionalNumber(a.workspace, b.workspace);
239
+ case 'MANUAL':
240
+ return 0;
241
+ }
242
+ }
243
+ function defaultSortOrder(sortedBy) {
244
+ return sortedBy === 'PRIORITY' ? 'DESC' : 'ASC';
245
+ }
246
+ /**
247
+ * Sorts the supplied tasks using Todoist view-ordering rules.
248
+ *
249
+ * The function performs no I/O and always returns a new array. Exact project,
250
+ * workspace, assignee, localized text, and due-time ordering requires the
251
+ * corresponding context. Callers must sort each independent view section or
252
+ * group separately and preserve any subtask structure they render.
253
+ *
254
+ * This function sorts only the tasks supplied to it. Fetch every page first
255
+ * when globally correct ordering is required for a paginated query.
256
+ */
257
+ function sortTasks(tasks, options, context = {}) {
258
+ var _a, _b, _c;
259
+ const locale = Array.isArray(context.locale)
260
+ ? [...context.locale]
261
+ : ((_a = context.locale) !== null && _a !== void 0 ? _a : DEFAULT_LOCALE);
262
+ const collator = new Intl.Collator(locale, { sensitivity: 'base', numeric: true });
263
+ const formatter = createDateTimeFormatter((_b = context.timezone) !== null && _b !== void 0 ? _b : DEFAULT_TIMEZONE);
264
+ const sortedBy = options.sortedBy;
265
+ const prepared = prepareTasks(tasks, context, {
266
+ formatter,
267
+ resolveAssignee: sortedBy === 'ASSIGNEE',
268
+ });
269
+ const direction = sortedBy && sortedBy !== 'MANUAL' ? ((_c = options.sortOrder) !== null && _c !== void 0 ? _c : defaultSortOrder(sortedBy)) : null;
270
+ const directionMultiplier = direction === 'DESC' ? -1 : 1;
271
+ return prepared
272
+ .sort((a, b) => {
273
+ if (sortedBy && sortedBy !== 'MANUAL') {
274
+ const primary = comparePrimary({ sortedBy, a, b, collator }) * directionMultiplier;
275
+ if (primary !== 0)
276
+ return primary;
277
+ }
278
+ return compareDefault(a, b, options.defaultOrder) || compareNumber(a.index, b.index);
279
+ })
280
+ .map(({ task }) => task);
281
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isActiveViewOption = isActiveViewOption;
4
+ exports.findViewOptions = findViewOptions;
5
+ /** Returns whether saved view options are active rather than a deletion tombstone. */
6
+ function isActiveViewOption(options) {
7
+ return options.isDeleted !== true;
8
+ }
9
+ /** Finds the active saved options for one logical view. */
10
+ function findViewOptions(options, args) {
11
+ var _a;
12
+ const targetObjectId = (_a = args.objectId) !== null && _a !== void 0 ? _a : null;
13
+ return options.find((entry) => {
14
+ var _a;
15
+ return isActiveViewOption(entry) &&
16
+ ((_a = entry.objectId) !== null && _a !== void 0 ? _a : null) === targetObjectId &&
17
+ args.viewTypes.includes(entry.viewType);
18
+ });
19
+ }
@@ -0,0 +1,21 @@
1
+ import { performSyncRequest } from '../transport/sync-request.js';
2
+ import { createCommand } from '../utils/sync-helpers.js';
3
+ import { isActiveViewOption } from '../utils/view-options.js';
4
+ import { BaseClient } from './base-client.js';
5
+ /** Internal sub-client for saved view-option reads and writes. */
6
+ export class ViewOptionsClient extends BaseClient {
7
+ async getViewOptions() {
8
+ var _a;
9
+ const response = await performSyncRequest(this.syncContext, {
10
+ resourceTypes: ['view_options'],
11
+ syncToken: '*',
12
+ });
13
+ return ((_a = response.viewOptions) !== null && _a !== void 0 ? _a : []).filter(isActiveViewOption);
14
+ }
15
+ async setViewOptions(args, requestId) {
16
+ await performSyncRequest(this.syncContext, { commands: [createCommand('view_options_set', args)] }, { requestId, hasSyncCommands: true });
17
+ }
18
+ async deleteViewOptions(args, requestId) {
19
+ await performSyncRequest(this.syncContext, { commands: [createCommand('view_options_delete', args)] }, { requestId, hasSyncCommands: true });
20
+ }
21
+ }
package/dist/esm/index.js CHANGED
@@ -2,4 +2,4 @@ export * from './todoist-api.js';
2
2
  export * from './authentication.js';
3
3
  export * from './types/index.js';
4
4
  export * from './utils/index.js';
5
- export { getDefaultDispatcher } from './transport/http-dispatcher.js';
5
+ export { getDefaultDispatcher, getDefaultTransport } from './transport/http-dispatcher.js';
@@ -16,6 +16,7 @@ import { TaskClient } from './clients/task-client.js';
16
16
  import { TemplateClient } from './clients/template-client.js';
17
17
  import { UiExtensionClient } from './clients/ui-extension-client.js';
18
18
  import { UploadClient } from './clients/upload-client.js';
19
+ import { ViewOptionsClient } from './clients/view-options-client.js';
19
20
  import { WorkspaceClient } from './clients/workspace-client.js';
20
21
  import { ENDPOINT_REST_USER, getSyncBaseUri } from './consts/endpoints.js';
21
22
  import { request } from './transport/http-client.js';
@@ -62,6 +63,7 @@ export class TodoistApi {
62
63
  this.billingClient = new BillingClient(clientDeps);
63
64
  this.appClient = new AppClient(clientDeps);
64
65
  this.uiExtensionClient = new UiExtensionClient(clientDeps);
66
+ this.viewOptionsClient = new ViewOptionsClient(clientDeps);
65
67
  }
66
68
  /**
67
69
  * Executes a raw Sync API request.
@@ -95,6 +97,34 @@ export class TodoistApi {
95
97
  customFetch: this.customFetch,
96
98
  }, syncRequest, { requestId, hasSyncCommands: Boolean((_a = syncRequest.commands) === null || _a === void 0 ? void 0 : _a.length) });
97
99
  }
100
+ /**
101
+ * Retrieves the authenticated user's active saved view options.
102
+ *
103
+ * @returns A promise that resolves to the active saved view options.
104
+ */
105
+ async getViewOptions() {
106
+ return this.viewOptionsClient.getViewOptions();
107
+ }
108
+ /**
109
+ * Sets saved options for a view.
110
+ *
111
+ * @param args - The saved view options to set.
112
+ * @param requestId - Optional custom identifier for the request.
113
+ * @returns A promise that resolves when the view options are saved.
114
+ */
115
+ async setViewOptions(args, requestId) {
116
+ return this.viewOptionsClient.setViewOptions(args, requestId);
117
+ }
118
+ /**
119
+ * Deletes the saved options for a view.
120
+ *
121
+ * @param args - The view whose saved options to delete.
122
+ * @param requestId - Optional custom identifier for the request.
123
+ * @returns A promise that resolves when the view options are deleted.
124
+ */
125
+ async deleteViewOptions(args, requestId) {
126
+ return this.viewOptionsClient.deleteViewOptions(args, requestId);
127
+ }
98
128
  /**
99
129
  * Retrieves information about the authenticated user.
100
130
  *
@@ -10,6 +10,23 @@ let defaultTransportPromise;
10
10
  * The default dispatcher and its paired `fetch`, as a single value so the two
11
11
  * are always read consistently. Resolves to `undefined` outside Node (browser/
12
12
  * edge), where callers use the global `fetch` with no dispatcher.
13
+ *
14
+ * Use this when supplying a `customFetch` that should still honour the proxy
15
+ * environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`) and decode
16
+ * compressed responses the way the SDK does by default.
17
+ *
18
+ * Both halves must be used together. `dispatcher` decompresses the response
19
+ * body itself, so a `fetch` from a different undici build will try to decode
20
+ * it a second time and the request fails mid-stream with `terminated`. A
21
+ * `fetch` of `undefined` means the runtime's global `fetch` is the correct
22
+ * partner for this dispatcher.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const transport = await getDefaultTransport()
27
+ * const fetchImpl = transport?.fetch ?? fetch
28
+ * const response = await fetchImpl(url, { ...options, dispatcher: transport?.dispatcher })
29
+ * ```
13
30
  */
14
31
  export function getDefaultTransport() {
15
32
  if (!isNodeEnvironment()) {
@@ -29,6 +46,14 @@ export function getDefaultTransport() {
29
46
  }
30
47
  return defaultTransportPromise;
31
48
  }
49
+ /**
50
+ * The default dispatcher on its own.
51
+ *
52
+ * @deprecated Use {@link getDefaultTransport} instead, which returns this
53
+ * dispatcher together with the `fetch` it must be used with. Attaching this
54
+ * dispatcher to the runtime's global `fetch` decodes compressed responses
55
+ * twice on Node 26 and the request fails with `terminated`.
56
+ */
32
57
  export async function getDefaultDispatcher() {
33
58
  var _a;
34
59
  return (_a = (await getDefaultTransport())) === null || _a === void 0 ? void 0 : _a.dispatcher;
@@ -49,18 +49,27 @@ export const SORT_ORDERS = ['ASC', 'DESC'];
49
49
  export const SortOrderSchema = z.enum(SORT_ORDERS).nullable();
50
50
  /** Available calendar layout modes. */
51
51
  export const CALENDAR_LAYOUTS = ['WEEK', 'MONTH'];
52
+ /** Available task color sources for calendar views. */
53
+ export const CALENDAR_COLORS = ['PRIORITY', 'LABEL', 'PROJECT'];
52
54
  export const CalendarSettingsSchema = z.looseObject({
53
- layout: z.enum(CALENDAR_LAYOUTS).optional(),
55
+ layout: z.enum(CALENDAR_LAYOUTS).nullable().optional(),
56
+ visibleDayCount: z
57
+ .union([z.literal(1), z.literal(3), z.literal(7)])
58
+ .nullable()
59
+ .optional(),
60
+ color: z.enum(CALENDAR_COLORS).nullable().optional(),
54
61
  });
55
62
  export const ViewOptionsSchema = z.looseObject({
56
63
  viewType: ViewTypeSchema,
57
- objectId: z.string().optional(),
64
+ objectId: z.string().nullable().optional(),
58
65
  groupedBy: GroupedBySchema.optional(),
59
66
  filteredBy: z.string().nullable().optional(),
60
- viewMode: ViewModeSchema.optional(),
67
+ viewMode: ViewModeSchema.nullable().optional(),
61
68
  showCompletedTasks: z.boolean().optional(),
62
69
  sortedBy: SortedBySchema.optional(),
63
70
  sortOrder: SortOrderSchema.optional(),
71
+ calendarSettings: CalendarSettingsSchema.nullable().optional(),
72
+ isDeleted: z.boolean().optional(),
64
73
  });
65
74
  export const ProjectViewOptionsDefaultsSchema = z.looseObject({
66
75
  projectId: z.string(),
@@ -3,4 +3,6 @@ export * from './sanitization.js';
3
3
  export { getTaskUrl, getProjectUrl, getSectionUrl } from './url-helpers.js';
4
4
  export { createCommand } from './sync-helpers.js';
5
5
  export * from './project-helpers.js';
6
+ export * from './task-sorting.js';
7
+ export * from './view-options.js';
6
8
  export { parseWebhookPayload, verifyWebhookSignature } from './webhook-parser.js';