@electric-sql/pglite-sync
Version:
ElectricSQL Sync for PGlite
1 lines • 47 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/subscriptionState.ts","../src/apply.ts"],"sourcesContent":["import type { Row } from '@electric-sql/client'\nimport {\n ChangeMessage,\n isChangeMessage,\n isControlMessage,\n ShapeStreamOptions,\n} from '@electric-sql/client'\nimport { MultiShapeStream } from '@electric-sql/experimental'\nimport type { Extension, PGliteInterface } from '@electric-sql/pglite'\nimport {\n migrateSubscriptionMetadataTables,\n getSubscriptionState,\n updateSubscriptionState,\n deleteSubscriptionState,\n SubscriptionState,\n} from './subscriptionState'\nimport type {\n ElectricSyncOptions,\n SyncShapesToTablesOptions,\n SyncShapesToTablesResult,\n SyncShapeToTableOptions,\n SyncShapeToTableResult,\n InsertChangeMessage,\n Lsn,\n} from './types'\nimport {\n applyInsertsToTable,\n applyMessageToTable,\n applyMessagesToTableWithCopy,\n applyMessagesToTableWithJson,\n} from './apply'\n\nexport * from './types'\n\nasync function createPlugin(\n pg: PGliteInterface,\n options?: ElectricSyncOptions,\n) {\n const debug = options?.debug ?? false\n const metadataSchema = options?.metadataSchema ?? 'electric'\n const streams: Array<{\n stream: MultiShapeStream<Record<string, Row<unknown>>>\n aborter: AbortController\n }> = []\n\n // We keep an in-memory lock per table such that two\n // shapes are not synced into one table - this will be\n // resolved by using reference counting in shadow tables\n const shapePerTableLock = new Map<string, void>()\n\n let initMetadataTablesDone = false\n const initMetadataTables = async () => {\n if (initMetadataTablesDone) return\n initMetadataTablesDone = true\n await migrateSubscriptionMetadataTables({\n pg,\n metadataSchema,\n })\n }\n\n const syncShapesToTables = async ({\n key,\n shapes,\n useCopy = false, // DEPRECATED: use initialInsertMethod instead\n initialInsertMethod = 'insert',\n onInitialSync,\n }: SyncShapesToTablesOptions): Promise<SyncShapesToTablesResult> => {\n let unsubscribed = false\n await initMetadataTables()\n\n Object.values(shapes)\n .filter((shape) => !shape.onMustRefetch) // Shapes with onMustRefetch bypass the lock\n .forEach((shape) => {\n if (shapePerTableLock.has(shape.table)) {\n throw new Error('Already syncing shape for table ' + shape.table)\n }\n shapePerTableLock.set(shape.table)\n })\n\n let subState: SubscriptionState | null = null\n\n // if key is not null, ensure persistence of subscription state\n // is possible and check if it is already persisted\n if (key !== null) {\n subState = await getSubscriptionState({\n pg,\n metadataSchema,\n subscriptionKey: key,\n })\n if (debug && subState) {\n console.log('resuming from subscription state', subState)\n }\n }\n\n // If it's a new subscription there is no state to resume from\n const isNewSubscription = subState === null\n\n // We need to handle the old useCopy option\n // We check if it's set to true and initialInsertMethod is it's default value\n if (useCopy && initialInsertMethod === 'insert') {\n initialInsertMethod = 'csv'\n console.warn(\n 'The useCopy option is deprecated and will be removed in a future version. Use initialInsertMethod instead.',\n )\n }\n\n // If it's a new subscription we can do a `COPY FROM` to insert the initial data\n // TODO: in future when we can have multiple shapes on the same table we will need\n // to make sure we only do a `COPY FROM` on the first shape on the table as they\n // may overlap and so the insert logic will be wrong.\n let useInsert = !isNewSubscription || initialInsertMethod === 'insert'\n\n // Track if onInitialSync has been called\n let onInitialSyncCalled = false\n\n // Map of shape name to lsn to changes\n // We accumulate changes for each lsn and then apply them all at once\n const changes = new Map<string, Map<Lsn, ChangeMessage<Row<unknown>>[]>>(\n Object.keys(shapes).map((key) => [key, new Map()]),\n )\n\n // We track the highest completely buffered lsn for each shape\n const completeLsns = new Map<string, Lsn>(\n Object.keys(shapes).map((key) => [key, BigInt(-1)]),\n )\n\n // We track which shapes need a truncate\n // These are truncated at the start of the next commit\n const truncateNeeded = new Set<string>()\n\n // We also have to track the last lsn that we have committed\n // This is across all shapes\n const lastCommittedLsn: Lsn = subState?.last_lsn ?? BigInt(-1)\n\n // We need our own aborter to be able to abort the streams but still accept the\n // signals from the user for each shape, and so we monitor the user provided signal\n // for each shape and abort our own aborter when the user signal is aborted.\n const aborter = new AbortController()\n Object.values(shapes)\n .filter((shapeOptions) => !!shapeOptions.shape.signal)\n .forEach((shapeOptions) => {\n shapeOptions.shape.signal!.addEventListener(\n 'abort',\n () => aborter.abort(),\n {\n once: true,\n },\n )\n })\n\n const multiShapeStream = new MultiShapeStream<Record<string, Row<unknown>>>(\n {\n shapes: Object.fromEntries(\n Object.entries(shapes).map(([key, shapeOptions]) => {\n const shapeMetadata = subState?.shape_metadata[key]\n return [\n key,\n {\n ...shapeOptions.shape,\n ...(shapeMetadata\n ? {\n offset: shapeMetadata.offset,\n handle: shapeMetadata.handle,\n }\n : {}),\n signal: aborter.signal,\n } satisfies ShapeStreamOptions,\n ]\n }),\n ),\n },\n )\n\n const insertMethods = {\n json: applyMessagesToTableWithJson,\n csv: applyMessagesToTableWithCopy,\n useCopy: applyMessagesToTableWithCopy,\n insert: applyInsertsToTable,\n } as const\n\n const commitUpToLsn = async (targetLsn: Lsn) => {\n // We need to collect all the messages for each shape that we need to commit\n const messagesToCommit = new Map<string, ChangeMessage<Row<unknown>>[]>(\n Object.keys(shapes).map((shapeName) => [shapeName, []]),\n )\n for (const [shapeName, shapeChanges] of changes.entries()) {\n const messagesForShape = messagesToCommit.get(shapeName)!\n for (const lsn of shapeChanges.keys()) {\n if (lsn <= targetLsn) {\n for (const message of shapeChanges.get(lsn)!) {\n messagesForShape.push(message)\n }\n shapeChanges.delete(lsn)\n }\n }\n }\n\n await pg.transaction(async (tx) => {\n if (debug) {\n console.time('commit')\n }\n\n // Set the syncing flag to true during this transaction so that\n // user defined triggers on the table are able to chose how to run\n // during a sync\n await tx.exec(`SET LOCAL ${metadataSchema}.syncing = true;`)\n\n for (const [shapeName, initialMessages] of messagesToCommit.entries()) {\n const shape = shapes[shapeName]\n let messages = initialMessages\n\n // If we need to truncate the table, do so\n if (truncateNeeded.has(shapeName)) {\n if (debug) {\n console.log('truncating table', shape.table)\n }\n if (shape.onMustRefetch) {\n await shape.onMustRefetch(tx)\n } else {\n const schema = shape.schema || 'public'\n await tx.exec(`DELETE FROM \"${schema}\".\"${shape.table}\";`)\n }\n truncateNeeded.delete(shapeName)\n }\n\n // Apply the changes to the table\n if (!useInsert) {\n // We can do a `COPY FROM`/json_to_recordset to insert the initial data\n // Split messageAggregator into initial inserts and remaining messages\n const initialInserts: InsertChangeMessage[] = []\n const remainingMessages: ChangeMessage<any>[] = []\n let foundNonInsert = false\n for (const message of messages) {\n if (!foundNonInsert && message.headers.operation === 'insert') {\n initialInserts.push(message as InsertChangeMessage)\n } else {\n foundNonInsert = true\n remainingMessages.push(message)\n }\n }\n if (initialInserts.length > 0 && initialInsertMethod === 'csv') {\n // As `COPY FROM` doesn't trigger a NOTIFY, we pop\n // the last insert message and and add it to the be beginning\n // of the remaining messages to be applied after the `COPY FROM`\n remainingMessages.unshift(initialInserts.pop()!)\n }\n messages = remainingMessages\n\n // Do the `COPY FROM`/json_to_recordset with initial inserts\n if (initialInserts.length > 0) {\n await insertMethods[initialInsertMethod]({\n pg: tx,\n table: shape.table,\n schema: shape.schema,\n messages: initialInserts as InsertChangeMessage[],\n mapColumns: shape.mapColumns,\n debug,\n })\n\n // We don't want to do a `COPY FROM`/json_to_recordset again after that\n useInsert = true\n }\n }\n\n const bulkInserts: InsertChangeMessage[] = []\n let change: ChangeMessage<any> | null = null\n const messagesLength = messages.length\n for (let i = 0; i < messagesLength; i++) {\n const changeMessage = messages[i]\n if (changeMessage.headers.operation === 'insert') {\n bulkInserts.push(changeMessage as InsertChangeMessage)\n } else {\n change = changeMessage\n }\n\n if (change || i === messagesLength - 1) {\n if (bulkInserts.length > 0) {\n await applyInsertsToTable({\n pg: tx,\n table: shape.table,\n schema: shape.schema,\n messages: bulkInserts as InsertChangeMessage[],\n mapColumns: shape.mapColumns,\n debug,\n })\n bulkInserts.length = 0\n }\n if (change) {\n await applyMessageToTable({\n pg: tx,\n table: shape.table,\n schema: shape.schema,\n message: change,\n mapColumns: shape.mapColumns,\n primaryKey: shape.primaryKey,\n debug,\n })\n change = null\n }\n }\n }\n }\n\n if (key) {\n await updateSubscriptionState({\n pg: tx,\n metadataSchema,\n subscriptionKey: key,\n shapeMetadata: Object.fromEntries(\n Object.keys(shapes).map((shapeName) => [\n shapeName,\n {\n handle: multiShapeStream.shapes[shapeName].shapeHandle!,\n offset: multiShapeStream.shapes[shapeName].lastOffset,\n },\n ]),\n ),\n lastLsn: targetLsn,\n debug,\n })\n }\n if (unsubscribed) {\n await tx.rollback()\n }\n })\n if (debug) console.timeEnd('commit')\n if (\n onInitialSync &&\n !onInitialSyncCalled &&\n multiShapeStream.isUpToDate\n ) {\n onInitialSync()\n onInitialSyncCalled = true\n }\n }\n\n multiShapeStream.subscribe(async (messages) => {\n if (unsubscribed) {\n return\n }\n if (debug) {\n console.log('received messages', messages.length)\n }\n messages.forEach((message) => {\n const lastCommittedLsnForShape =\n completeLsns.get(message.shape) ?? BigInt(-1) // we default to -1 if there are no previous changes\n if (isChangeMessage(message)) {\n const shapeChanges = changes.get(message.shape)!\n const lsn =\n typeof message.headers.lsn === 'string'\n ? BigInt(message.headers.lsn)\n : BigInt(0) // we default to 0 if there no lsn on the message\n if (lsn <= lastCommittedLsnForShape) {\n // We are replaying changes / have already seen this lsn\n // skip and move on to the next message\n return\n }\n const isLastOfLsn =\n (message.headers.last as boolean | undefined) ?? false\n if (!shapeChanges.has(lsn)) {\n shapeChanges.set(lsn, [])\n }\n shapeChanges.get(lsn)!.push(message)\n if (isLastOfLsn) {\n completeLsns.set(message.shape, lsn)\n }\n } else if (isControlMessage(message)) {\n switch (message.headers.control) {\n case 'up-to-date': {\n // Update the complete lsn for this shape\n if (debug) {\n console.log('received up-to-date', message)\n }\n if (typeof message.headers.global_last_seen_lsn !== `string`) {\n throw new Error(`global_last_seen_lsn is not a string`)\n }\n const globalLastSeenLsn = BigInt(\n message.headers.global_last_seen_lsn,\n )\n if (globalLastSeenLsn <= lastCommittedLsnForShape) {\n // We are replaying changes / have already seen this lsn\n // skip and move on to the next message\n return\n }\n completeLsns.set(message.shape, globalLastSeenLsn)\n break\n }\n case 'must-refetch': {\n // Reset the changes for this shape\n if (debug) {\n console.log('received must-refetch', message)\n }\n const shapeChanges = changes.get(message.shape)!\n shapeChanges.clear()\n completeLsns.set(message.shape, BigInt(-1))\n // Track that we need to truncate the table for this shape\n truncateNeeded.add(message.shape)\n break\n }\n }\n }\n })\n const lowestCommittedLsn = Array.from(completeLsns.values()).reduce(\n (m, e) => (e < m ? e : m), // Min of all complete lsn\n )\n\n // Normal commit needed\n const isCommitNeeded = lowestCommittedLsn > lastCommittedLsn\n // We've had a must-refetch and are catching up on one of the shape\n const isMustRefetchAndCatchingUp =\n lowestCommittedLsn >= lastCommittedLsn && truncateNeeded.size > 0\n\n if (isCommitNeeded || isMustRefetchAndCatchingUp) {\n // We have new changes to commit\n commitUpToLsn(lowestCommittedLsn)\n // Await a timeout to start a new task and allow other connections to do work\n await new Promise((resolve) => setTimeout(resolve))\n }\n })\n\n streams.push({\n stream: multiShapeStream,\n aborter,\n })\n const unsubscribe = () => {\n if (debug) {\n console.log('unsubscribing')\n }\n unsubscribed = true\n multiShapeStream.unsubscribeAll()\n aborter.abort()\n for (const shape of Object.values(shapes)) {\n shapePerTableLock.delete(shape.table)\n }\n }\n return {\n unsubscribe,\n get isUpToDate() {\n return multiShapeStream.isUpToDate\n },\n streams: Object.fromEntries(\n Object.keys(shapes).map((shapeName) => [\n shapeName,\n multiShapeStream.shapes[shapeName],\n ]),\n ),\n }\n }\n\n const syncShapeToTable = async (\n options: SyncShapeToTableOptions,\n ): Promise<SyncShapeToTableResult> => {\n const multiShapeSub = await syncShapesToTables({\n shapes: {\n shape: {\n shape: options.shape,\n table: options.table,\n schema: options.schema,\n mapColumns: options.mapColumns,\n primaryKey: options.primaryKey,\n onMustRefetch: options.onMustRefetch,\n },\n },\n key: options.shapeKey,\n useCopy: options.useCopy, // DEPRECATED: use initialInsertMethod instead\n initialInsertMethod: options.initialInsertMethod,\n onInitialSync: options.onInitialSync,\n })\n return {\n unsubscribe: multiShapeSub.unsubscribe,\n get isUpToDate() {\n return multiShapeSub.isUpToDate\n },\n stream: multiShapeSub.streams.shape,\n }\n }\n const deleteSubscription = async (key: string) => {\n await deleteSubscriptionState({\n pg,\n metadataSchema,\n subscriptionKey: key,\n })\n }\n\n const namespaceObj = {\n initMetadataTables,\n syncShapesToTables,\n syncShapeToTable,\n deleteSubscription,\n }\n\n const close = async () => {\n for (const { stream, aborter } of streams) {\n stream.unsubscribeAll()\n aborter.abort()\n }\n }\n\n return {\n namespaceObj,\n close,\n }\n}\n\nexport type SyncNamespaceObj = Awaited<\n ReturnType<typeof createPlugin>\n>['namespaceObj']\n\nexport type PGliteWithSync = PGliteInterface & {\n sync: SyncNamespaceObj\n}\n\nexport function electricSync(options?: ElectricSyncOptions) {\n return {\n name: 'ElectricSQL Sync',\n setup: async (pg: PGliteInterface) => {\n const { namespaceObj, close } = await createPlugin(pg, options)\n return {\n namespaceObj,\n close,\n }\n },\n } satisfies Extension\n}\n","import type { PGliteInterface, Transaction } from '@electric-sql/pglite'\nimport type { Offset } from '@electric-sql/client'\nimport { SubscriptionKey, Lsn } from './types'\n\nconst subscriptionTableName = `subscriptions_metadata`\n\nexport interface SubscriptionState {\n key: SubscriptionKey\n shape_metadata: Record<string, ShapeSubscriptionState>\n last_lsn: Lsn\n}\n\nexport interface ShapeSubscriptionState {\n handle: string\n offset: Offset\n}\n\nexport interface GetSubscriptionStateOptions {\n readonly pg: PGliteInterface | Transaction\n readonly metadataSchema: string\n readonly subscriptionKey: SubscriptionKey\n}\n\n/**\n * Get the subscription state for a given key.\n * @param options - The options for the subscription state.\n * @returns The subscription state or null if it does not exist.\n */\nexport async function getSubscriptionState({\n pg,\n metadataSchema,\n subscriptionKey,\n}: GetSubscriptionStateOptions): Promise<SubscriptionState | null> {\n const result = await pg.query<SubscriptionState>(\n `\n SELECT key, shape_metadata, last_lsn\n FROM ${subscriptionMetadataTableName(metadataSchema)}\n WHERE key = $1\n `,\n [subscriptionKey],\n )\n\n if (result.rows.length === 0) {\n return null\n } else if (result.rows.length > 1) {\n throw new Error(`Multiple subscriptions found for key: ${subscriptionKey}`)\n }\n\n const res = result.rows[0]\n\n if (typeof res.last_lsn === 'string') {\n return {\n ...res,\n last_lsn: BigInt(res.last_lsn),\n }\n } else {\n throw new Error(`Invalid last_lsn type: ${typeof res.last_lsn}`)\n }\n}\n\nexport interface UpdateSubscriptionStateOptions {\n pg: PGliteInterface | Transaction\n metadataSchema: string\n subscriptionKey: SubscriptionKey\n shapeMetadata: Record<string, ShapeSubscriptionState>\n lastLsn: Lsn\n debug?: boolean\n}\n\n/**\n * Update the subscription state for a given key.\n * @param options - The options for the subscription state.\n */\nexport async function updateSubscriptionState({\n pg,\n metadataSchema,\n subscriptionKey,\n shapeMetadata,\n lastLsn,\n debug,\n}: UpdateSubscriptionStateOptions) {\n if (debug) {\n console.log(\n 'updating subscription state',\n subscriptionKey,\n shapeMetadata,\n lastLsn,\n )\n }\n await pg.query(\n `\n INSERT INTO ${subscriptionMetadataTableName(metadataSchema)}\n (key, shape_metadata, last_lsn)\n VALUES\n ($1, $2, $3)\n ON CONFLICT(key)\n DO UPDATE SET\n shape_metadata = EXCLUDED.shape_metadata,\n last_lsn = EXCLUDED.last_lsn;\n `,\n [subscriptionKey, shapeMetadata, lastLsn.toString()],\n )\n}\n\nexport interface DeleteSubscriptionStateOptions {\n pg: PGliteInterface | Transaction\n metadataSchema: string\n subscriptionKey: SubscriptionKey\n}\n\n/**\n * Delete the subscription state for a given key.\n * @param options - The options for the subscription state.\n */\nexport async function deleteSubscriptionState({\n pg,\n metadataSchema,\n subscriptionKey,\n}: DeleteSubscriptionStateOptions) {\n await pg.query(\n `DELETE FROM ${subscriptionMetadataTableName(metadataSchema)} WHERE key = $1`,\n [subscriptionKey],\n )\n}\n\nexport interface MigrateSubscriptionMetadataTablesOptions {\n pg: PGliteInterface | Transaction\n metadataSchema: string\n}\n\n/**\n * Migrate the subscription metadata tables.\n * @param options - The options for the subscription metadata tables.\n */\nexport async function migrateSubscriptionMetadataTables({\n pg,\n metadataSchema,\n}: MigrateSubscriptionMetadataTablesOptions) {\n await pg.exec(\n `\n SET ${metadataSchema}.syncing = false;\n CREATE SCHEMA IF NOT EXISTS \"${metadataSchema}\";\n CREATE TABLE IF NOT EXISTS ${subscriptionMetadataTableName(metadataSchema)} (\n key TEXT PRIMARY KEY,\n shape_metadata JSONB NOT NULL,\n last_lsn TEXT NOT NULL\n );\n `,\n )\n}\n\nfunction subscriptionMetadataTableName(metadataSchema: string) {\n return `\"${metadataSchema}\".\"${subscriptionTableName}\"`\n}\n","import { ChangeMessage } from '@electric-sql/client'\nimport type { PGliteInterface, Transaction } from '@electric-sql/pglite'\nimport type { MapColumns, InsertChangeMessage } from './types'\n\nexport interface ApplyMessageToTableOptions {\n pg: PGliteInterface | Transaction\n table: string\n schema?: string\n message: ChangeMessage<any>\n mapColumns?: MapColumns\n primaryKey: string[]\n debug: boolean\n}\n\nexport async function applyMessageToTable({\n pg,\n table,\n schema = 'public',\n message,\n mapColumns,\n primaryKey,\n debug,\n}: ApplyMessageToTableOptions) {\n const data = mapColumns ? doMapColumns(mapColumns, message) : message.value\n\n switch (message.headers.operation) {\n case 'insert': {\n if (debug) console.log('inserting', data)\n const columns = Object.keys(data)\n return await pg.query(\n `\n INSERT INTO \"${schema}\".\"${table}\"\n (${columns.map((s) => '\"' + s + '\"').join(', ')})\n VALUES\n (${columns.map((_v, i) => '$' + (i + 1)).join(', ')})\n `,\n columns.map((column) => data[column]),\n )\n }\n\n case 'update': {\n if (debug) console.log('updating', data)\n const columns = Object.keys(data).filter(\n // we don't update the primary key, they are used to identify the row\n (column) => !primaryKey.includes(column),\n )\n if (columns.length === 0) return // nothing to update\n return await pg.query(\n `\n UPDATE \"${schema}\".\"${table}\"\n SET ${columns\n .map((column, i) => '\"' + column + '\" = $' + (i + 1))\n .join(', ')}\n WHERE ${primaryKey\n .map(\n (column, i) =>\n '\"' + column + '\" = $' + (columns.length + i + 1),\n )\n .join(' AND ')}\n `,\n [\n ...columns.map((column) => data[column]),\n ...primaryKey.map((column) => data[column]),\n ],\n )\n }\n\n case 'delete': {\n if (debug) console.log('deleting', data)\n return await pg.query(\n `\n DELETE FROM \"${schema}\".\"${table}\"\n WHERE ${primaryKey\n .map((column, i) => '\"' + column + '\" = $' + (i + 1))\n .join(' AND ')}\n `,\n [...primaryKey.map((column) => data[column])],\n )\n }\n }\n}\n\nexport interface BulkApplyMessagesToTableOptions {\n pg: PGliteInterface | Transaction\n table: string\n schema?: string\n messages: InsertChangeMessage[]\n mapColumns?: MapColumns\n debug: boolean\n}\n\nexport async function applyInsertsToTable({\n pg,\n table,\n schema = 'public',\n messages,\n mapColumns,\n debug,\n}: BulkApplyMessagesToTableOptions) {\n // Map the messages to the data to be inserted\n const data: Record<string, object>[] = messages.map((message) =>\n mapColumns ? doMapColumns(mapColumns, message) : message.value,\n )\n\n if (debug) console.log('inserting', data)\n\n // Get column names from the first message\n const columns = Object.keys(data[0])\n\n // Calculate size of a single value\n const getValueSize = (value: any): number => {\n if (value === null) return 0\n\n // Handle binary data types\n if (value instanceof ArrayBuffer) return value.byteLength\n if (value instanceof Blob) return value.size\n if (value instanceof Uint8Array) return value.byteLength\n if (value instanceof DataView) return value.byteLength\n if (ArrayBuffer.isView(value)) return value.byteLength\n\n // Handle regular types\n switch (typeof value) {\n case 'string':\n return value.length\n case 'number':\n return 8 // assuming 8 bytes for numbers\n case 'boolean':\n return 1\n default:\n if (value instanceof Date) return 8\n return value?.toString()?.length || 0\n }\n }\n\n // Calculate size of a single row's values in bytes\n const getRowSize = (row: Record<string, any>): number => {\n return columns.reduce((size, column) => {\n const value = row[column]\n if (value === null) return size\n\n // Handle arrays\n if (Array.isArray(value)) {\n if (value.length === 0) return size\n\n // Check first element to determine array type\n const firstElement = value[0]\n\n // Handle homogeneous arrays\n switch (typeof firstElement) {\n case 'number':\n return size + value.length * 8 // 8 bytes per number\n case 'string':\n return (\n size + value.reduce((arrSize, str) => arrSize + str.length, 0)\n )\n case 'boolean':\n return size + value.length // 1 byte per boolean\n default:\n if (firstElement instanceof Date) {\n return size + value.length * 8 // 8 bytes per date\n }\n // Handle mixed or other types of arrays (including binary data)\n return (\n size +\n value.reduce((arrSize, item) => arrSize + getValueSize(item), 0)\n )\n }\n }\n\n return size + getValueSize(value)\n }, 0)\n }\n\n const MAX_PARAMS = 32_000\n const MAX_BYTES = 50 * 1024 * 1024 // 50MB\n\n // Helper function to execute a batch insert\n const executeBatch = async (batch: Record<string, any>[]) => {\n const sql = `\n INSERT INTO \"${schema}\".\"${table}\"\n (${columns.map((s) => `\"${s}\"`).join(', ')})\n VALUES\n ${batch.map((_, j) => `(${columns.map((_v, k) => '$' + (j * columns.length + k + 1)).join(', ')})`).join(', ')}\n `\n const values = batch.flatMap((message) =>\n columns.map((column) => message[column]),\n )\n await pg.query(sql, values)\n }\n\n let currentBatch: Record<string, any>[] = []\n let currentBatchSize = 0\n let currentBatchParams = 0\n\n for (let i = 0; i < data.length; i++) {\n const row = data[i]\n const rowSize = getRowSize(row)\n const rowParams = columns.length\n\n // Check if adding this row would exceed either limit\n if (\n currentBatch.length > 0 &&\n (currentBatchSize + rowSize > MAX_BYTES ||\n currentBatchParams + rowParams > MAX_PARAMS)\n ) {\n if (debug && currentBatchSize + rowSize > MAX_BYTES) {\n console.log('batch size limit exceeded, executing batch')\n }\n if (debug && currentBatchParams + rowParams > MAX_PARAMS) {\n console.log('batch params limit exceeded, executing batch')\n }\n await executeBatch(currentBatch)\n\n // Reset batch\n currentBatch = []\n currentBatchSize = 0\n currentBatchParams = 0\n }\n\n // Add row to current batch\n currentBatch.push(row)\n currentBatchSize += rowSize\n currentBatchParams += rowParams\n }\n\n // Execute final batch if there are any remaining rows\n if (currentBatch.length > 0) {\n await executeBatch(currentBatch)\n }\n\n if (debug) console.log(`Inserted ${messages.length} rows using INSERT`)\n}\n\nexport async function applyMessagesToTableWithJson({\n pg,\n table,\n schema = 'public',\n messages,\n mapColumns,\n debug,\n}: BulkApplyMessagesToTableOptions) {\n if (debug) console.log('applying messages with json_to_recordset')\n\n // Map the messages to the data to be inserted\n const data: Record<string, object>[] = messages.map((message) =>\n mapColumns ? doMapColumns(mapColumns, message) : message.value,\n )\n const columns = (\n await pg.query<{\n column_name: string\n udt_name: string\n data_type: string\n }>(\n `\n SELECT column_name, udt_name, data_type\n FROM information_schema.columns\n WHERE table_name = $1 AND table_schema = $2\n `,\n [table, schema],\n )\n ).rows.filter((x) =>\n Object.prototype.hasOwnProperty.call(data[0], x.column_name),\n )\n\n const MAX = 10_000\n for (let i = 0; i < data.length; i += MAX) {\n const maxdata = data.slice(i, i + MAX)\n await pg.query(\n `\n INSERT INTO \"${schema}\".\"${table}\"\n SELECT x.* from json_to_recordset($1) as x(${columns\n .map(\n (x) =>\n `${x.column_name} ${x.udt_name.replace(/^_/, '')}` +\n (x.data_type === 'ARRAY' ? `[]` : ''),\n )\n .join(', ')})\n `,\n [maxdata],\n )\n }\n\n if (debug)\n console.log(`Inserted ${messages.length} rows using json_to_recordset`)\n}\n\nexport async function applyMessagesToTableWithCopy({\n pg,\n table,\n schema = 'public',\n messages,\n mapColumns,\n debug,\n}: BulkApplyMessagesToTableOptions) {\n if (debug) console.log('applying messages with COPY')\n\n // Map the messages to the data to be inserted\n const data: Record<string, any>[] = messages.map((message) =>\n mapColumns ? doMapColumns(mapColumns, message) : message.value,\n )\n\n // Get column names from the first message\n const columns = Object.keys(data[0])\n\n // Create CSV data\n const csvData = data\n .map((message) => {\n return columns\n .map((column) => {\n const value = message[column]\n // Escape double quotes and wrap in quotes if necessary\n if (\n typeof value === 'string' &&\n (value.includes(',') || value.includes('\"') || value.includes('\\n'))\n ) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`\n }\n return value === null ? '\\\\N' : value\n })\n .join(',')\n })\n .join('\\n')\n const csvBlob = new Blob([csvData], { type: 'text/csv' })\n\n // Perform COPY FROM\n await pg.query(\n `\n COPY \"${schema}\".\"${table}\" (${columns.map((c) => `\"${c}\"`).join(', ')})\n FROM '/dev/blob'\n WITH (FORMAT csv, NULL '\\\\N')\n `,\n [],\n {\n blob: csvBlob,\n },\n )\n\n if (debug) console.log(`Inserted ${messages.length} rows using COPY`)\n}\n\nfunction doMapColumns(\n mapColumns: MapColumns,\n message: ChangeMessage<any>,\n): Record<string, any> {\n if (typeof mapColumns === 'function') {\n return mapColumns(message)\n }\n const mappedColumns: Record<string, any> = {}\n for (const [key, value] of Object.entries(mapColumns)) {\n mappedColumns[key] = message.value[value]\n }\n return mappedColumns\n}\n"],"mappings":"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","names":["src_exports","__export","electricSync","__toCommonJS","import_client","import_experimental","subscriptionTableName","getSubscriptionState","pg","metadataSchema","subscriptionKey","result","subscriptionMetadataTableName","res","updateSubscriptionState","shapeMetadata","lastLsn","debug","deleteSubscriptionState","migrateSubscriptionMetadataTables","applyMessageToTable","pg","table","schema","message","mapColumns","primaryKey","debug","data","doMapColumns","columns","s","_v","i","column","applyInsertsToTable","messages","getValueSize","value","getRowSize","row","size","firstElement","arrSize","str","item","MAX_PARAMS","MAX_BYTES","executeBatch","batch","sql","_","j","k","values","currentBatch","currentBatchSize","currentBatchParams","rowSize","rowParams","applyMessagesToTableWithJson","x","MAX","maxdata","applyMessagesToTableWithCopy","csvData","csvBlob","c","mappedColumns","key","createPlugin","pg","options","debug","metadataSchema","streams","shapePerTableLock","initMetadataTablesDone","initMetadataTables","migrateSubscriptionMetadataTables","syncShapesToTables","key","shapes","useCopy","initialInsertMethod","onInitialSync","unsubscribed","shape","subState","getSubscriptionState","isNewSubscription","useInsert","onInitialSyncCalled","changes","completeLsns","truncateNeeded","lastCommittedLsn","aborter","shapeOptions","multiShapeStream","shapeMetadata","insertMethods","applyMessagesToTableWithJson","applyMessagesToTableWithCopy","applyInsertsToTable","commitUpToLsn","targetLsn","messagesToCommit","shapeName","shapeChanges","messagesForShape","lsn","message","tx","initialMessages","messages","schema","initialInserts","remainingMessages","foundNonInsert","bulkInserts","change","messagesLength","i","changeMessage","applyMessageToTable","updateSubscriptionState","lastCommittedLsnForShape","isLastOfLsn","globalLastSeenLsn","lowestCommittedLsn","m","e","isCommitNeeded","isMustRefetchAndCatchingUp","resolve","multiShapeSub","deleteSubscriptionState","stream","electricSync","namespaceObj","close"]}