@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/dist/services/task.js
CHANGED
|
@@ -1,24 +1,97 @@
|
|
|
1
1
|
import { buildUrl, toServiceException } from "./_util.js";
|
|
2
2
|
import { StructuredError } from "../error.js";
|
|
3
3
|
import { safeStringify } from "../json.js";
|
|
4
|
+
/** API version string used for task CRUD, comment, tag, and attachment endpoints. */
|
|
4
5
|
const TASK_API_VERSION = '2026-02-24';
|
|
6
|
+
/** API version string used for the task activity analytics endpoint. */
|
|
5
7
|
const TASK_ACTIVITY_API_VERSION = '2026-02-28';
|
|
8
|
+
/** Thrown when a task ID parameter is empty or not a string. */
|
|
6
9
|
const TaskIdRequiredError = StructuredError('TaskIdRequiredError', 'Task ID is required and must be a non-empty string');
|
|
10
|
+
/** Thrown when a task title is empty or not a string. */
|
|
7
11
|
const TaskTitleRequiredError = StructuredError('TaskTitleRequiredError', 'Task title is required and must be a non-empty string');
|
|
12
|
+
/** Thrown when a comment ID parameter is empty or not a string. */
|
|
8
13
|
const CommentIdRequiredError = StructuredError('CommentIdRequiredError', 'Comment ID is required and must be a non-empty string');
|
|
14
|
+
/** Thrown when a comment body is empty or not a string. */
|
|
9
15
|
const CommentBodyRequiredError = StructuredError('CommentBodyRequiredError', 'Comment body is required and must be a non-empty string');
|
|
16
|
+
/** Thrown when a tag ID parameter is empty or not a string. */
|
|
10
17
|
const TagIdRequiredError = StructuredError('TagIdRequiredError', 'Tag ID is required and must be a non-empty string');
|
|
18
|
+
/** Thrown when a tag name is empty or not a string. */
|
|
11
19
|
const TagNameRequiredError = StructuredError('TagNameRequiredError', 'Tag name is required and must be a non-empty string');
|
|
20
|
+
/** Thrown when an attachment ID parameter is empty or not a string. */
|
|
12
21
|
const AttachmentIdRequiredError = StructuredError('AttachmentIdRequiredError', 'Attachment ID is required and must be a non-empty string');
|
|
22
|
+
/** Thrown when a user ID parameter is empty or not a string. */
|
|
13
23
|
const UserIdRequiredError = StructuredError('UserIdRequiredError', 'User ID is required and must be a non-empty string');
|
|
24
|
+
/**
|
|
25
|
+
* Thrown when the API returns a success HTTP status but the response body indicates failure.
|
|
26
|
+
*/
|
|
14
27
|
const TaskStorageResponseError = StructuredError('TaskStorageResponseError')();
|
|
28
|
+
/**
|
|
29
|
+
* Client for the Agentuity Task management service.
|
|
30
|
+
*
|
|
31
|
+
* Provides a full-featured project management API including task CRUD, hierarchical
|
|
32
|
+
* organization (epics → features → tasks), comments, tags, file attachments via
|
|
33
|
+
* presigned S3 URLs, changelog tracking, and activity analytics.
|
|
34
|
+
*
|
|
35
|
+
* Tasks support lifecycle management through status transitions (`open` → `in_progress`
|
|
36
|
+
* → `done`/`closed`/`cancelled`) with automatic date tracking for each transition.
|
|
37
|
+
*
|
|
38
|
+
* All methods validate inputs client-side and throw structured errors for invalid
|
|
39
|
+
* parameters. API errors throw {@link ServiceException}.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const tasks = new TaskStorageService(baseUrl, adapter);
|
|
44
|
+
*
|
|
45
|
+
* // Create a task
|
|
46
|
+
* const task = await tasks.create({
|
|
47
|
+
* title: 'Implement login flow',
|
|
48
|
+
* type: 'feature',
|
|
49
|
+
* created_id: 'user_123',
|
|
50
|
+
* creator: { id: 'user_123', name: 'Alice' },
|
|
51
|
+
* priority: 'high',
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* // Add a comment
|
|
55
|
+
* await tasks.createComment(task.id, 'Started working on this', 'user_123');
|
|
56
|
+
*
|
|
57
|
+
* // List open tasks
|
|
58
|
+
* const { tasks: openTasks } = await tasks.list({ status: 'open' });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
15
61
|
export class TaskStorageService {
|
|
16
62
|
#adapter;
|
|
17
63
|
#baseUrl;
|
|
64
|
+
/**
|
|
65
|
+
* Creates a new TaskStorageService instance.
|
|
66
|
+
*
|
|
67
|
+
* @param baseUrl - The base URL of the task management API
|
|
68
|
+
* @param adapter - The HTTP fetch adapter used for making API requests
|
|
69
|
+
*/
|
|
18
70
|
constructor(baseUrl, adapter) {
|
|
19
71
|
this.#adapter = adapter;
|
|
20
72
|
this.#baseUrl = baseUrl;
|
|
21
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Create a new task.
|
|
76
|
+
*
|
|
77
|
+
* @param params - The task creation parameters including title, type, and optional fields
|
|
78
|
+
* @returns The newly created task
|
|
79
|
+
* @throws {@link TaskTitleRequiredError} if the title is empty or not a string
|
|
80
|
+
* @throws {@link ServiceException} if the API request fails
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const task = await tasks.create({
|
|
85
|
+
* title: 'Fix login bug',
|
|
86
|
+
* type: 'bug',
|
|
87
|
+
* created_id: 'user_123',
|
|
88
|
+
* priority: 'high',
|
|
89
|
+
* creator: { id: 'user_123', name: 'Alice' },
|
|
90
|
+
* project: { id: 'proj_456', name: 'Auth Service' },
|
|
91
|
+
* });
|
|
92
|
+
* console.log('Created:', task.id);
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
22
95
|
async create(params) {
|
|
23
96
|
if (!params?.title || typeof params.title !== 'string' || params.title.trim().length === 0) {
|
|
24
97
|
throw new TaskTitleRequiredError();
|
|
@@ -50,6 +123,24 @@ export class TaskStorageService {
|
|
|
50
123
|
}
|
|
51
124
|
throw await toServiceException('POST', url, res.response);
|
|
52
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Get a task by its ID.
|
|
128
|
+
*
|
|
129
|
+
* @param id - The unique task identifier
|
|
130
|
+
* @returns The task if found, or `null` if the task does not exist
|
|
131
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
132
|
+
* @throws {@link ServiceException} if the API request fails
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const task = await tasks.get('task_abc123');
|
|
137
|
+
* if (task) {
|
|
138
|
+
* console.log(task.title, task.status);
|
|
139
|
+
* } else {
|
|
140
|
+
* console.log('Task not found');
|
|
141
|
+
* }
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
53
144
|
async get(id) {
|
|
54
145
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
55
146
|
throw new TaskIdRequiredError();
|
|
@@ -78,6 +169,26 @@ export class TaskStorageService {
|
|
|
78
169
|
}
|
|
79
170
|
throw await toServiceException('GET', url, res.response);
|
|
80
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* List tasks with optional filtering and pagination.
|
|
174
|
+
*
|
|
175
|
+
* @param params - Optional filter and pagination parameters
|
|
176
|
+
* @returns Paginated list of tasks matching the filters
|
|
177
|
+
* @throws {@link ServiceException} if the API request fails
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* // List all open high-priority bugs
|
|
182
|
+
* const result = await tasks.list({
|
|
183
|
+
* status: 'open',
|
|
184
|
+
* type: 'bug',
|
|
185
|
+
* priority: 'high',
|
|
186
|
+
* sort: '-created_at',
|
|
187
|
+
* limit: 20,
|
|
188
|
+
* });
|
|
189
|
+
* console.log(`Found ${result.total} bugs, showing ${result.tasks.length}`);
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
81
192
|
async list(params) {
|
|
82
193
|
const queryParams = new URLSearchParams();
|
|
83
194
|
if (params?.status)
|
|
@@ -130,6 +241,26 @@ export class TaskStorageService {
|
|
|
130
241
|
}
|
|
131
242
|
throw await toServiceException('GET', url, res.response);
|
|
132
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Partially update an existing task.
|
|
246
|
+
*
|
|
247
|
+
* @param id - The unique task identifier
|
|
248
|
+
* @param params - Fields to update; only provided fields are changed
|
|
249
|
+
* @returns The updated task
|
|
250
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
251
|
+
* @throws {@link TaskTitleRequiredError} if a title is provided but is empty
|
|
252
|
+
* @throws {@link ServiceException} if the API request fails
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```typescript
|
|
256
|
+
* const updated = await tasks.update('task_abc123', {
|
|
257
|
+
* status: 'in_progress',
|
|
258
|
+
* priority: 'high',
|
|
259
|
+
* assignee: { id: 'user_456', name: 'Bob' },
|
|
260
|
+
* });
|
|
261
|
+
* console.log('Updated status:', updated.status);
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
133
264
|
async update(id, params) {
|
|
134
265
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
135
266
|
throw new TaskIdRequiredError();
|
|
@@ -161,6 +292,20 @@ export class TaskStorageService {
|
|
|
161
292
|
}
|
|
162
293
|
throw await toServiceException('PATCH', url, res.response);
|
|
163
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Close a task by setting its status to closed.
|
|
297
|
+
*
|
|
298
|
+
* @param id - The unique task identifier
|
|
299
|
+
* @returns The closed task with updated `closed_date`
|
|
300
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
301
|
+
* @throws {@link ServiceException} if the API request fails
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```typescript
|
|
305
|
+
* const closed = await tasks.close('task_abc123');
|
|
306
|
+
* console.log('Closed at:', closed.closed_date);
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
164
309
|
async close(id) {
|
|
165
310
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
166
311
|
throw new TaskIdRequiredError();
|
|
@@ -186,6 +331,26 @@ export class TaskStorageService {
|
|
|
186
331
|
}
|
|
187
332
|
throw await toServiceException('DELETE', url, res.response);
|
|
188
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Get the changelog (audit trail) for a task, showing all field changes over time.
|
|
336
|
+
*
|
|
337
|
+
* @param id - The unique task identifier
|
|
338
|
+
* @param params - Optional pagination parameters
|
|
339
|
+
* @returns Paginated list of changelog entries ordered by most recent first
|
|
340
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
341
|
+
* @throws {@link ServiceException} if the API request fails
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
* ```typescript
|
|
345
|
+
* const { changelog, total } = await tasks.changelog('task_abc123', {
|
|
346
|
+
* limit: 10,
|
|
347
|
+
* offset: 0,
|
|
348
|
+
* });
|
|
349
|
+
* for (const entry of changelog) {
|
|
350
|
+
* console.log(`${entry.field}: ${entry.old_value} → ${entry.new_value}`);
|
|
351
|
+
* }
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
189
354
|
async changelog(id, params) {
|
|
190
355
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
191
356
|
throw new TaskIdRequiredError();
|
|
@@ -217,6 +382,20 @@ export class TaskStorageService {
|
|
|
217
382
|
}
|
|
218
383
|
throw await toServiceException('GET', url, res.response);
|
|
219
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* Soft-delete a task, marking it as deleted without permanent removal.
|
|
387
|
+
*
|
|
388
|
+
* @param id - The unique task identifier
|
|
389
|
+
* @returns The soft-deleted task
|
|
390
|
+
* @throws {@link TaskIdRequiredError} if the ID is empty or not a string
|
|
391
|
+
* @throws {@link ServiceException} if the API request fails
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```typescript
|
|
395
|
+
* const deleted = await tasks.softDelete('task_abc123');
|
|
396
|
+
* console.log('Soft-deleted task:', deleted.id);
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
220
399
|
async softDelete(id) {
|
|
221
400
|
if (!id || typeof id !== 'string' || id.trim().length === 0) {
|
|
222
401
|
throw new TaskIdRequiredError();
|
|
@@ -242,6 +421,30 @@ export class TaskStorageService {
|
|
|
242
421
|
}
|
|
243
422
|
throw await toServiceException('POST', url, res.response);
|
|
244
423
|
}
|
|
424
|
+
/**
|
|
425
|
+
* Create a comment on a task.
|
|
426
|
+
*
|
|
427
|
+
* @param taskId - The ID of the task to comment on
|
|
428
|
+
* @param body - The comment text content (must be non-empty)
|
|
429
|
+
* @param userId - The ID of the user authoring the comment
|
|
430
|
+
* @param author - Optional entity reference with the author's display name
|
|
431
|
+
* @returns The newly created comment
|
|
432
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
433
|
+
* @throws {@link CommentBodyRequiredError} if the body is empty or not a string
|
|
434
|
+
* @throws {@link UserIdRequiredError} if the user ID is empty or not a string
|
|
435
|
+
* @throws {@link ServiceException} if the API request fails
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* ```typescript
|
|
439
|
+
* const comment = await tasks.createComment(
|
|
440
|
+
* 'task_abc123',
|
|
441
|
+
* 'This is ready for review.',
|
|
442
|
+
* 'user_456',
|
|
443
|
+
* { id: 'user_456', name: 'Bob' },
|
|
444
|
+
* );
|
|
445
|
+
* console.log('Comment created:', comment.id);
|
|
446
|
+
* ```
|
|
447
|
+
*/
|
|
245
448
|
async createComment(taskId, body, userId, author) {
|
|
246
449
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
247
450
|
throw new TaskIdRequiredError();
|
|
@@ -278,6 +481,20 @@ export class TaskStorageService {
|
|
|
278
481
|
}
|
|
279
482
|
throw await toServiceException('POST', url, res.response);
|
|
280
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* Get a comment by its ID.
|
|
486
|
+
*
|
|
487
|
+
* @param commentId - The unique comment identifier
|
|
488
|
+
* @returns The comment
|
|
489
|
+
* @throws {@link CommentIdRequiredError} if the comment ID is empty or not a string
|
|
490
|
+
* @throws {@link ServiceException} if the API request fails
|
|
491
|
+
*
|
|
492
|
+
* @example
|
|
493
|
+
* ```typescript
|
|
494
|
+
* const comment = await tasks.getComment('comment_xyz789');
|
|
495
|
+
* console.log(`${comment.author?.name}: ${comment.body}`);
|
|
496
|
+
* ```
|
|
497
|
+
*/
|
|
281
498
|
async getComment(commentId) {
|
|
282
499
|
if (!commentId || typeof commentId !== 'string' || commentId.trim().length === 0) {
|
|
283
500
|
throw new CommentIdRequiredError();
|
|
@@ -303,6 +520,25 @@ export class TaskStorageService {
|
|
|
303
520
|
}
|
|
304
521
|
throw await toServiceException('GET', url, res.response);
|
|
305
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Update a comment's body text.
|
|
525
|
+
*
|
|
526
|
+
* @param commentId - The unique comment identifier
|
|
527
|
+
* @param body - The new comment text (must be non-empty)
|
|
528
|
+
* @returns The updated comment
|
|
529
|
+
* @throws {@link CommentIdRequiredError} if the comment ID is empty or not a string
|
|
530
|
+
* @throws {@link CommentBodyRequiredError} if the body is empty or not a string
|
|
531
|
+
* @throws {@link ServiceException} if the API request fails
|
|
532
|
+
*
|
|
533
|
+
* @example
|
|
534
|
+
* ```typescript
|
|
535
|
+
* const updated = await tasks.updateComment(
|
|
536
|
+
* 'comment_xyz789',
|
|
537
|
+
* 'Updated: This is now ready for final review.',
|
|
538
|
+
* );
|
|
539
|
+
* console.log('Updated at:', updated.updated_at);
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
306
542
|
async updateComment(commentId, body) {
|
|
307
543
|
if (!commentId || typeof commentId !== 'string' || commentId.trim().length === 0) {
|
|
308
544
|
throw new CommentIdRequiredError();
|
|
@@ -333,6 +569,19 @@ export class TaskStorageService {
|
|
|
333
569
|
}
|
|
334
570
|
throw await toServiceException('PATCH', url, res.response);
|
|
335
571
|
}
|
|
572
|
+
/**
|
|
573
|
+
* Delete a comment permanently.
|
|
574
|
+
*
|
|
575
|
+
* @param commentId - The unique comment identifier
|
|
576
|
+
* @throws {@link CommentIdRequiredError} if the comment ID is empty or not a string
|
|
577
|
+
* @throws {@link ServiceException} if the API request fails
|
|
578
|
+
*
|
|
579
|
+
* @example
|
|
580
|
+
* ```typescript
|
|
581
|
+
* await tasks.deleteComment('comment_xyz789');
|
|
582
|
+
* console.log('Comment deleted');
|
|
583
|
+
* ```
|
|
584
|
+
*/
|
|
336
585
|
async deleteComment(commentId) {
|
|
337
586
|
if (!commentId || typeof commentId !== 'string' || commentId.trim().length === 0) {
|
|
338
587
|
throw new CommentIdRequiredError();
|
|
@@ -358,6 +607,26 @@ export class TaskStorageService {
|
|
|
358
607
|
}
|
|
359
608
|
throw await toServiceException('DELETE', url, res.response);
|
|
360
609
|
}
|
|
610
|
+
/**
|
|
611
|
+
* List comments on a task with optional pagination.
|
|
612
|
+
*
|
|
613
|
+
* @param taskId - The ID of the task whose comments to list
|
|
614
|
+
* @param params - Optional pagination parameters
|
|
615
|
+
* @returns Paginated list of comments
|
|
616
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
617
|
+
* @throws {@link ServiceException} if the API request fails
|
|
618
|
+
*
|
|
619
|
+
* @example
|
|
620
|
+
* ```typescript
|
|
621
|
+
* const { comments, total } = await tasks.listComments('task_abc123', {
|
|
622
|
+
* limit: 25,
|
|
623
|
+
* offset: 0,
|
|
624
|
+
* });
|
|
625
|
+
* for (const c of comments) {
|
|
626
|
+
* console.log(`${c.author?.name}: ${c.body}`);
|
|
627
|
+
* }
|
|
628
|
+
* ```
|
|
629
|
+
*/
|
|
361
630
|
async listComments(taskId, params) {
|
|
362
631
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
363
632
|
throw new TaskIdRequiredError();
|
|
@@ -389,6 +658,21 @@ export class TaskStorageService {
|
|
|
389
658
|
}
|
|
390
659
|
throw await toServiceException('GET', url, res.response);
|
|
391
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Create a new tag for categorizing tasks.
|
|
663
|
+
*
|
|
664
|
+
* @param name - The tag display name (must be non-empty)
|
|
665
|
+
* @param color - Optional hex color code (e.g., `'#ff0000'`)
|
|
666
|
+
* @returns The newly created tag
|
|
667
|
+
* @throws {@link TagNameRequiredError} if the name is empty or not a string
|
|
668
|
+
* @throws {@link ServiceException} if the API request fails
|
|
669
|
+
*
|
|
670
|
+
* @example
|
|
671
|
+
* ```typescript
|
|
672
|
+
* const tag = await tasks.createTag('urgent', '#ff0000');
|
|
673
|
+
* console.log('Created tag:', tag.id, tag.name);
|
|
674
|
+
* ```
|
|
675
|
+
*/
|
|
392
676
|
async createTag(name, color) {
|
|
393
677
|
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
|
394
678
|
throw new TagNameRequiredError();
|
|
@@ -419,6 +703,20 @@ export class TaskStorageService {
|
|
|
419
703
|
}
|
|
420
704
|
throw await toServiceException('POST', url, res.response);
|
|
421
705
|
}
|
|
706
|
+
/**
|
|
707
|
+
* Get a tag by its ID.
|
|
708
|
+
*
|
|
709
|
+
* @param tagId - The unique tag identifier
|
|
710
|
+
* @returns The tag
|
|
711
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
712
|
+
* @throws {@link ServiceException} if the API request fails
|
|
713
|
+
*
|
|
714
|
+
* @example
|
|
715
|
+
* ```typescript
|
|
716
|
+
* const tag = await tasks.getTag('tag_def456');
|
|
717
|
+
* console.log(`${tag.name} (${tag.color})`);
|
|
718
|
+
* ```
|
|
719
|
+
*/
|
|
422
720
|
async getTag(tagId) {
|
|
423
721
|
if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
|
|
424
722
|
throw new TagIdRequiredError();
|
|
@@ -444,6 +742,23 @@ export class TaskStorageService {
|
|
|
444
742
|
}
|
|
445
743
|
throw await toServiceException('GET', url, res.response);
|
|
446
744
|
}
|
|
745
|
+
/**
|
|
746
|
+
* Update a tag's name and optionally its color.
|
|
747
|
+
*
|
|
748
|
+
* @param tagId - The unique tag identifier
|
|
749
|
+
* @param name - The new tag name (must be non-empty)
|
|
750
|
+
* @param color - Optional new hex color code
|
|
751
|
+
* @returns The updated tag
|
|
752
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
753
|
+
* @throws {@link TagNameRequiredError} if the name is empty or not a string
|
|
754
|
+
* @throws {@link ServiceException} if the API request fails
|
|
755
|
+
*
|
|
756
|
+
* @example
|
|
757
|
+
* ```typescript
|
|
758
|
+
* const updated = await tasks.updateTag('tag_def456', 'critical', '#cc0000');
|
|
759
|
+
* console.log('Updated:', updated.name);
|
|
760
|
+
* ```
|
|
761
|
+
*/
|
|
447
762
|
async updateTag(tagId, name, color) {
|
|
448
763
|
if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
|
|
449
764
|
throw new TagIdRequiredError();
|
|
@@ -477,6 +792,19 @@ export class TaskStorageService {
|
|
|
477
792
|
}
|
|
478
793
|
throw await toServiceException('PATCH', url, res.response);
|
|
479
794
|
}
|
|
795
|
+
/**
|
|
796
|
+
* Delete a tag permanently.
|
|
797
|
+
*
|
|
798
|
+
* @param tagId - The unique tag identifier
|
|
799
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
800
|
+
* @throws {@link ServiceException} if the API request fails
|
|
801
|
+
*
|
|
802
|
+
* @example
|
|
803
|
+
* ```typescript
|
|
804
|
+
* await tasks.deleteTag('tag_def456');
|
|
805
|
+
* console.log('Tag deleted');
|
|
806
|
+
* ```
|
|
807
|
+
*/
|
|
480
808
|
async deleteTag(tagId) {
|
|
481
809
|
if (!tagId || typeof tagId !== 'string' || tagId.trim().length === 0) {
|
|
482
810
|
throw new TagIdRequiredError();
|
|
@@ -502,6 +830,20 @@ export class TaskStorageService {
|
|
|
502
830
|
}
|
|
503
831
|
throw await toServiceException('DELETE', url, res.response);
|
|
504
832
|
}
|
|
833
|
+
/**
|
|
834
|
+
* List all tags in the organization.
|
|
835
|
+
*
|
|
836
|
+
* @returns List of all tags
|
|
837
|
+
* @throws {@link ServiceException} if the API request fails
|
|
838
|
+
*
|
|
839
|
+
* @example
|
|
840
|
+
* ```typescript
|
|
841
|
+
* const { tags } = await tasks.listTags();
|
|
842
|
+
* for (const tag of tags) {
|
|
843
|
+
* console.log(`${tag.name} (${tag.color ?? 'no color'})`);
|
|
844
|
+
* }
|
|
845
|
+
* ```
|
|
846
|
+
*/
|
|
505
847
|
async listTags() {
|
|
506
848
|
const url = buildUrl(this.#baseUrl, `/task/tags/list/${TASK_API_VERSION}`);
|
|
507
849
|
const signal = AbortSignal.timeout(30_000);
|
|
@@ -524,6 +866,21 @@ export class TaskStorageService {
|
|
|
524
866
|
}
|
|
525
867
|
throw await toServiceException('GET', url, res.response);
|
|
526
868
|
}
|
|
869
|
+
/**
|
|
870
|
+
* Associate a tag with a task.
|
|
871
|
+
*
|
|
872
|
+
* @param taskId - The ID of the task
|
|
873
|
+
* @param tagId - The ID of the tag to add
|
|
874
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
875
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
876
|
+
* @throws {@link ServiceException} if the API request fails
|
|
877
|
+
*
|
|
878
|
+
* @example
|
|
879
|
+
* ```typescript
|
|
880
|
+
* await tasks.addTagToTask('task_abc123', 'tag_def456');
|
|
881
|
+
* console.log('Tag added to task');
|
|
882
|
+
* ```
|
|
883
|
+
*/
|
|
527
884
|
async addTagToTask(taskId, tagId) {
|
|
528
885
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
529
886
|
throw new TaskIdRequiredError();
|
|
@@ -552,6 +909,21 @@ export class TaskStorageService {
|
|
|
552
909
|
}
|
|
553
910
|
throw await toServiceException('POST', url, res.response);
|
|
554
911
|
}
|
|
912
|
+
/**
|
|
913
|
+
* Remove a tag association from a task.
|
|
914
|
+
*
|
|
915
|
+
* @param taskId - The ID of the task
|
|
916
|
+
* @param tagId - The ID of the tag to remove
|
|
917
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
918
|
+
* @throws {@link TagIdRequiredError} if the tag ID is empty or not a string
|
|
919
|
+
* @throws {@link ServiceException} if the API request fails
|
|
920
|
+
*
|
|
921
|
+
* @example
|
|
922
|
+
* ```typescript
|
|
923
|
+
* await tasks.removeTagFromTask('task_abc123', 'tag_def456');
|
|
924
|
+
* console.log('Tag removed from task');
|
|
925
|
+
* ```
|
|
926
|
+
*/
|
|
555
927
|
async removeTagFromTask(taskId, tagId) {
|
|
556
928
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
557
929
|
throw new TaskIdRequiredError();
|
|
@@ -580,6 +952,20 @@ export class TaskStorageService {
|
|
|
580
952
|
}
|
|
581
953
|
throw await toServiceException('DELETE', url, res.response);
|
|
582
954
|
}
|
|
955
|
+
/**
|
|
956
|
+
* List all tags associated with a specific task.
|
|
957
|
+
*
|
|
958
|
+
* @param taskId - The ID of the task
|
|
959
|
+
* @returns Array of tags on the task
|
|
960
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
961
|
+
* @throws {@link ServiceException} if the API request fails
|
|
962
|
+
*
|
|
963
|
+
* @example
|
|
964
|
+
* ```typescript
|
|
965
|
+
* const tags = await tasks.listTagsForTask('task_abc123');
|
|
966
|
+
* console.log('Tags:', tags.map((t) => t.name).join(', '));
|
|
967
|
+
* ```
|
|
968
|
+
*/
|
|
583
969
|
async listTagsForTask(taskId) {
|
|
584
970
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
585
971
|
throw new TaskIdRequiredError();
|
|
@@ -605,7 +991,33 @@ export class TaskStorageService {
|
|
|
605
991
|
}
|
|
606
992
|
throw await toServiceException('GET', url, res.response);
|
|
607
993
|
}
|
|
608
|
-
|
|
994
|
+
/**
|
|
995
|
+
* Initiate a file upload to a task. Returns a presigned S3 URL for direct upload.
|
|
996
|
+
*
|
|
997
|
+
* @remarks
|
|
998
|
+
* After receiving the presigned URL, upload the file content via HTTP PUT to that URL.
|
|
999
|
+
* Then call {@link TaskStorageService.confirmAttachment | confirmAttachment} to finalize.
|
|
1000
|
+
*
|
|
1001
|
+
* @param taskId - The ID of the task to attach the file to
|
|
1002
|
+
* @param params - Attachment metadata including filename, content type, and size
|
|
1003
|
+
* @returns The created attachment record and a presigned upload URL
|
|
1004
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
1005
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1006
|
+
*
|
|
1007
|
+
* @example
|
|
1008
|
+
* ```typescript
|
|
1009
|
+
* const { attachment, presigned_url } = await tasks.uploadAttachment(
|
|
1010
|
+
* 'task_abc123',
|
|
1011
|
+
* { filename: 'report.pdf', content_type: 'application/pdf', size: 102400 },
|
|
1012
|
+
* );
|
|
1013
|
+
*
|
|
1014
|
+
* // Upload the file to S3
|
|
1015
|
+
* await fetch(presigned_url, { method: 'PUT', body: fileContent });
|
|
1016
|
+
*
|
|
1017
|
+
* // Confirm the upload
|
|
1018
|
+
* await tasks.confirmAttachment(attachment.id);
|
|
1019
|
+
* ```
|
|
1020
|
+
*/
|
|
609
1021
|
async uploadAttachment(taskId, params) {
|
|
610
1022
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
611
1023
|
throw new TaskIdRequiredError();
|
|
@@ -633,6 +1045,24 @@ export class TaskStorageService {
|
|
|
633
1045
|
}
|
|
634
1046
|
throw await toServiceException('POST', url, res.response);
|
|
635
1047
|
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Confirm that a file upload has completed successfully.
|
|
1050
|
+
*
|
|
1051
|
+
* @remarks
|
|
1052
|
+
* Call this after successfully uploading the file to the presigned URL
|
|
1053
|
+
* returned by {@link TaskStorageService.uploadAttachment | uploadAttachment}.
|
|
1054
|
+
*
|
|
1055
|
+
* @param attachmentId - The unique attachment identifier
|
|
1056
|
+
* @returns The confirmed attachment record
|
|
1057
|
+
* @throws {@link AttachmentIdRequiredError} if the attachment ID is empty or not a string
|
|
1058
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1059
|
+
*
|
|
1060
|
+
* @example
|
|
1061
|
+
* ```typescript
|
|
1062
|
+
* const confirmed = await tasks.confirmAttachment('att_ghi789');
|
|
1063
|
+
* console.log('Confirmed:', confirmed.filename);
|
|
1064
|
+
* ```
|
|
1065
|
+
*/
|
|
636
1066
|
async confirmAttachment(attachmentId) {
|
|
637
1067
|
if (!attachmentId || typeof attachmentId !== 'string' || attachmentId.trim().length === 0) {
|
|
638
1068
|
throw new AttachmentIdRequiredError();
|
|
@@ -658,6 +1088,20 @@ export class TaskStorageService {
|
|
|
658
1088
|
}
|
|
659
1089
|
throw await toServiceException('POST', url, res.response);
|
|
660
1090
|
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Get a presigned S3 URL for downloading an attachment.
|
|
1093
|
+
*
|
|
1094
|
+
* @param attachmentId - The unique attachment identifier
|
|
1095
|
+
* @returns A presigned download URL with expiry information
|
|
1096
|
+
* @throws {@link AttachmentIdRequiredError} if the attachment ID is empty or not a string
|
|
1097
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1098
|
+
*
|
|
1099
|
+
* @example
|
|
1100
|
+
* ```typescript
|
|
1101
|
+
* const { presigned_url, expiry_seconds } = await tasks.downloadAttachment('att_ghi789');
|
|
1102
|
+
* console.log(`Download URL (expires in ${expiry_seconds}s):`, presigned_url);
|
|
1103
|
+
* ```
|
|
1104
|
+
*/
|
|
661
1105
|
async downloadAttachment(attachmentId) {
|
|
662
1106
|
if (!attachmentId || typeof attachmentId !== 'string' || attachmentId.trim().length === 0) {
|
|
663
1107
|
throw new AttachmentIdRequiredError();
|
|
@@ -683,6 +1127,22 @@ export class TaskStorageService {
|
|
|
683
1127
|
}
|
|
684
1128
|
throw await toServiceException('POST', url, res.response);
|
|
685
1129
|
}
|
|
1130
|
+
/**
|
|
1131
|
+
* List all attachments on a task.
|
|
1132
|
+
*
|
|
1133
|
+
* @param taskId - The ID of the task
|
|
1134
|
+
* @returns List of attachments with total count
|
|
1135
|
+
* @throws {@link TaskIdRequiredError} if the task ID is empty or not a string
|
|
1136
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1137
|
+
*
|
|
1138
|
+
* @example
|
|
1139
|
+
* ```typescript
|
|
1140
|
+
* const { attachments, total } = await tasks.listAttachments('task_abc123');
|
|
1141
|
+
* for (const att of attachments) {
|
|
1142
|
+
* console.log(`${att.filename} (${att.content_type}, ${att.size} bytes)`);
|
|
1143
|
+
* }
|
|
1144
|
+
* ```
|
|
1145
|
+
*/
|
|
686
1146
|
async listAttachments(taskId) {
|
|
687
1147
|
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
|
|
688
1148
|
throw new TaskIdRequiredError();
|
|
@@ -708,6 +1168,19 @@ export class TaskStorageService {
|
|
|
708
1168
|
}
|
|
709
1169
|
throw await toServiceException('GET', url, res.response);
|
|
710
1170
|
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Delete an attachment permanently.
|
|
1173
|
+
*
|
|
1174
|
+
* @param attachmentId - The unique attachment identifier
|
|
1175
|
+
* @throws {@link AttachmentIdRequiredError} if the attachment ID is empty or not a string
|
|
1176
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1177
|
+
*
|
|
1178
|
+
* @example
|
|
1179
|
+
* ```typescript
|
|
1180
|
+
* await tasks.deleteAttachment('att_ghi789');
|
|
1181
|
+
* console.log('Attachment deleted');
|
|
1182
|
+
* ```
|
|
1183
|
+
*/
|
|
711
1184
|
async deleteAttachment(attachmentId) {
|
|
712
1185
|
if (!attachmentId || typeof attachmentId !== 'string' || attachmentId.trim().length === 0) {
|
|
713
1186
|
throw new AttachmentIdRequiredError();
|
|
@@ -733,6 +1206,20 @@ export class TaskStorageService {
|
|
|
733
1206
|
}
|
|
734
1207
|
throw await toServiceException('DELETE', url, res.response);
|
|
735
1208
|
}
|
|
1209
|
+
/**
|
|
1210
|
+
* List all users who have been referenced in tasks (as creators, assignees, or closers).
|
|
1211
|
+
*
|
|
1212
|
+
* @returns List of user entity references
|
|
1213
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1214
|
+
*
|
|
1215
|
+
* @example
|
|
1216
|
+
* ```typescript
|
|
1217
|
+
* const { users } = await tasks.listUsers();
|
|
1218
|
+
* for (const user of users) {
|
|
1219
|
+
* console.log(`${user.name} (${user.id})`);
|
|
1220
|
+
* }
|
|
1221
|
+
* ```
|
|
1222
|
+
*/
|
|
736
1223
|
async listUsers() {
|
|
737
1224
|
const url = buildUrl(this.#baseUrl, `/task/users/${TASK_API_VERSION}`);
|
|
738
1225
|
const signal = AbortSignal.timeout(30_000);
|
|
@@ -755,6 +1242,20 @@ export class TaskStorageService {
|
|
|
755
1242
|
}
|
|
756
1243
|
throw await toServiceException('GET', url, res.response);
|
|
757
1244
|
}
|
|
1245
|
+
/**
|
|
1246
|
+
* List all projects that have been referenced in tasks.
|
|
1247
|
+
*
|
|
1248
|
+
* @returns List of project entity references
|
|
1249
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1250
|
+
*
|
|
1251
|
+
* @example
|
|
1252
|
+
* ```typescript
|
|
1253
|
+
* const { projects } = await tasks.listProjects();
|
|
1254
|
+
* for (const project of projects) {
|
|
1255
|
+
* console.log(`${project.name} (${project.id})`);
|
|
1256
|
+
* }
|
|
1257
|
+
* ```
|
|
1258
|
+
*/
|
|
758
1259
|
async listProjects() {
|
|
759
1260
|
const url = buildUrl(this.#baseUrl, `/task/projects/${TASK_API_VERSION}`);
|
|
760
1261
|
const signal = AbortSignal.timeout(30_000);
|
|
@@ -777,6 +1278,22 @@ export class TaskStorageService {
|
|
|
777
1278
|
}
|
|
778
1279
|
throw await toServiceException('GET', url, res.response);
|
|
779
1280
|
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Get task activity time-series data showing daily task counts by status.
|
|
1283
|
+
*
|
|
1284
|
+
* @param params - Optional parameters controlling the number of days to retrieve
|
|
1285
|
+
* @returns Time-series activity data with daily snapshots
|
|
1286
|
+
* @throws {@link ServiceException} if the API request fails
|
|
1287
|
+
*
|
|
1288
|
+
* @example
|
|
1289
|
+
* ```typescript
|
|
1290
|
+
* const { activity, days } = await tasks.getActivity({ days: 30 });
|
|
1291
|
+
* console.log(`Activity over ${days} days:`);
|
|
1292
|
+
* for (const point of activity) {
|
|
1293
|
+
* console.log(`${point.date}: ${point.open} open, ${point.inProgress} in progress`);
|
|
1294
|
+
* }
|
|
1295
|
+
* ```
|
|
1296
|
+
*/
|
|
780
1297
|
async getActivity(params) {
|
|
781
1298
|
const queryParams = new URLSearchParams();
|
|
782
1299
|
if (params?.days !== undefined)
|