jira-datacenter-api-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,886 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BoardResource: () => BoardResource,
24
+ IssueResource: () => IssueResource,
25
+ JiraApiError: () => JiraApiError,
26
+ JiraClient: () => JiraClient,
27
+ ProjectResource: () => ProjectResource,
28
+ Security: () => Security,
29
+ SprintResource: () => SprintResource
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/security/Security.ts
34
+ function toBase64(value) {
35
+ if (typeof btoa !== "undefined") {
36
+ return btoa(value);
37
+ }
38
+ return Buffer.from(value).toString("base64");
39
+ }
40
+ var Security = class {
41
+ /**
42
+ * Creates a new Security instance with Basic Authentication credentials.
43
+ *
44
+ * @param apiUrl - The base URL of the Jira Data Center instance (e.g., `https://jira.example.com`).
45
+ * Must be a valid URL; throws if it cannot be parsed.
46
+ * @param user - The username to authenticate with
47
+ * @param token - The personal access token or password to authenticate with
48
+ *
49
+ * @throws {TypeError} If `apiUrl` is not a valid URL
50
+ */
51
+ constructor(apiUrl, user, token) {
52
+ if (!URL.canParse(apiUrl)) {
53
+ throw new TypeError(`Invalid apiUrl: "${apiUrl}" is not a valid URL`);
54
+ }
55
+ this.apiUrl = apiUrl.replace(/\/$/, "");
56
+ this.authorizationHeader = `Basic ${toBase64(`${user}:${token}`)}`;
57
+ }
58
+ /**
59
+ * Returns the base URL of the Jira Data Center instance, without a trailing slash.
60
+ *
61
+ * @returns The API base URL
62
+ */
63
+ getApiUrl() {
64
+ return this.apiUrl;
65
+ }
66
+ /**
67
+ * Returns the value of the `Authorization` header for Basic Authentication.
68
+ *
69
+ * @returns The Authorization header value in the format `Basic <base64-encoded-credentials>`
70
+ */
71
+ getAuthorizationHeader() {
72
+ return this.authorizationHeader;
73
+ }
74
+ /**
75
+ * Returns the full set of HTTP headers required for authenticated API requests.
76
+ *
77
+ * @returns An object containing `Authorization`, `Content-Type`, and `Accept` headers
78
+ */
79
+ getHeaders() {
80
+ return {
81
+ Authorization: this.authorizationHeader,
82
+ "Content-Type": "application/json",
83
+ Accept: "application/json"
84
+ };
85
+ }
86
+ };
87
+
88
+ // src/errors/JiraApiError.ts
89
+ var JiraApiError = class extends Error {
90
+ constructor(status, statusText) {
91
+ super(`Jira API error: ${status} ${statusText}`);
92
+ this.name = "JiraApiError";
93
+ this.status = status;
94
+ this.statusText = statusText;
95
+ }
96
+ };
97
+
98
+ // src/resources/IssueResource.ts
99
+ var IssueResource = class {
100
+ /** @internal */
101
+ constructor(request, issueIdOrKey) {
102
+ this.request = request;
103
+ this.basePath = `/issue/${issueIdOrKey}`;
104
+ }
105
+ /**
106
+ * Allows the resource to be awaited directly, resolving with the issue.
107
+ * Delegates to {@link IssueResource.get}.
108
+ */
109
+ then(onfulfilled, onrejected) {
110
+ return this.get().then(onfulfilled, onrejected);
111
+ }
112
+ /**
113
+ * Fetches the issue.
114
+ *
115
+ * `GET /rest/api/latest/issue/{issueIdOrKey}`
116
+ *
117
+ * @param params - Optional: `fields`, `expand`, `properties`, `updateHistory`
118
+ * @returns The issue object
119
+ */
120
+ async get(params) {
121
+ return this.request(
122
+ this.basePath,
123
+ params
124
+ );
125
+ }
126
+ /**
127
+ * Fetches comments for this issue.
128
+ *
129
+ * `GET /rest/api/latest/issue/{issueIdOrKey}/comment`
130
+ *
131
+ * @param params - Optional: `startAt`, `maxResults`, `orderBy`, `expand`
132
+ * @returns A paged response of comments
133
+ */
134
+ async comments(params) {
135
+ return this.request(
136
+ `${this.basePath}/comment`,
137
+ params
138
+ );
139
+ }
140
+ /**
141
+ * Fetches a single comment by ID.
142
+ *
143
+ * `GET /rest/api/latest/issue/{issueIdOrKey}/comment/{id}`
144
+ *
145
+ * @param commentId - The numeric comment ID
146
+ * @returns The comment object
147
+ */
148
+ async comment(commentId) {
149
+ return this.request(`${this.basePath}/comment/${commentId}`);
150
+ }
151
+ /**
152
+ * Fetches worklogs for this issue.
153
+ *
154
+ * `GET /rest/api/latest/issue/{issueIdOrKey}/worklog`
155
+ *
156
+ * @param params - Optional: `startAt`, `maxResults`, `expand`
157
+ * @returns A paged response of worklogs
158
+ */
159
+ async worklogs(params) {
160
+ return this.request(
161
+ `${this.basePath}/worklog`,
162
+ params
163
+ );
164
+ }
165
+ /**
166
+ * Fetches a single worklog by ID.
167
+ *
168
+ * `GET /rest/api/latest/issue/{issueIdOrKey}/worklog/{id}`
169
+ *
170
+ * @param worklogId - The numeric worklog ID
171
+ * @returns The worklog object
172
+ */
173
+ async worklog(worklogId) {
174
+ return this.request(`${this.basePath}/worklog/${worklogId}`);
175
+ }
176
+ /**
177
+ * Fetches the changelog for this issue.
178
+ *
179
+ * `GET /rest/api/latest/issue/{issueIdOrKey}/changelog`
180
+ *
181
+ * @param params - Optional: `startAt`, `maxResults`
182
+ * @returns A paged response of changelog entries
183
+ */
184
+ async changelog(params) {
185
+ return this.request(
186
+ `${this.basePath}/changelog`,
187
+ params
188
+ );
189
+ }
190
+ /**
191
+ * Fetches the available workflow transitions for this issue.
192
+ *
193
+ * `GET /rest/api/latest/issue/{issueIdOrKey}/transitions`
194
+ *
195
+ * @param params - Optional: `transitionId`, `expand`, `skipRemoteOnlyCondition`
196
+ * @returns An object containing the list of transitions
197
+ */
198
+ async transitions(params) {
199
+ return this.request(
200
+ `${this.basePath}/transitions`,
201
+ params
202
+ );
203
+ }
204
+ /**
205
+ * Fetches the remote links (external URLs) for this issue.
206
+ *
207
+ * `GET /rest/api/latest/issue/{issueIdOrKey}/remotelink`
208
+ *
209
+ * @param globalId - Optional global ID to filter by a specific remote link
210
+ * @returns An array of remote links
211
+ */
212
+ async remotelinks(globalId) {
213
+ return this.request(
214
+ `${this.basePath}/remotelink`,
215
+ globalId !== void 0 ? { globalId } : void 0
216
+ );
217
+ }
218
+ /**
219
+ * Fetches the vote information for this issue.
220
+ *
221
+ * `GET /rest/api/latest/issue/{issueIdOrKey}/votes`
222
+ *
223
+ * @returns The votes object
224
+ */
225
+ async votes() {
226
+ return this.request(`${this.basePath}/votes`);
227
+ }
228
+ /**
229
+ * Fetches the watcher information for this issue.
230
+ *
231
+ * `GET /rest/api/latest/issue/{issueIdOrKey}/watchers`
232
+ *
233
+ * @returns The watchers object
234
+ */
235
+ async watchers() {
236
+ return this.request(`${this.basePath}/watchers`);
237
+ }
238
+ };
239
+
240
+ // src/resources/ProjectResource.ts
241
+ var ProjectResource = class {
242
+ /** @internal */
243
+ constructor(request, projectIdOrKey) {
244
+ this.request = request;
245
+ this.basePath = `/project/${projectIdOrKey}`;
246
+ }
247
+ /**
248
+ * Allows the resource to be awaited directly, resolving with the project.
249
+ * Delegates to {@link ProjectResource.get}.
250
+ */
251
+ then(onfulfilled, onrejected) {
252
+ return this.get().then(onfulfilled, onrejected);
253
+ }
254
+ /**
255
+ * Fetches the project details.
256
+ *
257
+ * `GET /rest/api/latest/project/{projectIdOrKey}`
258
+ *
259
+ * @param params - Optional: `expand`
260
+ * @returns The project object
261
+ */
262
+ async get(params) {
263
+ return this.request(
264
+ this.basePath,
265
+ params
266
+ );
267
+ }
268
+ /**
269
+ * Fetches the components defined in this project.
270
+ *
271
+ * `GET /rest/api/latest/project/{projectIdOrKey}/components`
272
+ *
273
+ * @returns An array of project components
274
+ */
275
+ async components() {
276
+ return this.request(`${this.basePath}/components`);
277
+ }
278
+ /**
279
+ * Fetches the versions defined in this project.
280
+ *
281
+ * `GET /rest/api/latest/project/{projectIdOrKey}/versions`
282
+ *
283
+ * @param expand - Optional: `'operations'`
284
+ * @returns An array of project versions
285
+ */
286
+ async versions(expand) {
287
+ return this.request(
288
+ `${this.basePath}/versions`,
289
+ expand !== void 0 ? { expand } : void 0
290
+ );
291
+ }
292
+ /**
293
+ * Fetches the statuses available in this project, grouped by issue type.
294
+ *
295
+ * `GET /rest/api/latest/project/{projectIdOrKey}/statuses`
296
+ *
297
+ * @returns An array of issue type → statuses mappings
298
+ */
299
+ async statuses() {
300
+ return this.request(`${this.basePath}/statuses`);
301
+ }
302
+ /**
303
+ * Fetches the roles defined in this project.
304
+ *
305
+ * `GET /rest/api/latest/project/{projectIdOrKey}/role`
306
+ *
307
+ * @returns A record mapping role names to their resource URLs
308
+ */
309
+ async roles() {
310
+ return this.request(`${this.basePath}/role`);
311
+ }
312
+ /**
313
+ * Fetches a single role by ID, including the list of actors.
314
+ *
315
+ * `GET /rest/api/latest/project/{projectIdOrKey}/role/{id}`
316
+ *
317
+ * @param roleId - The numeric role ID
318
+ * @returns The project role object with actors
319
+ */
320
+ async role(roleId) {
321
+ return this.request(`${this.basePath}/role/${roleId}`);
322
+ }
323
+ };
324
+
325
+ // src/resources/SprintResource.ts
326
+ var SprintResource = class {
327
+ /** @internal */
328
+ constructor(request, agileApiPath, sprintId) {
329
+ this.request = request;
330
+ this.agileApiPath = agileApiPath;
331
+ this.basePath = `/sprint/${sprintId}`;
332
+ }
333
+ /**
334
+ * Allows the resource to be awaited directly, resolving with the sprint.
335
+ * Delegates to {@link SprintResource.get}.
336
+ */
337
+ then(onfulfilled, onrejected) {
338
+ return this.get().then(onfulfilled, onrejected);
339
+ }
340
+ /**
341
+ * Fetches the sprint details.
342
+ *
343
+ * `GET /rest/agile/latest/sprint/{sprintId}`
344
+ *
345
+ * @returns The sprint object
346
+ */
347
+ async get() {
348
+ return this.request(this.basePath, void 0, { apiPath: this.agileApiPath });
349
+ }
350
+ /**
351
+ * Fetches issues in this sprint.
352
+ *
353
+ * `GET /rest/agile/latest/sprint/{sprintId}/issue`
354
+ *
355
+ * @param params - Optional: `startAt`, `maxResults`, `jql`, `fields`, `expand`
356
+ * @returns A search response containing the sprint's issues
357
+ */
358
+ async issues(params) {
359
+ return this.request(
360
+ `${this.basePath}/issue`,
361
+ params,
362
+ { apiPath: this.agileApiPath }
363
+ );
364
+ }
365
+ };
366
+
367
+ // src/resources/BoardResource.ts
368
+ var BoardResource = class {
369
+ /** @internal */
370
+ constructor(request, agileApiPath, boardId) {
371
+ this.request = request;
372
+ this.agileApiPath = agileApiPath;
373
+ this.basePath = `/board/${boardId}`;
374
+ }
375
+ /**
376
+ * Allows the resource to be awaited directly, resolving with the board.
377
+ * Delegates to {@link BoardResource.get}.
378
+ */
379
+ then(onfulfilled, onrejected) {
380
+ return this.get().then(onfulfilled, onrejected);
381
+ }
382
+ /**
383
+ * Fetches the board details.
384
+ *
385
+ * `GET /rest/agile/latest/board/{boardId}`
386
+ *
387
+ * @returns The board object
388
+ */
389
+ async get() {
390
+ return this.request(this.basePath, void 0, { apiPath: this.agileApiPath });
391
+ }
392
+ /**
393
+ * Fetches sprints for this board.
394
+ *
395
+ * `GET /rest/agile/latest/board/{boardId}/sprint`
396
+ *
397
+ * @param params - Optional: `startAt`, `maxResults`, `state`
398
+ * @returns A paged response of sprints
399
+ */
400
+ async sprints(params) {
401
+ return this.request(
402
+ `${this.basePath}/sprint`,
403
+ params,
404
+ { apiPath: this.agileApiPath }
405
+ );
406
+ }
407
+ /**
408
+ * Fetches all issues on this board (across all sprints and the backlog).
409
+ *
410
+ * `GET /rest/agile/latest/board/{boardId}/issue`
411
+ *
412
+ * @param params - Optional: `startAt`, `maxResults`, `jql`, `fields`, `expand`
413
+ * @returns A search response containing the board's issues
414
+ */
415
+ async issues(params) {
416
+ return this.request(
417
+ `${this.basePath}/issue`,
418
+ params,
419
+ { apiPath: this.agileApiPath }
420
+ );
421
+ }
422
+ /**
423
+ * Fetches the backlog issues for this board.
424
+ *
425
+ * `GET /rest/agile/latest/board/{boardId}/backlog`
426
+ *
427
+ * @param params - Optional: `startAt`, `maxResults`, `jql`, `fields`, `expand`
428
+ * @returns A search response containing the backlog issues
429
+ */
430
+ async backlog(params) {
431
+ return this.request(
432
+ `${this.basePath}/backlog`,
433
+ params,
434
+ { apiPath: this.agileApiPath }
435
+ );
436
+ }
437
+ /**
438
+ * Returns a {@link SprintResource} for a given sprint ID.
439
+ *
440
+ * The returned resource can be awaited directly to fetch sprint info,
441
+ * or chained to access sprint issues.
442
+ *
443
+ * @param sprintId - The numeric sprint ID
444
+ * @returns A chainable sprint resource
445
+ *
446
+ * @example
447
+ * ```typescript
448
+ * const sprint = await jiraClient.board(42).sprint(10);
449
+ * const sprintIssues = await jiraClient.board(42).sprint(10).issues();
450
+ * ```
451
+ */
452
+ sprint(sprintId) {
453
+ return new SprintResource(this.request, this.agileApiPath, sprintId);
454
+ }
455
+ };
456
+
457
+ // src/JiraClient.ts
458
+ var JiraClient = class {
459
+ /**
460
+ * @param options - Connection and authentication options
461
+ * @throws {TypeError} If `apiUrl` is not a valid URL
462
+ */
463
+ constructor({ apiUrl, apiPath = "rest/api/latest", agileApiPath = "rest/agile/latest", user, token }) {
464
+ this.listeners = /* @__PURE__ */ new Map();
465
+ this.security = new Security(apiUrl, user, token);
466
+ this.apiPath = apiPath.replace(/^\/|\/$/g, "");
467
+ this.agileApiPath = agileApiPath.replace(/^\/|\/$/g, "");
468
+ }
469
+ /**
470
+ * Subscribes to a client event.
471
+ *
472
+ * @example
473
+ * ```typescript
474
+ * jira.on('request', (event) => {
475
+ * console.log(`${event.method} ${event.url} — ${event.durationMs}ms`);
476
+ * if (event.error) console.error('Request failed:', event.error);
477
+ * });
478
+ * ```
479
+ */
480
+ on(event, callback) {
481
+ const callbacks = this.listeners.get(event) ?? [];
482
+ callbacks.push(callback);
483
+ this.listeners.set(event, callbacks);
484
+ return this;
485
+ }
486
+ emit(event, payload) {
487
+ const callbacks = this.listeners.get(event) ?? [];
488
+ for (const cb of callbacks) {
489
+ cb(payload);
490
+ }
491
+ }
492
+ /**
493
+ * Performs an authenticated GET request to the Jira REST API.
494
+ * @internal
495
+ */
496
+ async request(path, params, options) {
497
+ const apiPath = options?.apiPath ?? this.apiPath;
498
+ const base = `${this.security.getApiUrl()}/${apiPath}${path}`;
499
+ const url = buildUrl(base, params);
500
+ const startedAt = /* @__PURE__ */ new Date();
501
+ let statusCode;
502
+ try {
503
+ const response = await fetch(url, { headers: this.security.getHeaders() });
504
+ statusCode = response.status;
505
+ if (!response.ok) {
506
+ throw new JiraApiError(response.status, response.statusText);
507
+ }
508
+ const data = await response.json();
509
+ this.emit("request", { url, method: "GET", startedAt, finishedAt: /* @__PURE__ */ new Date(), durationMs: Date.now() - startedAt.getTime(), statusCode });
510
+ return data;
511
+ } catch (err) {
512
+ const finishedAt = /* @__PURE__ */ new Date();
513
+ this.emit("request", { url, method: "GET", startedAt, finishedAt, durationMs: finishedAt.getTime() - startedAt.getTime(), statusCode, error: err instanceof Error ? err : new Error(String(err)) });
514
+ throw err;
515
+ }
516
+ }
517
+ async requestPost(path, body, options) {
518
+ const apiPath = options?.apiPath ?? this.apiPath;
519
+ const url = `${this.security.getApiUrl()}/${apiPath}${path}`;
520
+ const startedAt = /* @__PURE__ */ new Date();
521
+ let statusCode;
522
+ try {
523
+ const response = await fetch(url, {
524
+ method: "POST",
525
+ headers: this.security.getHeaders(),
526
+ body: JSON.stringify(body)
527
+ });
528
+ statusCode = response.status;
529
+ if (!response.ok) {
530
+ throw new JiraApiError(response.status, response.statusText);
531
+ }
532
+ const data = await response.json();
533
+ this.emit("request", { url, method: "POST", startedAt, finishedAt: /* @__PURE__ */ new Date(), durationMs: Date.now() - startedAt.getTime(), statusCode });
534
+ return data;
535
+ } catch (err) {
536
+ const finishedAt = /* @__PURE__ */ new Date();
537
+ this.emit("request", { url, method: "POST", startedAt, finishedAt, durationMs: finishedAt.getTime() - startedAt.getTime(), statusCode, error: err instanceof Error ? err : new Error(String(err)) });
538
+ throw err;
539
+ }
540
+ }
541
+ // ─── Issue ───────────────────────────────────────────────────────────────────
542
+ /**
543
+ * Returns an {@link IssueResource} for a given issue key or ID, providing access
544
+ * to issue data and sub-resources (comments, changelog, transitions, etc.).
545
+ *
546
+ * The returned resource can be awaited directly to fetch the issue,
547
+ * or chained to access nested resources.
548
+ *
549
+ * @param issueIdOrKey - The issue key (e.g., `'PROJ-42'`) or numeric ID
550
+ * @returns A chainable issue resource
551
+ *
552
+ * @example
553
+ * ```typescript
554
+ * const issue = await jira.issue('PROJ-42');
555
+ * const comments = await jira.issue('PROJ-42').comments();
556
+ * const changelog = await jira.issue('PROJ-42').changelog();
557
+ * const transitions = await jira.issue('PROJ-42').transitions();
558
+ * ```
559
+ */
560
+ issue(issueIdOrKey) {
561
+ const requestFn = (path, params, options) => this.request(path, params, options);
562
+ return new IssueResource(requestFn, issueIdOrKey);
563
+ }
564
+ // ─── Search ──────────────────────────────────────────────────────────────────
565
+ /**
566
+ * Searches for issues using JQL.
567
+ *
568
+ * `GET /rest/api/latest/search`
569
+ *
570
+ * @param params - Optional: `jql`, `startAt`, `maxResults`, `fields`, `expand`, `validateQuery`
571
+ * @returns A search response containing matching issues
572
+ *
573
+ * @example
574
+ * ```typescript
575
+ * const results = await jira.search({
576
+ * jql: 'project = PROJ AND status = Open ORDER BY created DESC',
577
+ * maxResults: 50,
578
+ * fields: 'summary,status,assignee,priority',
579
+ * });
580
+ * ```
581
+ */
582
+ async search(params) {
583
+ return this.request(
584
+ "/search",
585
+ params
586
+ );
587
+ }
588
+ // ─── Projects ────────────────────────────────────────────────────────────────
589
+ /**
590
+ * Fetches all projects accessible to the authenticated user.
591
+ *
592
+ * `GET /rest/api/latest/project`
593
+ *
594
+ * @param params - Optional: `expand`, `recent`
595
+ * @returns An array of projects
596
+ */
597
+ async projects(params) {
598
+ return this.request(
599
+ "/project",
600
+ params
601
+ );
602
+ }
603
+ /**
604
+ * Returns a {@link ProjectResource} for a given project key or ID.
605
+ *
606
+ * The returned resource can be awaited directly to fetch project info,
607
+ * or chained to access nested resources.
608
+ *
609
+ * @param projectIdOrKey - The project key (e.g., `'PROJ'`) or numeric ID
610
+ * @returns A chainable project resource
611
+ *
612
+ * @example
613
+ * ```typescript
614
+ * const project = await jira.project('PROJ');
615
+ * const components = await jira.project('PROJ').components();
616
+ * const versions = await jira.project('PROJ').versions();
617
+ * const statuses = await jira.project('PROJ').statuses();
618
+ * ```
619
+ */
620
+ project(projectIdOrKey) {
621
+ const requestFn = (path, params, options) => this.request(path, params, options);
622
+ return new ProjectResource(requestFn, projectIdOrKey);
623
+ }
624
+ // ─── Users ───────────────────────────────────────────────────────────────────
625
+ /**
626
+ * Searches for users.
627
+ *
628
+ * `GET /rest/api/latest/user/search`
629
+ *
630
+ * @param params - Optional: `username`, `startAt`, `maxResults`
631
+ * @returns An array of users
632
+ */
633
+ async users(params) {
634
+ return this.request(
635
+ "/user/search",
636
+ params
637
+ );
638
+ }
639
+ /**
640
+ * Fetches a single user by username or key.
641
+ *
642
+ * `GET /rest/api/latest/user`
643
+ *
644
+ * @param username - The username (login name) to look up
645
+ * @returns The user object
646
+ *
647
+ * @example
648
+ * ```typescript
649
+ * const user = await jira.user('pilmee');
650
+ * ```
651
+ */
652
+ async user(username) {
653
+ return this.request("/user", { username });
654
+ }
655
+ /**
656
+ * Fetches the currently authenticated user.
657
+ *
658
+ * `GET /rest/api/latest/myself`
659
+ *
660
+ * @returns The authenticated user object
661
+ *
662
+ * @example
663
+ * ```typescript
664
+ * const me = await jira.currentUser();
665
+ * ```
666
+ */
667
+ async currentUser() {
668
+ return this.request("/myself");
669
+ }
670
+ // ─── Boards (Agile) ──────────────────────────────────────────────────────────
671
+ /**
672
+ * Fetches all boards accessible to the authenticated user.
673
+ *
674
+ * `GET /rest/agile/latest/board`
675
+ *
676
+ * @param params - Optional: `startAt`, `maxResults`, `type`, `name`, `projectKeyOrId`
677
+ * @returns A paged response of boards
678
+ */
679
+ async boards(params) {
680
+ return this.request(
681
+ "/board",
682
+ params,
683
+ { apiPath: this.agileApiPath }
684
+ );
685
+ }
686
+ /**
687
+ * Returns a {@link BoardResource} for a given board ID.
688
+ *
689
+ * The returned resource can be awaited directly to fetch board info,
690
+ * or chained to access nested resources (sprints, issues, backlog).
691
+ *
692
+ * @param boardId - The numeric board ID
693
+ * @returns A chainable board resource
694
+ *
695
+ * @example
696
+ * ```typescript
697
+ * const board = await jira.board(42);
698
+ * const sprints = await jira.board(42).sprints({ state: 'active' });
699
+ * const backlog = await jira.board(42).backlog({ maxResults: 50 });
700
+ * const sprintIssues = await jira.board(42).sprint(10).issues();
701
+ * ```
702
+ */
703
+ board(boardId) {
704
+ const requestFn = (path, params, options) => this.request(path, params, options);
705
+ return new BoardResource(requestFn, this.agileApiPath, boardId);
706
+ }
707
+ // ─── Metadata ────────────────────────────────────────────────────────────────
708
+ /**
709
+ * Fetches all issue types available to the authenticated user.
710
+ *
711
+ * `GET /rest/api/latest/issuetype`
712
+ *
713
+ * @returns An array of issue types
714
+ */
715
+ async issuetypes() {
716
+ return this.request("/issuetype");
717
+ }
718
+ /**
719
+ * Fetches a single issue type by ID.
720
+ *
721
+ * `GET /rest/api/latest/issuetype/{id}`
722
+ *
723
+ * @param id - The issue type ID
724
+ * @returns The issue type object
725
+ */
726
+ async issuetype(id) {
727
+ return this.request(`/issuetype/${id}`);
728
+ }
729
+ /**
730
+ * Fetches all priorities.
731
+ *
732
+ * `GET /rest/api/latest/priority`
733
+ *
734
+ * @returns An array of priorities
735
+ */
736
+ async priorities() {
737
+ return this.request("/priority");
738
+ }
739
+ /**
740
+ * Fetches a single priority by ID.
741
+ *
742
+ * `GET /rest/api/latest/priority/{id}`
743
+ *
744
+ * @param id - The priority ID
745
+ * @returns The priority object
746
+ */
747
+ async priority(id) {
748
+ return this.request(`/priority/${id}`);
749
+ }
750
+ /**
751
+ * Fetches all statuses.
752
+ *
753
+ * `GET /rest/api/latest/status`
754
+ *
755
+ * @returns An array of statuses
756
+ */
757
+ async statuses() {
758
+ return this.request("/status");
759
+ }
760
+ /**
761
+ * Fetches a single status by ID or name.
762
+ *
763
+ * `GET /rest/api/latest/status/{idOrName}`
764
+ *
765
+ * @param idOrName - The status ID or name
766
+ * @returns The status object
767
+ */
768
+ async status(idOrName) {
769
+ return this.request(`/status/${encodeURIComponent(idOrName)}`);
770
+ }
771
+ /**
772
+ * Fetches all issue fields (system and custom).
773
+ *
774
+ * `GET /rest/api/latest/field`
775
+ *
776
+ * @returns An array of fields
777
+ */
778
+ async fields() {
779
+ return this.request("/field");
780
+ }
781
+ /**
782
+ * Fetches all issue link types.
783
+ *
784
+ * `GET /rest/api/latest/issueLinkType`
785
+ *
786
+ * @returns An object with the list of issue link types
787
+ */
788
+ async issueLinkTypes() {
789
+ return this.request("/issueLinkType");
790
+ }
791
+ /**
792
+ * Fetches the authenticated user's favourite filters.
793
+ *
794
+ * `GET /rest/api/latest/filter/favourite`
795
+ *
796
+ * @returns An array of filters
797
+ */
798
+ async favouriteFilters() {
799
+ return this.request("/filter/favourite");
800
+ }
801
+ /**
802
+ * Fetches a single filter by ID.
803
+ *
804
+ * `GET /rest/api/latest/filter/{id}`
805
+ *
806
+ * @param filterId - The numeric filter ID
807
+ * @returns The filter object
808
+ */
809
+ async filter(filterId) {
810
+ return this.request(`/filter/${filterId}`);
811
+ }
812
+ /**
813
+ * Fetches issues using a `POST` search, which supports larger JQL queries
814
+ * and additional options such as specifying the fields list as an array.
815
+ *
816
+ * `POST /rest/api/latest/search`
817
+ *
818
+ * @param body - Search request body
819
+ * @returns A search response containing matching issues
820
+ *
821
+ * @example
822
+ * ```typescript
823
+ * const results = await jira.searchPost({
824
+ * jql: 'project = PROJ AND status = Open',
825
+ * maxResults: 100,
826
+ * fields: ['summary', 'status', 'assignee'],
827
+ * });
828
+ * ```
829
+ */
830
+ async searchPost(body) {
831
+ return this.requestPost("/search", body);
832
+ }
833
+ // ─── Components & Versions ───────────────────────────────────────────────────
834
+ /**
835
+ * Fetches a single component by ID.
836
+ *
837
+ * `GET /rest/api/latest/component/{id}`
838
+ *
839
+ * @param componentId - The component ID
840
+ * @returns The component object
841
+ */
842
+ async component(componentId) {
843
+ return this.request(`/component/${componentId}`);
844
+ }
845
+ /**
846
+ * Fetches a single version by ID.
847
+ *
848
+ * `GET /rest/api/latest/version/{id}`
849
+ *
850
+ * @param versionId - The version ID
851
+ * @returns The version object
852
+ */
853
+ async version(versionId) {
854
+ return this.request(`/version/${versionId}`);
855
+ }
856
+ /**
857
+ * Fetches the related issue counts for a version.
858
+ *
859
+ * `GET /rest/api/latest/version/{id}/relatedIssueCounts`
860
+ *
861
+ * @param versionId - The version ID
862
+ * @returns Issue counts by type
863
+ */
864
+ async versionIssueCounts(versionId) {
865
+ return this.request(`/version/${versionId}/relatedIssueCounts`);
866
+ }
867
+ /**
868
+ * Fetches the number of unresolved issues for a version.
869
+ *
870
+ * `GET /rest/api/latest/version/{id}/unresolvedIssueCount`
871
+ *
872
+ * @param versionId - The version ID
873
+ * @returns The unresolved issue count
874
+ */
875
+ async versionUnresolvedIssueCount(versionId) {
876
+ return this.request(`/version/${versionId}/unresolvedIssueCount`);
877
+ }
878
+ };
879
+ function buildUrl(base, params) {
880
+ if (!params) return base;
881
+ const entries = Object.entries(params).filter(([, v]) => v !== void 0);
882
+ if (entries.length === 0) return base;
883
+ const search = new URLSearchParams(entries.map(([k, v]) => [k, String(v)]));
884
+ return `${base}?${search.toString()}`;
885
+ }
886
+ //# sourceMappingURL=index.js.map