UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

1 lines 6.6 kB
{"version":3,"file":"paced-mutations.cjs","sources":["../../src/paced-mutations.ts"],"sourcesContent":["import { createTransaction } from './transactions'\nimport type { MutationFn, Transaction } from './types'\nimport type { Strategy } from './strategies/types'\n\n/**\n * Configuration for creating a paced mutations manager\n */\nexport interface PacedMutationsConfig<\n TVariables = unknown,\n T extends object = Record<string, unknown>,\n> {\n /**\n * Callback to apply optimistic updates immediately.\n * Receives the variables passed to the mutate function.\n */\n onMutate: (variables: TVariables) => void\n /**\n * Function to execute the mutation on the server.\n * Receives the transaction parameters containing all merged mutations.\n */\n mutationFn: MutationFn<T>\n /**\n * Strategy for controlling mutation execution timing\n * Examples: debounceStrategy, queueStrategy, throttleStrategy\n */\n strategy: Strategy\n /**\n * Custom metadata to associate with transactions\n */\n metadata?: Record<string, unknown>\n}\n\n/**\n * Creates a paced mutations manager with pluggable timing strategies.\n *\n * This function provides a way to control when and how optimistic mutations\n * are persisted to the backend, using strategies like debouncing, queuing,\n * or throttling. The optimistic updates are applied immediately via `onMutate`,\n * and the actual persistence is controlled by the strategy.\n *\n * The returned function accepts variables of type TVariables and returns a\n * Transaction object that can be awaited to know when persistence completes\n * or to handle errors.\n *\n * @param config - Configuration including onMutate, mutationFn and strategy\n * @returns A function that accepts variables and returns a Transaction\n *\n * @example\n * ```ts\n * // Debounced mutations for auto-save\n * const updateTodo = createPacedMutations<string>({\n * onMutate: (text) => {\n * // Apply optimistic update immediately\n * collection.update(id, draft => { draft.text = text })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 500 })\n * })\n *\n * // Call with variables, returns a transaction\n * const tx = updateTodo('New text')\n *\n * // Await persistence or handle errors\n * await tx.isPersisted.promise\n * ```\n *\n * @example\n * ```ts\n * // Queue strategy for sequential processing\n * const addTodo = createPacedMutations<{ text: string }>({\n * onMutate: ({ text }) => {\n * collection.insert({ id: uuid(), text, completed: false })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: queueStrategy({\n * wait: 200,\n * addItemsTo: 'back',\n * getItemsFrom: 'front'\n * })\n * })\n * ```\n */\nexport function createPacedMutations<\n TVariables = unknown,\n T extends object = Record<string, unknown>,\n>(\n config: PacedMutationsConfig<TVariables, T>,\n): (variables: TVariables) => Transaction<T> {\n const { onMutate, mutationFn, strategy, ...transactionConfig } = config\n\n // The currently active transaction (pending, not yet persisting)\n let activeTransaction: Transaction<T> | null = null\n\n // Commit callback that the strategy will call when it's time to persist\n const commitCallback = () => {\n if (!activeTransaction) {\n throw new Error(\n `Strategy callback called but no active transaction exists. This indicates a bug in the strategy implementation.`,\n )\n }\n\n if (activeTransaction.state !== `pending`) {\n throw new Error(\n `Strategy callback called but active transaction is in state \"${activeTransaction.state}\". Expected \"pending\".`,\n )\n }\n\n const txToCommit = activeTransaction\n\n // Clear active transaction reference before committing\n activeTransaction = null\n\n // Commit the transaction\n txToCommit.commit().catch(() => {\n // Errors are handled via transaction.isPersisted.promise\n // This catch prevents unhandled promise rejections\n })\n\n return txToCommit\n }\n\n /**\n * Executes a mutation with the given variables. Creates a new transaction if none is active,\n * or adds to the existing active transaction. The strategy controls when\n * the transaction is actually committed.\n */\n function mutate(variables: TVariables): Transaction<T> {\n // Create a new transaction if we don't have an active one\n if (!activeTransaction || activeTransaction.state !== `pending`) {\n activeTransaction = createTransaction<T>({\n ...transactionConfig,\n mutationFn,\n autoCommit: false,\n })\n }\n\n // Execute onMutate with variables to apply optimistic updates\n activeTransaction.mutate(() => {\n onMutate(variables)\n })\n\n // Save reference before calling strategy.execute\n const txToReturn = activeTransaction\n\n // For queue strategy, pass a function that commits the captured transaction\n // This prevents the error when commitCallback tries to access the cleared activeTransaction\n if (strategy._type === `queue`) {\n const capturedTx = activeTransaction\n activeTransaction = null // Clear so next mutation creates a new transaction\n strategy.execute(() => {\n capturedTx.commit().catch(() => {\n // Errors are handled via transaction.isPersisted.promise\n })\n return capturedTx\n })\n } else {\n // For debounce/throttle, use commitCallback which manages activeTransaction\n strategy.execute(commitCallback)\n }\n\n return txToReturn\n }\n\n return mutate\n}\n"],"names":["createTransaction"],"mappings":";;;AAsFO,SAAS,qBAId,QAC2C;AAC3C,QAAM,EAAE,UAAU,YAAY,UAAU,GAAG,sBAAsB;AAGjE,MAAI,oBAA2C;AAG/C,QAAM,iBAAiB,MAAM;AAC3B,QAAI,CAAC,mBAAmB;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AAEA,QAAI,kBAAkB,UAAU,WAAW;AACzC,YAAM,IAAI;AAAA,QACR,gEAAgE,kBAAkB,KAAK;AAAA,MAAA;AAAA,IAE3F;AAEA,UAAM,aAAa;AAGnB,wBAAoB;AAGpB,eAAW,SAAS,MAAM,MAAM;AAAA,IAGhC,CAAC;AAED,WAAO;AAAA,EACT;AAOA,WAAS,OAAO,WAAuC;AAErD,QAAI,CAAC,qBAAqB,kBAAkB,UAAU,WAAW;AAC/D,0BAAoBA,aAAAA,kBAAqB;AAAA,QACvC,GAAG;AAAA,QACH;AAAA,QACA,YAAY;AAAA,MAAA,CACb;AAAA,IACH;AAGA,sBAAkB,OAAO,MAAM;AAC7B,eAAS,SAAS;AAAA,IACpB,CAAC;AAGD,UAAM,aAAa;AAInB,QAAI,SAAS,UAAU,SAAS;AAC9B,YAAM,aAAa;AACnB,0BAAoB;AACpB,eAAS,QAAQ,MAAM;AACrB,mBAAW,SAAS,MAAM,MAAM;AAAA,QAEhC,CAAC;AACD,eAAO;AAAA,MACT,CAAC;AAAA,IACH,OAAO;AAEL,eAAS,QAAQ,cAAc;AAAA,IACjC;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;;"}