@tanstack/db 0.5.10 → 0.5.12

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 (224) hide show
  1. package/dist/cjs/SortedMap.cjs +40 -26
  2. package/dist/cjs/SortedMap.cjs.map +1 -1
  3. package/dist/cjs/SortedMap.d.cts +10 -15
  4. package/dist/cjs/collection/change-events.cjs +1 -1
  5. package/dist/cjs/collection/change-events.cjs.map +1 -1
  6. package/dist/cjs/collection/changes.cjs.map +1 -1
  7. package/dist/cjs/collection/events.cjs.map +1 -1
  8. package/dist/cjs/collection/events.d.cts +12 -4
  9. package/dist/cjs/collection/index.cjs +2 -1
  10. package/dist/cjs/collection/index.cjs.map +1 -1
  11. package/dist/cjs/collection/indexes.cjs.map +1 -1
  12. package/dist/cjs/collection/lifecycle.cjs.map +1 -1
  13. package/dist/cjs/collection/mutations.cjs +5 -2
  14. package/dist/cjs/collection/mutations.cjs.map +1 -1
  15. package/dist/cjs/collection/state.cjs +6 -5
  16. package/dist/cjs/collection/state.cjs.map +1 -1
  17. package/dist/cjs/collection/state.d.cts +4 -1
  18. package/dist/cjs/collection/subscription.cjs +60 -53
  19. package/dist/cjs/collection/subscription.cjs.map +1 -1
  20. package/dist/cjs/collection/subscription.d.cts +18 -4
  21. package/dist/cjs/collection/sync.cjs.map +1 -1
  22. package/dist/cjs/errors.cjs +9 -0
  23. package/dist/cjs/errors.cjs.map +1 -1
  24. package/dist/cjs/errors.d.cts +3 -0
  25. package/dist/cjs/event-emitter.cjs.map +1 -1
  26. package/dist/cjs/index.cjs +4 -0
  27. package/dist/cjs/index.cjs.map +1 -1
  28. package/dist/cjs/index.d.cts +2 -1
  29. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  30. package/dist/cjs/indexes/base-index.cjs.map +1 -1
  31. package/dist/cjs/indexes/btree-index.cjs +8 -6
  32. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  33. package/dist/cjs/indexes/lazy-index.cjs.map +1 -1
  34. package/dist/cjs/indexes/reverse-index.cjs.map +1 -1
  35. package/dist/cjs/local-only.cjs.map +1 -1
  36. package/dist/cjs/local-storage.cjs.map +1 -1
  37. package/dist/cjs/optimistic-action.cjs.map +1 -1
  38. package/dist/cjs/paced-mutations.cjs.map +1 -1
  39. package/dist/cjs/proxy.cjs.map +1 -1
  40. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  41. package/dist/cjs/query/builder/index.cjs.map +1 -1
  42. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  43. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  44. package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
  45. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  46. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  47. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  48. package/dist/cjs/query/compiler/order-by.cjs +91 -38
  49. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  50. package/dist/cjs/query/compiler/order-by.d.cts +6 -2
  51. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  52. package/dist/cjs/query/expression-helpers.cjs.map +1 -1
  53. package/dist/cjs/query/index.d.cts +1 -1
  54. package/dist/cjs/query/ir.cjs.map +1 -1
  55. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  56. package/dist/cjs/query/live/collection-registry.cjs.map +1 -1
  57. package/dist/cjs/query/live/collection-subscriber.cjs +30 -15
  58. package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
  59. package/dist/cjs/query/live/internal.cjs.map +1 -1
  60. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  61. package/dist/cjs/query/optimizer.cjs.map +1 -1
  62. package/dist/cjs/query/predicate-utils.cjs +19 -2
  63. package/dist/cjs/query/predicate-utils.cjs.map +1 -1
  64. package/dist/cjs/query/predicate-utils.d.cts +32 -1
  65. package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
  66. package/dist/cjs/scheduler.cjs.map +1 -1
  67. package/dist/cjs/strategies/debounceStrategy.cjs.map +1 -1
  68. package/dist/cjs/strategies/queueStrategy.cjs.map +1 -1
  69. package/dist/cjs/strategies/throttleStrategy.cjs.map +1 -1
  70. package/dist/cjs/transactions.cjs.map +1 -1
  71. package/dist/cjs/types.d.cts +43 -5
  72. package/dist/cjs/utils/browser-polyfills.cjs.map +1 -1
  73. package/dist/cjs/utils/btree.cjs.map +1 -1
  74. package/dist/cjs/utils/comparison.cjs.map +1 -1
  75. package/dist/cjs/utils/cursor.cjs +39 -0
  76. package/dist/cjs/utils/cursor.cjs.map +1 -0
  77. package/dist/cjs/utils/cursor.d.cts +18 -0
  78. package/dist/cjs/utils/index-optimization.cjs.map +1 -1
  79. package/dist/cjs/utils.cjs.map +1 -1
  80. package/dist/esm/SortedMap.d.ts +10 -15
  81. package/dist/esm/SortedMap.js +40 -26
  82. package/dist/esm/SortedMap.js.map +1 -1
  83. package/dist/esm/collection/change-events.js +1 -1
  84. package/dist/esm/collection/change-events.js.map +1 -1
  85. package/dist/esm/collection/changes.js.map +1 -1
  86. package/dist/esm/collection/events.d.ts +12 -4
  87. package/dist/esm/collection/events.js.map +1 -1
  88. package/dist/esm/collection/index.js +2 -1
  89. package/dist/esm/collection/index.js.map +1 -1
  90. package/dist/esm/collection/indexes.js.map +1 -1
  91. package/dist/esm/collection/lifecycle.js.map +1 -1
  92. package/dist/esm/collection/mutations.js +6 -3
  93. package/dist/esm/collection/mutations.js.map +1 -1
  94. package/dist/esm/collection/state.d.ts +4 -1
  95. package/dist/esm/collection/state.js +6 -5
  96. package/dist/esm/collection/state.js.map +1 -1
  97. package/dist/esm/collection/subscription.d.ts +18 -4
  98. package/dist/esm/collection/subscription.js +61 -54
  99. package/dist/esm/collection/subscription.js.map +1 -1
  100. package/dist/esm/collection/sync.js.map +1 -1
  101. package/dist/esm/errors.d.ts +3 -0
  102. package/dist/esm/errors.js +9 -0
  103. package/dist/esm/errors.js.map +1 -1
  104. package/dist/esm/event-emitter.js.map +1 -1
  105. package/dist/esm/index.d.ts +2 -1
  106. package/dist/esm/index.js +6 -2
  107. package/dist/esm/index.js.map +1 -1
  108. package/dist/esm/indexes/auto-index.js.map +1 -1
  109. package/dist/esm/indexes/base-index.js.map +1 -1
  110. package/dist/esm/indexes/btree-index.js +8 -6
  111. package/dist/esm/indexes/btree-index.js.map +1 -1
  112. package/dist/esm/indexes/lazy-index.js.map +1 -1
  113. package/dist/esm/indexes/reverse-index.js.map +1 -1
  114. package/dist/esm/local-only.js.map +1 -1
  115. package/dist/esm/local-storage.js.map +1 -1
  116. package/dist/esm/optimistic-action.js.map +1 -1
  117. package/dist/esm/paced-mutations.js.map +1 -1
  118. package/dist/esm/proxy.js.map +1 -1
  119. package/dist/esm/query/builder/functions.js.map +1 -1
  120. package/dist/esm/query/builder/index.js.map +1 -1
  121. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  122. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  123. package/dist/esm/query/compiler/expressions.js.map +1 -1
  124. package/dist/esm/query/compiler/group-by.js.map +1 -1
  125. package/dist/esm/query/compiler/index.js.map +1 -1
  126. package/dist/esm/query/compiler/joins.js.map +1 -1
  127. package/dist/esm/query/compiler/order-by.d.ts +6 -2
  128. package/dist/esm/query/compiler/order-by.js +91 -38
  129. package/dist/esm/query/compiler/order-by.js.map +1 -1
  130. package/dist/esm/query/compiler/select.js.map +1 -1
  131. package/dist/esm/query/expression-helpers.js.map +1 -1
  132. package/dist/esm/query/index.d.ts +1 -1
  133. package/dist/esm/query/ir.js.map +1 -1
  134. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  135. package/dist/esm/query/live/collection-registry.js.map +1 -1
  136. package/dist/esm/query/live/collection-subscriber.js +30 -15
  137. package/dist/esm/query/live/collection-subscriber.js.map +1 -1
  138. package/dist/esm/query/live/internal.js.map +1 -1
  139. package/dist/esm/query/live-query-collection.js.map +1 -1
  140. package/dist/esm/query/optimizer.js.map +1 -1
  141. package/dist/esm/query/predicate-utils.d.ts +32 -1
  142. package/dist/esm/query/predicate-utils.js +19 -2
  143. package/dist/esm/query/predicate-utils.js.map +1 -1
  144. package/dist/esm/query/subset-dedupe.js.map +1 -1
  145. package/dist/esm/scheduler.js.map +1 -1
  146. package/dist/esm/strategies/debounceStrategy.js.map +1 -1
  147. package/dist/esm/strategies/queueStrategy.js.map +1 -1
  148. package/dist/esm/strategies/throttleStrategy.js.map +1 -1
  149. package/dist/esm/transactions.js.map +1 -1
  150. package/dist/esm/types.d.ts +43 -5
  151. package/dist/esm/utils/browser-polyfills.js.map +1 -1
  152. package/dist/esm/utils/btree.js.map +1 -1
  153. package/dist/esm/utils/comparison.js.map +1 -1
  154. package/dist/esm/utils/cursor.d.ts +18 -0
  155. package/dist/esm/utils/cursor.js +39 -0
  156. package/dist/esm/utils/cursor.js.map +1 -0
  157. package/dist/esm/utils/index-optimization.js.map +1 -1
  158. package/dist/esm/utils.js.map +1 -1
  159. package/package.json +30 -28
  160. package/src/SortedMap.ts +50 -31
  161. package/src/collection/change-events.ts +23 -21
  162. package/src/collection/changes.ts +12 -12
  163. package/src/collection/events.ts +20 -10
  164. package/src/collection/index.ts +47 -46
  165. package/src/collection/indexes.ts +14 -14
  166. package/src/collection/lifecycle.ts +16 -16
  167. package/src/collection/mutations.ts +25 -20
  168. package/src/collection/state.ts +43 -36
  169. package/src/collection/subscription.ts +114 -83
  170. package/src/collection/sync.ts +13 -13
  171. package/src/duplicate-instance-check.ts +1 -1
  172. package/src/errors.ts +49 -40
  173. package/src/event-emitter.ts +5 -5
  174. package/src/index.ts +21 -20
  175. package/src/indexes/auto-index.ts +11 -11
  176. package/src/indexes/base-index.ts +13 -13
  177. package/src/indexes/btree-index.ts +21 -17
  178. package/src/indexes/index-options.ts +3 -3
  179. package/src/indexes/lazy-index.ts +8 -8
  180. package/src/indexes/reverse-index.ts +5 -5
  181. package/src/local-only.ts +12 -12
  182. package/src/local-storage.ts +17 -17
  183. package/src/optimistic-action.ts +5 -5
  184. package/src/paced-mutations.ts +6 -6
  185. package/src/proxy.ts +43 -43
  186. package/src/query/builder/functions.ts +28 -28
  187. package/src/query/builder/index.ts +22 -22
  188. package/src/query/builder/ref-proxy.ts +4 -4
  189. package/src/query/builder/types.ts +8 -8
  190. package/src/query/compiler/evaluators.ts +9 -9
  191. package/src/query/compiler/expressions.ts +6 -6
  192. package/src/query/compiler/group-by.ts +24 -24
  193. package/src/query/compiler/index.ts +44 -44
  194. package/src/query/compiler/joins.ts +37 -37
  195. package/src/query/compiler/order-by.ts +170 -77
  196. package/src/query/compiler/select.ts +13 -13
  197. package/src/query/compiler/types.ts +2 -2
  198. package/src/query/expression-helpers.ts +16 -16
  199. package/src/query/index.ts +10 -9
  200. package/src/query/ir.ts +13 -13
  201. package/src/query/live/collection-config-builder.ts +53 -53
  202. package/src/query/live/collection-registry.ts +6 -6
  203. package/src/query/live/collection-subscriber.ts +87 -48
  204. package/src/query/live/internal.ts +1 -1
  205. package/src/query/live/types.ts +4 -4
  206. package/src/query/live-query-collection.ts +15 -15
  207. package/src/query/optimizer.ts +29 -29
  208. package/src/query/predicate-utils.ts +105 -50
  209. package/src/query/subset-dedupe.ts +6 -6
  210. package/src/scheduler.ts +3 -3
  211. package/src/strategies/debounceStrategy.ts +6 -6
  212. package/src/strategies/index.ts +4 -4
  213. package/src/strategies/queueStrategy.ts +5 -5
  214. package/src/strategies/throttleStrategy.ts +6 -6
  215. package/src/strategies/types.ts +2 -2
  216. package/src/transactions.ts +9 -9
  217. package/src/types.ts +51 -12
  218. package/src/utils/array-utils.ts +1 -1
  219. package/src/utils/browser-polyfills.ts +2 -2
  220. package/src/utils/btree.ts +22 -22
  221. package/src/utils/comparison.ts +3 -3
  222. package/src/utils/cursor.ts +78 -0
  223. package/src/utils/index-optimization.ts +14 -14
  224. package/src/utils.ts +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"predicate-utils.js","sources":["../../../src/query/predicate-utils.ts"],"sourcesContent":["import { Func, Value } from \"./ir.js\"\nimport type { BasicExpression, OrderBy, PropRef } from \"./ir.js\"\nimport type { LoadSubsetOptions } from \"../types.js\"\n\n/**\n * Check if one where clause is a logical subset of another.\n * Returns true if the subset predicate is more restrictive than (or equal to) the superset predicate.\n *\n * @example\n * // age > 20 is subset of age > 10 (more restrictive)\n * isWhereSubset(gt(ref('age'), val(20)), gt(ref('age'), val(10))) // true\n *\n * @example\n * // age > 10 AND name = 'X' is subset of age > 10 (more conditions)\n * isWhereSubset(and(gt(ref('age'), val(10)), eq(ref('name'), val('X'))), gt(ref('age'), val(10))) // true\n *\n * @param subset - The potentially more restrictive predicate\n * @param superset - The potentially less restrictive predicate\n * @returns true if subset logically implies superset\n */\nexport function isWhereSubset(\n subset: BasicExpression<boolean> | undefined,\n superset: BasicExpression<boolean> | undefined\n): boolean {\n // undefined/missing where clause means \"no filter\" (all data)\n // Both undefined means subset relationship holds (all data ⊆ all data)\n if (subset === undefined && superset === undefined) {\n return true\n }\n\n // If subset is undefined but superset is not, we're requesting ALL data\n // but have only loaded SOME data - subset relationship does NOT hold\n if (subset === undefined && superset !== undefined) {\n return false\n }\n\n // If superset is undefined (no filter = all data loaded),\n // then any constrained subset is contained\n if (superset === undefined && subset !== undefined) {\n return true\n }\n\n return isWhereSubsetInternal(subset!, superset!)\n}\n\nfunction makeDisjunction(\n preds: Array<BasicExpression<boolean>>\n): BasicExpression<boolean> {\n if (preds.length === 0) {\n return new Value(false)\n }\n if (preds.length === 1) {\n return preds[0]!\n }\n return new Func(`or`, preds)\n}\n\nfunction convertInToOr(inField: InField) {\n const equalities = inField.values.map(\n (value) => new Func(`eq`, [inField.ref, new Value(value)])\n )\n return makeDisjunction(equalities)\n}\n\nfunction isWhereSubsetInternal(\n subset: BasicExpression<boolean>,\n superset: BasicExpression<boolean>\n): boolean {\n // If subset is false it is requesting no data,\n // thus the result set is empty\n // and the empty set is a subset of any set\n if (subset.type === `val` && subset.value === false) {\n return true\n }\n\n // If expressions are structurally equal, subset relationship holds\n if (areExpressionsEqual(subset, superset)) {\n return true\n }\n\n // Handle superset being an AND: subset must imply ALL conjuncts\n // If superset is (A AND B), then subset ⊆ (A AND B) only if subset ⊆ A AND subset ⊆ B\n // Example: (age > 20) ⊆ (age > 10 AND status = 'active') is false (doesn't imply status condition)\n if (superset.type === `func` && superset.name === `and`) {\n return superset.args.every((arg) =>\n isWhereSubsetInternal(subset, arg as BasicExpression<boolean>)\n )\n }\n\n // Handle subset being an AND: (A AND B) implies both A and B\n if (subset.type === `func` && subset.name === `and`) {\n // For (A AND B) ⊆ C, since (A AND B) implies A, we check if any conjunct implies C\n return subset.args.some((arg) =>\n isWhereSubsetInternal(arg as BasicExpression<boolean>, superset)\n )\n }\n\n // Turn x IN [A, B, C] into x = A OR x = B OR x = C\n // for unified handling of IN and OR\n if (subset.type === `func` && subset.name === `in`) {\n const inField = extractInField(subset)\n if (inField) {\n return isWhereSubsetInternal(convertInToOr(inField), superset)\n }\n }\n\n if (superset.type === `func` && superset.name === `in`) {\n const inField = extractInField(superset)\n if (inField) {\n return isWhereSubsetInternal(subset, convertInToOr(inField))\n }\n }\n\n // Handle OR in subset: (A OR B) is subset of C only if both A and B are subsets of C\n if (subset.type === `func` && subset.name === `or`) {\n return subset.args.every((arg) =>\n isWhereSubsetInternal(arg as BasicExpression<boolean>, superset)\n )\n }\n\n // Handle OR in superset: subset ⊆ (A OR B) if subset ⊆ A or subset ⊆ B\n // (A OR B) as superset means data can satisfy A or B\n // If subset is contained in any disjunct, it's contained in the union\n if (superset.type === `func` && superset.name === `or`) {\n return superset.args.some((arg) =>\n isWhereSubsetInternal(subset, arg as BasicExpression<boolean>)\n )\n }\n\n // Handle comparison operators on the same field\n if (subset.type === `func` && superset.type === `func`) {\n const subsetFunc = subset as Func\n const supersetFunc = superset as Func\n\n // Check if both are comparisons on the same field\n const subsetField = extractComparisonField(subsetFunc)\n const supersetField = extractComparisonField(supersetFunc)\n\n if (\n subsetField &&\n supersetField &&\n areRefsEqual(subsetField.ref, supersetField.ref)\n ) {\n return isComparisonSubset(\n subsetFunc,\n subsetField.value,\n supersetFunc,\n supersetField.value\n )\n }\n\n /*\n // Handle eq vs in\n if (subsetFunc.name === `eq` && supersetFunc.name === `in`) {\n const subsetFieldEq = extractEqualityField(subsetFunc)\n const supersetFieldIn = extractInField(supersetFunc)\n if (\n subsetFieldEq &&\n supersetFieldIn &&\n areRefsEqual(subsetFieldEq.ref, supersetFieldIn.ref)\n ) {\n // field = X is subset of field IN [X, Y, Z] if X is in the array\n // Use cached primitive set and metadata from extraction\n return arrayIncludesWithSet(\n supersetFieldIn.values,\n subsetFieldEq.value,\n supersetFieldIn.primitiveSet ?? null,\n supersetFieldIn.areAllPrimitives\n )\n }\n }\n\n // Handle in vs in\n if (subsetFunc.name === `in` && supersetFunc.name === `in`) {\n const subsetFieldIn = extractInField(subsetFunc)\n const supersetFieldIn = extractInField(supersetFunc)\n if (\n subsetFieldIn &&\n supersetFieldIn &&\n areRefsEqual(subsetFieldIn.ref, supersetFieldIn.ref)\n ) {\n // field IN [A, B] is subset of field IN [A, B, C] if all values in subset are in superset\n // Use cached primitive set and metadata from extraction\n return subsetFieldIn.values.every((subVal) =>\n arrayIncludesWithSet(\n supersetFieldIn.values,\n subVal,\n supersetFieldIn.primitiveSet ?? null,\n supersetFieldIn.areAllPrimitives\n )\n )\n }\n }\n */\n }\n\n // Conservative: if we can't determine, return false\n return false\n}\n\n/**\n * Helper to combine where predicates with common logic for AND/OR operations\n */\nfunction combineWherePredicates(\n predicates: Array<BasicExpression<boolean>>,\n operation: `and` | `or`,\n simplifyFn: (\n preds: Array<BasicExpression<boolean>>\n ) => BasicExpression<boolean> | null\n): BasicExpression<boolean> {\n const emptyValue = operation === `and` ? true : false\n const identityValue = operation === `and` ? true : false\n\n if (predicates.length === 0) {\n return { type: `val`, value: emptyValue } as BasicExpression<boolean>\n }\n\n if (predicates.length === 1) {\n return predicates[0]!\n }\n\n // Flatten nested expressions of the same operation\n const flatPredicates: Array<BasicExpression<boolean>> = []\n for (const pred of predicates) {\n if (pred.type === `func` && pred.name === operation) {\n flatPredicates.push(...pred.args)\n } else {\n flatPredicates.push(pred)\n }\n }\n\n // Group predicates by field for simplification\n const grouped = groupPredicatesByField(flatPredicates)\n\n // Simplify each group\n const simplified: Array<BasicExpression<boolean>> = []\n for (const [field, preds] of grouped.entries()) {\n if (field === null) {\n // Complex predicates that we can't group by field\n simplified.push(...preds)\n } else {\n // Try to simplify same-field predicates\n const result = simplifyFn(preds)\n\n // For intersection: check for empty set (contradiction)\n if (\n operation === `and` &&\n result &&\n result.type === `val` &&\n result.value === false\n ) {\n // Intersection is empty (conflicting constraints) - entire AND is false\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // For union: result may be null if simplification failed\n if (result) {\n simplified.push(result)\n }\n }\n }\n\n if (simplified.length === 0) {\n return { type: `val`, value: identityValue } as BasicExpression<boolean>\n }\n\n if (simplified.length === 1) {\n return simplified[0]!\n }\n\n // Return combined predicate\n return {\n type: `func`,\n name: operation,\n args: simplified,\n } as BasicExpression<boolean>\n}\n\n/**\n * Combine multiple where predicates with OR logic (union).\n * Returns a predicate that is satisfied when any input predicate is satisfied.\n * Simplifies when possible (e.g., age > 10 OR age > 20 → age > 10).\n *\n * @example\n * // Take least restrictive\n * unionWherePredicates([gt(ref('age'), val(10)), gt(ref('age'), val(20))]) // age > 10\n *\n * @example\n * // Combine equals into IN\n * unionWherePredicates([eq(ref('age'), val(5)), eq(ref('age'), val(10))]) // age IN [5, 10]\n *\n * @param predicates - Array of where predicates to union\n * @returns Combined predicate representing the union\n */\nexport function unionWherePredicates(\n predicates: Array<BasicExpression<boolean>>\n): BasicExpression<boolean> {\n return combineWherePredicates(predicates, `or`, unionSameFieldPredicates)\n}\n\n/**\n * Compute the difference between two where predicates: `fromPredicate AND NOT(subtractPredicate)`.\n * Returns the simplified predicate, or null if the difference cannot be simplified\n * (in which case the caller should fetch the full fromPredicate).\n *\n * @example\n * // Range difference\n * minusWherePredicates(\n * gt(ref('age'), val(10)), // age > 10\n * gt(ref('age'), val(20)) // age > 20\n * ) // → age > 10 AND age <= 20\n *\n * @example\n * // Set difference\n * minusWherePredicates(\n * inOp(ref('status'), ['A', 'B', 'C', 'D']), // status IN ['A','B','C','D']\n * inOp(ref('status'), ['B', 'C']) // status IN ['B','C']\n * ) // → status IN ['A', 'D']\n *\n * @example\n * // Common conditions\n * minusWherePredicates(\n * and(gt(ref('age'), val(10)), eq(ref('status'), val('active'))), // age > 10 AND status = 'active'\n * and(gt(ref('age'), val(20)), eq(ref('status'), val('active'))) // age > 20 AND status = 'active'\n * ) // → age > 10 AND age <= 20 AND status = 'active'\n *\n * @example\n * // Complete overlap - empty result\n * minusWherePredicates(\n * gt(ref('age'), val(20)), // age > 20\n * gt(ref('age'), val(10)) // age > 10\n * ) // → {type: 'val', value: false} (empty set)\n *\n * @param fromPredicate - The predicate to subtract from\n * @param subtractPredicate - The predicate to subtract\n * @returns The simplified difference, or null if cannot be simplified\n */\nexport function minusWherePredicates(\n fromPredicate: BasicExpression<boolean> | undefined,\n subtractPredicate: BasicExpression<boolean> | undefined\n): BasicExpression<boolean> | null {\n // If nothing to subtract, return the original\n if (subtractPredicate === undefined) {\n return (\n fromPredicate ??\n ({ type: `val`, value: true } as BasicExpression<boolean>)\n )\n }\n\n // If from is undefined then we are asking for all data\n // so we need to load all data minus what we already loaded\n // i.e. we need to load NOT(subtractPredicate)\n if (fromPredicate === undefined) {\n return {\n type: `func`,\n name: `not`,\n args: [subtractPredicate],\n } as BasicExpression<boolean>\n }\n\n // Check if fromPredicate is entirely contained in subtractPredicate\n // In that case, fromPredicate AND NOT(subtractPredicate) = empty set\n if (isWhereSubset(fromPredicate, subtractPredicate)) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // Try to detect and handle common conditions\n const commonConditions = findCommonConditions(\n fromPredicate,\n subtractPredicate\n )\n if (commonConditions.length > 0) {\n // Extract predicates without common conditions\n const fromWithoutCommon = removeConditions(fromPredicate, commonConditions)\n const subtractWithoutCommon = removeConditions(\n subtractPredicate,\n commonConditions\n )\n\n // Recursively compute difference on simplified predicates\n const simplifiedDifference = minusWherePredicates(\n fromWithoutCommon,\n subtractWithoutCommon\n )\n\n if (simplifiedDifference !== null) {\n // Combine the simplified difference with common conditions\n return combineConditions([...commonConditions, simplifiedDifference])\n }\n }\n\n // Check if they are on the same field - if so, we can try to simplify\n if (fromPredicate.type === `func` && subtractPredicate.type === `func`) {\n const result = minusSameFieldPredicates(fromPredicate, subtractPredicate)\n if (result !== null) {\n return result\n }\n }\n\n // Can't simplify - return null to indicate caller should fetch full fromPredicate\n return null\n}\n\n/**\n * Helper function to compute difference for same-field predicates\n */\nfunction minusSameFieldPredicates(\n fromPred: Func,\n subtractPred: Func\n): BasicExpression<boolean> | null {\n // Extract field information\n const fromField =\n extractComparisonField(fromPred) ||\n extractEqualityField(fromPred) ||\n extractInField(fromPred)\n const subtractField =\n extractComparisonField(subtractPred) ||\n extractEqualityField(subtractPred) ||\n extractInField(subtractPred)\n\n // Must be on the same field\n if (\n !fromField ||\n !subtractField ||\n !areRefsEqual(fromField.ref, subtractField.ref)\n ) {\n return null\n }\n\n // Handle IN minus IN: status IN [A,B,C,D] - status IN [B,C] = status IN [A,D]\n if (fromPred.name === `in` && subtractPred.name === `in`) {\n const fromInField = fromField as InField\n const subtractInField = subtractField as InField\n\n // Filter out values that are in the subtract set\n const remainingValues = fromInField.values.filter(\n (v) =>\n !arrayIncludesWithSet(\n subtractInField.values,\n v,\n subtractInField.primitiveSet ?? null,\n subtractInField.areAllPrimitives\n )\n )\n\n if (remainingValues.length === 0) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n if (remainingValues.length === 1) {\n return {\n type: `func`,\n name: `eq`,\n args: [fromField.ref, { type: `val`, value: remainingValues[0] }],\n } as BasicExpression<boolean>\n }\n\n return {\n type: `func`,\n name: `in`,\n args: [fromField.ref, { type: `val`, value: remainingValues }],\n } as BasicExpression<boolean>\n }\n\n // Handle IN minus equality: status IN [A,B,C] - status = B = status IN [A,C]\n if (fromPred.name === `in` && subtractPred.name === `eq`) {\n const fromInField = fromField as InField\n const subtractValue = (subtractField as { ref: PropRef; value: any }).value\n\n const remainingValues = fromInField.values.filter(\n (v) => !areValuesEqual(v, subtractValue)\n )\n\n if (remainingValues.length === 0) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n if (remainingValues.length === 1) {\n return {\n type: `func`,\n name: `eq`,\n args: [fromField.ref, { type: `val`, value: remainingValues[0] }],\n } as BasicExpression<boolean>\n }\n\n return {\n type: `func`,\n name: `in`,\n args: [fromField.ref, { type: `val`, value: remainingValues }],\n } as BasicExpression<boolean>\n }\n\n // Handle equality minus equality: age = 15 - age = 15 = empty, age = 15 - age = 20 = age = 15\n if (fromPred.name === `eq` && subtractPred.name === `eq`) {\n const fromValue = (fromField as { ref: PropRef; value: any }).value\n const subtractValue = (subtractField as { ref: PropRef; value: any }).value\n\n if (areValuesEqual(fromValue, subtractValue)) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // No overlap - return original\n return fromPred as BasicExpression<boolean>\n }\n\n // Handle range minus range: age > 10 - age > 20 = age > 10 AND age <= 20\n const fromComp = extractComparisonField(fromPred)\n const subtractComp = extractComparisonField(subtractPred)\n\n if (\n fromComp &&\n subtractComp &&\n areRefsEqual(fromComp.ref, subtractComp.ref)\n ) {\n // Try to compute the difference using range logic\n const result = minusRangePredicates(\n fromPred,\n fromComp.value,\n subtractPred,\n subtractComp.value\n )\n return result\n }\n\n // Can't simplify\n return null\n}\n\n/**\n * Helper to compute difference between range predicates\n */\nfunction minusRangePredicates(\n fromFunc: Func,\n fromValue: any,\n subtractFunc: Func,\n subtractValue: any\n): BasicExpression<boolean> | null {\n const fromOp = fromFunc.name as `gt` | `gte` | `lt` | `lte` | `eq`\n const subtractOp = subtractFunc.name as `gt` | `gte` | `lt` | `lte` | `eq`\n const ref = (extractComparisonField(fromFunc) ||\n extractEqualityField(fromFunc))!.ref\n\n // age > 10 - age > 20 = (age > 10 AND age <= 20)\n if (fromOp === `gt` && subtractOp === `gt`) {\n if (fromValue < subtractValue) {\n // Result is: fromValue < field <= subtractValue\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n // fromValue >= subtractValue means no overlap\n return fromFunc as BasicExpression<boolean>\n }\n\n // age >= 10 - age >= 20 = (age >= 10 AND age < 20)\n if (fromOp === `gte` && subtractOp === `gte`) {\n if (fromValue < subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age > 10 - age >= 20 = (age > 10 AND age < 20)\n if (fromOp === `gt` && subtractOp === `gte`) {\n if (fromValue < subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age >= 10 - age > 20 = (age >= 10 AND age <= 20)\n if (fromOp === `gte` && subtractOp === `gt`) {\n if (fromValue <= subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age < 30 - age < 20 = (age >= 20 AND age < 30)\n if (fromOp === `lt` && subtractOp === `lt`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age <= 30 - age <= 20 = (age > 20 AND age <= 30)\n if (fromOp === `lte` && subtractOp === `lte`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age < 30 - age <= 20 = (age > 20 AND age < 30)\n if (fromOp === `lt` && subtractOp === `lte`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age <= 30 - age < 20 = (age >= 20 AND age <= 30)\n if (fromOp === `lte` && subtractOp === `lt`) {\n if (fromValue >= subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // Can't simplify other combinations\n return null\n}\n\n/**\n * Check if one orderBy clause is a subset of another.\n * Returns true if the subset ordering requirements are satisfied by the superset ordering.\n *\n * @example\n * // Subset is prefix of superset\n * isOrderBySubset([{expr: age, asc}], [{expr: age, asc}, {expr: name, desc}]) // true\n *\n * @param subset - The ordering requirements to check\n * @param superset - The ordering that might satisfy the requirements\n * @returns true if subset is satisfied by superset\n */\nexport function isOrderBySubset(\n subset: OrderBy | undefined,\n superset: OrderBy | undefined\n): boolean {\n // No ordering requirement is always satisfied\n if (!subset || subset.length === 0) {\n return true\n }\n\n // If there's no superset ordering but subset requires ordering, not satisfied\n if (!superset || superset.length === 0) {\n return false\n }\n\n // Check if subset is a prefix of superset with matching expressions and compare options\n if (subset.length > superset.length) {\n return false\n }\n\n for (let i = 0; i < subset.length; i++) {\n const subClause = subset[i]!\n const superClause = superset[i]!\n\n // Check if expressions match\n if (!areExpressionsEqual(subClause.expression, superClause.expression)) {\n return false\n }\n\n // Check if compare options match\n if (\n !areCompareOptionsEqual(\n subClause.compareOptions,\n superClause.compareOptions\n )\n ) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Check if one limit is a subset of another.\n * Returns true if the subset limit requirements are satisfied by the superset limit.\n *\n * @example\n * isLimitSubset(10, 20) // true (requesting 10 items when 20 are available)\n * isLimitSubset(20, 10) // false (requesting 20 items when only 10 are available)\n * isLimitSubset(10, undefined) // true (requesting 10 items when unlimited are available)\n *\n * @param subset - The limit requirement to check\n * @param superset - The limit that might satisfy the requirement\n * @returns true if subset is satisfied by superset\n */\nexport function isLimitSubset(\n subset: number | undefined,\n superset: number | undefined\n): boolean {\n // Unlimited superset satisfies any limit requirement\n if (superset === undefined) {\n return true\n }\n\n // If requesting all data (no limit), we need unlimited data to satisfy it\n // But we know superset is not unlimited so we return false\n if (subset === undefined) {\n return false\n }\n\n // Otherwise, subset must be less than or equal to superset\n return subset <= superset\n}\n\n/**\n * Check if one predicate (where + orderBy + limit) is a subset of another.\n * Returns true if all aspects of the subset predicate are satisfied by the superset.\n *\n * @example\n * isPredicateSubset(\n * { where: gt(ref('age'), val(20)), limit: 10 },\n * { where: gt(ref('age'), val(10)), limit: 20 }\n * ) // true\n *\n * @param subset - The predicate requirements to check\n * @param superset - The predicate that might satisfy the requirements\n * @returns true if subset is satisfied by superset\n */\nexport function isPredicateSubset(\n subset: LoadSubsetOptions,\n superset: LoadSubsetOptions\n): boolean {\n // When the superset has a limit, we can only determine subset relationship\n // if the where clauses are equal (not just subset relationship).\n //\n // This is because a limited query only loads a portion of the matching rows.\n // A more restrictive where clause might require rows outside that portion.\n //\n // Example: superset = {where: undefined, limit: 10, orderBy: desc}\n // subset = {where: LIKE 'search%', limit: 10, orderBy: desc}\n // The top 10 items matching 'search%' might include items outside the overall top 10.\n //\n // However, if the where clauses are equal, then the subset relationship can\n // be determined by orderBy and limit alone:\n // Example: superset = {where: status='active', limit: 10, orderBy: desc}\n // subset = {where: status='active', limit: 5, orderBy: desc}\n // The top 5 active items ARE contained in the top 10 active items.\n if (superset.limit !== undefined) {\n // For limited supersets, where clauses must be equal\n if (!areWhereClausesEqual(subset.where, superset.where)) {\n return false\n }\n return (\n isOrderBySubset(subset.orderBy, superset.orderBy) &&\n isLimitSubset(subset.limit, superset.limit)\n )\n }\n\n // For unlimited supersets, use the normal subset logic\n return (\n isWhereSubset(subset.where, superset.where) &&\n isOrderBySubset(subset.orderBy, superset.orderBy) &&\n isLimitSubset(subset.limit, superset.limit)\n )\n}\n\n/**\n * Check if two where clauses are structurally equal.\n * Used for limited query subset checks where subset relationship isn't sufficient.\n */\nfunction areWhereClausesEqual(\n a: BasicExpression<boolean> | undefined,\n b: BasicExpression<boolean> | undefined\n): boolean {\n if (a === undefined && b === undefined) {\n return true\n }\n if (a === undefined || b === undefined) {\n return false\n }\n return areExpressionsEqual(a, b)\n}\n\n// ============================================================================\n// Helper functions\n// ============================================================================\n\n/**\n * Find common conditions between two predicates.\n * Returns an array of conditions that appear in both predicates.\n */\nfunction findCommonConditions(\n predicate1: BasicExpression<boolean>,\n predicate2: BasicExpression<boolean>\n): Array<BasicExpression<boolean>> {\n const conditions1 = extractAllConditions(predicate1)\n const conditions2 = extractAllConditions(predicate2)\n\n const common: Array<BasicExpression<boolean>> = []\n\n for (const cond1 of conditions1) {\n for (const cond2 of conditions2) {\n if (areExpressionsEqual(cond1, cond2)) {\n // Avoid duplicates\n if (!common.some((c) => areExpressionsEqual(c, cond1))) {\n common.push(cond1)\n }\n break\n }\n }\n }\n\n return common\n}\n\n/**\n * Extract all individual conditions from a predicate, flattening AND operations.\n */\nfunction extractAllConditions(\n predicate: BasicExpression<boolean>\n): Array<BasicExpression<boolean>> {\n if (predicate.type === `func` && predicate.name === `and`) {\n const conditions: Array<BasicExpression<boolean>> = []\n for (const arg of predicate.args) {\n conditions.push(...extractAllConditions(arg as BasicExpression<boolean>))\n }\n return conditions\n }\n\n return [predicate]\n}\n\n/**\n * Remove specified conditions from a predicate.\n * Returns the predicate with the specified conditions removed, or undefined if all conditions are removed.\n */\nfunction removeConditions(\n predicate: BasicExpression<boolean>,\n conditionsToRemove: Array<BasicExpression<boolean>>\n): BasicExpression<boolean> | undefined {\n if (predicate.type === `func` && predicate.name === `and`) {\n const remainingArgs = predicate.args.filter(\n (arg) =>\n !conditionsToRemove.some((cond) =>\n areExpressionsEqual(arg as BasicExpression<boolean>, cond)\n )\n )\n\n if (remainingArgs.length === 0) {\n return undefined\n } else if (remainingArgs.length === 1) {\n return remainingArgs[0]!\n } else {\n return {\n type: `func`,\n name: `and`,\n args: remainingArgs,\n } as BasicExpression<boolean>\n }\n }\n\n // For non-AND predicates, don't remove anything\n return predicate\n}\n\n/**\n * Combine multiple conditions into a single predicate using AND logic.\n * Flattens nested AND operations to avoid unnecessary nesting.\n */\nfunction combineConditions(\n conditions: Array<BasicExpression<boolean>>\n): BasicExpression<boolean> {\n if (conditions.length === 0) {\n return { type: `val`, value: true } as BasicExpression<boolean>\n } else if (conditions.length === 1) {\n return conditions[0]!\n } else {\n // Flatten all conditions, including those that are already AND operations\n const flattenedConditions: Array<BasicExpression<boolean>> = []\n\n for (const condition of conditions) {\n if (condition.type === `func` && condition.name === `and`) {\n // Flatten nested AND operations\n flattenedConditions.push(...condition.args)\n } else {\n flattenedConditions.push(condition)\n }\n }\n\n if (flattenedConditions.length === 1) {\n return flattenedConditions[0]!\n } else {\n return {\n type: `func`,\n name: `and`,\n args: flattenedConditions,\n } as BasicExpression<boolean>\n }\n }\n}\n\n/**\n * Find a predicate with a specific operator and value\n */\nfunction findPredicateWithOperator(\n predicates: Array<BasicExpression<boolean>>,\n operator: string,\n value: any\n): BasicExpression<boolean> | undefined {\n return predicates.find((p) => {\n if (p.type === `func`) {\n const f = p as Func\n const field = extractComparisonField(f)\n return f.name === operator && field && areValuesEqual(field.value, value)\n }\n return false\n })\n}\n\nfunction areExpressionsEqual(a: BasicExpression, b: BasicExpression): boolean {\n if (a.type !== b.type) {\n return false\n }\n\n if (a.type === `val` && b.type === `val`) {\n return areValuesEqual(a.value, b.value)\n }\n\n if (a.type === `ref` && b.type === `ref`) {\n return areRefsEqual(a, b)\n }\n\n if (a.type === `func` && b.type === `func`) {\n const aFunc = a\n const bFunc = b\n if (aFunc.name !== bFunc.name) {\n return false\n }\n if (aFunc.args.length !== bFunc.args.length) {\n return false\n }\n return aFunc.args.every((arg, i) =>\n areExpressionsEqual(arg, bFunc.args[i]!)\n )\n }\n\n return false\n}\n\nfunction areValuesEqual(a: any, b: any): boolean {\n // Simple equality check - could be enhanced for deep object comparison\n if (a === b) {\n return true\n }\n\n // Handle NaN\n if (typeof a === `number` && typeof b === `number` && isNaN(a) && isNaN(b)) {\n return true\n }\n\n // Handle Date objects\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime()\n }\n\n // For arrays and objects, use reference equality\n // (In practice, we don't need deep equality for these cases -\n // same object reference means same value for our use case)\n if (\n typeof a === `object` &&\n typeof b === `object` &&\n a !== null &&\n b !== null\n ) {\n return a === b\n }\n\n return false\n}\n\nfunction areRefsEqual(a: PropRef, b: PropRef): boolean {\n if (a.path.length !== b.path.length) {\n return false\n }\n return a.path.every((segment, i) => segment === b.path[i])\n}\n\n/**\n * Check if a value is a primitive (string, number, boolean, null, undefined)\n * Primitives can use Set for fast lookups\n */\nfunction isPrimitive(value: any): boolean {\n return (\n value === null ||\n value === undefined ||\n typeof value === `string` ||\n typeof value === `number` ||\n typeof value === `boolean`\n )\n}\n\n/**\n * Check if all values in an array are primitives\n */\nfunction areAllPrimitives(values: Array<any>): boolean {\n return values.every(isPrimitive)\n}\n\n/**\n * Check if a value is in an array, with optional pre-built Set for optimization.\n * The primitiveSet is cached in InField during extraction and reused for all lookups.\n */\nfunction arrayIncludesWithSet(\n array: Array<any>,\n value: any,\n primitiveSet: Set<any> | null,\n arrayIsAllPrimitives?: boolean\n): boolean {\n // Fast path: use pre-built Set for O(1) lookup\n if (primitiveSet) {\n // Skip isPrimitive check if we know the value must be primitive for a match\n // (if array is all primitives, only primitives can match)\n if (arrayIsAllPrimitives || isPrimitive(value)) {\n return primitiveSet.has(value)\n }\n return false // Non-primitive can't be in primitive-only set\n }\n\n // Fallback: use areValuesEqual for Dates and objects\n return array.some((v) => areValuesEqual(v, value))\n}\n\n/**\n * Get the maximum of two values, handling both numbers and Dates\n */\nfunction maxValue(a: any, b: any): any {\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() > b.getTime() ? a : b\n }\n return Math.max(a, b)\n}\n\n/**\n * Get the minimum of two values, handling both numbers and Dates\n */\nfunction minValue(a: any, b: any): any {\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() < b.getTime() ? a : b\n }\n return Math.min(a, b)\n}\n\nfunction areCompareOptionsEqual(\n a: { direction?: `asc` | `desc`; [key: string]: any },\n b: { direction?: `asc` | `desc`; [key: string]: any }\n): boolean {\n // For now, just compare direction - could be enhanced for other options\n return a.direction === b.direction\n}\n\ninterface ComparisonField {\n ref: PropRef\n value: any\n}\n\nfunction extractComparisonField(func: Func): ComparisonField | null {\n // Handle comparison operators: eq, gt, gte, lt, lte\n if ([`eq`, `gt`, `gte`, `lt`, `lte`].includes(func.name)) {\n // Assume first arg is ref, second is value\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (firstArg?.type === `ref` && secondArg?.type === `val`) {\n return {\n ref: firstArg,\n value: secondArg.value,\n }\n }\n }\n\n return null\n}\n\nfunction extractEqualityField(func: Func): ComparisonField | null {\n if (func.name === `eq`) {\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (firstArg?.type === `ref` && secondArg?.type === `val`) {\n return {\n ref: firstArg,\n value: secondArg.value,\n }\n }\n }\n return null\n}\n\ninterface InField {\n ref: PropRef\n values: Array<any>\n // Cached optimization data (computed once, reused many times)\n areAllPrimitives?: boolean\n primitiveSet?: Set<any> | null\n}\n\nfunction extractInField(func: Func): InField | null {\n if (func.name === `in`) {\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (\n firstArg?.type === `ref` &&\n secondArg?.type === `val` &&\n Array.isArray(secondArg.value)\n ) {\n let values = secondArg.value\n // Precompute optimization metadata once\n const allPrimitives = areAllPrimitives(values)\n let primitiveSet: Set<any> | null = null\n\n if (allPrimitives && values.length > 10) {\n // Build Set and dedupe values at the same time\n primitiveSet = new Set(values)\n // If we found duplicates, use the deduped array going forward\n if (primitiveSet.size < values.length) {\n values = Array.from(primitiveSet)\n }\n }\n\n return {\n ref: firstArg,\n values,\n areAllPrimitives: allPrimitives,\n primitiveSet,\n }\n }\n }\n return null\n}\n\nfunction isComparisonSubset(\n subsetFunc: Func,\n subsetValue: any,\n supersetFunc: Func,\n supersetValue: any\n): boolean {\n const subOp = subsetFunc.name\n const superOp = supersetFunc.name\n\n // Handle same operator\n if (subOp === superOp) {\n if (subOp === `eq`) {\n // field = X is subset of field = X only\n // Fast path: primitives can use strict equality\n if (isPrimitive(subsetValue) && isPrimitive(supersetValue)) {\n return subsetValue === supersetValue\n }\n return areValuesEqual(subsetValue, supersetValue)\n } else if (subOp === `gt`) {\n // field > 20 is subset of field > 10 if 20 > 10\n return subsetValue >= supersetValue\n } else if (subOp === `gte`) {\n // field >= 20 is subset of field >= 10 if 20 >= 10\n return subsetValue >= supersetValue\n } else if (subOp === `lt`) {\n // field < 10 is subset of field < 20 if 10 <= 20\n return subsetValue <= supersetValue\n } else if (subOp === `lte`) {\n // field <= 10 is subset of field <= 20 if 10 <= 20\n return subsetValue <= supersetValue\n }\n }\n\n // Handle different operators on same field\n // eq vs gt/gte: field = 15 is subset of field > 10 if 15 > 10\n if (subOp === `eq` && superOp === `gt`) {\n return subsetValue > supersetValue\n }\n if (subOp === `eq` && superOp === `gte`) {\n return subsetValue >= supersetValue\n }\n if (subOp === `eq` && superOp === `lt`) {\n return subsetValue < supersetValue\n }\n if (subOp === `eq` && superOp === `lte`) {\n return subsetValue <= supersetValue\n }\n\n // gt/gte vs gte/gt\n if (subOp === `gt` && superOp === `gte`) {\n // field > 10 is subset of field >= 10 if 10 >= 10 (always true for same value)\n return subsetValue >= supersetValue\n }\n if (subOp === `gte` && superOp === `gt`) {\n // field >= 11 is subset of field > 10 if 11 > 10\n return subsetValue > supersetValue\n }\n\n // lt/lte vs lte/lt\n if (subOp === `lt` && superOp === `lte`) {\n // field < 10 is subset of field <= 10 if 10 <= 10\n return subsetValue <= supersetValue\n }\n if (subOp === `lte` && superOp === `lt`) {\n // field <= 9 is subset of field < 10 if 9 < 10\n return subsetValue < supersetValue\n }\n\n return false\n}\n\nfunction groupPredicatesByField(\n predicates: Array<BasicExpression<boolean>>\n): Map<string | null, Array<BasicExpression<boolean>>> {\n const groups = new Map<string | null, Array<BasicExpression<boolean>>>()\n\n for (const pred of predicates) {\n let fieldKey: string | null = null\n\n if (pred.type === `func`) {\n const func = pred as Func\n const field =\n extractComparisonField(func) ||\n extractEqualityField(func) ||\n extractInField(func)\n if (field) {\n fieldKey = field.ref.path.join(`.`)\n }\n }\n\n const group = groups.get(fieldKey) || []\n group.push(pred)\n groups.set(fieldKey, group)\n }\n\n return groups\n}\n\nfunction unionSameFieldPredicates(\n predicates: Array<BasicExpression<boolean>>\n): BasicExpression<boolean> | null {\n if (predicates.length === 1) {\n return predicates[0]!\n }\n\n // Try to extract range constraints\n let maxGt: number | null = null\n let maxGte: number | null = null\n let minLt: number | null = null\n let minLte: number | null = null\n const eqValues: Set<any> = new Set()\n const inValues: Set<any> = new Set()\n const otherPredicates: Array<BasicExpression<boolean>> = []\n\n for (const pred of predicates) {\n if (pred.type === `func`) {\n const func = pred as Func\n const field = extractComparisonField(func)\n\n if (field) {\n const value = field.value\n if (func.name === `gt`) {\n maxGt = maxGt === null ? value : minValue(maxGt, value)\n } else if (func.name === `gte`) {\n maxGte = maxGte === null ? value : minValue(maxGte, value)\n } else if (func.name === `lt`) {\n minLt = minLt === null ? value : maxValue(minLt, value)\n } else if (func.name === `lte`) {\n minLte = minLte === null ? value : maxValue(minLte, value)\n } else if (func.name === `eq`) {\n eqValues.add(value)\n } else {\n otherPredicates.push(pred)\n }\n } else {\n const inField = extractInField(func)\n if (inField) {\n for (const val of inField.values) {\n inValues.add(val)\n }\n } else {\n otherPredicates.push(pred)\n }\n }\n } else {\n otherPredicates.push(pred)\n }\n }\n\n // If we have multiple equality values, combine into IN\n if (eqValues.size > 1 || (eqValues.size > 0 && inValues.size > 0)) {\n const allValues = [...eqValues, ...inValues]\n const ref = predicates.find((p) => {\n if (p.type === `func`) {\n const field =\n extractComparisonField(p as Func) || extractInField(p as Func)\n return field !== null\n }\n return false\n })\n\n if (ref && ref.type === `func`) {\n const field =\n extractComparisonField(ref as Func) || extractInField(ref as Func)\n if (field) {\n return {\n type: `func`,\n name: `in`,\n args: [\n field.ref,\n { type: `val`, value: allValues } as BasicExpression,\n ],\n } as BasicExpression<boolean>\n }\n }\n }\n\n // Build the least restrictive range\n const result: Array<BasicExpression<boolean>> = []\n\n // Choose the least restrictive lower bound\n if (maxGt !== null && maxGte !== null) {\n // Take the smaller one (less restrictive)\n const pred =\n maxGte <= maxGt\n ? findPredicateWithOperator(predicates, `gte`, maxGte)\n : findPredicateWithOperator(predicates, `gt`, maxGt)\n if (pred) result.push(pred)\n } else if (maxGt !== null) {\n const pred = findPredicateWithOperator(predicates, `gt`, maxGt)\n if (pred) result.push(pred)\n } else if (maxGte !== null) {\n const pred = findPredicateWithOperator(predicates, `gte`, maxGte)\n if (pred) result.push(pred)\n }\n\n // Choose the least restrictive upper bound\n if (minLt !== null && minLte !== null) {\n const pred =\n minLte >= minLt\n ? findPredicateWithOperator(predicates, `lte`, minLte)\n : findPredicateWithOperator(predicates, `lt`, minLt)\n if (pred) result.push(pred)\n } else if (minLt !== null) {\n const pred = findPredicateWithOperator(predicates, `lt`, minLt)\n if (pred) result.push(pred)\n } else if (minLte !== null) {\n const pred = findPredicateWithOperator(predicates, `lte`, minLte)\n if (pred) result.push(pred)\n }\n\n // Add single eq value\n if (eqValues.size === 1 && inValues.size === 0) {\n const pred = findPredicateWithOperator(predicates, `eq`, [...eqValues][0])\n if (pred) result.push(pred)\n }\n\n // Add IN if only IN values\n if (eqValues.size === 0 && inValues.size > 0) {\n result.push(\n predicates.find((p) => {\n if (p.type === `func`) {\n return (p as Func).name === `in`\n }\n return false\n })!\n )\n }\n\n // Add other predicates\n result.push(...otherPredicates)\n\n if (result.length === 0) {\n return { type: `val`, value: true } as BasicExpression<boolean>\n }\n\n if (result.length === 1) {\n return result[0]!\n }\n\n return {\n type: `func`,\n name: `or`,\n args: result,\n } as BasicExpression<boolean>\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"predicate-utils.js","sources":["../../../src/query/predicate-utils.ts"],"sourcesContent":["import { Func, Value } from './ir.js'\nimport type { BasicExpression, OrderBy, PropRef } from './ir.js'\nimport type { LoadSubsetOptions } from '../types.js'\n\n/**\n * Check if one where clause is a logical subset of another.\n * Returns true if the subset predicate is more restrictive than (or equal to) the superset predicate.\n *\n * @example\n * // age > 20 is subset of age > 10 (more restrictive)\n * isWhereSubset(gt(ref('age'), val(20)), gt(ref('age'), val(10))) // true\n *\n * @example\n * // age > 10 AND name = 'X' is subset of age > 10 (more conditions)\n * isWhereSubset(and(gt(ref('age'), val(10)), eq(ref('name'), val('X'))), gt(ref('age'), val(10))) // true\n *\n * @param subset - The potentially more restrictive predicate\n * @param superset - The potentially less restrictive predicate\n * @returns true if subset logically implies superset\n */\nexport function isWhereSubset(\n subset: BasicExpression<boolean> | undefined,\n superset: BasicExpression<boolean> | undefined,\n): boolean {\n // undefined/missing where clause means \"no filter\" (all data)\n // Both undefined means subset relationship holds (all data ⊆ all data)\n if (subset === undefined && superset === undefined) {\n return true\n }\n\n // If subset is undefined but superset is not, we're requesting ALL data\n // but have only loaded SOME data - subset relationship does NOT hold\n if (subset === undefined && superset !== undefined) {\n return false\n }\n\n // If superset is undefined (no filter = all data loaded),\n // then any constrained subset is contained\n if (superset === undefined && subset !== undefined) {\n return true\n }\n\n return isWhereSubsetInternal(subset!, superset!)\n}\n\nfunction makeDisjunction(\n preds: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> {\n if (preds.length === 0) {\n return new Value(false)\n }\n if (preds.length === 1) {\n return preds[0]!\n }\n return new Func(`or`, preds)\n}\n\nfunction convertInToOr(inField: InField) {\n const equalities = inField.values.map(\n (value) => new Func(`eq`, [inField.ref, new Value(value)]),\n )\n return makeDisjunction(equalities)\n}\n\nfunction isWhereSubsetInternal(\n subset: BasicExpression<boolean>,\n superset: BasicExpression<boolean>,\n): boolean {\n // If subset is false it is requesting no data,\n // thus the result set is empty\n // and the empty set is a subset of any set\n if (subset.type === `val` && subset.value === false) {\n return true\n }\n\n // If expressions are structurally equal, subset relationship holds\n if (areExpressionsEqual(subset, superset)) {\n return true\n }\n\n // Handle superset being an AND: subset must imply ALL conjuncts\n // If superset is (A AND B), then subset ⊆ (A AND B) only if subset ⊆ A AND subset ⊆ B\n // Example: (age > 20) ⊆ (age > 10 AND status = 'active') is false (doesn't imply status condition)\n if (superset.type === `func` && superset.name === `and`) {\n return superset.args.every((arg) =>\n isWhereSubsetInternal(subset, arg as BasicExpression<boolean>),\n )\n }\n\n // Handle subset being an AND: (A AND B) implies both A and B\n if (subset.type === `func` && subset.name === `and`) {\n // For (A AND B) ⊆ C, since (A AND B) implies A, we check if any conjunct implies C\n return subset.args.some((arg) =>\n isWhereSubsetInternal(arg as BasicExpression<boolean>, superset),\n )\n }\n\n // Turn x IN [A, B, C] into x = A OR x = B OR x = C\n // for unified handling of IN and OR\n if (subset.type === `func` && subset.name === `in`) {\n const inField = extractInField(subset)\n if (inField) {\n return isWhereSubsetInternal(convertInToOr(inField), superset)\n }\n }\n\n if (superset.type === `func` && superset.name === `in`) {\n const inField = extractInField(superset)\n if (inField) {\n return isWhereSubsetInternal(subset, convertInToOr(inField))\n }\n }\n\n // Handle OR in subset: (A OR B) is subset of C only if both A and B are subsets of C\n if (subset.type === `func` && subset.name === `or`) {\n return subset.args.every((arg) =>\n isWhereSubsetInternal(arg as BasicExpression<boolean>, superset),\n )\n }\n\n // Handle OR in superset: subset ⊆ (A OR B) if subset ⊆ A or subset ⊆ B\n // (A OR B) as superset means data can satisfy A or B\n // If subset is contained in any disjunct, it's contained in the union\n if (superset.type === `func` && superset.name === `or`) {\n return superset.args.some((arg) =>\n isWhereSubsetInternal(subset, arg as BasicExpression<boolean>),\n )\n }\n\n // Handle comparison operators on the same field\n if (subset.type === `func` && superset.type === `func`) {\n const subsetFunc = subset as Func\n const supersetFunc = superset as Func\n\n // Check if both are comparisons on the same field\n const subsetField = extractComparisonField(subsetFunc)\n const supersetField = extractComparisonField(supersetFunc)\n\n if (\n subsetField &&\n supersetField &&\n areRefsEqual(subsetField.ref, supersetField.ref)\n ) {\n return isComparisonSubset(\n subsetFunc,\n subsetField.value,\n supersetFunc,\n supersetField.value,\n )\n }\n\n /*\n // Handle eq vs in\n if (subsetFunc.name === `eq` && supersetFunc.name === `in`) {\n const subsetFieldEq = extractEqualityField(subsetFunc)\n const supersetFieldIn = extractInField(supersetFunc)\n if (\n subsetFieldEq &&\n supersetFieldIn &&\n areRefsEqual(subsetFieldEq.ref, supersetFieldIn.ref)\n ) {\n // field = X is subset of field IN [X, Y, Z] if X is in the array\n // Use cached primitive set and metadata from extraction\n return arrayIncludesWithSet(\n supersetFieldIn.values,\n subsetFieldEq.value,\n supersetFieldIn.primitiveSet ?? null,\n supersetFieldIn.areAllPrimitives\n )\n }\n }\n\n // Handle in vs in\n if (subsetFunc.name === `in` && supersetFunc.name === `in`) {\n const subsetFieldIn = extractInField(subsetFunc)\n const supersetFieldIn = extractInField(supersetFunc)\n if (\n subsetFieldIn &&\n supersetFieldIn &&\n areRefsEqual(subsetFieldIn.ref, supersetFieldIn.ref)\n ) {\n // field IN [A, B] is subset of field IN [A, B, C] if all values in subset are in superset\n // Use cached primitive set and metadata from extraction\n return subsetFieldIn.values.every((subVal) =>\n arrayIncludesWithSet(\n supersetFieldIn.values,\n subVal,\n supersetFieldIn.primitiveSet ?? null,\n supersetFieldIn.areAllPrimitives\n )\n )\n }\n }\n */\n }\n\n // Conservative: if we can't determine, return false\n return false\n}\n\n/**\n * Helper to combine where predicates with common logic for AND/OR operations\n */\nfunction combineWherePredicates(\n predicates: Array<BasicExpression<boolean>>,\n operation: `and` | `or`,\n simplifyFn: (\n preds: Array<BasicExpression<boolean>>,\n ) => BasicExpression<boolean> | null,\n): BasicExpression<boolean> {\n const emptyValue = operation === `and` ? true : false\n const identityValue = operation === `and` ? true : false\n\n if (predicates.length === 0) {\n return { type: `val`, value: emptyValue } as BasicExpression<boolean>\n }\n\n if (predicates.length === 1) {\n return predicates[0]!\n }\n\n // Flatten nested expressions of the same operation\n const flatPredicates: Array<BasicExpression<boolean>> = []\n for (const pred of predicates) {\n if (pred.type === `func` && pred.name === operation) {\n flatPredicates.push(...pred.args)\n } else {\n flatPredicates.push(pred)\n }\n }\n\n // Group predicates by field for simplification\n const grouped = groupPredicatesByField(flatPredicates)\n\n // Simplify each group\n const simplified: Array<BasicExpression<boolean>> = []\n for (const [field, preds] of grouped.entries()) {\n if (field === null) {\n // Complex predicates that we can't group by field\n simplified.push(...preds)\n } else {\n // Try to simplify same-field predicates\n const result = simplifyFn(preds)\n\n // For intersection: check for empty set (contradiction)\n if (\n operation === `and` &&\n result &&\n result.type === `val` &&\n result.value === false\n ) {\n // Intersection is empty (conflicting constraints) - entire AND is false\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // For union: result may be null if simplification failed\n if (result) {\n simplified.push(result)\n }\n }\n }\n\n if (simplified.length === 0) {\n return { type: `val`, value: identityValue } as BasicExpression<boolean>\n }\n\n if (simplified.length === 1) {\n return simplified[0]!\n }\n\n // Return combined predicate\n return {\n type: `func`,\n name: operation,\n args: simplified,\n } as BasicExpression<boolean>\n}\n\n/**\n * Combine multiple where predicates with OR logic (union).\n * Returns a predicate that is satisfied when any input predicate is satisfied.\n * Simplifies when possible (e.g., age > 10 OR age > 20 → age > 10).\n *\n * @example\n * // Take least restrictive\n * unionWherePredicates([gt(ref('age'), val(10)), gt(ref('age'), val(20))]) // age > 10\n *\n * @example\n * // Combine equals into IN\n * unionWherePredicates([eq(ref('age'), val(5)), eq(ref('age'), val(10))]) // age IN [5, 10]\n *\n * @param predicates - Array of where predicates to union\n * @returns Combined predicate representing the union\n */\nexport function unionWherePredicates(\n predicates: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> {\n return combineWherePredicates(predicates, `or`, unionSameFieldPredicates)\n}\n\n/**\n * Compute the difference between two where predicates: `fromPredicate AND NOT(subtractPredicate)`.\n * Returns the simplified predicate, or null if the difference cannot be simplified\n * (in which case the caller should fetch the full fromPredicate).\n *\n * @example\n * // Range difference\n * minusWherePredicates(\n * gt(ref('age'), val(10)), // age > 10\n * gt(ref('age'), val(20)) // age > 20\n * ) // → age > 10 AND age <= 20\n *\n * @example\n * // Set difference\n * minusWherePredicates(\n * inOp(ref('status'), ['A', 'B', 'C', 'D']), // status IN ['A','B','C','D']\n * inOp(ref('status'), ['B', 'C']) // status IN ['B','C']\n * ) // → status IN ['A', 'D']\n *\n * @example\n * // Common conditions\n * minusWherePredicates(\n * and(gt(ref('age'), val(10)), eq(ref('status'), val('active'))), // age > 10 AND status = 'active'\n * and(gt(ref('age'), val(20)), eq(ref('status'), val('active'))) // age > 20 AND status = 'active'\n * ) // → age > 10 AND age <= 20 AND status = 'active'\n *\n * @example\n * // Complete overlap - empty result\n * minusWherePredicates(\n * gt(ref('age'), val(20)), // age > 20\n * gt(ref('age'), val(10)) // age > 10\n * ) // → {type: 'val', value: false} (empty set)\n *\n * @param fromPredicate - The predicate to subtract from\n * @param subtractPredicate - The predicate to subtract\n * @returns The simplified difference, or null if cannot be simplified\n */\nexport function minusWherePredicates(\n fromPredicate: BasicExpression<boolean> | undefined,\n subtractPredicate: BasicExpression<boolean> | undefined,\n): BasicExpression<boolean> | null {\n // If nothing to subtract, return the original\n if (subtractPredicate === undefined) {\n return (\n fromPredicate ??\n ({ type: `val`, value: true } as BasicExpression<boolean>)\n )\n }\n\n // If from is undefined then we are asking for all data\n // so we need to load all data minus what we already loaded\n // i.e. we need to load NOT(subtractPredicate)\n if (fromPredicate === undefined) {\n return {\n type: `func`,\n name: `not`,\n args: [subtractPredicate],\n } as BasicExpression<boolean>\n }\n\n // Check if fromPredicate is entirely contained in subtractPredicate\n // In that case, fromPredicate AND NOT(subtractPredicate) = empty set\n if (isWhereSubset(fromPredicate, subtractPredicate)) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // Try to detect and handle common conditions\n const commonConditions = findCommonConditions(\n fromPredicate,\n subtractPredicate,\n )\n if (commonConditions.length > 0) {\n // Extract predicates without common conditions\n const fromWithoutCommon = removeConditions(fromPredicate, commonConditions)\n const subtractWithoutCommon = removeConditions(\n subtractPredicate,\n commonConditions,\n )\n\n // Recursively compute difference on simplified predicates\n const simplifiedDifference = minusWherePredicates(\n fromWithoutCommon,\n subtractWithoutCommon,\n )\n\n if (simplifiedDifference !== null) {\n // Combine the simplified difference with common conditions\n return combineConditions([...commonConditions, simplifiedDifference])\n }\n }\n\n // Check if they are on the same field - if so, we can try to simplify\n if (fromPredicate.type === `func` && subtractPredicate.type === `func`) {\n const result = minusSameFieldPredicates(fromPredicate, subtractPredicate)\n if (result !== null) {\n return result\n }\n }\n\n // Can't simplify - return null to indicate caller should fetch full fromPredicate\n return null\n}\n\n/**\n * Helper function to compute difference for same-field predicates\n */\nfunction minusSameFieldPredicates(\n fromPred: Func,\n subtractPred: Func,\n): BasicExpression<boolean> | null {\n // Extract field information\n const fromField =\n extractComparisonField(fromPred) ||\n extractEqualityField(fromPred) ||\n extractInField(fromPred)\n const subtractField =\n extractComparisonField(subtractPred) ||\n extractEqualityField(subtractPred) ||\n extractInField(subtractPred)\n\n // Must be on the same field\n if (\n !fromField ||\n !subtractField ||\n !areRefsEqual(fromField.ref, subtractField.ref)\n ) {\n return null\n }\n\n // Handle IN minus IN: status IN [A,B,C,D] - status IN [B,C] = status IN [A,D]\n if (fromPred.name === `in` && subtractPred.name === `in`) {\n const fromInField = fromField as InField\n const subtractInField = subtractField as InField\n\n // Filter out values that are in the subtract set\n const remainingValues = fromInField.values.filter(\n (v) =>\n !arrayIncludesWithSet(\n subtractInField.values,\n v,\n subtractInField.primitiveSet ?? null,\n subtractInField.areAllPrimitives,\n ),\n )\n\n if (remainingValues.length === 0) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n if (remainingValues.length === 1) {\n return {\n type: `func`,\n name: `eq`,\n args: [fromField.ref, { type: `val`, value: remainingValues[0] }],\n } as BasicExpression<boolean>\n }\n\n return {\n type: `func`,\n name: `in`,\n args: [fromField.ref, { type: `val`, value: remainingValues }],\n } as BasicExpression<boolean>\n }\n\n // Handle IN minus equality: status IN [A,B,C] - status = B = status IN [A,C]\n if (fromPred.name === `in` && subtractPred.name === `eq`) {\n const fromInField = fromField as InField\n const subtractValue = (subtractField as { ref: PropRef; value: any }).value\n\n const remainingValues = fromInField.values.filter(\n (v) => !areValuesEqual(v, subtractValue),\n )\n\n if (remainingValues.length === 0) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n if (remainingValues.length === 1) {\n return {\n type: `func`,\n name: `eq`,\n args: [fromField.ref, { type: `val`, value: remainingValues[0] }],\n } as BasicExpression<boolean>\n }\n\n return {\n type: `func`,\n name: `in`,\n args: [fromField.ref, { type: `val`, value: remainingValues }],\n } as BasicExpression<boolean>\n }\n\n // Handle equality minus equality: age = 15 - age = 15 = empty, age = 15 - age = 20 = age = 15\n if (fromPred.name === `eq` && subtractPred.name === `eq`) {\n const fromValue = (fromField as { ref: PropRef; value: any }).value\n const subtractValue = (subtractField as { ref: PropRef; value: any }).value\n\n if (areValuesEqual(fromValue, subtractValue)) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // No overlap - return original\n return fromPred as BasicExpression<boolean>\n }\n\n // Handle range minus range: age > 10 - age > 20 = age > 10 AND age <= 20\n const fromComp = extractComparisonField(fromPred)\n const subtractComp = extractComparisonField(subtractPred)\n\n if (\n fromComp &&\n subtractComp &&\n areRefsEqual(fromComp.ref, subtractComp.ref)\n ) {\n // Try to compute the difference using range logic\n const result = minusRangePredicates(\n fromPred,\n fromComp.value,\n subtractPred,\n subtractComp.value,\n )\n return result\n }\n\n // Can't simplify\n return null\n}\n\n/**\n * Helper to compute difference between range predicates\n */\nfunction minusRangePredicates(\n fromFunc: Func,\n fromValue: any,\n subtractFunc: Func,\n subtractValue: any,\n): BasicExpression<boolean> | null {\n const fromOp = fromFunc.name as `gt` | `gte` | `lt` | `lte` | `eq`\n const subtractOp = subtractFunc.name as `gt` | `gte` | `lt` | `lte` | `eq`\n const ref = (extractComparisonField(fromFunc) ||\n extractEqualityField(fromFunc))!.ref\n\n // age > 10 - age > 20 = (age > 10 AND age <= 20)\n if (fromOp === `gt` && subtractOp === `gt`) {\n if (fromValue < subtractValue) {\n // Result is: fromValue < field <= subtractValue\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n // fromValue >= subtractValue means no overlap\n return fromFunc as BasicExpression<boolean>\n }\n\n // age >= 10 - age >= 20 = (age >= 10 AND age < 20)\n if (fromOp === `gte` && subtractOp === `gte`) {\n if (fromValue < subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age > 10 - age >= 20 = (age > 10 AND age < 20)\n if (fromOp === `gt` && subtractOp === `gte`) {\n if (fromValue < subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age >= 10 - age > 20 = (age >= 10 AND age <= 20)\n if (fromOp === `gte` && subtractOp === `gt`) {\n if (fromValue <= subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age < 30 - age < 20 = (age >= 20 AND age < 30)\n if (fromOp === `lt` && subtractOp === `lt`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age <= 30 - age <= 20 = (age > 20 AND age <= 30)\n if (fromOp === `lte` && subtractOp === `lte`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age < 30 - age <= 20 = (age > 20 AND age < 30)\n if (fromOp === `lt` && subtractOp === `lte`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age <= 30 - age < 20 = (age >= 20 AND age <= 30)\n if (fromOp === `lte` && subtractOp === `lt`) {\n if (fromValue >= subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // Can't simplify other combinations\n return null\n}\n\n/**\n * Check if one orderBy clause is a subset of another.\n * Returns true if the subset ordering requirements are satisfied by the superset ordering.\n *\n * @example\n * // Subset is prefix of superset\n * isOrderBySubset([{expr: age, asc}], [{expr: age, asc}, {expr: name, desc}]) // true\n *\n * @param subset - The ordering requirements to check\n * @param superset - The ordering that might satisfy the requirements\n * @returns true if subset is satisfied by superset\n */\nexport function isOrderBySubset(\n subset: OrderBy | undefined,\n superset: OrderBy | undefined,\n): boolean {\n // No ordering requirement is always satisfied\n if (!subset || subset.length === 0) {\n return true\n }\n\n // If there's no superset ordering but subset requires ordering, not satisfied\n if (!superset || superset.length === 0) {\n return false\n }\n\n // Check if subset is a prefix of superset with matching expressions and compare options\n if (subset.length > superset.length) {\n return false\n }\n\n for (let i = 0; i < subset.length; i++) {\n const subClause = subset[i]!\n const superClause = superset[i]!\n\n // Check if expressions match\n if (!areExpressionsEqual(subClause.expression, superClause.expression)) {\n return false\n }\n\n // Check if compare options match\n if (\n !areCompareOptionsEqual(\n subClause.compareOptions,\n superClause.compareOptions,\n )\n ) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Check if one limit is a subset of another.\n * Returns true if the subset limit requirements are satisfied by the superset limit.\n *\n * Note: This function does NOT consider offset. For offset-aware subset checking,\n * use `isOffsetLimitSubset` instead.\n *\n * @example\n * isLimitSubset(10, 20) // true (requesting 10 items when 20 are available)\n * isLimitSubset(20, 10) // false (requesting 20 items when only 10 are available)\n * isLimitSubset(10, undefined) // true (requesting 10 items when unlimited are available)\n *\n * @param subset - The limit requirement to check\n * @param superset - The limit that might satisfy the requirement\n * @returns true if subset is satisfied by superset\n */\nexport function isLimitSubset(\n subset: number | undefined,\n superset: number | undefined,\n): boolean {\n // Unlimited superset satisfies any limit requirement\n if (superset === undefined) {\n return true\n }\n\n // If requesting all data (no limit), we need unlimited data to satisfy it\n // But we know superset is not unlimited so we return false\n if (subset === undefined) {\n return false\n }\n\n // Otherwise, subset must be less than or equal to superset\n return subset <= superset\n}\n\n/**\n * Check if one offset+limit range is a subset of another.\n * Returns true if the subset range is fully contained within the superset range.\n *\n * A query with `{limit: 10, offset: 0}` loads rows [0, 10).\n * A query with `{limit: 10, offset: 20}` loads rows [20, 30).\n *\n * For subset to be satisfied by superset:\n * - Superset must start at or before subset (superset.offset <= subset.offset)\n * - Superset must end at or after subset (superset.offset + superset.limit >= subset.offset + subset.limit)\n *\n * @example\n * isOffsetLimitSubset({ offset: 0, limit: 5 }, { offset: 0, limit: 10 }) // true\n * isOffsetLimitSubset({ offset: 5, limit: 5 }, { offset: 0, limit: 10 }) // true (rows 5-9 within 0-9)\n * isOffsetLimitSubset({ offset: 5, limit: 10 }, { offset: 0, limit: 10 }) // false (rows 5-14 exceed 0-9)\n * isOffsetLimitSubset({ offset: 20, limit: 10 }, { offset: 0, limit: 10 }) // false (rows 20-29 outside 0-9)\n *\n * @param subset - The offset+limit requirements to check\n * @param superset - The offset+limit that might satisfy the requirements\n * @returns true if subset range is fully contained within superset range\n */\nexport function isOffsetLimitSubset(\n subset: { offset?: number; limit?: number },\n superset: { offset?: number; limit?: number },\n): boolean {\n const subsetOffset = subset.offset ?? 0\n const supersetOffset = superset.offset ?? 0\n\n // Superset must start at or before subset\n if (supersetOffset > subsetOffset) {\n return false\n }\n\n // If superset is unlimited, it covers everything from its offset onwards\n if (superset.limit === undefined) {\n return true\n }\n\n // If subset is unlimited but superset has a limit, subset can't be satisfied\n if (subset.limit === undefined) {\n return false\n }\n\n // Both have limits - check if subset range is within superset range\n const subsetEnd = subsetOffset + subset.limit\n const supersetEnd = supersetOffset + superset.limit\n\n return subsetEnd <= supersetEnd\n}\n\n/**\n * Check if one predicate (where + orderBy + limit + offset) is a subset of another.\n * Returns true if all aspects of the subset predicate are satisfied by the superset.\n *\n * @example\n * isPredicateSubset(\n * { where: gt(ref('age'), val(20)), limit: 10 },\n * { where: gt(ref('age'), val(10)), limit: 20 }\n * ) // true\n *\n * @param subset - The predicate requirements to check\n * @param superset - The predicate that might satisfy the requirements\n * @returns true if subset is satisfied by superset\n */\nexport function isPredicateSubset(\n subset: LoadSubsetOptions,\n superset: LoadSubsetOptions,\n): boolean {\n // When the superset has a limit, we can only determine subset relationship\n // if the where clauses are equal (not just subset relationship).\n //\n // This is because a limited query only loads a portion of the matching rows.\n // A more restrictive where clause might require rows outside that portion.\n //\n // Example: superset = {where: undefined, limit: 10, orderBy: desc}\n // subset = {where: LIKE 'search%', limit: 10, orderBy: desc}\n // The top 10 items matching 'search%' might include items outside the overall top 10.\n //\n // However, if the where clauses are equal, then the subset relationship can\n // be determined by orderBy, limit, and offset:\n // Example: superset = {where: status='active', limit: 10, offset: 0, orderBy: desc}\n // subset = {where: status='active', limit: 5, offset: 0, orderBy: desc}\n // The top 5 active items ARE contained in the top 10 active items.\n if (superset.limit !== undefined) {\n // For limited supersets, where clauses must be equal\n if (!areWhereClausesEqual(subset.where, superset.where)) {\n return false\n }\n return (\n isOrderBySubset(subset.orderBy, superset.orderBy) &&\n isOffsetLimitSubset(subset, superset)\n )\n }\n\n // For unlimited supersets, use the normal subset logic\n // Still need to consider offset - an unlimited query with offset only covers\n // rows from that offset onwards\n return (\n isWhereSubset(subset.where, superset.where) &&\n isOrderBySubset(subset.orderBy, superset.orderBy) &&\n isOffsetLimitSubset(subset, superset)\n )\n}\n\n/**\n * Check if two where clauses are structurally equal.\n * Used for limited query subset checks where subset relationship isn't sufficient.\n */\nfunction areWhereClausesEqual(\n a: BasicExpression<boolean> | undefined,\n b: BasicExpression<boolean> | undefined,\n): boolean {\n if (a === undefined && b === undefined) {\n return true\n }\n if (a === undefined || b === undefined) {\n return false\n }\n return areExpressionsEqual(a, b)\n}\n\n// ============================================================================\n// Helper functions\n// ============================================================================\n\n/**\n * Find common conditions between two predicates.\n * Returns an array of conditions that appear in both predicates.\n */\nfunction findCommonConditions(\n predicate1: BasicExpression<boolean>,\n predicate2: BasicExpression<boolean>,\n): Array<BasicExpression<boolean>> {\n const conditions1 = extractAllConditions(predicate1)\n const conditions2 = extractAllConditions(predicate2)\n\n const common: Array<BasicExpression<boolean>> = []\n\n for (const cond1 of conditions1) {\n for (const cond2 of conditions2) {\n if (areExpressionsEqual(cond1, cond2)) {\n // Avoid duplicates\n if (!common.some((c) => areExpressionsEqual(c, cond1))) {\n common.push(cond1)\n }\n break\n }\n }\n }\n\n return common\n}\n\n/**\n * Extract all individual conditions from a predicate, flattening AND operations.\n */\nfunction extractAllConditions(\n predicate: BasicExpression<boolean>,\n): Array<BasicExpression<boolean>> {\n if (predicate.type === `func` && predicate.name === `and`) {\n const conditions: Array<BasicExpression<boolean>> = []\n for (const arg of predicate.args) {\n conditions.push(...extractAllConditions(arg as BasicExpression<boolean>))\n }\n return conditions\n }\n\n return [predicate]\n}\n\n/**\n * Remove specified conditions from a predicate.\n * Returns the predicate with the specified conditions removed, or undefined if all conditions are removed.\n */\nfunction removeConditions(\n predicate: BasicExpression<boolean>,\n conditionsToRemove: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> | undefined {\n if (predicate.type === `func` && predicate.name === `and`) {\n const remainingArgs = predicate.args.filter(\n (arg) =>\n !conditionsToRemove.some((cond) =>\n areExpressionsEqual(arg as BasicExpression<boolean>, cond),\n ),\n )\n\n if (remainingArgs.length === 0) {\n return undefined\n } else if (remainingArgs.length === 1) {\n return remainingArgs[0]!\n } else {\n return {\n type: `func`,\n name: `and`,\n args: remainingArgs,\n } as BasicExpression<boolean>\n }\n }\n\n // For non-AND predicates, don't remove anything\n return predicate\n}\n\n/**\n * Combine multiple conditions into a single predicate using AND logic.\n * Flattens nested AND operations to avoid unnecessary nesting.\n */\nfunction combineConditions(\n conditions: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> {\n if (conditions.length === 0) {\n return { type: `val`, value: true } as BasicExpression<boolean>\n } else if (conditions.length === 1) {\n return conditions[0]!\n } else {\n // Flatten all conditions, including those that are already AND operations\n const flattenedConditions: Array<BasicExpression<boolean>> = []\n\n for (const condition of conditions) {\n if (condition.type === `func` && condition.name === `and`) {\n // Flatten nested AND operations\n flattenedConditions.push(...condition.args)\n } else {\n flattenedConditions.push(condition)\n }\n }\n\n if (flattenedConditions.length === 1) {\n return flattenedConditions[0]!\n } else {\n return {\n type: `func`,\n name: `and`,\n args: flattenedConditions,\n } as BasicExpression<boolean>\n }\n }\n}\n\n/**\n * Find a predicate with a specific operator and value\n */\nfunction findPredicateWithOperator(\n predicates: Array<BasicExpression<boolean>>,\n operator: string,\n value: any,\n): BasicExpression<boolean> | undefined {\n return predicates.find((p) => {\n if (p.type === `func`) {\n const f = p as Func\n const field = extractComparisonField(f)\n return f.name === operator && field && areValuesEqual(field.value, value)\n }\n return false\n })\n}\n\nfunction areExpressionsEqual(a: BasicExpression, b: BasicExpression): boolean {\n if (a.type !== b.type) {\n return false\n }\n\n if (a.type === `val` && b.type === `val`) {\n return areValuesEqual(a.value, b.value)\n }\n\n if (a.type === `ref` && b.type === `ref`) {\n return areRefsEqual(a, b)\n }\n\n if (a.type === `func` && b.type === `func`) {\n const aFunc = a\n const bFunc = b\n if (aFunc.name !== bFunc.name) {\n return false\n }\n if (aFunc.args.length !== bFunc.args.length) {\n return false\n }\n return aFunc.args.every((arg, i) =>\n areExpressionsEqual(arg, bFunc.args[i]!),\n )\n }\n\n return false\n}\n\nfunction areValuesEqual(a: any, b: any): boolean {\n // Simple equality check - could be enhanced for deep object comparison\n if (a === b) {\n return true\n }\n\n // Handle NaN\n if (typeof a === `number` && typeof b === `number` && isNaN(a) && isNaN(b)) {\n return true\n }\n\n // Handle Date objects\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime()\n }\n\n // For arrays and objects, use reference equality\n // (In practice, we don't need deep equality for these cases -\n // same object reference means same value for our use case)\n if (\n typeof a === `object` &&\n typeof b === `object` &&\n a !== null &&\n b !== null\n ) {\n return a === b\n }\n\n return false\n}\n\nfunction areRefsEqual(a: PropRef, b: PropRef): boolean {\n if (a.path.length !== b.path.length) {\n return false\n }\n return a.path.every((segment, i) => segment === b.path[i])\n}\n\n/**\n * Check if a value is a primitive (string, number, boolean, null, undefined)\n * Primitives can use Set for fast lookups\n */\nfunction isPrimitive(value: any): boolean {\n return (\n value === null ||\n value === undefined ||\n typeof value === `string` ||\n typeof value === `number` ||\n typeof value === `boolean`\n )\n}\n\n/**\n * Check if all values in an array are primitives\n */\nfunction areAllPrimitives(values: Array<any>): boolean {\n return values.every(isPrimitive)\n}\n\n/**\n * Check if a value is in an array, with optional pre-built Set for optimization.\n * The primitiveSet is cached in InField during extraction and reused for all lookups.\n */\nfunction arrayIncludesWithSet(\n array: Array<any>,\n value: any,\n primitiveSet: Set<any> | null,\n arrayIsAllPrimitives?: boolean,\n): boolean {\n // Fast path: use pre-built Set for O(1) lookup\n if (primitiveSet) {\n // Skip isPrimitive check if we know the value must be primitive for a match\n // (if array is all primitives, only primitives can match)\n if (arrayIsAllPrimitives || isPrimitive(value)) {\n return primitiveSet.has(value)\n }\n return false // Non-primitive can't be in primitive-only set\n }\n\n // Fallback: use areValuesEqual for Dates and objects\n return array.some((v) => areValuesEqual(v, value))\n}\n\n/**\n * Get the maximum of two values, handling both numbers and Dates\n */\nfunction maxValue(a: any, b: any): any {\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() > b.getTime() ? a : b\n }\n return Math.max(a, b)\n}\n\n/**\n * Get the minimum of two values, handling both numbers and Dates\n */\nfunction minValue(a: any, b: any): any {\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() < b.getTime() ? a : b\n }\n return Math.min(a, b)\n}\n\nfunction areCompareOptionsEqual(\n a: { direction?: `asc` | `desc`; [key: string]: any },\n b: { direction?: `asc` | `desc`; [key: string]: any },\n): boolean {\n // For now, just compare direction - could be enhanced for other options\n return a.direction === b.direction\n}\n\ninterface ComparisonField {\n ref: PropRef\n value: any\n}\n\nfunction extractComparisonField(func: Func): ComparisonField | null {\n // Handle comparison operators: eq, gt, gte, lt, lte\n if ([`eq`, `gt`, `gte`, `lt`, `lte`].includes(func.name)) {\n // Assume first arg is ref, second is value\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (firstArg?.type === `ref` && secondArg?.type === `val`) {\n return {\n ref: firstArg,\n value: secondArg.value,\n }\n }\n }\n\n return null\n}\n\nfunction extractEqualityField(func: Func): ComparisonField | null {\n if (func.name === `eq`) {\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (firstArg?.type === `ref` && secondArg?.type === `val`) {\n return {\n ref: firstArg,\n value: secondArg.value,\n }\n }\n }\n return null\n}\n\ninterface InField {\n ref: PropRef\n values: Array<any>\n // Cached optimization data (computed once, reused many times)\n areAllPrimitives?: boolean\n primitiveSet?: Set<any> | null\n}\n\nfunction extractInField(func: Func): InField | null {\n if (func.name === `in`) {\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (\n firstArg?.type === `ref` &&\n secondArg?.type === `val` &&\n Array.isArray(secondArg.value)\n ) {\n let values = secondArg.value\n // Precompute optimization metadata once\n const allPrimitives = areAllPrimitives(values)\n let primitiveSet: Set<any> | null = null\n\n if (allPrimitives && values.length > 10) {\n // Build Set and dedupe values at the same time\n primitiveSet = new Set(values)\n // If we found duplicates, use the deduped array going forward\n if (primitiveSet.size < values.length) {\n values = Array.from(primitiveSet)\n }\n }\n\n return {\n ref: firstArg,\n values,\n areAllPrimitives: allPrimitives,\n primitiveSet,\n }\n }\n }\n return null\n}\n\nfunction isComparisonSubset(\n subsetFunc: Func,\n subsetValue: any,\n supersetFunc: Func,\n supersetValue: any,\n): boolean {\n const subOp = subsetFunc.name\n const superOp = supersetFunc.name\n\n // Handle same operator\n if (subOp === superOp) {\n if (subOp === `eq`) {\n // field = X is subset of field = X only\n // Fast path: primitives can use strict equality\n if (isPrimitive(subsetValue) && isPrimitive(supersetValue)) {\n return subsetValue === supersetValue\n }\n return areValuesEqual(subsetValue, supersetValue)\n } else if (subOp === `gt`) {\n // field > 20 is subset of field > 10 if 20 > 10\n return subsetValue >= supersetValue\n } else if (subOp === `gte`) {\n // field >= 20 is subset of field >= 10 if 20 >= 10\n return subsetValue >= supersetValue\n } else if (subOp === `lt`) {\n // field < 10 is subset of field < 20 if 10 <= 20\n return subsetValue <= supersetValue\n } else if (subOp === `lte`) {\n // field <= 10 is subset of field <= 20 if 10 <= 20\n return subsetValue <= supersetValue\n }\n }\n\n // Handle different operators on same field\n // eq vs gt/gte: field = 15 is subset of field > 10 if 15 > 10\n if (subOp === `eq` && superOp === `gt`) {\n return subsetValue > supersetValue\n }\n if (subOp === `eq` && superOp === `gte`) {\n return subsetValue >= supersetValue\n }\n if (subOp === `eq` && superOp === `lt`) {\n return subsetValue < supersetValue\n }\n if (subOp === `eq` && superOp === `lte`) {\n return subsetValue <= supersetValue\n }\n\n // gt/gte vs gte/gt\n if (subOp === `gt` && superOp === `gte`) {\n // field > 10 is subset of field >= 10 if 10 >= 10 (always true for same value)\n return subsetValue >= supersetValue\n }\n if (subOp === `gte` && superOp === `gt`) {\n // field >= 11 is subset of field > 10 if 11 > 10\n return subsetValue > supersetValue\n }\n\n // lt/lte vs lte/lt\n if (subOp === `lt` && superOp === `lte`) {\n // field < 10 is subset of field <= 10 if 10 <= 10\n return subsetValue <= supersetValue\n }\n if (subOp === `lte` && superOp === `lt`) {\n // field <= 9 is subset of field < 10 if 9 < 10\n return subsetValue < supersetValue\n }\n\n return false\n}\n\nfunction groupPredicatesByField(\n predicates: Array<BasicExpression<boolean>>,\n): Map<string | null, Array<BasicExpression<boolean>>> {\n const groups = new Map<string | null, Array<BasicExpression<boolean>>>()\n\n for (const pred of predicates) {\n let fieldKey: string | null = null\n\n if (pred.type === `func`) {\n const func = pred as Func\n const field =\n extractComparisonField(func) ||\n extractEqualityField(func) ||\n extractInField(func)\n if (field) {\n fieldKey = field.ref.path.join(`.`)\n }\n }\n\n const group = groups.get(fieldKey) || []\n group.push(pred)\n groups.set(fieldKey, group)\n }\n\n return groups\n}\n\nfunction unionSameFieldPredicates(\n predicates: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> | null {\n if (predicates.length === 1) {\n return predicates[0]!\n }\n\n // Try to extract range constraints\n let maxGt: number | null = null\n let maxGte: number | null = null\n let minLt: number | null = null\n let minLte: number | null = null\n const eqValues: Set<any> = new Set()\n const inValues: Set<any> = new Set()\n const otherPredicates: Array<BasicExpression<boolean>> = []\n\n for (const pred of predicates) {\n if (pred.type === `func`) {\n const func = pred as Func\n const field = extractComparisonField(func)\n\n if (field) {\n const value = field.value\n if (func.name === `gt`) {\n maxGt = maxGt === null ? value : minValue(maxGt, value)\n } else if (func.name === `gte`) {\n maxGte = maxGte === null ? value : minValue(maxGte, value)\n } else if (func.name === `lt`) {\n minLt = minLt === null ? value : maxValue(minLt, value)\n } else if (func.name === `lte`) {\n minLte = minLte === null ? value : maxValue(minLte, value)\n } else if (func.name === `eq`) {\n eqValues.add(value)\n } else {\n otherPredicates.push(pred)\n }\n } else {\n const inField = extractInField(func)\n if (inField) {\n for (const val of inField.values) {\n inValues.add(val)\n }\n } else {\n otherPredicates.push(pred)\n }\n }\n } else {\n otherPredicates.push(pred)\n }\n }\n\n // If we have multiple equality values, combine into IN\n if (eqValues.size > 1 || (eqValues.size > 0 && inValues.size > 0)) {\n const allValues = [...eqValues, ...inValues]\n const ref = predicates.find((p) => {\n if (p.type === `func`) {\n const field =\n extractComparisonField(p as Func) || extractInField(p as Func)\n return field !== null\n }\n return false\n })\n\n if (ref && ref.type === `func`) {\n const field =\n extractComparisonField(ref as Func) || extractInField(ref as Func)\n if (field) {\n return {\n type: `func`,\n name: `in`,\n args: [\n field.ref,\n { type: `val`, value: allValues } as BasicExpression,\n ],\n } as BasicExpression<boolean>\n }\n }\n }\n\n // Build the least restrictive range\n const result: Array<BasicExpression<boolean>> = []\n\n // Choose the least restrictive lower bound\n if (maxGt !== null && maxGte !== null) {\n // Take the smaller one (less restrictive)\n const pred =\n maxGte <= maxGt\n ? findPredicateWithOperator(predicates, `gte`, maxGte)\n : findPredicateWithOperator(predicates, `gt`, maxGt)\n if (pred) result.push(pred)\n } else if (maxGt !== null) {\n const pred = findPredicateWithOperator(predicates, `gt`, maxGt)\n if (pred) result.push(pred)\n } else if (maxGte !== null) {\n const pred = findPredicateWithOperator(predicates, `gte`, maxGte)\n if (pred) result.push(pred)\n }\n\n // Choose the least restrictive upper bound\n if (minLt !== null && minLte !== null) {\n const pred =\n minLte >= minLt\n ? findPredicateWithOperator(predicates, `lte`, minLte)\n : findPredicateWithOperator(predicates, `lt`, minLt)\n if (pred) result.push(pred)\n } else if (minLt !== null) {\n const pred = findPredicateWithOperator(predicates, `lt`, minLt)\n if (pred) result.push(pred)\n } else if (minLte !== null) {\n const pred = findPredicateWithOperator(predicates, `lte`, minLte)\n if (pred) result.push(pred)\n }\n\n // Add single eq value\n if (eqValues.size === 1 && inValues.size === 0) {\n const pred = findPredicateWithOperator(predicates, `eq`, [...eqValues][0])\n if (pred) result.push(pred)\n }\n\n // Add IN if only IN values\n if (eqValues.size === 0 && inValues.size > 0) {\n result.push(\n predicates.find((p) => {\n if (p.type === `func`) {\n return (p as Func).name === `in`\n }\n return false\n })!,\n )\n }\n\n // Add other predicates\n result.push(...otherPredicates)\n\n if (result.length === 0) {\n return { type: `val`, value: true } as BasicExpression<boolean>\n }\n\n if (result.length === 1) {\n return result[0]!\n }\n\n return {\n type: `func`,\n name: `or`,\n args: result,\n } as BasicExpression<boolean>\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"subset-dedupe.js","sources":["../../../src/query/subset-dedupe.ts"],"sourcesContent":["import {\n isPredicateSubset,\n isWhereSubset,\n minusWherePredicates,\n unionWherePredicates,\n} from \"./predicate-utils.js\"\nimport type { BasicExpression } from \"./ir.js\"\nimport type { LoadSubsetOptions } from \"../types.js\"\n\n/**\n * Deduplicated wrapper for a loadSubset function.\n * Tracks what data has been loaded and avoids redundant calls by applying\n * subset logic to predicates.\n *\n * @param opts - The options for the DeduplicatedLoadSubset\n * @param opts.loadSubset - The underlying loadSubset function to wrap\n * @param opts.onDeduplicate - An optional callback function that is invoked when a loadSubset call is deduplicated.\n * If the call is deduplicated because the requested data is being loaded by an inflight request,\n * then this callback is invoked when the inflight request completes successfully and the data is fully loaded.\n * This callback is useful if you need to track rows per query, in which case you can't ignore deduplicated calls\n * because you need to know which rows were loaded for each query.\n * @example\n * const dedupe = new DeduplicatedLoadSubset({ loadSubset: myLoadSubset, onDeduplicate: (opts) => console.log(`Call was deduplicated:`, opts) })\n *\n * // First call - fetches data\n * await dedupe.loadSubset({ where: gt(ref('age'), val(10)) })\n *\n * // Second call - subset of first, returns true immediately\n * await dedupe.loadSubset({ where: gt(ref('age'), val(20)) })\n *\n * // Clear state to start fresh\n * dedupe.reset()\n */\nexport class DeduplicatedLoadSubset {\n // The underlying loadSubset function to wrap\n private readonly _loadSubset: (\n options: LoadSubsetOptions\n ) => true | Promise<void>\n\n // An optional callback function that is invoked when a loadSubset call is deduplicated.\n private readonly onDeduplicate:\n | ((options: LoadSubsetOptions) => void)\n | undefined\n\n // Combined where predicate for all unlimited calls (no limit)\n private unlimitedWhere: BasicExpression<boolean> | undefined = undefined\n\n // Flag to track if we've loaded all data (unlimited call with no where clause)\n private hasLoadedAllData = false\n\n // List of all limited calls (with limit, possibly with orderBy)\n // We clone options before storing to prevent mutation of stored predicates\n private limitedCalls: Array<LoadSubsetOptions> = []\n\n // Track in-flight calls to prevent concurrent duplicate requests\n // We store both the options and the promise so we can apply subset logic\n private inflightCalls: Array<{\n options: LoadSubsetOptions\n promise: Promise<void>\n }> = []\n\n // Generation counter to invalidate in-flight requests after reset()\n // When reset() is called, this increments, and any in-flight completion handlers\n // check if their captured generation matches before updating tracking state\n private generation = 0\n\n constructor(opts: {\n loadSubset: (options: LoadSubsetOptions) => true | Promise<void>\n onDeduplicate?: (options: LoadSubsetOptions) => void\n }) {\n this._loadSubset = opts.loadSubset\n this.onDeduplicate = opts.onDeduplicate\n }\n\n /**\n * Load a subset of data, with automatic deduplication based on previously\n * loaded predicates and in-flight requests.\n *\n * This method is auto-bound, so it can be safely passed as a callback without\n * losing its `this` context (e.g., `loadSubset: dedupe.loadSubset` in a sync config).\n *\n * @param options - The predicate options (where, orderBy, limit)\n * @returns true if data is already loaded, or a Promise that resolves when data is loaded\n */\n loadSubset = (options: LoadSubsetOptions): true | Promise<void> => {\n // If we've loaded all data, everything is covered\n if (this.hasLoadedAllData) {\n this.onDeduplicate?.(options)\n return true\n }\n\n // Check against unlimited combined predicate\n // If we've loaded all data matching a where clause, we don't need to refetch subsets\n if (this.unlimitedWhere !== undefined && options.where !== undefined) {\n if (isWhereSubset(options.where, this.unlimitedWhere)) {\n this.onDeduplicate?.(options)\n return true // Data already loaded via unlimited call\n }\n }\n\n // Check against limited calls\n if (options.limit !== undefined) {\n const alreadyLoaded = this.limitedCalls.some((loaded) =>\n isPredicateSubset(options, loaded)\n )\n\n if (alreadyLoaded) {\n this.onDeduplicate?.(options)\n return true // Already loaded\n }\n }\n\n // Check against in-flight calls using the same subset logic as resolved calls\n // This prevents duplicate requests when concurrent calls have subset relationships\n const matchingInflight = this.inflightCalls.find((inflight) =>\n isPredicateSubset(options, inflight.options)\n )\n\n if (matchingInflight !== undefined) {\n // An in-flight call will load data that covers this request\n // Return the same promise so this caller waits for the data to load\n // The in-flight promise already handles tracking updates when it completes\n const prom = matchingInflight.promise\n // Call `onDeduplicate` when the inflight request has loaded the data\n prom.then(() => this.onDeduplicate?.(options)).catch() // ignore errors\n return prom\n }\n\n // Not fully covered by existing data\n // Compute the subset of data that is not covered by the existing data\n // such that we only have to load that subset of missing data\n const clonedOptions = cloneOptions(options)\n if (this.unlimitedWhere !== undefined && options.limit === undefined) {\n // Compute difference to get only the missing data\n // We can only do this for unlimited queries\n // and we can only remove data that was loaded from unlimited queries\n // because with limited queries we have no way to express that we already loaded part of the matching data\n clonedOptions.where =\n minusWherePredicates(clonedOptions.where, this.unlimitedWhere) ??\n clonedOptions.where\n }\n\n // Call underlying loadSubset to load the missing data\n const resultPromise = this._loadSubset(clonedOptions)\n\n // Handle both sync (true) and async (Promise<void>) return values\n if (resultPromise === true) {\n // Sync return - update tracking synchronously\n // Clone options before storing to protect against caller mutation\n this.updateTracking(clonedOptions)\n return true\n } else {\n // Async return - track the promise and update tracking after it resolves\n\n // Capture the current generation - this lets us detect if reset() was called\n // while this request was in-flight, so we can skip updating tracking state\n const capturedGeneration = this.generation\n\n // We need to create a reference to the in-flight entry so we can remove it later\n const inflightEntry = {\n options: clonedOptions, // Store cloned options for subset matching\n promise: resultPromise\n .then((result) => {\n // Only update tracking if this request is still from the current generation\n // If reset() was called, the generation will have incremented and we should\n // not repopulate the state that was just cleared\n if (capturedGeneration === this.generation) {\n // Use the cloned options that we captured before any caller mutations\n // This ensures we track exactly what was loaded, not what the caller changed\n this.updateTracking(clonedOptions)\n }\n return result\n })\n .finally(() => {\n // Always remove from in-flight array on completion OR rejection\n // This ensures failed requests can be retried instead of being cached forever\n const index = this.inflightCalls.indexOf(inflightEntry)\n if (index !== -1) {\n this.inflightCalls.splice(index, 1)\n }\n }),\n }\n\n // Store the in-flight entry so concurrent subset calls can wait for it\n this.inflightCalls.push(inflightEntry)\n return inflightEntry.promise\n }\n }\n\n /**\n * Reset all tracking state.\n * Clears the history of loaded predicates and in-flight calls.\n * Use this when you want to start fresh, for example after clearing the underlying data store.\n *\n * Note: Any in-flight requests will still complete, but they will not update the tracking\n * state after the reset. This prevents old requests from repopulating cleared state.\n */\n reset(): void {\n this.unlimitedWhere = undefined\n this.hasLoadedAllData = false\n this.limitedCalls = []\n this.inflightCalls = []\n // Increment generation to invalidate any in-flight completion handlers\n // This ensures requests that were started before reset() don't repopulate the state\n this.generation++\n }\n\n private updateTracking(options: LoadSubsetOptions): void {\n // Update tracking based on whether this was a limited or unlimited call\n if (options.limit === undefined) {\n // Unlimited call - update combined where predicate\n // We ignore orderBy for unlimited calls as mentioned in requirements\n if (options.where === undefined) {\n // No where clause = all data loaded\n this.hasLoadedAllData = true\n this.unlimitedWhere = undefined\n this.limitedCalls = []\n this.inflightCalls = []\n } else if (this.unlimitedWhere === undefined) {\n this.unlimitedWhere = options.where\n } else {\n this.unlimitedWhere = unionWherePredicates([\n this.unlimitedWhere,\n options.where,\n ])\n }\n } else {\n // Limited call - add to list for future subset checks\n // Options are already cloned by caller to prevent mutation issues\n this.limitedCalls.push(options)\n }\n }\n}\n\n/**\n * Clones a LoadSubsetOptions object to prevent mutation of stored predicates.\n * This is crucial because callers often reuse the same options object and mutate\n * properties like limit or where between calls. Without cloning, our stored history\n * would reflect the mutated values rather than what was actually loaded.\n */\nexport function cloneOptions(options: LoadSubsetOptions): LoadSubsetOptions {\n return { ...options }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subset-dedupe.js","sources":["../../../src/query/subset-dedupe.ts"],"sourcesContent":["import {\n isPredicateSubset,\n isWhereSubset,\n minusWherePredicates,\n unionWherePredicates,\n} from './predicate-utils.js'\nimport type { BasicExpression } from './ir.js'\nimport type { LoadSubsetOptions } from '../types.js'\n\n/**\n * Deduplicated wrapper for a loadSubset function.\n * Tracks what data has been loaded and avoids redundant calls by applying\n * subset logic to predicates.\n *\n * @param opts - The options for the DeduplicatedLoadSubset\n * @param opts.loadSubset - The underlying loadSubset function to wrap\n * @param opts.onDeduplicate - An optional callback function that is invoked when a loadSubset call is deduplicated.\n * If the call is deduplicated because the requested data is being loaded by an inflight request,\n * then this callback is invoked when the inflight request completes successfully and the data is fully loaded.\n * This callback is useful if you need to track rows per query, in which case you can't ignore deduplicated calls\n * because you need to know which rows were loaded for each query.\n * @example\n * const dedupe = new DeduplicatedLoadSubset({ loadSubset: myLoadSubset, onDeduplicate: (opts) => console.log(`Call was deduplicated:`, opts) })\n *\n * // First call - fetches data\n * await dedupe.loadSubset({ where: gt(ref('age'), val(10)) })\n *\n * // Second call - subset of first, returns true immediately\n * await dedupe.loadSubset({ where: gt(ref('age'), val(20)) })\n *\n * // Clear state to start fresh\n * dedupe.reset()\n */\nexport class DeduplicatedLoadSubset {\n // The underlying loadSubset function to wrap\n private readonly _loadSubset: (\n options: LoadSubsetOptions,\n ) => true | Promise<void>\n\n // An optional callback function that is invoked when a loadSubset call is deduplicated.\n private readonly onDeduplicate:\n | ((options: LoadSubsetOptions) => void)\n | undefined\n\n // Combined where predicate for all unlimited calls (no limit)\n private unlimitedWhere: BasicExpression<boolean> | undefined = undefined\n\n // Flag to track if we've loaded all data (unlimited call with no where clause)\n private hasLoadedAllData = false\n\n // List of all limited calls (with limit, possibly with orderBy)\n // We clone options before storing to prevent mutation of stored predicates\n private limitedCalls: Array<LoadSubsetOptions> = []\n\n // Track in-flight calls to prevent concurrent duplicate requests\n // We store both the options and the promise so we can apply subset logic\n private inflightCalls: Array<{\n options: LoadSubsetOptions\n promise: Promise<void>\n }> = []\n\n // Generation counter to invalidate in-flight requests after reset()\n // When reset() is called, this increments, and any in-flight completion handlers\n // check if their captured generation matches before updating tracking state\n private generation = 0\n\n constructor(opts: {\n loadSubset: (options: LoadSubsetOptions) => true | Promise<void>\n onDeduplicate?: (options: LoadSubsetOptions) => void\n }) {\n this._loadSubset = opts.loadSubset\n this.onDeduplicate = opts.onDeduplicate\n }\n\n /**\n * Load a subset of data, with automatic deduplication based on previously\n * loaded predicates and in-flight requests.\n *\n * This method is auto-bound, so it can be safely passed as a callback without\n * losing its `this` context (e.g., `loadSubset: dedupe.loadSubset` in a sync config).\n *\n * @param options - The predicate options (where, orderBy, limit)\n * @returns true if data is already loaded, or a Promise that resolves when data is loaded\n */\n loadSubset = (options: LoadSubsetOptions): true | Promise<void> => {\n // If we've loaded all data, everything is covered\n if (this.hasLoadedAllData) {\n this.onDeduplicate?.(options)\n return true\n }\n\n // Check against unlimited combined predicate\n // If we've loaded all data matching a where clause, we don't need to refetch subsets\n if (this.unlimitedWhere !== undefined && options.where !== undefined) {\n if (isWhereSubset(options.where, this.unlimitedWhere)) {\n this.onDeduplicate?.(options)\n return true // Data already loaded via unlimited call\n }\n }\n\n // Check against limited calls\n if (options.limit !== undefined) {\n const alreadyLoaded = this.limitedCalls.some((loaded) =>\n isPredicateSubset(options, loaded),\n )\n\n if (alreadyLoaded) {\n this.onDeduplicate?.(options)\n return true // Already loaded\n }\n }\n\n // Check against in-flight calls using the same subset logic as resolved calls\n // This prevents duplicate requests when concurrent calls have subset relationships\n const matchingInflight = this.inflightCalls.find((inflight) =>\n isPredicateSubset(options, inflight.options),\n )\n\n if (matchingInflight !== undefined) {\n // An in-flight call will load data that covers this request\n // Return the same promise so this caller waits for the data to load\n // The in-flight promise already handles tracking updates when it completes\n const prom = matchingInflight.promise\n // Call `onDeduplicate` when the inflight request has loaded the data\n prom.then(() => this.onDeduplicate?.(options)).catch() // ignore errors\n return prom\n }\n\n // Not fully covered by existing data\n // Compute the subset of data that is not covered by the existing data\n // such that we only have to load that subset of missing data\n const clonedOptions = cloneOptions(options)\n if (this.unlimitedWhere !== undefined && options.limit === undefined) {\n // Compute difference to get only the missing data\n // We can only do this for unlimited queries\n // and we can only remove data that was loaded from unlimited queries\n // because with limited queries we have no way to express that we already loaded part of the matching data\n clonedOptions.where =\n minusWherePredicates(clonedOptions.where, this.unlimitedWhere) ??\n clonedOptions.where\n }\n\n // Call underlying loadSubset to load the missing data\n const resultPromise = this._loadSubset(clonedOptions)\n\n // Handle both sync (true) and async (Promise<void>) return values\n if (resultPromise === true) {\n // Sync return - update tracking synchronously\n // Clone options before storing to protect against caller mutation\n this.updateTracking(clonedOptions)\n return true\n } else {\n // Async return - track the promise and update tracking after it resolves\n\n // Capture the current generation - this lets us detect if reset() was called\n // while this request was in-flight, so we can skip updating tracking state\n const capturedGeneration = this.generation\n\n // We need to create a reference to the in-flight entry so we can remove it later\n const inflightEntry = {\n options: clonedOptions, // Store cloned options for subset matching\n promise: resultPromise\n .then((result) => {\n // Only update tracking if this request is still from the current generation\n // If reset() was called, the generation will have incremented and we should\n // not repopulate the state that was just cleared\n if (capturedGeneration === this.generation) {\n // Use the cloned options that we captured before any caller mutations\n // This ensures we track exactly what was loaded, not what the caller changed\n this.updateTracking(clonedOptions)\n }\n return result\n })\n .finally(() => {\n // Always remove from in-flight array on completion OR rejection\n // This ensures failed requests can be retried instead of being cached forever\n const index = this.inflightCalls.indexOf(inflightEntry)\n if (index !== -1) {\n this.inflightCalls.splice(index, 1)\n }\n }),\n }\n\n // Store the in-flight entry so concurrent subset calls can wait for it\n this.inflightCalls.push(inflightEntry)\n return inflightEntry.promise\n }\n }\n\n /**\n * Reset all tracking state.\n * Clears the history of loaded predicates and in-flight calls.\n * Use this when you want to start fresh, for example after clearing the underlying data store.\n *\n * Note: Any in-flight requests will still complete, but they will not update the tracking\n * state after the reset. This prevents old requests from repopulating cleared state.\n */\n reset(): void {\n this.unlimitedWhere = undefined\n this.hasLoadedAllData = false\n this.limitedCalls = []\n this.inflightCalls = []\n // Increment generation to invalidate any in-flight completion handlers\n // This ensures requests that were started before reset() don't repopulate the state\n this.generation++\n }\n\n private updateTracking(options: LoadSubsetOptions): void {\n // Update tracking based on whether this was a limited or unlimited call\n if (options.limit === undefined) {\n // Unlimited call - update combined where predicate\n // We ignore orderBy for unlimited calls as mentioned in requirements\n if (options.where === undefined) {\n // No where clause = all data loaded\n this.hasLoadedAllData = true\n this.unlimitedWhere = undefined\n this.limitedCalls = []\n this.inflightCalls = []\n } else if (this.unlimitedWhere === undefined) {\n this.unlimitedWhere = options.where\n } else {\n this.unlimitedWhere = unionWherePredicates([\n this.unlimitedWhere,\n options.where,\n ])\n }\n } else {\n // Limited call - add to list for future subset checks\n // Options are already cloned by caller to prevent mutation issues\n this.limitedCalls.push(options)\n }\n }\n}\n\n/**\n * Clones a LoadSubsetOptions object to prevent mutation of stored predicates.\n * This is crucial because callers often reuse the same options object and mutate\n * properties like limit or where between calls. Without cloning, our stored history\n * would reflect the mutated values rather than what was actually loaded.\n */\nexport function cloneOptions(options: LoadSubsetOptions): LoadSubsetOptions {\n return { ...options }\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.js","sources":["../../src/scheduler.ts"],"sourcesContent":["/**\n * Identifier used to scope scheduled work. Maps to a transaction id for live queries.\n */\nexport type SchedulerContextId = string | symbol\n\n/**\n * Options for {@link Scheduler.schedule}. Jobs are identified by `jobId` within a context\n * and may declare dependencies.\n */\ninterface ScheduleOptions {\n contextId?: SchedulerContextId\n jobId: unknown\n dependencies?: Iterable<unknown>\n run: () => void\n}\n\n/**\n * State per context. Queue preserves order, jobs hold run functions, dependencies track\n * prerequisites, and completed records which jobs have run during the current flush.\n */\ninterface SchedulerContextState {\n queue: Array<unknown>\n jobs: Map<unknown, () => void>\n dependencies: Map<unknown, Set<unknown>>\n completed: Set<unknown>\n}\n\ninterface PendingAwareJob {\n hasPendingGraphRun: (contextId: SchedulerContextId) => boolean\n}\n\nfunction isPendingAwareJob(dep: any): dep is PendingAwareJob {\n return (\n typeof dep === `object` &&\n dep !== null &&\n typeof dep.hasPendingGraphRun === `function`\n )\n}\n\n/**\n * Scoped scheduler that coalesces work by context and job.\n *\n * - **context** (e.g. transaction id) defines the batching boundary; work is queued until flushed.\n * - **job id** deduplicates work within a context; scheduling the same job replaces the previous run function.\n * - Without a context id, work executes immediately.\n *\n * Callers manage their own state; the scheduler only orchestrates execution order.\n */\nexport class Scheduler {\n private contexts = new Map<SchedulerContextId, SchedulerContextState>()\n private clearListeners = new Set<(contextId: SchedulerContextId) => void>()\n\n /**\n * Get or create the state bucket for a context.\n */\n private getOrCreateContext(\n contextId: SchedulerContextId\n ): SchedulerContextState {\n let context = this.contexts.get(contextId)\n if (!context) {\n context = {\n queue: [],\n jobs: new Map(),\n dependencies: new Map(),\n completed: new Set(),\n }\n this.contexts.set(contextId, context)\n }\n return context\n }\n\n /**\n * Schedule work. Without a context id, executes immediately.\n * Otherwise queues the job to be flushed once dependencies are satisfied.\n * Scheduling the same jobId again replaces the previous run function.\n */\n schedule({ contextId, jobId, dependencies, run }: ScheduleOptions): void {\n if (typeof contextId === `undefined`) {\n run()\n return\n }\n\n const context = this.getOrCreateContext(contextId)\n\n // If this is a new job, add it to the queue\n if (!context.jobs.has(jobId)) {\n context.queue.push(jobId)\n }\n\n // Store or replace the run function\n context.jobs.set(jobId, run)\n\n // Update dependencies\n if (dependencies) {\n const depSet = new Set<unknown>(dependencies)\n depSet.delete(jobId)\n context.dependencies.set(jobId, depSet)\n } else if (!context.dependencies.has(jobId)) {\n context.dependencies.set(jobId, new Set())\n }\n\n // Clear completion status since we're rescheduling\n context.completed.delete(jobId)\n }\n\n /**\n * Flush all queued work for a context. Jobs with unmet dependencies are retried.\n * Throws if a pass completes without running any job (dependency cycle).\n */\n flush(contextId: SchedulerContextId): void {\n const context = this.contexts.get(contextId)\n if (!context) return\n\n const { queue, jobs, dependencies, completed } = context\n\n while (queue.length > 0) {\n let ranThisPass = false\n const jobsThisPass = queue.length\n\n for (let i = 0; i < jobsThisPass; i++) {\n const jobId = queue.shift()!\n const run = jobs.get(jobId)\n if (!run) {\n dependencies.delete(jobId)\n completed.delete(jobId)\n continue\n }\n\n const deps = dependencies.get(jobId)\n let ready = !deps\n if (deps) {\n ready = true\n for (const dep of deps) {\n if (dep === jobId) continue\n\n const depHasPending =\n isPendingAwareJob(dep) && dep.hasPendingGraphRun(contextId)\n\n // Treat dependencies as blocking if the dep has a pending run in this\n // context or if it's enqueued and not yet complete. If the dep is\n // neither pending nor enqueued, consider it satisfied to avoid deadlocks\n // on lazy sources that never schedule work.\n if (\n (jobs.has(dep) && !completed.has(dep)) ||\n (!jobs.has(dep) && depHasPending)\n ) {\n ready = false\n break\n }\n }\n }\n\n if (ready) {\n jobs.delete(jobId)\n dependencies.delete(jobId)\n // Run the job. If it throws, we don't mark it complete, allowing the\n // error to propagate while maintaining scheduler state consistency.\n run()\n completed.add(jobId)\n ranThisPass = true\n } else {\n queue.push(jobId)\n }\n }\n\n if (!ranThisPass) {\n throw new Error(\n `Scheduler detected unresolved dependencies for context ${String(\n contextId\n )}.`\n )\n }\n }\n\n this.contexts.delete(contextId)\n }\n\n /**\n * Flush all contexts with pending work. Useful during tear-down.\n */\n flushAll(): void {\n for (const contextId of Array.from(this.contexts.keys())) {\n this.flush(contextId)\n }\n }\n\n /** Clear all scheduled jobs for a context. */\n clear(contextId: SchedulerContextId): void {\n this.contexts.delete(contextId)\n // Notify listeners that this context was cleared\n this.clearListeners.forEach((listener) => listener(contextId))\n }\n\n /** Register a listener to be notified when a context is cleared. */\n onClear(listener: (contextId: SchedulerContextId) => void): () => void {\n this.clearListeners.add(listener)\n return () => this.clearListeners.delete(listener)\n }\n\n /** Check if a context has pending jobs. */\n hasPendingJobs(contextId: SchedulerContextId): boolean {\n const context = this.contexts.get(contextId)\n return !!context && context.jobs.size > 0\n }\n\n /** Remove a single job from a context and clean up its dependencies. */\n clearJob(contextId: SchedulerContextId, jobId: unknown): void {\n const context = this.contexts.get(contextId)\n if (!context) return\n\n context.jobs.delete(jobId)\n context.dependencies.delete(jobId)\n context.completed.delete(jobId)\n context.queue = context.queue.filter((id) => id !== jobId)\n\n if (context.jobs.size === 0) {\n this.contexts.delete(contextId)\n }\n }\n}\n\nexport const transactionScopedScheduler = new Scheduler()\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scheduler.js","sources":["../../src/scheduler.ts"],"sourcesContent":["/**\n * Identifier used to scope scheduled work. Maps to a transaction id for live queries.\n */\nexport type SchedulerContextId = string | symbol\n\n/**\n * Options for {@link Scheduler.schedule}. Jobs are identified by `jobId` within a context\n * and may declare dependencies.\n */\ninterface ScheduleOptions {\n contextId?: SchedulerContextId\n jobId: unknown\n dependencies?: Iterable<unknown>\n run: () => void\n}\n\n/**\n * State per context. Queue preserves order, jobs hold run functions, dependencies track\n * prerequisites, and completed records which jobs have run during the current flush.\n */\ninterface SchedulerContextState {\n queue: Array<unknown>\n jobs: Map<unknown, () => void>\n dependencies: Map<unknown, Set<unknown>>\n completed: Set<unknown>\n}\n\ninterface PendingAwareJob {\n hasPendingGraphRun: (contextId: SchedulerContextId) => boolean\n}\n\nfunction isPendingAwareJob(dep: any): dep is PendingAwareJob {\n return (\n typeof dep === `object` &&\n dep !== null &&\n typeof dep.hasPendingGraphRun === `function`\n )\n}\n\n/**\n * Scoped scheduler that coalesces work by context and job.\n *\n * - **context** (e.g. transaction id) defines the batching boundary; work is queued until flushed.\n * - **job id** deduplicates work within a context; scheduling the same job replaces the previous run function.\n * - Without a context id, work executes immediately.\n *\n * Callers manage their own state; the scheduler only orchestrates execution order.\n */\nexport class Scheduler {\n private contexts = new Map<SchedulerContextId, SchedulerContextState>()\n private clearListeners = new Set<(contextId: SchedulerContextId) => void>()\n\n /**\n * Get or create the state bucket for a context.\n */\n private getOrCreateContext(\n contextId: SchedulerContextId,\n ): SchedulerContextState {\n let context = this.contexts.get(contextId)\n if (!context) {\n context = {\n queue: [],\n jobs: new Map(),\n dependencies: new Map(),\n completed: new Set(),\n }\n this.contexts.set(contextId, context)\n }\n return context\n }\n\n /**\n * Schedule work. Without a context id, executes immediately.\n * Otherwise queues the job to be flushed once dependencies are satisfied.\n * Scheduling the same jobId again replaces the previous run function.\n */\n schedule({ contextId, jobId, dependencies, run }: ScheduleOptions): void {\n if (typeof contextId === `undefined`) {\n run()\n return\n }\n\n const context = this.getOrCreateContext(contextId)\n\n // If this is a new job, add it to the queue\n if (!context.jobs.has(jobId)) {\n context.queue.push(jobId)\n }\n\n // Store or replace the run function\n context.jobs.set(jobId, run)\n\n // Update dependencies\n if (dependencies) {\n const depSet = new Set<unknown>(dependencies)\n depSet.delete(jobId)\n context.dependencies.set(jobId, depSet)\n } else if (!context.dependencies.has(jobId)) {\n context.dependencies.set(jobId, new Set())\n }\n\n // Clear completion status since we're rescheduling\n context.completed.delete(jobId)\n }\n\n /**\n * Flush all queued work for a context. Jobs with unmet dependencies are retried.\n * Throws if a pass completes without running any job (dependency cycle).\n */\n flush(contextId: SchedulerContextId): void {\n const context = this.contexts.get(contextId)\n if (!context) return\n\n const { queue, jobs, dependencies, completed } = context\n\n while (queue.length > 0) {\n let ranThisPass = false\n const jobsThisPass = queue.length\n\n for (let i = 0; i < jobsThisPass; i++) {\n const jobId = queue.shift()!\n const run = jobs.get(jobId)\n if (!run) {\n dependencies.delete(jobId)\n completed.delete(jobId)\n continue\n }\n\n const deps = dependencies.get(jobId)\n let ready = !deps\n if (deps) {\n ready = true\n for (const dep of deps) {\n if (dep === jobId) continue\n\n const depHasPending =\n isPendingAwareJob(dep) && dep.hasPendingGraphRun(contextId)\n\n // Treat dependencies as blocking if the dep has a pending run in this\n // context or if it's enqueued and not yet complete. If the dep is\n // neither pending nor enqueued, consider it satisfied to avoid deadlocks\n // on lazy sources that never schedule work.\n if (\n (jobs.has(dep) && !completed.has(dep)) ||\n (!jobs.has(dep) && depHasPending)\n ) {\n ready = false\n break\n }\n }\n }\n\n if (ready) {\n jobs.delete(jobId)\n dependencies.delete(jobId)\n // Run the job. If it throws, we don't mark it complete, allowing the\n // error to propagate while maintaining scheduler state consistency.\n run()\n completed.add(jobId)\n ranThisPass = true\n } else {\n queue.push(jobId)\n }\n }\n\n if (!ranThisPass) {\n throw new Error(\n `Scheduler detected unresolved dependencies for context ${String(\n contextId,\n )}.`,\n )\n }\n }\n\n this.contexts.delete(contextId)\n }\n\n /**\n * Flush all contexts with pending work. Useful during tear-down.\n */\n flushAll(): void {\n for (const contextId of Array.from(this.contexts.keys())) {\n this.flush(contextId)\n }\n }\n\n /** Clear all scheduled jobs for a context. */\n clear(contextId: SchedulerContextId): void {\n this.contexts.delete(contextId)\n // Notify listeners that this context was cleared\n this.clearListeners.forEach((listener) => listener(contextId))\n }\n\n /** Register a listener to be notified when a context is cleared. */\n onClear(listener: (contextId: SchedulerContextId) => void): () => void {\n this.clearListeners.add(listener)\n return () => this.clearListeners.delete(listener)\n }\n\n /** Check if a context has pending jobs. */\n hasPendingJobs(contextId: SchedulerContextId): boolean {\n const context = this.contexts.get(contextId)\n return !!context && context.jobs.size > 0\n }\n\n /** Remove a single job from a context and clean up its dependencies. */\n clearJob(contextId: SchedulerContextId, jobId: unknown): void {\n const context = this.contexts.get(contextId)\n if (!context) return\n\n context.jobs.delete(jobId)\n context.dependencies.delete(jobId)\n context.completed.delete(jobId)\n context.queue = context.queue.filter((id) => id !== jobId)\n\n if (context.jobs.size === 0) {\n this.contexts.delete(contextId)\n }\n }\n}\n\nexport const transactionScopedScheduler = new Scheduler()\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"debounceStrategy.js","sources":["../../../src/strategies/debounceStrategy.ts"],"sourcesContent":["import { LiteDebouncer } from \"@tanstack/pacer-lite/lite-debouncer\"\nimport type { DebounceStrategy, DebounceStrategyOptions } from \"./types\"\nimport type { Transaction } from \"../transactions\"\n\n/**\n * Creates a debounce strategy that delays transaction execution until after\n * a period of inactivity.\n *\n * Ideal for scenarios like search inputs or auto-save fields where you want\n * to wait for the user to stop typing before persisting changes.\n *\n * @param options - Configuration for the debounce behavior\n * @returns A debounce strategy instance\n *\n * @example\n * ```ts\n * const mutate = usePacedMutations({\n * onMutate: (value) => {\n * collection.update(id, draft => { draft.value = value })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 500 })\n * })\n * ```\n */\nexport function debounceStrategy(\n options: DebounceStrategyOptions\n): DebounceStrategy {\n const debouncer = new LiteDebouncer(\n (callback: () => Transaction) => callback(),\n options\n )\n\n return {\n _type: `debounce`,\n options,\n execute: <T extends object = Record<string, unknown>>(\n fn: () => Transaction<T>\n ) => {\n debouncer.maybeExecute(fn as () => Transaction)\n },\n cleanup: () => {\n debouncer.cancel()\n },\n }\n}\n"],"names":[],"mappings":";AA2BO,SAAS,iBACd,SACkB;AAClB,QAAM,YAAY,IAAI;AAAA,IACpB,CAAC,aAAgC,SAAA;AAAA,IACjC;AAAA,EAAA;AAGF,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA,SAAS,CACP,OACG;AACH,gBAAU,aAAa,EAAuB;AAAA,IAChD;AAAA,IACA,SAAS,MAAM;AACb,gBAAU,OAAA;AAAA,IACZ;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"debounceStrategy.js","sources":["../../../src/strategies/debounceStrategy.ts"],"sourcesContent":["import { LiteDebouncer } from '@tanstack/pacer-lite/lite-debouncer'\nimport type { DebounceStrategy, DebounceStrategyOptions } from './types'\nimport type { Transaction } from '../transactions'\n\n/**\n * Creates a debounce strategy that delays transaction execution until after\n * a period of inactivity.\n *\n * Ideal for scenarios like search inputs or auto-save fields where you want\n * to wait for the user to stop typing before persisting changes.\n *\n * @param options - Configuration for the debounce behavior\n * @returns A debounce strategy instance\n *\n * @example\n * ```ts\n * const mutate = usePacedMutations({\n * onMutate: (value) => {\n * collection.update(id, draft => { draft.value = value })\n * },\n * mutationFn: async ({ transaction }) => {\n * await api.save(transaction.mutations)\n * },\n * strategy: debounceStrategy({ wait: 500 })\n * })\n * ```\n */\nexport function debounceStrategy(\n options: DebounceStrategyOptions,\n): DebounceStrategy {\n const debouncer = new LiteDebouncer(\n (callback: () => Transaction) => callback(),\n options,\n )\n\n return {\n _type: `debounce`,\n options,\n execute: <T extends object = Record<string, unknown>>(\n fn: () => Transaction<T>,\n ) => {\n debouncer.maybeExecute(fn as () => Transaction)\n },\n cleanup: () => {\n debouncer.cancel()\n },\n }\n}\n"],"names":[],"mappings":";AA2BO,SAAS,iBACd,SACkB;AAClB,QAAM,YAAY,IAAI;AAAA,IACpB,CAAC,aAAgC,SAAA;AAAA,IACjC;AAAA,EAAA;AAGF,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA,SAAS,CACP,OACG;AACH,gBAAU,aAAa,EAAuB;AAAA,IAChD;AAAA,IACA,SAAS,MAAM;AACb,gBAAU,OAAA;AAAA,IACZ;AAAA,EAAA;AAEJ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"queueStrategy.js","sources":["../../../src/strategies/queueStrategy.ts"],"sourcesContent":["import { LiteQueuer } from \"@tanstack/pacer-lite/lite-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 // Manual promise chaining to ensure async serialization\n // LiteQueuer (unlike AsyncQueuer from @tanstack/pacer) lacks built-in async queue\n // primitives and concurrency control. We compensate by manually chaining promises\n // to ensure each transaction completes before the next one starts.\n let processingChain = Promise.resolve()\n\n const queuer = new LiteQueuer<() => Transaction>(\n (fn) => {\n // Chain each transaction to the previous one's completion\n processingChain = processingChain\n .then(async () => {\n const transaction = fn()\n // Wait for the transaction to be persisted before processing next item\n await transaction.isPersisted.promise\n })\n .catch(() => {\n // Errors are handled via transaction.isPersisted.promise and surfaced there.\n // This catch prevents unhandled promise rejections from breaking the chain,\n // ensuring subsequent transactions can still execute even if one fails.\n })\n },\n {\n wait: options?.wait ?? 0,\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\n return {\n _type: `queue`,\n options,\n execute: <T extends object = Record<string, unknown>>(\n fn: () => Transaction<T>\n ) => {\n // Add the transaction-creating function to the queue\n queuer.addItem(fn as () => Transaction)\n },\n cleanup: () => {\n queuer.stop()\n queuer.clear()\n },\n }\n}\n"],"names":[],"mappings":";AA6CO,SAAS,cAAc,SAA+C;AAK3E,MAAI,kBAAkB,QAAQ,QAAA;AAE9B,QAAM,SAAS,IAAI;AAAA,IACjB,CAAC,OAAO;AAEN,wBAAkB,gBACf,KAAK,YAAY;AAChB,cAAM,cAAc,GAAA;AAEpB,cAAM,YAAY,YAAY;AAAA,MAChC,CAAC,EACA,MAAM,MAAM;AAAA,MAIb,CAAC;AAAA,IACL;AAAA,IACA;AAAA,MACE,MAAM,SAAS,QAAQ;AAAA,MACvB,SAAS,SAAS;AAAA,MAClB,YAAY,SAAS,cAAc;AAAA;AAAA,MACnC,cAAc,SAAS,gBAAgB;AAAA;AAAA,MACvC,SAAS;AAAA;AAAA,IAAA;AAAA,EACX;AAGF,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA,SAAS,CACP,OACG;AAEH,aAAO,QAAQ,EAAuB;AAAA,IACxC;AAAA,IACA,SAAS,MAAM;AACb,aAAO,KAAA;AACP,aAAO,MAAA;AAAA,IACT;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"queueStrategy.js","sources":["../../../src/strategies/queueStrategy.ts"],"sourcesContent":["import { LiteQueuer } from '@tanstack/pacer-lite/lite-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 // Manual promise chaining to ensure async serialization\n // LiteQueuer (unlike AsyncQueuer from @tanstack/pacer) lacks built-in async queue\n // primitives and concurrency control. We compensate by manually chaining promises\n // to ensure each transaction completes before the next one starts.\n let processingChain = Promise.resolve()\n\n const queuer = new LiteQueuer<() => Transaction>(\n (fn) => {\n // Chain each transaction to the previous one's completion\n processingChain = processingChain\n .then(async () => {\n const transaction = fn()\n // Wait for the transaction to be persisted before processing next item\n await transaction.isPersisted.promise\n })\n .catch(() => {\n // Errors are handled via transaction.isPersisted.promise and surfaced there.\n // This catch prevents unhandled promise rejections from breaking the chain,\n // ensuring subsequent transactions can still execute even if one fails.\n })\n },\n {\n wait: options?.wait ?? 0,\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\n return {\n _type: `queue`,\n options,\n execute: <T extends object = Record<string, unknown>>(\n fn: () => Transaction<T>,\n ) => {\n // Add the transaction-creating function to the queue\n queuer.addItem(fn as () => Transaction)\n },\n cleanup: () => {\n queuer.stop()\n queuer.clear()\n },\n }\n}\n"],"names":[],"mappings":";AA6CO,SAAS,cAAc,SAA+C;AAK3E,MAAI,kBAAkB,QAAQ,QAAA;AAE9B,QAAM,SAAS,IAAI;AAAA,IACjB,CAAC,OAAO;AAEN,wBAAkB,gBACf,KAAK,YAAY;AAChB,cAAM,cAAc,GAAA;AAEpB,cAAM,YAAY,YAAY;AAAA,MAChC,CAAC,EACA,MAAM,MAAM;AAAA,MAIb,CAAC;AAAA,IACL;AAAA,IACA;AAAA,MACE,MAAM,SAAS,QAAQ;AAAA,MACvB,SAAS,SAAS;AAAA,MAClB,YAAY,SAAS,cAAc;AAAA;AAAA,MACnC,cAAc,SAAS,gBAAgB;AAAA;AAAA,MACvC,SAAS;AAAA;AAAA,IAAA;AAAA,EACX;AAGF,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA,SAAS,CACP,OACG;AAEH,aAAO,QAAQ,EAAuB;AAAA,IACxC;AAAA,IACA,SAAS,MAAM;AACb,aAAO,KAAA;AACP,aAAO,MAAA;AAAA,IACT;AAAA,EAAA;AAEJ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"throttleStrategy.js","sources":["../../../src/strategies/throttleStrategy.ts"],"sourcesContent":["import { LiteThrottler } from \"@tanstack/pacer-lite/lite-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 = usePacedMutations({\n * onMutate: (volume) => {\n * settingsCollection.update('volume', draft => { draft.value = volume })\n * },\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 = usePacedMutations({\n * onMutate: (data) => {\n * collection.update(id, draft => { Object.assign(draft, data) })\n * },\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 LiteThrottler(\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":[],"mappings":";AA+CO,SAAS,iBACd,SACkB;AAClB,QAAM,YAAY,IAAI;AAAA,IACpB,CAAC,aAAgC,SAAA;AAAA,IACjC;AAAA,EAAA;AAGF,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA,SAAS,CACP,OACG;AACH,gBAAU,aAAa,EAAuB;AAAA,IAChD;AAAA,IACA,SAAS,MAAM;AACb,gBAAU,OAAA;AAAA,IACZ;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"throttleStrategy.js","sources":["../../../src/strategies/throttleStrategy.ts"],"sourcesContent":["import { LiteThrottler } from '@tanstack/pacer-lite/lite-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 = usePacedMutations({\n * onMutate: (volume) => {\n * settingsCollection.update('volume', draft => { draft.value = volume })\n * },\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 = usePacedMutations({\n * onMutate: (data) => {\n * collection.update(id, draft => { Object.assign(draft, data) })\n * },\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 LiteThrottler(\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":[],"mappings":";AA+CO,SAAS,iBACd,SACkB;AAClB,QAAM,YAAY,IAAI;AAAA,IACpB,CAAC,aAAgC,SAAA;AAAA,IACjC;AAAA,EAAA;AAGF,SAAO;AAAA,IACL,OAAO;AAAA,IACP;AAAA,IACA,SAAS,CACP,OACG;AACH,gBAAU,aAAa,EAAuB;AAAA,IAChD;AAAA,IACA,SAAS,MAAM;AACb,gBAAU,OAAA;AAAA,IACZ;AAAA,EAAA;AAEJ;"}