@rawdash/connector-jira 0.22.0 → 0.23.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 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(board.id, signal);
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.22.0",
3
+ "version": "0.23.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.22.0"
27
+ "@rawdash/core": "0.23.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "fast-check": "^4.8.0",