lakesync 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.d.ts +87 -9
- package/dist/adapter.js +8 -2
- package/dist/analyst.js +1 -1
- package/dist/{base-poller-Dfaj05py.d.ts → base-poller-CBvhdvcj.d.ts} +1 -1
- package/dist/catalogue.d.ts +1 -1
- package/dist/catalogue.js +2 -2
- package/dist/{chunk-265CMYJH.js → chunk-46CKACNC.js} +2 -2
- package/dist/{chunk-C7ECMNQ4.js → chunk-7UBS6MFH.js} +295 -1
- package/dist/chunk-7UBS6MFH.js.map +1 -0
- package/dist/{chunk-TIPMVLIG.js → chunk-B3QEUG6E.js} +2 -2
- package/dist/{chunk-E7ZDOJCP.js → chunk-L4ZL5JA7.js} +2 -2
- package/dist/{chunk-HAR3YPCY.js → chunk-PWGQ3PXE.js} +32 -9
- package/dist/chunk-PWGQ3PXE.js.map +1 -0
- package/dist/{chunk-SXQB6JT6.js → chunk-SZSGSTVZ.js} +4 -2
- package/dist/{chunk-SXQB6JT6.js.map → chunk-SZSGSTVZ.js.map} +1 -1
- package/dist/{chunk-L6LTCXJ4.js → chunk-TVLTXHW6.js} +4 -2
- package/dist/{chunk-L6LTCXJ4.js.map → chunk-TVLTXHW6.js.map} +1 -1
- package/dist/{chunk-NCMXLWEW.js → chunk-Z7FGLEQU.js} +337 -20
- package/dist/chunk-Z7FGLEQU.js.map +1 -0
- package/dist/client.d.ts +12 -4
- package/dist/client.js +48 -3
- package/dist/client.js.map +1 -1
- package/dist/compactor.d.ts +1 -1
- package/dist/compactor.js +3 -3
- package/dist/connector-jira.d.ts +2 -2
- package/dist/connector-jira.js +2 -2
- package/dist/connector-salesforce.d.ts +2 -2
- package/dist/connector-salesforce.js +2 -2
- package/dist/{coordinator-CSbsqp5C.d.ts → coordinator-DN8D8C7W.d.ts} +11 -1
- package/dist/{db-types-CPAPw8Ws.d.ts → db-types-B6_JKQWK.d.ts} +1 -1
- package/dist/{gateway-Cej8JUh9.d.ts → gateway-CvO7Xy3T.d.ts} +5 -3
- package/dist/gateway-server.d.ts +4 -4
- package/dist/gateway-server.js +14 -8
- package/dist/gateway-server.js.map +1 -1
- package/dist/gateway.d.ts +12 -7
- package/dist/gateway.js +7 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +9 -1
- package/dist/parquet.d.ts +1 -1
- package/dist/parquet.js +2 -2
- package/dist/proto.d.ts +1 -1
- package/dist/proto.js +2 -2
- package/dist/react.d.ts +32 -3
- package/dist/react.js +54 -19
- package/dist/react.js.map +1 -1
- package/dist/registry-BN_9spxE.d.ts +53 -0
- package/dist/{resolver-B10tk8Er.d.ts → resolver-BZURzdlL.d.ts} +1 -1
- package/dist/{src-PPKRY5GD.js → src-RR7I76OL.js} +3 -3
- package/dist/{src-TLTET7JZ.js → src-SLVE5567.js} +10 -2
- package/dist/{src-VVCNNYND.js → src-V2CTPR7V.js} +3 -3
- package/dist/{types-BUzzVRD6.d.ts → types-GGBfZBKQ.d.ts} +3 -0
- package/package.json +1 -1
- package/dist/chunk-C7ECMNQ4.js.map +0 -1
- package/dist/chunk-HAR3YPCY.js.map +0 -1
- package/dist/chunk-NCMXLWEW.js.map +0 -1
- /package/dist/{chunk-265CMYJH.js.map → chunk-46CKACNC.js.map} +0 -0
- /package/dist/{chunk-TIPMVLIG.js.map → chunk-B3QEUG6E.js.map} +0 -0
- /package/dist/{chunk-E7ZDOJCP.js.map → chunk-L4ZL5JA7.js.map} +0 -0
- /package/dist/{src-PPKRY5GD.js.map → src-RR7I76OL.js.map} +0 -0
- /package/dist/{src-TLTET7JZ.js.map → src-SLVE5567.js.map} +0 -0
- /package/dist/{src-VVCNNYND.js.map → src-V2CTPR7V.js.map} +0 -0
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
LakeSyncError,
|
|
5
5
|
Ok,
|
|
6
6
|
extractDelta,
|
|
7
|
+
registerOutputSchemas,
|
|
7
8
|
registerPollerFactory
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7UBS6MFH.js";
|
|
9
10
|
|
|
10
11
|
// ../connector-jira/src/errors.ts
|
|
11
12
|
var JiraApiError = class extends LakeSyncError {
|
|
@@ -457,6 +458,7 @@ async function testConnection(config) {
|
|
|
457
458
|
}
|
|
458
459
|
|
|
459
460
|
// ../connector-jira/src/index.ts
|
|
461
|
+
registerOutputSchemas("jira", JIRA_TABLE_SCHEMAS);
|
|
460
462
|
registerPollerFactory("jira", (config, gateway) => {
|
|
461
463
|
const ingest = config.ingest ? {
|
|
462
464
|
intervalMs: config.ingest.intervalMs,
|
|
@@ -477,4 +479,4 @@ export {
|
|
|
477
479
|
JIRA_TABLE_SCHEMAS,
|
|
478
480
|
testConnection
|
|
479
481
|
};
|
|
480
|
-
//# sourceMappingURL=chunk-
|
|
482
|
+
//# sourceMappingURL=chunk-SZSGSTVZ.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","../../connector-jira/src/schemas.ts","../../connector-jira/src/test-connection.ts","../../connector-jira/src/index.ts"],"sourcesContent":["import { LakeSyncError } from \"@lakesync/core\";\n\n/** HTTP error from the Jira REST API. */\nexport class JiraApiError extends LakeSyncError {\n\t/** HTTP status code returned by Jira. */\n\treadonly statusCode: number;\n\t/** Raw response body from Jira. */\n\treadonly responseBody: string;\n\n\tconstructor(statusCode: number, responseBody: string, cause?: Error) {\n\t\tsuper(`Jira API error (${statusCode}): ${responseBody}`, \"JIRA_API_ERROR\", cause);\n\t\tthis.statusCode = statusCode;\n\t\tthis.responseBody = responseBody;\n\t}\n}\n\n/** Rate limit error (HTTP 429) from the Jira REST API. */\nexport class JiraRateLimitError extends LakeSyncError {\n\t/** Milliseconds to wait before retrying. */\n\treadonly retryAfterMs: number;\n\n\tconstructor(retryAfterMs: number, cause?: Error) {\n\t\tsuper(`Jira rate limited — retry after ${retryAfterMs}ms`, \"JIRA_RATE_LIMITED\", cause);\n\t\tthis.retryAfterMs = retryAfterMs;\n\t}\n}\n","// ---------------------------------------------------------------------------\n// JiraClient — HTTP wrapper for Jira Cloud REST API v3\n// ---------------------------------------------------------------------------\n\nimport { Err, Ok, type Result } from \"@lakesync/core\";\nimport { JiraApiError, JiraRateLimitError } from \"./errors\";\nimport type {\n\tJiraComment,\n\tJiraCommentPage,\n\tJiraConnectorConfig,\n\tJiraIssue,\n\tJiraProject,\n\tJiraProjectPage,\n\tJiraSearchResponse,\n} from \"./types\";\n\nconst MAX_RESULTS = 100;\nconst MAX_RATE_LIMIT_RETRIES = 3;\nconst DEFAULT_RETRY_AFTER_MS = 10_000;\n\n/** Fields requested for issue search. */\nconst ISSUE_FIELDS = [\n\t\"summary\",\n\t\"description\",\n\t\"status\",\n\t\"priority\",\n\t\"assignee\",\n\t\"reporter\",\n\t\"labels\",\n\t\"created\",\n\t\"updated\",\n\t\"project\",\n\t\"issuetype\",\n];\n\n/**\n * HTTP client for the Jira Cloud REST API v3.\n *\n * Uses Basic authentication (email + API token) and global `fetch`.\n * All public methods return `Result<T, JiraApiError>`.\n */\nexport class JiraClient {\n\tprivate readonly baseUrl: string;\n\tprivate readonly authHeader: string;\n\n\tconstructor(config: JiraConnectorConfig) {\n\t\tthis.baseUrl = `https://${config.domain}.atlassian.net`;\n\t\tthis.authHeader = `Basic ${btoa(`${config.email}:${config.apiToken}`)}`;\n\t}\n\n\t/**\n\t * Search for issues via JQL with auto-pagination.\n\t *\n\t * Uses the `/rest/api/3/search/jql` endpoint with token-based pagination.\n\t * When `updatedSince` is provided, appends `AND updated >= \"date\"` to the JQL.\n\t * Empty JQL is replaced with `project is not EMPTY` (the new endpoint\n\t * rejects unbounded queries).\n\t *\n\t * @param limit — optional cap on the total number of issues returned.\n\t */\n\tasync searchIssues(\n\t\tjql: string,\n\t\tupdatedSince?: string,\n\t\tlimit?: number,\n\t): Promise<Result<JiraIssue[], JiraApiError | JiraRateLimitError>> {\n\t\tlet effectiveJql = jql || \"\";\n\n\t\tif (updatedSince) {\n\t\t\tconst clause = `updated >= \"${updatedSince}\"`;\n\t\t\teffectiveJql = effectiveJql.length > 0 ? `(${effectiveJql}) AND ${clause}` : clause;\n\t\t}\n\n\t\t// The /search/jql endpoint rejects unbounded queries\n\t\tif (effectiveJql.length === 0) {\n\t\t\teffectiveJql = \"project is not EMPTY\";\n\t\t}\n\n\t\tconst allIssues: JiraIssue[] = [];\n\t\tlet nextPageToken: string | undefined;\n\t\tconst pageSize = limit !== undefined ? Math.min(limit, MAX_RESULTS) : MAX_RESULTS;\n\n\t\twhile (true) {\n\t\t\tconst body: Record<string, unknown> = {\n\t\t\t\tjql: effectiveJql,\n\t\t\t\tmaxResults: pageSize,\n\t\t\t\tfields: ISSUE_FIELDS,\n\t\t\t};\n\t\t\tif (nextPageToken) {\n\t\t\t\tbody.nextPageToken = nextPageToken;\n\t\t\t}\n\n\t\t\tconst result = await this.request<JiraSearchResponse>(\"/rest/api/3/search/jql\", \"POST\", body);\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tconst page = result.value;\n\t\t\tfor (const issue of page.issues) {\n\t\t\t\tallIssues.push(issue);\n\t\t\t}\n\n\t\t\tif (limit !== undefined && allIssues.length >= limit) break;\n\t\t\tif (page.isLast || !page.nextPageToken) break;\n\t\t\tnextPageToken = page.nextPageToken;\n\t\t}\n\n\t\treturn Ok(limit !== undefined ? allIssues.slice(0, limit) : allIssues);\n\t}\n\n\t/**\n\t * Fetch all comments for a given issue key with auto-pagination.\n\t */\n\tasync getComments(\n\t\tissueKey: string,\n\t): Promise<Result<JiraComment[], JiraApiError | JiraRateLimitError>> {\n\t\tconst allComments: JiraComment[] = [];\n\t\tlet startAt = 0;\n\n\t\twhile (true) {\n\t\t\tconst result = await this.request<JiraCommentPage>(\n\t\t\t\t`/rest/api/3/issue/${encodeURIComponent(issueKey)}/comment?startAt=${startAt}&maxResults=${MAX_RESULTS}`,\n\t\t\t\t\"GET\",\n\t\t\t);\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tconst page = result.value;\n\t\t\tfor (const comment of page.comments) {\n\t\t\t\tallComments.push(comment);\n\t\t\t}\n\n\t\t\tstartAt += page.maxResults;\n\t\t\tif (startAt >= page.total) break;\n\t\t}\n\n\t\treturn Ok(allComments);\n\t}\n\n\t/**\n\t * Fetch all projects with auto-pagination.\n\t */\n\tasync getProjects(): Promise<Result<JiraProject[], JiraApiError | JiraRateLimitError>> {\n\t\tconst allProjects: JiraProject[] = [];\n\t\tlet startAt = 0;\n\n\t\twhile (true) {\n\t\t\tconst result = await this.request<JiraProjectPage>(\n\t\t\t\t`/rest/api/3/project/search?startAt=${startAt}&maxResults=${MAX_RESULTS}`,\n\t\t\t\t\"GET\",\n\t\t\t);\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tconst page = result.value;\n\t\t\tfor (const project of page.values) {\n\t\t\t\tallProjects.push(project);\n\t\t\t}\n\n\t\t\tstartAt += page.maxResults;\n\t\t\tif (startAt >= page.total) break;\n\t\t}\n\n\t\treturn Ok(allProjects);\n\t}\n\n\t/**\n\t * Fetch the currently authenticated user.\n\t *\n\t * Calls `GET /rest/api/3/myself` — the cheapest auth-validating endpoint.\n\t */\n\tasync getCurrentUser(): Promise<\n\t\tResult<{ displayName: string; emailAddress: string }, JiraApiError | JiraRateLimitError>\n\t> {\n\t\treturn this.request(\"/rest/api/3/myself\", \"GET\");\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal HTTP helpers\n\t// -----------------------------------------------------------------------\n\n\t/** Make an HTTP request with rate-limit retry logic. */\n\tprivate async request<T>(\n\t\tpath: string,\n\t\tmethod: \"GET\" | \"POST\",\n\t\tbody?: unknown,\n\t): Promise<Result<T, JiraApiError | JiraRateLimitError>> {\n\t\tlet lastError: JiraApiError | undefined;\n\n\t\tfor (let attempt = 0; attempt <= MAX_RATE_LIMIT_RETRIES; attempt++) {\n\t\t\tconst headers: Record<string, string> = {\n\t\t\t\tAuthorization: this.authHeader,\n\t\t\t\tAccept: \"application/json\",\n\t\t\t};\n\n\t\t\tconst init: RequestInit = { method, headers };\n\n\t\t\tif (body !== undefined) {\n\t\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t\t\tinit.body = JSON.stringify(body);\n\t\t\t}\n\n\t\t\tconst response = await fetch(`${this.baseUrl}${path}`, init);\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst data = (await response.json()) as T;\n\t\t\t\treturn Ok(data);\n\t\t\t}\n\n\t\t\tif (response.status === 429) {\n\t\t\t\tconst retryAfter = response.headers.get(\"Retry-After\");\n\t\t\t\tconst waitMs = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : DEFAULT_RETRY_AFTER_MS;\n\n\t\t\t\tif (attempt < MAX_RATE_LIMIT_RETRIES) {\n\t\t\t\t\tawait sleep(waitMs);\n\t\t\t\t\tlastError = new JiraApiError(429, `Rate limited, retried after ${waitMs}ms`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn Err(new JiraRateLimitError(waitMs));\n\t\t\t}\n\n\t\t\tconst responseBody = await response.text();\n\t\t\treturn Err(new JiraApiError(response.status, responseBody));\n\t\t}\n\n\t\treturn Err(lastError ?? new JiraApiError(0, \"Unknown error after retries\"));\n\t}\n}\n\n/** Sleep for the given number of milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n","// ---------------------------------------------------------------------------\n// Jira Entity → Flat LakeSync Row Mapping\n// ---------------------------------------------------------------------------\n\nimport type { JiraComment, JiraIssue, JiraProject } from \"./types\";\n\n/**\n * Map a Jira issue to a flat row for the `jira_issues` table.\n *\n * The row ID is the issue key (e.g. \"PROJ-123\").\n */\nexport function mapIssue(issue: JiraIssue): { rowId: string; row: Record<string, unknown> } {\n\tconst f = issue.fields;\n\treturn {\n\t\trowId: issue.key,\n\t\trow: {\n\t\t\tjira_id: issue.id,\n\t\t\tkey: issue.key,\n\t\t\tsummary: f.summary ?? null,\n\t\t\tdescription: f.description != null ? JSON.stringify(f.description) : null,\n\t\t\tstatus: f.status?.name ?? null,\n\t\t\tpriority: f.priority?.name ?? null,\n\t\t\tissue_type: f.issuetype?.name ?? null,\n\t\t\tassignee_name: f.assignee?.displayName ?? null,\n\t\t\tassignee_email: f.assignee?.emailAddress ?? null,\n\t\t\treporter_name: f.reporter?.displayName ?? null,\n\t\t\treporter_email: f.reporter?.emailAddress ?? null,\n\t\t\tlabels: f.labels != null ? JSON.stringify(f.labels) : null,\n\t\t\tproject_key: f.project?.key ?? null,\n\t\t\tproject_name: f.project?.name ?? null,\n\t\t\tcreated: f.created ?? null,\n\t\t\tupdated: f.updated ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Jira comment to a flat row for the `jira_comments` table.\n *\n * The row ID is \"{issueKey}:{commentId}\".\n */\nexport function mapComment(\n\tissueKey: string,\n\tcomment: JiraComment,\n): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: `${issueKey}:${comment.id}`,\n\t\trow: {\n\t\t\tjira_id: comment.id,\n\t\t\tissue_key: issueKey,\n\t\t\tbody: comment.body != null ? JSON.stringify(comment.body) : null,\n\t\t\tauthor_name: comment.author?.displayName ?? null,\n\t\t\tauthor_email: comment.author?.emailAddress ?? null,\n\t\t\tcreated: comment.created ?? null,\n\t\t\tupdated: comment.updated ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Jira project to a flat row for the `jira_projects` table.\n *\n * The row ID is the project key (e.g. \"PROJ\").\n */\nexport function mapProject(project: JiraProject): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: project.key,\n\t\trow: {\n\t\t\tjira_id: project.id,\n\t\t\tkey: project.key,\n\t\t\tname: project.name,\n\t\t\tproject_type: project.projectTypeKey ?? null,\n\t\t\tlead_name: project.lead?.displayName ?? null,\n\t\t},\n\t};\n}\n","// ---------------------------------------------------------------------------\n// JiraSourcePoller — polls Jira Cloud and pushes deltas to SyncGateway\n// ---------------------------------------------------------------------------\n\nimport { BaseSourcePoller, extractDelta, type PushTarget } from \"@lakesync/core\";\nimport { JiraClient } from \"./client\";\nimport { mapComment, mapIssue, mapProject } from \"./mapping\";\nimport type { JiraConnectorConfig, JiraIngestConfig } from \"./types\";\n\nconst DEFAULT_INTERVAL_MS = 30_000;\n\n/**\n * Polls Jira Cloud for issues, comments, and projects and pushes\n * detected changes into a gateway via streaming accumulation.\n *\n * Uses {@link BaseSourcePoller.accumulateDelta} to push deltas in\n * memory-bounded chunks instead of collecting all deltas in a single array.\n */\nexport class JiraSourcePoller extends BaseSourcePoller {\n\tprivate readonly connectionConfig: JiraConnectorConfig;\n\tprivate readonly client: JiraClient;\n\n\t/** Cursor: max `fields.updated` value from the last issue poll. */\n\tprivate lastUpdated: string | undefined;\n\n\t/** In-memory snapshot for comment diff (keyed by rowId). */\n\tprivate commentSnapshot = new Map<string, Record<string, unknown>>();\n\n\t/** In-memory snapshot for project diff (keyed by project key). */\n\tprivate projectSnapshot = new Map<string, Record<string, unknown>>();\n\n\t/** Export cursor state as a JSON-serialisable object for external persistence. */\n\toverride getCursorState(): Record<string, unknown> {\n\t\treturn {\n\t\t\tlastUpdated: this.lastUpdated,\n\t\t\tcommentSnapshot: Array.from(this.commentSnapshot.entries()),\n\t\t\tprojectSnapshot: Array.from(this.projectSnapshot.entries()),\n\t\t};\n\t}\n\n\t/** Restore cursor state from a previously exported snapshot. */\n\toverride setCursorState(state: Record<string, unknown>): void {\n\t\tthis.lastUpdated = state.lastUpdated as string | undefined;\n\t\tthis.commentSnapshot = new Map(\n\t\t\tstate.commentSnapshot as Array<[string, Record<string, unknown>]>,\n\t\t);\n\t\tthis.projectSnapshot = new Map(\n\t\t\tstate.projectSnapshot as Array<[string, Record<string, unknown>]>,\n\t\t);\n\t}\n\n\tconstructor(\n\t\tconnectionConfig: JiraConnectorConfig,\n\t\tingestConfig: JiraIngestConfig | undefined,\n\t\tname: string,\n\t\tgateway: PushTarget,\n\t\tclient?: JiraClient,\n\t) {\n\t\tsuper({\n\t\t\tname,\n\t\t\tintervalMs: ingestConfig?.intervalMs ?? DEFAULT_INTERVAL_MS,\n\t\t\tgateway,\n\t\t\tmemory: {\n\t\t\t\tchunkSize: ingestConfig?.chunkSize,\n\t\t\t\tmemoryBudgetBytes: ingestConfig?.memoryBudgetBytes,\n\t\t\t},\n\t\t});\n\t\tthis.connectionConfig = connectionConfig;\n\t\tthis.client = client ?? new JiraClient(connectionConfig);\n\t}\n\n\t/** Execute a single poll cycle across all entity types. */\n\tasync poll(): Promise<void> {\n\t\t// 1. Issues (cursor strategy via `updated` field)\n\t\tconst issueKeys = await this.pollIssues();\n\n\t\t// 2. Comments (diff per-issue, only for issues returned in this poll)\n\t\tconst includeComments = this.connectionConfig.includeComments ?? true;\n\t\tif (includeComments && issueKeys.length > 0) {\n\t\t\tawait this.pollComments(issueKeys);\n\t\t}\n\n\t\t// 3. Projects (full diff)\n\t\tconst includeProjects = this.connectionConfig.includeProjects ?? true;\n\t\tif (includeProjects) {\n\t\t\tawait this.pollProjects();\n\t\t}\n\n\t\tawait this.flushAccumulator();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Issues — cursor strategy via JQL `updated` field\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollIssues(): Promise<string[]> {\n\t\tconst result = await this.client.searchIssues(\n\t\t\tthis.connectionConfig.jql ?? \"\",\n\t\t\tthis.lastUpdated,\n\t\t);\n\n\t\tif (!result.ok) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst issues = result.value;\n\t\tif (issues.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst issueKeys: string[] = [];\n\t\tlet maxUpdated = this.lastUpdated;\n\n\t\tfor (const issue of issues) {\n\t\t\tconst { rowId, row } = mapIssue(issue);\n\t\t\tissueKeys.push(issue.key);\n\n\t\t\tconst delta = await extractDelta(null, row, {\n\t\t\t\ttable: \"jira_issues\",\n\t\t\t\trowId,\n\t\t\t\tclientId: this.clientId,\n\t\t\t\thlc: this.hlc.now(),\n\t\t\t});\n\n\t\t\tif (delta) {\n\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t}\n\n\t\t\t// Track max updated timestamp for cursor advancement\n\t\t\tconst updated = issue.fields.updated;\n\t\t\tif (updated && (!maxUpdated || updated > maxUpdated)) {\n\t\t\t\tmaxUpdated = updated;\n\t\t\t}\n\t\t}\n\n\t\tthis.lastUpdated = maxUpdated;\n\t\treturn issueKeys;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Comments — diff per-issue\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollComments(issueKeys: string[]): Promise<void> {\n\t\tfor (const issueKey of issueKeys) {\n\t\t\tconst result = await this.client.getComments(issueKey);\n\t\t\tif (!result.ok) continue;\n\n\t\t\tconst currentMap = new Map<string, Record<string, unknown>>();\n\n\t\t\tfor (const comment of result.value) {\n\t\t\t\tconst { rowId, row } = mapComment(issueKey, comment);\n\t\t\t\tcurrentMap.set(rowId, row);\n\n\t\t\t\tconst previous = this.commentSnapshot.get(rowId) ?? null;\n\t\t\t\tconst delta = await extractDelta(previous, row, {\n\t\t\t\t\ttable: \"jira_comments\",\n\t\t\t\t\trowId,\n\t\t\t\t\tclientId: this.clientId,\n\t\t\t\t\thlc: this.hlc.now(),\n\t\t\t\t});\n\n\t\t\t\tif (delta) {\n\t\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update snapshot for this issue's comments\n\t\t\tfor (const [rowId, row] of currentMap) {\n\t\t\t\tthis.commentSnapshot.set(rowId, row);\n\t\t\t}\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Projects — full diff\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollProjects(): Promise<void> {\n\t\tconst result = await this.client.getProjects();\n\t\tif (!result.ok) return;\n\n\t\tconst currentMap = new Map<string, Record<string, unknown>>();\n\n\t\tfor (const project of result.value) {\n\t\t\tconst { rowId, row } = mapProject(project);\n\t\t\tcurrentMap.set(rowId, row);\n\t\t}\n\n\t\tconst previousMap = this.projectSnapshot;\n\n\t\t// Detect inserts and updates\n\t\tfor (const [rowId, currentRow] of currentMap) {\n\t\t\tconst previousRow = previousMap.get(rowId) ?? null;\n\n\t\t\tconst delta = await extractDelta(previousRow, currentRow, {\n\t\t\t\ttable: \"jira_projects\",\n\t\t\t\trowId,\n\t\t\t\tclientId: this.clientId,\n\t\t\t\thlc: this.hlc.now(),\n\t\t\t});\n\n\t\t\tif (delta) {\n\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t}\n\t\t}\n\n\t\t// Detect deletes: rows in previous snapshot missing from current\n\t\tfor (const [rowId, previousRow] of previousMap) {\n\t\t\tif (!currentMap.has(rowId)) {\n\t\t\t\tconst delta = await extractDelta(previousRow, null, {\n\t\t\t\t\ttable: \"jira_projects\",\n\t\t\t\t\trowId,\n\t\t\t\t\tclientId: this.clientId,\n\t\t\t\t\thlc: this.hlc.now(),\n\t\t\t\t});\n\n\t\t\t\tif (delta) {\n\t\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Replace snapshot\n\t\tthis.projectSnapshot = currentMap;\n\t}\n}\n","// ---------------------------------------------------------------------------\n// Jira Table Schemas — declares the shape of each table produced by the poller\n// ---------------------------------------------------------------------------\n\nimport type { TableSchema } from \"@lakesync/core\";\n\n/** Schema for the `jira_issues` table (columns from {@link mapIssue}). */\nconst JIRA_ISSUES_SCHEMA: TableSchema = {\n\ttable: \"jira_issues\",\n\tcolumns: [\n\t\t{ name: \"jira_id\", type: \"string\" },\n\t\t{ name: \"key\", type: \"string\" },\n\t\t{ name: \"summary\", type: \"string\" },\n\t\t{ name: \"description\", type: \"string\" },\n\t\t{ name: \"status\", type: \"string\" },\n\t\t{ name: \"priority\", type: \"string\" },\n\t\t{ name: \"issue_type\", type: \"string\" },\n\t\t{ name: \"assignee_name\", type: \"string\" },\n\t\t{ name: \"assignee_email\", type: \"string\" },\n\t\t{ name: \"reporter_name\", type: \"string\" },\n\t\t{ name: \"reporter_email\", type: \"string\" },\n\t\t{ name: \"labels\", type: \"string\" },\n\t\t{ name: \"project_key\", type: \"string\" },\n\t\t{ name: \"project_name\", type: \"string\" },\n\t\t{ name: \"created\", type: \"string\" },\n\t\t{ name: \"updated\", type: \"string\" },\n\t],\n};\n\n/** Schema for the `jira_comments` table (columns from {@link mapComment}). */\nconst JIRA_COMMENTS_SCHEMA: TableSchema = {\n\ttable: \"jira_comments\",\n\tcolumns: [\n\t\t{ name: \"jira_id\", type: \"string\" },\n\t\t{ name: \"issue_key\", type: \"string\" },\n\t\t{ name: \"body\", type: \"string\" },\n\t\t{ name: \"author_name\", type: \"string\" },\n\t\t{ name: \"author_email\", type: \"string\" },\n\t\t{ name: \"created\", type: \"string\" },\n\t\t{ name: \"updated\", type: \"string\" },\n\t],\n};\n\n/** Schema for the `jira_projects` table (columns from {@link mapProject}). */\nconst JIRA_PROJECTS_SCHEMA: TableSchema = {\n\ttable: \"jira_projects\",\n\tcolumns: [\n\t\t{ name: \"jira_id\", type: \"string\" },\n\t\t{ name: \"key\", type: \"string\" },\n\t\t{ name: \"name\", type: \"string\" },\n\t\t{ name: \"project_type\", type: \"string\" },\n\t\t{ name: \"lead_name\", type: \"string\" },\n\t],\n};\n\n/** All table schemas produced by the Jira connector. */\nexport const JIRA_TABLE_SCHEMAS: ReadonlyArray<TableSchema> = [\n\tJIRA_ISSUES_SCHEMA,\n\tJIRA_COMMENTS_SCHEMA,\n\tJIRA_PROJECTS_SCHEMA,\n];\n","import { Ok, type Result } from \"@lakesync/core\";\nimport { JiraClient } from \"./client\";\nimport type { JiraApiError, JiraRateLimitError } from \"./errors\";\nimport type { JiraConnectorConfig } from \"./types\";\n\n/**\n * Test a Jira Cloud connection by authenticating and fetching the current user.\n *\n * Creates a `JiraClient` internally and calls `GET /rest/api/3/myself` —\n * the cheapest endpoint that validates credentials.\n */\nexport async function testConnection(\n\tconfig: JiraConnectorConfig,\n): Promise<Result<void, JiraApiError | JiraRateLimitError>> {\n\tconst client = new JiraClient(config);\n\tconst result = await client.getCurrentUser();\n\tif (!result.ok) return result;\n\treturn Ok(undefined);\n}\n","import { registerPollerFactory } from \"@lakesync/core\";\nimport { JiraSourcePoller } from \"./poller\";\nimport type { JiraIngestConfig } from \"./types\";\n\nexport { JiraClient } from \"./client\";\nexport { JiraApiError, JiraRateLimitError } from \"./errors\";\nexport { mapComment, mapIssue, mapProject } from \"./mapping\";\nexport { JiraSourcePoller } from \"./poller\";\nexport { JIRA_TABLE_SCHEMAS } from \"./schemas\";\nexport { testConnection } from \"./test-connection\";\nexport type {\n\tJiraComment,\n\tJiraCommentPage,\n\tJiraConnectorConfig,\n\tJiraIngestConfig,\n\tJiraIssue,\n\tJiraProject,\n\tJiraProjectPage,\n\tJiraSearchResponse,\n} from \"./types\";\n\n// Auto-register poller factory so createPoller(\"jira\", ...) works.\nregisterPollerFactory(\"jira\", (config, gateway) => {\n\tconst ingest: JiraIngestConfig | undefined = config.ingest\n\t\t? {\n\t\t\t\tintervalMs: config.ingest.intervalMs,\n\t\t\t\tchunkSize: config.ingest.chunkSize,\n\t\t\t\tmemoryBudgetBytes: config.ingest.memoryBudgetBytes,\n\t\t\t}\n\t\t: undefined;\n\treturn new JiraSourcePoller(config.jira!, ingest, config.name, gateway);\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../connector-jira/src/errors.ts","../../connector-jira/src/client.ts","../../connector-jira/src/mapping.ts","../../connector-jira/src/poller.ts","../../connector-jira/src/schemas.ts","../../connector-jira/src/test-connection.ts","../../connector-jira/src/index.ts"],"sourcesContent":["import { LakeSyncError } from \"@lakesync/core\";\n\n/** HTTP error from the Jira REST API. */\nexport class JiraApiError extends LakeSyncError {\n\t/** HTTP status code returned by Jira. */\n\treadonly statusCode: number;\n\t/** Raw response body from Jira. */\n\treadonly responseBody: string;\n\n\tconstructor(statusCode: number, responseBody: string, cause?: Error) {\n\t\tsuper(`Jira API error (${statusCode}): ${responseBody}`, \"JIRA_API_ERROR\", cause);\n\t\tthis.statusCode = statusCode;\n\t\tthis.responseBody = responseBody;\n\t}\n}\n\n/** Rate limit error (HTTP 429) from the Jira REST API. */\nexport class JiraRateLimitError extends LakeSyncError {\n\t/** Milliseconds to wait before retrying. */\n\treadonly retryAfterMs: number;\n\n\tconstructor(retryAfterMs: number, cause?: Error) {\n\t\tsuper(`Jira rate limited — retry after ${retryAfterMs}ms`, \"JIRA_RATE_LIMITED\", cause);\n\t\tthis.retryAfterMs = retryAfterMs;\n\t}\n}\n","// ---------------------------------------------------------------------------\n// JiraClient — HTTP wrapper for Jira Cloud REST API v3\n// ---------------------------------------------------------------------------\n\nimport { Err, Ok, type Result } from \"@lakesync/core\";\nimport { JiraApiError, JiraRateLimitError } from \"./errors\";\nimport type {\n\tJiraComment,\n\tJiraCommentPage,\n\tJiraConnectorConfig,\n\tJiraIssue,\n\tJiraProject,\n\tJiraProjectPage,\n\tJiraSearchResponse,\n} from \"./types\";\n\nconst MAX_RESULTS = 100;\nconst MAX_RATE_LIMIT_RETRIES = 3;\nconst DEFAULT_RETRY_AFTER_MS = 10_000;\n\n/** Fields requested for issue search. */\nconst ISSUE_FIELDS = [\n\t\"summary\",\n\t\"description\",\n\t\"status\",\n\t\"priority\",\n\t\"assignee\",\n\t\"reporter\",\n\t\"labels\",\n\t\"created\",\n\t\"updated\",\n\t\"project\",\n\t\"issuetype\",\n];\n\n/**\n * HTTP client for the Jira Cloud REST API v3.\n *\n * Uses Basic authentication (email + API token) and global `fetch`.\n * All public methods return `Result<T, JiraApiError>`.\n */\nexport class JiraClient {\n\tprivate readonly baseUrl: string;\n\tprivate readonly authHeader: string;\n\n\tconstructor(config: JiraConnectorConfig) {\n\t\tthis.baseUrl = `https://${config.domain}.atlassian.net`;\n\t\tthis.authHeader = `Basic ${btoa(`${config.email}:${config.apiToken}`)}`;\n\t}\n\n\t/**\n\t * Search for issues via JQL with auto-pagination.\n\t *\n\t * Uses the `/rest/api/3/search/jql` endpoint with token-based pagination.\n\t * When `updatedSince` is provided, appends `AND updated >= \"date\"` to the JQL.\n\t * Empty JQL is replaced with `project is not EMPTY` (the new endpoint\n\t * rejects unbounded queries).\n\t *\n\t * @param limit — optional cap on the total number of issues returned.\n\t */\n\tasync searchIssues(\n\t\tjql: string,\n\t\tupdatedSince?: string,\n\t\tlimit?: number,\n\t): Promise<Result<JiraIssue[], JiraApiError | JiraRateLimitError>> {\n\t\tlet effectiveJql = jql || \"\";\n\n\t\tif (updatedSince) {\n\t\t\tconst clause = `updated >= \"${updatedSince}\"`;\n\t\t\teffectiveJql = effectiveJql.length > 0 ? `(${effectiveJql}) AND ${clause}` : clause;\n\t\t}\n\n\t\t// The /search/jql endpoint rejects unbounded queries\n\t\tif (effectiveJql.length === 0) {\n\t\t\teffectiveJql = \"project is not EMPTY\";\n\t\t}\n\n\t\tconst allIssues: JiraIssue[] = [];\n\t\tlet nextPageToken: string | undefined;\n\t\tconst pageSize = limit !== undefined ? Math.min(limit, MAX_RESULTS) : MAX_RESULTS;\n\n\t\twhile (true) {\n\t\t\tconst body: Record<string, unknown> = {\n\t\t\t\tjql: effectiveJql,\n\t\t\t\tmaxResults: pageSize,\n\t\t\t\tfields: ISSUE_FIELDS,\n\t\t\t};\n\t\t\tif (nextPageToken) {\n\t\t\t\tbody.nextPageToken = nextPageToken;\n\t\t\t}\n\n\t\t\tconst result = await this.request<JiraSearchResponse>(\"/rest/api/3/search/jql\", \"POST\", body);\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tconst page = result.value;\n\t\t\tfor (const issue of page.issues) {\n\t\t\t\tallIssues.push(issue);\n\t\t\t}\n\n\t\t\tif (limit !== undefined && allIssues.length >= limit) break;\n\t\t\tif (page.isLast || !page.nextPageToken) break;\n\t\t\tnextPageToken = page.nextPageToken;\n\t\t}\n\n\t\treturn Ok(limit !== undefined ? allIssues.slice(0, limit) : allIssues);\n\t}\n\n\t/**\n\t * Fetch all comments for a given issue key with auto-pagination.\n\t */\n\tasync getComments(\n\t\tissueKey: string,\n\t): Promise<Result<JiraComment[], JiraApiError | JiraRateLimitError>> {\n\t\tconst allComments: JiraComment[] = [];\n\t\tlet startAt = 0;\n\n\t\twhile (true) {\n\t\t\tconst result = await this.request<JiraCommentPage>(\n\t\t\t\t`/rest/api/3/issue/${encodeURIComponent(issueKey)}/comment?startAt=${startAt}&maxResults=${MAX_RESULTS}`,\n\t\t\t\t\"GET\",\n\t\t\t);\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tconst page = result.value;\n\t\t\tfor (const comment of page.comments) {\n\t\t\t\tallComments.push(comment);\n\t\t\t}\n\n\t\t\tstartAt += page.maxResults;\n\t\t\tif (startAt >= page.total) break;\n\t\t}\n\n\t\treturn Ok(allComments);\n\t}\n\n\t/**\n\t * Fetch all projects with auto-pagination.\n\t */\n\tasync getProjects(): Promise<Result<JiraProject[], JiraApiError | JiraRateLimitError>> {\n\t\tconst allProjects: JiraProject[] = [];\n\t\tlet startAt = 0;\n\n\t\twhile (true) {\n\t\t\tconst result = await this.request<JiraProjectPage>(\n\t\t\t\t`/rest/api/3/project/search?startAt=${startAt}&maxResults=${MAX_RESULTS}`,\n\t\t\t\t\"GET\",\n\t\t\t);\n\n\t\t\tif (!result.ok) return result;\n\n\t\t\tconst page = result.value;\n\t\t\tfor (const project of page.values) {\n\t\t\t\tallProjects.push(project);\n\t\t\t}\n\n\t\t\tstartAt += page.maxResults;\n\t\t\tif (startAt >= page.total) break;\n\t\t}\n\n\t\treturn Ok(allProjects);\n\t}\n\n\t/**\n\t * Fetch the currently authenticated user.\n\t *\n\t * Calls `GET /rest/api/3/myself` — the cheapest auth-validating endpoint.\n\t */\n\tasync getCurrentUser(): Promise<\n\t\tResult<{ displayName: string; emailAddress: string }, JiraApiError | JiraRateLimitError>\n\t> {\n\t\treturn this.request(\"/rest/api/3/myself\", \"GET\");\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal HTTP helpers\n\t// -----------------------------------------------------------------------\n\n\t/** Make an HTTP request with rate-limit retry logic. */\n\tprivate async request<T>(\n\t\tpath: string,\n\t\tmethod: \"GET\" | \"POST\",\n\t\tbody?: unknown,\n\t): Promise<Result<T, JiraApiError | JiraRateLimitError>> {\n\t\tlet lastError: JiraApiError | undefined;\n\n\t\tfor (let attempt = 0; attempt <= MAX_RATE_LIMIT_RETRIES; attempt++) {\n\t\t\tconst headers: Record<string, string> = {\n\t\t\t\tAuthorization: this.authHeader,\n\t\t\t\tAccept: \"application/json\",\n\t\t\t};\n\n\t\t\tconst init: RequestInit = { method, headers };\n\n\t\t\tif (body !== undefined) {\n\t\t\t\theaders[\"Content-Type\"] = \"application/json\";\n\t\t\t\tinit.body = JSON.stringify(body);\n\t\t\t}\n\n\t\t\tconst response = await fetch(`${this.baseUrl}${path}`, init);\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst data = (await response.json()) as T;\n\t\t\t\treturn Ok(data);\n\t\t\t}\n\n\t\t\tif (response.status === 429) {\n\t\t\t\tconst retryAfter = response.headers.get(\"Retry-After\");\n\t\t\t\tconst waitMs = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : DEFAULT_RETRY_AFTER_MS;\n\n\t\t\t\tif (attempt < MAX_RATE_LIMIT_RETRIES) {\n\t\t\t\t\tawait sleep(waitMs);\n\t\t\t\t\tlastError = new JiraApiError(429, `Rate limited, retried after ${waitMs}ms`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn Err(new JiraRateLimitError(waitMs));\n\t\t\t}\n\n\t\t\tconst responseBody = await response.text();\n\t\t\treturn Err(new JiraApiError(response.status, responseBody));\n\t\t}\n\n\t\treturn Err(lastError ?? new JiraApiError(0, \"Unknown error after retries\"));\n\t}\n}\n\n/** Sleep for the given number of milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n","// ---------------------------------------------------------------------------\n// Jira Entity → Flat LakeSync Row Mapping\n// ---------------------------------------------------------------------------\n\nimport type { JiraComment, JiraIssue, JiraProject } from \"./types\";\n\n/**\n * Map a Jira issue to a flat row for the `jira_issues` table.\n *\n * The row ID is the issue key (e.g. \"PROJ-123\").\n */\nexport function mapIssue(issue: JiraIssue): { rowId: string; row: Record<string, unknown> } {\n\tconst f = issue.fields;\n\treturn {\n\t\trowId: issue.key,\n\t\trow: {\n\t\t\tjira_id: issue.id,\n\t\t\tkey: issue.key,\n\t\t\tsummary: f.summary ?? null,\n\t\t\tdescription: f.description != null ? JSON.stringify(f.description) : null,\n\t\t\tstatus: f.status?.name ?? null,\n\t\t\tpriority: f.priority?.name ?? null,\n\t\t\tissue_type: f.issuetype?.name ?? null,\n\t\t\tassignee_name: f.assignee?.displayName ?? null,\n\t\t\tassignee_email: f.assignee?.emailAddress ?? null,\n\t\t\treporter_name: f.reporter?.displayName ?? null,\n\t\t\treporter_email: f.reporter?.emailAddress ?? null,\n\t\t\tlabels: f.labels != null ? JSON.stringify(f.labels) : null,\n\t\t\tproject_key: f.project?.key ?? null,\n\t\t\tproject_name: f.project?.name ?? null,\n\t\t\tcreated: f.created ?? null,\n\t\t\tupdated: f.updated ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Jira comment to a flat row for the `jira_comments` table.\n *\n * The row ID is \"{issueKey}:{commentId}\".\n */\nexport function mapComment(\n\tissueKey: string,\n\tcomment: JiraComment,\n): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: `${issueKey}:${comment.id}`,\n\t\trow: {\n\t\t\tjira_id: comment.id,\n\t\t\tissue_key: issueKey,\n\t\t\tbody: comment.body != null ? JSON.stringify(comment.body) : null,\n\t\t\tauthor_name: comment.author?.displayName ?? null,\n\t\t\tauthor_email: comment.author?.emailAddress ?? null,\n\t\t\tcreated: comment.created ?? null,\n\t\t\tupdated: comment.updated ?? null,\n\t\t},\n\t};\n}\n\n/**\n * Map a Jira project to a flat row for the `jira_projects` table.\n *\n * The row ID is the project key (e.g. \"PROJ\").\n */\nexport function mapProject(project: JiraProject): { rowId: string; row: Record<string, unknown> } {\n\treturn {\n\t\trowId: project.key,\n\t\trow: {\n\t\t\tjira_id: project.id,\n\t\t\tkey: project.key,\n\t\t\tname: project.name,\n\t\t\tproject_type: project.projectTypeKey ?? null,\n\t\t\tlead_name: project.lead?.displayName ?? null,\n\t\t},\n\t};\n}\n","// ---------------------------------------------------------------------------\n// JiraSourcePoller — polls Jira Cloud and pushes deltas to SyncGateway\n// ---------------------------------------------------------------------------\n\nimport { BaseSourcePoller, extractDelta, type PushTarget } from \"@lakesync/core\";\nimport { JiraClient } from \"./client\";\nimport { mapComment, mapIssue, mapProject } from \"./mapping\";\nimport type { JiraConnectorConfig, JiraIngestConfig } from \"./types\";\n\nconst DEFAULT_INTERVAL_MS = 30_000;\n\n/**\n * Polls Jira Cloud for issues, comments, and projects and pushes\n * detected changes into a gateway via streaming accumulation.\n *\n * Uses {@link BaseSourcePoller.accumulateDelta} to push deltas in\n * memory-bounded chunks instead of collecting all deltas in a single array.\n */\nexport class JiraSourcePoller extends BaseSourcePoller {\n\tprivate readonly connectionConfig: JiraConnectorConfig;\n\tprivate readonly client: JiraClient;\n\n\t/** Cursor: max `fields.updated` value from the last issue poll. */\n\tprivate lastUpdated: string | undefined;\n\n\t/** In-memory snapshot for comment diff (keyed by rowId). */\n\tprivate commentSnapshot = new Map<string, Record<string, unknown>>();\n\n\t/** In-memory snapshot for project diff (keyed by project key). */\n\tprivate projectSnapshot = new Map<string, Record<string, unknown>>();\n\n\t/** Export cursor state as a JSON-serialisable object for external persistence. */\n\toverride getCursorState(): Record<string, unknown> {\n\t\treturn {\n\t\t\tlastUpdated: this.lastUpdated,\n\t\t\tcommentSnapshot: Array.from(this.commentSnapshot.entries()),\n\t\t\tprojectSnapshot: Array.from(this.projectSnapshot.entries()),\n\t\t};\n\t}\n\n\t/** Restore cursor state from a previously exported snapshot. */\n\toverride setCursorState(state: Record<string, unknown>): void {\n\t\tthis.lastUpdated = state.lastUpdated as string | undefined;\n\t\tthis.commentSnapshot = new Map(\n\t\t\tstate.commentSnapshot as Array<[string, Record<string, unknown>]>,\n\t\t);\n\t\tthis.projectSnapshot = new Map(\n\t\t\tstate.projectSnapshot as Array<[string, Record<string, unknown>]>,\n\t\t);\n\t}\n\n\tconstructor(\n\t\tconnectionConfig: JiraConnectorConfig,\n\t\tingestConfig: JiraIngestConfig | undefined,\n\t\tname: string,\n\t\tgateway: PushTarget,\n\t\tclient?: JiraClient,\n\t) {\n\t\tsuper({\n\t\t\tname,\n\t\t\tintervalMs: ingestConfig?.intervalMs ?? DEFAULT_INTERVAL_MS,\n\t\t\tgateway,\n\t\t\tmemory: {\n\t\t\t\tchunkSize: ingestConfig?.chunkSize,\n\t\t\t\tmemoryBudgetBytes: ingestConfig?.memoryBudgetBytes,\n\t\t\t},\n\t\t});\n\t\tthis.connectionConfig = connectionConfig;\n\t\tthis.client = client ?? new JiraClient(connectionConfig);\n\t}\n\n\t/** Execute a single poll cycle across all entity types. */\n\tasync poll(): Promise<void> {\n\t\t// 1. Issues (cursor strategy via `updated` field)\n\t\tconst issueKeys = await this.pollIssues();\n\n\t\t// 2. Comments (diff per-issue, only for issues returned in this poll)\n\t\tconst includeComments = this.connectionConfig.includeComments ?? true;\n\t\tif (includeComments && issueKeys.length > 0) {\n\t\t\tawait this.pollComments(issueKeys);\n\t\t}\n\n\t\t// 3. Projects (full diff)\n\t\tconst includeProjects = this.connectionConfig.includeProjects ?? true;\n\t\tif (includeProjects) {\n\t\t\tawait this.pollProjects();\n\t\t}\n\n\t\tawait this.flushAccumulator();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Issues — cursor strategy via JQL `updated` field\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollIssues(): Promise<string[]> {\n\t\tconst result = await this.client.searchIssues(\n\t\t\tthis.connectionConfig.jql ?? \"\",\n\t\t\tthis.lastUpdated,\n\t\t);\n\n\t\tif (!result.ok) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst issues = result.value;\n\t\tif (issues.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst issueKeys: string[] = [];\n\t\tlet maxUpdated = this.lastUpdated;\n\n\t\tfor (const issue of issues) {\n\t\t\tconst { rowId, row } = mapIssue(issue);\n\t\t\tissueKeys.push(issue.key);\n\n\t\t\tconst delta = await extractDelta(null, row, {\n\t\t\t\ttable: \"jira_issues\",\n\t\t\t\trowId,\n\t\t\t\tclientId: this.clientId,\n\t\t\t\thlc: this.hlc.now(),\n\t\t\t});\n\n\t\t\tif (delta) {\n\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t}\n\n\t\t\t// Track max updated timestamp for cursor advancement\n\t\t\tconst updated = issue.fields.updated;\n\t\t\tif (updated && (!maxUpdated || updated > maxUpdated)) {\n\t\t\t\tmaxUpdated = updated;\n\t\t\t}\n\t\t}\n\n\t\tthis.lastUpdated = maxUpdated;\n\t\treturn issueKeys;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Comments — diff per-issue\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollComments(issueKeys: string[]): Promise<void> {\n\t\tfor (const issueKey of issueKeys) {\n\t\t\tconst result = await this.client.getComments(issueKey);\n\t\t\tif (!result.ok) continue;\n\n\t\t\tconst currentMap = new Map<string, Record<string, unknown>>();\n\n\t\t\tfor (const comment of result.value) {\n\t\t\t\tconst { rowId, row } = mapComment(issueKey, comment);\n\t\t\t\tcurrentMap.set(rowId, row);\n\n\t\t\t\tconst previous = this.commentSnapshot.get(rowId) ?? null;\n\t\t\t\tconst delta = await extractDelta(previous, row, {\n\t\t\t\t\ttable: \"jira_comments\",\n\t\t\t\t\trowId,\n\t\t\t\t\tclientId: this.clientId,\n\t\t\t\t\thlc: this.hlc.now(),\n\t\t\t\t});\n\n\t\t\t\tif (delta) {\n\t\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update snapshot for this issue's comments\n\t\t\tfor (const [rowId, row] of currentMap) {\n\t\t\t\tthis.commentSnapshot.set(rowId, row);\n\t\t\t}\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Projects — full diff\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollProjects(): Promise<void> {\n\t\tconst result = await this.client.getProjects();\n\t\tif (!result.ok) return;\n\n\t\tconst currentMap = new Map<string, Record<string, unknown>>();\n\n\t\tfor (const project of result.value) {\n\t\t\tconst { rowId, row } = mapProject(project);\n\t\t\tcurrentMap.set(rowId, row);\n\t\t}\n\n\t\tconst previousMap = this.projectSnapshot;\n\n\t\t// Detect inserts and updates\n\t\tfor (const [rowId, currentRow] of currentMap) {\n\t\t\tconst previousRow = previousMap.get(rowId) ?? null;\n\n\t\t\tconst delta = await extractDelta(previousRow, currentRow, {\n\t\t\t\ttable: \"jira_projects\",\n\t\t\t\trowId,\n\t\t\t\tclientId: this.clientId,\n\t\t\t\thlc: this.hlc.now(),\n\t\t\t});\n\n\t\t\tif (delta) {\n\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t}\n\t\t}\n\n\t\t// Detect deletes: rows in previous snapshot missing from current\n\t\tfor (const [rowId, previousRow] of previousMap) {\n\t\t\tif (!currentMap.has(rowId)) {\n\t\t\t\tconst delta = await extractDelta(previousRow, null, {\n\t\t\t\t\ttable: \"jira_projects\",\n\t\t\t\t\trowId,\n\t\t\t\t\tclientId: this.clientId,\n\t\t\t\t\thlc: this.hlc.now(),\n\t\t\t\t});\n\n\t\t\t\tif (delta) {\n\t\t\t\t\tawait this.accumulateDelta(delta);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Replace snapshot\n\t\tthis.projectSnapshot = currentMap;\n\t}\n}\n","// ---------------------------------------------------------------------------\n// Jira Table Schemas — declares the shape of each table produced by the poller\n// ---------------------------------------------------------------------------\n\nimport type { TableSchema } from \"@lakesync/core\";\n\n/** Schema for the `jira_issues` table (columns from {@link mapIssue}). */\nconst JIRA_ISSUES_SCHEMA: TableSchema = {\n\ttable: \"jira_issues\",\n\tcolumns: [\n\t\t{ name: \"jira_id\", type: \"string\" },\n\t\t{ name: \"key\", type: \"string\" },\n\t\t{ name: \"summary\", type: \"string\" },\n\t\t{ name: \"description\", type: \"string\" },\n\t\t{ name: \"status\", type: \"string\" },\n\t\t{ name: \"priority\", type: \"string\" },\n\t\t{ name: \"issue_type\", type: \"string\" },\n\t\t{ name: \"assignee_name\", type: \"string\" },\n\t\t{ name: \"assignee_email\", type: \"string\" },\n\t\t{ name: \"reporter_name\", type: \"string\" },\n\t\t{ name: \"reporter_email\", type: \"string\" },\n\t\t{ name: \"labels\", type: \"string\" },\n\t\t{ name: \"project_key\", type: \"string\" },\n\t\t{ name: \"project_name\", type: \"string\" },\n\t\t{ name: \"created\", type: \"string\" },\n\t\t{ name: \"updated\", type: \"string\" },\n\t],\n};\n\n/** Schema for the `jira_comments` table (columns from {@link mapComment}). */\nconst JIRA_COMMENTS_SCHEMA: TableSchema = {\n\ttable: \"jira_comments\",\n\tcolumns: [\n\t\t{ name: \"jira_id\", type: \"string\" },\n\t\t{ name: \"issue_key\", type: \"string\" },\n\t\t{ name: \"body\", type: \"string\" },\n\t\t{ name: \"author_name\", type: \"string\" },\n\t\t{ name: \"author_email\", type: \"string\" },\n\t\t{ name: \"created\", type: \"string\" },\n\t\t{ name: \"updated\", type: \"string\" },\n\t],\n};\n\n/** Schema for the `jira_projects` table (columns from {@link mapProject}). */\nconst JIRA_PROJECTS_SCHEMA: TableSchema = {\n\ttable: \"jira_projects\",\n\tcolumns: [\n\t\t{ name: \"jira_id\", type: \"string\" },\n\t\t{ name: \"key\", type: \"string\" },\n\t\t{ name: \"name\", type: \"string\" },\n\t\t{ name: \"project_type\", type: \"string\" },\n\t\t{ name: \"lead_name\", type: \"string\" },\n\t],\n};\n\n/** All table schemas produced by the Jira connector. */\nexport const JIRA_TABLE_SCHEMAS: ReadonlyArray<TableSchema> = [\n\tJIRA_ISSUES_SCHEMA,\n\tJIRA_COMMENTS_SCHEMA,\n\tJIRA_PROJECTS_SCHEMA,\n];\n","import { Ok, type Result } from \"@lakesync/core\";\nimport { JiraClient } from \"./client\";\nimport type { JiraApiError, JiraRateLimitError } from \"./errors\";\nimport type { JiraConnectorConfig } from \"./types\";\n\n/**\n * Test a Jira Cloud connection by authenticating and fetching the current user.\n *\n * Creates a `JiraClient` internally and calls `GET /rest/api/3/myself` —\n * the cheapest endpoint that validates credentials.\n */\nexport async function testConnection(\n\tconfig: JiraConnectorConfig,\n): Promise<Result<void, JiraApiError | JiraRateLimitError>> {\n\tconst client = new JiraClient(config);\n\tconst result = await client.getCurrentUser();\n\tif (!result.ok) return result;\n\treturn Ok(undefined);\n}\n","import { registerOutputSchemas, registerPollerFactory } from \"@lakesync/core\";\nimport { JiraSourcePoller } from \"./poller\";\nimport { JIRA_TABLE_SCHEMAS } from \"./schemas\";\nimport type { JiraIngestConfig } from \"./types\";\n\nexport { JiraClient } from \"./client\";\nexport { JiraApiError, JiraRateLimitError } from \"./errors\";\nexport { mapComment, mapIssue, mapProject } from \"./mapping\";\nexport { JiraSourcePoller } from \"./poller\";\nexport { JIRA_TABLE_SCHEMAS } from \"./schemas\";\nexport { testConnection } from \"./test-connection\";\nexport type {\n\tJiraComment,\n\tJiraCommentPage,\n\tJiraConnectorConfig,\n\tJiraIngestConfig,\n\tJiraIssue,\n\tJiraProject,\n\tJiraProjectPage,\n\tJiraSearchResponse,\n} from \"./types\";\n\n// Auto-register output schemas so listConnectorDescriptors() includes table info.\nregisterOutputSchemas(\"jira\", JIRA_TABLE_SCHEMAS);\n\n// Auto-register poller factory so createPoller(\"jira\", ...) works.\nregisterPollerFactory(\"jira\", (config, gateway) => {\n\tconst ingest: JiraIngestConfig | undefined = config.ingest\n\t\t? {\n\t\t\t\tintervalMs: config.ingest.intervalMs,\n\t\t\t\tchunkSize: config.ingest.chunkSize,\n\t\t\t\tmemoryBudgetBytes: config.ingest.memoryBudgetBytes,\n\t\t\t}\n\t\t: undefined;\n\treturn new JiraSourcePoller(config.jira!, ingest, config.name, gateway);\n});\n"],"mappings":"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","names":[]}
|
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
LakeSyncError,
|
|
5
5
|
Ok,
|
|
6
6
|
extractDelta,
|
|
7
|
+
registerOutputSchemas,
|
|
7
8
|
registerPollerFactory
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7UBS6MFH.js";
|
|
9
10
|
|
|
10
11
|
// ../connector-salesforce/src/errors.ts
|
|
11
12
|
var SalesforceApiError = class extends LakeSyncError {
|
|
@@ -521,6 +522,7 @@ async function testConnection(config) {
|
|
|
521
522
|
}
|
|
522
523
|
|
|
523
524
|
// ../connector-salesforce/src/index.ts
|
|
525
|
+
registerOutputSchemas("salesforce", SALESFORCE_TABLE_SCHEMAS);
|
|
524
526
|
registerPollerFactory("salesforce", (config, gateway) => {
|
|
525
527
|
const ingest = config.ingest ? {
|
|
526
528
|
intervalMs: config.ingest.intervalMs,
|
|
@@ -542,4 +544,4 @@ export {
|
|
|
542
544
|
SALESFORCE_TABLE_SCHEMAS,
|
|
543
545
|
testConnection
|
|
544
546
|
};
|
|
545
|
-
//# sourceMappingURL=chunk-
|
|
547
|
+
//# sourceMappingURL=chunk-TVLTXHW6.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","../../connector-salesforce/src/schemas.ts","../../connector-salesforce/src/test-connection.ts","../../connector-salesforce/src/index.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\t/** Export cursor state as a JSON-serialisable object for external persistence. */\n\toverride getCursorState(): Record<string, unknown> {\n\t\treturn { ...this.cursors };\n\t}\n\n\t/** Restore cursor state from a previously exported snapshot. */\n\toverride setCursorState(state: Record<string, unknown>): void {\n\t\tconst incoming = state as Record<string, string | undefined>;\n\t\tthis.cursors = {\n\t\t\taccounts: incoming.accounts,\n\t\t\tcontacts: incoming.contacts,\n\t\t\topportunities: incoming.opportunities,\n\t\t\tleads: incoming.leads,\n\t\t};\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","// ---------------------------------------------------------------------------\n// Salesforce Table Schemas — column definitions for each synced entity\n// ---------------------------------------------------------------------------\n\nimport type { TableSchema } from \"@lakesync/core\";\n\n/** Column helper — all Salesforce columns are mapped to string. */\nfunction textCol(name: string): { name: string; type: \"string\" } {\n\treturn { name, type: \"string\" };\n}\n\n/** Table schemas for all Salesforce entity types synced by the connector. */\nexport const SALESFORCE_TABLE_SCHEMAS: ReadonlyArray<TableSchema> = [\n\t{\n\t\ttable: \"sf_accounts\",\n\t\tcolumns: [\n\t\t\ttextCol(\"sf_id\"),\n\t\t\ttextCol(\"name\"),\n\t\t\ttextCol(\"type\"),\n\t\t\ttextCol(\"industry\"),\n\t\t\ttextCol(\"website\"),\n\t\t\ttextCol(\"phone\"),\n\t\t\ttextCol(\"billing_city\"),\n\t\t\ttextCol(\"billing_state\"),\n\t\t\ttextCol(\"billing_country\"),\n\t\t\ttextCol(\"annual_revenue\"),\n\t\t\ttextCol(\"number_of_employees\"),\n\t\t\ttextCol(\"owner_name\"),\n\t\t\ttextCol(\"created_date\"),\n\t\t\ttextCol(\"last_modified_date\"),\n\t\t],\n\t},\n\t{\n\t\ttable: \"sf_contacts\",\n\t\tcolumns: [\n\t\t\ttextCol(\"sf_id\"),\n\t\t\ttextCol(\"first_name\"),\n\t\t\ttextCol(\"last_name\"),\n\t\t\ttextCol(\"email\"),\n\t\t\ttextCol(\"phone\"),\n\t\t\ttextCol(\"title\"),\n\t\t\ttextCol(\"account_id\"),\n\t\t\ttextCol(\"account_name\"),\n\t\t\ttextCol(\"mailing_city\"),\n\t\t\ttextCol(\"mailing_state\"),\n\t\t\ttextCol(\"mailing_country\"),\n\t\t\ttextCol(\"owner_name\"),\n\t\t\ttextCol(\"created_date\"),\n\t\t\ttextCol(\"last_modified_date\"),\n\t\t],\n\t},\n\t{\n\t\ttable: \"sf_opportunities\",\n\t\tcolumns: [\n\t\t\ttextCol(\"sf_id\"),\n\t\t\ttextCol(\"name\"),\n\t\t\ttextCol(\"stage_name\"),\n\t\t\ttextCol(\"amount\"),\n\t\t\ttextCol(\"close_date\"),\n\t\t\ttextCol(\"probability\"),\n\t\t\ttextCol(\"account_id\"),\n\t\t\ttextCol(\"account_name\"),\n\t\t\ttextCol(\"type\"),\n\t\t\ttextCol(\"lead_source\"),\n\t\t\ttextCol(\"is_closed\"),\n\t\t\ttextCol(\"is_won\"),\n\t\t\ttextCol(\"owner_name\"),\n\t\t\ttextCol(\"created_date\"),\n\t\t\ttextCol(\"last_modified_date\"),\n\t\t],\n\t},\n\t{\n\t\ttable: \"sf_leads\",\n\t\tcolumns: [\n\t\t\ttextCol(\"sf_id\"),\n\t\t\ttextCol(\"first_name\"),\n\t\t\ttextCol(\"last_name\"),\n\t\t\ttextCol(\"company\"),\n\t\t\ttextCol(\"email\"),\n\t\t\ttextCol(\"phone\"),\n\t\t\ttextCol(\"title\"),\n\t\t\ttextCol(\"status\"),\n\t\t\ttextCol(\"lead_source\"),\n\t\t\ttextCol(\"is_converted\"),\n\t\t\ttextCol(\"converted_account_id\"),\n\t\t\ttextCol(\"converted_contact_id\"),\n\t\t\ttextCol(\"converted_opportunity_id\"),\n\t\t\ttextCol(\"owner_name\"),\n\t\t\ttextCol(\"created_date\"),\n\t\t\ttextCol(\"last_modified_date\"),\n\t\t],\n\t},\n];\n","import type { Result } from \"@lakesync/core\";\nimport { SalesforceClient } from \"./client\";\nimport type { SalesforceAuthError } from \"./errors\";\nimport type { SalesforceConnectorConfig } from \"./types\";\n\n/**\n * Test a Salesforce connection by attempting OAuth authentication.\n *\n * Creates a `SalesforceClient` internally and calls `authenticate()` —\n * if the OAuth flow succeeds, the connection is valid.\n */\nexport async function testConnection(\n\tconfig: SalesforceConnectorConfig,\n): Promise<Result<void, SalesforceAuthError>> {\n\tconst client = new SalesforceClient(config);\n\treturn client.authenticate();\n}\n","import { registerPollerFactory } from \"@lakesync/core\";\nimport { SalesforceSourcePoller } from \"./poller\";\nimport type { SalesforceIngestConfig } from \"./types\";\n\nexport { SalesforceClient } from \"./client\";\nexport { SalesforceApiError, SalesforceAuthError } from \"./errors\";\nexport { mapAccount, mapContact, mapLead, mapOpportunity } from \"./mapping\";\nexport { SalesforceSourcePoller } from \"./poller\";\nexport { SALESFORCE_TABLE_SCHEMAS } from \"./schemas\";\nexport { testConnection } from \"./test-connection\";\nexport type {\n\tSalesforceAuthResponse,\n\tSalesforceConnectorConfig,\n\tSalesforceIngestConfig,\n\tSalesforceQueryResponse,\n\tSfAccount,\n\tSfContact,\n\tSfLead,\n\tSfOpportunity,\n} from \"./types\";\n\n// Auto-register poller factory so createPoller(\"salesforce\", ...) works.\nregisterPollerFactory(\"salesforce\", (config, gateway) => {\n\tconst ingest: SalesforceIngestConfig | undefined = config.ingest\n\t\t? {\n\t\t\t\tintervalMs: config.ingest.intervalMs,\n\t\t\t\tchunkSize: config.ingest.chunkSize,\n\t\t\t\tmemoryBudgetBytes: config.ingest.memoryBudgetBytes,\n\t\t\t}\n\t\t: undefined;\n\treturn new SalesforceSourcePoller(config.salesforce!, ingest, config.name, gateway);\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","../../connector-salesforce/src/schemas.ts","../../connector-salesforce/src/test-connection.ts","../../connector-salesforce/src/index.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\t/** Export cursor state as a JSON-serialisable object for external persistence. */\n\toverride getCursorState(): Record<string, unknown> {\n\t\treturn { ...this.cursors };\n\t}\n\n\t/** Restore cursor state from a previously exported snapshot. */\n\toverride setCursorState(state: Record<string, unknown>): void {\n\t\tconst incoming = state as Record<string, string | undefined>;\n\t\tthis.cursors = {\n\t\t\taccounts: incoming.accounts,\n\t\t\tcontacts: incoming.contacts,\n\t\t\topportunities: incoming.opportunities,\n\t\t\tleads: incoming.leads,\n\t\t};\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","// ---------------------------------------------------------------------------\n// Salesforce Table Schemas — column definitions for each synced entity\n// ---------------------------------------------------------------------------\n\nimport type { TableSchema } from \"@lakesync/core\";\n\n/** Column helper — all Salesforce columns are mapped to string. */\nfunction textCol(name: string): { name: string; type: \"string\" } {\n\treturn { name, type: \"string\" };\n}\n\n/** Table schemas for all Salesforce entity types synced by the connector. */\nexport const SALESFORCE_TABLE_SCHEMAS: ReadonlyArray<TableSchema> = [\n\t{\n\t\ttable: \"sf_accounts\",\n\t\tcolumns: [\n\t\t\ttextCol(\"sf_id\"),\n\t\t\ttextCol(\"name\"),\n\t\t\ttextCol(\"type\"),\n\t\t\ttextCol(\"industry\"),\n\t\t\ttextCol(\"website\"),\n\t\t\ttextCol(\"phone\"),\n\t\t\ttextCol(\"billing_city\"),\n\t\t\ttextCol(\"billing_state\"),\n\t\t\ttextCol(\"billing_country\"),\n\t\t\ttextCol(\"annual_revenue\"),\n\t\t\ttextCol(\"number_of_employees\"),\n\t\t\ttextCol(\"owner_name\"),\n\t\t\ttextCol(\"created_date\"),\n\t\t\ttextCol(\"last_modified_date\"),\n\t\t],\n\t},\n\t{\n\t\ttable: \"sf_contacts\",\n\t\tcolumns: [\n\t\t\ttextCol(\"sf_id\"),\n\t\t\ttextCol(\"first_name\"),\n\t\t\ttextCol(\"last_name\"),\n\t\t\ttextCol(\"email\"),\n\t\t\ttextCol(\"phone\"),\n\t\t\ttextCol(\"title\"),\n\t\t\ttextCol(\"account_id\"),\n\t\t\ttextCol(\"account_name\"),\n\t\t\ttextCol(\"mailing_city\"),\n\t\t\ttextCol(\"mailing_state\"),\n\t\t\ttextCol(\"mailing_country\"),\n\t\t\ttextCol(\"owner_name\"),\n\t\t\ttextCol(\"created_date\"),\n\t\t\ttextCol(\"last_modified_date\"),\n\t\t],\n\t},\n\t{\n\t\ttable: \"sf_opportunities\",\n\t\tcolumns: [\n\t\t\ttextCol(\"sf_id\"),\n\t\t\ttextCol(\"name\"),\n\t\t\ttextCol(\"stage_name\"),\n\t\t\ttextCol(\"amount\"),\n\t\t\ttextCol(\"close_date\"),\n\t\t\ttextCol(\"probability\"),\n\t\t\ttextCol(\"account_id\"),\n\t\t\ttextCol(\"account_name\"),\n\t\t\ttextCol(\"type\"),\n\t\t\ttextCol(\"lead_source\"),\n\t\t\ttextCol(\"is_closed\"),\n\t\t\ttextCol(\"is_won\"),\n\t\t\ttextCol(\"owner_name\"),\n\t\t\ttextCol(\"created_date\"),\n\t\t\ttextCol(\"last_modified_date\"),\n\t\t],\n\t},\n\t{\n\t\ttable: \"sf_leads\",\n\t\tcolumns: [\n\t\t\ttextCol(\"sf_id\"),\n\t\t\ttextCol(\"first_name\"),\n\t\t\ttextCol(\"last_name\"),\n\t\t\ttextCol(\"company\"),\n\t\t\ttextCol(\"email\"),\n\t\t\ttextCol(\"phone\"),\n\t\t\ttextCol(\"title\"),\n\t\t\ttextCol(\"status\"),\n\t\t\ttextCol(\"lead_source\"),\n\t\t\ttextCol(\"is_converted\"),\n\t\t\ttextCol(\"converted_account_id\"),\n\t\t\ttextCol(\"converted_contact_id\"),\n\t\t\ttextCol(\"converted_opportunity_id\"),\n\t\t\ttextCol(\"owner_name\"),\n\t\t\ttextCol(\"created_date\"),\n\t\t\ttextCol(\"last_modified_date\"),\n\t\t],\n\t},\n];\n","import type { Result } from \"@lakesync/core\";\nimport { SalesforceClient } from \"./client\";\nimport type { SalesforceAuthError } from \"./errors\";\nimport type { SalesforceConnectorConfig } from \"./types\";\n\n/**\n * Test a Salesforce connection by attempting OAuth authentication.\n *\n * Creates a `SalesforceClient` internally and calls `authenticate()` —\n * if the OAuth flow succeeds, the connection is valid.\n */\nexport async function testConnection(\n\tconfig: SalesforceConnectorConfig,\n): Promise<Result<void, SalesforceAuthError>> {\n\tconst client = new SalesforceClient(config);\n\treturn client.authenticate();\n}\n","import { registerOutputSchemas, registerPollerFactory } from \"@lakesync/core\";\nimport { SalesforceSourcePoller } from \"./poller\";\nimport { SALESFORCE_TABLE_SCHEMAS } from \"./schemas\";\nimport type { SalesforceIngestConfig } from \"./types\";\n\nexport { SalesforceClient } from \"./client\";\nexport { SalesforceApiError, SalesforceAuthError } from \"./errors\";\nexport { mapAccount, mapContact, mapLead, mapOpportunity } from \"./mapping\";\nexport { SalesforceSourcePoller } from \"./poller\";\nexport { SALESFORCE_TABLE_SCHEMAS } from \"./schemas\";\nexport { testConnection } from \"./test-connection\";\nexport type {\n\tSalesforceAuthResponse,\n\tSalesforceConnectorConfig,\n\tSalesforceIngestConfig,\n\tSalesforceQueryResponse,\n\tSfAccount,\n\tSfContact,\n\tSfLead,\n\tSfOpportunity,\n} from \"./types\";\n\n// Auto-register output schemas so listConnectorDescriptors() includes table info.\nregisterOutputSchemas(\"salesforce\", SALESFORCE_TABLE_SCHEMAS);\n\n// Auto-register poller factory so createPoller(\"salesforce\", ...) works.\nregisterPollerFactory(\"salesforce\", (config, gateway) => {\n\tconst ingest: SalesforceIngestConfig | undefined = config.ingest\n\t\t? {\n\t\t\t\tintervalMs: config.ingest.intervalMs,\n\t\t\t\tchunkSize: config.ingest.chunkSize,\n\t\t\t\tmemoryBudgetBytes: config.ingest.memoryBudgetBytes,\n\t\t\t}\n\t\t: undefined;\n\treturn new SalesforceSourcePoller(config.salesforce!, ingest, config.name, gateway);\n});\n"],"mappings":"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","names":["responseBody"]}
|