@tanstack/db 0.4.3 → 0.4.4
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.
- package/dist/cjs/collection/change-events.cjs +1 -1
- package/dist/cjs/collection/change-events.cjs.map +1 -1
- package/dist/cjs/collection/changes.cjs +7 -3
- package/dist/cjs/collection/changes.cjs.map +1 -1
- package/dist/cjs/collection/events.cjs +3 -6
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/index.cjs +4 -1
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +16 -4
- package/dist/cjs/collection/mutations.cjs +13 -20
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/state.cjs +9 -2
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/subscription.cjs +3 -4
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +2 -2
- package/dist/cjs/collection/sync.cjs +10 -2
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.cjs +4 -3
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.d.cts +2 -1
- package/dist/cjs/indexes/base-index.cjs +26 -0
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.d.cts +47 -2
- package/dist/cjs/indexes/btree-index.cjs +45 -9
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.d.cts +15 -0
- package/dist/cjs/indexes/lazy-index.cjs +3 -6
- package/dist/cjs/indexes/lazy-index.cjs.map +1 -1
- package/dist/cjs/indexes/reverse-index.cjs +78 -0
- package/dist/cjs/indexes/reverse-index.cjs.map +1 -0
- package/dist/cjs/indexes/reverse-index.d.cts +30 -0
- package/dist/cjs/proxy.cjs +1 -1
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/index.cjs +21 -0
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/index.d.cts +16 -1
- package/dist/cjs/query/builder/types.d.cts +7 -0
- package/dist/cjs/query/compiler/evaluators.cjs +1 -1
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs +2 -10
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.cjs +6 -6
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs +4 -5
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +2 -2
- package/dist/cjs/query/compiler/select.cjs +1 -1
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/index.d.cts +1 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +1 -0
- package/dist/cjs/query/live/collection-config-builder.cjs +3 -2
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +2 -2
- package/dist/cjs/query/live/collection-subscriber.cjs +2 -3
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/types.d.cts +4 -0
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +7 -4
- package/dist/cjs/query/optimizer.cjs +2 -4
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/transactions.cjs +2 -3
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +13 -0
- package/dist/cjs/utils/btree.cjs +1 -1
- package/dist/cjs/utils/btree.cjs.map +1 -1
- package/dist/cjs/utils/index-optimization.cjs +7 -2
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils/index-optimization.d.cts +3 -2
- package/dist/cjs/utils.cjs +6 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +2 -3
- package/dist/esm/collection/change-events.js +1 -1
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/changes.js +7 -3
- package/dist/esm/collection/changes.js.map +1 -1
- package/dist/esm/collection/events.js +3 -6
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.d.ts +16 -4
- package/dist/esm/collection/index.js +4 -1
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/mutations.js +13 -20
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.js +9 -2
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +2 -2
- package/dist/esm/collection/subscription.js +3 -4
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.js +10 -2
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/indexes/auto-index.d.ts +2 -1
- package/dist/esm/indexes/auto-index.js +4 -3
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.d.ts +47 -2
- package/dist/esm/indexes/base-index.js +26 -0
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/btree-index.d.ts +15 -0
- package/dist/esm/indexes/btree-index.js +45 -9
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/lazy-index.js +3 -6
- package/dist/esm/indexes/lazy-index.js.map +1 -1
- package/dist/esm/indexes/reverse-index.d.ts +30 -0
- package/dist/esm/indexes/reverse-index.js +78 -0
- package/dist/esm/indexes/reverse-index.js.map +1 -0
- package/dist/esm/proxy.js +1 -1
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/builder/index.d.ts +16 -1
- package/dist/esm/query/builder/index.js +21 -0
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +7 -0
- package/dist/esm/query/compiler/evaluators.js +1 -1
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js +3 -11
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/joins.js +6 -6
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +2 -2
- package/dist/esm/query/compiler/order-by.js +4 -5
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js +1 -1
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/index.d.ts +1 -1
- package/dist/esm/query/ir.d.ts +1 -0
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +2 -2
- package/dist/esm/query/live/collection-config-builder.js +3 -2
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.js +2 -3
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live/types.d.ts +4 -0
- package/dist/esm/query/live-query-collection.d.ts +7 -4
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.js +2 -4
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/transactions.js +2 -3
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +13 -0
- package/dist/esm/utils/btree.js +1 -1
- package/dist/esm/utils/btree.js.map +1 -1
- package/dist/esm/utils/index-optimization.d.ts +3 -2
- package/dist/esm/utils/index-optimization.js +7 -2
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/dist/esm/utils.d.ts +2 -3
- package/dist/esm/utils.js +6 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/collection/changes.ts +10 -4
- package/src/collection/index.ts +38 -5
- package/src/collection/state.ts +22 -5
- package/src/collection/subscription.ts +3 -3
- package/src/collection/sync.ts +17 -2
- package/src/indexes/auto-index.ts +8 -3
- package/src/indexes/base-index.ts +94 -4
- package/src/indexes/btree-index.ts +58 -7
- package/src/indexes/reverse-index.ts +120 -0
- package/src/query/builder/index.ts +30 -2
- package/src/query/builder/types.ts +12 -0
- package/src/query/compiler/group-by.ts +1 -10
- package/src/query/compiler/order-by.ts +15 -18
- package/src/query/index.ts +1 -0
- package/src/query/ir.ts +1 -0
- package/src/query/live/collection-config-builder.ts +3 -2
- package/src/query/live/types.ts +5 -0
- package/src/query/live-query-collection.ts +34 -8
- package/src/types.ts +22 -0
- package/src/utils/index-optimization.ts +19 -5
- package/src/utils.ts +8 -0
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Generic utility functions\n */\n\ninterface TypedArray {\n length: number\n [index: number]: number\n}\n\n/**\n * Deep equality function that compares two values recursively\n * Handles primitives, objects, arrays, Date, RegExp, Map, Set, TypedArrays, and Temporal objects\n *\n * @param a - First value to compare\n * @param b - Second value to compare\n * @returns True if the values are deeply equal, false otherwise\n *\n * @example\n * ```typescript\n * deepEquals({ a: 1, b: 2 }, { b: 2, a: 1 }) // true (property order doesn't matter)\n * deepEquals([1, { x: 2 }], [1, { x: 2 }]) // true\n * deepEquals({ a: 1 }, { a: 2 }) // false\n * deepEquals(new Date('2023-01-01'), new Date('2023-01-01')) // true\n * deepEquals(new Map([['a', 1]]), new Map([['a', 1]])) // true\n * ```\n */\nexport function deepEquals(a: any, b: any): boolean {\n return deepEqualsInternal(a, b, new Map())\n}\n\n/**\n * Internal implementation with cycle detection to prevent infinite recursion\n */\nfunction deepEqualsInternal(\n a: any,\n b: any,\n visited: Map<object, object>\n): boolean {\n // Handle strict equality (primitives, same reference)\n if (a === b) return true\n\n // Handle null/undefined\n if (a == null || b == null) return false\n\n // Handle different types\n if (typeof a !== typeof b) return false\n\n // Handle Date objects\n if (a instanceof Date) {\n if (!(b instanceof Date)) return false\n return a.getTime() === b.getTime()\n }\n\n // Handle RegExp objects\n if (a instanceof RegExp) {\n if (!(b instanceof RegExp)) return false\n return a.source === b.source && a.flags === b.flags\n }\n\n // Handle Map objects - only if both are Maps\n if (a instanceof Map) {\n if (!(b instanceof Map)) return false\n if (a.size !== b.size) return false\n\n // Check for circular references\n if (visited.has(a)) {\n return visited.get(a) === b\n }\n visited.set(a, b)\n\n const entries = Array.from(a.entries())\n const result = entries.every(([key, val]) => {\n return b.has(key) && deepEqualsInternal(val, b.get(key), visited)\n })\n\n visited.delete(a)\n return result\n }\n\n // Handle Set objects - only if both are Sets\n if (a instanceof Set) {\n if (!(b instanceof Set)) return false\n if (a.size !== b.size) return false\n\n // Check for circular references\n if (visited.has(a)) {\n return visited.get(a) === b\n }\n visited.set(a, b)\n\n // Convert to arrays for comparison\n const aValues = Array.from(a)\n const bValues = Array.from(b)\n\n // Simple comparison for primitive values\n if (aValues.every((val) => typeof val !== `object`)) {\n visited.delete(a)\n return aValues.every((val) => b.has(val))\n }\n\n // For objects in sets, we need to do a more complex comparison\n // This is a simplified approach and may not work for all cases\n const result = aValues.length === bValues.length\n visited.delete(a)\n return result\n }\n\n // Handle TypedArrays\n if (\n ArrayBuffer.isView(a) &&\n ArrayBuffer.isView(b) &&\n !(a instanceof DataView) &&\n !(b instanceof DataView)\n ) {\n const typedA = a as unknown as TypedArray\n const typedB = b as unknown as TypedArray\n if (typedA.length !== typedB.length) return false\n\n for (let i = 0; i < typedA.length; i++) {\n if (typedA[i] !== typedB[i]) return false\n }\n\n return true\n }\n\n // Handle Temporal objects\n // Check if both are Temporal objects of the same type\n if (isTemporal(a) && isTemporal(b)) {\n const aTag = getStringTag(a)\n const bTag = getStringTag(b)\n\n // If they're different Temporal types, they're not equal\n if (aTag !== bTag) return false\n\n // Use Temporal's built-in equals method if available\n if (typeof a.equals === `function`) {\n return a.equals(b)\n }\n\n // Fallback to toString comparison for other types\n return a.toString() === b.toString()\n }\n\n // Handle arrays\n if (Array.isArray(a)) {\n if (!Array.isArray(b) || a.length !== b.length) return false\n\n // Check for circular references\n if (visited.has(a)) {\n return visited.get(a) === b\n }\n visited.set(a, b)\n\n const result = a.every((item, index) =>\n deepEqualsInternal(item, b[index], visited)\n )\n visited.delete(a)\n return result\n }\n\n // Handle objects\n if (typeof a === `object`) {\n // Check for circular references\n if (visited.has(a)) {\n return visited.get(a) === b\n }\n visited.set(a, b)\n\n // Get all keys from both objects\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n\n // Check if they have the same number of keys\n if (keysA.length !== keysB.length) {\n visited.delete(a)\n return false\n }\n\n // Check if all keys exist in both objects and their values are equal\n const result = keysA.every(\n (key) => key in b && deepEqualsInternal(a[key], b[key], visited)\n )\n\n visited.delete(a)\n return result\n }\n\n // For primitives that aren't strictly equal\n return false\n}\n\nconst temporalTypes = [\n `Temporal.Duration`,\n `Temporal.Instant`,\n `Temporal.PlainDate`,\n `Temporal.PlainDateTime`,\n `Temporal.PlainMonthDay`,\n `Temporal.PlainTime`,\n `Temporal.PlainYearMonth`,\n `Temporal.ZonedDateTime`,\n]\n\nfunction getStringTag(a: any): any {\n return a[Symbol.toStringTag]\n}\n\n/** Checks if the value is a Temporal object by checking for the Temporal brand */\nexport function isTemporal(a: any): boolean {\n const tag = getStringTag(a)\n return typeof tag === `string` && temporalTypes.includes(tag)\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Generic utility functions\n */\n\nimport type { CompareOptions } from \"./query/builder/types\"\n\ninterface TypedArray {\n length: number\n [index: number]: number\n}\n\n/**\n * Deep equality function that compares two values recursively\n * Handles primitives, objects, arrays, Date, RegExp, Map, Set, TypedArrays, and Temporal objects\n *\n * @param a - First value to compare\n * @param b - Second value to compare\n * @returns True if the values are deeply equal, false otherwise\n *\n * @example\n * ```typescript\n * deepEquals({ a: 1, b: 2 }, { b: 2, a: 1 }) // true (property order doesn't matter)\n * deepEquals([1, { x: 2 }], [1, { x: 2 }]) // true\n * deepEquals({ a: 1 }, { a: 2 }) // false\n * deepEquals(new Date('2023-01-01'), new Date('2023-01-01')) // true\n * deepEquals(new Map([['a', 1]]), new Map([['a', 1]])) // true\n * ```\n */\nexport function deepEquals(a: any, b: any): boolean {\n return deepEqualsInternal(a, b, new Map())\n}\n\n/**\n * Internal implementation with cycle detection to prevent infinite recursion\n */\nfunction deepEqualsInternal(\n a: any,\n b: any,\n visited: Map<object, object>\n): boolean {\n // Handle strict equality (primitives, same reference)\n if (a === b) return true\n\n // Handle null/undefined\n if (a == null || b == null) return false\n\n // Handle different types\n if (typeof a !== typeof b) return false\n\n // Handle Date objects\n if (a instanceof Date) {\n if (!(b instanceof Date)) return false\n return a.getTime() === b.getTime()\n }\n\n // Handle RegExp objects\n if (a instanceof RegExp) {\n if (!(b instanceof RegExp)) return false\n return a.source === b.source && a.flags === b.flags\n }\n\n // Handle Map objects - only if both are Maps\n if (a instanceof Map) {\n if (!(b instanceof Map)) return false\n if (a.size !== b.size) return false\n\n // Check for circular references\n if (visited.has(a)) {\n return visited.get(a) === b\n }\n visited.set(a, b)\n\n const entries = Array.from(a.entries())\n const result = entries.every(([key, val]) => {\n return b.has(key) && deepEqualsInternal(val, b.get(key), visited)\n })\n\n visited.delete(a)\n return result\n }\n\n // Handle Set objects - only if both are Sets\n if (a instanceof Set) {\n if (!(b instanceof Set)) return false\n if (a.size !== b.size) return false\n\n // Check for circular references\n if (visited.has(a)) {\n return visited.get(a) === b\n }\n visited.set(a, b)\n\n // Convert to arrays for comparison\n const aValues = Array.from(a)\n const bValues = Array.from(b)\n\n // Simple comparison for primitive values\n if (aValues.every((val) => typeof val !== `object`)) {\n visited.delete(a)\n return aValues.every((val) => b.has(val))\n }\n\n // For objects in sets, we need to do a more complex comparison\n // This is a simplified approach and may not work for all cases\n const result = aValues.length === bValues.length\n visited.delete(a)\n return result\n }\n\n // Handle TypedArrays\n if (\n ArrayBuffer.isView(a) &&\n ArrayBuffer.isView(b) &&\n !(a instanceof DataView) &&\n !(b instanceof DataView)\n ) {\n const typedA = a as unknown as TypedArray\n const typedB = b as unknown as TypedArray\n if (typedA.length !== typedB.length) return false\n\n for (let i = 0; i < typedA.length; i++) {\n if (typedA[i] !== typedB[i]) return false\n }\n\n return true\n }\n\n // Handle Temporal objects\n // Check if both are Temporal objects of the same type\n if (isTemporal(a) && isTemporal(b)) {\n const aTag = getStringTag(a)\n const bTag = getStringTag(b)\n\n // If they're different Temporal types, they're not equal\n if (aTag !== bTag) return false\n\n // Use Temporal's built-in equals method if available\n if (typeof a.equals === `function`) {\n return a.equals(b)\n }\n\n // Fallback to toString comparison for other types\n return a.toString() === b.toString()\n }\n\n // Handle arrays\n if (Array.isArray(a)) {\n if (!Array.isArray(b) || a.length !== b.length) return false\n\n // Check for circular references\n if (visited.has(a)) {\n return visited.get(a) === b\n }\n visited.set(a, b)\n\n const result = a.every((item, index) =>\n deepEqualsInternal(item, b[index], visited)\n )\n visited.delete(a)\n return result\n }\n\n // Handle objects\n if (typeof a === `object`) {\n // Check for circular references\n if (visited.has(a)) {\n return visited.get(a) === b\n }\n visited.set(a, b)\n\n // Get all keys from both objects\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n\n // Check if they have the same number of keys\n if (keysA.length !== keysB.length) {\n visited.delete(a)\n return false\n }\n\n // Check if all keys exist in both objects and their values are equal\n const result = keysA.every(\n (key) => key in b && deepEqualsInternal(a[key], b[key], visited)\n )\n\n visited.delete(a)\n return result\n }\n\n // For primitives that aren't strictly equal\n return false\n}\n\nconst temporalTypes = [\n `Temporal.Duration`,\n `Temporal.Instant`,\n `Temporal.PlainDate`,\n `Temporal.PlainDateTime`,\n `Temporal.PlainMonthDay`,\n `Temporal.PlainTime`,\n `Temporal.PlainYearMonth`,\n `Temporal.ZonedDateTime`,\n]\n\nfunction getStringTag(a: any): any {\n return a[Symbol.toStringTag]\n}\n\n/** Checks if the value is a Temporal object by checking for the Temporal brand */\nexport function isTemporal(a: any): boolean {\n const tag = getStringTag(a)\n return typeof tag === `string` && temporalTypes.includes(tag)\n}\n\nexport const DEFAULT_COMPARE_OPTIONS: CompareOptions = {\n direction: `asc`,\n nulls: `first`,\n stringSort: `locale`,\n}\n"],"names":[],"mappings":"AA4BO,SAAS,WAAW,GAAQ,GAAiB;AAClD,SAAO,mBAAmB,GAAG,GAAG,oBAAI,KAAK;AAC3C;AAKA,SAAS,mBACP,GACA,GACA,SACS;AAET,MAAI,MAAM,EAAG,QAAO;AAGpB,MAAI,KAAK,QAAQ,KAAK,KAAM,QAAO;AAGnC,MAAI,OAAO,MAAM,OAAO,EAAG,QAAO;AAGlC,MAAI,aAAa,MAAM;AACrB,QAAI,EAAE,aAAa,MAAO,QAAO;AACjC,WAAO,EAAE,cAAc,EAAE,QAAA;AAAA,EAC3B;AAGA,MAAI,aAAa,QAAQ;AACvB,QAAI,EAAE,aAAa,QAAS,QAAO;AACnC,WAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE;AAAA,EAChD;AAGA,MAAI,aAAa,KAAK;AACpB,QAAI,EAAE,aAAa,KAAM,QAAO;AAChC,QAAI,EAAE,SAAS,EAAE,KAAM,QAAO;AAG9B,QAAI,QAAQ,IAAI,CAAC,GAAG;AAClB,aAAO,QAAQ,IAAI,CAAC,MAAM;AAAA,IAC5B;AACA,YAAQ,IAAI,GAAG,CAAC;AAEhB,UAAM,UAAU,MAAM,KAAK,EAAE,SAAS;AACtC,UAAM,SAAS,QAAQ,MAAM,CAAC,CAAC,KAAK,GAAG,MAAM;AAC3C,aAAO,EAAE,IAAI,GAAG,KAAK,mBAAmB,KAAK,EAAE,IAAI,GAAG,GAAG,OAAO;AAAA,IAClE,CAAC;AAED,YAAQ,OAAO,CAAC;AAChB,WAAO;AAAA,EACT;AAGA,MAAI,aAAa,KAAK;AACpB,QAAI,EAAE,aAAa,KAAM,QAAO;AAChC,QAAI,EAAE,SAAS,EAAE,KAAM,QAAO;AAG9B,QAAI,QAAQ,IAAI,CAAC,GAAG;AAClB,aAAO,QAAQ,IAAI,CAAC,MAAM;AAAA,IAC5B;AACA,YAAQ,IAAI,GAAG,CAAC;AAGhB,UAAM,UAAU,MAAM,KAAK,CAAC;AAC5B,UAAM,UAAU,MAAM,KAAK,CAAC;AAG5B,QAAI,QAAQ,MAAM,CAAC,QAAQ,OAAO,QAAQ,QAAQ,GAAG;AACnD,cAAQ,OAAO,CAAC;AAChB,aAAO,QAAQ,MAAM,CAAC,QAAQ,EAAE,IAAI,GAAG,CAAC;AAAA,IAC1C;AAIA,UAAM,SAAS,QAAQ,WAAW,QAAQ;AAC1C,YAAQ,OAAO,CAAC;AAChB,WAAO;AAAA,EACT;AAGA,MACE,YAAY,OAAO,CAAC,KACpB,YAAY,OAAO,CAAC,KACpB,EAAE,aAAa,aACf,EAAE,aAAa,WACf;AACA,UAAM,SAAS;AACf,UAAM,SAAS;AACf,QAAI,OAAO,WAAW,OAAO,OAAQ,QAAO;AAE5C,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAI,OAAO,CAAC,MAAM,OAAO,CAAC,EAAG,QAAO;AAAA,IACtC;AAEA,WAAO;AAAA,EACT;AAIA,MAAI,WAAW,CAAC,KAAK,WAAW,CAAC,GAAG;AAClC,UAAM,OAAO,aAAa,CAAC;AAC3B,UAAM,OAAO,aAAa,CAAC;AAG3B,QAAI,SAAS,KAAM,QAAO;AAG1B,QAAI,OAAO,EAAE,WAAW,YAAY;AAClC,aAAO,EAAE,OAAO,CAAC;AAAA,IACnB;AAGA,WAAO,EAAE,eAAe,EAAE,SAAA;AAAA,EAC5B;AAGA,MAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,QAAI,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,OAAQ,QAAO;AAGvD,QAAI,QAAQ,IAAI,CAAC,GAAG;AAClB,aAAO,QAAQ,IAAI,CAAC,MAAM;AAAA,IAC5B;AACA,YAAQ,IAAI,GAAG,CAAC;AAEhB,UAAM,SAAS,EAAE;AAAA,MAAM,CAAC,MAAM,UAC5B,mBAAmB,MAAM,EAAE,KAAK,GAAG,OAAO;AAAA,IAAA;AAE5C,YAAQ,OAAO,CAAC;AAChB,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,MAAM,UAAU;AAEzB,QAAI,QAAQ,IAAI,CAAC,GAAG;AAClB,aAAO,QAAQ,IAAI,CAAC,MAAM;AAAA,IAC5B;AACA,YAAQ,IAAI,GAAG,CAAC;AAGhB,UAAM,QAAQ,OAAO,KAAK,CAAC;AAC3B,UAAM,QAAQ,OAAO,KAAK,CAAC;AAG3B,QAAI,MAAM,WAAW,MAAM,QAAQ;AACjC,cAAQ,OAAO,CAAC;AAChB,aAAO;AAAA,IACT;AAGA,UAAM,SAAS,MAAM;AAAA,MACnB,CAAC,QAAQ,OAAO,KAAK,mBAAmB,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,OAAO;AAAA,IAAA;AAGjE,YAAQ,OAAO,CAAC;AAChB,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AAEA,MAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,aAAa,GAAa;AACjC,SAAO,EAAE,OAAO,WAAW;AAC7B;AAGO,SAAS,WAAW,GAAiB;AAC1C,QAAM,MAAM,aAAa,CAAC;AAC1B,SAAO,OAAO,QAAQ,YAAY,cAAc,SAAS,GAAG;AAC9D;AAEO,MAAM,0BAA0C;AAAA,EACrD,WAAW;AAAA,EACX,OAAO;AAAA,EACP,YAAY;AACd;"}
|
package/package.json
CHANGED
|
@@ -68,14 +68,20 @@ export class CollectionChangesManager<
|
|
|
68
68
|
// Either we're not batching, or we're forcing emission (user action or ending batch cycle)
|
|
69
69
|
let eventsToEmit = changes
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
if (forceEmit) {
|
|
72
|
+
// Force emit is used to end a batch (e.g. after a sync commit). Combine any
|
|
73
|
+
// buffered optimistic events with the final changes so subscribers see the
|
|
74
|
+
// whole picture, even if the sync diff is empty.
|
|
75
|
+
if (this.batchedEvents.length > 0) {
|
|
76
|
+
eventsToEmit = [...this.batchedEvents, ...changes]
|
|
77
|
+
}
|
|
74
78
|
this.batchedEvents = []
|
|
75
79
|
this.shouldBatchEvents = false
|
|
76
80
|
}
|
|
77
81
|
|
|
78
|
-
if (eventsToEmit.length === 0)
|
|
82
|
+
if (eventsToEmit.length === 0) {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
79
85
|
|
|
80
86
|
// Emit to all listeners
|
|
81
87
|
for (const subscription of this.changeSubscriptions) {
|
package/src/collection/index.ts
CHANGED
|
@@ -24,7 +24,9 @@ import type {
|
|
|
24
24
|
InferSchemaInput,
|
|
25
25
|
InferSchemaOutput,
|
|
26
26
|
InsertConfig,
|
|
27
|
+
NonSingleResult,
|
|
27
28
|
OperationConfig,
|
|
29
|
+
SingleResult,
|
|
28
30
|
SubscribeChangesOptions,
|
|
29
31
|
Transaction as TransactionType,
|
|
30
32
|
UtilsRecord,
|
|
@@ -50,6 +52,7 @@ export interface Collection<
|
|
|
50
52
|
TInsertInput extends object = T,
|
|
51
53
|
> extends CollectionImpl<T, TKey, TUtils, TSchema, TInsertInput> {
|
|
52
54
|
readonly utils: TUtils
|
|
55
|
+
readonly singleResult?: true
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
/**
|
|
@@ -132,8 +135,22 @@ export function createCollection<
|
|
|
132
135
|
options: CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
|
|
133
136
|
schema: T
|
|
134
137
|
utils?: TUtils
|
|
135
|
-
}
|
|
136
|
-
): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>>
|
|
138
|
+
} & NonSingleResult
|
|
139
|
+
): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>> &
|
|
140
|
+
NonSingleResult
|
|
141
|
+
|
|
142
|
+
// Overload for when schema is provided and singleResult is true
|
|
143
|
+
export function createCollection<
|
|
144
|
+
T extends StandardSchemaV1,
|
|
145
|
+
TKey extends string | number = string | number,
|
|
146
|
+
TUtils extends UtilsRecord = {},
|
|
147
|
+
>(
|
|
148
|
+
options: CollectionConfig<InferSchemaOutput<T>, TKey, T> & {
|
|
149
|
+
schema: T
|
|
150
|
+
utils?: TUtils
|
|
151
|
+
} & SingleResult
|
|
152
|
+
): Collection<InferSchemaOutput<T>, TKey, TUtils, T, InferSchemaInput<T>> &
|
|
153
|
+
SingleResult
|
|
137
154
|
|
|
138
155
|
// Overload for when no schema is provided
|
|
139
156
|
// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config
|
|
@@ -145,8 +162,21 @@ export function createCollection<
|
|
|
145
162
|
options: CollectionConfig<T, TKey, never> & {
|
|
146
163
|
schema?: never // prohibit schema if an explicit type is provided
|
|
147
164
|
utils?: TUtils
|
|
148
|
-
}
|
|
149
|
-
): Collection<T, TKey, TUtils, never, T>
|
|
165
|
+
} & NonSingleResult
|
|
166
|
+
): Collection<T, TKey, TUtils, never, T> & NonSingleResult
|
|
167
|
+
|
|
168
|
+
// Overload for when no schema is provided and singleResult is true
|
|
169
|
+
// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config
|
|
170
|
+
export function createCollection<
|
|
171
|
+
T extends object,
|
|
172
|
+
TKey extends string | number = string | number,
|
|
173
|
+
TUtils extends UtilsRecord = {},
|
|
174
|
+
>(
|
|
175
|
+
options: CollectionConfig<T, TKey, never> & {
|
|
176
|
+
schema?: never // prohibit schema if an explicit type is provided
|
|
177
|
+
utils?: TUtils
|
|
178
|
+
} & SingleResult
|
|
179
|
+
): Collection<T, TKey, TUtils, never, T> & SingleResult
|
|
150
180
|
|
|
151
181
|
// Implementation
|
|
152
182
|
export function createCollection(
|
|
@@ -428,7 +458,10 @@ export class CollectionImpl<
|
|
|
428
458
|
* // Create a ordered index with custom options
|
|
429
459
|
* const ageIndex = collection.createIndex((row) => row.age, {
|
|
430
460
|
* indexType: BTreeIndex,
|
|
431
|
-
* options: {
|
|
461
|
+
* options: {
|
|
462
|
+
* compareFn: customComparator,
|
|
463
|
+
* compareOptions: { direction: 'asc', nulls: 'first', stringSort: 'lexical' }
|
|
464
|
+
* },
|
|
432
465
|
* name: 'age_btree'
|
|
433
466
|
* })
|
|
434
467
|
*
|
package/src/collection/state.ts
CHANGED
|
@@ -217,7 +217,11 @@ export class CollectionStateManager<
|
|
|
217
217
|
triggeredByUserAction: boolean = false
|
|
218
218
|
): void {
|
|
219
219
|
// Skip redundant recalculations when we're in the middle of committing sync transactions
|
|
220
|
-
|
|
220
|
+
// While the sync pipeline is replaying a large batch we still want to honour
|
|
221
|
+
// fresh optimistic mutations from the UI. Only skip recompute for the
|
|
222
|
+
// internal sync-driven redraws; user-triggered work (triggeredByUserAction)
|
|
223
|
+
// must run so live queries stay responsive during long commits.
|
|
224
|
+
if (this.isCommittingSyncTransactions && !triggeredByUserAction) {
|
|
221
225
|
return
|
|
222
226
|
}
|
|
223
227
|
|
|
@@ -708,10 +712,23 @@ export class CollectionStateManager<
|
|
|
708
712
|
|
|
709
713
|
// Check if this sync operation is redundant with a completed optimistic operation
|
|
710
714
|
const completedOp = completedOptimisticOps.get(key)
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
+
let isRedundantSync = false
|
|
716
|
+
|
|
717
|
+
if (completedOp) {
|
|
718
|
+
if (
|
|
719
|
+
completedOp.type === `delete` &&
|
|
720
|
+
previousVisibleValue !== undefined &&
|
|
721
|
+
newVisibleValue === undefined &&
|
|
722
|
+
deepEquals(completedOp.value, previousVisibleValue)
|
|
723
|
+
) {
|
|
724
|
+
isRedundantSync = true
|
|
725
|
+
} else if (
|
|
726
|
+
newVisibleValue !== undefined &&
|
|
727
|
+
deepEquals(completedOp.value, newVisibleValue)
|
|
728
|
+
) {
|
|
729
|
+
isRedundantSync = true
|
|
730
|
+
}
|
|
731
|
+
}
|
|
715
732
|
|
|
716
733
|
if (!isRedundantSync) {
|
|
717
734
|
if (
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
createFilteredCallback,
|
|
6
6
|
} from "./change-events.js"
|
|
7
7
|
import type { BasicExpression } from "../query/ir.js"
|
|
8
|
-
import type {
|
|
8
|
+
import type { IndexInterface } from "../indexes/base-index.js"
|
|
9
9
|
import type { ChangeMessage } from "../types.js"
|
|
10
10
|
import type { CollectionImpl } from "./index.js"
|
|
11
11
|
|
|
@@ -38,7 +38,7 @@ export class CollectionSubscription {
|
|
|
38
38
|
|
|
39
39
|
private filteredCallback: (changes: Array<ChangeMessage<any, any>>) => void
|
|
40
40
|
|
|
41
|
-
private orderByIndex:
|
|
41
|
+
private orderByIndex: IndexInterface<string | number> | undefined
|
|
42
42
|
|
|
43
43
|
constructor(
|
|
44
44
|
private collection: CollectionImpl<any, any, any, any, any>,
|
|
@@ -65,7 +65,7 @@ export class CollectionSubscription {
|
|
|
65
65
|
: this.callback
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
setOrderByIndex(index:
|
|
68
|
+
setOrderByIndex(index: IndexInterface<any>) {
|
|
69
69
|
this.orderByIndex = index
|
|
70
70
|
}
|
|
71
71
|
|
package/src/collection/sync.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
SyncTransactionAlreadyCommittedError,
|
|
8
8
|
SyncTransactionAlreadyCommittedWriteError,
|
|
9
9
|
} from "../errors"
|
|
10
|
+
import { deepEquals } from "../utils"
|
|
10
11
|
import type { StandardSchemaV1 } from "@standard-schema/spec"
|
|
11
12
|
import type { ChangeMessage, CollectionConfig } from "../types"
|
|
12
13
|
import type { CollectionImpl } from "./index.js"
|
|
@@ -84,6 +85,8 @@ export class CollectionSyncManager<
|
|
|
84
85
|
}
|
|
85
86
|
const key = this.config.getKey(messageWithoutKey.value)
|
|
86
87
|
|
|
88
|
+
let messageType = messageWithoutKey.type
|
|
89
|
+
|
|
87
90
|
// Check if an item with this key already exists when inserting
|
|
88
91
|
if (messageWithoutKey.type === `insert`) {
|
|
89
92
|
const insertingIntoExistingSynced = state.syncedData.has(key)
|
|
@@ -96,17 +99,29 @@ export class CollectionSyncManager<
|
|
|
96
99
|
!hasPendingDeleteForKey &&
|
|
97
100
|
!isTruncateTransaction
|
|
98
101
|
) {
|
|
99
|
-
|
|
102
|
+
const existingValue = state.syncedData.get(key)
|
|
103
|
+
if (
|
|
104
|
+
existingValue !== undefined &&
|
|
105
|
+
deepEquals(existingValue, messageWithoutKey.value)
|
|
106
|
+
) {
|
|
107
|
+
// The "insert" is an echo of a value we already have locally.
|
|
108
|
+
// Treat it as an update so we preserve optimistic intent without
|
|
109
|
+
// throwing a duplicate-key error during reconciliation.
|
|
110
|
+
messageType = `update`
|
|
111
|
+
} else {
|
|
112
|
+
throw new DuplicateKeySyncError(key, this.id)
|
|
113
|
+
}
|
|
100
114
|
}
|
|
101
115
|
}
|
|
102
116
|
|
|
103
117
|
const message: ChangeMessage<TOutput> = {
|
|
104
118
|
...messageWithoutKey,
|
|
119
|
+
type: messageType,
|
|
105
120
|
key,
|
|
106
121
|
}
|
|
107
122
|
pendingTransaction.operations.push(message)
|
|
108
123
|
|
|
109
|
-
if (
|
|
124
|
+
if (messageType === `delete`) {
|
|
110
125
|
pendingTransaction.deletedKeys.add(key)
|
|
111
126
|
}
|
|
112
127
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { DEFAULT_COMPARE_OPTIONS } from "../utils"
|
|
1
2
|
import { BTreeIndex } from "./btree-index"
|
|
3
|
+
import type { CompareOptions } from "../query/builder/types"
|
|
2
4
|
import type { BasicExpression } from "../query/ir"
|
|
3
5
|
import type { CollectionImpl } from "../collection/index.js"
|
|
4
6
|
|
|
@@ -30,6 +32,7 @@ export function ensureIndexForField<
|
|
|
30
32
|
fieldName: string,
|
|
31
33
|
fieldPath: Array<string>,
|
|
32
34
|
collection: CollectionImpl<T, TKey, any, any, any>,
|
|
35
|
+
compareOptions: CompareOptions = DEFAULT_COMPARE_OPTIONS,
|
|
33
36
|
compareFn?: (a: any, b: any) => number
|
|
34
37
|
) {
|
|
35
38
|
if (!shouldAutoIndex(collection)) {
|
|
@@ -37,8 +40,10 @@ export function ensureIndexForField<
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
// Check if we already have an index for this field
|
|
40
|
-
const existingIndex = Array.from(collection.indexes.values()).find(
|
|
41
|
-
index
|
|
43
|
+
const existingIndex = Array.from(collection.indexes.values()).find(
|
|
44
|
+
(index) =>
|
|
45
|
+
index.matchesField(fieldPath) &&
|
|
46
|
+
index.matchesCompareOptions(compareOptions)
|
|
42
47
|
)
|
|
43
48
|
|
|
44
49
|
if (existingIndex) {
|
|
@@ -50,7 +55,7 @@ export function ensureIndexForField<
|
|
|
50
55
|
collection.createIndex((row) => (row as any)[fieldName], {
|
|
51
56
|
name: `auto_${fieldName}`,
|
|
52
57
|
indexType: BTreeIndex,
|
|
53
|
-
options: compareFn ? { compareFn } : {},
|
|
58
|
+
options: compareFn ? { compareFn, compareOptions } : {},
|
|
54
59
|
})
|
|
55
60
|
} catch (error) {
|
|
56
61
|
console.warn(`Failed to create auto-index for field "${fieldName}":`, error)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { compileSingleRowExpression } from "../query/compiler/evaluators.js"
|
|
2
2
|
import { comparisonFunctions } from "../query/builder/functions.js"
|
|
3
|
-
import
|
|
3
|
+
import { DEFAULT_COMPARE_OPTIONS, deepEquals } from "../utils.js"
|
|
4
|
+
import type { RangeQueryOptions } from "./btree-index.js"
|
|
5
|
+
import type { CompareOptions } from "../query/builder/types.js"
|
|
6
|
+
import type { BasicExpression, OrderByDirection } from "../query/ir.js"
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Operations that indexes can support, imported from available comparison functions
|
|
@@ -22,12 +25,57 @@ export interface IndexStats {
|
|
|
22
25
|
readonly lastUpdated: Date
|
|
23
26
|
}
|
|
24
27
|
|
|
28
|
+
export interface IndexInterface<
|
|
29
|
+
TKey extends string | number = string | number,
|
|
30
|
+
> {
|
|
31
|
+
add: (key: TKey, item: any) => void
|
|
32
|
+
remove: (key: TKey, item: any) => void
|
|
33
|
+
update: (key: TKey, oldItem: any, newItem: any) => void
|
|
34
|
+
|
|
35
|
+
build: (entries: Iterable<[TKey, any]>) => void
|
|
36
|
+
clear: () => void
|
|
37
|
+
|
|
38
|
+
lookup: (operation: IndexOperation, value: any) => Set<TKey>
|
|
39
|
+
|
|
40
|
+
equalityLookup: (value: any) => Set<TKey>
|
|
41
|
+
inArrayLookup: (values: Array<any>) => Set<TKey>
|
|
42
|
+
|
|
43
|
+
rangeQuery: (options: RangeQueryOptions) => Set<TKey>
|
|
44
|
+
rangeQueryReversed: (options: RangeQueryOptions) => Set<TKey>
|
|
45
|
+
|
|
46
|
+
take: (
|
|
47
|
+
n: number,
|
|
48
|
+
from?: TKey,
|
|
49
|
+
filterFn?: (key: TKey) => boolean
|
|
50
|
+
) => Array<TKey>
|
|
51
|
+
takeReversed: (
|
|
52
|
+
n: number,
|
|
53
|
+
from?: TKey,
|
|
54
|
+
filterFn?: (key: TKey) => boolean
|
|
55
|
+
) => Array<TKey>
|
|
56
|
+
|
|
57
|
+
get keyCount(): number
|
|
58
|
+
get orderedEntriesArray(): Array<[any, Set<TKey>]>
|
|
59
|
+
get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]>
|
|
60
|
+
|
|
61
|
+
get indexedKeysSet(): Set<TKey>
|
|
62
|
+
get valueMapData(): Map<any, Set<TKey>>
|
|
63
|
+
|
|
64
|
+
supports: (operation: IndexOperation) => boolean
|
|
65
|
+
|
|
66
|
+
matchesField: (fieldPath: Array<string>) => boolean
|
|
67
|
+
matchesCompareOptions: (compareOptions: CompareOptions) => boolean
|
|
68
|
+
matchesDirection: (direction: OrderByDirection) => boolean
|
|
69
|
+
|
|
70
|
+
getStats: () => IndexStats
|
|
71
|
+
}
|
|
72
|
+
|
|
25
73
|
/**
|
|
26
74
|
* Base abstract class that all index types extend
|
|
27
75
|
*/
|
|
28
|
-
export abstract class BaseIndex<
|
|
29
|
-
TKey
|
|
30
|
-
|
|
76
|
+
export abstract class BaseIndex<TKey extends string | number = string | number>
|
|
77
|
+
implements IndexInterface<TKey>
|
|
78
|
+
{
|
|
31
79
|
public readonly id: number
|
|
32
80
|
public readonly name?: string
|
|
33
81
|
public readonly expression: BasicExpression
|
|
@@ -36,6 +84,7 @@ export abstract class BaseIndex<
|
|
|
36
84
|
protected lookupCount = 0
|
|
37
85
|
protected totalLookupTime = 0
|
|
38
86
|
protected lastUpdated = new Date()
|
|
87
|
+
protected compareOptions: CompareOptions
|
|
39
88
|
|
|
40
89
|
constructor(
|
|
41
90
|
id: number,
|
|
@@ -45,6 +94,7 @@ export abstract class BaseIndex<
|
|
|
45
94
|
) {
|
|
46
95
|
this.id = id
|
|
47
96
|
this.expression = expression
|
|
97
|
+
this.compareOptions = DEFAULT_COMPARE_OPTIONS
|
|
48
98
|
this.name = name
|
|
49
99
|
this.initialize(options)
|
|
50
100
|
}
|
|
@@ -61,7 +111,20 @@ export abstract class BaseIndex<
|
|
|
61
111
|
from?: TKey,
|
|
62
112
|
filterFn?: (key: TKey) => boolean
|
|
63
113
|
): Array<TKey>
|
|
114
|
+
abstract takeReversed(
|
|
115
|
+
n: number,
|
|
116
|
+
from?: TKey,
|
|
117
|
+
filterFn?: (key: TKey) => boolean
|
|
118
|
+
): Array<TKey>
|
|
64
119
|
abstract get keyCount(): number
|
|
120
|
+
abstract equalityLookup(value: any): Set<TKey>
|
|
121
|
+
abstract inArrayLookup(values: Array<any>): Set<TKey>
|
|
122
|
+
abstract rangeQuery(options: RangeQueryOptions): Set<TKey>
|
|
123
|
+
abstract rangeQueryReversed(options: RangeQueryOptions): Set<TKey>
|
|
124
|
+
abstract get orderedEntriesArray(): Array<[any, Set<TKey>]>
|
|
125
|
+
abstract get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]>
|
|
126
|
+
abstract get indexedKeysSet(): Set<TKey>
|
|
127
|
+
abstract get valueMapData(): Map<any, Set<TKey>>
|
|
65
128
|
|
|
66
129
|
// Common methods
|
|
67
130
|
supports(operation: IndexOperation): boolean {
|
|
@@ -76,6 +139,33 @@ export abstract class BaseIndex<
|
|
|
76
139
|
)
|
|
77
140
|
}
|
|
78
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Checks if the compare options match the index's compare options.
|
|
144
|
+
* The direction is ignored because the index can be reversed if the direction is different.
|
|
145
|
+
*/
|
|
146
|
+
matchesCompareOptions(compareOptions: CompareOptions): boolean {
|
|
147
|
+
const thisCompareOptionsWithoutDirection = {
|
|
148
|
+
...this.compareOptions,
|
|
149
|
+
direction: undefined,
|
|
150
|
+
}
|
|
151
|
+
const compareOptionsWithoutDirection = {
|
|
152
|
+
...compareOptions,
|
|
153
|
+
direction: undefined,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return deepEquals(
|
|
157
|
+
thisCompareOptionsWithoutDirection,
|
|
158
|
+
compareOptionsWithoutDirection
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Checks if the index matches the provided direction.
|
|
164
|
+
*/
|
|
165
|
+
matchesDirection(direction: OrderByDirection): boolean {
|
|
166
|
+
return this.compareOptions.direction === direction
|
|
167
|
+
}
|
|
168
|
+
|
|
79
169
|
getStats(): IndexStats {
|
|
80
170
|
return {
|
|
81
171
|
entryCount: this.keyCount,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BTree } from "../utils/btree.js"
|
|
2
2
|
import { defaultComparator, normalizeValue } from "../utils/comparison.js"
|
|
3
3
|
import { BaseIndex } from "./base-index.js"
|
|
4
|
+
import type { CompareOptions } from "../query/builder/types.js"
|
|
4
5
|
import type { BasicExpression } from "../query/ir.js"
|
|
5
6
|
import type { IndexOperation } from "./base-index.js"
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ import type { IndexOperation } from "./base-index.js"
|
|
|
9
10
|
*/
|
|
10
11
|
export interface BTreeIndexOptions {
|
|
11
12
|
compareFn?: (a: any, b: any) => number
|
|
13
|
+
compareOptions?: CompareOptions
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -53,6 +55,9 @@ export class BTreeIndex<
|
|
|
53
55
|
) {
|
|
54
56
|
super(id, expression, name, options)
|
|
55
57
|
this.compareFn = options?.compareFn ?? defaultComparator
|
|
58
|
+
if (options?.compareOptions) {
|
|
59
|
+
this.compareOptions = options!.compareOptions
|
|
60
|
+
}
|
|
56
61
|
this.orderedEntries = new BTree(this.compareFn)
|
|
57
62
|
}
|
|
58
63
|
|
|
@@ -240,18 +245,31 @@ export class BTreeIndex<
|
|
|
240
245
|
}
|
|
241
246
|
|
|
242
247
|
/**
|
|
243
|
-
*
|
|
244
|
-
* @param n - The number of items to return
|
|
245
|
-
* @param from - The item to start from (exclusive). Starts from the smallest item (inclusive) if not provided.
|
|
246
|
-
* @returns The next n items after the provided key. Returns the first n items if no from item is provided.
|
|
248
|
+
* Performs a reversed range query
|
|
247
249
|
*/
|
|
248
|
-
|
|
250
|
+
rangeQueryReversed(options: RangeQueryOptions = {}): Set<TKey> {
|
|
251
|
+
const { from, to, fromInclusive = true, toInclusive = true } = options
|
|
252
|
+
return this.rangeQuery({
|
|
253
|
+
from: to ?? this.orderedEntries.maxKey(),
|
|
254
|
+
to: from ?? this.orderedEntries.minKey(),
|
|
255
|
+
fromInclusive: toInclusive,
|
|
256
|
+
toInclusive: fromInclusive,
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private takeInternal(
|
|
261
|
+
n: number,
|
|
262
|
+
nextPair: (k?: any) => [any, any] | undefined,
|
|
263
|
+
from?: any,
|
|
264
|
+
filterFn?: (key: TKey) => boolean
|
|
265
|
+
): Array<TKey> {
|
|
249
266
|
const keysInResult: Set<TKey> = new Set()
|
|
250
267
|
const result: Array<TKey> = []
|
|
251
|
-
|
|
268
|
+
let pair: [any, any] | undefined
|
|
252
269
|
let key = normalizeValue(from)
|
|
253
270
|
|
|
254
|
-
while ((
|
|
271
|
+
while ((pair = nextPair(key)) !== undefined && result.length < n) {
|
|
272
|
+
key = pair[0]
|
|
255
273
|
const keys = this.valueMap.get(key)
|
|
256
274
|
if (keys) {
|
|
257
275
|
const it = keys.values()
|
|
@@ -268,6 +286,32 @@ export class BTreeIndex<
|
|
|
268
286
|
return result
|
|
269
287
|
}
|
|
270
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Returns the next n items after the provided item or the first n items if no from item is provided.
|
|
291
|
+
* @param n - The number of items to return
|
|
292
|
+
* @param from - The item to start from (exclusive). Starts from the smallest item (inclusive) if not provided.
|
|
293
|
+
* @returns The next n items after the provided key. Returns the first n items if no from item is provided.
|
|
294
|
+
*/
|
|
295
|
+
take(n: number, from?: any, filterFn?: (key: TKey) => boolean): Array<TKey> {
|
|
296
|
+
const nextPair = (k?: any) => this.orderedEntries.nextHigherPair(k)
|
|
297
|
+
return this.takeInternal(n, nextPair, from, filterFn)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Returns the next n items **before** the provided item (in descending order) or the last n items if no from item is provided.
|
|
302
|
+
* @param n - The number of items to return
|
|
303
|
+
* @param from - The item to start from (exclusive). Starts from the largest item (inclusive) if not provided.
|
|
304
|
+
* @returns The next n items **before** the provided key. Returns the last n items if no from item is provided.
|
|
305
|
+
*/
|
|
306
|
+
takeReversed(
|
|
307
|
+
n: number,
|
|
308
|
+
from?: any,
|
|
309
|
+
filterFn?: (key: TKey) => boolean
|
|
310
|
+
): Array<TKey> {
|
|
311
|
+
const nextPair = (k?: any) => this.orderedEntries.nextLowerPair(k)
|
|
312
|
+
return this.takeInternal(n, nextPair, from, filterFn)
|
|
313
|
+
}
|
|
314
|
+
|
|
271
315
|
/**
|
|
272
316
|
* Performs an IN array lookup
|
|
273
317
|
*/
|
|
@@ -296,6 +340,13 @@ export class BTreeIndex<
|
|
|
296
340
|
.map((key) => [key, this.valueMap.get(key) ?? new Set()])
|
|
297
341
|
}
|
|
298
342
|
|
|
343
|
+
get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]> {
|
|
344
|
+
return this.takeReversed(this.orderedEntries.size).map((key) => [
|
|
345
|
+
key,
|
|
346
|
+
this.valueMap.get(key) ?? new Set(),
|
|
347
|
+
])
|
|
348
|
+
}
|
|
349
|
+
|
|
299
350
|
get valueMapData(): Map<any, Set<TKey>> {
|
|
300
351
|
return this.valueMap
|
|
301
352
|
}
|