@agentuity/core 1.0.29 → 1.0.30
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/services/email.d.ts +507 -8
- package/dist/services/email.d.ts.map +1 -1
- package/dist/services/email.js +325 -1
- package/dist/services/email.js.map +1 -1
- package/dist/services/keyvalue.d.ts +5 -2
- package/dist/services/keyvalue.d.ts.map +1 -1
- package/dist/services/keyvalue.js +44 -1
- package/dist/services/keyvalue.js.map +1 -1
- package/dist/services/schedule.d.ts +413 -0
- package/dist/services/schedule.d.ts.map +1 -1
- package/dist/services/schedule.js +221 -0
- package/dist/services/schedule.js.map +1 -1
- package/dist/services/task.d.ts +1005 -1
- package/dist/services/task.d.ts.map +1 -1
- package/dist/services/task.js +518 -1
- package/dist/services/task.js.map +1 -1
- package/dist/services/webhook.d.ts +352 -0
- package/dist/services/webhook.d.ts.map +1 -1
- package/dist/services/webhook.js +254 -0
- package/dist/services/webhook.js.map +1 -1
- package/package.json +2 -2
- package/src/services/email.ts +560 -9
- package/src/services/keyvalue.ts +51 -3
- package/src/services/schedule.ts +442 -0
- package/src/services/task.ts +1158 -13
- package/src/services/webhook.ts +412 -0
package/src/services/task.ts
CHANGED
|
@@ -3,316 +3,1018 @@ import { buildUrl, toServiceException } from './_util.ts';
|
|
|
3
3
|
import { StructuredError } from '../error.ts';
|
|
4
4
|
import { safeStringify } from '../json.ts';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Priority level for a task, from highest (`'high'`) to no priority (`'none'`).
|
|
8
|
+
*/
|
|
7
9
|
export type TaskPriority = 'high' | 'medium' | 'low' | 'none';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The classification of a task.
|
|
13
|
+
*
|
|
14
|
+
* - `'epic'` — Large initiatives that span multiple features or tasks.
|
|
15
|
+
* - `'feature'` — New capabilities to be built.
|
|
16
|
+
* - `'enhancement'` — Improvements to existing features.
|
|
17
|
+
* - `'bug'` — Defects to be fixed.
|
|
18
|
+
* - `'task'` — General work items.
|
|
19
|
+
*/
|
|
8
20
|
export type TaskType = 'epic' | 'feature' | 'enhancement' | 'bug' | 'task';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The lifecycle status of a task.
|
|
24
|
+
*
|
|
25
|
+
* - `'open'` — Created, not yet started.
|
|
26
|
+
* - `'in_progress'` — Actively being worked on.
|
|
27
|
+
* - `'done'` — Work completed.
|
|
28
|
+
* - `'closed'` — Resolved and closed.
|
|
29
|
+
* - `'cancelled'` — Abandoned.
|
|
30
|
+
*/
|
|
9
31
|
export type TaskStatus = 'open' | 'in_progress' | 'closed' | 'done' | 'cancelled';
|
|
10
32
|
|
|
11
|
-
|
|
33
|
+
/**
|
|
34
|
+
* A lightweight reference to a user or project entity, containing just the ID
|
|
35
|
+
* and display name. Used for creator, assignee, closer, and project associations.
|
|
36
|
+
*/
|
|
12
37
|
export interface EntityRef {
|
|
38
|
+
/** Unique identifier of the referenced entity. */
|
|
13
39
|
id: string;
|
|
40
|
+
|
|
41
|
+
/** Human-readable display name of the entity. */
|
|
14
42
|
name: string;
|
|
15
43
|
}
|
|
16
44
|
|
|
17
|
-
|
|
45
|
+
/**
|
|
46
|
+
* A work item in the task management system.
|
|
47
|
+
*
|
|
48
|
+
* Tasks can represent epics, features, bugs, enhancements, or generic tasks.
|
|
49
|
+
* They support hierarchical organization via {@link Task.parent_id | parent_id},
|
|
50
|
+
* assignment tracking, and lifecycle management through status transitions.
|
|
51
|
+
*
|
|
52
|
+
* @remarks
|
|
53
|
+
* Status transitions are tracked automatically — when a task moves to a new status,
|
|
54
|
+
* the corresponding date field (e.g., {@link Task.open_date | open_date},
|
|
55
|
+
* {@link Task.in_progress_date | in_progress_date}) is set by the server.
|
|
56
|
+
*/
|
|
18
57
|
export interface Task {
|
|
58
|
+
/** Unique identifier for the task. */
|
|
19
59
|
id: string;
|
|
60
|
+
|
|
61
|
+
/** ISO 8601 timestamp when the task was created. */
|
|
20
62
|
created_at: string;
|
|
63
|
+
|
|
64
|
+
/** ISO 8601 timestamp when the task was last modified. */
|
|
21
65
|
updated_at: string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The task title.
|
|
69
|
+
*
|
|
70
|
+
* @remarks Must be non-empty and at most 1024 characters.
|
|
71
|
+
*/
|
|
22
72
|
title: string;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Detailed description of the task.
|
|
76
|
+
*
|
|
77
|
+
* @remarks Maximum 65,536 characters.
|
|
78
|
+
*/
|
|
23
79
|
description?: string;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Arbitrary key-value metadata attached to the task.
|
|
83
|
+
* Can be used for custom fields, integrations, or filtering.
|
|
84
|
+
*/
|
|
24
85
|
metadata?: Record<string, unknown>;
|
|
86
|
+
|
|
87
|
+
/** The priority level of the task. */
|
|
25
88
|
priority: TaskPriority;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* ID of the parent task, enabling hierarchical task organization
|
|
92
|
+
* (e.g., an epic containing features).
|
|
93
|
+
*/
|
|
26
94
|
parent_id?: string;
|
|
95
|
+
|
|
96
|
+
/** The classification of this task. */
|
|
27
97
|
type: TaskType;
|
|
98
|
+
|
|
99
|
+
/** The current lifecycle status of the task. */
|
|
28
100
|
status: TaskStatus;
|
|
101
|
+
|
|
102
|
+
/** ISO 8601 timestamp when the task was moved to `'open'` status. */
|
|
29
103
|
open_date?: string;
|
|
104
|
+
|
|
105
|
+
/** ISO 8601 timestamp when the task was moved to `'in_progress'` status. */
|
|
30
106
|
in_progress_date?: string;
|
|
107
|
+
|
|
108
|
+
/** ISO 8601 timestamp when the task was closed. */
|
|
31
109
|
closed_date?: string;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* ID of the user who created the task.
|
|
113
|
+
*
|
|
114
|
+
* @remarks Legacy field; prefer {@link Task.creator | creator}.
|
|
115
|
+
*/
|
|
32
116
|
created_id: string;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* ID of the user the task is assigned to.
|
|
120
|
+
*
|
|
121
|
+
* @remarks Legacy field; prefer {@link Task.assignee | assignee}.
|
|
122
|
+
*/
|
|
33
123
|
assigned_id?: string;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* ID of the user who closed the task.
|
|
127
|
+
*
|
|
128
|
+
* @remarks Legacy field; prefer {@link Task.closer | closer}.
|
|
129
|
+
*/
|
|
34
130
|
closed_id?: string;
|
|
131
|
+
|
|
132
|
+
/** Reference to the user who created the task. */
|
|
35
133
|
creator?: EntityRef;
|
|
134
|
+
|
|
135
|
+
/** Reference to the user the task is assigned to. */
|
|
36
136
|
assignee?: EntityRef;
|
|
137
|
+
|
|
138
|
+
/** Reference to the user who closed the task. */
|
|
37
139
|
closer?: EntityRef;
|
|
140
|
+
|
|
141
|
+
/** Reference to the project this task belongs to. */
|
|
38
142
|
project?: EntityRef;
|
|
143
|
+
|
|
144
|
+
/** ISO 8601 timestamp when the task was cancelled. */
|
|
39
145
|
cancelled_date?: string;
|
|
40
|
-
|
|
146
|
+
|
|
147
|
+
/** Array of tags associated with this task. */
|
|
41
148
|
tags?: Tag[];
|
|
149
|
+
|
|
150
|
+
/** Array of comments on this task. */
|
|
42
151
|
comments?: Comment[];
|
|
43
152
|
}
|
|
44
153
|
|
|
45
|
-
|
|
154
|
+
/**
|
|
155
|
+
* A comment on a task, supporting threaded discussion.
|
|
156
|
+
*/
|
|
46
157
|
export interface Comment {
|
|
158
|
+
/** Unique identifier for the comment. */
|
|
47
159
|
id: string;
|
|
160
|
+
|
|
161
|
+
/** ISO 8601 timestamp when the comment was created. */
|
|
48
162
|
created_at: string;
|
|
163
|
+
|
|
164
|
+
/** ISO 8601 timestamp when the comment was last edited. */
|
|
49
165
|
updated_at: string;
|
|
166
|
+
|
|
167
|
+
/** ID of the task this comment belongs to. */
|
|
50
168
|
task_id: string;
|
|
169
|
+
|
|
170
|
+
/** ID of the user who authored the comment. */
|
|
51
171
|
user_id: string;
|
|
172
|
+
|
|
173
|
+
/** Reference to the comment author with display name. */
|
|
52
174
|
author?: EntityRef;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The comment text content.
|
|
178
|
+
*
|
|
179
|
+
* @remarks Must be non-empty.
|
|
180
|
+
*/
|
|
53
181
|
body: string;
|
|
54
182
|
}
|
|
55
183
|
|
|
56
|
-
|
|
184
|
+
/**
|
|
185
|
+
* A label that can be applied to tasks for categorization and filtering.
|
|
186
|
+
*/
|
|
57
187
|
export interface Tag {
|
|
188
|
+
/** Unique identifier for the tag. */
|
|
58
189
|
id: string;
|
|
190
|
+
|
|
191
|
+
/** ISO 8601 timestamp when the tag was created. */
|
|
59
192
|
created_at: string;
|
|
193
|
+
|
|
194
|
+
/** Display name of the tag. */
|
|
60
195
|
name: string;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Optional hex color code for the tag.
|
|
199
|
+
*
|
|
200
|
+
* @example '#ff0000'
|
|
201
|
+
*/
|
|
61
202
|
color?: string;
|
|
62
203
|
}
|
|
63
204
|
|
|
64
|
-
|
|
205
|
+
/**
|
|
206
|
+
* A record of a single field change on a task, providing an audit trail.
|
|
207
|
+
*/
|
|
65
208
|
export interface TaskChangelogEntry {
|
|
209
|
+
/** Unique identifier for the changelog entry. */
|
|
66
210
|
id: string;
|
|
211
|
+
|
|
212
|
+
/** ISO 8601 timestamp when the change occurred. */
|
|
67
213
|
created_at: string;
|
|
214
|
+
|
|
215
|
+
/** ID of the task that was changed. */
|
|
68
216
|
task_id: string;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Name of the field that was changed.
|
|
220
|
+
*
|
|
221
|
+
* @example 'status'
|
|
222
|
+
* @example 'priority'
|
|
223
|
+
* @example 'assigned_id'
|
|
224
|
+
*/
|
|
69
225
|
field: string;
|
|
226
|
+
|
|
227
|
+
/** The previous value of the field (as a string), or `undefined` if the field was newly set. */
|
|
70
228
|
old_value?: string;
|
|
229
|
+
|
|
230
|
+
/** The new value of the field (as a string), or `undefined` if the field was cleared. */
|
|
71
231
|
new_value?: string;
|
|
72
232
|
}
|
|
73
233
|
|
|
74
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Parameters for creating a new task.
|
|
236
|
+
*/
|
|
75
237
|
export interface CreateTaskParams {
|
|
238
|
+
/**
|
|
239
|
+
* The task title (required).
|
|
240
|
+
*
|
|
241
|
+
* @remarks Must be non-empty and at most 1024 characters.
|
|
242
|
+
*/
|
|
76
243
|
title: string;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Detailed description of the task.
|
|
247
|
+
*
|
|
248
|
+
* @remarks Maximum 65,536 characters.
|
|
249
|
+
*/
|
|
77
250
|
description?: string;
|
|
251
|
+
|
|
252
|
+
/** Arbitrary key-value metadata. */
|
|
78
253
|
metadata?: Record<string, unknown>;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Priority level. Defaults to `'none'` if not provided.
|
|
257
|
+
*
|
|
258
|
+
* @default 'none'
|
|
259
|
+
*/
|
|
79
260
|
priority?: TaskPriority;
|
|
261
|
+
|
|
262
|
+
/** ID of the parent task for hierarchical organization. */
|
|
80
263
|
parent_id?: string;
|
|
264
|
+
|
|
265
|
+
/** The task classification (required). */
|
|
81
266
|
type: TaskType;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Initial status. Defaults to `'open'` if not provided.
|
|
270
|
+
*
|
|
271
|
+
* @default 'open'
|
|
272
|
+
*/
|
|
82
273
|
status?: TaskStatus;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* ID of the creator.
|
|
277
|
+
*
|
|
278
|
+
* @remarks Legacy field; prefer {@link CreateTaskParams.creator | creator}.
|
|
279
|
+
*/
|
|
83
280
|
created_id: string;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* ID of the assigned user.
|
|
284
|
+
*
|
|
285
|
+
* @remarks Legacy field; prefer {@link CreateTaskParams.assignee | assignee}.
|
|
286
|
+
*/
|
|
84
287
|
assigned_id?: string;
|
|
288
|
+
|
|
289
|
+
/** Reference to the user creating the task (id and name). */
|
|
85
290
|
creator?: EntityRef;
|
|
291
|
+
|
|
292
|
+
/** Reference to the user being assigned the task. */
|
|
86
293
|
assignee?: EntityRef;
|
|
294
|
+
|
|
295
|
+
/** Reference to the project this task belongs to. */
|
|
87
296
|
project?: EntityRef;
|
|
297
|
+
|
|
298
|
+
/** Array of tag IDs to associate with the task at creation. */
|
|
88
299
|
tag_ids?: string[];
|
|
89
300
|
}
|
|
90
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Parameters for partially updating an existing task.
|
|
304
|
+
*
|
|
305
|
+
* @remarks Only provided fields are modified; omitted fields remain unchanged.
|
|
306
|
+
*/
|
|
91
307
|
export interface UpdateTaskParams {
|
|
308
|
+
/**
|
|
309
|
+
* Updated task title.
|
|
310
|
+
*
|
|
311
|
+
* @remarks Must be non-empty and at most 1024 characters if provided.
|
|
312
|
+
*/
|
|
92
313
|
title?: string;
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Updated description.
|
|
317
|
+
*
|
|
318
|
+
* @remarks Maximum 65,536 characters.
|
|
319
|
+
*/
|
|
93
320
|
description?: string;
|
|
321
|
+
|
|
322
|
+
/** Updated key-value metadata. */
|
|
94
323
|
metadata?: Record<string, unknown>;
|
|
324
|
+
|
|
325
|
+
/** Updated priority level. */
|
|
95
326
|
priority?: TaskPriority;
|
|
327
|
+
|
|
328
|
+
/** Updated parent task ID. */
|
|
96
329
|
parent_id?: string;
|
|
330
|
+
|
|
331
|
+
/** Updated task classification. */
|
|
97
332
|
type?: TaskType;
|
|
333
|
+
|
|
334
|
+
/** Updated lifecycle status. */
|
|
98
335
|
status?: TaskStatus;
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Updated assigned user ID.
|
|
339
|
+
*
|
|
340
|
+
* @remarks Legacy field; prefer {@link UpdateTaskParams.assignee | assignee}.
|
|
341
|
+
*/
|
|
99
342
|
assigned_id?: string;
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* ID of the user closing the task.
|
|
346
|
+
*
|
|
347
|
+
* @remarks Legacy field; prefer {@link UpdateTaskParams.closer | closer}.
|
|
348
|
+
*/
|
|
100
349
|
closed_id?: string;
|
|
350
|
+
|
|
351
|
+
/** Reference to the user being assigned the task. */
|
|
101
352
|
assignee?: EntityRef;
|
|
353
|
+
|
|
354
|
+
/** Reference to the user closing the task. */
|
|
102
355
|
closer?: EntityRef;
|
|
356
|
+
|
|
357
|
+
/** Reference to the project this task belongs to. */
|
|
103
358
|
project?: EntityRef;
|
|
104
359
|
}
|
|
105
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Parameters for filtering and paginating the task list.
|
|
363
|
+
*/
|
|
106
364
|
export interface ListTasksParams {
|
|
365
|
+
/** Filter by task status. */
|
|
107
366
|
status?: TaskStatus;
|
|
367
|
+
|
|
368
|
+
/** Filter by task type. */
|
|
108
369
|
type?: TaskType;
|
|
370
|
+
|
|
371
|
+
/** Filter by priority level. */
|
|
109
372
|
priority?: TaskPriority;
|
|
373
|
+
|
|
374
|
+
/** Filter by assigned user ID. */
|
|
110
375
|
assigned_id?: string;
|
|
376
|
+
|
|
377
|
+
/** Filter by parent task ID (get subtasks). */
|
|
111
378
|
parent_id?: string;
|
|
379
|
+
|
|
380
|
+
/** Filter by project ID. */
|
|
112
381
|
project_id?: string;
|
|
382
|
+
|
|
383
|
+
/** Filter by tag ID. */
|
|
113
384
|
tag_id?: string;
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Filter for soft-deleted tasks.
|
|
388
|
+
*
|
|
389
|
+
* @default false
|
|
390
|
+
*/
|
|
114
391
|
deleted?: boolean;
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Sort field. Prefix with `-` for descending order.
|
|
395
|
+
*
|
|
396
|
+
* @remarks Supported values: `'created_at'`, `'updated_at'`, `'priority'`.
|
|
397
|
+
* Prefix with `-` for descending (e.g., `'-created_at'`).
|
|
398
|
+
*/
|
|
115
399
|
sort?: string;
|
|
400
|
+
|
|
401
|
+
/** Sort direction: `'asc'` or `'desc'`. */
|
|
116
402
|
order?: 'asc' | 'desc';
|
|
403
|
+
|
|
404
|
+
/** Maximum number of results to return. */
|
|
117
405
|
limit?: number;
|
|
406
|
+
|
|
407
|
+
/** Number of results to skip for pagination. */
|
|
118
408
|
offset?: number;
|
|
119
409
|
}
|
|
120
410
|
|
|
411
|
+
/**
|
|
412
|
+
* Paginated list of tasks with total count.
|
|
413
|
+
*/
|
|
121
414
|
export interface ListTasksResult {
|
|
415
|
+
/** Array of tasks matching the query. */
|
|
122
416
|
tasks: Task[];
|
|
417
|
+
|
|
418
|
+
/** Total number of tasks matching the filters (before pagination). */
|
|
123
419
|
total: number;
|
|
420
|
+
|
|
421
|
+
/** The limit that was applied. */
|
|
124
422
|
limit: number;
|
|
423
|
+
|
|
424
|
+
/** The offset that was applied. */
|
|
125
425
|
offset: number;
|
|
126
426
|
}
|
|
127
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Paginated list of changelog entries for a task.
|
|
430
|
+
*/
|
|
128
431
|
export interface TaskChangelogResult {
|
|
432
|
+
/** Array of change records. */
|
|
129
433
|
changelog: TaskChangelogEntry[];
|
|
434
|
+
|
|
435
|
+
/** Total number of changelog entries. */
|
|
130
436
|
total: number;
|
|
437
|
+
|
|
438
|
+
/** Applied limit. */
|
|
131
439
|
limit: number;
|
|
440
|
+
|
|
441
|
+
/** Applied offset. */
|
|
132
442
|
offset: number;
|
|
133
443
|
}
|
|
134
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Paginated list of comments on a task.
|
|
447
|
+
*/
|
|
135
448
|
export interface ListCommentsResult {
|
|
449
|
+
/** Array of comments. */
|
|
136
450
|
comments: Comment[];
|
|
451
|
+
|
|
452
|
+
/** Total number of comments. */
|
|
137
453
|
total: number;
|
|
454
|
+
|
|
455
|
+
/** Applied limit. */
|
|
138
456
|
limit: number;
|
|
457
|
+
|
|
458
|
+
/** Applied offset. */
|
|
139
459
|
offset: number;
|
|
140
460
|
}
|
|
141
461
|
|
|
462
|
+
/**
|
|
463
|
+
* List of all tags in the organization.
|
|
464
|
+
*/
|
|
142
465
|
export interface ListTagsResult {
|
|
466
|
+
/** Array of tags. */
|
|
143
467
|
tags: Tag[];
|
|
144
468
|
}
|
|
145
469
|
|
|
146
|
-
|
|
470
|
+
/**
|
|
471
|
+
* A file attachment on a task. Attachments are stored in S3 and accessed via presigned URLs.
|
|
472
|
+
*/
|
|
147
473
|
export interface Attachment {
|
|
474
|
+
/** Unique identifier for the attachment. */
|
|
148
475
|
id: string;
|
|
476
|
+
|
|
477
|
+
/** ISO 8601 timestamp when the attachment was uploaded. */
|
|
149
478
|
created_at: string;
|
|
479
|
+
|
|
480
|
+
/** ID of the task this attachment belongs to. */
|
|
150
481
|
task_id: string;
|
|
482
|
+
|
|
483
|
+
/** ID of the user who uploaded the attachment. */
|
|
151
484
|
user_id: string;
|
|
485
|
+
|
|
486
|
+
/** Reference to the uploader with display name. */
|
|
152
487
|
author?: EntityRef;
|
|
488
|
+
|
|
489
|
+
/** Original filename of the uploaded file. */
|
|
153
490
|
filename: string;
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* MIME type of the file.
|
|
494
|
+
*
|
|
495
|
+
* @example 'application/pdf'
|
|
496
|
+
*/
|
|
154
497
|
content_type?: string;
|
|
498
|
+
|
|
499
|
+
/** File size in bytes. */
|
|
155
500
|
size?: number;
|
|
156
501
|
}
|
|
157
502
|
|
|
503
|
+
/**
|
|
504
|
+
* Parameters for initiating a file upload to a task.
|
|
505
|
+
*/
|
|
158
506
|
export interface CreateAttachmentParams {
|
|
507
|
+
/** The filename for the attachment (required). */
|
|
159
508
|
filename: string;
|
|
509
|
+
|
|
510
|
+
/** MIME type of the file. */
|
|
160
511
|
content_type?: string;
|
|
512
|
+
|
|
513
|
+
/** File size in bytes. */
|
|
161
514
|
size?: number;
|
|
162
515
|
}
|
|
163
516
|
|
|
517
|
+
/**
|
|
518
|
+
* Response from initiating an attachment upload. Contains a presigned S3 URL for direct upload.
|
|
519
|
+
*/
|
|
164
520
|
export interface PresignUploadResponse {
|
|
521
|
+
/** The created attachment record. */
|
|
165
522
|
attachment: Attachment;
|
|
523
|
+
|
|
524
|
+
/** A presigned S3 URL to upload the file content via HTTP PUT. */
|
|
166
525
|
presigned_url: string;
|
|
526
|
+
|
|
527
|
+
/** Number of seconds until the presigned URL expires. */
|
|
167
528
|
expiry_seconds: number;
|
|
168
529
|
}
|
|
169
530
|
|
|
531
|
+
/**
|
|
532
|
+
* Response containing a presigned S3 URL for downloading an attachment.
|
|
533
|
+
*/
|
|
170
534
|
export interface PresignDownloadResponse {
|
|
535
|
+
/** A presigned S3 URL to download the file via HTTP GET. */
|
|
171
536
|
presigned_url: string;
|
|
537
|
+
|
|
538
|
+
/** Number of seconds until the presigned URL expires. */
|
|
172
539
|
expiry_seconds: number;
|
|
173
540
|
}
|
|
174
541
|
|
|
542
|
+
/**
|
|
543
|
+
* List of attachments on a task.
|
|
544
|
+
*/
|
|
175
545
|
export interface ListAttachmentsResult {
|
|
546
|
+
/** Array of attachment records. */
|
|
176
547
|
attachments: Attachment[];
|
|
548
|
+
|
|
549
|
+
/** Total number of attachments. */
|
|
177
550
|
total: number;
|
|
178
551
|
}
|
|
179
552
|
|
|
553
|
+
/**
|
|
554
|
+
* List of all users who have been referenced in tasks (as creators, assignees, or closers).
|
|
555
|
+
*/
|
|
180
556
|
export interface ListUsersResult {
|
|
557
|
+
/** Array of user entity references. */
|
|
181
558
|
users: EntityRef[];
|
|
182
559
|
}
|
|
183
560
|
|
|
561
|
+
/**
|
|
562
|
+
* List of all projects that have been referenced in tasks.
|
|
563
|
+
*/
|
|
184
564
|
export interface ListProjectsResult {
|
|
565
|
+
/** Array of project entity references. */
|
|
185
566
|
projects: EntityRef[];
|
|
186
567
|
}
|
|
187
568
|
|
|
569
|
+
/**
|
|
570
|
+
* Parameters for querying task activity time-series data.
|
|
571
|
+
*/
|
|
188
572
|
export interface TaskActivityParams {
|
|
189
|
-
|
|
573
|
+
/**
|
|
574
|
+
* Number of days of activity to retrieve.
|
|
575
|
+
*
|
|
576
|
+
* @remarks Minimum 7, maximum 365.
|
|
577
|
+
* @default 90
|
|
578
|
+
*/
|
|
579
|
+
days?: number;
|
|
190
580
|
}
|
|
191
581
|
|
|
582
|
+
/**
|
|
583
|
+
* A single day's snapshot of task counts by status.
|
|
584
|
+
*/
|
|
192
585
|
export interface TaskActivityDataPoint {
|
|
193
|
-
|
|
586
|
+
/**
|
|
587
|
+
* The date in `YYYY-MM-DD` format.
|
|
588
|
+
*
|
|
589
|
+
* @example '2026-02-28'
|
|
590
|
+
*/
|
|
591
|
+
date: string;
|
|
592
|
+
|
|
593
|
+
/** Number of tasks in `'open'` status on this date. */
|
|
194
594
|
open: number;
|
|
595
|
+
|
|
596
|
+
/** Number of tasks in `'in_progress'` status on this date. */
|
|
195
597
|
inProgress: number;
|
|
598
|
+
|
|
599
|
+
/** Number of tasks in `'done'` status on this date. */
|
|
196
600
|
done: number;
|
|
601
|
+
|
|
602
|
+
/** Number of tasks in `'closed'` status on this date. */
|
|
197
603
|
closed: number;
|
|
604
|
+
|
|
605
|
+
/** Number of tasks in `'cancelled'` status on this date. */
|
|
198
606
|
cancelled: number;
|
|
199
607
|
}
|
|
200
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Task activity time-series data.
|
|
611
|
+
*/
|
|
201
612
|
export interface TaskActivityResult {
|
|
613
|
+
/** Array of daily activity snapshots, ordered chronologically. */
|
|
202
614
|
activity: TaskActivityDataPoint[];
|
|
615
|
+
|
|
616
|
+
/** The number of days of data returned. */
|
|
203
617
|
days: number;
|
|
204
618
|
}
|
|
205
619
|
|
|
620
|
+
/**
|
|
621
|
+
* Interface defining the contract for task storage operations.
|
|
622
|
+
*
|
|
623
|
+
* Implemented by {@link TaskStorageService}.
|
|
624
|
+
*/
|
|
206
625
|
export interface TaskStorage {
|
|
626
|
+
/**
|
|
627
|
+
* Create a new task.
|
|
628
|
+
*
|
|
629
|
+
* @param params - The task creation parameters
|
|
630
|
+
* @returns The newly created task
|
|
631
|
+
*/
|
|
207
632
|
create(params: CreateTaskParams): Promise<Task>;
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Get a task by its ID.
|
|
636
|
+
*
|
|
637
|
+
* @param id - The unique task identifier
|
|
638
|
+
* @returns The task if found, or `null` if not found
|
|
639
|
+
*/
|
|
208
640
|
get(id: string): Promise<Task | null>;
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* List tasks with optional filtering and pagination.
|
|
644
|
+
*
|
|
645
|
+
* @param params - Optional filter and pagination parameters
|
|
646
|
+
* @returns Paginated list of matching tasks
|
|
647
|
+
*/
|
|
209
648
|
list(params?: ListTasksParams): Promise<ListTasksResult>;
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Partially update an existing task.
|
|
652
|
+
*
|
|
653
|
+
* @param id - The unique task identifier
|
|
654
|
+
* @param params - Fields to update (only provided fields are changed)
|
|
655
|
+
* @returns The updated task
|
|
656
|
+
*/
|
|
210
657
|
update(id: string, params: UpdateTaskParams): Promise<Task>;
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Close a task by setting its status to closed.
|
|
661
|
+
*
|
|
662
|
+
* @param id - The unique task identifier
|
|
663
|
+
* @returns The closed task
|
|
664
|
+
*/
|
|
211
665
|
close(id: string): Promise<Task>;
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Soft-delete a task, marking it as deleted without permanent removal.
|
|
669
|
+
*
|
|
670
|
+
* @param id - The unique task identifier
|
|
671
|
+
* @returns The soft-deleted task
|
|
672
|
+
*/
|
|
212
673
|
softDelete(id: string): Promise<Task>;
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Get the changelog (audit trail) for a task.
|
|
677
|
+
*
|
|
678
|
+
* @param id - The unique task identifier
|
|
679
|
+
* @param params - Optional pagination parameters
|
|
680
|
+
* @returns Paginated list of changelog entries
|
|
681
|
+
*/
|
|
213
682
|
changelog(
|
|
214
683
|
id: string,
|
|
215
684
|
params?: { limit?: number; offset?: number }
|
|
216
685
|
): Promise<TaskChangelogResult>;
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Create a comment on a task.
|
|
689
|
+
*
|
|
690
|
+
* @param taskId - The ID of the task to comment on
|
|
691
|
+
* @param body - The comment text content
|
|
692
|
+
* @param userId - The ID of the user authoring the comment
|
|
693
|
+
* @param author - Optional entity reference with display name
|
|
694
|
+
* @returns The newly created comment
|
|
695
|
+
*/
|
|
217
696
|
createComment(
|
|
218
697
|
taskId: string,
|
|
219
698
|
body: string,
|
|
220
699
|
userId: string,
|
|
221
700
|
author?: EntityRef
|
|
222
701
|
): Promise<Comment>;
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Get a comment by its ID.
|
|
705
|
+
*
|
|
706
|
+
* @param commentId - The unique comment identifier
|
|
707
|
+
* @returns The comment
|
|
708
|
+
*/
|
|
223
709
|
getComment(commentId: string): Promise<Comment>;
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Update a comment's body text.
|
|
713
|
+
*
|
|
714
|
+
* @param commentId - The unique comment identifier
|
|
715
|
+
* @param body - The new comment text
|
|
716
|
+
* @returns The updated comment
|
|
717
|
+
*/
|
|
224
718
|
updateComment(commentId: string, body: string): Promise<Comment>;
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Delete a comment.
|
|
722
|
+
*
|
|
723
|
+
* @param commentId - The unique comment identifier
|
|
724
|
+
*/
|
|
225
725
|
deleteComment(commentId: string): Promise<void>;
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* List comments on a task with optional pagination.
|
|
729
|
+
*
|
|
730
|
+
* @param taskId - The ID of the task
|
|
731
|
+
* @param params - Optional pagination parameters
|
|
732
|
+
* @returns Paginated list of comments
|
|
733
|
+
*/
|
|
226
734
|
listComments(
|
|
227
735
|
taskId: string,
|
|
228
736
|
params?: { limit?: number; offset?: number }
|
|
229
737
|
): Promise<ListCommentsResult>;
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Create a new tag.
|
|
741
|
+
*
|
|
742
|
+
* @param name - The tag display name
|
|
743
|
+
* @param color - Optional hex color code (e.g., `'#ff0000'`)
|
|
744
|
+
* @returns The newly created tag
|
|
745
|
+
*/
|
|
230
746
|
createTag(name: string, color?: string): Promise<Tag>;
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Get a tag by its ID.
|
|
750
|
+
*
|
|
751
|
+
* @param tagId - The unique tag identifier
|
|
752
|
+
* @returns The tag
|
|
753
|
+
*/
|
|
231
754
|
getTag(tagId: string): Promise<Tag>;
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Update a tag's name and optionally its color.
|
|
758
|
+
*
|
|
759
|
+
* @param tagId - The unique tag identifier
|
|
760
|
+
* @param name - The new tag name
|
|
761
|
+
* @param color - Optional new hex color code
|
|
762
|
+
* @returns The updated tag
|
|
763
|
+
*/
|
|
232
764
|
updateTag(tagId: string, name: string, color?: string): Promise<Tag>;
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Delete a tag.
|
|
768
|
+
*
|
|
769
|
+
* @param tagId - The unique tag identifier
|
|
770
|
+
*/
|
|
233
771
|
deleteTag(tagId: string): Promise<void>;
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* List all tags in the organization.
|
|
775
|
+
*
|
|
776
|
+
* @returns List of all tags
|
|
777
|
+
*/
|
|
234
778
|
listTags(): Promise<ListTagsResult>;
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Associate a tag with a task.
|
|
782
|
+
*
|
|
783
|
+
* @param taskId - The ID of the task
|
|
784
|
+
* @param tagId - The ID of the tag to add
|
|
785
|
+
*/
|
|
235
786
|
addTagToTask(taskId: string, tagId: string): Promise<void>;
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Remove a tag association from a task.
|
|
790
|
+
*
|
|
791
|
+
* @param taskId - The ID of the task
|
|
792
|
+
* @param tagId - The ID of the tag to remove
|
|
793
|
+
*/
|
|
236
794
|
removeTagFromTask(taskId: string, tagId: string): Promise<void>;
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* List all tags associated with a specific task.
|
|
798
|
+
*
|
|
799
|
+
* @param taskId - The ID of the task
|
|
800
|
+
* @returns Array of tags on the task
|
|
801
|
+
*/
|
|
237
802
|
listTagsForTask(taskId: string): Promise<Tag[]>;
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Initiate a file upload to a task. Returns a presigned S3 URL for direct upload.
|
|
806
|
+
*
|
|
807
|
+
* @param taskId - The ID of the task to attach the file to
|
|
808
|
+
* @param params - Attachment metadata (filename, content type, size)
|
|
809
|
+
* @returns The attachment record and a presigned upload URL
|
|
810
|
+
*/
|
|
238
811
|
uploadAttachment(taskId: string, params: CreateAttachmentParams): Promise<PresignUploadResponse>;
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Confirm that a file upload has completed successfully.
|
|
815
|
+
*
|
|
816
|
+
* @param attachmentId - The unique attachment identifier
|
|
817
|
+
* @returns The confirmed attachment record
|
|
818
|
+
*/
|
|
239
819
|
confirmAttachment(attachmentId: string): Promise<Attachment>;
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Get a presigned S3 URL for downloading an attachment.
|
|
823
|
+
*
|
|
824
|
+
* @param attachmentId - The unique attachment identifier
|
|
825
|
+
* @returns A presigned download URL
|
|
826
|
+
*/
|
|
240
827
|
downloadAttachment(attachmentId: string): Promise<PresignDownloadResponse>;
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* List all attachments on a task.
|
|
831
|
+
*
|
|
832
|
+
* @param taskId - The ID of the task
|
|
833
|
+
* @returns List of attachments with total count
|
|
834
|
+
*/
|
|
241
835
|
listAttachments(taskId: string): Promise<ListAttachmentsResult>;
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Delete an attachment.
|
|
839
|
+
*
|
|
840
|
+
* @param attachmentId - The unique attachment identifier
|
|
841
|
+
*/
|
|
242
842
|
deleteAttachment(attachmentId: string): Promise<void>;
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* List all users who have been referenced in tasks.
|
|
846
|
+
*
|
|
847
|
+
* @returns List of user entity references
|
|
848
|
+
*/
|
|
243
849
|
listUsers(): Promise<ListUsersResult>;
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* List all projects that have been referenced in tasks.
|
|
853
|
+
*
|
|
854
|
+
* @returns List of project entity references
|
|
855
|
+
*/
|
|
244
856
|
listProjects(): Promise<ListProjectsResult>;
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Get task activity time-series data showing daily status counts.
|
|
860
|
+
*
|
|
861
|
+
* @param params - Optional parameters controlling the number of days to retrieve
|
|
862
|
+
* @returns Time-series activity data
|
|
863
|
+
*/
|
|
245
864
|
getActivity(params?: TaskActivityParams): Promise<TaskActivityResult>;
|
|
246
865
|
}
|
|
247
866
|
|
|
867
|
+
/** API version string used for task CRUD, comment, tag, and attachment endpoints. */
|
|
248
868
|
const TASK_API_VERSION = '2026-02-24';
|
|
869
|
+
|
|
870
|
+
/** API version string used for the task activity analytics endpoint. */
|
|
249
871
|
const TASK_ACTIVITY_API_VERSION = '2026-02-28';
|
|
250
872
|
|
|
873
|
+
/** Thrown when a task ID parameter is empty or not a string. */
|
|
251
874
|
const TaskIdRequiredError = StructuredError(
|
|
252
875
|
'TaskIdRequiredError',
|
|
253
876
|
'Task ID is required and must be a non-empty string'
|
|
254
877
|
);
|
|
255
878
|
|
|
879
|
+
/** Thrown when a task title is empty or not a string. */
|
|
256
880
|
const TaskTitleRequiredError = StructuredError(
|
|
257
881
|
'TaskTitleRequiredError',
|
|
258
882
|
'Task title is required and must be a non-empty string'
|
|
259
883
|
);
|
|
260
884
|
|
|
885
|
+
/** Thrown when a comment ID parameter is empty or not a string. */
|
|
261
886
|
const CommentIdRequiredError = StructuredError(
|
|
262
887
|
'CommentIdRequiredError',
|
|
263
888
|
'Comment ID is required and must be a non-empty string'
|
|
264
889
|
);
|
|
265
890
|
|
|
891
|
+
/** Thrown when a comment body is empty or not a string. */
|
|
266
892
|
const CommentBodyRequiredError = StructuredError(
|
|
267
893
|
'CommentBodyRequiredError',
|
|
268
894
|
'Comment body is required and must be a non-empty string'
|
|
269
895
|
);
|
|
270
896
|
|
|
897
|
+
/** Thrown when a tag ID parameter is empty or not a string. */
|
|
271
898
|
const TagIdRequiredError = StructuredError(
|
|
272
899
|
'TagIdRequiredError',
|
|
273
900
|
'Tag ID is required and must be a non-empty string'
|
|
274
901
|
);
|
|
275
902
|
|
|
903
|
+
/** Thrown when a tag name is empty or not a string. */
|
|
276
904
|
const TagNameRequiredError = StructuredError(
|
|
277
905
|
'TagNameRequiredError',
|
|
278
906
|
'Tag name is required and must be a non-empty string'
|
|
279
907
|
);
|
|
280
908
|
|
|
909
|
+
/** Thrown when an attachment ID parameter is empty or not a string. */
|
|
281
910
|
const AttachmentIdRequiredError = StructuredError(
|
|
282
911
|
'AttachmentIdRequiredError',
|
|
283
912
|
'Attachment ID is required and must be a non-empty string'
|
|
284
913
|
);
|
|
285
914
|
|
|
915
|
+
/** Thrown when a user ID parameter is empty or not a string. */
|
|
286
916
|
const UserIdRequiredError = StructuredError(
|
|
287
917
|
'UserIdRequiredError',
|
|
288
918
|
'User ID is required and must be a non-empty string'
|
|
289
919
|
);
|
|
290
920
|
|
|
921
|
+
/**
|
|
922
|
+
* Thrown when the API returns a success HTTP status but the response body indicates failure.
|
|
923
|
+
*/
|
|
291
924
|
const TaskStorageResponseError = StructuredError('TaskStorageResponseError')<{
|
|
292
925
|
status: number;
|
|
293
926
|
}>();
|
|
294
927
|
|
|
928
|
+
/**
|
|
929
|
+
* Internal API success response envelope for task operations.
|
|
930
|
+
*/
|
|
295
931
|
interface TaskSuccessResponse<T> {
|
|
296
932
|
success: true;
|
|
297
933
|
data: T;
|
|
298
934
|
}
|
|
299
935
|
|
|
936
|
+
/**
|
|
937
|
+
* Internal API error response envelope for task operations.
|
|
938
|
+
*/
|
|
300
939
|
interface TaskErrorResponse {
|
|
301
940
|
success: false;
|
|
302
941
|
message: string;
|
|
303
942
|
}
|
|
304
943
|
|
|
944
|
+
/**
|
|
945
|
+
* Discriminated union of API success and error responses for task operations.
|
|
946
|
+
*/
|
|
305
947
|
type TaskResponse<T> = TaskSuccessResponse<T> | TaskErrorResponse;
|
|
306
948
|
|
|
949
|
+
/**
|
|
950
|
+
* Client for the Agentuity Task management service.
|
|
951
|
+
*
|
|
952
|
+
* Provides a full-featured project management API including task CRUD, hierarchical
|
|
953
|
+
* organization (epics → features → tasks), comments, tags, file attachments via
|
|
954
|
+
* presigned S3 URLs, changelog tracking, and activity analytics.
|
|
955
|
+
*
|
|
956
|
+
* Tasks support lifecycle management through status transitions (`open` → `in_progress`
|
|
957
|
+
* → `done`/`closed`/`cancelled`) with automatic date tracking for each transition.
|
|
958
|
+
*
|
|
959
|
+
* All methods validate inputs client-side and throw structured errors for invalid
|
|
960
|
+
* parameters. API errors throw {@link ServiceException}.
|
|
961
|
+
*
|
|
962
|
+
* @example
|
|
963
|
+
* ```typescript
|
|
964
|
+
* const tasks = new TaskStorageService(baseUrl, adapter);
|
|
965
|
+
*
|
|
966
|
+
* // Create a task
|
|
967
|
+
* const task = await tasks.create({
|
|
968
|
+
* title: 'Implement login flow',
|
|
969
|
+
* type: 'feature',
|
|
970
|
+
* created_id: 'user_123',
|
|
971
|
+
* creator: { id: 'user_123', name: 'Alice' },
|
|
972
|
+
* priority: 'high',
|
|
973
|
+
* });
|
|
974
|
+
*
|
|
975
|
+
* // Add a comment
|
|
976
|
+
* await tasks.createComment(task.id, 'Started working on this', 'user_123');
|
|
977
|
+
*
|
|
978
|
+
* // List open tasks
|
|
979
|
+
* const { tasks: openTasks } = await tasks.list({ status: 'open' });
|
|
980
|
+
* ```
|
|
981
|
+
*/
|
|
307
982
|
export class TaskStorageService implements TaskStorage {
|
|
308
983
|
#adapter: FetchAdapter;
|
|
309
984
|
#baseUrl: string;
|
|
310
985
|
|
|
986
|
+
/**
|
|
987
|
+
* Creates a new TaskStorageService instance.
|
|
988
|
+
*
|
|
989
|
+
* @param baseUrl - The base URL of the task management API
|
|
990
|
+
* @param adapter - The HTTP fetch adapter used for making API requests
|
|
991
|
+
*/
|
|
311
992
|
constructor(baseUrl: string, adapter: FetchAdapter) {
|
|
312
993
|
this.#adapter = adapter;
|
|
313
994
|
this.#baseUrl = baseUrl;
|
|
314
995
|
}
|
|
315
996
|
|
|
997
|
+
/**
|
|
998
|
+
* Create a new task.
|
|
999
|
+
*
|
|
1000
|
+
* @param params - The task creation parameters including title, type, and optional fields
|
|
1001
|
+
* @returns The newly created task
|
|
1002
|
+
* @throws {@link TaskTitleRequiredError} if the title is empty or not a string
|
|
1003
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1004
|
+
*
|
|
1005
|
+
* @example
|
|
1006
|
+
* ```typescript
|
|
1007
|
+
* const task = await tasks.create({
|
|
1008
|
+
* title: 'Fix login bug',
|
|
1009
|
+
* type: 'bug',
|
|
1010
|
+
* created_id: 'user_123',
|
|
1011
|
+
* priority: 'high',
|
|
1012
|
+
* creator: { id: 'user_123', name: 'Alice' },
|
|
1013
|
+
* project: { id: 'proj_456', name: 'Auth Service' },
|
|
1014
|
+
* });
|
|
1015
|
+
* console.log('Created:', task.id);
|
|
1016
|
+
* ```
|
|
1017
|
+
*/
|
|
316
1018
|
async create(params: CreateTaskParams): Promise<Task> {
|
|
317
1019
|
if (!params?.title || typeof params.title !== 'string' || params.title.trim().length === 0) {
|
|
318
1020
|
throw new TaskTitleRequiredError();
|
|
@@ -349,6 +1051,24 @@ export class TaskStorageService implements TaskStorage {
|
|
|
349
1051
|
throw await toServiceException('POST', url, res.response);
|
|
350
1052
|
}
|
|
351
1053
|
|
|
1054
|
+
/**
|
|
1055
|
+
* Get a task by its ID.
|
|
1056
|
+
*
|
|
1057
|
+
* @param id - The unique task identifier
|
|
1058
|
+
* @returns The task if found, or `null` if the task does not exist
|
|
1059
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
1060
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1061
|
+
*
|
|
1062
|
+
* @example
|
|
1063
|
+
* ```typescript
|
|
1064
|
+
* const task = await tasks.get('task_abc123');
|
|
1065
|
+
* if (task) {
|
|
1066
|
+
* console.log(task.title, task.status);
|
|
1067
|
+
* } else {
|
|
1068
|
+
* console.log('Task not found');
|
|
1069
|
+
* }
|
|
1070
|
+
* ```
|
|
1071
|
+
*/
|
|
352
1072
|
async get(id: string): Promise<Task | null> {
|
|
353
1073
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
354
1074
|
throw new TaskIdRequiredError();
|
|
@@ -383,6 +1103,26 @@ export class TaskStorageService implements TaskStorage {
|
|
|
383
1103
|
throw await toServiceException('GET', url, res.response);
|
|
384
1104
|
}
|
|
385
1105
|
|
|
1106
|
+
/**
|
|
1107
|
+
* List tasks with optional filtering and pagination.
|
|
1108
|
+
*
|
|
1109
|
+
* @param params - Optional filter and pagination parameters
|
|
1110
|
+
* @returns Paginated list of tasks matching the filters
|
|
1111
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1112
|
+
*
|
|
1113
|
+
* @example
|
|
1114
|
+
* ```typescript
|
|
1115
|
+
* // List all open high-priority bugs
|
|
1116
|
+
* const result = await tasks.list({
|
|
1117
|
+
* status: 'open',
|
|
1118
|
+
* type: 'bug',
|
|
1119
|
+
* priority: 'high',
|
|
1120
|
+
* sort: '-created_at',
|
|
1121
|
+
* limit: 20,
|
|
1122
|
+
* });
|
|
1123
|
+
* console.log(`Found ${result.total} bugs, showing ${result.tasks.length}`);
|
|
1124
|
+
* ```
|
|
1125
|
+
*/
|
|
386
1126
|
async list(params?: ListTasksParams): Promise<ListTasksResult> {
|
|
387
1127
|
const queryParams = new URLSearchParams();
|
|
388
1128
|
if (params?.status) queryParams.set('status', params.status);
|
|
@@ -431,6 +1171,26 @@ export class TaskStorageService implements TaskStorage {
|
|
|
431
1171
|
throw await toServiceException('GET', url, res.response);
|
|
432
1172
|
}
|
|
433
1173
|
|
|
1174
|
+
/**
|
|
1175
|
+
* Partially update an existing task.
|
|
1176
|
+
*
|
|
1177
|
+
* @param id - The unique task identifier
|
|
1178
|
+
* @param params - Fields to update; only provided fields are changed
|
|
1179
|
+
* @returns The updated task
|
|
1180
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
1181
|
+
* @throws {@link TaskTitleRequiredError} if a title is provided but is empty
|
|
1182
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1183
|
+
*
|
|
1184
|
+
* @example
|
|
1185
|
+
* ```typescript
|
|
1186
|
+
* const updated = await tasks.update('task_abc123', {
|
|
1187
|
+
* status: 'in_progress',
|
|
1188
|
+
* priority: 'high',
|
|
1189
|
+
* assignee: { id: 'user_456', name: 'Bob' },
|
|
1190
|
+
* });
|
|
1191
|
+
* console.log('Updated status:', updated.status);
|
|
1192
|
+
* ```
|
|
1193
|
+
*/
|
|
434
1194
|
async update(id: string, params: UpdateTaskParams): Promise<Task> {
|
|
435
1195
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
436
1196
|
throw new TaskIdRequiredError();
|
|
@@ -469,6 +1229,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
469
1229
|
throw await toServiceException('PATCH', url, res.response);
|
|
470
1230
|
}
|
|
471
1231
|
|
|
1232
|
+
/**
|
|
1233
|
+
* Close a task by setting its status to closed.
|
|
1234
|
+
*
|
|
1235
|
+
* @param id - The unique task identifier
|
|
1236
|
+
* @returns The closed task with updated `closed_date`
|
|
1237
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
1238
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1239
|
+
*
|
|
1240
|
+
* @example
|
|
1241
|
+
* ```typescript
|
|
1242
|
+
* const closed = await tasks.close('task_abc123');
|
|
1243
|
+
* console.log('Closed at:', closed.closed_date);
|
|
1244
|
+
* ```
|
|
1245
|
+
*/
|
|
472
1246
|
async close(id: string): Promise<Task> {
|
|
473
1247
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
474
1248
|
throw new TaskIdRequiredError();
|
|
@@ -499,6 +1273,26 @@ export class TaskStorageService implements TaskStorage {
|
|
|
499
1273
|
throw await toServiceException('DELETE', url, res.response);
|
|
500
1274
|
}
|
|
501
1275
|
|
|
1276
|
+
/**
|
|
1277
|
+
* Get the changelog (audit trail) for a task, showing all field changes over time.
|
|
1278
|
+
*
|
|
1279
|
+
* @param id - The unique task identifier
|
|
1280
|
+
* @param params - Optional pagination parameters
|
|
1281
|
+
* @returns Paginated list of changelog entries ordered by most recent first
|
|
1282
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
1283
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1284
|
+
*
|
|
1285
|
+
* @example
|
|
1286
|
+
* ```typescript
|
|
1287
|
+
* const { changelog, total } = await tasks.changelog('task_abc123', {
|
|
1288
|
+
* limit: 10,
|
|
1289
|
+
* offset: 0,
|
|
1290
|
+
* });
|
|
1291
|
+
* for (const entry of changelog) {
|
|
1292
|
+
* console.log(`${entry.field}: ${entry.old_value} → ${entry.new_value}`);
|
|
1293
|
+
* }
|
|
1294
|
+
* ```
|
|
1295
|
+
*/
|
|
502
1296
|
async changelog(
|
|
503
1297
|
id: string,
|
|
504
1298
|
params?: { limit?: number; offset?: number }
|
|
@@ -542,6 +1336,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
542
1336
|
throw await toServiceException('GET', url, res.response);
|
|
543
1337
|
}
|
|
544
1338
|
|
|
1339
|
+
/**
|
|
1340
|
+
* Soft-delete a task, marking it as deleted without permanent removal.
|
|
1341
|
+
*
|
|
1342
|
+
* @param id - The unique task identifier
|
|
1343
|
+
* @returns The soft-deleted task
|
|
1344
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
1345
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1346
|
+
*
|
|
1347
|
+
* @example
|
|
1348
|
+
* ```typescript
|
|
1349
|
+
* const deleted = await tasks.softDelete('task_abc123');
|
|
1350
|
+
* console.log('Soft-deleted task:', deleted.id);
|
|
1351
|
+
* ```
|
|
1352
|
+
*/
|
|
545
1353
|
async softDelete(id: string): Promise<Task> {
|
|
546
1354
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
547
1355
|
throw new TaskIdRequiredError();
|
|
@@ -575,6 +1383,30 @@ export class TaskStorageService implements TaskStorage {
|
|
|
575
1383
|
throw await toServiceException('POST', url, res.response);
|
|
576
1384
|
}
|
|
577
1385
|
|
|
1386
|
+
/**
|
|
1387
|
+
* Create a comment on a task.
|
|
1388
|
+
*
|
|
1389
|
+
* @param taskId - The ID of the task to comment on
|
|
1390
|
+
* @param body - The comment text content (must be non-empty)
|
|
1391
|
+
* @param userId - The ID of the user authoring the comment
|
|
1392
|
+
* @param author - Optional entity reference with the author's display name
|
|
1393
|
+
* @returns The newly created comment
|
|
1394
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
1395
|
+
* @throws {@link CommentBodyRequiredError} if the body is empty or not a string
|
|
1396
|
+
* @throws {@link UserIdRequiredError} if the user ID is empty or not a string
|
|
1397
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1398
|
+
*
|
|
1399
|
+
* @example
|
|
1400
|
+
* ```typescript
|
|
1401
|
+
* const comment = await tasks.createComment(
|
|
1402
|
+
* 'task_abc123',
|
|
1403
|
+
* 'This is ready for review.',
|
|
1404
|
+
* 'user_456',
|
|
1405
|
+
* { id: 'user_456', name: 'Bob' },
|
|
1406
|
+
* );
|
|
1407
|
+
* console.log('Comment created:', comment.id);
|
|
1408
|
+
* ```
|
|
1409
|
+
*/
|
|
578
1410
|
async createComment(
|
|
579
1411
|
taskId: string,
|
|
580
1412
|
body: string,
|
|
@@ -624,6 +1456,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
624
1456
|
throw await toServiceException('POST', url, res.response);
|
|
625
1457
|
}
|
|
626
1458
|
|
|
1459
|
+
/**
|
|
1460
|
+
* Get a comment by its ID.
|
|
1461
|
+
*
|
|
1462
|
+
* @param commentId - The unique comment identifier
|
|
1463
|
+
* @returns The comment
|
|
1464
|
+
* @throws {@link CommentIdRequiredError} if the comment ID is empty or not a string
|
|
1465
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1466
|
+
*
|
|
1467
|
+
* @example
|
|
1468
|
+
* ```typescript
|
|
1469
|
+
* const comment = await tasks.getComment('comment_xyz789');
|
|
1470
|
+
* console.log(`${comment.author?.name}: ${comment.body}`);
|
|
1471
|
+
* ```
|
|
1472
|
+
*/
|
|
627
1473
|
async getComment(commentId: string): Promise<Comment> {
|
|
628
1474
|
if (!commentId || typeof commentId !== 'string' || commentId.trim().length === 0) {
|
|
629
1475
|
throw new CommentIdRequiredError();
|
|
@@ -657,6 +1503,25 @@ export class TaskStorageService implements TaskStorage {
|
|
|
657
1503
|
throw await toServiceException('GET', url, res.response);
|
|
658
1504
|
}
|
|
659
1505
|
|
|
1506
|
+
/**
|
|
1507
|
+
* Update a comment's body text.
|
|
1508
|
+
*
|
|
1509
|
+
* @param commentId - The unique comment identifier
|
|
1510
|
+
* @param body - The new comment text (must be non-empty)
|
|
1511
|
+
* @returns The updated comment
|
|
1512
|
+
* @throws {@link CommentIdRequiredError} if the comment ID is empty or not a string
|
|
1513
|
+
* @throws {@link CommentBodyRequiredError} if the body is empty or not a string
|
|
1514
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1515
|
+
*
|
|
1516
|
+
* @example
|
|
1517
|
+
* ```typescript
|
|
1518
|
+
* const updated = await tasks.updateComment(
|
|
1519
|
+
* 'comment_xyz789',
|
|
1520
|
+
* 'Updated: This is now ready for final review.',
|
|
1521
|
+
* );
|
|
1522
|
+
* console.log('Updated at:', updated.updated_at);
|
|
1523
|
+
* ```
|
|
1524
|
+
*/
|
|
660
1525
|
async updateComment(commentId: string, body: string): Promise<Comment> {
|
|
661
1526
|
if (!commentId || typeof commentId !== 'string' || commentId.trim().length === 0) {
|
|
662
1527
|
throw new CommentIdRequiredError();
|
|
@@ -695,6 +1560,19 @@ export class TaskStorageService implements TaskStorage {
|
|
|
695
1560
|
throw await toServiceException('PATCH', url, res.response);
|
|
696
1561
|
}
|
|
697
1562
|
|
|
1563
|
+
/**
|
|
1564
|
+
* Delete a comment permanently.
|
|
1565
|
+
*
|
|
1566
|
+
* @param commentId - The unique comment identifier
|
|
1567
|
+
* @throws {@link CommentIdRequiredError} if the comment ID is empty or not a string
|
|
1568
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1569
|
+
*
|
|
1570
|
+
* @example
|
|
1571
|
+
* ```typescript
|
|
1572
|
+
* await tasks.deleteComment('comment_xyz789');
|
|
1573
|
+
* console.log('Comment deleted');
|
|
1574
|
+
* ```
|
|
1575
|
+
*/
|
|
698
1576
|
async deleteComment(commentId: string): Promise<void> {
|
|
699
1577
|
if (!commentId || typeof commentId !== 'string' || commentId.trim().length === 0) {
|
|
700
1578
|
throw new CommentIdRequiredError();
|
|
@@ -728,6 +1606,26 @@ export class TaskStorageService implements TaskStorage {
|
|
|
728
1606
|
throw await toServiceException('DELETE', url, res.response);
|
|
729
1607
|
}
|
|
730
1608
|
|
|
1609
|
+
/**
|
|
1610
|
+
* List comments on a task with optional pagination.
|
|
1611
|
+
*
|
|
1612
|
+
* @param taskId - The ID of the task whose comments to list
|
|
1613
|
+
* @param params - Optional pagination parameters
|
|
1614
|
+
* @returns Paginated list of comments
|
|
1615
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
1616
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1617
|
+
*
|
|
1618
|
+
* @example
|
|
1619
|
+
* ```typescript
|
|
1620
|
+
* const { comments, total } = await tasks.listComments('task_abc123', {
|
|
1621
|
+
* limit: 25,
|
|
1622
|
+
* offset: 0,
|
|
1623
|
+
* });
|
|
1624
|
+
* for (const c of comments) {
|
|
1625
|
+
* console.log(`${c.author?.name}: ${c.body}`);
|
|
1626
|
+
* }
|
|
1627
|
+
* ```
|
|
1628
|
+
*/
|
|
731
1629
|
async listComments(
|
|
732
1630
|
taskId: string,
|
|
733
1631
|
params?: { limit?: number; offset?: number }
|
|
@@ -771,6 +1669,21 @@ export class TaskStorageService implements TaskStorage {
|
|
|
771
1669
|
throw await toServiceException('GET', url, res.response);
|
|
772
1670
|
}
|
|
773
1671
|
|
|
1672
|
+
/**
|
|
1673
|
+
* Create a new tag for categorizing tasks.
|
|
1674
|
+
*
|
|
1675
|
+
* @param name - The tag display name (must be non-empty)
|
|
1676
|
+
* @param color - Optional hex color code (e.g., `'#ff0000'`)
|
|
1677
|
+
* @returns The newly created tag
|
|
1678
|
+
* @throws {@link TagNameRequiredError} if the name is empty or not a string
|
|
1679
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1680
|
+
*
|
|
1681
|
+
* @example
|
|
1682
|
+
* ```typescript
|
|
1683
|
+
* const tag = await tasks.createTag('urgent', '#ff0000');
|
|
1684
|
+
* console.log('Created tag:', tag.id, tag.name);
|
|
1685
|
+
* ```
|
|
1686
|
+
*/
|
|
774
1687
|
async createTag(name: string, color?: string): Promise<Tag> {
|
|
775
1688
|
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
|
776
1689
|
throw new TagNameRequiredError();
|
|
@@ -806,6 +1719,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
806
1719
|
throw await toServiceException('POST', url, res.response);
|
|
807
1720
|
}
|
|
808
1721
|
|
|
1722
|
+
/**
|
|
1723
|
+
* Get a tag by its ID.
|
|
1724
|
+
*
|
|
1725
|
+
* @param tagId - The unique tag identifier
|
|
1726
|
+
* @returns The tag
|
|
1727
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
1728
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1729
|
+
*
|
|
1730
|
+
* @example
|
|
1731
|
+
* ```typescript
|
|
1732
|
+
* const tag = await tasks.getTag('tag_def456');
|
|
1733
|
+
* console.log(`${tag.name} (${tag.color})`);
|
|
1734
|
+
* ```
|
|
1735
|
+
*/
|
|
809
1736
|
async getTag(tagId: string): Promise<Tag> {
|
|
810
1737
|
if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
|
|
811
1738
|
throw new TagIdRequiredError();
|
|
@@ -839,6 +1766,23 @@ export class TaskStorageService implements TaskStorage {
|
|
|
839
1766
|
throw await toServiceException('GET', url, res.response);
|
|
840
1767
|
}
|
|
841
1768
|
|
|
1769
|
+
/**
|
|
1770
|
+
* Update a tag's name and optionally its color.
|
|
1771
|
+
*
|
|
1772
|
+
* @param tagId - The unique tag identifier
|
|
1773
|
+
* @param name - The new tag name (must be non-empty)
|
|
1774
|
+
* @param color - Optional new hex color code
|
|
1775
|
+
* @returns The updated tag
|
|
1776
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
1777
|
+
* @throws {@link TagNameRequiredError} if the name is empty or not a string
|
|
1778
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1779
|
+
*
|
|
1780
|
+
* @example
|
|
1781
|
+
* ```typescript
|
|
1782
|
+
* const updated = await tasks.updateTag('tag_def456', 'critical', '#cc0000');
|
|
1783
|
+
* console.log('Updated:', updated.name);
|
|
1784
|
+
* ```
|
|
1785
|
+
*/
|
|
842
1786
|
async updateTag(tagId: string, name: string, color?: string): Promise<Tag> {
|
|
843
1787
|
if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
|
|
844
1788
|
throw new TagIdRequiredError();
|
|
@@ -880,6 +1824,19 @@ export class TaskStorageService implements TaskStorage {
|
|
|
880
1824
|
throw await toServiceException('PATCH', url, res.response);
|
|
881
1825
|
}
|
|
882
1826
|
|
|
1827
|
+
/**
|
|
1828
|
+
* Delete a tag permanently.
|
|
1829
|
+
*
|
|
1830
|
+
* @param tagId - The unique tag identifier
|
|
1831
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
1832
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1833
|
+
*
|
|
1834
|
+
* @example
|
|
1835
|
+
* ```typescript
|
|
1836
|
+
* await tasks.deleteTag('tag_def456');
|
|
1837
|
+
* console.log('Tag deleted');
|
|
1838
|
+
* ```
|
|
1839
|
+
*/
|
|
883
1840
|
async deleteTag(tagId: string): Promise<void> {
|
|
884
1841
|
if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
|
|
885
1842
|
throw new TagIdRequiredError();
|
|
@@ -913,6 +1870,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
913
1870
|
throw await toServiceException('DELETE', url, res.response);
|
|
914
1871
|
}
|
|
915
1872
|
|
|
1873
|
+
/**
|
|
1874
|
+
* List all tags in the organization.
|
|
1875
|
+
*
|
|
1876
|
+
* @returns List of all tags
|
|
1877
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1878
|
+
*
|
|
1879
|
+
* @example
|
|
1880
|
+
* ```typescript
|
|
1881
|
+
* const { tags } = await tasks.listTags();
|
|
1882
|
+
* for (const tag of tags) {
|
|
1883
|
+
* console.log(`${tag.name} (${tag.color ?? 'no color'})`);
|
|
1884
|
+
* }
|
|
1885
|
+
* ```
|
|
1886
|
+
*/
|
|
916
1887
|
async listTags(): Promise<ListTagsResult> {
|
|
917
1888
|
const url = buildUrl(this.#baseUrl, `/task/tags/list/${TASK_API_VERSION}`);
|
|
918
1889
|
const signal = AbortSignal.timeout(30_000);
|
|
@@ -939,6 +1910,21 @@ export class TaskStorageService implements TaskStorage {
|
|
|
939
1910
|
throw await toServiceException('GET', url, res.response);
|
|
940
1911
|
}
|
|
941
1912
|
|
|
1913
|
+
/**
|
|
1914
|
+
* Associate a tag with a task.
|
|
1915
|
+
*
|
|
1916
|
+
* @param taskId - The ID of the task
|
|
1917
|
+
* @param tagId - The ID of the tag to add
|
|
1918
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
1919
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
1920
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1921
|
+
*
|
|
1922
|
+
* @example
|
|
1923
|
+
* ```typescript
|
|
1924
|
+
* await tasks.addTagToTask('task_abc123', 'tag_def456');
|
|
1925
|
+
* console.log('Tag added to task');
|
|
1926
|
+
* ```
|
|
1927
|
+
*/
|
|
942
1928
|
async addTagToTask(taskId: string, tagId: string): Promise<void> {
|
|
943
1929
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
944
1930
|
throw new TaskIdRequiredError();
|
|
@@ -975,6 +1961,21 @@ export class TaskStorageService implements TaskStorage {
|
|
|
975
1961
|
throw await toServiceException('POST', url, res.response);
|
|
976
1962
|
}
|
|
977
1963
|
|
|
1964
|
+
/**
|
|
1965
|
+
* Remove a tag association from a task.
|
|
1966
|
+
*
|
|
1967
|
+
* @param taskId - The ID of the task
|
|
1968
|
+
* @param tagId - The ID of the tag to remove
|
|
1969
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
1970
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
1971
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1972
|
+
*
|
|
1973
|
+
* @example
|
|
1974
|
+
* ```typescript
|
|
1975
|
+
* await tasks.removeTagFromTask('task_abc123', 'tag_def456');
|
|
1976
|
+
* console.log('Tag removed from task');
|
|
1977
|
+
* ```
|
|
1978
|
+
*/
|
|
978
1979
|
async removeTagFromTask(taskId: string, tagId: string): Promise<void> {
|
|
979
1980
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
980
1981
|
throw new TaskIdRequiredError();
|
|
@@ -1011,6 +2012,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1011
2012
|
throw await toServiceException('DELETE', url, res.response);
|
|
1012
2013
|
}
|
|
1013
2014
|
|
|
2015
|
+
/**
|
|
2016
|
+
* List all tags associated with a specific task.
|
|
2017
|
+
*
|
|
2018
|
+
* @param taskId - The ID of the task
|
|
2019
|
+
* @returns Array of tags on the task
|
|
2020
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
2021
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2022
|
+
*
|
|
2023
|
+
* @example
|
|
2024
|
+
* ```typescript
|
|
2025
|
+
* const tags = await tasks.listTagsForTask('task_abc123');
|
|
2026
|
+
* console.log('Tags:', tags.map((t) => t.name).join(', '));
|
|
2027
|
+
* ```
|
|
2028
|
+
*/
|
|
1014
2029
|
async listTagsForTask(taskId: string): Promise<Tag[]> {
|
|
1015
2030
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
1016
2031
|
throw new TaskIdRequiredError();
|
|
@@ -1044,8 +2059,33 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1044
2059
|
throw await toServiceException('GET', url, res.response);
|
|
1045
2060
|
}
|
|
1046
2061
|
|
|
1047
|
-
|
|
1048
|
-
|
|
2062
|
+
/**
|
|
2063
|
+
* Initiate a file upload to a task. Returns a presigned S3 URL for direct upload.
|
|
2064
|
+
*
|
|
2065
|
+
* @remarks
|
|
2066
|
+
* After receiving the presigned URL, upload the file content via HTTP PUT to that URL.
|
|
2067
|
+
* Then call {@link TaskStorageService.confirmAttachment | confirmAttachment} to finalize.
|
|
2068
|
+
*
|
|
2069
|
+
* @param taskId - The ID of the task to attach the file to
|
|
2070
|
+
* @param params - Attachment metadata including filename, content type, and size
|
|
2071
|
+
* @returns The created attachment record and a presigned upload URL
|
|
2072
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
2073
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2074
|
+
*
|
|
2075
|
+
* @example
|
|
2076
|
+
* ```typescript
|
|
2077
|
+
* const { attachment, presigned_url } = await tasks.uploadAttachment(
|
|
2078
|
+
* 'task_abc123',
|
|
2079
|
+
* { filename: 'report.pdf', content_type: 'application/pdf', size: 102400 },
|
|
2080
|
+
* );
|
|
2081
|
+
*
|
|
2082
|
+
* // Upload the file to S3
|
|
2083
|
+
* await fetch(presigned_url, { method: 'PUT', body: fileContent });
|
|
2084
|
+
*
|
|
2085
|
+
* // Confirm the upload
|
|
2086
|
+
* await tasks.confirmAttachment(attachment.id);
|
|
2087
|
+
* ```
|
|
2088
|
+
*/
|
|
1049
2089
|
async uploadAttachment(
|
|
1050
2090
|
taskId: string,
|
|
1051
2091
|
params: CreateAttachmentParams
|
|
@@ -1084,6 +2124,24 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1084
2124
|
throw await toServiceException('POST', url, res.response);
|
|
1085
2125
|
}
|
|
1086
2126
|
|
|
2127
|
+
/**
|
|
2128
|
+
* Confirm that a file upload has completed successfully.
|
|
2129
|
+
*
|
|
2130
|
+
* @remarks
|
|
2131
|
+
* Call this after successfully uploading the file to the presigned URL
|
|
2132
|
+
* returned by {@link TaskStorageService.uploadAttachment | uploadAttachment}.
|
|
2133
|
+
*
|
|
2134
|
+
* @param attachmentId - The unique attachment identifier
|
|
2135
|
+
* @returns The confirmed attachment record
|
|
2136
|
+
* @throws {@link AttachmentIdRequiredError} if the attachment ID is empty or not a string
|
|
2137
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2138
|
+
*
|
|
2139
|
+
* @example
|
|
2140
|
+
* ```typescript
|
|
2141
|
+
* const confirmed = await tasks.confirmAttachment('att_ghi789');
|
|
2142
|
+
* console.log('Confirmed:', confirmed.filename);
|
|
2143
|
+
* ```
|
|
2144
|
+
*/
|
|
1087
2145
|
async confirmAttachment(attachmentId: string): Promise<Attachment> {
|
|
1088
2146
|
if (!attachmentId || typeof attachmentId !== 'string' || attachmentId.trim().length === 0) {
|
|
1089
2147
|
throw new AttachmentIdRequiredError();
|
|
@@ -1117,6 +2175,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1117
2175
|
throw await toServiceException('POST', url, res.response);
|
|
1118
2176
|
}
|
|
1119
2177
|
|
|
2178
|
+
/**
|
|
2179
|
+
* Get a presigned S3 URL for downloading an attachment.
|
|
2180
|
+
*
|
|
2181
|
+
* @param attachmentId - The unique attachment identifier
|
|
2182
|
+
* @returns A presigned download URL with expiry information
|
|
2183
|
+
* @throws {@link AttachmentIdRequiredError} if the attachment ID is empty or not a string
|
|
2184
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2185
|
+
*
|
|
2186
|
+
* @example
|
|
2187
|
+
* ```typescript
|
|
2188
|
+
* const { presigned_url, expiry_seconds } = await tasks.downloadAttachment('att_ghi789');
|
|
2189
|
+
* console.log(`Download URL (expires in ${expiry_seconds}s):`, presigned_url);
|
|
2190
|
+
* ```
|
|
2191
|
+
*/
|
|
1120
2192
|
async downloadAttachment(attachmentId: string): Promise<PresignDownloadResponse> {
|
|
1121
2193
|
if (!attachmentId || typeof attachmentId !== 'string' || attachmentId.trim().length === 0) {
|
|
1122
2194
|
throw new AttachmentIdRequiredError();
|
|
@@ -1150,6 +2222,22 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1150
2222
|
throw await toServiceException('POST', url, res.response);
|
|
1151
2223
|
}
|
|
1152
2224
|
|
|
2225
|
+
/**
|
|
2226
|
+
* List all attachments on a task.
|
|
2227
|
+
*
|
|
2228
|
+
* @param taskId - The ID of the task
|
|
2229
|
+
* @returns List of attachments with total count
|
|
2230
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
2231
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2232
|
+
*
|
|
2233
|
+
* @example
|
|
2234
|
+
* ```typescript
|
|
2235
|
+
* const { attachments, total } = await tasks.listAttachments('task_abc123');
|
|
2236
|
+
* for (const att of attachments) {
|
|
2237
|
+
* console.log(`${att.filename} (${att.content_type}, ${att.size} bytes)`);
|
|
2238
|
+
* }
|
|
2239
|
+
* ```
|
|
2240
|
+
*/
|
|
1153
2241
|
async listAttachments(taskId: string): Promise<ListAttachmentsResult> {
|
|
1154
2242
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
1155
2243
|
throw new TaskIdRequiredError();
|
|
@@ -1183,6 +2271,19 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1183
2271
|
throw await toServiceException('GET', url, res.response);
|
|
1184
2272
|
}
|
|
1185
2273
|
|
|
2274
|
+
/**
|
|
2275
|
+
* Delete an attachment permanently.
|
|
2276
|
+
*
|
|
2277
|
+
* @param attachmentId - The unique attachment identifier
|
|
2278
|
+
* @throws {@link AttachmentIdRequiredError} if the attachment ID is empty or not a string
|
|
2279
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2280
|
+
*
|
|
2281
|
+
* @example
|
|
2282
|
+
* ```typescript
|
|
2283
|
+
* await tasks.deleteAttachment('att_ghi789');
|
|
2284
|
+
* console.log('Attachment deleted');
|
|
2285
|
+
* ```
|
|
2286
|
+
*/
|
|
1186
2287
|
async deleteAttachment(attachmentId: string): Promise<void> {
|
|
1187
2288
|
if (!attachmentId || typeof attachmentId !== 'string' || attachmentId.trim().length === 0) {
|
|
1188
2289
|
throw new AttachmentIdRequiredError();
|
|
@@ -1216,6 +2317,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1216
2317
|
throw await toServiceException('DELETE', url, res.response);
|
|
1217
2318
|
}
|
|
1218
2319
|
|
|
2320
|
+
/**
|
|
2321
|
+
* List all users who have been referenced in tasks (as creators, assignees, or closers).
|
|
2322
|
+
*
|
|
2323
|
+
* @returns List of user entity references
|
|
2324
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2325
|
+
*
|
|
2326
|
+
* @example
|
|
2327
|
+
* ```typescript
|
|
2328
|
+
* const { users } = await tasks.listUsers();
|
|
2329
|
+
* for (const user of users) {
|
|
2330
|
+
* console.log(`${user.name} (${user.id})`);
|
|
2331
|
+
* }
|
|
2332
|
+
* ```
|
|
2333
|
+
*/
|
|
1219
2334
|
async listUsers(): Promise<ListUsersResult> {
|
|
1220
2335
|
const url = buildUrl(this.#baseUrl, `/task/users/${TASK_API_VERSION}`);
|
|
1221
2336
|
const signal = AbortSignal.timeout(30_000);
|
|
@@ -1242,6 +2357,20 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1242
2357
|
throw await toServiceException('GET', url, res.response);
|
|
1243
2358
|
}
|
|
1244
2359
|
|
|
2360
|
+
/**
|
|
2361
|
+
* List all projects that have been referenced in tasks.
|
|
2362
|
+
*
|
|
2363
|
+
* @returns List of project entity references
|
|
2364
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2365
|
+
*
|
|
2366
|
+
* @example
|
|
2367
|
+
* ```typescript
|
|
2368
|
+
* const { projects } = await tasks.listProjects();
|
|
2369
|
+
* for (const project of projects) {
|
|
2370
|
+
* console.log(`${project.name} (${project.id})`);
|
|
2371
|
+
* }
|
|
2372
|
+
* ```
|
|
2373
|
+
*/
|
|
1245
2374
|
async listProjects(): Promise<ListProjectsResult> {
|
|
1246
2375
|
const url = buildUrl(this.#baseUrl, `/task/projects/${TASK_API_VERSION}`);
|
|
1247
2376
|
const signal = AbortSignal.timeout(30_000);
|
|
@@ -1268,6 +2397,22 @@ export class TaskStorageService implements TaskStorage {
|
|
|
1268
2397
|
throw await toServiceException('GET', url, res.response);
|
|
1269
2398
|
}
|
|
1270
2399
|
|
|
2400
|
+
/**
|
|
2401
|
+
* Get task activity time-series data showing daily task counts by status.
|
|
2402
|
+
*
|
|
2403
|
+
* @param params - Optional parameters controlling the number of days to retrieve
|
|
2404
|
+
* @returns Time-series activity data with daily snapshots
|
|
2405
|
+
* @throws {@link ServiceException} if the API request fails
|
|
2406
|
+
*
|
|
2407
|
+
* @example
|
|
2408
|
+
* ```typescript
|
|
2409
|
+
* const { activity, days } = await tasks.getActivity({ days: 30 });
|
|
2410
|
+
* console.log(`Activity over ${days} days:`);
|
|
2411
|
+
* for (const point of activity) {
|
|
2412
|
+
* console.log(`${point.date}: ${point.open} open, ${point.inProgress} in progress`);
|
|
2413
|
+
* }
|
|
2414
|
+
* ```
|
|
2415
|
+
*/
|
|
1271
2416
|
async getActivity(params?: TaskActivityParams): Promise<TaskActivityResult> {
|
|
1272
2417
|
const queryParams = new URLSearchParams();
|
|
1273
2418
|
if (params?.days !== undefined) queryParams.set('days', String(params.days));
|