@rawdash/connector-bitbucket 0.21.1 → 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
@@ -38,6 +38,11 @@ declare const bitbucketResources: {
38
38
  readonly description: "Open, merged, declined, and superseded pull requests with author, source/target branches, and close timestamp.";
39
39
  readonly endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests?state=OPEN,MERGED,DECLINED,SUPERSEDED";
40
40
  readonly notes: "Paginated newest-first by `updated_on`; the connector stops once a page is entirely older than `options.since`.";
41
+ readonly filterable: [{
42
+ readonly field: "state";
43
+ readonly ops: ["eq"];
44
+ readonly values: ["OPEN", "MERGED", "DECLINED", "SUPERSEDED"];
45
+ }];
41
46
  readonly responses: {
42
47
  readonly pull_requests: z.ZodObject<{
43
48
  values: z.ZodArray<z.ZodObject<{
@@ -86,6 +91,7 @@ declare const bitbucketResources: {
86
91
  readonly description: "Bitbucket Pipelines runs with state, result, target ref/commit, trigger, duration, and create/complete timestamps.";
87
92
  readonly endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/";
88
93
  readonly notes: "Paginated newest-first by `created_on`; the connector stops once a page is entirely older than `options.since`.";
94
+ readonly filterable: [];
89
95
  readonly responses: {
90
96
  readonly pipelines: z.ZodObject<{
91
97
  values: z.ZodArray<z.ZodObject<{
@@ -134,6 +140,7 @@ declare const bitbucketResources: {
134
140
  readonly description: "Pipeline lifecycle events. One event per pipeline covering created_on to completed_on (or updated_on if not yet finished), tagged with the terminal state and result.";
135
141
  readonly endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/";
136
142
  readonly notes: "Derived from the same pipelines response that builds the `pipeline` resource; the Bitbucket API does not expose an intermediate state-transition history endpoint.";
143
+ readonly filterable: [];
137
144
  };
138
145
  };
139
146
  declare const id = "bitbucket";
@@ -145,6 +152,11 @@ declare class BitbucketConnector extends BaseConnector<BitbucketSettings, Bitbuc
145
152
  readonly description: "Open, merged, declined, and superseded pull requests with author, source/target branches, and close timestamp.";
146
153
  readonly endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests?state=OPEN,MERGED,DECLINED,SUPERSEDED";
147
154
  readonly notes: "Paginated newest-first by `updated_on`; the connector stops once a page is entirely older than `options.since`.";
155
+ readonly filterable: [{
156
+ readonly field: "state";
157
+ readonly ops: ["eq"];
158
+ readonly values: ["OPEN", "MERGED", "DECLINED", "SUPERSEDED"];
159
+ }];
148
160
  readonly responses: {
149
161
  readonly pull_requests: z.ZodObject<{
150
162
  values: z.ZodArray<z.ZodObject<{
@@ -193,6 +205,7 @@ declare class BitbucketConnector extends BaseConnector<BitbucketSettings, Bitbuc
193
205
  readonly description: "Bitbucket Pipelines runs with state, result, target ref/commit, trigger, duration, and create/complete timestamps.";
194
206
  readonly endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/";
195
207
  readonly notes: "Paginated newest-first by `created_on`; the connector stops once a page is entirely older than `options.since`.";
208
+ readonly filterable: [];
196
209
  readonly responses: {
197
210
  readonly pipelines: z.ZodObject<{
198
211
  values: z.ZodArray<z.ZodObject<{
@@ -241,6 +254,7 @@ declare class BitbucketConnector extends BaseConnector<BitbucketSettings, Bitbuc
241
254
  readonly description: "Pipeline lifecycle events. One event per pipeline covering created_on to completed_on (or updated_on if not yet finished), tagged with the terminal state and result.";
242
255
  readonly endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/";
243
256
  readonly notes: "Derived from the same pipelines response that builds the `pipeline` resource; the Bitbucket API does not expose an intermediate state-transition history endpoint.";
257
+ readonly filterable: [];
244
258
  };
245
259
  };
246
260
  static readonly schemas: object & {
@@ -345,6 +359,7 @@ declare class BitbucketConnector extends BaseConnector<BitbucketSettings, Bitbuc
345
359
  private activePhases;
346
360
  private isResourceAllowed;
347
361
  private buildPullRequestsUrl;
362
+ private singleSpec;
348
363
  private buildPipelinesUrl;
349
364
  private pullRequestsPath;
350
365
  private pipelinesPath;
package/dist/index.js CHANGED
@@ -82,6 +82,7 @@ var doc = defineConnectorDoc({
82
82
  tagline: "Sync pull requests, pipelines, and pipeline lifecycle events from Bitbucket Cloud repositories.",
83
83
  vendor: {
84
84
  name: "Atlassian",
85
+ domain: "bitbucket.org",
85
86
  apiDocs: "https://developer.atlassian.com/cloud/bitbucket/rest/intro/",
86
87
  website: "https://bitbucket.org"
87
88
  },
@@ -203,6 +204,13 @@ var bitbucketResources = defineResources({
203
204
  description: "Open, merged, declined, and superseded pull requests with author, source/target branches, and close timestamp.",
204
205
  endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests?state=OPEN,MERGED,DECLINED,SUPERSEDED",
205
206
  notes: "Paginated newest-first by `updated_on`; the connector stops once a page is entirely older than `options.since`.",
207
+ filterable: [
208
+ {
209
+ field: "state",
210
+ ops: ["eq"],
211
+ values: ["OPEN", "MERGED", "DECLINED", "SUPERSEDED"]
212
+ }
213
+ ],
206
214
  responses: { pull_requests: pullRequestsResponseSchema }
207
215
  },
208
216
  pipeline: {
@@ -210,16 +218,35 @@ var bitbucketResources = defineResources({
210
218
  description: "Bitbucket Pipelines runs with state, result, target ref/commit, trigger, duration, and create/complete timestamps.",
211
219
  endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/",
212
220
  notes: "Paginated newest-first by `created_on`; the connector stops once a page is entirely older than `options.since`.",
221
+ filterable: [],
213
222
  responses: { pipelines: pipelinesResponseSchema }
214
223
  },
215
224
  pipeline_event: {
216
225
  shape: "event",
217
226
  description: "Pipeline lifecycle events. One event per pipeline covering created_on to completed_on (or updated_on if not yet finished), tagged with the terminal state and result.",
218
227
  endpoint: "GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/",
219
- notes: "Derived from the same pipelines response that builds the `pipeline` resource; the Bitbucket API does not expose an intermediate state-transition history endpoint."
228
+ notes: "Derived from the same pipelines response that builds the `pipeline` resource; the Bitbucket API does not expose an intermediate state-transition history endpoint.",
229
+ filterable: []
220
230
  }
221
231
  });
222
232
  var id = "bitbucket";
233
+ var PULL_REQUEST_STATES = /* @__PURE__ */ new Set([
234
+ "OPEN",
235
+ "MERGED",
236
+ "DECLINED",
237
+ "SUPERSEDED"
238
+ ]);
239
+ function pushablePullRequestState(filter) {
240
+ if (!filter) {
241
+ return null;
242
+ }
243
+ for (const clause of filter) {
244
+ if ("field" in clause && clause.field === "state" && clause.op === "eq" && typeof clause.value === "string" && PULL_REQUEST_STATES.has(clause.value)) {
245
+ return clause.value;
246
+ }
247
+ }
248
+ return null;
249
+ }
223
250
  var BitbucketConnector = class _BitbucketConnector extends BaseConnector {
224
251
  static id = id;
225
252
  static resources = bitbucketResources;
@@ -310,18 +337,25 @@ var BitbucketConnector = class _BitbucketConnector extends BaseConnector {
310
337
  // -------------------------------------------------------------------------
311
338
  // URL builders
312
339
  // -------------------------------------------------------------------------
313
- buildPullRequestsUrl(repoSlugValue, options) {
340
+ buildPullRequestsUrl(repoSlugValue, options, spec) {
314
341
  const u = new URL(
315
342
  `${API_BASE}/2.0/repositories/${encodeURIComponent(this.settings.workspace)}/${encodeURIComponent(repoSlugValue)}/pullrequests`
316
343
  );
317
344
  u.searchParams.set("pagelen", String(PAGE_SIZE));
318
345
  u.searchParams.set("sort", "-updated_on");
319
- u.searchParams.set("state", "OPEN,MERGED,DECLINED,SUPERSEDED");
346
+ u.searchParams.set(
347
+ "state",
348
+ pushablePullRequestState(spec?.filter) ?? "OPEN,MERGED,DECLINED,SUPERSEDED"
349
+ );
320
350
  if (options.since) {
321
351
  u.searchParams.set("q", `updated_on >= ${options.since}`);
322
352
  }
323
353
  return u.toString();
324
354
  }
355
+ singleSpec(options, resource) {
356
+ const specs = options.fetchSpecs?.[resource];
357
+ return specs && specs.length === 1 ? specs[0] : void 0;
358
+ }
325
359
  buildPipelinesUrl(repoSlugValue, options) {
326
360
  const u = new URL(
327
361
  `${API_BASE}/2.0/repositories/${encodeURIComponent(this.settings.workspace)}/${encodeURIComponent(repoSlugValue)}/pipelines/`
@@ -353,7 +387,11 @@ var BitbucketConnector = class _BitbucketConnector extends BaseConnector {
353
387
  }
354
388
  const slug = repos[idx];
355
389
  const expectedPath = this.pullRequestsPath(slug);
356
- const fetchUrl = this.sanitizeUrl(rawPageUrl, expectedPath) ?? this.buildPullRequestsUrl(slug, options);
390
+ const fetchUrl = this.sanitizeUrl(rawPageUrl, expectedPath) ?? this.buildPullRequestsUrl(
391
+ slug,
392
+ options,
393
+ this.singleSpec(options, "pull_request")
394
+ );
357
395
  const res = await this.fetch(
358
396
  fetchUrl,
359
397
  "pull_requests",
@@ -470,7 +508,7 @@ var BitbucketConnector = class _BitbucketConnector extends BaseConnector {
470
508
  }
471
509
  }
472
510
  }
473
- async writePipelines(storage, items, page, options) {
511
+ async writePipelines(storage, items, page, options, seenPipelineIds) {
474
512
  const pipelineAllowed = this.isResourceAllowed(
475
513
  "pipeline",
476
514
  options.resources
@@ -494,6 +532,11 @@ var BitbucketConnector = class _BitbucketConnector extends BaseConnector {
494
532
  if (createdMs === null) {
495
533
  continue;
496
534
  }
535
+ const entityId = `${this.settings.workspace}/${batch.repoSlug}:${pipeline.uuid}`;
536
+ if (seenPipelineIds.has(entityId)) {
537
+ continue;
538
+ }
539
+ seenPipelineIds.add(entityId);
497
540
  const completedMs = parseEpoch(pipeline.completed_on ?? null, "iso");
498
541
  const durationMs = pipeline.duration_in_seconds !== null && pipeline.duration_in_seconds !== void 0 ? Math.round(pipeline.duration_in_seconds * 1e3) : completedMs !== null ? completedMs - createdMs : null;
499
542
  const result = pipeline.state.result?.name ?? null;
@@ -503,7 +546,7 @@ var BitbucketConnector = class _BitbucketConnector extends BaseConnector {
503
546
  if (pipelineAllowed) {
504
547
  await storage.entity({
505
548
  type: "pipeline",
506
- id: `${this.settings.workspace}/${batch.repoSlug}:${pipeline.uuid}`,
549
+ id: entityId,
507
550
  attributes: {
508
551
  workspace: this.settings.workspace,
509
552
  repo_slug: batch.repoSlug,
@@ -559,6 +602,7 @@ var BitbucketConnector = class _BitbucketConnector extends BaseConnector {
559
602
  async sync(options, storage, signal) {
560
603
  const cursor = this.resolveCursor(options.cursor);
561
604
  const phases = this.activePhases(options.resources);
605
+ const seenPipelineIds = /* @__PURE__ */ new Set();
562
606
  return paginateChunked({
563
607
  phases,
564
608
  cursor,
@@ -577,7 +621,13 @@ var BitbucketConnector = class _BitbucketConnector extends BaseConnector {
577
621
  case "pull_requests":
578
622
  return this.writePullRequests(storage, items, page, options);
579
623
  case "pipelines":
580
- return this.writePipelines(storage, items, page, options);
624
+ return this.writePipelines(
625
+ storage,
626
+ items,
627
+ page,
628
+ options,
629
+ seenPipelineIds
630
+ );
581
631
  }
582
632
  }
583
633
  });
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/bitbucket.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 ChunkedSyncCursor,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type FetchPageResult,\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\n// ---------------------------------------------------------------------------\n// configFields\n// ---------------------------------------------------------------------------\n\nconst repoSlug = z\n .string()\n .min(1)\n .regex(\n /^[^/\\s?#]+$/,\n 'Use the repository slug only (no workspace prefix, slashes, or query).',\n );\n\nexport const configFields = defineConfigFields(\n z.object({\n workspace: z\n .string()\n .min(1)\n .regex(\n /^[^/\\s?#]+$/,\n 'Use the workspace slug only (no slashes, spaces, or query).',\n )\n .meta({\n label: 'Workspace',\n description:\n 'Bitbucket Cloud workspace slug (the segment shown in repo URLs after bitbucket.org/).',\n placeholder: 'my-workspace',\n }),\n username: z.string().min(1).meta({\n label: 'Atlassian username',\n description:\n 'Atlassian account username paired with the app password for Basic auth (find it under Personal settings -> Account settings).',\n placeholder: 'janedoe',\n }),\n appPassword: z.object({ $secret: z.string() }).meta({\n label: 'App password',\n description:\n 'Bitbucket app password with `Repositories:Read` and `Pipelines:Read` scopes. Create one at Personal settings -> App passwords.',\n placeholder: 'ATBB...',\n secret: true,\n }),\n repoSlugs: z\n .array(repoSlug)\n .nonempty()\n .refine((slugs) => new Set(slugs).size === slugs.length, {\n error: 'Repository slugs must be unique.',\n })\n .meta({\n label: 'Repository slugs',\n description:\n 'Repositories to sync, named by their slug within the workspace (no `workspace/` prefix).',\n }),\n resources: z\n .array(z.enum(['pull_request', 'pipeline', 'pipeline_event']))\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n \"Which Bitbucket resources to sync. Omit to sync all of them. 'pipeline_event' rides the 'pipeline' phase - enabling it without 'pipeline' still fetches pipelines but skips writing pipeline entities.\",\n }),\n }),\n);\n\n// ---------------------------------------------------------------------------\n// Connector documentation metadata\n// ---------------------------------------------------------------------------\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Bitbucket',\n category: 'engineering',\n brandColor: '#0052CC',\n tagline:\n 'Sync pull requests, pipelines, and pipeline lifecycle events from Bitbucket Cloud repositories.',\n vendor: {\n name: 'Atlassian',\n apiDocs: 'https://developer.atlassian.com/cloud/bitbucket/rest/intro/',\n website: 'https://bitbucket.org',\n },\n auth: {\n summary:\n 'Authenticates over HTTP Basic auth using an Atlassian account username and a Bitbucket app password. The password is scoped to the projects and repositories the account can already read.',\n setup: [\n 'Open Bitbucket -> Personal settings -> App passwords (https://bitbucket.org/account/settings/app-passwords/).',\n 'Create an app password with `Repositories:Read` and `Pipelines:Read` scopes.',\n 'Store it as a secret and reference it from the connector config as `appPassword: secret(\"BITBUCKET_APP_PASSWORD\")`, alongside your `workspace`, `username`, and the list of `repoSlugs` to sync.',\n ],\n },\n rateLimit:\n 'Bitbucket Cloud applies hourly per-IP and per-user limits (around 1,000 requests/hour for app-password auth). Pagination uses a `next` URL in each response and a configurable `pagelen` (capped at 50 here).',\n limitations: [\n 'Bitbucket Server / Data Center are out of scope; this connector targets Bitbucket Cloud only.',\n 'Pipeline state-transition events are synthesized: one `pipeline_event` is emitted per pipeline lifecycle (created_on to completed_on/updated_on), not one per intermediate state change.',\n 'Repository discovery is not automatic - configure each repository slug explicitly via `repoSlugs`.',\n ],\n});\n\n// ---------------------------------------------------------------------------\n// Settings and credentials\n// ---------------------------------------------------------------------------\n\nexport type BitbucketResource = 'pull_request' | 'pipeline' | 'pipeline_event';\n\nexport interface BitbucketSettings {\n workspace: string;\n repoSlugs: readonly string[];\n resources?: readonly BitbucketResource[];\n}\n\nconst bitbucketCredentials = {\n username: {\n description: 'Atlassian account username',\n auth: 'required' as const,\n },\n appPassword: {\n description: 'Bitbucket app password',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype BitbucketCredentials = typeof bitbucketCredentials;\n\nconst API_HOST = 'api.bitbucket.org';\nconst API_BASE = `https://${API_HOST}`;\nconst PAGE_SIZE = 50;\n\n// ---------------------------------------------------------------------------\n// Sync phases + cursor\n// ---------------------------------------------------------------------------\n\nconst PHASE_ORDER = ['pull_requests', 'pipelines'] as const;\n\ntype BitbucketPhase = (typeof PHASE_ORDER)[number];\n\ntype BitbucketSyncCursor = ChunkedSyncCursor<BitbucketPhase, string>;\n\nconst isBitbucketSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\n// Page-cursor encoding: `<repoIdx>|<pageUrl?>`.\n// - null page -> start at repoIdx=0 with no URL yet\n// - \"<idx>|\" -> start of repo at idx, build initial URL\n// - \"<idx>|<url>\" -> continuing pagination for repo at idx\nfunction decodePage(page: string | null): {\n idx: number;\n url: string | null;\n} {\n if (page === null) {\n return { idx: 0, url: null };\n }\n const sep = page.indexOf('|');\n if (sep === -1) {\n return { idx: 0, url: null };\n }\n const idxRaw = Number.parseInt(page.slice(0, sep), 10);\n const url = page.slice(sep + 1);\n return {\n idx: Number.isFinite(idxRaw) && idxRaw >= 0 ? idxRaw : 0,\n url: url === '' ? null : url,\n };\n}\n\nfunction encodePage(idx: number, url: string | null): string {\n return `${idx}|${url ?? ''}`;\n}\n\n// ---------------------------------------------------------------------------\n// Bitbucket API types\n// ---------------------------------------------------------------------------\n\ninterface BitbucketAccountRef {\n uuid?: string | null;\n display_name?: string | null;\n nickname?: string | null;\n account_id?: string | null;\n}\n\ninterface BitbucketBranchRef {\n branch?: { name?: string | null } | null;\n commit?: { hash?: string | null } | null;\n}\n\ninterface BitbucketPullRequest {\n id: number;\n title: string;\n state: string;\n author?: BitbucketAccountRef | null;\n source?: BitbucketBranchRef | null;\n destination?: BitbucketBranchRef | null;\n created_on: string;\n updated_on: string;\n closed_on?: string | null;\n links?: { html?: { href?: string | null } | null } | null;\n}\n\ninterface BitbucketPullRequestsResponse {\n values: BitbucketPullRequest[];\n next?: string | null;\n page?: number | null;\n pagelen?: number | null;\n}\n\ninterface BitbucketPipelineTarget {\n ref_name?: string | null;\n commit?: { hash?: string | null } | null;\n selector?: { type?: string | null; pattern?: string | null } | null;\n}\n\ninterface BitbucketPipelineState {\n name: string;\n type?: string | null;\n result?: { name?: string | null } | null;\n}\n\ninterface BitbucketPipeline {\n uuid: string;\n build_number: number;\n state: BitbucketPipelineState;\n creator?: BitbucketAccountRef | null;\n target?: BitbucketPipelineTarget | null;\n trigger?: { name?: string | null; type?: string | null } | null;\n created_on: string;\n completed_on?: string | null;\n duration_in_seconds?: number | null;\n build_seconds_used?: number | null;\n}\n\ninterface BitbucketPipelinesResponse {\n values: BitbucketPipeline[];\n next?: string | null;\n page?: number | null;\n pagelen?: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Zod response schemas\n// ---------------------------------------------------------------------------\n\nconst accountRefSchema = z.object({\n uuid: z.string().nullable().optional(),\n display_name: z.string().nullable().optional(),\n nickname: z.string().nullable().optional(),\n account_id: z.string().nullable().optional(),\n});\n\nconst branchRefSchema = z.object({\n branch: z\n .object({ name: z.string().nullable().optional() })\n .nullable()\n .optional(),\n commit: z\n .object({ hash: z.string().nullable().optional() })\n .nullable()\n .optional(),\n});\n\nconst pullRequestSchema = z.object({\n id: z.number().int().nonnegative(),\n title: z.string(),\n state: z.string().min(1),\n author: accountRefSchema.nullable().optional(),\n source: branchRefSchema.nullable().optional(),\n destination: branchRefSchema.nullable().optional(),\n created_on: z.iso.datetime(),\n updated_on: z.iso.datetime(),\n closed_on: z.iso.datetime().nullable().optional(),\n links: z\n .object({\n html: z\n .object({ href: z.string().nullable().optional() })\n .nullable()\n .optional(),\n })\n .nullable()\n .optional(),\n});\n\nconst pullRequestsResponseSchema = z.object({\n values: z.array(pullRequestSchema),\n next: z.string().nullable().optional(),\n page: z.number().int().nullable().optional(),\n pagelen: z.number().int().nullable().optional(),\n});\n\nconst pipelineStateSchema = z.object({\n name: z.string().min(1),\n type: z.string().nullable().optional(),\n result: z\n .object({ name: z.string().nullable().optional() })\n .nullable()\n .optional(),\n});\n\nconst pipelineTargetSchema = z.object({\n ref_name: z.string().nullable().optional(),\n commit: z\n .object({ hash: z.string().nullable().optional() })\n .nullable()\n .optional(),\n selector: z\n .object({\n type: z.string().nullable().optional(),\n pattern: z.string().nullable().optional(),\n })\n .nullable()\n .optional(),\n});\n\nconst pipelineSchema = z.object({\n uuid: z.string().min(1),\n build_number: z.number().int().nonnegative(),\n state: pipelineStateSchema,\n creator: accountRefSchema.nullable().optional(),\n target: pipelineTargetSchema.nullable().optional(),\n trigger: z\n .object({\n name: z.string().nullable().optional(),\n type: z.string().nullable().optional(),\n })\n .nullable()\n .optional(),\n created_on: z.iso.datetime(),\n completed_on: z.iso.datetime().nullable().optional(),\n duration_in_seconds: z.number().nullable().optional(),\n build_seconds_used: z.number().nullable().optional(),\n});\n\nconst pipelinesResponseSchema = z.object({\n values: z.array(pipelineSchema),\n next: z.string().nullable().optional(),\n page: z.number().int().nullable().optional(),\n pagelen: z.number().int().nullable().optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Resource definitions\n// ---------------------------------------------------------------------------\n\nexport const bitbucketResources = defineResources({\n pull_request: {\n shape: 'entity',\n description:\n 'Open, merged, declined, and superseded pull requests with author, source/target branches, and close timestamp.',\n endpoint:\n 'GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests?state=OPEN,MERGED,DECLINED,SUPERSEDED',\n notes:\n 'Paginated newest-first by `updated_on`; the connector stops once a page is entirely older than `options.since`.',\n responses: { pull_requests: pullRequestsResponseSchema },\n },\n pipeline: {\n shape: 'entity',\n description:\n 'Bitbucket Pipelines runs with state, result, target ref/commit, trigger, duration, and create/complete timestamps.',\n endpoint: 'GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/',\n notes:\n 'Paginated newest-first by `created_on`; the connector stops once a page is entirely older than `options.since`.',\n responses: { pipelines: pipelinesResponseSchema },\n },\n pipeline_event: {\n shape: 'event',\n description:\n 'Pipeline lifecycle events. One event per pipeline covering created_on to completed_on (or updated_on if not yet finished), tagged with the terminal state and result.',\n endpoint: 'GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/',\n notes:\n 'Derived from the same pipelines response that builds the `pipeline` resource; the Bitbucket API does not expose an intermediate state-transition history endpoint.',\n },\n});\n\nexport const id = 'bitbucket';\n\n// ---------------------------------------------------------------------------\n// Connector class\n// ---------------------------------------------------------------------------\n\ninterface RepoBatch<T> {\n repoSlug: string;\n items: T[];\n}\n\nexport class BitbucketConnector extends BaseConnector<\n BitbucketSettings,\n BitbucketCredentials\n> {\n static readonly id = id;\n\n static readonly resources = bitbucketResources;\n\n static readonly schemas = schemasFromResources(bitbucketResources);\n\n static create(input: unknown, ctx?: ConnectorContext): BitbucketConnector {\n const parsed = configFields.parse(input);\n return new BitbucketConnector(\n {\n workspace: parsed.workspace,\n repoSlugs: parsed.repoSlugs,\n resources: parsed.resources,\n },\n { username: parsed.username, appPassword: parsed.appPassword },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = bitbucketCredentials;\n\n private buildHeaders(): Record<string, string> {\n const basic = btoa(`${this.creds.username}:${this.creds.appPassword}`);\n return {\n Authorization: `Basic ${basic}`,\n Accept: 'application/json',\n 'User-Agent': connectorUserAgent('bitbucket'),\n };\n }\n\n private fetch<T>(\n url: string,\n resource: string,\n signal: AbortSignal | undefined,\n ): Promise<HttpResponse<T>> {\n return this.get<T>(url, {\n resource,\n headers: this.buildHeaders(),\n signal,\n });\n }\n\n private sanitizeUrl(url: string | null, expectedPath: string): string | null {\n if (!url) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== 'https:' || u.host !== API_HOST) {\n return null;\n }\n if (u.pathname !== expectedPath) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n }\n\n private static readonly PHASE_RESOURCES: Record<\n BitbucketPhase,\n readonly BitbucketResource[]\n > = {\n pull_requests: ['pull_request'],\n pipelines: ['pipeline', 'pipeline_event'],\n };\n\n private activePhases(\n optionsResources: ReadonlySet<string> | undefined,\n ): BitbucketPhase[] {\n const fromSettings = selectActivePhases<BitbucketResource, BitbucketPhase>(\n (r) => {\n switch (r) {\n case 'pull_request':\n return 'pull_requests';\n case 'pipeline':\n case 'pipeline_event':\n return 'pipelines';\n }\n },\n PHASE_ORDER,\n this.settings.resources,\n );\n if (optionsResources === undefined) {\n return fromSettings;\n }\n return fromSettings.filter((phase) =>\n BitbucketConnector.PHASE_RESOURCES[phase].some((r) =>\n optionsResources.has(r),\n ),\n );\n }\n\n private isResourceAllowed(\n resource: BitbucketResource,\n optionsResources: ReadonlySet<string> | undefined,\n ): boolean {\n const fromSettings = this.settings.resources;\n if (\n fromSettings &&\n fromSettings.length > 0 &&\n !fromSettings.includes(resource)\n ) {\n return false;\n }\n if (optionsResources !== undefined && !optionsResources.has(resource)) {\n return false;\n }\n return true;\n }\n\n // -------------------------------------------------------------------------\n // URL builders\n // -------------------------------------------------------------------------\n\n private buildPullRequestsUrl(\n repoSlugValue: string,\n options: SyncOptions,\n ): string {\n const u = new URL(\n `${API_BASE}/2.0/repositories/${encodeURIComponent(this.settings.workspace)}/${encodeURIComponent(repoSlugValue)}/pullrequests`,\n );\n u.searchParams.set('pagelen', String(PAGE_SIZE));\n u.searchParams.set('sort', '-updated_on');\n u.searchParams.set('state', 'OPEN,MERGED,DECLINED,SUPERSEDED');\n if (options.since) {\n u.searchParams.set('q', `updated_on >= ${options.since}`);\n }\n return u.toString();\n }\n\n private buildPipelinesUrl(\n repoSlugValue: string,\n options: SyncOptions,\n ): string {\n const u = new URL(\n `${API_BASE}/2.0/repositories/${encodeURIComponent(this.settings.workspace)}/${encodeURIComponent(repoSlugValue)}/pipelines/`,\n );\n u.searchParams.set('pagelen', String(PAGE_SIZE));\n u.searchParams.set('sort', '-created_on');\n if (options.since) {\n u.searchParams.set('q', `created_on >= ${options.since}`);\n }\n return u.toString();\n }\n\n private pullRequestsPath(repoSlugValue: string): string {\n return `/2.0/repositories/${this.settings.workspace}/${repoSlugValue}/pullrequests`;\n }\n\n private pipelinesPath(repoSlugValue: string): string {\n return `/2.0/repositories/${this.settings.workspace}/${repoSlugValue}/pipelines/`;\n }\n\n // -------------------------------------------------------------------------\n // Fetchers\n // -------------------------------------------------------------------------\n\n private async fetchPullRequestsPage(\n options: SyncOptions,\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<FetchPageResult<string>> {\n const repos = this.settings.repoSlugs;\n if (repos.length === 0) {\n return { items: [], next: null };\n }\n const { idx, url: rawPageUrl } = decodePage(page);\n if (idx >= repos.length) {\n return { items: [], next: null };\n }\n const slug = repos[idx]!;\n const expectedPath = this.pullRequestsPath(slug);\n const fetchUrl =\n this.sanitizeUrl(rawPageUrl, expectedPath) ??\n this.buildPullRequestsUrl(slug, options);\n const res = await this.fetch<BitbucketPullRequestsResponse>(\n fetchUrl,\n 'pull_requests',\n signal,\n );\n const rows = res.body.values;\n const cutoff = options.since ? parseEpoch(options.since, 'iso') : null;\n let filtered: BitbucketPullRequest[];\n let cutoffReached: boolean;\n if (cutoff !== null) {\n filtered = rows.filter((pr) => {\n const ts = parseEpoch(pr.updated_on, 'iso');\n return ts === null || ts >= cutoff;\n });\n const last = rows.at(-1);\n const lastTs = last ? parseEpoch(last.updated_on, 'iso') : null;\n cutoffReached = lastTs !== null && lastTs < cutoff;\n } else {\n filtered = rows;\n cutoffReached = false;\n }\n const safeNext = this.sanitizeUrl(res.body.next ?? null, expectedPath);\n const nextWithinRepo = cutoffReached ? null : safeNext;\n const batch: RepoBatch<BitbucketPullRequest> = {\n repoSlug: slug,\n items: filtered,\n };\n if (nextWithinRepo !== null) {\n return { items: [batch], next: encodePage(idx, nextWithinRepo) };\n }\n const nextIdx = idx + 1;\n const next = nextIdx < repos.length ? encodePage(nextIdx, null) : null;\n return { items: [batch], next };\n }\n\n private async fetchPipelinesPage(\n options: SyncOptions,\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<FetchPageResult<string>> {\n const repos = this.settings.repoSlugs;\n if (repos.length === 0) {\n return { items: [], next: null };\n }\n const { idx, url: rawPageUrl } = decodePage(page);\n if (idx >= repos.length) {\n return { items: [], next: null };\n }\n const slug = repos[idx]!;\n const expectedPath = this.pipelinesPath(slug);\n const fetchUrl =\n this.sanitizeUrl(rawPageUrl, expectedPath) ??\n this.buildPipelinesUrl(slug, options);\n const res = await this.fetch<BitbucketPipelinesResponse>(\n fetchUrl,\n 'pipelines',\n signal,\n );\n const rows = res.body.values;\n const cutoff = options.since ? parseEpoch(options.since, 'iso') : null;\n let filtered: BitbucketPipeline[];\n let cutoffReached: boolean;\n if (cutoff !== null) {\n filtered = rows.filter((p) => {\n const ts = parseEpoch(p.created_on, 'iso');\n return ts === null || ts >= cutoff;\n });\n const last = rows.at(-1);\n const lastTs = last ? parseEpoch(last.created_on, 'iso') : null;\n cutoffReached = lastTs !== null && lastTs < cutoff;\n } else {\n filtered = rows;\n cutoffReached = false;\n }\n const safeNext = this.sanitizeUrl(res.body.next ?? null, expectedPath);\n const nextWithinRepo = cutoffReached ? null : safeNext;\n const batch: RepoBatch<BitbucketPipeline> = {\n repoSlug: slug,\n items: filtered,\n };\n if (nextWithinRepo !== null) {\n return { items: [batch], next: encodePage(idx, nextWithinRepo) };\n }\n const nextIdx = idx + 1;\n const next = nextIdx < repos.length ? encodePage(nextIdx, null) : null;\n return { items: [batch], next };\n }\n\n // -------------------------------------------------------------------------\n // Writers\n // -------------------------------------------------------------------------\n\n private async writePullRequests(\n storage: StorageHandle,\n items: unknown[],\n page: string | null,\n options: SyncOptions,\n ): Promise<void> {\n if (page === null && !options.since) {\n await storage.entities([], { types: ['pull_request'] });\n }\n const batches = items as RepoBatch<BitbucketPullRequest>[];\n for (const batch of batches) {\n for (const pr of batch.items) {\n const createdMs = parseEpoch(pr.created_on, 'iso');\n const updatedMs = parseEpoch(pr.updated_on, 'iso');\n if (createdMs === null || updatedMs === null) {\n continue;\n }\n await storage.entity({\n type: 'pull_request',\n id: `${this.settings.workspace}/${batch.repoSlug}:${pr.id}`,\n attributes: {\n workspace: this.settings.workspace,\n repo_slug: batch.repoSlug,\n pull_request_id: pr.id,\n title: pr.title,\n state: pr.state,\n author: pr.author?.nickname ?? pr.author?.display_name ?? null,\n source_branch: pr.source?.branch?.name ?? null,\n destination_branch: pr.destination?.branch?.name ?? null,\n web_url: pr.links?.html?.href ?? null,\n created_at: createdMs,\n closed_at: parseEpoch(pr.closed_on ?? null, 'iso'),\n },\n updated_at: updatedMs,\n });\n }\n }\n }\n\n private async writePipelines(\n storage: StorageHandle,\n items: unknown[],\n page: string | null,\n options: SyncOptions,\n ): Promise<void> {\n const pipelineAllowed = this.isResourceAllowed(\n 'pipeline',\n options.resources,\n );\n const eventAllowed = this.isResourceAllowed(\n 'pipeline_event',\n options.resources,\n );\n if (page === null && !options.since) {\n if (pipelineAllowed) {\n await storage.entities([], { types: ['pipeline'] });\n }\n if (eventAllowed) {\n await storage.events([], { names: ['pipeline_event'] });\n }\n }\n const batches = items as RepoBatch<BitbucketPipeline>[];\n for (const batch of batches) {\n for (const pipeline of batch.items) {\n const createdMs = parseEpoch(pipeline.created_on, 'iso');\n if (createdMs === null) {\n continue;\n }\n const completedMs = parseEpoch(pipeline.completed_on ?? null, 'iso');\n const durationMs =\n pipeline.duration_in_seconds !== null &&\n pipeline.duration_in_seconds !== undefined\n ? Math.round(pipeline.duration_in_seconds * 1000)\n : completedMs !== null\n ? completedMs - createdMs\n : null;\n const result = pipeline.state.result?.name ?? null;\n const refName = pipeline.target?.ref_name ?? null;\n const commitHash = pipeline.target?.commit?.hash ?? null;\n const triggerType = pipeline.trigger?.type ?? null;\n if (pipelineAllowed) {\n await storage.entity({\n type: 'pipeline',\n id: `${this.settings.workspace}/${batch.repoSlug}:${pipeline.uuid}`,\n attributes: {\n workspace: this.settings.workspace,\n repo_slug: batch.repoSlug,\n uuid: pipeline.uuid,\n build_number: pipeline.build_number,\n state: pipeline.state.name,\n result,\n ref_name: refName,\n commit: commitHash,\n trigger_type: triggerType,\n creator:\n pipeline.creator?.nickname ??\n pipeline.creator?.display_name ??\n null,\n created_at: createdMs,\n completed_at: completedMs,\n duration_ms: durationMs,\n },\n updated_at: completedMs ?? createdMs,\n });\n }\n if (eventAllowed) {\n await storage.event({\n name: 'pipeline_event',\n start_ts: createdMs,\n end_ts: completedMs,\n attributes: {\n workspace: this.settings.workspace,\n repo_slug: batch.repoSlug,\n uuid: pipeline.uuid,\n build_number: pipeline.build_number,\n state: pipeline.state.name,\n result,\n ref_name: refName,\n commit: commitHash,\n trigger_type: triggerType,\n duration_ms: durationMs,\n },\n });\n }\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Cursor resume\n // -------------------------------------------------------------------------\n\n private resolveCursor(cursor: unknown): BitbucketSyncCursor | undefined {\n if (!isBitbucketSyncCursor(cursor)) {\n return undefined;\n }\n return { phase: cursor.phase, page: cursor.page };\n }\n\n // -------------------------------------------------------------------------\n // sync()\n // -------------------------------------------------------------------------\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = this.resolveCursor(options.cursor);\n const phases = this.activePhases(options.resources);\n return paginateChunked<BitbucketPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n switch (phase) {\n case 'pull_requests':\n return this.fetchPullRequestsPage(options, page, sig);\n case 'pipelines':\n return this.fetchPipelinesPage(options, page, sig);\n }\n },\n writeBatch: async (phase, items, page) => {\n switch (phase) {\n case 'pull_requests':\n return this.writePullRequests(storage, items, page, options);\n case 'pipelines':\n return this.writePipelines(storage, items, page, options);\n }\n },\n });\n }\n}\n","import { BitbucketConnector } from './bitbucket';\n\nexport {\n bitbucketResources as resources,\n BitbucketConnector,\n configFields,\n doc,\n id,\n} from './bitbucket';\nexport type { BitbucketResource, BitbucketSettings } from './bitbucket';\nexport default BitbucketConnector;\n"],"mappings":"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","names":[]}
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/bitbucket.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 ChunkedSyncCursor,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type FetchPageResult,\n type FetchSpec,\n type FilterClause,\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\n// ---------------------------------------------------------------------------\n// configFields\n// ---------------------------------------------------------------------------\n\nconst repoSlug = z\n .string()\n .min(1)\n .regex(\n /^[^/\\s?#]+$/,\n 'Use the repository slug only (no workspace prefix, slashes, or query).',\n );\n\nexport const configFields = defineConfigFields(\n z.object({\n workspace: z\n .string()\n .min(1)\n .regex(\n /^[^/\\s?#]+$/,\n 'Use the workspace slug only (no slashes, spaces, or query).',\n )\n .meta({\n label: 'Workspace',\n description:\n 'Bitbucket Cloud workspace slug (the segment shown in repo URLs after bitbucket.org/).',\n placeholder: 'my-workspace',\n }),\n username: z.string().min(1).meta({\n label: 'Atlassian username',\n description:\n 'Atlassian account username paired with the app password for Basic auth (find it under Personal settings -> Account settings).',\n placeholder: 'janedoe',\n }),\n appPassword: z.object({ $secret: z.string() }).meta({\n label: 'App password',\n description:\n 'Bitbucket app password with `Repositories:Read` and `Pipelines:Read` scopes. Create one at Personal settings -> App passwords.',\n placeholder: 'ATBB...',\n secret: true,\n }),\n repoSlugs: z\n .array(repoSlug)\n .nonempty()\n .refine((slugs) => new Set(slugs).size === slugs.length, {\n error: 'Repository slugs must be unique.',\n })\n .meta({\n label: 'Repository slugs',\n description:\n 'Repositories to sync, named by their slug within the workspace (no `workspace/` prefix).',\n }),\n resources: z\n .array(z.enum(['pull_request', 'pipeline', 'pipeline_event']))\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n \"Which Bitbucket resources to sync. Omit to sync all of them. 'pipeline_event' rides the 'pipeline' phase - enabling it without 'pipeline' still fetches pipelines but skips writing pipeline entities.\",\n }),\n }),\n);\n\n// ---------------------------------------------------------------------------\n// Connector documentation metadata\n// ---------------------------------------------------------------------------\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Bitbucket',\n category: 'engineering',\n brandColor: '#0052CC',\n tagline:\n 'Sync pull requests, pipelines, and pipeline lifecycle events from Bitbucket Cloud repositories.',\n vendor: {\n name: 'Atlassian',\n domain: 'bitbucket.org',\n apiDocs: 'https://developer.atlassian.com/cloud/bitbucket/rest/intro/',\n website: 'https://bitbucket.org',\n },\n auth: {\n summary:\n 'Authenticates over HTTP Basic auth using an Atlassian account username and a Bitbucket app password. The password is scoped to the projects and repositories the account can already read.',\n setup: [\n 'Open Bitbucket -> Personal settings -> App passwords (https://bitbucket.org/account/settings/app-passwords/).',\n 'Create an app password with `Repositories:Read` and `Pipelines:Read` scopes.',\n 'Store it as a secret and reference it from the connector config as `appPassword: secret(\"BITBUCKET_APP_PASSWORD\")`, alongside your `workspace`, `username`, and the list of `repoSlugs` to sync.',\n ],\n },\n rateLimit:\n 'Bitbucket Cloud applies hourly per-IP and per-user limits (around 1,000 requests/hour for app-password auth). Pagination uses a `next` URL in each response and a configurable `pagelen` (capped at 50 here).',\n limitations: [\n 'Bitbucket Server / Data Center are out of scope; this connector targets Bitbucket Cloud only.',\n 'Pipeline state-transition events are synthesized: one `pipeline_event` is emitted per pipeline lifecycle (created_on to completed_on/updated_on), not one per intermediate state change.',\n 'Repository discovery is not automatic - configure each repository slug explicitly via `repoSlugs`.',\n ],\n});\n\n// ---------------------------------------------------------------------------\n// Settings and credentials\n// ---------------------------------------------------------------------------\n\nexport type BitbucketResource = 'pull_request' | 'pipeline' | 'pipeline_event';\n\nexport interface BitbucketSettings {\n workspace: string;\n repoSlugs: readonly string[];\n resources?: readonly BitbucketResource[];\n}\n\nconst bitbucketCredentials = {\n username: {\n description: 'Atlassian account username',\n auth: 'required' as const,\n },\n appPassword: {\n description: 'Bitbucket app password',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype BitbucketCredentials = typeof bitbucketCredentials;\n\nconst API_HOST = 'api.bitbucket.org';\nconst API_BASE = `https://${API_HOST}`;\nconst PAGE_SIZE = 50;\n\n// ---------------------------------------------------------------------------\n// Sync phases + cursor\n// ---------------------------------------------------------------------------\n\nconst PHASE_ORDER = ['pull_requests', 'pipelines'] as const;\n\ntype BitbucketPhase = (typeof PHASE_ORDER)[number];\n\ntype BitbucketSyncCursor = ChunkedSyncCursor<BitbucketPhase, string>;\n\nconst isBitbucketSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\n// Page-cursor encoding: `<repoIdx>|<pageUrl?>`.\n// - null page -> start at repoIdx=0 with no URL yet\n// - \"<idx>|\" -> start of repo at idx, build initial URL\n// - \"<idx>|<url>\" -> continuing pagination for repo at idx\nfunction decodePage(page: string | null): {\n idx: number;\n url: string | null;\n} {\n if (page === null) {\n return { idx: 0, url: null };\n }\n const sep = page.indexOf('|');\n if (sep === -1) {\n return { idx: 0, url: null };\n }\n const idxRaw = Number.parseInt(page.slice(0, sep), 10);\n const url = page.slice(sep + 1);\n return {\n idx: Number.isFinite(idxRaw) && idxRaw >= 0 ? idxRaw : 0,\n url: url === '' ? null : url,\n };\n}\n\nfunction encodePage(idx: number, url: string | null): string {\n return `${idx}|${url ?? ''}`;\n}\n\n// ---------------------------------------------------------------------------\n// Bitbucket API types\n// ---------------------------------------------------------------------------\n\ninterface BitbucketAccountRef {\n uuid?: string | null;\n display_name?: string | null;\n nickname?: string | null;\n account_id?: string | null;\n}\n\ninterface BitbucketBranchRef {\n branch?: { name?: string | null } | null;\n commit?: { hash?: string | null } | null;\n}\n\ninterface BitbucketPullRequest {\n id: number;\n title: string;\n state: string;\n author?: BitbucketAccountRef | null;\n source?: BitbucketBranchRef | null;\n destination?: BitbucketBranchRef | null;\n created_on: string;\n updated_on: string;\n closed_on?: string | null;\n links?: { html?: { href?: string | null } | null } | null;\n}\n\ninterface BitbucketPullRequestsResponse {\n values: BitbucketPullRequest[];\n next?: string | null;\n page?: number | null;\n pagelen?: number | null;\n}\n\ninterface BitbucketPipelineTarget {\n ref_name?: string | null;\n commit?: { hash?: string | null } | null;\n selector?: { type?: string | null; pattern?: string | null } | null;\n}\n\ninterface BitbucketPipelineState {\n name: string;\n type?: string | null;\n result?: { name?: string | null } | null;\n}\n\ninterface BitbucketPipeline {\n uuid: string;\n build_number: number;\n state: BitbucketPipelineState;\n creator?: BitbucketAccountRef | null;\n target?: BitbucketPipelineTarget | null;\n trigger?: { name?: string | null; type?: string | null } | null;\n created_on: string;\n completed_on?: string | null;\n duration_in_seconds?: number | null;\n build_seconds_used?: number | null;\n}\n\ninterface BitbucketPipelinesResponse {\n values: BitbucketPipeline[];\n next?: string | null;\n page?: number | null;\n pagelen?: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Zod response schemas\n// ---------------------------------------------------------------------------\n\nconst accountRefSchema = z.object({\n uuid: z.string().nullable().optional(),\n display_name: z.string().nullable().optional(),\n nickname: z.string().nullable().optional(),\n account_id: z.string().nullable().optional(),\n});\n\nconst branchRefSchema = z.object({\n branch: z\n .object({ name: z.string().nullable().optional() })\n .nullable()\n .optional(),\n commit: z\n .object({ hash: z.string().nullable().optional() })\n .nullable()\n .optional(),\n});\n\nconst pullRequestSchema = z.object({\n id: z.number().int().nonnegative(),\n title: z.string(),\n state: z.string().min(1),\n author: accountRefSchema.nullable().optional(),\n source: branchRefSchema.nullable().optional(),\n destination: branchRefSchema.nullable().optional(),\n created_on: z.iso.datetime(),\n updated_on: z.iso.datetime(),\n closed_on: z.iso.datetime().nullable().optional(),\n links: z\n .object({\n html: z\n .object({ href: z.string().nullable().optional() })\n .nullable()\n .optional(),\n })\n .nullable()\n .optional(),\n});\n\nconst pullRequestsResponseSchema = z.object({\n values: z.array(pullRequestSchema),\n next: z.string().nullable().optional(),\n page: z.number().int().nullable().optional(),\n pagelen: z.number().int().nullable().optional(),\n});\n\nconst pipelineStateSchema = z.object({\n name: z.string().min(1),\n type: z.string().nullable().optional(),\n result: z\n .object({ name: z.string().nullable().optional() })\n .nullable()\n .optional(),\n});\n\nconst pipelineTargetSchema = z.object({\n ref_name: z.string().nullable().optional(),\n commit: z\n .object({ hash: z.string().nullable().optional() })\n .nullable()\n .optional(),\n selector: z\n .object({\n type: z.string().nullable().optional(),\n pattern: z.string().nullable().optional(),\n })\n .nullable()\n .optional(),\n});\n\nconst pipelineSchema = z.object({\n uuid: z.string().min(1),\n build_number: z.number().int().nonnegative(),\n state: pipelineStateSchema,\n creator: accountRefSchema.nullable().optional(),\n target: pipelineTargetSchema.nullable().optional(),\n trigger: z\n .object({\n name: z.string().nullable().optional(),\n type: z.string().nullable().optional(),\n })\n .nullable()\n .optional(),\n created_on: z.iso.datetime(),\n completed_on: z.iso.datetime().nullable().optional(),\n duration_in_seconds: z.number().nullable().optional(),\n build_seconds_used: z.number().nullable().optional(),\n});\n\nconst pipelinesResponseSchema = z.object({\n values: z.array(pipelineSchema),\n next: z.string().nullable().optional(),\n page: z.number().int().nullable().optional(),\n pagelen: z.number().int().nullable().optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Resource definitions\n// ---------------------------------------------------------------------------\n\nexport const bitbucketResources = defineResources({\n pull_request: {\n shape: 'entity',\n description:\n 'Open, merged, declined, and superseded pull requests with author, source/target branches, and close timestamp.',\n endpoint:\n 'GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests?state=OPEN,MERGED,DECLINED,SUPERSEDED',\n notes:\n 'Paginated newest-first by `updated_on`; the connector stops once a page is entirely older than `options.since`.',\n filterable: [\n {\n field: 'state',\n ops: ['eq'],\n values: ['OPEN', 'MERGED', 'DECLINED', 'SUPERSEDED'],\n },\n ],\n responses: { pull_requests: pullRequestsResponseSchema },\n },\n pipeline: {\n shape: 'entity',\n description:\n 'Bitbucket Pipelines runs with state, result, target ref/commit, trigger, duration, and create/complete timestamps.',\n endpoint: 'GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/',\n notes:\n 'Paginated newest-first by `created_on`; the connector stops once a page is entirely older than `options.since`.',\n filterable: [],\n responses: { pipelines: pipelinesResponseSchema },\n },\n pipeline_event: {\n shape: 'event',\n description:\n 'Pipeline lifecycle events. One event per pipeline covering created_on to completed_on (or updated_on if not yet finished), tagged with the terminal state and result.',\n endpoint: 'GET /2.0/repositories/{workspace}/{repo_slug}/pipelines/',\n notes:\n 'Derived from the same pipelines response that builds the `pipeline` resource; the Bitbucket API does not expose an intermediate state-transition history endpoint.',\n filterable: [],\n },\n});\n\nexport const id = 'bitbucket';\n\n// ---------------------------------------------------------------------------\n// Connector class\n// ---------------------------------------------------------------------------\n\ninterface RepoBatch<T> {\n repoSlug: string;\n items: T[];\n}\n\nconst PULL_REQUEST_STATES = new Set([\n 'OPEN',\n 'MERGED',\n 'DECLINED',\n 'SUPERSEDED',\n]);\n\nfunction pushablePullRequestState(\n filter: FilterClause[] | undefined,\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 === 'state' &&\n clause.op === 'eq' &&\n typeof clause.value === 'string' &&\n PULL_REQUEST_STATES.has(clause.value)\n ) {\n return clause.value;\n }\n }\n return null;\n}\n\nexport class BitbucketConnector extends BaseConnector<\n BitbucketSettings,\n BitbucketCredentials\n> {\n static readonly id = id;\n\n static readonly resources = bitbucketResources;\n\n static readonly schemas = schemasFromResources(bitbucketResources);\n\n static create(input: unknown, ctx?: ConnectorContext): BitbucketConnector {\n const parsed = configFields.parse(input);\n return new BitbucketConnector(\n {\n workspace: parsed.workspace,\n repoSlugs: parsed.repoSlugs,\n resources: parsed.resources,\n },\n { username: parsed.username, appPassword: parsed.appPassword },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = bitbucketCredentials;\n\n private buildHeaders(): Record<string, string> {\n const basic = btoa(`${this.creds.username}:${this.creds.appPassword}`);\n return {\n Authorization: `Basic ${basic}`,\n Accept: 'application/json',\n 'User-Agent': connectorUserAgent('bitbucket'),\n };\n }\n\n private fetch<T>(\n url: string,\n resource: string,\n signal: AbortSignal | undefined,\n ): Promise<HttpResponse<T>> {\n return this.get<T>(url, {\n resource,\n headers: this.buildHeaders(),\n signal,\n });\n }\n\n private sanitizeUrl(url: string | null, expectedPath: string): string | null {\n if (!url) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== 'https:' || u.host !== API_HOST) {\n return null;\n }\n if (u.pathname !== expectedPath) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n }\n\n private static readonly PHASE_RESOURCES: Record<\n BitbucketPhase,\n readonly BitbucketResource[]\n > = {\n pull_requests: ['pull_request'],\n pipelines: ['pipeline', 'pipeline_event'],\n };\n\n private activePhases(\n optionsResources: ReadonlySet<string> | undefined,\n ): BitbucketPhase[] {\n const fromSettings = selectActivePhases<BitbucketResource, BitbucketPhase>(\n (r) => {\n switch (r) {\n case 'pull_request':\n return 'pull_requests';\n case 'pipeline':\n case 'pipeline_event':\n return 'pipelines';\n }\n },\n PHASE_ORDER,\n this.settings.resources,\n );\n if (optionsResources === undefined) {\n return fromSettings;\n }\n return fromSettings.filter((phase) =>\n BitbucketConnector.PHASE_RESOURCES[phase].some((r) =>\n optionsResources.has(r),\n ),\n );\n }\n\n private isResourceAllowed(\n resource: BitbucketResource,\n optionsResources: ReadonlySet<string> | undefined,\n ): boolean {\n const fromSettings = this.settings.resources;\n if (\n fromSettings &&\n fromSettings.length > 0 &&\n !fromSettings.includes(resource)\n ) {\n return false;\n }\n if (optionsResources !== undefined && !optionsResources.has(resource)) {\n return false;\n }\n return true;\n }\n\n // -------------------------------------------------------------------------\n // URL builders\n // -------------------------------------------------------------------------\n\n private buildPullRequestsUrl(\n repoSlugValue: string,\n options: SyncOptions,\n spec?: FetchSpec,\n ): string {\n const u = new URL(\n `${API_BASE}/2.0/repositories/${encodeURIComponent(this.settings.workspace)}/${encodeURIComponent(repoSlugValue)}/pullrequests`,\n );\n u.searchParams.set('pagelen', String(PAGE_SIZE));\n u.searchParams.set('sort', '-updated_on');\n u.searchParams.set(\n 'state',\n pushablePullRequestState(spec?.filter) ??\n 'OPEN,MERGED,DECLINED,SUPERSEDED',\n );\n if (options.since) {\n u.searchParams.set('q', `updated_on >= ${options.since}`);\n }\n return u.toString();\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 buildPipelinesUrl(\n repoSlugValue: string,\n options: SyncOptions,\n ): string {\n const u = new URL(\n `${API_BASE}/2.0/repositories/${encodeURIComponent(this.settings.workspace)}/${encodeURIComponent(repoSlugValue)}/pipelines/`,\n );\n u.searchParams.set('pagelen', String(PAGE_SIZE));\n u.searchParams.set('sort', '-created_on');\n if (options.since) {\n u.searchParams.set('q', `created_on >= ${options.since}`);\n }\n return u.toString();\n }\n\n private pullRequestsPath(repoSlugValue: string): string {\n return `/2.0/repositories/${this.settings.workspace}/${repoSlugValue}/pullrequests`;\n }\n\n private pipelinesPath(repoSlugValue: string): string {\n return `/2.0/repositories/${this.settings.workspace}/${repoSlugValue}/pipelines/`;\n }\n\n // -------------------------------------------------------------------------\n // Fetchers\n // -------------------------------------------------------------------------\n\n private async fetchPullRequestsPage(\n options: SyncOptions,\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<FetchPageResult<string>> {\n const repos = this.settings.repoSlugs;\n if (repos.length === 0) {\n return { items: [], next: null };\n }\n const { idx, url: rawPageUrl } = decodePage(page);\n if (idx >= repos.length) {\n return { items: [], next: null };\n }\n const slug = repos[idx]!;\n const expectedPath = this.pullRequestsPath(slug);\n const fetchUrl =\n this.sanitizeUrl(rawPageUrl, expectedPath) ??\n this.buildPullRequestsUrl(\n slug,\n options,\n this.singleSpec(options, 'pull_request'),\n );\n const res = await this.fetch<BitbucketPullRequestsResponse>(\n fetchUrl,\n 'pull_requests',\n signal,\n );\n const rows = res.body.values;\n const cutoff = options.since ? parseEpoch(options.since, 'iso') : null;\n let filtered: BitbucketPullRequest[];\n let cutoffReached: boolean;\n if (cutoff !== null) {\n filtered = rows.filter((pr) => {\n const ts = parseEpoch(pr.updated_on, 'iso');\n return ts === null || ts >= cutoff;\n });\n const last = rows.at(-1);\n const lastTs = last ? parseEpoch(last.updated_on, 'iso') : null;\n cutoffReached = lastTs !== null && lastTs < cutoff;\n } else {\n filtered = rows;\n cutoffReached = false;\n }\n const safeNext = this.sanitizeUrl(res.body.next ?? null, expectedPath);\n const nextWithinRepo = cutoffReached ? null : safeNext;\n const batch: RepoBatch<BitbucketPullRequest> = {\n repoSlug: slug,\n items: filtered,\n };\n if (nextWithinRepo !== null) {\n return { items: [batch], next: encodePage(idx, nextWithinRepo) };\n }\n const nextIdx = idx + 1;\n const next = nextIdx < repos.length ? encodePage(nextIdx, null) : null;\n return { items: [batch], next };\n }\n\n private async fetchPipelinesPage(\n options: SyncOptions,\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<FetchPageResult<string>> {\n const repos = this.settings.repoSlugs;\n if (repos.length === 0) {\n return { items: [], next: null };\n }\n const { idx, url: rawPageUrl } = decodePage(page);\n if (idx >= repos.length) {\n return { items: [], next: null };\n }\n const slug = repos[idx]!;\n const expectedPath = this.pipelinesPath(slug);\n const fetchUrl =\n this.sanitizeUrl(rawPageUrl, expectedPath) ??\n this.buildPipelinesUrl(slug, options);\n const res = await this.fetch<BitbucketPipelinesResponse>(\n fetchUrl,\n 'pipelines',\n signal,\n );\n const rows = res.body.values;\n const cutoff = options.since ? parseEpoch(options.since, 'iso') : null;\n let filtered: BitbucketPipeline[];\n let cutoffReached: boolean;\n if (cutoff !== null) {\n filtered = rows.filter((p) => {\n const ts = parseEpoch(p.created_on, 'iso');\n return ts === null || ts >= cutoff;\n });\n const last = rows.at(-1);\n const lastTs = last ? parseEpoch(last.created_on, 'iso') : null;\n cutoffReached = lastTs !== null && lastTs < cutoff;\n } else {\n filtered = rows;\n cutoffReached = false;\n }\n const safeNext = this.sanitizeUrl(res.body.next ?? null, expectedPath);\n const nextWithinRepo = cutoffReached ? null : safeNext;\n const batch: RepoBatch<BitbucketPipeline> = {\n repoSlug: slug,\n items: filtered,\n };\n if (nextWithinRepo !== null) {\n return { items: [batch], next: encodePage(idx, nextWithinRepo) };\n }\n const nextIdx = idx + 1;\n const next = nextIdx < repos.length ? encodePage(nextIdx, null) : null;\n return { items: [batch], next };\n }\n\n // -------------------------------------------------------------------------\n // Writers\n // -------------------------------------------------------------------------\n\n private async writePullRequests(\n storage: StorageHandle,\n items: unknown[],\n page: string | null,\n options: SyncOptions,\n ): Promise<void> {\n if (page === null && !options.since) {\n await storage.entities([], { types: ['pull_request'] });\n }\n const batches = items as RepoBatch<BitbucketPullRequest>[];\n for (const batch of batches) {\n for (const pr of batch.items) {\n const createdMs = parseEpoch(pr.created_on, 'iso');\n const updatedMs = parseEpoch(pr.updated_on, 'iso');\n if (createdMs === null || updatedMs === null) {\n continue;\n }\n await storage.entity({\n type: 'pull_request',\n id: `${this.settings.workspace}/${batch.repoSlug}:${pr.id}`,\n attributes: {\n workspace: this.settings.workspace,\n repo_slug: batch.repoSlug,\n pull_request_id: pr.id,\n title: pr.title,\n state: pr.state,\n author: pr.author?.nickname ?? pr.author?.display_name ?? null,\n source_branch: pr.source?.branch?.name ?? null,\n destination_branch: pr.destination?.branch?.name ?? null,\n web_url: pr.links?.html?.href ?? null,\n created_at: createdMs,\n closed_at: parseEpoch(pr.closed_on ?? null, 'iso'),\n },\n updated_at: updatedMs,\n });\n }\n }\n }\n\n private async writePipelines(\n storage: StorageHandle,\n items: unknown[],\n page: string | null,\n options: SyncOptions,\n seenPipelineIds: Set<string>,\n ): Promise<void> {\n const pipelineAllowed = this.isResourceAllowed(\n 'pipeline',\n options.resources,\n );\n const eventAllowed = this.isResourceAllowed(\n 'pipeline_event',\n options.resources,\n );\n if (page === null && !options.since) {\n if (pipelineAllowed) {\n await storage.entities([], { types: ['pipeline'] });\n }\n if (eventAllowed) {\n await storage.events([], { names: ['pipeline_event'] });\n }\n }\n const batches = items as RepoBatch<BitbucketPipeline>[];\n for (const batch of batches) {\n for (const pipeline of batch.items) {\n const createdMs = parseEpoch(pipeline.created_on, 'iso');\n if (createdMs === null) {\n continue;\n }\n const entityId = `${this.settings.workspace}/${batch.repoSlug}:${pipeline.uuid}`;\n if (seenPipelineIds.has(entityId)) {\n continue;\n }\n seenPipelineIds.add(entityId);\n const completedMs = parseEpoch(pipeline.completed_on ?? null, 'iso');\n const durationMs =\n pipeline.duration_in_seconds !== null &&\n pipeline.duration_in_seconds !== undefined\n ? Math.round(pipeline.duration_in_seconds * 1000)\n : completedMs !== null\n ? completedMs - createdMs\n : null;\n const result = pipeline.state.result?.name ?? null;\n const refName = pipeline.target?.ref_name ?? null;\n const commitHash = pipeline.target?.commit?.hash ?? null;\n const triggerType = pipeline.trigger?.type ?? null;\n if (pipelineAllowed) {\n await storage.entity({\n type: 'pipeline',\n id: entityId,\n attributes: {\n workspace: this.settings.workspace,\n repo_slug: batch.repoSlug,\n uuid: pipeline.uuid,\n build_number: pipeline.build_number,\n state: pipeline.state.name,\n result,\n ref_name: refName,\n commit: commitHash,\n trigger_type: triggerType,\n creator:\n pipeline.creator?.nickname ??\n pipeline.creator?.display_name ??\n null,\n created_at: createdMs,\n completed_at: completedMs,\n duration_ms: durationMs,\n },\n updated_at: completedMs ?? createdMs,\n });\n }\n if (eventAllowed) {\n await storage.event({\n name: 'pipeline_event',\n start_ts: createdMs,\n end_ts: completedMs,\n attributes: {\n workspace: this.settings.workspace,\n repo_slug: batch.repoSlug,\n uuid: pipeline.uuid,\n build_number: pipeline.build_number,\n state: pipeline.state.name,\n result,\n ref_name: refName,\n commit: commitHash,\n trigger_type: triggerType,\n duration_ms: durationMs,\n },\n });\n }\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Cursor resume\n // -------------------------------------------------------------------------\n\n private resolveCursor(cursor: unknown): BitbucketSyncCursor | undefined {\n if (!isBitbucketSyncCursor(cursor)) {\n return undefined;\n }\n return { phase: cursor.phase, page: cursor.page };\n }\n\n // -------------------------------------------------------------------------\n // sync()\n // -------------------------------------------------------------------------\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = this.resolveCursor(options.cursor);\n const phases = this.activePhases(options.resources);\n const seenPipelineIds = new Set<string>();\n return paginateChunked<BitbucketPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n switch (phase) {\n case 'pull_requests':\n return this.fetchPullRequestsPage(options, page, sig);\n case 'pipelines':\n return this.fetchPipelinesPage(options, page, sig);\n }\n },\n writeBatch: async (phase, items, page) => {\n switch (phase) {\n case 'pull_requests':\n return this.writePullRequests(storage, items, page, options);\n case 'pipelines':\n return this.writePipelines(\n storage,\n items,\n page,\n options,\n seenPipelineIds,\n );\n }\n },\n });\n }\n}\n","import { BitbucketConnector } from './bitbucket';\n\nexport {\n bitbucketResources as resources,\n BitbucketConnector,\n configFields,\n doc,\n id,\n} from './bitbucket';\nexport type { BitbucketResource, BitbucketSettings } from './bitbucket';\nexport default BitbucketConnector;\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rawdash/connector-bitbucket",
3
- "version": "0.21.1",
3
+ "version": "0.23.0",
4
4
  "description": "Rawdash connector for Bitbucket Cloud — pull requests, pipelines, and pipeline state-transition events",
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.21.1"
27
+ "@rawdash/core": "0.23.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "fast-check": "^4.8.0",
@@ -32,7 +32,7 @@
32
32
  "typescript": "^5.7.2",
33
33
  "vitest": "^4.1.4",
34
34
  "@rawdash/connector-shared": "0.3.1",
35
- "@rawdash/connector-test-utils": "0.0.9"
35
+ "@rawdash/connector-test-utils": "0.0.10"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsup",