@tanstack/db 0.4.20 → 0.5.0

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 (127) hide show
  1. package/dist/cjs/collection/change-events.cjs +10 -12
  2. package/dist/cjs/collection/change-events.cjs.map +1 -1
  3. package/dist/cjs/collection/change-events.d.cts +1 -8
  4. package/dist/cjs/collection/index.cjs +18 -0
  5. package/dist/cjs/collection/index.cjs.map +1 -1
  6. package/dist/cjs/collection/index.d.cts +7 -5
  7. package/dist/cjs/index.cjs +21 -3
  8. package/dist/cjs/index.cjs.map +1 -1
  9. package/dist/cjs/index.d.cts +2 -0
  10. package/dist/cjs/indexes/auto-index.cjs +7 -3
  11. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  12. package/dist/cjs/local-storage.cjs.map +1 -1
  13. package/dist/cjs/local-storage.d.cts +2 -2
  14. package/dist/cjs/query/builder/functions.cjs +34 -0
  15. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  16. package/dist/cjs/query/builder/functions.d.cts +5 -0
  17. package/dist/cjs/query/builder/index.cjs +2 -2
  18. package/dist/cjs/query/builder/index.cjs.map +1 -1
  19. package/dist/cjs/query/builder/types.d.cts +3 -22
  20. package/dist/cjs/query/compiler/evaluators.cjs +57 -4
  21. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  22. package/dist/cjs/query/compiler/evaluators.d.cts +13 -0
  23. package/dist/cjs/query/compiler/expressions.cjs +4 -1
  24. package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
  25. package/dist/cjs/query/compiler/group-by.cjs +3 -3
  26. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  27. package/dist/cjs/query/compiler/index.cjs +2 -2
  28. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  29. package/dist/cjs/query/compiler/order-by.cjs +18 -6
  30. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  31. package/dist/cjs/query/compiler/order-by.d.cts +7 -1
  32. package/dist/cjs/query/expression-helpers.cjs +217 -0
  33. package/dist/cjs/query/expression-helpers.cjs.map +1 -0
  34. package/dist/cjs/query/expression-helpers.d.cts +216 -0
  35. package/dist/cjs/query/index.d.cts +2 -0
  36. package/dist/cjs/query/live/collection-config-builder.cjs +13 -0
  37. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  38. package/dist/cjs/query/live/collection-config-builder.d.cts +1 -0
  39. package/dist/cjs/query/live/types.d.cts +6 -1
  40. package/dist/cjs/query/predicate-utils.cjs +816 -0
  41. package/dist/cjs/query/predicate-utils.cjs.map +1 -0
  42. package/dist/cjs/query/predicate-utils.d.cts +116 -0
  43. package/dist/cjs/query/subset-dedupe.cjs +111 -0
  44. package/dist/cjs/query/subset-dedupe.cjs.map +1 -0
  45. package/dist/cjs/query/subset-dedupe.d.cts +66 -0
  46. package/dist/cjs/types.d.cts +29 -0
  47. package/dist/cjs/utils/comparison.cjs +30 -0
  48. package/dist/cjs/utils/comparison.cjs.map +1 -1
  49. package/dist/cjs/utils/comparison.d.cts +7 -1
  50. package/dist/cjs/utils/index-optimization.cjs +26 -22
  51. package/dist/cjs/utils/index-optimization.cjs.map +1 -1
  52. package/dist/cjs/utils/index-optimization.d.cts +5 -4
  53. package/dist/esm/collection/change-events.d.ts +1 -8
  54. package/dist/esm/collection/change-events.js +7 -9
  55. package/dist/esm/collection/change-events.js.map +1 -1
  56. package/dist/esm/collection/index.d.ts +7 -5
  57. package/dist/esm/collection/index.js +18 -0
  58. package/dist/esm/collection/index.js.map +1 -1
  59. package/dist/esm/index.d.ts +2 -0
  60. package/dist/esm/index.js +19 -1
  61. package/dist/esm/index.js.map +1 -1
  62. package/dist/esm/indexes/auto-index.js +7 -3
  63. package/dist/esm/indexes/auto-index.js.map +1 -1
  64. package/dist/esm/local-storage.d.ts +2 -2
  65. package/dist/esm/local-storage.js.map +1 -1
  66. package/dist/esm/query/builder/functions.d.ts +5 -0
  67. package/dist/esm/query/builder/functions.js +34 -0
  68. package/dist/esm/query/builder/functions.js.map +1 -1
  69. package/dist/esm/query/builder/index.js +2 -2
  70. package/dist/esm/query/builder/index.js.map +1 -1
  71. package/dist/esm/query/builder/types.d.ts +3 -22
  72. package/dist/esm/query/compiler/evaluators.d.ts +13 -0
  73. package/dist/esm/query/compiler/evaluators.js +59 -6
  74. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  75. package/dist/esm/query/compiler/expressions.js +4 -1
  76. package/dist/esm/query/compiler/expressions.js.map +1 -1
  77. package/dist/esm/query/compiler/group-by.js +4 -4
  78. package/dist/esm/query/compiler/group-by.js.map +1 -1
  79. package/dist/esm/query/compiler/index.js +3 -3
  80. package/dist/esm/query/compiler/index.js.map +1 -1
  81. package/dist/esm/query/compiler/order-by.d.ts +7 -1
  82. package/dist/esm/query/compiler/order-by.js +18 -6
  83. package/dist/esm/query/compiler/order-by.js.map +1 -1
  84. package/dist/esm/query/expression-helpers.d.ts +216 -0
  85. package/dist/esm/query/expression-helpers.js +217 -0
  86. package/dist/esm/query/expression-helpers.js.map +1 -0
  87. package/dist/esm/query/index.d.ts +2 -0
  88. package/dist/esm/query/live/collection-config-builder.d.ts +1 -0
  89. package/dist/esm/query/live/collection-config-builder.js +13 -0
  90. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  91. package/dist/esm/query/live/types.d.ts +6 -1
  92. package/dist/esm/query/predicate-utils.d.ts +116 -0
  93. package/dist/esm/query/predicate-utils.js +816 -0
  94. package/dist/esm/query/predicate-utils.js.map +1 -0
  95. package/dist/esm/query/subset-dedupe.d.ts +66 -0
  96. package/dist/esm/query/subset-dedupe.js +111 -0
  97. package/dist/esm/query/subset-dedupe.js.map +1 -0
  98. package/dist/esm/types.d.ts +29 -0
  99. package/dist/esm/utils/comparison.d.ts +7 -1
  100. package/dist/esm/utils/comparison.js +30 -0
  101. package/dist/esm/utils/comparison.js.map +1 -1
  102. package/dist/esm/utils/index-optimization.d.ts +5 -4
  103. package/dist/esm/utils/index-optimization.js +26 -22
  104. package/dist/esm/utils/index-optimization.js.map +1 -1
  105. package/package.json +2 -2
  106. package/src/collection/change-events.ts +14 -24
  107. package/src/collection/index.ts +32 -4
  108. package/src/index.ts +4 -0
  109. package/src/indexes/auto-index.ts +8 -4
  110. package/src/local-storage.ts +11 -3
  111. package/src/query/builder/functions.ts +39 -0
  112. package/src/query/builder/index.ts +2 -2
  113. package/src/query/builder/types.ts +3 -25
  114. package/src/query/compiler/evaluators.ts +103 -5
  115. package/src/query/compiler/expressions.ts +3 -0
  116. package/src/query/compiler/group-by.ts +4 -4
  117. package/src/query/compiler/index.ts +3 -3
  118. package/src/query/compiler/order-by.ts +33 -7
  119. package/src/query/expression-helpers.ts +522 -0
  120. package/src/query/index.ts +12 -0
  121. package/src/query/live/collection-config-builder.ts +27 -0
  122. package/src/query/live/types.ts +11 -1
  123. package/src/query/predicate-utils.ts +1415 -0
  124. package/src/query/subset-dedupe.ts +243 -0
  125. package/src/types.ts +39 -0
  126. package/src/utils/comparison.ts +70 -1
  127. package/src/utils/index-optimization.ts +77 -63
@@ -4,6 +4,7 @@ const evaluators = require("../query/compiler/evaluators.cjs");
4
4
  const indexOptimization = require("../utils/index-optimization.cjs");
5
5
  const autoIndex = require("../indexes/auto-index.cjs");
6
6
  const comparison = require("../utils/comparison.cjs");
7
+ const orderBy = require("../query/compiler/order-by.cjs");
7
8
  function currentStateAsChanges(collection, options = {}) {
8
9
  const collectFilteredResults = (filterFn) => {
9
10
  const result = [];
@@ -53,7 +54,7 @@ function currentStateAsChanges(collection, options = {}) {
53
54
  const expression = options.where;
54
55
  const optimizationResult = indexOptimization.optimizeExpressionWithIndexes(
55
56
  expression,
56
- collection.indexes
57
+ collection
57
58
  );
58
59
  if (optimizationResult.canOptimize) {
59
60
  const result = [];
@@ -92,7 +93,7 @@ function createFilterFunctionFromExpression(expression) {
92
93
  try {
93
94
  const evaluator = evaluators.compileSingleRowExpression(expression);
94
95
  const result = evaluator(item);
95
- return Boolean(result);
96
+ return evaluators.toBooleanPredicate(result);
96
97
  } catch {
97
98
  return false;
98
99
  }
@@ -136,24 +137,21 @@ function createFilteredCallback(originalCallback, options) {
136
137
  }
137
138
  };
138
139
  }
139
- function getOrderedKeys(collection, orderBy, limit, whereFilter, optimizedOnly) {
140
- if (orderBy.length === 1) {
141
- const clause = orderBy[0];
140
+ function getOrderedKeys(collection, orderBy$1, limit, whereFilter, optimizedOnly) {
141
+ if (orderBy$1.length === 1) {
142
+ const clause = orderBy$1[0];
142
143
  const orderByExpression = clause.expression;
143
144
  if (orderByExpression.type === `ref`) {
144
145
  const propRef = orderByExpression;
145
146
  const fieldPath = propRef.path;
147
+ const compareOpts = orderBy.buildCompareOptions(clause, collection);
146
148
  autoIndex.ensureIndexForField(
147
149
  fieldPath[0],
148
150
  fieldPath,
149
151
  collection,
150
- clause.compareOptions
151
- );
152
- const index = indexOptimization.findIndexForField(
153
- collection.indexes,
154
- fieldPath,
155
- clause.compareOptions
152
+ compareOpts
156
153
  );
154
+ const index = indexOptimization.findIndexForField(collection, fieldPath, compareOpts);
157
155
  if (index && index.supports(`gt`)) {
158
156
  const filterFn = (key) => {
159
157
  const value = collection.get(key);
@@ -176,7 +174,7 @@ function getOrderedKeys(collection, orderBy, limit, whereFilter, optimizedOnly)
176
174
  }
177
175
  }
178
176
  const compare = (a, b) => {
179
- for (const clause of orderBy) {
177
+ for (const clause of orderBy$1) {
180
178
  const compareFn = comparison.makeComparator(clause.compareOptions);
181
179
  const aValue = extractValueFromItem(a.value, clause.expression);
182
180
  const bValue = extractValueFromItem(b.value, clause.expression);
@@ -1 +1 @@
1
- {"version":3,"file":"change-events.cjs","sources":["../../../src/collection/change-events.ts"],"sourcesContent":["import {\n createSingleRowRefProxy,\n toExpression,\n} from \"../query/builder/ref-proxy\"\nimport { compileSingleRowExpression } from \"../query/compiler/evaluators.js\"\nimport {\n findIndexForField,\n optimizeExpressionWithIndexes,\n} from \"../utils/index-optimization.js\"\nimport { ensureIndexForField } from \"../indexes/auto-index.js\"\nimport { makeComparator } from \"../utils/comparison.js\"\nimport type {\n ChangeMessage,\n CurrentStateAsChangesOptions,\n SubscribeChangesOptions,\n} from \"../types\"\nimport type { Collection, CollectionImpl } from \"./index.js\"\nimport type { SingleRowRefProxy } from \"../query/builder/ref-proxy\"\nimport type { BasicExpression, OrderBy } from \"../query/ir.js\"\n\n/**\n * Interface for a collection-like object that provides the necessary methods\n * for the change events system to work\n */\nexport interface CollectionLike<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n> extends Pick<\n Collection<T, TKey>,\n `get` | `has` | `entries` | `indexes` | `id`\n > {}\n\n/**\n * Returns the current state of the collection as an array of changes\n * @param collection - The collection to get changes from\n * @param options - Options including optional where filter, orderBy, and limit\n * @returns An array of changes\n * @example\n * // Get all items as changes\n * const allChanges = currentStateAsChanges(collection)\n *\n * // Get only items matching a condition\n * const activeChanges = currentStateAsChanges(collection, {\n * where: (row) => row.status === 'active'\n * })\n *\n * // Get only items using a pre-compiled expression\n * const activeChanges = currentStateAsChanges(collection, {\n * where: eq(row.status, 'active')\n * })\n *\n * // Get items ordered by name with limit\n * const topUsers = currentStateAsChanges(collection, {\n * orderBy: [{ expression: row.name, compareOptions: { direction: 'asc' } }],\n * limit: 10\n * })\n *\n * // Get active users ordered by score (highest score first)\n * const topActiveUsers = currentStateAsChanges(collection, {\n * where: eq(row.status, 'active'),\n * orderBy: [{ expression: row.score, compareOptions: { direction: 'desc' } }],\n * })\n */\nexport function currentStateAsChanges<\n T extends object,\n TKey extends string | number,\n>(\n collection: CollectionLike<T, TKey>,\n options: CurrentStateAsChangesOptions = {}\n): Array<ChangeMessage<T>> | void {\n // Helper function to collect filtered results\n const collectFilteredResults = (\n filterFn?: (value: T) => boolean\n ): Array<ChangeMessage<T>> => {\n const result: Array<ChangeMessage<T>> = []\n for (const [key, value] of collection.entries()) {\n // If no filter function is provided, include all items\n if (filterFn?.(value) ?? true) {\n result.push({\n type: `insert`,\n key,\n value,\n })\n }\n }\n return result\n }\n\n // Validate that limit without orderBy doesn't happen\n if (options.limit !== undefined && !options.orderBy) {\n throw new Error(`limit cannot be used without orderBy`)\n }\n\n // First check if orderBy is present (optionally with limit)\n if (options.orderBy) {\n // Create where filter function if present\n const whereFilter = options.where\n ? createFilterFunctionFromExpression(options.where)\n : undefined\n\n // Get ordered keys using index optimization when possible\n const orderedKeys = getOrderedKeys(\n collection,\n options.orderBy,\n options.limit,\n whereFilter,\n options.optimizedOnly\n )\n\n if (orderedKeys === undefined) {\n // `getOrderedKeys` returned undefined because we asked for `optimizedOnly` and there was no index to use\n return\n }\n\n // Convert keys to change messages\n const result: Array<ChangeMessage<T>> = []\n for (const key of orderedKeys) {\n const value = collection.get(key)\n if (value !== undefined) {\n result.push({\n type: `insert`,\n key,\n value,\n })\n }\n }\n return result\n }\n\n // If no orderBy OR orderBy optimization failed, use where clause optimization\n if (!options.where) {\n // No filtering, return all items\n return collectFilteredResults()\n }\n\n // There's a where clause, let's see if we can use an index\n try {\n const expression: BasicExpression<boolean> = options.where\n\n // Try to optimize the query using indexes\n const optimizationResult = optimizeExpressionWithIndexes(\n expression,\n collection.indexes\n )\n\n if (optimizationResult.canOptimize) {\n // Use index optimization\n const result: Array<ChangeMessage<T>> = []\n for (const key of optimizationResult.matchingKeys) {\n const value = collection.get(key)\n if (value !== undefined) {\n result.push({\n type: `insert`,\n key,\n value,\n })\n }\n }\n return result\n } else {\n if (options.optimizedOnly) {\n return\n }\n\n const filterFn = createFilterFunctionFromExpression(expression)\n return collectFilteredResults(filterFn)\n }\n } catch (error) {\n // If anything goes wrong with the where clause, fall back to full scan\n console.warn(\n `${collection.id ? `[${collection.id}] ` : ``}Error processing where clause, falling back to full scan:`,\n error\n )\n\n const filterFn = createFilterFunctionFromExpression(options.where)\n\n if (options.optimizedOnly) {\n return\n }\n\n return collectFilteredResults(filterFn)\n }\n}\n\n/**\n * Creates a filter function from a where callback\n * @param whereCallback - The callback function that defines the filter condition\n * @returns A function that takes an item and returns true if it matches the filter\n */\nexport function createFilterFunction<T extends object>(\n whereCallback: (row: SingleRowRefProxy<T>) => any\n): (item: T) => boolean {\n return (item: T): boolean => {\n try {\n // First try the RefProxy approach for query builder functions\n const singleRowRefProxy = createSingleRowRefProxy<T>()\n const whereExpression = whereCallback(singleRowRefProxy)\n const expression = toExpression(whereExpression)\n const evaluator = compileSingleRowExpression(expression)\n const result = evaluator(item as Record<string, unknown>)\n // WHERE clauses should always evaluate to boolean predicates (Kevin's feedback)\n return result\n } catch {\n // If RefProxy approach fails (e.g., arithmetic operations), fall back to direct evaluation\n try {\n // Create a simple proxy that returns actual values for arithmetic operations\n const simpleProxy = new Proxy(item as any, {\n get(target, prop) {\n return target[prop]\n },\n }) as SingleRowRefProxy<T>\n\n const result = whereCallback(simpleProxy)\n return result\n } catch {\n // If both approaches fail, exclude the item\n return false\n }\n }\n }\n}\n\n/**\n * Creates a filter function from a pre-compiled expression\n * @param expression - The pre-compiled expression to evaluate\n * @returns A function that takes an item and returns true if it matches the filter\n */\nexport function createFilterFunctionFromExpression<T extends object>(\n expression: BasicExpression<boolean>\n): (item: T) => boolean {\n return (item: T): boolean => {\n try {\n const evaluator = compileSingleRowExpression(expression)\n const result = evaluator(item as Record<string, unknown>)\n return Boolean(result)\n } catch {\n // If evaluation fails, exclude the item\n return false\n }\n }\n}\n\n/**\n * Creates a filtered callback that only calls the original callback with changes that match the where clause\n * @param originalCallback - The original callback to filter\n * @param options - The subscription options containing the where clause\n * @returns A filtered callback function\n */\nexport function createFilteredCallback<T extends object>(\n originalCallback: (changes: Array<ChangeMessage<T>>) => void,\n options: SubscribeChangesOptions\n): (changes: Array<ChangeMessage<T>>) => void {\n const filterFn = createFilterFunctionFromExpression(options.whereExpression!)\n\n return (changes: Array<ChangeMessage<T>>) => {\n const filteredChanges: Array<ChangeMessage<T>> = []\n\n for (const change of changes) {\n if (change.type === `insert`) {\n // For inserts, check if the new value matches the filter\n if (filterFn(change.value)) {\n filteredChanges.push(change)\n }\n } else if (change.type === `update`) {\n // For updates, we need to check both old and new values\n const newValueMatches = filterFn(change.value)\n const oldValueMatches = change.previousValue\n ? filterFn(change.previousValue)\n : false\n\n if (newValueMatches && oldValueMatches) {\n // Both old and new match: emit update\n filteredChanges.push(change)\n } else if (newValueMatches && !oldValueMatches) {\n // New matches but old didn't: emit insert\n filteredChanges.push({\n ...change,\n type: `insert`,\n })\n } else if (!newValueMatches && oldValueMatches) {\n // Old matched but new doesn't: emit delete\n filteredChanges.push({\n ...change,\n type: `delete`,\n value: change.previousValue!, // Use the previous value for the delete\n })\n }\n // If neither matches, don't emit anything\n } else {\n // For deletes, include if the previous value would have matched\n // (so subscribers know something they were tracking was deleted)\n if (filterFn(change.value)) {\n filteredChanges.push(change)\n }\n }\n }\n\n // Always call the original callback if we have filtered changes OR\n // if the original changes array was empty (which indicates a ready signal)\n if (filteredChanges.length > 0 || changes.length === 0) {\n originalCallback(filteredChanges)\n }\n }\n}\n\n/**\n * Gets ordered keys from a collection using index optimization when possible\n * @param collection - The collection to get keys from\n * @param orderBy - The order by clause\n * @param limit - Optional limit on number of keys to return\n * @param whereFilter - Optional filter function to apply while traversing\n * @returns Array of keys in sorted order\n */\nfunction getOrderedKeys<T extends object, TKey extends string | number>(\n collection: CollectionLike<T, TKey>,\n orderBy: OrderBy,\n limit?: number,\n whereFilter?: (item: T) => boolean,\n optimizedOnly?: boolean\n): Array<TKey> | undefined {\n // For single-column orderBy on a ref expression, try index optimization\n if (orderBy.length === 1) {\n const clause = orderBy[0]!\n const orderByExpression = clause.expression\n\n if (orderByExpression.type === `ref`) {\n const propRef = orderByExpression\n const fieldPath = propRef.path\n\n // Ensure index exists for this field\n ensureIndexForField(\n fieldPath[0]!,\n fieldPath,\n collection as CollectionImpl<T, TKey>,\n clause.compareOptions\n )\n\n // Find the index\n const index = findIndexForField(\n collection.indexes,\n fieldPath,\n clause.compareOptions\n )\n\n if (index && index.supports(`gt`)) {\n // Use index optimization\n const filterFn = (key: TKey): boolean => {\n const value = collection.get(key)\n if (value === undefined) {\n return false\n }\n return whereFilter?.(value) ?? true\n }\n\n // Take the keys that match the filter and limit\n // if no limit is provided `index.keyCount` is used,\n // i.e. we will take all keys that match the filter\n return index.take(limit ?? index.keyCount, undefined, filterFn)\n }\n }\n }\n\n if (optimizedOnly) {\n return\n }\n\n // Fallback: collect all items and sort in memory\n const allItems: Array<{ key: TKey; value: T }> = []\n for (const [key, value] of collection.entries()) {\n if (whereFilter?.(value) ?? true) {\n allItems.push({ key, value })\n }\n }\n\n // Sort using makeComparator\n const compare = (a: { key: TKey; value: T }, b: { key: TKey; value: T }) => {\n for (const clause of orderBy) {\n const compareFn = makeComparator(clause.compareOptions)\n\n // Extract values for comparison\n const aValue = extractValueFromItem(a.value, clause.expression)\n const bValue = extractValueFromItem(b.value, clause.expression)\n\n const result = compareFn(aValue, bValue)\n if (result !== 0) {\n return result\n }\n }\n return 0\n }\n\n allItems.sort(compare)\n const sortedKeys = allItems.map((item) => item.key)\n\n // Apply limit if provided\n if (limit !== undefined) {\n return sortedKeys.slice(0, limit)\n }\n\n // if no limit is provided, we will return all keys\n return sortedKeys\n}\n\n/**\n * Helper function to extract a value from an item based on an expression\n */\nfunction extractValueFromItem(item: any, expression: BasicExpression): any {\n if (expression.type === `ref`) {\n const propRef = expression\n let value = item\n for (const pathPart of propRef.path) {\n value = value?.[pathPart]\n }\n return value\n } else if (expression.type === `val`) {\n return expression.value\n } else {\n // It must be a function\n const evaluator = compileSingleRowExpression(expression)\n return evaluator(item as Record<string, unknown>)\n }\n}\n"],"names":["optimizeExpressionWithIndexes","compileSingleRowExpression","ensureIndexForField","findIndexForField","makeComparator"],"mappings":"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"}
1
+ {"version":3,"file":"change-events.cjs","sources":["../../../src/collection/change-events.ts"],"sourcesContent":["import {\n createSingleRowRefProxy,\n toExpression,\n} from \"../query/builder/ref-proxy\"\nimport {\n compileSingleRowExpression,\n toBooleanPredicate,\n} from \"../query/compiler/evaluators.js\"\nimport {\n findIndexForField,\n optimizeExpressionWithIndexes,\n} from \"../utils/index-optimization.js\"\nimport { ensureIndexForField } from \"../indexes/auto-index.js\"\nimport { makeComparator } from \"../utils/comparison.js\"\nimport { buildCompareOptions } from \"../query/compiler/order-by\"\nimport type {\n ChangeMessage,\n CollectionLike,\n CurrentStateAsChangesOptions,\n SubscribeChangesOptions,\n} from \"../types\"\nimport type { CollectionImpl } from \"./index.js\"\nimport type { SingleRowRefProxy } from \"../query/builder/ref-proxy\"\nimport type { BasicExpression, OrderBy } from \"../query/ir.js\"\n\n/**\n * Returns the current state of the collection as an array of changes\n * @param collection - The collection to get changes from\n * @param options - Options including optional where filter, orderBy, and limit\n * @returns An array of changes\n * @example\n * // Get all items as changes\n * const allChanges = currentStateAsChanges(collection)\n *\n * // Get only items matching a condition\n * const activeChanges = currentStateAsChanges(collection, {\n * where: (row) => row.status === 'active'\n * })\n *\n * // Get only items using a pre-compiled expression\n * const activeChanges = currentStateAsChanges(collection, {\n * where: eq(row.status, 'active')\n * })\n *\n * // Get items ordered by name with limit\n * const topUsers = currentStateAsChanges(collection, {\n * orderBy: [{ expression: row.name, compareOptions: { direction: 'asc' } }],\n * limit: 10\n * })\n *\n * // Get active users ordered by score (highest score first)\n * const topActiveUsers = currentStateAsChanges(collection, {\n * where: eq(row.status, 'active'),\n * orderBy: [{ expression: row.score, compareOptions: { direction: 'desc' } }],\n * })\n */\nexport function currentStateAsChanges<\n T extends object,\n TKey extends string | number,\n>(\n collection: CollectionLike<T, TKey>,\n options: CurrentStateAsChangesOptions = {}\n): Array<ChangeMessage<T>> | void {\n // Helper function to collect filtered results\n const collectFilteredResults = (\n filterFn?: (value: T) => boolean\n ): Array<ChangeMessage<T>> => {\n const result: Array<ChangeMessage<T>> = []\n for (const [key, value] of collection.entries()) {\n // If no filter function is provided, include all items\n if (filterFn?.(value) ?? true) {\n result.push({\n type: `insert`,\n key,\n value,\n })\n }\n }\n return result\n }\n\n // Validate that limit without orderBy doesn't happen\n if (options.limit !== undefined && !options.orderBy) {\n throw new Error(`limit cannot be used without orderBy`)\n }\n\n // First check if orderBy is present (optionally with limit)\n if (options.orderBy) {\n // Create where filter function if present\n const whereFilter = options.where\n ? createFilterFunctionFromExpression(options.where)\n : undefined\n\n // Get ordered keys using index optimization when possible\n const orderedKeys = getOrderedKeys(\n collection,\n options.orderBy,\n options.limit,\n whereFilter,\n options.optimizedOnly\n )\n\n if (orderedKeys === undefined) {\n // `getOrderedKeys` returned undefined because we asked for `optimizedOnly` and there was no index to use\n return\n }\n\n // Convert keys to change messages\n const result: Array<ChangeMessage<T>> = []\n for (const key of orderedKeys) {\n const value = collection.get(key)\n if (value !== undefined) {\n result.push({\n type: `insert`,\n key,\n value,\n })\n }\n }\n return result\n }\n\n // If no orderBy OR orderBy optimization failed, use where clause optimization\n if (!options.where) {\n // No filtering, return all items\n return collectFilteredResults()\n }\n\n // There's a where clause, let's see if we can use an index\n try {\n const expression: BasicExpression<boolean> = options.where\n\n // Try to optimize the query using indexes\n const optimizationResult = optimizeExpressionWithIndexes(\n expression,\n collection\n )\n\n if (optimizationResult.canOptimize) {\n // Use index optimization\n const result: Array<ChangeMessage<T>> = []\n for (const key of optimizationResult.matchingKeys) {\n const value = collection.get(key)\n if (value !== undefined) {\n result.push({\n type: `insert`,\n key,\n value,\n })\n }\n }\n return result\n } else {\n if (options.optimizedOnly) {\n return\n }\n\n const filterFn = createFilterFunctionFromExpression(expression)\n return collectFilteredResults(filterFn)\n }\n } catch (error) {\n // If anything goes wrong with the where clause, fall back to full scan\n console.warn(\n `${collection.id ? `[${collection.id}] ` : ``}Error processing where clause, falling back to full scan:`,\n error\n )\n\n const filterFn = createFilterFunctionFromExpression(options.where)\n\n if (options.optimizedOnly) {\n return\n }\n\n return collectFilteredResults(filterFn)\n }\n}\n\n/**\n * Creates a filter function from a where callback\n * @param whereCallback - The callback function that defines the filter condition\n * @returns A function that takes an item and returns true if it matches the filter\n */\nexport function createFilterFunction<T extends object>(\n whereCallback: (row: SingleRowRefProxy<T>) => any\n): (item: T) => boolean {\n return (item: T): boolean => {\n try {\n // First try the RefProxy approach for query builder functions\n const singleRowRefProxy = createSingleRowRefProxy<T>()\n const whereExpression = whereCallback(singleRowRefProxy)\n const expression = toExpression(whereExpression)\n const evaluator = compileSingleRowExpression(expression)\n const result = evaluator(item as Record<string, unknown>)\n // WHERE clauses should always evaluate to boolean predicates (Kevin's feedback)\n return toBooleanPredicate(result)\n } catch {\n // If RefProxy approach fails (e.g., arithmetic operations), fall back to direct evaluation\n try {\n // Create a simple proxy that returns actual values for arithmetic operations\n const simpleProxy = new Proxy(item as any, {\n get(target, prop) {\n return target[prop]\n },\n }) as SingleRowRefProxy<T>\n\n const result = whereCallback(simpleProxy)\n return toBooleanPredicate(result)\n } catch {\n // If both approaches fail, exclude the item\n return false\n }\n }\n }\n}\n\n/**\n * Creates a filter function from a pre-compiled expression\n * @param expression - The pre-compiled expression to evaluate\n * @returns A function that takes an item and returns true if it matches the filter\n */\nexport function createFilterFunctionFromExpression<T extends object>(\n expression: BasicExpression<boolean>\n): (item: T) => boolean {\n return (item: T): boolean => {\n try {\n const evaluator = compileSingleRowExpression(expression)\n const result = evaluator(item as Record<string, unknown>)\n return toBooleanPredicate(result)\n } catch {\n // If evaluation fails, exclude the item\n return false\n }\n }\n}\n\n/**\n * Creates a filtered callback that only calls the original callback with changes that match the where clause\n * @param originalCallback - The original callback to filter\n * @param options - The subscription options containing the where clause\n * @returns A filtered callback function\n */\nexport function createFilteredCallback<T extends object>(\n originalCallback: (changes: Array<ChangeMessage<T>>) => void,\n options: SubscribeChangesOptions\n): (changes: Array<ChangeMessage<T>>) => void {\n const filterFn = createFilterFunctionFromExpression(options.whereExpression!)\n\n return (changes: Array<ChangeMessage<T>>) => {\n const filteredChanges: Array<ChangeMessage<T>> = []\n\n for (const change of changes) {\n if (change.type === `insert`) {\n // For inserts, check if the new value matches the filter\n if (filterFn(change.value)) {\n filteredChanges.push(change)\n }\n } else if (change.type === `update`) {\n // For updates, we need to check both old and new values\n const newValueMatches = filterFn(change.value)\n const oldValueMatches = change.previousValue\n ? filterFn(change.previousValue)\n : false\n\n if (newValueMatches && oldValueMatches) {\n // Both old and new match: emit update\n filteredChanges.push(change)\n } else if (newValueMatches && !oldValueMatches) {\n // New matches but old didn't: emit insert\n filteredChanges.push({\n ...change,\n type: `insert`,\n })\n } else if (!newValueMatches && oldValueMatches) {\n // Old matched but new doesn't: emit delete\n filteredChanges.push({\n ...change,\n type: `delete`,\n value: change.previousValue!, // Use the previous value for the delete\n })\n }\n // If neither matches, don't emit anything\n } else {\n // For deletes, include if the previous value would have matched\n // (so subscribers know something they were tracking was deleted)\n if (filterFn(change.value)) {\n filteredChanges.push(change)\n }\n }\n }\n\n // Always call the original callback if we have filtered changes OR\n // if the original changes array was empty (which indicates a ready signal)\n if (filteredChanges.length > 0 || changes.length === 0) {\n originalCallback(filteredChanges)\n }\n }\n}\n\n/**\n * Gets ordered keys from a collection using index optimization when possible\n * @param collection - The collection to get keys from\n * @param orderBy - The order by clause\n * @param limit - Optional limit on number of keys to return\n * @param whereFilter - Optional filter function to apply while traversing\n * @returns Array of keys in sorted order\n */\nfunction getOrderedKeys<T extends object, TKey extends string | number>(\n collection: CollectionLike<T, TKey>,\n orderBy: OrderBy,\n limit?: number,\n whereFilter?: (item: T) => boolean,\n optimizedOnly?: boolean\n): Array<TKey> | undefined {\n // For single-column orderBy on a ref expression, try index optimization\n if (orderBy.length === 1) {\n const clause = orderBy[0]!\n const orderByExpression = clause.expression\n\n if (orderByExpression.type === `ref`) {\n const propRef = orderByExpression\n const fieldPath = propRef.path\n const compareOpts = buildCompareOptions(clause, collection)\n\n // Ensure index exists for this field\n ensureIndexForField(\n fieldPath[0]!,\n fieldPath,\n collection as CollectionImpl<T, TKey>,\n compareOpts\n )\n\n // Find the index\n const index = findIndexForField(collection, fieldPath, compareOpts)\n\n if (index && index.supports(`gt`)) {\n // Use index optimization\n const filterFn = (key: TKey): boolean => {\n const value = collection.get(key)\n if (value === undefined) {\n return false\n }\n return whereFilter?.(value) ?? true\n }\n\n // Take the keys that match the filter and limit\n // if no limit is provided `index.keyCount` is used,\n // i.e. we will take all keys that match the filter\n return index.take(limit ?? index.keyCount, undefined, filterFn)\n }\n }\n }\n\n if (optimizedOnly) {\n return\n }\n\n // Fallback: collect all items and sort in memory\n const allItems: Array<{ key: TKey; value: T }> = []\n for (const [key, value] of collection.entries()) {\n if (whereFilter?.(value) ?? true) {\n allItems.push({ key, value })\n }\n }\n\n // Sort using makeComparator\n const compare = (a: { key: TKey; value: T }, b: { key: TKey; value: T }) => {\n for (const clause of orderBy) {\n const compareFn = makeComparator(clause.compareOptions)\n\n // Extract values for comparison\n const aValue = extractValueFromItem(a.value, clause.expression)\n const bValue = extractValueFromItem(b.value, clause.expression)\n\n const result = compareFn(aValue, bValue)\n if (result !== 0) {\n return result\n }\n }\n return 0\n }\n\n allItems.sort(compare)\n const sortedKeys = allItems.map((item) => item.key)\n\n // Apply limit if provided\n if (limit !== undefined) {\n return sortedKeys.slice(0, limit)\n }\n\n // if no limit is provided, we will return all keys\n return sortedKeys\n}\n\n/**\n * Helper function to extract a value from an item based on an expression\n */\nfunction extractValueFromItem(item: any, expression: BasicExpression): any {\n if (expression.type === `ref`) {\n const propRef = expression\n let value = item\n for (const pathPart of propRef.path) {\n value = value?.[pathPart]\n }\n return value\n } else if (expression.type === `val`) {\n return expression.value\n } else {\n // It must be a function\n const evaluator = compileSingleRowExpression(expression)\n return evaluator(item as Record<string, unknown>)\n }\n}\n"],"names":["optimizeExpressionWithIndexes","compileSingleRowExpression","toBooleanPredicate","orderBy","buildCompareOptions","ensureIndexForField","findIndexForField","makeComparator"],"mappings":"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"}
@@ -1,13 +1,6 @@
1
- import { ChangeMessage, CurrentStateAsChangesOptions, SubscribeChangesOptions } from '../types.cjs';
2
- import { Collection } from './index.js';
1
+ import { ChangeMessage, CollectionLike, CurrentStateAsChangesOptions, SubscribeChangesOptions } from '../types.cjs';
3
2
  import { SingleRowRefProxy } from '../query/builder/ref-proxy.cjs';
4
3
  import { BasicExpression } from '../query/ir.js';
5
- /**
6
- * Interface for a collection-like object that provides the necessary methods
7
- * for the change events system to work
8
- */
9
- export interface CollectionLike<T extends object = Record<string, unknown>, TKey extends string | number = string | number> extends Pick<Collection<T, TKey>, `get` | `has` | `entries` | `indexes` | `id`> {
10
- }
11
4
  /**
12
5
  * Returns the current state of the collection as an array of changes
13
6
  * @param collection - The collection to get changes from
@@ -57,6 +57,7 @@ class CollectionImpl {
57
57
  this._mutations = new mutations.CollectionMutationsManager(config, this.id);
58
58
  this._state = new state.CollectionStateManager(config);
59
59
  this._sync = new sync.CollectionSyncManager(config, this.id);
60
+ this.comparisonOpts = buildCompareOptionsFromConfig(config);
60
61
  this._changes.setDeps({
61
62
  collection: this,
62
63
  // Required for passing to CollectionSubscription
@@ -269,6 +270,9 @@ class CollectionImpl {
269
270
  validateData(data, type, key) {
270
271
  return this._mutations.validateData(data, type, key);
271
272
  }
273
+ get compareOptions() {
274
+ return { ...this.comparisonOpts };
275
+ }
272
276
  update(keys, configOrCallback, maybeCallback) {
273
277
  return this._mutations.update(keys, configOrCallback, maybeCallback);
274
278
  }
@@ -423,6 +427,20 @@ class CollectionImpl {
423
427
  return Promise.resolve();
424
428
  }
425
429
  }
430
+ function buildCompareOptionsFromConfig(config) {
431
+ if (config.defaultStringCollation) {
432
+ const options = config.defaultStringCollation;
433
+ return {
434
+ stringSort: options.stringSort ?? `locale`,
435
+ locale: options.stringSort === `locale` ? options.locale : void 0,
436
+ localeOptions: options.stringSort === `locale` ? options.localeOptions : void 0
437
+ };
438
+ } else {
439
+ return {
440
+ stringSort: `locale`
441
+ };
442
+ }
443
+ }
426
444
  exports.CollectionImpl = CollectionImpl;
427
445
  exports.createCollection = createCollection;
428
446
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../../src/collection/index.ts"],"sourcesContent":["import {\n CollectionRequiresConfigError,\n CollectionRequiresSyncConfigError,\n} from \"../errors\"\nimport { currentStateAsChanges } from \"./change-events\"\n\nimport { CollectionStateManager } from \"./state\"\nimport { CollectionChangesManager } from \"./changes\"\nimport { CollectionLifecycleManager } from \"./lifecycle.js\"\nimport { CollectionSyncManager } from \"./sync\"\nimport { CollectionIndexesManager } from \"./indexes\"\nimport { CollectionMutationsManager } from \"./mutations\"\nimport { CollectionEventsManager } from \"./events.js\"\nimport type { CollectionSubscription } from \"./subscription\"\nimport type { AllCollectionEvents, CollectionEventHandler } from \"./events.js\"\nimport type { BaseIndex, IndexResolver } from \"../indexes/base-index.js\"\nimport type { IndexOptions } from \"../indexes/index-options.js\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n CollectionStatus,\n CurrentStateAsChangesOptions,\n Fn,\n InferSchemaInput,\n InferSchemaOutput,\n InsertConfig,\n NonSingleResult,\n OperationConfig,\n SingleResult,\n SubscribeChangesOptions,\n Transaction as TransactionType,\n UtilsRecord,\n WritableDeep,\n} from \"../types\"\nimport type { SingleRowRefProxy } from \"../query/builder/ref-proxy\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\nimport type { BTreeIndex } from \"../indexes/btree-index.js\"\nimport type { IndexProxy } from \"../indexes/lazy-index.js\"\n\n/**\n * Enhanced Collection interface that includes both data type T and utilities TUtils\n * @template T - The type of items in the collection\n * @template TKey - The type of the key for the collection\n * @template TUtils - The utilities record type\n * @template TInsertInput - The type for insert operations (can be different from T for schemas with defaults)\n */\nexport interface Collection<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = UtilsRecord,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInsertInput extends object = T,\n> extends CollectionImpl<T, TKey, TUtils, TSchema, TInsertInput> {\n readonly utils: TUtils\n readonly singleResult?: true\n}\n\n/**\n * Creates a new Collection instance with the given configuration\n *\n * @template T - The schema type if a schema is provided, otherwise the type of items in the collection\n * @template TKey - The type of the key for the collection\n * @template TUtils - The utilities record type\n * @param options - Collection options with optional utilities\n * @returns A new Collection with utilities exposed both at top level and under .utils\n *\n * @example\n * // Pattern 1: With operation handlers (direct collection calls)\n * const todos = createCollection({\n * id: \"todos\",\n * getKey: (todo) => todo.id,\n * schema,\n * onInsert: async ({ transaction, collection }) => {\n * // Send to API\n * await api.createTodo(transaction.mutations[0].modified)\n * },\n * onUpdate: async ({ transaction, collection }) => {\n * await api.updateTodo(transaction.mutations[0].modified)\n * },\n * onDelete: async ({ transaction, collection }) => {\n * await api.deleteTodo(transaction.mutations[0].key)\n * },\n * sync: { sync: () => {} }\n * })\n *\n * // Direct usage (handlers manage transactions)\n * const tx = todos.insert({ id: \"1\", text: \"Buy milk\", completed: false })\n * await tx.isPersisted.promise\n *\n * @example\n * // Pattern 2: Manual transaction management\n * const todos = createCollection({\n * getKey: (todo) => todo.id,\n * schema: todoSchema,\n * sync: { sync: () => {} }\n * })\n *\n * // Explicit transaction usage\n * const tx = createTransaction({\n * mutationFn: async ({ transaction }) => {\n * // Handle all mutations in transaction\n * await api.saveChanges(transaction.mutations)\n * }\n * })\n *\n * tx.mutate(() => {\n * todos.insert({ id: \"1\", text: \"Buy milk\" })\n * todos.update(\"2\", draft => { draft.completed = true })\n * })\n *\n * await tx.isPersisted.promise\n *\n * @example\n * // Using schema for type inference (preferred as it also gives you client side validation)\n * const todoSchema = z.object({\n * id: z.string(),\n * title: z.string(),\n * completed: z.boolean()\n * })\n *\n * const todos = createCollection({\n * schema: todoSchema,\n * getKey: (todo) => todo.id,\n * sync: { sync: () => {} }\n * })\n *\n */\n\n// Overload for when schema is provided\nexport function createCollection<\n T extends StandardSchemaV1,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = UtilsRecord,\n>(\n options: CollectionConfig<InferSchemaOutput<T>, TKey, T> & {\n schema: T\n utils?: TUtils\n } & NonSingleResult\n): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>> &\n NonSingleResult\n\n// Overload for when schema is provided and singleResult is true\nexport function createCollection<\n T extends StandardSchemaV1,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = UtilsRecord,\n>(\n options: CollectionConfig<InferSchemaOutput<T>, TKey, T> & {\n schema: T\n utils?: TUtils\n } & SingleResult\n): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>> &\n SingleResult\n\n// Overload for when no schema is provided\n// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config\nexport function createCollection<\n T extends object,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = UtilsRecord,\n>(\n options: CollectionConfig<T, TKey, never> & {\n schema?: never // prohibit schema if an explicit type is provided\n utils?: TUtils\n } & NonSingleResult\n): Collection<T, TKey, TUtils, never, T> & NonSingleResult\n\n// Overload for when no schema is provided and singleResult is true\n// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config\nexport function createCollection<\n T extends object,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = UtilsRecord,\n>(\n options: CollectionConfig<T, TKey, never> & {\n schema?: never // prohibit schema if an explicit type is provided\n utils?: TUtils\n } & SingleResult\n): Collection<T, TKey, TUtils, never, T> & SingleResult\n\n// Implementation\nexport function createCollection(\n options: CollectionConfig<any, string | number, any> & {\n schema?: StandardSchemaV1\n utils?: UtilsRecord\n }\n): Collection<any, string | number, UtilsRecord, any, any> {\n const collection = new CollectionImpl<any, string | number, any, any, any>(\n options\n )\n\n // Attach utils to collection\n if (options.utils) {\n collection.utils = options.utils\n } else {\n collection.utils = {}\n }\n\n return collection\n}\n\nexport class CollectionImpl<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = {},\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n public id: string\n public config: CollectionConfig<TOutput, TKey, TSchema>\n\n // Utilities namespace\n // This is populated by createCollection\n public utils: Record<string, Fn> = {}\n\n // Managers\n private _events: CollectionEventsManager\n private _changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n public _lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n public _sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n private _indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n private _mutations: CollectionMutationsManager<\n TOutput,\n TKey,\n TUtils,\n TSchema,\n TInput\n >\n // The core state of the collection is \"public\" so that is accessible in tests\n // and for debugging\n public _state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n\n /**\n * Creates a new Collection instance\n *\n * @param config - Configuration object for the collection\n * @throws Error if sync config is missing\n */\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>) {\n // eslint-disable-next-line\n if (!config) {\n throw new CollectionRequiresConfigError()\n }\n\n // eslint-disable-next-line\n if (!config.sync) {\n throw new CollectionRequiresSyncConfigError()\n }\n\n if (config.id) {\n this.id = config.id\n } else {\n this.id = crypto.randomUUID()\n }\n\n // Set default values for optional config properties\n this.config = {\n ...config,\n autoIndex: config.autoIndex ?? `eager`,\n }\n\n this._changes = new CollectionChangesManager()\n this._events = new CollectionEventsManager()\n this._indexes = new CollectionIndexesManager()\n this._lifecycle = new CollectionLifecycleManager(config, this.id)\n this._mutations = new CollectionMutationsManager(config, this.id)\n this._state = new CollectionStateManager(config)\n this._sync = new CollectionSyncManager(config, this.id)\n\n this._changes.setDeps({\n collection: this, // Required for passing to CollectionSubscription\n lifecycle: this._lifecycle,\n sync: this._sync,\n events: this._events,\n })\n this._events.setDeps({\n collection: this, // Required for adding to emitted events\n })\n this._indexes.setDeps({\n state: this._state,\n lifecycle: this._lifecycle,\n })\n this._lifecycle.setDeps({\n changes: this._changes,\n events: this._events,\n indexes: this._indexes,\n state: this._state,\n sync: this._sync,\n })\n this._mutations.setDeps({\n collection: this, // Required for passing to config.onInsert/onUpdate/onDelete and annotating mutations\n lifecycle: this._lifecycle,\n state: this._state,\n })\n this._state.setDeps({\n collection: this, // Required for filtering events to only include this collection\n lifecycle: this._lifecycle,\n changes: this._changes,\n indexes: this._indexes,\n })\n this._sync.setDeps({\n collection: this, // Required for passing to config.sync callback\n state: this._state,\n lifecycle: this._lifecycle,\n events: this._events,\n })\n\n // Only start sync immediately if explicitly enabled\n if (config.startSync === true) {\n this._sync.startSync()\n }\n }\n\n /**\n * Gets the current status of the collection\n */\n public get status(): CollectionStatus {\n return this._lifecycle.status\n }\n\n /**\n * Get the number of subscribers to the collection\n */\n public get subscriberCount(): number {\n return this._changes.activeSubscribersCount\n }\n\n /**\n * Register a callback to be executed when the collection first becomes ready\n * Useful for preloading collections\n * @param callback Function to call when the collection first becomes ready\n * @example\n * collection.onFirstReady(() => {\n * console.log('Collection is ready for the first time')\n * // Safe to access collection.state now\n * })\n */\n public onFirstReady(callback: () => void): void {\n return this._lifecycle.onFirstReady(callback)\n }\n\n /**\n * Check if the collection is ready for use\n * Returns true if the collection has been marked as ready by its sync implementation\n * @returns true if the collection is ready, false otherwise\n * @example\n * if (collection.isReady()) {\n * console.log('Collection is ready, data is available')\n * // Safe to access collection.state\n * } else {\n * console.log('Collection is still loading')\n * }\n */\n public isReady(): boolean {\n return this._lifecycle.status === `ready`\n }\n\n /**\n * Check if the collection is currently loading more data\n * @returns true if the collection has pending load more operations, false otherwise\n */\n public get isLoadingSubset(): boolean {\n return this._sync.isLoadingSubset\n }\n\n /**\n * Start sync immediately - internal method for compiled queries\n * This bypasses lazy loading for special cases like live query results\n */\n public startSyncImmediate(): void {\n this._sync.startSync()\n }\n\n /**\n * Preload the collection data by starting sync if not already started\n * Multiple concurrent calls will share the same promise\n */\n public preload(): Promise<void> {\n return this._sync.preload()\n }\n\n /**\n * Get the current value for a key (virtual derived state)\n */\n public get(key: TKey): TOutput | undefined {\n return this._state.get(key)\n }\n\n /**\n * Check if a key exists in the collection (virtual derived state)\n */\n public has(key: TKey): boolean {\n return this._state.has(key)\n }\n\n /**\n * Get the current size of the collection (cached)\n */\n public get size(): number {\n return this._state.size\n }\n\n /**\n * Get all keys (virtual derived state)\n */\n public *keys(): IterableIterator<TKey> {\n yield* this._state.keys()\n }\n\n /**\n * Get all values (virtual derived state)\n */\n public *values(): IterableIterator<TOutput> {\n yield* this._state.values()\n }\n\n /**\n * Get all entries (virtual derived state)\n */\n public *entries(): IterableIterator<[TKey, TOutput]> {\n yield* this._state.entries()\n }\n\n /**\n * Get all entries (virtual derived state)\n */\n public *[Symbol.iterator](): IterableIterator<[TKey, TOutput]> {\n yield* this._state[Symbol.iterator]()\n }\n\n /**\n * Execute a callback for each entry in the collection\n */\n public forEach(\n callbackfn: (value: TOutput, key: TKey, index: number) => void\n ): void {\n return this._state.forEach(callbackfn)\n }\n\n /**\n * Create a new array with the results of calling a function for each entry in the collection\n */\n public map<U>(\n callbackfn: (value: TOutput, key: TKey, index: number) => U\n ): Array<U> {\n return this._state.map(callbackfn)\n }\n\n public getKeyFromItem(item: TOutput): TKey {\n return this.config.getKey(item)\n }\n\n /**\n * Creates an index on a collection for faster queries.\n * Indexes significantly improve query performance by allowing constant time lookups\n * and logarithmic time range queries instead of full scans.\n *\n * @template TResolver - The type of the index resolver (constructor or async loader)\n * @param indexCallback - Function that extracts the indexed value from each item\n * @param config - Configuration including index type and type-specific options\n * @returns An index proxy that provides access to the index when ready\n *\n * @example\n * // Create a default B+ tree index\n * const ageIndex = collection.createIndex((row) => row.age)\n *\n * // Create a ordered index with custom options\n * const ageIndex = collection.createIndex((row) => row.age, {\n * indexType: BTreeIndex,\n * options: {\n * compareFn: customComparator,\n * compareOptions: { direction: 'asc', nulls: 'first', stringSort: 'lexical' }\n * },\n * name: 'age_btree'\n * })\n *\n * // Create an async-loaded index\n * const textIndex = collection.createIndex((row) => row.content, {\n * indexType: async () => {\n * const { FullTextIndex } = await import('./indexes/fulltext.js')\n * return FullTextIndex\n * },\n * options: { language: 'en' }\n * })\n */\n public createIndex<TResolver extends IndexResolver<TKey> = typeof BTreeIndex>(\n indexCallback: (row: SingleRowRefProxy<TOutput>) => any,\n config: IndexOptions<TResolver> = {}\n ): IndexProxy<TKey> {\n return this._indexes.createIndex(indexCallback, config)\n }\n\n /**\n * Get resolved indexes for query optimization\n */\n get indexes(): Map<number, BaseIndex<TKey>> {\n return this._indexes.indexes\n }\n\n /**\n * Validates the data against the schema\n */\n public validateData(\n data: unknown,\n type: `insert` | `update`,\n key?: TKey\n ): TOutput | never {\n return this._mutations.validateData(data, type, key)\n }\n\n /**\n * Inserts one or more items into the collection\n * @param items - Single item or array of items to insert\n * @param config - Optional configuration including metadata\n * @returns A Transaction object representing the insert operation(s)\n * @throws {SchemaValidationError} If the data fails schema validation\n * @example\n * // Insert a single todo (requires onInsert handler)\n * const tx = collection.insert({ id: \"1\", text: \"Buy milk\", completed: false })\n * await tx.isPersisted.promise\n *\n * @example\n * // Insert multiple todos at once\n * const tx = collection.insert([\n * { id: \"1\", text: \"Buy milk\", completed: false },\n * { id: \"2\", text: \"Walk dog\", completed: true }\n * ])\n * await tx.isPersisted.promise\n *\n * @example\n * // Insert with metadata\n * const tx = collection.insert({ id: \"1\", text: \"Buy groceries\" },\n * { metadata: { source: \"mobile-app\" } }\n * )\n * await tx.isPersisted.promise\n *\n * @example\n * // Handle errors\n * try {\n * const tx = collection.insert({ id: \"1\", text: \"New item\" })\n * await tx.isPersisted.promise\n * console.log('Insert successful')\n * } catch (error) {\n * console.log('Insert failed:', error)\n * }\n */\n insert = (data: TInput | Array<TInput>, config?: InsertConfig) => {\n return this._mutations.insert(data, config)\n }\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param keys - Single key or array of keys to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update single item by key\n * const tx = collection.update(\"todo-1\", (draft) => {\n * draft.completed = true\n * })\n * await tx.isPersisted.promise\n *\n * @example\n * // Update multiple items\n * const tx = collection.update([\"todo-1\", \"todo-2\"], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n * await tx.isPersisted.promise\n *\n * @example\n * // Update with metadata\n * const tx = collection.update(\"todo-1\",\n * { metadata: { reason: \"user update\" } },\n * (draft) => { draft.text = \"Updated text\" }\n * )\n * await tx.isPersisted.promise\n *\n * @example\n * // Handle errors\n * try {\n * const tx = collection.update(\"item-1\", draft => { draft.value = \"new\" })\n * await tx.isPersisted.promise\n * console.log('Update successful')\n * } catch (error) {\n * console.log('Update failed:', error)\n * }\n */\n\n // Overload 1: Update multiple items with a callback\n update(\n key: Array<TKey | unknown>,\n callback: (drafts: Array<WritableDeep<TInput>>) => void\n ): TransactionType\n\n // Overload 2: Update multiple items with config and a callback\n update(\n keys: Array<TKey | unknown>,\n config: OperationConfig,\n callback: (drafts: Array<WritableDeep<TInput>>) => void\n ): TransactionType\n\n // Overload 3: Update a single item with a callback\n update(\n id: TKey | unknown,\n callback: (draft: WritableDeep<TInput>) => void\n ): TransactionType\n\n // Overload 4: Update a single item with config and a callback\n update(\n id: TKey | unknown,\n config: OperationConfig,\n callback: (draft: WritableDeep<TInput>) => void\n ): TransactionType\n\n update(\n keys: (TKey | unknown) | Array<TKey | unknown>,\n configOrCallback:\n | ((draft: WritableDeep<TInput>) => void)\n | ((drafts: Array<WritableDeep<TInput>>) => void)\n | OperationConfig,\n maybeCallback?:\n | ((draft: WritableDeep<TInput>) => void)\n | ((drafts: Array<WritableDeep<TInput>>) => void)\n ) {\n return this._mutations.update(keys, configOrCallback, maybeCallback)\n }\n\n /**\n * Deletes one or more items from the collection\n * @param keys - Single key or array of keys to delete\n * @param config - Optional configuration including metadata\n * @returns A Transaction object representing the delete operation(s)\n * @example\n * // Delete a single item\n * const tx = collection.delete(\"todo-1\")\n * await tx.isPersisted.promise\n *\n * @example\n * // Delete multiple items\n * const tx = collection.delete([\"todo-1\", \"todo-2\"])\n * await tx.isPersisted.promise\n *\n * @example\n * // Delete with metadata\n * const tx = collection.delete(\"todo-1\", { metadata: { reason: \"completed\" } })\n * await tx.isPersisted.promise\n *\n * @example\n * // Handle errors\n * try {\n * const tx = collection.delete(\"item-1\")\n * await tx.isPersisted.promise\n * console.log('Delete successful')\n * } catch (error) {\n * console.log('Delete failed:', error)\n * }\n */\n delete = (\n keys: Array<TKey> | TKey,\n config?: OperationConfig\n ): TransactionType<any> => {\n return this._mutations.delete(keys, config)\n }\n\n /**\n * Gets the current state of the collection as a Map\n * @returns Map containing all items in the collection, with keys as identifiers\n * @example\n * const itemsMap = collection.state\n * console.log(`Collection has ${itemsMap.size} items`)\n *\n * for (const [key, item] of itemsMap) {\n * console.log(`${key}: ${item.title}`)\n * }\n *\n * // Check if specific item exists\n * if (itemsMap.has(\"todo-1\")) {\n * console.log(\"Todo 1 exists:\", itemsMap.get(\"todo-1\"))\n * }\n */\n get state() {\n const result = new Map<TKey, TOutput>()\n for (const [key, value] of this.entries()) {\n result.set(key, value)\n }\n return result\n }\n\n /**\n * Gets the current state of the collection as a Map, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to a Map containing all items in the collection\n */\n stateWhenReady(): Promise<Map<TKey, TOutput>> {\n // If we already have data or collection is ready, resolve immediately\n if (this.size > 0 || this.isReady()) {\n return Promise.resolve(this.state)\n }\n\n // Use preload to ensure the collection starts loading, then return the state\n return this.preload().then(() => this.state)\n }\n\n /**\n * Gets the current state of the collection as an Array\n *\n * @returns An Array containing all items in the collection\n */\n get toArray() {\n return Array.from(this.values())\n }\n\n /**\n * Gets the current state of the collection as an Array, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to an Array containing all items in the collection\n */\n toArrayWhenReady(): Promise<Array<TOutput>> {\n // If we already have data or collection is ready, resolve immediately\n if (this.size > 0 || this.isReady()) {\n return Promise.resolve(this.toArray)\n }\n\n // Use preload to ensure the collection starts loading, then return the array\n return this.preload().then(() => this.toArray)\n }\n\n /**\n * Returns the current state of the collection as an array of changes\n * @param options - Options including optional where filter\n * @returns An array of changes\n * @example\n * // Get all items as changes\n * const allChanges = collection.currentStateAsChanges()\n *\n * // Get only items matching a condition\n * const activeChanges = collection.currentStateAsChanges({\n * where: (row) => row.status === 'active'\n * })\n *\n * // Get only items using a pre-compiled expression\n * const activeChanges = collection.currentStateAsChanges({\n * whereExpression: eq(row.status, 'active')\n * })\n */\n public currentStateAsChanges(\n options: CurrentStateAsChangesOptions = {}\n ): Array<ChangeMessage<TOutput>> | void {\n return currentStateAsChanges(this, options)\n }\n\n /**\n * Subscribe to changes in the collection\n * @param callback - Function called when items change\n * @param options - Subscription options including includeInitialState and where filter\n * @returns Unsubscribe function - Call this to stop listening for changes\n * @example\n * // Basic subscription\n * const subscription = collection.subscribeChanges((changes) => {\n * changes.forEach(change => {\n * console.log(`${change.type}: ${change.key}`, change.value)\n * })\n * })\n *\n * // Later: subscription.unsubscribe()\n *\n * @example\n * // Include current state immediately\n * const subscription = collection.subscribeChanges((changes) => {\n * updateUI(changes)\n * }, { includeInitialState: true })\n *\n * @example\n * // Subscribe only to changes matching a condition\n * const subscription = collection.subscribeChanges((changes) => {\n * updateUI(changes)\n * }, {\n * includeInitialState: true,\n * where: (row) => row.status === 'active'\n * })\n *\n * @example\n * // Subscribe using a pre-compiled expression\n * const subscription = collection.subscribeChanges((changes) => {\n * updateUI(changes)\n * }, {\n * includeInitialState: true,\n * whereExpression: eq(row.status, 'active')\n * })\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<TOutput>>) => void,\n options: SubscribeChangesOptions = {}\n ): CollectionSubscription {\n return this._changes.subscribeChanges(callback, options)\n }\n\n /**\n * Subscribe to a collection event\n */\n public on<T extends keyof AllCollectionEvents>(\n event: T,\n callback: CollectionEventHandler<T>\n ) {\n return this._events.on(event, callback)\n }\n\n /**\n * Subscribe to a collection event once\n */\n public once<T extends keyof AllCollectionEvents>(\n event: T,\n callback: CollectionEventHandler<T>\n ) {\n return this._events.once(event, callback)\n }\n\n /**\n * Unsubscribe from a collection event\n */\n public off<T extends keyof AllCollectionEvents>(\n event: T,\n callback: CollectionEventHandler<T>\n ) {\n this._events.off(event, callback)\n }\n\n /**\n * Wait for a collection event\n */\n public waitFor<T extends keyof AllCollectionEvents>(\n event: T,\n timeout?: number\n ) {\n return this._events.waitFor(event, timeout)\n }\n\n /**\n * Clean up the collection by stopping sync and clearing data\n * This can be called manually or automatically by garbage collection\n */\n public async cleanup(): Promise<void> {\n this._lifecycle.cleanup()\n return Promise.resolve()\n }\n}\n"],"names":["config","CollectionRequiresConfigError","CollectionRequiresSyncConfigError","CollectionChangesManager","CollectionEventsManager","CollectionIndexesManager","CollectionLifecycleManager","CollectionMutationsManager","CollectionStateManager","CollectionSyncManager","currentStateAsChanges"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../../../src/collection/index.ts"],"sourcesContent":["import {\n CollectionRequiresConfigError,\n CollectionRequiresSyncConfigError,\n} from \"../errors\"\nimport { currentStateAsChanges } from \"./change-events\"\n\nimport { CollectionStateManager } from \"./state\"\nimport { CollectionChangesManager } from \"./changes\"\nimport { CollectionLifecycleManager } from \"./lifecycle.js\"\nimport { CollectionSyncManager } from \"./sync\"\nimport { CollectionIndexesManager } from \"./indexes\"\nimport { CollectionMutationsManager } from \"./mutations\"\nimport { CollectionEventsManager } from \"./events.js\"\nimport type { CollectionSubscription } from \"./subscription\"\nimport type { AllCollectionEvents, CollectionEventHandler } from \"./events.js\"\nimport type { BaseIndex, IndexResolver } from \"../indexes/base-index.js\"\nimport type { IndexOptions } from \"../indexes/index-options.js\"\nimport type {\n ChangeMessage,\n CollectionConfig,\n CollectionStatus,\n CurrentStateAsChangesOptions,\n Fn,\n InferSchemaInput,\n InferSchemaOutput,\n InsertConfig,\n NonSingleResult,\n OperationConfig,\n SingleResult,\n StringCollationConfig,\n SubscribeChangesOptions,\n Transaction as TransactionType,\n UtilsRecord,\n WritableDeep,\n} from \"../types\"\nimport type { SingleRowRefProxy } from \"../query/builder/ref-proxy\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\nimport type { BTreeIndex } from \"../indexes/btree-index.js\"\nimport type { IndexProxy } from \"../indexes/lazy-index.js\"\n\n/**\n * Enhanced Collection interface that includes both data type T and utilities TUtils\n * @template T - The type of items in the collection\n * @template TKey - The type of the key for the collection\n * @template TUtils - The utilities record type\n * @template TInsertInput - The type for insert operations (can be different from T for schemas with defaults)\n */\nexport interface Collection<\n T extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = UtilsRecord,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInsertInput extends object = T,\n> extends CollectionImpl<T, TKey, TUtils, TSchema, TInsertInput> {\n readonly utils: TUtils\n readonly singleResult?: true\n}\n\n/**\n * Creates a new Collection instance with the given configuration\n *\n * @template T - The schema type if a schema is provided, otherwise the type of items in the collection\n * @template TKey - The type of the key for the collection\n * @template TUtils - The utilities record type\n * @param options - Collection options with optional utilities\n * @returns A new Collection with utilities exposed both at top level and under .utils\n *\n * @example\n * // Pattern 1: With operation handlers (direct collection calls)\n * const todos = createCollection({\n * id: \"todos\",\n * getKey: (todo) => todo.id,\n * schema,\n * onInsert: async ({ transaction, collection }) => {\n * // Send to API\n * await api.createTodo(transaction.mutations[0].modified)\n * },\n * onUpdate: async ({ transaction, collection }) => {\n * await api.updateTodo(transaction.mutations[0].modified)\n * },\n * onDelete: async ({ transaction, collection }) => {\n * await api.deleteTodo(transaction.mutations[0].key)\n * },\n * sync: { sync: () => {} }\n * })\n *\n * // Direct usage (handlers manage transactions)\n * const tx = todos.insert({ id: \"1\", text: \"Buy milk\", completed: false })\n * await tx.isPersisted.promise\n *\n * @example\n * // Pattern 2: Manual transaction management\n * const todos = createCollection({\n * getKey: (todo) => todo.id,\n * schema: todoSchema,\n * sync: { sync: () => {} }\n * })\n *\n * // Explicit transaction usage\n * const tx = createTransaction({\n * mutationFn: async ({ transaction }) => {\n * // Handle all mutations in transaction\n * await api.saveChanges(transaction.mutations)\n * }\n * })\n *\n * tx.mutate(() => {\n * todos.insert({ id: \"1\", text: \"Buy milk\" })\n * todos.update(\"2\", draft => { draft.completed = true })\n * })\n *\n * await tx.isPersisted.promise\n *\n * @example\n * // Using schema for type inference (preferred as it also gives you client side validation)\n * const todoSchema = z.object({\n * id: z.string(),\n * title: z.string(),\n * completed: z.boolean()\n * })\n *\n * const todos = createCollection({\n * schema: todoSchema,\n * getKey: (todo) => todo.id,\n * sync: { sync: () => {} }\n * })\n *\n */\n\n// Overload for when schema is provided\nexport function createCollection<\n T extends StandardSchemaV1,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = UtilsRecord,\n>(\n options: CollectionConfig<InferSchemaOutput<T>, TKey, T, TUtils> & {\n schema: T\n utils?: TUtils\n } & NonSingleResult\n): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>> &\n NonSingleResult\n\n// Overload for when schema is provided and singleResult is true\nexport function createCollection<\n T extends StandardSchemaV1,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = UtilsRecord,\n>(\n options: CollectionConfig<InferSchemaOutput<T>, TKey, T, TUtils> & {\n schema: T\n utils?: TUtils\n } & SingleResult\n): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>> &\n SingleResult\n\n// Overload for when no schema is provided\n// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config\nexport function createCollection<\n T extends object,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = UtilsRecord,\n>(\n options: CollectionConfig<T, TKey, never, TUtils> & {\n schema?: never // prohibit schema if an explicit type is provided\n utils?: TUtils\n } & NonSingleResult\n): Collection<T, TKey, TUtils, never, T> & NonSingleResult\n\n// Overload for when no schema is provided and singleResult is true\n// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config\nexport function createCollection<\n T extends object,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = UtilsRecord,\n>(\n options: CollectionConfig<T, TKey, never, TUtils> & {\n schema?: never // prohibit schema if an explicit type is provided\n utils?: TUtils\n } & SingleResult\n): Collection<T, TKey, TUtils, never, T> & SingleResult\n\n// Implementation\nexport function createCollection(\n options: CollectionConfig<any, string | number, any> & {\n schema?: StandardSchemaV1\n utils?: UtilsRecord\n }\n): Collection<any, string | number, UtilsRecord, any, any> {\n const collection = new CollectionImpl<any, string | number, any, any, any>(\n options\n )\n\n // Attach utils to collection\n if (options.utils) {\n collection.utils = options.utils\n } else {\n collection.utils = {}\n }\n\n return collection\n}\n\nexport class CollectionImpl<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TUtils extends UtilsRecord = {},\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n public id: string\n public config: CollectionConfig<TOutput, TKey, TSchema>\n\n // Utilities namespace\n // This is populated by createCollection\n public utils: Record<string, Fn> = {}\n\n // Managers\n private _events: CollectionEventsManager\n private _changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n public _lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n public _sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n private _indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n private _mutations: CollectionMutationsManager<\n TOutput,\n TKey,\n TUtils,\n TSchema,\n TInput\n >\n // The core state of the collection is \"public\" so that is accessible in tests\n // and for debugging\n public _state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n\n private comparisonOpts: StringCollationConfig\n\n /**\n * Creates a new Collection instance\n *\n * @param config - Configuration object for the collection\n * @throws Error if sync config is missing\n */\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>) {\n // eslint-disable-next-line\n if (!config) {\n throw new CollectionRequiresConfigError()\n }\n\n // eslint-disable-next-line\n if (!config.sync) {\n throw new CollectionRequiresSyncConfigError()\n }\n\n if (config.id) {\n this.id = config.id\n } else {\n this.id = crypto.randomUUID()\n }\n\n // Set default values for optional config properties\n this.config = {\n ...config,\n autoIndex: config.autoIndex ?? `eager`,\n }\n\n this._changes = new CollectionChangesManager()\n this._events = new CollectionEventsManager()\n this._indexes = new CollectionIndexesManager()\n this._lifecycle = new CollectionLifecycleManager(config, this.id)\n this._mutations = new CollectionMutationsManager(config, this.id)\n this._state = new CollectionStateManager(config)\n this._sync = new CollectionSyncManager(config, this.id)\n\n this.comparisonOpts = buildCompareOptionsFromConfig(config)\n\n this._changes.setDeps({\n collection: this, // Required for passing to CollectionSubscription\n lifecycle: this._lifecycle,\n sync: this._sync,\n events: this._events,\n })\n this._events.setDeps({\n collection: this, // Required for adding to emitted events\n })\n this._indexes.setDeps({\n state: this._state,\n lifecycle: this._lifecycle,\n })\n this._lifecycle.setDeps({\n changes: this._changes,\n events: this._events,\n indexes: this._indexes,\n state: this._state,\n sync: this._sync,\n })\n this._mutations.setDeps({\n collection: this, // Required for passing to config.onInsert/onUpdate/onDelete and annotating mutations\n lifecycle: this._lifecycle,\n state: this._state,\n })\n this._state.setDeps({\n collection: this, // Required for filtering events to only include this collection\n lifecycle: this._lifecycle,\n changes: this._changes,\n indexes: this._indexes,\n })\n this._sync.setDeps({\n collection: this, // Required for passing to config.sync callback\n state: this._state,\n lifecycle: this._lifecycle,\n events: this._events,\n })\n\n // Only start sync immediately if explicitly enabled\n if (config.startSync === true) {\n this._sync.startSync()\n }\n }\n\n /**\n * Gets the current status of the collection\n */\n public get status(): CollectionStatus {\n return this._lifecycle.status\n }\n\n /**\n * Get the number of subscribers to the collection\n */\n public get subscriberCount(): number {\n return this._changes.activeSubscribersCount\n }\n\n /**\n * Register a callback to be executed when the collection first becomes ready\n * Useful for preloading collections\n * @param callback Function to call when the collection first becomes ready\n * @example\n * collection.onFirstReady(() => {\n * console.log('Collection is ready for the first time')\n * // Safe to access collection.state now\n * })\n */\n public onFirstReady(callback: () => void): void {\n return this._lifecycle.onFirstReady(callback)\n }\n\n /**\n * Check if the collection is ready for use\n * Returns true if the collection has been marked as ready by its sync implementation\n * @returns true if the collection is ready, false otherwise\n * @example\n * if (collection.isReady()) {\n * console.log('Collection is ready, data is available')\n * // Safe to access collection.state\n * } else {\n * console.log('Collection is still loading')\n * }\n */\n public isReady(): boolean {\n return this._lifecycle.status === `ready`\n }\n\n /**\n * Check if the collection is currently loading more data\n * @returns true if the collection has pending load more operations, false otherwise\n */\n public get isLoadingSubset(): boolean {\n return this._sync.isLoadingSubset\n }\n\n /**\n * Start sync immediately - internal method for compiled queries\n * This bypasses lazy loading for special cases like live query results\n */\n public startSyncImmediate(): void {\n this._sync.startSync()\n }\n\n /**\n * Preload the collection data by starting sync if not already started\n * Multiple concurrent calls will share the same promise\n */\n public preload(): Promise<void> {\n return this._sync.preload()\n }\n\n /**\n * Get the current value for a key (virtual derived state)\n */\n public get(key: TKey): TOutput | undefined {\n return this._state.get(key)\n }\n\n /**\n * Check if a key exists in the collection (virtual derived state)\n */\n public has(key: TKey): boolean {\n return this._state.has(key)\n }\n\n /**\n * Get the current size of the collection (cached)\n */\n public get size(): number {\n return this._state.size\n }\n\n /**\n * Get all keys (virtual derived state)\n */\n public *keys(): IterableIterator<TKey> {\n yield* this._state.keys()\n }\n\n /**\n * Get all values (virtual derived state)\n */\n public *values(): IterableIterator<TOutput> {\n yield* this._state.values()\n }\n\n /**\n * Get all entries (virtual derived state)\n */\n public *entries(): IterableIterator<[TKey, TOutput]> {\n yield* this._state.entries()\n }\n\n /**\n * Get all entries (virtual derived state)\n */\n public *[Symbol.iterator](): IterableIterator<[TKey, TOutput]> {\n yield* this._state[Symbol.iterator]()\n }\n\n /**\n * Execute a callback for each entry in the collection\n */\n public forEach(\n callbackfn: (value: TOutput, key: TKey, index: number) => void\n ): void {\n return this._state.forEach(callbackfn)\n }\n\n /**\n * Create a new array with the results of calling a function for each entry in the collection\n */\n public map<U>(\n callbackfn: (value: TOutput, key: TKey, index: number) => U\n ): Array<U> {\n return this._state.map(callbackfn)\n }\n\n public getKeyFromItem(item: TOutput): TKey {\n return this.config.getKey(item)\n }\n\n /**\n * Creates an index on a collection for faster queries.\n * Indexes significantly improve query performance by allowing constant time lookups\n * and logarithmic time range queries instead of full scans.\n *\n * @template TResolver - The type of the index resolver (constructor or async loader)\n * @param indexCallback - Function that extracts the indexed value from each item\n * @param config - Configuration including index type and type-specific options\n * @returns An index proxy that provides access to the index when ready\n *\n * @example\n * // Create a default B+ tree index\n * const ageIndex = collection.createIndex((row) => row.age)\n *\n * // Create a ordered index with custom options\n * const ageIndex = collection.createIndex((row) => row.age, {\n * indexType: BTreeIndex,\n * options: {\n * compareFn: customComparator,\n * compareOptions: { direction: 'asc', nulls: 'first', stringSort: 'lexical' }\n * },\n * name: 'age_btree'\n * })\n *\n * // Create an async-loaded index\n * const textIndex = collection.createIndex((row) => row.content, {\n * indexType: async () => {\n * const { FullTextIndex } = await import('./indexes/fulltext.js')\n * return FullTextIndex\n * },\n * options: { language: 'en' }\n * })\n */\n public createIndex<TResolver extends IndexResolver<TKey> = typeof BTreeIndex>(\n indexCallback: (row: SingleRowRefProxy<TOutput>) => any,\n config: IndexOptions<TResolver> = {}\n ): IndexProxy<TKey> {\n return this._indexes.createIndex(indexCallback, config)\n }\n\n /**\n * Get resolved indexes for query optimization\n */\n get indexes(): Map<number, BaseIndex<TKey>> {\n return this._indexes.indexes\n }\n\n /**\n * Validates the data against the schema\n */\n public validateData(\n data: unknown,\n type: `insert` | `update`,\n key?: TKey\n ): TOutput | never {\n return this._mutations.validateData(data, type, key)\n }\n\n get compareOptions(): StringCollationConfig {\n // return a copy such that no one can mutate the internal comparison object\n return { ...this.comparisonOpts }\n }\n\n /**\n * Inserts one or more items into the collection\n * @param items - Single item or array of items to insert\n * @param config - Optional configuration including metadata\n * @returns A Transaction object representing the insert operation(s)\n * @throws {SchemaValidationError} If the data fails schema validation\n * @example\n * // Insert a single todo (requires onInsert handler)\n * const tx = collection.insert({ id: \"1\", text: \"Buy milk\", completed: false })\n * await tx.isPersisted.promise\n *\n * @example\n * // Insert multiple todos at once\n * const tx = collection.insert([\n * { id: \"1\", text: \"Buy milk\", completed: false },\n * { id: \"2\", text: \"Walk dog\", completed: true }\n * ])\n * await tx.isPersisted.promise\n *\n * @example\n * // Insert with metadata\n * const tx = collection.insert({ id: \"1\", text: \"Buy groceries\" },\n * { metadata: { source: \"mobile-app\" } }\n * )\n * await tx.isPersisted.promise\n *\n * @example\n * // Handle errors\n * try {\n * const tx = collection.insert({ id: \"1\", text: \"New item\" })\n * await tx.isPersisted.promise\n * console.log('Insert successful')\n * } catch (error) {\n * console.log('Insert failed:', error)\n * }\n */\n insert = (data: TInput | Array<TInput>, config?: InsertConfig) => {\n return this._mutations.insert(data, config)\n }\n\n /**\n * Updates one or more items in the collection using a callback function\n * @param keys - Single key or array of keys to update\n * @param configOrCallback - Either update configuration or update callback\n * @param maybeCallback - Update callback if config was provided\n * @returns A Transaction object representing the update operation(s)\n * @throws {SchemaValidationError} If the updated data fails schema validation\n * @example\n * // Update single item by key\n * const tx = collection.update(\"todo-1\", (draft) => {\n * draft.completed = true\n * })\n * await tx.isPersisted.promise\n *\n * @example\n * // Update multiple items\n * const tx = collection.update([\"todo-1\", \"todo-2\"], (drafts) => {\n * drafts.forEach(draft => { draft.completed = true })\n * })\n * await tx.isPersisted.promise\n *\n * @example\n * // Update with metadata\n * const tx = collection.update(\"todo-1\",\n * { metadata: { reason: \"user update\" } },\n * (draft) => { draft.text = \"Updated text\" }\n * )\n * await tx.isPersisted.promise\n *\n * @example\n * // Handle errors\n * try {\n * const tx = collection.update(\"item-1\", draft => { draft.value = \"new\" })\n * await tx.isPersisted.promise\n * console.log('Update successful')\n * } catch (error) {\n * console.log('Update failed:', error)\n * }\n */\n\n // Overload 1: Update multiple items with a callback\n update(\n key: Array<TKey | unknown>,\n callback: (drafts: Array<WritableDeep<TInput>>) => void\n ): TransactionType\n\n // Overload 2: Update multiple items with config and a callback\n update(\n keys: Array<TKey | unknown>,\n config: OperationConfig,\n callback: (drafts: Array<WritableDeep<TInput>>) => void\n ): TransactionType\n\n // Overload 3: Update a single item with a callback\n update(\n id: TKey | unknown,\n callback: (draft: WritableDeep<TInput>) => void\n ): TransactionType\n\n // Overload 4: Update a single item with config and a callback\n update(\n id: TKey | unknown,\n config: OperationConfig,\n callback: (draft: WritableDeep<TInput>) => void\n ): TransactionType\n\n update(\n keys: (TKey | unknown) | Array<TKey | unknown>,\n configOrCallback:\n | ((draft: WritableDeep<TInput>) => void)\n | ((drafts: Array<WritableDeep<TInput>>) => void)\n | OperationConfig,\n maybeCallback?:\n | ((draft: WritableDeep<TInput>) => void)\n | ((drafts: Array<WritableDeep<TInput>>) => void)\n ) {\n return this._mutations.update(keys, configOrCallback, maybeCallback)\n }\n\n /**\n * Deletes one or more items from the collection\n * @param keys - Single key or array of keys to delete\n * @param config - Optional configuration including metadata\n * @returns A Transaction object representing the delete operation(s)\n * @example\n * // Delete a single item\n * const tx = collection.delete(\"todo-1\")\n * await tx.isPersisted.promise\n *\n * @example\n * // Delete multiple items\n * const tx = collection.delete([\"todo-1\", \"todo-2\"])\n * await tx.isPersisted.promise\n *\n * @example\n * // Delete with metadata\n * const tx = collection.delete(\"todo-1\", { metadata: { reason: \"completed\" } })\n * await tx.isPersisted.promise\n *\n * @example\n * // Handle errors\n * try {\n * const tx = collection.delete(\"item-1\")\n * await tx.isPersisted.promise\n * console.log('Delete successful')\n * } catch (error) {\n * console.log('Delete failed:', error)\n * }\n */\n delete = (\n keys: Array<TKey> | TKey,\n config?: OperationConfig\n ): TransactionType<any> => {\n return this._mutations.delete(keys, config)\n }\n\n /**\n * Gets the current state of the collection as a Map\n * @returns Map containing all items in the collection, with keys as identifiers\n * @example\n * const itemsMap = collection.state\n * console.log(`Collection has ${itemsMap.size} items`)\n *\n * for (const [key, item] of itemsMap) {\n * console.log(`${key}: ${item.title}`)\n * }\n *\n * // Check if specific item exists\n * if (itemsMap.has(\"todo-1\")) {\n * console.log(\"Todo 1 exists:\", itemsMap.get(\"todo-1\"))\n * }\n */\n get state() {\n const result = new Map<TKey, TOutput>()\n for (const [key, value] of this.entries()) {\n result.set(key, value)\n }\n return result\n }\n\n /**\n * Gets the current state of the collection as a Map, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to a Map containing all items in the collection\n */\n stateWhenReady(): Promise<Map<TKey, TOutput>> {\n // If we already have data or collection is ready, resolve immediately\n if (this.size > 0 || this.isReady()) {\n return Promise.resolve(this.state)\n }\n\n // Use preload to ensure the collection starts loading, then return the state\n return this.preload().then(() => this.state)\n }\n\n /**\n * Gets the current state of the collection as an Array\n *\n * @returns An Array containing all items in the collection\n */\n get toArray() {\n return Array.from(this.values())\n }\n\n /**\n * Gets the current state of the collection as an Array, but only resolves when data is available\n * Waits for the first sync commit to complete before resolving\n *\n * @returns Promise that resolves to an Array containing all items in the collection\n */\n toArrayWhenReady(): Promise<Array<TOutput>> {\n // If we already have data or collection is ready, resolve immediately\n if (this.size > 0 || this.isReady()) {\n return Promise.resolve(this.toArray)\n }\n\n // Use preload to ensure the collection starts loading, then return the array\n return this.preload().then(() => this.toArray)\n }\n\n /**\n * Returns the current state of the collection as an array of changes\n * @param options - Options including optional where filter\n * @returns An array of changes\n * @example\n * // Get all items as changes\n * const allChanges = collection.currentStateAsChanges()\n *\n * // Get only items matching a condition\n * const activeChanges = collection.currentStateAsChanges({\n * where: (row) => row.status === 'active'\n * })\n *\n * // Get only items using a pre-compiled expression\n * const activeChanges = collection.currentStateAsChanges({\n * whereExpression: eq(row.status, 'active')\n * })\n */\n public currentStateAsChanges(\n options: CurrentStateAsChangesOptions = {}\n ): Array<ChangeMessage<TOutput>> | void {\n return currentStateAsChanges(this, options)\n }\n\n /**\n * Subscribe to changes in the collection\n * @param callback - Function called when items change\n * @param options - Subscription options including includeInitialState and where filter\n * @returns Unsubscribe function - Call this to stop listening for changes\n * @example\n * // Basic subscription\n * const subscription = collection.subscribeChanges((changes) => {\n * changes.forEach(change => {\n * console.log(`${change.type}: ${change.key}`, change.value)\n * })\n * })\n *\n * // Later: subscription.unsubscribe()\n *\n * @example\n * // Include current state immediately\n * const subscription = collection.subscribeChanges((changes) => {\n * updateUI(changes)\n * }, { includeInitialState: true })\n *\n * @example\n * // Subscribe only to changes matching a condition\n * const subscription = collection.subscribeChanges((changes) => {\n * updateUI(changes)\n * }, {\n * includeInitialState: true,\n * where: (row) => row.status === 'active'\n * })\n *\n * @example\n * // Subscribe using a pre-compiled expression\n * const subscription = collection.subscribeChanges((changes) => {\n * updateUI(changes)\n * }, {\n * includeInitialState: true,\n * whereExpression: eq(row.status, 'active')\n * })\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<TOutput>>) => void,\n options: SubscribeChangesOptions = {}\n ): CollectionSubscription {\n return this._changes.subscribeChanges(callback, options)\n }\n\n /**\n * Subscribe to a collection event\n */\n public on<T extends keyof AllCollectionEvents>(\n event: T,\n callback: CollectionEventHandler<T>\n ) {\n return this._events.on(event, callback)\n }\n\n /**\n * Subscribe to a collection event once\n */\n public once<T extends keyof AllCollectionEvents>(\n event: T,\n callback: CollectionEventHandler<T>\n ) {\n return this._events.once(event, callback)\n }\n\n /**\n * Unsubscribe from a collection event\n */\n public off<T extends keyof AllCollectionEvents>(\n event: T,\n callback: CollectionEventHandler<T>\n ) {\n this._events.off(event, callback)\n }\n\n /**\n * Wait for a collection event\n */\n public waitFor<T extends keyof AllCollectionEvents>(\n event: T,\n timeout?: number\n ) {\n return this._events.waitFor(event, timeout)\n }\n\n /**\n * Clean up the collection by stopping sync and clearing data\n * This can be called manually or automatically by garbage collection\n */\n public async cleanup(): Promise<void> {\n this._lifecycle.cleanup()\n return Promise.resolve()\n }\n}\n\nfunction buildCompareOptionsFromConfig(\n config: CollectionConfig<any, any, any>\n): StringCollationConfig {\n if (config.defaultStringCollation) {\n const options = config.defaultStringCollation\n return {\n stringSort: options.stringSort ?? `locale`,\n locale: options.stringSort === `locale` ? options.locale : undefined,\n localeOptions:\n options.stringSort === `locale` ? options.localeOptions : undefined,\n }\n } else {\n return {\n stringSort: `locale`,\n }\n }\n}\n"],"names":["config","CollectionRequiresConfigError","CollectionRequiresSyncConfigError","CollectionChangesManager","CollectionEventsManager","CollectionIndexesManager","CollectionLifecycleManager","CollectionMutationsManager","CollectionStateManager","CollectionSyncManager","currentStateAsChanges"],"mappings":"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"}
@@ -5,7 +5,7 @@ import { CollectionSubscription } from './subscription.cjs';
5
5
  import { AllCollectionEvents, CollectionEventHandler } from './events.js';
6
6
  import { BaseIndex, IndexResolver } from '../indexes/base-index.js';
7
7
  import { IndexOptions } from '../indexes/index-options.js';
8
- import { ChangeMessage, CollectionConfig, CollectionStatus, CurrentStateAsChangesOptions, Fn, InferSchemaInput, InferSchemaOutput, InsertConfig, NonSingleResult, OperationConfig, SingleResult, SubscribeChangesOptions, Transaction as TransactionType, UtilsRecord, WritableDeep } from '../types.cjs';
8
+ import { ChangeMessage, CollectionConfig, CollectionStatus, CurrentStateAsChangesOptions, Fn, InferSchemaInput, InferSchemaOutput, InsertConfig, NonSingleResult, OperationConfig, SingleResult, StringCollationConfig, SubscribeChangesOptions, Transaction as TransactionType, UtilsRecord, WritableDeep } from '../types.cjs';
9
9
  import { SingleRowRefProxy } from '../query/builder/ref-proxy.cjs';
10
10
  import { StandardSchemaV1 } from '@standard-schema/spec';
11
11
  import { BTreeIndex } from '../indexes/btree-index.js';
@@ -91,19 +91,19 @@ export interface Collection<T extends object = Record<string, unknown>, TKey ext
91
91
  * })
92
92
  *
93
93
  */
94
- export declare function createCollection<T extends StandardSchemaV1, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord>(options: CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
94
+ export declare function createCollection<T extends StandardSchemaV1, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord>(options: CollectionConfig<InferSchemaOutput<T>, TKey, T, TUtils> & {
95
95
  schema: T;
96
96
  utils?: TUtils;
97
97
  } & NonSingleResult): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>> & NonSingleResult;
98
- export declare function createCollection<T extends StandardSchemaV1, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord>(options: CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
98
+ export declare function createCollection<T extends StandardSchemaV1, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord>(options: CollectionConfig<InferSchemaOutput<T>, TKey, T, TUtils> & {
99
99
  schema: T;
100
100
  utils?: TUtils;
101
101
  } & SingleResult): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>> & SingleResult;
102
- export declare function createCollection<T extends object, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord>(options: CollectionConfig<T, TKey, never> & {
102
+ export declare function createCollection<T extends object, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord>(options: CollectionConfig<T, TKey, never, TUtils> & {
103
103
  schema?: never;
104
104
  utils?: TUtils;
105
105
  } & NonSingleResult): Collection<T, TKey, TUtils, never, T> & NonSingleResult;
106
- export declare function createCollection<T extends object, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord>(options: CollectionConfig<T, TKey, never> & {
106
+ export declare function createCollection<T extends object, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord>(options: CollectionConfig<T, TKey, never, TUtils> & {
107
107
  schema?: never;
108
108
  utils?: TUtils;
109
109
  } & SingleResult): Collection<T, TKey, TUtils, never, T> & SingleResult;
@@ -118,6 +118,7 @@ export declare class CollectionImpl<TOutput extends object = Record<string, unkn
118
118
  private _indexes;
119
119
  private _mutations;
120
120
  _state: CollectionStateManager<TOutput, TKey, TSchema, TInput>;
121
+ private comparisonOpts;
121
122
  /**
122
123
  * Creates a new Collection instance
123
124
  *
@@ -251,6 +252,7 @@ export declare class CollectionImpl<TOutput extends object = Record<string, unkn
251
252
  * Validates the data against the schema
252
253
  */
253
254
  validateData(data: unknown, type: `insert` | `update`, key?: TKey): TOutput | never;
255
+ get compareOptions(): StringCollationConfig;
254
256
  /**
255
257
  * Inserts one or more items into the collection
256
258
  * @param items - Single item or array of items to insert
@@ -13,10 +13,13 @@ const pacedMutations = require("./paced-mutations.cjs");
13
13
  const baseIndex = require("./indexes/base-index.cjs");
14
14
  const btreeIndex = require("./indexes/btree-index.cjs");
15
15
  const lazyIndex = require("./indexes/lazy-index.cjs");
16
- const index$1 = require("./query/builder/index.cjs");
16
+ const expressionHelpers = require("./query/expression-helpers.cjs");
17
17
  const functions = require("./query/builder/functions.cjs");
18
+ const index$1 = require("./query/builder/index.cjs");
18
19
  const index$2 = require("./query/compiler/index.cjs");
19
20
  const liveQueryCollection = require("./query/live-query-collection.cjs");
21
+ const predicateUtils = require("./query/predicate-utils.cjs");
22
+ const subsetDedupe = require("./query/subset-dedupe.cjs");
20
23
  const debounceStrategy = require("./strategies/debounceStrategy.cjs");
21
24
  const queueStrategy = require("./strategies/queueStrategy.cjs");
22
25
  const throttleStrategy = require("./strategies/throttleStrategy.cjs");
@@ -121,8 +124,13 @@ exports.IndexOperation = baseIndex.IndexOperation;
121
124
  exports.BTreeIndex = btreeIndex.BTreeIndex;
122
125
  exports.IndexProxy = lazyIndex.IndexProxy;
123
126
  exports.LazyIndexWrapper = lazyIndex.LazyIndexWrapper;
124
- exports.BaseQueryBuilder = index$1.BaseQueryBuilder;
125
- exports.Query = index$1.Query;
127
+ exports.extractFieldPath = expressionHelpers.extractFieldPath;
128
+ exports.extractSimpleComparisons = expressionHelpers.extractSimpleComparisons;
129
+ exports.extractValue = expressionHelpers.extractValue;
130
+ exports.parseLoadSubsetOptions = expressionHelpers.parseLoadSubsetOptions;
131
+ exports.parseOrderByExpression = expressionHelpers.parseOrderByExpression;
132
+ exports.parseWhereExpression = expressionHelpers.parseWhereExpression;
133
+ exports.walkExpression = expressionHelpers.walkExpression;
126
134
  exports.add = functions.add;
127
135
  exports.and = functions.and;
128
136
  exports.avg = functions.avg;
@@ -144,12 +152,22 @@ exports.lte = functions.lte;
144
152
  exports.max = functions.max;
145
153
  exports.min = functions.min;
146
154
  exports.not = functions.not;
155
+ exports.operators = functions.operators;
147
156
  exports.or = functions.or;
148
157
  exports.sum = functions.sum;
149
158
  exports.upper = functions.upper;
159
+ exports.BaseQueryBuilder = index$1.BaseQueryBuilder;
160
+ exports.Query = index$1.Query;
150
161
  exports.compileQuery = index$2.compileQuery;
151
162
  exports.createLiveQueryCollection = liveQueryCollection.createLiveQueryCollection;
152
163
  exports.liveQueryCollectionOptions = liveQueryCollection.liveQueryCollectionOptions;
164
+ exports.isLimitSubset = predicateUtils.isLimitSubset;
165
+ exports.isOrderBySubset = predicateUtils.isOrderBySubset;
166
+ exports.isPredicateSubset = predicateUtils.isPredicateSubset;
167
+ exports.isWhereSubset = predicateUtils.isWhereSubset;
168
+ exports.minusWherePredicates = predicateUtils.minusWherePredicates;
169
+ exports.unionWherePredicates = predicateUtils.unionWherePredicates;
170
+ exports.DeduplicatedLoadSubset = subsetDedupe.DeduplicatedLoadSubset;
153
171
  exports.debounceStrategy = debounceStrategy.debounceStrategy;
154
172
  exports.queueStrategy = queueStrategy.queueStrategy;
155
173
  exports.throttleStrategy = throttleStrategy.throttleStrategy;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -15,5 +15,7 @@ export * from './indexes/base-index.js';
15
15
  export * from './indexes/btree-index.js';
16
16
  export * from './indexes/lazy-index.js';
17
17
  export { type IndexOptions } from './indexes/index-options.js';
18
+ export * from './query/expression-helpers.js';
18
19
  export type { Collection } from './collection/index.js';
19
20
  export { IR };
21
+ export { operators, type OperatorName } from './query/builder/functions.js';
@@ -8,12 +8,16 @@ function shouldAutoIndex(collection) {
8
8
  }
9
9
  return true;
10
10
  }
11
- function ensureIndexForField(fieldName, fieldPath, collection, compareOptions = utils.DEFAULT_COMPARE_OPTIONS, compareFn) {
11
+ function ensureIndexForField(fieldName, fieldPath, collection, compareOptions, compareFn) {
12
12
  if (!shouldAutoIndex(collection)) {
13
13
  return;
14
14
  }
15
+ const compareOpts = compareOptions ?? {
16
+ ...utils.DEFAULT_COMPARE_OPTIONS,
17
+ ...collection.compareOptions
18
+ };
15
19
  const existingIndex = Array.from(collection.indexes.values()).find(
16
- (index) => index.matchesField(fieldPath) && index.matchesCompareOptions(compareOptions)
20
+ (index) => index.matchesField(fieldPath) && index.matchesCompareOptions(compareOpts)
17
21
  );
18
22
  if (existingIndex) {
19
23
  return;
@@ -30,7 +34,7 @@ function ensureIndexForField(fieldName, fieldPath, collection, compareOptions =
30
34
  {
31
35
  name: `auto:${fieldPath.join(`.`)}`,
32
36
  indexType: btreeIndex.BTreeIndex,
33
- options: compareFn ? { compareFn, compareOptions } : {}
37
+ options: compareFn ? { compareFn, compareOptions: compareOpts } : {}
34
38
  }
35
39
  );
36
40
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"auto-index.cjs","sources":["../../../src/indexes/auto-index.ts"],"sourcesContent":["import { DEFAULT_COMPARE_OPTIONS } from \"../utils\"\nimport { BTreeIndex } from \"./btree-index\"\nimport type { CompareOptions } from \"../query/builder/types\"\nimport type { BasicExpression } from \"../query/ir\"\nimport type { CollectionImpl } from \"../collection/index.js\"\n\nexport interface AutoIndexConfig {\n autoIndex?: `off` | `eager`\n}\n\nfunction shouldAutoIndex(collection: CollectionImpl<any, any, any, any, any>) {\n // Only proceed if auto-indexing is enabled\n if (collection.config.autoIndex !== `eager`) {\n return false\n }\n\n return true\n}\n\nexport function ensureIndexForField<\n T extends Record<string, any>,\n TKey extends string | number,\n>(\n fieldName: string,\n fieldPath: Array<string>,\n collection: CollectionImpl<T, TKey, any, any, any>,\n compareOptions: CompareOptions = DEFAULT_COMPARE_OPTIONS,\n compareFn?: (a: any, b: any) => number\n) {\n if (!shouldAutoIndex(collection)) {\n return\n }\n\n // Check if we already have an index for this field\n const existingIndex = Array.from(collection.indexes.values()).find(\n (index) =>\n index.matchesField(fieldPath) &&\n index.matchesCompareOptions(compareOptions)\n )\n\n if (existingIndex) {\n return // Index already exists\n }\n\n // Create a new index for this field using the collection's createIndex method\n try {\n // Use the proxy-based approach to create the proper accessor for nested paths\n collection.createIndex(\n (row) => {\n // Navigate through the field path\n let current: any = row\n for (const part of fieldPath) {\n current = current[part]\n }\n return current\n },\n {\n name: `auto:${fieldPath.join(`.`)}`,\n indexType: BTreeIndex,\n options: compareFn ? { compareFn, compareOptions } : {},\n }\n )\n } catch (error) {\n console.warn(\n `${collection.id ? `[${collection.id}] ` : ``}Failed to create auto-index for field path \"${fieldPath.join(`.`)}\":`,\n error\n )\n }\n}\n\n/**\n * Analyzes a where expression and creates indexes for all simple operations on single fields\n */\nexport function ensureIndexForExpression<\n T extends Record<string, any>,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionImpl<T, TKey, any, any, any>\n): void {\n if (!shouldAutoIndex(collection)) {\n return\n }\n\n // Extract all indexable expressions and create indexes for them\n const indexableExpressions = extractIndexableExpressions(expression)\n\n for (const { fieldName, fieldPath } of indexableExpressions) {\n ensureIndexForField(fieldName, fieldPath, collection)\n }\n}\n\n/**\n * Extracts all indexable expressions from a where expression\n */\nfunction extractIndexableExpressions(\n expression: BasicExpression\n): Array<{ fieldName: string; fieldPath: Array<string> }> {\n const results: Array<{ fieldName: string; fieldPath: Array<string> }> = []\n\n function extractFromExpression(expr: BasicExpression): void {\n if (expr.type !== `func`) {\n return\n }\n\n const func = expr as any\n\n // Handle 'and' expressions by recursively processing all arguments\n if (func.name === `and`) {\n for (const arg of func.args) {\n extractFromExpression(arg)\n }\n return\n }\n\n // Check if this is a supported operation\n const supportedOperations = [`eq`, `gt`, `gte`, `lt`, `lte`, `in`]\n if (!supportedOperations.includes(func.name)) {\n return\n }\n\n // Check if the first argument is a property reference\n if (func.args.length < 1 || func.args[0].type !== `ref`) {\n return\n }\n\n const fieldRef = func.args[0]\n const fieldPath = fieldRef.path\n\n // Skip if the path is empty\n if (fieldPath.length === 0) {\n return\n }\n\n // For nested paths, use the full path joined with underscores as the field name\n // For simple paths, use the first (and only) element\n const fieldName = fieldPath.join(`_`)\n results.push({ fieldName, fieldPath })\n }\n\n extractFromExpression(expression)\n return results\n}\n"],"names":["DEFAULT_COMPARE_OPTIONS","BTreeIndex"],"mappings":";;;;AAUA,SAAS,gBAAgB,YAAqD;AAE5E,MAAI,WAAW,OAAO,cAAc,SAAS;AAC3C,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,SAAS,oBAId,WACA,WACA,YACA,iBAAiCA,MAAAA,yBACjC,WACA;AACA,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC;AAAA,EACF;AAGA,QAAM,gBAAgB,MAAM,KAAK,WAAW,QAAQ,OAAA,CAAQ,EAAE;AAAA,IAC5D,CAAC,UACC,MAAM,aAAa,SAAS,KAC5B,MAAM,sBAAsB,cAAc;AAAA,EAAA;AAG9C,MAAI,eAAe;AACjB;AAAA,EACF;AAGA,MAAI;AAEF,eAAW;AAAA,MACT,CAAC,QAAQ;AAEP,YAAI,UAAe;AACnB,mBAAW,QAAQ,WAAW;AAC5B,oBAAU,QAAQ,IAAI;AAAA,QACxB;AACA,eAAO;AAAA,MACT;AAAA,MACA;AAAA,QACE,MAAM,QAAQ,UAAU,KAAK,GAAG,CAAC;AAAA,QACjC,WAAWC,WAAAA;AAAAA,QACX,SAAS,YAAY,EAAE,WAAW,eAAA,IAAmB,CAAA;AAAA,MAAC;AAAA,IACxD;AAAA,EAEJ,SAAS,OAAO;AACd,YAAQ;AAAA,MACN,GAAG,WAAW,KAAK,IAAI,WAAW,EAAE,OAAO,EAAE,+CAA+C,UAAU,KAAK,GAAG,CAAC;AAAA,MAC/G;AAAA,IAAA;AAAA,EAEJ;AACF;AAKO,SAAS,yBAId,YACA,YACM;AACN,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC;AAAA,EACF;AAGA,QAAM,uBAAuB,4BAA4B,UAAU;AAEnE,aAAW,EAAE,WAAW,UAAA,KAAe,sBAAsB;AAC3D,wBAAoB,WAAW,WAAW,UAAU;AAAA,EACtD;AACF;AAKA,SAAS,4BACP,YACwD;AACxD,QAAM,UAAkE,CAAA;AAExE,WAAS,sBAAsB,MAA6B;AAC1D,QAAI,KAAK,SAAS,QAAQ;AACxB;AAAA,IACF;AAEA,UAAM,OAAO;AAGb,QAAI,KAAK,SAAS,OAAO;AACvB,iBAAW,OAAO,KAAK,MAAM;AAC3B,8BAAsB,GAAG;AAAA,MAC3B;AACA;AAAA,IACF;AAGA,UAAM,sBAAsB,CAAC,MAAM,MAAM,OAAO,MAAM,OAAO,IAAI;AACjE,QAAI,CAAC,oBAAoB,SAAS,KAAK,IAAI,GAAG;AAC5C;AAAA,IACF;AAGA,QAAI,KAAK,KAAK,SAAS,KAAK,KAAK,KAAK,CAAC,EAAE,SAAS,OAAO;AACvD;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,UAAM,YAAY,SAAS;AAG3B,QAAI,UAAU,WAAW,GAAG;AAC1B;AAAA,IACF;AAIA,UAAM,YAAY,UAAU,KAAK,GAAG;AACpC,YAAQ,KAAK,EAAE,WAAW,UAAA,CAAW;AAAA,EACvC;AAEA,wBAAsB,UAAU;AAChC,SAAO;AACT;;;"}
1
+ {"version":3,"file":"auto-index.cjs","sources":["../../../src/indexes/auto-index.ts"],"sourcesContent":["import { DEFAULT_COMPARE_OPTIONS } from \"../utils\"\nimport { BTreeIndex } from \"./btree-index\"\nimport type { CompareOptions } from \"../query/builder/types\"\nimport type { BasicExpression } from \"../query/ir\"\nimport type { CollectionImpl } from \"../collection/index.js\"\n\nexport interface AutoIndexConfig {\n autoIndex?: `off` | `eager`\n}\n\nfunction shouldAutoIndex(collection: CollectionImpl<any, any, any, any, any>) {\n // Only proceed if auto-indexing is enabled\n if (collection.config.autoIndex !== `eager`) {\n return false\n }\n\n return true\n}\n\nexport function ensureIndexForField<\n T extends Record<string, any>,\n TKey extends string | number,\n>(\n fieldName: string,\n fieldPath: Array<string>,\n collection: CollectionImpl<T, TKey, any, any, any>,\n compareOptions?: CompareOptions,\n compareFn?: (a: any, b: any) => number\n) {\n if (!shouldAutoIndex(collection)) {\n return\n }\n\n const compareOpts = compareOptions ?? {\n ...DEFAULT_COMPARE_OPTIONS,\n ...collection.compareOptions,\n }\n\n // Check if we already have an index for this field\n const existingIndex = Array.from(collection.indexes.values()).find(\n (index) =>\n index.matchesField(fieldPath) && index.matchesCompareOptions(compareOpts)\n )\n\n if (existingIndex) {\n return // Index already exists\n }\n\n // Create a new index for this field using the collection's createIndex method\n try {\n // Use the proxy-based approach to create the proper accessor for nested paths\n collection.createIndex(\n (row) => {\n // Navigate through the field path\n let current: any = row\n for (const part of fieldPath) {\n current = current[part]\n }\n return current\n },\n {\n name: `auto:${fieldPath.join(`.`)}`,\n indexType: BTreeIndex,\n options: compareFn ? { compareFn, compareOptions: compareOpts } : {},\n }\n )\n } catch (error) {\n console.warn(\n `${collection.id ? `[${collection.id}] ` : ``}Failed to create auto-index for field path \"${fieldPath.join(`.`)}\":`,\n error\n )\n }\n}\n\n/**\n * Analyzes a where expression and creates indexes for all simple operations on single fields\n */\nexport function ensureIndexForExpression<\n T extends Record<string, any>,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionImpl<T, TKey, any, any, any>\n): void {\n if (!shouldAutoIndex(collection)) {\n return\n }\n\n // Extract all indexable expressions and create indexes for them\n const indexableExpressions = extractIndexableExpressions(expression)\n\n for (const { fieldName, fieldPath } of indexableExpressions) {\n ensureIndexForField(fieldName, fieldPath, collection)\n }\n}\n\n/**\n * Extracts all indexable expressions from a where expression\n */\nfunction extractIndexableExpressions(\n expression: BasicExpression\n): Array<{ fieldName: string; fieldPath: Array<string> }> {\n const results: Array<{ fieldName: string; fieldPath: Array<string> }> = []\n\n function extractFromExpression(expr: BasicExpression): void {\n if (expr.type !== `func`) {\n return\n }\n\n const func = expr as any\n\n // Handle 'and' expressions by recursively processing all arguments\n if (func.name === `and`) {\n for (const arg of func.args) {\n extractFromExpression(arg)\n }\n return\n }\n\n // Check if this is a supported operation\n const supportedOperations = [`eq`, `gt`, `gte`, `lt`, `lte`, `in`]\n if (!supportedOperations.includes(func.name)) {\n return\n }\n\n // Check if the first argument is a property reference\n if (func.args.length < 1 || func.args[0].type !== `ref`) {\n return\n }\n\n const fieldRef = func.args[0]\n const fieldPath = fieldRef.path\n\n // Skip if the path is empty\n if (fieldPath.length === 0) {\n return\n }\n\n // For nested paths, use the full path joined with underscores as the field name\n // For simple paths, use the first (and only) element\n const fieldName = fieldPath.join(`_`)\n results.push({ fieldName, fieldPath })\n }\n\n extractFromExpression(expression)\n return results\n}\n"],"names":["DEFAULT_COMPARE_OPTIONS","BTreeIndex"],"mappings":";;;;AAUA,SAAS,gBAAgB,YAAqD;AAE5E,MAAI,WAAW,OAAO,cAAc,SAAS;AAC3C,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,SAAS,oBAId,WACA,WACA,YACA,gBACA,WACA;AACA,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC;AAAA,EACF;AAEA,QAAM,cAAc,kBAAkB;AAAA,IACpC,GAAGA,MAAAA;AAAAA,IACH,GAAG,WAAW;AAAA,EAAA;AAIhB,QAAM,gBAAgB,MAAM,KAAK,WAAW,QAAQ,OAAA,CAAQ,EAAE;AAAA,IAC5D,CAAC,UACC,MAAM,aAAa,SAAS,KAAK,MAAM,sBAAsB,WAAW;AAAA,EAAA;AAG5E,MAAI,eAAe;AACjB;AAAA,EACF;AAGA,MAAI;AAEF,eAAW;AAAA,MACT,CAAC,QAAQ;AAEP,YAAI,UAAe;AACnB,mBAAW,QAAQ,WAAW;AAC5B,oBAAU,QAAQ,IAAI;AAAA,QACxB;AACA,eAAO;AAAA,MACT;AAAA,MACA;AAAA,QACE,MAAM,QAAQ,UAAU,KAAK,GAAG,CAAC;AAAA,QACjC,WAAWC,WAAAA;AAAAA,QACX,SAAS,YAAY,EAAE,WAAW,gBAAgB,YAAA,IAAgB,CAAA;AAAA,MAAC;AAAA,IACrE;AAAA,EAEJ,SAAS,OAAO;AACd,YAAQ;AAAA,MACN,GAAG,WAAW,KAAK,IAAI,WAAW,EAAE,OAAO,EAAE,+CAA+C,UAAU,KAAK,GAAG,CAAC;AAAA,MAC/G;AAAA,IAAA;AAAA,EAEJ;AACF;AAKO,SAAS,yBAId,YACA,YACM;AACN,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC;AAAA,EACF;AAGA,QAAM,uBAAuB,4BAA4B,UAAU;AAEnE,aAAW,EAAE,WAAW,UAAA,KAAe,sBAAsB;AAC3D,wBAAoB,WAAW,WAAW,UAAU;AAAA,EACtD;AACF;AAKA,SAAS,4BACP,YACwD;AACxD,QAAM,UAAkE,CAAA;AAExE,WAAS,sBAAsB,MAA6B;AAC1D,QAAI,KAAK,SAAS,QAAQ;AACxB;AAAA,IACF;AAEA,UAAM,OAAO;AAGb,QAAI,KAAK,SAAS,OAAO;AACvB,iBAAW,OAAO,KAAK,MAAM;AAC3B,8BAAsB,GAAG;AAAA,MAC3B;AACA;AAAA,IACF;AAGA,UAAM,sBAAsB,CAAC,MAAM,MAAM,OAAO,MAAM,OAAO,IAAI;AACjE,QAAI,CAAC,oBAAoB,SAAS,KAAK,IAAI,GAAG;AAC5C;AAAA,IACF;AAGA,QAAI,KAAK,KAAK,SAAS,KAAK,KAAK,KAAK,CAAC,EAAE,SAAS,OAAO;AACvD;AAAA,IACF;AAEA,UAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,UAAM,YAAY,SAAS;AAG3B,QAAI,UAAU,WAAW,GAAG;AAC1B;AAAA,IACF;AAIA,UAAM,YAAY,UAAU,KAAK,GAAG;AACpC,YAAQ,KAAK,EAAE,WAAW,UAAA,CAAW;AAAA,EACvC;AAEA,wBAAsB,UAAU;AAChC,SAAO;AACT;;;"}