lakesync 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/adapter-types-DwsQGQS4.d.ts +94 -0
  2. package/dist/adapter.d.ts +23 -49
  3. package/dist/adapter.js +9 -4
  4. package/dist/analyst.js +1 -1
  5. package/dist/{base-poller-Bj9kX9dv.d.ts → base-poller-Y7ORYgUv.d.ts} +2 -0
  6. package/dist/catalogue.js +2 -2
  7. package/dist/{chunk-LDFFCG2K.js → chunk-4SG66H5K.js} +44 -31
  8. package/dist/chunk-4SG66H5K.js.map +1 -0
  9. package/dist/{chunk-LPWXOYNS.js → chunk-C4KD6YKP.js} +59 -43
  10. package/dist/chunk-C4KD6YKP.js.map +1 -0
  11. package/dist/{chunk-JI4C4R5H.js → chunk-FIIHPQMQ.js} +196 -118
  12. package/dist/chunk-FIIHPQMQ.js.map +1 -0
  13. package/dist/{chunk-TMLG32QV.js → chunk-U2NV4DUX.js} +2 -2
  14. package/dist/{chunk-QNITY4F6.js → chunk-XVP5DJJ7.js} +16 -13
  15. package/dist/{chunk-QNITY4F6.js.map → chunk-XVP5DJJ7.js.map} +1 -1
  16. package/dist/{chunk-KVSWLIJR.js → chunk-YHYBLU6W.js} +2 -2
  17. package/dist/{chunk-PYRS74YP.js → chunk-ZNY4DSFU.js} +16 -13
  18. package/dist/{chunk-PYRS74YP.js.map → chunk-ZNY4DSFU.js.map} +1 -1
  19. package/dist/{chunk-SSICS5KI.js → chunk-ZU7RC7CT.js} +2 -2
  20. package/dist/client.d.ts +28 -10
  21. package/dist/client.js +150 -29
  22. package/dist/client.js.map +1 -1
  23. package/dist/compactor.d.ts +1 -1
  24. package/dist/compactor.js +3 -3
  25. package/dist/connector-jira.d.ts +13 -3
  26. package/dist/connector-jira.js +6 -2
  27. package/dist/connector-salesforce.d.ts +13 -3
  28. package/dist/connector-salesforce.js +6 -2
  29. package/dist/{coordinator-NXy6tA0h.d.ts → coordinator-eGmZMnJ_.d.ts} +99 -16
  30. package/dist/create-poller-Cc2MGfhh.d.ts +55 -0
  31. package/dist/factory-DFfR-030.d.ts +33 -0
  32. package/dist/gateway-server.d.ts +398 -95
  33. package/dist/gateway-server.js +743 -56
  34. package/dist/gateway-server.js.map +1 -1
  35. package/dist/gateway.d.ts +14 -8
  36. package/dist/gateway.js +6 -5
  37. package/dist/index.d.ts +45 -73
  38. package/dist/index.js +5 -3
  39. package/dist/parquet.js +2 -2
  40. package/dist/proto.js +2 -2
  41. package/dist/react.d.ts +3 -3
  42. package/dist/{registry-BcspAtZI.d.ts → registry-Dd8JuW8T.d.ts} +1 -1
  43. package/dist/{request-handler-pUvL7ozF.d.ts → request-handler-B1I5xDOx.d.ts} +71 -27
  44. package/dist/{src-ROW4XLO7.js → src-WU7IBVC4.js} +6 -4
  45. package/dist/{types-BrcD1oJg.d.ts → types-D2C9jTbL.d.ts} +33 -23
  46. package/package.json +1 -1
  47. package/dist/auth-CAVutXzx.d.ts +0 -30
  48. package/dist/chunk-JI4C4R5H.js.map +0 -1
  49. package/dist/chunk-LDFFCG2K.js.map +0 -1
  50. package/dist/chunk-LPWXOYNS.js.map +0 -1
  51. package/dist/db-types-CfLMUBfW.d.ts +0 -29
  52. package/dist/src-B6NLV3FP.js +0 -27
  53. package/dist/src-ROW4XLO7.js.map +0 -1
  54. package/dist/src-ZRHKG42A.js +0 -25
  55. package/dist/src-ZRHKG42A.js.map +0 -1
  56. package/dist/types-DSC_EiwR.d.ts +0 -45
  57. /package/dist/{chunk-TMLG32QV.js.map → chunk-U2NV4DUX.js.map} +0 -0
  58. /package/dist/{chunk-KVSWLIJR.js.map → chunk-YHYBLU6W.js.map} +0 -0
  59. /package/dist/{chunk-SSICS5KI.js.map → chunk-ZU7RC7CT.js.map} +0 -0
  60. /package/dist/{src-B6NLV3FP.js.map → src-WU7IBVC4.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../gateway-server/src/auth-middleware.ts","../../gateway-server/src/cluster.ts","../../gateway-server/src/ingest/poller.ts","../../gateway-server/src/connector-manager.ts","../../gateway-server/src/cors-middleware.ts","../../gateway-server/src/persistence.ts","../../gateway-server/src/router.ts","../../gateway-server/src/server.ts","../../gateway-server/src/shared-buffer.ts","../../gateway-server/src/ws-manager.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Auth Middleware — JWT validation for HTTP requests\n// ---------------------------------------------------------------------------\n\nimport type { IncomingMessage } from \"node:http\";\nimport type { AuthClaims } from \"./auth\";\nimport { verifyToken } from \"./auth\";\n\n/** Result of authentication: either authenticated claims or an error. */\nexport type AuthResult =\n\t| { authenticated: true; claims: AuthClaims }\n\t| { authenticated: false; status: number; message: string };\n\n/** Set of actions that require admin role. */\nconst ADMIN_ACTIONS = new Set([\n\t\"flush\",\n\t\"schema\",\n\t\"sync-rules\",\n\t\"register-connector\",\n\t\"unregister-connector\",\n\t\"list-connectors\",\n\t\"metrics\",\n]);\n\n/**\n * Extract the Bearer token from an Authorization header.\n * Returns the raw token string, or null if missing/malformed.\n */\nexport function extractBearerToken(req: IncomingMessage): string | null {\n\tconst header = req.headers.authorization;\n\tif (!header) return null;\n\tconst match = header.match(/^Bearer\\s+(\\S+)$/);\n\treturn match?.[1] ?? null;\n}\n\n/**\n * Authenticate an HTTP request.\n *\n * When `jwtSecret` is undefined, auth is disabled and all requests pass.\n * Otherwise validates the Bearer token, checks gateway ID, and enforces\n * admin role for admin actions.\n */\nexport async function authenticateRequest(\n\treq: IncomingMessage,\n\trouteGatewayId: string,\n\trouteAction: string,\n\tjwtSecret: string | undefined,\n): Promise<AuthResult> {\n\tif (!jwtSecret) {\n\t\treturn { authenticated: true, claims: undefined as unknown as AuthClaims };\n\t}\n\n\tconst token = extractBearerToken(req);\n\tif (!token) {\n\t\treturn { authenticated: false, status: 401, message: \"Missing Bearer token\" };\n\t}\n\n\tconst authResult = await verifyToken(token, jwtSecret);\n\tif (!authResult.ok) {\n\t\treturn { authenticated: false, status: 401, message: authResult.error.message };\n\t}\n\n\tconst claims = authResult.value;\n\n\t// Verify JWT gateway ID matches the route\n\tif (claims.gatewayId !== routeGatewayId) {\n\t\treturn {\n\t\t\tauthenticated: false,\n\t\t\tstatus: 403,\n\t\t\tmessage: \"Gateway ID mismatch: JWT authorises a different gateway\",\n\t\t};\n\t}\n\n\t// Admin route protection\n\tif (ADMIN_ACTIONS.has(routeAction) && claims.role !== \"admin\") {\n\t\treturn { authenticated: false, status: 403, message: \"Admin role required\" };\n\t}\n\n\treturn { authenticated: true, claims };\n}\n\n/** Check whether auth is disabled (no jwtSecret configured). */\nexport function isAuthDisabled(jwtSecret: string | undefined): boolean {\n\treturn jwtSecret === undefined;\n}\n","import type { DatabaseAdapter } from \"@lakesync/adapter\";\n\n/**\n * Interface for distributed locking across gateway-server instances.\n *\n * Used to coordinate exclusive operations (e.g. flush) across\n * multiple instances behind a load balancer.\n */\nexport interface DistributedLock {\n\t/** Attempt to acquire a lock. Returns true if acquired. */\n\tacquire(key: string, ttlMs: number): Promise<boolean>;\n\t/** Release a previously acquired lock. */\n\trelease(key: string): Promise<void>;\n}\n\n/**\n * Database-backed distributed lock using advisory lock semantics.\n *\n * Uses the adapter's `ensureSchema` + `insertDeltas` to maintain a\n * dedicated `__lakesync_locks` table. Lock entries are isolated from\n * regular sync data — they use a reserved table prefix that sync\n * queries should never match.\n *\n * Acquire is atomic: the adapter's upsert semantics (INSERT ON CONFLICT\n * or equivalent) ensure only one instance can hold a lock. Release\n * is best-effort — the TTL provides a safety net against holder crashes.\n *\n * Note: This implementation piggybacks on the DatabaseAdapter interface\n * because the adapter abstraction doesn't expose raw SQL. For adapters\n * that support native advisory locks (e.g. Postgres pg_advisory_lock),\n * prefer a specialised implementation of {@link DistributedLock}.\n */\nexport class AdapterBasedLock implements DistributedLock {\n\tprivate readonly adapter: DatabaseAdapter;\n\tprivate readonly instanceId: string;\n\n\tconstructor(adapter: DatabaseAdapter, instanceId?: string) {\n\t\tthis.adapter = adapter;\n\t\tthis.instanceId = instanceId ?? crypto.randomUUID();\n\t}\n\n\tasync acquire(key: string, ttlMs: number): Promise<boolean> {\n\t\ttry {\n\t\t\t// Use insertDeltas to attempt an atomic lock acquisition.\n\t\t\t// The adapter's upsert semantics handle conflict resolution.\n\t\t\tconst now = Date.now();\n\t\t\tconst result = await this.adapter.insertDeltas([\n\t\t\t\t{\n\t\t\t\t\top: \"INSERT\",\n\t\t\t\t\ttable: \"__lakesync_locks\",\n\t\t\t\t\trowId: key,\n\t\t\t\t\tclientId: this.instanceId,\n\t\t\t\t\tcolumns: [\n\t\t\t\t\t\t{ column: \"holder\", value: this.instanceId },\n\t\t\t\t\t\t{ column: \"expires_at\", value: now + ttlMs },\n\t\t\t\t\t],\n\t\t\t\t\thlc: this.makeHlc(now),\n\t\t\t\t\tdeltaId: `lock-${key}-${now}`,\n\t\t\t\t},\n\t\t\t]);\n\t\t\treturn result.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync release(key: string): Promise<void> {\n\t\ttry {\n\t\t\tconst now = Date.now();\n\t\t\tawait this.adapter.insertDeltas([\n\t\t\t\t{\n\t\t\t\t\top: \"DELETE\",\n\t\t\t\t\ttable: \"__lakesync_locks\",\n\t\t\t\t\trowId: key,\n\t\t\t\t\tclientId: this.instanceId,\n\t\t\t\t\tcolumns: [],\n\t\t\t\t\thlc: this.makeHlc(now),\n\t\t\t\t\tdeltaId: `unlock-${key}-${now}`,\n\t\t\t\t},\n\t\t\t]);\n\t\t} catch {\n\t\t\t// Best-effort release — TTL will expire the lock anyway\n\t\t}\n\t}\n\n\t/**\n\t * Create an HLC-format timestamp from wall clock time.\n\t *\n\t * Uses the standard 48-bit wall + 16-bit counter encoding.\n\t */\n\tprivate makeHlc(wallMs: number): import(\"@lakesync/core\").HLCTimestamp {\n\t\treturn (BigInt(wallMs) << 16n) as import(\"@lakesync/core\").HLCTimestamp;\n\t}\n}\n","// ---------------------------------------------------------------------------\n// SourcePoller — polls external databases and pushes deltas to SyncGateway\n// ---------------------------------------------------------------------------\n\nimport type { HLCTimestamp, RowDelta, SyncPush } from \"@lakesync/core\";\nimport { extractDelta, HLC } from \"@lakesync/core\";\nimport type { SyncGateway } from \"@lakesync/gateway\";\nimport type { IngestSourceConfig, IngestTableConfig } from \"./types\";\n\nconst DEFAULT_INTERVAL_MS = 10_000;\nconst DEFAULT_LOOKBACK_MS = 5_000;\nconst LARGE_SNAPSHOT_WARN = 50_000;\n\n/** Per-table state for cursor strategy. */\ninterface CursorState {\n\tlastCursor: unknown;\n}\n\n/** Per-table state for diff strategy. */\ninterface DiffState {\n\tsnapshot: Map<string, Record<string, unknown>>;\n}\n\n/**\n * Polls an external data source and pushes detected changes into a\n * {@link SyncGateway} via `handlePush()`.\n *\n * Supports two change detection strategies:\n * - **cursor**: fast incremental polling using a monotonically increasing column\n * - **diff**: full-table comparison detecting inserts, updates, and deletes\n */\nexport class SourcePoller {\n\tprivate readonly config: IngestSourceConfig;\n\tprivate readonly gateway: SyncGateway;\n\tprivate readonly hlc: HLC;\n\tprivate readonly clientId: string;\n\n\tprivate timer: ReturnType<typeof setTimeout> | null = null;\n\tprivate running = false;\n\n\t/** Cursor state per table (keyed by table name). */\n\tprivate cursorStates = new Map<string, CursorState>();\n\t/** Diff snapshot per table (keyed by table name). */\n\tprivate diffStates = new Map<string, DiffState>();\n\n\tconstructor(config: IngestSourceConfig, gateway: SyncGateway) {\n\t\tthis.config = config;\n\t\tthis.gateway = gateway;\n\t\tthis.hlc = new HLC();\n\t\tthis.clientId = `ingest:${config.name}`;\n\t}\n\n\t/** Start the polling loop. */\n\tstart(): void {\n\t\tif (this.running) return;\n\t\tthis.running = true;\n\t\tthis.schedulePoll();\n\t}\n\n\t/** Stop the polling loop. */\n\tstop(): void {\n\t\tthis.running = false;\n\t\tif (this.timer) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = null;\n\t\t}\n\t}\n\n\t/** Whether the poller is currently running. */\n\tget isRunning(): boolean {\n\t\treturn this.running;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Poll scheduling (recursive setTimeout — no overlap)\n\t// -----------------------------------------------------------------------\n\n\tprivate schedulePoll(): void {\n\t\tif (!this.running) return;\n\n\t\tthis.timer = setTimeout(async () => {\n\t\t\ttry {\n\t\t\t\tawait this.poll();\n\t\t\t} catch {\n\t\t\t\t// Swallow errors — a failed poll must never crash the server\n\t\t\t}\n\t\t\tthis.schedulePoll();\n\t\t}, this.config.intervalMs ?? DEFAULT_INTERVAL_MS);\n\t}\n\n\t/** Execute a single poll cycle across all configured tables. */\n\tasync poll(): Promise<void> {\n\t\tconst allDeltas: RowDelta[] = [];\n\n\t\tfor (const table of this.config.tables) {\n\t\t\tconst deltas =\n\t\t\t\ttable.strategy.type === \"cursor\"\n\t\t\t\t\t? await this.pollCursor(table)\n\t\t\t\t\t: await this.pollDiff(table);\n\n\t\t\tfor (const d of deltas) {\n\t\t\t\tallDeltas.push(d);\n\t\t\t}\n\t\t}\n\n\t\tif (allDeltas.length === 0) return;\n\n\t\tconst push: SyncPush = {\n\t\t\tclientId: this.clientId,\n\t\t\tdeltas: allDeltas,\n\t\t\tlastSeenHlc: 0n as HLCTimestamp,\n\t\t};\n\n\t\tthis.gateway.handlePush(push);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Cursor strategy\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollCursor(tableConfig: IngestTableConfig): Promise<RowDelta[]> {\n\t\tconst strategy = tableConfig.strategy;\n\t\tif (strategy.type !== \"cursor\") return [];\n\n\t\tconst rowIdCol = tableConfig.rowIdColumn ?? \"id\";\n\t\tconst state = this.cursorStates.get(tableConfig.table);\n\t\tconst lookbackMs = strategy.lookbackMs ?? DEFAULT_LOOKBACK_MS;\n\n\t\tlet rows: Record<string, unknown>[];\n\n\t\tif (state?.lastCursor != null) {\n\t\t\t// Subsequent poll: apply look-back overlap for late-committing transactions\n\t\t\tlet effectiveCursor = state.lastCursor;\n\t\t\tif (typeof effectiveCursor === \"number\") {\n\t\t\t\teffectiveCursor = effectiveCursor - lookbackMs;\n\t\t\t} else if (effectiveCursor instanceof Date) {\n\t\t\t\teffectiveCursor = new Date(effectiveCursor.getTime() - lookbackMs);\n\t\t\t} else if (typeof effectiveCursor === \"string\") {\n\t\t\t\t// Attempt ISO date string\n\t\t\t\tconst parsed = Date.parse(effectiveCursor);\n\t\t\t\tif (!Number.isNaN(parsed)) {\n\t\t\t\t\teffectiveCursor = new Date(parsed - lookbackMs).toISOString();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst sql = `SELECT * FROM (${tableConfig.query}) AS _src WHERE ${strategy.cursorColumn} > $1 ORDER BY ${strategy.cursorColumn} ASC`;\n\t\t\trows = await this.config.queryFn(sql, [effectiveCursor]);\n\t\t} else {\n\t\t\t// First poll: fetch everything\n\t\t\tconst sql = `SELECT * FROM (${tableConfig.query}) AS _src ORDER BY ${strategy.cursorColumn} ASC`;\n\t\t\trows = await this.config.queryFn(sql);\n\t\t}\n\n\t\tif (rows.length === 0) return [];\n\n\t\t// Update cursor to max value\n\t\tconst lastRow = rows[rows.length - 1]!;\n\t\tconst newCursor = lastRow[strategy.cursorColumn];\n\t\tthis.cursorStates.set(tableConfig.table, { lastCursor: newCursor });\n\n\t\t// Convert rows to deltas\n\t\t// Cursor strategy cannot determine previous state — every row is an INSERT/upsert\n\t\tconst deltas: RowDelta[] = [];\n\t\tfor (const row of rows) {\n\t\t\tconst rowId = String(row[rowIdCol]);\n\t\t\tconst after = { ...row };\n\t\t\tdelete after[rowIdCol];\n\n\t\t\tconst delta = await extractDelta(null, after, {\n\t\t\t\ttable: tableConfig.table,\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\tdeltas.push(delta);\n\t\t\t}\n\t\t}\n\n\t\treturn deltas;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Diff strategy\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollDiff(tableConfig: IngestTableConfig): Promise<RowDelta[]> {\n\t\tconst rowIdCol = tableConfig.rowIdColumn ?? \"id\";\n\t\tconst rows = await this.config.queryFn(tableConfig.query);\n\n\t\tconst currentMap = new Map<string, Record<string, unknown>>();\n\t\tfor (const row of rows) {\n\t\t\tconst rowId = String(row[rowIdCol]);\n\t\t\tcurrentMap.set(rowId, row);\n\t\t}\n\n\t\tif (currentMap.size > LARGE_SNAPSHOT_WARN) {\n\t\t\tconsole.warn(\n\t\t\t\t`[lakesync:ingest] Diff snapshot for \"${tableConfig.table}\" has ${currentMap.size} rows (>50k). Consider using cursor strategy.`,\n\t\t\t);\n\t\t}\n\n\t\tconst state = this.diffStates.get(tableConfig.table);\n\t\tconst previousMap = state?.snapshot ?? new Map<string, Record<string, unknown>>();\n\n\t\tconst deltas: RowDelta[] = [];\n\n\t\t// Detect inserts and updates\n\t\tfor (const [rowId, currentRow] of currentMap) {\n\t\t\tconst previousRow = previousMap.get(rowId);\n\n\t\t\t// Build column maps without rowId column\n\t\t\tconst after = { ...currentRow };\n\t\t\tdelete after[rowIdCol];\n\n\t\t\tlet before: Record<string, unknown> | null = null;\n\t\t\tif (previousRow) {\n\t\t\t\tbefore = { ...previousRow };\n\t\t\t\tdelete before[rowIdCol];\n\t\t\t}\n\n\t\t\tconst delta = await extractDelta(before, after, {\n\t\t\t\ttable: tableConfig.table,\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\tdeltas.push(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 before = { ...previousRow };\n\t\t\t\tdelete before[rowIdCol];\n\n\t\t\t\tconst delta = await extractDelta(before, null, {\n\t\t\t\t\ttable: tableConfig.table,\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\tdeltas.push(delta);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Replace snapshot\n\t\tthis.diffStates.set(tableConfig.table, { snapshot: currentMap });\n\n\t\treturn deltas;\n\t}\n}\n","// ---------------------------------------------------------------------------\n// Connector Manager — registration, poller lifecycle, adapter creation\n// ---------------------------------------------------------------------------\n\nimport { createDatabaseAdapter, createQueryFn, type DatabaseAdapter } from \"@lakesync/adapter\";\nimport { isActionHandler } from \"@lakesync/core\";\nimport type { ConfigStore, SyncGateway } from \"@lakesync/gateway\";\nimport {\n\ttype HandlerResult,\n\thandleListConnectors,\n\thandleRegisterConnector,\n\thandleUnregisterConnector,\n} from \"@lakesync/gateway\";\nimport { SourcePoller } from \"./ingest/poller\";\nimport type { IngestSourceConfig } from \"./ingest/types\";\n\n/** Common lifecycle interface for source pollers (SQL or API-based). */\ninterface Poller {\n\tstart(): void;\n\tstop(): void;\n\treadonly isRunning: boolean;\n}\n\n/**\n * Manages connector registration, adapter creation, and poller lifecycle.\n *\n * Encapsulates the if/else chains for different connector types and\n * handles clean-up on unregistration.\n */\nexport class ConnectorManager {\n\tprivate readonly adapters = new Map<string, DatabaseAdapter>();\n\tprivate readonly pollers = new Map<string, Poller>();\n\n\tconstructor(\n\t\tprivate readonly configStore: ConfigStore,\n\t\tprivate readonly gateway: SyncGateway,\n\t) {}\n\n\t/**\n\t * Register a connector from raw JSON body.\n\t *\n\t * Validates via shared handler, creates adapter/poller as appropriate,\n\t * registers with the gateway.\n\t */\n\tasync register(raw: string): Promise<HandlerResult> {\n\t\t// Use shared handler for validation and ConfigStore registration\n\t\tconst result = await handleRegisterConnector(raw, this.configStore);\n\t\tif (result.status !== 200) {\n\t\t\treturn result;\n\t\t}\n\n\t\t// Extract the registered config from the ConfigStore\n\t\tconst connectors = await this.configStore.getConnectors();\n\t\tconst registeredName = (result.body as { name: string }).name;\n\t\tconst config = connectors[registeredName];\n\t\tif (!config) {\n\t\t\treturn result;\n\t\t}\n\n\t\t// Jira connectors use their own API-based poller\n\t\tif (config.type === \"jira\") {\n\t\t\ttry {\n\t\t\t\tconst { JiraSourcePoller } = await import(\"@lakesync/connector-jira\");\n\t\t\t\tconst ingestConfig = config.ingest ? { intervalMs: config.ingest.intervalMs } : undefined;\n\t\t\t\tconst poller = new JiraSourcePoller(config.jira, ingestConfig, config.name, this.gateway);\n\t\t\t\tpoller.start();\n\t\t\t\tthis.pollers.set(config.name, poller);\n\t\t\t\treturn result;\n\t\t\t} catch (err) {\n\t\t\t\tawait this.rollbackRegistration(connectors, registeredName);\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\treturn { status: 500, body: { error: `Failed to load Jira connector: ${message}` } };\n\t\t\t}\n\t\t}\n\n\t\t// Salesforce connectors use their own API-based poller\n\t\tif (config.type === \"salesforce\") {\n\t\t\ttry {\n\t\t\t\tconst { SalesforceSourcePoller } = await import(\"@lakesync/connector-salesforce\");\n\t\t\t\tconst ingestConfig = config.ingest ? { intervalMs: config.ingest.intervalMs } : undefined;\n\t\t\t\tconst poller = new SalesforceSourcePoller(\n\t\t\t\t\tconfig.salesforce,\n\t\t\t\t\tingestConfig,\n\t\t\t\t\tconfig.name,\n\t\t\t\t\tthis.gateway,\n\t\t\t\t);\n\t\t\t\tpoller.start();\n\t\t\t\tthis.pollers.set(config.name, poller);\n\t\t\t\treturn result;\n\t\t\t} catch (err) {\n\t\t\t\tawait this.rollbackRegistration(connectors, registeredName);\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 500,\n\t\t\t\t\tbody: { error: `Failed to load Salesforce connector: ${message}` },\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Database-based connectors (postgres, mysql, bigquery)\n\t\tconst adapterResult = createDatabaseAdapter(config);\n\t\tif (!adapterResult.ok) {\n\t\t\tawait this.rollbackRegistration(connectors, registeredName);\n\t\t\treturn { status: 500, body: { error: adapterResult.error.message } };\n\t\t}\n\n\t\tconst adapter = adapterResult.value;\n\t\tthis.gateway.registerSource(config.name, adapter);\n\t\tthis.adapters.set(config.name, adapter);\n\n\t\t// Auto-register as action handler if the adapter supports actions\n\t\tif (isActionHandler(adapter)) {\n\t\t\tthis.gateway.registerActionHandler(config.name, adapter);\n\t\t}\n\n\t\t// Start ingest poller if configured\n\t\tif (config.ingest) {\n\t\t\tconst queryFn = await createQueryFn(config);\n\t\t\tif (queryFn) {\n\t\t\t\tconst pollerConfig: IngestSourceConfig = {\n\t\t\t\t\tname: config.name,\n\t\t\t\t\tqueryFn,\n\t\t\t\t\ttables: config.ingest.tables.map((t) => ({\n\t\t\t\t\t\ttable: t.table,\n\t\t\t\t\t\tquery: t.query,\n\t\t\t\t\t\trowIdColumn: t.rowIdColumn,\n\t\t\t\t\t\tstrategy: t.strategy,\n\t\t\t\t\t})),\n\t\t\t\t\tintervalMs: config.ingest.intervalMs,\n\t\t\t\t};\n\t\t\t\tconst poller = new SourcePoller(pollerConfig, this.gateway);\n\t\t\t\tpoller.start();\n\t\t\t\tthis.pollers.set(config.name, poller);\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Unregister a connector by name.\n\t *\n\t * Stops poller, closes adapter, and unregisters from gateway.\n\t */\n\tasync unregister(name: string): Promise<HandlerResult> {\n\t\tconst result = await handleUnregisterConnector(name, this.configStore);\n\t\tif (result.status !== 200) {\n\t\t\treturn result;\n\t\t}\n\n\t\t// Stop poller if running\n\t\tconst poller = this.pollers.get(name);\n\t\tif (poller) {\n\t\t\tpoller.stop();\n\t\t\tthis.pollers.delete(name);\n\t\t}\n\n\t\t// Close adapter\n\t\tconst adapter = this.adapters.get(name);\n\t\tif (adapter) {\n\t\t\tawait adapter.close();\n\t\t\tthis.adapters.delete(name);\n\t\t}\n\n\t\t// Unregister from gateway\n\t\tthis.gateway.unregisterSource(name);\n\t\tthis.gateway.unregisterActionHandler(name);\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * List registered connectors with live polling status.\n\t */\n\tasync list(): Promise<HandlerResult> {\n\t\tconst result = await handleListConnectors(this.configStore);\n\t\tif (result.status !== 200) {\n\t\t\treturn result;\n\t\t}\n\n\t\tconst list = result.body as Array<{ name: string; type: string; hasIngest: boolean }>;\n\t\tconst augmented = list.map((c) => ({\n\t\t\t...c,\n\t\t\tisPolling: this.pollers.get(c.name)?.isRunning ?? false,\n\t\t}));\n\n\t\treturn { status: 200, body: augmented };\n\t}\n\n\t/** Stop all pollers and close all adapters. */\n\tasync stopAll(): Promise<void> {\n\t\tfor (const [, poller] of this.pollers) {\n\t\t\tpoller.stop();\n\t\t}\n\t\tthis.pollers.clear();\n\n\t\tfor (const [, adapter] of this.adapters) {\n\t\t\tawait adapter.close();\n\t\t}\n\t\tthis.adapters.clear();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal helpers\n\t// -----------------------------------------------------------------------\n\n\tprivate async rollbackRegistration(\n\t\tconnectors: Record<string, import(\"@lakesync/core\").ConnectorConfig>,\n\t\tname: string,\n\t): Promise<void> {\n\t\tdelete connectors[name];\n\t\tawait this.configStore.setConnectors(connectors);\n\t}\n}\n","// ---------------------------------------------------------------------------\n// CORS Middleware\n// ---------------------------------------------------------------------------\n\nimport type { ServerResponse } from \"node:http\";\n\n/** Configuration for CORS header generation. */\nexport interface CorsConfig {\n\t/** Allowed origins. When empty/omitted, all origins are reflected. */\n\tallowedOrigins?: string[];\n}\n\n/**\n * Build CORS response headers for the given origin.\n *\n * When `allowedOrigins` is set, only listed origins receive CORS headers.\n * When omitted, the request origin is reflected (or `*` if no origin header).\n */\nexport function corsHeaders(\n\torigin: string | null | undefined,\n\tconfig: CorsConfig,\n): Record<string, string> {\n\tconst { allowedOrigins } = config;\n\tlet allowOrigin = \"*\";\n\n\tif (allowedOrigins && allowedOrigins.length > 0) {\n\t\tif (origin && allowedOrigins.includes(origin)) {\n\t\t\tallowOrigin = origin;\n\t\t} else {\n\t\t\treturn {};\n\t\t}\n\t} else if (origin) {\n\t\tallowOrigin = origin;\n\t}\n\n\treturn {\n\t\t\"Access-Control-Allow-Origin\": allowOrigin,\n\t\t\"Access-Control-Allow-Methods\": \"GET, POST, DELETE, OPTIONS\",\n\t\t\"Access-Control-Allow-Headers\": \"Authorization, Content-Type\",\n\t\t\"Access-Control-Max-Age\": \"86400\",\n\t};\n}\n\n/** Handle CORS preflight (OPTIONS) request. Returns true if handled. */\nexport function handlePreflight(\n\tmethod: string,\n\tres: ServerResponse,\n\tcorsH: Record<string, string>,\n): boolean {\n\tif (method !== \"OPTIONS\") return false;\n\tres.writeHead(204, corsH);\n\tres.end();\n\treturn true;\n}\n","import type { RowDelta } from \"@lakesync/core\";\nimport { bigintReplacer, bigintReviver } from \"@lakesync/core\";\n\n/**\n * Persistence interface for buffering unflushed deltas across restarts.\n *\n * Implementations must be synchronous (no async) to avoid race conditions\n * during the push-then-flush cycle.\n */\nexport interface DeltaPersistence {\n\t/** Append a batch of deltas to the persistence store. */\n\tappendBatch(deltas: RowDelta[]): void;\n\t/** Load all persisted deltas. */\n\tloadAll(): RowDelta[];\n\t/** Clear all persisted deltas (after successful flush). */\n\tclear(): void;\n\t/** Release resources. */\n\tclose(): void;\n}\n\n/**\n * In-memory persistence (no durability across restarts).\n * Used as the default when `persistence` is \"memory\".\n */\nexport class MemoryPersistence implements DeltaPersistence {\n\tprivate buffer: RowDelta[] = [];\n\n\tappendBatch(deltas: RowDelta[]): void {\n\t\tthis.buffer.push(...deltas);\n\t}\n\n\tloadAll(): RowDelta[] {\n\t\treturn [...this.buffer];\n\t}\n\n\tclear(): void {\n\t\tthis.buffer = [];\n\t}\n\n\tclose(): void {\n\t\tthis.buffer = [];\n\t}\n}\n\n/**\n * SQLite-backed persistence using `better-sqlite3`.\n *\n * Stores deltas as JSON rows in a single table. On startup, loads all\n * rows back as RowDeltas. On flush success, truncates the table.\n */\nexport class SqlitePersistence implements DeltaPersistence {\n\tprivate db: import(\"better-sqlite3\").Database;\n\n\tconstructor(path: string) {\n\t\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\t\tconst Database = require(\"better-sqlite3\") as typeof import(\"better-sqlite3\");\n\t\tthis.db = new Database(path);\n\t\tthis.db.pragma(\"journal_mode = WAL\");\n\t\tthis.db.exec(\n\t\t\t\"CREATE TABLE IF NOT EXISTS unflushed_deltas (id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT NOT NULL)\",\n\t\t);\n\t}\n\n\tappendBatch(deltas: RowDelta[]): void {\n\t\tconst stmt = this.db.prepare(\"INSERT INTO unflushed_deltas (data) VALUES (?)\");\n\t\tconst tx = this.db.transaction(() => {\n\t\t\tfor (const delta of deltas) {\n\t\t\t\tstmt.run(JSON.stringify(delta, bigintReplacer));\n\t\t\t}\n\t\t});\n\t\ttx();\n\t}\n\n\tloadAll(): RowDelta[] {\n\t\tconst rows = this.db.prepare(\"SELECT data FROM unflushed_deltas ORDER BY id\").all() as Array<{\n\t\t\tdata: string;\n\t\t}>;\n\t\treturn rows.map((row) => JSON.parse(row.data, bigintReviver) as RowDelta);\n\t}\n\n\tclear(): void {\n\t\tthis.db.exec(\"DELETE FROM unflushed_deltas\");\n\t}\n\n\tclose(): void {\n\t\tthis.db.close();\n\t}\n}\n","// ---------------------------------------------------------------------------\n// Router — URL pattern matching and route dispatch\n// ---------------------------------------------------------------------------\n\n/** Matched route information. */\nexport interface RouteMatch {\n\tgatewayId: string;\n\taction: string;\n\t/** Extra route parameters (e.g. connector name from DELETE path). */\n\tconnectorName?: string;\n}\n\n/** Route definition: [method, pattern, action, captureConnectorName?] */\ntype RouteEntry = [string, RegExp, string, boolean?];\n\n/** Route definitions for the gateway server. */\nconst ROUTES: ReadonlyArray<RouteEntry> = [\n\t[\"POST\", /^\\/sync\\/([^/]+)\\/push$/, \"push\"],\n\t[\"GET\", /^\\/sync\\/([^/]+)\\/pull$/, \"pull\"],\n\t[\"POST\", /^\\/sync\\/([^/]+)\\/action$/, \"action\"],\n\t[\"GET\", /^\\/sync\\/([^/]+)\\/actions$/, \"describe-actions\"],\n\t[\"GET\", /^\\/sync\\/([^/]+)\\/ws$/, \"ws\"],\n\t[\"POST\", /^\\/admin\\/flush\\/([^/]+)$/, \"flush\"],\n\t[\"POST\", /^\\/admin\\/schema\\/([^/]+)$/, \"schema\"],\n\t[\"POST\", /^\\/admin\\/sync-rules\\/([^/]+)$/, \"sync-rules\"],\n\t[\"POST\", /^\\/admin\\/connectors\\/([^/]+)$/, \"register-connector\"],\n\t[\"GET\", /^\\/admin\\/connectors\\/([^/]+)$/, \"list-connectors\"],\n\t[\"DELETE\", /^\\/admin\\/connectors\\/([^/]+)\\/([^/]+)$/, \"unregister-connector\", true],\n\t[\"GET\", /^\\/admin\\/metrics\\/([^/]+)$/, \"metrics\"],\n];\n\n/**\n * Match a request pathname and method against the route table.\n *\n * Returns the matched route or null if no route matches.\n */\nexport function matchRoute(pathname: string, method: string): RouteMatch | null {\n\tfor (const [m, pattern, action, hasConnector] of ROUTES) {\n\t\tif (method !== m) continue;\n\t\tconst match = pathname.match(pattern);\n\t\tif (!match) continue;\n\t\treturn {\n\t\t\tgatewayId: match[1]!,\n\t\t\taction,\n\t\t\t...(hasConnector ? { connectorName: match[2]! } : {}),\n\t\t};\n\t}\n\treturn null;\n}\n","import { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport type { DatabaseAdapter, LakeAdapter } from \"@lakesync/adapter\";\nimport type { HLCTimestamp, RowDelta, SyncResponse } from \"@lakesync/core\";\nimport { bigintReplacer } from \"@lakesync/core\";\nimport type { ConfigStore, HandlerResult } from \"@lakesync/gateway\";\nimport {\n\tDEFAULT_MAX_BUFFER_AGE_MS,\n\tDEFAULT_MAX_BUFFER_BYTES,\n\thandleActionRequest,\n\thandleFlushRequest,\n\thandleListConnectorTypes,\n\thandleMetrics,\n\thandlePullRequest,\n\thandlePushRequest,\n\thandleSaveSchema,\n\thandleSaveSyncRules,\n\tMAX_PUSH_PAYLOAD_BYTES,\n\tMemoryConfigStore,\n\tSyncGateway,\n} from \"@lakesync/gateway\";\nimport type { AuthClaims } from \"./auth\";\nimport { authenticateRequest } from \"./auth-middleware\";\nimport type { DistributedLock } from \"./cluster\";\nimport { ConnectorManager } from \"./connector-manager\";\nimport { corsHeaders, handlePreflight } from \"./cors-middleware\";\nimport { SourcePoller } from \"./ingest/poller\";\nimport type { IngestSourceConfig } from \"./ingest/types\";\nimport { type DeltaPersistence, MemoryPersistence, SqlitePersistence } from \"./persistence\";\nimport { matchRoute } from \"./router\";\nimport { SharedBuffer } from \"./shared-buffer\";\nimport { WebSocketManager } from \"./ws-manager\";\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\n/** Configuration for the self-hosted gateway server. */\nexport interface GatewayServerConfig {\n\t/** Port to listen on (default 3000). */\n\tport?: number;\n\t/** Unique gateway identifier. */\n\tgatewayId: string;\n\t/** Storage adapter — LakeAdapter (S3/R2) or DatabaseAdapter (Postgres/MySQL). */\n\tadapter?: LakeAdapter | DatabaseAdapter;\n\t/** Maximum buffer size in bytes before triggering flush (default 4 MiB). */\n\tmaxBufferBytes?: number;\n\t/** Maximum buffer age in milliseconds before triggering flush (default 30s). */\n\tmaxBufferAgeMs?: number;\n\t/** HMAC-SHA256 secret for JWT verification. When omitted, auth is disabled. */\n\tjwtSecret?: string;\n\t/** Interval in milliseconds between periodic flushes (default 30s). */\n\tflushIntervalMs?: number;\n\t/** CORS allowed origins. When omitted, all origins are reflected. */\n\tallowedOrigins?: string[];\n\t/** DeltaBuffer persistence strategy (default \"memory\"). */\n\tpersistence?: \"memory\" | \"sqlite\";\n\t/** Path to the SQLite file when `persistence` is \"sqlite\" (default \"./lakesync-buffer.sqlite\"). */\n\tsqlitePath?: string;\n\t/** Polling ingest sources. Each source is polled independently. */\n\tingestSources?: IngestSourceConfig[];\n\t/** Optional clustering configuration for multi-instance deployment. */\n\tcluster?: {\n\t\t/** Distributed lock for coordinated flush. */\n\t\tlock: DistributedLock;\n\t\t/** Shared database adapter for cross-instance visibility. */\n\t\tsharedAdapter: DatabaseAdapter;\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_PORT = 3000;\nconst DEFAULT_FLUSH_INTERVAL_MS = 30_000;\n\n// ---------------------------------------------------------------------------\n// Node HTTP helpers\n// ---------------------------------------------------------------------------\n\n/** Read the full request body as a string. */\nfunction readBody(req: IncomingMessage): Promise<string> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst chunks: Buffer[] = [];\n\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\treq.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf-8\")));\n\t\treq.on(\"error\", reject);\n\t});\n}\n\n/** Send a JSON response. */\nfunction sendJson(\n\tres: ServerResponse,\n\tbody: unknown,\n\tstatus = 200,\n\textraHeaders?: Record<string, string>,\n): void {\n\tconst json = JSON.stringify(body, bigintReplacer);\n\tres.writeHead(status, {\n\t\t\"Content-Type\": \"application/json\",\n\t\t...extraHeaders,\n\t});\n\tres.end(json);\n}\n\n/** Send a JSON error response. */\nfunction sendError(\n\tres: ServerResponse,\n\tmessage: string,\n\tstatus: number,\n\textraHeaders?: Record<string, string>,\n): void {\n\tsendJson(res, { error: message }, status, extraHeaders);\n}\n\n/** Send a HandlerResult as HTTP response. */\nfunction sendResult(\n\tres: ServerResponse,\n\tresult: HandlerResult,\n\tcorsH: Record<string, string>,\n): void {\n\tsendJson(res, result.body, result.status, corsH);\n}\n\n// ---------------------------------------------------------------------------\n// GatewayServer\n// ---------------------------------------------------------------------------\n\n/**\n * Self-hosted HTTP gateway server wrapping {@link SyncGateway}.\n *\n * Composes extracted modules: cors-middleware, auth-middleware, router,\n * ws-manager, and connector-manager. Request handling follows the\n * pipeline: cors -> auth -> route -> handler.\n *\n * @example\n * ```ts\n * const server = new GatewayServer({\n * gatewayId: \"my-gateway\",\n * port: 3000,\n * adapter: new PostgresAdapter({ connectionString: \"...\" }),\n * jwtSecret: process.env.JWT_SECRET,\n * });\n * await server.start();\n * ```\n */\nexport class GatewayServer {\n\tprivate readonly gateway: SyncGateway;\n\tprivate readonly config: Required<\n\t\tPick<GatewayServerConfig, \"port\" | \"gatewayId\" | \"flushIntervalMs\">\n\t> &\n\t\tGatewayServerConfig;\n\tprivate readonly configStore: ConfigStore;\n\tprivate readonly persistence: DeltaPersistence;\n\tprivate readonly connectors: ConnectorManager;\n\tprivate readonly sharedBuffer: SharedBuffer | null;\n\n\tprivate httpServer: Server | null = null;\n\tprivate wsManager: WebSocketManager | null = null;\n\tprivate flushTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate resolvedPort = 0;\n\tprivate pollers: SourcePoller[] = [];\n\n\tconstructor(config: GatewayServerConfig) {\n\t\tthis.config = {\n\t\t\tport: config.port ?? DEFAULT_PORT,\n\t\t\tflushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,\n\t\t\t...config,\n\t\t};\n\n\t\tthis.gateway = new SyncGateway(\n\t\t\t{\n\t\t\t\tgatewayId: config.gatewayId,\n\t\t\t\tmaxBufferBytes: config.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES,\n\t\t\t\tmaxBufferAgeMs: config.maxBufferAgeMs ?? DEFAULT_MAX_BUFFER_AGE_MS,\n\t\t\t},\n\t\t\tconfig.adapter,\n\t\t);\n\n\t\tthis.configStore = new MemoryConfigStore();\n\n\t\tthis.persistence =\n\t\t\tconfig.persistence === \"sqlite\"\n\t\t\t\t? new SqlitePersistence(config.sqlitePath ?? \"./lakesync-buffer.sqlite\")\n\t\t\t\t: new MemoryPersistence();\n\n\t\tthis.sharedBuffer = config.cluster ? new SharedBuffer(config.cluster.sharedAdapter) : null;\n\n\t\tthis.connectors = new ConnectorManager(this.configStore, this.gateway);\n\t}\n\n\t/**\n\t * Start the HTTP server and periodic flush timer.\n\t *\n\t * Rehydrates unflushed deltas from the persistence layer directly\n\t * into the buffer (bypassing push validation) before accepting\n\t * connections.\n\t */\n\tasync start(): Promise<void> {\n\t\t// Rehydrate unflushed deltas directly into the buffer\n\t\tconst persisted = this.persistence.loadAll();\n\t\tif (persisted.length > 0) {\n\t\t\tfor (const delta of persisted) {\n\t\t\t\tthis.gateway.buffer.append(delta);\n\t\t\t}\n\t\t\tthis.persistence.clear();\n\t\t}\n\n\t\tthis.httpServer = createServer((req, res) => {\n\t\t\tvoid this.handleRequest(req, res);\n\t\t});\n\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tthis.httpServer!.listen(this.config.port, () => {\n\t\t\t\tconst addr = this.httpServer!.address();\n\t\t\t\tif (addr && typeof addr === \"object\") {\n\t\t\t\t\tthis.resolvedPort = addr.port;\n\t\t\t\t}\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\n\t\t// WebSocket manager\n\t\tthis.wsManager = new WebSocketManager(\n\t\t\tthis.gateway,\n\t\t\tthis.configStore,\n\t\t\tthis.config.gatewayId,\n\t\t\tthis.config.jwtSecret,\n\t\t);\n\t\tthis.wsManager.attach(this.httpServer);\n\n\t\t// Periodic flush\n\t\tthis.flushTimer = setInterval(() => {\n\t\t\tvoid this.periodicFlush();\n\t\t}, this.config.flushIntervalMs);\n\n\t\t// Start ingest pollers\n\t\tif (this.config.ingestSources) {\n\t\t\tfor (const source of this.config.ingestSources) {\n\t\t\t\tconst poller = new SourcePoller(source, this.gateway);\n\t\t\t\tpoller.start();\n\t\t\t\tthis.pollers.push(poller);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Stop the server, pollers, connectors, and WebSocket connections. */\n\tasync stop(): Promise<void> {\n\t\t// Stop dynamic connectors (pollers + adapters)\n\t\tawait this.connectors.stopAll();\n\n\t\t// Stop ingest pollers\n\t\tfor (const poller of this.pollers) {\n\t\t\tpoller.stop();\n\t\t}\n\t\tthis.pollers = [];\n\n\t\tif (this.flushTimer) {\n\t\t\tclearInterval(this.flushTimer);\n\t\t\tthis.flushTimer = null;\n\t\t}\n\n\t\t// Close WebSocket connections\n\t\tif (this.wsManager) {\n\t\t\tthis.wsManager.close();\n\t\t\tthis.wsManager = null;\n\t\t}\n\n\t\tif (this.httpServer) {\n\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\tthis.httpServer!.close(() => resolve());\n\t\t\t});\n\t\t\tthis.httpServer = null;\n\t\t}\n\n\t\tthis.persistence.close();\n\t}\n\n\t/** The port the server is listening on (available after start). */\n\tget port(): number {\n\t\treturn this.resolvedPort || this.config.port;\n\t}\n\n\t/** The underlying SyncGateway instance for direct access. */\n\tget gatewayInstance(): SyncGateway {\n\t\treturn this.gateway;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Request handling — cors -> auth -> route -> handler\n\t// -----------------------------------------------------------------------\n\n\tprivate async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst method = req.method ?? \"GET\";\n\t\tconst rawUrl = req.url ?? \"/\";\n\t\tconst url = new URL(rawUrl, `http://${req.headers.host ?? \"localhost\"}`);\n\t\tconst pathname = url.pathname;\n\t\tconst origin = req.headers.origin ?? null;\n\n\t\t// Step 1: CORS headers\n\t\tconst corsH = corsHeaders(origin, { allowedOrigins: this.config.allowedOrigins });\n\n\t\t// Step 2: CORS preflight\n\t\tif (handlePreflight(method, res, corsH)) return;\n\n\t\t// Step 3: Static routes (no auth)\n\t\tif (pathname === \"/health\" && method === \"GET\") {\n\t\t\tsendJson(res, { status: \"ok\" }, 200, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\tif (pathname === \"/connectors/types\" && method === \"GET\") {\n\t\t\tconst result = handleListConnectorTypes();\n\t\t\tsendResult(res, result, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\t// Step 4: Route matching\n\t\tconst route = matchRoute(pathname, method);\n\t\tif (!route) {\n\t\t\tsendError(res, \"Not found\", 404, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\tif (route.gatewayId !== this.config.gatewayId) {\n\t\t\tsendError(res, \"Gateway ID mismatch\", 404, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\t// Step 5: Authentication\n\t\tconst authResult = await authenticateRequest(\n\t\t\treq,\n\t\t\troute.gatewayId,\n\t\t\troute.action,\n\t\t\tthis.config.jwtSecret,\n\t\t);\n\t\tif (!authResult.authenticated) {\n\t\t\tsendError(res, authResult.message, authResult.status, corsH);\n\t\t\treturn;\n\t\t}\n\t\tconst auth: AuthClaims | undefined = this.config.jwtSecret ? authResult.claims : undefined;\n\n\t\t// Step 6: Route dispatch\n\t\tswitch (route.action) {\n\t\t\tcase \"push\":\n\t\t\t\tawait this.handlePush(req, res, corsH, auth);\n\t\t\t\tbreak;\n\t\t\tcase \"pull\":\n\t\t\t\tawait this.handlePull(url, res, corsH, auth);\n\t\t\t\tbreak;\n\t\t\tcase \"action\":\n\t\t\t\tawait this.handleAction(req, res, corsH, auth);\n\t\t\t\tbreak;\n\t\t\tcase \"describe-actions\":\n\t\t\t\tthis.handleDescribeActions(res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"flush\":\n\t\t\t\tawait this.handleFlush(res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"schema\":\n\t\t\t\tawait this.handleSaveSchemaRoute(req, res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"sync-rules\":\n\t\t\t\tawait this.handleSaveSyncRulesRoute(req, res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"register-connector\":\n\t\t\t\tawait this.handleRegisterConnectorRoute(req, res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"unregister-connector\":\n\t\t\t\tawait this.handleUnregisterConnectorRoute(route.connectorName!, res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"list-connectors\":\n\t\t\t\tawait this.handleListConnectorsRoute(res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"metrics\":\n\t\t\t\tthis.handleMetricsRoute(res, corsH);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tsendError(res, \"Not found\", 404, corsH);\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Route handlers — thin wrappers delegating to shared handlers or modules\n\t// -----------------------------------------------------------------------\n\n\tprivate async handlePush(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t\tauth?: AuthClaims,\n\t): Promise<void> {\n\t\tconst contentLength = Number(req.headers[\"content-length\"] ?? \"0\");\n\t\tif (contentLength > MAX_PUSH_PAYLOAD_BYTES) {\n\t\t\tsendError(res, \"Payload too large (max 1 MiB)\", 413, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\tconst raw = await readBody(req);\n\t\tconst result = handlePushRequest(this.gateway, raw, auth?.clientId, {\n\t\t\tpersistBatch: (deltas) => this.persistence.appendBatch(deltas),\n\t\t\tclearPersistence: () => this.persistence.clear(),\n\t\t\tbroadcastFn: (deltas, serverHlc, excludeClientId) =>\n\t\t\t\tthis.wsManager?.broadcastDeltas(deltas, serverHlc, excludeClientId),\n\t\t});\n\n\t\t// Shared buffer write-through for cross-instance visibility\n\t\tif (result.status === 200 && this.sharedBuffer) {\n\t\t\tconst pushResult = result.body as { deltas: RowDelta[]; serverHlc: HLCTimestamp };\n\t\t\tif (pushResult.deltas.length > 0) {\n\t\t\t\tawait this.sharedBuffer.writeThroughPush(pushResult.deltas);\n\t\t\t}\n\t\t}\n\n\t\tsendResult(res, result, corsH);\n\t}\n\n\tprivate async handlePull(\n\t\turl: URL,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t\tauth?: AuthClaims,\n\t): Promise<void> {\n\t\tconst syncRules = await this.configStore.getSyncRules(this.config.gatewayId);\n\t\tconst result = await handlePullRequest(\n\t\t\tthis.gateway,\n\t\t\t{\n\t\t\t\tsince: url.searchParams.get(\"since\"),\n\t\t\t\tclientId: url.searchParams.get(\"clientId\"),\n\t\t\t\tlimit: url.searchParams.get(\"limit\"),\n\t\t\t\tsource: url.searchParams.get(\"source\"),\n\t\t\t},\n\t\t\tauth?.customClaims,\n\t\t\tsyncRules,\n\t\t);\n\n\t\t// Merge with shared buffer for cross-instance visibility\n\t\tlet body = result.body;\n\t\tif (result.status === 200 && this.sharedBuffer) {\n\t\t\tconst sinceParam = url.searchParams.get(\"since\");\n\t\t\tif (sinceParam) {\n\t\t\t\ttry {\n\t\t\t\t\tconst sinceHlc = BigInt(sinceParam) as HLCTimestamp;\n\t\t\t\t\tbody = await this.sharedBuffer.mergePull(body as SyncResponse, sinceHlc);\n\t\t\t\t} catch {\n\t\t\t\t\t// If since parsing fails, pull handler already returned an error\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsendJson(res, body, result.status, corsH);\n\t}\n\n\tprivate async handleAction(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t\tauth?: AuthClaims,\n\t): Promise<void> {\n\t\tconst raw = await readBody(req);\n\t\tconst result = await handleActionRequest(this.gateway, raw, auth?.clientId, auth?.customClaims);\n\t\tsendResult(res, result, corsH);\n\t}\n\n\tprivate handleDescribeActions(res: ServerResponse, corsH: Record<string, string>): void {\n\t\tsendJson(res, this.gateway.describeActions(), 200, corsH);\n\t}\n\n\tprivate async handleFlush(res: ServerResponse, corsH: Record<string, string>): Promise<void> {\n\t\tconst result = await handleFlushRequest(this.gateway, {\n\t\t\tclearPersistence: () => this.persistence.clear(),\n\t\t});\n\t\tsendResult(res, result, corsH);\n\t}\n\n\tprivate async handleSaveSchemaRoute(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t): Promise<void> {\n\t\tconst raw = await readBody(req);\n\t\tconst result = await handleSaveSchema(raw, this.configStore, this.config.gatewayId);\n\t\tsendResult(res, result, corsH);\n\t}\n\n\tprivate async handleSaveSyncRulesRoute(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t): Promise<void> {\n\t\tconst raw = await readBody(req);\n\t\tconst result = await handleSaveSyncRules(raw, this.configStore, this.config.gatewayId);\n\t\tsendResult(res, result, corsH);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Connector management — delegates to ConnectorManager\n\t// -----------------------------------------------------------------------\n\n\tprivate async handleRegisterConnectorRoute(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t): Promise<void> {\n\t\tconst raw = await readBody(req);\n\t\tconst result = await this.connectors.register(raw);\n\t\tsendResult(res, result, corsH);\n\t}\n\n\tprivate async handleUnregisterConnectorRoute(\n\t\tname: string,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t): Promise<void> {\n\t\tconst result = await this.connectors.unregister(name);\n\t\tsendResult(res, result, corsH);\n\t}\n\n\tprivate async handleListConnectorsRoute(\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t): Promise<void> {\n\t\tconst result = await this.connectors.list();\n\t\tsendJson(res, result.body, result.status, corsH);\n\t}\n\n\tprivate handleMetricsRoute(res: ServerResponse, corsH: Record<string, string>): void {\n\t\tconst result = handleMetrics(this.gateway, { process: process.memoryUsage() });\n\t\tsendResult(res, result, corsH);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Periodic flush\n\t// -----------------------------------------------------------------------\n\n\tprivate async periodicFlush(): Promise<void> {\n\t\tif (this.gateway.bufferStats.logSize === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst lock = this.config.cluster?.lock;\n\t\tconst lockKey = `flush:${this.config.gatewayId}`;\n\t\tif (lock) {\n\t\t\tconst acquired = await lock.acquire(lockKey, 30_000);\n\t\t\tif (!acquired) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this.gateway.flush();\n\t\t\tif (result.ok) {\n\t\t\t\tthis.persistence.clear();\n\t\t\t}\n\t\t} finally {\n\t\t\tif (lock) {\n\t\t\t\tawait lock.release(lockKey);\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { DatabaseAdapter } from \"@lakesync/adapter\";\nimport type { HLCTimestamp, RowDelta, SyncResponse } from \"@lakesync/core\";\n\n/**\n * Write-through buffer that pushes to both the in-memory gateway buffer\n * and a shared database adapter for cross-instance visibility.\n *\n * Pull merges in-memory buffer results with adapter query results,\n * deduplicating by deltaId.\n */\nexport class SharedBuffer {\n\tconstructor(private readonly sharedAdapter: DatabaseAdapter) {}\n\n\t/**\n\t * Write-through push: write to shared adapter for cross-instance visibility.\n\t *\n\t * Gateway buffer handles fast reads; shared adapter handles\n\t * cross-instance visibility and durability.\n\t */\n\tasync writeThroughPush(deltas: RowDelta[]): Promise<void> {\n\t\t// Best-effort write to shared adapter (don't fail the push if this fails)\n\t\ttry {\n\t\t\tawait this.sharedAdapter.insertDeltas(deltas);\n\t\t} catch {\n\t\t\t// Shared adapter write failed — local buffer is still authoritative\n\t\t}\n\t}\n\n\t/**\n\t * Merge pull: combine local buffer results with shared adapter results.\n\t *\n\t * Deduplicates by deltaId to avoid returning the same delta twice.\n\t */\n\tasync mergePull(localResult: SyncResponse, sinceHlc: HLCTimestamp): Promise<SyncResponse> {\n\t\ttry {\n\t\t\tconst adapterResult = await this.sharedAdapter.queryDeltasSince(sinceHlc);\n\t\t\tif (!adapterResult.ok) {\n\t\t\t\treturn localResult; // Fallback to local-only\n\t\t\t}\n\n\t\t\t// Deduplicate by deltaId\n\t\t\tconst seenIds = new Set(localResult.deltas.map((d) => d.deltaId));\n\t\t\tconst additional = adapterResult.value.filter((d) => !seenIds.has(d.deltaId));\n\n\t\t\tif (additional.length === 0) {\n\t\t\t\treturn localResult;\n\t\t\t}\n\n\t\t\tconst merged = [...localResult.deltas, ...additional];\n\t\t\tmerged.sort((a, b) => (a.hlc < b.hlc ? -1 : a.hlc > b.hlc ? 1 : 0));\n\n\t\t\treturn {\n\t\t\t\tdeltas: merged,\n\t\t\t\tserverHlc: localResult.serverHlc,\n\t\t\t\thasMore: true,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn localResult; // Fallback to local-only on error\n\t\t}\n\t}\n}\n","// ---------------------------------------------------------------------------\n// WebSocket Manager — upgrade, message parsing, broadcast, client tracking\n// ---------------------------------------------------------------------------\n\nimport type { IncomingMessage, Server } from \"node:http\";\nimport type { HLCTimestamp, ResolvedClaims, RowDelta, SyncRulesContext } from \"@lakesync/core\";\nimport { filterDeltas } from \"@lakesync/core\";\nimport type { ConfigStore, SyncGateway } from \"@lakesync/gateway\";\nimport {\n\tdecodeSyncPull,\n\tdecodeSyncPush,\n\tencodeBroadcastFrame,\n\tencodeSyncResponse,\n\tTAG_SYNC_PULL,\n\tTAG_SYNC_PUSH,\n} from \"@lakesync/proto\";\nimport { WebSocketServer, type WebSocket as WsWebSocket } from \"ws\";\nimport type { AuthClaims } from \"./auth\";\nimport { verifyToken } from \"./auth\";\nimport { extractBearerToken } from \"./auth-middleware\";\n\n/** Metadata stored for each connected WebSocket client. */\ninterface WsClientMeta {\n\tclientId: string;\n\tclaims: Record<string, unknown>;\n}\n\n/**\n * Manages WebSocket connections, message handling, and broadcasting.\n *\n * Decouples the WebSocket protocol from the HTTP server lifecycle.\n */\nexport class WebSocketManager {\n\tprivate readonly wss: WebSocketServer;\n\tprivate readonly clients = new Map<WsWebSocket, WsClientMeta>();\n\n\tconstructor(\n\t\tprivate readonly gateway: SyncGateway,\n\t\tprivate readonly configStore: ConfigStore,\n\t\tprivate readonly gatewayId: string,\n\t\tprivate readonly jwtSecret: string | undefined,\n\t) {\n\t\tthis.wss = new WebSocketServer({ noServer: true });\n\t}\n\n\t/** Attach upgrade listener to an HTTP server. */\n\tattach(httpServer: Server): void {\n\t\thttpServer.on(\"upgrade\", (req, socket, head) => {\n\t\t\tvoid this.handleUpgrade(req, socket, head);\n\t\t});\n\t}\n\n\t/**\n\t * Broadcast ingested deltas to all connected WebSocket clients except the sender.\n\t */\n\tbroadcastDeltas(deltas: RowDelta[], serverHlc: HLCTimestamp, excludeClientId: string): void {\n\t\tif (deltas.length === 0) return;\n\t\tvoid this.broadcastDeltasAsync(deltas, serverHlc, excludeClientId);\n\t}\n\n\t/** Close all connections and shut down the WebSocket server. */\n\tclose(): void {\n\t\tfor (const ws of this.clients.keys()) {\n\t\t\ttry {\n\t\t\t\tws.close(1001, \"Server shutting down\");\n\t\t\t} catch {\n\t\t\t\t/* ignore */\n\t\t\t}\n\t\t}\n\t\tthis.clients.clear();\n\t\tthis.wss.close();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Upgrade handling\n\t// -----------------------------------------------------------------------\n\n\tprivate async handleUpgrade(\n\t\treq: IncomingMessage,\n\t\tsocket: import(\"node:stream\").Duplex,\n\t\thead: Buffer,\n\t): Promise<void> {\n\t\tconst url = new URL(req.url ?? \"/\", `http://${req.headers.host ?? \"localhost\"}`);\n\n\t\t// Authenticate\n\t\tlet token = extractBearerToken(req);\n\t\tif (!token) {\n\t\t\ttoken = url.searchParams.get(\"token\");\n\t\t}\n\n\t\tif (!token && this.jwtSecret) {\n\t\t\tsocket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\n\t\tlet auth: AuthClaims | undefined;\n\t\tif (this.jwtSecret && token) {\n\t\t\tconst authResult = await verifyToken(token, this.jwtSecret);\n\t\t\tif (!authResult.ok) {\n\t\t\t\tsocket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n\t\t\t\tsocket.destroy();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tauth = authResult.value;\n\n\t\t\t// Verify gateway ID\n\t\t\tconst gwMatch = url.pathname.match(/^\\/sync\\/([^/]+)\\/ws$/);\n\t\t\tif (!gwMatch || gwMatch[1] !== auth.gatewayId) {\n\t\t\t\tsocket.write(\"HTTP/1.1 403 Forbidden\\r\\n\\r\\n\");\n\t\t\t\tsocket.destroy();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tthis.wss.handleUpgrade(req, socket, head, (ws) => {\n\t\t\tconst clientId = auth?.clientId ?? `anon-${crypto.randomUUID()}`;\n\t\t\tconst claims: Record<string, unknown> = auth?.customClaims ?? {};\n\n\t\t\tthis.clients.set(ws, { clientId, claims });\n\n\t\t\tws.on(\"message\", (data: Buffer) => {\n\t\t\t\tvoid this.handleMessage(ws, data, clientId, claims);\n\t\t\t});\n\n\t\t\tws.on(\"close\", () => {\n\t\t\t\tthis.clients.delete(ws);\n\t\t\t});\n\n\t\t\tws.on(\"error\", () => {\n\t\t\t\tthis.clients.delete(ws);\n\t\t\t});\n\t\t});\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Message handling\n\t// -----------------------------------------------------------------------\n\n\tprivate async handleMessage(\n\t\tws: WsWebSocket,\n\t\tdata: Buffer,\n\t\tclientId: string,\n\t\tclaims: Record<string, unknown>,\n\t): Promise<void> {\n\t\tconst bytes = new Uint8Array(data);\n\t\tif (bytes.length < 2) {\n\t\t\tws.close(1002, \"Message too short\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst tag = bytes[0];\n\t\tconst payload = bytes.subarray(1);\n\n\t\tif (tag === TAG_SYNC_PUSH) {\n\t\t\tconst decoded = decodeSyncPush(payload);\n\t\t\tif (!decoded.ok) {\n\t\t\t\tws.close(1008, decoded.error.message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst result = this.gateway.handlePush(decoded.value);\n\t\t\tif (!result.ok) {\n\t\t\t\tws.close(1008, result.error.message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Send push response\n\t\t\tconst response = encodeSyncResponse({\n\t\t\t\tdeltas: [],\n\t\t\t\tserverHlc: result.value.serverHlc,\n\t\t\t\thasMore: false,\n\t\t\t});\n\t\t\tif (response.ok) {\n\t\t\t\tws.send(response.value);\n\t\t\t}\n\n\t\t\t// Broadcast to other clients\n\t\t\tthis.broadcastDeltas(result.value.deltas, result.value.serverHlc, clientId);\n\t\t} else if (tag === TAG_SYNC_PULL) {\n\t\t\tconst decoded = decodeSyncPull(payload);\n\t\t\tif (!decoded.ok) {\n\t\t\t\tws.close(1008, decoded.error.message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst context = await this.buildSyncRulesContext(claims);\n\t\t\tconst pullResult = this.gateway.handlePull(\n\t\t\t\tdecoded.value,\n\t\t\t\tcontext,\n\t\t\t) as import(\"@lakesync/core\").Result<\n\t\t\t\timport(\"@lakesync/core\").SyncResponse,\n\t\t\t\t{ message: string }\n\t\t\t>;\n\t\t\tif (!pullResult.ok) {\n\t\t\t\tws.close(1008, pullResult.error.message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst response = encodeSyncResponse(pullResult.value);\n\t\t\tif (response.ok) {\n\t\t\t\tws.send(response.value);\n\t\t\t}\n\t\t} else {\n\t\t\tws.close(1002, `Unknown message tag: 0x${tag!.toString(16).padStart(2, \"0\")}`);\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Sync rules\n\t// -----------------------------------------------------------------------\n\n\tprivate async buildSyncRulesContext(\n\t\tclaims: Record<string, unknown>,\n\t): Promise<SyncRulesContext | undefined> {\n\t\tconst rules = await this.configStore.getSyncRules(this.gatewayId);\n\t\tif (!rules || rules.buckets.length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn { claims: claims as ResolvedClaims, rules };\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Broadcast\n\t// -----------------------------------------------------------------------\n\n\tprivate async broadcastDeltasAsync(\n\t\tdeltas: RowDelta[],\n\t\tserverHlc: HLCTimestamp,\n\t\texcludeClientId: string,\n\t): Promise<void> {\n\t\tconst rules = await this.configStore.getSyncRules(this.gatewayId);\n\n\t\tfor (const [ws, meta] of this.clients) {\n\t\t\tif (meta.clientId === excludeClientId) continue;\n\n\t\t\ttry {\n\t\t\t\tlet filtered: RowDelta[] = deltas;\n\t\t\t\tif (rules && rules.buckets.length > 0) {\n\t\t\t\t\tconst context: SyncRulesContext = {\n\t\t\t\t\t\tclaims: meta.claims as ResolvedClaims,\n\t\t\t\t\t\trules,\n\t\t\t\t\t};\n\t\t\t\t\tfiltered = filterDeltas(deltas, context);\n\t\t\t\t}\n\n\t\t\t\tif (filtered.length === 0) continue;\n\n\t\t\t\tconst frame = encodeBroadcastFrame({\n\t\t\t\t\tdeltas: filtered,\n\t\t\t\t\tserverHlc,\n\t\t\t\t\thasMore: false,\n\t\t\t\t});\n\n\t\t\t\tif (!frame.ok) continue;\n\t\t\t\tws.send(frame.value);\n\t\t\t} catch {\n\t\t\t\t// Socket may have closed -- silently skip\n\t\t\t}\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../gateway-server/src/auth-middleware.ts","../../gateway-server/src/cluster.ts","../../gateway-server/src/ingest/poller.ts","../../gateway-server/src/connector-manager.ts","../../gateway-server/src/cors-middleware.ts","../../gateway-server/src/logger.ts","../../gateway-server/src/metrics.ts","../../gateway-server/src/persistence.ts","../../gateway-server/src/rate-limiter.ts","../../gateway-server/src/router.ts","../../gateway-server/src/server.ts","../../gateway-server/src/shared-buffer.ts","../../gateway-server/src/ws-manager.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Auth Middleware — JWT validation for HTTP requests\n// ---------------------------------------------------------------------------\n\nimport type { IncomingMessage } from \"node:http\";\nimport type { AuthClaims } from \"./auth\";\nimport { verifyToken } from \"./auth\";\n\n/** Result of authentication: either authenticated claims or an error. */\nexport type AuthResult =\n\t| { authenticated: true; claims: AuthClaims }\n\t| { authenticated: false; status: number; message: string };\n\n/** Set of actions that require admin role. */\nconst ADMIN_ACTIONS = new Set([\n\t\"flush\",\n\t\"schema\",\n\t\"sync-rules\",\n\t\"register-connector\",\n\t\"unregister-connector\",\n\t\"list-connectors\",\n\t\"metrics\",\n]);\n\n/**\n * Extract the Bearer token from an Authorization header.\n * Returns the raw token string, or null if missing/malformed.\n */\nexport function extractBearerToken(req: IncomingMessage): string | null {\n\tconst header = req.headers.authorization;\n\tif (!header) return null;\n\tconst match = header.match(/^Bearer\\s+(\\S+)$/);\n\treturn match?.[1] ?? null;\n}\n\n/**\n * Authenticate an HTTP request.\n *\n * When `jwtSecret` is undefined, auth is disabled and all requests pass.\n * Otherwise validates the Bearer token, checks gateway ID, and enforces\n * admin role for admin actions.\n */\nexport async function authenticateRequest(\n\treq: IncomingMessage,\n\trouteGatewayId: string,\n\trouteAction: string,\n\tjwtSecret: string | undefined,\n): Promise<AuthResult> {\n\tif (!jwtSecret) {\n\t\treturn { authenticated: true, claims: undefined as unknown as AuthClaims };\n\t}\n\n\tconst token = extractBearerToken(req);\n\tif (!token) {\n\t\treturn { authenticated: false, status: 401, message: \"Missing Bearer token\" };\n\t}\n\n\tconst authResult = await verifyToken(token, jwtSecret);\n\tif (!authResult.ok) {\n\t\treturn { authenticated: false, status: 401, message: authResult.error.message };\n\t}\n\n\tconst claims = authResult.value;\n\n\t// Verify JWT gateway ID matches the route\n\tif (claims.gatewayId !== routeGatewayId) {\n\t\treturn {\n\t\t\tauthenticated: false,\n\t\t\tstatus: 403,\n\t\t\tmessage: \"Gateway ID mismatch: JWT authorises a different gateway\",\n\t\t};\n\t}\n\n\t// Admin route protection\n\tif (ADMIN_ACTIONS.has(routeAction) && claims.role !== \"admin\") {\n\t\treturn { authenticated: false, status: 403, message: \"Admin role required\" };\n\t}\n\n\treturn { authenticated: true, claims };\n}\n\n/** Check whether auth is disabled (no jwtSecret configured). */\nexport function isAuthDisabled(jwtSecret: string | undefined): boolean {\n\treturn jwtSecret === undefined;\n}\n","import type { DatabaseAdapter } from \"@lakesync/core\";\n\n/**\n * Interface for distributed locking across gateway-server instances.\n *\n * Used to coordinate exclusive operations (e.g. flush) across\n * multiple instances behind a load balancer.\n */\nexport interface DistributedLock {\n\t/** Attempt to acquire a lock. Returns true if acquired. */\n\tacquire(key: string, ttlMs: number): Promise<boolean>;\n\t/** Release a previously acquired lock. */\n\trelease(key: string): Promise<void>;\n}\n\n/**\n * Database-backed distributed lock using advisory lock semantics.\n *\n * Uses the adapter's `ensureSchema` + `insertDeltas` to maintain a\n * dedicated `__lakesync_locks` table. Lock entries are isolated from\n * regular sync data — they use a reserved table prefix that sync\n * queries should never match.\n *\n * Acquire is atomic: the adapter's upsert semantics (INSERT ON CONFLICT\n * or equivalent) ensure only one instance can hold a lock. Release\n * is best-effort — the TTL provides a safety net against holder crashes.\n *\n * Note: This implementation piggybacks on the DatabaseAdapter interface\n * because the adapter abstraction doesn't expose raw SQL. For adapters\n * that support native advisory locks (e.g. Postgres pg_advisory_lock),\n * prefer a specialised implementation of {@link DistributedLock}.\n */\nexport class AdapterBasedLock implements DistributedLock {\n\tprivate readonly adapter: DatabaseAdapter;\n\tprivate readonly instanceId: string;\n\n\tconstructor(adapter: DatabaseAdapter, instanceId?: string) {\n\t\tthis.adapter = adapter;\n\t\tthis.instanceId = instanceId ?? crypto.randomUUID();\n\t}\n\n\tasync acquire(key: string, ttlMs: number): Promise<boolean> {\n\t\ttry {\n\t\t\t// Use insertDeltas to attempt an atomic lock acquisition.\n\t\t\t// The adapter's upsert semantics handle conflict resolution.\n\t\t\tconst now = Date.now();\n\t\t\tconst result = await this.adapter.insertDeltas([\n\t\t\t\t{\n\t\t\t\t\top: \"INSERT\",\n\t\t\t\t\ttable: \"__lakesync_locks\",\n\t\t\t\t\trowId: key,\n\t\t\t\t\tclientId: this.instanceId,\n\t\t\t\t\tcolumns: [\n\t\t\t\t\t\t{ column: \"holder\", value: this.instanceId },\n\t\t\t\t\t\t{ column: \"expires_at\", value: now + ttlMs },\n\t\t\t\t\t],\n\t\t\t\t\thlc: this.makeHlc(now),\n\t\t\t\t\tdeltaId: `lock-${key}-${now}`,\n\t\t\t\t},\n\t\t\t]);\n\t\t\treturn result.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync release(key: string): Promise<void> {\n\t\ttry {\n\t\t\tconst now = Date.now();\n\t\t\tawait this.adapter.insertDeltas([\n\t\t\t\t{\n\t\t\t\t\top: \"DELETE\",\n\t\t\t\t\ttable: \"__lakesync_locks\",\n\t\t\t\t\trowId: key,\n\t\t\t\t\tclientId: this.instanceId,\n\t\t\t\t\tcolumns: [],\n\t\t\t\t\thlc: this.makeHlc(now),\n\t\t\t\t\tdeltaId: `unlock-${key}-${now}`,\n\t\t\t\t},\n\t\t\t]);\n\t\t} catch {\n\t\t\t// Best-effort release — TTL will expire the lock anyway\n\t\t}\n\t}\n\n\t/**\n\t * Create an HLC-format timestamp from wall clock time.\n\t *\n\t * Uses the standard 48-bit wall + 16-bit counter encoding.\n\t */\n\tprivate makeHlc(wallMs: number): import(\"@lakesync/core\").HLCTimestamp {\n\t\treturn (BigInt(wallMs) << 16n) as import(\"@lakesync/core\").HLCTimestamp;\n\t}\n}\n","// ---------------------------------------------------------------------------\n// SourcePoller — polls external databases and pushes deltas to SyncGateway\n// ---------------------------------------------------------------------------\n\nimport type { HLCTimestamp, RowDelta, SyncPush } from \"@lakesync/core\";\nimport { extractDelta, HLC } from \"@lakesync/core\";\nimport type { SyncGateway } from \"@lakesync/gateway\";\nimport type { IngestSourceConfig, IngestTableConfig } from \"./types\";\n\nconst DEFAULT_INTERVAL_MS = 10_000;\nconst DEFAULT_LOOKBACK_MS = 5_000;\nconst LARGE_SNAPSHOT_WARN = 50_000;\n\n/** Per-table state for cursor strategy. */\ninterface CursorState {\n\tlastCursor: unknown;\n}\n\n/** Per-table state for diff strategy. */\ninterface DiffState {\n\tsnapshot: Map<string, Record<string, unknown>>;\n}\n\n/**\n * Polls an external data source and pushes detected changes into a\n * {@link SyncGateway} via `handlePush()`.\n *\n * Supports two change detection strategies:\n * - **cursor**: fast incremental polling using a monotonically increasing column\n * - **diff**: full-table comparison detecting inserts, updates, and deletes\n */\nexport class SourcePoller {\n\tprivate readonly config: IngestSourceConfig;\n\tprivate readonly gateway: SyncGateway;\n\tprivate readonly hlc: HLC;\n\tprivate readonly clientId: string;\n\n\tprivate timer: ReturnType<typeof setTimeout> | null = null;\n\tprivate running = false;\n\n\t/** Cursor state per table (keyed by table name). */\n\tprivate cursorStates = new Map<string, CursorState>();\n\t/** Diff snapshot per table (keyed by table name). */\n\tprivate diffStates = new Map<string, DiffState>();\n\n\t/** Optional callback invoked after each poll with the current cursor state. */\n\tpublic onCursorUpdate?: (state: Record<string, unknown>) => void;\n\n\tconstructor(config: IngestSourceConfig, gateway: SyncGateway) {\n\t\tthis.config = config;\n\t\tthis.gateway = gateway;\n\t\tthis.hlc = new HLC();\n\t\tthis.clientId = `ingest:${config.name}`;\n\t}\n\n\t/** Start the polling loop. */\n\tstart(): void {\n\t\tif (this.running) return;\n\t\tthis.running = true;\n\t\tthis.schedulePoll();\n\t}\n\n\t/** Stop the polling loop. */\n\tstop(): void {\n\t\tthis.running = false;\n\t\tif (this.timer) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = null;\n\t\t}\n\t}\n\n\t/** Whether the poller is currently running. */\n\tget isRunning(): boolean {\n\t\treturn this.running;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Poll scheduling (recursive setTimeout — no overlap)\n\t// -----------------------------------------------------------------------\n\n\tprivate schedulePoll(): void {\n\t\tif (!this.running) return;\n\n\t\tthis.timer = setTimeout(async () => {\n\t\t\ttry {\n\t\t\t\tawait this.poll();\n\t\t\t} catch {\n\t\t\t\t// Swallow errors — a failed poll must never crash the server\n\t\t\t}\n\t\t\tthis.schedulePoll();\n\t\t}, this.config.intervalMs ?? DEFAULT_INTERVAL_MS);\n\t}\n\n\t/** Export cursor state as a JSON-serialisable object for external persistence. */\n\tgetCursorState(): Record<string, unknown> {\n\t\tconst cursors: Record<string, unknown> = {};\n\t\tfor (const [table, state] of this.cursorStates) {\n\t\t\tcursors[table] = state.lastCursor;\n\t\t}\n\t\treturn { cursorStates: cursors };\n\t}\n\n\t/** Restore cursor state from a previously exported snapshot. */\n\tsetCursorState(state: Record<string, unknown>): void {\n\t\tconst cursors = state.cursorStates as Record<string, unknown> | undefined;\n\t\tif (!cursors) return;\n\t\tfor (const [table, cursor] of Object.entries(cursors)) {\n\t\t\tthis.cursorStates.set(table, { lastCursor: cursor });\n\t\t}\n\t}\n\n\t/** Execute a single poll cycle across all configured tables. */\n\tasync poll(): Promise<void> {\n\t\tconst allDeltas: RowDelta[] = [];\n\n\t\tfor (const table of this.config.tables) {\n\t\t\tconst deltas =\n\t\t\t\ttable.strategy.type === \"cursor\"\n\t\t\t\t\t? await this.pollCursor(table)\n\t\t\t\t\t: await this.pollDiff(table);\n\n\t\t\tfor (const d of deltas) {\n\t\t\t\tallDeltas.push(d);\n\t\t\t}\n\t\t}\n\n\t\tif (allDeltas.length === 0) {\n\t\t\tif (this.onCursorUpdate) {\n\t\t\t\tthis.onCursorUpdate(this.getCursorState());\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst push: SyncPush = {\n\t\t\tclientId: this.clientId,\n\t\t\tdeltas: allDeltas,\n\t\t\tlastSeenHlc: 0n as HLCTimestamp,\n\t\t};\n\n\t\tthis.gateway.handlePush(push);\n\n\t\tif (this.onCursorUpdate) {\n\t\t\tthis.onCursorUpdate(this.getCursorState());\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Cursor strategy\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollCursor(tableConfig: IngestTableConfig): Promise<RowDelta[]> {\n\t\tconst strategy = tableConfig.strategy;\n\t\tif (strategy.type !== \"cursor\") return [];\n\n\t\tconst rowIdCol = tableConfig.rowIdColumn ?? \"id\";\n\t\tconst state = this.cursorStates.get(tableConfig.table);\n\t\tconst lookbackMs = strategy.lookbackMs ?? DEFAULT_LOOKBACK_MS;\n\n\t\tlet rows: Record<string, unknown>[];\n\n\t\tif (state?.lastCursor != null) {\n\t\t\t// Subsequent poll: apply look-back overlap for late-committing transactions\n\t\t\tlet effectiveCursor = state.lastCursor;\n\t\t\tif (typeof effectiveCursor === \"number\") {\n\t\t\t\teffectiveCursor = effectiveCursor - lookbackMs;\n\t\t\t} else if (effectiveCursor instanceof Date) {\n\t\t\t\teffectiveCursor = new Date(effectiveCursor.getTime() - lookbackMs);\n\t\t\t} else if (typeof effectiveCursor === \"string\") {\n\t\t\t\t// Attempt ISO date string\n\t\t\t\tconst parsed = Date.parse(effectiveCursor);\n\t\t\t\tif (!Number.isNaN(parsed)) {\n\t\t\t\t\teffectiveCursor = new Date(parsed - lookbackMs).toISOString();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst sql = `SELECT * FROM (${tableConfig.query}) AS _src WHERE ${strategy.cursorColumn} > $1 ORDER BY ${strategy.cursorColumn} ASC`;\n\t\t\trows = await this.config.queryFn(sql, [effectiveCursor]);\n\t\t} else {\n\t\t\t// First poll: fetch everything\n\t\t\tconst sql = `SELECT * FROM (${tableConfig.query}) AS _src ORDER BY ${strategy.cursorColumn} ASC`;\n\t\t\trows = await this.config.queryFn(sql);\n\t\t}\n\n\t\tif (rows.length === 0) return [];\n\n\t\t// Update cursor to max value\n\t\tconst lastRow = rows[rows.length - 1]!;\n\t\tconst newCursor = lastRow[strategy.cursorColumn];\n\t\tthis.cursorStates.set(tableConfig.table, { lastCursor: newCursor });\n\n\t\t// Convert rows to deltas\n\t\t// Cursor strategy cannot determine previous state — every row is an INSERT/upsert\n\t\tconst deltas: RowDelta[] = [];\n\t\tfor (const row of rows) {\n\t\t\tconst rowId = String(row[rowIdCol]);\n\t\t\tconst after = { ...row };\n\t\t\tdelete after[rowIdCol];\n\n\t\t\tconst delta = await extractDelta(null, after, {\n\t\t\t\ttable: tableConfig.table,\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\tdeltas.push(delta);\n\t\t\t}\n\t\t}\n\n\t\treturn deltas;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Diff strategy\n\t// -----------------------------------------------------------------------\n\n\tprivate async pollDiff(tableConfig: IngestTableConfig): Promise<RowDelta[]> {\n\t\tconst rowIdCol = tableConfig.rowIdColumn ?? \"id\";\n\t\tconst rows = await this.config.queryFn(tableConfig.query);\n\n\t\tconst currentMap = new Map<string, Record<string, unknown>>();\n\t\tfor (const row of rows) {\n\t\t\tconst rowId = String(row[rowIdCol]);\n\t\t\tcurrentMap.set(rowId, row);\n\t\t}\n\n\t\tif (currentMap.size > LARGE_SNAPSHOT_WARN) {\n\t\t\tconsole.warn(\n\t\t\t\t`[lakesync:ingest] Diff snapshot for \"${tableConfig.table}\" has ${currentMap.size} rows (>50k). Consider using cursor strategy.`,\n\t\t\t);\n\t\t}\n\n\t\tconst state = this.diffStates.get(tableConfig.table);\n\t\tconst previousMap = state?.snapshot ?? new Map<string, Record<string, unknown>>();\n\n\t\tconst deltas: RowDelta[] = [];\n\n\t\t// Detect inserts and updates\n\t\tfor (const [rowId, currentRow] of currentMap) {\n\t\t\tconst previousRow = previousMap.get(rowId);\n\n\t\t\t// Build column maps without rowId column\n\t\t\tconst after = { ...currentRow };\n\t\t\tdelete after[rowIdCol];\n\n\t\t\tlet before: Record<string, unknown> | null = null;\n\t\t\tif (previousRow) {\n\t\t\t\tbefore = { ...previousRow };\n\t\t\t\tdelete before[rowIdCol];\n\t\t\t}\n\n\t\t\tconst delta = await extractDelta(before, after, {\n\t\t\t\ttable: tableConfig.table,\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\tdeltas.push(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 before = { ...previousRow };\n\t\t\t\tdelete before[rowIdCol];\n\n\t\t\t\tconst delta = await extractDelta(before, null, {\n\t\t\t\t\ttable: tableConfig.table,\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\tdeltas.push(delta);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Replace snapshot\n\t\tthis.diffStates.set(tableConfig.table, { snapshot: currentMap });\n\n\t\treturn deltas;\n\t}\n}\n","// ---------------------------------------------------------------------------\n// Connector Manager — registry-based connector registration and lifecycle\n// ---------------------------------------------------------------------------\n\nimport {\n\ttype AdapterFactoryRegistry,\n\tcreateDatabaseAdapter,\n\tcreateQueryFn,\n\tdefaultAdapterFactoryRegistry,\n} from \"@lakesync/adapter\";\nimport {\n\ttype ConnectorConfig,\n\tcreatePoller,\n\tcreatePollerRegistry,\n\ttype DatabaseAdapter,\n\tisActionHandler,\n\ttype PollerRegistry,\n} from \"@lakesync/core\";\nimport type { ConfigStore, SyncGateway } from \"@lakesync/gateway\";\nimport {\n\ttype HandlerResult,\n\thandleListConnectors,\n\thandleRegisterConnector,\n\thandleUnregisterConnector,\n} from \"@lakesync/gateway\";\nimport { SourcePoller } from \"./ingest/poller\";\nimport type { IngestSourceConfig } from \"./ingest/types\";\nimport type { DeltaPersistence } from \"./persistence\";\n\n/** Common lifecycle interface for source pollers (SQL or API-based). */\ninterface Poller {\n\tstart(): void;\n\tstop(): void;\n\treadonly isRunning: boolean;\n}\n\n/**\n * Manages connector registration, adapter creation, and poller lifecycle.\n *\n * Uses {@link PollerRegistry} and {@link AdapterFactoryRegistry} for\n * dispatch — no if/else chains for specific connector types.\n */\nexport class ConnectorManager {\n\tprivate readonly adapters = new Map<string, DatabaseAdapter>();\n\tprivate readonly pollers = new Map<string, Poller>();\n\tprivate readonly pollerRegistry: PollerRegistry;\n\tprivate readonly adapterRegistry: AdapterFactoryRegistry;\n\tprivate readonly persistence: DeltaPersistence | null;\n\n\tconstructor(\n\t\tprivate readonly configStore: ConfigStore,\n\t\tprivate readonly gateway: SyncGateway,\n\t\toptions?: {\n\t\t\tpollerRegistry?: PollerRegistry;\n\t\t\tadapterRegistry?: AdapterFactoryRegistry;\n\t\t\tpersistence?: DeltaPersistence;\n\t\t},\n\t) {\n\t\tthis.pollerRegistry = options?.pollerRegistry ?? createPollerRegistry();\n\t\tthis.adapterRegistry = options?.adapterRegistry ?? defaultAdapterFactoryRegistry();\n\t\tthis.persistence = options?.persistence ?? null;\n\t}\n\n\t/**\n\t * Register a connector from raw JSON body.\n\t *\n\t * Validates via shared handler, then dispatches to the appropriate\n\t * registry: poller registry for API-based connectors, adapter registry\n\t * for database-based connectors.\n\t */\n\tasync register(raw: string): Promise<HandlerResult> {\n\t\t// Use shared handler for validation and ConfigStore registration\n\t\tconst result = await handleRegisterConnector(raw, this.configStore);\n\t\tif (result.status !== 200) {\n\t\t\treturn result;\n\t\t}\n\n\t\t// Extract the registered config from the ConfigStore\n\t\tconst connectors = await this.configStore.getConnectors();\n\t\tconst registeredName = (result.body as { name: string }).name;\n\t\tconst config = connectors[registeredName];\n\t\tif (!config) {\n\t\t\treturn result;\n\t\t}\n\n\t\t// Path 1: PollerRegistry has a factory — create an API-based poller\n\t\tconst pollerFactory = this.pollerRegistry.get(config.type);\n\t\tif (pollerFactory) {\n\t\t\ttry {\n\t\t\t\tconst poller = createPoller(config, this.gateway, this.pollerRegistry);\n\n\t\t\t\t// Restore persisted cursor state\n\t\t\t\tif (this.persistence) {\n\t\t\t\t\tconst saved = this.persistence.loadCursor(config.name);\n\t\t\t\t\tif (saved) {\n\t\t\t\t\t\tpoller.setCursorState(JSON.parse(saved));\n\t\t\t\t\t}\n\t\t\t\t\tpoller.onCursorUpdate = (state) => {\n\t\t\t\t\t\tthis.persistence!.saveCursor(config.name, JSON.stringify(state));\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tpoller.start();\n\t\t\t\tthis.pollers.set(config.name, poller);\n\t\t\t\treturn result;\n\t\t\t} catch (err) {\n\t\t\t\tawait this.rollbackRegistration(connectors, registeredName);\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\treturn {\n\t\t\t\t\tstatus: 500,\n\t\t\t\t\tbody: { error: `Failed to create poller for \"${config.type}\": ${message}` },\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Path 2: AdapterFactoryRegistry has a factory — database-based connector\n\t\tconst adapterResult = createDatabaseAdapter(config, this.adapterRegistry);\n\t\tif (!adapterResult.ok) {\n\t\t\tawait this.rollbackRegistration(connectors, registeredName);\n\t\t\treturn { status: 500, body: { error: adapterResult.error.message } };\n\t\t}\n\n\t\tconst adapter = adapterResult.value;\n\t\tthis.gateway.registerSource(config.name, adapter);\n\t\tthis.adapters.set(config.name, adapter);\n\n\t\t// Auto-register as action handler if the adapter supports actions\n\t\tif (isActionHandler(adapter)) {\n\t\t\tthis.gateway.registerActionHandler(config.name, adapter);\n\t\t}\n\n\t\t// Start ingest poller if configured\n\t\tif (config.ingest) {\n\t\t\tconst queryFn = await createQueryFn(config);\n\t\t\tif (queryFn) {\n\t\t\t\tconst pollerConfig: IngestSourceConfig = {\n\t\t\t\t\tname: config.name,\n\t\t\t\t\tqueryFn,\n\t\t\t\t\ttables: config.ingest.tables.map((t) => ({\n\t\t\t\t\t\ttable: t.table,\n\t\t\t\t\t\tquery: t.query,\n\t\t\t\t\t\trowIdColumn: t.rowIdColumn,\n\t\t\t\t\t\tstrategy: t.strategy,\n\t\t\t\t\t})),\n\t\t\t\t\tintervalMs: config.ingest.intervalMs,\n\t\t\t\t};\n\t\t\t\tconst poller = new SourcePoller(pollerConfig, this.gateway);\n\n\t\t\t\t// Restore persisted cursor state\n\t\t\t\tif (this.persistence) {\n\t\t\t\t\tconst saved = this.persistence.loadCursor(config.name);\n\t\t\t\t\tif (saved) {\n\t\t\t\t\t\tpoller.setCursorState(JSON.parse(saved));\n\t\t\t\t\t}\n\t\t\t\t\tpoller.onCursorUpdate = (state) => {\n\t\t\t\t\t\tthis.persistence!.saveCursor(config.name, JSON.stringify(state));\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tpoller.start();\n\t\t\t\tthis.pollers.set(config.name, poller);\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Unregister a connector by name.\n\t *\n\t * Stops poller, closes adapter, and unregisters from gateway.\n\t */\n\tasync unregister(name: string): Promise<HandlerResult> {\n\t\tconst result = await handleUnregisterConnector(name, this.configStore);\n\t\tif (result.status !== 200) {\n\t\t\treturn result;\n\t\t}\n\n\t\t// Stop poller if running\n\t\tconst poller = this.pollers.get(name);\n\t\tif (poller) {\n\t\t\tpoller.stop();\n\t\t\tthis.pollers.delete(name);\n\t\t}\n\n\t\t// Close adapter\n\t\tconst adapter = this.adapters.get(name);\n\t\tif (adapter) {\n\t\t\tawait adapter.close();\n\t\t\tthis.adapters.delete(name);\n\t\t}\n\n\t\t// Unregister from gateway\n\t\tthis.gateway.unregisterSource(name);\n\t\tthis.gateway.unregisterActionHandler(name);\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * List registered connectors with live polling status.\n\t */\n\tasync list(): Promise<HandlerResult> {\n\t\tconst result = await handleListConnectors(this.configStore);\n\t\tif (result.status !== 200) {\n\t\t\treturn result;\n\t\t}\n\n\t\tconst list = result.body as Array<{ name: string; type: string; hasIngest: boolean }>;\n\t\tconst augmented = list.map((c) => ({\n\t\t\t...c,\n\t\t\tisPolling: this.pollers.get(c.name)?.isRunning ?? false,\n\t\t}));\n\n\t\treturn { status: 200, body: augmented };\n\t}\n\n\t/** Stop all pollers and close all adapters. */\n\tasync stopAll(): Promise<void> {\n\t\tfor (const [, poller] of this.pollers) {\n\t\t\tpoller.stop();\n\t\t}\n\t\tthis.pollers.clear();\n\n\t\tfor (const [, adapter] of this.adapters) {\n\t\t\tawait adapter.close();\n\t\t}\n\t\tthis.adapters.clear();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal helpers\n\t// -----------------------------------------------------------------------\n\n\tprivate async rollbackRegistration(\n\t\tconnectors: Record<string, ConnectorConfig>,\n\t\tname: string,\n\t): Promise<void> {\n\t\tdelete connectors[name];\n\t\tawait this.configStore.setConnectors(connectors);\n\t}\n}\n","// ---------------------------------------------------------------------------\n// CORS Middleware\n// ---------------------------------------------------------------------------\n\nimport type { ServerResponse } from \"node:http\";\n\n/** Configuration for CORS header generation. */\nexport interface CorsConfig {\n\t/** Allowed origins. When empty/omitted, all origins are reflected. */\n\tallowedOrigins?: string[];\n}\n\n/**\n * Build CORS response headers for the given origin.\n *\n * When `allowedOrigins` is set, only listed origins receive CORS headers.\n * When omitted, the request origin is reflected (or `*` if no origin header).\n */\nexport function corsHeaders(\n\torigin: string | null | undefined,\n\tconfig: CorsConfig,\n): Record<string, string> {\n\tconst { allowedOrigins } = config;\n\tlet allowOrigin = \"*\";\n\n\tif (allowedOrigins && allowedOrigins.length > 0) {\n\t\tif (origin && allowedOrigins.includes(origin)) {\n\t\t\tallowOrigin = origin;\n\t\t} else {\n\t\t\treturn {};\n\t\t}\n\t} else if (origin) {\n\t\tallowOrigin = origin;\n\t}\n\n\treturn {\n\t\t\"Access-Control-Allow-Origin\": allowOrigin,\n\t\t\"Access-Control-Allow-Methods\": \"GET, POST, DELETE, OPTIONS\",\n\t\t\"Access-Control-Allow-Headers\": \"Authorization, Content-Type\",\n\t\t\"Access-Control-Max-Age\": \"86400\",\n\t};\n}\n\n/** Handle CORS preflight (OPTIONS) request. Returns true if handled. */\nexport function handlePreflight(\n\tmethod: string,\n\tres: ServerResponse,\n\tcorsH: Record<string, string>,\n): boolean {\n\tif (method !== \"OPTIONS\") return false;\n\tres.writeHead(204, corsH);\n\tres.end();\n\treturn true;\n}\n","// ---------------------------------------------------------------------------\n// Structured Logger — minimal JSON-lines logger for gateway-server\n// ---------------------------------------------------------------------------\n\n/** Supported log levels, ordered by severity. */\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\n/** A single structured log entry. */\nexport interface LogEntry {\n\tlevel: LogLevel;\n\tmsg: string;\n\tts: string;\n\t[key: string]: unknown;\n}\n\n/** Numeric severity values for level comparison. */\nconst LEVEL_VALUE: Record<LogLevel, number> = {\n\tdebug: 0,\n\tinfo: 1,\n\twarn: 2,\n\terror: 3,\n};\n\n/**\n * Minimal structured logger that outputs JSON lines to stdout.\n *\n * Supports log-level filtering and child loggers with bound context.\n * No external dependencies — compatible with any log aggregator that\n * consumes JSON lines.\n *\n * @example\n * ```ts\n * const logger = new Logger(\"info\");\n * const reqLogger = logger.child({ requestId: \"abc-123\" });\n * reqLogger.info(\"push received\", { deltas: 5 });\n * // => {\"level\":\"info\",\"msg\":\"push received\",\"ts\":\"...\",\"requestId\":\"abc-123\",\"deltas\":5}\n * ```\n */\nexport class Logger {\n\tprivate readonly minLevelValue: number;\n\tprivate readonly bindings: Record<string, unknown>;\n\n\t/** Output function — defaults to stdout, overridable for testing. */\n\tprivate readonly writeFn: (line: string) => void;\n\n\tconstructor(\n\t\tminLevel: LogLevel = \"info\",\n\t\tbindings: Record<string, unknown> = {},\n\t\twriteFn?: (line: string) => void,\n\t) {\n\t\tthis.minLevelValue = LEVEL_VALUE[minLevel];\n\t\tthis.bindings = bindings;\n\t\tthis.writeFn = writeFn ?? ((line) => process.stdout.write(`${line}\\n`));\n\t}\n\n\t/** Log at debug level. */\n\tdebug(msg: string, data?: Record<string, unknown>): void {\n\t\tthis.log(\"debug\", msg, data);\n\t}\n\n\t/** Log at info level. */\n\tinfo(msg: string, data?: Record<string, unknown>): void {\n\t\tthis.log(\"info\", msg, data);\n\t}\n\n\t/** Log at warn level. */\n\twarn(msg: string, data?: Record<string, unknown>): void {\n\t\tthis.log(\"warn\", msg, data);\n\t}\n\n\t/** Log at error level. */\n\terror(msg: string, data?: Record<string, unknown>): void {\n\t\tthis.log(\"error\", msg, data);\n\t}\n\n\t/**\n\t * Create a child logger with additional bound context.\n\t *\n\t * The child inherits the parent's level and write function, plus\n\t * merges any parent bindings with the new ones.\n\t */\n\tchild(bindings: Record<string, unknown>): Logger {\n\t\treturn new Logger(this.minLevelName(), { ...this.bindings, ...bindings }, this.writeFn);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Internal\n\t// -----------------------------------------------------------------------\n\n\tprivate log(level: LogLevel, msg: string, data?: Record<string, unknown>): void {\n\t\tif (LEVEL_VALUE[level] < this.minLevelValue) return;\n\n\t\tconst entry: LogEntry = {\n\t\t\tlevel,\n\t\t\tmsg,\n\t\t\tts: new Date().toISOString(),\n\t\t\t...this.bindings,\n\t\t\t...data,\n\t\t};\n\n\t\tthis.writeFn(JSON.stringify(entry));\n\t}\n\n\tprivate minLevelName(): LogLevel {\n\t\tfor (const [name, val] of Object.entries(LEVEL_VALUE)) {\n\t\t\tif (val === this.minLevelValue) return name as LogLevel;\n\t\t}\n\t\treturn \"info\";\n\t}\n}\n","// ---------------------------------------------------------------------------\n// Prometheus-Compatible Metrics — counters, gauges, histograms\n// ---------------------------------------------------------------------------\n\n/** Label set for a metric observation. */\nexport type Labels = Record<string, string>;\n\n// ---------------------------------------------------------------------------\n// Counter\n// ---------------------------------------------------------------------------\n\n/**\n * Monotonically increasing counter.\n *\n * @example\n * ```ts\n * const pushTotal = new Counter(\"lakesync_push_total\", \"Total push requests\");\n * pushTotal.inc({ status: \"ok\" });\n * ```\n */\nexport class Counter {\n\tprivate readonly values = new Map<string, number>();\n\n\tconstructor(\n\t\treadonly name: string,\n\t\treadonly help: string,\n\t) {}\n\n\t/** Increment the counter by `n` (default 1). */\n\tinc(labels: Labels = {}, n = 1): void {\n\t\tconst key = labelKey(labels);\n\t\tthis.values.set(key, (this.values.get(key) ?? 0) + n);\n\t}\n\n\t/** Return the current value for the given labels. */\n\tget(labels: Labels = {}): number {\n\t\treturn this.values.get(labelKey(labels)) ?? 0;\n\t}\n\n\t/** Reset all values. */\n\treset(): void {\n\t\tthis.values.clear();\n\t}\n\n\t/** Serialise to Prometheus text exposition format. */\n\texpose(): string {\n\t\tconst lines: string[] = [];\n\t\tlines.push(`# HELP ${this.name} ${this.help}`);\n\t\tlines.push(`# TYPE ${this.name} counter`);\n\t\tfor (const [key, val] of this.values) {\n\t\t\tlines.push(`${this.name}${key} ${val}`);\n\t\t}\n\t\treturn lines.join(\"\\n\");\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Gauge\n// ---------------------------------------------------------------------------\n\n/**\n * Gauge that can go up and down.\n *\n * @example\n * ```ts\n * const bufferBytes = new Gauge(\"lakesync_buffer_bytes\", \"Buffer size in bytes\");\n * bufferBytes.set({}, 1024);\n * ```\n */\nexport class Gauge {\n\tprivate readonly values = new Map<string, number>();\n\n\tconstructor(\n\t\treadonly name: string,\n\t\treadonly help: string,\n\t) {}\n\n\t/** Set to an absolute value. */\n\tset(labels: Labels = {}, value: number = 0): void {\n\t\tthis.values.set(labelKey(labels), value);\n\t}\n\n\t/** Increment by `n` (default 1). */\n\tinc(labels: Labels = {}, n = 1): void {\n\t\tconst key = labelKey(labels);\n\t\tthis.values.set(key, (this.values.get(key) ?? 0) + n);\n\t}\n\n\t/** Decrement by `n` (default 1). */\n\tdec(labels: Labels = {}, n = 1): void {\n\t\tconst key = labelKey(labels);\n\t\tthis.values.set(key, (this.values.get(key) ?? 0) - n);\n\t}\n\n\t/** Return the current value for the given labels. */\n\tget(labels: Labels = {}): number {\n\t\treturn this.values.get(labelKey(labels)) ?? 0;\n\t}\n\n\t/** Reset all values. */\n\treset(): void {\n\t\tthis.values.clear();\n\t}\n\n\t/** Serialise to Prometheus text exposition format. */\n\texpose(): string {\n\t\tconst lines: string[] = [];\n\t\tlines.push(`# HELP ${this.name} ${this.help}`);\n\t\tlines.push(`# TYPE ${this.name} gauge`);\n\t\tfor (const [key, val] of this.values) {\n\t\t\tlines.push(`${this.name}${key} ${val}`);\n\t\t}\n\t\treturn lines.join(\"\\n\");\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Histogram\n// ---------------------------------------------------------------------------\n\n/** Internal per-label-set histogram state. */\ninterface HistogramBucket {\n\tbucketCounts: number[];\n\tsum: number;\n\tcount: number;\n}\n\n/**\n * Histogram with configurable buckets.\n *\n * @example\n * ```ts\n * const latency = new Histogram(\n * \"lakesync_push_latency_ms\",\n * \"Push latency in ms\",\n * [1, 5, 10, 50, 100, 500],\n * );\n * latency.observe({}, 42);\n * ```\n */\nexport class Histogram {\n\tprivate readonly data = new Map<string, HistogramBucket>();\n\n\tconstructor(\n\t\treadonly name: string,\n\t\treadonly help: string,\n\t\treadonly buckets: number[],\n\t) {\n\t\t// Ensure buckets are sorted ascending\n\t\tthis.buckets = [...buckets].sort((a, b) => a - b);\n\t}\n\n\t/** Record an observation. */\n\tobserve(labels: Labels = {}, value: number = 0): void {\n\t\tconst key = labelKey(labels);\n\t\tlet bucket = this.data.get(key);\n\t\tif (!bucket) {\n\t\t\tbucket = {\n\t\t\t\tbucketCounts: new Array(this.buckets.length + 1).fill(0) as number[],\n\t\t\t\tsum: 0,\n\t\t\t\tcount: 0,\n\t\t\t};\n\t\t\tthis.data.set(key, bucket);\n\t\t}\n\t\tbucket.sum += value;\n\t\tbucket.count += 1;\n\t\tfor (let i = 0; i < this.buckets.length; i++) {\n\t\t\tif (value <= this.buckets[i]!) {\n\t\t\t\tbucket.bucketCounts[i]!++;\n\t\t\t}\n\t\t}\n\t\t// +Inf bucket (last element)\n\t\tbucket.bucketCounts[this.buckets.length]!++;\n\t}\n\n\t/** Return the count of observations for the given labels. */\n\tgetCount(labels: Labels = {}): number {\n\t\treturn this.data.get(labelKey(labels))?.count ?? 0;\n\t}\n\n\t/** Return the sum of observations for the given labels. */\n\tgetSum(labels: Labels = {}): number {\n\t\treturn this.data.get(labelKey(labels))?.sum ?? 0;\n\t}\n\n\t/** Reset all values. */\n\treset(): void {\n\t\tthis.data.clear();\n\t}\n\n\t/** Serialise to Prometheus text exposition format. */\n\texpose(): string {\n\t\tconst lines: string[] = [];\n\t\tlines.push(`# HELP ${this.name} ${this.help}`);\n\t\tlines.push(`# TYPE ${this.name} histogram`);\n\n\t\tfor (const [key, bucket] of this.data) {\n\t\t\tconst labelStr = key === \"\" ? \"\" : key;\n\t\t\tconst separator = labelStr === \"\" ? \"{\" : `${labelStr.slice(0, -1)},`;\n\t\t\tconst closeBrace = \"}\";\n\n\t\t\tfor (let i = 0; i < this.buckets.length; i++) {\n\t\t\t\tconst le = this.buckets[i]!;\n\t\t\t\tlines.push(\n\t\t\t\t\t`${this.name}_bucket${separator}le=\"${le}\"${closeBrace} ${bucket.bucketCounts[i]}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tlines.push(\n\t\t\t\t`${this.name}_bucket${separator}le=\"+Inf\"${closeBrace} ${bucket.bucketCounts[this.buckets.length]}`,\n\t\t\t);\n\t\t\tlines.push(`${this.name}_sum${labelStr} ${bucket.sum}`);\n\t\t\tlines.push(`${this.name}_count${labelStr} ${bucket.count}`);\n\t\t}\n\n\t\treturn lines.join(\"\\n\");\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Metrics Registry\n// ---------------------------------------------------------------------------\n\n/**\n * Pre-configured metrics registry for the gateway server.\n *\n * Exposes all standard lakesync metrics and a single `expose()` method\n * that returns the complete Prometheus text exposition payload.\n */\nexport class MetricsRegistry {\n\treadonly pushTotal = new Counter(\"lakesync_push_total\", \"Total push requests\");\n\treadonly pullTotal = new Counter(\"lakesync_pull_total\", \"Total pull requests\");\n\treadonly flushTotal = new Counter(\"lakesync_flush_total\", \"Total flush operations\");\n\n\treadonly flushDuration = new Histogram(\n\t\t\"lakesync_flush_duration_ms\",\n\t\t\"Flush duration in milliseconds\",\n\t\t[10, 50, 100, 500, 1000, 5000],\n\t);\n\n\treadonly pushLatency = new Histogram(\n\t\t\"lakesync_push_latency_ms\",\n\t\t\"Push request latency in milliseconds\",\n\t\t[1, 5, 10, 50, 100, 500],\n\t);\n\n\treadonly bufferBytes = new Gauge(\"lakesync_buffer_bytes\", \"Current buffer size in bytes\");\n\treadonly bufferDeltas = new Gauge(\"lakesync_buffer_deltas\", \"Current number of buffered deltas\");\n\treadonly wsConnections = new Gauge(\"lakesync_ws_connections\", \"Active WebSocket connections\");\n\treadonly activeRequests = new Gauge(\"lakesync_active_requests\", \"In-flight HTTP requests\");\n\n\t/** Return the full Prometheus text exposition payload. */\n\texpose(): string {\n\t\tconst sections = [\n\t\t\tthis.pushTotal.expose(),\n\t\t\tthis.pullTotal.expose(),\n\t\t\tthis.flushTotal.expose(),\n\t\t\tthis.flushDuration.expose(),\n\t\t\tthis.pushLatency.expose(),\n\t\t\tthis.bufferBytes.expose(),\n\t\t\tthis.bufferDeltas.expose(),\n\t\t\tthis.wsConnections.expose(),\n\t\t\tthis.activeRequests.expose(),\n\t\t];\n\t\treturn `${sections.join(\"\\n\\n\")}\\n`;\n\t}\n\n\t/** Reset all metrics. */\n\treset(): void {\n\t\tthis.pushTotal.reset();\n\t\tthis.pullTotal.reset();\n\t\tthis.flushTotal.reset();\n\t\tthis.flushDuration.reset();\n\t\tthis.pushLatency.reset();\n\t\tthis.bufferBytes.reset();\n\t\tthis.bufferDeltas.reset();\n\t\tthis.wsConnections.reset();\n\t\tthis.activeRequests.reset();\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Build a Prometheus-format label key string like `{status=\"ok\"}`. */\nfunction labelKey(labels: Labels): string {\n\tconst entries = Object.entries(labels);\n\tif (entries.length === 0) return \"\";\n\tconst parts = entries.map(([k, v]) => `${k}=\"${v}\"`).join(\",\");\n\treturn `{${parts}}`;\n}\n","import type { RowDelta } from \"@lakesync/core\";\nimport { bigintReplacer, bigintReviver } from \"@lakesync/core\";\n\n/**\n * Persistence interface for buffering unflushed deltas across restarts.\n *\n * Implementations must be synchronous (no async) to avoid race conditions\n * during the push-then-flush cycle.\n */\nexport interface DeltaPersistence {\n\t/** Append a batch of deltas to the persistence store. */\n\tappendBatch(deltas: RowDelta[]): void;\n\t/** Load all persisted deltas. */\n\tloadAll(): RowDelta[];\n\t/** Clear all persisted deltas (after successful flush). */\n\tclear(): void;\n\t/** Release resources. */\n\tclose(): void;\n\t/** Persist the cursor state for a connector. */\n\tsaveCursor(connectorName: string, cursor: string): void;\n\t/** Load the persisted cursor state for a connector, or null if none exists. */\n\tloadCursor(connectorName: string): string | null;\n}\n\n/**\n * In-memory persistence (no durability across restarts).\n * Used as the default when `persistence` is \"memory\".\n */\nexport class MemoryPersistence implements DeltaPersistence {\n\tprivate buffer: RowDelta[] = [];\n\tprivate cursors = new Map<string, string>();\n\n\tappendBatch(deltas: RowDelta[]): void {\n\t\tthis.buffer.push(...deltas);\n\t}\n\n\tloadAll(): RowDelta[] {\n\t\treturn [...this.buffer];\n\t}\n\n\tclear(): void {\n\t\tthis.buffer = [];\n\t}\n\n\tclose(): void {\n\t\tthis.buffer = [];\n\t\tthis.cursors.clear();\n\t}\n\n\tsaveCursor(connectorName: string, cursor: string): void {\n\t\tthis.cursors.set(connectorName, cursor);\n\t}\n\n\tloadCursor(connectorName: string): string | null {\n\t\treturn this.cursors.get(connectorName) ?? null;\n\t}\n}\n\n/**\n * SQLite-backed persistence using `better-sqlite3`.\n *\n * Stores deltas as JSON rows in a single table. On startup, loads all\n * rows back as RowDeltas. On flush success, truncates the table.\n */\nexport class SqlitePersistence implements DeltaPersistence {\n\tprivate db: import(\"better-sqlite3\").Database;\n\n\tconstructor(path: string) {\n\t\t// eslint-disable-next-line @typescript-eslint/no-require-imports\n\t\tconst Database = require(\"better-sqlite3\") as typeof import(\"better-sqlite3\");\n\t\tthis.db = new Database(path);\n\t\tthis.db.pragma(\"journal_mode = WAL\");\n\t\tthis.db.exec(\n\t\t\t\"CREATE TABLE IF NOT EXISTS unflushed_deltas (id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT NOT NULL)\",\n\t\t);\n\t\tthis.db.exec(\n\t\t\t\"CREATE TABLE IF NOT EXISTS connector_cursors (name TEXT PRIMARY KEY, cursor TEXT NOT NULL)\",\n\t\t);\n\t}\n\n\tappendBatch(deltas: RowDelta[]): void {\n\t\tconst stmt = this.db.prepare(\"INSERT INTO unflushed_deltas (data) VALUES (?)\");\n\t\tconst tx = this.db.transaction(() => {\n\t\t\tfor (const delta of deltas) {\n\t\t\t\tstmt.run(JSON.stringify(delta, bigintReplacer));\n\t\t\t}\n\t\t});\n\t\ttx();\n\t}\n\n\tloadAll(): RowDelta[] {\n\t\tconst rows = this.db.prepare(\"SELECT data FROM unflushed_deltas ORDER BY id\").all() as Array<{\n\t\t\tdata: string;\n\t\t}>;\n\t\treturn rows.map((row) => JSON.parse(row.data, bigintReviver) as RowDelta);\n\t}\n\n\tclear(): void {\n\t\tthis.db.exec(\"DELETE FROM unflushed_deltas\");\n\t}\n\n\tsaveCursor(connectorName: string, cursor: string): void {\n\t\tthis.db\n\t\t\t.prepare(\n\t\t\t\t\"INSERT INTO connector_cursors (name, cursor) VALUES (?, ?) ON CONFLICT(name) DO UPDATE SET cursor = excluded.cursor\",\n\t\t\t)\n\t\t\t.run(connectorName, cursor);\n\t}\n\n\tloadCursor(connectorName: string): string | null {\n\t\tconst row = this.db\n\t\t\t.prepare(\"SELECT cursor FROM connector_cursors WHERE name = ?\")\n\t\t\t.get(connectorName) as { cursor: string } | undefined;\n\t\treturn row?.cursor ?? null;\n\t}\n\n\tclose(): void {\n\t\tthis.db.close();\n\t}\n}\n","// ---------------------------------------------------------------------------\n// Per-Client Rate Limiter — fixed-window token bucket\n// ---------------------------------------------------------------------------\n\n/** Configuration for the per-client rate limiter. */\nexport interface RateLimiterConfig {\n\t/** Maximum requests per window (default: 100). */\n\tmaxRequests?: number;\n\t/** Window size in milliseconds (default: 60_000). */\n\twindowMs?: number;\n}\n\ninterface ClientWindow {\n\tcount: number;\n\twindowStart: number;\n}\n\nconst DEFAULT_MAX_REQUESTS = 100;\nconst DEFAULT_WINDOW_MS = 60_000;\nconst CLEANUP_INTERVAL_MS = 60_000;\n\n/**\n * Fixed-window per-client rate limiter.\n *\n * Tracks request counts per client within a sliding window. Stale entries\n * are periodically cleaned up to prevent unbounded memory growth.\n */\nexport class RateLimiter {\n\tprivate readonly maxRequests: number;\n\tprivate readonly windowMs: number;\n\tprivate readonly clients = new Map<string, ClientWindow>();\n\tprivate cleanupTimer: ReturnType<typeof setInterval> | null = null;\n\n\tconstructor(config: RateLimiterConfig = {}) {\n\t\tthis.maxRequests = config.maxRequests ?? DEFAULT_MAX_REQUESTS;\n\t\tthis.windowMs = config.windowMs ?? DEFAULT_WINDOW_MS;\n\n\t\tthis.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL_MS);\n\t\t// Allow the process to exit without waiting for the cleanup timer\n\t\tif (this.cleanupTimer.unref) {\n\t\t\tthis.cleanupTimer.unref();\n\t\t}\n\t}\n\n\t/**\n\t * Attempt to consume one request token for the given client.\n\t *\n\t * @returns `true` if the request is allowed, `false` if rate-limited.\n\t */\n\ttryConsume(clientId: string): boolean {\n\t\tconst now = Date.now();\n\t\tconst entry = this.clients.get(clientId);\n\n\t\tif (!entry || now - entry.windowStart >= this.windowMs) {\n\t\t\t// New window\n\t\t\tthis.clients.set(clientId, { count: 1, windowStart: now });\n\t\t\treturn true;\n\t\t}\n\n\t\tif (entry.count >= this.maxRequests) {\n\t\t\treturn false;\n\t\t}\n\n\t\tentry.count++;\n\t\treturn true;\n\t}\n\n\t/**\n\t * Calculate the number of seconds until the current window resets\n\t * for a given client. Used for the Retry-After header.\n\t */\n\tretryAfterSeconds(clientId: string): number {\n\t\tconst entry = this.clients.get(clientId);\n\t\tif (!entry) return 0;\n\t\tconst elapsed = Date.now() - entry.windowStart;\n\t\tconst remaining = Math.max(0, this.windowMs - elapsed);\n\t\treturn Math.ceil(remaining / 1000);\n\t}\n\n\t/** Remove all tracked clients and reset state. */\n\treset(): void {\n\t\tthis.clients.clear();\n\t}\n\n\t/** Stop the periodic cleanup timer. */\n\tdispose(): void {\n\t\tif (this.cleanupTimer) {\n\t\t\tclearInterval(this.cleanupTimer);\n\t\t\tthis.cleanupTimer = null;\n\t\t}\n\t\tthis.clients.clear();\n\t}\n\n\t/** Remove stale entries whose window has expired. */\n\tprivate cleanup(): void {\n\t\tconst now = Date.now();\n\t\tfor (const [clientId, entry] of this.clients) {\n\t\t\tif (now - entry.windowStart >= this.windowMs) {\n\t\t\t\tthis.clients.delete(clientId);\n\t\t\t}\n\t\t}\n\t}\n}\n","// ---------------------------------------------------------------------------\n// Router — URL pattern matching and route dispatch\n// ---------------------------------------------------------------------------\n\n/** Matched route information. */\nexport interface RouteMatch {\n\tgatewayId: string;\n\taction: string;\n\t/** Extra route parameters (e.g. connector name from DELETE path). */\n\tconnectorName?: string;\n}\n\n/** Route definition: [method, pattern, action, captureConnectorName?] */\ntype RouteEntry = [string, RegExp, string, boolean?];\n\n/** Route definitions for the gateway server. */\nconst ROUTES: ReadonlyArray<RouteEntry> = [\n\t[\"POST\", /^\\/sync\\/([^/]+)\\/push$/, \"push\"],\n\t[\"GET\", /^\\/sync\\/([^/]+)\\/pull$/, \"pull\"],\n\t[\"POST\", /^\\/sync\\/([^/]+)\\/action$/, \"action\"],\n\t[\"GET\", /^\\/sync\\/([^/]+)\\/actions$/, \"describe-actions\"],\n\t[\"GET\", /^\\/sync\\/([^/]+)\\/ws$/, \"ws\"],\n\t[\"POST\", /^\\/admin\\/flush\\/([^/]+)$/, \"flush\"],\n\t[\"POST\", /^\\/admin\\/schema\\/([^/]+)$/, \"schema\"],\n\t[\"POST\", /^\\/admin\\/sync-rules\\/([^/]+)$/, \"sync-rules\"],\n\t[\"POST\", /^\\/admin\\/connectors\\/([^/]+)$/, \"register-connector\"],\n\t[\"GET\", /^\\/admin\\/connectors\\/([^/]+)$/, \"list-connectors\"],\n\t[\"DELETE\", /^\\/admin\\/connectors\\/([^/]+)\\/([^/]+)$/, \"unregister-connector\", true],\n\t[\"GET\", /^\\/admin\\/metrics\\/([^/]+)$/, \"metrics\"],\n];\n\n/**\n * Match a request pathname and method against the route table.\n *\n * Returns the matched route or null if no route matches.\n */\nexport function matchRoute(pathname: string, method: string): RouteMatch | null {\n\tfor (const [m, pattern, action, hasConnector] of ROUTES) {\n\t\tif (method !== m) continue;\n\t\tconst match = pathname.match(pattern);\n\t\tif (!match) continue;\n\t\treturn {\n\t\t\tgatewayId: match[1]!,\n\t\t\taction,\n\t\t\t...(hasConnector ? { connectorName: match[2]! } : {}),\n\t\t};\n\t}\n\treturn null;\n}\n","import { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport type { AdapterFactoryRegistry } from \"@lakesync/adapter\";\nimport { jiraPollerFactory } from \"@lakesync/connector-jira\";\nimport { salesforcePollerFactory } from \"@lakesync/connector-salesforce\";\nimport type {\n\tDatabaseAdapter,\n\tHLCTimestamp,\n\tLakeAdapter,\n\tPollerRegistry,\n\tRowDelta,\n\tSyncResponse,\n} from \"@lakesync/core\";\nimport { bigintReplacer, createPollerRegistry, isDatabaseAdapter } from \"@lakesync/core\";\nimport type { ConfigStore, HandlerResult } from \"@lakesync/gateway\";\nimport {\n\tDEFAULT_MAX_BUFFER_AGE_MS,\n\tDEFAULT_MAX_BUFFER_BYTES,\n\thandleActionRequest,\n\thandleFlushRequest,\n\thandleListConnectorTypes,\n\thandleMetrics,\n\thandlePullRequest,\n\thandlePushRequest,\n\thandleSaveSchema,\n\thandleSaveSyncRules,\n\tMAX_PUSH_PAYLOAD_BYTES,\n\tMemoryConfigStore,\n\tSyncGateway,\n} from \"@lakesync/gateway\";\nimport type { AuthClaims } from \"./auth\";\nimport { authenticateRequest } from \"./auth-middleware\";\nimport type { DistributedLock } from \"./cluster\";\nimport { ConnectorManager } from \"./connector-manager\";\nimport { corsHeaders, handlePreflight } from \"./cors-middleware\";\nimport { SourcePoller } from \"./ingest/poller\";\nimport type { IngestSourceConfig } from \"./ingest/types\";\nimport { Logger, type LogLevel } from \"./logger\";\nimport { MetricsRegistry } from \"./metrics\";\nimport { type DeltaPersistence, MemoryPersistence, SqlitePersistence } from \"./persistence\";\nimport { RateLimiter, type RateLimiterConfig } from \"./rate-limiter\";\nimport { matchRoute } from \"./router\";\nimport { SharedBuffer, type SharedBufferConfig } from \"./shared-buffer\";\nimport { type WebSocketLimitsConfig, WebSocketManager } from \"./ws-manager\";\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\n/** Configuration for the self-hosted gateway server. */\nexport interface GatewayServerConfig {\n\t/** Port to listen on (default 3000). */\n\tport?: number;\n\t/** Unique gateway identifier. */\n\tgatewayId: string;\n\t/** Storage adapter — LakeAdapter (S3/R2) or DatabaseAdapter (Postgres/MySQL). */\n\tadapter?: LakeAdapter | DatabaseAdapter;\n\t/** Maximum buffer size in bytes before triggering flush (default 4 MiB). */\n\tmaxBufferBytes?: number;\n\t/** Maximum buffer age in milliseconds before triggering flush (default 30s). */\n\tmaxBufferAgeMs?: number;\n\t/** HMAC-SHA256 secret for JWT verification. When omitted, auth is disabled. */\n\tjwtSecret?: string;\n\t/** Interval in milliseconds between periodic flushes (default 30s). */\n\tflushIntervalMs?: number;\n\t/** CORS allowed origins. When omitted, all origins are reflected. */\n\tallowedOrigins?: string[];\n\t/** DeltaBuffer persistence strategy (default \"memory\"). */\n\tpersistence?: \"memory\" | \"sqlite\";\n\t/** Path to the SQLite file when `persistence` is \"sqlite\" (default \"./lakesync-buffer.sqlite\"). */\n\tsqlitePath?: string;\n\t/** Polling ingest sources. Each source is polled independently. */\n\tingestSources?: IngestSourceConfig[];\n\t/** Optional clustering configuration for multi-instance deployment. */\n\tcluster?: {\n\t\t/** Distributed lock for coordinated flush. */\n\t\tlock: DistributedLock;\n\t\t/** Shared database adapter for cross-instance visibility. */\n\t\tsharedAdapter: DatabaseAdapter;\n\t\t/** Shared buffer configuration (consistency mode, etc.). */\n\t\tsharedBufferConfig?: SharedBufferConfig;\n\t};\n\t/** Custom poller registry for connector pollers. Defaults to Jira + Salesforce. */\n\tpollerRegistry?: PollerRegistry;\n\t/** Custom adapter factory registry. Defaults to Postgres, MySQL, BigQuery. */\n\tadapterRegistry?: AdapterFactoryRegistry;\n\t/** Drain timeout in milliseconds for graceful shutdown (default 10s). */\n\tdrainTimeoutMs?: number;\n\t/** Request timeout in milliseconds (default 30s). Aborts with 504 on timeout. */\n\trequestTimeoutMs?: number;\n\t/** Flush timeout in milliseconds for periodic flushes (default 60s). */\n\tflushTimeoutMs?: number;\n\t/** Per-client rate limiter configuration. When provided, rate limiting is enabled. */\n\trateLimiter?: RateLimiterConfig;\n\t/** WebSocket connection and message rate limits. */\n\twsLimits?: WebSocketLimitsConfig;\n\t/** Minimum log level for the structured logger (default \"info\"). */\n\tlogLevel?: LogLevel;\n\t/** Whether to enable the Prometheus metrics endpoint at GET /metrics (default true). */\n\tenableMetrics?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_PORT = 3000;\nconst DEFAULT_FLUSH_INTERVAL_MS = 30_000;\nconst DEFAULT_DRAIN_TIMEOUT_MS = 10_000;\nconst DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\nconst DEFAULT_FLUSH_TIMEOUT_MS = 60_000;\nconst DEFAULT_ADAPTER_HEALTH_TIMEOUT_MS = 5_000;\n\n// ---------------------------------------------------------------------------\n// Node HTTP helpers\n// ---------------------------------------------------------------------------\n\n/** Read the full request body as a string. */\nfunction readBody(req: IncomingMessage): Promise<string> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst chunks: Buffer[] = [];\n\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\treq.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf-8\")));\n\t\treq.on(\"error\", reject);\n\t});\n}\n\n/** Send a JSON response. */\nfunction sendJson(\n\tres: ServerResponse,\n\tbody: unknown,\n\tstatus = 200,\n\textraHeaders?: Record<string, string>,\n): void {\n\tconst json = JSON.stringify(body, bigintReplacer);\n\tres.writeHead(status, {\n\t\t\"Content-Type\": \"application/json\",\n\t\t...extraHeaders,\n\t});\n\tres.end(json);\n}\n\n/** Send a JSON error response. */\nfunction sendError(\n\tres: ServerResponse,\n\tmessage: string,\n\tstatus: number,\n\textraHeaders?: Record<string, string>,\n): void {\n\tsendJson(res, { error: message }, status, extraHeaders);\n}\n\n/** Send a HandlerResult as HTTP response. */\nfunction sendResult(\n\tres: ServerResponse,\n\tresult: HandlerResult,\n\tcorsH: Record<string, string>,\n): void {\n\tsendJson(res, result.body, result.status, corsH);\n}\n\n// ---------------------------------------------------------------------------\n// GatewayServer\n// ---------------------------------------------------------------------------\n\n/**\n * Self-hosted HTTP gateway server wrapping {@link SyncGateway}.\n *\n * Composes extracted modules: cors-middleware, auth-middleware, router,\n * ws-manager, and connector-manager. Request handling follows the\n * pipeline: cors -> auth -> route -> handler.\n *\n * @example\n * ```ts\n * const server = new GatewayServer({\n * gatewayId: \"my-gateway\",\n * port: 3000,\n * adapter: new PostgresAdapter({ connectionString: \"...\" }),\n * jwtSecret: process.env.JWT_SECRET,\n * });\n * await server.start();\n * ```\n */\nexport class GatewayServer {\n\tprivate readonly gateway: SyncGateway;\n\tprivate readonly config: Required<\n\t\tPick<GatewayServerConfig, \"port\" | \"gatewayId\" | \"flushIntervalMs\">\n\t> &\n\t\tGatewayServerConfig;\n\tprivate readonly configStore: ConfigStore;\n\tprivate readonly persistence: DeltaPersistence;\n\tprivate readonly connectors: ConnectorManager;\n\tprivate readonly sharedBuffer: SharedBuffer | null;\n\tprivate readonly rateLimiter: RateLimiter | null;\n\tprivate readonly logger: Logger;\n\tprivate readonly metrics: MetricsRegistry;\n\n\tprivate httpServer: Server | null = null;\n\tprivate wsManager: WebSocketManager | null = null;\n\tprivate flushTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate resolvedPort = 0;\n\tprivate pollers: SourcePoller[] = [];\n\n\t/** Whether the server is draining (rejecting new requests during shutdown). */\n\tprivate draining = false;\n\t/** Number of in-flight requests currently being handled. */\n\tprivate activeRequests = 0;\n\t/** Signal handler cleanup functions. */\n\tprivate signalCleanup: (() => void) | null = null;\n\n\tconstructor(config: GatewayServerConfig) {\n\t\tthis.config = {\n\t\t\tport: config.port ?? DEFAULT_PORT,\n\t\t\tflushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,\n\t\t\t...config,\n\t\t};\n\n\t\tthis.gateway = new SyncGateway(\n\t\t\t{\n\t\t\t\tgatewayId: config.gatewayId,\n\t\t\t\tmaxBufferBytes: config.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES,\n\t\t\t\tmaxBufferAgeMs: config.maxBufferAgeMs ?? DEFAULT_MAX_BUFFER_AGE_MS,\n\t\t\t},\n\t\t\tconfig.adapter,\n\t\t);\n\n\t\tthis.configStore = new MemoryConfigStore();\n\n\t\tthis.persistence =\n\t\t\tconfig.persistence === \"sqlite\"\n\t\t\t\t? new SqlitePersistence(config.sqlitePath ?? \"./lakesync-buffer.sqlite\")\n\t\t\t\t: new MemoryPersistence();\n\n\t\tthis.sharedBuffer = config.cluster\n\t\t\t? new SharedBuffer(config.cluster.sharedAdapter, config.cluster.sharedBufferConfig)\n\t\t\t: null;\n\n\t\t// Build poller registry — default includes Jira + Salesforce\n\t\tconst pollerRegistry =\n\t\t\tconfig.pollerRegistry ??\n\t\t\tcreatePollerRegistry()\n\t\t\t\t.with(\"jira\", jiraPollerFactory)\n\t\t\t\t.with(\"salesforce\", salesforcePollerFactory);\n\n\t\tthis.connectors = new ConnectorManager(this.configStore, this.gateway, {\n\t\t\tpollerRegistry,\n\t\t\tadapterRegistry: config.adapterRegistry,\n\t\t\tpersistence: this.persistence,\n\t\t});\n\n\t\tthis.rateLimiter = config.rateLimiter ? new RateLimiter(config.rateLimiter) : null;\n\t\tthis.logger = new Logger(config.logLevel ?? \"info\");\n\t\tthis.metrics = new MetricsRegistry();\n\t}\n\n\t/**\n\t * Start the HTTP server and periodic flush timer.\n\t *\n\t * Rehydrates unflushed deltas from the persistence layer directly\n\t * into the buffer (bypassing push validation) before accepting\n\t * connections.\n\t */\n\tasync start(): Promise<void> {\n\t\t// Rehydrate unflushed deltas directly into the buffer\n\t\tconst persisted = this.persistence.loadAll();\n\t\tif (persisted.length > 0) {\n\t\t\tthis.gateway.rehydrate(persisted);\n\t\t\tthis.persistence.clear();\n\t\t}\n\n\t\tthis.httpServer = createServer((req, res) => {\n\t\t\tvoid this.handleRequest(req, res);\n\t\t});\n\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tthis.httpServer!.listen(this.config.port, () => {\n\t\t\t\tconst addr = this.httpServer!.address();\n\t\t\t\tif (addr && typeof addr === \"object\") {\n\t\t\t\t\tthis.resolvedPort = addr.port;\n\t\t\t\t}\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\n\t\t// WebSocket manager\n\t\tthis.wsManager = new WebSocketManager(\n\t\t\tthis.gateway,\n\t\t\tthis.configStore,\n\t\t\tthis.config.gatewayId,\n\t\t\tthis.config.jwtSecret,\n\t\t\tthis.config.wsLimits,\n\t\t);\n\t\tthis.wsManager.attach(this.httpServer);\n\n\t\t// Periodic flush\n\t\tthis.flushTimer = setInterval(() => {\n\t\t\tvoid this.periodicFlush();\n\t\t}, this.config.flushIntervalMs);\n\n\t\t// Start ingest pollers\n\t\tif (this.config.ingestSources) {\n\t\t\tfor (const source of this.config.ingestSources) {\n\t\t\t\tconst poller = new SourcePoller(source, this.gateway);\n\n\t\t\t\t// Restore persisted cursor state\n\t\t\t\tconst saved = this.persistence.loadCursor(source.name);\n\t\t\t\tif (saved) {\n\t\t\t\t\tpoller.setCursorState(JSON.parse(saved));\n\t\t\t\t}\n\t\t\t\tpoller.onCursorUpdate = (state) => {\n\t\t\t\t\tthis.persistence.saveCursor(source.name, JSON.stringify(state));\n\t\t\t\t};\n\n\t\t\t\tpoller.start();\n\t\t\t\tthis.pollers.push(poller);\n\t\t\t}\n\t\t}\n\n\t\t// Signal handlers for graceful shutdown\n\t\tthis.setupSignalHandlers();\n\t}\n\n\t/** Stop the server, pollers, connectors, and WebSocket connections. */\n\tasync stop(): Promise<void> {\n\t\t// Remove signal handlers\n\t\tif (this.signalCleanup) {\n\t\t\tthis.signalCleanup();\n\t\t\tthis.signalCleanup = null;\n\t\t}\n\n\t\t// Stop dynamic connectors (pollers + adapters)\n\t\tawait this.connectors.stopAll();\n\n\t\t// Stop ingest pollers\n\t\tfor (const poller of this.pollers) {\n\t\t\tpoller.stop();\n\t\t}\n\t\tthis.pollers = [];\n\n\t\tif (this.flushTimer) {\n\t\t\tclearInterval(this.flushTimer);\n\t\t\tthis.flushTimer = null;\n\t\t}\n\n\t\t// Close WebSocket connections\n\t\tif (this.wsManager) {\n\t\t\tthis.wsManager.close();\n\t\t\tthis.wsManager = null;\n\t\t}\n\n\t\tif (this.httpServer) {\n\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\tthis.httpServer!.close(() => resolve());\n\t\t\t});\n\t\t\tthis.httpServer = null;\n\t\t}\n\n\t\tif (this.rateLimiter) {\n\t\t\tthis.rateLimiter.dispose();\n\t\t}\n\n\t\tthis.persistence.close();\n\t}\n\n\t/** Whether the server is currently draining connections. */\n\tget isDraining(): boolean {\n\t\treturn this.draining;\n\t}\n\n\t/** The port the server is listening on (available after start). */\n\tget port(): number {\n\t\treturn this.resolvedPort || this.config.port;\n\t}\n\n\t/** The underlying SyncGateway instance for direct access. */\n\tget gatewayInstance(): SyncGateway {\n\t\treturn this.gateway;\n\t}\n\n\t/** The Prometheus metrics registry. */\n\tget metricsRegistry(): MetricsRegistry {\n\t\treturn this.metrics;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Request handling — cors -> auth -> route -> handler\n\t// -----------------------------------------------------------------------\n\n\tprivate async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst method = req.method ?? \"GET\";\n\t\tconst rawUrl = req.url ?? \"/\";\n\t\tconst url = new URL(rawUrl, `http://${req.headers.host ?? \"localhost\"}`);\n\t\tconst pathname = url.pathname;\n\t\tconst origin = req.headers.origin ?? null;\n\t\tconst requestId = crypto.randomUUID();\n\t\tconst reqLogger = this.logger.child({ requestId, method, path: pathname });\n\n\t\t// Track active requests\n\t\tthis.metrics.activeRequests.inc();\n\t\tres.on(\"close\", () => {\n\t\t\tthis.metrics.activeRequests.dec();\n\t\t});\n\n\t\t// Step 1: CORS headers\n\t\tconst corsH = corsHeaders(origin, { allowedOrigins: this.config.allowedOrigins });\n\n\t\t// Step 2: CORS preflight\n\t\tif (handlePreflight(method, res, corsH)) return;\n\n\t\t// Step 3: Static routes (no auth) — always available even during drain\n\t\tif (pathname === \"/health\" && method === \"GET\") {\n\t\t\tsendJson(res, { status: \"ok\" }, 200, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\tif (pathname === \"/ready\" && method === \"GET\") {\n\t\t\tawait this.handleReady(res, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\t// Prometheus metrics endpoint (no auth required)\n\t\tif (pathname === \"/metrics\" && method === \"GET\") {\n\t\t\tthis.updateBufferGauges();\n\t\t\tconst body = this.metrics.expose();\n\t\t\tres.writeHead(200, {\n\t\t\t\t\"Content-Type\": \"text/plain; version=0.0.4; charset=utf-8\",\n\t\t\t\t...corsH,\n\t\t\t});\n\t\t\tres.end(body);\n\t\t\treturn;\n\t\t}\n\n\t\tif (pathname === \"/connectors/types\" && method === \"GET\") {\n\t\t\tconst result = handleListConnectorTypes();\n\t\t\tsendResult(res, result, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\t// Step 3b: Reject new requests during drain\n\t\tif (this.draining) {\n\t\t\tsendError(res, \"Service is shutting down\", 503, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\t// Step 3c: Request timeout\n\t\tconst timeoutMs = this.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n\t\tres.setTimeout(timeoutMs, () => {\n\t\t\tif (!res.writableEnded) {\n\t\t\t\tsendError(res, \"Request timeout\", 504, corsH);\n\t\t\t}\n\t\t});\n\n\t\t// Track active requests for graceful shutdown\n\t\tthis.activeRequests++;\n\t\ttry {\n\t\t\tawait this.dispatchRoute(req, res, method, url, pathname, corsH, reqLogger);\n\t\t} finally {\n\t\t\tthis.activeRequests--;\n\t\t}\n\t}\n\n\t/** Dispatch an authenticated request to the correct route handler. */\n\tprivate async dispatchRoute(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tmethod: string,\n\t\turl: URL,\n\t\tpathname: string,\n\t\tcorsH: Record<string, string>,\n\t\treqLogger: Logger,\n\t): Promise<void> {\n\t\t// Step 4: Route matching\n\t\tconst route = matchRoute(pathname, method);\n\t\tif (!route) {\n\t\t\tsendError(res, \"Not found\", 404, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\tif (route.gatewayId !== this.config.gatewayId) {\n\t\t\tsendError(res, \"Gateway ID mismatch\", 404, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\t// Step 5: Authentication\n\t\tconst authResult = await authenticateRequest(\n\t\t\treq,\n\t\t\troute.gatewayId,\n\t\t\troute.action,\n\t\t\tthis.config.jwtSecret,\n\t\t);\n\t\tif (!authResult.authenticated) {\n\t\t\tsendError(res, authResult.message, authResult.status, corsH);\n\t\t\treturn;\n\t\t}\n\t\tconst auth: AuthClaims | undefined = this.config.jwtSecret ? authResult.claims : undefined;\n\n\t\t// Step 5b: Rate limiting (after auth, before dispatch)\n\t\tif (this.rateLimiter) {\n\t\t\tconst clientKey = auth?.clientId ?? req.socket.remoteAddress ?? \"unknown\";\n\t\t\tif (!this.rateLimiter.tryConsume(clientKey)) {\n\t\t\t\tconst retryAfter = this.rateLimiter.retryAfterSeconds(clientKey);\n\t\t\t\tsendError(res, \"Too many requests\", 429, {\n\t\t\t\t\t...corsH,\n\t\t\t\t\t\"Retry-After\": String(retryAfter),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Step 6: Route dispatch\n\t\tswitch (route.action) {\n\t\t\tcase \"push\":\n\t\t\t\tawait this.handlePush(req, res, corsH, auth, reqLogger);\n\t\t\t\tbreak;\n\t\t\tcase \"pull\":\n\t\t\t\tawait this.handlePull(url, res, corsH, auth, reqLogger);\n\t\t\t\tbreak;\n\t\t\tcase \"action\":\n\t\t\t\tawait this.handleAction(req, res, corsH, auth);\n\t\t\t\tbreak;\n\t\t\tcase \"describe-actions\":\n\t\t\t\tthis.handleDescribeActions(res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"flush\":\n\t\t\t\tawait this.handleFlush(res, corsH, reqLogger);\n\t\t\t\tbreak;\n\t\t\tcase \"schema\":\n\t\t\t\tawait this.handleSaveSchemaRoute(req, res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"sync-rules\":\n\t\t\t\tawait this.handleSaveSyncRulesRoute(req, res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"register-connector\":\n\t\t\t\tawait this.handleRegisterConnectorRoute(req, res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"unregister-connector\":\n\t\t\t\tawait this.handleUnregisterConnectorRoute(route.connectorName!, res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"list-connectors\":\n\t\t\t\tawait this.handleListConnectorsRoute(res, corsH);\n\t\t\t\tbreak;\n\t\t\tcase \"metrics\":\n\t\t\t\tthis.handleMetricsRoute(res, corsH);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tsendError(res, \"Not found\", 404, corsH);\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Route handlers — thin wrappers delegating to shared handlers or modules\n\t// -----------------------------------------------------------------------\n\n\tprivate async handlePush(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t\tauth?: AuthClaims,\n\t\treqLogger?: Logger,\n\t): Promise<void> {\n\t\tconst start = performance.now();\n\t\tconst contentLength = Number(req.headers[\"content-length\"] ?? \"0\");\n\t\tif (contentLength > MAX_PUSH_PAYLOAD_BYTES) {\n\t\t\tthis.metrics.pushTotal.inc({ status: \"error\" });\n\t\t\tsendError(res, \"Payload too large (max 1 MiB)\", 413, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\tconst raw = await readBody(req);\n\t\tconst result = handlePushRequest(this.gateway, raw, auth?.clientId, {\n\t\t\tpersistBatch: (deltas) => this.persistence.appendBatch(deltas),\n\t\t\tclearPersistence: () => this.persistence.clear(),\n\t\t\tbroadcastFn: (deltas, serverHlc, excludeClientId) =>\n\t\t\t\tthis.wsManager?.broadcastDeltas(deltas, serverHlc, excludeClientId),\n\t\t});\n\n\t\t// Shared buffer write-through for cross-instance visibility\n\t\tif (result.status === 200 && this.sharedBuffer) {\n\t\t\tconst pushResult = result.body as { deltas: RowDelta[]; serverHlc: HLCTimestamp };\n\t\t\tif (pushResult.deltas.length > 0) {\n\t\t\t\tconst writeResult = await this.sharedBuffer.writeThroughPush(pushResult.deltas);\n\t\t\t\tif (!writeResult.ok) {\n\t\t\t\t\tthis.metrics.pushTotal.inc({ status: \"error\" });\n\t\t\t\t\tsendError(res, writeResult.error.message, 502, corsH);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst status = result.status === 200 ? \"ok\" : \"error\";\n\t\tconst durationMs = Math.round(performance.now() - start);\n\t\tthis.metrics.pushTotal.inc({ status });\n\t\tthis.metrics.pushLatency.observe({}, performance.now() - start);\n\t\tthis.updateBufferGauges();\n\t\treqLogger?.info(\"push completed\", { status: result.status, durationMs });\n\n\t\tsendResult(res, result, corsH);\n\t}\n\n\tprivate async handlePull(\n\t\turl: URL,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t\tauth?: AuthClaims,\n\t\treqLogger?: Logger,\n\t): Promise<void> {\n\t\tconst syncRules = await this.configStore.getSyncRules(this.config.gatewayId);\n\t\tconst result = await handlePullRequest(\n\t\t\tthis.gateway,\n\t\t\t{\n\t\t\t\tsince: url.searchParams.get(\"since\"),\n\t\t\t\tclientId: url.searchParams.get(\"clientId\"),\n\t\t\t\tlimit: url.searchParams.get(\"limit\"),\n\t\t\t\tsource: url.searchParams.get(\"source\"),\n\t\t\t},\n\t\t\tauth?.customClaims,\n\t\t\tsyncRules,\n\t\t);\n\n\t\t// Merge with shared buffer for cross-instance visibility\n\t\tlet body = result.body;\n\t\tif (result.status === 200 && this.sharedBuffer) {\n\t\t\tconst sinceParam = url.searchParams.get(\"since\");\n\t\t\tif (sinceParam) {\n\t\t\t\ttry {\n\t\t\t\t\tconst sinceHlc = BigInt(sinceParam) as HLCTimestamp;\n\t\t\t\t\tbody = await this.sharedBuffer.mergePull(body as SyncResponse, sinceHlc);\n\t\t\t\t} catch {\n\t\t\t\t\t// If since parsing fails, pull handler already returned an error\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst pullStatus = result.status === 200 ? \"ok\" : \"error\";\n\t\tthis.metrics.pullTotal.inc({ status: pullStatus });\n\t\treqLogger?.info(\"pull completed\", { status: result.status });\n\n\t\tsendJson(res, body, result.status, corsH);\n\t}\n\n\tprivate async handleAction(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t\tauth?: AuthClaims,\n\t): Promise<void> {\n\t\tconst raw = await readBody(req);\n\t\tconst result = await handleActionRequest(this.gateway, raw, auth?.clientId, auth?.customClaims);\n\t\tsendResult(res, result, corsH);\n\t}\n\n\tprivate handleDescribeActions(res: ServerResponse, corsH: Record<string, string>): void {\n\t\tsendJson(res, this.gateway.describeActions(), 200, corsH);\n\t}\n\n\tprivate async handleFlush(\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t\treqLogger?: Logger,\n\t): Promise<void> {\n\t\tconst start = performance.now();\n\t\tconst result = await handleFlushRequest(this.gateway, {\n\t\t\tclearPersistence: () => this.persistence.clear(),\n\t\t});\n\t\tconst durationMs = Math.round(performance.now() - start);\n\t\tconst status = result.status === 200 ? \"ok\" : \"error\";\n\t\tthis.metrics.flushTotal.inc({ status });\n\t\tthis.metrics.flushDuration.observe({}, performance.now() - start);\n\t\tthis.updateBufferGauges();\n\t\treqLogger?.info(\"flush completed\", { status: result.status, durationMs });\n\t\tsendResult(res, result, corsH);\n\t}\n\n\tprivate async handleSaveSchemaRoute(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t): Promise<void> {\n\t\tconst raw = await readBody(req);\n\t\tconst result = await handleSaveSchema(raw, this.configStore, this.config.gatewayId);\n\t\tsendResult(res, result, corsH);\n\t}\n\n\tprivate async handleSaveSyncRulesRoute(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t): Promise<void> {\n\t\tconst raw = await readBody(req);\n\t\tconst result = await handleSaveSyncRules(raw, this.configStore, this.config.gatewayId);\n\t\tsendResult(res, result, corsH);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Connector management — delegates to ConnectorManager\n\t// -----------------------------------------------------------------------\n\n\tprivate async handleRegisterConnectorRoute(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t): Promise<void> {\n\t\tconst raw = await readBody(req);\n\t\tconst result = await this.connectors.register(raw);\n\t\tsendResult(res, result, corsH);\n\t}\n\n\tprivate async handleUnregisterConnectorRoute(\n\t\tname: string,\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t): Promise<void> {\n\t\tconst result = await this.connectors.unregister(name);\n\t\tsendResult(res, result, corsH);\n\t}\n\n\tprivate async handleListConnectorsRoute(\n\t\tres: ServerResponse,\n\t\tcorsH: Record<string, string>,\n\t): Promise<void> {\n\t\tconst result = await this.connectors.list();\n\t\tsendJson(res, result.body, result.status, corsH);\n\t}\n\n\tprivate handleMetricsRoute(res: ServerResponse, corsH: Record<string, string>): void {\n\t\tconst result = handleMetrics(this.gateway, { process: process.memoryUsage() });\n\t\tsendResult(res, result, corsH);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Buffer gauge helpers\n\t// -----------------------------------------------------------------------\n\n\t/** Synchronise buffer gauge metrics with the current buffer state. */\n\tprivate updateBufferGauges(): void {\n\t\tconst stats = this.gateway.bufferStats;\n\t\tthis.metrics.bufferBytes.set({}, stats.byteSize);\n\t\tthis.metrics.bufferDeltas.set({}, stats.logSize);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Readiness probe\n\t// -----------------------------------------------------------------------\n\n\t/** Handle GET /ready — checks draining status and adapter health. */\n\tprivate async handleReady(res: ServerResponse, corsH: Record<string, string>): Promise<void> {\n\t\tif (this.draining) {\n\t\t\tsendJson(res, { status: \"not_ready\", reason: \"draining\" }, 503, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\tconst adapterHealthy = await this.checkAdapterHealth();\n\t\tif (!adapterHealthy) {\n\t\t\tsendJson(res, { status: \"not_ready\", reason: \"adapter unreachable\" }, 503, corsH);\n\t\t\treturn;\n\t\t}\n\n\t\tsendJson(res, { status: \"ready\" }, 200, corsH);\n\t}\n\n\t/**\n\t * Check whether the configured adapter is reachable.\n\t *\n\t * For a DatabaseAdapter, attempts a lightweight query with a timeout.\n\t * For a LakeAdapter, attempts a headObject call (404 still means reachable).\n\t * Returns true when no adapter is configured (stateless mode).\n\t */\n\tprivate async checkAdapterHealth(): Promise<boolean> {\n\t\tconst adapter = this.config.adapter;\n\t\tif (!adapter) return true;\n\n\t\tconst timeoutMs = DEFAULT_ADAPTER_HEALTH_TIMEOUT_MS;\n\t\tconst timeoutPromise = new Promise<false>((resolve) => {\n\t\t\tsetTimeout(() => resolve(false), timeoutMs);\n\t\t});\n\n\t\ttry {\n\t\t\tif (isDatabaseAdapter(adapter)) {\n\t\t\t\tconst healthCheck = adapter\n\t\t\t\t\t.queryDeltasSince(0n as HLCTimestamp, [])\n\t\t\t\t\t.then((result) => result.ok);\n\t\t\t\treturn await Promise.race([healthCheck, timeoutPromise]);\n\t\t\t}\n\t\t\t// LakeAdapter — try headObject on a known key\n\t\t\tconst healthCheck = (adapter as LakeAdapter)\n\t\t\t\t.headObject(\"__health__\")\n\t\t\t\t.then(() => true)\n\t\t\t\t.catch(() => true); // S3 404 is still \"reachable\"\n\t\t\treturn await Promise.race([healthCheck, timeoutPromise]);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Graceful shutdown — signal handlers\n\t// -----------------------------------------------------------------------\n\n\t/** Register SIGTERM/SIGINT handlers for graceful shutdown. */\n\tprivate setupSignalHandlers(): void {\n\t\tconst shutdown = () => {\n\t\t\tvoid this.gracefulShutdown();\n\t\t};\n\t\tprocess.on(\"SIGTERM\", shutdown);\n\t\tprocess.on(\"SIGINT\", shutdown);\n\t\tthis.signalCleanup = () => {\n\t\t\tprocess.off(\"SIGTERM\", shutdown);\n\t\t\tprocess.off(\"SIGINT\", shutdown);\n\t\t};\n\t}\n\n\t/** Graceful shutdown: stop accepting, drain, flush, exit. */\n\tprivate async gracefulShutdown(): Promise<void> {\n\t\tif (this.draining) return;\n\t\tthis.draining = true;\n\n\t\tthis.logger.info(\"Graceful shutdown initiated, draining requests...\");\n\n\t\t// Stop accepting new connections\n\t\tif (this.httpServer) {\n\t\t\tthis.httpServer.close();\n\t\t}\n\n\t\t// Wait for active requests to drain (up to drainTimeoutMs)\n\t\tconst drainTimeout = this.config.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;\n\t\tconst start = Date.now();\n\t\twhile (this.activeRequests > 0 && Date.now() - start < drainTimeout) {\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, 100));\n\t\t}\n\n\t\t// Final flush\n\t\ttry {\n\t\t\tawait this.gateway.flush();\n\t\t} catch {\n\t\t\t// Best-effort flush\n\t\t}\n\n\t\tawait this.stop();\n\t\tprocess.exit(0);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Periodic flush\n\t// -----------------------------------------------------------------------\n\n\tprivate async periodicFlush(): Promise<void> {\n\t\tif (this.gateway.bufferStats.logSize === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst lock = this.config.cluster?.lock;\n\t\tconst lockKey = `flush:${this.config.gatewayId}`;\n\t\tif (lock) {\n\t\t\tconst acquired = await lock.acquire(lockKey, 30_000);\n\t\t\tif (!acquired) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tconst flushTimeoutMs = this.config.flushTimeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS;\n\t\tconst start = performance.now();\n\t\ttry {\n\t\t\tconst flushPromise = this.gateway.flush();\n\t\t\tconst timeoutPromise = new Promise<{ ok: false; timedOut: true }>((resolve) => {\n\t\t\t\tsetTimeout(() => resolve({ ok: false, timedOut: true }), flushTimeoutMs);\n\t\t\t});\n\n\t\t\tconst result = await Promise.race([flushPromise, timeoutPromise]);\n\n\t\t\tif (\"timedOut\" in result) {\n\t\t\t\tthis.logger.warn(`Periodic flush timed out after ${flushTimeoutMs}ms`);\n\t\t\t\tthis.metrics.flushTotal.inc({ status: \"error\" });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (result.ok) {\n\t\t\t\tthis.persistence.clear();\n\t\t\t\tthis.metrics.flushTotal.inc({ status: \"ok\" });\n\t\t\t} else {\n\t\t\t\tthis.metrics.flushTotal.inc({ status: \"error\" });\n\t\t\t}\n\t\t\tthis.metrics.flushDuration.observe({}, performance.now() - start);\n\t\t\tthis.updateBufferGauges();\n\t\t} catch (err) {\n\t\t\tthis.metrics.flushTotal.inc({ status: \"error\" });\n\t\t\tthis.logger.error(\"periodic flush failed\", {\n\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t});\n\t\t} finally {\n\t\t\tif (lock) {\n\t\t\t\tawait lock.release(lockKey);\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { DatabaseAdapter, HLCTimestamp, RowDelta, SyncResponse } from \"@lakesync/core\";\nimport { Err, Ok, type Result } from \"@lakesync/core\";\n\n/** Consistency mode for shared buffer writes. */\nexport type ConsistencyMode = \"eventual\" | \"strong\";\n\n/** Configuration for SharedBuffer. */\nexport interface SharedBufferConfig {\n\t/**\n\t * Controls how shared adapter write failures are handled.\n\t *\n\t * - `\"eventual\"` (default): Shared writes are best-effort. Failures are\n\t * logged but do not fail the push. Local buffer remains authoritative.\n\t * - `\"strong\"`: Shared writes must succeed. Failures are returned as errors,\n\t * allowing the caller to decide whether to fail the push.\n\t */\n\tconsistencyMode?: ConsistencyMode;\n}\n\n/** Error returned by SharedBuffer in strong consistency mode. */\nexport interface SharedBufferError {\n\tcode: \"SHARED_WRITE_FAILED\";\n\tmessage: string;\n}\n\n/**\n * Write-through buffer that pushes to both the in-memory gateway buffer\n * and a shared database adapter for cross-instance visibility.\n *\n * Pull merges in-memory buffer results with adapter query results,\n * deduplicating by deltaId.\n */\nexport class SharedBuffer {\n\tprivate readonly consistencyMode: ConsistencyMode;\n\n\tconstructor(\n\t\tprivate readonly sharedAdapter: DatabaseAdapter,\n\t\tconfig?: SharedBufferConfig,\n\t) {\n\t\tthis.consistencyMode = config?.consistencyMode ?? \"eventual\";\n\t}\n\n\t/**\n\t * Write-through push: write to shared adapter for cross-instance visibility.\n\t *\n\t * In \"eventual\" mode (default), failures are logged but do not fail the push.\n\t * In \"strong\" mode, failures are returned as errors.\n\t */\n\tasync writeThroughPush(deltas: RowDelta[]): Promise<Result<void, SharedBufferError>> {\n\t\ttry {\n\t\t\tconst result = await this.sharedAdapter.insertDeltas(deltas);\n\t\t\tif (!result.ok) {\n\t\t\t\tif (this.consistencyMode === \"strong\") {\n\t\t\t\t\treturn Err({ code: \"SHARED_WRITE_FAILED\" as const, message: result.error.message });\n\t\t\t\t}\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[lakesync] Shared buffer write failed (eventual mode): ${result.error.message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn Ok(undefined);\n\t\t} catch (error: unknown) {\n\t\t\tif (this.consistencyMode === \"strong\") {\n\t\t\t\treturn Err({\n\t\t\t\t\tcode: \"SHARED_WRITE_FAILED\" as const,\n\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t});\n\t\t\t}\n\t\t\tconsole.warn(\n\t\t\t\t`[lakesync] Shared buffer write error (eventual mode): ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\treturn Ok(undefined);\n\t\t}\n\t}\n\n\t/**\n\t * Merge pull: combine local buffer results with shared adapter results.\n\t *\n\t * Deduplicates by deltaId to avoid returning the same delta twice.\n\t */\n\tasync mergePull(localResult: SyncResponse, sinceHlc: HLCTimestamp): Promise<SyncResponse> {\n\t\ttry {\n\t\t\tconst adapterResult = await this.sharedAdapter.queryDeltasSince(sinceHlc);\n\t\t\tif (!adapterResult.ok) {\n\t\t\t\treturn localResult; // Fallback to local-only\n\t\t\t}\n\n\t\t\t// Deduplicate by deltaId\n\t\t\tconst seenIds = new Set(localResult.deltas.map((d) => d.deltaId));\n\t\t\tconst additional = adapterResult.value.filter((d) => !seenIds.has(d.deltaId));\n\n\t\t\tif (additional.length === 0) {\n\t\t\t\treturn localResult;\n\t\t\t}\n\n\t\t\tconst merged = [...localResult.deltas, ...additional];\n\t\t\tmerged.sort((a, b) => (a.hlc < b.hlc ? -1 : a.hlc > b.hlc ? 1 : 0));\n\n\t\t\treturn {\n\t\t\t\tdeltas: merged,\n\t\t\t\tserverHlc: localResult.serverHlc,\n\t\t\t\thasMore: true,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn localResult; // Fallback to local-only on error\n\t\t}\n\t}\n}\n","// ---------------------------------------------------------------------------\n// WebSocket Manager — upgrade, message parsing, broadcast, client tracking\n// ---------------------------------------------------------------------------\n\nimport type { IncomingMessage, Server } from \"node:http\";\nimport type { HLCTimestamp, ResolvedClaims, RowDelta, SyncRulesContext } from \"@lakesync/core\";\nimport { filterDeltas } from \"@lakesync/core\";\nimport type { ConfigStore, SyncGateway } from \"@lakesync/gateway\";\nimport {\n\tdecodeSyncPull,\n\tdecodeSyncPush,\n\tencodeBroadcastFrame,\n\tencodeSyncResponse,\n\tTAG_SYNC_PULL,\n\tTAG_SYNC_PUSH,\n} from \"@lakesync/proto\";\nimport { WebSocketServer, type WebSocket as WsWebSocket } from \"ws\";\nimport type { AuthClaims } from \"./auth\";\nimport { verifyToken } from \"./auth\";\nimport { extractBearerToken } from \"./auth-middleware\";\n\n/** Configuration for WebSocket connection and message rate limits. */\nexport interface WebSocketLimitsConfig {\n\t/** Maximum concurrent WebSocket connections (default: 1000). */\n\tmaxConnections?: number;\n\t/** Maximum messages per second per client (default: 50). */\n\tmaxMessagesPerSecond?: number;\n}\n\n/** Metadata stored for each connected WebSocket client. */\ninterface WsClientMeta {\n\tclientId: string;\n\tclaims: Record<string, unknown>;\n}\n\n/** Per-client message rate tracking. */\ninterface MessageRateEntry {\n\tcount: number;\n\twindowStart: number;\n}\n\n/**\n * Manages WebSocket connections, message handling, and broadcasting.\n *\n * Decouples the WebSocket protocol from the HTTP server lifecycle.\n */\nexport class WebSocketManager {\n\tprivate readonly wss: WebSocketServer;\n\tprivate readonly clients = new Map<WsWebSocket, WsClientMeta>();\n\tprivate readonly maxConnections: number;\n\tprivate readonly maxMessagesPerSecond: number;\n\tprivate readonly messageRates = new Map<WsWebSocket, MessageRateEntry>();\n\tprivate rateResetTimer: ReturnType<typeof setInterval> | null = null;\n\n\tconstructor(\n\t\tprivate readonly gateway: SyncGateway,\n\t\tprivate readonly configStore: ConfigStore,\n\t\tprivate readonly gatewayId: string,\n\t\tprivate readonly jwtSecret: string | undefined,\n\t\tlimits?: WebSocketLimitsConfig,\n\t) {\n\t\tthis.wss = new WebSocketServer({ noServer: true });\n\t\tthis.maxConnections = limits?.maxConnections ?? 1000;\n\t\tthis.maxMessagesPerSecond = limits?.maxMessagesPerSecond ?? 50;\n\n\t\t// Reset message rate counters every second\n\t\tthis.rateResetTimer = setInterval(() => {\n\t\t\tthis.messageRates.clear();\n\t\t}, 1000);\n\t\tif (this.rateResetTimer.unref) {\n\t\t\tthis.rateResetTimer.unref();\n\t\t}\n\t}\n\n\t/** The current number of connected clients. */\n\tget connectionCount(): number {\n\t\treturn this.clients.size;\n\t}\n\n\t/** Attach upgrade listener to an HTTP server. */\n\tattach(httpServer: Server): void {\n\t\thttpServer.on(\"upgrade\", (req, socket, head) => {\n\t\t\tvoid this.handleUpgrade(req, socket, head);\n\t\t});\n\t}\n\n\t/**\n\t * Broadcast ingested deltas to all connected WebSocket clients except the sender.\n\t */\n\tbroadcastDeltas(deltas: RowDelta[], serverHlc: HLCTimestamp, excludeClientId: string): void {\n\t\tif (deltas.length === 0) return;\n\t\tvoid this.broadcastDeltasAsync(deltas, serverHlc, excludeClientId);\n\t}\n\n\t/** Close all connections and shut down the WebSocket server. */\n\tclose(): void {\n\t\tif (this.rateResetTimer) {\n\t\t\tclearInterval(this.rateResetTimer);\n\t\t\tthis.rateResetTimer = null;\n\t\t}\n\t\tthis.messageRates.clear();\n\n\t\tfor (const ws of this.clients.keys()) {\n\t\t\ttry {\n\t\t\t\tws.close(1001, \"Server shutting down\");\n\t\t\t} catch {\n\t\t\t\t/* ignore */\n\t\t\t}\n\t\t}\n\t\tthis.clients.clear();\n\t\tthis.wss.close();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Upgrade handling\n\t// -----------------------------------------------------------------------\n\n\tprivate async handleUpgrade(\n\t\treq: IncomingMessage,\n\t\tsocket: import(\"node:stream\").Duplex,\n\t\thead: Buffer,\n\t): Promise<void> {\n\t\t// Reject if at connection limit\n\t\tif (this.clients.size >= this.maxConnections) {\n\t\t\tsocket.write(\"HTTP/1.1 503 Service Unavailable\\r\\n\\r\\n\");\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\n\t\tconst url = new URL(req.url ?? \"/\", `http://${req.headers.host ?? \"localhost\"}`);\n\n\t\t// Authenticate\n\t\tlet token = extractBearerToken(req);\n\t\tif (!token) {\n\t\t\ttoken = url.searchParams.get(\"token\");\n\t\t}\n\n\t\tif (!token && this.jwtSecret) {\n\t\t\tsocket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\n\t\tlet auth: AuthClaims | undefined;\n\t\tif (this.jwtSecret && token) {\n\t\t\tconst authResult = await verifyToken(token, this.jwtSecret);\n\t\t\tif (!authResult.ok) {\n\t\t\t\tsocket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n\t\t\t\tsocket.destroy();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tauth = authResult.value;\n\n\t\t\t// Verify gateway ID\n\t\t\tconst gwMatch = url.pathname.match(/^\\/sync\\/([^/]+)\\/ws$/);\n\t\t\tif (!gwMatch || gwMatch[1] !== auth.gatewayId) {\n\t\t\t\tsocket.write(\"HTTP/1.1 403 Forbidden\\r\\n\\r\\n\");\n\t\t\t\tsocket.destroy();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tthis.wss.handleUpgrade(req, socket, head, (ws) => {\n\t\t\tconst clientId = auth?.clientId ?? `anon-${crypto.randomUUID()}`;\n\t\t\tconst claims: Record<string, unknown> = auth?.customClaims ?? {};\n\n\t\t\tthis.clients.set(ws, { clientId, claims });\n\n\t\t\tws.on(\"message\", (data: Buffer) => {\n\t\t\t\t// Per-client message rate limiting\n\t\t\t\tif (!this.checkMessageRate(ws)) {\n\t\t\t\t\tws.close(1008, \"Rate limit exceeded\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tvoid this.handleMessage(ws, data, clientId, claims);\n\t\t\t});\n\n\t\t\tws.on(\"close\", () => {\n\t\t\t\tthis.clients.delete(ws);\n\t\t\t\tthis.messageRates.delete(ws);\n\t\t\t});\n\n\t\t\tws.on(\"error\", () => {\n\t\t\t\tthis.clients.delete(ws);\n\t\t\t\tthis.messageRates.delete(ws);\n\t\t\t});\n\t\t});\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Message rate limiting\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Check and increment the message rate for a client.\n\t * @returns `true` if the message is allowed, `false` if rate-limited.\n\t */\n\tprivate checkMessageRate(ws: WsWebSocket): boolean {\n\t\tconst now = Date.now();\n\t\tconst entry = this.messageRates.get(ws);\n\n\t\tif (!entry || now - entry.windowStart >= 1000) {\n\t\t\tthis.messageRates.set(ws, { count: 1, windowStart: now });\n\t\t\treturn true;\n\t\t}\n\n\t\tif (entry.count >= this.maxMessagesPerSecond) {\n\t\t\treturn false;\n\t\t}\n\n\t\tentry.count++;\n\t\treturn true;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Message handling\n\t// -----------------------------------------------------------------------\n\n\tprivate async handleMessage(\n\t\tws: WsWebSocket,\n\t\tdata: Buffer,\n\t\tclientId: string,\n\t\tclaims: Record<string, unknown>,\n\t): Promise<void> {\n\t\tconst bytes = new Uint8Array(data);\n\t\tif (bytes.length < 2) {\n\t\t\tws.close(1002, \"Message too short\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst tag = bytes[0];\n\t\tconst payload = bytes.subarray(1);\n\n\t\tif (tag === TAG_SYNC_PUSH) {\n\t\t\tconst decoded = decodeSyncPush(payload);\n\t\t\tif (!decoded.ok) {\n\t\t\t\tws.close(1008, decoded.error.message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst result = this.gateway.handlePush(decoded.value);\n\t\t\tif (!result.ok) {\n\t\t\t\tws.close(1008, result.error.message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Send push response\n\t\t\tconst response = encodeSyncResponse({\n\t\t\t\tdeltas: [],\n\t\t\t\tserverHlc: result.value.serverHlc,\n\t\t\t\thasMore: false,\n\t\t\t});\n\t\t\tif (response.ok) {\n\t\t\t\tws.send(response.value);\n\t\t\t}\n\n\t\t\t// Broadcast to other clients\n\t\t\tthis.broadcastDeltas(result.value.deltas, result.value.serverHlc, clientId);\n\t\t} else if (tag === TAG_SYNC_PULL) {\n\t\t\tconst decoded = decodeSyncPull(payload);\n\t\t\tif (!decoded.ok) {\n\t\t\t\tws.close(1008, decoded.error.message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst context = await this.buildSyncRulesContext(claims);\n\t\t\tconst pullResult = this.gateway.handlePull(\n\t\t\t\tdecoded.value,\n\t\t\t\tcontext,\n\t\t\t) as import(\"@lakesync/core\").Result<\n\t\t\t\timport(\"@lakesync/core\").SyncResponse,\n\t\t\t\t{ message: string }\n\t\t\t>;\n\t\t\tif (!pullResult.ok) {\n\t\t\t\tws.close(1008, pullResult.error.message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst response = encodeSyncResponse(pullResult.value);\n\t\t\tif (response.ok) {\n\t\t\t\tws.send(response.value);\n\t\t\t}\n\t\t} else {\n\t\t\tws.close(1002, `Unknown message tag: 0x${tag!.toString(16).padStart(2, \"0\")}`);\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Sync rules\n\t// -----------------------------------------------------------------------\n\n\tprivate async buildSyncRulesContext(\n\t\tclaims: Record<string, unknown>,\n\t): Promise<SyncRulesContext | undefined> {\n\t\tconst rules = await this.configStore.getSyncRules(this.gatewayId);\n\t\tif (!rules || rules.buckets.length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn { claims: claims as ResolvedClaims, rules };\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Broadcast\n\t// -----------------------------------------------------------------------\n\n\tprivate async broadcastDeltasAsync(\n\t\tdeltas: RowDelta[],\n\t\tserverHlc: HLCTimestamp,\n\t\texcludeClientId: string,\n\t): Promise<void> {\n\t\tconst rules = await this.configStore.getSyncRules(this.gatewayId);\n\n\t\tfor (const [ws, meta] of this.clients) {\n\t\t\tif (meta.clientId === excludeClientId) continue;\n\n\t\t\ttry {\n\t\t\t\tlet filtered: RowDelta[] = deltas;\n\t\t\t\tif (rules && rules.buckets.length > 0) {\n\t\t\t\t\tconst context: SyncRulesContext = {\n\t\t\t\t\t\tclaims: meta.claims as ResolvedClaims,\n\t\t\t\t\t\trules,\n\t\t\t\t\t};\n\t\t\t\t\tfiltered = filterDeltas(deltas, context);\n\t\t\t\t}\n\n\t\t\t\tif (filtered.length === 0) continue;\n\n\t\t\t\tconst frame = encodeBroadcastFrame({\n\t\t\t\t\tdeltas: filtered,\n\t\t\t\t\tserverHlc,\n\t\t\t\t\thasMore: false,\n\t\t\t\t});\n\n\t\t\t\tif (!frame.ok) continue;\n\t\t\t\tws.send(frame.value);\n\t\t\t} catch {\n\t\t\t\t// Socket may have closed -- silently skip\n\t\t\t}\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["healthCheck"]}
package/dist/gateway.d.ts CHANGED
@@ -1,17 +1,16 @@
1
1
  import { S as SyncRulesConfig, R as ResolvedClaims, b as SyncRulesContext, i as ActionPush } from './types-Bs-QyOe-.js';
2
2
  export { d as ActionDiscovery, g as ActionHandler, j as ActionResponse, l as AuthContext } from './types-Bs-QyOe-.js';
3
- export { I as IngestTarget, i as isIngestTarget } from './base-poller-Bj9kX9dv.js';
4
3
  import { T as TableSchema, R as RowDelta, e as SyncPull, S as SyncPush } from './types-BdGBv2ba.js';
5
4
  export { f as SyncResponse } from './types-BdGBv2ba.js';
5
+ export { I as IngestTarget, i as isIngestTarget } from './base-poller-Y7ORYgUv.js';
6
6
  export { b as bigintReplacer, a as bigintReviver } from './json-dYtqiL0F.js';
7
- import { D as DeltaBuffer } from './request-handler-pUvL7ozF.js';
8
- export { A as ActionCacheConfig, a as ActionDispatcher, B as BufferConfig, C as ConfigStore, F as FlushEnvelope, G as GatewayConfig, b as GatewayState, H as HandlePushResult, c as HandlerResult, M as MemoryConfigStore, S as SchemaManager, d as SyncGateway, h as handleActionRequest, e as handleFlushRequest, f as handleListConnectorTypes, g as handleListConnectors, i as handleMetrics, j as handlePullRequest, k as handlePushRequest, l as handleRegisterConnector, m as handleSaveSchema, n as handleSaveSyncRules, o as handleUnregisterConnector } from './request-handler-pUvL7ozF.js';
9
- import { R as Result, F as FlushError, H as HLCTimestamp } from './result-CojzlFE2.js';
10
- import { D as DatabaseAdapter } from './db-types-CfLMUBfW.js';
11
- import { L as LakeAdapter } from './types-DSC_EiwR.js';
7
+ import { D as DeltaBuffer } from './request-handler-B1I5xDOx.js';
8
+ export { A as ActionCacheConfig, a as ActionDispatcher, B as BufferConfig, C as CachedActionResult, b as ConfigStore, F as FlushEnvelope, G as GatewayConfig, c as GatewayState, H as HandlePushResult, d as HandlerResult, I as IdempotencyCache, e as IdempotencyCacheConfig, M as MemoryConfigStore, f as MemoryIdempotencyCache, S as SchemaManager, g as SyncGateway, h as handleActionRequest, i as handleFlushRequest, j as handleListConnectorTypes, k as handleListConnectors, l as handleMetrics, m as handlePullRequest, n as handlePushRequest, o as handleRegisterConnector, p as handleSaveSchema, q as handleSaveSyncRules, r as handleUnregisterConnector } from './request-handler-B1I5xDOx.js';
12
9
  import { N as NessieCatalogueClient } from './nessie-client-DrNikVXy.js';
10
+ import { R as Result, F as FlushError, H as HLCTimestamp } from './result-CojzlFE2.js';
11
+ import { L as LakeAdapter, D as DatabaseAdapter } from './adapter-types-DwsQGQS4.js';
13
12
  import './hlc-DiD8QNG3.js';
14
- import './types-BrcD1oJg.js';
13
+ import './types-D2C9jTbL.js';
15
14
 
16
15
  /** Maximum push payload size (1 MiB). */
17
16
  declare const MAX_PUSH_PAYLOAD_BYTES = 1048576;
@@ -34,6 +33,8 @@ interface FlushConfig {
34
33
  flushFormat?: "json" | "parquet";
35
34
  tableSchema?: TableSchema;
36
35
  catalogue?: NessieCatalogueClient;
36
+ /** Optional callback invoked when materialisation fails. Useful for metrics/alerting. */
37
+ onMaterialisationFailure?: (table: string, deltaCount: number, error: Error) => void;
37
38
  }
38
39
  /** Dependencies injected into flush operations. */
39
40
  interface FlushDeps {
@@ -47,6 +48,11 @@ declare function hlcRange(entries: RowDelta[]): {
47
48
  min: HLCTimestamp;
48
49
  max: HLCTimestamp;
49
50
  };
51
+ /** Strategy for flushing deltas to a specific adapter type. */
52
+ interface FlushStrategy {
53
+ /** Flush entries to the target adapter. */
54
+ flush(entries: RowDelta[], byteSize: number, deps: FlushDeps, keyPrefix?: string): Promise<Result<void, FlushError>>;
55
+ }
50
56
  /**
51
57
  * Flush a set of entries to the configured adapter.
52
58
  *
@@ -155,4 +161,4 @@ declare function pushErrorToStatus(code: string): number;
155
161
  */
156
162
  declare function buildSyncRulesContext(rules: SyncRulesConfig | undefined, claims: ResolvedClaims): SyncRulesContext | undefined;
157
163
 
158
- export { ActionPush, DEFAULT_MAX_BUFFER_AGE_MS, DEFAULT_MAX_BUFFER_BYTES, DEFAULT_PULL_LIMIT, DeltaBuffer, type FlushConfig, FlushCoordinator, type FlushCoordinatorDeps, type FlushDeps, MAX_DELTAS_PER_PUSH, MAX_PULL_LIMIT, MAX_PUSH_PAYLOAD_BYTES, type RequestError, SourceRegistry, SyncPull, SyncPush, VALID_COLUMN_TYPES, buildSyncRulesContext, commitToCatalogue, flushEntries, hlcRange, parseJson, parsePullParams, pushErrorToStatus, validateActionBody, validatePushBody, validateSchemaBody };
164
+ export { ActionPush, DEFAULT_MAX_BUFFER_AGE_MS, DEFAULT_MAX_BUFFER_BYTES, DEFAULT_PULL_LIMIT, DeltaBuffer, type FlushConfig, FlushCoordinator, type FlushCoordinatorDeps, type FlushDeps, type FlushStrategy, MAX_DELTAS_PER_PUSH, MAX_PULL_LIMIT, MAX_PUSH_PAYLOAD_BYTES, type RequestError, SourceRegistry, SyncPull, SyncPush, VALID_COLUMN_TYPES, buildSyncRulesContext, commitToCatalogue, flushEntries, hlcRange, parseJson, parsePullParams, pushErrorToStatus, validateActionBody, validatePushBody, validateSchemaBody };
package/dist/gateway.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  MAX_PULL_LIMIT,
10
10
  MAX_PUSH_PAYLOAD_BYTES,
11
11
  MemoryConfigStore,
12
+ MemoryIdempotencyCache,
12
13
  SchemaManager,
13
14
  SourceRegistry,
14
15
  SyncGateway,
@@ -34,15 +35,14 @@ import {
34
35
  validateActionBody,
35
36
  validatePushBody,
36
37
  validateSchemaBody
37
- } from "./chunk-JI4C4R5H.js";
38
- import "./chunk-LPWXOYNS.js";
39
- import "./chunk-TMLG32QV.js";
40
- import "./chunk-SSICS5KI.js";
38
+ } from "./chunk-FIIHPQMQ.js";
39
+ import "./chunk-U2NV4DUX.js";
40
+ import "./chunk-ZU7RC7CT.js";
41
41
  import {
42
42
  bigintReplacer,
43
43
  bigintReviver,
44
44
  isIngestTarget
45
- } from "./chunk-LDFFCG2K.js";
45
+ } from "./chunk-4SG66H5K.js";
46
46
  import "./chunk-DGUM43GV.js";
47
47
  export {
48
48
  ActionDispatcher,
@@ -55,6 +55,7 @@ export {
55
55
  MAX_PULL_LIMIT,
56
56
  MAX_PUSH_PAYLOAD_BYTES,
57
57
  MemoryConfigStore,
58
+ MemoryIdempotencyCache,
58
59
  SchemaManager,
59
60
  SourceRegistry,
60
61
  SyncGateway,