@tanstack/react-optimistic
Version:
React hooks for optimistic updates
1 lines • 9.38 kB
Source Map (JSON)
{"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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"}