@rawdash/connector-gitlab 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -38,6 +38,7 @@ declare const gitlabResources: {
38
38
  readonly description: "GitLab projects (repositories) with namespace path, default branch, and archived/visibility flags.";
39
39
  readonly endpoint: "GET /api/v4/projects/{id}";
40
40
  readonly notes: "Discovered from configured `projectIds` and from `groupIds` via GET /api/v4/groups/{id}/projects?include_subgroups=true.";
41
+ readonly filterable: [];
41
42
  readonly responses: {
42
43
  readonly projects: z.ZodArray<z.ZodObject<{
43
44
  id: z.ZodNumber;
@@ -56,6 +57,11 @@ declare const gitlabResources: {
56
57
  readonly shape: "entity";
57
58
  readonly description: "Open, merged, and closed merge requests with author, source/target branches, and merge timestamps.";
58
59
  readonly endpoint: "GET /api/v4/projects/{id}/merge_requests";
60
+ readonly filterable: [{
61
+ readonly field: "state";
62
+ readonly ops: ["eq"];
63
+ readonly values: ["opened", "closed", "merged", "locked"];
64
+ }];
59
65
  readonly responses: {
60
66
  readonly merge_requests: z.ZodArray<z.ZodObject<{
61
67
  id: z.ZodNumber;
@@ -89,6 +95,10 @@ declare const gitlabResources: {
89
95
  readonly shape: "entity";
90
96
  readonly description: "CI/CD pipelines with status, ref, commit sha, source, duration, and start/finish timestamps.";
91
97
  readonly endpoint: "GET /api/v4/projects/{id}/pipelines";
98
+ readonly filterable: [{
99
+ readonly field: "status";
100
+ readonly ops: ["eq"];
101
+ }];
92
102
  readonly responses: {
93
103
  readonly pipelines: z.ZodArray<z.ZodObject<{
94
104
  id: z.ZodNumber;
@@ -112,11 +122,17 @@ declare const gitlabResources: {
112
122
  readonly description: "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.";
113
123
  readonly endpoint: "GET /api/v4/projects/{id}/pipelines";
114
124
  readonly notes: "Derived from the same pipelines response that builds the `pipeline` resource; the GitLab API does not expose an intermediate state-transition history endpoint.";
125
+ readonly filterable: [];
115
126
  };
116
127
  readonly issue: {
117
128
  readonly shape: "entity";
118
129
  readonly description: "Open and closed issues with labels, author, assignees, and close timestamp.";
119
130
  readonly endpoint: "GET /api/v4/projects/{id}/issues";
131
+ readonly filterable: [{
132
+ readonly field: "state";
133
+ readonly ops: ["eq"];
134
+ readonly values: ["opened", "closed"];
135
+ }];
120
136
  readonly responses: {
121
137
  readonly issues: z.ZodArray<z.ZodObject<{
122
138
  id: z.ZodNumber;
@@ -146,6 +162,7 @@ declare const gitlabResources: {
146
162
  readonly shape: "entity";
147
163
  readonly description: "Project releases keyed by tag name, including released_at and the publishing author.";
148
164
  readonly endpoint: "GET /api/v4/projects/{id}/releases";
165
+ readonly filterable: [];
149
166
  readonly responses: {
150
167
  readonly releases: z.ZodArray<z.ZodObject<{
151
168
  tag_name: z.ZodString;
@@ -171,6 +188,7 @@ declare class GitLabConnector extends BaseConnector<GitLabSettings, GitLabCreden
171
188
  readonly description: "GitLab projects (repositories) with namespace path, default branch, and archived/visibility flags.";
172
189
  readonly endpoint: "GET /api/v4/projects/{id}";
173
190
  readonly notes: "Discovered from configured `projectIds` and from `groupIds` via GET /api/v4/groups/{id}/projects?include_subgroups=true.";
191
+ readonly filterable: [];
174
192
  readonly responses: {
175
193
  readonly projects: z.ZodArray<z.ZodObject<{
176
194
  id: z.ZodNumber;
@@ -189,6 +207,11 @@ declare class GitLabConnector extends BaseConnector<GitLabSettings, GitLabCreden
189
207
  readonly shape: "entity";
190
208
  readonly description: "Open, merged, and closed merge requests with author, source/target branches, and merge timestamps.";
191
209
  readonly endpoint: "GET /api/v4/projects/{id}/merge_requests";
210
+ readonly filterable: [{
211
+ readonly field: "state";
212
+ readonly ops: ["eq"];
213
+ readonly values: ["opened", "closed", "merged", "locked"];
214
+ }];
192
215
  readonly responses: {
193
216
  readonly merge_requests: z.ZodArray<z.ZodObject<{
194
217
  id: z.ZodNumber;
@@ -222,6 +245,10 @@ declare class GitLabConnector extends BaseConnector<GitLabSettings, GitLabCreden
222
245
  readonly shape: "entity";
223
246
  readonly description: "CI/CD pipelines with status, ref, commit sha, source, duration, and start/finish timestamps.";
224
247
  readonly endpoint: "GET /api/v4/projects/{id}/pipelines";
248
+ readonly filterable: [{
249
+ readonly field: "status";
250
+ readonly ops: ["eq"];
251
+ }];
225
252
  readonly responses: {
226
253
  readonly pipelines: z.ZodArray<z.ZodObject<{
227
254
  id: z.ZodNumber;
@@ -245,11 +272,17 @@ declare class GitLabConnector extends BaseConnector<GitLabSettings, GitLabCreden
245
272
  readonly description: "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.";
246
273
  readonly endpoint: "GET /api/v4/projects/{id}/pipelines";
247
274
  readonly notes: "Derived from the same pipelines response that builds the `pipeline` resource; the GitLab API does not expose an intermediate state-transition history endpoint.";
275
+ readonly filterable: [];
248
276
  };
249
277
  readonly issue: {
250
278
  readonly shape: "entity";
251
279
  readonly description: "Open and closed issues with labels, author, assignees, and close timestamp.";
252
280
  readonly endpoint: "GET /api/v4/projects/{id}/issues";
281
+ readonly filterable: [{
282
+ readonly field: "state";
283
+ readonly ops: ["eq"];
284
+ readonly values: ["opened", "closed"];
285
+ }];
253
286
  readonly responses: {
254
287
  readonly issues: z.ZodArray<z.ZodObject<{
255
288
  id: z.ZodNumber;
@@ -279,6 +312,7 @@ declare class GitLabConnector extends BaseConnector<GitLabSettings, GitLabCreden
279
312
  readonly shape: "entity";
280
313
  readonly description: "Project releases keyed by tag name, including released_at and the publishing author.";
281
314
  readonly endpoint: "GET /api/v4/projects/{id}/releases";
315
+ readonly filterable: [];
282
316
  readonly responses: {
283
317
  readonly releases: z.ZodArray<z.ZodObject<{
284
318
  tag_name: z.ZodString;
@@ -413,6 +447,7 @@ declare class GitLabConnector extends BaseConnector<GitLabSettings, GitLabCreden
413
447
  private fetchGroupProjects;
414
448
  private fetchProjectMetadata;
415
449
  private fetchProjectsPhase;
450
+ private singleSpec;
416
451
  private buildListPageUrl;
417
452
  private fetchListPhase;
418
453
  private writeProjects;
package/dist/index.js CHANGED
@@ -263,40 +263,69 @@ var gitlabResources = defineResources({
263
263
  description: "GitLab projects (repositories) with namespace path, default branch, and archived/visibility flags.",
264
264
  endpoint: "GET /api/v4/projects/{id}",
265
265
  notes: "Discovered from configured `projectIds` and from `groupIds` via GET /api/v4/groups/{id}/projects?include_subgroups=true.",
266
+ filterable: [],
266
267
  responses: { projects: projectsResponseSchema }
267
268
  },
268
269
  merge_request: {
269
270
  shape: "entity",
270
271
  description: "Open, merged, and closed merge requests with author, source/target branches, and merge timestamps.",
271
272
  endpoint: "GET /api/v4/projects/{id}/merge_requests",
273
+ filterable: [
274
+ {
275
+ field: "state",
276
+ ops: ["eq"],
277
+ values: ["opened", "closed", "merged", "locked"]
278
+ }
279
+ ],
272
280
  responses: { merge_requests: mergeRequestsResponseSchema }
273
281
  },
274
282
  pipeline: {
275
283
  shape: "entity",
276
284
  description: "CI/CD pipelines with status, ref, commit sha, source, duration, and start/finish timestamps.",
277
285
  endpoint: "GET /api/v4/projects/{id}/pipelines",
286
+ filterable: [{ field: "status", ops: ["eq"] }],
278
287
  responses: { pipelines: pipelinesResponseSchema }
279
288
  },
280
289
  pipeline_event: {
281
290
  shape: "event",
282
291
  description: "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.",
283
292
  endpoint: "GET /api/v4/projects/{id}/pipelines",
284
- notes: "Derived from the same pipelines response that builds the `pipeline` resource; the GitLab API does not expose an intermediate state-transition history endpoint."
293
+ notes: "Derived from the same pipelines response that builds the `pipeline` resource; the GitLab API does not expose an intermediate state-transition history endpoint.",
294
+ filterable: []
285
295
  },
286
296
  issue: {
287
297
  shape: "entity",
288
298
  description: "Open and closed issues with labels, author, assignees, and close timestamp.",
289
299
  endpoint: "GET /api/v4/projects/{id}/issues",
300
+ filterable: [{ field: "state", ops: ["eq"], values: ["opened", "closed"] }],
290
301
  responses: { issues: issuesResponseSchema }
291
302
  },
292
303
  release: {
293
304
  shape: "entity",
294
305
  description: "Project releases keyed by tag name, including released_at and the publishing author.",
295
306
  endpoint: "GET /api/v4/projects/{id}/releases",
307
+ filterable: [],
296
308
  responses: { releases: releasesResponseSchema }
297
309
  }
298
310
  });
299
311
  var id = "gitlab";
312
+ var LIST_RESOURCE_DEF_KEY = {
313
+ merge_requests: "merge_request",
314
+ pipelines: "pipeline",
315
+ issues: "issue",
316
+ releases: "release"
317
+ };
318
+ function pushableEq(filter, field) {
319
+ if (!filter) {
320
+ return null;
321
+ }
322
+ for (const clause of filter) {
323
+ if ("field" in clause && clause.field === field && clause.op === "eq" && typeof clause.value === "string") {
324
+ return clause.value;
325
+ }
326
+ }
327
+ return null;
328
+ }
300
329
  var GitLabConnector = class _GitLabConnector extends BaseConnector {
301
330
  static id = id;
302
331
  static resources = gitlabResources;
@@ -471,12 +500,16 @@ var GitLabConnector = class _GitLabConnector extends BaseConnector {
471
500
  const next = nextIdx < projects.length ? encodePage(nextIdx, null) : null;
472
501
  return { items: project ? [project] : [], next };
473
502
  }
474
- buildListPageUrl(projectId, resource, options) {
503
+ singleSpec(options, resource) {
504
+ const specs = options.fetchSpecs?.[resource];
505
+ return specs && specs.length === 1 ? specs[0] : void 0;
506
+ }
507
+ buildListPageUrl(projectId, resource, options, spec) {
475
508
  const u = new URL(`${this.apiBase()}/projects/${projectId}/${resource}`);
476
509
  u.searchParams.set("per_page", String(PAGE_SIZE));
477
510
  switch (resource) {
478
511
  case "merge_requests":
479
- u.searchParams.set("state", "all");
512
+ u.searchParams.set("state", pushableEq(spec?.filter, "state") ?? "all");
480
513
  u.searchParams.set("order_by", "updated_at");
481
514
  u.searchParams.set("sort", "desc");
482
515
  u.searchParams.set("scope", "all");
@@ -484,15 +517,20 @@ var GitLabConnector = class _GitLabConnector extends BaseConnector {
484
517
  u.searchParams.set("updated_after", options.since);
485
518
  }
486
519
  break;
487
- case "pipelines":
520
+ case "pipelines": {
521
+ const status = pushableEq(spec?.filter, "status");
522
+ if (status !== null) {
523
+ u.searchParams.set("status", status);
524
+ }
488
525
  u.searchParams.set("order_by", "updated_at");
489
526
  u.searchParams.set("sort", "desc");
490
527
  if (options.since) {
491
528
  u.searchParams.set("updated_after", options.since);
492
529
  }
493
530
  break;
531
+ }
494
532
  case "issues":
495
- u.searchParams.set("state", "all");
533
+ u.searchParams.set("state", pushableEq(spec?.filter, "state") ?? "all");
496
534
  u.searchParams.set("order_by", "updated_at");
497
535
  u.searchParams.set("sort", "desc");
498
536
  u.searchParams.set("scope", "all");
@@ -518,7 +556,12 @@ var GitLabConnector = class _GitLabConnector extends BaseConnector {
518
556
  }
519
557
  const projectId = projects[idx];
520
558
  const expectedPath = `/api/v4/projects/${projectId}/${resource}`;
521
- const fetchUrl = this.sanitizeUrl(rawPageUrl, expectedPath) ?? this.buildListPageUrl(projectId, resource, options);
559
+ const fetchUrl = this.sanitizeUrl(rawPageUrl, expectedPath) ?? this.buildListPageUrl(
560
+ projectId,
561
+ resource,
562
+ options,
563
+ this.singleSpec(options, LIST_RESOURCE_DEF_KEY[resource])
564
+ );
522
565
  const res = await this.fetch(fetchUrl, resource, signal);
523
566
  const rawNext = parseLinkHeader(res.headers.get("link"))["next"] ?? null;
524
567
  const safeNext = this.sanitizeUrl(rawNext, expectedPath);
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 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 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 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 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 },\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 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 responses: { releases: releasesResponseSchema },\n },\n});\n\nexport const id = 'gitlab';\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 buildListPageUrl(\n projectId: number,\n resource: 'merge_requests' | 'pipelines' | 'issues' | 'releases',\n options: SyncOptions,\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', '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 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 case 'issues':\n u.searchParams.set('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(projectId, resource, options);\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 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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rawdash/connector-gitlab",
3
- "version": "0.22.0",
3
+ "version": "0.23.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.22.0"
27
+ "@rawdash/core": "0.23.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "fast-check": "^4.8.0",