@rawdash/connector-clickup 0.0.1

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/index.js ADDED
@@ -0,0 +1,673 @@
1
+ // ../../connector-shared/dist/index.js
2
+ var HTTP_CLIENT_VERSION = "0.0.0";
3
+ var DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;
4
+ function connectorUserAgent(connectorId) {
5
+ return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;
6
+ }
7
+ function standardRateLimitPolicy(config) {
8
+ const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;
9
+ const multiplier = resetUnit === "s" ? 1e3 : 1;
10
+ return {
11
+ parse(h) {
12
+ const remainingRaw = h.get(remainingHeader);
13
+ if (remainingRaw === null || remainingRaw.trim() === "") {
14
+ return null;
15
+ }
16
+ const remaining = Number(remainingRaw);
17
+ if (!Number.isFinite(remaining)) {
18
+ return null;
19
+ }
20
+ const resetRaw = h.get(resetHeader);
21
+ if (resetRaw === null) {
22
+ if (resetFallbackMs === void 0) {
23
+ return null;
24
+ }
25
+ return {
26
+ remaining,
27
+ resetAt: new Date(Date.now() + resetFallbackMs)
28
+ };
29
+ }
30
+ if (resetRaw.trim() === "") {
31
+ return null;
32
+ }
33
+ const reset = Number(resetRaw);
34
+ if (!Number.isFinite(reset) || reset < 0) {
35
+ return null;
36
+ }
37
+ const resetMs = reset * multiplier;
38
+ if (!Number.isFinite(resetMs)) {
39
+ return null;
40
+ }
41
+ return { remaining, resetAt: new Date(resetMs) };
42
+ }
43
+ };
44
+ }
45
+ function parseEpoch(value, unit) {
46
+ if (value === null || value === void 0) {
47
+ return null;
48
+ }
49
+ if (unit === "iso") {
50
+ if (typeof value !== "string") {
51
+ return null;
52
+ }
53
+ const ms = new Date(value).getTime();
54
+ return Number.isFinite(ms) ? ms : null;
55
+ }
56
+ if (typeof value === "string" && value.trim() === "") {
57
+ return null;
58
+ }
59
+ const n = typeof value === "number" ? value : Number(value);
60
+ if (!Number.isFinite(n)) {
61
+ return null;
62
+ }
63
+ const result = unit === "s" ? n * 1e3 : n;
64
+ return Number.isFinite(result) ? result : null;
65
+ }
66
+
67
+ // src/clickup.ts
68
+ import {
69
+ BaseConnector,
70
+ defineConfigFields,
71
+ defineConnectorDoc,
72
+ defineResources,
73
+ makeChunkedCursorGuard,
74
+ paginateChunked,
75
+ schemasFromResources,
76
+ selectActivePhases
77
+ } from "@rawdash/core";
78
+ import { z } from "zod";
79
+ var configFields = defineConfigFields(
80
+ z.object({
81
+ apiToken: z.object({ $secret: z.string() }).meta({
82
+ label: "API Token",
83
+ description: "ClickUp personal API token. Create one at ClickUp -> Settings -> Apps -> API Token.",
84
+ placeholder: "pk_...",
85
+ secret: true
86
+ }),
87
+ teamId: z.string().min(1).meta({
88
+ label: "Workspace ID",
89
+ description: "ClickUp Workspace (team) ID to sync. Find it in the URL: app.clickup.com/<workspace_id>/home.",
90
+ placeholder: "9000000000"
91
+ }),
92
+ resources: z.array(z.enum(["spaces", "folders", "lists", "tasks", "task_events"])).nonempty().optional().meta({
93
+ label: "Resources",
94
+ description: "Which ClickUp resources to sync. Omit to sync all of them. 'task_events' derives created / closed lifecycle events from each task's timestamps and shares the task query with 'tasks'."
95
+ })
96
+ })
97
+ );
98
+ var doc = defineConnectorDoc({
99
+ displayName: "ClickUp",
100
+ category: "product",
101
+ brandColor: "#7B68EE",
102
+ tagline: "Sync spaces, folders, lists, tasks, and task lifecycle events from a ClickUp workspace for throughput, open-work, and status-distribution analytics.",
103
+ vendor: {
104
+ name: "ClickUp",
105
+ domain: "clickup.com",
106
+ apiDocs: "https://clickup.com/api",
107
+ website: "https://clickup.com"
108
+ },
109
+ auth: {
110
+ summary: "Authenticates with a ClickUp personal API token sent in the Authorization header. The token scopes the sync to the workspaces, spaces, and tasks the issuing user can access.",
111
+ setup: [
112
+ "Open ClickUp -> Settings -> Apps.",
113
+ "Under API Token, click Generate (or copy the existing personal token). It starts with pk_.",
114
+ 'Store it as a secret and reference it from the connector config as `apiToken: secret("CLICKUP_API_TOKEN")`, alongside your Workspace ID.'
115
+ ]
116
+ },
117
+ rateLimit: "ClickUp rate-limits per token (100 requests/minute on the Free Forever / Unlimited plans, higher on Business+) and exposes X-RateLimit-Remaining / X-RateLimit-Reset headers; the shared HTTP client backs off on 429.",
118
+ limitations: [
119
+ "Personal API token auth only (OAuth app installs are out of scope).",
120
+ "Task lifecycle events (created / closed) are derived from each task's own date_created / date_closed fields rather than the per-task activity feed, which avoids an N+1 sync; the event scope is cleared and rewritten from a full task scan on every sync.",
121
+ "Custom fields, comments, time tracking, and goals are out of scope."
122
+ ]
123
+ });
124
+ var clickupCredentials = {
125
+ apiToken: {
126
+ description: "ClickUp personal API token",
127
+ auth: "required"
128
+ }
129
+ };
130
+ var clickupRateLimit = standardRateLimitPolicy({
131
+ remainingHeader: "x-ratelimit-remaining",
132
+ resetHeader: "x-ratelimit-reset",
133
+ resetUnit: "s",
134
+ resetFallbackMs: 6e4
135
+ });
136
+ var PHASE_ORDER = [
137
+ "spaces",
138
+ "folders",
139
+ "lists",
140
+ "tasks",
141
+ "task_events"
142
+ ];
143
+ var isClickUpSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);
144
+ var SPACE_ENTITY = "clickup_space";
145
+ var FOLDER_ENTITY = "clickup_folder";
146
+ var LIST_ENTITY = "clickup_list";
147
+ var TASK_ENTITY = "clickup_task";
148
+ var TASK_EVENT = "clickup_task_event";
149
+ var API_BASE = "https://api.clickup.com/api/v2";
150
+ var TASKS_PER_PAGE = 100;
151
+ var idString = z.string().min(1);
152
+ var spaceSchema = z.object({
153
+ id: idString,
154
+ name: z.string(),
155
+ private: z.boolean().nullish(),
156
+ archived: z.boolean().nullish()
157
+ });
158
+ var spacesResponseSchema = z.object({
159
+ spaces: z.array(spaceSchema).nullish()
160
+ });
161
+ var folderSchema = z.object({
162
+ id: idString,
163
+ name: z.string(),
164
+ hidden: z.boolean().nullish(),
165
+ archived: z.boolean().nullish(),
166
+ task_count: z.union([z.string(), z.number()]).nullish(),
167
+ space: z.object({ id: idString, name: z.string().nullish() }).nullish()
168
+ });
169
+ var foldersResponseSchema = z.object({
170
+ folders: z.array(folderSchema).nullish()
171
+ });
172
+ var listSchema = z.object({
173
+ id: idString,
174
+ name: z.string(),
175
+ archived: z.boolean().nullish(),
176
+ task_count: z.number().nullish(),
177
+ status: z.object({ status: z.string().nullish() }).nullish(),
178
+ folder: z.object({ id: idString, name: z.string().nullish() }).nullish(),
179
+ space: z.object({ id: idString, name: z.string().nullish() }).nullish()
180
+ });
181
+ var listsResponseSchema = z.object({
182
+ lists: z.array(listSchema).nullish()
183
+ });
184
+ var taskSchema = z.object({
185
+ id: idString,
186
+ name: z.string(),
187
+ status: z.object({ status: z.string().nullish(), type: z.string().nullish() }).nullish(),
188
+ priority: z.object({ priority: z.string().nullish() }).nullish(),
189
+ date_created: z.string().nullish(),
190
+ date_updated: z.string().nullish(),
191
+ date_closed: z.string().nullish(),
192
+ date_done: z.string().nullish(),
193
+ due_date: z.string().nullish(),
194
+ time_estimate: z.number().nullish(),
195
+ creator: z.object({ id: z.union([z.string(), z.number()]) }).nullish(),
196
+ assignees: z.array(z.object({ id: z.union([z.string(), z.number()]) })).nullish(),
197
+ tags: z.array(z.object({ name: z.string() })).nullish(),
198
+ url: z.string().nullish(),
199
+ list: z.object({ id: idString, name: z.string().nullish() }).nullish(),
200
+ folder: z.object({ id: idString, name: z.string().nullish() }).nullish(),
201
+ space: z.object({ id: idString }).nullish()
202
+ });
203
+ var tasksResponseSchema = z.object({
204
+ tasks: z.array(taskSchema).nullish(),
205
+ last_page: z.boolean().nullish()
206
+ });
207
+ var clickupResources = defineResources({
208
+ [SPACE_ENTITY]: {
209
+ shape: "entity",
210
+ filterable: [],
211
+ description: "Workspace spaces with their name and privacy flag.",
212
+ endpoint: "GET /team/{team_id}/space",
213
+ fields: [
214
+ { name: "name", description: "Space name." },
215
+ { name: "private", description: "Whether the space is private." },
216
+ { name: "archived", description: "Whether the space is archived." }
217
+ ],
218
+ responses: { spaces: spacesResponseSchema }
219
+ },
220
+ [FOLDER_ENTITY]: {
221
+ shape: "entity",
222
+ filterable: [{ field: "spaceId", ops: ["eq"] }],
223
+ description: "Folders within each space, with their parent space.",
224
+ endpoint: "GET /space/{space_id}/folder",
225
+ fields: [
226
+ { name: "name", description: "Folder name." },
227
+ { name: "spaceId", description: "Parent space id." },
228
+ {
229
+ name: "taskCount",
230
+ description: "Number of tasks across the folder at sync time."
231
+ },
232
+ { name: "archived", description: "Whether the folder is archived." }
233
+ ],
234
+ responses: { folders: foldersResponseSchema }
235
+ },
236
+ [LIST_ENTITY]: {
237
+ shape: "entity",
238
+ filterable: [{ field: "spaceId", ops: ["eq"] }],
239
+ description: "Lists (folder-scoped and folderless) with their parent folder and space.",
240
+ endpoint: "GET /space/{space_id}/list and GET /folder/{folder_id}/list",
241
+ fields: [
242
+ { name: "name", description: "List name." },
243
+ {
244
+ name: "folderId",
245
+ description: "Parent folder id (null if folderless)."
246
+ },
247
+ { name: "spaceId", description: "Parent space id." },
248
+ {
249
+ name: "taskCount",
250
+ description: "Number of tasks in the list at sync time."
251
+ },
252
+ { name: "archived", description: "Whether the list is archived." }
253
+ ],
254
+ responses: { lists: listsResponseSchema }
255
+ },
256
+ [TASK_ENTITY]: {
257
+ shape: "entity",
258
+ filterable: [
259
+ {
260
+ field: "statusType",
261
+ ops: ["eq"],
262
+ values: ["open", "custom", "closed", "done"]
263
+ },
264
+ { field: "status", ops: ["eq"] },
265
+ { field: "listId", ops: ["eq"] }
266
+ ],
267
+ description: "Tasks with their status, priority, assignees, parent list / folder / space, tags, and lifecycle timestamps.",
268
+ endpoint: "GET /team/{team_id}/task",
269
+ fields: [
270
+ { name: "name", description: "Task name." },
271
+ {
272
+ name: "status",
273
+ description: 'Current status name (e.g. "in progress").'
274
+ },
275
+ {
276
+ name: "statusType",
277
+ description: "Status category: open, custom, closed, or done."
278
+ },
279
+ {
280
+ name: "priority",
281
+ description: "Priority label (urgent / high / normal / low), or null."
282
+ },
283
+ { name: "listId", description: "Parent list id." },
284
+ { name: "folderId", description: "Parent folder id." },
285
+ { name: "spaceId", description: "Parent space id." },
286
+ { name: "assignees", description: "Assignee user ids." },
287
+ { name: "assigneeCount", description: "Number of assignees." },
288
+ { name: "tags", description: "Tag names on the task." },
289
+ {
290
+ name: "createdAt",
291
+ description: "When the task was created (Unix ms)."
292
+ },
293
+ {
294
+ name: "closedAt",
295
+ description: "When the task was closed (Unix ms; null if open)."
296
+ },
297
+ {
298
+ name: "dueDate",
299
+ description: "Task due date (Unix ms; null if unset)."
300
+ }
301
+ ],
302
+ responses: { tasks: tasksResponseSchema }
303
+ },
304
+ [TASK_EVENT]: {
305
+ shape: "event",
306
+ filterable: [
307
+ { field: "kind", ops: ["eq"], values: ["created", "closed"] },
308
+ { field: "listId", ops: ["eq"] }
309
+ ],
310
+ description: "Task lifecycle events (created / closed) derived from each task's date_created and date_closed. The scope is cleared and rewritten from a full task scan on every sync (including incremental runs).",
311
+ endpoint: "GET /team/{team_id}/task",
312
+ notes: "Derived from each task's own date_created / date_closed timestamps, not from a separate per-task activity call. Drives created-per-day and closed-per-day throughput timeseries.",
313
+ fields: [
314
+ { name: "kind", description: '"created" or "closed".' },
315
+ { name: "taskId", description: "Task the event belongs to." },
316
+ { name: "listId", description: "Parent list id, denormalised." },
317
+ { name: "spaceId", description: "Parent space id, denormalised." },
318
+ { name: "status", description: "Task status name at sync time." }
319
+ ],
320
+ responses: { task_events: tasksResponseSchema }
321
+ }
322
+ });
323
+ var id = "clickup";
324
+ function epochMs(value) {
325
+ return parseEpoch(value ?? null, "ms");
326
+ }
327
+ function tagNames(tags) {
328
+ if (!tags) {
329
+ return [];
330
+ }
331
+ return tags.map((t) => t.name).filter((n) => typeof n === "string" && n !== "");
332
+ }
333
+ function assigneeIds(assignees) {
334
+ if (!assignees) {
335
+ return [];
336
+ }
337
+ return assignees.map((a) => String(a.id));
338
+ }
339
+ function pushableEq(filter, field) {
340
+ if (!filter) {
341
+ return null;
342
+ }
343
+ for (const clause of filter) {
344
+ if ("field" in clause && clause.field === field && clause.op === "eq" && typeof clause.value === "string") {
345
+ return clause.value;
346
+ }
347
+ }
348
+ return null;
349
+ }
350
+ var ClickUpConnector = class _ClickUpConnector extends BaseConnector {
351
+ static id = id;
352
+ static resources = clickupResources;
353
+ static schemas = schemasFromResources(clickupResources);
354
+ static create(input, ctx) {
355
+ const parsed = configFields.parse(input);
356
+ return new _ClickUpConnector(
357
+ { teamId: parsed.teamId, resources: parsed.resources },
358
+ { apiToken: parsed.apiToken },
359
+ ctx
360
+ );
361
+ }
362
+ id = id;
363
+ credentials = clickupCredentials;
364
+ spacesCache;
365
+ buildHeaders() {
366
+ return {
367
+ Authorization: this.creds.apiToken,
368
+ Accept: "application/json",
369
+ "User-Agent": connectorUserAgent("clickup")
370
+ };
371
+ }
372
+ apiGet(url, resource, signal) {
373
+ return this.get(url, {
374
+ resource,
375
+ headers: this.buildHeaders(),
376
+ signal,
377
+ rateLimit: clickupRateLimit
378
+ });
379
+ }
380
+ get teamPath() {
381
+ return `/team/${encodeURIComponent(this.settings.teamId)}`;
382
+ }
383
+ async getSpaces(signal) {
384
+ if (this.spacesCache) {
385
+ return this.spacesCache;
386
+ }
387
+ const res = await this.apiGet(
388
+ `${API_BASE}${this.teamPath}/space?archived=false`,
389
+ "spaces",
390
+ signal
391
+ );
392
+ this.spacesCache = res.body.spaces ?? [];
393
+ return this.spacesCache;
394
+ }
395
+ async getFolders(spaceId, signal) {
396
+ const res = await this.apiGet(
397
+ `${API_BASE}/space/${encodeURIComponent(spaceId)}/folder?archived=false`,
398
+ "folders",
399
+ signal
400
+ );
401
+ return res.body.folders ?? [];
402
+ }
403
+ async getFolderlessLists(spaceId, signal) {
404
+ const res = await this.apiGet(
405
+ `${API_BASE}/space/${encodeURIComponent(spaceId)}/list?archived=false`,
406
+ "lists",
407
+ signal
408
+ );
409
+ return res.body.lists ?? [];
410
+ }
411
+ async getFolderLists(folderId, signal) {
412
+ const res = await this.apiGet(
413
+ `${API_BASE}/folder/${encodeURIComponent(folderId)}/list?archived=false`,
414
+ "lists",
415
+ signal
416
+ );
417
+ return res.body.lists ?? [];
418
+ }
419
+ buildTasksUrl(page, options, applySince) {
420
+ const url = new URL(`${API_BASE}${this.teamPath}/task`);
421
+ url.searchParams.set("page", String(page));
422
+ url.searchParams.set("include_closed", "true");
423
+ url.searchParams.set("subtasks", "true");
424
+ url.searchParams.set("order_by", "updated");
425
+ const listId = pushableEq(
426
+ this.singleSpec(options, TASK_ENTITY)?.filter,
427
+ "listId"
428
+ );
429
+ if (listId !== null) {
430
+ url.searchParams.append("list_ids[]", listId);
431
+ }
432
+ if (applySince && options.since) {
433
+ const sinceMs = parseEpoch(options.since, "iso");
434
+ if (sinceMs !== null) {
435
+ url.searchParams.set("date_updated_gt", String(sinceMs));
436
+ }
437
+ }
438
+ return url.toString();
439
+ }
440
+ singleSpec(options, resource) {
441
+ const specs = options.fetchSpecs?.[resource];
442
+ return specs && specs.length === 1 ? specs[0] : void 0;
443
+ }
444
+ async fetchSpacesPage(signal) {
445
+ const spaces = await this.getSpaces(signal);
446
+ return { items: spaces, next: null };
447
+ }
448
+ async fetchFoldersPage(signal) {
449
+ const spaces = await this.getSpaces(signal);
450
+ const folders = [];
451
+ for (const space of spaces) {
452
+ const spaceFolders = await this.getFolders(space.id, signal);
453
+ for (const folder of spaceFolders) {
454
+ folders.push({ ...folder, space: folder.space ?? { id: space.id } });
455
+ }
456
+ }
457
+ return { items: folders, next: null };
458
+ }
459
+ async fetchListsPage(signal) {
460
+ const spaces = await this.getSpaces(signal);
461
+ const lists = [];
462
+ for (const space of spaces) {
463
+ const folderless = await this.getFolderlessLists(space.id, signal);
464
+ for (const list of folderless) {
465
+ lists.push({ ...list, space: list.space ?? { id: space.id } });
466
+ }
467
+ const folders = await this.getFolders(space.id, signal);
468
+ for (const folder of folders) {
469
+ const folderLists = await this.getFolderLists(folder.id, signal);
470
+ for (const list of folderLists) {
471
+ lists.push({
472
+ ...list,
473
+ folder: list.folder ?? { id: folder.id, name: folder.name },
474
+ space: list.space ?? { id: space.id }
475
+ });
476
+ }
477
+ }
478
+ }
479
+ return { items: lists, next: null };
480
+ }
481
+ async fetchTasksPage(phase, page, options, signal) {
482
+ const pageNum = page === null ? 0 : Number(page);
483
+ const safePage = Number.isFinite(pageNum) && pageNum >= 0 ? pageNum : 0;
484
+ const url = this.buildTasksUrl(safePage, options, phase === "tasks");
485
+ const res = await this.apiGet(url, phase, signal);
486
+ const tasks = res.body.tasks ?? [];
487
+ const lastPage = res.body.last_page;
488
+ const exhausted = tasks.length === 0 || (typeof lastPage === "boolean" ? lastPage : tasks.length < TASKS_PER_PAGE);
489
+ return { items: tasks, next: exhausted ? null : String(safePage + 1) };
490
+ }
491
+ async writeSpaces(storage, spaces) {
492
+ for (const space of spaces) {
493
+ await storage.entity({
494
+ type: SPACE_ENTITY,
495
+ id: space.id,
496
+ attributes: {
497
+ name: space.name,
498
+ private: space.private ?? null,
499
+ archived: space.archived ?? null
500
+ },
501
+ updated_at: 0
502
+ });
503
+ }
504
+ }
505
+ async writeFolders(storage, folders) {
506
+ for (const folder of folders) {
507
+ await storage.entity({
508
+ type: FOLDER_ENTITY,
509
+ id: folder.id,
510
+ attributes: {
511
+ name: folder.name,
512
+ spaceId: folder.space?.id ?? null,
513
+ taskCount: folder.task_count === null || folder.task_count === void 0 ? null : Number(folder.task_count),
514
+ archived: folder.archived ?? null
515
+ },
516
+ updated_at: 0
517
+ });
518
+ }
519
+ }
520
+ async writeLists(storage, lists) {
521
+ for (const list of lists) {
522
+ await storage.entity({
523
+ type: LIST_ENTITY,
524
+ id: list.id,
525
+ attributes: {
526
+ name: list.name,
527
+ folderId: list.folder?.id ?? null,
528
+ spaceId: list.space?.id ?? null,
529
+ taskCount: list.task_count ?? null,
530
+ archived: list.archived ?? null
531
+ },
532
+ updated_at: 0
533
+ });
534
+ }
535
+ }
536
+ async writeTasks(storage, tasks) {
537
+ for (const task of tasks) {
538
+ const attributes = {
539
+ name: task.name,
540
+ status: task.status?.status ?? null,
541
+ statusType: task.status?.type ?? null,
542
+ priority: task.priority?.priority ?? null,
543
+ listId: task.list?.id ?? null,
544
+ folderId: task.folder?.id ?? null,
545
+ spaceId: task.space?.id ?? null,
546
+ assignees: assigneeIds(task.assignees),
547
+ assigneeCount: task.assignees?.length ?? 0,
548
+ tags: tagNames(task.tags),
549
+ creatorId: task.creator?.id === null || task.creator?.id === void 0 ? null : String(task.creator.id),
550
+ url: task.url ?? null,
551
+ timeEstimate: task.time_estimate ?? null,
552
+ dueDate: epochMs(task.due_date),
553
+ createdAt: epochMs(task.date_created),
554
+ closedAt: epochMs(task.date_closed),
555
+ doneAt: epochMs(task.date_done)
556
+ };
557
+ await storage.entity({
558
+ type: TASK_ENTITY,
559
+ id: task.id,
560
+ attributes,
561
+ updated_at: epochMs(task.date_updated) ?? epochMs(task.date_created) ?? 0
562
+ });
563
+ }
564
+ }
565
+ async writeTaskEvents(storage, tasks) {
566
+ for (const task of tasks) {
567
+ const base = {
568
+ taskId: task.id,
569
+ listId: task.list?.id ?? null,
570
+ spaceId: task.space?.id ?? null,
571
+ status: task.status?.status ?? null
572
+ };
573
+ const createdMs = epochMs(task.date_created);
574
+ if (createdMs !== null) {
575
+ await storage.event({
576
+ name: TASK_EVENT,
577
+ start_ts: createdMs,
578
+ end_ts: null,
579
+ attributes: { ...base, kind: "created" }
580
+ });
581
+ }
582
+ const closedMs = epochMs(task.date_closed);
583
+ if (closedMs !== null) {
584
+ await storage.event({
585
+ name: TASK_EVENT,
586
+ start_ts: closedMs,
587
+ end_ts: null,
588
+ attributes: { ...base, kind: "closed" }
589
+ });
590
+ }
591
+ }
592
+ }
593
+ async clearScopeOnFirstPage(storage, phase, isFull) {
594
+ if (phase === "task_events") {
595
+ await storage.events([], { names: [TASK_EVENT] });
596
+ return;
597
+ }
598
+ if (!isFull) {
599
+ return;
600
+ }
601
+ const entityType = ENTITY_TYPE_BY_PHASE[phase];
602
+ if (entityType) {
603
+ await storage.entities([], { types: [entityType] });
604
+ }
605
+ }
606
+ async writePhase(storage, phase, items) {
607
+ switch (phase) {
608
+ case "spaces":
609
+ return this.writeSpaces(storage, items);
610
+ case "folders":
611
+ return this.writeFolders(storage, items);
612
+ case "lists":
613
+ return this.writeLists(storage, items);
614
+ case "tasks":
615
+ return this.writeTasks(storage, items);
616
+ case "task_events":
617
+ return this.writeTaskEvents(storage, items);
618
+ }
619
+ }
620
+ async sync(options, storage, signal) {
621
+ this.spacesCache = void 0;
622
+ const cursor = isClickUpSyncCursor(options.cursor) ? options.cursor : void 0;
623
+ const isFull = options.mode === "full";
624
+ const phases = selectActivePhases(
625
+ (r) => r,
626
+ PHASE_ORDER,
627
+ this.settings.resources
628
+ );
629
+ return paginateChunked({
630
+ phases,
631
+ cursor,
632
+ signal,
633
+ logger: this.logger,
634
+ fetchPage: async (phase, page, sig) => {
635
+ switch (phase) {
636
+ case "spaces":
637
+ return this.fetchSpacesPage(sig);
638
+ case "folders":
639
+ return this.fetchFoldersPage(sig);
640
+ case "lists":
641
+ return this.fetchListsPage(sig);
642
+ case "tasks":
643
+ case "task_events":
644
+ return this.fetchTasksPage(phase, page, options, sig);
645
+ }
646
+ },
647
+ writeBatch: async (phase, items, page) => {
648
+ if (page === null) {
649
+ await this.clearScopeOnFirstPage(storage, phase, isFull);
650
+ }
651
+ await this.writePhase(storage, phase, items);
652
+ }
653
+ });
654
+ }
655
+ };
656
+ var ENTITY_TYPE_BY_PHASE = {
657
+ spaces: SPACE_ENTITY,
658
+ folders: FOLDER_ENTITY,
659
+ lists: LIST_ENTITY,
660
+ tasks: TASK_ENTITY
661
+ };
662
+
663
+ // src/index.ts
664
+ var index_default = ClickUpConnector;
665
+ export {
666
+ ClickUpConnector,
667
+ configFields,
668
+ index_default as default,
669
+ doc,
670
+ id,
671
+ clickupResources as resources
672
+ };
673
+ //# sourceMappingURL=index.js.map