@tanstack/db 0.5.33 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/collection/change-events.cjs.map +1 -1
- package/dist/cjs/collection/change-events.d.cts +3 -2
- package/dist/cjs/collection/changes.cjs +13 -4
- package/dist/cjs/collection/changes.cjs.map +1 -1
- package/dist/cjs/collection/changes.d.cts +10 -1
- package/dist/cjs/collection/cleanup-queue.cjs +89 -0
- package/dist/cjs/collection/cleanup-queue.cjs.map +1 -0
- package/dist/cjs/collection/cleanup-queue.d.cts +30 -0
- package/dist/cjs/collection/events.cjs +14 -0
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/events.d.cts +39 -1
- package/dist/cjs/collection/index.cjs +66 -28
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +49 -36
- package/dist/cjs/collection/indexes.cjs +211 -62
- package/dist/cjs/collection/indexes.cjs.map +1 -1
- package/dist/cjs/collection/indexes.d.cts +27 -17
- package/dist/cjs/collection/lifecycle.cjs +5 -22
- package/dist/cjs/collection/lifecycle.cjs.map +1 -1
- package/dist/cjs/collection/lifecycle.d.cts +0 -1
- package/dist/cjs/collection/mutations.cjs +18 -0
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/mutations.d.cts +1 -0
- package/dist/cjs/collection/state.cjs +381 -53
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/state.d.cts +65 -1
- package/dist/cjs/collection/subscription.cjs +6 -0
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +4 -0
- package/dist/cjs/collection/sync.cjs +108 -1
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/collection/sync.d.cts +2 -0
- package/dist/cjs/collection/transaction-metadata.cjs +5 -0
- package/dist/cjs/collection/transaction-metadata.cjs.map +1 -0
- package/dist/cjs/collection/transaction-metadata.d.cts +1 -0
- package/dist/cjs/errors.cjs +8 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +3 -0
- package/dist/cjs/index.cjs +22 -4
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +11 -3
- package/dist/cjs/indexes/auto-index.cjs +13 -6
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.cjs +0 -3
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.d.cts +2 -6
- package/dist/cjs/indexes/basic-index.cjs +361 -0
- package/dist/cjs/indexes/basic-index.cjs.map +1 -0
- package/dist/cjs/indexes/basic-index.d.cts +102 -0
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.d.cts +1 -1
- package/dist/cjs/indexes/index-options.d.cts +8 -9
- package/dist/cjs/indexes/index-registry.cjs +89 -0
- package/dist/cjs/indexes/index-registry.cjs.map +1 -0
- package/dist/cjs/indexes/index-registry.d.cts +61 -0
- package/dist/cjs/local-only.cjs +5 -0
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.cjs +27 -11
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.d.cts +25 -3
- package/dist/cjs/query/builder/index.cjs +200 -39
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/index.d.cts +4 -3
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.d.cts +14 -3
- package/dist/cjs/query/builder/types.d.cts +84 -19
- package/dist/cjs/query/compiler/evaluators.cjs +51 -0
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs +100 -28
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.d.cts +4 -2
- package/dist/cjs/query/compiler/index.cjs +283 -11
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +30 -2
- package/dist/cjs/query/compiler/order-by.cjs +29 -10
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +1 -1
- package/dist/cjs/query/compiler/select.cjs +8 -0
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/index.d.cts +2 -1
- package/dist/cjs/query/ir.cjs +18 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +21 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +501 -5
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +7 -0
- package/dist/cjs/query/live/types.d.cts +3 -3
- package/dist/cjs/query/live/utils.cjs +43 -3
- package/dist/cjs/query/live/utils.cjs.map +1 -1
- package/dist/cjs/query/live/utils.d.cts +1 -0
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +9 -6
- package/dist/cjs/query/query-once.cjs.map +1 -1
- package/dist/cjs/query/query-once.d.cts +7 -5
- package/dist/cjs/query/subset-dedupe.cjs +9 -3
- package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
- package/dist/cjs/types.d.cts +42 -8
- package/dist/cjs/utils/array-utils.cjs +27 -0
- package/dist/cjs/utils/array-utils.cjs.map +1 -0
- package/dist/cjs/utils/array-utils.d.cts +16 -0
- package/dist/cjs/utils/comparison.cjs +11 -0
- package/dist/cjs/utils/comparison.cjs.map +1 -1
- package/dist/cjs/utils/index-optimization.cjs +4 -0
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils.cjs +7 -9
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +6 -1
- package/dist/cjs/virtual-props.cjs +33 -0
- package/dist/cjs/virtual-props.cjs.map +1 -0
- package/dist/cjs/virtual-props.d.cts +196 -0
- package/dist/esm/collection/change-events.d.ts +3 -2
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/changes.d.ts +10 -1
- package/dist/esm/collection/changes.js +13 -4
- package/dist/esm/collection/changes.js.map +1 -1
- package/dist/esm/collection/cleanup-queue.d.ts +30 -0
- package/dist/esm/collection/cleanup-queue.js +89 -0
- package/dist/esm/collection/cleanup-queue.js.map +1 -0
- package/dist/esm/collection/events.d.ts +39 -1
- package/dist/esm/collection/events.js +14 -0
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.d.ts +49 -36
- package/dist/esm/collection/index.js +67 -29
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/indexes.d.ts +27 -17
- package/dist/esm/collection/indexes.js +211 -62
- package/dist/esm/collection/indexes.js.map +1 -1
- package/dist/esm/collection/lifecycle.d.ts +0 -1
- package/dist/esm/collection/lifecycle.js +5 -22
- package/dist/esm/collection/lifecycle.js.map +1 -1
- package/dist/esm/collection/mutations.d.ts +1 -0
- package/dist/esm/collection/mutations.js +18 -0
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.d.ts +65 -1
- package/dist/esm/collection/state.js +381 -53
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +4 -0
- package/dist/esm/collection/subscription.js +6 -0
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.d.ts +2 -0
- package/dist/esm/collection/sync.js +108 -1
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/collection/transaction-metadata.d.ts +1 -0
- package/dist/esm/collection/transaction-metadata.js +5 -0
- package/dist/esm/collection/transaction-metadata.js.map +1 -0
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.js +8 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +11 -3
- package/dist/esm/index.js +25 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes/auto-index.js +13 -6
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.d.ts +2 -6
- package/dist/esm/indexes/base-index.js +1 -4
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/basic-index.d.ts +102 -0
- package/dist/esm/indexes/basic-index.js +361 -0
- package/dist/esm/indexes/basic-index.js.map +1 -0
- package/dist/esm/indexes/btree-index.d.ts +1 -1
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/index-options.d.ts +8 -9
- package/dist/esm/indexes/index-registry.d.ts +61 -0
- package/dist/esm/indexes/index-registry.js +89 -0
- package/dist/esm/indexes/index-registry.js.map +1 -0
- package/dist/esm/local-only.js +5 -0
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/query/builder/functions.d.ts +25 -3
- package/dist/esm/query/builder/functions.js +27 -11
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.d.ts +4 -3
- package/dist/esm/query/builder/index.js +201 -40
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.d.ts +14 -3
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +84 -19
- package/dist/esm/query/compiler/evaluators.js +51 -0
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/group-by.d.ts +4 -2
- package/dist/esm/query/compiler/group-by.js +101 -29
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.d.ts +30 -2
- package/dist/esm/query/compiler/index.js +285 -13
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +1 -1
- package/dist/esm/query/compiler/order-by.js +30 -11
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js +8 -0
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/index.d.ts +2 -1
- package/dist/esm/query/ir.d.ts +21 -1
- package/dist/esm/query/ir.js +18 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +7 -0
- package/dist/esm/query/live/collection-config-builder.js +503 -7
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/types.d.ts +3 -3
- package/dist/esm/query/live/utils.d.ts +1 -0
- package/dist/esm/query/live/utils.js +43 -3
- package/dist/esm/query/live/utils.js.map +1 -1
- package/dist/esm/query/live-query-collection.d.ts +9 -6
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/query-once.d.ts +7 -5
- package/dist/esm/query/query-once.js.map +1 -1
- package/dist/esm/query/subset-dedupe.js +9 -3
- package/dist/esm/query/subset-dedupe.js.map +1 -1
- package/dist/esm/types.d.ts +42 -8
- package/dist/esm/utils/array-utils.d.ts +16 -0
- package/dist/esm/utils/array-utils.js +27 -0
- package/dist/esm/utils/array-utils.js.map +1 -0
- package/dist/esm/utils/comparison.js +11 -0
- package/dist/esm/utils/comparison.js.map +1 -1
- package/dist/esm/utils/index-optimization.js +4 -0
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/dist/esm/utils.d.ts +6 -1
- package/dist/esm/utils.js +7 -9
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/virtual-props.d.ts +196 -0
- package/dist/esm/virtual-props.js +33 -0
- package/dist/esm/virtual-props.js.map +1 -0
- package/package.json +2 -2
- package/skills/db-core/collection-setup/references/electric-adapter.md +1 -1
- package/src/collection/change-events.ts +13 -9
- package/src/collection/changes.ts +30 -7
- package/src/collection/cleanup-queue.ts +105 -0
- package/src/collection/events.ts +65 -0
- package/src/collection/index.ts +110 -45
- package/src/collection/indexes.ts +283 -76
- package/src/collection/lifecycle.ts +5 -26
- package/src/collection/mutations.ts +21 -0
- package/src/collection/state.ts +545 -71
- package/src/collection/subscription.ts +7 -0
- package/src/collection/sync.ts +137 -0
- package/src/collection/transaction-metadata.ts +1 -0
- package/src/errors.ts +9 -0
- package/src/index.ts +46 -3
- package/src/indexes/auto-index.ts +18 -8
- package/src/indexes/base-index.ts +2 -10
- package/src/indexes/basic-index.ts +507 -0
- package/src/indexes/btree-index.ts +1 -1
- package/src/indexes/index-options.ts +17 -37
- package/src/indexes/index-registry.ts +174 -0
- package/src/local-only.ts +7 -0
- package/src/query/builder/functions.ts +84 -7
- package/src/query/builder/index.ts +329 -9
- package/src/query/builder/ref-proxy.ts +22 -4
- package/src/query/builder/types.ts +257 -62
- package/src/query/compiler/evaluators.ts +57 -0
- package/src/query/compiler/group-by.ts +156 -35
- package/src/query/compiler/index.ts +445 -15
- package/src/query/compiler/order-by.ts +51 -12
- package/src/query/compiler/select.ts +9 -0
- package/src/query/index.ts +7 -0
- package/src/query/ir.ts +23 -2
- package/src/query/live/collection-config-builder.ts +809 -9
- package/src/query/live/types.ts +10 -4
- package/src/query/live/utils.ts +64 -3
- package/src/query/live-query-collection.ts +43 -18
- package/src/query/query-once.ts +31 -12
- package/src/query/subset-dedupe.ts +11 -7
- package/src/types.ts +49 -9
- package/src/utils/array-utils.ts +49 -0
- package/src/utils/comparison.ts +14 -0
- package/src/utils/index-optimization.ts +4 -0
- package/src/utils.ts +12 -9
- package/src/virtual-props.ts +282 -0
- package/dist/cjs/indexes/lazy-index.cjs +0 -190
- package/dist/cjs/indexes/lazy-index.cjs.map +0 -1
- package/dist/cjs/indexes/lazy-index.d.cts +0 -96
- package/dist/esm/indexes/lazy-index.d.ts +0 -96
- package/dist/esm/indexes/lazy-index.js +0 -190
- package/dist/esm/indexes/lazy-index.js.map +0 -1
- package/src/indexes/lazy-index.ts +0 -251
|
@@ -21,7 +21,7 @@ export interface RangeQueryOptions {
|
|
|
21
21
|
* B+Tree index for sorted data with range queries
|
|
22
22
|
* This maintains items in sorted order and provides efficient range operations
|
|
23
23
|
*/
|
|
24
|
-
export declare class BTreeIndex<TKey extends string | number
|
|
24
|
+
export declare class BTreeIndex<TKey extends string | number = string | number> extends BaseIndex<TKey> {
|
|
25
25
|
readonly supportedOperations: Set<"eq" | "gt" | "gte" | "lt" | "lte" | "in" | "like" | "ilike">;
|
|
26
26
|
private orderedEntries;
|
|
27
27
|
private valueMap;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"btree-index.js","sources":["../../../src/indexes/btree-index.ts"],"sourcesContent":["import { compareKeys } from '@tanstack/db-ivm'\nimport { BTree } from '../utils/btree.js'\nimport {\n defaultComparator,\n denormalizeUndefined,\n normalizeForBTree,\n} from '../utils/comparison.js'\nimport { BaseIndex } from './base-index.js'\nimport type { CompareOptions } from '../query/builder/types.js'\nimport type { BasicExpression } from '../query/ir.js'\nimport type { IndexOperation } from './base-index.js'\n\n/**\n * Options for Ordered index\n */\nexport interface BTreeIndexOptions {\n compareFn?: (a: any, b: any) => number\n compareOptions?: CompareOptions\n}\n\n/**\n * Options for range queries\n */\nexport interface RangeQueryOptions {\n from?: any\n to?: any\n fromInclusive?: boolean\n toInclusive?: boolean\n}\n\n/**\n * B+Tree index for sorted data with range queries\n * This maintains items in sorted order and provides efficient range operations\n */\nexport class BTreeIndex<\n TKey extends string | number | undefined = string | number | undefined,\n> extends BaseIndex<TKey> {\n public readonly supportedOperations = new Set<IndexOperation>([\n `eq`,\n `gt`,\n `gte`,\n `lt`,\n `lte`,\n `in`,\n ])\n\n // Internal data structures - private to hide implementation details\n // The `orderedEntries` B+ tree is used for efficient range queries\n // The `valueMap` is used for O(1) lookups of PKs by indexed value\n private orderedEntries: BTree<any, undefined> // we don't associate values with the keys of the B+ tree (the keys are indexed values)\n private valueMap = new Map<any, Set<TKey>>() // instead we store a mapping of indexed values to a set of PKs\n private indexedKeys = new Set<TKey>()\n private compareFn: (a: any, b: any) => number = defaultComparator\n\n constructor(\n id: number,\n expression: BasicExpression,\n name?: string,\n options?: any,\n ) {\n super(id, expression, name, options)\n\n // Get the base compare function\n const baseCompareFn = options?.compareFn ?? defaultComparator\n\n // Wrap it to denormalize sentinels before comparison\n // This ensures UNDEFINED_SENTINEL is converted back to undefined\n // before being passed to the baseCompareFn (which can be user-provided and is unaware of the UNDEFINED_SENTINEL)\n this.compareFn = (a: any, b: any) =>\n baseCompareFn(denormalizeUndefined(a), denormalizeUndefined(b))\n\n if (options?.compareOptions) {\n this.compareOptions = options!.compareOptions\n }\n this.orderedEntries = new BTree(this.compareFn)\n }\n\n protected initialize(_options?: BTreeIndexOptions): void {}\n\n /**\n * Adds a value to the index\n */\n add(key: TKey, item: any): void {\n let indexedValue: any\n try {\n indexedValue = this.evaluateIndexExpression(item)\n } catch (error) {\n throw new Error(\n `Failed to evaluate index expression for key ${key}: ${error}`,\n )\n }\n\n // Normalize the value for Map key usage\n const normalizedValue = normalizeForBTree(indexedValue)\n\n // Check if this value already exists\n if (this.valueMap.has(normalizedValue)) {\n // Add to existing set\n this.valueMap.get(normalizedValue)!.add(key)\n } else {\n // Create new set for this value\n const keySet = new Set<TKey>([key])\n this.valueMap.set(normalizedValue, keySet)\n this.orderedEntries.set(normalizedValue, undefined)\n }\n\n this.indexedKeys.add(key)\n this.updateTimestamp()\n }\n\n /**\n * Removes a value from the index\n */\n remove(key: TKey, item: any): void {\n let indexedValue: any\n try {\n indexedValue = this.evaluateIndexExpression(item)\n } catch (error) {\n console.warn(\n `Failed to evaluate index expression for key ${key} during removal:`,\n error,\n )\n return\n }\n\n // Normalize the value for Map key usage\n const normalizedValue = normalizeForBTree(indexedValue)\n\n if (this.valueMap.has(normalizedValue)) {\n const keySet = this.valueMap.get(normalizedValue)!\n keySet.delete(key)\n\n // If set is now empty, remove the entry entirely\n if (keySet.size === 0) {\n this.valueMap.delete(normalizedValue)\n\n // Remove from ordered entries\n this.orderedEntries.delete(normalizedValue)\n }\n }\n\n this.indexedKeys.delete(key)\n this.updateTimestamp()\n }\n\n /**\n * Updates a value in the index\n */\n update(key: TKey, oldItem: any, newItem: any): void {\n this.remove(key, oldItem)\n this.add(key, newItem)\n }\n\n /**\n * Builds the index from a collection of entries\n */\n build(entries: Iterable<[TKey, any]>): void {\n this.clear()\n\n for (const [key, item] of entries) {\n this.add(key, item)\n }\n }\n\n /**\n * Clears all data from the index\n */\n clear(): void {\n this.orderedEntries.clear()\n this.valueMap.clear()\n this.indexedKeys.clear()\n this.updateTimestamp()\n }\n\n /**\n * Performs a lookup operation\n */\n lookup(operation: IndexOperation, value: any): Set<TKey> {\n const startTime = performance.now()\n\n let result: Set<TKey>\n\n switch (operation) {\n case `eq`:\n result = this.equalityLookup(value)\n break\n case `gt`:\n result = this.rangeQuery({ from: value, fromInclusive: false })\n break\n case `gte`:\n result = this.rangeQuery({ from: value, fromInclusive: true })\n break\n case `lt`:\n result = this.rangeQuery({ to: value, toInclusive: false })\n break\n case `lte`:\n result = this.rangeQuery({ to: value, toInclusive: true })\n break\n case `in`:\n result = this.inArrayLookup(value)\n break\n default:\n throw new Error(`Operation ${operation} not supported by BTreeIndex`)\n }\n\n this.trackLookup(startTime)\n return result\n }\n\n /**\n * Gets the number of indexed keys\n */\n get keyCount(): number {\n return this.indexedKeys.size\n }\n\n // Public methods for backward compatibility (used by tests)\n\n /**\n * Performs an equality lookup\n */\n equalityLookup(value: any): Set<TKey> {\n const normalizedValue = normalizeForBTree(value)\n return new Set(this.valueMap.get(normalizedValue) ?? [])\n }\n\n /**\n * Performs a range query with options\n * This is more efficient for compound queries like \"WHERE a > 5 AND a < 10\"\n */\n rangeQuery(options: RangeQueryOptions = {}): Set<TKey> {\n const { from, to, fromInclusive = true, toInclusive = true } = options\n const result = new Set<TKey>()\n\n // Check if from/to were explicitly provided (even if undefined)\n // vs not provided at all (should use min/max key)\n const hasFrom = `from` in options\n const hasTo = `to` in options\n\n const fromKey = hasFrom\n ? normalizeForBTree(from)\n : this.orderedEntries.minKey()\n const toKey = hasTo ? normalizeForBTree(to) : this.orderedEntries.maxKey()\n\n this.orderedEntries.forRange(\n fromKey,\n toKey,\n toInclusive,\n (indexedValue, _) => {\n if (!fromInclusive && this.compareFn(indexedValue, from) === 0) {\n // the B+ tree `forRange` method does not support exclusive lower bounds\n // so we need to exclude it manually\n return\n }\n\n const keys = this.valueMap.get(indexedValue)\n if (keys) {\n keys.forEach((key) => result.add(key))\n }\n },\n )\n\n return result\n }\n\n /**\n * Performs a reversed range query\n */\n rangeQueryReversed(options: RangeQueryOptions = {}): Set<TKey> {\n const { from, to, fromInclusive = true, toInclusive = true } = options\n const hasFrom = `from` in options\n const hasTo = `to` in options\n\n // Swap from/to for reversed query, respecting explicit undefined values\n return this.rangeQuery({\n from: hasTo ? to : this.orderedEntries.maxKey(),\n to: hasFrom ? from : this.orderedEntries.minKey(),\n fromInclusive: toInclusive,\n toInclusive: fromInclusive,\n })\n }\n\n /**\n * Internal method for taking items from the index.\n * @param n - The number of items to return\n * @param nextPair - Function to get the next pair from the BTree\n * @param from - Already normalized! undefined means \"start from beginning/end\", sentinel means \"start from the key undefined\"\n * @param filterFn - Optional filter function\n * @param reversed - Whether to reverse the order of keys within each value\n */\n private takeInternal(\n n: number,\n nextPair: (k?: any) => [any, any] | undefined,\n from: any,\n filterFn?: (key: TKey) => boolean,\n reversed: boolean = false,\n ): Array<TKey> {\n const keysInResult: Set<TKey> = new Set()\n const result: Array<TKey> = []\n let pair: [any, any] | undefined\n let key = from // Use as-is - it's already normalized by the caller\n\n while ((pair = nextPair(key)) !== undefined && result.length < n) {\n key = pair[0]\n const keys = this.valueMap.get(key) as\n | Set<Exclude<TKey, undefined>>\n | undefined\n if (keys && keys.size > 0) {\n // Sort keys for deterministic order, reverse if needed\n const sorted = Array.from(keys).sort(compareKeys)\n if (reversed) sorted.reverse()\n for (const ks of sorted) {\n if (result.length >= n) break\n if (!keysInResult.has(ks) && (filterFn?.(ks) ?? true)) {\n result.push(ks)\n keysInResult.add(ks)\n }\n }\n }\n }\n\n return result\n }\n\n /**\n * Returns the next n items after the provided item.\n * @param n - The number of items to return\n * @param from - The item to start from (exclusive).\n * @returns The next n items after the provided key.\n */\n take(n: number, from: any, filterFn?: (key: TKey) => boolean): Array<TKey> {\n const nextPair = (k?: any) => this.orderedEntries.nextHigherPair(k)\n // Normalize the from value\n const normalizedFrom = normalizeForBTree(from)\n return this.takeInternal(n, nextPair, normalizedFrom, filterFn)\n }\n\n /**\n * Returns the first n items from the beginning.\n * @param n - The number of items to return\n * @param filterFn - Optional filter function\n * @returns The first n items\n */\n takeFromStart(n: number, filterFn?: (key: TKey) => boolean): Array<TKey> {\n const nextPair = (k?: any) => this.orderedEntries.nextHigherPair(k)\n // Pass undefined to mean \"start from beginning\" (BTree's native behavior)\n return this.takeInternal(n, nextPair, undefined, filterFn)\n }\n\n /**\n * Returns the next n items **before** the provided item (in descending order).\n * @param n - The number of items to return\n * @param from - The item to start from (exclusive). Required.\n * @returns The next n items **before** the provided key.\n */\n takeReversed(\n n: number,\n from: any,\n filterFn?: (key: TKey) => boolean,\n ): Array<TKey> {\n const nextPair = (k?: any) => this.orderedEntries.nextLowerPair(k)\n // Normalize the from value\n const normalizedFrom = normalizeForBTree(from)\n return this.takeInternal(n, nextPair, normalizedFrom, filterFn, true)\n }\n\n /**\n * Returns the last n items from the end.\n * @param n - The number of items to return\n * @param filterFn - Optional filter function\n * @returns The last n items\n */\n takeReversedFromEnd(\n n: number,\n filterFn?: (key: TKey) => boolean,\n ): Array<TKey> {\n const nextPair = (k?: any) => this.orderedEntries.nextLowerPair(k)\n // Pass undefined to mean \"start from end\" (BTree's native behavior)\n return this.takeInternal(n, nextPair, undefined, filterFn, true)\n }\n\n /**\n * Performs an IN array lookup\n */\n inArrayLookup(values: Array<any>): Set<TKey> {\n const result = new Set<TKey>()\n\n for (const value of values) {\n const normalizedValue = normalizeForBTree(value)\n const keys = this.valueMap.get(normalizedValue)\n if (keys) {\n keys.forEach((key) => result.add(key))\n }\n }\n\n return result\n }\n\n // Getter methods for testing compatibility\n get indexedKeysSet(): Set<TKey> {\n return this.indexedKeys\n }\n\n get orderedEntriesArray(): Array<[any, Set<TKey>]> {\n return this.orderedEntries\n .keysArray()\n .map((key) => [\n denormalizeUndefined(key),\n this.valueMap.get(key) ?? new Set(),\n ])\n }\n\n get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]> {\n return this.takeReversedFromEnd(this.orderedEntries.size).map((key) => [\n denormalizeUndefined(key),\n this.valueMap.get(key) ?? new Set(),\n ])\n }\n\n get valueMapData(): Map<any, Set<TKey>> {\n // Return a new Map with denormalized keys\n const result = new Map<any, Set<TKey>>()\n for (const [key, value] of this.valueMap) {\n result.set(denormalizeUndefined(key), value)\n }\n return result\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"btree-index.js","sources":["../../../src/indexes/btree-index.ts"],"sourcesContent":["import { compareKeys } from '@tanstack/db-ivm'\nimport { BTree } from '../utils/btree.js'\nimport {\n defaultComparator,\n denormalizeUndefined,\n normalizeForBTree,\n} from '../utils/comparison.js'\nimport { BaseIndex } from './base-index.js'\nimport type { CompareOptions } from '../query/builder/types.js'\nimport type { BasicExpression } from '../query/ir.js'\nimport type { IndexOperation } from './base-index.js'\n\n/**\n * Options for Ordered index\n */\nexport interface BTreeIndexOptions {\n compareFn?: (a: any, b: any) => number\n compareOptions?: CompareOptions\n}\n\n/**\n * Options for range queries\n */\nexport interface RangeQueryOptions {\n from?: any\n to?: any\n fromInclusive?: boolean\n toInclusive?: boolean\n}\n\n/**\n * B+Tree index for sorted data with range queries\n * This maintains items in sorted order and provides efficient range operations\n */\nexport class BTreeIndex<\n TKey extends string | number = string | number,\n> extends BaseIndex<TKey> {\n public readonly supportedOperations = new Set<IndexOperation>([\n `eq`,\n `gt`,\n `gte`,\n `lt`,\n `lte`,\n `in`,\n ])\n\n // Internal data structures - private to hide implementation details\n // The `orderedEntries` B+ tree is used for efficient range queries\n // The `valueMap` is used for O(1) lookups of PKs by indexed value\n private orderedEntries: BTree<any, undefined> // we don't associate values with the keys of the B+ tree (the keys are indexed values)\n private valueMap = new Map<any, Set<TKey>>() // instead we store a mapping of indexed values to a set of PKs\n private indexedKeys = new Set<TKey>()\n private compareFn: (a: any, b: any) => number = defaultComparator\n\n constructor(\n id: number,\n expression: BasicExpression,\n name?: string,\n options?: any,\n ) {\n super(id, expression, name, options)\n\n // Get the base compare function\n const baseCompareFn = options?.compareFn ?? defaultComparator\n\n // Wrap it to denormalize sentinels before comparison\n // This ensures UNDEFINED_SENTINEL is converted back to undefined\n // before being passed to the baseCompareFn (which can be user-provided and is unaware of the UNDEFINED_SENTINEL)\n this.compareFn = (a: any, b: any) =>\n baseCompareFn(denormalizeUndefined(a), denormalizeUndefined(b))\n\n if (options?.compareOptions) {\n this.compareOptions = options!.compareOptions\n }\n this.orderedEntries = new BTree(this.compareFn)\n }\n\n protected initialize(_options?: BTreeIndexOptions): void {}\n\n /**\n * Adds a value to the index\n */\n add(key: TKey, item: any): void {\n let indexedValue: any\n try {\n indexedValue = this.evaluateIndexExpression(item)\n } catch (error) {\n throw new Error(\n `Failed to evaluate index expression for key ${key}: ${error}`,\n )\n }\n\n // Normalize the value for Map key usage\n const normalizedValue = normalizeForBTree(indexedValue)\n\n // Check if this value already exists\n if (this.valueMap.has(normalizedValue)) {\n // Add to existing set\n this.valueMap.get(normalizedValue)!.add(key)\n } else {\n // Create new set for this value\n const keySet = new Set<TKey>([key])\n this.valueMap.set(normalizedValue, keySet)\n this.orderedEntries.set(normalizedValue, undefined)\n }\n\n this.indexedKeys.add(key)\n this.updateTimestamp()\n }\n\n /**\n * Removes a value from the index\n */\n remove(key: TKey, item: any): void {\n let indexedValue: any\n try {\n indexedValue = this.evaluateIndexExpression(item)\n } catch (error) {\n console.warn(\n `Failed to evaluate index expression for key ${key} during removal:`,\n error,\n )\n return\n }\n\n // Normalize the value for Map key usage\n const normalizedValue = normalizeForBTree(indexedValue)\n\n if (this.valueMap.has(normalizedValue)) {\n const keySet = this.valueMap.get(normalizedValue)!\n keySet.delete(key)\n\n // If set is now empty, remove the entry entirely\n if (keySet.size === 0) {\n this.valueMap.delete(normalizedValue)\n\n // Remove from ordered entries\n this.orderedEntries.delete(normalizedValue)\n }\n }\n\n this.indexedKeys.delete(key)\n this.updateTimestamp()\n }\n\n /**\n * Updates a value in the index\n */\n update(key: TKey, oldItem: any, newItem: any): void {\n this.remove(key, oldItem)\n this.add(key, newItem)\n }\n\n /**\n * Builds the index from a collection of entries\n */\n build(entries: Iterable<[TKey, any]>): void {\n this.clear()\n\n for (const [key, item] of entries) {\n this.add(key, item)\n }\n }\n\n /**\n * Clears all data from the index\n */\n clear(): void {\n this.orderedEntries.clear()\n this.valueMap.clear()\n this.indexedKeys.clear()\n this.updateTimestamp()\n }\n\n /**\n * Performs a lookup operation\n */\n lookup(operation: IndexOperation, value: any): Set<TKey> {\n const startTime = performance.now()\n\n let result: Set<TKey>\n\n switch (operation) {\n case `eq`:\n result = this.equalityLookup(value)\n break\n case `gt`:\n result = this.rangeQuery({ from: value, fromInclusive: false })\n break\n case `gte`:\n result = this.rangeQuery({ from: value, fromInclusive: true })\n break\n case `lt`:\n result = this.rangeQuery({ to: value, toInclusive: false })\n break\n case `lte`:\n result = this.rangeQuery({ to: value, toInclusive: true })\n break\n case `in`:\n result = this.inArrayLookup(value)\n break\n default:\n throw new Error(`Operation ${operation} not supported by BTreeIndex`)\n }\n\n this.trackLookup(startTime)\n return result\n }\n\n /**\n * Gets the number of indexed keys\n */\n get keyCount(): number {\n return this.indexedKeys.size\n }\n\n // Public methods for backward compatibility (used by tests)\n\n /**\n * Performs an equality lookup\n */\n equalityLookup(value: any): Set<TKey> {\n const normalizedValue = normalizeForBTree(value)\n return new Set(this.valueMap.get(normalizedValue) ?? [])\n }\n\n /**\n * Performs a range query with options\n * This is more efficient for compound queries like \"WHERE a > 5 AND a < 10\"\n */\n rangeQuery(options: RangeQueryOptions = {}): Set<TKey> {\n const { from, to, fromInclusive = true, toInclusive = true } = options\n const result = new Set<TKey>()\n\n // Check if from/to were explicitly provided (even if undefined)\n // vs not provided at all (should use min/max key)\n const hasFrom = `from` in options\n const hasTo = `to` in options\n\n const fromKey = hasFrom\n ? normalizeForBTree(from)\n : this.orderedEntries.minKey()\n const toKey = hasTo ? normalizeForBTree(to) : this.orderedEntries.maxKey()\n\n this.orderedEntries.forRange(\n fromKey,\n toKey,\n toInclusive,\n (indexedValue, _) => {\n if (!fromInclusive && this.compareFn(indexedValue, from) === 0) {\n // the B+ tree `forRange` method does not support exclusive lower bounds\n // so we need to exclude it manually\n return\n }\n\n const keys = this.valueMap.get(indexedValue)\n if (keys) {\n keys.forEach((key) => result.add(key))\n }\n },\n )\n\n return result\n }\n\n /**\n * Performs a reversed range query\n */\n rangeQueryReversed(options: RangeQueryOptions = {}): Set<TKey> {\n const { from, to, fromInclusive = true, toInclusive = true } = options\n const hasFrom = `from` in options\n const hasTo = `to` in options\n\n // Swap from/to for reversed query, respecting explicit undefined values\n return this.rangeQuery({\n from: hasTo ? to : this.orderedEntries.maxKey(),\n to: hasFrom ? from : this.orderedEntries.minKey(),\n fromInclusive: toInclusive,\n toInclusive: fromInclusive,\n })\n }\n\n /**\n * Internal method for taking items from the index.\n * @param n - The number of items to return\n * @param nextPair - Function to get the next pair from the BTree\n * @param from - Already normalized! undefined means \"start from beginning/end\", sentinel means \"start from the key undefined\"\n * @param filterFn - Optional filter function\n * @param reversed - Whether to reverse the order of keys within each value\n */\n private takeInternal(\n n: number,\n nextPair: (k?: any) => [any, any] | undefined,\n from: any,\n filterFn?: (key: TKey) => boolean,\n reversed: boolean = false,\n ): Array<TKey> {\n const keysInResult: Set<TKey> = new Set()\n const result: Array<TKey> = []\n let pair: [any, any] | undefined\n let key = from // Use as-is - it's already normalized by the caller\n\n while ((pair = nextPair(key)) !== undefined && result.length < n) {\n key = pair[0]\n const keys = this.valueMap.get(key) as\n | Set<Exclude<TKey, undefined>>\n | undefined\n if (keys && keys.size > 0) {\n // Sort keys for deterministic order, reverse if needed\n const sorted = Array.from(keys).sort(compareKeys)\n if (reversed) sorted.reverse()\n for (const ks of sorted) {\n if (result.length >= n) break\n if (!keysInResult.has(ks) && (filterFn?.(ks) ?? true)) {\n result.push(ks)\n keysInResult.add(ks)\n }\n }\n }\n }\n\n return result\n }\n\n /**\n * Returns the next n items after the provided item.\n * @param n - The number of items to return\n * @param from - The item to start from (exclusive).\n * @returns The next n items after the provided key.\n */\n take(n: number, from: any, filterFn?: (key: TKey) => boolean): Array<TKey> {\n const nextPair = (k?: any) => this.orderedEntries.nextHigherPair(k)\n // Normalize the from value\n const normalizedFrom = normalizeForBTree(from)\n return this.takeInternal(n, nextPair, normalizedFrom, filterFn)\n }\n\n /**\n * Returns the first n items from the beginning.\n * @param n - The number of items to return\n * @param filterFn - Optional filter function\n * @returns The first n items\n */\n takeFromStart(n: number, filterFn?: (key: TKey) => boolean): Array<TKey> {\n const nextPair = (k?: any) => this.orderedEntries.nextHigherPair(k)\n // Pass undefined to mean \"start from beginning\" (BTree's native behavior)\n return this.takeInternal(n, nextPair, undefined, filterFn)\n }\n\n /**\n * Returns the next n items **before** the provided item (in descending order).\n * @param n - The number of items to return\n * @param from - The item to start from (exclusive). Required.\n * @returns The next n items **before** the provided key.\n */\n takeReversed(\n n: number,\n from: any,\n filterFn?: (key: TKey) => boolean,\n ): Array<TKey> {\n const nextPair = (k?: any) => this.orderedEntries.nextLowerPair(k)\n // Normalize the from value\n const normalizedFrom = normalizeForBTree(from)\n return this.takeInternal(n, nextPair, normalizedFrom, filterFn, true)\n }\n\n /**\n * Returns the last n items from the end.\n * @param n - The number of items to return\n * @param filterFn - Optional filter function\n * @returns The last n items\n */\n takeReversedFromEnd(\n n: number,\n filterFn?: (key: TKey) => boolean,\n ): Array<TKey> {\n const nextPair = (k?: any) => this.orderedEntries.nextLowerPair(k)\n // Pass undefined to mean \"start from end\" (BTree's native behavior)\n return this.takeInternal(n, nextPair, undefined, filterFn, true)\n }\n\n /**\n * Performs an IN array lookup\n */\n inArrayLookup(values: Array<any>): Set<TKey> {\n const result = new Set<TKey>()\n\n for (const value of values) {\n const normalizedValue = normalizeForBTree(value)\n const keys = this.valueMap.get(normalizedValue)\n if (keys) {\n keys.forEach((key) => result.add(key))\n }\n }\n\n return result\n }\n\n // Getter methods for testing compatibility\n get indexedKeysSet(): Set<TKey> {\n return this.indexedKeys\n }\n\n get orderedEntriesArray(): Array<[any, Set<TKey>]> {\n return this.orderedEntries\n .keysArray()\n .map((key) => [\n denormalizeUndefined(key),\n this.valueMap.get(key) ?? new Set(),\n ])\n }\n\n get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]> {\n return this.takeReversedFromEnd(this.orderedEntries.size).map((key) => [\n denormalizeUndefined(key),\n this.valueMap.get(key) ?? new Set(),\n ])\n }\n\n get valueMapData(): Map<any, Set<TKey>> {\n // Return a new Map with denormalized keys\n const result = new Map<any, Set<TKey>>()\n for (const [key, value] of this.valueMap) {\n result.set(denormalizeUndefined(key), value)\n }\n return result\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { IndexConstructor
|
|
1
|
+
import { IndexConstructor } from './base-index.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Options for creating an index
|
|
4
4
|
*/
|
|
5
|
-
export interface IndexOptions<
|
|
5
|
+
export interface IndexOptions<TIndexType extends IndexConstructor = IndexConstructor> {
|
|
6
|
+
/** Optional name for the index */
|
|
6
7
|
name?: string;
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
/** Index type to use (e.g., BasicIndex, BTreeIndex) */
|
|
9
|
+
indexType?: TIndexType;
|
|
10
|
+
/** Options passed to the index constructor */
|
|
11
|
+
options?: TIndexType extends new (id: number, expr: any, name?: string, options?: infer O) => any ? O : never;
|
|
9
12
|
}
|
|
10
|
-
/**
|
|
11
|
-
* Utility type to extract the constructed index type from a resolver
|
|
12
|
-
*/
|
|
13
|
-
export type ResolvedIndexType<TResolver extends IndexResolver> = TResolver extends IndexConstructor<any> ? InstanceType<TResolver> : TResolver extends () => Promise<IndexConstructor<any>> ? TResolver extends () => Promise<infer TCtor> ? TCtor extends IndexConstructor<any> ? InstanceType<TCtor> : never : never : never;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index Dev Mode - Helps developers identify when indexes would improve performance
|
|
3
|
+
*
|
|
4
|
+
* Dev mode suggestions are ON by default in non-production builds.
|
|
5
|
+
*/
|
|
6
|
+
export interface IndexDevModeConfig {
|
|
7
|
+
/** Enable dev mode index suggestions */
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
/** Suggest indexes when collection has more than this many items */
|
|
10
|
+
collectionSizeThreshold: number;
|
|
11
|
+
/** Suggest indexes when queries take longer than this (ms) */
|
|
12
|
+
slowQueryThresholdMs: number;
|
|
13
|
+
/** Custom handler for index suggestions */
|
|
14
|
+
onSuggestion: ((suggestion: IndexSuggestion) => void) | null;
|
|
15
|
+
}
|
|
16
|
+
export interface IndexSuggestion {
|
|
17
|
+
type: `collection-size` | `slow-query` | `frequent-field`;
|
|
18
|
+
collectionId: string;
|
|
19
|
+
fieldPath: Array<string>;
|
|
20
|
+
message: string;
|
|
21
|
+
collectionSize?: number;
|
|
22
|
+
queryTimeMs?: number;
|
|
23
|
+
queryCount?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Configure dev mode for index suggestions
|
|
27
|
+
*/
|
|
28
|
+
export declare function configureIndexDevMode(config: Partial<IndexDevModeConfig>): void;
|
|
29
|
+
/**
|
|
30
|
+
* Get current dev mode configuration
|
|
31
|
+
*/
|
|
32
|
+
export declare function getIndexDevModeConfig(): IndexDevModeConfig;
|
|
33
|
+
/**
|
|
34
|
+
* Check if dev mode is enabled
|
|
35
|
+
*/
|
|
36
|
+
export declare function isDevModeEnabled(): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Emit an index suggestion (dev mode only)
|
|
39
|
+
*/
|
|
40
|
+
export declare function emitIndexSuggestion(suggestion: IndexSuggestion): void;
|
|
41
|
+
/**
|
|
42
|
+
* Track a query for dev mode analysis
|
|
43
|
+
*/
|
|
44
|
+
export declare function trackQuery(collectionId: string, fieldPath: Array<string>, executionTimeMs: number): void;
|
|
45
|
+
/**
|
|
46
|
+
* Check collection size and suggest index if needed (dev mode)
|
|
47
|
+
*/
|
|
48
|
+
export declare function checkCollectionSizeForIndex(collectionId: string, collectionSize: number, fieldPath: Array<string>): void;
|
|
49
|
+
/**
|
|
50
|
+
* Clear query pattern tracking (useful for tests)
|
|
51
|
+
*/
|
|
52
|
+
export declare function clearQueryPatterns(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Get query patterns (useful for debugging/testing)
|
|
55
|
+
*/
|
|
56
|
+
export declare function getQueryPatterns(): Map<string, {
|
|
57
|
+
fieldPath: Array<string>;
|
|
58
|
+
queryCount: number;
|
|
59
|
+
totalTimeMs: number;
|
|
60
|
+
avgTimeMs: number;
|
|
61
|
+
}>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
let devModeConfig = {
|
|
2
|
+
enabled: true,
|
|
3
|
+
collectionSizeThreshold: 1e3,
|
|
4
|
+
slowQueryThresholdMs: 10,
|
|
5
|
+
onSuggestion: null
|
|
6
|
+
};
|
|
7
|
+
const queryPatterns = /* @__PURE__ */ new Map();
|
|
8
|
+
function configureIndexDevMode(config) {
|
|
9
|
+
devModeConfig = { ...devModeConfig, ...config };
|
|
10
|
+
}
|
|
11
|
+
function getIndexDevModeConfig() {
|
|
12
|
+
return devModeConfig;
|
|
13
|
+
}
|
|
14
|
+
function isDevModeEnabled() {
|
|
15
|
+
return devModeConfig.enabled && process.env.NODE_ENV !== `production`;
|
|
16
|
+
}
|
|
17
|
+
function emitIndexSuggestion(suggestion) {
|
|
18
|
+
if (!isDevModeEnabled()) return;
|
|
19
|
+
if (devModeConfig.onSuggestion) {
|
|
20
|
+
try {
|
|
21
|
+
devModeConfig.onSuggestion(suggestion);
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
console.warn(
|
|
26
|
+
`[TanStack DB] Index suggestion for "${suggestion.collectionId}":
|
|
27
|
+
${suggestion.message}
|
|
28
|
+
Field: ${suggestion.fieldPath.join(`.`)}
|
|
29
|
+
Add index: collection.createIndex((row) => row.${suggestion.fieldPath.join(`.`)})`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function trackQuery(collectionId, fieldPath, executionTimeMs) {
|
|
34
|
+
if (!isDevModeEnabled()) return;
|
|
35
|
+
const key = `${collectionId}:${fieldPath.join(`.`)}`;
|
|
36
|
+
const existing = queryPatterns.get(key);
|
|
37
|
+
if (existing) {
|
|
38
|
+
existing.queryCount++;
|
|
39
|
+
existing.totalTimeMs += executionTimeMs;
|
|
40
|
+
existing.avgTimeMs = existing.totalTimeMs / existing.queryCount;
|
|
41
|
+
} else {
|
|
42
|
+
queryPatterns.set(key, {
|
|
43
|
+
fieldPath,
|
|
44
|
+
queryCount: 1,
|
|
45
|
+
totalTimeMs: executionTimeMs,
|
|
46
|
+
avgTimeMs: executionTimeMs
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const pattern = queryPatterns.get(key);
|
|
50
|
+
if (pattern.avgTimeMs > devModeConfig.slowQueryThresholdMs) {
|
|
51
|
+
emitIndexSuggestion({
|
|
52
|
+
type: `slow-query`,
|
|
53
|
+
collectionId,
|
|
54
|
+
fieldPath,
|
|
55
|
+
message: `Queries on "${fieldPath.join(`.`)}" are slow (avg ${pattern.avgTimeMs.toFixed(1)}ms). Consider adding an index.`,
|
|
56
|
+
queryTimeMs: pattern.avgTimeMs,
|
|
57
|
+
queryCount: pattern.queryCount
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function checkCollectionSizeForIndex(collectionId, collectionSize, fieldPath) {
|
|
62
|
+
if (!isDevModeEnabled()) return;
|
|
63
|
+
if (collectionSize > devModeConfig.collectionSizeThreshold) {
|
|
64
|
+
emitIndexSuggestion({
|
|
65
|
+
type: `collection-size`,
|
|
66
|
+
collectionId,
|
|
67
|
+
fieldPath,
|
|
68
|
+
message: `Collection has ${collectionSize} items. Queries on "${fieldPath.join(`.`)}" may benefit from an index.`,
|
|
69
|
+
collectionSize
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function clearQueryPatterns() {
|
|
74
|
+
queryPatterns.clear();
|
|
75
|
+
}
|
|
76
|
+
function getQueryPatterns() {
|
|
77
|
+
return new Map(queryPatterns);
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
checkCollectionSizeForIndex,
|
|
81
|
+
clearQueryPatterns,
|
|
82
|
+
configureIndexDevMode,
|
|
83
|
+
emitIndexSuggestion,
|
|
84
|
+
getIndexDevModeConfig,
|
|
85
|
+
getQueryPatterns,
|
|
86
|
+
isDevModeEnabled,
|
|
87
|
+
trackQuery
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=index-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-registry.js","sources":["../../../src/indexes/index-registry.ts"],"sourcesContent":["/**\n * Index Dev Mode - Helps developers identify when indexes would improve performance\n *\n * Dev mode suggestions are ON by default in non-production builds.\n */\n\n// Dev mode detection settings - ON by default in non-production\nlet devModeConfig: IndexDevModeConfig = {\n enabled: true,\n collectionSizeThreshold: 1000,\n slowQueryThresholdMs: 10,\n onSuggestion: null,\n}\n\nexport interface IndexDevModeConfig {\n /** Enable dev mode index suggestions */\n enabled: boolean\n /** Suggest indexes when collection has more than this many items */\n collectionSizeThreshold: number\n /** Suggest indexes when queries take longer than this (ms) */\n slowQueryThresholdMs: number\n /** Custom handler for index suggestions */\n onSuggestion: ((suggestion: IndexSuggestion) => void) | null\n}\n\nexport interface IndexSuggestion {\n type: `collection-size` | `slow-query` | `frequent-field`\n collectionId: string\n fieldPath: Array<string>\n message: string\n collectionSize?: number\n queryTimeMs?: number\n queryCount?: number\n}\n\n// Track query patterns for dev mode\nconst queryPatterns = new Map<\n string,\n {\n fieldPath: Array<string>\n queryCount: number\n totalTimeMs: number\n avgTimeMs: number\n }\n>()\n\n/**\n * Configure dev mode for index suggestions\n */\nexport function configureIndexDevMode(\n config: Partial<IndexDevModeConfig>,\n): void {\n devModeConfig = { ...devModeConfig, ...config }\n}\n\n/**\n * Get current dev mode configuration\n */\nexport function getIndexDevModeConfig(): IndexDevModeConfig {\n return devModeConfig\n}\n\n/**\n * Check if dev mode is enabled\n */\nexport function isDevModeEnabled(): boolean {\n return devModeConfig.enabled && process.env.NODE_ENV !== `production`\n}\n\n/**\n * Emit an index suggestion (dev mode only)\n */\nexport function emitIndexSuggestion(suggestion: IndexSuggestion): void {\n if (!isDevModeEnabled()) return\n\n if (devModeConfig.onSuggestion) {\n try {\n devModeConfig.onSuggestion(suggestion)\n } catch {\n // Don't let a buggy callback crash query execution\n }\n } else {\n // Default: log to console with helpful formatting\n console.warn(\n `[TanStack DB] Index suggestion for \"${suggestion.collectionId}\":\\n` +\n ` ${suggestion.message}\\n` +\n ` Field: ${suggestion.fieldPath.join(`.`)}\\n` +\n ` Add index: collection.createIndex((row) => row.${suggestion.fieldPath.join(`.`)})`,\n )\n }\n}\n\n/**\n * Track a query for dev mode analysis\n */\nexport function trackQuery(\n collectionId: string,\n fieldPath: Array<string>,\n executionTimeMs: number,\n): void {\n if (!isDevModeEnabled()) return\n\n const key = `${collectionId}:${fieldPath.join(`.`)}`\n const existing = queryPatterns.get(key)\n\n if (existing) {\n existing.queryCount++\n existing.totalTimeMs += executionTimeMs\n existing.avgTimeMs = existing.totalTimeMs / existing.queryCount\n } else {\n queryPatterns.set(key, {\n fieldPath,\n queryCount: 1,\n totalTimeMs: executionTimeMs,\n avgTimeMs: executionTimeMs,\n })\n }\n\n // Check if we should suggest an index\n const pattern = queryPatterns.get(key)!\n if (pattern.avgTimeMs > devModeConfig.slowQueryThresholdMs) {\n emitIndexSuggestion({\n type: `slow-query`,\n collectionId,\n fieldPath,\n message: `Queries on \"${fieldPath.join(`.`)}\" are slow (avg ${pattern.avgTimeMs.toFixed(1)}ms). Consider adding an index.`,\n queryTimeMs: pattern.avgTimeMs,\n queryCount: pattern.queryCount,\n })\n }\n}\n\n/**\n * Check collection size and suggest index if needed (dev mode)\n */\nexport function checkCollectionSizeForIndex(\n collectionId: string,\n collectionSize: number,\n fieldPath: Array<string>,\n): void {\n if (!isDevModeEnabled()) return\n\n if (collectionSize > devModeConfig.collectionSizeThreshold) {\n emitIndexSuggestion({\n type: `collection-size`,\n collectionId,\n fieldPath,\n message: `Collection has ${collectionSize} items. Queries on \"${fieldPath.join(`.`)}\" may benefit from an index.`,\n collectionSize,\n })\n }\n}\n\n/**\n * Clear query pattern tracking (useful for tests)\n */\nexport function clearQueryPatterns(): void {\n queryPatterns.clear()\n}\n\n/**\n * Get query patterns (useful for debugging/testing)\n */\nexport function getQueryPatterns(): Map<\n string,\n {\n fieldPath: Array<string>\n queryCount: number\n totalTimeMs: number\n avgTimeMs: number\n }\n> {\n return new Map(queryPatterns)\n}\n"],"names":[],"mappings":"AAOA,IAAI,gBAAoC;AAAA,EACtC,SAAS;AAAA,EACT,yBAAyB;AAAA,EACzB,sBAAsB;AAAA,EACtB,cAAc;AAChB;AAwBA,MAAM,oCAAoB,IAAA;AAanB,SAAS,sBACd,QACM;AACN,kBAAgB,EAAE,GAAG,eAAe,GAAG,OAAA;AACzC;AAKO,SAAS,wBAA4C;AAC1D,SAAO;AACT;AAKO,SAAS,mBAA4B;AAC1C,SAAO,cAAc,WAAW,QAAQ,IAAI,aAAa;AAC3D;AAKO,SAAS,oBAAoB,YAAmC;AACrE,MAAI,CAAC,mBAAoB;AAEzB,MAAI,cAAc,cAAc;AAC9B,QAAI;AACF,oBAAc,aAAa,UAAU;AAAA,IACvC,QAAQ;AAAA,IAER;AAAA,EACF,OAAO;AAEL,YAAQ;AAAA,MACN,uCAAuC,WAAW,YAAY;AAAA,IACvD,WAAW,OAAO;AAAA,WACX,WAAW,UAAU,KAAK,GAAG,CAAC;AAAA,mDACU,WAAW,UAAU,KAAK,GAAG,CAAC;AAAA,IAAA;AAAA,EAExF;AACF;AAKO,SAAS,WACd,cACA,WACA,iBACM;AACN,MAAI,CAAC,mBAAoB;AAEzB,QAAM,MAAM,GAAG,YAAY,IAAI,UAAU,KAAK,GAAG,CAAC;AAClD,QAAM,WAAW,cAAc,IAAI,GAAG;AAEtC,MAAI,UAAU;AACZ,aAAS;AACT,aAAS,eAAe;AACxB,aAAS,YAAY,SAAS,cAAc,SAAS;AAAA,EACvD,OAAO;AACL,kBAAc,IAAI,KAAK;AAAA,MACrB;AAAA,MACA,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,WAAW;AAAA,IAAA,CACZ;AAAA,EACH;AAGA,QAAM,UAAU,cAAc,IAAI,GAAG;AACrC,MAAI,QAAQ,YAAY,cAAc,sBAAsB;AAC1D,wBAAoB;AAAA,MAClB,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,SAAS,eAAe,UAAU,KAAK,GAAG,CAAC,mBAAmB,QAAQ,UAAU,QAAQ,CAAC,CAAC;AAAA,MAC1F,aAAa,QAAQ;AAAA,MACrB,YAAY,QAAQ;AAAA,IAAA,CACrB;AAAA,EACH;AACF;AAKO,SAAS,4BACd,cACA,gBACA,WACM;AACN,MAAI,CAAC,mBAAoB;AAEzB,MAAI,iBAAiB,cAAc,yBAAyB;AAC1D,wBAAoB;AAAA,MAClB,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,SAAS,kBAAkB,cAAc,uBAAuB,UAAU,KAAK,GAAG,CAAC;AAAA,MACnF;AAAA,IAAA,CACD;AAAA,EACH;AACF;AAKO,SAAS,qBAA2B;AACzC,gBAAc,MAAA;AAChB;AAKO,SAAS,mBAQd;AACA,SAAO,IAAI,IAAI,aAAa;AAC9B;"}
|
package/dist/esm/local-only.js
CHANGED
|
@@ -68,7 +68,12 @@ function createLocalOnlySync(initialData) {
|
|
|
68
68
|
syncWrite = write;
|
|
69
69
|
syncCommit = commit;
|
|
70
70
|
collection = params.collection;
|
|
71
|
+
params.collection._state.isLocalOnly = true;
|
|
71
72
|
if (initialData && initialData.length > 0) {
|
|
73
|
+
for (const item of initialData) {
|
|
74
|
+
const key = params.collection.getKeyFromItem(item);
|
|
75
|
+
params.collection._state.pendingLocalChanges.add(key);
|
|
76
|
+
}
|
|
72
77
|
begin();
|
|
73
78
|
initialData.forEach((item) => {
|
|
74
79
|
write({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-only.js","sources":["../../src/local-only.ts"],"sourcesContent":["import type {\n BaseCollectionConfig,\n CollectionConfig,\n DeleteMutationFnParams,\n InferSchemaOutput,\n InsertMutationFnParams,\n OperationType,\n PendingMutation,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from './types'\nimport type { Collection } from './collection/index'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\n\n/**\n * Configuration interface for Local-only collection options\n * @template T - The type of items in the collection\n * @template TSchema - The schema type for validation\n * @template TKey - The type of the key returned by `getKey`\n */\nexport interface LocalOnlyCollectionConfig<\n T extends object = object,\n TSchema extends StandardSchemaV1 = never,\n TKey extends string | number = string | number,\n> extends Omit<\n BaseCollectionConfig<T, TKey, TSchema, LocalOnlyCollectionUtils>,\n `gcTime` | `startSync`\n> {\n /**\n * Optional initial data to populate the collection with on creation\n * This data will be applied during the initial sync process\n */\n initialData?: Array<T>\n}\n\n/**\n * Local-only collection utilities type\n */\nexport interface LocalOnlyCollectionUtils extends UtilsRecord {\n /**\n * Accepts mutations from a transaction that belong to this collection and persists them.\n * This should be called in your transaction's mutationFn to persist local-only data.\n *\n * @param transaction - The transaction containing mutations to accept\n * @example\n * const localData = createCollection(localOnlyCollectionOptions({...}))\n *\n * const tx = createTransaction({\n * mutationFn: async ({ transaction }) => {\n * // Make API call first\n * await api.save(...)\n * // Then persist local-only mutations after success\n * localData.utils.acceptMutations(transaction)\n * }\n * })\n */\n acceptMutations: (transaction: {\n mutations: Array<PendingMutation<Record<string, unknown>>>\n }) => void\n}\n\ntype LocalOnlyCollectionOptionsResult<\n T extends object,\n TKey extends string | number,\n TSchema extends StandardSchemaV1 | never = never,\n> = CollectionConfig<T, TKey, TSchema, LocalOnlyCollectionUtils> & {\n utils: LocalOnlyCollectionUtils\n}\n\n/**\n * Creates Local-only collection options for use with a standard Collection\n *\n * This is an in-memory collection that doesn't sync with external sources but uses a loopback sync config\n * that immediately \"syncs\" all optimistic changes to the collection, making them permanent.\n * Perfect for local-only data that doesn't need persistence or external synchronization.\n *\n * **Using with Manual Transactions:**\n *\n * For manual transactions, you must call `utils.acceptMutations()` in your transaction's `mutationFn`\n * to persist changes made during `tx.mutate()`. This is necessary because local-only collections\n * don't participate in the standard mutation handler flow for manual transactions.\n *\n * @template T - The schema type if a schema is provided, otherwise the type of items in the collection\n * @template TKey - The type of the key returned by getKey\n * @param config - Configuration options for the Local-only collection\n * @returns Collection options with utilities including acceptMutations\n *\n * @example\n * // Basic local-only collection\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // Local-only collection with initial data\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * initialData: [\n * { id: 1, name: 'Item 1' },\n * { id: 2, name: 'Item 2' },\n * ],\n * })\n * )\n *\n * @example\n * // Local-only collection with mutation handlers\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * onInsert: async ({ transaction }) => {\n * console.log('Item inserted:', transaction.mutations[0].modified)\n * // Custom logic after insert\n * },\n * })\n * )\n *\n * @example\n * // Using with manual transactions\n * const localData = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * })\n * )\n *\n * const tx = createTransaction({\n * mutationFn: async ({ transaction }) => {\n * // Use local data in API call\n * const localMutations = transaction.mutations.filter(m => m.collection === localData)\n * await api.save({ metadata: localMutations[0]?.modified })\n *\n * // Persist local-only mutations after API success\n * localData.utils.acceptMutations(transaction)\n * }\n * })\n *\n * tx.mutate(() => {\n * localData.insert({ id: 1, data: 'metadata' })\n * apiCollection.insert({ id: 2, data: 'main data' })\n * })\n *\n * await tx.commit()\n */\n\n// Overload for when schema is provided\nexport function localOnlyCollectionOptions<\n T extends StandardSchemaV1,\n TKey extends string | number = string | number,\n>(\n config: LocalOnlyCollectionConfig<InferSchemaOutput<T>, T, TKey> & {\n schema: T\n },\n): LocalOnlyCollectionOptionsResult<InferSchemaOutput<T>, TKey, T> & {\n schema: T\n}\n\n// Overload for when no schema is provided\n// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config\nexport function localOnlyCollectionOptions<\n T extends object,\n TKey extends string | number = string | number,\n>(\n config: LocalOnlyCollectionConfig<T, never, TKey> & {\n schema?: never // prohibit schema\n },\n): LocalOnlyCollectionOptionsResult<T, TKey> & {\n schema?: never // no schema in the result\n}\n\nexport function localOnlyCollectionOptions<\n T extends object = object,\n TSchema extends StandardSchemaV1 = never,\n TKey extends string | number = string | number,\n>(\n config: LocalOnlyCollectionConfig<T, TSchema, TKey>,\n): LocalOnlyCollectionOptionsResult<T, TKey, TSchema> & {\n schema?: StandardSchemaV1\n} {\n const { initialData, onInsert, onUpdate, onDelete, id, ...restConfig } =\n config\n\n const collectionId = id ?? crypto.randomUUID()\n\n // Create the sync configuration with transaction confirmation capability\n const syncResult = createLocalOnlySync<T, TKey>(initialData)\n\n /**\n * Create wrapper handlers that call user handlers first, then confirm transactions\n * Wraps the user's onInsert handler to also confirm the transaction immediately\n */\n const wrappedOnInsert = async (\n params: InsertMutationFnParams<T, TKey, LocalOnlyCollectionUtils>,\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onInsert) {\n handlerResult = (await onInsert(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n /**\n * Wrapper for onUpdate handler that also confirms the transaction immediately\n */\n const wrappedOnUpdate = async (\n params: UpdateMutationFnParams<T, TKey, LocalOnlyCollectionUtils>,\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onUpdate) {\n handlerResult = (await onUpdate(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n /**\n * Wrapper for onDelete handler that also confirms the transaction immediately\n */\n const wrappedOnDelete = async (\n params: DeleteMutationFnParams<T, TKey, LocalOnlyCollectionUtils>,\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onDelete) {\n handlerResult = (await onDelete(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n /**\n * Accepts mutations from a transaction that belong to this collection and persists them\n */\n const acceptMutations = (transaction: {\n mutations: Array<PendingMutation<Record<string, unknown>>>\n }) => {\n // Filter mutations that belong to this collection\n const collectionMutations = transaction.mutations.filter(\n (m) => m.collection.id === collectionId,\n )\n\n if (collectionMutations.length === 0) {\n return\n }\n\n // Persist the mutations through sync\n syncResult.confirmOperationsSync(\n collectionMutations as Array<PendingMutation<T>>,\n )\n }\n\n return {\n ...restConfig,\n id: collectionId,\n sync: syncResult.sync,\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n acceptMutations,\n },\n startSync: true,\n gcTime: 0,\n } as LocalOnlyCollectionOptionsResult<T, TKey, TSchema> & {\n schema?: StandardSchemaV1\n }\n}\n\n/**\n * Internal function to create Local-only sync configuration with transaction confirmation\n *\n * This captures the sync functions and provides synchronous confirmation of operations.\n * It creates a loopback sync that immediately confirms all optimistic operations,\n * making them permanent in the collection.\n *\n * @param initialData - Optional array of initial items to populate the collection\n * @returns Object with sync configuration and confirmOperationsSync function\n */\nfunction createLocalOnlySync<T extends object, TKey extends string | number>(\n initialData?: Array<T>,\n) {\n // Capture sync functions and collection for transaction confirmation\n let syncBegin: (() => void) | null = null\n let syncWrite: ((message: { type: OperationType; value: T }) => void) | null =\n null\n let syncCommit: (() => void) | null = null\n let collection: Collection<T, TKey, LocalOnlyCollectionUtils> | null = null\n\n const sync: SyncConfig<T, TKey> = {\n /**\n * Sync function that captures sync parameters and applies initial data\n * @param params - Sync parameters containing begin, write, and commit functions\n * @returns Unsubscribe function (empty since no ongoing sync is needed)\n */\n sync: (params) => {\n const { begin, write, commit, markReady } = params\n\n // Capture sync functions and collection for later use\n syncBegin = begin\n syncWrite = write\n syncCommit = commit\n collection = params.collection\n\n // Apply initial data if provided\n if (initialData && initialData.length > 0) {\n begin()\n initialData.forEach((item) => {\n write({\n type: `insert`,\n value: item,\n })\n })\n commit()\n }\n\n // Mark collection as ready since local-only collections are immediately ready\n markReady()\n\n // Return empty unsubscribe function - no ongoing sync needed\n return () => {}\n },\n /**\n * Get sync metadata - returns empty object for local-only collections\n * @returns Empty metadata object\n */\n getSyncMetadata: () => ({}),\n }\n\n /**\n * Synchronously confirms optimistic operations by immediately writing through sync\n *\n * This loops through transaction mutations and applies them to move from optimistic to synced state.\n * It's called after user handlers to make optimistic changes permanent.\n *\n * @param mutations - Array of mutation objects from the transaction\n */\n const confirmOperationsSync = (mutations: Array<PendingMutation<T>>) => {\n if (!syncBegin || !syncWrite || !syncCommit) {\n return // Sync not initialized yet, which is fine\n }\n\n // Immediately write back through sync interface\n syncBegin()\n mutations.forEach((mutation) => {\n if (syncWrite) {\n syncWrite({\n type: mutation.type,\n value: mutation.modified,\n })\n }\n })\n syncCommit()\n }\n\n return {\n sync,\n confirmOperationsSync,\n collection,\n }\n}\n"],"names":[],"mappings":"AA4KO,SAAS,2BAKd,QAGA;AACA,QAAM,EAAE,aAAa,UAAU,UAAU,UAAU,IAAI,GAAG,eACxD;AAEF,QAAM,eAAe,MAAM,OAAO,WAAA;AAGlC,QAAM,aAAa,oBAA6B,WAAW;AAM3D,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,CAAC,gBAEnB;AAEJ,UAAM,sBAAsB,YAAY,UAAU;AAAA,MAChD,CAAC,MAAM,EAAE,WAAW,OAAO;AAAA,IAAA;AAG7B,QAAI,oBAAoB,WAAW,GAAG;AACpC;AAAA,IACF;AAGA,eAAW;AAAA,MACT;AAAA,IAAA;AAAA,EAEJ;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,IAAI;AAAA,IACJ,MAAM,WAAW;AAAA,IACjB,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,OAAO;AAAA,MACL;AAAA,IAAA;AAAA,IAEF,WAAW;AAAA,IACX,QAAQ;AAAA,EAAA;AAIZ;AAYA,SAAS,oBACP,aACA;AAEA,MAAI,YAAiC;AACrC,MAAI,YACF;AACF,MAAI,aAAkC;AACtC,MAAI,aAAmE;AAEvE,QAAM,OAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMhC,MAAM,CAAC,WAAW;AAChB,YAAM,EAAE,OAAO,OAAO,QAAQ,cAAc;AAG5C,kBAAY;AACZ,kBAAY;AACZ,mBAAa;AACb,mBAAa,OAAO;AAGpB,UAAI,eAAe,YAAY,SAAS,GAAG;AACzC,cAAA;AACA,oBAAY,QAAQ,CAAC,SAAS;AAC5B,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,OAAO;AAAA,UAAA,CACR;AAAA,QACH,CAAC;AACD,eAAA;AAAA,MACF;AAGA,gBAAA;AAGA,aAAO,MAAM;AAAA,MAAC;AAAA,IAChB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,iBAAiB,OAAO,CAAA;AAAA,EAAC;AAW3B,QAAM,wBAAwB,CAAC,cAAyC;AACtE,QAAI,CAAC,aAAa,CAAC,aAAa,CAAC,YAAY;AAC3C;AAAA,IACF;AAGA,cAAA;AACA,cAAU,QAAQ,CAAC,aAAa;AAC9B,UAAI,WAAW;AACb,kBAAU;AAAA,UACR,MAAM,SAAS;AAAA,UACf,OAAO,SAAS;AAAA,QAAA,CACjB;AAAA,MACH;AAAA,IACF,CAAC;AACD,eAAA;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|
|
1
|
+
{"version":3,"file":"local-only.js","sources":["../../src/local-only.ts"],"sourcesContent":["import type {\n BaseCollectionConfig,\n CollectionConfig,\n DeleteMutationFnParams,\n InferSchemaOutput,\n InsertMutationFnParams,\n OperationType,\n PendingMutation,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from './types'\nimport type { Collection } from './collection/index'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\n\n/**\n * Configuration interface for Local-only collection options\n * @template T - The type of items in the collection\n * @template TSchema - The schema type for validation\n * @template TKey - The type of the key returned by `getKey`\n */\nexport interface LocalOnlyCollectionConfig<\n T extends object = object,\n TSchema extends StandardSchemaV1 = never,\n TKey extends string | number = string | number,\n> extends Omit<\n BaseCollectionConfig<T, TKey, TSchema, LocalOnlyCollectionUtils>,\n `gcTime` | `startSync`\n> {\n /**\n * Optional initial data to populate the collection with on creation\n * This data will be applied during the initial sync process\n */\n initialData?: Array<T>\n}\n\n/**\n * Local-only collection utilities type\n */\nexport interface LocalOnlyCollectionUtils extends UtilsRecord {\n /**\n * Accepts mutations from a transaction that belong to this collection and persists them.\n * This should be called in your transaction's mutationFn to persist local-only data.\n *\n * @param transaction - The transaction containing mutations to accept\n * @example\n * const localData = createCollection(localOnlyCollectionOptions({...}))\n *\n * const tx = createTransaction({\n * mutationFn: async ({ transaction }) => {\n * // Make API call first\n * await api.save(...)\n * // Then persist local-only mutations after success\n * localData.utils.acceptMutations(transaction)\n * }\n * })\n */\n acceptMutations: (transaction: {\n mutations: Array<PendingMutation<Record<string, unknown>>>\n }) => void\n}\n\ntype LocalOnlyCollectionOptionsResult<\n T extends object,\n TKey extends string | number,\n TSchema extends StandardSchemaV1 | never = never,\n> = CollectionConfig<T, TKey, TSchema, LocalOnlyCollectionUtils> & {\n utils: LocalOnlyCollectionUtils\n}\n\n/**\n * Creates Local-only collection options for use with a standard Collection\n *\n * This is an in-memory collection that doesn't sync with external sources but uses a loopback sync config\n * that immediately \"syncs\" all optimistic changes to the collection, making them permanent.\n * Perfect for local-only data that doesn't need persistence or external synchronization.\n *\n * **Using with Manual Transactions:**\n *\n * For manual transactions, you must call `utils.acceptMutations()` in your transaction's `mutationFn`\n * to persist changes made during `tx.mutate()`. This is necessary because local-only collections\n * don't participate in the standard mutation handler flow for manual transactions.\n *\n * @template T - The schema type if a schema is provided, otherwise the type of items in the collection\n * @template TKey - The type of the key returned by getKey\n * @param config - Configuration options for the Local-only collection\n * @returns Collection options with utilities including acceptMutations\n *\n * @example\n * // Basic local-only collection\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // Local-only collection with initial data\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * initialData: [\n * { id: 1, name: 'Item 1' },\n * { id: 2, name: 'Item 2' },\n * ],\n * })\n * )\n *\n * @example\n * // Local-only collection with mutation handlers\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * onInsert: async ({ transaction }) => {\n * console.log('Item inserted:', transaction.mutations[0].modified)\n * // Custom logic after insert\n * },\n * })\n * )\n *\n * @example\n * // Using with manual transactions\n * const localData = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * })\n * )\n *\n * const tx = createTransaction({\n * mutationFn: async ({ transaction }) => {\n * // Use local data in API call\n * const localMutations = transaction.mutations.filter(m => m.collection === localData)\n * await api.save({ metadata: localMutations[0]?.modified })\n *\n * // Persist local-only mutations after API success\n * localData.utils.acceptMutations(transaction)\n * }\n * })\n *\n * tx.mutate(() => {\n * localData.insert({ id: 1, data: 'metadata' })\n * apiCollection.insert({ id: 2, data: 'main data' })\n * })\n *\n * await tx.commit()\n */\n\n// Overload for when schema is provided\nexport function localOnlyCollectionOptions<\n T extends StandardSchemaV1,\n TKey extends string | number = string | number,\n>(\n config: LocalOnlyCollectionConfig<InferSchemaOutput<T>, T, TKey> & {\n schema: T\n },\n): LocalOnlyCollectionOptionsResult<InferSchemaOutput<T>, TKey, T> & {\n schema: T\n}\n\n// Overload for when no schema is provided\n// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config\nexport function localOnlyCollectionOptions<\n T extends object,\n TKey extends string | number = string | number,\n>(\n config: LocalOnlyCollectionConfig<T, never, TKey> & {\n schema?: never // prohibit schema\n },\n): LocalOnlyCollectionOptionsResult<T, TKey> & {\n schema?: never // no schema in the result\n}\n\nexport function localOnlyCollectionOptions<\n T extends object = object,\n TSchema extends StandardSchemaV1 = never,\n TKey extends string | number = string | number,\n>(\n config: LocalOnlyCollectionConfig<T, TSchema, TKey>,\n): LocalOnlyCollectionOptionsResult<T, TKey, TSchema> & {\n schema?: StandardSchemaV1\n} {\n const { initialData, onInsert, onUpdate, onDelete, id, ...restConfig } =\n config\n\n const collectionId = id ?? crypto.randomUUID()\n\n // Create the sync configuration with transaction confirmation capability\n const syncResult = createLocalOnlySync<T, TKey>(initialData)\n\n /**\n * Create wrapper handlers that call user handlers first, then confirm transactions\n * Wraps the user's onInsert handler to also confirm the transaction immediately\n */\n const wrappedOnInsert = async (\n params: InsertMutationFnParams<T, TKey, LocalOnlyCollectionUtils>,\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onInsert) {\n handlerResult = (await onInsert(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n /**\n * Wrapper for onUpdate handler that also confirms the transaction immediately\n */\n const wrappedOnUpdate = async (\n params: UpdateMutationFnParams<T, TKey, LocalOnlyCollectionUtils>,\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onUpdate) {\n handlerResult = (await onUpdate(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n /**\n * Wrapper for onDelete handler that also confirms the transaction immediately\n */\n const wrappedOnDelete = async (\n params: DeleteMutationFnParams<T, TKey, LocalOnlyCollectionUtils>,\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onDelete) {\n handlerResult = (await onDelete(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n /**\n * Accepts mutations from a transaction that belong to this collection and persists them\n */\n const acceptMutations = (transaction: {\n mutations: Array<PendingMutation<Record<string, unknown>>>\n }) => {\n // Filter mutations that belong to this collection\n const collectionMutations = transaction.mutations.filter(\n (m) => m.collection.id === collectionId,\n )\n\n if (collectionMutations.length === 0) {\n return\n }\n\n // Persist the mutations through sync\n syncResult.confirmOperationsSync(\n collectionMutations as Array<PendingMutation<T>>,\n )\n }\n\n return {\n ...restConfig,\n id: collectionId,\n sync: syncResult.sync,\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {\n acceptMutations,\n },\n startSync: true,\n gcTime: 0,\n } as LocalOnlyCollectionOptionsResult<T, TKey, TSchema> & {\n schema?: StandardSchemaV1\n }\n}\n\n/**\n * Internal function to create Local-only sync configuration with transaction confirmation\n *\n * This captures the sync functions and provides synchronous confirmation of operations.\n * It creates a loopback sync that immediately confirms all optimistic operations,\n * making them permanent in the collection.\n *\n * @param initialData - Optional array of initial items to populate the collection\n * @returns Object with sync configuration and confirmOperationsSync function\n */\nfunction createLocalOnlySync<T extends object, TKey extends string | number>(\n initialData?: Array<T>,\n) {\n // Capture sync functions and collection for transaction confirmation\n let syncBegin: (() => void) | null = null\n let syncWrite: ((message: { type: OperationType; value: T }) => void) | null =\n null\n let syncCommit: (() => void) | null = null\n let collection: Collection<T, TKey, LocalOnlyCollectionUtils> | null = null\n\n const sync: SyncConfig<T, TKey> = {\n /**\n * Sync function that captures sync parameters and applies initial data\n * @param params - Sync parameters containing begin, write, and commit functions\n * @returns Unsubscribe function (empty since no ongoing sync is needed)\n */\n sync: (params) => {\n const { begin, write, commit, markReady } = params\n\n // Capture sync functions and collection for later use\n syncBegin = begin\n syncWrite = write\n syncCommit = commit\n collection = params.collection\n params.collection._state.isLocalOnly = true\n\n // Apply initial data if provided\n if (initialData && initialData.length > 0) {\n // Mark initial data as local so $origin is 'local' for local-only collections\n for (const item of initialData) {\n const key = params.collection.getKeyFromItem(item)\n params.collection._state.pendingLocalChanges.add(key)\n }\n\n begin()\n initialData.forEach((item) => {\n write({\n type: `insert`,\n value: item,\n })\n })\n commit()\n }\n\n // Mark collection as ready since local-only collections are immediately ready\n markReady()\n\n // Return empty unsubscribe function - no ongoing sync needed\n return () => {}\n },\n /**\n * Get sync metadata - returns empty object for local-only collections\n * @returns Empty metadata object\n */\n getSyncMetadata: () => ({}),\n }\n\n /**\n * Synchronously confirms optimistic operations by immediately writing through sync\n *\n * This loops through transaction mutations and applies them to move from optimistic to synced state.\n * It's called after user handlers to make optimistic changes permanent.\n *\n * @param mutations - Array of mutation objects from the transaction\n */\n const confirmOperationsSync = (mutations: Array<PendingMutation<T>>) => {\n if (!syncBegin || !syncWrite || !syncCommit) {\n return // Sync not initialized yet, which is fine\n }\n\n // Immediately write back through sync interface\n syncBegin()\n mutations.forEach((mutation) => {\n if (syncWrite) {\n syncWrite({\n type: mutation.type,\n value: mutation.modified,\n })\n }\n })\n syncCommit()\n }\n\n return {\n sync,\n confirmOperationsSync,\n collection,\n }\n}\n"],"names":[],"mappings":"AA4KO,SAAS,2BAKd,QAGA;AACA,QAAM,EAAE,aAAa,UAAU,UAAU,UAAU,IAAI,GAAG,eACxD;AAEF,QAAM,eAAe,MAAM,OAAO,WAAA;AAGlC,QAAM,aAAa,oBAA6B,WAAW;AAM3D,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,CAAC,gBAEnB;AAEJ,UAAM,sBAAsB,YAAY,UAAU;AAAA,MAChD,CAAC,MAAM,EAAE,WAAW,OAAO;AAAA,IAAA;AAG7B,QAAI,oBAAoB,WAAW,GAAG;AACpC;AAAA,IACF;AAGA,eAAW;AAAA,MACT;AAAA,IAAA;AAAA,EAEJ;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,IAAI;AAAA,IACJ,MAAM,WAAW;AAAA,IACjB,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,OAAO;AAAA,MACL;AAAA,IAAA;AAAA,IAEF,WAAW;AAAA,IACX,QAAQ;AAAA,EAAA;AAIZ;AAYA,SAAS,oBACP,aACA;AAEA,MAAI,YAAiC;AACrC,MAAI,YACF;AACF,MAAI,aAAkC;AACtC,MAAI,aAAmE;AAEvE,QAAM,OAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMhC,MAAM,CAAC,WAAW;AAChB,YAAM,EAAE,OAAO,OAAO,QAAQ,cAAc;AAG5C,kBAAY;AACZ,kBAAY;AACZ,mBAAa;AACb,mBAAa,OAAO;AACpB,aAAO,WAAW,OAAO,cAAc;AAGvC,UAAI,eAAe,YAAY,SAAS,GAAG;AAEzC,mBAAW,QAAQ,aAAa;AAC9B,gBAAM,MAAM,OAAO,WAAW,eAAe,IAAI;AACjD,iBAAO,WAAW,OAAO,oBAAoB,IAAI,GAAG;AAAA,QACtD;AAEA,cAAA;AACA,oBAAY,QAAQ,CAAC,SAAS;AAC5B,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,OAAO;AAAA,UAAA,CACR;AAAA,QACH,CAAC;AACD,eAAA;AAAA,MACF;AAGA,gBAAA;AAGA,aAAO,MAAM;AAAA,MAAC;AAAA,IAChB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,iBAAiB,OAAO,CAAA;AAAA,EAAC;AAW3B,QAAM,wBAAwB,CAAC,cAAyC;AACtE,QAAI,CAAC,aAAa,CAAC,aAAa,CAAC,YAAY;AAC3C;AAAA,IACF;AAGA,cAAA;AACA,cAAU,QAAQ,CAAC,aAAa;AAC9B,UAAI,WAAW;AACb,kBAAU;AAAA,UACR,MAAM,SAAS;AAAA,UACf,OAAO,SAAS;AAAA,QAAA,CACjB;AAAA,MACH;AAAA,IACF,CAAC;AACD,eAAA;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Aggregate, BasicExpression } from '../ir.js';
|
|
2
2
|
import { RefProxy } from './ref-proxy.js';
|
|
3
|
-
import { RefLeaf } from './types.js';
|
|
3
|
+
import { Context, GetRawResult, RefLeaf, StringifiableScalar } from './types.js';
|
|
4
|
+
import { QueryBuilder } from './index.js';
|
|
4
5
|
type StringRef = RefLeaf<string> | RefLeaf<string | null> | RefLeaf<string | undefined>;
|
|
5
6
|
type StringRefProxy = RefProxy<string> | RefProxy<string | null> | RefProxy<string | undefined>;
|
|
6
7
|
type StringBasicExpression = BasicExpression<string> | BasicExpression<string | null> | BasicExpression<string | undefined>;
|
|
7
8
|
type StringLike = StringRef | StringRefProxy | StringBasicExpression | string | null | undefined;
|
|
8
9
|
type ComparisonOperand<T> = RefProxy<T> | RefLeaf<T> | T | BasicExpression<T> | undefined | null;
|
|
9
10
|
type ComparisonOperandPrimitive<T extends string | number | boolean> = T | BasicExpression<T> | undefined | null;
|
|
10
|
-
type ExpressionLike = BasicExpression | RefProxy<any> | RefLeaf<any> |
|
|
11
|
+
type ExpressionLike = Aggregate | BasicExpression | RefProxy<any> | RefLeaf<any> | string | number | boolean | bigint | Date | null | undefined | Array<unknown>;
|
|
11
12
|
type ExtractType<T> = T extends RefProxy<infer U> ? U : T extends RefLeaf<infer U> ? U : T extends BasicExpression<infer U> ? U : T;
|
|
12
13
|
type AggregateReturnType<T> = ExtractType<T> extends infer U ? U extends number | undefined | null | Date | bigint | string ? Aggregate<U> : Aggregate<number | undefined | null | Date | bigint | string> : Aggregate<number | undefined | null | Date | bigint | string>;
|
|
13
14
|
type StringFunctionReturnType<T> = ExtractType<T> extends infer U ? U extends string | undefined | null ? BasicExpression<U> : BasicExpression<string | undefined | null> : BasicExpression<string | undefined | null>;
|
|
@@ -42,8 +43,16 @@ export declare function ilike(left: StringLike, right: StringLike): BasicExpress
|
|
|
42
43
|
export declare function upper<T extends ExpressionLike>(arg: T): StringFunctionReturnType<T>;
|
|
43
44
|
export declare function lower<T extends ExpressionLike>(arg: T): StringFunctionReturnType<T>;
|
|
44
45
|
export declare function length<T extends ExpressionLike>(arg: T): NumericFunctionReturnType<T>;
|
|
46
|
+
export declare function concat<T extends StringifiableScalar>(arg: ToArrayWrapper<T>): ConcatToArrayWrapper<T>;
|
|
45
47
|
export declare function concat(...args: Array<ExpressionLike>): BasicExpression<string>;
|
|
46
|
-
|
|
48
|
+
type CoalesceArgTypes<T extends Array<ExpressionLike>> = {
|
|
49
|
+
[K in keyof T]: NonNullable<ExtractType<T[K]>>;
|
|
50
|
+
}[number];
|
|
51
|
+
type HasGuaranteedNonNull<T extends Array<ExpressionLike>> = {
|
|
52
|
+
[K in keyof T]: null extends ExtractType<T[K]> ? false : undefined extends ExtractType<T[K]> ? false : true;
|
|
53
|
+
}[number] extends false ? false : true;
|
|
54
|
+
type CoalesceReturnType<T extends Array<ExpressionLike>> = HasGuaranteedNonNull<T> extends true ? BasicExpression<CoalesceArgTypes<T>> : BasicExpression<CoalesceArgTypes<T> | null>;
|
|
55
|
+
export declare function coalesce<T extends [ExpressionLike, ...Array<ExpressionLike>]>(...args: T): CoalesceReturnType<T>;
|
|
47
56
|
export declare function add<T1 extends ExpressionLike, T2 extends ExpressionLike>(left: T1, right: T2): BinaryNumericReturnType<T1, T2>;
|
|
48
57
|
export declare function count(arg: ExpressionLike): Aggregate<number>;
|
|
49
58
|
export declare function avg<T extends ExpressionLike>(arg: T): AggregateReturnType<T>;
|
|
@@ -59,4 +68,17 @@ export declare const comparisonFunctions: readonly ["eq", "gt", "gte", "lt", "lt
|
|
|
59
68
|
*/
|
|
60
69
|
export declare const operators: readonly ["eq", "gt", "gte", "lt", "lte", "in", "like", "ilike", "and", "or", "not", "isNull", "isUndefined", "upper", "lower", "length", "concat", "add", "coalesce", "count", "avg", "sum", "min", "max"];
|
|
61
70
|
export type OperatorName = (typeof operators)[number];
|
|
71
|
+
export declare class ToArrayWrapper<_T = unknown> {
|
|
72
|
+
readonly query: QueryBuilder<any>;
|
|
73
|
+
readonly _type: `toArray`;
|
|
74
|
+
readonly _result: _T;
|
|
75
|
+
constructor(query: QueryBuilder<any>);
|
|
76
|
+
}
|
|
77
|
+
export declare class ConcatToArrayWrapper<_T = unknown> {
|
|
78
|
+
readonly query: QueryBuilder<any>;
|
|
79
|
+
readonly _type: `concatToArray`;
|
|
80
|
+
readonly _result: _T;
|
|
81
|
+
constructor(query: QueryBuilder<any>);
|
|
82
|
+
}
|
|
83
|
+
export declare function toArray<TContext extends Context>(query: QueryBuilder<TContext>): ToArrayWrapper<GetRawResult<TContext>>;
|
|
62
84
|
export {};
|
|
@@ -57,6 +57,17 @@ function length(arg) {
|
|
|
57
57
|
return new Func(`length`, [toExpression(arg)]);
|
|
58
58
|
}
|
|
59
59
|
function concat(...args) {
|
|
60
|
+
const toArrayArg = args.find(
|
|
61
|
+
(arg) => arg instanceof ToArrayWrapper
|
|
62
|
+
);
|
|
63
|
+
if (toArrayArg) {
|
|
64
|
+
if (args.length !== 1) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`concat(toArray(...)) currently supports only a single toArray(...) argument`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return new ConcatToArrayWrapper(toArrayArg.query);
|
|
70
|
+
}
|
|
60
71
|
return new Func(
|
|
61
72
|
`concat`,
|
|
62
73
|
args.map((arg) => toExpression(arg))
|
|
@@ -89,16 +100,6 @@ function min(arg) {
|
|
|
89
100
|
function max(arg) {
|
|
90
101
|
return new Aggregate(`max`, [toExpression(arg)]);
|
|
91
102
|
}
|
|
92
|
-
const comparisonFunctions = [
|
|
93
|
-
`eq`,
|
|
94
|
-
`gt`,
|
|
95
|
-
`gte`,
|
|
96
|
-
`lt`,
|
|
97
|
-
`lte`,
|
|
98
|
-
`in`,
|
|
99
|
-
`like`,
|
|
100
|
-
`ilike`
|
|
101
|
-
];
|
|
102
103
|
const operators = [
|
|
103
104
|
// Comparison operators
|
|
104
105
|
`eq`,
|
|
@@ -132,12 +133,26 @@ const operators = [
|
|
|
132
133
|
`min`,
|
|
133
134
|
`max`
|
|
134
135
|
];
|
|
136
|
+
class ToArrayWrapper {
|
|
137
|
+
constructor(query) {
|
|
138
|
+
this.query = query;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
class ConcatToArrayWrapper {
|
|
142
|
+
constructor(query) {
|
|
143
|
+
this.query = query;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function toArray(query) {
|
|
147
|
+
return new ToArrayWrapper(query);
|
|
148
|
+
}
|
|
135
149
|
export {
|
|
150
|
+
ConcatToArrayWrapper,
|
|
151
|
+
ToArrayWrapper,
|
|
136
152
|
add,
|
|
137
153
|
and,
|
|
138
154
|
avg,
|
|
139
155
|
coalesce,
|
|
140
|
-
comparisonFunctions,
|
|
141
156
|
concat,
|
|
142
157
|
count,
|
|
143
158
|
eq,
|
|
@@ -158,6 +173,7 @@ export {
|
|
|
158
173
|
operators,
|
|
159
174
|
or,
|
|
160
175
|
sum,
|
|
176
|
+
toArray,
|
|
161
177
|
upper
|
|
162
178
|
};
|
|
163
179
|
//# sourceMappingURL=functions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"functions.js","sources":["../../../../src/query/builder/functions.ts"],"sourcesContent":["import { Aggregate, Func } from '../ir'\nimport { toExpression } from './ref-proxy.js'\nimport type { BasicExpression } from '../ir'\nimport type { RefProxy } from './ref-proxy.js'\nimport type { RefLeaf } from './types.js'\n\ntype StringRef =\n | RefLeaf<string>\n | RefLeaf<string | null>\n | RefLeaf<string | undefined>\ntype StringRefProxy =\n | RefProxy<string>\n | RefProxy<string | null>\n | RefProxy<string | undefined>\ntype StringBasicExpression =\n | BasicExpression<string>\n | BasicExpression<string | null>\n | BasicExpression<string | undefined>\ntype StringLike =\n | StringRef\n | StringRefProxy\n | StringBasicExpression\n | string\n | null\n | undefined\n\ntype ComparisonOperand<T> =\n | RefProxy<T>\n | RefLeaf<T>\n | T\n | BasicExpression<T>\n | undefined\n | null\ntype ComparisonOperandPrimitive<T extends string | number | boolean> =\n | T\n | BasicExpression<T>\n | undefined\n | null\n\n// Helper type for any expression-like value\ntype ExpressionLike = BasicExpression | RefProxy<any> | RefLeaf<any> | any\n\n// Helper type to extract the underlying type from various expression types\ntype ExtractType<T> =\n T extends RefProxy<infer U>\n ? U\n : T extends RefLeaf<infer U>\n ? U\n : T extends BasicExpression<infer U>\n ? U\n : T\n\n// Helper type to determine aggregate return type based on input nullability\ntype AggregateReturnType<T> =\n ExtractType<T> extends infer U\n ? U extends number | undefined | null | Date | bigint | string\n ? Aggregate<U>\n : Aggregate<number | undefined | null | Date | bigint | string>\n : Aggregate<number | undefined | null | Date | bigint | string>\n\n// Helper type to determine string function return type based on input nullability\ntype StringFunctionReturnType<T> =\n ExtractType<T> extends infer U\n ? U extends string | undefined | null\n ? BasicExpression<U>\n : BasicExpression<string | undefined | null>\n : BasicExpression<string | undefined | null>\n\n// Helper type to determine numeric function return type based on input nullability\n// This handles string, array, and number inputs for functions like length()\ntype NumericFunctionReturnType<T> =\n ExtractType<T> extends infer U\n ? U extends string | Array<any> | undefined | null | number\n ? BasicExpression<MapToNumber<U>>\n : BasicExpression<number | undefined | null>\n : BasicExpression<number | undefined | null>\n\n// Transform string/array types to number while preserving nullability\ntype MapToNumber<T> = T extends string | Array<any>\n ? number\n : T extends undefined\n ? undefined\n : T extends null\n ? null\n : T\n\n// Helper type for binary numeric operations (combines nullability of both operands)\ntype BinaryNumericReturnType<T1, T2> =\n ExtractType<T1> extends infer U1\n ? ExtractType<T2> extends infer U2\n ? U1 extends number\n ? U2 extends number\n ? BasicExpression<number>\n : U2 extends number | undefined\n ? BasicExpression<number | undefined>\n : U2 extends number | null\n ? BasicExpression<number | null>\n : BasicExpression<number | undefined | null>\n : U1 extends number | undefined\n ? U2 extends number\n ? BasicExpression<number | undefined>\n : U2 extends number | undefined\n ? BasicExpression<number | undefined>\n : BasicExpression<number | undefined | null>\n : U1 extends number | null\n ? U2 extends number\n ? BasicExpression<number | null>\n : BasicExpression<number | undefined | null>\n : BasicExpression<number | undefined | null>\n : BasicExpression<number | undefined | null>\n : BasicExpression<number | undefined | null>\n\n// Operators\n\nexport function eq<T>(\n left: ComparisonOperand<T>,\n right: ComparisonOperand<T>,\n): BasicExpression<boolean>\nexport function eq<T extends string | number | boolean>(\n left: ComparisonOperandPrimitive<T>,\n right: ComparisonOperandPrimitive<T>,\n): BasicExpression<boolean>\nexport function eq<T>(left: Aggregate<T>, right: any): BasicExpression<boolean>\nexport function eq(left: any, right: any): BasicExpression<boolean> {\n return new Func(`eq`, [toExpression(left), toExpression(right)])\n}\n\nexport function gt<T>(\n left: ComparisonOperand<T>,\n right: ComparisonOperand<T>,\n): BasicExpression<boolean>\nexport function gt<T extends string | number>(\n left: ComparisonOperandPrimitive<T>,\n right: ComparisonOperandPrimitive<T>,\n): BasicExpression<boolean>\nexport function gt<T>(left: Aggregate<T>, right: any): BasicExpression<boolean>\nexport function gt(left: any, right: any): BasicExpression<boolean> {\n return new Func(`gt`, [toExpression(left), toExpression(right)])\n}\n\nexport function gte<T>(\n left: ComparisonOperand<T>,\n right: ComparisonOperand<T>,\n): BasicExpression<boolean>\nexport function gte<T extends string | number>(\n left: ComparisonOperandPrimitive<T>,\n right: ComparisonOperandPrimitive<T>,\n): BasicExpression<boolean>\nexport function gte<T>(left: Aggregate<T>, right: any): BasicExpression<boolean>\nexport function gte(left: any, right: any): BasicExpression<boolean> {\n return new Func(`gte`, [toExpression(left), toExpression(right)])\n}\n\nexport function lt<T>(\n left: ComparisonOperand<T>,\n right: ComparisonOperand<T>,\n): BasicExpression<boolean>\nexport function lt<T extends string | number>(\n left: ComparisonOperandPrimitive<T>,\n right: ComparisonOperandPrimitive<T>,\n): BasicExpression<boolean>\nexport function lt<T>(left: Aggregate<T>, right: any): BasicExpression<boolean>\nexport function lt(left: any, right: any): BasicExpression<boolean> {\n return new Func(`lt`, [toExpression(left), toExpression(right)])\n}\n\nexport function lte<T>(\n left: ComparisonOperand<T>,\n right: ComparisonOperand<T>,\n): BasicExpression<boolean>\nexport function lte<T extends string | number>(\n left: ComparisonOperandPrimitive<T>,\n right: ComparisonOperandPrimitive<T>,\n): BasicExpression<boolean>\nexport function lte<T>(left: Aggregate<T>, right: any): BasicExpression<boolean>\nexport function lte(left: any, right: any): BasicExpression<boolean> {\n return new Func(`lte`, [toExpression(left), toExpression(right)])\n}\n\n// Overloads for and() - support 2 or more arguments\nexport function and(\n left: ExpressionLike,\n right: ExpressionLike,\n): BasicExpression<boolean>\nexport function and(\n left: ExpressionLike,\n right: ExpressionLike,\n ...rest: Array<ExpressionLike>\n): BasicExpression<boolean>\nexport function and(\n left: ExpressionLike,\n right: ExpressionLike,\n ...rest: Array<ExpressionLike>\n): BasicExpression<boolean> {\n const allArgs = [left, right, ...rest]\n return new Func(\n `and`,\n allArgs.map((arg) => toExpression(arg)),\n )\n}\n\n// Overloads for or() - support 2 or more arguments\nexport function or(\n left: ExpressionLike,\n right: ExpressionLike,\n): BasicExpression<boolean>\nexport function or(\n left: ExpressionLike,\n right: ExpressionLike,\n ...rest: Array<ExpressionLike>\n): BasicExpression<boolean>\nexport function or(\n left: ExpressionLike,\n right: ExpressionLike,\n ...rest: Array<ExpressionLike>\n): BasicExpression<boolean> {\n const allArgs = [left, right, ...rest]\n return new Func(\n `or`,\n allArgs.map((arg) => toExpression(arg)),\n )\n}\n\nexport function not(value: ExpressionLike): BasicExpression<boolean> {\n return new Func(`not`, [toExpression(value)])\n}\n\n// Null/undefined checking functions\nexport function isUndefined(value: ExpressionLike): BasicExpression<boolean> {\n return new Func(`isUndefined`, [toExpression(value)])\n}\n\nexport function isNull(value: ExpressionLike): BasicExpression<boolean> {\n return new Func(`isNull`, [toExpression(value)])\n}\n\nexport function inArray(\n value: ExpressionLike,\n array: ExpressionLike,\n): BasicExpression<boolean> {\n return new Func(`in`, [toExpression(value), toExpression(array)])\n}\n\nexport function like(\n left: StringLike,\n right: StringLike,\n): BasicExpression<boolean>\nexport function like(left: any, right: any): BasicExpression<boolean> {\n return new Func(`like`, [toExpression(left), toExpression(right)])\n}\n\nexport function ilike(\n left: StringLike,\n right: StringLike,\n): BasicExpression<boolean> {\n return new Func(`ilike`, [toExpression(left), toExpression(right)])\n}\n\n// Functions\n\nexport function upper<T extends ExpressionLike>(\n arg: T,\n): StringFunctionReturnType<T> {\n return new Func(`upper`, [toExpression(arg)]) as StringFunctionReturnType<T>\n}\n\nexport function lower<T extends ExpressionLike>(\n arg: T,\n): StringFunctionReturnType<T> {\n return new Func(`lower`, [toExpression(arg)]) as StringFunctionReturnType<T>\n}\n\nexport function length<T extends ExpressionLike>(\n arg: T,\n): NumericFunctionReturnType<T> {\n return new Func(`length`, [toExpression(arg)]) as NumericFunctionReturnType<T>\n}\n\nexport function concat(\n ...args: Array<ExpressionLike>\n): BasicExpression<string> {\n return new Func(\n `concat`,\n args.map((arg) => toExpression(arg)),\n )\n}\n\nexport function coalesce(...args: Array<ExpressionLike>): BasicExpression<any> {\n return new Func(\n `coalesce`,\n args.map((arg) => toExpression(arg)),\n )\n}\n\nexport function add<T1 extends ExpressionLike, T2 extends ExpressionLike>(\n left: T1,\n right: T2,\n): BinaryNumericReturnType<T1, T2> {\n return new Func(`add`, [\n toExpression(left),\n toExpression(right),\n ]) as BinaryNumericReturnType<T1, T2>\n}\n\n// Aggregates\n\nexport function count(arg: ExpressionLike): Aggregate<number> {\n return new Aggregate(`count`, [toExpression(arg)])\n}\n\nexport function avg<T extends ExpressionLike>(arg: T): AggregateReturnType<T> {\n return new Aggregate(`avg`, [toExpression(arg)]) as AggregateReturnType<T>\n}\n\nexport function sum<T extends ExpressionLike>(arg: T): AggregateReturnType<T> {\n return new Aggregate(`sum`, [toExpression(arg)]) as AggregateReturnType<T>\n}\n\nexport function min<T extends ExpressionLike>(arg: T): AggregateReturnType<T> {\n return new Aggregate(`min`, [toExpression(arg)]) as AggregateReturnType<T>\n}\n\nexport function max<T extends ExpressionLike>(arg: T): AggregateReturnType<T> {\n return new Aggregate(`max`, [toExpression(arg)]) as AggregateReturnType<T>\n}\n\n/**\n * List of comparison function names that can be used with indexes\n */\nexport const comparisonFunctions = [\n `eq`,\n `gt`,\n `gte`,\n `lt`,\n `lte`,\n `in`,\n `like`,\n `ilike`,\n] as const\n\n/**\n * All supported operator names in TanStack DB expressions\n */\nexport const operators = [\n // Comparison operators\n `eq`,\n `gt`,\n `gte`,\n `lt`,\n `lte`,\n `in`,\n `like`,\n `ilike`,\n // Logical operators\n `and`,\n `or`,\n `not`,\n // Null checking\n `isNull`,\n `isUndefined`,\n // String functions\n `upper`,\n `lower`,\n `length`,\n `concat`,\n // Numeric functions\n `add`,\n // Utility functions\n `coalesce`,\n // Aggregate functions\n `count`,\n `avg`,\n `sum`,\n `min`,\n `max`,\n] as const\n\nexport type OperatorName = (typeof operators)[number]\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"functions.js","sources":["../../../../src/query/builder/functions.ts"],"sourcesContent":["import { Aggregate, Func } from '../ir'\nimport { toExpression } from './ref-proxy.js'\nimport type { BasicExpression } from '../ir'\nimport type { RefProxy } from './ref-proxy.js'\nimport type {\n Context,\n GetRawResult,\n RefLeaf,\n StringifiableScalar,\n} from './types.js'\nimport type { QueryBuilder } from './index.js'\n\ntype StringRef =\n | RefLeaf<string>\n | RefLeaf<string | null>\n | RefLeaf<string | undefined>\ntype StringRefProxy =\n | RefProxy<string>\n | RefProxy<string | null>\n | RefProxy<string | undefined>\ntype StringBasicExpression =\n | BasicExpression<string>\n | BasicExpression<string | null>\n | BasicExpression<string | undefined>\ntype StringLike =\n | StringRef\n | StringRefProxy\n | StringBasicExpression\n | string\n | null\n | undefined\n\ntype ComparisonOperand<T> =\n | RefProxy<T>\n | RefLeaf<T>\n | T\n | BasicExpression<T>\n | undefined\n | null\ntype ComparisonOperandPrimitive<T extends string | number | boolean> =\n | T\n | BasicExpression<T>\n | undefined\n | null\n\n// Helper type for values that can be lowered to expressions.\ntype ExpressionLike =\n | Aggregate\n | BasicExpression\n | RefProxy<any>\n | RefLeaf<any>\n | string\n | number\n | boolean\n | bigint\n | Date\n | null\n | undefined\n | Array<unknown>\n\n// Helper type to extract the underlying type from various expression types\ntype ExtractType<T> =\n T extends RefProxy<infer U>\n ? U\n : T extends RefLeaf<infer U>\n ? U\n : T extends BasicExpression<infer U>\n ? U\n : T\n\n// Helper type to determine aggregate return type based on input nullability\ntype AggregateReturnType<T> =\n ExtractType<T> extends infer U\n ? U extends number | undefined | null | Date | bigint | string\n ? Aggregate<U>\n : Aggregate<number | undefined | null | Date | bigint | string>\n : Aggregate<number | undefined | null | Date | bigint | string>\n\n// Helper type to determine string function return type based on input nullability\ntype StringFunctionReturnType<T> =\n ExtractType<T> extends infer U\n ? U extends string | undefined | null\n ? BasicExpression<U>\n : BasicExpression<string | undefined | null>\n : BasicExpression<string | undefined | null>\n\n// Helper type to determine numeric function return type based on input nullability\n// This handles string, array, and number inputs for functions like length()\ntype NumericFunctionReturnType<T> =\n ExtractType<T> extends infer U\n ? U extends string | Array<any> | undefined | null | number\n ? BasicExpression<MapToNumber<U>>\n : BasicExpression<number | undefined | null>\n : BasicExpression<number | undefined | null>\n\n// Transform string/array types to number while preserving nullability\ntype MapToNumber<T> = T extends string | Array<any>\n ? number\n : T extends undefined\n ? undefined\n : T extends null\n ? null\n : T\n\n// Helper type for binary numeric operations (combines nullability of both operands)\ntype BinaryNumericReturnType<T1, T2> =\n ExtractType<T1> extends infer U1\n ? ExtractType<T2> extends infer U2\n ? U1 extends number\n ? U2 extends number\n ? BasicExpression<number>\n : U2 extends number | undefined\n ? BasicExpression<number | undefined>\n : U2 extends number | null\n ? BasicExpression<number | null>\n : BasicExpression<number | undefined | null>\n : U1 extends number | undefined\n ? U2 extends number\n ? BasicExpression<number | undefined>\n : U2 extends number | undefined\n ? BasicExpression<number | undefined>\n : BasicExpression<number | undefined | null>\n : U1 extends number | null\n ? U2 extends number\n ? BasicExpression<number | null>\n : BasicExpression<number | undefined | null>\n : BasicExpression<number | undefined | null>\n : BasicExpression<number | undefined | null>\n : BasicExpression<number | undefined | null>\n\n// Operators\n\nexport function eq<T>(\n left: ComparisonOperand<T>,\n right: ComparisonOperand<T>,\n): BasicExpression<boolean>\nexport function eq<T extends string | number | boolean>(\n left: ComparisonOperandPrimitive<T>,\n right: ComparisonOperandPrimitive<T>,\n): BasicExpression<boolean>\nexport function eq<T>(left: Aggregate<T>, right: any): BasicExpression<boolean>\nexport function eq(left: any, right: any): BasicExpression<boolean> {\n return new Func(`eq`, [toExpression(left), toExpression(right)])\n}\n\nexport function gt<T>(\n left: ComparisonOperand<T>,\n right: ComparisonOperand<T>,\n): BasicExpression<boolean>\nexport function gt<T extends string | number>(\n left: ComparisonOperandPrimitive<T>,\n right: ComparisonOperandPrimitive<T>,\n): BasicExpression<boolean>\nexport function gt<T>(left: Aggregate<T>, right: any): BasicExpression<boolean>\nexport function gt(left: any, right: any): BasicExpression<boolean> {\n return new Func(`gt`, [toExpression(left), toExpression(right)])\n}\n\nexport function gte<T>(\n left: ComparisonOperand<T>,\n right: ComparisonOperand<T>,\n): BasicExpression<boolean>\nexport function gte<T extends string | number>(\n left: ComparisonOperandPrimitive<T>,\n right: ComparisonOperandPrimitive<T>,\n): BasicExpression<boolean>\nexport function gte<T>(left: Aggregate<T>, right: any): BasicExpression<boolean>\nexport function gte(left: any, right: any): BasicExpression<boolean> {\n return new Func(`gte`, [toExpression(left), toExpression(right)])\n}\n\nexport function lt<T>(\n left: ComparisonOperand<T>,\n right: ComparisonOperand<T>,\n): BasicExpression<boolean>\nexport function lt<T extends string | number>(\n left: ComparisonOperandPrimitive<T>,\n right: ComparisonOperandPrimitive<T>,\n): BasicExpression<boolean>\nexport function lt<T>(left: Aggregate<T>, right: any): BasicExpression<boolean>\nexport function lt(left: any, right: any): BasicExpression<boolean> {\n return new Func(`lt`, [toExpression(left), toExpression(right)])\n}\n\nexport function lte<T>(\n left: ComparisonOperand<T>,\n right: ComparisonOperand<T>,\n): BasicExpression<boolean>\nexport function lte<T extends string | number>(\n left: ComparisonOperandPrimitive<T>,\n right: ComparisonOperandPrimitive<T>,\n): BasicExpression<boolean>\nexport function lte<T>(left: Aggregate<T>, right: any): BasicExpression<boolean>\nexport function lte(left: any, right: any): BasicExpression<boolean> {\n return new Func(`lte`, [toExpression(left), toExpression(right)])\n}\n\n// Overloads for and() - support 2 or more arguments\nexport function and(\n left: ExpressionLike,\n right: ExpressionLike,\n): BasicExpression<boolean>\nexport function and(\n left: ExpressionLike,\n right: ExpressionLike,\n ...rest: Array<ExpressionLike>\n): BasicExpression<boolean>\nexport function and(\n left: ExpressionLike,\n right: ExpressionLike,\n ...rest: Array<ExpressionLike>\n): BasicExpression<boolean> {\n const allArgs = [left, right, ...rest]\n return new Func(\n `and`,\n allArgs.map((arg) => toExpression(arg)),\n )\n}\n\n// Overloads for or() - support 2 or more arguments\nexport function or(\n left: ExpressionLike,\n right: ExpressionLike,\n): BasicExpression<boolean>\nexport function or(\n left: ExpressionLike,\n right: ExpressionLike,\n ...rest: Array<ExpressionLike>\n): BasicExpression<boolean>\nexport function or(\n left: ExpressionLike,\n right: ExpressionLike,\n ...rest: Array<ExpressionLike>\n): BasicExpression<boolean> {\n const allArgs = [left, right, ...rest]\n return new Func(\n `or`,\n allArgs.map((arg) => toExpression(arg)),\n )\n}\n\nexport function not(value: ExpressionLike): BasicExpression<boolean> {\n return new Func(`not`, [toExpression(value)])\n}\n\n// Null/undefined checking functions\nexport function isUndefined(value: ExpressionLike): BasicExpression<boolean> {\n return new Func(`isUndefined`, [toExpression(value)])\n}\n\nexport function isNull(value: ExpressionLike): BasicExpression<boolean> {\n return new Func(`isNull`, [toExpression(value)])\n}\n\nexport function inArray(\n value: ExpressionLike,\n array: ExpressionLike,\n): BasicExpression<boolean> {\n return new Func(`in`, [toExpression(value), toExpression(array)])\n}\n\nexport function like(\n left: StringLike,\n right: StringLike,\n): BasicExpression<boolean>\nexport function like(left: any, right: any): BasicExpression<boolean> {\n return new Func(`like`, [toExpression(left), toExpression(right)])\n}\n\nexport function ilike(\n left: StringLike,\n right: StringLike,\n): BasicExpression<boolean> {\n return new Func(`ilike`, [toExpression(left), toExpression(right)])\n}\n\n// Functions\n\nexport function upper<T extends ExpressionLike>(\n arg: T,\n): StringFunctionReturnType<T> {\n return new Func(`upper`, [toExpression(arg)]) as StringFunctionReturnType<T>\n}\n\nexport function lower<T extends ExpressionLike>(\n arg: T,\n): StringFunctionReturnType<T> {\n return new Func(`lower`, [toExpression(arg)]) as StringFunctionReturnType<T>\n}\n\nexport function length<T extends ExpressionLike>(\n arg: T,\n): NumericFunctionReturnType<T> {\n return new Func(`length`, [toExpression(arg)]) as NumericFunctionReturnType<T>\n}\n\nexport function concat<T extends StringifiableScalar>(\n arg: ToArrayWrapper<T>,\n): ConcatToArrayWrapper<T>\nexport function concat(...args: Array<ExpressionLike>): BasicExpression<string>\nexport function concat(\n ...args: Array<ExpressionLike | ToArrayWrapper<any>>\n): BasicExpression<string> | ConcatToArrayWrapper<any> {\n const toArrayArg = args.find(\n (arg): arg is ToArrayWrapper<any> => arg instanceof ToArrayWrapper,\n )\n\n if (toArrayArg) {\n if (args.length !== 1) {\n throw new Error(\n `concat(toArray(...)) currently supports only a single toArray(...) argument`,\n )\n }\n return new ConcatToArrayWrapper(toArrayArg.query)\n }\n\n return new Func(\n `concat`,\n args.map((arg) => toExpression(arg)),\n )\n}\n\n// Helper type for coalesce: extracts non-nullish value types from all args\ntype CoalesceArgTypes<T extends Array<ExpressionLike>> = {\n [K in keyof T]: NonNullable<ExtractType<T[K]>>\n}[number]\n\n// Whether any arg in the tuple is statically guaranteed non-null (i.e., does not include null | undefined)\ntype HasGuaranteedNonNull<T extends Array<ExpressionLike>> = {\n [K in keyof T]: null extends ExtractType<T[K]>\n ? false\n : undefined extends ExtractType<T[K]>\n ? false\n : true\n}[number] extends false\n ? false\n : true\n\n// coalesce() return type: union of all non-null arg types; null included unless a guaranteed non-null arg exists\ntype CoalesceReturnType<T extends Array<ExpressionLike>> =\n HasGuaranteedNonNull<T> extends true\n ? BasicExpression<CoalesceArgTypes<T>>\n : BasicExpression<CoalesceArgTypes<T> | null>\n\nexport function coalesce<T extends [ExpressionLike, ...Array<ExpressionLike>]>(\n ...args: T\n): CoalesceReturnType<T> {\n return new Func(\n `coalesce`,\n args.map((arg) => toExpression(arg)),\n ) as CoalesceReturnType<T>\n}\n\nexport function add<T1 extends ExpressionLike, T2 extends ExpressionLike>(\n left: T1,\n right: T2,\n): BinaryNumericReturnType<T1, T2> {\n return new Func(`add`, [\n toExpression(left),\n toExpression(right),\n ]) as BinaryNumericReturnType<T1, T2>\n}\n\n// Aggregates\n\nexport function count(arg: ExpressionLike): Aggregate<number> {\n return new Aggregate(`count`, [toExpression(arg)])\n}\n\nexport function avg<T extends ExpressionLike>(arg: T): AggregateReturnType<T> {\n return new Aggregate(`avg`, [toExpression(arg)]) as AggregateReturnType<T>\n}\n\nexport function sum<T extends ExpressionLike>(arg: T): AggregateReturnType<T> {\n return new Aggregate(`sum`, [toExpression(arg)]) as AggregateReturnType<T>\n}\n\nexport function min<T extends ExpressionLike>(arg: T): AggregateReturnType<T> {\n return new Aggregate(`min`, [toExpression(arg)]) as AggregateReturnType<T>\n}\n\nexport function max<T extends ExpressionLike>(arg: T): AggregateReturnType<T> {\n return new Aggregate(`max`, [toExpression(arg)]) as AggregateReturnType<T>\n}\n\n/**\n * List of comparison function names that can be used with indexes\n */\nexport const comparisonFunctions = [\n `eq`,\n `gt`,\n `gte`,\n `lt`,\n `lte`,\n `in`,\n `like`,\n `ilike`,\n] as const\n\n/**\n * All supported operator names in TanStack DB expressions\n */\nexport const operators = [\n // Comparison operators\n `eq`,\n `gt`,\n `gte`,\n `lt`,\n `lte`,\n `in`,\n `like`,\n `ilike`,\n // Logical operators\n `and`,\n `or`,\n `not`,\n // Null checking\n `isNull`,\n `isUndefined`,\n // String functions\n `upper`,\n `lower`,\n `length`,\n `concat`,\n // Numeric functions\n `add`,\n // Utility functions\n `coalesce`,\n // Aggregate functions\n `count`,\n `avg`,\n `sum`,\n `min`,\n `max`,\n] as const\n\nexport type OperatorName = (typeof operators)[number]\n\nexport class ToArrayWrapper<_T = unknown> {\n declare readonly _type: `toArray`\n declare readonly _result: _T\n constructor(public readonly query: QueryBuilder<any>) {}\n}\n\nexport class ConcatToArrayWrapper<_T = unknown> {\n declare readonly _type: `concatToArray`\n declare readonly _result: _T\n constructor(public readonly query: QueryBuilder<any>) {}\n}\n\nexport function toArray<TContext extends Context>(\n query: QueryBuilder<TContext>,\n): ToArrayWrapper<GetRawResult<TContext>> {\n return new ToArrayWrapper(query)\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SingleResult } from '../../types.js';
|
|
2
2
|
import { OrderByDirection, QueryIR } from '../ir.js';
|
|
3
|
-
import { Context, FunctionalHavingRow, GetResult, GroupByCallback, JoinOnCallback, MergeContextForJoinCallback, MergeContextWithJoinType, OrderByCallback, OrderByOptions, RefsForContext, ResultTypeFromSelect, SchemaFromSource, SelectObject, Source, WhereCallback, WithResult } from './types.js';
|
|
3
|
+
import { Context, FunctionalHavingRow, GetResult, GroupByCallback, JoinOnCallback, MergeContextForJoinCallback, MergeContextWithJoinType, NonScalarSelectObject, OrderByCallback, OrderByOptions, RefsForContext, ResultTypeFromSelect, ResultTypeFromSelectValue, ScalarSelectValue, SchemaFromSource, SelectObject, Source, WhereCallback, WithResult } from './types.js';
|
|
4
4
|
export declare class BaseQueryBuilder<TContext extends Context = Context> {
|
|
5
5
|
private readonly query;
|
|
6
6
|
constructor(query?: Partial<QueryIR>);
|
|
@@ -217,7 +217,8 @@ export declare class BaseQueryBuilder<TContext extends Context = Context> {
|
|
|
217
217
|
* }))
|
|
218
218
|
* ```
|
|
219
219
|
*/
|
|
220
|
-
select<TSelectObject extends SelectObject>(callback: (refs: RefsForContext<TContext>) => TSelectObject): QueryBuilder<WithResult<TContext, ResultTypeFromSelect<TSelectObject>>>;
|
|
220
|
+
select<TSelectObject extends SelectObject>(callback: (refs: RefsForContext<TContext>) => NonScalarSelectObject<TSelectObject>): QueryBuilder<WithResult<TContext, ResultTypeFromSelect<TSelectObject>>>;
|
|
221
|
+
select<TSelectValue extends ScalarSelectValue>(callback: (refs: RefsForContext<TContext>) => TSelectValue): QueryBuilder<WithResult<TContext, ResultTypeFromSelectValue<TSelectValue>>>;
|
|
221
222
|
/**
|
|
222
223
|
* Sort the query results by one or more columns
|
|
223
224
|
*
|
|
@@ -319,7 +320,7 @@ export declare class BaseQueryBuilder<TContext extends Context = Context> {
|
|
|
319
320
|
* // Get countries our users are from
|
|
320
321
|
* query
|
|
321
322
|
* .from({ users: usersCollection })
|
|
322
|
-
* .select(({users}) => users.country)
|
|
323
|
+
* .select(({users}) => ({ country: users.country }))
|
|
323
324
|
* .distinct()
|
|
324
325
|
* ```
|
|
325
326
|
*/
|