lakesync 0.1.8 → 0.2.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/adapter-types-DwsQGQS4.d.ts +94 -0
- package/dist/adapter.d.ts +23 -49
- package/dist/adapter.js +9 -4
- package/dist/analyst.js +1 -1
- package/dist/{base-poller-Bj9kX9dv.d.ts → base-poller-Y7ORYgUv.d.ts} +2 -0
- package/dist/catalogue.js +2 -2
- package/dist/{chunk-LDFFCG2K.js → chunk-4SG66H5K.js} +44 -31
- package/dist/chunk-4SG66H5K.js.map +1 -0
- package/dist/{chunk-LPWXOYNS.js → chunk-C4KD6YKP.js} +59 -43
- package/dist/chunk-C4KD6YKP.js.map +1 -0
- package/dist/{chunk-JI4C4R5H.js → chunk-FIIHPQMQ.js} +196 -118
- package/dist/chunk-FIIHPQMQ.js.map +1 -0
- package/dist/{chunk-TMLG32QV.js → chunk-U2NV4DUX.js} +2 -2
- package/dist/{chunk-QNITY4F6.js → chunk-XVP5DJJ7.js} +16 -13
- package/dist/{chunk-QNITY4F6.js.map → chunk-XVP5DJJ7.js.map} +1 -1
- package/dist/{chunk-KVSWLIJR.js → chunk-YHYBLU6W.js} +2 -2
- package/dist/{chunk-PYRS74YP.js → chunk-ZNY4DSFU.js} +16 -13
- package/dist/{chunk-PYRS74YP.js.map → chunk-ZNY4DSFU.js.map} +1 -1
- package/dist/{chunk-SSICS5KI.js → chunk-ZU7RC7CT.js} +2 -2
- package/dist/client.d.ts +28 -10
- package/dist/client.js +150 -29
- package/dist/client.js.map +1 -1
- package/dist/compactor.d.ts +1 -1
- package/dist/compactor.js +3 -3
- package/dist/connector-jira.d.ts +13 -3
- package/dist/connector-jira.js +6 -2
- package/dist/connector-salesforce.d.ts +13 -3
- package/dist/connector-salesforce.js +6 -2
- package/dist/{coordinator-NXy6tA0h.d.ts → coordinator-eGmZMnJ_.d.ts} +99 -16
- package/dist/create-poller-Cc2MGfhh.d.ts +55 -0
- package/dist/factory-DFfR-030.d.ts +33 -0
- package/dist/gateway-server.d.ts +398 -95
- package/dist/gateway-server.js +743 -56
- package/dist/gateway-server.js.map +1 -1
- package/dist/gateway.d.ts +14 -8
- package/dist/gateway.js +6 -5
- package/dist/index.d.ts +45 -73
- package/dist/index.js +5 -3
- package/dist/parquet.js +2 -2
- package/dist/proto.js +2 -2
- package/dist/react.d.ts +3 -3
- package/dist/{registry-BcspAtZI.d.ts → registry-Dd8JuW8T.d.ts} +1 -1
- package/dist/{request-handler-pUvL7ozF.d.ts → request-handler-B1I5xDOx.d.ts} +71 -27
- package/dist/{src-ROW4XLO7.js → src-WU7IBVC4.js} +6 -4
- package/dist/{types-BrcD1oJg.d.ts → types-D2C9jTbL.d.ts} +33 -23
- package/package.json +1 -1
- package/dist/auth-CAVutXzx.d.ts +0 -30
- package/dist/chunk-JI4C4R5H.js.map +0 -1
- package/dist/chunk-LDFFCG2K.js.map +0 -1
- package/dist/chunk-LPWXOYNS.js.map +0 -1
- package/dist/db-types-CfLMUBfW.d.ts +0 -29
- package/dist/src-B6NLV3FP.js +0 -27
- package/dist/src-ROW4XLO7.js.map +0 -1
- package/dist/src-ZRHKG42A.js +0 -25
- package/dist/src-ZRHKG42A.js.map +0 -1
- package/dist/types-DSC_EiwR.d.ts +0 -45
- /package/dist/{chunk-TMLG32QV.js.map → chunk-U2NV4DUX.js.map} +0 -0
- /package/dist/{chunk-KVSWLIJR.js.map → chunk-YHYBLU6W.js.map} +0 -0
- /package/dist/{chunk-SSICS5KI.js.map → chunk-ZU7RC7CT.js.map} +0 -0
- /package/dist/{src-B6NLV3FP.js.map → src-WU7IBVC4.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../connector-jira/src/errors.ts","../../connector-jira/src/client.ts","../../connector-jira/src/mapping.ts","../../connector-jira/src/poller.ts","../../connector-jira/src/schemas.ts","../../connector-jira/src/test-connection.ts","../../connector-jira/src/index.ts"],"sourcesContent":["import { LakeSyncError } from \"@lakesync/core\";\n\n/** HTTP error from the Jira REST API. */\nexport class JiraApiError extends LakeSyncError {\n\t/** HTTP status code returned by Jira. */\n\treadonly statusCode: number;\n\t/** Raw response body from Jira. */\n\treadonly responseBody: string;\n\n\tconstructor(statusCode: number, responseBody: string, cause?: Error) {\n\t\tsuper(`Jira API error (${statusCode}): ${responseBody}`, \"JIRA_API_ERROR\", cause);\n\t\tthis.statusCode = statusCode;\n\t\tthis.responseBody = responseBody;\n\t}\n}\n\n/** Rate limit error (HTTP 429) from the Jira REST API. */\nexport class JiraRateLimitError extends LakeSyncError {\n\t/** Milliseconds to wait before retrying. */\n\treadonly retryAfterMs: number;\n\n\tconstructor(retryAfterMs: number, cause?: Error) {\n\t\tsuper(`Jira rate limited — retry after ${retryAfterMs}ms`, \"JIRA_RATE_LIMITED\", cause);\n\t\tthis.retryAfterMs = retryAfterMs;\n\t}\n}\n","// ---------------------------------------------------------------------------\n// JiraClient — HTTP wrapper for Jira Cloud REST API v3\n// ---------------------------------------------------------------------------\n\nimport { Err, Ok, type Result } from \"@lakesync/core\";\nimport { JiraApiError, JiraRateLimitError } from \"./errors\";\nimport type {\n\tJiraComment,\n\tJiraCommentPage,\n\tJiraConnectorConfig,\n\tJiraIssue,\n\tJiraProject,\n\tJiraProjectPage,\n\tJiraSearchResponse,\n} from \"./types\";\n\nconst MAX_RESULTS = 100;\nconst MAX_RATE_LIMIT_RETRIES = 3;\nconst DEFAULT_RETRY_AFTER_MS = 10_000;\n\n/** Fields requested for issue search. */\nconst ISSUE_FIELDS = [\n\t\"summary\",\n\t\"description\",\n\t\"status\",\n\t\"priority\",\n\t\"assignee\",\n\t\"reporter\",\n\t\"labels\",\n\t\"created\",\n\t\"updated\",\n\t\"project\",\n\t\"issuetype\",\n];\n\n/**\n * HTTP client for the Jira Cloud REST API v3.\n *\n * Uses Basic authentication (email + API token) and global `fetch`.\n * All public methods return `Result<T, JiraApiError>`.\n */\nexport class JiraClient {\n\tprivate readonly baseUrl: string;\n\tprivate readonly authHeader: string;\n\n\tconstructor(config: JiraConnectorConfig) {\n\t\tthis.baseUrl = `https://${config.domain}.atlassian.net`;\n\t\tthis.authHeader = `Basic ${btoa(`${config.email}:${config.apiToken}`)}`;\n\t}\n\n\t/**\n\t * Search for issues via JQL with auto-pagination.\n\t *\n\t * Uses the `/rest/api/3/search/jql` endpoint with token-based pagination.\n\t * When `updatedSince` is provided, appends `AND updated >= \"date\"` to the JQL.\n\t * Empty JQL is replaced with `project is not EMPTY` (the new endpoint\n\t * rejects unbounded queries).\n\t *\n\t * @param limit — optional cap on the total number of issues returned.\n\t */\n\tasync searchIssues(\n\t\tjql: string,\n\t\tupdatedSince?: string,\n\t\tlimit?: number,\n\t): Promise<Result<JiraIssue[], JiraApiError | JiraRateLimitError>> {\n\t\tlet effectiveJql = jql || \"\";\n\n\t\tif (updatedSince) {\n\t\t\tconst clause = `updated >= \"${updatedSince}\"`;\n\t\t\teffectiveJql = effectiveJql.length > 0 ? `(${effectiveJql}) AND ${clause}` : clause;\n\t\t}\n\n\t\t// The /search/jql endpoint rejects unbounded queries\n\t\tif (effectiveJql.length === 0) {\n\t\t\teffectiveJql = \"project is not EMPTY\";\n\t\t}\n\n\t\tconst allIssues: JiraIssue[] = [];\n\t\tlet nextPageToken: string | undefined;\n\t\tconst pageSize = limit !== undefined ? Math.min(limit, MAX_RESULTS) : MAX_RESULTS;\n\n\t\twhile (true) {\n\t\t\tconst body: Record<string, unknown> = {\n\t\t\t\tjql: effectiveJql,\n\t\t\t\tmaxResults: pageSize,\n\t\t\t\tfields: ISSUE_FIELDS,\n\t\t\t};\n\t\t\tif (nextPageToken) {\n\t\t\t\tbody.nextPageToken = nextPageToken;\n\t\t\t}\n\n\t\t\tconst result = await this.request<JiraSearchResponse>(\"/rest/api/3/search/jql\", \"POST\", body);\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tconst page = result.value;\n\t\t\tfor (const issue of page.issues) {\n\t\t\t\tallIssues.push(issue);\n\t\t\t}\n\n\t\t\tif (limit !== undefined && allIssues.length >= limit) break;\n\t\t\tif (page.isLast || !page.nextPageToken) break;\n\t\t\tnextPageToken = page.nextPageToken;\n\t\t}\n\n\t\treturn Ok(limit !== undefined ? allIssues.slice(0, limit) : allIssues);\n\t}\n\n\t/**\n\t * Fetch all comments for a given issue key with auto-pagination.\n\t */\n\tasync getComments(\n\t\tissueKey: string,\n\t): Promise<Result<JiraComment[], JiraApiError | JiraRateLimitError>> {\n\t\tconst allComments: JiraComment[] = [];\n\t\tlet startAt = 0;\n\n\t\twhile (true) {\n\t\t\tconst result = await this.request<JiraCommentPage>(\n\t\t\t\t`/rest/api/3/issue/${encodeURIComponent(issueKey)}/comment?startAt=${startAt}&maxResults=${MAX_RESULTS}`,\n\t\t\t\t\"GET\",\n\t\t\t);\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tconst page = result.value;\n\t\t\tfor (const comment of page.comments) {\n\t\t\t\tallComments.push(comment);\n\t\t\t}\n\n\t\t\tstartAt += page.maxResults;\n\t\t\tif (startAt >= page.total) break;\n\t\t}\n\n\t\treturn Ok(allComments);\n\t}\n\n\t/**\n\t * Fetch all projects with auto-pagination.\n\t */\n\tasync getProjects(): Promise<Result<JiraProject[], JiraApiError | JiraRateLimitError>> {\n\t\tconst allProjects: JiraProject[] = [];\n\t\tlet startAt = 0;\n\n\t\twhile (true) {\n\t\t\tconst result = await this.request<JiraProjectPage>(\n\t\t\t\t`/rest/api/3/project/search?startAt=${startAt}&maxResults=${MAX_RESULTS}`,\n\t\t\t\t\"GET\",\n\t\t\t);\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tconst page = result.value;\n\t\t\tfor (const project of page.values) {\n\t\t\t\tallProjects.push(project);\n\t\t\t}\n\n\t\t\tstartAt += page.maxResults;\n\t\t\tif (startAt >= page.total) break;\n\t\t}\n\n\t\treturn Ok(allProjects);\n\t}\n\n\t/**\n\t * Fetch the currently authenticated user.\n\t *\n\t * Calls `GET /rest/api/3/myself` — the cheapest auth-validating endpoint.\n\t */\n\tasync getCurrentUser(): Promise<\n\t\tResult<{ displayName: string; emailAddress: string }, JiraApiError | JiraRateLimitError>\n\t> {\n\t\treturn this.request(\"/rest/api/3/myself\", \"GET\");\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal HTTP helpers\n\t// -----------------------------------------------------------------------\n\n\t/** Make an HTTP request with rate-limit retry logic. */\n\tprivate async request<T>(\n\t\tpath: string,\n\t\tmethod: \"GET\" | \"POST\",\n\t\tbody?: unknown,\n\t): Promise<Result<T, JiraApiError | JiraRateLimitError>> {\n\t\tlet lastError: JiraApiError | undefined;\n\n\t\tfor (let attempt = 0; attempt <= MAX_RATE_LIMIT_RETRIES; attempt++) {\n\t\t\tconst headers: Record<string, string> = {\n\t\t\t\tAuthorization: this.authHeader,\n\t\t\t\tAccept: \"application/json\",\n\t\t\t};\n\n\t\t\tconst init: RequestInit = { method, headers };\n\n\t\t\tif (body !== undefined) {\n\t\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t\t\tinit.body = JSON.stringify(body);\n\t\t\t}\n\n\t\t\tconst response = await fetch(`${this.baseUrl}${path}`, init);\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst data = (await response.json()) as T;\n\t\t\t\treturn Ok(data);\n\t\t\t}\n\n\t\t\tif (response.status === 429) {\n\t\t\t\tconst retryAfter = response.headers.get(\"Retry-After\");\n\t\t\t\tconst waitMs = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : DEFAULT_RETRY_AFTER_MS;\n\n\t\t\t\tif (attempt < MAX_RATE_LIMIT_RETRIES) {\n\t\t\t\t\tawait sleep(waitMs);\n\t\t\t\t\tlastError = new JiraApiError(429, `Rate limited, retried after ${waitMs}ms`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn Err(new JiraRateLimitError(waitMs));\n\t\t\t}\n\n\t\t\tconst responseBody = await response.text();\n\t\t\treturn Err(new JiraApiError(response.status, responseBody));\n\t\t}\n\n\t\treturn Err(lastError ?? new JiraApiError(0, \"Unknown error after retries\"));\n\t}\n}\n\n/** Sleep for the given number of milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n","// ---------------------------------------------------------------------------\n// Jira Entity → Flat LakeSync Row Mapping\n// ---------------------------------------------------------------------------\n\nimport type { JiraComment, JiraIssue, JiraProject } from \"./types\";\n\n/**\n * Map a Jira issue to a flat row for the `jira_issues` table.\n *\n * The row ID is the issue key (e.g. \"PROJ-123\").\n */\nexport function mapIssue(issue: JiraIssue): { rowId: string; row: Record<string, unknown> } {\n\tconst f = issue.fields;\n\treturn {\n\t\trowId: issue.key,\n\t\trow: {\n\t\t\tjira_id: issue.id,\n\t\t\tkey: issue.key,\n\t\t\tsummary: f.summary ?? null,\n\t\t\tdescription: f.description != null ? JSON.stringify(f.description) : null,\n\t\t\tstatus: f.status?.name ?? null,\n\t\t\tpriority: f.priority?.name ?? null,\n\t\t\tissue_type: f.issuetype?.name ?? null,\n\t\t\tassignee_name: f.assignee?.displayName ?? null,\n\t\t\tassignee_email: f.assignee?.emailAddress ?? null,\n\t\t\treporter_name: f.reporter?.displayName ?? null,\n\t\t\treporter_email: f.reporter?.emailAddress ?? null,\n\t\t\tlabels: f.labels != null ? JSON.stringify(f.labels) : null,\n\t\t\tproject_key: f.project?.key ?? null,\n\t\t\tproject_name: f.project?.name ?? null,\n\t\t\tcreated: f.created ?? null,\n\t\t\tupdated: f.updated ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Jira comment to a flat row for the `jira_comments` table.\n *\n * The row ID is \"{issueKey}:{commentId}\".\n */\nexport function mapComment(\n\tissueKey: string,\n\tcomment: JiraComment,\n): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: `${issueKey}:${comment.id}`,\n\t\trow: {\n\t\t\tjira_id: comment.id,\n\t\t\tissue_key: issueKey,\n\t\t\tbody: comment.body != null ? JSON.stringify(comment.body) : null,\n\t\t\tauthor_name: comment.author?.displayName ?? null,\n\t\t\tauthor_email: comment.author?.emailAddress ?? null,\n\t\t\tcreated: comment.created ?? null,\n\t\t\tupdated: comment.updated ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Jira project to a flat row for the `jira_projects` table.\n *\n * The row ID is the project key (e.g. \"PROJ\").\n */\nexport function mapProject(project: JiraProject): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: project.key,\n\t\trow: {\n\t\t\tjira_id: project.id,\n\t\t\tkey: project.key,\n\t\t\tname: project.name,\n\t\t\tproject_type: project.projectTypeKey ?? null,\n\t\t\tlead_name: project.lead?.displayName ?? null,\n\t\t},\n\t};\n}\n","// ---------------------------------------------------------------------------\n// JiraSourcePoller — polls Jira Cloud and pushes deltas to SyncGateway\n// ---------------------------------------------------------------------------\n\nimport { BaseSourcePoller, extractDelta, type PushTarget } from \"@lakesync/core\";\nimport { JiraClient } from \"./client\";\nimport { mapComment, mapIssue, mapProject } from \"./mapping\";\nimport type { JiraConnectorConfig, JiraIngestConfig } from \"./types\";\n\nconst DEFAULT_INTERVAL_MS = 30_000;\n\n/**\n * Polls Jira Cloud for issues, comments, and projects and pushes\n * detected changes into a gateway via streaming accumulation.\n *\n * Uses {@link BaseSourcePoller.accumulateDelta} to push deltas in\n * memory-bounded chunks instead of collecting all deltas in a single array.\n */\nexport class JiraSourcePoller extends BaseSourcePoller {\n\tprivate readonly connectionConfig: JiraConnectorConfig;\n\tprivate readonly client: JiraClient;\n\n\t/** Cursor: max `fields.updated` value from the last issue poll. */\n\tprivate lastUpdated: string | undefined;\n\n\t/** In-memory snapshot for comment diff (keyed by rowId). */\n\tprivate commentSnapshot = new Map<string, Record<string, unknown>>();\n\n\t/** In-memory snapshot for project diff (keyed by project key). */\n\tprivate projectSnapshot = new Map<string, Record<string, unknown>>();\n\n\t/** Export cursor state as a JSON-serialisable object for external persistence. */\n\toverride getCursorState(): Record<string, unknown> {\n\t\treturn {\n\t\t\tlastUpdated: this.lastUpdated,\n\t\t\tcommentSnapshot: Array.from(this.commentSnapshot.entries()),\n\t\t\tprojectSnapshot: Array.from(this.projectSnapshot.entries()),\n\t\t};\n\t}\n\n\t/** Restore cursor state from a previously exported snapshot. */\n\toverride setCursorState(state: Record<string, unknown>): void {\n\t\tthis.lastUpdated = state.lastUpdated as string | undefined;\n\t\tthis.commentSnapshot = new Map(\n\t\t\tstate.commentSnapshot as Array<[string, Record<string, unknown>]>,\n\t\t);\n\t\tthis.projectSnapshot = new Map(\n\t\t\tstate.projectSnapshot as Array<[string, Record<string, unknown>]>,\n\t\t);\n\t}\n\n\tconstructor(\n\t\tconnectionConfig: JiraConnectorConfig,\n\t\tingestConfig: JiraIngestConfig | undefined,\n\t\tname: string,\n\t\tgateway: PushTarget,\n\t\tclient?: JiraClient,\n\t) {\n\t\tsuper({\n\t\t\tname,\n\t\t\tintervalMs: ingestConfig?.intervalMs ?? DEFAULT_INTERVAL_MS,\n\t\t\tgateway,\n\t\t\tmemory: {\n\t\t\t\tchunkSize: ingestConfig?.chunkSize,\n\t\t\t\tmemoryBudgetBytes: ingestConfig?.memoryBudgetBytes,\n\t\t\t},\n\t\t});\n\t\tthis.connectionConfig = connectionConfig;\n\t\tthis.client = client ?? new JiraClient(connectionConfig);\n\t}\n\n\t/** Execute a single poll cycle across all entity types. */\n\tasync poll(): Promise<void> {\n\t\t// 1. Issues (cursor strategy via `updated` field)\n\t\tconst issueKeys = await this.pollIssues();\n\n\t\t// 2. Comments (diff per-issue, only for issues returned in this poll)\n\t\tconst includeComments = this.connectionConfig.includeComments ?? true;\n\t\tif (includeComments && issueKeys.length > 0) {\n\t\t\tawait this.pollComments(issueKeys);\n\t\t}\n\n\t\t// 3. Projects (full diff)\n\t\tconst includeProjects = this.connectionConfig.includeProjects ?? true;\n\t\tif (includeProjects) {\n\t\t\tawait this.pollProjects();\n\t\t}\n\n\t\tawait this.flushAccumulator();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Issues — cursor strategy via JQL `updated` field\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollIssues(): Promise<string[]> {\n\t\tconst result = await this.client.searchIssues(\n\t\t\tthis.connectionConfig.jql ?? \"\",\n\t\t\tthis.lastUpdated,\n\t\t);\n\n\t\tif (!result.ok) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst issues = result.value;\n\t\tif (issues.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst issueKeys: string[] = [];\n\t\tlet maxUpdated = this.lastUpdated;\n\n\t\tfor (const issue of issues) {\n\t\t\tconst { rowId, row } = mapIssue(issue);\n\t\t\tissueKeys.push(issue.key);\n\n\t\t\tconst delta = await extractDelta(null, row, {\n\t\t\t\ttable: \"jira_issues\",\n\t\t\t\trowId,\n\t\t\t\tclientId: this.clientId,\n\t\t\t\thlc: this.hlc.now(),\n\t\t\t});\n\n\t\t\tif (delta) {\n\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t}\n\n\t\t\t// Track max updated timestamp for cursor advancement\n\t\t\tconst updated = issue.fields.updated;\n\t\t\tif (updated && (!maxUpdated || updated > maxUpdated)) {\n\t\t\t\tmaxUpdated = updated;\n\t\t\t}\n\t\t}\n\n\t\tthis.lastUpdated = maxUpdated;\n\t\treturn issueKeys;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Comments — diff per-issue\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollComments(issueKeys: string[]): Promise<void> {\n\t\tfor (const issueKey of issueKeys) {\n\t\t\tconst result = await this.client.getComments(issueKey);\n\t\t\tif (!result.ok) continue;\n\n\t\t\tconst currentMap = new Map<string, Record<string, unknown>>();\n\n\t\t\tfor (const comment of result.value) {\n\t\t\t\tconst { rowId, row } = mapComment(issueKey, comment);\n\t\t\t\tcurrentMap.set(rowId, row);\n\n\t\t\t\tconst previous = this.commentSnapshot.get(rowId) ?? null;\n\t\t\t\tconst delta = await extractDelta(previous, row, {\n\t\t\t\t\ttable: \"jira_comments\",\n\t\t\t\t\trowId,\n\t\t\t\t\tclientId: this.clientId,\n\t\t\t\t\thlc: this.hlc.now(),\n\t\t\t\t});\n\n\t\t\t\tif (delta) {\n\t\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update snapshot for this issue's comments\n\t\t\tfor (const [rowId, row] of currentMap) {\n\t\t\t\tthis.commentSnapshot.set(rowId, row);\n\t\t\t}\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Projects — full diff\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollProjects(): Promise<void> {\n\t\tconst result = await this.client.getProjects();\n\t\tif (!result.ok) return;\n\n\t\tconst currentMap = new Map<string, Record<string, unknown>>();\n\n\t\tfor (const project of result.value) {\n\t\t\tconst { rowId, row } = mapProject(project);\n\t\t\tcurrentMap.set(rowId, row);\n\t\t}\n\n\t\tconst previousMap = this.projectSnapshot;\n\n\t\t// Detect inserts and updates\n\t\tfor (const [rowId, currentRow] of currentMap) {\n\t\t\tconst previousRow = previousMap.get(rowId) ?? null;\n\n\t\t\tconst delta = await extractDelta(previousRow, currentRow, {\n\t\t\t\ttable: \"jira_projects\",\n\t\t\t\trowId,\n\t\t\t\tclientId: this.clientId,\n\t\t\t\thlc: this.hlc.now(),\n\t\t\t});\n\n\t\t\tif (delta) {\n\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t}\n\t\t}\n\n\t\t// Detect deletes: rows in previous snapshot missing from current\n\t\tfor (const [rowId, previousRow] of previousMap) {\n\t\t\tif (!currentMap.has(rowId)) {\n\t\t\t\tconst delta = await extractDelta(previousRow, null, {\n\t\t\t\t\ttable: \"jira_projects\",\n\t\t\t\t\trowId,\n\t\t\t\t\tclientId: this.clientId,\n\t\t\t\t\thlc: this.hlc.now(),\n\t\t\t\t});\n\n\t\t\t\tif (delta) {\n\t\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Replace snapshot\n\t\tthis.projectSnapshot = currentMap;\n\t}\n}\n","// ---------------------------------------------------------------------------\n// Jira Table Schemas — declares the shape of each table produced by the poller\n// ---------------------------------------------------------------------------\n\nimport type { TableSchema } from \"@lakesync/core\";\n\n/** Schema for the `jira_issues` table (columns from {@link mapIssue}). */\nconst JIRA_ISSUES_SCHEMA: TableSchema = {\n\ttable: \"jira_issues\",\n\tcolumns: [\n\t\t{ name: \"jira_id\", type: \"string\" },\n\t\t{ name: \"key\", type: \"string\" },\n\t\t{ name: \"summary\", type: \"string\" },\n\t\t{ name: \"description\", type: \"string\" },\n\t\t{ name: \"status\", type: \"string\" },\n\t\t{ name: \"priority\", type: \"string\" },\n\t\t{ name: \"issue_type\", type: \"string\" },\n\t\t{ name: \"assignee_name\", type: \"string\" },\n\t\t{ name: \"assignee_email\", type: \"string\" },\n\t\t{ name: \"reporter_name\", type: \"string\" },\n\t\t{ name: \"reporter_email\", type: \"string\" },\n\t\t{ name: \"labels\", type: \"string\" },\n\t\t{\n\t\t\tname: \"project_key\",\n\t\t\ttype: \"string\",\n\t\t\treferences: { table: \"jira_projects\", column: \"key\", cardinality: \"many-to-one\" },\n\t\t},\n\t\t{ name: \"project_name\", type: \"string\" },\n\t\t{ name: \"created\", type: \"string\" },\n\t\t{ name: \"updated\", type: \"string\" },\n\t],\n};\n\n/** Schema for the `jira_comments` table (columns from {@link mapComment}). */\nconst JIRA_COMMENTS_SCHEMA: TableSchema = {\n\ttable: \"jira_comments\",\n\tcolumns: [\n\t\t{ name: \"jira_id\", type: \"string\" },\n\t\t{\n\t\t\tname: \"issue_key\",\n\t\t\ttype: \"string\",\n\t\t\treferences: { table: \"jira_issues\", column: \"key\", cardinality: \"many-to-one\" },\n\t\t},\n\t\t{ name: \"body\", type: \"string\" },\n\t\t{ name: \"author_name\", type: \"string\" },\n\t\t{ name: \"author_email\", type: \"string\" },\n\t\t{ name: \"created\", type: \"string\" },\n\t\t{ name: \"updated\", type: \"string\" },\n\t],\n};\n\n/** Schema for the `jira_projects` table (columns from {@link mapProject}). */\nconst JIRA_PROJECTS_SCHEMA: TableSchema = {\n\ttable: \"jira_projects\",\n\tcolumns: [\n\t\t{ name: \"jira_id\", type: \"string\" },\n\t\t{ name: \"key\", type: \"string\" },\n\t\t{ name: \"name\", type: \"string\" },\n\t\t{ name: \"project_type\", type: \"string\" },\n\t\t{ name: \"lead_name\", type: \"string\" },\n\t],\n};\n\n/** All table schemas produced by the Jira connector. */\nexport const JIRA_TABLE_SCHEMAS: ReadonlyArray<TableSchema> = [\n\tJIRA_ISSUES_SCHEMA,\n\tJIRA_COMMENTS_SCHEMA,\n\tJIRA_PROJECTS_SCHEMA,\n];\n","import { Ok, type Result } from \"@lakesync/core\";\nimport { JiraClient } from \"./client\";\nimport type { JiraApiError, JiraRateLimitError } from \"./errors\";\nimport type { JiraConnectorConfig } from \"./types\";\n\n/**\n * Test a Jira Cloud connection by authenticating and fetching the current user.\n *\n * Creates a `JiraClient` internally and calls `GET /rest/api/3/myself` —\n * the cheapest endpoint that validates credentials.\n */\nexport async function testConnection(\n\tconfig: JiraConnectorConfig,\n): Promise<Result<void, JiraApiError | JiraRateLimitError>> {\n\tconst client = new JiraClient(config);\n\tconst result = await client.getCurrentUser();\n\tif (!result.ok) return result;\n\treturn Ok(undefined);\n}\n","import { registerOutputSchemas, registerPollerFactory } from \"@lakesync/core\";\nimport { JiraSourcePoller } from \"./poller\";\nimport { JIRA_TABLE_SCHEMAS } from \"./schemas\";\nimport type { JiraIngestConfig } from \"./types\";\n\nexport { JiraClient } from \"./client\";\nexport { JiraApiError, JiraRateLimitError } from \"./errors\";\nexport { mapComment, mapIssue, mapProject } from \"./mapping\";\nexport { JiraSourcePoller } from \"./poller\";\nexport { JIRA_TABLE_SCHEMAS } from \"./schemas\";\nexport { testConnection } from \"./test-connection\";\nexport type {\n\tJiraComment,\n\tJiraCommentPage,\n\tJiraConnectorConfig,\n\tJiraIngestConfig,\n\tJiraIssue,\n\tJiraProject,\n\tJiraProjectPage,\n\tJiraSearchResponse,\n} from \"./types\";\n\n// Auto-register output schemas so listConnectorDescriptors() includes table info.\nregisterOutputSchemas(\"jira\", JIRA_TABLE_SCHEMAS);\n\n// Auto-register poller factory so createPoller(\"jira\", ...) works.\nregisterPollerFactory(\"jira\", (config, gateway) => {\n\tif (config.type !== \"jira\") {\n\t\tthrow new Error(`Expected connector type \"jira\", got \"${config.type}\"`);\n\t}\n\tconst ingest: JiraIngestConfig | undefined = config.ingest\n\t\t? {\n\t\t\t\tintervalMs: config.ingest.intervalMs,\n\t\t\t\tchunkSize: config.ingest.chunkSize,\n\t\t\t\tmemoryBudgetBytes: config.ingest.memoryBudgetBytes,\n\t\t\t}\n\t\t: undefined;\n\treturn new JiraSourcePoller(config.jira, ingest, config.name, gateway);\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../connector-jira/src/errors.ts","../../connector-jira/src/client.ts","../../connector-jira/src/mapping.ts","../../connector-jira/src/poller.ts","../../connector-jira/src/schemas.ts","../../connector-jira/src/test-connection.ts","../../connector-jira/src/index.ts"],"sourcesContent":["import { LakeSyncError } from \"@lakesync/core\";\n\n/** HTTP error from the Jira REST API. */\nexport class JiraApiError extends LakeSyncError {\n\t/** HTTP status code returned by Jira. */\n\treadonly statusCode: number;\n\t/** Raw response body from Jira. */\n\treadonly responseBody: string;\n\n\tconstructor(statusCode: number, responseBody: string, cause?: Error) {\n\t\tsuper(`Jira API error (${statusCode}): ${responseBody}`, \"JIRA_API_ERROR\", cause);\n\t\tthis.statusCode = statusCode;\n\t\tthis.responseBody = responseBody;\n\t}\n}\n\n/** Rate limit error (HTTP 429) from the Jira REST API. */\nexport class JiraRateLimitError extends LakeSyncError {\n\t/** Milliseconds to wait before retrying. */\n\treadonly retryAfterMs: number;\n\n\tconstructor(retryAfterMs: number, cause?: Error) {\n\t\tsuper(`Jira rate limited — retry after ${retryAfterMs}ms`, \"JIRA_RATE_LIMITED\", cause);\n\t\tthis.retryAfterMs = retryAfterMs;\n\t}\n}\n","// ---------------------------------------------------------------------------\n// JiraClient — HTTP wrapper for Jira Cloud REST API v3\n// ---------------------------------------------------------------------------\n\nimport { Err, Ok, type Result } from \"@lakesync/core\";\nimport { JiraApiError, JiraRateLimitError } from \"./errors\";\nimport type {\n\tJiraComment,\n\tJiraCommentPage,\n\tJiraConnectorConfig,\n\tJiraIssue,\n\tJiraProject,\n\tJiraProjectPage,\n\tJiraSearchResponse,\n} from \"./types\";\n\nconst MAX_RESULTS = 100;\nconst MAX_RATE_LIMIT_RETRIES = 3;\nconst DEFAULT_RETRY_AFTER_MS = 10_000;\n\n/** Fields requested for issue search. */\nconst ISSUE_FIELDS = [\n\t\"summary\",\n\t\"description\",\n\t\"status\",\n\t\"priority\",\n\t\"assignee\",\n\t\"reporter\",\n\t\"labels\",\n\t\"created\",\n\t\"updated\",\n\t\"project\",\n\t\"issuetype\",\n];\n\n/**\n * HTTP client for the Jira Cloud REST API v3.\n *\n * Uses Basic authentication (email + API token) and global `fetch`.\n * All public methods return `Result<T, JiraApiError>`.\n */\nexport class JiraClient {\n\tprivate readonly baseUrl: string;\n\tprivate readonly authHeader: string;\n\n\tconstructor(config: JiraConnectorConfig) {\n\t\tthis.baseUrl = `https://${config.domain}.atlassian.net`;\n\t\tthis.authHeader = `Basic ${btoa(`${config.email}:${config.apiToken}`)}`;\n\t}\n\n\t/**\n\t * Search for issues via JQL with auto-pagination.\n\t *\n\t * Uses the `/rest/api/3/search/jql` endpoint with token-based pagination.\n\t * When `updatedSince` is provided, appends `AND updated >= \"date\"` to the JQL.\n\t * Empty JQL is replaced with `project is not EMPTY` (the new endpoint\n\t * rejects unbounded queries).\n\t *\n\t * @param limit — optional cap on the total number of issues returned.\n\t */\n\tasync searchIssues(\n\t\tjql: string,\n\t\tupdatedSince?: string,\n\t\tlimit?: number,\n\t): Promise<Result<JiraIssue[], JiraApiError | JiraRateLimitError>> {\n\t\tlet effectiveJql = jql || \"\";\n\n\t\tif (updatedSince) {\n\t\t\tconst clause = `updated >= \"${updatedSince}\"`;\n\t\t\teffectiveJql = effectiveJql.length > 0 ? `(${effectiveJql}) AND ${clause}` : clause;\n\t\t}\n\n\t\t// The /search/jql endpoint rejects unbounded queries\n\t\tif (effectiveJql.length === 0) {\n\t\t\teffectiveJql = \"project is not EMPTY\";\n\t\t}\n\n\t\tconst allIssues: JiraIssue[] = [];\n\t\tlet nextPageToken: string | undefined;\n\t\tconst pageSize = limit !== undefined ? Math.min(limit, MAX_RESULTS) : MAX_RESULTS;\n\n\t\twhile (true) {\n\t\t\tconst body: Record<string, unknown> = {\n\t\t\t\tjql: effectiveJql,\n\t\t\t\tmaxResults: pageSize,\n\t\t\t\tfields: ISSUE_FIELDS,\n\t\t\t};\n\t\t\tif (nextPageToken) {\n\t\t\t\tbody.nextPageToken = nextPageToken;\n\t\t\t}\n\n\t\t\tconst result = await this.request<JiraSearchResponse>(\"/rest/api/3/search/jql\", \"POST\", body);\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tconst page = result.value;\n\t\t\tfor (const issue of page.issues) {\n\t\t\t\tallIssues.push(issue);\n\t\t\t}\n\n\t\t\tif (limit !== undefined && allIssues.length >= limit) break;\n\t\t\tif (page.isLast || !page.nextPageToken) break;\n\t\t\tnextPageToken = page.nextPageToken;\n\t\t}\n\n\t\treturn Ok(limit !== undefined ? allIssues.slice(0, limit) : allIssues);\n\t}\n\n\t/**\n\t * Fetch all comments for a given issue key with auto-pagination.\n\t */\n\tasync getComments(\n\t\tissueKey: string,\n\t): Promise<Result<JiraComment[], JiraApiError | JiraRateLimitError>> {\n\t\tconst allComments: JiraComment[] = [];\n\t\tlet startAt = 0;\n\n\t\twhile (true) {\n\t\t\tconst result = await this.request<JiraCommentPage>(\n\t\t\t\t`/rest/api/3/issue/${encodeURIComponent(issueKey)}/comment?startAt=${startAt}&maxResults=${MAX_RESULTS}`,\n\t\t\t\t\"GET\",\n\t\t\t);\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tconst page = result.value;\n\t\t\tfor (const comment of page.comments) {\n\t\t\t\tallComments.push(comment);\n\t\t\t}\n\n\t\t\tstartAt += page.maxResults;\n\t\t\tif (startAt >= page.total) break;\n\t\t}\n\n\t\treturn Ok(allComments);\n\t}\n\n\t/**\n\t * Fetch all projects with auto-pagination.\n\t */\n\tasync getProjects(): Promise<Result<JiraProject[], JiraApiError | JiraRateLimitError>> {\n\t\tconst allProjects: JiraProject[] = [];\n\t\tlet startAt = 0;\n\n\t\twhile (true) {\n\t\t\tconst result = await this.request<JiraProjectPage>(\n\t\t\t\t`/rest/api/3/project/search?startAt=${startAt}&maxResults=${MAX_RESULTS}`,\n\t\t\t\t\"GET\",\n\t\t\t);\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tconst page = result.value;\n\t\t\tfor (const project of page.values) {\n\t\t\t\tallProjects.push(project);\n\t\t\t}\n\n\t\t\tstartAt += page.maxResults;\n\t\t\tif (startAt >= page.total) break;\n\t\t}\n\n\t\treturn Ok(allProjects);\n\t}\n\n\t/**\n\t * Fetch the currently authenticated user.\n\t *\n\t * Calls `GET /rest/api/3/myself` — the cheapest auth-validating endpoint.\n\t */\n\tasync getCurrentUser(): Promise<\n\t\tResult<{ displayName: string; emailAddress: string }, JiraApiError | JiraRateLimitError>\n\t> {\n\t\treturn this.request(\"/rest/api/3/myself\", \"GET\");\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal HTTP helpers\n\t// -----------------------------------------------------------------------\n\n\t/** Make an HTTP request with rate-limit retry logic. */\n\tprivate async request<T>(\n\t\tpath: string,\n\t\tmethod: \"GET\" | \"POST\",\n\t\tbody?: unknown,\n\t): Promise<Result<T, JiraApiError | JiraRateLimitError>> {\n\t\tlet lastError: JiraApiError | undefined;\n\n\t\tfor (let attempt = 0; attempt <= MAX_RATE_LIMIT_RETRIES; attempt++) {\n\t\t\tconst headers: Record<string, string> = {\n\t\t\t\tAuthorization: this.authHeader,\n\t\t\t\tAccept: \"application/json\",\n\t\t\t};\n\n\t\t\tconst init: RequestInit = { method, headers };\n\n\t\t\tif (body !== undefined) {\n\t\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t\t\tinit.body = JSON.stringify(body);\n\t\t\t}\n\n\t\t\tconst response = await fetch(`${this.baseUrl}${path}`, init);\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst data = (await response.json()) as T;\n\t\t\t\treturn Ok(data);\n\t\t\t}\n\n\t\t\tif (response.status === 429) {\n\t\t\t\tconst retryAfter = response.headers.get(\"Retry-After\");\n\t\t\t\tconst waitMs = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : DEFAULT_RETRY_AFTER_MS;\n\n\t\t\t\tif (attempt < MAX_RATE_LIMIT_RETRIES) {\n\t\t\t\t\tawait sleep(waitMs);\n\t\t\t\t\tlastError = new JiraApiError(429, `Rate limited, retried after ${waitMs}ms`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn Err(new JiraRateLimitError(waitMs));\n\t\t\t}\n\n\t\t\tconst responseBody = await response.text();\n\t\t\treturn Err(new JiraApiError(response.status, responseBody));\n\t\t}\n\n\t\treturn Err(lastError ?? new JiraApiError(0, \"Unknown error after retries\"));\n\t}\n}\n\n/** Sleep for the given number of milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n","// ---------------------------------------------------------------------------\n// Jira Entity → Flat LakeSync Row Mapping\n// ---------------------------------------------------------------------------\n\nimport type { JiraComment, JiraIssue, JiraProject } from \"./types\";\n\n/**\n * Map a Jira issue to a flat row for the `jira_issues` table.\n *\n * The row ID is the issue key (e.g. \"PROJ-123\").\n */\nexport function mapIssue(issue: JiraIssue): { rowId: string; row: Record<string, unknown> } {\n\tconst f = issue.fields;\n\treturn {\n\t\trowId: issue.key,\n\t\trow: {\n\t\t\tjira_id: issue.id,\n\t\t\tkey: issue.key,\n\t\t\tsummary: f.summary ?? null,\n\t\t\tdescription: f.description != null ? JSON.stringify(f.description) : null,\n\t\t\tstatus: f.status?.name ?? null,\n\t\t\tpriority: f.priority?.name ?? null,\n\t\t\tissue_type: f.issuetype?.name ?? null,\n\t\t\tassignee_name: f.assignee?.displayName ?? null,\n\t\t\tassignee_email: f.assignee?.emailAddress ?? null,\n\t\t\treporter_name: f.reporter?.displayName ?? null,\n\t\t\treporter_email: f.reporter?.emailAddress ?? null,\n\t\t\tlabels: f.labels != null ? JSON.stringify(f.labels) : null,\n\t\t\tproject_key: f.project?.key ?? null,\n\t\t\tproject_name: f.project?.name ?? null,\n\t\t\tcreated: f.created ?? null,\n\t\t\tupdated: f.updated ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Jira comment to a flat row for the `jira_comments` table.\n *\n * The row ID is \"{issueKey}:{commentId}\".\n */\nexport function mapComment(\n\tissueKey: string,\n\tcomment: JiraComment,\n): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: `${issueKey}:${comment.id}`,\n\t\trow: {\n\t\t\tjira_id: comment.id,\n\t\t\tissue_key: issueKey,\n\t\t\tbody: comment.body != null ? JSON.stringify(comment.body) : null,\n\t\t\tauthor_name: comment.author?.displayName ?? null,\n\t\t\tauthor_email: comment.author?.emailAddress ?? null,\n\t\t\tcreated: comment.created ?? null,\n\t\t\tupdated: comment.updated ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Jira project to a flat row for the `jira_projects` table.\n *\n * The row ID is the project key (e.g. \"PROJ\").\n */\nexport function mapProject(project: JiraProject): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: project.key,\n\t\trow: {\n\t\t\tjira_id: project.id,\n\t\t\tkey: project.key,\n\t\t\tname: project.name,\n\t\t\tproject_type: project.projectTypeKey ?? null,\n\t\t\tlead_name: project.lead?.displayName ?? null,\n\t\t},\n\t};\n}\n","// ---------------------------------------------------------------------------\n// JiraSourcePoller — polls Jira Cloud and pushes deltas to SyncGateway\n// ---------------------------------------------------------------------------\n\nimport { BaseSourcePoller, extractDelta, type PushTarget } from \"@lakesync/core\";\nimport { JiraClient } from \"./client\";\nimport { mapComment, mapIssue, mapProject } from \"./mapping\";\nimport type { JiraConnectorConfig, JiraIngestConfig } from \"./types\";\n\nconst DEFAULT_INTERVAL_MS = 30_000;\n\n/**\n * Polls Jira Cloud for issues, comments, and projects and pushes\n * detected changes into a gateway via streaming accumulation.\n *\n * Uses {@link BaseSourcePoller.accumulateDelta} to push deltas in\n * memory-bounded chunks instead of collecting all deltas in a single array.\n */\nexport class JiraSourcePoller extends BaseSourcePoller {\n\tprivate readonly connectionConfig: JiraConnectorConfig;\n\tprivate readonly client: JiraClient;\n\n\t/** Cursor: max `fields.updated` value from the last issue poll. */\n\tprivate lastUpdated: string | undefined;\n\n\t/** In-memory snapshot for comment diff (keyed by rowId). */\n\tprivate commentSnapshot = new Map<string, Record<string, unknown>>();\n\n\t/** In-memory snapshot for project diff (keyed by project key). */\n\tprivate projectSnapshot = new Map<string, Record<string, unknown>>();\n\n\t/** Export cursor state as a JSON-serialisable object for external persistence. */\n\toverride getCursorState(): Record<string, unknown> {\n\t\treturn {\n\t\t\tlastUpdated: this.lastUpdated,\n\t\t\tcommentSnapshot: Array.from(this.commentSnapshot.entries()),\n\t\t\tprojectSnapshot: Array.from(this.projectSnapshot.entries()),\n\t\t};\n\t}\n\n\t/** Restore cursor state from a previously exported snapshot. */\n\toverride setCursorState(state: Record<string, unknown>): void {\n\t\tthis.lastUpdated = state.lastUpdated as string | undefined;\n\t\tthis.commentSnapshot = new Map(\n\t\t\tstate.commentSnapshot as Array<[string, Record<string, unknown>]>,\n\t\t);\n\t\tthis.projectSnapshot = new Map(\n\t\t\tstate.projectSnapshot as Array<[string, Record<string, unknown>]>,\n\t\t);\n\t}\n\n\tconstructor(\n\t\tconnectionConfig: JiraConnectorConfig,\n\t\tingestConfig: JiraIngestConfig | undefined,\n\t\tname: string,\n\t\tgateway: PushTarget,\n\t\tclient?: JiraClient,\n\t) {\n\t\tsuper({\n\t\t\tname,\n\t\t\tintervalMs: ingestConfig?.intervalMs ?? DEFAULT_INTERVAL_MS,\n\t\t\tgateway,\n\t\t\tmemory: {\n\t\t\t\tchunkSize: ingestConfig?.chunkSize,\n\t\t\t\tmemoryBudgetBytes: ingestConfig?.memoryBudgetBytes,\n\t\t\t},\n\t\t});\n\t\tthis.connectionConfig = connectionConfig;\n\t\tthis.client = client ?? new JiraClient(connectionConfig);\n\t}\n\n\t/** Execute a single poll cycle across all entity types. */\n\tasync poll(): Promise<void> {\n\t\t// 1. Issues (cursor strategy via `updated` field)\n\t\tconst issueKeys = await this.pollIssues();\n\n\t\t// 2. Comments (diff per-issue, only for issues returned in this poll)\n\t\tconst includeComments = this.connectionConfig.includeComments ?? true;\n\t\tif (includeComments && issueKeys.length > 0) {\n\t\t\tawait this.pollComments(issueKeys);\n\t\t}\n\n\t\t// 3. Projects (full diff)\n\t\tconst includeProjects = this.connectionConfig.includeProjects ?? true;\n\t\tif (includeProjects) {\n\t\t\tawait this.pollProjects();\n\t\t}\n\n\t\tawait this.flushAccumulator();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Issues — cursor strategy via JQL `updated` field\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollIssues(): Promise<string[]> {\n\t\tconst result = await this.client.searchIssues(\n\t\t\tthis.connectionConfig.jql ?? \"\",\n\t\t\tthis.lastUpdated,\n\t\t);\n\n\t\tif (!result.ok) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst issues = result.value;\n\t\tif (issues.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst issueKeys: string[] = [];\n\t\tlet maxUpdated = this.lastUpdated;\n\n\t\tfor (const issue of issues) {\n\t\t\tconst { rowId, row } = mapIssue(issue);\n\t\t\tissueKeys.push(issue.key);\n\n\t\t\tconst delta = await extractDelta(null, row, {\n\t\t\t\ttable: \"jira_issues\",\n\t\t\t\trowId,\n\t\t\t\tclientId: this.clientId,\n\t\t\t\thlc: this.hlc.now(),\n\t\t\t});\n\n\t\t\tif (delta) {\n\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t}\n\n\t\t\t// Track max updated timestamp for cursor advancement\n\t\t\tconst updated = issue.fields.updated;\n\t\t\tif (updated && (!maxUpdated || updated > maxUpdated)) {\n\t\t\t\tmaxUpdated = updated;\n\t\t\t}\n\t\t}\n\n\t\tthis.lastUpdated = maxUpdated;\n\t\treturn issueKeys;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Comments — diff per-issue\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollComments(issueKeys: string[]): Promise<void> {\n\t\tfor (const issueKey of issueKeys) {\n\t\t\tconst result = await this.client.getComments(issueKey);\n\t\t\tif (!result.ok) continue;\n\n\t\t\tconst currentMap = new Map<string, Record<string, unknown>>();\n\n\t\t\tfor (const comment of result.value) {\n\t\t\t\tconst { rowId, row } = mapComment(issueKey, comment);\n\t\t\t\tcurrentMap.set(rowId, row);\n\n\t\t\t\tconst previous = this.commentSnapshot.get(rowId) ?? null;\n\t\t\t\tconst delta = await extractDelta(previous, row, {\n\t\t\t\t\ttable: \"jira_comments\",\n\t\t\t\t\trowId,\n\t\t\t\t\tclientId: this.clientId,\n\t\t\t\t\thlc: this.hlc.now(),\n\t\t\t\t});\n\n\t\t\t\tif (delta) {\n\t\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update snapshot for this issue's comments\n\t\t\tfor (const [rowId, row] of currentMap) {\n\t\t\t\tthis.commentSnapshot.set(rowId, row);\n\t\t\t}\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Projects — full diff\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollProjects(): Promise<void> {\n\t\tconst result = await this.client.getProjects();\n\t\tif (!result.ok) return;\n\n\t\tconst currentMap = new Map<string, Record<string, unknown>>();\n\n\t\tfor (const project of result.value) {\n\t\t\tconst { rowId, row } = mapProject(project);\n\t\t\tcurrentMap.set(rowId, row);\n\t\t}\n\n\t\tconst previousMap = this.projectSnapshot;\n\n\t\t// Detect inserts and updates\n\t\tfor (const [rowId, currentRow] of currentMap) {\n\t\t\tconst previousRow = previousMap.get(rowId) ?? null;\n\n\t\t\tconst delta = await extractDelta(previousRow, currentRow, {\n\t\t\t\ttable: \"jira_projects\",\n\t\t\t\trowId,\n\t\t\t\tclientId: this.clientId,\n\t\t\t\thlc: this.hlc.now(),\n\t\t\t});\n\n\t\t\tif (delta) {\n\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t}\n\t\t}\n\n\t\t// Detect deletes: rows in previous snapshot missing from current\n\t\tfor (const [rowId, previousRow] of previousMap) {\n\t\t\tif (!currentMap.has(rowId)) {\n\t\t\t\tconst delta = await extractDelta(previousRow, null, {\n\t\t\t\t\ttable: \"jira_projects\",\n\t\t\t\t\trowId,\n\t\t\t\t\tclientId: this.clientId,\n\t\t\t\t\thlc: this.hlc.now(),\n\t\t\t\t});\n\n\t\t\t\tif (delta) {\n\t\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Replace snapshot\n\t\tthis.projectSnapshot = currentMap;\n\t}\n}\n","// ---------------------------------------------------------------------------\n// Jira Table Schemas — declares the shape of each table produced by the poller\n// ---------------------------------------------------------------------------\n\nimport type { TableSchema } from \"@lakesync/core\";\n\n/** Schema for the `jira_issues` table (columns from {@link mapIssue}). */\nconst JIRA_ISSUES_SCHEMA: TableSchema = {\n\ttable: \"jira_issues\",\n\tcolumns: [\n\t\t{ name: \"jira_id\", type: \"string\" },\n\t\t{ name: \"key\", type: \"string\" },\n\t\t{ name: \"summary\", type: \"string\" },\n\t\t{ name: \"description\", type: \"string\" },\n\t\t{ name: \"status\", type: \"string\" },\n\t\t{ name: \"priority\", type: \"string\" },\n\t\t{ name: \"issue_type\", type: \"string\" },\n\t\t{ name: \"assignee_name\", type: \"string\" },\n\t\t{ name: \"assignee_email\", type: \"string\" },\n\t\t{ name: \"reporter_name\", type: \"string\" },\n\t\t{ name: \"reporter_email\", type: \"string\" },\n\t\t{ name: \"labels\", type: \"string\" },\n\t\t{\n\t\t\tname: \"project_key\",\n\t\t\ttype: \"string\",\n\t\t\treferences: { table: \"jira_projects\", column: \"key\", cardinality: \"many-to-one\" },\n\t\t},\n\t\t{ name: \"project_name\", type: \"string\" },\n\t\t{ name: \"created\", type: \"string\" },\n\t\t{ name: \"updated\", type: \"string\" },\n\t],\n};\n\n/** Schema for the `jira_comments` table (columns from {@link mapComment}). */\nconst JIRA_COMMENTS_SCHEMA: TableSchema = {\n\ttable: \"jira_comments\",\n\tcolumns: [\n\t\t{ name: \"jira_id\", type: \"string\" },\n\t\t{\n\t\t\tname: \"issue_key\",\n\t\t\ttype: \"string\",\n\t\t\treferences: { table: \"jira_issues\", column: \"key\", cardinality: \"many-to-one\" },\n\t\t},\n\t\t{ name: \"body\", type: \"string\" },\n\t\t{ name: \"author_name\", type: \"string\" },\n\t\t{ name: \"author_email\", type: \"string\" },\n\t\t{ name: \"created\", type: \"string\" },\n\t\t{ name: \"updated\", type: \"string\" },\n\t],\n};\n\n/** Schema for the `jira_projects` table (columns from {@link mapProject}). */\nconst JIRA_PROJECTS_SCHEMA: TableSchema = {\n\ttable: \"jira_projects\",\n\tcolumns: [\n\t\t{ name: \"jira_id\", type: \"string\" },\n\t\t{ name: \"key\", type: \"string\" },\n\t\t{ name: \"name\", type: \"string\" },\n\t\t{ name: \"project_type\", type: \"string\" },\n\t\t{ name: \"lead_name\", type: \"string\" },\n\t],\n};\n\n/** All table schemas produced by the Jira connector. */\nexport const JIRA_TABLE_SCHEMAS: ReadonlyArray<TableSchema> = [\n\tJIRA_ISSUES_SCHEMA,\n\tJIRA_COMMENTS_SCHEMA,\n\tJIRA_PROJECTS_SCHEMA,\n];\n","import { Ok, type Result } from \"@lakesync/core\";\nimport { JiraClient } from \"./client\";\nimport type { JiraApiError, JiraRateLimitError } from \"./errors\";\nimport type { JiraConnectorConfig } from \"./types\";\n\n/**\n * Test a Jira Cloud connection by authenticating and fetching the current user.\n *\n * Creates a `JiraClient` internally and calls `GET /rest/api/3/myself` —\n * the cheapest endpoint that validates credentials.\n */\nexport async function testConnection(\n\tconfig: JiraConnectorConfig,\n): Promise<Result<void, JiraApiError | JiraRateLimitError>> {\n\tconst client = new JiraClient(config);\n\tconst result = await client.getCurrentUser();\n\tif (!result.ok) return result;\n\treturn Ok(undefined);\n}\n","import {\n\ttype BaseSourcePoller,\n\ttype ConnectorConfig,\n\ttype JiraConnectorConfigFull,\n\ttype PushTarget,\n\tregisterOutputSchemas,\n} from \"@lakesync/core\";\nimport { JiraSourcePoller } from \"./poller\";\nimport { JIRA_TABLE_SCHEMAS } from \"./schemas\";\nimport type { JiraIngestConfig } from \"./types\";\n\nexport { JiraClient } from \"./client\";\nexport { JiraApiError, JiraRateLimitError } from \"./errors\";\nexport { mapComment, mapIssue, mapProject } from \"./mapping\";\nexport { JiraSourcePoller } from \"./poller\";\nexport { JIRA_TABLE_SCHEMAS } from \"./schemas\";\nexport { testConnection } from \"./test-connection\";\nexport type {\n\tJiraComment,\n\tJiraCommentPage,\n\tJiraConnectorConfig,\n\tJiraIngestConfig,\n\tJiraIssue,\n\tJiraProject,\n\tJiraProjectPage,\n\tJiraSearchResponse,\n} from \"./types\";\n\n/**\n * Poller factory for Jira connectors.\n *\n * Register with a {@link import(\"@lakesync/core\").PollerRegistry} via `.with(\"jira\", jiraPollerFactory)`.\n */\nexport function jiraPollerFactory(config: ConnectorConfig, gateway: PushTarget): BaseSourcePoller {\n\tif (config.type !== \"jira\") {\n\t\tthrow new Error(`Expected connector type \"jira\", got \"${config.type}\"`);\n\t}\n\tconst typed = config as JiraConnectorConfigFull;\n\tconst ingest: JiraIngestConfig | undefined = typed.ingest\n\t\t? {\n\t\t\t\tintervalMs: typed.ingest.intervalMs,\n\t\t\t\tchunkSize: typed.ingest.chunkSize,\n\t\t\t\tmemoryBudgetBytes: typed.ingest.memoryBudgetBytes,\n\t\t\t}\n\t\t: undefined;\n\treturn new JiraSourcePoller(typed.jira, ingest, typed.name, gateway);\n}\n\n/** @deprecated Use {@link jiraPollerFactory} instead. */\nexport const createJiraPoller = jiraPollerFactory;\n\n// Auto-register output schemas so listConnectorDescriptors() includes table info.\nregisterOutputSchemas(\"jira\", JIRA_TABLE_SCHEMAS);\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
Err,
|
|
3
3
|
FlushError,
|
|
4
4
|
Ok
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-4SG66H5K.js";
|
|
6
6
|
|
|
7
7
|
// ../parquet/src/arrow-schema.ts
|
|
8
8
|
import * as arrow from "apache-arrow";
|
|
@@ -254,4 +254,4 @@ export {
|
|
|
254
254
|
readParquetToDeltas,
|
|
255
255
|
writeDeltasToParquet
|
|
256
256
|
};
|
|
257
|
-
//# sourceMappingURL=chunk-
|
|
257
|
+
//# sourceMappingURL=chunk-ZU7RC7CT.js.map
|
package/dist/client.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { R as Result, S as SchemaError, L as LakeSyncError, H as HLCTimestamp } from './result-CojzlFE2.js';
|
|
2
2
|
export { u as unwrapOrThrow } from './result-CojzlFE2.js';
|
|
3
3
|
import { T as TableSchema, R as RowDelta, S as SyncPush, e as SyncPull, f as SyncResponse } from './types-BdGBv2ba.js';
|
|
4
|
-
import { S as SyncQueue, a as SyncCoordinatorConfig, b as SyncCoordinator, L as LocalDB,
|
|
5
|
-
export {
|
|
4
|
+
import { S as SyncQueue, a as SyncCoordinatorConfig, b as SyncCoordinator, L as LocalDB, T as TransportWithCapabilities, D as DbError, A as ActionQueue, c as ActionQueueEntry, Q as QueueEntry, d as SyncTransport, C as CheckpointTransport, e as ActionTransport, f as CheckpointResponse, R as RealtimeTransport } from './coordinator-eGmZMnJ_.js';
|
|
5
|
+
export { g as ActionQueueEntryStatus, h as DbConfig, P as PullFirstStrategy, i as PushFirstStrategy, j as QueueEntryStatus, k as SyncContext, l as SyncEvents, m as SyncMode, n as SyncState, o as SyncStrategy, p as SyncTracker, q as Transaction } from './coordinator-eGmZMnJ_.js';
|
|
6
6
|
import { A as Action, k as ActionResult, e as ActionErrorResult, d as ActionDiscovery, i as ActionPush, j as ActionResponse, l as AuthContext, a as ActionValidationError } from './types-Bs-QyOe-.js';
|
|
7
7
|
import { H as HLC } from './hlc-DiD8QNG3.js';
|
|
8
|
-
import { a as ConnectorDescriptor } from './registry-
|
|
8
|
+
import { a as ConnectorDescriptor } from './registry-Dd8JuW8T.js';
|
|
9
9
|
import { C as ConflictResolver } from './resolver-CXxmC0jR.js';
|
|
10
|
-
import './types-
|
|
10
|
+
import './types-D2C9jTbL.js';
|
|
11
11
|
|
|
12
12
|
/** Configuration for creating a LakeSync client via {@link createClient}. */
|
|
13
13
|
interface CreateClientConfig {
|
|
@@ -47,7 +47,7 @@ interface LakeSyncClient {
|
|
|
47
47
|
/** The local SQLite database. */
|
|
48
48
|
db: LocalDB;
|
|
49
49
|
/** The transport used for gateway communication. */
|
|
50
|
-
transport:
|
|
50
|
+
transport: TransportWithCapabilities;
|
|
51
51
|
/** Stop auto-sync, close the database, and release resources. */
|
|
52
52
|
destroy(): Promise<void>;
|
|
53
53
|
}
|
|
@@ -341,7 +341,7 @@ declare class ActionProcessor {
|
|
|
341
341
|
private onComplete;
|
|
342
342
|
constructor(config: {
|
|
343
343
|
actionQueue: ActionQueue;
|
|
344
|
-
transport:
|
|
344
|
+
transport: TransportWithCapabilities;
|
|
345
345
|
clientId: string;
|
|
346
346
|
hlc: HLC;
|
|
347
347
|
maxRetries: number;
|
|
@@ -477,6 +477,12 @@ interface HttpTransportConfig {
|
|
|
477
477
|
gatewayId: string;
|
|
478
478
|
/** Bearer token for authentication */
|
|
479
479
|
token: string;
|
|
480
|
+
/**
|
|
481
|
+
* Optional callback to retrieve a fresh token before each request.
|
|
482
|
+
* When set, takes priority over the static `token` field.
|
|
483
|
+
* On a 401 response, the callback is invoked again and the request retried once.
|
|
484
|
+
*/
|
|
485
|
+
getToken?: () => string | Promise<string>;
|
|
480
486
|
/** Optional custom fetch implementation (useful for testing) */
|
|
481
487
|
fetch?: typeof globalThis.fetch;
|
|
482
488
|
}
|
|
@@ -486,16 +492,20 @@ interface HttpTransportConfig {
|
|
|
486
492
|
* Sends push requests via POST and pull requests via GET, using
|
|
487
493
|
* BigInt-safe JSON serialisation for HLC timestamps.
|
|
488
494
|
*/
|
|
489
|
-
declare class HttpTransport implements SyncTransport {
|
|
495
|
+
declare class HttpTransport implements SyncTransport, CheckpointTransport, ActionTransport {
|
|
490
496
|
private readonly baseUrl;
|
|
491
497
|
private readonly gatewayId;
|
|
492
498
|
private readonly token;
|
|
499
|
+
private readonly getToken;
|
|
493
500
|
private readonly _fetch;
|
|
494
501
|
constructor(config: HttpTransportConfig);
|
|
502
|
+
/** Resolve the current bearer token, preferring getToken callback over static token. */
|
|
503
|
+
private resolveToken;
|
|
495
504
|
/**
|
|
496
505
|
* Push local deltas to the remote gateway.
|
|
497
506
|
*
|
|
498
507
|
* Sends a POST request with the push payload as BigInt-safe JSON.
|
|
508
|
+
* On 401, if `getToken` is configured, refreshes the token and retries once.
|
|
499
509
|
*/
|
|
500
510
|
push(msg: SyncPush): Promise<Result<{
|
|
501
511
|
serverHlc: HLCTimestamp;
|
|
@@ -505,6 +515,7 @@ declare class HttpTransport implements SyncTransport {
|
|
|
505
515
|
* Pull remote deltas from the gateway.
|
|
506
516
|
*
|
|
507
517
|
* Sends a GET request with query parameters for the pull cursor.
|
|
518
|
+
* On 401, if `getToken` is configured, refreshes the token and retries once.
|
|
508
519
|
*/
|
|
509
520
|
pull(msg: SyncPull): Promise<Result<SyncResponse, LakeSyncError>>;
|
|
510
521
|
/**
|
|
@@ -559,7 +570,7 @@ interface LocalGateway {
|
|
|
559
570
|
* Useful for testing and single-tab offline demos where the client
|
|
560
571
|
* and gateway run in the same process.
|
|
561
572
|
*/
|
|
562
|
-
declare class LocalTransport implements SyncTransport {
|
|
573
|
+
declare class LocalTransport implements SyncTransport, CheckpointTransport, ActionTransport {
|
|
563
574
|
private readonly gateway;
|
|
564
575
|
constructor(gateway: LocalGateway);
|
|
565
576
|
/** Push local deltas to the in-process gateway */
|
|
@@ -575,6 +586,8 @@ declare class LocalTransport implements SyncTransport {
|
|
|
575
586
|
executeAction(msg: ActionPush): Promise<Result<ActionResponse, LakeSyncError>>;
|
|
576
587
|
/** Discover available connectors and their supported action types. */
|
|
577
588
|
describeActions(): Promise<Result<ActionDiscovery, LakeSyncError>>;
|
|
589
|
+
/** List available connector types — not supported by local transport. */
|
|
590
|
+
listConnectorTypes(): Promise<Result<ConnectorDescriptor[], LakeSyncError>>;
|
|
578
591
|
}
|
|
579
592
|
|
|
580
593
|
/** Configuration for the WebSocket sync transport. */
|
|
@@ -583,6 +596,11 @@ interface WebSocketTransportConfig {
|
|
|
583
596
|
url: string;
|
|
584
597
|
/** Bearer token (passed as ?token= query param for browser compat). */
|
|
585
598
|
token: string;
|
|
599
|
+
/**
|
|
600
|
+
* Optional callback to retrieve a fresh token on connect/reconnect.
|
|
601
|
+
* When set, takes priority over the static `token` field.
|
|
602
|
+
*/
|
|
603
|
+
getToken?: () => string | Promise<string>;
|
|
586
604
|
/** Called when server broadcasts deltas. */
|
|
587
605
|
onBroadcast?: (deltas: RowDelta[], serverHlc: HLCTimestamp) => void;
|
|
588
606
|
/** Reconnect base delay in ms (default 1000). */
|
|
@@ -604,7 +622,7 @@ interface WebSocketTransportConfig {
|
|
|
604
622
|
* Checkpoints are delegated to an internal {@link HttpTransport} (large
|
|
605
623
|
* binary payloads are better suited to HTTP).
|
|
606
624
|
*/
|
|
607
|
-
declare class WebSocketTransport implements SyncTransport {
|
|
625
|
+
declare class WebSocketTransport implements SyncTransport, RealtimeTransport, CheckpointTransport {
|
|
608
626
|
private readonly config;
|
|
609
627
|
private readonly reconnectBaseMs;
|
|
610
628
|
private readonly reconnectMaxMs;
|
|
@@ -649,4 +667,4 @@ declare class WebSocketTransport implements SyncTransport {
|
|
|
649
667
|
private sendAndAwaitResponse;
|
|
650
668
|
}
|
|
651
669
|
|
|
652
|
-
export { type ActionCompleteCallback, ActionProcessor, ActionQueue, ActionQueueEntry, AutoSyncScheduler, CheckpointResponse, type CreateClientConfig, DbError, HttpTransport, type HttpTransportConfig, IDBActionQueue, IDBQueue, type LakeSyncClient, LocalDB, type LocalGateway, LocalTransport, MemoryActionQueue, MemoryOutbox, MemoryQueue, type Outbox, type OutboxEntry, type OutboxEntryStatus, QueueEntry, SchemaSynchroniser, SyncCoordinator, SyncCoordinatorConfig, SyncQueue, SyncTransport, WebSocketTransport, type WebSocketTransportConfig, applyRemoteDeltas, createClient, deleteSnapshot, getSchema, loadSnapshot, migrateSchema, registerSchema, saveSnapshot };
|
|
670
|
+
export { type ActionCompleteCallback, ActionProcessor, ActionQueue, ActionQueueEntry, ActionTransport, AutoSyncScheduler, CheckpointResponse, CheckpointTransport, type CreateClientConfig, DbError, HttpTransport, type HttpTransportConfig, IDBActionQueue, IDBQueue, type LakeSyncClient, LocalDB, type LocalGateway, LocalTransport, MemoryActionQueue, MemoryOutbox, MemoryQueue, type Outbox, type OutboxEntry, type OutboxEntryStatus, QueueEntry, RealtimeTransport, SchemaSynchroniser, SyncCoordinator, SyncCoordinatorConfig, SyncQueue, SyncTransport, TransportWithCapabilities, WebSocketTransport, type WebSocketTransportConfig, applyRemoteDeltas, createClient, deleteSnapshot, getSchema, loadSnapshot, migrateSchema, registerSchema, saveSnapshot };
|