@tanstack/db 0.4.15 → 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/cjs/index.cjs +8 -0
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/index.d.cts +2 -0
  4. package/dist/cjs/indexes/auto-index.cjs +17 -8
  5. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  6. package/dist/cjs/paced-mutations.cjs +52 -0
  7. package/dist/cjs/paced-mutations.cjs.map +1 -0
  8. package/dist/cjs/paced-mutations.d.cts +81 -0
  9. package/dist/cjs/query/optimizer.cjs +17 -2
  10. package/dist/cjs/query/optimizer.cjs.map +1 -1
  11. package/dist/cjs/strategies/debounceStrategy.cjs +21 -0
  12. package/dist/cjs/strategies/debounceStrategy.cjs.map +1 -0
  13. package/dist/cjs/strategies/debounceStrategy.d.cts +22 -0
  14. package/dist/cjs/strategies/index.d.cts +4 -0
  15. package/dist/cjs/strategies/queueStrategy.cjs +33 -0
  16. package/dist/cjs/strategies/queueStrategy.cjs.map +1 -0
  17. package/dist/cjs/strategies/queueStrategy.d.cts +43 -0
  18. package/dist/cjs/strategies/throttleStrategy.cjs +21 -0
  19. package/dist/cjs/strategies/throttleStrategy.cjs.map +1 -0
  20. package/dist/cjs/strategies/throttleStrategy.d.cts +39 -0
  21. package/dist/cjs/strategies/types.d.cts +103 -0
  22. package/dist/esm/index.d.ts +2 -0
  23. package/dist/esm/index.js +8 -0
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/indexes/auto-index.js +17 -8
  26. package/dist/esm/indexes/auto-index.js.map +1 -1
  27. package/dist/esm/paced-mutations.d.ts +81 -0
  28. package/dist/esm/paced-mutations.js +52 -0
  29. package/dist/esm/paced-mutations.js.map +1 -0
  30. package/dist/esm/query/optimizer.js +17 -2
  31. package/dist/esm/query/optimizer.js.map +1 -1
  32. package/dist/esm/strategies/debounceStrategy.d.ts +22 -0
  33. package/dist/esm/strategies/debounceStrategy.js +21 -0
  34. package/dist/esm/strategies/debounceStrategy.js.map +1 -0
  35. package/dist/esm/strategies/index.d.ts +4 -0
  36. package/dist/esm/strategies/queueStrategy.d.ts +43 -0
  37. package/dist/esm/strategies/queueStrategy.js +33 -0
  38. package/dist/esm/strategies/queueStrategy.js.map +1 -0
  39. package/dist/esm/strategies/throttleStrategy.d.ts +39 -0
  40. package/dist/esm/strategies/throttleStrategy.js +21 -0
  41. package/dist/esm/strategies/throttleStrategy.js.map +1 -0
  42. package/dist/esm/strategies/types.d.ts +103 -0
  43. package/package.json +3 -1
  44. package/src/index.ts +2 -0
  45. package/src/indexes/auto-index.ts +23 -10
  46. package/src/paced-mutations.ts +169 -0
  47. package/src/query/optimizer.ts +31 -4
  48. package/src/strategies/debounceStrategy.ts +45 -0
  49. package/src/strategies/index.ts +17 -0
  50. package/src/strategies/queueStrategy.ts +75 -0
  51. package/src/strategies/throttleStrategy.ts +62 -0
  52. package/src/strategies/types.ts +130 -0
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const asyncQueuer = require("@tanstack/pacer/async-queuer");
4
+ function queueStrategy(options) {
5
+ const queuer = new asyncQueuer.AsyncQueuer({
6
+ concurrency: 1,
7
+ // Process one at a time to ensure serialization
8
+ wait: options?.wait,
9
+ maxSize: options?.maxSize,
10
+ addItemsTo: options?.addItemsTo ?? `back`,
11
+ // Default FIFO: add to back
12
+ getItemsFrom: options?.getItemsFrom ?? `front`,
13
+ // Default FIFO: get from front
14
+ started: true
15
+ // Start processing immediately
16
+ });
17
+ return {
18
+ _type: `queue`,
19
+ options,
20
+ execute: (fn) => {
21
+ queuer.addItem(async () => {
22
+ const transaction = fn();
23
+ await transaction.isPersisted.promise;
24
+ });
25
+ },
26
+ cleanup: () => {
27
+ queuer.stop();
28
+ queuer.clear();
29
+ }
30
+ };
31
+ }
32
+ exports.queueStrategy = queueStrategy;
33
+ //# sourceMappingURL=queueStrategy.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queueStrategy.cjs","sources":["../../../src/strategies/queueStrategy.ts"],"sourcesContent":["import { AsyncQueuer } from \"@tanstack/pacer/async-queuer\"\nimport type { QueueStrategy, QueueStrategyOptions } from \"./types\"\nimport type { Transaction } from \"../transactions\"\n\n/**\n * Creates a queue strategy that processes all mutations in order with proper serialization.\n *\n * Unlike other strategies that may drop executions, queue ensures every\n * mutation is processed sequentially. Each transaction commit completes before\n * the next one starts. Useful when data consistency is critical and\n * every operation must complete in order.\n *\n * @param options - Configuration for queue behavior (FIFO/LIFO, timing, size limits)\n * @returns A queue strategy instance\n *\n * @example\n * ```ts\n * // FIFO queue - process in order received\n * const mutate = usePacedMutations({\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 *\n * @example\n * ```ts\n * // LIFO queue - process most recent first\n * const mutate = usePacedMutations({\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: queueStrategy({\n * wait: 200,\n * addItemsTo: 'back',\n * getItemsFrom: 'back'\n * })\n * })\n * ```\n */\nexport function queueStrategy(options?: QueueStrategyOptions): QueueStrategy {\n const queuer = new AsyncQueuer<void>({\n concurrency: 1, // Process one at a time to ensure serialization\n wait: options?.wait,\n maxSize: options?.maxSize,\n addItemsTo: options?.addItemsTo ?? `back`, // Default FIFO: add to back\n getItemsFrom: options?.getItemsFrom ?? `front`, // Default FIFO: get from front\n started: true, // Start processing immediately\n })\n\n return {\n _type: `queue`,\n options,\n execute: <T extends object = Record<string, unknown>>(\n fn: () => Transaction<T>\n ) => {\n // Wrap the callback in an async function that waits for persistence\n queuer.addItem(async () => {\n const transaction = fn()\n // Wait for the transaction to be persisted before processing next item\n // Note: fn() already calls commit(), we just wait for it to complete\n await transaction.isPersisted.promise\n })\n },\n cleanup: () => {\n queuer.stop()\n queuer.clear()\n },\n }\n}\n"],"names":["AsyncQueuer"],"mappings":";;;AA6CO,SAAS,cAAc,SAA+C;AAC3E,QAAM,SAAS,IAAIA,wBAAkB;AAAA,IACnC,aAAa;AAAA;AAAA,IACb,MAAM,SAAS;AAAA,IACf,SAAS,SAAS;AAAA,IAClB,YAAY,SAAS,cAAc;AAAA;AAAA,IACnC,cAAc,SAAS,gBAAgB;AAAA;AAAA,IACvC,SAAS;AAAA;AAAA,EAAA,CACV;AAED,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA,SAAS,CACP,OACG;AAEH,aAAO,QAAQ,YAAY;AACzB,cAAM,cAAc,GAAA;AAGpB,cAAM,YAAY,YAAY;AAAA,MAChC,CAAC;AAAA,IACH;AAAA,IACA,SAAS,MAAM;AACb,aAAO,KAAA;AACP,aAAO,MAAA;AAAA,IACT;AAAA,EAAA;AAEJ;;"}
@@ -0,0 +1,43 @@
1
+ import { QueueStrategy, QueueStrategyOptions } from './types.cjs';
2
+ /**
3
+ * Creates a queue strategy that processes all mutations in order with proper serialization.
4
+ *
5
+ * Unlike other strategies that may drop executions, queue ensures every
6
+ * mutation is processed sequentially. Each transaction commit completes before
7
+ * the next one starts. Useful when data consistency is critical and
8
+ * every operation must complete in order.
9
+ *
10
+ * @param options - Configuration for queue behavior (FIFO/LIFO, timing, size limits)
11
+ * @returns A queue strategy instance
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // FIFO queue - process in order received
16
+ * const mutate = usePacedMutations({
17
+ * mutationFn: async ({ transaction }) => {
18
+ * await api.save(transaction.mutations)
19
+ * },
20
+ * strategy: queueStrategy({
21
+ * wait: 200,
22
+ * addItemsTo: 'back',
23
+ * getItemsFrom: 'front'
24
+ * })
25
+ * })
26
+ * ```
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * // LIFO queue - process most recent first
31
+ * const mutate = usePacedMutations({
32
+ * mutationFn: async ({ transaction }) => {
33
+ * await api.save(transaction.mutations)
34
+ * },
35
+ * strategy: queueStrategy({
36
+ * wait: 200,
37
+ * addItemsTo: 'back',
38
+ * getItemsFrom: 'back'
39
+ * })
40
+ * })
41
+ * ```
42
+ */
43
+ export declare function queueStrategy(options?: QueueStrategyOptions): QueueStrategy;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const throttler = require("@tanstack/pacer/throttler");
4
+ function throttleStrategy(options) {
5
+ const throttler$1 = new throttler.Throttler(
6
+ (callback) => callback(),
7
+ options
8
+ );
9
+ return {
10
+ _type: `throttle`,
11
+ options,
12
+ execute: (fn) => {
13
+ throttler$1.maybeExecute(fn);
14
+ },
15
+ cleanup: () => {
16
+ throttler$1.cancel();
17
+ }
18
+ };
19
+ }
20
+ exports.throttleStrategy = throttleStrategy;
21
+ //# sourceMappingURL=throttleStrategy.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttleStrategy.cjs","sources":["../../../src/strategies/throttleStrategy.ts"],"sourcesContent":["import { Throttler } from \"@tanstack/pacer/throttler\"\nimport type { ThrottleStrategy, ThrottleStrategyOptions } from \"./types\"\nimport type { Transaction } from \"../transactions\"\n\n/**\n * Creates a throttle strategy that ensures transactions are evenly spaced\n * over time.\n *\n * Provides smooth, controlled execution patterns ideal for UI updates like\n * sliders, progress bars, or scroll handlers where you want consistent\n * execution timing.\n *\n * @param options - Configuration for throttle behavior\n * @returns A throttle strategy instance\n *\n * @example\n * ```ts\n * // Throttle slider updates to every 200ms\n * const mutate = useSerializedTransaction({\n * mutationFn: async ({ transaction }) => {\n * await api.updateVolume(transaction.mutations)\n * },\n * strategy: throttleStrategy({ wait: 200 })\n * })\n * ```\n *\n * @example\n * ```ts\n * // Throttle with leading and trailing execution\n * const mutate = useSerializedTransaction({\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: throttleStrategy({\n * wait: 500,\n * leading: true,\n * trailing: true\n * })\n * })\n * ```\n */\nexport function throttleStrategy(\n options: ThrottleStrategyOptions\n): ThrottleStrategy {\n const throttler = new Throttler(\n (callback: () => Transaction) => callback(),\n options\n )\n\n return {\n _type: `throttle`,\n options,\n execute: <T extends object = Record<string, unknown>>(\n fn: () => Transaction<T>\n ) => {\n throttler.maybeExecute(fn as () => Transaction)\n },\n cleanup: () => {\n throttler.cancel()\n },\n }\n}\n"],"names":["throttler","Throttler"],"mappings":";;;AAyCO,SAAS,iBACd,SACkB;AAClB,QAAMA,cAAY,IAAIC,UAAAA;AAAAA,IACpB,CAAC,aAAgC,SAAA;AAAA,IACjC;AAAA,EAAA;AAGF,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA,SAAS,CACP,OACG;AACHD,kBAAU,aAAa,EAAuB;AAAA,IAChD;AAAA,IACA,SAAS,MAAM;AACbA,kBAAU,OAAA;AAAA,IACZ;AAAA,EAAA;AAEJ;;"}
@@ -0,0 +1,39 @@
1
+ import { ThrottleStrategy, ThrottleStrategyOptions } from './types.cjs';
2
+ /**
3
+ * Creates a throttle strategy that ensures transactions are evenly spaced
4
+ * over time.
5
+ *
6
+ * Provides smooth, controlled execution patterns ideal for UI updates like
7
+ * sliders, progress bars, or scroll handlers where you want consistent
8
+ * execution timing.
9
+ *
10
+ * @param options - Configuration for throttle behavior
11
+ * @returns A throttle strategy instance
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // Throttle slider updates to every 200ms
16
+ * const mutate = useSerializedTransaction({
17
+ * mutationFn: async ({ transaction }) => {
18
+ * await api.updateVolume(transaction.mutations)
19
+ * },
20
+ * strategy: throttleStrategy({ wait: 200 })
21
+ * })
22
+ * ```
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * // Throttle with leading and trailing execution
27
+ * const mutate = useSerializedTransaction({
28
+ * mutationFn: async ({ transaction }) => {
29
+ * await api.save(transaction.mutations)
30
+ * },
31
+ * strategy: throttleStrategy({
32
+ * wait: 500,
33
+ * leading: true,
34
+ * trailing: true
35
+ * })
36
+ * })
37
+ * ```
38
+ */
39
+ export declare function throttleStrategy(options: ThrottleStrategyOptions): ThrottleStrategy;
@@ -0,0 +1,103 @@
1
+ import { Transaction } from '../transactions.cjs';
2
+ /**
3
+ * Base strategy interface that all strategy implementations must conform to
4
+ */
5
+ export interface BaseStrategy<TName extends string = string> {
6
+ /** Type discriminator for strategy identification */
7
+ _type: TName;
8
+ /**
9
+ * Execute a function according to the strategy's timing rules
10
+ * @param fn - The function to execute
11
+ * @returns The result of the function execution (if applicable)
12
+ */
13
+ execute: <T extends object = Record<string, unknown>>(fn: () => Transaction<T>) => void | Promise<void>;
14
+ /**
15
+ * Clean up any resources held by the strategy
16
+ * Should be called when the strategy is no longer needed
17
+ */
18
+ cleanup: () => void;
19
+ }
20
+ /**
21
+ * Options for debounce strategy
22
+ * Delays execution until after a period of inactivity
23
+ */
24
+ export interface DebounceStrategyOptions {
25
+ /** Wait time in milliseconds before execution */
26
+ wait: number;
27
+ /** Execute immediately on the first call */
28
+ leading?: boolean;
29
+ /** Execute after the wait period on the last call */
30
+ trailing?: boolean;
31
+ }
32
+ /**
33
+ * Debounce strategy that delays execution until activity stops
34
+ */
35
+ export interface DebounceStrategy extends BaseStrategy<`debounce`> {
36
+ options: DebounceStrategyOptions;
37
+ }
38
+ /**
39
+ * Options for queue strategy
40
+ * Processes all executions in order (FIFO/LIFO)
41
+ */
42
+ export interface QueueStrategyOptions {
43
+ /** Wait time between processing queue items (milliseconds) */
44
+ wait?: number;
45
+ /** Maximum queue size (items are dropped if exceeded) */
46
+ maxSize?: number;
47
+ /** Where to add new items in the queue */
48
+ addItemsTo?: `front` | `back`;
49
+ /** Where to get items from when processing */
50
+ getItemsFrom?: `front` | `back`;
51
+ }
52
+ /**
53
+ * Queue strategy that processes all executions in order
54
+ * FIFO: { addItemsTo: 'back', getItemsFrom: 'front' }
55
+ * LIFO: { addItemsTo: 'back', getItemsFrom: 'back' }
56
+ */
57
+ export interface QueueStrategy extends BaseStrategy<`queue`> {
58
+ options?: QueueStrategyOptions;
59
+ }
60
+ /**
61
+ * Options for throttle strategy
62
+ * Ensures executions are evenly spaced over time
63
+ */
64
+ export interface ThrottleStrategyOptions {
65
+ /** Minimum wait time between executions (milliseconds) */
66
+ wait: number;
67
+ /** Execute immediately on the first call */
68
+ leading?: boolean;
69
+ /** Execute on the last call after wait period */
70
+ trailing?: boolean;
71
+ }
72
+ /**
73
+ * Throttle strategy that spaces executions evenly over time
74
+ */
75
+ export interface ThrottleStrategy extends BaseStrategy<`throttle`> {
76
+ options: ThrottleStrategyOptions;
77
+ }
78
+ /**
79
+ * Options for batch strategy
80
+ * Groups multiple executions together
81
+ */
82
+ export interface BatchStrategyOptions {
83
+ /** Maximum items per batch */
84
+ maxSize?: number;
85
+ /** Maximum wait time before processing batch (milliseconds) */
86
+ wait?: number;
87
+ /** Custom logic to determine when to execute batch */
88
+ getShouldExecute?: (items: Array<any>) => boolean;
89
+ }
90
+ /**
91
+ * Batch strategy that groups multiple executions together
92
+ */
93
+ export interface BatchStrategy extends BaseStrategy<`batch`> {
94
+ options?: BatchStrategyOptions;
95
+ }
96
+ /**
97
+ * Union type of all available strategies
98
+ */
99
+ export type Strategy = DebounceStrategy | QueueStrategy | ThrottleStrategy | BatchStrategy;
100
+ /**
101
+ * Extract the options type from a strategy
102
+ */
103
+ export type StrategyOptions<T extends Strategy> = T extends DebounceStrategy ? DebounceStrategyOptions : T extends QueueStrategy ? QueueStrategyOptions : T extends ThrottleStrategy ? ThrottleStrategyOptions : T extends BatchStrategy ? BatchStrategyOptions : never;
@@ -9,6 +9,8 @@ export * from './optimistic-action.js';
9
9
  export * from './local-only.js';
10
10
  export * from './local-storage.js';
11
11
  export * from './errors.js';
12
+ export * from './paced-mutations.js';
13
+ export * from './strategies/index.js';
12
14
  export * from './indexes/base-index.js';
13
15
  export * from './indexes/btree-index.js';
14
16
  export * from './indexes/lazy-index.js';
package/dist/esm/index.js CHANGED
@@ -7,6 +7,7 @@ import { createOptimisticAction } from "./optimistic-action.js";
7
7
  import { localOnlyCollectionOptions } from "./local-only.js";
8
8
  import { localStorageCollectionOptions } from "./local-storage.js";
9
9
  import { AggregateFunctionNotInSelectError, AggregateNotSupportedError, CannotCombineEmptyExpressionListError, CollectionConfigurationError, CollectionInErrorStateError, CollectionInputNotFoundError, CollectionIsInErrorStateError, CollectionOperationError, CollectionRequiresConfigError, CollectionRequiresSyncConfigError, CollectionStateError, DeleteKeyNotFoundError, DistinctRequiresSelectError, DuplicateKeyError, DuplicateKeySyncError, EmptyReferencePathError, GroupByError, HavingRequiresGroupByError, InvalidCollectionStatusTransitionError, InvalidJoinCondition, InvalidJoinConditionLeftSourceError, InvalidJoinConditionRightSourceError, InvalidJoinConditionSameSourceError, InvalidJoinConditionSourceMismatchError, InvalidSchemaError, InvalidSourceError, InvalidStorageDataFormatError, InvalidStorageObjectFormatError, JoinCollectionNotFoundError, JoinConditionMustBeEqualityError, JoinError, KeyUpdateNotAllowedError, LimitOffsetRequireOrderByError, LocalStorageCollectionError, MissingAliasInputsError, MissingDeleteHandlerError, MissingHandlerError, MissingInsertHandlerError, MissingMutationFunctionError, MissingUpdateArgumentError, MissingUpdateHandlerError, NegativeActiveSubscribersError, NoKeysPassedToDeleteError, NoKeysPassedToUpdateError, NoPendingSyncTransactionCommitError, NoPendingSyncTransactionWriteError, NonAggregateExpressionNotInGroupByError, NonRetriableError, OnlyOneSourceAllowedError, QueryBuilderError, QueryCompilationError, QueryMustHaveFromClauseError, QueryOptimizerError, SchemaMustBeSynchronousError, SchemaValidationError, SerializationError, SetWindowRequiresOrderByError, StorageError, StorageKeyRequiredError, SubQueryMustHaveFromClauseError, SubscriptionNotFoundError, SyncCleanupError, SyncTransactionAlreadyCommittedError, SyncTransactionAlreadyCommittedWriteError, TanStackDBError, TransactionAlreadyCompletedRollbackError, TransactionError, TransactionNotPendingCommitError, TransactionNotPendingMutateError, UndefinedKeyError, UnknownExpressionTypeError, UnknownFunctionError, UnknownHavingExpressionTypeError, UnsupportedAggregateFunctionError, UnsupportedFromTypeError, UnsupportedJoinSourceTypeError, UnsupportedJoinTypeError, UpdateKeyNotFoundError, WhereClauseConversionError } from "./errors.js";
10
+ import { createPacedMutations } from "./paced-mutations.js";
10
11
  import { BaseIndex, IndexOperation } from "./indexes/base-index.js";
11
12
  import { BTreeIndex } from "./indexes/btree-index.js";
12
13
  import { IndexProxy, LazyIndexWrapper } from "./indexes/lazy-index.js";
@@ -14,6 +15,9 @@ import { BaseQueryBuilder, Query } from "./query/builder/index.js";
14
15
  import { add, and, avg, coalesce, concat, count, eq, gt, gte, ilike, inArray, isNull, isUndefined, length, like, lower, lt, lte, max, min, not, or, sum, upper } from "./query/builder/functions.js";
15
16
  import { compileQuery } from "./query/compiler/index.js";
16
17
  import { createLiveQueryCollection, liveQueryCollectionOptions } from "./query/live-query-collection.js";
18
+ import { debounceStrategy } from "./strategies/debounceStrategy.js";
19
+ import { queueStrategy } from "./strategies/queueStrategy.js";
20
+ import { throttleStrategy } from "./strategies/throttleStrategy.js";
17
21
  export {
18
22
  AggregateFunctionNotInSelectError,
19
23
  AggregateNotSupportedError,
@@ -116,7 +120,9 @@ export {
116
120
  createCollection,
117
121
  createLiveQueryCollection,
118
122
  createOptimisticAction,
123
+ createPacedMutations,
119
124
  createTransaction,
125
+ debounceStrategy,
120
126
  eq,
121
127
  getActiveTransaction,
122
128
  gt,
@@ -137,7 +143,9 @@ export {
137
143
  min,
138
144
  not,
139
145
  or,
146
+ queueStrategy,
140
147
  sum,
148
+ throttleStrategy,
141
149
  upper,
142
150
  withArrayChangeTracking,
143
151
  withChangeTracking
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;"}
@@ -17,14 +17,23 @@ function ensureIndexForField(fieldName, fieldPath, collection, compareOptions =
17
17
  return;
18
18
  }
19
19
  try {
20
- collection.createIndex((row) => row[fieldName], {
21
- name: `auto_${fieldName}`,
22
- indexType: BTreeIndex,
23
- options: compareFn ? { compareFn, compareOptions } : {}
24
- });
20
+ collection.createIndex(
21
+ (row) => {
22
+ let current = row;
23
+ for (const part of fieldPath) {
24
+ current = current[part];
25
+ }
26
+ return current;
27
+ },
28
+ {
29
+ name: `auto:${fieldPath.join(`.`)}`,
30
+ indexType: BTreeIndex,
31
+ options: compareFn ? { compareFn, compareOptions } : {}
32
+ }
33
+ );
25
34
  } catch (error) {
26
35
  console.warn(
27
- `${collection.id ? `[${collection.id}] ` : ``}Failed to create auto-index for field "${fieldName}":`,
36
+ `${collection.id ? `[${collection.id}] ` : ``}Failed to create auto-index for field path "${fieldPath.join(`.`)}":`,
28
37
  error
29
38
  );
30
39
  }
@@ -60,10 +69,10 @@ function extractIndexableExpressions(expression) {
60
69
  }
61
70
  const fieldRef = func.args[0];
62
71
  const fieldPath = fieldRef.path;
63
- if (fieldPath.length !== 1) {
72
+ if (fieldPath.length === 0) {
64
73
  return;
65
74
  }
66
- const fieldName = fieldPath[0];
75
+ const fieldName = fieldPath.join(`_`);
67
76
  results.push({ fieldName, fieldPath });
68
77
  }
69
78
  extractFromExpression(expression);
@@ -1 +1 @@
1
- {"version":3,"file":"auto-index.js","sources":["../../../src/indexes/auto-index.ts"],"sourcesContent":["import { DEFAULT_COMPARE_OPTIONS } from \"../utils\"\nimport { BTreeIndex } from \"./btree-index\"\nimport type { CompareOptions } from \"../query/builder/types\"\nimport type { BasicExpression } from \"../query/ir\"\nimport type { CollectionImpl } from \"../collection/index.js\"\n\nexport interface AutoIndexConfig {\n autoIndex?: `off` | `eager`\n}\n\nfunction shouldAutoIndex(collection: CollectionImpl<any, any, any, any, any>) {\n // Only proceed if auto-indexing is enabled\n if (collection.config.autoIndex !== `eager`) {\n return false\n }\n\n return true\n}\n\nexport function ensureIndexForField<\n T extends Record<string, any>,\n TKey extends string | number,\n>(\n fieldName: string,\n fieldPath: Array<string>,\n collection: CollectionImpl<T, TKey, any, any, any>,\n compareOptions: CompareOptions = DEFAULT_COMPARE_OPTIONS,\n compareFn?: (a: any, b: any) => number\n) {\n if (!shouldAutoIndex(collection)) {\n return\n }\n\n // Check if we already have an index for this field\n const existingIndex = Array.from(collection.indexes.values()).find(\n (index) =>\n index.matchesField(fieldPath) &&\n index.matchesCompareOptions(compareOptions)\n )\n\n if (existingIndex) {\n return // Index already exists\n }\n\n // Create a new index for this field using the collection's createIndex method\n try {\n collection.createIndex((row) => (row as any)[fieldName], {\n name: `auto_${fieldName}`,\n indexType: BTreeIndex,\n options: compareFn ? { compareFn, compareOptions } : {},\n })\n } catch (error) {\n console.warn(\n `${collection.id ? `[${collection.id}] ` : ``}Failed to create auto-index for field \"${fieldName}\":`,\n error\n )\n }\n}\n\n/**\n * Analyzes a where expression and creates indexes for all simple operations on single fields\n */\nexport function ensureIndexForExpression<\n T extends Record<string, any>,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionImpl<T, TKey, any, any, any>\n): void {\n if (!shouldAutoIndex(collection)) {\n return\n }\n\n // Extract all indexable expressions and create indexes for them\n const indexableExpressions = extractIndexableExpressions(expression)\n\n for (const { fieldName, fieldPath } of indexableExpressions) {\n ensureIndexForField(fieldName, fieldPath, collection)\n }\n}\n\n/**\n * Extracts all indexable expressions from a where expression\n */\nfunction extractIndexableExpressions(\n expression: BasicExpression\n): Array<{ fieldName: string; fieldPath: Array<string> }> {\n const results: Array<{ fieldName: string; fieldPath: Array<string> }> = []\n\n function extractFromExpression(expr: BasicExpression): void {\n if (expr.type !== `func`) {\n return\n }\n\n const func = expr as any\n\n // Handle 'and' expressions by recursively processing all arguments\n if (func.name === `and`) {\n for (const arg of func.args) {\n extractFromExpression(arg)\n }\n return\n }\n\n // Check if this is a supported operation\n const supportedOperations = [`eq`, `gt`, `gte`, `lt`, `lte`, `in`]\n if (!supportedOperations.includes(func.name)) {\n return\n }\n\n // Check if the first argument is a property reference (single field)\n if (func.args.length < 1 || func.args[0].type !== `ref`) {\n return\n }\n\n const fieldRef = func.args[0]\n const fieldPath = fieldRef.path\n\n // Skip if it's not a simple field (e.g., nested properties or array access)\n if (fieldPath.length !== 1) {\n return\n }\n\n const fieldName = fieldPath[0]\n results.push({ fieldName, fieldPath })\n }\n\n extractFromExpression(expression)\n return results\n}\n"],"names":[],"mappings":";;AAUA,SAAS,gBAAgB,YAAqD;AAE5E,MAAI,WAAW,OAAO,cAAc,SAAS;AAC3C,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,SAAS,oBAId,WACA,WACA,YACA,iBAAiC,yBACjC,WACA;AACA,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC;AAAA,EACF;AAGA,QAAM,gBAAgB,MAAM,KAAK,WAAW,QAAQ,OAAA,CAAQ,EAAE;AAAA,IAC5D,CAAC,UACC,MAAM,aAAa,SAAS,KAC5B,MAAM,sBAAsB,cAAc;AAAA,EAAA;AAG9C,MAAI,eAAe;AACjB;AAAA,EACF;AAGA,MAAI;AACF,eAAW,YAAY,CAAC,QAAS,IAAY,SAAS,GAAG;AAAA,MACvD,MAAM,QAAQ,SAAS;AAAA,MACvB,WAAW;AAAA,MACX,SAAS,YAAY,EAAE,WAAW,eAAA,IAAmB,CAAA;AAAA,IAAC,CACvD;AAAA,EACH,SAAS,OAAO;AACd,YAAQ;AAAA,MACN,GAAG,WAAW,KAAK,IAAI,WAAW,EAAE,OAAO,EAAE,0CAA0C,SAAS;AAAA,MAChG;AAAA,IAAA;AAAA,EAEJ;AACF;AAKO,SAAS,yBAId,YACA,YACM;AACN,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC;AAAA,EACF;AAGA,QAAM,uBAAuB,4BAA4B,UAAU;AAEnE,aAAW,EAAE,WAAW,UAAA,KAAe,sBAAsB;AAC3D,wBAAoB,WAAW,WAAW,UAAU;AAAA,EACtD;AACF;AAKA,SAAS,4BACP,YACwD;AACxD,QAAM,UAAkE,CAAA;AAExE,WAAS,sBAAsB,MAA6B;AAC1D,QAAI,KAAK,SAAS,QAAQ;AACxB;AAAA,IACF;AAEA,UAAM,OAAO;AAGb,QAAI,KAAK,SAAS,OAAO;AACvB,iBAAW,OAAO,KAAK,MAAM;AAC3B,8BAAsB,GAAG;AAAA,MAC3B;AACA;AAAA,IACF;AAGA,UAAM,sBAAsB,CAAC,MAAM,MAAM,OAAO,MAAM,OAAO,IAAI;AACjE,QAAI,CAAC,oBAAoB,SAAS,KAAK,IAAI,GAAG;AAC5C;AAAA,IACF;AAGA,QAAI,KAAK,KAAK,SAAS,KAAK,KAAK,KAAK,CAAC,EAAE,SAAS,OAAO;AACvD;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,UAAM,YAAY,SAAS;AAG3B,QAAI,UAAU,WAAW,GAAG;AAC1B;AAAA,IACF;AAEA,UAAM,YAAY,UAAU,CAAC;AAC7B,YAAQ,KAAK,EAAE,WAAW,UAAA,CAAW;AAAA,EACvC;AAEA,wBAAsB,UAAU;AAChC,SAAO;AACT;"}
1
+ {"version":3,"file":"auto-index.js","sources":["../../../src/indexes/auto-index.ts"],"sourcesContent":["import { DEFAULT_COMPARE_OPTIONS } from \"../utils\"\nimport { BTreeIndex } from \"./btree-index\"\nimport type { CompareOptions } from \"../query/builder/types\"\nimport type { BasicExpression } from \"../query/ir\"\nimport type { CollectionImpl } from \"../collection/index.js\"\n\nexport interface AutoIndexConfig {\n autoIndex?: `off` | `eager`\n}\n\nfunction shouldAutoIndex(collection: CollectionImpl<any, any, any, any, any>) {\n // Only proceed if auto-indexing is enabled\n if (collection.config.autoIndex !== `eager`) {\n return false\n }\n\n return true\n}\n\nexport function ensureIndexForField<\n T extends Record<string, any>,\n TKey extends string | number,\n>(\n fieldName: string,\n fieldPath: Array<string>,\n collection: CollectionImpl<T, TKey, any, any, any>,\n compareOptions: CompareOptions = DEFAULT_COMPARE_OPTIONS,\n compareFn?: (a: any, b: any) => number\n) {\n if (!shouldAutoIndex(collection)) {\n return\n }\n\n // Check if we already have an index for this field\n const existingIndex = Array.from(collection.indexes.values()).find(\n (index) =>\n index.matchesField(fieldPath) &&\n index.matchesCompareOptions(compareOptions)\n )\n\n if (existingIndex) {\n return // Index already exists\n }\n\n // Create a new index for this field using the collection's createIndex method\n try {\n // Use the proxy-based approach to create the proper accessor for nested paths\n collection.createIndex(\n (row) => {\n // Navigate through the field path\n let current: any = row\n for (const part of fieldPath) {\n current = current[part]\n }\n return current\n },\n {\n name: `auto:${fieldPath.join(`.`)}`,\n indexType: BTreeIndex,\n options: compareFn ? { compareFn, compareOptions } : {},\n }\n )\n } catch (error) {\n console.warn(\n `${collection.id ? `[${collection.id}] ` : ``}Failed to create auto-index for field path \"${fieldPath.join(`.`)}\":`,\n error\n )\n }\n}\n\n/**\n * Analyzes a where expression and creates indexes for all simple operations on single fields\n */\nexport function ensureIndexForExpression<\n T extends Record<string, any>,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionImpl<T, TKey, any, any, any>\n): void {\n if (!shouldAutoIndex(collection)) {\n return\n }\n\n // Extract all indexable expressions and create indexes for them\n const indexableExpressions = extractIndexableExpressions(expression)\n\n for (const { fieldName, fieldPath } of indexableExpressions) {\n ensureIndexForField(fieldName, fieldPath, collection)\n }\n}\n\n/**\n * Extracts all indexable expressions from a where expression\n */\nfunction extractIndexableExpressions(\n expression: BasicExpression\n): Array<{ fieldName: string; fieldPath: Array<string> }> {\n const results: Array<{ fieldName: string; fieldPath: Array<string> }> = []\n\n function extractFromExpression(expr: BasicExpression): void {\n if (expr.type !== `func`) {\n return\n }\n\n const func = expr as any\n\n // Handle 'and' expressions by recursively processing all arguments\n if (func.name === `and`) {\n for (const arg of func.args) {\n extractFromExpression(arg)\n }\n return\n }\n\n // Check if this is a supported operation\n const supportedOperations = [`eq`, `gt`, `gte`, `lt`, `lte`, `in`]\n if (!supportedOperations.includes(func.name)) {\n return\n }\n\n // Check if the first argument is a property reference\n if (func.args.length < 1 || func.args[0].type !== `ref`) {\n return\n }\n\n const fieldRef = func.args[0]\n const fieldPath = fieldRef.path\n\n // Skip if the path is empty\n if (fieldPath.length === 0) {\n return\n }\n\n // For nested paths, use the full path joined with underscores as the field name\n // For simple paths, use the first (and only) element\n const fieldName = fieldPath.join(`_`)\n results.push({ fieldName, fieldPath })\n }\n\n extractFromExpression(expression)\n return results\n}\n"],"names":[],"mappings":";;AAUA,SAAS,gBAAgB,YAAqD;AAE5E,MAAI,WAAW,OAAO,cAAc,SAAS;AAC3C,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,SAAS,oBAId,WACA,WACA,YACA,iBAAiC,yBACjC,WACA;AACA,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC;AAAA,EACF;AAGA,QAAM,gBAAgB,MAAM,KAAK,WAAW,QAAQ,OAAA,CAAQ,EAAE;AAAA,IAC5D,CAAC,UACC,MAAM,aAAa,SAAS,KAC5B,MAAM,sBAAsB,cAAc;AAAA,EAAA;AAG9C,MAAI,eAAe;AACjB;AAAA,EACF;AAGA,MAAI;AAEF,eAAW;AAAA,MACT,CAAC,QAAQ;AAEP,YAAI,UAAe;AACnB,mBAAW,QAAQ,WAAW;AAC5B,oBAAU,QAAQ,IAAI;AAAA,QACxB;AACA,eAAO;AAAA,MACT;AAAA,MACA;AAAA,QACE,MAAM,QAAQ,UAAU,KAAK,GAAG,CAAC;AAAA,QACjC,WAAW;AAAA,QACX,SAAS,YAAY,EAAE,WAAW,eAAA,IAAmB,CAAA;AAAA,MAAC;AAAA,IACxD;AAAA,EAEJ,SAAS,OAAO;AACd,YAAQ;AAAA,MACN,GAAG,WAAW,KAAK,IAAI,WAAW,EAAE,OAAO,EAAE,+CAA+C,UAAU,KAAK,GAAG,CAAC;AAAA,MAC/G;AAAA,IAAA;AAAA,EAEJ;AACF;AAKO,SAAS,yBAId,YACA,YACM;AACN,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC;AAAA,EACF;AAGA,QAAM,uBAAuB,4BAA4B,UAAU;AAEnE,aAAW,EAAE,WAAW,UAAA,KAAe,sBAAsB;AAC3D,wBAAoB,WAAW,WAAW,UAAU;AAAA,EACtD;AACF;AAKA,SAAS,4BACP,YACwD;AACxD,QAAM,UAAkE,CAAA;AAExE,WAAS,sBAAsB,MAA6B;AAC1D,QAAI,KAAK,SAAS,QAAQ;AACxB;AAAA,IACF;AAEA,UAAM,OAAO;AAGb,QAAI,KAAK,SAAS,OAAO;AACvB,iBAAW,OAAO,KAAK,MAAM;AAC3B,8BAAsB,GAAG;AAAA,MAC3B;AACA;AAAA,IACF;AAGA,UAAM,sBAAsB,CAAC,MAAM,MAAM,OAAO,MAAM,OAAO,IAAI;AACjE,QAAI,CAAC,oBAAoB,SAAS,KAAK,IAAI,GAAG;AAC5C;AAAA,IACF;AAGA,QAAI,KAAK,KAAK,SAAS,KAAK,KAAK,KAAK,CAAC,EAAE,SAAS,OAAO;AACvD;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,UAAM,YAAY,SAAS;AAG3B,QAAI,UAAU,WAAW,GAAG;AAC1B;AAAA,IACF;AAIA,UAAM,YAAY,UAAU,KAAK,GAAG;AACpC,YAAQ,KAAK,EAAE,WAAW,UAAA,CAAW;AAAA,EACvC;AAEA,wBAAsB,UAAU;AAChC,SAAO;AACT;"}
@@ -0,0 +1,81 @@
1
+ import { MutationFn, Transaction } from './types.js';
2
+ import { Strategy } from './strategies/types.js';
3
+ /**
4
+ * Configuration for creating a paced mutations manager
5
+ */
6
+ export interface PacedMutationsConfig<TVariables = unknown, T extends object = Record<string, unknown>> {
7
+ /**
8
+ * Callback to apply optimistic updates immediately.
9
+ * Receives the variables passed to the mutate function.
10
+ */
11
+ onMutate: (variables: TVariables) => void;
12
+ /**
13
+ * Function to execute the mutation on the server.
14
+ * Receives the transaction parameters containing all merged mutations.
15
+ */
16
+ mutationFn: MutationFn<T>;
17
+ /**
18
+ * Strategy for controlling mutation execution timing
19
+ * Examples: debounceStrategy, queueStrategy, throttleStrategy
20
+ */
21
+ strategy: Strategy;
22
+ /**
23
+ * Custom metadata to associate with transactions
24
+ */
25
+ metadata?: Record<string, unknown>;
26
+ }
27
+ /**
28
+ * Creates a paced mutations manager with pluggable timing strategies.
29
+ *
30
+ * This function provides a way to control when and how optimistic mutations
31
+ * are persisted to the backend, using strategies like debouncing, queuing,
32
+ * or throttling. The optimistic updates are applied immediately via `onMutate`,
33
+ * and the actual persistence is controlled by the strategy.
34
+ *
35
+ * The returned function accepts variables of type TVariables and returns a
36
+ * Transaction object that can be awaited to know when persistence completes
37
+ * or to handle errors.
38
+ *
39
+ * @param config - Configuration including onMutate, mutationFn and strategy
40
+ * @returns A function that accepts variables and returns a Transaction
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * // Debounced mutations for auto-save
45
+ * const updateTodo = createPacedMutations<string>({
46
+ * onMutate: (text) => {
47
+ * // Apply optimistic update immediately
48
+ * collection.update(id, draft => { draft.text = text })
49
+ * },
50
+ * mutationFn: async (text, { transaction }) => {
51
+ * await api.save(transaction.mutations)
52
+ * },
53
+ * strategy: debounceStrategy({ wait: 500 })
54
+ * })
55
+ *
56
+ * // Call with variables, returns a transaction
57
+ * const tx = updateTodo('New text')
58
+ *
59
+ * // Await persistence or handle errors
60
+ * await tx.isPersisted.promise
61
+ * ```
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * // Queue strategy for sequential processing
66
+ * const addTodo = createPacedMutations<{ text: string }>({
67
+ * onMutate: ({ text }) => {
68
+ * collection.insert({ id: uuid(), text, completed: false })
69
+ * },
70
+ * mutationFn: async ({ text }, { transaction }) => {
71
+ * await api.save(transaction.mutations)
72
+ * },
73
+ * strategy: queueStrategy({
74
+ * wait: 200,
75
+ * addItemsTo: 'back',
76
+ * getItemsFrom: 'front'
77
+ * })
78
+ * })
79
+ * ```
80
+ */
81
+ export declare function createPacedMutations<TVariables = unknown, T extends object = Record<string, unknown>>(config: PacedMutationsConfig<TVariables, T>): (variables: TVariables) => Transaction<T>;
@@ -0,0 +1,52 @@
1
+ import { createTransaction } from "./transactions.js";
2
+ function createPacedMutations(config) {
3
+ const { onMutate, mutationFn, strategy, ...transactionConfig } = config;
4
+ let activeTransaction = null;
5
+ const commitCallback = () => {
6
+ if (!activeTransaction) {
7
+ throw new Error(
8
+ `Strategy callback called but no active transaction exists. This indicates a bug in the strategy implementation.`
9
+ );
10
+ }
11
+ if (activeTransaction.state !== `pending`) {
12
+ throw new Error(
13
+ `Strategy callback called but active transaction is in state "${activeTransaction.state}". Expected "pending".`
14
+ );
15
+ }
16
+ const txToCommit = activeTransaction;
17
+ activeTransaction = null;
18
+ txToCommit.commit().catch(() => {
19
+ });
20
+ return txToCommit;
21
+ };
22
+ function mutate(variables) {
23
+ if (!activeTransaction || activeTransaction.state !== `pending`) {
24
+ activeTransaction = createTransaction({
25
+ ...transactionConfig,
26
+ mutationFn,
27
+ autoCommit: false
28
+ });
29
+ }
30
+ activeTransaction.mutate(() => {
31
+ onMutate(variables);
32
+ });
33
+ const txToReturn = activeTransaction;
34
+ if (strategy._type === `queue`) {
35
+ const capturedTx = activeTransaction;
36
+ activeTransaction = null;
37
+ strategy.execute(() => {
38
+ capturedTx.commit().catch(() => {
39
+ });
40
+ return capturedTx;
41
+ });
42
+ } else {
43
+ strategy.execute(commitCallback);
44
+ }
45
+ return txToReturn;
46
+ }
47
+ return mutate;
48
+ }
49
+ export {
50
+ createPacedMutations
51
+ };
52
+ //# sourceMappingURL=paced-mutations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paced-mutations.js","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 (text, { 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 ({ text }, { 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":[],"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,0BAAoB,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;"}
@@ -73,6 +73,14 @@ function applySingleLevelOptimization(query) {
73
73
  return query;
74
74
  }
75
75
  if (!query.join || query.join.length === 0) {
76
+ if (query.where.length > 1) {
77
+ const splitWhereClauses2 = splitAndClauses(query.where);
78
+ const combinedWhere = combineWithAnd(splitWhereClauses2);
79
+ return {
80
+ ...query,
81
+ where: [combinedWhere]
82
+ };
83
+ }
76
84
  return query;
77
85
  }
78
86
  const nonResidualWhereClauses = query.where.filter(
@@ -232,6 +240,13 @@ function applyOptimizations(query, groupedClauses) {
232
240
  remainingWhereClauses.push(createResidualWhere(clause));
233
241
  }
234
242
  }
243
+ const finalWhere = remainingWhereClauses.length > 1 ? [
244
+ combineWithAnd(
245
+ remainingWhereClauses.flatMap(
246
+ (clause) => splitAndClausesRecursive(getWhereExpression(clause))
247
+ )
248
+ )
249
+ ] : remainingWhereClauses;
235
250
  const optimizedQuery = {
236
251
  // Copy all non-optimized fields as-is
237
252
  select: query.select,
@@ -247,8 +262,8 @@ function applyOptimizations(query, groupedClauses) {
247
262
  // Use the optimized FROM and JOIN clauses
248
263
  from: optimizedFrom,
249
264
  join: optimizedJoins,
250
- // Only include WHERE clauses that weren't successfully optimized
251
- where: remainingWhereClauses.length > 0 ? remainingWhereClauses : []
265
+ // Include combined WHERE clauses
266
+ where: finalWhere.length > 0 ? finalWhere : []
252
267
  };
253
268
  return optimizedQuery;
254
269
  }