@rawdash/connector-jira 0.22.0 → 0.24.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.d.ts +37 -0
- package/dist/index.js +58 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ type JiraCredentials = typeof jiraCredentials;
|
|
|
41
41
|
declare const jiraResources: {
|
|
42
42
|
readonly jira_project: {
|
|
43
43
|
readonly shape: "entity";
|
|
44
|
+
readonly filterable: [];
|
|
44
45
|
readonly description: "Jira projects with key, name, type, and project lead. Restrict via projectKeys to limit the sync.";
|
|
45
46
|
readonly endpoint: "GET /rest/api/3/project/search";
|
|
46
47
|
readonly responses: {
|
|
@@ -64,6 +65,7 @@ declare const jiraResources: {
|
|
|
64
65
|
};
|
|
65
66
|
readonly jira_user: {
|
|
66
67
|
readonly shape: "entity";
|
|
68
|
+
readonly filterable: [];
|
|
67
69
|
readonly description: "Atlassian accounts visible to the connector, including display name, email, account type, and active state.";
|
|
68
70
|
readonly endpoint: "GET /rest/api/3/users/search";
|
|
69
71
|
readonly responses: {
|
|
@@ -78,6 +80,11 @@ declare const jiraResources: {
|
|
|
78
80
|
};
|
|
79
81
|
readonly jira_sprint: {
|
|
80
82
|
readonly shape: "entity";
|
|
83
|
+
readonly filterable: [{
|
|
84
|
+
readonly field: "state";
|
|
85
|
+
readonly ops: ["eq"];
|
|
86
|
+
readonly values: ["future", "active", "closed"];
|
|
87
|
+
}];
|
|
81
88
|
readonly description: "Sprints from scrum boards with state, start/end/complete dates, and owning board.";
|
|
82
89
|
readonly endpoint: "GET /rest/agile/1.0/board/{boardId}/sprint";
|
|
83
90
|
readonly responses: {
|
|
@@ -98,6 +105,16 @@ declare const jiraResources: {
|
|
|
98
105
|
};
|
|
99
106
|
readonly jira_issue: {
|
|
100
107
|
readonly shape: "entity";
|
|
108
|
+
readonly filterable: [{
|
|
109
|
+
readonly field: "statusName";
|
|
110
|
+
readonly ops: ["eq"];
|
|
111
|
+
}, {
|
|
112
|
+
readonly field: "priority";
|
|
113
|
+
readonly ops: ["eq"];
|
|
114
|
+
}, {
|
|
115
|
+
readonly field: "issueType";
|
|
116
|
+
readonly ops: ["eq"];
|
|
117
|
+
}];
|
|
101
118
|
readonly description: "Issues with status, priority, type, assignee, reporter, project, sprint, story points, and resolution date.";
|
|
102
119
|
readonly endpoint: "GET /rest/api/3/search/jql";
|
|
103
120
|
readonly notes: "sprintId is taken from the most recent sprint on the issue's sprint custom field.";
|
|
@@ -165,6 +182,7 @@ declare const jiraResources: {
|
|
|
165
182
|
};
|
|
166
183
|
readonly jira_issue_status_change: {
|
|
167
184
|
readonly shape: "event";
|
|
185
|
+
readonly filterable: [];
|
|
168
186
|
readonly description: "Status transition events derived from issue changelogs, capturing the from/to status, author, and project.";
|
|
169
187
|
readonly endpoint: "GET /rest/api/3/search/jql (expand=changelog)";
|
|
170
188
|
readonly notes: "start_ts is the changelog entry time, end_ts is null. Timestamps are Unix epoch milliseconds.";
|
|
@@ -176,6 +194,7 @@ declare class JiraConnector extends BaseConnector<JiraSettings, JiraCredentials>
|
|
|
176
194
|
static readonly resources: {
|
|
177
195
|
readonly jira_project: {
|
|
178
196
|
readonly shape: "entity";
|
|
197
|
+
readonly filterable: [];
|
|
179
198
|
readonly description: "Jira projects with key, name, type, and project lead. Restrict via projectKeys to limit the sync.";
|
|
180
199
|
readonly endpoint: "GET /rest/api/3/project/search";
|
|
181
200
|
readonly responses: {
|
|
@@ -199,6 +218,7 @@ declare class JiraConnector extends BaseConnector<JiraSettings, JiraCredentials>
|
|
|
199
218
|
};
|
|
200
219
|
readonly jira_user: {
|
|
201
220
|
readonly shape: "entity";
|
|
221
|
+
readonly filterable: [];
|
|
202
222
|
readonly description: "Atlassian accounts visible to the connector, including display name, email, account type, and active state.";
|
|
203
223
|
readonly endpoint: "GET /rest/api/3/users/search";
|
|
204
224
|
readonly responses: {
|
|
@@ -213,6 +233,11 @@ declare class JiraConnector extends BaseConnector<JiraSettings, JiraCredentials>
|
|
|
213
233
|
};
|
|
214
234
|
readonly jira_sprint: {
|
|
215
235
|
readonly shape: "entity";
|
|
236
|
+
readonly filterable: [{
|
|
237
|
+
readonly field: "state";
|
|
238
|
+
readonly ops: ["eq"];
|
|
239
|
+
readonly values: ["future", "active", "closed"];
|
|
240
|
+
}];
|
|
216
241
|
readonly description: "Sprints from scrum boards with state, start/end/complete dates, and owning board.";
|
|
217
242
|
readonly endpoint: "GET /rest/agile/1.0/board/{boardId}/sprint";
|
|
218
243
|
readonly responses: {
|
|
@@ -233,6 +258,16 @@ declare class JiraConnector extends BaseConnector<JiraSettings, JiraCredentials>
|
|
|
233
258
|
};
|
|
234
259
|
readonly jira_issue: {
|
|
235
260
|
readonly shape: "entity";
|
|
261
|
+
readonly filterable: [{
|
|
262
|
+
readonly field: "statusName";
|
|
263
|
+
readonly ops: ["eq"];
|
|
264
|
+
}, {
|
|
265
|
+
readonly field: "priority";
|
|
266
|
+
readonly ops: ["eq"];
|
|
267
|
+
}, {
|
|
268
|
+
readonly field: "issueType";
|
|
269
|
+
readonly ops: ["eq"];
|
|
270
|
+
}];
|
|
236
271
|
readonly description: "Issues with status, priority, type, assignee, reporter, project, sprint, story points, and resolution date.";
|
|
237
272
|
readonly endpoint: "GET /rest/api/3/search/jql";
|
|
238
273
|
readonly notes: "sprintId is taken from the most recent sprint on the issue's sprint custom field.";
|
|
@@ -300,6 +335,7 @@ declare class JiraConnector extends BaseConnector<JiraSettings, JiraCredentials>
|
|
|
300
335
|
};
|
|
301
336
|
readonly jira_issue_status_change: {
|
|
302
337
|
readonly shape: "event";
|
|
338
|
+
readonly filterable: [];
|
|
303
339
|
readonly description: "Status transition events derived from issue changelogs, capturing the from/to status, author, and project.";
|
|
304
340
|
readonly endpoint: "GET /rest/api/3/search/jql (expand=changelog)";
|
|
305
341
|
readonly notes: "start_ts is the changelog entry time, end_ts is null. Timestamps are Unix epoch milliseconds.";
|
|
@@ -423,6 +459,7 @@ declare class JiraConnector extends BaseConnector<JiraSettings, JiraCredentials>
|
|
|
423
459
|
private buildHeaders;
|
|
424
460
|
private fetch;
|
|
425
461
|
private activePhases;
|
|
462
|
+
private singleSpec;
|
|
426
463
|
private buildJql;
|
|
427
464
|
private fetchProjectsPage;
|
|
428
465
|
private fetchUsersPage;
|
package/dist/index.js
CHANGED
|
@@ -200,24 +200,34 @@ var issuesResponseSchema = z.object({
|
|
|
200
200
|
var jiraResources = defineResources({
|
|
201
201
|
jira_project: {
|
|
202
202
|
shape: "entity",
|
|
203
|
+
filterable: [],
|
|
203
204
|
description: "Jira projects with key, name, type, and project lead. Restrict via projectKeys to limit the sync.",
|
|
204
205
|
endpoint: "GET /rest/api/3/project/search",
|
|
205
206
|
responses: { projects: projectsResponseSchema }
|
|
206
207
|
},
|
|
207
208
|
jira_user: {
|
|
208
209
|
shape: "entity",
|
|
210
|
+
filterable: [],
|
|
209
211
|
description: "Atlassian accounts visible to the connector, including display name, email, account type, and active state.",
|
|
210
212
|
endpoint: "GET /rest/api/3/users/search",
|
|
211
213
|
responses: { users: usersResponseSchema }
|
|
212
214
|
},
|
|
213
215
|
jira_sprint: {
|
|
214
216
|
shape: "entity",
|
|
217
|
+
filterable: [
|
|
218
|
+
{ field: "state", ops: ["eq"], values: ["future", "active", "closed"] }
|
|
219
|
+
],
|
|
215
220
|
description: "Sprints from scrum boards with state, start/end/complete dates, and owning board.",
|
|
216
221
|
endpoint: "GET /rest/agile/1.0/board/{boardId}/sprint",
|
|
217
222
|
responses: { sprints: sprintsResponseSchema }
|
|
218
223
|
},
|
|
219
224
|
jira_issue: {
|
|
220
225
|
shape: "entity",
|
|
226
|
+
filterable: [
|
|
227
|
+
{ field: "statusName", ops: ["eq"] },
|
|
228
|
+
{ field: "priority", ops: ["eq"] },
|
|
229
|
+
{ field: "issueType", ops: ["eq"] }
|
|
230
|
+
],
|
|
221
231
|
description: "Issues with status, priority, type, assignee, reporter, project, sprint, story points, and resolution date.",
|
|
222
232
|
endpoint: "GET /rest/api/3/search/jql",
|
|
223
233
|
notes: "sprintId is taken from the most recent sprint on the issue's sprint custom field.",
|
|
@@ -225,6 +235,7 @@ var jiraResources = defineResources({
|
|
|
225
235
|
},
|
|
226
236
|
jira_issue_status_change: {
|
|
227
237
|
shape: "event",
|
|
238
|
+
filterable: [],
|
|
228
239
|
description: "Status transition events derived from issue changelogs, capturing the from/to status, author, and project.",
|
|
229
240
|
endpoint: "GET /rest/api/3/search/jql (expand=changelog)",
|
|
230
241
|
notes: "start_ts is the changelog entry time, end_ts is null. Timestamps are Unix epoch milliseconds."
|
|
@@ -279,6 +290,20 @@ function formatJqlDate(iso) {
|
|
|
279
290
|
const pad = (n) => String(n).padStart(2, "0");
|
|
280
291
|
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
281
292
|
}
|
|
293
|
+
function jqlQuote(value) {
|
|
294
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
295
|
+
}
|
|
296
|
+
function pushableEq(filter, field) {
|
|
297
|
+
if (!filter) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
for (const clause of filter) {
|
|
301
|
+
if ("field" in clause && clause.field === field && clause.op === "eq" && typeof clause.value === "string") {
|
|
302
|
+
return clause.value;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
282
307
|
var id = "jira";
|
|
283
308
|
var JiraConnector = class _JiraConnector extends BaseConnector {
|
|
284
309
|
static id = id;
|
|
@@ -344,13 +369,15 @@ var JiraConnector = class _JiraConnector extends BaseConnector {
|
|
|
344
369
|
this.settings.resources
|
|
345
370
|
);
|
|
346
371
|
}
|
|
372
|
+
singleSpec(options, resource) {
|
|
373
|
+
const specs = options.fetchSpecs?.[resource];
|
|
374
|
+
return specs && specs.length === 1 ? specs[0] : void 0;
|
|
375
|
+
}
|
|
347
376
|
buildJql(options) {
|
|
348
377
|
const clauses = [];
|
|
349
378
|
const keys = this.settings.projectKeys;
|
|
350
379
|
if (keys && keys.length > 0) {
|
|
351
|
-
const quoted = keys.map(
|
|
352
|
-
(k) => `"${k.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
|
|
353
|
-
);
|
|
380
|
+
const quoted = keys.map((k) => jqlQuote(k));
|
|
354
381
|
clauses.push(`project in (${quoted.join(",")})`);
|
|
355
382
|
}
|
|
356
383
|
if (options.mode === "latest" && options.since) {
|
|
@@ -359,6 +386,19 @@ var JiraConnector = class _JiraConnector extends BaseConnector {
|
|
|
359
386
|
clauses.push(`updated >= "${formatted}"`);
|
|
360
387
|
}
|
|
361
388
|
}
|
|
389
|
+
const filter = this.singleSpec(options, "jira_issue")?.filter;
|
|
390
|
+
const status = pushableEq(filter, "statusName");
|
|
391
|
+
if (status !== null) {
|
|
392
|
+
clauses.push(`status = ${jqlQuote(status)}`);
|
|
393
|
+
}
|
|
394
|
+
const priority = pushableEq(filter, "priority");
|
|
395
|
+
if (priority !== null) {
|
|
396
|
+
clauses.push(`priority = ${jqlQuote(priority)}`);
|
|
397
|
+
}
|
|
398
|
+
const issueType = pushableEq(filter, "issueType");
|
|
399
|
+
if (issueType !== null) {
|
|
400
|
+
clauses.push(`issuetype = ${jqlQuote(issueType)}`);
|
|
401
|
+
}
|
|
362
402
|
const where = clauses.join(" AND ");
|
|
363
403
|
return where.length > 0 ? `${where} ORDER BY updated ASC` : "ORDER BY updated ASC";
|
|
364
404
|
}
|
|
@@ -398,7 +438,11 @@ var JiraConnector = class _JiraConnector extends BaseConnector {
|
|
|
398
438
|
);
|
|
399
439
|
return res.body;
|
|
400
440
|
}
|
|
401
|
-
async fetchSprintsForBoard(boardId, signal) {
|
|
441
|
+
async fetchSprintsForBoard(boardId, options, signal) {
|
|
442
|
+
const state = pushableEq(
|
|
443
|
+
this.singleSpec(options, "jira_sprint")?.filter,
|
|
444
|
+
"state"
|
|
445
|
+
);
|
|
402
446
|
const out = [];
|
|
403
447
|
let startAt = 0;
|
|
404
448
|
while (true) {
|
|
@@ -408,6 +452,9 @@ var JiraConnector = class _JiraConnector extends BaseConnector {
|
|
|
408
452
|
);
|
|
409
453
|
u.searchParams.set("startAt", String(startAt));
|
|
410
454
|
u.searchParams.set("maxResults", String(SPRINTS_PAGE_SIZE));
|
|
455
|
+
if (state !== null) {
|
|
456
|
+
u.searchParams.set("state", state);
|
|
457
|
+
}
|
|
411
458
|
const res = await this.fetch(
|
|
412
459
|
u.toString(),
|
|
413
460
|
"sprints",
|
|
@@ -423,7 +470,7 @@ var JiraConnector = class _JiraConnector extends BaseConnector {
|
|
|
423
470
|
}
|
|
424
471
|
return out;
|
|
425
472
|
}
|
|
426
|
-
async fetchSprintsPage(page, signal) {
|
|
473
|
+
async fetchSprintsPage(page, options, signal) {
|
|
427
474
|
const startAt = parseOffset(page);
|
|
428
475
|
const boardsPage = await this.fetchBoardsPage(startAt, signal);
|
|
429
476
|
const boards = boardsPage.values;
|
|
@@ -432,7 +479,11 @@ var JiraConnector = class _JiraConnector extends BaseConnector {
|
|
|
432
479
|
if (board.type !== "scrum") {
|
|
433
480
|
continue;
|
|
434
481
|
}
|
|
435
|
-
const boardSprints = await this.fetchSprintsForBoard(
|
|
482
|
+
const boardSprints = await this.fetchSprintsForBoard(
|
|
483
|
+
board.id,
|
|
484
|
+
options,
|
|
485
|
+
signal
|
|
486
|
+
);
|
|
436
487
|
for (const s of boardSprints) {
|
|
437
488
|
sprints.push({ ...s, boardId: board.id });
|
|
438
489
|
}
|
|
@@ -608,7 +659,7 @@ var JiraConnector = class _JiraConnector extends BaseConnector {
|
|
|
608
659
|
case "users":
|
|
609
660
|
return this.fetchUsersPage(page, sig);
|
|
610
661
|
case "sprints":
|
|
611
|
-
return this.fetchSprintsPage(page, sig);
|
|
662
|
+
return this.fetchSprintsPage(page, options, sig);
|
|
612
663
|
case "issues":
|
|
613
664
|
return this.fetchIssuesPage(page, options, sig);
|
|
614
665
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../connector-shared/src/errors.ts","../../../connector-shared/src/retry.ts","../../../connector-shared/src/version.ts","../../../connector-shared/src/request.ts","../../../connector-shared/src/rate-limit.ts","../../../connector-shared/src/map-concurrent.ts","../../../connector-shared/src/sanitize.ts","../../../connector-shared/src/epoch.ts","../../../connector-shared/src/pagination.ts","../../../connector-shared/src/logger.ts","../src/jira.ts","../src/index.ts"],"sourcesContent":["import type { HttpResponse } from './types';\n\nexport type HttpErrorKind =\n | 'transient'\n | 'rate_limit'\n | 'auth'\n | 'upstream_bug'\n | 'client_bug';\n\nexport abstract class HttpClientError extends Error {\n abstract readonly kind: HttpErrorKind;\n readonly response?: HttpResponse;\n\n constructor(message: string, response?: HttpResponse) {\n super(message);\n this.name = new.target.name;\n this.response = response;\n }\n}\n\nexport class TransientError extends HttpClientError {\n readonly kind = 'transient' as const;\n}\n\nexport class RateLimitError extends HttpClientError {\n readonly kind = 'rate_limit' as const;\n readonly retryAfter?: Date;\n\n constructor(message: string, response?: HttpResponse, retryAfter?: Date) {\n super(message, response);\n this.retryAfter = retryAfter;\n }\n}\n\nexport class AuthError extends HttpClientError {\n readonly kind = 'auth' as const;\n}\n\nexport class UpstreamBugError extends HttpClientError {\n readonly kind = 'upstream_bug' as const;\n}\n\nexport class ClientBugError extends HttpClientError {\n readonly kind = 'client_bug' as const;\n}\n\nexport function classifyStatus(status: number): HttpErrorKind {\n if (status === 429) {\n return 'rate_limit';\n }\n if (status === 401 || status === 403) {\n return 'auth';\n }\n if (status === 408) {\n return 'transient';\n }\n if (status >= 500) {\n return 'upstream_bug';\n }\n if (status >= 400) {\n return 'client_bug';\n }\n return 'client_bug';\n}\n\nexport function errorForStatus(\n message: string,\n response: HttpResponse,\n retryAfter?: Date,\n): HttpClientError {\n const kind = classifyStatus(response.status);\n switch (kind) {\n case 'rate_limit':\n return new RateLimitError(message, response, retryAfter);\n case 'auth':\n return new AuthError(message, response);\n case 'transient':\n return new TransientError(message, response);\n case 'upstream_bug':\n return new UpstreamBugError(message, response);\n case 'client_bug':\n return new ClientBugError(message, response);\n }\n}\n","import { HttpClientError, RateLimitError, TransientError } from './errors';\n\nexport interface RetryPolicy {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (status: number | null, err?: Error) => boolean;\n}\n\nexport const defaultRetryOn = (status: number | null, err?: Error): boolean => {\n if (err instanceof RateLimitError) {\n return true;\n }\n if (err instanceof TransientError) {\n return true;\n }\n if (status === null) {\n return err instanceof Error && !(err instanceof HttpClientError);\n }\n if (status === 408 || status === 429) {\n return true;\n }\n if (status >= 500) {\n return true;\n }\n return false;\n};\n\nexport function backoffDelayMs(\n attempt: number,\n policy: Required<Pick<RetryPolicy, 'initialDelayMs' | 'maxDelayMs'>>,\n): number {\n const base = policy.initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, policy.maxDelayMs);\n}\n\nexport function parseRetryAfter(\n headerValue: string | null,\n now: Date = new Date(),\n): Date | undefined {\n if (!headerValue) {\n return undefined;\n }\n const trimmed = headerValue.trim();\n if (/^\\d+$/.test(trimmed)) {\n return new Date(now.getTime() + Number(trimmed) * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n return new Date(parsed);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new Error('Aborted'));\n }\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error('Aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export const HTTP_CLIENT_VERSION = '0.0.0';\n\nexport const DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n\nexport function connectorUserAgent(connectorId: string): string {\n return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n}\n","import {\n AuthError,\n ClientBugError,\n HttpClientError,\n RateLimitError,\n TransientError,\n UpstreamBugError,\n errorForStatus,\n} from './errors';\nimport { defaultRetryOn, parseRetryAfter, sleep } from './retry';\nimport type { FetchLike, HttpMethod, HttpRequest, HttpResponse } from './types';\nimport { DEFAULT_USER_AGENT } from './version';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\nconst DEFAULT_MAX_DELAY_MS = 60_000;\nconst OBSERVER_TIMEOUT_MS = 250;\n\nexport interface RequestObservation {\n url: string;\n method: HttpMethod;\n status: number;\n resource: string;\n requestId: string;\n body: unknown;\n}\n\nexport type RequestObserver = (\n event: RequestObservation,\n) => void | Promise<void>;\n\nexport interface RequestOptions {\n fetch?: FetchLike;\n observer?: RequestObserver;\n resource: string;\n requestId?: string;\n}\n\nasync function notifyObserver(\n observer: RequestObserver,\n event: RequestObservation,\n): Promise<void> {\n let result: void | Promise<void>;\n try {\n result = observer(event);\n } catch (err) {\n console.warn('[connector-shared] request observer threw:', err);\n return;\n }\n if (!(result instanceof Promise)) {\n return;\n }\n const guarded = result.catch((err) => {\n console.warn('[connector-shared] request observer rejected:', err);\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, OBSERVER_TIMEOUT_MS);\n });\n try {\n await Promise.race([guarded, timeout]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n\nfunction newRequestId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction mergeHeaders(\n defaults: Record<string, string>,\n overrides: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const [k, v] of Object.entries(defaults)) {\n merged[k.toLowerCase()] = v;\n }\n if (overrides) {\n for (const [k, v] of Object.entries(overrides)) {\n merged[k.toLowerCase()] = v;\n }\n }\n return merged;\n}\n\nfunction linkTimeoutSignal(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): { signal: AbortSignal; cancel: () => void } {\n const controller = new AbortController();\n const onParentAbort = () => {\n controller.abort(parent?.reason);\n };\n if (parent) {\n if (parent.aborted) {\n controller.abort(parent.reason);\n } else {\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n const timer = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (parent) {\n parent.removeEventListener('abort', onParentAbort);\n }\n },\n };\n}\n\nasync function readBody(res: Response, parseJson: boolean): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n const contentType = res.headers.get('content-type') ?? '';\n if (parseJson && contentType.includes('application/json')) {\n const text = await res.text();\n if (text.length === 0) {\n return null;\n }\n return JSON.parse(text);\n }\n return res.text();\n}\n\nexport async function request<T = unknown>(\n req: HttpRequest,\n options: RequestOptions,\n): Promise<HttpResponse<T>> {\n const fetchImpl: FetchLike = options.fetch ?? (globalThis.fetch as FetchLike);\n const retry = req.retry ?? {};\n const maxAttempts = retry.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const initialDelayMs = retry.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;\n const maxDelayMs = retry.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const timeoutMs = req.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const parseJson = req.parseJson ?? true;\n\n const headers = mergeHeaders(\n {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'application/json',\n },\n req.headers,\n );\n\n let lastErr: Error | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n req.signal?.throwIfAborted();\n\n const { signal, cancel } = linkTimeoutSignal(req.signal, timeoutMs);\n let res: Response;\n try {\n res = await fetchImpl(req.url, {\n method: req.method ?? 'GET',\n headers,\n body: req.body as RequestInit['body'],\n signal,\n });\n } catch (err) {\n cancel();\n if (req.signal?.aborted) {\n throw req.signal.reason ?? err;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n lastErr = error;\n if (attempt < maxAttempts - 1 && retryOn(null, error)) {\n const delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n await sleep(delay, req.signal);\n continue;\n }\n throw new TransientError(error.message);\n }\n cancel();\n\n const body = await readBody(res, parseJson);\n const httpResponse: HttpResponse<T> = {\n status: res.status,\n headers: res.headers,\n body: body as T,\n };\n if (req.rateLimit) {\n const state = req.rateLimit.parse(res.headers);\n if (state) {\n httpResponse.rateLimitState = state;\n }\n }\n\n if (options.observer) {\n await notifyObserver(options.observer, {\n url: req.url,\n method: req.method ?? 'GET',\n status: res.status,\n resource: options.resource,\n requestId: options.requestId ?? newRequestId(),\n body,\n });\n }\n\n if (res.ok) {\n return httpResponse;\n }\n\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const message = `HTTP ${res.status} ${res.statusText} for ${req.method ?? 'GET'} ${req.url}`;\n const err = errorForStatus(message, httpResponse, retryAfter);\n\n if (\n attempt < maxAttempts - 1 &&\n retryOn(res.status, err) &&\n !(err instanceof AuthError) &&\n !(err instanceof ClientBugError)\n ) {\n lastErr = err;\n let delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n if (err instanceof RateLimitError && retryAfter) {\n const wait = retryAfter.getTime() - Date.now();\n if (wait > 0) {\n delay = Math.min(wait, maxDelayMs);\n }\n }\n await sleep(delay, req.signal);\n continue;\n }\n\n throw err;\n }\n\n throw lastErr ?? new UpstreamBugError('Exhausted retry attempts');\n}\n\nfunction computeDelay(\n attempt: number,\n initialDelayMs: number,\n maxDelayMs: number,\n): number {\n const base = initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, maxDelayMs);\n}\n\nexport { HttpClientError };\n","export interface RateLimitState {\n remaining: number;\n resetAt: Date;\n}\n\nexport interface RateLimitPolicy {\n parse(headers: Headers): RateLimitState | null;\n}\n\nexport interface StandardRateLimitPolicyConfig {\n remainingHeader: string;\n resetHeader: string;\n resetUnit: 's' | 'ms';\n resetFallbackMs?: number;\n}\n\nexport function standardRateLimitPolicy(\n config: StandardRateLimitPolicyConfig,\n): RateLimitPolicy {\n const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;\n const multiplier = resetUnit === 's' ? 1000 : 1;\n return {\n parse(h) {\n const remainingRaw = h.get(remainingHeader);\n if (remainingRaw === null || remainingRaw.trim() === '') {\n return null;\n }\n const remaining = Number(remainingRaw);\n if (!Number.isFinite(remaining)) {\n return null;\n }\n const resetRaw = h.get(resetHeader);\n if (resetRaw === null) {\n if (resetFallbackMs === undefined) {\n return null;\n }\n return {\n remaining,\n resetAt: new Date(Date.now() + resetFallbackMs),\n };\n }\n if (resetRaw.trim() === '') {\n return null;\n }\n const reset = Number(resetRaw);\n if (!Number.isFinite(reset) || reset < 0) {\n return null;\n }\n const resetMs = reset * multiplier;\n if (!Number.isFinite(resetMs)) {\n return null;\n }\n return { remaining, resetAt: new Date(resetMs) };\n },\n };\n}\n","export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n if (items.length === 0) {\n return results;\n }\n const normalized = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;\n const limit = Math.max(1, Math.min(normalized, items.length));\n let next = 0;\n let failed = false;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const i = next++;\n if (i >= items.length) {\n return;\n }\n try {\n results[i] = await fn(items[i]!, i);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n\n const workers: Promise<void>[] = [];\n for (let w = 0; w < limit; w++) {\n workers.push(worker());\n }\n await Promise.all(workers);\n return results;\n}\n","export interface SanitizeAllowedUrlOptions {\n url: string | null;\n host: string;\n pathname: string;\n protocol?: 'https:' | 'http:';\n}\n\nexport function sanitizeAllowedUrl(\n options: SanitizeAllowedUrlOptions,\n): string | null {\n const { url, host, pathname, protocol = 'https:' } = options;\n if (url === null) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== protocol || u.host !== host || u.pathname !== pathname) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n}\n","export type EpochUnit = 'ms' | 's' | 'iso';\n\nexport function parseEpoch(\n value: number | string | null | undefined,\n unit: EpochUnit,\n): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (unit === 'iso') {\n if (typeof value !== 'string') {\n return null;\n }\n const ms = new Date(value).getTime();\n return Number.isFinite(ms) ? ms : null;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return null;\n }\n const n = typeof value === 'number' ? value : Number(value);\n if (!Number.isFinite(n)) {\n return null;\n }\n const result = unit === 's' ? n * 1000 : n;\n return Number.isFinite(result) ? result : null;\n}\n","import { request } from './request';\nimport type { HttpRequest } from './types';\n\nexport function parseLinkHeader(header: string | null): Record<string, string> {\n if (!header) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const part of header.split(',')) {\n const match = part.match(/<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"/);\n if (match) {\n result[match[2]!] = match[1]!;\n }\n }\n return result;\n}\n\nexport async function* paginateLink<T>(\n initial: HttpRequest,\n parse: (body: unknown) => T[],\n options: { resource: string },\n): AsyncIterable<T> {\n let next: string | null = initial.url;\n while (next) {\n const res: Awaited<ReturnType<typeof request>> = await request(\n {\n ...initial,\n url: next,\n },\n { resource: options.resource },\n );\n for (const item of parse(res.body)) {\n yield item;\n }\n const links = parseLinkHeader(res.headers.get('link'));\n next = links['next'] ?? null;\n }\n}\n\nexport async function* paginateCursor<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; nextCursor: string | null },\n buildNext: (req: HttpRequest, cursor: string) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let req: HttpRequest = initial;\n while (true) {\n const res = await request(req, { resource: options.resource });\n const { items, nextCursor } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!nextCursor) {\n return;\n }\n req = buildNext(req, nextCursor);\n }\n}\n\nexport async function* paginatePage<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; hasMore: boolean },\n buildPage: (req: HttpRequest, page: number) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let page = 1;\n while (true) {\n const req = page === 1 ? initial : buildPage(initial, page);\n const res = await request(req, { resource: options.resource });\n const { items, hasMore } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!hasMore || items.length === 0) {\n return;\n }\n page++;\n }\n}\n","export type LogFields = Record<string, unknown>;\n\nexport interface ConnectorLogger {\n info(event: string, fields?: LogFields): void;\n warn(event: string, fields?: LogFields): void;\n}\n\nexport interface ConnectorLoggerOptions {\n scope: string;\n}\n\nconst MAX_VALUE_LEN = 120;\n\nfunction truncate(s: string, max = MAX_VALUE_LEN): string {\n if (s.length <= max) {\n return s;\n }\n return `${s.slice(0, max - 1)}…`;\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (value === undefined) {\n return '';\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'string') {\n const t = truncate(value);\n if (/[\\s\"=]/.test(t)) {\n return JSON.stringify(t);\n }\n return t;\n }\n if (typeof value === 'bigint') {\n return value.toString();\n }\n let json: string | undefined;\n try {\n json = JSON.stringify(value);\n } catch {\n json = undefined;\n }\n return truncate(json ?? String(value));\n}\n\nexport function formatLogFields(fields?: LogFields): string {\n if (!fields) {\n return '';\n }\n const parts: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined) {\n continue;\n }\n parts.push(`${k}=${formatValue(v)}`);\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nexport function formatLogLine(\n scope: string,\n event: string,\n fields?: LogFields,\n): string {\n return `[${scope}] ${event}${formatLogFields(fields)}`;\n}\n\nexport function createDefaultConnectorLogger(\n opts: ConnectorLoggerOptions,\n): ConnectorLogger {\n return {\n info(event, fields) {\n console.info(formatLogLine(opts.scope, event, fields));\n },\n warn(event, fields) {\n console.warn(formatLogLine(opts.scope, event, fields));\n },\n };\n}\n\nconst NOOP_LOGGER: ConnectorLogger = {\n info() {},\n warn() {},\n};\n\nexport function noopConnectorLogger(): ConnectorLogger {\n return NOOP_LOGGER;\n}\n","import {\n type HttpResponse,\n connectorUserAgent,\n parseEpoch,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type JSONValue,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nexport const configFields = defineConfigFields(\n z.object({\n email: z.string().min(1).meta({\n label: 'Account email',\n description:\n 'Atlassian account email paired with the API token for Basic auth.',\n placeholder: 'you@yourorg.com',\n }),\n apiToken: z.object({ $secret: z.string() }).meta({\n label: 'API Token',\n description:\n 'Atlassian API token. Create one at id.atlassian.com → Security → API tokens.',\n placeholder: 'ATATT...',\n secret: true,\n }),\n host: z\n .string()\n .min(1)\n .regex(\n /^[^/\\s:?#]+$/,\n 'Use host only (no protocol, port, path, or query).',\n )\n .meta({\n label: 'Site host',\n description:\n 'Your Jira Cloud host, e.g. yourorg.atlassian.net (no protocol, no trailing slash).',\n placeholder: 'yourorg.atlassian.net',\n }),\n projectKeys: z.array(z.string().min(1)).nonempty().optional().meta({\n label: 'Project keys (optional)',\n description:\n 'Restrict the sync to specific Jira project keys (e.g. ENG, OPS). Omit to sync every project the account can see.',\n }),\n resources: z\n .array(z.enum(['projects', 'users', 'sprints', 'issues', 'issue_events']))\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n \"Which Jira resources to sync. Omit to sync all of them. 'issue_events' shares the issues query - enabling it without 'issues' still fetches issues (with changelog) but skips writing issue entities.\",\n }),\n storyPointsField: z.string().min(1).optional().meta({\n label: 'Story points field ID',\n description:\n 'Custom field ID holding story points (varies per site). Defaults to customfield_10016.',\n placeholder: 'customfield_10016',\n }),\n sprintField: z.string().min(1).optional().meta({\n label: 'Sprint field ID',\n description:\n 'Custom field ID holding the sprint association on issues. Defaults to customfield_10020.',\n placeholder: 'customfield_10020',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Jira',\n category: 'product',\n brandColor: '#0052CC',\n tagline:\n 'Sync projects, users, sprints, issues, and issue status-change events from a Jira Cloud site.',\n vendor: {\n name: 'Atlassian',\n domain: 'atlassian.com',\n apiDocs: 'https://developer.atlassian.com/cloud/jira/platform/rest/v3/',\n website: 'https://www.atlassian.com/software/jira',\n },\n auth: {\n summary:\n 'Authenticates over HTTP Basic auth using your Atlassian account email and an API token. The token must belong to an account with access to the projects you want to sync.',\n setup: [\n 'Open id.atlassian.com -> Security -> Create and manage API tokens.',\n 'Create an API token and copy its value.',\n 'Store the token as a secret and reference it from the connector config as `apiToken: secret(\"JIRA_API_TOKEN\")`, alongside your account email and site host (e.g. yourorg.atlassian.net).',\n 'Story points and the sprint association live on custom fields whose IDs differ per Jira site. Discover them at `https://{host}/rest/api/3/field` and set storyPointsField / sprintField to match.',\n ],\n },\n rateLimit:\n 'Jira Cloud uses cost-based rate limiting; 429 responses with Retry-After are honored.',\n limitations: [\n 'Sprints are only synced from scrum boards; kanban boards are skipped.',\n 'Issue status-change events are derived from each issue changelog; only `status` field transitions are written.',\n 'Targets Jira Cloud REST API v3 and the Agile API; Jira Data Center / Server are out of scope.',\n ],\n});\n\nexport type JiraResource =\n | 'projects'\n | 'users'\n | 'sprints'\n | 'issues'\n | 'issue_events';\n\nexport interface JiraSettings {\n host: string;\n projectKeys?: readonly string[];\n resources?: readonly JiraResource[];\n storyPointsField?: string;\n sprintField?: string;\n}\n\nconst jiraCredentials = {\n email: {\n description: 'Atlassian account email',\n auth: 'required' as const,\n },\n apiToken: {\n description: 'Atlassian API token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype JiraCredentials = typeof jiraCredentials;\n\nconst PHASE_ORDER = ['projects', 'users', 'sprints', 'issues'] as const;\n\ntype JiraPhase = (typeof PHASE_ORDER)[number];\n\nconst isJiraSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\ninterface JiraAccountRef {\n accountId: string;\n displayName?: string | null;\n}\n\ninterface JiraStatusCategory {\n key: string;\n name?: string | null;\n}\n\ninterface JiraStatus {\n name: string;\n statusCategory?: JiraStatusCategory | null;\n}\n\ninterface JiraNamed {\n name: string;\n}\n\ninterface JiraProjectRef {\n id: string;\n key: string;\n}\n\ninterface JiraIssueFields {\n summary?: string | null;\n status?: JiraStatus | null;\n priority?: JiraNamed | null;\n issuetype?: JiraNamed | null;\n assignee?: JiraAccountRef | null;\n reporter?: JiraAccountRef | null;\n project?: JiraProjectRef | null;\n created: string;\n updated: string;\n resolutiondate?: string | null;\n [key: string]: unknown;\n}\n\ninterface JiraChangelogItem {\n field: string;\n fromString?: string | null;\n toString?: string | null;\n}\n\ninterface JiraChangelogHistory {\n id: string;\n created: string;\n author?: JiraAccountRef | null;\n items: JiraChangelogItem[];\n}\n\ninterface JiraIssue {\n id: string;\n key: string;\n fields: JiraIssueFields;\n changelog?: { histories: JiraChangelogHistory[] } | null;\n}\n\ninterface JiraSearchResponse {\n issues: JiraIssue[];\n nextPageToken?: string | null;\n isLast?: boolean;\n}\n\ninterface JiraProject {\n id: string;\n key: string;\n name: string;\n projectTypeKey?: string | null;\n lead?: JiraAccountRef | null;\n}\n\ninterface JiraProjectPage {\n values: JiraProject[];\n isLast: boolean;\n startAt: number;\n maxResults: number;\n total: number;\n}\n\ninterface JiraUser {\n accountId: string;\n displayName?: string | null;\n emailAddress?: string | null;\n accountType?: string | null;\n active?: boolean | null;\n}\n\ninterface JiraSprint {\n id: number;\n name: string;\n state: 'active' | 'closed' | 'future';\n startDate?: string | null;\n endDate?: string | null;\n completeDate?: string | null;\n originBoardId?: number | null;\n}\n\ninterface JiraSprintWithBoard extends JiraSprint {\n boardId: number;\n}\n\ninterface JiraBoard {\n id: number;\n name: string;\n type: string;\n}\n\ninterface JiraAgilePage<T> {\n values: T[];\n isLast?: boolean;\n startAt: number;\n maxResults: number;\n}\n\nconst idString = z.string().min(1);\nconst nonNegInt = z.number().int().nonnegative();\n\nconst accountRefSchema = z.object({\n accountId: idString,\n displayName: z.string().nullable().optional(),\n});\n\nconst projectSchema = z.object({\n id: idString,\n key: z.string().min(1),\n name: z.string(),\n projectTypeKey: z.string().nullable().optional(),\n lead: accountRefSchema.nullable().optional(),\n});\n\nconst projectsResponseSchema = z.object({\n values: z.array(projectSchema),\n isLast: z.boolean(),\n startAt: nonNegInt,\n maxResults: nonNegInt,\n total: nonNegInt,\n});\n\nconst usersResponseSchema = z.array(\n z.object({\n accountId: idString,\n displayName: z.string().nullable().optional(),\n emailAddress: z.string().nullable().optional(),\n accountType: z.string().nullable().optional(),\n active: z.boolean().nullable().optional(),\n }),\n);\n\nconst sprintsResponseSchema = z.array(\n z.object({\n id: nonNegInt,\n name: z.string(),\n state: z.enum(['active', 'closed', 'future']),\n startDate: z.iso.datetime().nullable().optional(),\n endDate: z.iso.datetime().nullable().optional(),\n completeDate: z.iso.datetime().nullable().optional(),\n originBoardId: nonNegInt.nullable().optional(),\n }),\n);\n\nconst changelogHistorySchema = z.object({\n id: idString,\n created: z.iso.datetime(),\n author: accountRefSchema.nullable().optional(),\n items: z.array(\n z.object({\n field: z.string(),\n fromString: z.string().nullable().optional(),\n toString: z.string().nullable().optional(),\n }),\n ),\n});\n\nconst issueSchema = z.object({\n id: idString,\n key: z.string().min(1),\n fields: z.object({\n summary: z.string().nullable().optional(),\n status: z\n .object({\n name: z.string(),\n statusCategory: z\n .object({ key: z.string(), name: z.string().nullable().optional() })\n .nullable()\n .optional(),\n })\n .nullable()\n .optional(),\n priority: z.object({ name: z.string() }).nullable().optional(),\n issuetype: z.object({ name: z.string() }).nullable().optional(),\n assignee: accountRefSchema.nullable().optional(),\n reporter: accountRefSchema.nullable().optional(),\n project: z.object({ id: idString, key: z.string().min(1) }).nullable(),\n created: z.iso.datetime(),\n updated: z.iso.datetime(),\n resolutiondate: z.iso.datetime().nullable().optional(),\n customfield_10016: z.number().nullable().optional(),\n customfield_10020: z\n .array(z.object({ id: nonNegInt, name: z.string() }))\n .nullable()\n .optional(),\n }),\n changelog: z.object({ histories: z.array(changelogHistorySchema) }),\n});\n\nconst issuesResponseSchema = z.object({\n issues: z.array(issueSchema),\n nextPageToken: z.string().nullable().optional(),\n isLast: z.boolean().optional(),\n});\n\nexport const jiraResources = defineResources({\n jira_project: {\n shape: 'entity',\n description:\n 'Jira projects with key, name, type, and project lead. Restrict via projectKeys to limit the sync.',\n endpoint: 'GET /rest/api/3/project/search',\n responses: { projects: projectsResponseSchema },\n },\n jira_user: {\n shape: 'entity',\n description:\n 'Atlassian accounts visible to the connector, including display name, email, account type, and active state.',\n endpoint: 'GET /rest/api/3/users/search',\n responses: { users: usersResponseSchema },\n },\n jira_sprint: {\n shape: 'entity',\n description:\n 'Sprints from scrum boards with state, start/end/complete dates, and owning board.',\n endpoint: 'GET /rest/agile/1.0/board/{boardId}/sprint',\n responses: { sprints: sprintsResponseSchema },\n },\n jira_issue: {\n shape: 'entity',\n description:\n 'Issues with status, priority, type, assignee, reporter, project, sprint, story points, and resolution date.',\n endpoint: 'GET /rest/api/3/search/jql',\n notes:\n \"sprintId is taken from the most recent sprint on the issue's sprint custom field.\",\n responses: { issues: issuesResponseSchema },\n },\n jira_issue_status_change: {\n shape: 'event',\n description:\n 'Status transition events derived from issue changelogs, capturing the from/to status, author, and project.',\n endpoint: 'GET /rest/api/3/search/jql (expand=changelog)',\n notes:\n 'start_ts is the changelog entry time, end_ts is null. Timestamps are Unix epoch milliseconds.',\n },\n});\n\nconst PROJECTS_PAGE_SIZE = 50;\nconst USERS_PAGE_SIZE = 50;\nconst BOARDS_PAGE_SIZE = 50;\nconst SPRINTS_PAGE_SIZE = 50;\nconst ISSUES_PAGE_SIZE = 100;\nconst DEFAULT_STORY_POINTS_FIELD = 'customfield_10016';\nconst DEFAULT_SPRINT_FIELD = 'customfield_10020';\n\nconst ISSUE_FIELDS = [\n 'summary',\n 'status',\n 'priority',\n 'issuetype',\n 'assignee',\n 'reporter',\n 'project',\n 'created',\n 'updated',\n 'resolutiondate',\n] as const;\n\nfunction parseOffset(page: string | null): number {\n if (page === null) {\n return 0;\n }\n const n = Number.parseInt(page, 10);\n return Number.isFinite(n) && n >= 0 ? n : 0;\n}\n\nfunction extractSprintId(value: unknown): string | null {\n if (!Array.isArray(value) || value.length === 0) {\n return null;\n }\n const last = value[value.length - 1];\n if (last !== null && typeof last === 'object' && 'id' in last) {\n const id = (last as { id: unknown }).id;\n return id === null || id === undefined ? null : String(id);\n }\n if (typeof last === 'number' || typeof last === 'string') {\n return String(last);\n }\n return null;\n}\n\nfunction formatJqlDate(iso: string): string | null {\n const ms = parseEpoch(iso, 'iso');\n if (ms === null) {\n return null;\n }\n const d = new Date(ms);\n const pad = (n: number) => String(n).padStart(2, '0');\n return (\n `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ` +\n `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`\n );\n}\n\nexport const id = 'jira';\n\nexport class JiraConnector extends BaseConnector<\n JiraSettings,\n JiraCredentials\n> {\n static readonly id = id;\n\n static readonly resources = jiraResources;\n\n static readonly schemas = schemasFromResources(jiraResources);\n\n static create(input: unknown, ctx?: ConnectorContext): JiraConnector {\n const parsed = configFields.parse(input);\n return new JiraConnector(\n {\n host: parsed.host,\n projectKeys: parsed.projectKeys,\n resources: parsed.resources,\n storyPointsField: parsed.storyPointsField,\n sprintField: parsed.sprintField,\n },\n { email: parsed.email, apiToken: parsed.apiToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = jiraCredentials;\n\n private get baseUrl(): string {\n const host = this.settings.host\n .replace(/^https?:\\/\\//, '')\n .replace(/\\/+$/, '');\n return `https://${host}`;\n }\n\n private get storyPointsField(): string {\n return this.settings.storyPointsField ?? DEFAULT_STORY_POINTS_FIELD;\n }\n\n private get sprintField(): string {\n return this.settings.sprintField ?? DEFAULT_SPRINT_FIELD;\n }\n\n private buildHeaders(): Record<string, string> {\n const basic = btoa(`${this.creds.email}:${this.creds.apiToken}`);\n return {\n Authorization: `Basic ${basic}`,\n Accept: 'application/json',\n 'User-Agent': connectorUserAgent('jira'),\n };\n }\n\n private fetch<T>(\n url: string,\n resource: string,\n signal?: AbortSignal,\n ): Promise<HttpResponse<T>> {\n return this.get<T>(url, {\n resource,\n headers: this.buildHeaders(),\n signal,\n });\n }\n\n private activePhases(): JiraPhase[] {\n return selectActivePhases<JiraResource, JiraPhase>(\n (r) => {\n switch (r) {\n case 'projects':\n return 'projects';\n case 'users':\n return 'users';\n case 'sprints':\n return 'sprints';\n case 'issues':\n case 'issue_events':\n return 'issues';\n }\n },\n PHASE_ORDER,\n this.settings.resources,\n );\n }\n\n private buildJql(options: SyncOptions): string {\n const clauses: string[] = [];\n const keys = this.settings.projectKeys;\n if (keys && keys.length > 0) {\n const quoted = keys.map(\n (k) => `\"${k.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`,\n );\n clauses.push(`project in (${quoted.join(',')})`);\n }\n if (options.mode === 'latest' && options.since) {\n const formatted = formatJqlDate(options.since);\n if (formatted !== null) {\n clauses.push(`updated >= \"${formatted}\"`);\n }\n }\n const where = clauses.join(' AND ');\n return where.length > 0\n ? `${where} ORDER BY updated ASC`\n : 'ORDER BY updated ASC';\n }\n\n private async fetchProjectsPage(\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: JiraProject[]; next: string | null }> {\n const startAt = parseOffset(page);\n const u = new URL(`${this.baseUrl}/rest/api/3/project/search`);\n u.searchParams.set('startAt', String(startAt));\n u.searchParams.set('maxResults', String(PROJECTS_PAGE_SIZE));\n u.searchParams.set('expand', 'lead');\n const res = await this.fetch<JiraProjectPage>(\n u.toString(),\n 'projects',\n signal,\n );\n const values = res.body.values;\n const next =\n res.body.isLast || values.length === 0\n ? null\n : String(startAt + values.length);\n return { items: values, next };\n }\n\n private async fetchUsersPage(\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: JiraUser[]; next: string | null }> {\n const startAt = parseOffset(page);\n const u = new URL(`${this.baseUrl}/rest/api/3/users/search`);\n u.searchParams.set('startAt', String(startAt));\n u.searchParams.set('maxResults', String(USERS_PAGE_SIZE));\n const res = await this.fetch<JiraUser[]>(u.toString(), 'users', signal);\n const users = res.body;\n const next =\n users.length < USERS_PAGE_SIZE ? null : String(startAt + users.length);\n return { items: users, next };\n }\n\n private async fetchBoardsPage(\n startAt: number,\n signal: AbortSignal | undefined,\n ): Promise<JiraAgilePage<JiraBoard>> {\n const u = new URL(`${this.baseUrl}/rest/agile/1.0/board`);\n u.searchParams.set('startAt', String(startAt));\n u.searchParams.set('maxResults', String(BOARDS_PAGE_SIZE));\n const res = await this.fetch<JiraAgilePage<JiraBoard>>(\n u.toString(),\n 'sprints',\n signal,\n );\n return res.body;\n }\n\n private async fetchSprintsForBoard(\n boardId: number,\n signal: AbortSignal | undefined,\n ): Promise<JiraSprint[]> {\n const out: JiraSprint[] = [];\n let startAt = 0;\n while (true) {\n signal?.throwIfAborted();\n const u = new URL(\n `${this.baseUrl}/rest/agile/1.0/board/${boardId}/sprint`,\n );\n u.searchParams.set('startAt', String(startAt));\n u.searchParams.set('maxResults', String(SPRINTS_PAGE_SIZE));\n const res = await this.fetch<JiraAgilePage<JiraSprint>>(\n u.toString(),\n 'sprints',\n signal,\n );\n const values = res.body.values;\n out.push(...values);\n const isLast = res.body.isLast ?? values.length < SPRINTS_PAGE_SIZE;\n if (isLast || values.length === 0) {\n break;\n }\n startAt += values.length;\n }\n return out;\n }\n\n private async fetchSprintsPage(\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: JiraSprintWithBoard[]; next: string | null }> {\n const startAt = parseOffset(page);\n const boardsPage = await this.fetchBoardsPage(startAt, signal);\n const boards = boardsPage.values;\n const sprints: JiraSprintWithBoard[] = [];\n for (const board of boards) {\n if (board.type !== 'scrum') {\n continue;\n }\n const boardSprints = await this.fetchSprintsForBoard(board.id, signal);\n for (const s of boardSprints) {\n sprints.push({ ...s, boardId: board.id });\n }\n }\n const isLast = boardsPage.isLast ?? boards.length < BOARDS_PAGE_SIZE;\n const next = isLast ? null : String(startAt + boards.length);\n return { items: sprints, next };\n }\n\n private async fetchIssuesPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: JiraIssue[]; next: string | null }> {\n const u = new URL(`${this.baseUrl}/rest/api/3/search/jql`);\n u.searchParams.set('jql', this.buildJql(options));\n u.searchParams.set('maxResults', String(ISSUES_PAGE_SIZE));\n u.searchParams.set(\n 'fields',\n [...ISSUE_FIELDS, this.storyPointsField, this.sprintField].join(','),\n );\n u.searchParams.set('expand', 'changelog');\n if (page !== null) {\n u.searchParams.set('nextPageToken', page);\n }\n const res = await this.fetch<JiraSearchResponse>(\n u.toString(),\n 'issues',\n signal,\n );\n const token = res.body.nextPageToken ?? null;\n const next = res.body.isLast === true || token === null ? null : token;\n return { items: res.body.issues, next };\n }\n\n private async writeProjects(\n storage: StorageHandle,\n projects: JiraProject[],\n ): Promise<void> {\n const now = Date.now();\n for (const p of projects) {\n await storage.entity({\n type: 'jira_project',\n id: p.id,\n attributes: {\n key: p.key,\n name: p.name,\n projectTypeKey: p.projectTypeKey ?? null,\n leadAccountId: p.lead?.accountId ?? null,\n leadDisplayName: p.lead?.displayName ?? null,\n },\n updated_at: now,\n });\n }\n }\n\n private async writeUsers(\n storage: StorageHandle,\n users: JiraUser[],\n ): Promise<void> {\n const now = Date.now();\n for (const u of users) {\n if (!u.accountId) {\n continue;\n }\n await storage.entity({\n type: 'jira_user',\n id: u.accountId,\n attributes: {\n displayName: u.displayName ?? null,\n emailAddress: u.emailAddress ?? null,\n accountType: u.accountType ?? null,\n active: u.active ?? null,\n },\n updated_at: now,\n });\n }\n }\n\n private async writeSprints(\n storage: StorageHandle,\n sprints: JiraSprintWithBoard[],\n ): Promise<void> {\n const now = Date.now();\n for (const s of sprints) {\n const startMs = parseEpoch(s.startDate ?? null, 'iso');\n const endMs = parseEpoch(s.endDate ?? null, 'iso');\n const completeMs = parseEpoch(s.completeDate ?? null, 'iso');\n await storage.entity({\n type: 'jira_sprint',\n id: String(s.id),\n attributes: {\n name: s.name,\n state: s.state,\n boardId: s.boardId,\n originBoardId: s.originBoardId ?? null,\n startDate: startMs,\n endDate: endMs,\n completeDate: completeMs,\n },\n updated_at: completeMs ?? endMs ?? startMs ?? now,\n });\n }\n }\n\n private async writeIssues(\n storage: StorageHandle,\n issues: JiraIssue[],\n sinceMs: number | null,\n ): Promise<void> {\n const writeEntities = this.isResourceEnabled('issues');\n const writeEvents = this.isResourceEnabled('issue_events');\n\n for (const issue of issues) {\n const f = issue.fields;\n const createdMs = parseEpoch(f.created, 'iso');\n const updatedMs = parseEpoch(f.updated, 'iso');\n if (createdMs === null || updatedMs === null) {\n console.warn(\n `[connector-jira] skipping issue ${issue.key} with unparseable created/updated`,\n );\n continue;\n }\n const projectKey = f.project?.key ?? null;\n\n if (writeEntities) {\n const rawPoints = f[this.storyPointsField];\n const storyPoints =\n typeof rawPoints === 'number' && Number.isFinite(rawPoints)\n ? rawPoints\n : null;\n await storage.entity({\n type: 'jira_issue',\n id: issue.id,\n attributes: {\n key: issue.key,\n summary: f.summary ?? null,\n statusName: f.status?.name ?? null,\n statusCategory: f.status?.statusCategory?.key ?? null,\n priority: f.priority?.name ?? null,\n issueType: f.issuetype?.name ?? null,\n assigneeId: f.assignee?.accountId ?? null,\n reporterId: f.reporter?.accountId ?? null,\n projectKey,\n sprintId: extractSprintId(f[this.sprintField]),\n storyPoints,\n createdAt: createdMs,\n resolvedAt: parseEpoch(f.resolutiondate ?? null, 'iso'),\n },\n updated_at: updatedMs,\n });\n }\n\n if (writeEvents) {\n const histories = issue.changelog?.histories ?? [];\n for (const h of histories) {\n const ts = parseEpoch(h.created, 'iso');\n if (ts === null) {\n continue;\n }\n if (sinceMs !== null && ts <= sinceMs) {\n continue;\n }\n for (const item of h.items) {\n if (item.field !== 'status') {\n continue;\n }\n const attributes: Record<string, JSONValue> = {\n historyId: h.id,\n issueId: issue.id,\n issueKey: issue.key,\n projectKey,\n authorId: h.author?.accountId ?? null,\n fromStatus: item.fromString ?? null,\n toStatus: item.toString ?? null,\n };\n await storage.event({\n name: 'jira_issue_status_change',\n start_ts: ts,\n end_ts: null,\n attributes,\n });\n }\n }\n }\n }\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = isJiraSyncCursor(options.cursor)\n ? options.cursor\n : undefined;\n const isFull = options.mode === 'full';\n const sinceMs = options.since ? parseEpoch(options.since, 'iso') : null;\n const phases = this.activePhases();\n\n return paginateChunked<JiraPhase, string>({\n phases,\n cursor,\n signal,\n fetchPage: async (phase, page, sig) => {\n switch (phase) {\n case 'projects':\n return this.fetchProjectsPage(page, sig);\n case 'users':\n return this.fetchUsersPage(page, sig);\n case 'sprints':\n return this.fetchSprintsPage(page, sig);\n case 'issues':\n return this.fetchIssuesPage(page, options, sig);\n }\n },\n writeBatch: async (phase, items, page) => {\n if (isFull && page === null) {\n switch (phase) {\n case 'projects':\n await storage.entities([], { types: ['jira_project'] });\n break;\n case 'users':\n await storage.entities([], { types: ['jira_user'] });\n break;\n case 'sprints':\n await storage.entities([], { types: ['jira_sprint'] });\n break;\n case 'issues':\n if (this.isResourceEnabled('issues')) {\n await storage.entities([], { types: ['jira_issue'] });\n }\n if (this.isResourceEnabled('issue_events')) {\n await storage.events([], {\n names: ['jira_issue_status_change'],\n });\n }\n break;\n }\n }\n switch (phase) {\n case 'projects':\n return this.writeProjects(storage, items as JiraProject[]);\n case 'users':\n return this.writeUsers(storage, items as JiraUser[]);\n case 'sprints':\n return this.writeSprints(storage, items as JiraSprintWithBoard[]);\n case 'issues':\n return this.writeIssues(storage, items as JiraIssue[], sinceMs);\n }\n },\n });\n }\n}\n","import { JiraConnector } from './jira';\n\nexport {\n configFields,\n doc,\n JiraConnector,\n jiraResources as resources,\n id,\n} from './jira';\nexport type { JiraSettings, JiraResource } from './jira';\nexport default JiraConnector;\n"],"mappings":"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","names":["id"]}
|
|
1
|
+
{"version":3,"sources":["../../../connector-shared/src/errors.ts","../../../connector-shared/src/retry.ts","../../../connector-shared/src/version.ts","../../../connector-shared/src/request.ts","../../../connector-shared/src/rate-limit.ts","../../../connector-shared/src/map-concurrent.ts","../../../connector-shared/src/sanitize.ts","../../../connector-shared/src/epoch.ts","../../../connector-shared/src/pagination.ts","../../../connector-shared/src/logger.ts","../src/jira.ts","../src/index.ts"],"sourcesContent":["import type { HttpResponse } from './types';\n\nexport type HttpErrorKind =\n | 'transient'\n | 'rate_limit'\n | 'auth'\n | 'upstream_bug'\n | 'client_bug';\n\nexport abstract class HttpClientError extends Error {\n abstract readonly kind: HttpErrorKind;\n readonly response?: HttpResponse;\n\n constructor(message: string, response?: HttpResponse) {\n super(message);\n this.name = new.target.name;\n this.response = response;\n }\n}\n\nexport class TransientError extends HttpClientError {\n readonly kind = 'transient' as const;\n}\n\nexport class RateLimitError extends HttpClientError {\n readonly kind = 'rate_limit' as const;\n readonly retryAfter?: Date;\n\n constructor(message: string, response?: HttpResponse, retryAfter?: Date) {\n super(message, response);\n this.retryAfter = retryAfter;\n }\n}\n\nexport class AuthError extends HttpClientError {\n readonly kind = 'auth' as const;\n}\n\nexport class UpstreamBugError extends HttpClientError {\n readonly kind = 'upstream_bug' as const;\n}\n\nexport class ClientBugError extends HttpClientError {\n readonly kind = 'client_bug' as const;\n}\n\nexport function classifyStatus(status: number): HttpErrorKind {\n if (status === 429) {\n return 'rate_limit';\n }\n if (status === 401 || status === 403) {\n return 'auth';\n }\n if (status === 408) {\n return 'transient';\n }\n if (status >= 500) {\n return 'upstream_bug';\n }\n if (status >= 400) {\n return 'client_bug';\n }\n return 'client_bug';\n}\n\nexport function errorForStatus(\n message: string,\n response: HttpResponse,\n retryAfter?: Date,\n): HttpClientError {\n const kind = classifyStatus(response.status);\n switch (kind) {\n case 'rate_limit':\n return new RateLimitError(message, response, retryAfter);\n case 'auth':\n return new AuthError(message, response);\n case 'transient':\n return new TransientError(message, response);\n case 'upstream_bug':\n return new UpstreamBugError(message, response);\n case 'client_bug':\n return new ClientBugError(message, response);\n }\n}\n","import { HttpClientError, RateLimitError, TransientError } from './errors';\n\nexport interface RetryPolicy {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (status: number | null, err?: Error) => boolean;\n}\n\nexport const defaultRetryOn = (status: number | null, err?: Error): boolean => {\n if (err instanceof RateLimitError) {\n return true;\n }\n if (err instanceof TransientError) {\n return true;\n }\n if (status === null) {\n return err instanceof Error && !(err instanceof HttpClientError);\n }\n if (status === 408 || status === 429) {\n return true;\n }\n if (status >= 500) {\n return true;\n }\n return false;\n};\n\nexport function backoffDelayMs(\n attempt: number,\n policy: Required<Pick<RetryPolicy, 'initialDelayMs' | 'maxDelayMs'>>,\n): number {\n const base = policy.initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, policy.maxDelayMs);\n}\n\nexport function parseRetryAfter(\n headerValue: string | null,\n now: Date = new Date(),\n): Date | undefined {\n if (!headerValue) {\n return undefined;\n }\n const trimmed = headerValue.trim();\n if (/^\\d+$/.test(trimmed)) {\n return new Date(now.getTime() + Number(trimmed) * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n return new Date(parsed);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new Error('Aborted'));\n }\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error('Aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export const HTTP_CLIENT_VERSION = '0.0.0';\n\nexport const DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n\nexport function connectorUserAgent(connectorId: string): string {\n return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n}\n","import {\n AuthError,\n ClientBugError,\n HttpClientError,\n RateLimitError,\n TransientError,\n UpstreamBugError,\n errorForStatus,\n} from './errors';\nimport { defaultRetryOn, parseRetryAfter, sleep } from './retry';\nimport type { FetchLike, HttpMethod, HttpRequest, HttpResponse } from './types';\nimport { DEFAULT_USER_AGENT } from './version';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\nconst DEFAULT_MAX_DELAY_MS = 60_000;\nconst OBSERVER_TIMEOUT_MS = 250;\n\nexport interface RequestObservation {\n url: string;\n method: HttpMethod;\n status: number;\n resource: string;\n requestId: string;\n body: unknown;\n}\n\nexport type RequestObserver = (\n event: RequestObservation,\n) => void | Promise<void>;\n\nexport interface RequestOptions {\n fetch?: FetchLike;\n observer?: RequestObserver;\n resource: string;\n requestId?: string;\n}\n\nasync function notifyObserver(\n observer: RequestObserver,\n event: RequestObservation,\n): Promise<void> {\n let result: void | Promise<void>;\n try {\n result = observer(event);\n } catch (err) {\n console.warn('[connector-shared] request observer threw:', err);\n return;\n }\n if (!(result instanceof Promise)) {\n return;\n }\n const guarded = result.catch((err) => {\n console.warn('[connector-shared] request observer rejected:', err);\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, OBSERVER_TIMEOUT_MS);\n });\n try {\n await Promise.race([guarded, timeout]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n\nfunction newRequestId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction mergeHeaders(\n defaults: Record<string, string>,\n overrides: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const [k, v] of Object.entries(defaults)) {\n merged[k.toLowerCase()] = v;\n }\n if (overrides) {\n for (const [k, v] of Object.entries(overrides)) {\n merged[k.toLowerCase()] = v;\n }\n }\n return merged;\n}\n\nfunction linkTimeoutSignal(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): { signal: AbortSignal; cancel: () => void } {\n const controller = new AbortController();\n const onParentAbort = () => {\n controller.abort(parent?.reason);\n };\n if (parent) {\n if (parent.aborted) {\n controller.abort(parent.reason);\n } else {\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n const timer = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (parent) {\n parent.removeEventListener('abort', onParentAbort);\n }\n },\n };\n}\n\nasync function readBody(res: Response, parseJson: boolean): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n const contentType = res.headers.get('content-type') ?? '';\n if (parseJson && contentType.includes('application/json')) {\n const text = await res.text();\n if (text.length === 0) {\n return null;\n }\n return JSON.parse(text);\n }\n return res.text();\n}\n\nexport async function request<T = unknown>(\n req: HttpRequest,\n options: RequestOptions,\n): Promise<HttpResponse<T>> {\n const fetchImpl: FetchLike = options.fetch ?? (globalThis.fetch as FetchLike);\n const retry = req.retry ?? {};\n const maxAttempts = retry.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const initialDelayMs = retry.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;\n const maxDelayMs = retry.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const timeoutMs = req.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const parseJson = req.parseJson ?? true;\n\n const headers = mergeHeaders(\n {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'application/json',\n },\n req.headers,\n );\n\n let lastErr: Error | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n req.signal?.throwIfAborted();\n\n const { signal, cancel } = linkTimeoutSignal(req.signal, timeoutMs);\n let res: Response;\n try {\n res = await fetchImpl(req.url, {\n method: req.method ?? 'GET',\n headers,\n body: req.body as RequestInit['body'],\n signal,\n });\n } catch (err) {\n cancel();\n if (req.signal?.aborted) {\n throw req.signal.reason ?? err;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n lastErr = error;\n if (attempt < maxAttempts - 1 && retryOn(null, error)) {\n const delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n await sleep(delay, req.signal);\n continue;\n }\n throw new TransientError(error.message);\n }\n cancel();\n\n const body = await readBody(res, parseJson);\n const httpResponse: HttpResponse<T> = {\n status: res.status,\n headers: res.headers,\n body: body as T,\n };\n if (req.rateLimit) {\n const state = req.rateLimit.parse(res.headers);\n if (state) {\n httpResponse.rateLimitState = state;\n }\n }\n\n if (options.observer) {\n await notifyObserver(options.observer, {\n url: req.url,\n method: req.method ?? 'GET',\n status: res.status,\n resource: options.resource,\n requestId: options.requestId ?? newRequestId(),\n body,\n });\n }\n\n if (res.ok) {\n return httpResponse;\n }\n\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const message = `HTTP ${res.status} ${res.statusText} for ${req.method ?? 'GET'} ${req.url}`;\n const err = errorForStatus(message, httpResponse, retryAfter);\n\n if (\n attempt < maxAttempts - 1 &&\n retryOn(res.status, err) &&\n !(err instanceof AuthError) &&\n !(err instanceof ClientBugError)\n ) {\n lastErr = err;\n let delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n if (err instanceof RateLimitError && retryAfter) {\n const wait = retryAfter.getTime() - Date.now();\n if (wait > 0) {\n delay = Math.min(wait, maxDelayMs);\n }\n }\n await sleep(delay, req.signal);\n continue;\n }\n\n throw err;\n }\n\n throw lastErr ?? new UpstreamBugError('Exhausted retry attempts');\n}\n\nfunction computeDelay(\n attempt: number,\n initialDelayMs: number,\n maxDelayMs: number,\n): number {\n const base = initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, maxDelayMs);\n}\n\nexport { HttpClientError };\n","export interface RateLimitState {\n remaining: number;\n resetAt: Date;\n}\n\nexport interface RateLimitPolicy {\n parse(headers: Headers): RateLimitState | null;\n}\n\nexport interface StandardRateLimitPolicyConfig {\n remainingHeader: string;\n resetHeader: string;\n resetUnit: 's' | 'ms';\n resetFallbackMs?: number;\n}\n\nexport function standardRateLimitPolicy(\n config: StandardRateLimitPolicyConfig,\n): RateLimitPolicy {\n const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;\n const multiplier = resetUnit === 's' ? 1000 : 1;\n return {\n parse(h) {\n const remainingRaw = h.get(remainingHeader);\n if (remainingRaw === null || remainingRaw.trim() === '') {\n return null;\n }\n const remaining = Number(remainingRaw);\n if (!Number.isFinite(remaining)) {\n return null;\n }\n const resetRaw = h.get(resetHeader);\n if (resetRaw === null) {\n if (resetFallbackMs === undefined) {\n return null;\n }\n return {\n remaining,\n resetAt: new Date(Date.now() + resetFallbackMs),\n };\n }\n if (resetRaw.trim() === '') {\n return null;\n }\n const reset = Number(resetRaw);\n if (!Number.isFinite(reset) || reset < 0) {\n return null;\n }\n const resetMs = reset * multiplier;\n if (!Number.isFinite(resetMs)) {\n return null;\n }\n return { remaining, resetAt: new Date(resetMs) };\n },\n };\n}\n","export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n if (items.length === 0) {\n return results;\n }\n const normalized = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;\n const limit = Math.max(1, Math.min(normalized, items.length));\n let next = 0;\n let failed = false;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const i = next++;\n if (i >= items.length) {\n return;\n }\n try {\n results[i] = await fn(items[i]!, i);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n\n const workers: Promise<void>[] = [];\n for (let w = 0; w < limit; w++) {\n workers.push(worker());\n }\n await Promise.all(workers);\n return results;\n}\n","export interface SanitizeAllowedUrlOptions {\n url: string | null;\n host: string;\n pathname: string;\n protocol?: 'https:' | 'http:';\n}\n\nexport function sanitizeAllowedUrl(\n options: SanitizeAllowedUrlOptions,\n): string | null {\n const { url, host, pathname, protocol = 'https:' } = options;\n if (url === null) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== protocol || u.host !== host || u.pathname !== pathname) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n}\n","export type EpochUnit = 'ms' | 's' | 'iso';\n\nexport function parseEpoch(\n value: number | string | null | undefined,\n unit: EpochUnit,\n): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (unit === 'iso') {\n if (typeof value !== 'string') {\n return null;\n }\n const ms = new Date(value).getTime();\n return Number.isFinite(ms) ? ms : null;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return null;\n }\n const n = typeof value === 'number' ? value : Number(value);\n if (!Number.isFinite(n)) {\n return null;\n }\n const result = unit === 's' ? n * 1000 : n;\n return Number.isFinite(result) ? result : null;\n}\n","import { request } from './request';\nimport type { HttpRequest } from './types';\n\nexport function parseLinkHeader(header: string | null): Record<string, string> {\n if (!header) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const part of header.split(',')) {\n const match = part.match(/<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"/);\n if (match) {\n result[match[2]!] = match[1]!;\n }\n }\n return result;\n}\n\nexport async function* paginateLink<T>(\n initial: HttpRequest,\n parse: (body: unknown) => T[],\n options: { resource: string },\n): AsyncIterable<T> {\n let next: string | null = initial.url;\n while (next) {\n const res: Awaited<ReturnType<typeof request>> = await request(\n {\n ...initial,\n url: next,\n },\n { resource: options.resource },\n );\n for (const item of parse(res.body)) {\n yield item;\n }\n const links = parseLinkHeader(res.headers.get('link'));\n next = links['next'] ?? null;\n }\n}\n\nexport async function* paginateCursor<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; nextCursor: string | null },\n buildNext: (req: HttpRequest, cursor: string) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let req: HttpRequest = initial;\n while (true) {\n const res = await request(req, { resource: options.resource });\n const { items, nextCursor } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!nextCursor) {\n return;\n }\n req = buildNext(req, nextCursor);\n }\n}\n\nexport async function* paginatePage<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; hasMore: boolean },\n buildPage: (req: HttpRequest, page: number) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let page = 1;\n while (true) {\n const req = page === 1 ? initial : buildPage(initial, page);\n const res = await request(req, { resource: options.resource });\n const { items, hasMore } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!hasMore || items.length === 0) {\n return;\n }\n page++;\n }\n}\n","export type LogFields = Record<string, unknown>;\n\nexport interface ConnectorLogger {\n info(event: string, fields?: LogFields): void;\n warn(event: string, fields?: LogFields): void;\n}\n\nexport interface ConnectorLoggerOptions {\n scope: string;\n}\n\nconst MAX_VALUE_LEN = 120;\n\nfunction truncate(s: string, max = MAX_VALUE_LEN): string {\n if (s.length <= max) {\n return s;\n }\n return `${s.slice(0, max - 1)}…`;\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (value === undefined) {\n return '';\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'string') {\n const t = truncate(value);\n if (/[\\s\"=]/.test(t)) {\n return JSON.stringify(t);\n }\n return t;\n }\n if (typeof value === 'bigint') {\n return value.toString();\n }\n let json: string | undefined;\n try {\n json = JSON.stringify(value);\n } catch {\n json = undefined;\n }\n return truncate(json ?? String(value));\n}\n\nexport function formatLogFields(fields?: LogFields): string {\n if (!fields) {\n return '';\n }\n const parts: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined) {\n continue;\n }\n parts.push(`${k}=${formatValue(v)}`);\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nexport function formatLogLine(\n scope: string,\n event: string,\n fields?: LogFields,\n): string {\n return `[${scope}] ${event}${formatLogFields(fields)}`;\n}\n\nexport function createDefaultConnectorLogger(\n opts: ConnectorLoggerOptions,\n): ConnectorLogger {\n return {\n info(event, fields) {\n console.info(formatLogLine(opts.scope, event, fields));\n },\n warn(event, fields) {\n console.warn(formatLogLine(opts.scope, event, fields));\n },\n };\n}\n\nconst NOOP_LOGGER: ConnectorLogger = {\n info() {},\n warn() {},\n};\n\nexport function noopConnectorLogger(): ConnectorLogger {\n return NOOP_LOGGER;\n}\n","import {\n type HttpResponse,\n connectorUserAgent,\n parseEpoch,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type FetchSpec,\n type FilterClause,\n type JSONValue,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nexport const configFields = defineConfigFields(\n z.object({\n email: z.string().min(1).meta({\n label: 'Account email',\n description:\n 'Atlassian account email paired with the API token for Basic auth.',\n placeholder: 'you@yourorg.com',\n }),\n apiToken: z.object({ $secret: z.string() }).meta({\n label: 'API Token',\n description:\n 'Atlassian API token. Create one at id.atlassian.com → Security → API tokens.',\n placeholder: 'ATATT...',\n secret: true,\n }),\n host: z\n .string()\n .min(1)\n .regex(\n /^[^/\\s:?#]+$/,\n 'Use host only (no protocol, port, path, or query).',\n )\n .meta({\n label: 'Site host',\n description:\n 'Your Jira Cloud host, e.g. yourorg.atlassian.net (no protocol, no trailing slash).',\n placeholder: 'yourorg.atlassian.net',\n }),\n projectKeys: z.array(z.string().min(1)).nonempty().optional().meta({\n label: 'Project keys (optional)',\n description:\n 'Restrict the sync to specific Jira project keys (e.g. ENG, OPS). Omit to sync every project the account can see.',\n }),\n resources: z\n .array(z.enum(['projects', 'users', 'sprints', 'issues', 'issue_events']))\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n \"Which Jira resources to sync. Omit to sync all of them. 'issue_events' shares the issues query - enabling it without 'issues' still fetches issues (with changelog) but skips writing issue entities.\",\n }),\n storyPointsField: z.string().min(1).optional().meta({\n label: 'Story points field ID',\n description:\n 'Custom field ID holding story points (varies per site). Defaults to customfield_10016.',\n placeholder: 'customfield_10016',\n }),\n sprintField: z.string().min(1).optional().meta({\n label: 'Sprint field ID',\n description:\n 'Custom field ID holding the sprint association on issues. Defaults to customfield_10020.',\n placeholder: 'customfield_10020',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Jira',\n category: 'product',\n brandColor: '#0052CC',\n tagline:\n 'Sync projects, users, sprints, issues, and issue status-change events from a Jira Cloud site.',\n vendor: {\n name: 'Atlassian',\n domain: 'atlassian.com',\n apiDocs: 'https://developer.atlassian.com/cloud/jira/platform/rest/v3/',\n website: 'https://www.atlassian.com/software/jira',\n },\n auth: {\n summary:\n 'Authenticates over HTTP Basic auth using your Atlassian account email and an API token. The token must belong to an account with access to the projects you want to sync.',\n setup: [\n 'Open id.atlassian.com -> Security -> Create and manage API tokens.',\n 'Create an API token and copy its value.',\n 'Store the token as a secret and reference it from the connector config as `apiToken: secret(\"JIRA_API_TOKEN\")`, alongside your account email and site host (e.g. yourorg.atlassian.net).',\n 'Story points and the sprint association live on custom fields whose IDs differ per Jira site. Discover them at `https://{host}/rest/api/3/field` and set storyPointsField / sprintField to match.',\n ],\n },\n rateLimit:\n 'Jira Cloud uses cost-based rate limiting; 429 responses with Retry-After are honored.',\n limitations: [\n 'Sprints are only synced from scrum boards; kanban boards are skipped.',\n 'Issue status-change events are derived from each issue changelog; only `status` field transitions are written.',\n 'Targets Jira Cloud REST API v3 and the Agile API; Jira Data Center / Server are out of scope.',\n ],\n});\n\nexport type JiraResource =\n | 'projects'\n | 'users'\n | 'sprints'\n | 'issues'\n | 'issue_events';\n\nexport interface JiraSettings {\n host: string;\n projectKeys?: readonly string[];\n resources?: readonly JiraResource[];\n storyPointsField?: string;\n sprintField?: string;\n}\n\nconst jiraCredentials = {\n email: {\n description: 'Atlassian account email',\n auth: 'required' as const,\n },\n apiToken: {\n description: 'Atlassian API token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype JiraCredentials = typeof jiraCredentials;\n\nconst PHASE_ORDER = ['projects', 'users', 'sprints', 'issues'] as const;\n\ntype JiraPhase = (typeof PHASE_ORDER)[number];\n\nconst isJiraSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\ninterface JiraAccountRef {\n accountId: string;\n displayName?: string | null;\n}\n\ninterface JiraStatusCategory {\n key: string;\n name?: string | null;\n}\n\ninterface JiraStatus {\n name: string;\n statusCategory?: JiraStatusCategory | null;\n}\n\ninterface JiraNamed {\n name: string;\n}\n\ninterface JiraProjectRef {\n id: string;\n key: string;\n}\n\ninterface JiraIssueFields {\n summary?: string | null;\n status?: JiraStatus | null;\n priority?: JiraNamed | null;\n issuetype?: JiraNamed | null;\n assignee?: JiraAccountRef | null;\n reporter?: JiraAccountRef | null;\n project?: JiraProjectRef | null;\n created: string;\n updated: string;\n resolutiondate?: string | null;\n [key: string]: unknown;\n}\n\ninterface JiraChangelogItem {\n field: string;\n fromString?: string | null;\n toString?: string | null;\n}\n\ninterface JiraChangelogHistory {\n id: string;\n created: string;\n author?: JiraAccountRef | null;\n items: JiraChangelogItem[];\n}\n\ninterface JiraIssue {\n id: string;\n key: string;\n fields: JiraIssueFields;\n changelog?: { histories: JiraChangelogHistory[] } | null;\n}\n\ninterface JiraSearchResponse {\n issues: JiraIssue[];\n nextPageToken?: string | null;\n isLast?: boolean;\n}\n\ninterface JiraProject {\n id: string;\n key: string;\n name: string;\n projectTypeKey?: string | null;\n lead?: JiraAccountRef | null;\n}\n\ninterface JiraProjectPage {\n values: JiraProject[];\n isLast: boolean;\n startAt: number;\n maxResults: number;\n total: number;\n}\n\ninterface JiraUser {\n accountId: string;\n displayName?: string | null;\n emailAddress?: string | null;\n accountType?: string | null;\n active?: boolean | null;\n}\n\ninterface JiraSprint {\n id: number;\n name: string;\n state: 'active' | 'closed' | 'future';\n startDate?: string | null;\n endDate?: string | null;\n completeDate?: string | null;\n originBoardId?: number | null;\n}\n\ninterface JiraSprintWithBoard extends JiraSprint {\n boardId: number;\n}\n\ninterface JiraBoard {\n id: number;\n name: string;\n type: string;\n}\n\ninterface JiraAgilePage<T> {\n values: T[];\n isLast?: boolean;\n startAt: number;\n maxResults: number;\n}\n\nconst idString = z.string().min(1);\nconst nonNegInt = z.number().int().nonnegative();\n\nconst accountRefSchema = z.object({\n accountId: idString,\n displayName: z.string().nullable().optional(),\n});\n\nconst projectSchema = z.object({\n id: idString,\n key: z.string().min(1),\n name: z.string(),\n projectTypeKey: z.string().nullable().optional(),\n lead: accountRefSchema.nullable().optional(),\n});\n\nconst projectsResponseSchema = z.object({\n values: z.array(projectSchema),\n isLast: z.boolean(),\n startAt: nonNegInt,\n maxResults: nonNegInt,\n total: nonNegInt,\n});\n\nconst usersResponseSchema = z.array(\n z.object({\n accountId: idString,\n displayName: z.string().nullable().optional(),\n emailAddress: z.string().nullable().optional(),\n accountType: z.string().nullable().optional(),\n active: z.boolean().nullable().optional(),\n }),\n);\n\nconst sprintsResponseSchema = z.array(\n z.object({\n id: nonNegInt,\n name: z.string(),\n state: z.enum(['active', 'closed', 'future']),\n startDate: z.iso.datetime().nullable().optional(),\n endDate: z.iso.datetime().nullable().optional(),\n completeDate: z.iso.datetime().nullable().optional(),\n originBoardId: nonNegInt.nullable().optional(),\n }),\n);\n\nconst changelogHistorySchema = z.object({\n id: idString,\n created: z.iso.datetime(),\n author: accountRefSchema.nullable().optional(),\n items: z.array(\n z.object({\n field: z.string(),\n fromString: z.string().nullable().optional(),\n toString: z.string().nullable().optional(),\n }),\n ),\n});\n\nconst issueSchema = z.object({\n id: idString,\n key: z.string().min(1),\n fields: z.object({\n summary: z.string().nullable().optional(),\n status: z\n .object({\n name: z.string(),\n statusCategory: z\n .object({ key: z.string(), name: z.string().nullable().optional() })\n .nullable()\n .optional(),\n })\n .nullable()\n .optional(),\n priority: z.object({ name: z.string() }).nullable().optional(),\n issuetype: z.object({ name: z.string() }).nullable().optional(),\n assignee: accountRefSchema.nullable().optional(),\n reporter: accountRefSchema.nullable().optional(),\n project: z.object({ id: idString, key: z.string().min(1) }).nullable(),\n created: z.iso.datetime(),\n updated: z.iso.datetime(),\n resolutiondate: z.iso.datetime().nullable().optional(),\n customfield_10016: z.number().nullable().optional(),\n customfield_10020: z\n .array(z.object({ id: nonNegInt, name: z.string() }))\n .nullable()\n .optional(),\n }),\n changelog: z.object({ histories: z.array(changelogHistorySchema) }),\n});\n\nconst issuesResponseSchema = z.object({\n issues: z.array(issueSchema),\n nextPageToken: z.string().nullable().optional(),\n isLast: z.boolean().optional(),\n});\n\nexport const jiraResources = defineResources({\n jira_project: {\n shape: 'entity',\n filterable: [],\n description:\n 'Jira projects with key, name, type, and project lead. Restrict via projectKeys to limit the sync.',\n endpoint: 'GET /rest/api/3/project/search',\n responses: { projects: projectsResponseSchema },\n },\n jira_user: {\n shape: 'entity',\n filterable: [],\n description:\n 'Atlassian accounts visible to the connector, including display name, email, account type, and active state.',\n endpoint: 'GET /rest/api/3/users/search',\n responses: { users: usersResponseSchema },\n },\n jira_sprint: {\n shape: 'entity',\n filterable: [\n { field: 'state', ops: ['eq'], values: ['future', 'active', 'closed'] },\n ],\n description:\n 'Sprints from scrum boards with state, start/end/complete dates, and owning board.',\n endpoint: 'GET /rest/agile/1.0/board/{boardId}/sprint',\n responses: { sprints: sprintsResponseSchema },\n },\n jira_issue: {\n shape: 'entity',\n filterable: [\n { field: 'statusName', ops: ['eq'] },\n { field: 'priority', ops: ['eq'] },\n { field: 'issueType', ops: ['eq'] },\n ],\n description:\n 'Issues with status, priority, type, assignee, reporter, project, sprint, story points, and resolution date.',\n endpoint: 'GET /rest/api/3/search/jql',\n notes:\n \"sprintId is taken from the most recent sprint on the issue's sprint custom field.\",\n responses: { issues: issuesResponseSchema },\n },\n jira_issue_status_change: {\n shape: 'event',\n filterable: [],\n description:\n 'Status transition events derived from issue changelogs, capturing the from/to status, author, and project.',\n endpoint: 'GET /rest/api/3/search/jql (expand=changelog)',\n notes:\n 'start_ts is the changelog entry time, end_ts is null. Timestamps are Unix epoch milliseconds.',\n },\n});\n\nconst PROJECTS_PAGE_SIZE = 50;\nconst USERS_PAGE_SIZE = 50;\nconst BOARDS_PAGE_SIZE = 50;\nconst SPRINTS_PAGE_SIZE = 50;\nconst ISSUES_PAGE_SIZE = 100;\nconst DEFAULT_STORY_POINTS_FIELD = 'customfield_10016';\nconst DEFAULT_SPRINT_FIELD = 'customfield_10020';\n\nconst ISSUE_FIELDS = [\n 'summary',\n 'status',\n 'priority',\n 'issuetype',\n 'assignee',\n 'reporter',\n 'project',\n 'created',\n 'updated',\n 'resolutiondate',\n] as const;\n\nfunction parseOffset(page: string | null): number {\n if (page === null) {\n return 0;\n }\n const n = Number.parseInt(page, 10);\n return Number.isFinite(n) && n >= 0 ? n : 0;\n}\n\nfunction extractSprintId(value: unknown): string | null {\n if (!Array.isArray(value) || value.length === 0) {\n return null;\n }\n const last = value[value.length - 1];\n if (last !== null && typeof last === 'object' && 'id' in last) {\n const id = (last as { id: unknown }).id;\n return id === null || id === undefined ? null : String(id);\n }\n if (typeof last === 'number' || typeof last === 'string') {\n return String(last);\n }\n return null;\n}\n\nfunction formatJqlDate(iso: string): string | null {\n const ms = parseEpoch(iso, 'iso');\n if (ms === null) {\n return null;\n }\n const d = new Date(ms);\n const pad = (n: number) => String(n).padStart(2, '0');\n return (\n `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ` +\n `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`\n );\n}\n\nfunction jqlQuote(value: string): string {\n return `\"${value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`;\n}\n\nfunction pushableEq(\n filter: FilterClause[] | undefined,\n field: string,\n): string | null {\n if (!filter) {\n return null;\n }\n for (const clause of filter) {\n if (\n 'field' in clause &&\n clause.field === field &&\n clause.op === 'eq' &&\n typeof clause.value === 'string'\n ) {\n return clause.value;\n }\n }\n return null;\n}\n\nexport const id = 'jira';\n\nexport class JiraConnector extends BaseConnector<\n JiraSettings,\n JiraCredentials\n> {\n static readonly id = id;\n\n static readonly resources = jiraResources;\n\n static readonly schemas = schemasFromResources(jiraResources);\n\n static create(input: unknown, ctx?: ConnectorContext): JiraConnector {\n const parsed = configFields.parse(input);\n return new JiraConnector(\n {\n host: parsed.host,\n projectKeys: parsed.projectKeys,\n resources: parsed.resources,\n storyPointsField: parsed.storyPointsField,\n sprintField: parsed.sprintField,\n },\n { email: parsed.email, apiToken: parsed.apiToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = jiraCredentials;\n\n private get baseUrl(): string {\n const host = this.settings.host\n .replace(/^https?:\\/\\//, '')\n .replace(/\\/+$/, '');\n return `https://${host}`;\n }\n\n private get storyPointsField(): string {\n return this.settings.storyPointsField ?? DEFAULT_STORY_POINTS_FIELD;\n }\n\n private get sprintField(): string {\n return this.settings.sprintField ?? DEFAULT_SPRINT_FIELD;\n }\n\n private buildHeaders(): Record<string, string> {\n const basic = btoa(`${this.creds.email}:${this.creds.apiToken}`);\n return {\n Authorization: `Basic ${basic}`,\n Accept: 'application/json',\n 'User-Agent': connectorUserAgent('jira'),\n };\n }\n\n private fetch<T>(\n url: string,\n resource: string,\n signal?: AbortSignal,\n ): Promise<HttpResponse<T>> {\n return this.get<T>(url, {\n resource,\n headers: this.buildHeaders(),\n signal,\n });\n }\n\n private activePhases(): JiraPhase[] {\n return selectActivePhases<JiraResource, JiraPhase>(\n (r) => {\n switch (r) {\n case 'projects':\n return 'projects';\n case 'users':\n return 'users';\n case 'sprints':\n return 'sprints';\n case 'issues':\n case 'issue_events':\n return 'issues';\n }\n },\n PHASE_ORDER,\n this.settings.resources,\n );\n }\n\n private singleSpec(\n options: SyncOptions,\n resource: string,\n ): FetchSpec | undefined {\n const specs = options.fetchSpecs?.[resource];\n return specs && specs.length === 1 ? specs[0] : undefined;\n }\n\n private buildJql(options: SyncOptions): string {\n const clauses: string[] = [];\n const keys = this.settings.projectKeys;\n if (keys && keys.length > 0) {\n const quoted = keys.map((k) => jqlQuote(k));\n clauses.push(`project in (${quoted.join(',')})`);\n }\n if (options.mode === 'latest' && options.since) {\n const formatted = formatJqlDate(options.since);\n if (formatted !== null) {\n clauses.push(`updated >= \"${formatted}\"`);\n }\n }\n const filter = this.singleSpec(options, 'jira_issue')?.filter;\n const status = pushableEq(filter, 'statusName');\n if (status !== null) {\n clauses.push(`status = ${jqlQuote(status)}`);\n }\n const priority = pushableEq(filter, 'priority');\n if (priority !== null) {\n clauses.push(`priority = ${jqlQuote(priority)}`);\n }\n const issueType = pushableEq(filter, 'issueType');\n if (issueType !== null) {\n clauses.push(`issuetype = ${jqlQuote(issueType)}`);\n }\n const where = clauses.join(' AND ');\n return where.length > 0\n ? `${where} ORDER BY updated ASC`\n : 'ORDER BY updated ASC';\n }\n\n private async fetchProjectsPage(\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: JiraProject[]; next: string | null }> {\n const startAt = parseOffset(page);\n const u = new URL(`${this.baseUrl}/rest/api/3/project/search`);\n u.searchParams.set('startAt', String(startAt));\n u.searchParams.set('maxResults', String(PROJECTS_PAGE_SIZE));\n u.searchParams.set('expand', 'lead');\n const res = await this.fetch<JiraProjectPage>(\n u.toString(),\n 'projects',\n signal,\n );\n const values = res.body.values;\n const next =\n res.body.isLast || values.length === 0\n ? null\n : String(startAt + values.length);\n return { items: values, next };\n }\n\n private async fetchUsersPage(\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: JiraUser[]; next: string | null }> {\n const startAt = parseOffset(page);\n const u = new URL(`${this.baseUrl}/rest/api/3/users/search`);\n u.searchParams.set('startAt', String(startAt));\n u.searchParams.set('maxResults', String(USERS_PAGE_SIZE));\n const res = await this.fetch<JiraUser[]>(u.toString(), 'users', signal);\n const users = res.body;\n const next =\n users.length < USERS_PAGE_SIZE ? null : String(startAt + users.length);\n return { items: users, next };\n }\n\n private async fetchBoardsPage(\n startAt: number,\n signal: AbortSignal | undefined,\n ): Promise<JiraAgilePage<JiraBoard>> {\n const u = new URL(`${this.baseUrl}/rest/agile/1.0/board`);\n u.searchParams.set('startAt', String(startAt));\n u.searchParams.set('maxResults', String(BOARDS_PAGE_SIZE));\n const res = await this.fetch<JiraAgilePage<JiraBoard>>(\n u.toString(),\n 'sprints',\n signal,\n );\n return res.body;\n }\n\n private async fetchSprintsForBoard(\n boardId: number,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<JiraSprint[]> {\n const state = pushableEq(\n this.singleSpec(options, 'jira_sprint')?.filter,\n 'state',\n );\n const out: JiraSprint[] = [];\n let startAt = 0;\n while (true) {\n signal?.throwIfAborted();\n const u = new URL(\n `${this.baseUrl}/rest/agile/1.0/board/${boardId}/sprint`,\n );\n u.searchParams.set('startAt', String(startAt));\n u.searchParams.set('maxResults', String(SPRINTS_PAGE_SIZE));\n if (state !== null) {\n u.searchParams.set('state', state);\n }\n const res = await this.fetch<JiraAgilePage<JiraSprint>>(\n u.toString(),\n 'sprints',\n signal,\n );\n const values = res.body.values;\n out.push(...values);\n const isLast = res.body.isLast ?? values.length < SPRINTS_PAGE_SIZE;\n if (isLast || values.length === 0) {\n break;\n }\n startAt += values.length;\n }\n return out;\n }\n\n private async fetchSprintsPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: JiraSprintWithBoard[]; next: string | null }> {\n const startAt = parseOffset(page);\n const boardsPage = await this.fetchBoardsPage(startAt, signal);\n const boards = boardsPage.values;\n const sprints: JiraSprintWithBoard[] = [];\n for (const board of boards) {\n if (board.type !== 'scrum') {\n continue;\n }\n const boardSprints = await this.fetchSprintsForBoard(\n board.id,\n options,\n signal,\n );\n for (const s of boardSprints) {\n sprints.push({ ...s, boardId: board.id });\n }\n }\n const isLast = boardsPage.isLast ?? boards.length < BOARDS_PAGE_SIZE;\n const next = isLast ? null : String(startAt + boards.length);\n return { items: sprints, next };\n }\n\n private async fetchIssuesPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: JiraIssue[]; next: string | null }> {\n const u = new URL(`${this.baseUrl}/rest/api/3/search/jql`);\n u.searchParams.set('jql', this.buildJql(options));\n u.searchParams.set('maxResults', String(ISSUES_PAGE_SIZE));\n u.searchParams.set(\n 'fields',\n [...ISSUE_FIELDS, this.storyPointsField, this.sprintField].join(','),\n );\n u.searchParams.set('expand', 'changelog');\n if (page !== null) {\n u.searchParams.set('nextPageToken', page);\n }\n const res = await this.fetch<JiraSearchResponse>(\n u.toString(),\n 'issues',\n signal,\n );\n const token = res.body.nextPageToken ?? null;\n const next = res.body.isLast === true || token === null ? null : token;\n return { items: res.body.issues, next };\n }\n\n private async writeProjects(\n storage: StorageHandle,\n projects: JiraProject[],\n ): Promise<void> {\n const now = Date.now();\n for (const p of projects) {\n await storage.entity({\n type: 'jira_project',\n id: p.id,\n attributes: {\n key: p.key,\n name: p.name,\n projectTypeKey: p.projectTypeKey ?? null,\n leadAccountId: p.lead?.accountId ?? null,\n leadDisplayName: p.lead?.displayName ?? null,\n },\n updated_at: now,\n });\n }\n }\n\n private async writeUsers(\n storage: StorageHandle,\n users: JiraUser[],\n ): Promise<void> {\n const now = Date.now();\n for (const u of users) {\n if (!u.accountId) {\n continue;\n }\n await storage.entity({\n type: 'jira_user',\n id: u.accountId,\n attributes: {\n displayName: u.displayName ?? null,\n emailAddress: u.emailAddress ?? null,\n accountType: u.accountType ?? null,\n active: u.active ?? null,\n },\n updated_at: now,\n });\n }\n }\n\n private async writeSprints(\n storage: StorageHandle,\n sprints: JiraSprintWithBoard[],\n ): Promise<void> {\n const now = Date.now();\n for (const s of sprints) {\n const startMs = parseEpoch(s.startDate ?? null, 'iso');\n const endMs = parseEpoch(s.endDate ?? null, 'iso');\n const completeMs = parseEpoch(s.completeDate ?? null, 'iso');\n await storage.entity({\n type: 'jira_sprint',\n id: String(s.id),\n attributes: {\n name: s.name,\n state: s.state,\n boardId: s.boardId,\n originBoardId: s.originBoardId ?? null,\n startDate: startMs,\n endDate: endMs,\n completeDate: completeMs,\n },\n updated_at: completeMs ?? endMs ?? startMs ?? now,\n });\n }\n }\n\n private async writeIssues(\n storage: StorageHandle,\n issues: JiraIssue[],\n sinceMs: number | null,\n ): Promise<void> {\n const writeEntities = this.isResourceEnabled('issues');\n const writeEvents = this.isResourceEnabled('issue_events');\n\n for (const issue of issues) {\n const f = issue.fields;\n const createdMs = parseEpoch(f.created, 'iso');\n const updatedMs = parseEpoch(f.updated, 'iso');\n if (createdMs === null || updatedMs === null) {\n console.warn(\n `[connector-jira] skipping issue ${issue.key} with unparseable created/updated`,\n );\n continue;\n }\n const projectKey = f.project?.key ?? null;\n\n if (writeEntities) {\n const rawPoints = f[this.storyPointsField];\n const storyPoints =\n typeof rawPoints === 'number' && Number.isFinite(rawPoints)\n ? rawPoints\n : null;\n await storage.entity({\n type: 'jira_issue',\n id: issue.id,\n attributes: {\n key: issue.key,\n summary: f.summary ?? null,\n statusName: f.status?.name ?? null,\n statusCategory: f.status?.statusCategory?.key ?? null,\n priority: f.priority?.name ?? null,\n issueType: f.issuetype?.name ?? null,\n assigneeId: f.assignee?.accountId ?? null,\n reporterId: f.reporter?.accountId ?? null,\n projectKey,\n sprintId: extractSprintId(f[this.sprintField]),\n storyPoints,\n createdAt: createdMs,\n resolvedAt: parseEpoch(f.resolutiondate ?? null, 'iso'),\n },\n updated_at: updatedMs,\n });\n }\n\n if (writeEvents) {\n const histories = issue.changelog?.histories ?? [];\n for (const h of histories) {\n const ts = parseEpoch(h.created, 'iso');\n if (ts === null) {\n continue;\n }\n if (sinceMs !== null && ts <= sinceMs) {\n continue;\n }\n for (const item of h.items) {\n if (item.field !== 'status') {\n continue;\n }\n const attributes: Record<string, JSONValue> = {\n historyId: h.id,\n issueId: issue.id,\n issueKey: issue.key,\n projectKey,\n authorId: h.author?.accountId ?? null,\n fromStatus: item.fromString ?? null,\n toStatus: item.toString ?? null,\n };\n await storage.event({\n name: 'jira_issue_status_change',\n start_ts: ts,\n end_ts: null,\n attributes,\n });\n }\n }\n }\n }\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = isJiraSyncCursor(options.cursor)\n ? options.cursor\n : undefined;\n const isFull = options.mode === 'full';\n const sinceMs = options.since ? parseEpoch(options.since, 'iso') : null;\n const phases = this.activePhases();\n\n return paginateChunked<JiraPhase, string>({\n phases,\n cursor,\n signal,\n fetchPage: async (phase, page, sig) => {\n switch (phase) {\n case 'projects':\n return this.fetchProjectsPage(page, sig);\n case 'users':\n return this.fetchUsersPage(page, sig);\n case 'sprints':\n return this.fetchSprintsPage(page, options, sig);\n case 'issues':\n return this.fetchIssuesPage(page, options, sig);\n }\n },\n writeBatch: async (phase, items, page) => {\n if (isFull && page === null) {\n switch (phase) {\n case 'projects':\n await storage.entities([], { types: ['jira_project'] });\n break;\n case 'users':\n await storage.entities([], { types: ['jira_user'] });\n break;\n case 'sprints':\n await storage.entities([], { types: ['jira_sprint'] });\n break;\n case 'issues':\n if (this.isResourceEnabled('issues')) {\n await storage.entities([], { types: ['jira_issue'] });\n }\n if (this.isResourceEnabled('issue_events')) {\n await storage.events([], {\n names: ['jira_issue_status_change'],\n });\n }\n break;\n }\n }\n switch (phase) {\n case 'projects':\n return this.writeProjects(storage, items as JiraProject[]);\n case 'users':\n return this.writeUsers(storage, items as JiraUser[]);\n case 'sprints':\n return this.writeSprints(storage, items as JiraSprintWithBoard[]);\n case 'issues':\n return this.writeIssues(storage, items as JiraIssue[], sinceMs);\n }\n },\n });\n }\n}\n","import { JiraConnector } from './jira';\n\nexport {\n configFields,\n doc,\n JiraConnector,\n jiraResources as resources,\n id,\n} from './jira';\nexport type { JiraSettings, JiraResource } from './jira';\nexport default JiraConnector;\n"],"mappings":"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","names":["id"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rawdash/connector-jira",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Rawdash connector for Jira Cloud — issues, status-change events, sprints, projects, and users",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"zod": "^4.4.3",
|
|
27
|
-
"@rawdash/core": "0.
|
|
27
|
+
"@rawdash/core": "0.24.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"fast-check": "^4.8.0",
|