@rawdash/connector-gitlab 0.26.0 → 0.28.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/README.md CHANGED
@@ -42,6 +42,7 @@ A GitLab Personal Access Token (PAT) with the `read_api` scope is required. The
42
42
  - Endpoint: `GET /api/v4/projects/{id}/merge_requests`
43
43
  - **`pipeline`** _(entity)_ - CI/CD pipelines with status, ref, commit sha, source, duration, and start/finish timestamps.
44
44
  - Endpoint: `GET /api/v4/projects/{id}/pipelines`
45
+ - The pipelines list response omits duration and finished_at; each pipeline is enriched via GET /api/v4/projects/{id}/pipelines/{pipeline_id} to populate duration and finish time.
45
46
  - **`pipeline_event`** _(event)_ - Pipeline lifecycle events. One event per pipeline covering created_at to finished_at (or updated_at if not yet finished), tagged with the terminal status.
46
47
  - Endpoint: `GET /api/v4/projects/{id}/pipelines`
47
48
  - Derived from the same pipelines response that builds the `pipeline` resource; the GitLab API does not expose an intermediate state-transition history endpoint.
package/dist/index.d.ts CHANGED
@@ -95,6 +95,7 @@ declare const gitlabResources: {
95
95
  readonly shape: "entity";
96
96
  readonly description: "CI/CD pipelines with status, ref, commit sha, source, duration, and start/finish timestamps.";
97
97
  readonly endpoint: "GET /api/v4/projects/{id}/pipelines";
98
+ readonly notes: "The pipelines list response omits duration and finished_at; each pipeline is enriched via GET /api/v4/projects/{id}/pipelines/{pipeline_id} to populate duration and finish time.";
98
99
  readonly filterable: [{
99
100
  readonly field: "status";
100
101
  readonly ops: ["eq"];
@@ -245,6 +246,7 @@ declare class GitLabConnector extends BaseConnector<GitLabSettings, GitLabCreden
245
246
  readonly shape: "entity";
246
247
  readonly description: "CI/CD pipelines with status, ref, commit sha, source, duration, and start/finish timestamps.";
247
248
  readonly endpoint: "GET /api/v4/projects/{id}/pipelines";
249
+ readonly notes: "The pipelines list response omits duration and finished_at; each pipeline is enriched via GET /api/v4/projects/{id}/pipelines/{pipeline_id} to populate duration and finish time.";
248
250
  readonly filterable: [{
249
251
  readonly field: "status";
250
252
  readonly ops: ["eq"];
@@ -446,6 +448,8 @@ declare class GitLabConnector extends BaseConnector<GitLabSettings, GitLabCreden
446
448
  private resolveEffectiveProjectIds;
447
449
  private fetchGroupProjects;
448
450
  private fetchProjectMetadata;
451
+ private fetchPipelineDetail;
452
+ private enrichPipelineBatches;
449
453
  private fetchProjectsPhase;
450
454
  private singleSpec;
451
455
  private buildListPageUrl;
package/dist/index.js CHANGED
@@ -42,6 +42,36 @@ function standardRateLimitPolicy(config) {
42
42
  }
43
43
  };
44
44
  }
45
+ async function mapWithConcurrency(items, concurrency, fn) {
46
+ const results = new Array(items.length);
47
+ if (items.length === 0) {
48
+ return results;
49
+ }
50
+ const normalized = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;
51
+ const limit = Math.max(1, Math.min(normalized, items.length));
52
+ let next = 0;
53
+ let failed = false;
54
+ async function worker() {
55
+ while (!failed) {
56
+ const i = next++;
57
+ if (i >= items.length) {
58
+ return;
59
+ }
60
+ try {
61
+ results[i] = await fn(items[i], i);
62
+ } catch (err) {
63
+ failed = true;
64
+ throw err;
65
+ }
66
+ }
67
+ }
68
+ const workers = [];
69
+ for (let w = 0; w < limit; w++) {
70
+ workers.push(worker());
71
+ }
72
+ await Promise.all(workers);
73
+ return results;
74
+ }
45
75
  function parseLinkHeader(header) {
46
76
  if (!header) {
47
77
  return {};
@@ -150,6 +180,7 @@ var gitlabCredentials = {
150
180
  };
151
181
  var DEFAULT_HOST = "gitlab.com";
152
182
  var PAGE_SIZE = 100;
183
+ var PIPELINE_DETAIL_CONCURRENCY = 5;
153
184
  var gitlabRateLimit = standardRateLimitPolicy({
154
185
  remainingHeader: "ratelimit-remaining",
155
186
  resetHeader: "ratelimit-reset",
@@ -283,6 +314,7 @@ var gitlabResources = defineResources({
283
314
  shape: "entity",
284
315
  description: "CI/CD pipelines with status, ref, commit sha, source, duration, and start/finish timestamps.",
285
316
  endpoint: "GET /api/v4/projects/{id}/pipelines",
317
+ notes: "The pipelines list response omits duration and finished_at; each pipeline is enriched via GET /api/v4/projects/{id}/pipelines/{pipeline_id} to populate duration and finish time.",
286
318
  filterable: [{ field: "status", ops: ["eq"] }],
287
319
  responses: { pipelines: pipelinesResponseSchema }
288
320
  },
@@ -485,6 +517,44 @@ var GitLabConnector = class _GitLabConnector extends BaseConnector {
485
517
  this.projectMetadataCache.set(projectId, res.body);
486
518
  return res.body;
487
519
  }
520
+ async fetchPipelineDetail(projectId, pipelineId, signal) {
521
+ const url = `${this.apiBase()}/projects/${projectId}/pipelines/${pipelineId}`;
522
+ try {
523
+ const res = await this.fetch(
524
+ url,
525
+ "pipeline_detail",
526
+ signal
527
+ );
528
+ return res.body;
529
+ } catch (err) {
530
+ const status = err?.response?.status;
531
+ if (status === 404 || status === 410) {
532
+ return null;
533
+ }
534
+ throw err;
535
+ }
536
+ }
537
+ async enrichPipelineBatches(items, signal) {
538
+ const batches = items;
539
+ for (const batch of batches) {
540
+ await mapWithConcurrency(
541
+ batch.items,
542
+ PIPELINE_DETAIL_CONCURRENCY,
543
+ async (pipeline) => {
544
+ const detail = await this.fetchPipelineDetail(
545
+ batch.projectId,
546
+ pipeline.id,
547
+ signal
548
+ );
549
+ if (detail) {
550
+ pipeline.started_at = detail.started_at ?? null;
551
+ pipeline.finished_at = detail.finished_at ?? null;
552
+ pipeline.duration = detail.duration ?? null;
553
+ }
554
+ }
555
+ );
556
+ }
557
+ }
488
558
  async fetchProjectsPhase(page, signal) {
489
559
  const projects = await this.resolveEffectiveProjectIds(signal);
490
560
  if (projects.length === 0) {
@@ -786,14 +856,17 @@ var GitLabConnector = class _GitLabConnector extends BaseConnector {
786
856
  "merge_requests",
787
857
  (mr) => new Date(mr.updated_at).getTime()
788
858
  );
789
- case "pipelines":
790
- return this.fetchListPhase(
859
+ case "pipelines": {
860
+ const result = await this.fetchListPhase(
791
861
  options,
792
862
  page,
793
863
  sig,
794
864
  "pipelines",
795
865
  (p) => new Date(p.updated_at).getTime()
796
866
  );
867
+ await this.enrichPipelineBatches(result.items, sig);
868
+ return result;
869
+ }
797
870
  case "issues":
798
871
  return this.fetchListPhase(
799
872
  options,
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/gitlab.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 parseLinkHeader,\n standardRateLimitPolicy,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ChunkedSyncCursor,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type Event,\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\nconst positiveInt = z.number().int().positive();\n\nexport const configFields = defineConfigFields(\n z\n .object({\n apiToken: z.object({ $secret: z.string() }).meta({\n label: 'API Token',\n description:\n 'GitLab Personal Access Token with `read_api` scope. Create one at GitLab -> Preferences -> Access Tokens.',\n placeholder: 'glpat-...',\n secret: true,\n }),\n host: z\n .string()\n .min(1)\n .regex(\n /^[^/\\s:?#]+$/,\n 'Use host only (no protocol, port, path, or query).',\n )\n .optional()\n .meta({\n label: 'Host (optional)',\n description:\n 'Your GitLab host. Defaults to `gitlab.com`. For self-hosted, supply the hostname only (e.g. `gitlab.example.com`).',\n placeholder: 'gitlab.com',\n }),\n projectIds: z.array(positiveInt).nonempty().optional().meta({\n label: 'Project IDs (optional)',\n description:\n 'Numeric project IDs to sync directly (find one in Project -> Settings -> General). Combined with any projects discovered via `groupIds`.',\n }),\n groupIds: z.array(positiveInt).nonempty().optional().meta({\n label: 'Group IDs (optional)',\n description:\n 'Numeric group IDs whose projects (including subgroups) will be discovered and synced.',\n }),\n resources: z\n .array(\n z.enum([\n 'project',\n 'merge_request',\n 'pipeline',\n 'pipeline_event',\n 'issue',\n 'release',\n ]),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n \"Which GitLab 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 .refine(\n (v) =>\n (v.projectIds && v.projectIds.length > 0) ||\n (v.groupIds && v.groupIds.length > 0),\n {\n message: 'At least one of `projectIds` or `groupIds` must be provided.',\n path: ['projectIds'],\n },\n ),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'GitLab',\n category: 'engineering',\n brandColor: '#FC6D26',\n tagline:\n 'Sync projects, merge requests, pipelines, issues, and releases from GitLab.com or a self-hosted GitLab instance.',\n vendor: {\n name: 'GitLab',\n domain: 'gitlab.com',\n apiDocs: 'https://docs.gitlab.com/ee/api/',\n website: 'https://gitlab.com',\n },\n auth: {\n summary:\n 'A GitLab Personal Access Token (PAT) with the `read_api` scope is required. The PAT must belong to an account with read access to the projects and groups you want to sync. Self-hosted GitLab is supported by overriding the `host` field.',\n setup: [\n 'Open GitLab -> User Preferences -> Access Tokens (or the equivalent on your self-hosted instance).',\n 'Create a Personal Access Token with the `read_api` scope.',\n 'Store it as a secret and reference it from the connector config as `apiToken: secret(\"GITLAB_API_TOKEN\")`.',\n 'Set `projectIds` to a list of numeric project IDs, or `groupIds` to a list of numeric group IDs (or both). At least one must be set.',\n 'For self-hosted GitLab, set `host` to your instance hostname (no protocol or path), e.g. `gitlab.example.com`.',\n ],\n },\n rateLimit:\n 'GitLab returns standard `RateLimit-Remaining` / `RateLimit-Reset` headers (reset is a Unix timestamp in seconds); list pagination uses the Link header (page size 100).',\n limitations: [\n 'Container Registry, Packages, and GitLab Duo / AI features are out of scope.',\n 'Pipeline state-transition events are synthesized: one `pipeline_event` is emitted per pipeline lifecycle (created_at to finished_at/updated_at), not one per intermediate state change.',\n 'Group project discovery walks each group with `include_subgroups=true`; very large groups may take multiple sync chunks to enumerate.',\n ],\n});\n\nexport type GitLabResource =\n | 'project'\n | 'merge_request'\n | 'pipeline'\n | 'pipeline_event'\n | 'issue'\n | 'release';\n\nexport interface GitLabSettings {\n host: string;\n projectIds?: readonly number[];\n groupIds?: readonly number[];\n resources?: readonly GitLabResource[];\n}\n\nconst gitlabCredentials = {\n apiToken: {\n description: 'GitLab Personal Access Token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype GitLabCredentials = typeof gitlabCredentials;\n\nconst DEFAULT_HOST = 'gitlab.com';\nconst PAGE_SIZE = 100;\n\nconst gitlabRateLimit = standardRateLimitPolicy({\n remainingHeader: 'ratelimit-remaining',\n resetHeader: 'ratelimit-reset',\n resetUnit: 's',\n});\n\nconst PHASE_ORDER = [\n 'projects',\n 'merge_requests',\n 'pipelines',\n 'issues',\n 'releases',\n] as const;\n\ntype GitLabPhase = (typeof PHASE_ORDER)[number];\n\ntype GitLabSyncCursor = ChunkedSyncCursor<GitLabPhase, string>;\n\nconst isGitLabSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\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\ninterface GitLabUserRef {\n id: number;\n username: string;\n name?: string | null;\n}\n\ninterface GitLabProject {\n id: number;\n name: string;\n path_with_namespace: string;\n default_branch: string | null;\n web_url: string;\n created_at: string;\n last_activity_at?: string | null;\n archived?: boolean;\n visibility?: string;\n}\n\ninterface GitLabMergeRequest {\n id: number;\n iid: number;\n project_id: number;\n title: string;\n state: string;\n draft?: boolean;\n work_in_progress?: boolean;\n author: GitLabUserRef | null;\n assignees?: GitLabUserRef[];\n source_branch: string;\n target_branch: string;\n created_at: string;\n updated_at: string;\n merged_at: string | null;\n closed_at: string | null;\n web_url: string;\n}\n\ninterface GitLabPipeline {\n id: number;\n iid?: number;\n project_id: number;\n status: string;\n ref: string | null;\n sha: string;\n source: string | null;\n created_at: string;\n updated_at: string;\n started_at?: string | null;\n finished_at?: string | null;\n duration?: number | null;\n web_url: string;\n}\n\ninterface GitLabIssue {\n id: number;\n iid: number;\n project_id: number;\n title: string;\n state: string;\n labels: string[];\n author: GitLabUserRef | null;\n assignees?: GitLabUserRef[];\n created_at: string;\n updated_at: string;\n closed_at: string | null;\n web_url: string;\n}\n\ninterface GitLabRelease {\n tag_name: string;\n name: string | null;\n description?: string | null;\n created_at: string;\n released_at: string | null;\n author?: GitLabUserRef | null;\n}\n\nconst userRefSchema = z.object({\n id: z.number().int(),\n username: z.string().min(1),\n name: z.string().nullable().optional(),\n});\n\nconst projectSchema = z.object({\n id: z.number().int(),\n name: z.string().min(1),\n path_with_namespace: z.string().min(1),\n default_branch: z.string().nullable(),\n web_url: z.string(),\n created_at: z.iso.datetime(),\n last_activity_at: z.iso.datetime().nullable().optional(),\n archived: z.boolean().optional(),\n visibility: z.string().optional(),\n});\n\nconst projectsResponseSchema = z.array(projectSchema);\n\nconst mergeRequestSchema = z.object({\n id: z.number().int(),\n iid: z.number().int(),\n project_id: z.number().int(),\n title: z.string(),\n state: z.string().min(1),\n draft: z.boolean().optional(),\n work_in_progress: z.boolean().optional(),\n author: userRefSchema.nullable(),\n assignees: z.array(userRefSchema).optional(),\n source_branch: z.string(),\n target_branch: z.string(),\n created_at: z.iso.datetime(),\n updated_at: z.iso.datetime(),\n merged_at: z.iso.datetime().nullable(),\n closed_at: z.iso.datetime().nullable(),\n web_url: z.string(),\n});\n\nconst mergeRequestsResponseSchema = z.array(mergeRequestSchema);\n\nconst pipelineSchema = z.object({\n id: z.number().int(),\n iid: z.number().int().optional(),\n project_id: z.number().int(),\n status: z.string().min(1),\n ref: z.string().nullable(),\n sha: z.string().min(1),\n source: z.string().nullable(),\n created_at: z.iso.datetime(),\n updated_at: z.iso.datetime(),\n started_at: z.iso.datetime().nullable().optional(),\n finished_at: z.iso.datetime().nullable().optional(),\n duration: z.number().nullable().optional(),\n web_url: z.string(),\n});\n\nconst pipelinesResponseSchema = z.array(pipelineSchema);\n\nconst issueSchema = z.object({\n id: z.number().int(),\n iid: z.number().int(),\n project_id: z.number().int(),\n title: z.string(),\n state: z.string().min(1),\n labels: z.array(z.string()),\n author: userRefSchema.nullable(),\n assignees: z.array(userRefSchema).optional(),\n created_at: z.iso.datetime(),\n updated_at: z.iso.datetime(),\n closed_at: z.iso.datetime().nullable(),\n web_url: z.string(),\n});\n\nconst issuesResponseSchema = z.array(issueSchema);\n\nconst releaseSchema = z.object({\n tag_name: z.string().min(1),\n name: z.string().nullable(),\n description: z.string().nullable().optional(),\n created_at: z.iso.datetime(),\n released_at: z.iso.datetime().nullable(),\n author: userRefSchema.nullable().optional(),\n});\n\nconst releasesResponseSchema = z.array(releaseSchema);\n\nexport const gitlabResources = defineResources({\n project: {\n shape: 'entity',\n description:\n 'GitLab projects (repositories) with namespace path, default branch, and archived/visibility flags.',\n endpoint: 'GET /api/v4/projects/{id}',\n notes:\n 'Discovered from configured `projectIds` and from `groupIds` via GET /api/v4/groups/{id}/projects?include_subgroups=true.',\n filterable: [],\n responses: { projects: projectsResponseSchema },\n },\n merge_request: {\n shape: 'entity',\n description:\n 'Open, merged, and closed merge requests with author, source/target branches, and merge timestamps.',\n endpoint: 'GET /api/v4/projects/{id}/merge_requests',\n filterable: [\n {\n field: 'state',\n ops: ['eq'],\n values: ['opened', 'closed', 'merged', 'locked'],\n },\n ],\n responses: { merge_requests: mergeRequestsResponseSchema },\n },\n pipeline: {\n shape: 'entity',\n description:\n 'CI/CD pipelines with status, ref, commit sha, source, duration, and start/finish timestamps.',\n endpoint: 'GET /api/v4/projects/{id}/pipelines',\n filterable: [{ field: 'status', ops: ['eq'] }],\n responses: { pipelines: pipelinesResponseSchema },\n },\n pipeline_event: {\n shape: 'event',\n description:\n 'Pipeline lifecycle events. One event per pipeline covering created_at to finished_at (or updated_at if not yet finished), tagged with the terminal status.',\n endpoint: 'GET /api/v4/projects/{id}/pipelines',\n notes:\n 'Derived from the same pipelines response that builds the `pipeline` resource; the GitLab API does not expose an intermediate state-transition history endpoint.',\n filterable: [],\n },\n issue: {\n shape: 'entity',\n description:\n 'Open and closed issues with labels, author, assignees, and close timestamp.',\n endpoint: 'GET /api/v4/projects/{id}/issues',\n filterable: [{ field: 'state', ops: ['eq'], values: ['opened', 'closed'] }],\n responses: { issues: issuesResponseSchema },\n },\n release: {\n shape: 'entity',\n description:\n 'Project releases keyed by tag name, including released_at and the publishing author.',\n endpoint: 'GET /api/v4/projects/{id}/releases',\n filterable: [],\n responses: { releases: releasesResponseSchema },\n },\n});\n\nexport const id = 'gitlab';\n\nconst LIST_RESOURCE_DEF_KEY: Record<\n 'merge_requests' | 'pipelines' | 'issues' | 'releases',\n string\n> = {\n merge_requests: 'merge_request',\n pipelines: 'pipeline',\n issues: 'issue',\n releases: 'release',\n};\n\nfunction pushableEq(\n filter: FilterClause[] | undefined,\n field: string,\n): string | null {\n if (!filter) {\n return null;\n }\n for (const clause of filter) {\n if (\n 'field' in clause &&\n clause.field === field &&\n clause.op === 'eq' &&\n typeof clause.value === 'string'\n ) {\n return clause.value;\n }\n }\n return null;\n}\n\ninterface ProjectBatch<T> {\n projectId: number;\n items: T[];\n}\n\nexport class GitLabConnector extends BaseConnector<\n GitLabSettings,\n GitLabCredentials\n> {\n static readonly id = id;\n\n static readonly resources = gitlabResources;\n\n static readonly schemas = schemasFromResources(gitlabResources);\n\n static create(input: unknown, ctx?: ConnectorContext): GitLabConnector {\n const parsed = configFields.parse(input);\n return new GitLabConnector(\n {\n host: parsed.host ?? DEFAULT_HOST,\n projectIds: parsed.projectIds,\n groupIds: parsed.groupIds,\n resources: parsed.resources,\n },\n { apiToken: parsed.apiToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = gitlabCredentials;\n\n private effectiveProjectIds: number[] | null = null;\n private projectMetadataCache = new Map<number, GitLabProject>();\n\n constructor(\n settings: GitLabSettings,\n creds?: { apiToken: { $secret: string } | string },\n ctx?: ConnectorContext,\n ) {\n super({ ...settings, host: settings.host || DEFAULT_HOST }, creds, ctx);\n }\n\n private buildHeaders(): Record<string, string> {\n return {\n 'PRIVATE-TOKEN': this.creds.apiToken,\n Accept: 'application/json',\n 'User-Agent': connectorUserAgent('gitlab'),\n };\n }\n\n private apiBase(): string {\n return `https://${this.settings.host}/api/v4`;\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 rateLimit: gitlabRateLimit,\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 !== this.settings.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 GitLabPhase,\n readonly GitLabResource[]\n > = {\n projects: ['project'],\n merge_requests: ['merge_request'],\n pipelines: ['pipeline', 'pipeline_event'],\n issues: ['issue'],\n releases: ['release'],\n };\n\n private activePhases(\n optionsResources: ReadonlySet<string> | undefined,\n ): GitLabPhase[] {\n const fromSettings = selectActivePhases<GitLabResource, GitLabPhase>(\n (r) => {\n switch (r) {\n case 'project':\n return 'projects';\n case 'merge_request':\n return 'merge_requests';\n case 'pipeline':\n case 'pipeline_event':\n return 'pipelines';\n case 'issue':\n return 'issues';\n case 'release':\n return 'releases';\n }\n },\n PHASE_ORDER,\n this.settings.resources,\n );\n if (optionsResources === undefined) {\n return fromSettings;\n }\n return fromSettings.filter((phase) =>\n GitLabConnector.PHASE_RESOURCES[phase].some((r) =>\n optionsResources.has(r),\n ),\n );\n }\n\n private isResourceAllowed(\n resource: GitLabResource,\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 private async resolveEffectiveProjectIds(\n signal: AbortSignal | undefined,\n ): Promise<number[]> {\n if (this.effectiveProjectIds !== null) {\n return this.effectiveProjectIds;\n }\n const seen = new Set<number>();\n const ordered: number[] = [];\n const addId = (n: number) => {\n if (!seen.has(n)) {\n seen.add(n);\n ordered.push(n);\n }\n };\n for (const pid of this.settings.projectIds ?? []) {\n addId(pid);\n }\n for (const gid of this.settings.groupIds ?? []) {\n const projects = await this.fetchGroupProjects(gid, signal);\n for (const p of projects) {\n this.projectMetadataCache.set(p.id, p);\n addId(p.id);\n }\n }\n ordered.sort((a, b) => a - b);\n this.effectiveProjectIds = ordered;\n return ordered;\n }\n\n private async fetchGroupProjects(\n groupId: number,\n signal: AbortSignal | undefined,\n ): Promise<GitLabProject[]> {\n const out: GitLabProject[] = [];\n const baseUrl = `${this.apiBase()}/groups/${groupId}/projects`;\n let url: string | null =\n `${baseUrl}?per_page=${PAGE_SIZE}&include_subgroups=true&archived=false`;\n const expectedPath = `/api/v4/groups/${groupId}/projects`;\n while (url !== null) {\n const res = await this.fetch<GitLabProject[]>(\n url,\n 'group_projects',\n signal,\n );\n for (const project of res.body) {\n out.push(project);\n }\n const next = parseLinkHeader(res.headers.get('link'))['next'] ?? null;\n url = this.sanitizeUrl(next, expectedPath);\n }\n return out;\n }\n\n private async fetchProjectMetadata(\n projectId: number,\n signal: AbortSignal | undefined,\n ): Promise<GitLabProject | null> {\n if (this.projectMetadataCache.has(projectId)) {\n return this.projectMetadataCache.get(projectId)!;\n }\n const url = `${this.apiBase()}/projects/${projectId}`;\n const res = await this.fetch<GitLabProject>(url, 'project', signal);\n this.projectMetadataCache.set(projectId, res.body);\n return res.body;\n }\n\n private async fetchProjectsPhase(\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<FetchPageResult<string>> {\n const projects = await this.resolveEffectiveProjectIds(signal);\n if (projects.length === 0) {\n return { items: [], next: null };\n }\n const { idx } = decodePage(page);\n if (idx >= projects.length) {\n return { items: [], next: null };\n }\n const projectId = projects[idx]!;\n const project = await this.fetchProjectMetadata(projectId, signal);\n const nextIdx = idx + 1;\n const next = nextIdx < projects.length ? encodePage(nextIdx, null) : null;\n return { items: project ? [project] : [], next };\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 buildListPageUrl(\n projectId: number,\n resource: 'merge_requests' | 'pipelines' | 'issues' | 'releases',\n options: SyncOptions,\n spec?: FetchSpec,\n ): string {\n const u = new URL(`${this.apiBase()}/projects/${projectId}/${resource}`);\n u.searchParams.set('per_page', String(PAGE_SIZE));\n switch (resource) {\n case 'merge_requests':\n u.searchParams.set('state', pushableEq(spec?.filter, 'state') ?? 'all');\n u.searchParams.set('order_by', 'updated_at');\n u.searchParams.set('sort', 'desc');\n u.searchParams.set('scope', 'all');\n if (options.since) {\n u.searchParams.set('updated_after', options.since);\n }\n break;\n case 'pipelines': {\n const status = pushableEq(spec?.filter, 'status');\n if (status !== null) {\n u.searchParams.set('status', status);\n }\n u.searchParams.set('order_by', 'updated_at');\n u.searchParams.set('sort', 'desc');\n if (options.since) {\n u.searchParams.set('updated_after', options.since);\n }\n break;\n }\n case 'issues':\n u.searchParams.set('state', pushableEq(spec?.filter, 'state') ?? 'all');\n u.searchParams.set('order_by', 'updated_at');\n u.searchParams.set('sort', 'desc');\n u.searchParams.set('scope', 'all');\n if (options.since) {\n u.searchParams.set('updated_after', options.since);\n }\n break;\n case 'releases':\n u.searchParams.set('order_by', 'released_at');\n u.searchParams.set('sort', 'desc');\n break;\n }\n return u.toString();\n }\n\n private async fetchListPhase<T>(\n options: SyncOptions,\n page: string | null,\n signal: AbortSignal | undefined,\n resource: 'merge_requests' | 'pipelines' | 'issues' | 'releases',\n rowUpdatedAt: (row: T) => number,\n ): Promise<FetchPageResult<string>> {\n const projects = await this.resolveEffectiveProjectIds(signal);\n if (projects.length === 0) {\n return { items: [], next: null };\n }\n const { idx, url: rawPageUrl } = decodePage(page);\n if (idx >= projects.length) {\n return { items: [], next: null };\n }\n const projectId = projects[idx]!;\n const expectedPath = `/api/v4/projects/${projectId}/${resource}`;\n const fetchUrl =\n this.sanitizeUrl(rawPageUrl, expectedPath) ??\n this.buildListPageUrl(\n projectId,\n resource,\n options,\n this.singleSpec(options, LIST_RESOURCE_DEF_KEY[resource]),\n );\n const res = await this.fetch<T[]>(fetchUrl, resource, signal);\n const rawNext = parseLinkHeader(res.headers.get('link'))['next'] ?? null;\n const safeNext = this.sanitizeUrl(rawNext, expectedPath);\n const rows = res.body;\n\n const cutoff = options.since ? new Date(options.since).getTime() : null;\n let filtered: T[];\n let cutoffReached: boolean;\n if (cutoff !== null) {\n filtered = rows.filter((row) => rowUpdatedAt(row) >= cutoff);\n const last = rows.at(-1);\n cutoffReached = last !== undefined && rowUpdatedAt(last) < cutoff;\n } else {\n filtered = rows;\n cutoffReached = false;\n }\n\n const nextWithinProject = cutoffReached ? null : safeNext;\n const batch: ProjectBatch<T> = { projectId, items: filtered };\n if (nextWithinProject !== null) {\n return { items: [batch], next: encodePage(idx, nextWithinProject) };\n }\n const nextIdx = idx + 1;\n const next = nextIdx < projects.length ? encodePage(nextIdx, null) : null;\n return { items: [batch], next };\n }\n\n private async writeProjects(\n storage: StorageHandle,\n items: unknown[],\n page: string | null,\n ): Promise<void> {\n if (page === null) {\n await storage.entities([], { types: ['project'] });\n }\n const projects = items as GitLabProject[];\n for (const project of projects) {\n const updatedAt = new Date(\n project.last_activity_at ?? project.created_at,\n ).getTime();\n await storage.entity({\n type: 'project',\n id: String(project.id),\n attributes: {\n name: project.name,\n path_with_namespace: project.path_with_namespace,\n default_branch: project.default_branch ?? '',\n web_url: project.web_url,\n visibility: project.visibility ?? '',\n archived: project.archived ?? false,\n created_at: new Date(project.created_at).getTime(),\n },\n updated_at: updatedAt,\n });\n }\n }\n\n private async writeMergeRequests(\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: ['merge_request'] });\n }\n const batches = items as ProjectBatch<GitLabMergeRequest>[];\n for (const batch of batches) {\n for (const mr of batch.items) {\n await storage.entity({\n type: 'merge_request',\n id: `${batch.projectId}:${mr.iid}`,\n attributes: {\n project_id: batch.projectId,\n iid: mr.iid,\n title: mr.title,\n state: mr.state,\n draft: mr.draft ?? mr.work_in_progress ?? false,\n author: mr.author?.username ?? '',\n assignees: (mr.assignees ?? []).map((a) => a.username),\n source_branch: mr.source_branch,\n target_branch: mr.target_branch,\n web_url: mr.web_url,\n created_at: new Date(mr.created_at).getTime(),\n merged_at: mr.merged_at ? new Date(mr.merged_at).getTime() : null,\n closed_at: mr.closed_at ? new Date(mr.closed_at).getTime() : null,\n },\n updated_at: new Date(mr.updated_at).getTime(),\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 ProjectBatch<GitLabPipeline>[];\n const eventsById = new Map<string, Event>();\n for (const batch of batches) {\n for (const pipeline of batch.items) {\n const createdMs = new Date(pipeline.created_at).getTime();\n const updatedMs = new Date(pipeline.updated_at).getTime();\n const finishedMs = pipeline.finished_at\n ? new Date(pipeline.finished_at).getTime()\n : null;\n const durationMs =\n pipeline.duration !== null && pipeline.duration !== undefined\n ? Math.round(pipeline.duration * 1000)\n : finishedMs !== null\n ? finishedMs - createdMs\n : null;\n if (pipelineAllowed) {\n await storage.entity({\n type: 'pipeline',\n id: `${batch.projectId}:${pipeline.id}`,\n attributes: {\n project_id: batch.projectId,\n pipeline_id: pipeline.id,\n status: pipeline.status,\n ref: pipeline.ref ?? '',\n sha: pipeline.sha,\n source: pipeline.source ?? '',\n web_url: pipeline.web_url,\n created_at: createdMs,\n finished_at: finishedMs,\n duration_ms: durationMs,\n },\n updated_at: updatedMs,\n });\n }\n if (eventAllowed) {\n eventsById.set(`${batch.projectId}:${pipeline.id}`, {\n name: 'pipeline_event',\n start_ts: createdMs,\n end_ts: finishedMs ?? updatedMs,\n attributes: {\n project_id: batch.projectId,\n pipeline_id: pipeline.id,\n status: pipeline.status,\n ref: pipeline.ref ?? '',\n sha: pipeline.sha,\n source: pipeline.source ?? '',\n duration_ms: durationMs,\n },\n });\n }\n }\n }\n for (const event of eventsById.values()) {\n await storage.event(event);\n }\n }\n\n private async writeIssues(\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: ['issue'] });\n }\n const batches = items as ProjectBatch<GitLabIssue>[];\n for (const batch of batches) {\n for (const issue of batch.items) {\n await storage.entity({\n type: 'issue',\n id: `${batch.projectId}:${issue.iid}`,\n attributes: {\n project_id: batch.projectId,\n iid: issue.iid,\n title: issue.title,\n state: issue.state,\n labels: issue.labels,\n author: issue.author?.username ?? '',\n assignees: (issue.assignees ?? []).map((a) => a.username),\n web_url: issue.web_url,\n created_at: new Date(issue.created_at).getTime(),\n closed_at: issue.closed_at\n ? new Date(issue.closed_at).getTime()\n : null,\n },\n updated_at: new Date(issue.updated_at).getTime(),\n });\n }\n }\n }\n\n private async writeReleases(\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: ['release'] });\n }\n const batches = items as ProjectBatch<GitLabRelease>[];\n for (const batch of batches) {\n for (const release of batch.items) {\n const createdMs = new Date(release.created_at).getTime();\n const releasedMs = release.released_at\n ? new Date(release.released_at).getTime()\n : null;\n await storage.entity({\n type: 'release',\n id: `${batch.projectId}:${release.tag_name}`,\n attributes: {\n project_id: batch.projectId,\n tag_name: release.tag_name,\n name: release.name ?? '',\n description: release.description ?? '',\n author: release.author?.username ?? '',\n created_at: createdMs,\n released_at: releasedMs,\n },\n updated_at: releasedMs ?? createdMs,\n });\n }\n }\n }\n\n private resolveCursor(cursor: unknown): GitLabSyncCursor | undefined {\n if (!isGitLabSyncCursor(cursor)) {\n return undefined;\n }\n return { phase: cursor.phase, page: cursor.page };\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<GitLabPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n switch (phase) {\n case 'projects':\n return this.fetchProjectsPhase(page, sig);\n case 'merge_requests':\n return this.fetchListPhase<GitLabMergeRequest>(\n options,\n page,\n sig,\n 'merge_requests',\n (mr) => new Date(mr.updated_at).getTime(),\n );\n case 'pipelines':\n return this.fetchListPhase<GitLabPipeline>(\n options,\n page,\n sig,\n 'pipelines',\n (p) => new Date(p.updated_at).getTime(),\n );\n case 'issues':\n return this.fetchListPhase<GitLabIssue>(\n options,\n page,\n sig,\n 'issues',\n (i) => new Date(i.updated_at).getTime(),\n );\n case 'releases':\n return this.fetchListPhase<GitLabRelease>(\n options,\n page,\n sig,\n 'releases',\n (r) => new Date(r.released_at ?? r.created_at).getTime(),\n );\n }\n },\n writeBatch: async (phase, items, page) => {\n switch (phase) {\n case 'projects':\n return this.writeProjects(storage, items, page);\n case 'merge_requests':\n return this.writeMergeRequests(storage, items, page, options);\n case 'pipelines':\n return this.writePipelines(storage, items, page, options);\n case 'issues':\n return this.writeIssues(storage, items, page, options);\n case 'releases':\n return this.writeReleases(storage, items, page, options);\n }\n },\n });\n }\n}\n","import { GitLabConnector } from './gitlab';\n\nexport {\n configFields,\n doc,\n GitLabConnector,\n gitlabResources as resources,\n id,\n} from './gitlab';\nexport type { GitLabResource, GitLabSettings } from './gitlab';\nexport default GitLabConnector;\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/gitlab.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 mapWithConcurrency,\n parseLinkHeader,\n standardRateLimitPolicy,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ChunkedSyncCursor,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type Event,\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\nconst positiveInt = z.number().int().positive();\n\nexport const configFields = defineConfigFields(\n z\n .object({\n apiToken: z.object({ $secret: z.string() }).meta({\n label: 'API Token',\n description:\n 'GitLab Personal Access Token with `read_api` scope. Create one at GitLab -> Preferences -> Access Tokens.',\n placeholder: 'glpat-...',\n secret: true,\n }),\n host: z\n .string()\n .min(1)\n .regex(\n /^[^/\\s:?#]+$/,\n 'Use host only (no protocol, port, path, or query).',\n )\n .optional()\n .meta({\n label: 'Host (optional)',\n description:\n 'Your GitLab host. Defaults to `gitlab.com`. For self-hosted, supply the hostname only (e.g. `gitlab.example.com`).',\n placeholder: 'gitlab.com',\n }),\n projectIds: z.array(positiveInt).nonempty().optional().meta({\n label: 'Project IDs (optional)',\n description:\n 'Numeric project IDs to sync directly (find one in Project -> Settings -> General). Combined with any projects discovered via `groupIds`.',\n }),\n groupIds: z.array(positiveInt).nonempty().optional().meta({\n label: 'Group IDs (optional)',\n description:\n 'Numeric group IDs whose projects (including subgroups) will be discovered and synced.',\n }),\n resources: z\n .array(\n z.enum([\n 'project',\n 'merge_request',\n 'pipeline',\n 'pipeline_event',\n 'issue',\n 'release',\n ]),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n \"Which GitLab 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 .refine(\n (v) =>\n (v.projectIds && v.projectIds.length > 0) ||\n (v.groupIds && v.groupIds.length > 0),\n {\n message: 'At least one of `projectIds` or `groupIds` must be provided.',\n path: ['projectIds'],\n },\n ),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'GitLab',\n category: 'engineering',\n brandColor: '#FC6D26',\n tagline:\n 'Sync projects, merge requests, pipelines, issues, and releases from GitLab.com or a self-hosted GitLab instance.',\n vendor: {\n name: 'GitLab',\n domain: 'gitlab.com',\n apiDocs: 'https://docs.gitlab.com/ee/api/',\n website: 'https://gitlab.com',\n },\n auth: {\n summary:\n 'A GitLab Personal Access Token (PAT) with the `read_api` scope is required. The PAT must belong to an account with read access to the projects and groups you want to sync. Self-hosted GitLab is supported by overriding the `host` field.',\n setup: [\n 'Open GitLab -> User Preferences -> Access Tokens (or the equivalent on your self-hosted instance).',\n 'Create a Personal Access Token with the `read_api` scope.',\n 'Store it as a secret and reference it from the connector config as `apiToken: secret(\"GITLAB_API_TOKEN\")`.',\n 'Set `projectIds` to a list of numeric project IDs, or `groupIds` to a list of numeric group IDs (or both). At least one must be set.',\n 'For self-hosted GitLab, set `host` to your instance hostname (no protocol or path), e.g. `gitlab.example.com`.',\n ],\n },\n rateLimit:\n 'GitLab returns standard `RateLimit-Remaining` / `RateLimit-Reset` headers (reset is a Unix timestamp in seconds); list pagination uses the Link header (page size 100).',\n limitations: [\n 'Container Registry, Packages, and GitLab Duo / AI features are out of scope.',\n 'Pipeline state-transition events are synthesized: one `pipeline_event` is emitted per pipeline lifecycle (created_at to finished_at/updated_at), not one per intermediate state change.',\n 'Group project discovery walks each group with `include_subgroups=true`; very large groups may take multiple sync chunks to enumerate.',\n ],\n});\n\nexport type GitLabResource =\n | 'project'\n | 'merge_request'\n | 'pipeline'\n | 'pipeline_event'\n | 'issue'\n | 'release';\n\nexport interface GitLabSettings {\n host: string;\n projectIds?: readonly number[];\n groupIds?: readonly number[];\n resources?: readonly GitLabResource[];\n}\n\nconst gitlabCredentials = {\n apiToken: {\n description: 'GitLab Personal Access Token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype GitLabCredentials = typeof gitlabCredentials;\n\nconst DEFAULT_HOST = 'gitlab.com';\nconst PAGE_SIZE = 100;\nconst PIPELINE_DETAIL_CONCURRENCY = 5;\n\nconst gitlabRateLimit = standardRateLimitPolicy({\n remainingHeader: 'ratelimit-remaining',\n resetHeader: 'ratelimit-reset',\n resetUnit: 's',\n});\n\nconst PHASE_ORDER = [\n 'projects',\n 'merge_requests',\n 'pipelines',\n 'issues',\n 'releases',\n] as const;\n\ntype GitLabPhase = (typeof PHASE_ORDER)[number];\n\ntype GitLabSyncCursor = ChunkedSyncCursor<GitLabPhase, string>;\n\nconst isGitLabSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\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\ninterface GitLabUserRef {\n id: number;\n username: string;\n name?: string | null;\n}\n\ninterface GitLabProject {\n id: number;\n name: string;\n path_with_namespace: string;\n default_branch: string | null;\n web_url: string;\n created_at: string;\n last_activity_at?: string | null;\n archived?: boolean;\n visibility?: string;\n}\n\ninterface GitLabMergeRequest {\n id: number;\n iid: number;\n project_id: number;\n title: string;\n state: string;\n draft?: boolean;\n work_in_progress?: boolean;\n author: GitLabUserRef | null;\n assignees?: GitLabUserRef[];\n source_branch: string;\n target_branch: string;\n created_at: string;\n updated_at: string;\n merged_at: string | null;\n closed_at: string | null;\n web_url: string;\n}\n\ninterface GitLabPipeline {\n id: number;\n iid?: number;\n project_id: number;\n status: string;\n ref: string | null;\n sha: string;\n source: string | null;\n created_at: string;\n updated_at: string;\n started_at?: string | null;\n finished_at?: string | null;\n duration?: number | null;\n web_url: string;\n}\n\ninterface GitLabPipelineDetail {\n id: number;\n started_at?: string | null;\n finished_at?: string | null;\n duration?: number | null;\n}\n\ninterface GitLabIssue {\n id: number;\n iid: number;\n project_id: number;\n title: string;\n state: string;\n labels: string[];\n author: GitLabUserRef | null;\n assignees?: GitLabUserRef[];\n created_at: string;\n updated_at: string;\n closed_at: string | null;\n web_url: string;\n}\n\ninterface GitLabRelease {\n tag_name: string;\n name: string | null;\n description?: string | null;\n created_at: string;\n released_at: string | null;\n author?: GitLabUserRef | null;\n}\n\nconst userRefSchema = z.object({\n id: z.number().int(),\n username: z.string().min(1),\n name: z.string().nullable().optional(),\n});\n\nconst projectSchema = z.object({\n id: z.number().int(),\n name: z.string().min(1),\n path_with_namespace: z.string().min(1),\n default_branch: z.string().nullable(),\n web_url: z.string(),\n created_at: z.iso.datetime(),\n last_activity_at: z.iso.datetime().nullable().optional(),\n archived: z.boolean().optional(),\n visibility: z.string().optional(),\n});\n\nconst projectsResponseSchema = z.array(projectSchema);\n\nconst mergeRequestSchema = z.object({\n id: z.number().int(),\n iid: z.number().int(),\n project_id: z.number().int(),\n title: z.string(),\n state: z.string().min(1),\n draft: z.boolean().optional(),\n work_in_progress: z.boolean().optional(),\n author: userRefSchema.nullable(),\n assignees: z.array(userRefSchema).optional(),\n source_branch: z.string(),\n target_branch: z.string(),\n created_at: z.iso.datetime(),\n updated_at: z.iso.datetime(),\n merged_at: z.iso.datetime().nullable(),\n closed_at: z.iso.datetime().nullable(),\n web_url: z.string(),\n});\n\nconst mergeRequestsResponseSchema = z.array(mergeRequestSchema);\n\nconst pipelineSchema = z.object({\n id: z.number().int(),\n iid: z.number().int().optional(),\n project_id: z.number().int(),\n status: z.string().min(1),\n ref: z.string().nullable(),\n sha: z.string().min(1),\n source: z.string().nullable(),\n created_at: z.iso.datetime(),\n updated_at: z.iso.datetime(),\n started_at: z.iso.datetime().nullable().optional(),\n finished_at: z.iso.datetime().nullable().optional(),\n duration: z.number().nullable().optional(),\n web_url: z.string(),\n});\n\nconst pipelinesResponseSchema = z.array(pipelineSchema);\n\nconst issueSchema = z.object({\n id: z.number().int(),\n iid: z.number().int(),\n project_id: z.number().int(),\n title: z.string(),\n state: z.string().min(1),\n labels: z.array(z.string()),\n author: userRefSchema.nullable(),\n assignees: z.array(userRefSchema).optional(),\n created_at: z.iso.datetime(),\n updated_at: z.iso.datetime(),\n closed_at: z.iso.datetime().nullable(),\n web_url: z.string(),\n});\n\nconst issuesResponseSchema = z.array(issueSchema);\n\nconst releaseSchema = z.object({\n tag_name: z.string().min(1),\n name: z.string().nullable(),\n description: z.string().nullable().optional(),\n created_at: z.iso.datetime(),\n released_at: z.iso.datetime().nullable(),\n author: userRefSchema.nullable().optional(),\n});\n\nconst releasesResponseSchema = z.array(releaseSchema);\n\nexport const gitlabResources = defineResources({\n project: {\n shape: 'entity',\n description:\n 'GitLab projects (repositories) with namespace path, default branch, and archived/visibility flags.',\n endpoint: 'GET /api/v4/projects/{id}',\n notes:\n 'Discovered from configured `projectIds` and from `groupIds` via GET /api/v4/groups/{id}/projects?include_subgroups=true.',\n filterable: [],\n responses: { projects: projectsResponseSchema },\n },\n merge_request: {\n shape: 'entity',\n description:\n 'Open, merged, and closed merge requests with author, source/target branches, and merge timestamps.',\n endpoint: 'GET /api/v4/projects/{id}/merge_requests',\n filterable: [\n {\n field: 'state',\n ops: ['eq'],\n values: ['opened', 'closed', 'merged', 'locked'],\n },\n ],\n responses: { merge_requests: mergeRequestsResponseSchema },\n },\n pipeline: {\n shape: 'entity',\n description:\n 'CI/CD pipelines with status, ref, commit sha, source, duration, and start/finish timestamps.',\n endpoint: 'GET /api/v4/projects/{id}/pipelines',\n notes:\n 'The pipelines list response omits duration and finished_at; each pipeline is enriched via GET /api/v4/projects/{id}/pipelines/{pipeline_id} to populate duration and finish time.',\n filterable: [{ field: 'status', ops: ['eq'] }],\n responses: { pipelines: pipelinesResponseSchema },\n },\n pipeline_event: {\n shape: 'event',\n description:\n 'Pipeline lifecycle events. One event per pipeline covering created_at to finished_at (or updated_at if not yet finished), tagged with the terminal status.',\n endpoint: 'GET /api/v4/projects/{id}/pipelines',\n notes:\n 'Derived from the same pipelines response that builds the `pipeline` resource; the GitLab API does not expose an intermediate state-transition history endpoint.',\n filterable: [],\n },\n issue: {\n shape: 'entity',\n description:\n 'Open and closed issues with labels, author, assignees, and close timestamp.',\n endpoint: 'GET /api/v4/projects/{id}/issues',\n filterable: [{ field: 'state', ops: ['eq'], values: ['opened', 'closed'] }],\n responses: { issues: issuesResponseSchema },\n },\n release: {\n shape: 'entity',\n description:\n 'Project releases keyed by tag name, including released_at and the publishing author.',\n endpoint: 'GET /api/v4/projects/{id}/releases',\n filterable: [],\n responses: { releases: releasesResponseSchema },\n },\n});\n\nexport const id = 'gitlab';\n\nconst LIST_RESOURCE_DEF_KEY: Record<\n 'merge_requests' | 'pipelines' | 'issues' | 'releases',\n string\n> = {\n merge_requests: 'merge_request',\n pipelines: 'pipeline',\n issues: 'issue',\n releases: 'release',\n};\n\nfunction pushableEq(\n filter: FilterClause[] | undefined,\n field: string,\n): string | null {\n if (!filter) {\n return null;\n }\n for (const clause of filter) {\n if (\n 'field' in clause &&\n clause.field === field &&\n clause.op === 'eq' &&\n typeof clause.value === 'string'\n ) {\n return clause.value;\n }\n }\n return null;\n}\n\ninterface ProjectBatch<T> {\n projectId: number;\n items: T[];\n}\n\nexport class GitLabConnector extends BaseConnector<\n GitLabSettings,\n GitLabCredentials\n> {\n static readonly id = id;\n\n static readonly resources = gitlabResources;\n\n static readonly schemas = schemasFromResources(gitlabResources);\n\n static create(input: unknown, ctx?: ConnectorContext): GitLabConnector {\n const parsed = configFields.parse(input);\n return new GitLabConnector(\n {\n host: parsed.host ?? DEFAULT_HOST,\n projectIds: parsed.projectIds,\n groupIds: parsed.groupIds,\n resources: parsed.resources,\n },\n { apiToken: parsed.apiToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = gitlabCredentials;\n\n private effectiveProjectIds: number[] | null = null;\n private projectMetadataCache = new Map<number, GitLabProject>();\n\n constructor(\n settings: GitLabSettings,\n creds?: { apiToken: { $secret: string } | string },\n ctx?: ConnectorContext,\n ) {\n super({ ...settings, host: settings.host || DEFAULT_HOST }, creds, ctx);\n }\n\n private buildHeaders(): Record<string, string> {\n return {\n 'PRIVATE-TOKEN': this.creds.apiToken,\n Accept: 'application/json',\n 'User-Agent': connectorUserAgent('gitlab'),\n };\n }\n\n private apiBase(): string {\n return `https://${this.settings.host}/api/v4`;\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 rateLimit: gitlabRateLimit,\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 !== this.settings.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 GitLabPhase,\n readonly GitLabResource[]\n > = {\n projects: ['project'],\n merge_requests: ['merge_request'],\n pipelines: ['pipeline', 'pipeline_event'],\n issues: ['issue'],\n releases: ['release'],\n };\n\n private activePhases(\n optionsResources: ReadonlySet<string> | undefined,\n ): GitLabPhase[] {\n const fromSettings = selectActivePhases<GitLabResource, GitLabPhase>(\n (r) => {\n switch (r) {\n case 'project':\n return 'projects';\n case 'merge_request':\n return 'merge_requests';\n case 'pipeline':\n case 'pipeline_event':\n return 'pipelines';\n case 'issue':\n return 'issues';\n case 'release':\n return 'releases';\n }\n },\n PHASE_ORDER,\n this.settings.resources,\n );\n if (optionsResources === undefined) {\n return fromSettings;\n }\n return fromSettings.filter((phase) =>\n GitLabConnector.PHASE_RESOURCES[phase].some((r) =>\n optionsResources.has(r),\n ),\n );\n }\n\n private isResourceAllowed(\n resource: GitLabResource,\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 private async resolveEffectiveProjectIds(\n signal: AbortSignal | undefined,\n ): Promise<number[]> {\n if (this.effectiveProjectIds !== null) {\n return this.effectiveProjectIds;\n }\n const seen = new Set<number>();\n const ordered: number[] = [];\n const addId = (n: number) => {\n if (!seen.has(n)) {\n seen.add(n);\n ordered.push(n);\n }\n };\n for (const pid of this.settings.projectIds ?? []) {\n addId(pid);\n }\n for (const gid of this.settings.groupIds ?? []) {\n const projects = await this.fetchGroupProjects(gid, signal);\n for (const p of projects) {\n this.projectMetadataCache.set(p.id, p);\n addId(p.id);\n }\n }\n ordered.sort((a, b) => a - b);\n this.effectiveProjectIds = ordered;\n return ordered;\n }\n\n private async fetchGroupProjects(\n groupId: number,\n signal: AbortSignal | undefined,\n ): Promise<GitLabProject[]> {\n const out: GitLabProject[] = [];\n const baseUrl = `${this.apiBase()}/groups/${groupId}/projects`;\n let url: string | null =\n `${baseUrl}?per_page=${PAGE_SIZE}&include_subgroups=true&archived=false`;\n const expectedPath = `/api/v4/groups/${groupId}/projects`;\n while (url !== null) {\n const res = await this.fetch<GitLabProject[]>(\n url,\n 'group_projects',\n signal,\n );\n for (const project of res.body) {\n out.push(project);\n }\n const next = parseLinkHeader(res.headers.get('link'))['next'] ?? null;\n url = this.sanitizeUrl(next, expectedPath);\n }\n return out;\n }\n\n private async fetchProjectMetadata(\n projectId: number,\n signal: AbortSignal | undefined,\n ): Promise<GitLabProject | null> {\n if (this.projectMetadataCache.has(projectId)) {\n return this.projectMetadataCache.get(projectId)!;\n }\n const url = `${this.apiBase()}/projects/${projectId}`;\n const res = await this.fetch<GitLabProject>(url, 'project', signal);\n this.projectMetadataCache.set(projectId, res.body);\n return res.body;\n }\n\n private async fetchPipelineDetail(\n projectId: number,\n pipelineId: number,\n signal: AbortSignal | undefined,\n ): Promise<GitLabPipelineDetail | null> {\n const url = `${this.apiBase()}/projects/${projectId}/pipelines/${pipelineId}`;\n try {\n const res = await this.fetch<GitLabPipelineDetail>(\n url,\n 'pipeline_detail',\n signal,\n );\n return res.body;\n } catch (err: unknown) {\n const status = (err as { response?: { status?: number } })?.response\n ?.status;\n if (status === 404 || status === 410) {\n return null;\n }\n throw err;\n }\n }\n\n private async enrichPipelineBatches(\n items: unknown[],\n signal: AbortSignal | undefined,\n ): Promise<void> {\n const batches = items as ProjectBatch<GitLabPipeline>[];\n for (const batch of batches) {\n await mapWithConcurrency(\n batch.items,\n PIPELINE_DETAIL_CONCURRENCY,\n async (pipeline) => {\n const detail = await this.fetchPipelineDetail(\n batch.projectId,\n pipeline.id,\n signal,\n );\n if (detail) {\n pipeline.started_at = detail.started_at ?? null;\n pipeline.finished_at = detail.finished_at ?? null;\n pipeline.duration = detail.duration ?? null;\n }\n },\n );\n }\n }\n\n private async fetchProjectsPhase(\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<FetchPageResult<string>> {\n const projects = await this.resolveEffectiveProjectIds(signal);\n if (projects.length === 0) {\n return { items: [], next: null };\n }\n const { idx } = decodePage(page);\n if (idx >= projects.length) {\n return { items: [], next: null };\n }\n const projectId = projects[idx]!;\n const project = await this.fetchProjectMetadata(projectId, signal);\n const nextIdx = idx + 1;\n const next = nextIdx < projects.length ? encodePage(nextIdx, null) : null;\n return { items: project ? [project] : [], next };\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 buildListPageUrl(\n projectId: number,\n resource: 'merge_requests' | 'pipelines' | 'issues' | 'releases',\n options: SyncOptions,\n spec?: FetchSpec,\n ): string {\n const u = new URL(`${this.apiBase()}/projects/${projectId}/${resource}`);\n u.searchParams.set('per_page', String(PAGE_SIZE));\n switch (resource) {\n case 'merge_requests':\n u.searchParams.set('state', pushableEq(spec?.filter, 'state') ?? 'all');\n u.searchParams.set('order_by', 'updated_at');\n u.searchParams.set('sort', 'desc');\n u.searchParams.set('scope', 'all');\n if (options.since) {\n u.searchParams.set('updated_after', options.since);\n }\n break;\n case 'pipelines': {\n const status = pushableEq(spec?.filter, 'status');\n if (status !== null) {\n u.searchParams.set('status', status);\n }\n u.searchParams.set('order_by', 'updated_at');\n u.searchParams.set('sort', 'desc');\n if (options.since) {\n u.searchParams.set('updated_after', options.since);\n }\n break;\n }\n case 'issues':\n u.searchParams.set('state', pushableEq(spec?.filter, 'state') ?? 'all');\n u.searchParams.set('order_by', 'updated_at');\n u.searchParams.set('sort', 'desc');\n u.searchParams.set('scope', 'all');\n if (options.since) {\n u.searchParams.set('updated_after', options.since);\n }\n break;\n case 'releases':\n u.searchParams.set('order_by', 'released_at');\n u.searchParams.set('sort', 'desc');\n break;\n }\n return u.toString();\n }\n\n private async fetchListPhase<T>(\n options: SyncOptions,\n page: string | null,\n signal: AbortSignal | undefined,\n resource: 'merge_requests' | 'pipelines' | 'issues' | 'releases',\n rowUpdatedAt: (row: T) => number,\n ): Promise<FetchPageResult<string>> {\n const projects = await this.resolveEffectiveProjectIds(signal);\n if (projects.length === 0) {\n return { items: [], next: null };\n }\n const { idx, url: rawPageUrl } = decodePage(page);\n if (idx >= projects.length) {\n return { items: [], next: null };\n }\n const projectId = projects[idx]!;\n const expectedPath = `/api/v4/projects/${projectId}/${resource}`;\n const fetchUrl =\n this.sanitizeUrl(rawPageUrl, expectedPath) ??\n this.buildListPageUrl(\n projectId,\n resource,\n options,\n this.singleSpec(options, LIST_RESOURCE_DEF_KEY[resource]),\n );\n const res = await this.fetch<T[]>(fetchUrl, resource, signal);\n const rawNext = parseLinkHeader(res.headers.get('link'))['next'] ?? null;\n const safeNext = this.sanitizeUrl(rawNext, expectedPath);\n const rows = res.body;\n\n const cutoff = options.since ? new Date(options.since).getTime() : null;\n let filtered: T[];\n let cutoffReached: boolean;\n if (cutoff !== null) {\n filtered = rows.filter((row) => rowUpdatedAt(row) >= cutoff);\n const last = rows.at(-1);\n cutoffReached = last !== undefined && rowUpdatedAt(last) < cutoff;\n } else {\n filtered = rows;\n cutoffReached = false;\n }\n\n const nextWithinProject = cutoffReached ? null : safeNext;\n const batch: ProjectBatch<T> = { projectId, items: filtered };\n if (nextWithinProject !== null) {\n return { items: [batch], next: encodePage(idx, nextWithinProject) };\n }\n const nextIdx = idx + 1;\n const next = nextIdx < projects.length ? encodePage(nextIdx, null) : null;\n return { items: [batch], next };\n }\n\n private async writeProjects(\n storage: StorageHandle,\n items: unknown[],\n page: string | null,\n ): Promise<void> {\n if (page === null) {\n await storage.entities([], { types: ['project'] });\n }\n const projects = items as GitLabProject[];\n for (const project of projects) {\n const updatedAt = new Date(\n project.last_activity_at ?? project.created_at,\n ).getTime();\n await storage.entity({\n type: 'project',\n id: String(project.id),\n attributes: {\n name: project.name,\n path_with_namespace: project.path_with_namespace,\n default_branch: project.default_branch ?? '',\n web_url: project.web_url,\n visibility: project.visibility ?? '',\n archived: project.archived ?? false,\n created_at: new Date(project.created_at).getTime(),\n },\n updated_at: updatedAt,\n });\n }\n }\n\n private async writeMergeRequests(\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: ['merge_request'] });\n }\n const batches = items as ProjectBatch<GitLabMergeRequest>[];\n for (const batch of batches) {\n for (const mr of batch.items) {\n await storage.entity({\n type: 'merge_request',\n id: `${batch.projectId}:${mr.iid}`,\n attributes: {\n project_id: batch.projectId,\n iid: mr.iid,\n title: mr.title,\n state: mr.state,\n draft: mr.draft ?? mr.work_in_progress ?? false,\n author: mr.author?.username ?? '',\n assignees: (mr.assignees ?? []).map((a) => a.username),\n source_branch: mr.source_branch,\n target_branch: mr.target_branch,\n web_url: mr.web_url,\n created_at: new Date(mr.created_at).getTime(),\n merged_at: mr.merged_at ? new Date(mr.merged_at).getTime() : null,\n closed_at: mr.closed_at ? new Date(mr.closed_at).getTime() : null,\n },\n updated_at: new Date(mr.updated_at).getTime(),\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 ProjectBatch<GitLabPipeline>[];\n const eventsById = new Map<string, Event>();\n for (const batch of batches) {\n for (const pipeline of batch.items) {\n const createdMs = new Date(pipeline.created_at).getTime();\n const updatedMs = new Date(pipeline.updated_at).getTime();\n const finishedMs = pipeline.finished_at\n ? new Date(pipeline.finished_at).getTime()\n : null;\n const durationMs =\n pipeline.duration !== null && pipeline.duration !== undefined\n ? Math.round(pipeline.duration * 1000)\n : finishedMs !== null\n ? finishedMs - createdMs\n : null;\n if (pipelineAllowed) {\n await storage.entity({\n type: 'pipeline',\n id: `${batch.projectId}:${pipeline.id}`,\n attributes: {\n project_id: batch.projectId,\n pipeline_id: pipeline.id,\n status: pipeline.status,\n ref: pipeline.ref ?? '',\n sha: pipeline.sha,\n source: pipeline.source ?? '',\n web_url: pipeline.web_url,\n created_at: createdMs,\n finished_at: finishedMs,\n duration_ms: durationMs,\n },\n updated_at: updatedMs,\n });\n }\n if (eventAllowed) {\n eventsById.set(`${batch.projectId}:${pipeline.id}`, {\n name: 'pipeline_event',\n start_ts: createdMs,\n end_ts: finishedMs ?? updatedMs,\n attributes: {\n project_id: batch.projectId,\n pipeline_id: pipeline.id,\n status: pipeline.status,\n ref: pipeline.ref ?? '',\n sha: pipeline.sha,\n source: pipeline.source ?? '',\n duration_ms: durationMs,\n },\n });\n }\n }\n }\n for (const event of eventsById.values()) {\n await storage.event(event);\n }\n }\n\n private async writeIssues(\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: ['issue'] });\n }\n const batches = items as ProjectBatch<GitLabIssue>[];\n for (const batch of batches) {\n for (const issue of batch.items) {\n await storage.entity({\n type: 'issue',\n id: `${batch.projectId}:${issue.iid}`,\n attributes: {\n project_id: batch.projectId,\n iid: issue.iid,\n title: issue.title,\n state: issue.state,\n labels: issue.labels,\n author: issue.author?.username ?? '',\n assignees: (issue.assignees ?? []).map((a) => a.username),\n web_url: issue.web_url,\n created_at: new Date(issue.created_at).getTime(),\n closed_at: issue.closed_at\n ? new Date(issue.closed_at).getTime()\n : null,\n },\n updated_at: new Date(issue.updated_at).getTime(),\n });\n }\n }\n }\n\n private async writeReleases(\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: ['release'] });\n }\n const batches = items as ProjectBatch<GitLabRelease>[];\n for (const batch of batches) {\n for (const release of batch.items) {\n const createdMs = new Date(release.created_at).getTime();\n const releasedMs = release.released_at\n ? new Date(release.released_at).getTime()\n : null;\n await storage.entity({\n type: 'release',\n id: `${batch.projectId}:${release.tag_name}`,\n attributes: {\n project_id: batch.projectId,\n tag_name: release.tag_name,\n name: release.name ?? '',\n description: release.description ?? '',\n author: release.author?.username ?? '',\n created_at: createdMs,\n released_at: releasedMs,\n },\n updated_at: releasedMs ?? createdMs,\n });\n }\n }\n }\n\n private resolveCursor(cursor: unknown): GitLabSyncCursor | undefined {\n if (!isGitLabSyncCursor(cursor)) {\n return undefined;\n }\n return { phase: cursor.phase, page: cursor.page };\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<GitLabPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n switch (phase) {\n case 'projects':\n return this.fetchProjectsPhase(page, sig);\n case 'merge_requests':\n return this.fetchListPhase<GitLabMergeRequest>(\n options,\n page,\n sig,\n 'merge_requests',\n (mr) => new Date(mr.updated_at).getTime(),\n );\n case 'pipelines': {\n const result = await this.fetchListPhase<GitLabPipeline>(\n options,\n page,\n sig,\n 'pipelines',\n (p) => new Date(p.updated_at).getTime(),\n );\n await this.enrichPipelineBatches(result.items, sig);\n return result;\n }\n case 'issues':\n return this.fetchListPhase<GitLabIssue>(\n options,\n page,\n sig,\n 'issues',\n (i) => new Date(i.updated_at).getTime(),\n );\n case 'releases':\n return this.fetchListPhase<GitLabRelease>(\n options,\n page,\n sig,\n 'releases',\n (r) => new Date(r.released_at ?? r.created_at).getTime(),\n );\n }\n },\n writeBatch: async (phase, items, page) => {\n switch (phase) {\n case 'projects':\n return this.writeProjects(storage, items, page);\n case 'merge_requests':\n return this.writeMergeRequests(storage, items, page, options);\n case 'pipelines':\n return this.writePipelines(storage, items, page, options);\n case 'issues':\n return this.writeIssues(storage, items, page, options);\n case 'releases':\n return this.writeReleases(storage, items, page, options);\n }\n },\n });\n }\n}\n","import { GitLabConnector } from './gitlab';\n\nexport {\n configFields,\n doc,\n GitLabConnector,\n gitlabResources as resources,\n id,\n} from './gitlab';\nexport type { GitLabResource, GitLabSettings } from './gitlab';\nexport default GitLabConnector;\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rawdash/connector-gitlab",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "Rawdash connector for GitLab",
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.26.0"
27
+ "@rawdash/core": "0.28.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "fast-check": "^4.8.0",