{"version":3,"file":"electric.cjs","sources":["../../src/electric.ts"],"sourcesContent":["import {\n  ShapeStream,\n  isChangeMessage,\n  isControlMessage,\n} from \"@electric-sql/client\"\nimport { Store } from \"@tanstack/store\"\nimport type { SyncConfig } from \"@tanstack/optimistic\"\nimport type {\n  ControlMessage,\n  Message,\n  Row,\n  ShapeStreamOptions,\n} from \"@electric-sql/client\"\n\n// Re-exports\nexport type * from \"@tanstack/optimistic\"\nexport * from \"./useCollection\"\nexport type { Collection } from \"@tanstack/optimistic\"\n\n/**\n * Extended SyncConfig interface with ElectricSQL-specific functionality\n */\nexport interface ElectricSync extends SyncConfig {\n  /**\n   * Wait for a specific transaction ID to be synced\n   * @param txid The transaction ID to wait for\n   * @param timeout Optional timeout in milliseconds (defaults to 30000ms)\n   * @returns Promise that resolves when the txid is synced\n   */\n  awaitTxid: (txid: number, timeout?: number) => Promise<boolean>\n\n  /**\n   * Get the sync metadata for insert operations\n   * @returns Record containing primaryKey and relation information\n   */\n  getSyncMetadata: () => Record<string, unknown>\n}\n\nfunction isUpToDateMessage<T extends Row<unknown> = Row>(\n  message: Message<T>\n): message is ControlMessage & { up_to_date: true } {\n  return isControlMessage(message) && message.headers.control === `up-to-date`\n}\n\n// Check if a message contains txids in its headers\nfunction hasTxids<T extends Row<unknown> = Row>(\n  message: Message<T>\n): message is Message<T> & { headers: { txids?: Array<number> } } {\n  return (\n    `headers` in message &&\n    `txids` in message.headers &&\n    Array.isArray(message.headers.txids)\n  )\n}\n\n/**\n * Creates an ElectricSQL sync configuration\n *\n * @param streamOptions - Configuration options for the ShapeStream\n * @param options - Options for the ElectricSync configuration\n * @returns ElectricSync configuration\n */\nexport function createElectricSync<T extends Row<unknown> = Row>(\n  streamOptions: ShapeStreamOptions,\n  options: ElectricSyncOptions\n): ElectricSync {\n  const { primaryKey } = options\n\n  // Create a store to track seen txids\n  const seenTxids = new Store<Set<number>>(new Set())\n\n  // Store for the relation schema information\n  const relationSchema = new Store<string | undefined>(undefined)\n\n  // Function to check if a txid has been seen\n  const hasTxid = (txid: number): boolean => {\n    return seenTxids.state.has(txid)\n  }\n\n  // Function to await a specific txid\n  const awaitTxid = async (txid: number, timeout = 30000): Promise<boolean> => {\n    // If we've already seen this txid, resolve immediately\n    if (hasTxid(txid)) {\n      return true\n    }\n\n    // Otherwise, create a promise that resolves when the txid is seen\n    return new Promise<boolean>((resolve, reject) => {\n      // Set up a timeout\n      const timeoutId = setTimeout(() => {\n        unsubscribe()\n        reject(new Error(`Timeout waiting for txid: ${txid}`))\n      }, timeout)\n\n      // Subscribe to the store to watch for the txid\n      const unsubscribe = seenTxids.subscribe(() => {\n        if (hasTxid(txid)) {\n          clearTimeout(timeoutId)\n          unsubscribe()\n          resolve(true)\n        }\n      })\n    })\n  }\n\n  /**\n   * Generate a key from a row using the primaryKey columns\n   * @param row - The row data\n   * @returns A string key formed from the primary key values\n   */\n  const generateKeyFromRow = (row: T): string => {\n    // eslint-disable-next-line\n    if (!primaryKey || primaryKey.length === 0) {\n      throw new Error(`Primary key is required for Electric sync`)\n    }\n\n    return primaryKey\n      .map((key) => {\n        const value = row[key]\n        if (value === undefined) {\n          throw new Error(`Primary key column \"${key}\" not found in row`)\n        }\n        return String(value)\n      })\n      .join(`|`)\n  }\n\n  /**\n   * Get the sync metadata for insert operations\n   * @returns Record containing primaryKey and relation information\n   */\n  const getSyncMetadata = (): Record<string, unknown> => {\n    // Use the stored schema if available, otherwise default to 'public'\n    const schema = relationSchema.state || `public`\n\n    return {\n      primaryKey,\n      relation: streamOptions.params?.table\n        ? [schema, streamOptions.params.table]\n        : undefined,\n    }\n  }\n\n  return {\n    sync: ({ begin, write, commit }) => {\n      const stream = new ShapeStream(streamOptions)\n      let transactionStarted = false\n      let newTxids = new Set<number>()\n\n      stream.subscribe((messages: Array<Message<Row>>) => {\n        let hasUpToDate = false\n\n        for (const message of messages) {\n          // Check for txids in the message and add them to our store\n          if (hasTxids(message) && message.headers.txids) {\n            message.headers.txids.forEach((txid) => newTxids.add(txid))\n          }\n\n          // Check if the message contains schema information\n          if (isChangeMessage(message) && message.headers.schema) {\n            // Store the schema for future use if it's a valid string\n            if (typeof message.headers.schema === `string`) {\n              const schema: string = message.headers.schema\n              relationSchema.setState(() => schema)\n            }\n          }\n\n          if (isChangeMessage(message)) {\n            if (!transactionStarted) {\n              begin()\n              transactionStarted = true\n            }\n\n            // Use the message's key if available, otherwise generate one from the row using primaryKey\n            const key =\n              message.key || generateKeyFromRow(message.value as unknown as T)\n\n            // Include the primary key and relation info in the metadata\n            const enhancedMetadata = {\n              ...message.headers,\n              primaryKey,\n            }\n\n            write({\n              key,\n              type: message.headers.operation,\n              value: message.value,\n              metadata: enhancedMetadata,\n            })\n          } else if (isUpToDateMessage(message)) {\n            hasUpToDate = true\n          }\n        }\n\n        if (hasUpToDate && transactionStarted) {\n          commit()\n          seenTxids.setState((currentTxids) => {\n            const clonedSeen = new Set(currentTxids)\n            newTxids.forEach((txid) => clonedSeen.add(txid))\n\n            newTxids = new Set()\n            return clonedSeen\n          })\n          transactionStarted = false\n        }\n      })\n    },\n    // Expose the awaitTxid function\n    awaitTxid,\n    // Expose the getSyncMetadata function\n    getSyncMetadata,\n  }\n}\n\n/**\n * Configuration options for ElectricSync\n */\nexport interface ElectricSyncOptions {\n  /**\n   * Array of column names that form the primary key of the shape\n   */\n  primaryKey: Array<string>\n}\n"],"names":["isControlMessage","Store","ShapeStream","isChangeMessage"],"mappings":"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"}