@rawdash/connector-clerk 0.28.0 → 0.28.2
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.js.map +1 -1
- package/package.json +2 -2
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/clerk.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 connectorUserAgent,\n parseEpoch,\n standardRateLimitPolicy,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ChunkedSyncCursor,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nexport const configFields = defineConfigFields(\n z.object({\n secretKey: z.object({ $secret: z.string().min(1) }).meta({\n label: 'Secret key',\n description:\n 'Clerk Backend API secret key (starts with `sk_test_` or `sk_live_`). Create one at Clerk Dashboard -> API Keys.',\n placeholder: 'CLERK_SECRET_KEY',\n secret: true,\n }),\n apiUrl: z\n .string()\n .trim()\n .url('Must be a full URL, e.g. \"https://api.clerk.com\".')\n .default('https://api.clerk.com')\n .meta({\n label: 'API base URL',\n description:\n 'Clerk Backend API base URL. Defaults to https://api.clerk.com; override only if you are pinned to the legacy https://api.clerk.dev host.',\n placeholder: 'https://api.clerk.com',\n }),\n resources: z\n .array(\n z.enum(['users', 'organizations', 'sessions', 'daily_active_users']),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n 'Which Clerk resources to sync. Omit to sync all of them. The secret key has read access to every resource by default; the allowlist exists to skip phases your dashboards do not query.',\n }),\n dauLookbackDays: z.number().int().positive().max(90).optional().meta({\n label: 'DAU lookback (days)',\n description:\n 'How many days back to bucket users by last_active_at when computing the daily_active_users metric. Defaults to 30; the cap is 90.',\n placeholder: '30',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Clerk',\n category: 'security',\n brandColor: '#6C47FF',\n tagline:\n 'Sync users, organizations, sessions, and a derived daily-active-users metric from a Clerk application for sign-up, DAU, and active-session dashboards.',\n vendor: {\n name: 'Clerk',\n domain: 'clerk.com',\n apiDocs: 'https://clerk.com/docs/reference/backend-api',\n website: 'https://clerk.com',\n },\n auth: {\n summary:\n 'A Clerk Backend API secret key (Bearer token). Anyone with the key has read access to every resource the connector syncs.',\n setup: [\n 'Open the Clerk Dashboard for the application you want to sync and navigate to API Keys.',\n 'Copy the Secret key (it starts with `sk_test_` for development instances or `sk_live_` for production).',\n 'Store it as a rawdash secret and reference it from the connector config as `secretKey: secret(\"CLERK_SECRET_KEY\")`.',\n 'Treat the secret key like a root credential - rotate it from the dashboard if it leaks.',\n ],\n },\n rateLimit:\n 'Clerk Backend API throttles per instance (~20 req/s for production, lower for dev). Responses publish X-RateLimit-Remaining / X-RateLimit-Reset (Unix seconds) headers and the shared HTTP client backs off on 429 using the standard rate-limit policy.',\n limitations: [\n 'Each phase paginates via limit / offset and is capped at 50 pages per sync (~25,000 rows). Instances larger than that should run more frequent incremental syncs so each window fits under the cap.',\n 'The daily_active_users metric is derived by bucketing users by the day of their last_active_at timestamp - it counts users whose most recent activity fell on each day, not unique users active across overlapping days.',\n 'Webhooks, JWT templates, instance settings, and impersonation tokens are out of scope.',\n ],\n});\n\nexport type ClerkResource =\n | 'users'\n | 'organizations'\n | 'sessions'\n | 'daily_active_users';\n\nexport interface ClerkSettings {\n apiUrl?: string;\n resources?: readonly ClerkResource[];\n dauLookbackDays?: number;\n}\n\nconst clerkCredentials = {\n secretKey: {\n description: 'Clerk Backend API secret key',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype ClerkCredentials = typeof clerkCredentials;\n\nconst clerkRateLimit = standardRateLimitPolicy({\n remainingHeader: 'x-ratelimit-remaining',\n resetHeader: 'x-ratelimit-reset',\n resetUnit: 's',\n});\n\nconst PHASE_ORDER = [\n 'users',\n 'organizations',\n 'sessions',\n 'daily_active_users',\n] as const;\n\ntype ClerkPhase = (typeof PHASE_ORDER)[number];\n\ntype ClerkSyncCursor = ChunkedSyncCursor<ClerkPhase, string>;\n\nconst isClerkSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\nconst USER_ENTITY = 'clerk_user';\nconst ORG_ENTITY = 'clerk_organization';\nconst SESSION_EVENT = 'clerk_session';\nconst DAU_METRIC = 'clerk_daily_active_users';\n\nconst PAGE_SIZE = 500;\nconst MAX_PAGES = 50;\nconst DEFAULT_DAU_LOOKBACK_DAYS = 30;\nconst DEFAULT_API_URL = 'https://api.clerk.com';\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\nconst SESSION_STATUSES = [\n 'abandoned',\n 'active',\n 'ended',\n 'expired',\n 'removed',\n 'replaced',\n 'revoked',\n] as const;\ntype SessionStatus = (typeof SESSION_STATUSES)[number];\n\nconst idString = z.string().min(1);\n\nconst emailAddressSchema = z.object({\n id: z.string().optional(),\n email_address: z.string().nullish(),\n verification: z.object({ status: z.string().nullish() }).nullish(),\n});\n\nconst userSchema = z.object({\n id: idString,\n primary_email_address_id: z.string().nullish(),\n email_addresses: z.array(emailAddressSchema).nullish(),\n first_name: z.string().nullish(),\n last_name: z.string().nullish(),\n username: z.string().nullish(),\n last_sign_in_at: z.number().nullish(),\n last_active_at: z.number().nullish(),\n created_at: z.number().nullish(),\n updated_at: z.number().nullish(),\n banned: z.boolean().nullish(),\n locked: z.boolean().nullish(),\n});\n\nconst usersResponseSchema = z.array(userSchema);\n\nconst organizationSchema = z.object({\n id: idString,\n name: z.string().nullish(),\n slug: z.string().nullish(),\n members_count: z.number().nullish(),\n created_at: z.number().nullish(),\n updated_at: z.number().nullish(),\n});\n\nconst organizationsResponseSchema = z.union([\n z.object({\n data: z.array(organizationSchema),\n total_count: z.number().optional(),\n }),\n z.array(organizationSchema),\n]);\n\nconst sessionSchema = z.object({\n id: idString,\n user_id: z.string().nullish(),\n client_id: z.string().nullish(),\n status: z.string(),\n last_active_at: z.number().nullish(),\n expire_at: z.number().nullish(),\n abandon_at: z.number().nullish(),\n created_at: z.number().nullish(),\n updated_at: z.number().nullish(),\n});\n\nconst sessionsResponseSchema = z.array(sessionSchema);\n\nexport const clerkResources = defineResources({\n [USER_ENTITY]: {\n shape: 'entity',\n filterable: [\n { field: 'banned', ops: ['eq'], values: ['true', 'false'] },\n { field: 'locked', ops: ['eq'], values: ['true', 'false'] },\n ],\n description:\n 'Clerk users keyed by user id, with primary email, sign-in / activity timestamps, and banned / locked flags.',\n endpoint: 'GET /v1/users',\n notes:\n 'Uses offset pagination (limit / offset) capped at 50 pages (~25,000 users) per sync. Incremental syncs pass options.since through as the last_active_at_since filter.',\n fields: [\n { name: 'email', description: 'Primary email address (when present).' },\n {\n name: 'emailVerified',\n description:\n 'Whether the primary email address is verified (null if no email is set).',\n },\n {\n name: 'lastSignInAt',\n description: 'Most recent sign-in timestamp (Unix ms).',\n },\n {\n name: 'lastActiveAt',\n description:\n 'Most recent activity timestamp (Unix ms). Clerk updates this on every successful client request.',\n },\n {\n name: 'banned',\n description: 'Whether the user has been banned.',\n },\n {\n name: 'locked',\n description: 'Whether the user is locked from signing in.',\n },\n {\n name: 'createdAt',\n description: 'When the user account was created (Unix ms).',\n },\n ],\n responses: { users: usersResponseSchema },\n },\n [ORG_ENTITY]: {\n shape: 'entity',\n filterable: [],\n description:\n 'Clerk organizations keyed by organization id, with display name, slug, and members count.',\n endpoint: 'GET /v1/organizations',\n notes:\n 'Uses offset pagination (limit / offset) capped at 50 pages. Clerk has no created_at / updated_at filter for organizations, so each sync re-scans the full list and short-circuits once a page is entirely older than options.since.',\n fields: [\n { name: 'name', description: 'Organization display name.' },\n { name: 'slug', description: 'Organization URL slug.' },\n {\n name: 'membersCount',\n description: 'Number of users in the organization at sync time.',\n },\n {\n name: 'createdAt',\n description: 'When the organization was created (Unix ms).',\n },\n ],\n responses: { organizations: organizationsResponseSchema },\n },\n [SESSION_EVENT]: {\n shape: 'event',\n filterable: [\n {\n field: 'status',\n ops: ['eq'],\n values: SESSION_STATUSES as unknown as string[],\n },\n ],\n description:\n 'Clerk session events. One event per session row with start_ts set to created_at and attributes carrying user id, status, and last activity.',\n endpoint: 'GET /v1/sessions',\n notes:\n 'Uses offset pagination (limit / offset) capped at 50 pages. Clerk has no since filter on /v1/sessions, so the sync walks newest-first and stops once a page is entirely older than options.since.',\n fields: [\n { name: 'sessionId', description: 'Clerk session id.' },\n { name: 'userId', description: 'User the session belongs to.' },\n {\n name: 'status',\n description:\n 'Session status (active | ended | expired | abandoned | removed | replaced | revoked).',\n },\n {\n name: 'lastActiveAt',\n description: 'Most recent activity timestamp on the session (Unix ms).',\n },\n ],\n responses: { sessions: sessionsResponseSchema },\n },\n [DAU_METRIC]: {\n shape: 'metric',\n description:\n 'Daily active users derived from the Clerk users endpoint: one sample per UTC day in the configured lookback window, counting users whose last_active_at fell on that day.',\n endpoint: 'GET /v1/users',\n unit: 'count',\n granularity: '1d',\n dimensions: [],\n responses: { dau_users: usersResponseSchema },\n },\n});\n\nexport const id = 'clerk';\n\ntype ClerkUser = z.infer<typeof userSchema>;\ntype ClerkOrganization = z.infer<typeof organizationSchema>;\ntype ClerkSession = z.infer<typeof sessionSchema>;\ntype OrganizationsResponse = z.infer<typeof organizationsResponseSchema>;\n\nfunction primaryEmail(user: ClerkUser): {\n email: string | null;\n verified: boolean | null;\n} {\n const list = user.email_addresses ?? [];\n if (list.length === 0) {\n return { email: null, verified: null };\n }\n const primaryId = user.primary_email_address_id ?? null;\n const primary =\n (primaryId !== null ? list.find((e) => e.id === primaryId) : undefined) ??\n list[0]!;\n const verified =\n primary.verification?.status === 'verified'\n ? true\n : primary.verification?.status\n ? false\n : null;\n return { email: primary.email_address ?? null, verified };\n}\n\nfunction isSessionStatus(value: string): value is SessionStatus {\n return (SESSION_STATUSES as readonly string[]).includes(value);\n}\n\nfunction dayBucket(tsMs: number): number {\n return Math.floor(tsMs / DAY_MS) * DAY_MS;\n}\n\nfunction unwrapOrganizations(body: OrganizationsResponse): {\n items: ClerkOrganization[];\n totalCount: number | null;\n} {\n if (Array.isArray(body)) {\n return { items: body, totalCount: null };\n }\n return { items: body.data, totalCount: body.total_count ?? null };\n}\n\nexport class ClerkConnector extends BaseConnector<\n ClerkSettings,\n ClerkCredentials\n> {\n static readonly id = id;\n\n static readonly resources = clerkResources;\n\n static readonly schemas = schemasFromResources(clerkResources);\n\n static create(input: unknown, ctx?: ConnectorContext): ClerkConnector {\n const parsed = configFields.parse(input);\n return new ClerkConnector(\n {\n apiUrl: parsed.apiUrl,\n resources: parsed.resources,\n dauLookbackDays: parsed.dauLookbackDays,\n },\n {\n secretKey: parsed.secretKey,\n },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = clerkCredentials;\n\n private dauBuckets = new Map<number, Set<string>>();\n\n private baseUrl(): string {\n const raw = this.settings.apiUrl ?? DEFAULT_API_URL;\n return raw.replace(/\\/+$/, '');\n }\n\n private dauLookbackDays(): number {\n return this.settings.dauLookbackDays ?? DEFAULT_DAU_LOOKBACK_DAYS;\n }\n\n private dauCutoffMs(): number {\n return Date.now() - this.dauLookbackDays() * DAY_MS;\n }\n\n private parsePageCursor(page: string | null): number {\n if (!page) {\n return 0;\n }\n const n = Number.parseInt(page, 10);\n if (!Number.isFinite(n) || n < 0) {\n return 0;\n }\n return n;\n }\n\n private async apiGet<T>(\n url: string,\n resource: string,\n signal: AbortSignal | undefined,\n ) {\n return this.get<T>(url, {\n resource,\n headers: {\n Authorization: `Bearer ${this.creds.secretKey}`,\n Accept: 'application/json',\n 'User-Agent': connectorUserAgent('clerk'),\n },\n rateLimit: clerkRateLimit,\n signal,\n });\n }\n\n private buildUsersUrl(offset: number, options: SyncOptions): string {\n const u = new URL(`${this.baseUrl()}/v1/users`);\n u.searchParams.set('limit', String(PAGE_SIZE));\n u.searchParams.set('offset', String(offset));\n u.searchParams.set('order_by', '-last_active_at');\n if (options.since) {\n const sinceMs = Date.parse(options.since);\n if (Number.isFinite(sinceMs)) {\n u.searchParams.set('last_active_at_since', String(sinceMs));\n }\n }\n return u.toString();\n }\n\n private buildOrganizationsUrl(offset: number): string {\n const u = new URL(`${this.baseUrl()}/v1/organizations`);\n u.searchParams.set('limit', String(PAGE_SIZE));\n u.searchParams.set('offset', String(offset));\n u.searchParams.set('order_by', '-created_at');\n return u.toString();\n }\n\n private buildSessionsUrl(offset: number): string {\n const u = new URL(`${this.baseUrl()}/v1/sessions`);\n u.searchParams.set('limit', String(PAGE_SIZE));\n u.searchParams.set('offset', String(offset));\n return u.toString();\n }\n\n private buildDauUsersUrl(offset: number): string {\n const u = new URL(`${this.baseUrl()}/v1/users`);\n u.searchParams.set('limit', String(PAGE_SIZE));\n u.searchParams.set('offset', String(offset));\n u.searchParams.set('order_by', '-last_active_at');\n u.searchParams.set('last_active_at_since', String(this.dauCutoffMs()));\n return u.toString();\n }\n\n private async fetchUsersPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: ClerkUser[]; next: string | null }> {\n const offset = this.parsePageCursor(page);\n const url = this.buildUsersUrl(offset, options);\n const res = await this.apiGet<ClerkUser[]>(url, 'users', signal);\n const items = res.body;\n const nextOffset = offset + PAGE_SIZE;\n const pageIndex = Math.floor(offset / PAGE_SIZE);\n const hasMore = items.length >= PAGE_SIZE && pageIndex + 1 < MAX_PAGES;\n return { items, next: hasMore ? String(nextOffset) : null };\n }\n\n private async fetchOrganizationsPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: ClerkOrganization[]; next: string | null }> {\n const offset = this.parsePageCursor(page);\n const url = this.buildOrganizationsUrl(offset);\n const res = await this.apiGet<OrganizationsResponse>(\n url,\n 'organizations',\n signal,\n );\n const { items } = unwrapOrganizations(res.body);\n const sinceMs = options.since ? Date.parse(options.since) : NaN;\n const allOlder =\n Number.isFinite(sinceMs) &&\n items.length > 0 &&\n items.every((o) => (o.created_at ?? 0) < sinceMs);\n const nextOffset = offset + PAGE_SIZE;\n const pageIndex = Math.floor(offset / PAGE_SIZE);\n const hasMore =\n items.length >= PAGE_SIZE && !allOlder && pageIndex + 1 < MAX_PAGES;\n return { items, next: hasMore ? String(nextOffset) : null };\n }\n\n private async fetchSessionsPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: ClerkSession[]; next: string | null }> {\n const offset = this.parsePageCursor(page);\n const url = this.buildSessionsUrl(offset);\n const res = await this.apiGet<ClerkSession[]>(url, 'sessions', signal);\n const items = res.body;\n const sinceMs = options.since ? Date.parse(options.since) : NaN;\n const allOlder =\n Number.isFinite(sinceMs) &&\n items.length > 0 &&\n items.every((s) => (s.created_at ?? 0) < sinceMs);\n const nextOffset = offset + PAGE_SIZE;\n const pageIndex = Math.floor(offset / PAGE_SIZE);\n const hasMore =\n items.length >= PAGE_SIZE && !allOlder && pageIndex + 1 < MAX_PAGES;\n return { items, next: hasMore ? String(nextOffset) : null };\n }\n\n private async fetchDauUsersPage(\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: ClerkUser[]; next: string | null }> {\n const offset = this.parsePageCursor(page);\n const url = this.buildDauUsersUrl(offset);\n const res = await this.apiGet<ClerkUser[]>(url, 'dau_users', signal);\n const items = res.body;\n const nextOffset = offset + PAGE_SIZE;\n const pageIndex = Math.floor(offset / PAGE_SIZE);\n const hasMore = items.length >= PAGE_SIZE && pageIndex + 1 < MAX_PAGES;\n return { items, next: hasMore ? String(nextOffset) : null };\n }\n\n private async writeUsers(\n storage: StorageHandle,\n items: ClerkUser[],\n ): Promise<void> {\n for (const u of items) {\n const { email, verified } = primaryEmail(u);\n const lastSignIn = parseEpoch(u.last_sign_in_at ?? null, 'ms');\n const lastActive = parseEpoch(u.last_active_at ?? null, 'ms');\n const createdAt = parseEpoch(u.created_at ?? null, 'ms');\n const updatedAt = parseEpoch(u.updated_at ?? null, 'ms');\n await storage.entity({\n type: USER_ENTITY,\n id: u.id,\n attributes: {\n email,\n emailVerified: verified,\n lastSignInAt: lastSignIn,\n lastActiveAt: lastActive,\n banned: u.banned ?? false,\n locked: u.locked ?? false,\n createdAt,\n },\n updated_at: updatedAt ?? createdAt ?? 0,\n });\n }\n }\n\n private async writeOrganizations(\n storage: StorageHandle,\n items: ClerkOrganization[],\n ): Promise<void> {\n for (const o of items) {\n const createdAt = parseEpoch(o.created_at ?? null, 'ms');\n const updatedAt = parseEpoch(o.updated_at ?? null, 'ms');\n await storage.entity({\n type: ORG_ENTITY,\n id: o.id,\n attributes: {\n name: o.name ?? null,\n slug: o.slug ?? null,\n membersCount: o.members_count ?? null,\n createdAt,\n },\n updated_at: updatedAt ?? createdAt ?? 0,\n });\n }\n }\n\n private async writeSessions(\n storage: StorageHandle,\n items: ClerkSession[],\n ): Promise<void> {\n for (const s of items) {\n const startTs = parseEpoch(s.created_at ?? null, 'ms');\n if (startTs === null) {\n continue;\n }\n const status = isSessionStatus(s.status) ? s.status : 'active';\n const lastActive = parseEpoch(s.last_active_at ?? null, 'ms');\n await storage.event({\n name: SESSION_EVENT,\n start_ts: startTs,\n end_ts: null,\n attributes: {\n sessionId: s.id,\n userId: s.user_id ?? null,\n status,\n lastActiveAt: lastActive,\n },\n });\n }\n }\n\n private accumulateDau(items: ClerkUser[]): void {\n const cutoff = this.dauCutoffMs();\n for (const u of items) {\n const ts = u.last_active_at;\n if (typeof ts !== 'number' || !Number.isFinite(ts) || ts < cutoff) {\n continue;\n }\n const bucket = dayBucket(ts);\n const set = this.dauBuckets.get(bucket) ?? new Set<string>();\n set.add(u.id);\n this.dauBuckets.set(bucket, set);\n }\n }\n\n private async writeDauSamples(storage: StorageHandle): Promise<void> {\n const samples = Array.from(this.dauBuckets.entries())\n .sort(([a], [b]) => a - b)\n .map(([ts, set]) => ({\n name: DAU_METRIC,\n ts,\n value: set.size,\n attributes: {},\n }));\n await storage.metrics(samples, { names: [DAU_METRIC] });\n }\n\n private async clearScopeOnFirstPage(\n storage: StorageHandle,\n phase: ClerkPhase,\n isFull: boolean,\n ): Promise<void> {\n if (phase === 'daily_active_users') {\n this.dauBuckets.clear();\n await storage.metrics([], { names: [DAU_METRIC] });\n return;\n }\n if (!isFull) {\n return;\n }\n switch (phase) {\n case 'users':\n await storage.entities([], { types: [USER_ENTITY] });\n return;\n case 'organizations':\n await storage.entities([], { types: [ORG_ENTITY] });\n return;\n case 'sessions':\n await storage.events([], { names: [SESSION_EVENT] });\n return;\n }\n }\n\n private resolveCursor(cursor: unknown): ClerkSyncCursor | undefined {\n return isClerkSyncCursor(cursor) ? cursor : undefined;\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 isFull = options.mode === 'full';\n\n const phases = selectActivePhases<ClerkResource, ClerkPhase>(\n (r) => r,\n PHASE_ORDER,\n this.settings.resources,\n );\n\n return paginateChunked<ClerkPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n switch (phase) {\n case 'users':\n return this.fetchUsersPage(page, options, sig);\n case 'organizations':\n return this.fetchOrganizationsPage(page, options, sig);\n case 'sessions':\n return this.fetchSessionsPage(page, options, sig);\n case 'daily_active_users':\n return this.fetchDauUsersPage(page, sig);\n }\n },\n writeBatch: async (phase, items, page) => {\n if (page === null) {\n await this.clearScopeOnFirstPage(storage, phase, isFull);\n }\n switch (phase) {\n case 'users':\n await this.writeUsers(storage, items as ClerkUser[]);\n return;\n case 'organizations':\n await this.writeOrganizations(\n storage,\n items as ClerkOrganization[],\n );\n return;\n case 'sessions':\n await this.writeSessions(storage, items as ClerkSession[]);\n return;\n case 'daily_active_users':\n this.accumulateDau(items as ClerkUser[]);\n await this.writeDauSamples(storage);\n return;\n }\n },\n });\n }\n}\n","import { ClerkConnector } from './clerk';\n\nexport {\n ClerkConnector,\n clerkResources as resources,\n configFields,\n doc,\n id,\n} from './clerk';\nexport type { ClerkResource, ClerkSettings } from './clerk';\nexport default ClerkConnector;\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/clerk.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(\n res: Response,\n parseJson: boolean,\n binary: boolean,\n): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n if (binary) {\n return new Uint8Array(await res.arrayBuffer());\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 const binary = req.binary ?? false;\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, binary);\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 connectorUserAgent,\n parseEpoch,\n standardRateLimitPolicy,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ChunkedSyncCursor,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nexport const configFields = defineConfigFields(\n z.object({\n secretKey: z.object({ $secret: z.string().min(1) }).meta({\n label: 'Secret key',\n description:\n 'Clerk Backend API secret key (starts with `sk_test_` or `sk_live_`). Create one at Clerk Dashboard -> API Keys.',\n placeholder: 'CLERK_SECRET_KEY',\n secret: true,\n }),\n apiUrl: z\n .string()\n .trim()\n .url('Must be a full URL, e.g. \"https://api.clerk.com\".')\n .default('https://api.clerk.com')\n .meta({\n label: 'API base URL',\n description:\n 'Clerk Backend API base URL. Defaults to https://api.clerk.com; override only if you are pinned to the legacy https://api.clerk.dev host.',\n placeholder: 'https://api.clerk.com',\n }),\n resources: z\n .array(\n z.enum(['users', 'organizations', 'sessions', 'daily_active_users']),\n )\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n 'Which Clerk resources to sync. Omit to sync all of them. The secret key has read access to every resource by default; the allowlist exists to skip phases your dashboards do not query.',\n }),\n dauLookbackDays: z.number().int().positive().max(90).optional().meta({\n label: 'DAU lookback (days)',\n description:\n 'How many days back to bucket users by last_active_at when computing the daily_active_users metric. Defaults to 30; the cap is 90.',\n placeholder: '30',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Clerk',\n category: 'security',\n brandColor: '#6C47FF',\n tagline:\n 'Sync users, organizations, sessions, and a derived daily-active-users metric from a Clerk application for sign-up, DAU, and active-session dashboards.',\n vendor: {\n name: 'Clerk',\n domain: 'clerk.com',\n apiDocs: 'https://clerk.com/docs/reference/backend-api',\n website: 'https://clerk.com',\n },\n auth: {\n summary:\n 'A Clerk Backend API secret key (Bearer token). Anyone with the key has read access to every resource the connector syncs.',\n setup: [\n 'Open the Clerk Dashboard for the application you want to sync and navigate to API Keys.',\n 'Copy the Secret key (it starts with `sk_test_` for development instances or `sk_live_` for production).',\n 'Store it as a rawdash secret and reference it from the connector config as `secretKey: secret(\"CLERK_SECRET_KEY\")`.',\n 'Treat the secret key like a root credential - rotate it from the dashboard if it leaks.',\n ],\n },\n rateLimit:\n 'Clerk Backend API throttles per instance (~20 req/s for production, lower for dev). Responses publish X-RateLimit-Remaining / X-RateLimit-Reset (Unix seconds) headers and the shared HTTP client backs off on 429 using the standard rate-limit policy.',\n limitations: [\n 'Each phase paginates via limit / offset and is capped at 50 pages per sync (~25,000 rows). Instances larger than that should run more frequent incremental syncs so each window fits under the cap.',\n 'The daily_active_users metric is derived by bucketing users by the day of their last_active_at timestamp - it counts users whose most recent activity fell on each day, not unique users active across overlapping days.',\n 'Webhooks, JWT templates, instance settings, and impersonation tokens are out of scope.',\n ],\n});\n\nexport type ClerkResource =\n | 'users'\n | 'organizations'\n | 'sessions'\n | 'daily_active_users';\n\nexport interface ClerkSettings {\n apiUrl?: string;\n resources?: readonly ClerkResource[];\n dauLookbackDays?: number;\n}\n\nconst clerkCredentials = {\n secretKey: {\n description: 'Clerk Backend API secret key',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype ClerkCredentials = typeof clerkCredentials;\n\nconst clerkRateLimit = standardRateLimitPolicy({\n remainingHeader: 'x-ratelimit-remaining',\n resetHeader: 'x-ratelimit-reset',\n resetUnit: 's',\n});\n\nconst PHASE_ORDER = [\n 'users',\n 'organizations',\n 'sessions',\n 'daily_active_users',\n] as const;\n\ntype ClerkPhase = (typeof PHASE_ORDER)[number];\n\ntype ClerkSyncCursor = ChunkedSyncCursor<ClerkPhase, string>;\n\nconst isClerkSyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\nconst USER_ENTITY = 'clerk_user';\nconst ORG_ENTITY = 'clerk_organization';\nconst SESSION_EVENT = 'clerk_session';\nconst DAU_METRIC = 'clerk_daily_active_users';\n\nconst PAGE_SIZE = 500;\nconst MAX_PAGES = 50;\nconst DEFAULT_DAU_LOOKBACK_DAYS = 30;\nconst DEFAULT_API_URL = 'https://api.clerk.com';\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\nconst SESSION_STATUSES = [\n 'abandoned',\n 'active',\n 'ended',\n 'expired',\n 'removed',\n 'replaced',\n 'revoked',\n] as const;\ntype SessionStatus = (typeof SESSION_STATUSES)[number];\n\nconst idString = z.string().min(1);\n\nconst emailAddressSchema = z.object({\n id: z.string().optional(),\n email_address: z.string().nullish(),\n verification: z.object({ status: z.string().nullish() }).nullish(),\n});\n\nconst userSchema = z.object({\n id: idString,\n primary_email_address_id: z.string().nullish(),\n email_addresses: z.array(emailAddressSchema).nullish(),\n first_name: z.string().nullish(),\n last_name: z.string().nullish(),\n username: z.string().nullish(),\n last_sign_in_at: z.number().nullish(),\n last_active_at: z.number().nullish(),\n created_at: z.number().nullish(),\n updated_at: z.number().nullish(),\n banned: z.boolean().nullish(),\n locked: z.boolean().nullish(),\n});\n\nconst usersResponseSchema = z.array(userSchema);\n\nconst organizationSchema = z.object({\n id: idString,\n name: z.string().nullish(),\n slug: z.string().nullish(),\n members_count: z.number().nullish(),\n created_at: z.number().nullish(),\n updated_at: z.number().nullish(),\n});\n\nconst organizationsResponseSchema = z.union([\n z.object({\n data: z.array(organizationSchema),\n total_count: z.number().optional(),\n }),\n z.array(organizationSchema),\n]);\n\nconst sessionSchema = z.object({\n id: idString,\n user_id: z.string().nullish(),\n client_id: z.string().nullish(),\n status: z.string(),\n last_active_at: z.number().nullish(),\n expire_at: z.number().nullish(),\n abandon_at: z.number().nullish(),\n created_at: z.number().nullish(),\n updated_at: z.number().nullish(),\n});\n\nconst sessionsResponseSchema = z.array(sessionSchema);\n\nexport const clerkResources = defineResources({\n [USER_ENTITY]: {\n shape: 'entity',\n filterable: [\n { field: 'banned', ops: ['eq'], values: ['true', 'false'] },\n { field: 'locked', ops: ['eq'], values: ['true', 'false'] },\n ],\n description:\n 'Clerk users keyed by user id, with primary email, sign-in / activity timestamps, and banned / locked flags.',\n endpoint: 'GET /v1/users',\n notes:\n 'Uses offset pagination (limit / offset) capped at 50 pages (~25,000 users) per sync. Incremental syncs pass options.since through as the last_active_at_since filter.',\n fields: [\n { name: 'email', description: 'Primary email address (when present).' },\n {\n name: 'emailVerified',\n description:\n 'Whether the primary email address is verified (null if no email is set).',\n },\n {\n name: 'lastSignInAt',\n description: 'Most recent sign-in timestamp (Unix ms).',\n },\n {\n name: 'lastActiveAt',\n description:\n 'Most recent activity timestamp (Unix ms). Clerk updates this on every successful client request.',\n },\n {\n name: 'banned',\n description: 'Whether the user has been banned.',\n },\n {\n name: 'locked',\n description: 'Whether the user is locked from signing in.',\n },\n {\n name: 'createdAt',\n description: 'When the user account was created (Unix ms).',\n },\n ],\n responses: { users: usersResponseSchema },\n },\n [ORG_ENTITY]: {\n shape: 'entity',\n filterable: [],\n description:\n 'Clerk organizations keyed by organization id, with display name, slug, and members count.',\n endpoint: 'GET /v1/organizations',\n notes:\n 'Uses offset pagination (limit / offset) capped at 50 pages. Clerk has no created_at / updated_at filter for organizations, so each sync re-scans the full list and short-circuits once a page is entirely older than options.since.',\n fields: [\n { name: 'name', description: 'Organization display name.' },\n { name: 'slug', description: 'Organization URL slug.' },\n {\n name: 'membersCount',\n description: 'Number of users in the organization at sync time.',\n },\n {\n name: 'createdAt',\n description: 'When the organization was created (Unix ms).',\n },\n ],\n responses: { organizations: organizationsResponseSchema },\n },\n [SESSION_EVENT]: {\n shape: 'event',\n filterable: [\n {\n field: 'status',\n ops: ['eq'],\n values: SESSION_STATUSES as unknown as string[],\n },\n ],\n description:\n 'Clerk session events. One event per session row with start_ts set to created_at and attributes carrying user id, status, and last activity.',\n endpoint: 'GET /v1/sessions',\n notes:\n 'Uses offset pagination (limit / offset) capped at 50 pages. Clerk has no since filter on /v1/sessions, so the sync walks newest-first and stops once a page is entirely older than options.since.',\n fields: [\n { name: 'sessionId', description: 'Clerk session id.' },\n { name: 'userId', description: 'User the session belongs to.' },\n {\n name: 'status',\n description:\n 'Session status (active | ended | expired | abandoned | removed | replaced | revoked).',\n },\n {\n name: 'lastActiveAt',\n description: 'Most recent activity timestamp on the session (Unix ms).',\n },\n ],\n responses: { sessions: sessionsResponseSchema },\n },\n [DAU_METRIC]: {\n shape: 'metric',\n description:\n 'Daily active users derived from the Clerk users endpoint: one sample per UTC day in the configured lookback window, counting users whose last_active_at fell on that day.',\n endpoint: 'GET /v1/users',\n unit: 'count',\n granularity: '1d',\n dimensions: [],\n responses: { dau_users: usersResponseSchema },\n },\n});\n\nexport const id = 'clerk';\n\ntype ClerkUser = z.infer<typeof userSchema>;\ntype ClerkOrganization = z.infer<typeof organizationSchema>;\ntype ClerkSession = z.infer<typeof sessionSchema>;\ntype OrganizationsResponse = z.infer<typeof organizationsResponseSchema>;\n\nfunction primaryEmail(user: ClerkUser): {\n email: string | null;\n verified: boolean | null;\n} {\n const list = user.email_addresses ?? [];\n if (list.length === 0) {\n return { email: null, verified: null };\n }\n const primaryId = user.primary_email_address_id ?? null;\n const primary =\n (primaryId !== null ? list.find((e) => e.id === primaryId) : undefined) ??\n list[0]!;\n const verified =\n primary.verification?.status === 'verified'\n ? true\n : primary.verification?.status\n ? false\n : null;\n return { email: primary.email_address ?? null, verified };\n}\n\nfunction isSessionStatus(value: string): value is SessionStatus {\n return (SESSION_STATUSES as readonly string[]).includes(value);\n}\n\nfunction dayBucket(tsMs: number): number {\n return Math.floor(tsMs / DAY_MS) * DAY_MS;\n}\n\nfunction unwrapOrganizations(body: OrganizationsResponse): {\n items: ClerkOrganization[];\n totalCount: number | null;\n} {\n if (Array.isArray(body)) {\n return { items: body, totalCount: null };\n }\n return { items: body.data, totalCount: body.total_count ?? null };\n}\n\nexport class ClerkConnector extends BaseConnector<\n ClerkSettings,\n ClerkCredentials\n> {\n static readonly id = id;\n\n static readonly resources = clerkResources;\n\n static readonly schemas = schemasFromResources(clerkResources);\n\n static create(input: unknown, ctx?: ConnectorContext): ClerkConnector {\n const parsed = configFields.parse(input);\n return new ClerkConnector(\n {\n apiUrl: parsed.apiUrl,\n resources: parsed.resources,\n dauLookbackDays: parsed.dauLookbackDays,\n },\n {\n secretKey: parsed.secretKey,\n },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = clerkCredentials;\n\n private dauBuckets = new Map<number, Set<string>>();\n\n private baseUrl(): string {\n const raw = this.settings.apiUrl ?? DEFAULT_API_URL;\n return raw.replace(/\\/+$/, '');\n }\n\n private dauLookbackDays(): number {\n return this.settings.dauLookbackDays ?? DEFAULT_DAU_LOOKBACK_DAYS;\n }\n\n private dauCutoffMs(): number {\n return Date.now() - this.dauLookbackDays() * DAY_MS;\n }\n\n private parsePageCursor(page: string | null): number {\n if (!page) {\n return 0;\n }\n const n = Number.parseInt(page, 10);\n if (!Number.isFinite(n) || n < 0) {\n return 0;\n }\n return n;\n }\n\n private async apiGet<T>(\n url: string,\n resource: string,\n signal: AbortSignal | undefined,\n ) {\n return this.get<T>(url, {\n resource,\n headers: {\n Authorization: `Bearer ${this.creds.secretKey}`,\n Accept: 'application/json',\n 'User-Agent': connectorUserAgent('clerk'),\n },\n rateLimit: clerkRateLimit,\n signal,\n });\n }\n\n private buildUsersUrl(offset: number, options: SyncOptions): string {\n const u = new URL(`${this.baseUrl()}/v1/users`);\n u.searchParams.set('limit', String(PAGE_SIZE));\n u.searchParams.set('offset', String(offset));\n u.searchParams.set('order_by', '-last_active_at');\n if (options.since) {\n const sinceMs = Date.parse(options.since);\n if (Number.isFinite(sinceMs)) {\n u.searchParams.set('last_active_at_since', String(sinceMs));\n }\n }\n return u.toString();\n }\n\n private buildOrganizationsUrl(offset: number): string {\n const u = new URL(`${this.baseUrl()}/v1/organizations`);\n u.searchParams.set('limit', String(PAGE_SIZE));\n u.searchParams.set('offset', String(offset));\n u.searchParams.set('order_by', '-created_at');\n return u.toString();\n }\n\n private buildSessionsUrl(offset: number): string {\n const u = new URL(`${this.baseUrl()}/v1/sessions`);\n u.searchParams.set('limit', String(PAGE_SIZE));\n u.searchParams.set('offset', String(offset));\n return u.toString();\n }\n\n private buildDauUsersUrl(offset: number): string {\n const u = new URL(`${this.baseUrl()}/v1/users`);\n u.searchParams.set('limit', String(PAGE_SIZE));\n u.searchParams.set('offset', String(offset));\n u.searchParams.set('order_by', '-last_active_at');\n u.searchParams.set('last_active_at_since', String(this.dauCutoffMs()));\n return u.toString();\n }\n\n private async fetchUsersPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: ClerkUser[]; next: string | null }> {\n const offset = this.parsePageCursor(page);\n const url = this.buildUsersUrl(offset, options);\n const res = await this.apiGet<ClerkUser[]>(url, 'users', signal);\n const items = res.body;\n const nextOffset = offset + PAGE_SIZE;\n const pageIndex = Math.floor(offset / PAGE_SIZE);\n const hasMore = items.length >= PAGE_SIZE && pageIndex + 1 < MAX_PAGES;\n return { items, next: hasMore ? String(nextOffset) : null };\n }\n\n private async fetchOrganizationsPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: ClerkOrganization[]; next: string | null }> {\n const offset = this.parsePageCursor(page);\n const url = this.buildOrganizationsUrl(offset);\n const res = await this.apiGet<OrganizationsResponse>(\n url,\n 'organizations',\n signal,\n );\n const { items } = unwrapOrganizations(res.body);\n const sinceMs = options.since ? Date.parse(options.since) : NaN;\n const allOlder =\n Number.isFinite(sinceMs) &&\n items.length > 0 &&\n items.every((o) => (o.created_at ?? 0) < sinceMs);\n const nextOffset = offset + PAGE_SIZE;\n const pageIndex = Math.floor(offset / PAGE_SIZE);\n const hasMore =\n items.length >= PAGE_SIZE && !allOlder && pageIndex + 1 < MAX_PAGES;\n return { items, next: hasMore ? String(nextOffset) : null };\n }\n\n private async fetchSessionsPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: ClerkSession[]; next: string | null }> {\n const offset = this.parsePageCursor(page);\n const url = this.buildSessionsUrl(offset);\n const res = await this.apiGet<ClerkSession[]>(url, 'sessions', signal);\n const items = res.body;\n const sinceMs = options.since ? Date.parse(options.since) : NaN;\n const allOlder =\n Number.isFinite(sinceMs) &&\n items.length > 0 &&\n items.every((s) => (s.created_at ?? 0) < sinceMs);\n const nextOffset = offset + PAGE_SIZE;\n const pageIndex = Math.floor(offset / PAGE_SIZE);\n const hasMore =\n items.length >= PAGE_SIZE && !allOlder && pageIndex + 1 < MAX_PAGES;\n return { items, next: hasMore ? String(nextOffset) : null };\n }\n\n private async fetchDauUsersPage(\n page: string | null,\n signal: AbortSignal | undefined,\n ): Promise<{ items: ClerkUser[]; next: string | null }> {\n const offset = this.parsePageCursor(page);\n const url = this.buildDauUsersUrl(offset);\n const res = await this.apiGet<ClerkUser[]>(url, 'dau_users', signal);\n const items = res.body;\n const nextOffset = offset + PAGE_SIZE;\n const pageIndex = Math.floor(offset / PAGE_SIZE);\n const hasMore = items.length >= PAGE_SIZE && pageIndex + 1 < MAX_PAGES;\n return { items, next: hasMore ? String(nextOffset) : null };\n }\n\n private async writeUsers(\n storage: StorageHandle,\n items: ClerkUser[],\n ): Promise<void> {\n for (const u of items) {\n const { email, verified } = primaryEmail(u);\n const lastSignIn = parseEpoch(u.last_sign_in_at ?? null, 'ms');\n const lastActive = parseEpoch(u.last_active_at ?? null, 'ms');\n const createdAt = parseEpoch(u.created_at ?? null, 'ms');\n const updatedAt = parseEpoch(u.updated_at ?? null, 'ms');\n await storage.entity({\n type: USER_ENTITY,\n id: u.id,\n attributes: {\n email,\n emailVerified: verified,\n lastSignInAt: lastSignIn,\n lastActiveAt: lastActive,\n banned: u.banned ?? false,\n locked: u.locked ?? false,\n createdAt,\n },\n updated_at: updatedAt ?? createdAt ?? 0,\n });\n }\n }\n\n private async writeOrganizations(\n storage: StorageHandle,\n items: ClerkOrganization[],\n ): Promise<void> {\n for (const o of items) {\n const createdAt = parseEpoch(o.created_at ?? null, 'ms');\n const updatedAt = parseEpoch(o.updated_at ?? null, 'ms');\n await storage.entity({\n type: ORG_ENTITY,\n id: o.id,\n attributes: {\n name: o.name ?? null,\n slug: o.slug ?? null,\n membersCount: o.members_count ?? null,\n createdAt,\n },\n updated_at: updatedAt ?? createdAt ?? 0,\n });\n }\n }\n\n private async writeSessions(\n storage: StorageHandle,\n items: ClerkSession[],\n ): Promise<void> {\n for (const s of items) {\n const startTs = parseEpoch(s.created_at ?? null, 'ms');\n if (startTs === null) {\n continue;\n }\n const status = isSessionStatus(s.status) ? s.status : 'active';\n const lastActive = parseEpoch(s.last_active_at ?? null, 'ms');\n await storage.event({\n name: SESSION_EVENT,\n start_ts: startTs,\n end_ts: null,\n attributes: {\n sessionId: s.id,\n userId: s.user_id ?? null,\n status,\n lastActiveAt: lastActive,\n },\n });\n }\n }\n\n private accumulateDau(items: ClerkUser[]): void {\n const cutoff = this.dauCutoffMs();\n for (const u of items) {\n const ts = u.last_active_at;\n if (typeof ts !== 'number' || !Number.isFinite(ts) || ts < cutoff) {\n continue;\n }\n const bucket = dayBucket(ts);\n const set = this.dauBuckets.get(bucket) ?? new Set<string>();\n set.add(u.id);\n this.dauBuckets.set(bucket, set);\n }\n }\n\n private async writeDauSamples(storage: StorageHandle): Promise<void> {\n const samples = Array.from(this.dauBuckets.entries())\n .sort(([a], [b]) => a - b)\n .map(([ts, set]) => ({\n name: DAU_METRIC,\n ts,\n value: set.size,\n attributes: {},\n }));\n await storage.metrics(samples, { names: [DAU_METRIC] });\n }\n\n private async clearScopeOnFirstPage(\n storage: StorageHandle,\n phase: ClerkPhase,\n isFull: boolean,\n ): Promise<void> {\n if (phase === 'daily_active_users') {\n this.dauBuckets.clear();\n await storage.metrics([], { names: [DAU_METRIC] });\n return;\n }\n if (!isFull) {\n return;\n }\n switch (phase) {\n case 'users':\n await storage.entities([], { types: [USER_ENTITY] });\n return;\n case 'organizations':\n await storage.entities([], { types: [ORG_ENTITY] });\n return;\n case 'sessions':\n await storage.events([], { names: [SESSION_EVENT] });\n return;\n }\n }\n\n private resolveCursor(cursor: unknown): ClerkSyncCursor | undefined {\n return isClerkSyncCursor(cursor) ? cursor : undefined;\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 isFull = options.mode === 'full';\n\n const phases = selectActivePhases<ClerkResource, ClerkPhase>(\n (r) => r,\n PHASE_ORDER,\n this.settings.resources,\n );\n\n return paginateChunked<ClerkPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n fetchPage: async (phase, page, sig) => {\n switch (phase) {\n case 'users':\n return this.fetchUsersPage(page, options, sig);\n case 'organizations':\n return this.fetchOrganizationsPage(page, options, sig);\n case 'sessions':\n return this.fetchSessionsPage(page, options, sig);\n case 'daily_active_users':\n return this.fetchDauUsersPage(page, sig);\n }\n },\n writeBatch: async (phase, items, page) => {\n if (page === null) {\n await this.clearScopeOnFirstPage(storage, phase, isFull);\n }\n switch (phase) {\n case 'users':\n await this.writeUsers(storage, items as ClerkUser[]);\n return;\n case 'organizations':\n await this.writeOrganizations(\n storage,\n items as ClerkOrganization[],\n );\n return;\n case 'sessions':\n await this.writeSessions(storage, items as ClerkSession[]);\n return;\n case 'daily_active_users':\n this.accumulateDau(items as ClerkUser[]);\n await this.writeDauSamples(storage);\n return;\n }\n },\n });\n }\n}\n","import { ClerkConnector } from './clerk';\n\nexport {\n ClerkConnector,\n clerkResources as resources,\n configFields,\n doc,\n id,\n} from './clerk';\nexport type { ClerkResource, ClerkSettings } from './clerk';\nexport default ClerkConnector;\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rawdash/connector-clerk",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.2",
|
|
4
4
|
"description": "Rawdash connector for Clerk — syncs users, organizations, sessions, and a daily-active-users metric from the Clerk Backend API into the six-shape storage model",
|
|
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.28.
|
|
27
|
+
"@rawdash/core": "0.28.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"fast-check": "^4.8.0",
|