diff --git a/.changeset/add-timeout-support-electric.md b/.changeset/add-timeout-support-electric.md new file mode 100644 index 000000000..144e6eae2 --- /dev/null +++ b/.changeset/add-timeout-support-electric.md @@ -0,0 +1,25 @@ +--- +"@tanstack/electric-db-collection": minor +--- + +Add timeout support to electricCollectionOptions matching strategies. You can now specify a custom timeout when returning txids from mutation handlers (onInsert, onUpdate, onDelete). + +Previously, users could only customize timeouts when manually calling `collection.utils.awaitTxId()`, but not when using the automatic txid matching strategy. + +**Example:** + +```ts +const collection = createCollection( + electricCollectionOptions({ + // ... other config + onInsert: async ({ transaction }) => { + const newItem = transaction.mutations[0].modified + const result = await api.todos.create({ data: newItem }) + // Specify custom timeout (in milliseconds) + return { txid: result.txid, timeout: 10000 } + }, + }) +) +``` + +The timeout parameter is optional and defaults to 5000ms when not specified. It works with both single txids and arrays of txids. diff --git a/docs/reference/electric-db-collection/classes/ElectricDBCollectionError.md b/docs/reference/electric-db-collection/classes/ElectricDBCollectionError.md index 8ec928746..4ded4090d 100644 --- a/docs/reference/electric-db-collection/classes/ElectricDBCollectionError.md +++ b/docs/reference/electric-db-collection/classes/ElectricDBCollectionError.md @@ -5,7 +5,7 @@ title: ElectricDBCollectionError # Class: ElectricDBCollectionError -Defined in: [packages/electric-db-collection/src/errors.ts:4](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/errors.ts#L4) +Defined in: packages/electric-db-collection/src/errors.ts:4 ## Extends @@ -26,7 +26,7 @@ Defined in: [packages/electric-db-collection/src/errors.ts:4](https://github.com new ElectricDBCollectionError(message, collectionId?): ElectricDBCollectionError; ``` -Defined in: [packages/electric-db-collection/src/errors.ts:5](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/errors.ts#L5) +Defined in: packages/electric-db-collection/src/errors.ts:5 #### Parameters diff --git a/docs/reference/electric-db-collection/classes/ExpectedNumberInAwaitTxIdError.md b/docs/reference/electric-db-collection/classes/ExpectedNumberInAwaitTxIdError.md index a0c31569e..4190837a5 100644 --- a/docs/reference/electric-db-collection/classes/ExpectedNumberInAwaitTxIdError.md +++ b/docs/reference/electric-db-collection/classes/ExpectedNumberInAwaitTxIdError.md @@ -5,7 +5,7 @@ title: ExpectedNumberInAwaitTxIdError # Class: ExpectedNumberInAwaitTxIdError -Defined in: [packages/electric-db-collection/src/errors.ts:11](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/errors.ts#L11) +Defined in: packages/electric-db-collection/src/errors.ts:11 ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/electric-db-collection/src/errors.ts:11](https://github.co new ExpectedNumberInAwaitTxIdError(txIdType, collectionId?): ExpectedNumberInAwaitTxIdError; ``` -Defined in: [packages/electric-db-collection/src/errors.ts:12](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/errors.ts#L12) +Defined in: packages/electric-db-collection/src/errors.ts:12 #### Parameters diff --git a/docs/reference/electric-db-collection/classes/StreamAbortedError.md b/docs/reference/electric-db-collection/classes/StreamAbortedError.md index d08f6639f..997cec4ef 100644 --- a/docs/reference/electric-db-collection/classes/StreamAbortedError.md +++ b/docs/reference/electric-db-collection/classes/StreamAbortedError.md @@ -5,7 +5,7 @@ title: StreamAbortedError # Class: StreamAbortedError -Defined in: [packages/electric-db-collection/src/errors.ts:32](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/errors.ts#L32) +Defined in: packages/electric-db-collection/src/errors.ts:32 ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/electric-db-collection/src/errors.ts:32](https://github.co new StreamAbortedError(collectionId?): StreamAbortedError; ``` -Defined in: [packages/electric-db-collection/src/errors.ts:33](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/errors.ts#L33) +Defined in: packages/electric-db-collection/src/errors.ts:33 #### Parameters diff --git a/docs/reference/electric-db-collection/classes/TimeoutWaitingForMatchError.md b/docs/reference/electric-db-collection/classes/TimeoutWaitingForMatchError.md index 2cfd78e55..b390865c8 100644 --- a/docs/reference/electric-db-collection/classes/TimeoutWaitingForMatchError.md +++ b/docs/reference/electric-db-collection/classes/TimeoutWaitingForMatchError.md @@ -5,7 +5,7 @@ title: TimeoutWaitingForMatchError # Class: TimeoutWaitingForMatchError -Defined in: [packages/electric-db-collection/src/errors.ts:25](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/errors.ts#L25) +Defined in: packages/electric-db-collection/src/errors.ts:25 ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/electric-db-collection/src/errors.ts:25](https://github.co new TimeoutWaitingForMatchError(collectionId?): TimeoutWaitingForMatchError; ``` -Defined in: [packages/electric-db-collection/src/errors.ts:26](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/errors.ts#L26) +Defined in: packages/electric-db-collection/src/errors.ts:26 #### Parameters diff --git a/docs/reference/electric-db-collection/classes/TimeoutWaitingForTxIdError.md b/docs/reference/electric-db-collection/classes/TimeoutWaitingForTxIdError.md index 5f7f76187..ae570ef98 100644 --- a/docs/reference/electric-db-collection/classes/TimeoutWaitingForTxIdError.md +++ b/docs/reference/electric-db-collection/classes/TimeoutWaitingForTxIdError.md @@ -5,7 +5,7 @@ title: TimeoutWaitingForTxIdError # Class: TimeoutWaitingForTxIdError -Defined in: [packages/electric-db-collection/src/errors.ts:18](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/errors.ts#L18) +Defined in: packages/electric-db-collection/src/errors.ts:18 ## Extends @@ -19,7 +19,7 @@ Defined in: [packages/electric-db-collection/src/errors.ts:18](https://github.co new TimeoutWaitingForTxIdError(txId, collectionId?): TimeoutWaitingForTxIdError; ``` -Defined in: [packages/electric-db-collection/src/errors.ts:19](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/errors.ts#L19) +Defined in: packages/electric-db-collection/src/errors.ts:19 #### Parameters diff --git a/docs/reference/electric-db-collection/functions/electricCollectionOptions.md b/docs/reference/electric-db-collection/functions/electricCollectionOptions.md index 1d12a115d..fb9f9754e 100644 --- a/docs/reference/electric-db-collection/functions/electricCollectionOptions.md +++ b/docs/reference/electric-db-collection/functions/electricCollectionOptions.md @@ -11,7 +11,7 @@ title: electricCollectionOptions function electricCollectionOptions(config): CollectionConfig, string | number, T, UtilsRecord> & object; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:277](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L277) +Defined in: packages/electric-db-collection/src/electric.ts:293 Creates Electric collection options for use with a standard Collection @@ -43,7 +43,7 @@ Collection options with utilities function electricCollectionOptions(config): CollectionConfig & object; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:288](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L288) +Defined in: packages/electric-db-collection/src/electric.ts:304 Creates Electric collection options for use with a standard Collection diff --git a/docs/reference/electric-db-collection/interfaces/ElectricCollectionConfig.md b/docs/reference/electric-db-collection/interfaces/ElectricCollectionConfig.md index 5bf6b0af6..cbbf1d294 100644 --- a/docs/reference/electric-db-collection/interfaces/ElectricCollectionConfig.md +++ b/docs/reference/electric-db-collection/interfaces/ElectricCollectionConfig.md @@ -5,7 +5,7 @@ title: ElectricCollectionConfig # Interface: ElectricCollectionConfig\ -Defined in: [packages/electric-db-collection/src/electric.ts:102](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L102) +Defined in: packages/electric-db-collection/src/electric.ts:108 Configuration interface for Electric collection options @@ -35,7 +35,7 @@ The schema type for validation optional onDelete: (params) => Promise; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:208](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L208) +Defined in: packages/electric-db-collection/src/electric.ts:224 Optional asynchronous handler function called before a delete operation @@ -51,7 +51,7 @@ Object containing transaction and collection information `Promise`\<`MatchingStrategy`\> -Promise resolving to { txid } or void +Promise resolving to { txid, timeout? } or void #### Examples @@ -87,7 +87,7 @@ onDelete: async ({ transaction, collection }) => { optional onInsert: (params) => Promise; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:151](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L151) +Defined in: packages/electric-db-collection/src/electric.ts:167 Optional asynchronous handler function called before an insert operation @@ -103,7 +103,7 @@ Object containing transaction and collection information `Promise`\<`MatchingStrategy`\> -Promise resolving to { txid } or void +Promise resolving to { txid, timeout? } or void #### Examples @@ -118,6 +118,17 @@ onInsert: async ({ transaction }) => { } ``` +```ts +// Insert handler with custom timeout +onInsert: async ({ transaction }) => { + const newItem = transaction.mutations[0].modified + const result = await api.todos.create({ + data: newItem + }) + return { txid: result.txid, timeout: 10000 } // Wait up to 10 seconds +} +``` + ```ts // Insert handler with multiple items - return array of txids onInsert: async ({ transaction }) => { @@ -150,7 +161,7 @@ onInsert: async ({ transaction, collection }) => { optional onUpdate: (params) => Promise; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:180](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L180) +Defined in: packages/electric-db-collection/src/electric.ts:196 Optional asynchronous handler function called before an update operation @@ -166,7 +177,7 @@ Object containing transaction and collection information `Promise`\<`MatchingStrategy`\> -Promise resolving to { txid } or void +Promise resolving to { txid, timeout? } or void #### Examples @@ -203,7 +214,7 @@ onUpdate: async ({ transaction, collection }) => { shapeOptions: ShapeStreamOptions>; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:112](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L112) +Defined in: packages/electric-db-collection/src/electric.ts:118 Configuration options for the ElectricSQL ShapeStream @@ -215,4 +226,4 @@ Configuration options for the ElectricSQL ShapeStream optional syncMode: ElectricSyncMode; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:113](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L113) +Defined in: packages/electric-db-collection/src/electric.ts:119 diff --git a/docs/reference/electric-db-collection/interfaces/ElectricCollectionUtils.md b/docs/reference/electric-db-collection/interfaces/ElectricCollectionUtils.md index 569809ba5..4e7e1b920 100644 --- a/docs/reference/electric-db-collection/interfaces/ElectricCollectionUtils.md +++ b/docs/reference/electric-db-collection/interfaces/ElectricCollectionUtils.md @@ -5,7 +5,7 @@ title: ElectricCollectionUtils # Interface: ElectricCollectionUtils\ -Defined in: [packages/electric-db-collection/src/electric.ts:260](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L260) +Defined in: packages/electric-db-collection/src/electric.ts:276 Electric collection utilities type @@ -33,7 +33,7 @@ Electric collection utilities type awaitMatch: AwaitMatchFn; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:263](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L263) +Defined in: packages/electric-db-collection/src/electric.ts:279 *** @@ -43,4 +43,4 @@ Defined in: [packages/electric-db-collection/src/electric.ts:263](https://github awaitTxId: AwaitTxIdFn; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:262](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L262) +Defined in: packages/electric-db-collection/src/electric.ts:278 diff --git a/docs/reference/electric-db-collection/type-aliases/AwaitTxIdFn.md b/docs/reference/electric-db-collection/type-aliases/AwaitTxIdFn.md index f2a8589aa..aab4e10a9 100644 --- a/docs/reference/electric-db-collection/type-aliases/AwaitTxIdFn.md +++ b/docs/reference/electric-db-collection/type-aliases/AwaitTxIdFn.md @@ -9,7 +9,7 @@ title: AwaitTxIdFn type AwaitTxIdFn = (txId, timeout?) => Promise; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:247](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L247) +Defined in: packages/electric-db-collection/src/electric.ts:263 Type for the awaitTxId utility function diff --git a/docs/reference/electric-db-collection/type-aliases/Txid.md b/docs/reference/electric-db-collection/type-aliases/Txid.md index 306d044da..ca074cdaf 100644 --- a/docs/reference/electric-db-collection/type-aliases/Txid.md +++ b/docs/reference/electric-db-collection/type-aliases/Txid.md @@ -9,6 +9,6 @@ title: Txid type Txid = number; ``` -Defined in: [packages/electric-db-collection/src/electric.ts:46](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/src/electric.ts#L46) +Defined in: packages/electric-db-collection/src/electric.ts:46 Type representing a transaction ID in ElectricSQL diff --git a/packages/electric-db-collection/src/electric.ts b/packages/electric-db-collection/src/electric.ts index 03cae12b7..0b377f672 100644 --- a/packages/electric-db-collection/src/electric.ts +++ b/packages/electric-db-collection/src/electric.ts @@ -56,10 +56,16 @@ export type MatchFunction> = ( /** * Matching strategies for Electric synchronization * Handlers can return: - * - Txid strategy: { txid: number | number[] } (recommended) + * - Txid strategy: { txid: number | number[], timeout?: number } (recommended) * - Void (no return value) - mutation completes without waiting + * + * The optional timeout property specifies how long to wait for the txid(s) in milliseconds. + * If not specified, defaults to 5000ms. */ -export type MatchingStrategy = { txid: Txid | Array } | void +export type MatchingStrategy = { + txid: Txid | Array + timeout?: number +} | void /** * Type representing a snapshot end message @@ -115,7 +121,7 @@ export interface ElectricCollectionConfig< /** * Optional asynchronous handler function called before an insert operation * @param params Object containing transaction and collection information - * @returns Promise resolving to { txid } or void + * @returns Promise resolving to { txid, timeout? } or void * @example * // Basic Electric insert handler with txid (recommended) * onInsert: async ({ transaction }) => { @@ -127,6 +133,16 @@ export interface ElectricCollectionConfig< * } * * @example + * // Insert handler with custom timeout + * onInsert: async ({ transaction }) => { + * const newItem = transaction.mutations[0].modified + * const result = await api.todos.create({ + * data: newItem + * }) + * return { txid: result.txid, timeout: 10000 } // Wait up to 10 seconds + * } + * + * @example * // Insert handler with multiple items - return array of txids * onInsert: async ({ transaction }) => { * const items = transaction.mutations.map(m => m.modified) @@ -153,7 +169,7 @@ export interface ElectricCollectionConfig< /** * Optional asynchronous handler function called before an update operation * @param params Object containing transaction and collection information - * @returns Promise resolving to { txid } or void + * @returns Promise resolving to { txid, timeout? } or void * @example * // Basic Electric update handler with txid (recommended) * onUpdate: async ({ transaction }) => { @@ -182,7 +198,7 @@ export interface ElectricCollectionConfig< /** * Optional asynchronous handler function called before a delete operation * @param params Object containing transaction and collection information - * @returns Promise resolving to { txid } or void + * @returns Promise resolving to { txid, timeout? } or void * @example * // Basic Electric delete handler with txid (recommended) * onDelete: async ({ transaction }) => { @@ -531,11 +547,12 @@ export function electricCollectionOptions( ): Promise => { // Only wait if result contains txid if (result && `txid` in result) { + const timeout = result.timeout // Handle both single txid and array of txids if (Array.isArray(result.txid)) { - await Promise.all(result.txid.map((txid) => awaitTxId(txid))) + await Promise.all(result.txid.map((txid) => awaitTxId(txid, timeout))) } else { - await awaitTxId(result.txid) + await awaitTxId(result.txid, timeout) } } // If result is void/undefined, don't wait - mutation completes immediately diff --git a/packages/electric-db-collection/tests/electric.test.ts b/packages/electric-db-collection/tests/electric.test.ts index cc1853db7..8637972f4 100644 --- a/packages/electric-db-collection/tests/electric.test.ts +++ b/packages/electric-db-collection/tests/electric.test.ts @@ -672,6 +672,37 @@ describe(`Electric Integration`, () => { expect(onInsert).toHaveBeenCalled() }) + it(`should support custom timeout in matching strategy`, async () => { + const onInsert = vi.fn(async () => { + // Return a txid that will never arrive with a very short timeout + return { txid: 999999, timeout: 100 } + }) + + const config = { + id: `test-custom-timeout`, + shapeOptions: { + url: `http://test-url`, + params: { table: `test_table` }, + }, + startSync: true, + getKey: (item: Row) => item.id as number, + onInsert, + } + + const testCollection = createCollection(electricCollectionOptions(config)) + + // Insert data - should timeout after 100ms + const tx = testCollection.insert({ id: 1, name: `Timeout Test` }) + + // Verify that our onInsert handler was called + expect(onInsert).toHaveBeenCalled() + + // The transaction should reject due to timeout + await expect(tx.isPersisted.promise).rejects.toThrow( + `Timeout waiting for txId: 999999` + ) + }) + it(`should handle array of txids returned from handler`, async () => { // Create a fake backend that returns multiple txids const fakeBackend = {