@tanstack/db 0.5.10 → 0.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/SortedMap.cjs +40 -26
- package/dist/cjs/SortedMap.cjs.map +1 -1
- package/dist/cjs/SortedMap.d.cts +10 -15
- 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.map +1 -1
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/events.d.cts +12 -4
- package/dist/cjs/collection/index.cjs +2 -1
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/indexes.cjs.map +1 -1
- package/dist/cjs/collection/lifecycle.cjs.map +1 -1
- package/dist/cjs/collection/mutations.cjs +5 -2
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/state.cjs +6 -5
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/state.d.cts +4 -1
- package/dist/cjs/collection/subscription.cjs +60 -53
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +18 -4
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/errors.cjs +9 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +3 -0
- package/dist/cjs/event-emitter.cjs.map +1 -1
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -1
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.cjs +8 -6
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/lazy-index.cjs.map +1 -1
- package/dist/cjs/indexes/reverse-index.cjs.map +1 -1
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/local-storage.cjs.map +1 -1
- package/dist/cjs/optimistic-action.cjs.map +1 -1
- package/dist/cjs/paced-mutations.cjs.map +1 -1
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs +91 -38
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +6 -2
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/expression-helpers.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/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-registry.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.cjs +30 -15
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/internal.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/query/predicate-utils.cjs +19 -2
- package/dist/cjs/query/predicate-utils.cjs.map +1 -1
- package/dist/cjs/query/predicate-utils.d.cts +32 -1
- package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
- package/dist/cjs/scheduler.cjs.map +1 -1
- package/dist/cjs/strategies/debounceStrategy.cjs.map +1 -1
- package/dist/cjs/strategies/queueStrategy.cjs.map +1 -1
- package/dist/cjs/strategies/throttleStrategy.cjs.map +1 -1
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +43 -5
- package/dist/cjs/utils/browser-polyfills.cjs.map +1 -1
- package/dist/cjs/utils/btree.cjs.map +1 -1
- package/dist/cjs/utils/comparison.cjs.map +1 -1
- package/dist/cjs/utils/cursor.cjs +39 -0
- package/dist/cjs/utils/cursor.cjs.map +1 -0
- package/dist/cjs/utils/cursor.d.cts +18 -0
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/esm/SortedMap.d.ts +10 -15
- package/dist/esm/SortedMap.js +40 -26
- package/dist/esm/SortedMap.js.map +1 -1
- 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.map +1 -1
- package/dist/esm/collection/events.d.ts +12 -4
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.js +2 -1
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/indexes.js.map +1 -1
- package/dist/esm/collection/lifecycle.js.map +1 -1
- package/dist/esm/collection/mutations.js +6 -3
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.d.ts +4 -1
- package/dist/esm/collection/state.js +6 -5
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +18 -4
- package/dist/esm/collection/subscription.js +61 -54
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.js +9 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/event-emitter.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +6 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/btree-index.js +8 -6
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/lazy-index.js.map +1 -1
- package/dist/esm/indexes/reverse-index.js.map +1 -1
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/local-storage.js.map +1 -1
- package/dist/esm/optimistic-action.js.map +1 -1
- package/dist/esm/paced-mutations.js.map +1 -1
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/expressions.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +6 -2
- package/dist/esm/query/compiler/order-by.js +91 -38
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/expression-helpers.js.map +1 -1
- package/dist/esm/query/index.d.ts +1 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-registry.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.js +30 -15
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live/internal.js.map +1 -1
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/query/predicate-utils.d.ts +32 -1
- package/dist/esm/query/predicate-utils.js +19 -2
- package/dist/esm/query/predicate-utils.js.map +1 -1
- package/dist/esm/query/subset-dedupe.js.map +1 -1
- package/dist/esm/scheduler.js.map +1 -1
- package/dist/esm/strategies/debounceStrategy.js.map +1 -1
- package/dist/esm/strategies/queueStrategy.js.map +1 -1
- package/dist/esm/strategies/throttleStrategy.js.map +1 -1
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +43 -5
- package/dist/esm/utils/browser-polyfills.js.map +1 -1
- package/dist/esm/utils/btree.js.map +1 -1
- package/dist/esm/utils/comparison.js.map +1 -1
- package/dist/esm/utils/cursor.d.ts +18 -0
- package/dist/esm/utils/cursor.js +39 -0
- package/dist/esm/utils/cursor.js.map +1 -0
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +30 -28
- package/src/SortedMap.ts +50 -31
- package/src/collection/change-events.ts +23 -21
- package/src/collection/changes.ts +12 -12
- package/src/collection/events.ts +20 -10
- package/src/collection/index.ts +47 -46
- package/src/collection/indexes.ts +14 -14
- package/src/collection/lifecycle.ts +16 -16
- package/src/collection/mutations.ts +25 -20
- package/src/collection/state.ts +43 -36
- package/src/collection/subscription.ts +114 -83
- package/src/collection/sync.ts +13 -13
- package/src/duplicate-instance-check.ts +1 -1
- package/src/errors.ts +49 -40
- package/src/event-emitter.ts +5 -5
- package/src/index.ts +21 -20
- package/src/indexes/auto-index.ts +11 -11
- package/src/indexes/base-index.ts +13 -13
- package/src/indexes/btree-index.ts +21 -17
- package/src/indexes/index-options.ts +3 -3
- package/src/indexes/lazy-index.ts +8 -8
- package/src/indexes/reverse-index.ts +5 -5
- package/src/local-only.ts +12 -12
- package/src/local-storage.ts +17 -17
- package/src/optimistic-action.ts +5 -5
- package/src/paced-mutations.ts +6 -6
- package/src/proxy.ts +43 -43
- package/src/query/builder/functions.ts +28 -28
- package/src/query/builder/index.ts +22 -22
- package/src/query/builder/ref-proxy.ts +4 -4
- package/src/query/builder/types.ts +8 -8
- package/src/query/compiler/evaluators.ts +9 -9
- package/src/query/compiler/expressions.ts +6 -6
- package/src/query/compiler/group-by.ts +24 -24
- package/src/query/compiler/index.ts +44 -44
- package/src/query/compiler/joins.ts +37 -37
- package/src/query/compiler/order-by.ts +170 -77
- package/src/query/compiler/select.ts +13 -13
- package/src/query/compiler/types.ts +2 -2
- package/src/query/expression-helpers.ts +16 -16
- package/src/query/index.ts +10 -9
- package/src/query/ir.ts +13 -13
- package/src/query/live/collection-config-builder.ts +53 -53
- package/src/query/live/collection-registry.ts +6 -6
- package/src/query/live/collection-subscriber.ts +87 -48
- package/src/query/live/internal.ts +1 -1
- package/src/query/live/types.ts +4 -4
- package/src/query/live-query-collection.ts +15 -15
- package/src/query/optimizer.ts +29 -29
- package/src/query/predicate-utils.ts +105 -50
- package/src/query/subset-dedupe.ts +6 -6
- package/src/scheduler.ts +3 -3
- package/src/strategies/debounceStrategy.ts +6 -6
- package/src/strategies/index.ts +4 -4
- package/src/strategies/queueStrategy.ts +5 -5
- package/src/strategies/throttleStrategy.ts +6 -6
- package/src/strategies/types.ts +2 -2
- package/src/transactions.ts +9 -9
- package/src/types.ts +51 -12
- package/src/utils/array-utils.ts +1 -1
- package/src/utils/browser-polyfills.ts +2 -2
- package/src/utils/btree.ts +22 -22
- package/src/utils/comparison.ts +3 -3
- package/src/utils/cursor.ts +78 -0
- package/src/utils/index-optimization.ts +14 -14
- package/src/utils.ts +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index-optimization.cjs","sources":["../../../src/utils/index-optimization.ts"],"sourcesContent":["/**\n * # Index-Based Query Optimization\n *\n * This module provides utilities for optimizing query expressions by leveraging\n * available indexes to quickly find matching keys instead of scanning all data.\n *\n * This is different from the query structure optimizer in `query/optimizer.ts`\n * which rewrites query IR structure. This module focuses on using indexes during\n * query execution to speed up data filtering.\n *\n * ## Key Features:\n * - Uses indexes to find matching keys for WHERE conditions\n * - Supports AND/OR logic with set operations\n * - Handles range queries (eq, gt, gte, lt, lte)\n * - Optimizes IN array expressions\n */\n\nimport { DEFAULT_COMPARE_OPTIONS } from \"../utils.js\"\nimport { ReverseIndex } from \"../indexes/reverse-index.js\"\nimport type { CompareOptions } from \"../query/builder/types.js\"\nimport type { IndexInterface, IndexOperation } from \"../indexes/base-index.js\"\nimport type { BasicExpression } from \"../query/ir.js\"\nimport type { CollectionLike } from \"../types.js\"\n\n/**\n * Result of index-based query optimization\n */\nexport interface OptimizationResult<TKey> {\n canOptimize: boolean\n matchingKeys: Set<TKey>\n}\n\n/**\n * Finds an index that matches a given field path\n */\nexport function findIndexForField<TKey extends string | number>(\n collection: CollectionLike<any, TKey>,\n fieldPath: Array<string>,\n compareOptions?: CompareOptions\n): IndexInterface<TKey> | undefined {\n const compareOpts = compareOptions ?? {\n ...DEFAULT_COMPARE_OPTIONS,\n ...collection.compareOptions,\n }\n\n for (const index of collection.indexes.values()) {\n if (\n index.matchesField(fieldPath) &&\n index.matchesCompareOptions(compareOpts)\n ) {\n if (!index.matchesDirection(compareOpts.direction)) {\n return new ReverseIndex(index)\n }\n return index\n }\n }\n return undefined\n}\n\n/**\n * Intersects multiple sets (AND logic)\n */\nexport function intersectSets<T>(sets: Array<Set<T>>): Set<T> {\n if (sets.length === 0) return new Set()\n if (sets.length === 1) return new Set(sets[0])\n\n let result = new Set(sets[0])\n for (let i = 1; i < sets.length; i++) {\n const newResult = new Set<T>()\n for (const item of result) {\n if (sets[i]!.has(item)) {\n newResult.add(item)\n }\n }\n result = newResult\n }\n return result\n}\n\n/**\n * Unions multiple sets (OR logic)\n */\nexport function unionSets<T>(sets: Array<Set<T>>): Set<T> {\n const result = new Set<T>()\n for (const set of sets) {\n for (const item of set) {\n result.add(item)\n }\n }\n return result\n}\n\n/**\n * Optimizes a query expression using available indexes to find matching keys\n */\nexport function optimizeExpressionWithIndexes<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n return optimizeQueryRecursive(expression, collection)\n}\n\n/**\n * Recursively optimizes query expressions\n */\nfunction optimizeQueryRecursive<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return optimizeSimpleComparison(expression, collection)\n\n case `and`:\n return optimizeAndExpression(expression, collection)\n\n case `or`:\n return optimizeOrExpression(expression, collection)\n\n case `in`:\n return optimizeInArrayExpression(expression, collection)\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an expression can be optimized\n */\nexport function canOptimizeExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return canOptimizeSimpleComparison(expression, collection)\n\n case `and`:\n return canOptimizeAndExpression(expression, collection)\n\n case `or`:\n return canOptimizeOrExpression(expression, collection)\n\n case `in`:\n return canOptimizeInArrayExpression(expression, collection)\n }\n }\n\n return false\n}\n\n/**\n * Optimizes compound range queries on the same field\n * Example: WHERE age > 5 AND age < 10\n */\nfunction optimizeCompoundRangeQuery<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // Group range operations by field\n const fieldOperations = new Map<\n string,\n Array<{\n operation: `gt` | `gte` | `lt` | `lte`\n value: any\n }>\n >()\n\n // Collect all range operations from AND arguments\n for (const arg of expression.args) {\n if (arg.type === `func` && [`gt`, `gte`, `lt`, `lte`].includes(arg.name)) {\n const rangeOp = arg as any\n if (rangeOp.args.length === 2) {\n const leftArg = rangeOp.args[0]!\n const rightArg = rangeOp.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = rangeOp.name as `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const fieldKey = fieldPath.join(`.`)\n const value = (valueArg as any).value\n\n if (!fieldOperations.has(fieldKey)) {\n fieldOperations.set(fieldKey, [])\n }\n fieldOperations.get(fieldKey)!.push({ operation, value })\n }\n }\n }\n }\n\n // Check if we have multiple operations on the same field\n for (const [fieldKey, operations] of fieldOperations) {\n if (operations.length >= 2) {\n const fieldPath = fieldKey.split(`.`)\n const index = findIndexForField(collection, fieldPath)\n\n if (index && index.supports(`gt`) && index.supports(`lt`)) {\n // Build range query options\n let from: any = undefined\n let to: any = undefined\n let fromInclusive = true\n let toInclusive = true\n\n for (const { operation, value } of operations) {\n switch (operation) {\n case `gt`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = false\n }\n break\n case `gte`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = true\n }\n break\n case `lt`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = false\n }\n break\n case `lte`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = true\n }\n break\n }\n }\n\n const matchingKeys = (index as any).rangeQuery({\n from,\n to,\n fromInclusive,\n toInclusive,\n })\n\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Optimizes simple comparison expressions (eq, gt, gte, lt, lte)\n */\nfunction optimizeSimpleComparison<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = expression.name as `eq` | `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n // eq stays the same\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(collection, fieldPath)\n\n if (index) {\n const queryValue = (valueArg as any).value\n\n // Map operation to IndexOperation enum\n const indexOperation = operation as IndexOperation\n\n // Check if the index supports this operation\n if (!index.supports(indexOperation)) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const matchingKeys = index.lookup(indexOperation, queryValue)\n return { canOptimize: true, matchingKeys }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if a simple comparison can be optimized\n */\nfunction canOptimizeSimpleComparison<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldPath: Array<string> | null = null\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n fieldPath = (leftArg as any).path\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n fieldPath = (rightArg as any).path\n }\n\n if (fieldPath) {\n const index = findIndexForField(collection, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n\n/**\n * Optimizes AND expressions\n */\nfunction optimizeAndExpression<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // First, try to optimize compound range queries on the same field\n const compoundRangeResult = optimizeCompoundRangeQuery(expression, collection)\n if (compoundRangeResult.canOptimize) {\n return compoundRangeResult\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, collection)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use intersectSets utility for AND logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const intersectedKeys = intersectSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: intersectedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an AND expression can be optimized\n */\nfunction canOptimizeAndExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, collection))\n}\n\n/**\n * Optimizes OR expressions\n */\nfunction optimizeOrExpression<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, collection)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use unionSets utility for OR logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const unionedKeys = unionSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: unionedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an OR expression can be optimized\n */\nfunction canOptimizeOrExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, collection))\n}\n\n/**\n * Optimizes IN array expressions\n */\nfunction optimizeInArrayExpression<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const values = (arrayArg as any).value\n const index = findIndexForField(collection, fieldPath)\n\n if (index) {\n // Check if the index supports IN operation\n if (index.supports(`in`)) {\n const matchingKeys = index.lookup(`in`, values)\n return { canOptimize: true, matchingKeys }\n } else if (index.supports(`eq`)) {\n // Fallback to multiple equality lookups\n const matchingKeys = new Set<TKey>()\n for (const value of values) {\n const keysForValue = index.lookup(`eq`, value)\n for (const key of keysForValue) {\n matchingKeys.add(key)\n }\n }\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an IN array expression can be optimized\n */\nfunction canOptimizeInArrayExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(collection, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n"],"names":["DEFAULT_COMPARE_OPTIONS","ReverseIndex"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index-optimization.cjs","sources":["../../../src/utils/index-optimization.ts"],"sourcesContent":["/**\n * # Index-Based Query Optimization\n *\n * This module provides utilities for optimizing query expressions by leveraging\n * available indexes to quickly find matching keys instead of scanning all data.\n *\n * This is different from the query structure optimizer in `query/optimizer.ts`\n * which rewrites query IR structure. This module focuses on using indexes during\n * query execution to speed up data filtering.\n *\n * ## Key Features:\n * - Uses indexes to find matching keys for WHERE conditions\n * - Supports AND/OR logic with set operations\n * - Handles range queries (eq, gt, gte, lt, lte)\n * - Optimizes IN array expressions\n */\n\nimport { DEFAULT_COMPARE_OPTIONS } from '../utils.js'\nimport { ReverseIndex } from '../indexes/reverse-index.js'\nimport type { CompareOptions } from '../query/builder/types.js'\nimport type { IndexInterface, IndexOperation } from '../indexes/base-index.js'\nimport type { BasicExpression } from '../query/ir.js'\nimport type { CollectionLike } from '../types.js'\n\n/**\n * Result of index-based query optimization\n */\nexport interface OptimizationResult<TKey> {\n canOptimize: boolean\n matchingKeys: Set<TKey>\n}\n\n/**\n * Finds an index that matches a given field path\n */\nexport function findIndexForField<TKey extends string | number>(\n collection: CollectionLike<any, TKey>,\n fieldPath: Array<string>,\n compareOptions?: CompareOptions,\n): IndexInterface<TKey> | undefined {\n const compareOpts = compareOptions ?? {\n ...DEFAULT_COMPARE_OPTIONS,\n ...collection.compareOptions,\n }\n\n for (const index of collection.indexes.values()) {\n if (\n index.matchesField(fieldPath) &&\n index.matchesCompareOptions(compareOpts)\n ) {\n if (!index.matchesDirection(compareOpts.direction)) {\n return new ReverseIndex(index)\n }\n return index\n }\n }\n return undefined\n}\n\n/**\n * Intersects multiple sets (AND logic)\n */\nexport function intersectSets<T>(sets: Array<Set<T>>): Set<T> {\n if (sets.length === 0) return new Set()\n if (sets.length === 1) return new Set(sets[0])\n\n let result = new Set(sets[0])\n for (let i = 1; i < sets.length; i++) {\n const newResult = new Set<T>()\n for (const item of result) {\n if (sets[i]!.has(item)) {\n newResult.add(item)\n }\n }\n result = newResult\n }\n return result\n}\n\n/**\n * Unions multiple sets (OR logic)\n */\nexport function unionSets<T>(sets: Array<Set<T>>): Set<T> {\n const result = new Set<T>()\n for (const set of sets) {\n for (const item of set) {\n result.add(item)\n }\n }\n return result\n}\n\n/**\n * Optimizes a query expression using available indexes to find matching keys\n */\nexport function optimizeExpressionWithIndexes<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n return optimizeQueryRecursive(expression, collection)\n}\n\n/**\n * Recursively optimizes query expressions\n */\nfunction optimizeQueryRecursive<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return optimizeSimpleComparison(expression, collection)\n\n case `and`:\n return optimizeAndExpression(expression, collection)\n\n case `or`:\n return optimizeOrExpression(expression, collection)\n\n case `in`:\n return optimizeInArrayExpression(expression, collection)\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an expression can be optimized\n */\nexport function canOptimizeExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return canOptimizeSimpleComparison(expression, collection)\n\n case `and`:\n return canOptimizeAndExpression(expression, collection)\n\n case `or`:\n return canOptimizeOrExpression(expression, collection)\n\n case `in`:\n return canOptimizeInArrayExpression(expression, collection)\n }\n }\n\n return false\n}\n\n/**\n * Optimizes compound range queries on the same field\n * Example: WHERE age > 5 AND age < 10\n */\nfunction optimizeCompoundRangeQuery<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // Group range operations by field\n const fieldOperations = new Map<\n string,\n Array<{\n operation: `gt` | `gte` | `lt` | `lte`\n value: any\n }>\n >()\n\n // Collect all range operations from AND arguments\n for (const arg of expression.args) {\n if (arg.type === `func` && [`gt`, `gte`, `lt`, `lte`].includes(arg.name)) {\n const rangeOp = arg as any\n if (rangeOp.args.length === 2) {\n const leftArg = rangeOp.args[0]!\n const rightArg = rangeOp.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = rangeOp.name as `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const fieldKey = fieldPath.join(`.`)\n const value = (valueArg as any).value\n\n if (!fieldOperations.has(fieldKey)) {\n fieldOperations.set(fieldKey, [])\n }\n fieldOperations.get(fieldKey)!.push({ operation, value })\n }\n }\n }\n }\n\n // Check if we have multiple operations on the same field\n for (const [fieldKey, operations] of fieldOperations) {\n if (operations.length >= 2) {\n const fieldPath = fieldKey.split(`.`)\n const index = findIndexForField(collection, fieldPath)\n\n if (index && index.supports(`gt`) && index.supports(`lt`)) {\n // Build range query options\n let from: any = undefined\n let to: any = undefined\n let fromInclusive = true\n let toInclusive = true\n\n for (const { operation, value } of operations) {\n switch (operation) {\n case `gt`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = false\n }\n break\n case `gte`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = true\n }\n break\n case `lt`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = false\n }\n break\n case `lte`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = true\n }\n break\n }\n }\n\n const matchingKeys = (index as any).rangeQuery({\n from,\n to,\n fromInclusive,\n toInclusive,\n })\n\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Optimizes simple comparison expressions (eq, gt, gte, lt, lte)\n */\nfunction optimizeSimpleComparison<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = expression.name as `eq` | `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n // eq stays the same\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(collection, fieldPath)\n\n if (index) {\n const queryValue = (valueArg as any).value\n\n // Map operation to IndexOperation enum\n const indexOperation = operation as IndexOperation\n\n // Check if the index supports this operation\n if (!index.supports(indexOperation)) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const matchingKeys = index.lookup(indexOperation, queryValue)\n return { canOptimize: true, matchingKeys }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if a simple comparison can be optimized\n */\nfunction canOptimizeSimpleComparison<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldPath: Array<string> | null = null\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n fieldPath = (leftArg as any).path\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n fieldPath = (rightArg as any).path\n }\n\n if (fieldPath) {\n const index = findIndexForField(collection, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n\n/**\n * Optimizes AND expressions\n */\nfunction optimizeAndExpression<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // First, try to optimize compound range queries on the same field\n const compoundRangeResult = optimizeCompoundRangeQuery(expression, collection)\n if (compoundRangeResult.canOptimize) {\n return compoundRangeResult\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, collection)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use intersectSets utility for AND logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const intersectedKeys = intersectSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: intersectedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an AND expression can be optimized\n */\nfunction canOptimizeAndExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, collection))\n}\n\n/**\n * Optimizes OR expressions\n */\nfunction optimizeOrExpression<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, collection)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use unionSets utility for OR logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const unionedKeys = unionSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: unionedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an OR expression can be optimized\n */\nfunction canOptimizeOrExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, collection))\n}\n\n/**\n * Optimizes IN array expressions\n */\nfunction optimizeInArrayExpression<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const values = (arrayArg as any).value\n const index = findIndexForField(collection, fieldPath)\n\n if (index) {\n // Check if the index supports IN operation\n if (index.supports(`in`)) {\n const matchingKeys = index.lookup(`in`, values)\n return { canOptimize: true, matchingKeys }\n } else if (index.supports(`eq`)) {\n // Fallback to multiple equality lookups\n const matchingKeys = new Set<TKey>()\n for (const value of values) {\n const keysForValue = index.lookup(`eq`, value)\n for (const key of keysForValue) {\n matchingKeys.add(key)\n }\n }\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an IN array expression can be optimized\n */\nfunction canOptimizeInArrayExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(collection, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n"],"names":["DEFAULT_COMPARE_OPTIONS","ReverseIndex"],"mappings":"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"}
|
package/dist/cjs/utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.cjs","sources":["../../src/utils.ts"],"sourcesContent":["/**\n * Generic utility functions\n */\n\nimport type { CompareOptions } from
|
|
1
|
+
{"version":3,"file":"utils.cjs","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/dist/esm/SortedMap.d.ts
CHANGED
|
@@ -1,33 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A Map implementation that keeps its entries sorted based on a comparator function
|
|
3
|
-
* @template TKey - The type of keys in the map
|
|
3
|
+
* @template TKey - The type of keys in the map (must be string | number)
|
|
4
4
|
* @template TValue - The type of values in the map
|
|
5
5
|
*/
|
|
6
|
-
export declare class SortedMap<TKey, TValue> {
|
|
6
|
+
export declare class SortedMap<TKey extends string | number, TValue> {
|
|
7
7
|
private map;
|
|
8
8
|
private sortedKeys;
|
|
9
9
|
private comparator;
|
|
10
10
|
/**
|
|
11
11
|
* Creates a new SortedMap instance
|
|
12
12
|
*
|
|
13
|
-
* @param comparator - Optional function to compare values for sorting
|
|
13
|
+
* @param comparator - Optional function to compare values for sorting.
|
|
14
|
+
* If not provided, entries are sorted by key only.
|
|
14
15
|
*/
|
|
15
16
|
constructor(comparator?: (a: TValue, b: TValue) => number);
|
|
16
|
-
/**
|
|
17
|
-
* Default comparator function used when none is provided
|
|
18
|
-
*
|
|
19
|
-
* @param a - First value to compare
|
|
20
|
-
* @param b - Second value to compare
|
|
21
|
-
* @returns -1 if a < b, 1 if a > b, 0 if equal
|
|
22
|
-
*/
|
|
23
|
-
private defaultComparator;
|
|
24
17
|
/**
|
|
25
18
|
* Finds the index where a key-value pair should be inserted to maintain sort order.
|
|
26
|
-
* Uses binary search to find the correct position based on the value
|
|
27
|
-
*
|
|
19
|
+
* Uses binary search to find the correct position based on the value (if comparator provided),
|
|
20
|
+
* with key-based tie-breaking for deterministic ordering when values compare as equal.
|
|
21
|
+
* If no comparator is provided, sorts by key only.
|
|
22
|
+
* Runs in O(log n) time.
|
|
28
23
|
*
|
|
29
|
-
* @param key - The key to find position for
|
|
30
|
-
* @param value - The value to compare against
|
|
24
|
+
* @param key - The key to find position for (used as tie-breaker or primary sort when no comparator)
|
|
25
|
+
* @param value - The value to compare against (only used if comparator is provided)
|
|
31
26
|
* @returns The index where the key should be inserted
|
|
32
27
|
*/
|
|
33
28
|
private indexOf;
|
package/dist/esm/SortedMap.js
CHANGED
|
@@ -1,49 +1,63 @@
|
|
|
1
|
+
import { compareKeys } from "@tanstack/db-ivm";
|
|
1
2
|
class SortedMap {
|
|
2
3
|
/**
|
|
3
4
|
* Creates a new SortedMap instance
|
|
4
5
|
*
|
|
5
|
-
* @param comparator - Optional function to compare values for sorting
|
|
6
|
+
* @param comparator - Optional function to compare values for sorting.
|
|
7
|
+
* If not provided, entries are sorted by key only.
|
|
6
8
|
*/
|
|
7
9
|
constructor(comparator) {
|
|
8
10
|
this.map = /* @__PURE__ */ new Map();
|
|
9
11
|
this.sortedKeys = [];
|
|
10
|
-
this.comparator = comparator
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Default comparator function used when none is provided
|
|
14
|
-
*
|
|
15
|
-
* @param a - First value to compare
|
|
16
|
-
* @param b - Second value to compare
|
|
17
|
-
* @returns -1 if a < b, 1 if a > b, 0 if equal
|
|
18
|
-
*/
|
|
19
|
-
defaultComparator(a, b) {
|
|
20
|
-
if (a < b) return -1;
|
|
21
|
-
if (a > b) return 1;
|
|
22
|
-
return 0;
|
|
12
|
+
this.comparator = comparator;
|
|
23
13
|
}
|
|
24
14
|
/**
|
|
25
15
|
* Finds the index where a key-value pair should be inserted to maintain sort order.
|
|
26
|
-
* Uses binary search to find the correct position based on the value
|
|
27
|
-
*
|
|
16
|
+
* Uses binary search to find the correct position based on the value (if comparator provided),
|
|
17
|
+
* with key-based tie-breaking for deterministic ordering when values compare as equal.
|
|
18
|
+
* If no comparator is provided, sorts by key only.
|
|
19
|
+
* Runs in O(log n) time.
|
|
28
20
|
*
|
|
29
|
-
* @param key - The key to find position for
|
|
30
|
-
* @param value - The value to compare against
|
|
21
|
+
* @param key - The key to find position for (used as tie-breaker or primary sort when no comparator)
|
|
22
|
+
* @param value - The value to compare against (only used if comparator is provided)
|
|
31
23
|
* @returns The index where the key should be inserted
|
|
32
24
|
*/
|
|
33
|
-
indexOf(value) {
|
|
25
|
+
indexOf(key, value) {
|
|
34
26
|
let left = 0;
|
|
35
27
|
let right = this.sortedKeys.length;
|
|
28
|
+
if (!this.comparator) {
|
|
29
|
+
while (left < right) {
|
|
30
|
+
const mid = Math.floor((left + right) / 2);
|
|
31
|
+
const midKey = this.sortedKeys[mid];
|
|
32
|
+
const keyComparison = compareKeys(key, midKey);
|
|
33
|
+
if (keyComparison < 0) {
|
|
34
|
+
right = mid;
|
|
35
|
+
} else if (keyComparison > 0) {
|
|
36
|
+
left = mid + 1;
|
|
37
|
+
} else {
|
|
38
|
+
return mid;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return left;
|
|
42
|
+
}
|
|
36
43
|
while (left < right) {
|
|
37
44
|
const mid = Math.floor((left + right) / 2);
|
|
38
45
|
const midKey = this.sortedKeys[mid];
|
|
39
46
|
const midValue = this.map.get(midKey);
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
47
|
+
const valueComparison = this.comparator(value, midValue);
|
|
48
|
+
if (valueComparison < 0) {
|
|
42
49
|
right = mid;
|
|
43
|
-
} else if (
|
|
50
|
+
} else if (valueComparison > 0) {
|
|
44
51
|
left = mid + 1;
|
|
45
52
|
} else {
|
|
46
|
-
|
|
53
|
+
const keyComparison = compareKeys(key, midKey);
|
|
54
|
+
if (keyComparison < 0) {
|
|
55
|
+
right = mid;
|
|
56
|
+
} else if (keyComparison > 0) {
|
|
57
|
+
left = mid + 1;
|
|
58
|
+
} else {
|
|
59
|
+
return mid;
|
|
60
|
+
}
|
|
47
61
|
}
|
|
48
62
|
}
|
|
49
63
|
return left;
|
|
@@ -58,10 +72,10 @@ class SortedMap {
|
|
|
58
72
|
set(key, value) {
|
|
59
73
|
if (this.map.has(key)) {
|
|
60
74
|
const oldValue = this.map.get(key);
|
|
61
|
-
const oldIndex = this.indexOf(oldValue);
|
|
75
|
+
const oldIndex = this.indexOf(key, oldValue);
|
|
62
76
|
this.sortedKeys.splice(oldIndex, 1);
|
|
63
77
|
}
|
|
64
|
-
const index = this.indexOf(value);
|
|
78
|
+
const index = this.indexOf(key, value);
|
|
65
79
|
this.sortedKeys.splice(index, 0, key);
|
|
66
80
|
this.map.set(key, value);
|
|
67
81
|
return this;
|
|
@@ -84,7 +98,7 @@ class SortedMap {
|
|
|
84
98
|
delete(key) {
|
|
85
99
|
if (this.map.has(key)) {
|
|
86
100
|
const oldValue = this.map.get(key);
|
|
87
|
-
const index = this.indexOf(oldValue);
|
|
101
|
+
const index = this.indexOf(key, oldValue);
|
|
88
102
|
this.sortedKeys.splice(index, 1);
|
|
89
103
|
return this.map.delete(key);
|
|
90
104
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SortedMap.js","sources":["../../src/SortedMap.ts"],"sourcesContent":["/**\n * A Map implementation that keeps its entries sorted based on a comparator function\n * @template TKey - The type of keys in the map\n * @template TValue - The type of values in the map\n */\nexport class SortedMap<TKey, TValue> {\n private map: Map<TKey, TValue>\n private sortedKeys: Array<TKey>\n private comparator: (a: TValue, b: TValue) => number\n\n /**\n * Creates a new SortedMap instance\n *\n * @param comparator - Optional function to compare values for sorting
|
|
1
|
+
{"version":3,"file":"SortedMap.js","sources":["../../src/SortedMap.ts"],"sourcesContent":["import { compareKeys } from '@tanstack/db-ivm'\n\n/**\n * A Map implementation that keeps its entries sorted based on a comparator function\n * @template TKey - The type of keys in the map (must be string | number)\n * @template TValue - The type of values in the map\n */\nexport class SortedMap<TKey extends string | number, TValue> {\n private map: Map<TKey, TValue>\n private sortedKeys: Array<TKey>\n private comparator: ((a: TValue, b: TValue) => number) | undefined\n\n /**\n * Creates a new SortedMap instance\n *\n * @param comparator - Optional function to compare values for sorting.\n * If not provided, entries are sorted by key only.\n */\n constructor(comparator?: (a: TValue, b: TValue) => number) {\n this.map = new Map<TKey, TValue>()\n this.sortedKeys = []\n this.comparator = comparator\n }\n\n /**\n * Finds the index where a key-value pair should be inserted to maintain sort order.\n * Uses binary search to find the correct position based on the value (if comparator provided),\n * with key-based tie-breaking for deterministic ordering when values compare as equal.\n * If no comparator is provided, sorts by key only.\n * Runs in O(log n) time.\n *\n * @param key - The key to find position for (used as tie-breaker or primary sort when no comparator)\n * @param value - The value to compare against (only used if comparator is provided)\n * @returns The index where the key should be inserted\n */\n private indexOf(key: TKey, value: TValue): number {\n let left = 0\n let right = this.sortedKeys.length\n\n // Fast path: no comparator means sort by key only\n if (!this.comparator) {\n while (left < right) {\n const mid = Math.floor((left + right) / 2)\n const midKey = this.sortedKeys[mid]!\n const keyComparison = compareKeys(key, midKey)\n if (keyComparison < 0) {\n right = mid\n } else if (keyComparison > 0) {\n left = mid + 1\n } else {\n return mid\n }\n }\n return left\n }\n\n // With comparator: sort by value first, then key as tie-breaker\n while (left < right) {\n const mid = Math.floor((left + right) / 2)\n const midKey = this.sortedKeys[mid]!\n const midValue = this.map.get(midKey)!\n const valueComparison = this.comparator(value, midValue)\n\n if (valueComparison < 0) {\n right = mid\n } else if (valueComparison > 0) {\n left = mid + 1\n } else {\n // Values are equal, use key as tie-breaker for deterministic ordering\n const keyComparison = compareKeys(key, midKey)\n if (keyComparison < 0) {\n right = mid\n } else if (keyComparison > 0) {\n left = mid + 1\n } else {\n // Same key (shouldn't happen during insert, but handle for lookups)\n return mid\n }\n }\n }\n\n return left\n }\n\n /**\n * Sets a key-value pair in the map and maintains sort order\n *\n * @param key - The key to set\n * @param value - The value to associate with the key\n * @returns This SortedMap instance for chaining\n */\n set(key: TKey, value: TValue): this {\n if (this.map.has(key)) {\n // Need to remove the old key from the sorted keys array\n const oldValue = this.map.get(key)!\n const oldIndex = this.indexOf(key, oldValue)\n this.sortedKeys.splice(oldIndex, 1)\n }\n\n // Insert the new key at the correct position\n const index = this.indexOf(key, value)\n this.sortedKeys.splice(index, 0, key)\n\n this.map.set(key, value)\n\n return this\n }\n\n /**\n * Gets a value by its key\n *\n * @param key - The key to look up\n * @returns The value associated with the key, or undefined if not found\n */\n get(key: TKey): TValue | undefined {\n return this.map.get(key)\n }\n\n /**\n * Removes a key-value pair from the map\n *\n * @param key - The key to remove\n * @returns True if the key was found and removed, false otherwise\n */\n delete(key: TKey): boolean {\n if (this.map.has(key)) {\n const oldValue = this.map.get(key)\n const index = this.indexOf(key, oldValue!)\n this.sortedKeys.splice(index, 1)\n return this.map.delete(key)\n }\n\n return false\n }\n\n /**\n * Checks if a key exists in the map\n *\n * @param key - The key to check\n * @returns True if the key exists, false otherwise\n */\n has(key: TKey): boolean {\n return this.map.has(key)\n }\n\n /**\n * Removes all key-value pairs from the map\n */\n clear(): void {\n this.map.clear()\n this.sortedKeys = []\n }\n\n /**\n * Gets the number of key-value pairs in the map\n */\n get size(): number {\n return this.map.size\n }\n\n /**\n * Default iterator that returns entries in sorted order\n *\n * @returns An iterator for the map's entries\n */\n *[Symbol.iterator](): IterableIterator<[TKey, TValue]> {\n for (const key of this.sortedKeys) {\n yield [key, this.map.get(key)!] as [TKey, TValue]\n }\n }\n\n /**\n * Returns an iterator for the map's entries in sorted order\n *\n * @returns An iterator for the map's entries\n */\n entries(): IterableIterator<[TKey, TValue]> {\n return this[Symbol.iterator]()\n }\n\n /**\n * Returns an iterator for the map's keys in sorted order\n *\n * @returns An iterator for the map's keys\n */\n keys(): IterableIterator<TKey> {\n return this.sortedKeys[Symbol.iterator]()\n }\n\n /**\n * Returns an iterator for the map's values in sorted order\n *\n * @returns An iterator for the map's values\n */\n values(): IterableIterator<TValue> {\n return function* (this: SortedMap<TKey, TValue>) {\n for (const key of this.sortedKeys) {\n yield this.map.get(key)!\n }\n }.call(this)\n }\n\n /**\n * Executes a callback function for each key-value pair in the map in sorted order\n *\n * @param callbackfn - Function to execute for each entry\n */\n forEach(\n callbackfn: (value: TValue, key: TKey, map: Map<TKey, TValue>) => void,\n ): void {\n for (const key of this.sortedKeys) {\n callbackfn(this.map.get(key)!, key, this.map)\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -87,9 +87,9 @@ function currentStateAsChanges(collection, options = {}) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
function createFilterFunctionFromExpression(expression) {
|
|
90
|
+
const evaluator = compileSingleRowExpression(expression);
|
|
90
91
|
return (item) => {
|
|
91
92
|
try {
|
|
92
|
-
const evaluator = compileSingleRowExpression(expression);
|
|
93
93
|
const result = evaluator(item);
|
|
94
94
|
return toBooleanPredicate(result);
|
|
95
95
|
} catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-events.js","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":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"change-events.js","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 // Compile expression once when filter function is created, not on every invocation\n const evaluator = compileSingleRowExpression(expression)\n\n return (item: T): boolean => {\n try {\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":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changes.js","sources":["../../../src/collection/changes.ts"],"sourcesContent":["import { NegativeActiveSubscribersError } from
|
|
1
|
+
{"version":3,"file":"changes.js","sources":["../../../src/collection/changes.ts"],"sourcesContent":["import { NegativeActiveSubscribersError } from '../errors'\nimport { CollectionSubscription } from './subscription.js'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { ChangeMessage, SubscribeChangesOptions } from '../types'\nimport type { CollectionLifecycleManager } from './lifecycle.js'\nimport type { CollectionSyncManager } from './sync.js'\nimport type { CollectionEventsManager } from './events.js'\nimport type { CollectionImpl } from './index.js'\n\nexport class CollectionChangesManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n private sync!: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n private events!: CollectionEventsManager\n private collection!: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n\n public activeSubscribersCount = 0\n public changeSubscriptions = new Set<CollectionSubscription>()\n public batchedEvents: Array<ChangeMessage<TOutput, TKey>> = []\n public shouldBatchEvents = false\n\n /**\n * Creates a new CollectionChangesManager instance\n */\n constructor() {}\n\n public setDeps(deps: {\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n events: CollectionEventsManager\n collection: CollectionImpl<TOutput, TKey, any, TSchema, TInput>\n }) {\n this.lifecycle = deps.lifecycle\n this.sync = deps.sync\n this.events = deps.events\n this.collection = deps.collection\n }\n\n /**\n * Emit an empty ready event to notify subscribers that the collection is ready\n * This bypasses the normal empty array check in emitEvents\n */\n public emitEmptyReadyEvent(): void {\n // Emit empty array directly to all subscribers\n for (const subscription of this.changeSubscriptions) {\n subscription.emitEvents([])\n }\n }\n\n /**\n * Emit events either immediately or batch them for later emission\n */\n public emitEvents(\n changes: Array<ChangeMessage<TOutput, TKey>>,\n forceEmit = false,\n ): void {\n // Skip batching for user actions (forceEmit=true) to keep UI responsive\n if (this.shouldBatchEvents && !forceEmit) {\n // Add events to the batch\n this.batchedEvents.push(...changes)\n return\n }\n\n // Either we're not batching, or we're forcing emission (user action or ending batch cycle)\n let eventsToEmit = changes\n\n if (forceEmit) {\n // Force emit is used to end a batch (e.g. after a sync commit). Combine any\n // buffered optimistic events with the final changes so subscribers see the\n // whole picture, even if the sync diff is empty.\n if (this.batchedEvents.length > 0) {\n eventsToEmit = [...this.batchedEvents, ...changes]\n }\n this.batchedEvents = []\n this.shouldBatchEvents = false\n }\n\n if (eventsToEmit.length === 0) {\n return\n }\n\n // Emit to all listeners\n for (const subscription of this.changeSubscriptions) {\n subscription.emitEvents(eventsToEmit)\n }\n }\n\n /**\n * Subscribe to changes in the collection\n */\n public subscribeChanges(\n callback: (changes: Array<ChangeMessage<TOutput>>) => void,\n options: SubscribeChangesOptions = {},\n ): CollectionSubscription {\n // Start sync and track subscriber\n this.addSubscriber()\n\n const subscription = new CollectionSubscription(this.collection, callback, {\n ...options,\n onUnsubscribe: () => {\n this.removeSubscriber()\n this.changeSubscriptions.delete(subscription)\n },\n })\n\n if (options.includeInitialState) {\n subscription.requestSnapshot({ trackLoadSubsetPromise: false })\n }\n\n // Add to batched listeners\n this.changeSubscriptions.add(subscription)\n\n return subscription\n }\n\n /**\n * Increment the active subscribers count and start sync if needed\n */\n private addSubscriber(): void {\n const previousSubscriberCount = this.activeSubscribersCount\n this.activeSubscribersCount++\n this.lifecycle.cancelGCTimer()\n\n // Start sync if collection was cleaned up\n if (\n this.lifecycle.status === `cleaned-up` ||\n this.lifecycle.status === `idle`\n ) {\n this.sync.startSync()\n }\n\n this.events.emitSubscribersChange(\n this.activeSubscribersCount,\n previousSubscriberCount,\n )\n }\n\n /**\n * Decrement the active subscribers count and start GC timer if needed\n */\n private removeSubscriber(): void {\n const previousSubscriberCount = this.activeSubscribersCount\n this.activeSubscribersCount--\n\n if (this.activeSubscribersCount === 0) {\n this.lifecycle.startGCTimer()\n } else if (this.activeSubscribersCount < 0) {\n throw new NegativeActiveSubscribersError()\n }\n\n this.events.emitSubscribersChange(\n this.activeSubscribersCount,\n previousSubscriberCount,\n )\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 cleanup(): void {\n this.batchedEvents = []\n this.shouldBatchEvents = false\n }\n}\n"],"names":[],"mappings":";;AASO,MAAM,yBAKX;AAAA;AAAA;AAAA;AAAA,EAcA,cAAc;AARd,SAAO,yBAAyB;AAChC,SAAO,0CAA0B,IAAA;AACjC,SAAO,gBAAqD,CAAA;AAC5D,SAAO,oBAAoB;AAAA,EAKZ;AAAA,EAER,QAAQ,MAKZ;AACD,SAAK,YAAY,KAAK;AACtB,SAAK,OAAO,KAAK;AACjB,SAAK,SAAS,KAAK;AACnB,SAAK,aAAa,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,sBAA4B;AAEjC,eAAW,gBAAgB,KAAK,qBAAqB;AACnD,mBAAa,WAAW,EAAE;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKO,WACL,SACA,YAAY,OACN;AAEN,QAAI,KAAK,qBAAqB,CAAC,WAAW;AAExC,WAAK,cAAc,KAAK,GAAG,OAAO;AAClC;AAAA,IACF;AAGA,QAAI,eAAe;AAEnB,QAAI,WAAW;AAIb,UAAI,KAAK,cAAc,SAAS,GAAG;AACjC,uBAAe,CAAC,GAAG,KAAK,eAAe,GAAG,OAAO;AAAA,MACnD;AACA,WAAK,gBAAgB,CAAA;AACrB,WAAK,oBAAoB;AAAA,IAC3B;AAEA,QAAI,aAAa,WAAW,GAAG;AAC7B;AAAA,IACF;AAGA,eAAW,gBAAgB,KAAK,qBAAqB;AACnD,mBAAa,WAAW,YAAY;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKO,iBACL,UACA,UAAmC,IACX;AAExB,SAAK,cAAA;AAEL,UAAM,eAAe,IAAI,uBAAuB,KAAK,YAAY,UAAU;AAAA,MACzE,GAAG;AAAA,MACH,eAAe,MAAM;AACnB,aAAK,iBAAA;AACL,aAAK,oBAAoB,OAAO,YAAY;AAAA,MAC9C;AAAA,IAAA,CACD;AAED,QAAI,QAAQ,qBAAqB;AAC/B,mBAAa,gBAAgB,EAAE,wBAAwB,MAAA,CAAO;AAAA,IAChE;AAGA,SAAK,oBAAoB,IAAI,YAAY;AAEzC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAsB;AAC5B,UAAM,0BAA0B,KAAK;AACrC,SAAK;AACL,SAAK,UAAU,cAAA;AAGf,QACE,KAAK,UAAU,WAAW,gBAC1B,KAAK,UAAU,WAAW,QAC1B;AACA,WAAK,KAAK,UAAA;AAAA,IACZ;AAEA,SAAK,OAAO;AAAA,MACV,KAAK;AAAA,MACL;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAyB;AAC/B,UAAM,0BAA0B,KAAK;AACrC,SAAK;AAEL,QAAI,KAAK,2BAA2B,GAAG;AACrC,WAAK,UAAU,aAAA;AAAA,IACjB,WAAW,KAAK,yBAAyB,GAAG;AAC1C,YAAM,IAAI,+BAAA;AAAA,IACZ;AAEA,SAAK,OAAO;AAAA,MACV,KAAK;AAAA,MACL;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,UAAgB;AACrB,SAAK,gBAAgB,CAAA;AACrB,SAAK,oBAAoB;AAAA,EAC3B;AACF;"}
|
|
@@ -38,14 +38,22 @@ export interface CollectionLoadingSubsetChangeEvent {
|
|
|
38
38
|
previousIsLoadingSubset: boolean;
|
|
39
39
|
loadingSubsetTransition: `start` | `end`;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Event emitted when the collection is truncated (all data cleared)
|
|
43
|
+
*/
|
|
44
|
+
export interface CollectionTruncateEvent {
|
|
45
|
+
type: `truncate`;
|
|
46
|
+
collection: Collection<any, any, any, any, any>;
|
|
47
|
+
}
|
|
41
48
|
export type AllCollectionEvents = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
'status:change': CollectionStatusChangeEvent;
|
|
50
|
+
'subscribers:change': CollectionSubscribersChangeEvent;
|
|
51
|
+
'loadingSubset:change': CollectionLoadingSubsetChangeEvent;
|
|
52
|
+
truncate: CollectionTruncateEvent;
|
|
45
53
|
} & {
|
|
46
54
|
[K in CollectionStatus as `status:${K}`]: CollectionStatusEvent<K>;
|
|
47
55
|
};
|
|
48
|
-
export type CollectionEvent = AllCollectionEvents[keyof AllCollectionEvents] | CollectionStatusChangeEvent | CollectionSubscribersChangeEvent | CollectionLoadingSubsetChangeEvent;
|
|
56
|
+
export type CollectionEvent = AllCollectionEvents[keyof AllCollectionEvents] | CollectionStatusChangeEvent | CollectionSubscribersChangeEvent | CollectionLoadingSubsetChangeEvent | CollectionTruncateEvent;
|
|
49
57
|
export type CollectionEventHandler<T extends keyof AllCollectionEvents> = (event: AllCollectionEvents[T]) => void;
|
|
50
58
|
export declare class CollectionEventsManager extends EventEmitter<AllCollectionEvents> {
|
|
51
59
|
private collection;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sources":["../../../src/collection/events.ts"],"sourcesContent":["import { EventEmitter } from
|
|
1
|
+
{"version":3,"file":"events.js","sources":["../../../src/collection/events.ts"],"sourcesContent":["import { EventEmitter } from '../event-emitter.js'\nimport type { Collection } from './index.js'\nimport type { CollectionStatus } from '../types.js'\n\n/**\n * Event emitted when the collection status changes\n */\nexport interface CollectionStatusChangeEvent {\n type: `status:change`\n collection: Collection\n previousStatus: CollectionStatus\n status: CollectionStatus\n}\n\n/**\n * Event emitted when the collection status changes to a specific status\n */\nexport interface CollectionStatusEvent<T extends CollectionStatus> {\n type: `status:${T}`\n collection: Collection\n previousStatus: CollectionStatus\n status: T\n}\n\n/**\n * Event emitted when the number of subscribers to the collection changes\n */\nexport interface CollectionSubscribersChangeEvent {\n type: `subscribers:change`\n collection: Collection\n previousSubscriberCount: number\n subscriberCount: number\n}\n\n/**\n * Event emitted when the collection's loading more state changes\n */\nexport interface CollectionLoadingSubsetChangeEvent {\n type: `loadingSubset:change`\n collection: Collection<any, any, any, any, any>\n isLoadingSubset: boolean\n previousIsLoadingSubset: boolean\n loadingSubsetTransition: `start` | `end`\n}\n\n/**\n * Event emitted when the collection is truncated (all data cleared)\n */\nexport interface CollectionTruncateEvent {\n type: `truncate`\n collection: Collection<any, any, any, any, any>\n}\n\nexport type AllCollectionEvents = {\n 'status:change': CollectionStatusChangeEvent\n 'subscribers:change': CollectionSubscribersChangeEvent\n 'loadingSubset:change': CollectionLoadingSubsetChangeEvent\n truncate: CollectionTruncateEvent\n} & {\n [K in CollectionStatus as `status:${K}`]: CollectionStatusEvent<K>\n}\n\nexport type CollectionEvent =\n | AllCollectionEvents[keyof AllCollectionEvents]\n | CollectionStatusChangeEvent\n | CollectionSubscribersChangeEvent\n | CollectionLoadingSubsetChangeEvent\n | CollectionTruncateEvent\n\nexport type CollectionEventHandler<T extends keyof AllCollectionEvents> = (\n event: AllCollectionEvents[T],\n) => void\n\nexport class CollectionEventsManager extends EventEmitter<AllCollectionEvents> {\n private collection!: Collection<any, any, any, any, any>\n\n constructor() {\n super()\n }\n\n setDeps(deps: { collection: Collection<any, any, any, any, any> }) {\n this.collection = deps.collection\n }\n\n /**\n * Emit an event to all listeners\n * Public API for emitting collection events\n */\n emit<T extends keyof AllCollectionEvents>(\n event: T,\n eventPayload: AllCollectionEvents[T],\n ): void {\n this.emitInner(event, eventPayload)\n }\n\n emitStatusChange<T extends CollectionStatus>(\n status: T,\n previousStatus: CollectionStatus,\n ) {\n this.emit(`status:change`, {\n type: `status:change`,\n collection: this.collection,\n previousStatus,\n status,\n })\n\n // Emit specific status event using type assertion\n const eventKey: `status:${T}` = `status:${status}`\n this.emit(eventKey, {\n type: eventKey,\n collection: this.collection,\n previousStatus,\n status,\n } as AllCollectionEvents[`status:${T}`])\n }\n\n emitSubscribersChange(\n subscriberCount: number,\n previousSubscriberCount: number,\n ) {\n this.emit(`subscribers:change`, {\n type: `subscribers:change`,\n collection: this.collection,\n previousSubscriberCount,\n subscriberCount,\n })\n }\n\n cleanup() {\n this.clearListeners()\n }\n}\n"],"names":[],"mappings":";AAyEO,MAAM,gCAAgC,aAAkC;AAAA,EAG7E,cAAc;AACZ,UAAA;AAAA,EACF;AAAA,EAEA,QAAQ,MAA2D;AACjE,SAAK,aAAa,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,KACE,OACA,cACM;AACN,SAAK,UAAU,OAAO,YAAY;AAAA,EACpC;AAAA,EAEA,iBACE,QACA,gBACA;AACA,SAAK,KAAK,iBAAiB;AAAA,MACzB,MAAM;AAAA,MACN,YAAY,KAAK;AAAA,MACjB;AAAA,MACA;AAAA,IAAA,CACD;AAGD,UAAM,WAA0B,UAAU,MAAM;AAChD,SAAK,KAAK,UAAU;AAAA,MAClB,MAAM;AAAA,MACN,YAAY,KAAK;AAAA,MACjB;AAAA,MACA;AAAA,IAAA,CACqC;AAAA,EACzC;AAAA,EAEA,sBACE,iBACA,yBACA;AACA,SAAK,KAAK,sBAAsB;AAAA,MAC9B,MAAM;AAAA,MACN,YAAY,KAAK;AAAA,MACjB;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEA,UAAU;AACR,SAAK,eAAA;AAAA,EACP;AACF;"}
|
|
@@ -89,7 +89,8 @@ class CollectionImpl {
|
|
|
89
89
|
// Required for filtering events to only include this collection
|
|
90
90
|
lifecycle: this._lifecycle,
|
|
91
91
|
changes: this._changes,
|
|
92
|
-
indexes: this._indexes
|
|
92
|
+
indexes: this._indexes,
|
|
93
|
+
events: this._events
|
|
93
94
|
});
|
|
94
95
|
this._sync.setDeps({
|
|
95
96
|
collection: this,
|