lakesync 0.1.0 → 0.1.1

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.
@@ -5,7 +5,6 @@ import {
5
5
  Ok,
6
6
  extractDelta
7
7
  } from "./chunk-ICNT7I3K.js";
8
- import "./chunk-7D4SUZUM.js";
9
8
 
10
9
  // ../connector-jira/src/errors.ts
11
10
  var JiraApiError = class extends LakeSyncError {
@@ -371,13 +370,14 @@ var JiraSourcePoller = class extends BaseSourcePoller {
371
370
  this.projectSnapshot = currentMap;
372
371
  }
373
372
  };
373
+
374
374
  export {
375
375
  JiraApiError,
376
- JiraClient,
377
376
  JiraRateLimitError,
378
- JiraSourcePoller,
379
- mapComment,
377
+ JiraClient,
380
378
  mapIssue,
381
- mapProject
379
+ mapComment,
380
+ mapProject,
381
+ JiraSourcePoller
382
382
  };
383
- //# sourceMappingURL=src-QU2YLPZY.js.map
383
+ //# sourceMappingURL=chunk-DIQZDK4C.js.map
@@ -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"],"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// 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\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"],"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"],"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// 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\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"],"mappings":"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","names":[]}
@@ -5,7 +5,6 @@ import {
5
5
  Ok,
6
6
  extractDelta
7
7
  } from "./chunk-ICNT7I3K.js";
8
- import "./chunk-7D4SUZUM.js";
9
8
 
10
9
  // ../connector-salesforce/src/errors.ts
11
10
  var SalesforceApiError = class extends LakeSyncError {
@@ -413,14 +412,15 @@ var SalesforceSourcePoller = class extends BaseSourcePoller {
413
412
  return `SELECT ${fields} FROM ${sObjectType}${where} ORDER BY LastModifiedDate ASC`;
414
413
  }
415
414
  };
415
+
416
416
  export {
417
417
  SalesforceApiError,
418
418
  SalesforceAuthError,
419
419
  SalesforceClient,
420
- SalesforceSourcePoller,
421
420
  mapAccount,
422
421
  mapContact,
422
+ mapOpportunity,
423
423
  mapLead,
424
- mapOpportunity
424
+ SalesforceSourcePoller
425
425
  };
426
- //# sourceMappingURL=src-WZNPHANQ.js.map
426
+ //# sourceMappingURL=chunk-G6RQSXTJ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../connector-salesforce/src/errors.ts","../../connector-salesforce/src/client.ts","../../connector-salesforce/src/mapping.ts","../../connector-salesforce/src/poller.ts"],"sourcesContent":["import { LakeSyncError } from \"@lakesync/core\";\n\n/** HTTP error from the Salesforce REST API. */\nexport class SalesforceApiError extends LakeSyncError {\n\t/** HTTP status code returned by Salesforce. */\n\treadonly statusCode: number;\n\t/** Raw response body from Salesforce. */\n\treadonly responseBody: string;\n\n\tconstructor(statusCode: number, responseBody: string, cause?: Error) {\n\t\tsuper(`Salesforce API error (${statusCode}): ${responseBody}`, \"SALESFORCE_API_ERROR\", cause);\n\t\tthis.statusCode = statusCode;\n\t\tthis.responseBody = responseBody;\n\t}\n}\n\n/** Authentication failure from the Salesforce OAuth token endpoint. */\nexport class SalesforceAuthError extends LakeSyncError {\n\tconstructor(message: string, cause?: Error) {\n\t\tsuper(message, \"SALESFORCE_AUTH_ERROR\", cause);\n\t}\n}\n","// ---------------------------------------------------------------------------\n// SalesforceClient — HTTP wrapper for Salesforce REST API\n// ---------------------------------------------------------------------------\n\nimport { Err, Ok, type Result } from \"@lakesync/core\";\nimport { SalesforceApiError, SalesforceAuthError } from \"./errors\";\nimport type {\n\tSalesforceAuthResponse,\n\tSalesforceConnectorConfig,\n\tSalesforceQueryResponse,\n} from \"./types\";\n\nconst DEFAULT_API_VERSION = \"v62.0\";\nconst MAX_RETRY_ATTEMPTS = 3;\nconst DEFAULT_RETRY_AFTER_MS = 10_000;\n\n/**\n * HTTP client for the Salesforce REST API.\n *\n * Uses OAuth 2.0 Username-Password flow for authentication and global `fetch`.\n * All public methods return `Result<T, SalesforceApiError | SalesforceAuthError>`.\n */\nexport class SalesforceClient {\n\tprivate readonly config: SalesforceConnectorConfig;\n\tprivate readonly apiVersion: string;\n\tprivate readonly loginUrl: string;\n\n\tprivate accessToken: string | null = null;\n\tprivate instanceUrl: string;\n\n\tconstructor(config: SalesforceConnectorConfig) {\n\t\tthis.config = config;\n\t\tthis.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;\n\t\tthis.loginUrl = config.isSandbox\n\t\t\t? \"https://test.salesforce.com\"\n\t\t\t: \"https://login.salesforce.com\";\n\t\tthis.instanceUrl = config.instanceUrl;\n\t}\n\n\t/**\n\t * Authenticate via OAuth 2.0 Username-Password flow.\n\t *\n\t * Stores access token and updates instance URL from the response.\n\t */\n\tasync authenticate(): Promise<Result<void, SalesforceAuthError>> {\n\t\tconst body = new URLSearchParams({\n\t\t\tgrant_type: \"password\",\n\t\t\tclient_id: this.config.clientId,\n\t\t\tclient_secret: this.config.clientSecret,\n\t\t\tusername: this.config.username,\n\t\t\tpassword: this.config.password,\n\t\t});\n\n\t\tlet response: Response;\n\t\ttry {\n\t\t\tresponse = await fetch(`${this.loginUrl}/services/oauth2/token`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\t\t\tbody: body.toString(),\n\t\t\t});\n\t\t} catch (err) {\n\t\t\treturn Err(\n\t\t\t\tnew SalesforceAuthError(\n\t\t\t\t\t`Failed to connect to Salesforce auth endpoint: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t\terr instanceof Error ? err : undefined,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text();\n\t\t\treturn Err(\n\t\t\t\tnew SalesforceAuthError(`Salesforce authentication failed (${response.status}): ${text}`),\n\t\t\t);\n\t\t}\n\n\t\tconst data = (await response.json()) as SalesforceAuthResponse;\n\t\tthis.accessToken = data.access_token;\n\t\tthis.instanceUrl = data.instance_url;\n\n\t\treturn Ok(undefined);\n\t}\n\n\t/**\n\t * Execute a SOQL query with auto-pagination.\n\t *\n\t * Automatically authenticates on first call and re-authenticates on 401.\n\t */\n\tasync query<T>(soql: string): Promise<Result<T[], SalesforceApiError | SalesforceAuthError>> {\n\t\t// Ensure we have a token\n\t\tif (!this.accessToken) {\n\t\t\tconst authResult = await this.authenticate();\n\t\t\tif (!authResult.ok) return authResult;\n\t\t}\n\n\t\tconst allRecords: T[] = [];\n\t\tlet url = `${this.instanceUrl}/services/data/${this.apiVersion}/query?q=${encodeURIComponent(soql)}`;\n\n\t\twhile (true) {\n\t\t\tconst result = await this.request<SalesforceQueryResponse<T>>(url);\n\n\t\t\t// Re-auth on 401 and retry once\n\t\t\tif (\n\t\t\t\t!result.ok &&\n\t\t\t\tresult.error instanceof SalesforceApiError &&\n\t\t\t\tresult.error.statusCode === 401\n\t\t\t) {\n\t\t\t\tconst authResult = await this.authenticate();\n\t\t\t\tif (!authResult.ok) return authResult;\n\n\t\t\t\tconst retryResult = await this.request<SalesforceQueryResponse<T>>(url);\n\t\t\t\tif (!retryResult.ok) return retryResult;\n\n\t\t\t\tfor (const record of retryResult.value.records) {\n\t\t\t\t\tallRecords.push(record);\n\t\t\t\t}\n\n\t\t\t\tif (retryResult.value.done || !retryResult.value.nextRecordsUrl) break;\n\t\t\t\turl = `${this.instanceUrl}${retryResult.value.nextRecordsUrl}`;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tfor (const record of result.value.records) {\n\t\t\t\tallRecords.push(record);\n\t\t\t}\n\n\t\t\tif (result.value.done || !result.value.nextRecordsUrl) break;\n\t\t\turl = `${this.instanceUrl}${result.value.nextRecordsUrl}`;\n\t\t}\n\n\t\treturn Ok(allRecords);\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\turl: string,\n\t): Promise<Result<T, SalesforceApiError | SalesforceAuthError>> {\n\t\tfor (let attempt = 0; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {\n\t\t\tconst headers: Record<string, string> = {\n\t\t\t\tAuthorization: `Bearer ${this.accessToken}`,\n\t\t\t\tAccept: \"application/json\",\n\t\t\t};\n\n\t\t\tconst response = await fetch(url, { method: \"GET\", headers });\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\t// Rate limit: 503 with Retry-After\n\t\t\tif (response.status === 503) {\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_RETRY_ATTEMPTS) {\n\t\t\t\t\tawait sleep(waitMs);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst responseBody = await response.text();\n\t\t\t\treturn Err(new SalesforceApiError(503, responseBody));\n\t\t\t}\n\n\t\t\tconst responseBody = await response.text();\n\t\t\treturn Err(new SalesforceApiError(response.status, responseBody));\n\t\t}\n\n\t\treturn Err(new SalesforceApiError(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// Salesforce Entity → Flat LakeSync Row Mapping\n// ---------------------------------------------------------------------------\n\nimport type { SfAccount, SfContact, SfLead, SfOpportunity } from \"./types\";\n\n/**\n * Map a Salesforce Account to a flat row for the `sf_accounts` table.\n *\n * The row ID is the Salesforce record Id.\n */\nexport function mapAccount(account: SfAccount): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: account.Id,\n\t\trow: {\n\t\t\tsf_id: account.Id,\n\t\t\tname: account.Name ?? null,\n\t\t\ttype: account.Type ?? null,\n\t\t\tindustry: account.Industry ?? null,\n\t\t\twebsite: account.Website ?? null,\n\t\t\tphone: account.Phone ?? null,\n\t\t\tbilling_city: account.BillingCity ?? null,\n\t\t\tbilling_state: account.BillingState ?? null,\n\t\t\tbilling_country: account.BillingCountry ?? null,\n\t\t\tannual_revenue: account.AnnualRevenue ?? null,\n\t\t\tnumber_of_employees: account.NumberOfEmployees ?? null,\n\t\t\towner_name: account.Owner?.Name ?? null,\n\t\t\tcreated_date: account.CreatedDate ?? null,\n\t\t\tlast_modified_date: account.LastModifiedDate ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Salesforce Contact to a flat row for the `sf_contacts` table.\n *\n * The row ID is the Salesforce record Id.\n */\nexport function mapContact(contact: SfContact): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: contact.Id,\n\t\trow: {\n\t\t\tsf_id: contact.Id,\n\t\t\tfirst_name: contact.FirstName ?? null,\n\t\t\tlast_name: contact.LastName ?? null,\n\t\t\temail: contact.Email ?? null,\n\t\t\tphone: contact.Phone ?? null,\n\t\t\ttitle: contact.Title ?? null,\n\t\t\taccount_id: contact.AccountId ?? null,\n\t\t\taccount_name: contact.Account?.Name ?? null,\n\t\t\tmailing_city: contact.MailingCity ?? null,\n\t\t\tmailing_state: contact.MailingState ?? null,\n\t\t\tmailing_country: contact.MailingCountry ?? null,\n\t\t\towner_name: contact.Owner?.Name ?? null,\n\t\t\tcreated_date: contact.CreatedDate ?? null,\n\t\t\tlast_modified_date: contact.LastModifiedDate ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Salesforce Opportunity to a flat row for the `sf_opportunities` table.\n *\n * The row ID is the Salesforce record Id.\n */\nexport function mapOpportunity(opportunity: SfOpportunity): {\n\trowId: string;\n\trow: Record<string, unknown>;\n} {\n\treturn {\n\t\trowId: opportunity.Id,\n\t\trow: {\n\t\t\tsf_id: opportunity.Id,\n\t\t\tname: opportunity.Name ?? null,\n\t\t\tstage_name: opportunity.StageName ?? null,\n\t\t\tamount: opportunity.Amount ?? null,\n\t\t\tclose_date: opportunity.CloseDate ?? null,\n\t\t\tprobability: opportunity.Probability ?? null,\n\t\t\taccount_id: opportunity.AccountId ?? null,\n\t\t\taccount_name: opportunity.Account?.Name ?? null,\n\t\t\ttype: opportunity.Type ?? null,\n\t\t\tlead_source: opportunity.LeadSource ?? null,\n\t\t\tis_closed: opportunity.IsClosed ?? null,\n\t\t\tis_won: opportunity.IsWon ?? null,\n\t\t\towner_name: opportunity.Owner?.Name ?? null,\n\t\t\tcreated_date: opportunity.CreatedDate ?? null,\n\t\t\tlast_modified_date: opportunity.LastModifiedDate ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Salesforce Lead to a flat row for the `sf_leads` table.\n *\n * The row ID is the Salesforce record Id.\n */\nexport function mapLead(lead: SfLead): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: lead.Id,\n\t\trow: {\n\t\t\tsf_id: lead.Id,\n\t\t\tfirst_name: lead.FirstName ?? null,\n\t\t\tlast_name: lead.LastName ?? null,\n\t\t\tcompany: lead.Company ?? null,\n\t\t\temail: lead.Email ?? null,\n\t\t\tphone: lead.Phone ?? null,\n\t\t\ttitle: lead.Title ?? null,\n\t\t\tstatus: lead.Status ?? null,\n\t\t\tlead_source: lead.LeadSource ?? null,\n\t\t\tis_converted: lead.IsConverted ?? null,\n\t\t\tconverted_account_id: lead.ConvertedAccountId ?? null,\n\t\t\tconverted_contact_id: lead.ConvertedContactId ?? null,\n\t\t\tconverted_opportunity_id: lead.ConvertedOpportunityId ?? null,\n\t\t\towner_name: lead.Owner?.Name ?? null,\n\t\t\tcreated_date: lead.CreatedDate ?? null,\n\t\t\tlast_modified_date: lead.LastModifiedDate ?? null,\n\t\t},\n\t};\n}\n","// ---------------------------------------------------------------------------\n// SalesforceSourcePoller — polls Salesforce CRM and pushes deltas to SyncGateway\n// ---------------------------------------------------------------------------\n\nimport { BaseSourcePoller, extractDelta, type PushTarget } from \"@lakesync/core\";\nimport { SalesforceClient } from \"./client\";\nimport { mapAccount, mapContact, mapLead, mapOpportunity } from \"./mapping\";\nimport type {\n\tSalesforceConnectorConfig,\n\tSalesforceIngestConfig,\n\tSfAccount,\n\tSfContact,\n\tSfLead,\n\tSfOpportunity,\n} from \"./types\";\n\nconst DEFAULT_INTERVAL_MS = 30_000;\n\n// ---------------------------------------------------------------------------\n// SOQL field lists\n// ---------------------------------------------------------------------------\n\nconst ACCOUNT_FIELDS = [\n\t\"Id\",\n\t\"Name\",\n\t\"Type\",\n\t\"Industry\",\n\t\"Website\",\n\t\"Phone\",\n\t\"BillingCity\",\n\t\"BillingState\",\n\t\"BillingCountry\",\n\t\"AnnualRevenue\",\n\t\"NumberOfEmployees\",\n\t\"Owner.Name\",\n\t\"CreatedDate\",\n\t\"LastModifiedDate\",\n].join(\", \");\n\nconst CONTACT_FIELDS = [\n\t\"Id\",\n\t\"FirstName\",\n\t\"LastName\",\n\t\"Email\",\n\t\"Phone\",\n\t\"Title\",\n\t\"AccountId\",\n\t\"Account.Name\",\n\t\"MailingCity\",\n\t\"MailingState\",\n\t\"MailingCountry\",\n\t\"Owner.Name\",\n\t\"CreatedDate\",\n\t\"LastModifiedDate\",\n].join(\", \");\n\nconst OPPORTUNITY_FIELDS = [\n\t\"Id\",\n\t\"Name\",\n\t\"StageName\",\n\t\"Amount\",\n\t\"CloseDate\",\n\t\"Probability\",\n\t\"AccountId\",\n\t\"Account.Name\",\n\t\"Type\",\n\t\"LeadSource\",\n\t\"IsClosed\",\n\t\"IsWon\",\n\t\"Owner.Name\",\n\t\"CreatedDate\",\n\t\"LastModifiedDate\",\n].join(\", \");\n\nconst LEAD_FIELDS = [\n\t\"Id\",\n\t\"FirstName\",\n\t\"LastName\",\n\t\"Company\",\n\t\"Email\",\n\t\"Phone\",\n\t\"Title\",\n\t\"Status\",\n\t\"LeadSource\",\n\t\"IsConverted\",\n\t\"ConvertedAccountId\",\n\t\"ConvertedContactId\",\n\t\"ConvertedOpportunityId\",\n\t\"Owner.Name\",\n\t\"CreatedDate\",\n\t\"LastModifiedDate\",\n].join(\", \");\n\n/**\n * Polls Salesforce CRM for accounts, contacts, opportunities, and leads\n * and pushes 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 SalesforceSourcePoller extends BaseSourcePoller {\n\tprivate readonly connectionConfig: SalesforceConnectorConfig;\n\tprivate readonly client: SalesforceClient;\n\n\t/** Per-entity cursors: max LastModifiedDate from the last poll. */\n\tprivate cursors: Record<string, string | undefined> = {\n\t\taccounts: undefined,\n\t\tcontacts: undefined,\n\t\topportunities: undefined,\n\t\tleads: undefined,\n\t};\n\n\tconstructor(\n\t\tconnectionConfig: SalesforceConnectorConfig,\n\t\tingestConfig: SalesforceIngestConfig | undefined,\n\t\tname: string,\n\t\tgateway: PushTarget,\n\t\tclient?: SalesforceClient,\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 SalesforceClient(connectionConfig);\n\t}\n\n\t/** Execute a single poll cycle across all enabled entity types. */\n\tasync poll(): Promise<void> {\n\t\tconst includeAccounts = this.connectionConfig.includeAccounts ?? true;\n\t\tif (includeAccounts) {\n\t\t\tawait this.pollEntity<SfAccount>(\n\t\t\t\t\"Account\",\n\t\t\t\tACCOUNT_FIELDS,\n\t\t\t\t\"accounts\",\n\t\t\t\t\"sf_accounts\",\n\t\t\t\tmapAccount,\n\t\t\t);\n\t\t}\n\n\t\tconst includeContacts = this.connectionConfig.includeContacts ?? true;\n\t\tif (includeContacts) {\n\t\t\tawait this.pollEntity<SfContact>(\n\t\t\t\t\"Contact\",\n\t\t\t\tCONTACT_FIELDS,\n\t\t\t\t\"contacts\",\n\t\t\t\t\"sf_contacts\",\n\t\t\t\tmapContact,\n\t\t\t);\n\t\t}\n\n\t\tconst includeOpportunities = this.connectionConfig.includeOpportunities ?? true;\n\t\tif (includeOpportunities) {\n\t\t\tawait this.pollEntity<SfOpportunity>(\n\t\t\t\t\"Opportunity\",\n\t\t\t\tOPPORTUNITY_FIELDS,\n\t\t\t\t\"opportunities\",\n\t\t\t\t\"sf_opportunities\",\n\t\t\t\tmapOpportunity,\n\t\t\t);\n\t\t}\n\n\t\tconst includeLeads = this.connectionConfig.includeLeads ?? true;\n\t\tif (includeLeads) {\n\t\t\tawait this.pollEntity<SfLead>(\"Lead\", LEAD_FIELDS, \"leads\", \"sf_leads\", mapLead);\n\t\t}\n\n\t\tawait this.flushAccumulator();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Generic entity polling via LastModifiedDate cursor\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollEntity<T extends { Id: string; LastModifiedDate: string | null }>(\n\t\tsObjectType: string,\n\t\tfields: string,\n\t\tcursorKey: string,\n\t\ttable: string,\n\t\tmapFn: (record: T) => { rowId: string; row: Record<string, unknown> },\n\t): Promise<void> {\n\t\tconst cursor = this.cursors[cursorKey];\n\t\tconst soql = this.buildSoql(sObjectType, fields, cursor);\n\n\t\tconst result = await this.client.query<T>(soql);\n\t\tif (!result.ok) return;\n\n\t\tconst records = result.value;\n\t\tif (records.length === 0) return;\n\n\t\tlet maxLastModified = cursor;\n\n\t\tfor (const record of records) {\n\t\t\tconst { rowId, row } = mapFn(record);\n\n\t\t\tconst delta = await extractDelta(null, row, {\n\t\t\t\ttable,\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\tconst lastModified = record.LastModifiedDate;\n\t\t\tif (lastModified && (!maxLastModified || lastModified > maxLastModified)) {\n\t\t\t\tmaxLastModified = lastModified;\n\t\t\t}\n\t\t}\n\n\t\tthis.cursors[cursorKey] = maxLastModified;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// SOQL query builder\n\t// -----------------------------------------------------------------------\n\n\tprivate buildSoql(sObjectType: string, fields: string, cursor: string | undefined): string {\n\t\tconst clauses: string[] = [];\n\n\t\tif (cursor) {\n\t\t\tclauses.push(`LastModifiedDate > ${cursor}`);\n\t\t}\n\n\t\tif (this.connectionConfig.soqlFilter) {\n\t\t\tclauses.push(this.connectionConfig.soqlFilter);\n\t\t}\n\n\t\tconst where = clauses.length > 0 ? ` WHERE ${clauses.join(\" AND \")}` : \"\";\n\t\treturn `SELECT ${fields} FROM ${sObjectType}${where} ORDER BY LastModifiedDate ASC`;\n\t}\n}\n"],"mappings":"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","names":["responseBody"]}
1
+ {"version":3,"sources":["../../connector-salesforce/src/errors.ts","../../connector-salesforce/src/client.ts","../../connector-salesforce/src/mapping.ts","../../connector-salesforce/src/poller.ts"],"sourcesContent":["import { LakeSyncError } from \"@lakesync/core\";\n\n/** HTTP error from the Salesforce REST API. */\nexport class SalesforceApiError extends LakeSyncError {\n\t/** HTTP status code returned by Salesforce. */\n\treadonly statusCode: number;\n\t/** Raw response body from Salesforce. */\n\treadonly responseBody: string;\n\n\tconstructor(statusCode: number, responseBody: string, cause?: Error) {\n\t\tsuper(`Salesforce API error (${statusCode}): ${responseBody}`, \"SALESFORCE_API_ERROR\", cause);\n\t\tthis.statusCode = statusCode;\n\t\tthis.responseBody = responseBody;\n\t}\n}\n\n/** Authentication failure from the Salesforce OAuth token endpoint. */\nexport class SalesforceAuthError extends LakeSyncError {\n\tconstructor(message: string, cause?: Error) {\n\t\tsuper(message, \"SALESFORCE_AUTH_ERROR\", cause);\n\t}\n}\n","// ---------------------------------------------------------------------------\n// SalesforceClient — HTTP wrapper for Salesforce REST API\n// ---------------------------------------------------------------------------\n\nimport { Err, Ok, type Result } from \"@lakesync/core\";\nimport { SalesforceApiError, SalesforceAuthError } from \"./errors\";\nimport type {\n\tSalesforceAuthResponse,\n\tSalesforceConnectorConfig,\n\tSalesforceQueryResponse,\n} from \"./types\";\n\nconst DEFAULT_API_VERSION = \"v62.0\";\nconst MAX_RETRY_ATTEMPTS = 3;\nconst DEFAULT_RETRY_AFTER_MS = 10_000;\n\n/**\n * HTTP client for the Salesforce REST API.\n *\n * Uses OAuth 2.0 Username-Password flow for authentication and global `fetch`.\n * All public methods return `Result<T, SalesforceApiError | SalesforceAuthError>`.\n */\nexport class SalesforceClient {\n\tprivate readonly config: SalesforceConnectorConfig;\n\tprivate readonly apiVersion: string;\n\tprivate readonly loginUrl: string;\n\n\tprivate accessToken: string | null = null;\n\tprivate instanceUrl: string;\n\n\tconstructor(config: SalesforceConnectorConfig) {\n\t\tthis.config = config;\n\t\tthis.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;\n\t\tthis.loginUrl = config.isSandbox\n\t\t\t? \"https://test.salesforce.com\"\n\t\t\t: \"https://login.salesforce.com\";\n\t\tthis.instanceUrl = config.instanceUrl;\n\t}\n\n\t/**\n\t * Authenticate via OAuth 2.0 Username-Password flow.\n\t *\n\t * Stores access token and updates instance URL from the response.\n\t */\n\tasync authenticate(): Promise<Result<void, SalesforceAuthError>> {\n\t\tconst body = new URLSearchParams({\n\t\t\tgrant_type: \"password\",\n\t\t\tclient_id: this.config.clientId,\n\t\t\tclient_secret: this.config.clientSecret,\n\t\t\tusername: this.config.username,\n\t\t\tpassword: this.config.password,\n\t\t});\n\n\t\tlet response: Response;\n\t\ttry {\n\t\t\tresponse = await fetch(`${this.loginUrl}/services/oauth2/token`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\t\t\tbody: body.toString(),\n\t\t\t});\n\t\t} catch (err) {\n\t\t\treturn Err(\n\t\t\t\tnew SalesforceAuthError(\n\t\t\t\t\t`Failed to connect to Salesforce auth endpoint: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t\terr instanceof Error ? err : undefined,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text();\n\t\t\treturn Err(\n\t\t\t\tnew SalesforceAuthError(`Salesforce authentication failed (${response.status}): ${text}`),\n\t\t\t);\n\t\t}\n\n\t\tconst data = (await response.json()) as SalesforceAuthResponse;\n\t\tthis.accessToken = data.access_token;\n\t\tthis.instanceUrl = data.instance_url;\n\n\t\treturn Ok(undefined);\n\t}\n\n\t/**\n\t * Execute a SOQL query with auto-pagination.\n\t *\n\t * Automatically authenticates on first call and re-authenticates on 401.\n\t */\n\tasync query<T>(soql: string): Promise<Result<T[], SalesforceApiError | SalesforceAuthError>> {\n\t\t// Ensure we have a token\n\t\tif (!this.accessToken) {\n\t\t\tconst authResult = await this.authenticate();\n\t\t\tif (!authResult.ok) return authResult;\n\t\t}\n\n\t\tconst allRecords: T[] = [];\n\t\tlet url = `${this.instanceUrl}/services/data/${this.apiVersion}/query?q=${encodeURIComponent(soql)}`;\n\n\t\twhile (true) {\n\t\t\tconst result = await this.request<SalesforceQueryResponse<T>>(url);\n\n\t\t\t// Re-auth on 401 and retry once\n\t\t\tif (\n\t\t\t\t!result.ok &&\n\t\t\t\tresult.error instanceof SalesforceApiError &&\n\t\t\t\tresult.error.statusCode === 401\n\t\t\t) {\n\t\t\t\tconst authResult = await this.authenticate();\n\t\t\t\tif (!authResult.ok) return authResult;\n\n\t\t\t\tconst retryResult = await this.request<SalesforceQueryResponse<T>>(url);\n\t\t\t\tif (!retryResult.ok) return retryResult;\n\n\t\t\t\tfor (const record of retryResult.value.records) {\n\t\t\t\t\tallRecords.push(record);\n\t\t\t\t}\n\n\t\t\t\tif (retryResult.value.done || !retryResult.value.nextRecordsUrl) break;\n\t\t\t\turl = `${this.instanceUrl}${retryResult.value.nextRecordsUrl}`;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tfor (const record of result.value.records) {\n\t\t\t\tallRecords.push(record);\n\t\t\t}\n\n\t\t\tif (result.value.done || !result.value.nextRecordsUrl) break;\n\t\t\turl = `${this.instanceUrl}${result.value.nextRecordsUrl}`;\n\t\t}\n\n\t\treturn Ok(allRecords);\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\turl: string,\n\t): Promise<Result<T, SalesforceApiError | SalesforceAuthError>> {\n\t\tfor (let attempt = 0; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {\n\t\t\tconst headers: Record<string, string> = {\n\t\t\t\tAuthorization: `Bearer ${this.accessToken}`,\n\t\t\t\tAccept: \"application/json\",\n\t\t\t};\n\n\t\t\tconst response = await fetch(url, { method: \"GET\", headers });\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\t// Rate limit: 503 with Retry-After\n\t\t\tif (response.status === 503) {\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_RETRY_ATTEMPTS) {\n\t\t\t\t\tawait sleep(waitMs);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst responseBody = await response.text();\n\t\t\t\treturn Err(new SalesforceApiError(503, responseBody));\n\t\t\t}\n\n\t\t\tconst responseBody = await response.text();\n\t\t\treturn Err(new SalesforceApiError(response.status, responseBody));\n\t\t}\n\n\t\treturn Err(new SalesforceApiError(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// Salesforce Entity → Flat LakeSync Row Mapping\n// ---------------------------------------------------------------------------\n\nimport type { SfAccount, SfContact, SfLead, SfOpportunity } from \"./types\";\n\n/**\n * Map a Salesforce Account to a flat row for the `sf_accounts` table.\n *\n * The row ID is the Salesforce record Id.\n */\nexport function mapAccount(account: SfAccount): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: account.Id,\n\t\trow: {\n\t\t\tsf_id: account.Id,\n\t\t\tname: account.Name ?? null,\n\t\t\ttype: account.Type ?? null,\n\t\t\tindustry: account.Industry ?? null,\n\t\t\twebsite: account.Website ?? null,\n\t\t\tphone: account.Phone ?? null,\n\t\t\tbilling_city: account.BillingCity ?? null,\n\t\t\tbilling_state: account.BillingState ?? null,\n\t\t\tbilling_country: account.BillingCountry ?? null,\n\t\t\tannual_revenue: account.AnnualRevenue ?? null,\n\t\t\tnumber_of_employees: account.NumberOfEmployees ?? null,\n\t\t\towner_name: account.Owner?.Name ?? null,\n\t\t\tcreated_date: account.CreatedDate ?? null,\n\t\t\tlast_modified_date: account.LastModifiedDate ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Salesforce Contact to a flat row for the `sf_contacts` table.\n *\n * The row ID is the Salesforce record Id.\n */\nexport function mapContact(contact: SfContact): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: contact.Id,\n\t\trow: {\n\t\t\tsf_id: contact.Id,\n\t\t\tfirst_name: contact.FirstName ?? null,\n\t\t\tlast_name: contact.LastName ?? null,\n\t\t\temail: contact.Email ?? null,\n\t\t\tphone: contact.Phone ?? null,\n\t\t\ttitle: contact.Title ?? null,\n\t\t\taccount_id: contact.AccountId ?? null,\n\t\t\taccount_name: contact.Account?.Name ?? null,\n\t\t\tmailing_city: contact.MailingCity ?? null,\n\t\t\tmailing_state: contact.MailingState ?? null,\n\t\t\tmailing_country: contact.MailingCountry ?? null,\n\t\t\towner_name: contact.Owner?.Name ?? null,\n\t\t\tcreated_date: contact.CreatedDate ?? null,\n\t\t\tlast_modified_date: contact.LastModifiedDate ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Salesforce Opportunity to a flat row for the `sf_opportunities` table.\n *\n * The row ID is the Salesforce record Id.\n */\nexport function mapOpportunity(opportunity: SfOpportunity): {\n\trowId: string;\n\trow: Record<string, unknown>;\n} {\n\treturn {\n\t\trowId: opportunity.Id,\n\t\trow: {\n\t\t\tsf_id: opportunity.Id,\n\t\t\tname: opportunity.Name ?? null,\n\t\t\tstage_name: opportunity.StageName ?? null,\n\t\t\tamount: opportunity.Amount ?? null,\n\t\t\tclose_date: opportunity.CloseDate ?? null,\n\t\t\tprobability: opportunity.Probability ?? null,\n\t\t\taccount_id: opportunity.AccountId ?? null,\n\t\t\taccount_name: opportunity.Account?.Name ?? null,\n\t\t\ttype: opportunity.Type ?? null,\n\t\t\tlead_source: opportunity.LeadSource ?? null,\n\t\t\tis_closed: opportunity.IsClosed ?? null,\n\t\t\tis_won: opportunity.IsWon ?? null,\n\t\t\towner_name: opportunity.Owner?.Name ?? null,\n\t\t\tcreated_date: opportunity.CreatedDate ?? null,\n\t\t\tlast_modified_date: opportunity.LastModifiedDate ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Salesforce Lead to a flat row for the `sf_leads` table.\n *\n * The row ID is the Salesforce record Id.\n */\nexport function mapLead(lead: SfLead): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: lead.Id,\n\t\trow: {\n\t\t\tsf_id: lead.Id,\n\t\t\tfirst_name: lead.FirstName ?? null,\n\t\t\tlast_name: lead.LastName ?? null,\n\t\t\tcompany: lead.Company ?? null,\n\t\t\temail: lead.Email ?? null,\n\t\t\tphone: lead.Phone ?? null,\n\t\t\ttitle: lead.Title ?? null,\n\t\t\tstatus: lead.Status ?? null,\n\t\t\tlead_source: lead.LeadSource ?? null,\n\t\t\tis_converted: lead.IsConverted ?? null,\n\t\t\tconverted_account_id: lead.ConvertedAccountId ?? null,\n\t\t\tconverted_contact_id: lead.ConvertedContactId ?? null,\n\t\t\tconverted_opportunity_id: lead.ConvertedOpportunityId ?? null,\n\t\t\towner_name: lead.Owner?.Name ?? null,\n\t\t\tcreated_date: lead.CreatedDate ?? null,\n\t\t\tlast_modified_date: lead.LastModifiedDate ?? null,\n\t\t},\n\t};\n}\n","// ---------------------------------------------------------------------------\n// SalesforceSourcePoller — polls Salesforce CRM and pushes deltas to SyncGateway\n// ---------------------------------------------------------------------------\n\nimport { BaseSourcePoller, extractDelta, type PushTarget } from \"@lakesync/core\";\nimport { SalesforceClient } from \"./client\";\nimport { mapAccount, mapContact, mapLead, mapOpportunity } from \"./mapping\";\nimport type {\n\tSalesforceConnectorConfig,\n\tSalesforceIngestConfig,\n\tSfAccount,\n\tSfContact,\n\tSfLead,\n\tSfOpportunity,\n} from \"./types\";\n\nconst DEFAULT_INTERVAL_MS = 30_000;\n\n// ---------------------------------------------------------------------------\n// SOQL field lists\n// ---------------------------------------------------------------------------\n\nconst ACCOUNT_FIELDS = [\n\t\"Id\",\n\t\"Name\",\n\t\"Type\",\n\t\"Industry\",\n\t\"Website\",\n\t\"Phone\",\n\t\"BillingCity\",\n\t\"BillingState\",\n\t\"BillingCountry\",\n\t\"AnnualRevenue\",\n\t\"NumberOfEmployees\",\n\t\"Owner.Name\",\n\t\"CreatedDate\",\n\t\"LastModifiedDate\",\n].join(\", \");\n\nconst CONTACT_FIELDS = [\n\t\"Id\",\n\t\"FirstName\",\n\t\"LastName\",\n\t\"Email\",\n\t\"Phone\",\n\t\"Title\",\n\t\"AccountId\",\n\t\"Account.Name\",\n\t\"MailingCity\",\n\t\"MailingState\",\n\t\"MailingCountry\",\n\t\"Owner.Name\",\n\t\"CreatedDate\",\n\t\"LastModifiedDate\",\n].join(\", \");\n\nconst OPPORTUNITY_FIELDS = [\n\t\"Id\",\n\t\"Name\",\n\t\"StageName\",\n\t\"Amount\",\n\t\"CloseDate\",\n\t\"Probability\",\n\t\"AccountId\",\n\t\"Account.Name\",\n\t\"Type\",\n\t\"LeadSource\",\n\t\"IsClosed\",\n\t\"IsWon\",\n\t\"Owner.Name\",\n\t\"CreatedDate\",\n\t\"LastModifiedDate\",\n].join(\", \");\n\nconst LEAD_FIELDS = [\n\t\"Id\",\n\t\"FirstName\",\n\t\"LastName\",\n\t\"Company\",\n\t\"Email\",\n\t\"Phone\",\n\t\"Title\",\n\t\"Status\",\n\t\"LeadSource\",\n\t\"IsConverted\",\n\t\"ConvertedAccountId\",\n\t\"ConvertedContactId\",\n\t\"ConvertedOpportunityId\",\n\t\"Owner.Name\",\n\t\"CreatedDate\",\n\t\"LastModifiedDate\",\n].join(\", \");\n\n/**\n * Polls Salesforce CRM for accounts, contacts, opportunities, and leads\n * and pushes 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 SalesforceSourcePoller extends BaseSourcePoller {\n\tprivate readonly connectionConfig: SalesforceConnectorConfig;\n\tprivate readonly client: SalesforceClient;\n\n\t/** Per-entity cursors: max LastModifiedDate from the last poll. */\n\tprivate cursors: Record<string, string | undefined> = {\n\t\taccounts: undefined,\n\t\tcontacts: undefined,\n\t\topportunities: undefined,\n\t\tleads: undefined,\n\t};\n\n\tconstructor(\n\t\tconnectionConfig: SalesforceConnectorConfig,\n\t\tingestConfig: SalesforceIngestConfig | undefined,\n\t\tname: string,\n\t\tgateway: PushTarget,\n\t\tclient?: SalesforceClient,\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 SalesforceClient(connectionConfig);\n\t}\n\n\t/** Execute a single poll cycle across all enabled entity types. */\n\tasync poll(): Promise<void> {\n\t\tconst includeAccounts = this.connectionConfig.includeAccounts ?? true;\n\t\tif (includeAccounts) {\n\t\t\tawait this.pollEntity<SfAccount>(\n\t\t\t\t\"Account\",\n\t\t\t\tACCOUNT_FIELDS,\n\t\t\t\t\"accounts\",\n\t\t\t\t\"sf_accounts\",\n\t\t\t\tmapAccount,\n\t\t\t);\n\t\t}\n\n\t\tconst includeContacts = this.connectionConfig.includeContacts ?? true;\n\t\tif (includeContacts) {\n\t\t\tawait this.pollEntity<SfContact>(\n\t\t\t\t\"Contact\",\n\t\t\t\tCONTACT_FIELDS,\n\t\t\t\t\"contacts\",\n\t\t\t\t\"sf_contacts\",\n\t\t\t\tmapContact,\n\t\t\t);\n\t\t}\n\n\t\tconst includeOpportunities = this.connectionConfig.includeOpportunities ?? true;\n\t\tif (includeOpportunities) {\n\t\t\tawait this.pollEntity<SfOpportunity>(\n\t\t\t\t\"Opportunity\",\n\t\t\t\tOPPORTUNITY_FIELDS,\n\t\t\t\t\"opportunities\",\n\t\t\t\t\"sf_opportunities\",\n\t\t\t\tmapOpportunity,\n\t\t\t);\n\t\t}\n\n\t\tconst includeLeads = this.connectionConfig.includeLeads ?? true;\n\t\tif (includeLeads) {\n\t\t\tawait this.pollEntity<SfLead>(\"Lead\", LEAD_FIELDS, \"leads\", \"sf_leads\", mapLead);\n\t\t}\n\n\t\tawait this.flushAccumulator();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Generic entity polling via LastModifiedDate cursor\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollEntity<T extends { Id: string; LastModifiedDate: string | null }>(\n\t\tsObjectType: string,\n\t\tfields: string,\n\t\tcursorKey: string,\n\t\ttable: string,\n\t\tmapFn: (record: T) => { rowId: string; row: Record<string, unknown> },\n\t): Promise<void> {\n\t\tconst cursor = this.cursors[cursorKey];\n\t\tconst soql = this.buildSoql(sObjectType, fields, cursor);\n\n\t\tconst result = await this.client.query<T>(soql);\n\t\tif (!result.ok) return;\n\n\t\tconst records = result.value;\n\t\tif (records.length === 0) return;\n\n\t\tlet maxLastModified = cursor;\n\n\t\tfor (const record of records) {\n\t\t\tconst { rowId, row } = mapFn(record);\n\n\t\t\tconst delta = await extractDelta(null, row, {\n\t\t\t\ttable,\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\tconst lastModified = record.LastModifiedDate;\n\t\t\tif (lastModified && (!maxLastModified || lastModified > maxLastModified)) {\n\t\t\t\tmaxLastModified = lastModified;\n\t\t\t}\n\t\t}\n\n\t\tthis.cursors[cursorKey] = maxLastModified;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// SOQL query builder\n\t// -----------------------------------------------------------------------\n\n\tprivate buildSoql(sObjectType: string, fields: string, cursor: string | undefined): string {\n\t\tconst clauses: string[] = [];\n\n\t\tif (cursor) {\n\t\t\tclauses.push(`LastModifiedDate > ${cursor}`);\n\t\t}\n\n\t\tif (this.connectionConfig.soqlFilter) {\n\t\t\tclauses.push(this.connectionConfig.soqlFilter);\n\t\t}\n\n\t\tconst where = clauses.length > 0 ? ` WHERE ${clauses.join(\" AND \")}` : \"\";\n\t\treturn `SELECT ${fields} FROM ${sObjectType}${where} ORDER BY LastModifiedDate ASC`;\n\t}\n}\n"],"mappings":"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","names":["responseBody"]}
@@ -0,0 +1,207 @@
1
+ import { L as LakeSyncError, R as Result } from './result-CojzlFE2.js';
2
+ import { B as BaseSourcePoller, a as PushTarget } from './base-poller-Qo_SmCZs.js';
3
+ import './types-V_jVu2sA.js';
4
+ import './hlc-DiD8QNG3.js';
5
+
6
+ /** HTTP error from the Jira REST API. */
7
+ declare class JiraApiError extends LakeSyncError {
8
+ /** HTTP status code returned by Jira. */
9
+ readonly statusCode: number;
10
+ /** Raw response body from Jira. */
11
+ readonly responseBody: string;
12
+ constructor(statusCode: number, responseBody: string, cause?: Error);
13
+ }
14
+ /** Rate limit error (HTTP 429) from the Jira REST API. */
15
+ declare class JiraRateLimitError extends LakeSyncError {
16
+ /** Milliseconds to wait before retrying. */
17
+ readonly retryAfterMs: number;
18
+ constructor(retryAfterMs: number, cause?: Error);
19
+ }
20
+
21
+ /** Connection configuration for a Jira Cloud source. */
22
+ interface JiraConnectorConfig {
23
+ /** Jira Cloud domain (e.g. "mycompany" for mycompany.atlassian.net). */
24
+ domain: string;
25
+ /** Email address for Basic auth. */
26
+ email: string;
27
+ /** API token paired with the email. */
28
+ apiToken: string;
29
+ /** Optional JQL filter to scope issue polling. */
30
+ jql?: string;
31
+ /** Whether to include comments (default true). */
32
+ includeComments?: boolean;
33
+ /** Whether to include projects (default true). */
34
+ includeProjects?: boolean;
35
+ }
36
+ /** Ingest configuration for the Jira poller. */
37
+ interface JiraIngestConfig {
38
+ /** Poll interval in milliseconds (default 30 000). */
39
+ intervalMs?: number;
40
+ /** Number of deltas per push chunk (default 500). */
41
+ chunkSize?: number;
42
+ /** Approximate memory budget in bytes — triggers flush at 70%. */
43
+ memoryBudgetBytes?: number;
44
+ }
45
+ /** Issue search response from POST /rest/api/3/search/jql. */
46
+ interface JiraSearchResponse {
47
+ issues: JiraIssue[];
48
+ /** Opaque token for fetching the next page (absent on last page). */
49
+ nextPageToken?: string;
50
+ /** True when this is the final page. */
51
+ isLast?: boolean;
52
+ }
53
+ /** A single Jira issue. */
54
+ interface JiraIssue {
55
+ id: string;
56
+ key: string;
57
+ fields: {
58
+ summary: string | null;
59
+ description: unknown | null;
60
+ status: {
61
+ name: string;
62
+ } | null;
63
+ priority: {
64
+ name: string;
65
+ } | null;
66
+ issuetype: {
67
+ name: string;
68
+ } | null;
69
+ assignee: {
70
+ displayName: string;
71
+ emailAddress: string;
72
+ } | null;
73
+ reporter: {
74
+ displayName: string;
75
+ emailAddress: string;
76
+ } | null;
77
+ labels: string[] | null;
78
+ project: {
79
+ key: string;
80
+ name: string;
81
+ } | null;
82
+ created: string | null;
83
+ updated: string | null;
84
+ };
85
+ }
86
+ /** Paginated comment response from GET /rest/api/3/issue/{key}/comment. */
87
+ interface JiraCommentPage {
88
+ startAt: number;
89
+ maxResults: number;
90
+ total: number;
91
+ comments: JiraComment[];
92
+ }
93
+ /** A single Jira comment. */
94
+ interface JiraComment {
95
+ id: string;
96
+ body: unknown | null;
97
+ author: {
98
+ displayName: string;
99
+ emailAddress: string;
100
+ } | null;
101
+ created: string | null;
102
+ updated: string | null;
103
+ }
104
+ /** Paginated project response from GET /rest/api/3/project/search. */
105
+ interface JiraProjectPage {
106
+ startAt: number;
107
+ maxResults: number;
108
+ total: number;
109
+ values: JiraProject[];
110
+ }
111
+ /** A single Jira project. */
112
+ interface JiraProject {
113
+ id: string;
114
+ key: string;
115
+ name: string;
116
+ projectTypeKey: string | null;
117
+ lead: {
118
+ displayName: string;
119
+ } | null;
120
+ }
121
+
122
+ /**
123
+ * HTTP client for the Jira Cloud REST API v3.
124
+ *
125
+ * Uses Basic authentication (email + API token) and global `fetch`.
126
+ * All public methods return `Result<T, JiraApiError>`.
127
+ */
128
+ declare class JiraClient {
129
+ private readonly baseUrl;
130
+ private readonly authHeader;
131
+ constructor(config: JiraConnectorConfig);
132
+ /**
133
+ * Search for issues via JQL with auto-pagination.
134
+ *
135
+ * Uses the `/rest/api/3/search/jql` endpoint with token-based pagination.
136
+ * When `updatedSince` is provided, appends `AND updated >= "date"` to the JQL.
137
+ * Empty JQL is replaced with `project is not EMPTY` (the new endpoint
138
+ * rejects unbounded queries).
139
+ *
140
+ * @param limit — optional cap on the total number of issues returned.
141
+ */
142
+ searchIssues(jql: string, updatedSince?: string, limit?: number): Promise<Result<JiraIssue[], JiraApiError | JiraRateLimitError>>;
143
+ /**
144
+ * Fetch all comments for a given issue key with auto-pagination.
145
+ */
146
+ getComments(issueKey: string): Promise<Result<JiraComment[], JiraApiError | JiraRateLimitError>>;
147
+ /**
148
+ * Fetch all projects with auto-pagination.
149
+ */
150
+ getProjects(): Promise<Result<JiraProject[], JiraApiError | JiraRateLimitError>>;
151
+ /** Make an HTTP request with rate-limit retry logic. */
152
+ private request;
153
+ }
154
+
155
+ /**
156
+ * Map a Jira issue to a flat row for the `jira_issues` table.
157
+ *
158
+ * The row ID is the issue key (e.g. "PROJ-123").
159
+ */
160
+ declare function mapIssue(issue: JiraIssue): {
161
+ rowId: string;
162
+ row: Record<string, unknown>;
163
+ };
164
+ /**
165
+ * Map a Jira comment to a flat row for the `jira_comments` table.
166
+ *
167
+ * The row ID is "{issueKey}:{commentId}".
168
+ */
169
+ declare function mapComment(issueKey: string, comment: JiraComment): {
170
+ rowId: string;
171
+ row: Record<string, unknown>;
172
+ };
173
+ /**
174
+ * Map a Jira project to a flat row for the `jira_projects` table.
175
+ *
176
+ * The row ID is the project key (e.g. "PROJ").
177
+ */
178
+ declare function mapProject(project: JiraProject): {
179
+ rowId: string;
180
+ row: Record<string, unknown>;
181
+ };
182
+
183
+ /**
184
+ * Polls Jira Cloud for issues, comments, and projects and pushes
185
+ * detected changes into a gateway via streaming accumulation.
186
+ *
187
+ * Uses {@link BaseSourcePoller.accumulateDelta} to push deltas in
188
+ * memory-bounded chunks instead of collecting all deltas in a single array.
189
+ */
190
+ declare class JiraSourcePoller extends BaseSourcePoller {
191
+ private readonly connectionConfig;
192
+ private readonly client;
193
+ /** Cursor: max `fields.updated` value from the last issue poll. */
194
+ private lastUpdated;
195
+ /** In-memory snapshot for comment diff (keyed by rowId). */
196
+ private commentSnapshot;
197
+ /** In-memory snapshot for project diff (keyed by project key). */
198
+ private projectSnapshot;
199
+ constructor(connectionConfig: JiraConnectorConfig, ingestConfig: JiraIngestConfig | undefined, name: string, gateway: PushTarget, client?: JiraClient);
200
+ /** Execute a single poll cycle across all entity types. */
201
+ poll(): Promise<void>;
202
+ private pollIssues;
203
+ private pollComments;
204
+ private pollProjects;
205
+ }
206
+
207
+ export { JiraApiError, JiraClient, type JiraComment, type JiraCommentPage, type JiraConnectorConfig, type JiraIngestConfig, type JiraIssue, type JiraProject, type JiraProjectPage, JiraRateLimitError, type JiraSearchResponse, JiraSourcePoller, mapComment, mapIssue, mapProject };
@@ -0,0 +1,21 @@
1
+ import {
2
+ JiraApiError,
3
+ JiraClient,
4
+ JiraRateLimitError,
5
+ JiraSourcePoller,
6
+ mapComment,
7
+ mapIssue,
8
+ mapProject
9
+ } from "./chunk-DIQZDK4C.js";
10
+ import "./chunk-ICNT7I3K.js";
11
+ import "./chunk-7D4SUZUM.js";
12
+ export {
13
+ JiraApiError,
14
+ JiraClient,
15
+ JiraRateLimitError,
16
+ JiraSourcePoller,
17
+ mapComment,
18
+ mapIssue,
19
+ mapProject
20
+ };
21
+ //# sourceMappingURL=connector-jira.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,239 @@
1
+ import { L as LakeSyncError, R as Result } from './result-CojzlFE2.js';
2
+ import { B as BaseSourcePoller, a as PushTarget } from './base-poller-Qo_SmCZs.js';
3
+ import './types-V_jVu2sA.js';
4
+ import './hlc-DiD8QNG3.js';
5
+
6
+ /** HTTP error from the Salesforce REST API. */
7
+ declare class SalesforceApiError extends LakeSyncError {
8
+ /** HTTP status code returned by Salesforce. */
9
+ readonly statusCode: number;
10
+ /** Raw response body from Salesforce. */
11
+ readonly responseBody: string;
12
+ constructor(statusCode: number, responseBody: string, cause?: Error);
13
+ }
14
+ /** Authentication failure from the Salesforce OAuth token endpoint. */
15
+ declare class SalesforceAuthError extends LakeSyncError {
16
+ constructor(message: string, cause?: Error);
17
+ }
18
+
19
+ /** Connection configuration for a Salesforce CRM source. */
20
+ interface SalesforceConnectorConfig {
21
+ /** Salesforce instance URL (e.g. "https://mycompany.salesforce.com"). */
22
+ instanceUrl: string;
23
+ /** Connected App consumer key. */
24
+ clientId: string;
25
+ /** Connected App consumer secret. */
26
+ clientSecret: string;
27
+ /** Salesforce username. */
28
+ username: string;
29
+ /** Salesforce password + security token concatenated. */
30
+ password: string;
31
+ /** REST API version (default "v62.0"). */
32
+ apiVersion?: string;
33
+ /** Use test.salesforce.com for auth (default false). */
34
+ isSandbox?: boolean;
35
+ /** Optional WHERE clause fragment appended to all SOQL queries. */
36
+ soqlFilter?: string;
37
+ /** Whether to include Account objects (default true). */
38
+ includeAccounts?: boolean;
39
+ /** Whether to include Contact objects (default true). */
40
+ includeContacts?: boolean;
41
+ /** Whether to include Opportunity objects (default true). */
42
+ includeOpportunities?: boolean;
43
+ /** Whether to include Lead objects (default true). */
44
+ includeLeads?: boolean;
45
+ }
46
+ /** Ingest configuration for the Salesforce poller. */
47
+ interface SalesforceIngestConfig {
48
+ /** Poll interval in milliseconds (default 30 000). */
49
+ intervalMs?: number;
50
+ /** Number of deltas per push chunk (default 500). */
51
+ chunkSize?: number;
52
+ /** Approximate memory budget in bytes — triggers flush at 70%. */
53
+ memoryBudgetBytes?: number;
54
+ }
55
+ /** OAuth 2.0 token response from the Salesforce token endpoint. */
56
+ interface SalesforceAuthResponse {
57
+ access_token: string;
58
+ instance_url: string;
59
+ token_type: string;
60
+ issued_at: string;
61
+ signature: string;
62
+ }
63
+ /** SOQL query response envelope. */
64
+ interface SalesforceQueryResponse<T> {
65
+ totalSize: number;
66
+ done: boolean;
67
+ nextRecordsUrl?: string;
68
+ records: T[];
69
+ }
70
+ /** Salesforce Account sObject (fields used by the connector). */
71
+ interface SfAccount {
72
+ Id: string;
73
+ Name: string | null;
74
+ Type: string | null;
75
+ Industry: string | null;
76
+ Website: string | null;
77
+ Phone: string | null;
78
+ BillingCity: string | null;
79
+ BillingState: string | null;
80
+ BillingCountry: string | null;
81
+ AnnualRevenue: number | null;
82
+ NumberOfEmployees: number | null;
83
+ Owner: {
84
+ Name: string;
85
+ } | null;
86
+ CreatedDate: string | null;
87
+ LastModifiedDate: string | null;
88
+ }
89
+ /** Salesforce Contact sObject (fields used by the connector). */
90
+ interface SfContact {
91
+ Id: string;
92
+ FirstName: string | null;
93
+ LastName: string | null;
94
+ Email: string | null;
95
+ Phone: string | null;
96
+ Title: string | null;
97
+ AccountId: string | null;
98
+ Account: {
99
+ Name: string;
100
+ } | null;
101
+ MailingCity: string | null;
102
+ MailingState: string | null;
103
+ MailingCountry: string | null;
104
+ Owner: {
105
+ Name: string;
106
+ } | null;
107
+ CreatedDate: string | null;
108
+ LastModifiedDate: string | null;
109
+ }
110
+ /** Salesforce Opportunity sObject (fields used by the connector). */
111
+ interface SfOpportunity {
112
+ Id: string;
113
+ Name: string | null;
114
+ StageName: string | null;
115
+ Amount: number | null;
116
+ CloseDate: string | null;
117
+ Probability: number | null;
118
+ AccountId: string | null;
119
+ Account: {
120
+ Name: string;
121
+ } | null;
122
+ Type: string | null;
123
+ LeadSource: string | null;
124
+ IsClosed: boolean | null;
125
+ IsWon: boolean | null;
126
+ Owner: {
127
+ Name: string;
128
+ } | null;
129
+ CreatedDate: string | null;
130
+ LastModifiedDate: string | null;
131
+ }
132
+ /** Salesforce Lead sObject (fields used by the connector). */
133
+ interface SfLead {
134
+ Id: string;
135
+ FirstName: string | null;
136
+ LastName: string | null;
137
+ Company: string | null;
138
+ Email: string | null;
139
+ Phone: string | null;
140
+ Title: string | null;
141
+ Status: string | null;
142
+ LeadSource: string | null;
143
+ IsConverted: boolean | null;
144
+ ConvertedAccountId: string | null;
145
+ ConvertedContactId: string | null;
146
+ ConvertedOpportunityId: string | null;
147
+ Owner: {
148
+ Name: string;
149
+ } | null;
150
+ CreatedDate: string | null;
151
+ LastModifiedDate: string | null;
152
+ }
153
+
154
+ /**
155
+ * HTTP client for the Salesforce REST API.
156
+ *
157
+ * Uses OAuth 2.0 Username-Password flow for authentication and global `fetch`.
158
+ * All public methods return `Result<T, SalesforceApiError | SalesforceAuthError>`.
159
+ */
160
+ declare class SalesforceClient {
161
+ private readonly config;
162
+ private readonly apiVersion;
163
+ private readonly loginUrl;
164
+ private accessToken;
165
+ private instanceUrl;
166
+ constructor(config: SalesforceConnectorConfig);
167
+ /**
168
+ * Authenticate via OAuth 2.0 Username-Password flow.
169
+ *
170
+ * Stores access token and updates instance URL from the response.
171
+ */
172
+ authenticate(): Promise<Result<void, SalesforceAuthError>>;
173
+ /**
174
+ * Execute a SOQL query with auto-pagination.
175
+ *
176
+ * Automatically authenticates on first call and re-authenticates on 401.
177
+ */
178
+ query<T>(soql: string): Promise<Result<T[], SalesforceApiError | SalesforceAuthError>>;
179
+ /** Make an HTTP request with rate-limit retry logic. */
180
+ private request;
181
+ }
182
+
183
+ /**
184
+ * Map a Salesforce Account to a flat row for the `sf_accounts` table.
185
+ *
186
+ * The row ID is the Salesforce record Id.
187
+ */
188
+ declare function mapAccount(account: SfAccount): {
189
+ rowId: string;
190
+ row: Record<string, unknown>;
191
+ };
192
+ /**
193
+ * Map a Salesforce Contact to a flat row for the `sf_contacts` table.
194
+ *
195
+ * The row ID is the Salesforce record Id.
196
+ */
197
+ declare function mapContact(contact: SfContact): {
198
+ rowId: string;
199
+ row: Record<string, unknown>;
200
+ };
201
+ /**
202
+ * Map a Salesforce Opportunity to a flat row for the `sf_opportunities` table.
203
+ *
204
+ * The row ID is the Salesforce record Id.
205
+ */
206
+ declare function mapOpportunity(opportunity: SfOpportunity): {
207
+ rowId: string;
208
+ row: Record<string, unknown>;
209
+ };
210
+ /**
211
+ * Map a Salesforce Lead to a flat row for the `sf_leads` table.
212
+ *
213
+ * The row ID is the Salesforce record Id.
214
+ */
215
+ declare function mapLead(lead: SfLead): {
216
+ rowId: string;
217
+ row: Record<string, unknown>;
218
+ };
219
+
220
+ /**
221
+ * Polls Salesforce CRM for accounts, contacts, opportunities, and leads
222
+ * and pushes detected changes into a gateway via streaming accumulation.
223
+ *
224
+ * Uses {@link BaseSourcePoller.accumulateDelta} to push deltas in
225
+ * memory-bounded chunks instead of collecting all deltas in a single array.
226
+ */
227
+ declare class SalesforceSourcePoller extends BaseSourcePoller {
228
+ private readonly connectionConfig;
229
+ private readonly client;
230
+ /** Per-entity cursors: max LastModifiedDate from the last poll. */
231
+ private cursors;
232
+ constructor(connectionConfig: SalesforceConnectorConfig, ingestConfig: SalesforceIngestConfig | undefined, name: string, gateway: PushTarget, client?: SalesforceClient);
233
+ /** Execute a single poll cycle across all enabled entity types. */
234
+ poll(): Promise<void>;
235
+ private pollEntity;
236
+ private buildSoql;
237
+ }
238
+
239
+ export { SalesforceApiError, SalesforceAuthError, type SalesforceAuthResponse, SalesforceClient, type SalesforceConnectorConfig, type SalesforceIngestConfig, type SalesforceQueryResponse, SalesforceSourcePoller, type SfAccount, type SfContact, type SfLead, type SfOpportunity, mapAccount, mapContact, mapLead, mapOpportunity };
@@ -0,0 +1,23 @@
1
+ import {
2
+ SalesforceApiError,
3
+ SalesforceAuthError,
4
+ SalesforceClient,
5
+ SalesforceSourcePoller,
6
+ mapAccount,
7
+ mapContact,
8
+ mapLead,
9
+ mapOpportunity
10
+ } from "./chunk-G6RQSXTJ.js";
11
+ import "./chunk-ICNT7I3K.js";
12
+ import "./chunk-7D4SUZUM.js";
13
+ export {
14
+ SalesforceApiError,
15
+ SalesforceAuthError,
16
+ SalesforceClient,
17
+ SalesforceSourcePoller,
18
+ mapAccount,
19
+ mapContact,
20
+ mapLead,
21
+ mapOpportunity
22
+ };
23
+ //# sourceMappingURL=connector-salesforce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -4486,7 +4486,7 @@ var GatewayServer = class {
4486
4486
  }
4487
4487
  if (config.type === "jira" && config.jira) {
4488
4488
  try {
4489
- const { JiraSourcePoller } = await import("./src-QU2YLPZY.js");
4489
+ const { JiraSourcePoller } = await import("./src-BLID3SYL.js");
4490
4490
  const ingestConfig = config.ingest ? { intervalMs: config.ingest.intervalMs } : void 0;
4491
4491
  const poller = new JiraSourcePoller(config.jira, ingestConfig, config.name, this.gateway);
4492
4492
  poller.start();
@@ -4502,7 +4502,7 @@ var GatewayServer = class {
4502
4502
  }
4503
4503
  if (config.type === "salesforce" && config.salesforce) {
4504
4504
  try {
4505
- const { SalesforceSourcePoller } = await import("./src-WZNPHANQ.js");
4505
+ const { SalesforceSourcePoller } = await import("./src-LSVSNMO2.js");
4506
4506
  const ingestConfig = config.ingest ? { intervalMs: config.ingest.intervalMs } : void 0;
4507
4507
  const poller = new SalesforceSourcePoller(
4508
4508
  config.salesforce,
@@ -0,0 +1,21 @@
1
+ import {
2
+ JiraApiError,
3
+ JiraClient,
4
+ JiraRateLimitError,
5
+ JiraSourcePoller,
6
+ mapComment,
7
+ mapIssue,
8
+ mapProject
9
+ } from "./chunk-DIQZDK4C.js";
10
+ import "./chunk-ICNT7I3K.js";
11
+ import "./chunk-7D4SUZUM.js";
12
+ export {
13
+ JiraApiError,
14
+ JiraClient,
15
+ JiraRateLimitError,
16
+ JiraSourcePoller,
17
+ mapComment,
18
+ mapIssue,
19
+ mapProject
20
+ };
21
+ //# sourceMappingURL=src-BLID3SYL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,23 @@
1
+ import {
2
+ SalesforceApiError,
3
+ SalesforceAuthError,
4
+ SalesforceClient,
5
+ SalesforceSourcePoller,
6
+ mapAccount,
7
+ mapContact,
8
+ mapLead,
9
+ mapOpportunity
10
+ } from "./chunk-G6RQSXTJ.js";
11
+ import "./chunk-ICNT7I3K.js";
12
+ import "./chunk-7D4SUZUM.js";
13
+ export {
14
+ SalesforceApiError,
15
+ SalesforceAuthError,
16
+ SalesforceClient,
17
+ SalesforceSourcePoller,
18
+ mapAccount,
19
+ mapContact,
20
+ mapLead,
21
+ mapOpportunity
22
+ };
23
+ //# sourceMappingURL=src-LSVSNMO2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lakesync",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "Local-first sync engine with pluggable backends — Postgres, MySQL, BigQuery, S3/R2 via Apache Iceberg",
6
6
  "license": "Apache-2.0",
@@ -53,6 +53,14 @@
53
53
  "./react": {
54
54
  "types": "./dist/react.d.ts",
55
55
  "import": "./dist/react.js"
56
+ },
57
+ "./connector-jira": {
58
+ "types": "./dist/connector-jira.d.ts",
59
+ "import": "./dist/connector-jira.js"
60
+ },
61
+ "./connector-salesforce": {
62
+ "types": "./dist/connector-salesforce.d.ts",
63
+ "import": "./dist/connector-salesforce.js"
56
64
  }
57
65
  },
58
66
  "scripts": {