@tanstack/db
Version:
A reactive client store for building super fast apps on sync
92 lines (88 loc) • 3.07 kB
text/typescript
import { LiteQueuer } from '@tanstack/pacer-lite/lite-queuer'
import type { QueueStrategy, QueueStrategyOptions } from './types'
import type { Transaction } from '../transactions'
/**
* Creates a queue strategy that processes all mutations in order with proper serialization.
*
* Unlike other strategies that may drop executions, queue ensures every
* mutation is processed sequentially. Each transaction commit completes before
* the next one starts. Useful when data consistency is critical and
* every operation must complete in order.
*
* @param options - Configuration for queue behavior (FIFO/LIFO, timing, size limits)
* @returns A queue strategy instance
*
* @example
* ```ts
* // FIFO queue - process in order received
* const mutate = usePacedMutations({
* mutationFn: async ({ transaction }) => {
* await api.save(transaction.mutations)
* },
* strategy: queueStrategy({
* wait: 200,
* addItemsTo: 'back',
* getItemsFrom: 'front'
* })
* })
* ```
*
* @example
* ```ts
* // LIFO queue - process most recent first
* const mutate = usePacedMutations({
* mutationFn: async ({ transaction }) => {
* await api.save(transaction.mutations)
* },
* strategy: queueStrategy({
* wait: 200,
* addItemsTo: 'back',
* getItemsFrom: 'back'
* })
* })
* ```
*/
export function queueStrategy(options?: QueueStrategyOptions): QueueStrategy {
// Manual promise chaining to ensure async serialization
// LiteQueuer (unlike AsyncQueuer from @tanstack/pacer) lacks built-in async queue
// primitives and concurrency control. We compensate by manually chaining promises
// to ensure each transaction completes before the next one starts.
let processingChain = Promise.resolve()
const queuer = new LiteQueuer<() => Transaction>(
(fn) => {
// Chain each transaction to the previous one's completion
processingChain = processingChain
.then(async () => {
const transaction = fn()
// Wait for the transaction to be persisted before processing next item
await transaction.isPersisted.promise
})
.catch(() => {
// Errors are handled via transaction.isPersisted.promise and surfaced there.
// This catch prevents unhandled promise rejections from breaking the chain,
// ensuring subsequent transactions can still execute even if one fails.
})
},
{
wait: options?.wait ?? 0,
maxSize: options?.maxSize,
addItemsTo: options?.addItemsTo ?? `back`, // Default FIFO: add to back
getItemsFrom: options?.getItemsFrom ?? `front`, // Default FIFO: get from front
started: true, // Start processing immediately
},
)
return {
_type: `queue`,
options,
execute: <T extends object = Record<string, unknown>>(
fn: () => Transaction<T>,
) => {
// Add the transaction-creating function to the queue
queuer.addItem(fn as () => Transaction)
},
cleanup: () => {
queuer.stop()
queuer.clear()
},
}
}