@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-query-collection.js","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { createCollection } from '../collection/index.js'\nimport { CollectionConfigBuilder } from './live/collection-config-builder.js'\nimport {\n getBuilderFromConfig,\n registerCollectionBuilder,\n} from './live/collection-registry.js'\nimport type { LiveQueryCollectionUtils } from './live/collection-config-builder.js'\nimport type { LiveQueryCollectionConfig } from './live/types.js'\nimport type {
|
|
1
|
+
{"version":3,"file":"live-query-collection.js","sources":["../../../src/query/live-query-collection.ts"],"sourcesContent":["import { createCollection } from '../collection/index.js'\nimport { CollectionConfigBuilder } from './live/collection-config-builder.js'\nimport {\n getBuilderFromConfig,\n registerCollectionBuilder,\n} from './live/collection-registry.js'\nimport type { LiveQueryCollectionUtils } from './live/collection-config-builder.js'\nimport type { LiveQueryCollectionConfig } from './live/types.js'\nimport type {\n ExtractContext,\n InitialQueryBuilder,\n QueryBuilder,\n} from './builder/index.js'\nimport type { Collection } from '../collection/index.js'\nimport type {\n CollectionConfig,\n CollectionConfigSingleRowOption,\n NonSingleResult,\n SingleResult,\n UtilsRecord,\n} from '../types.js'\nimport type {\n Context,\n RootObjectResultConstraint,\n RootQueryBuilder,\n RootQueryFn,\n RootQueryResult,\n} from './builder/types.js'\n\ntype CollectionConfigForContext<\n TContext extends Context,\n TResult extends object,\n TUtils extends UtilsRecord = {},\n> = TContext extends SingleResult\n ? CollectionConfigSingleRowOption<TResult, string | number, never, TUtils> &\n SingleResult\n : CollectionConfigSingleRowOption<TResult, string | number, never, TUtils> &\n NonSingleResult\n\ntype CollectionForContext<\n TContext extends Context,\n TResult extends object,\n TUtils extends UtilsRecord = {},\n> = TContext extends SingleResult\n ? Collection<TResult, string | number, TUtils> & SingleResult\n : Collection<TResult, string | number, TUtils> & NonSingleResult\n\n/**\n * Creates live query collection options for use with createCollection\n *\n * @example\n * ```typescript\n * const options = liveQueryCollectionOptions({\n * // id is optional - will auto-generate if not provided\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => eq(post.published, true))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * content: post.content,\n * })),\n * // getKey is optional - will use stream key if not provided\n * })\n *\n * const collection = createCollection(options)\n * ```\n *\n * @param config - Configuration options for the live query collection\n * @returns Collection options that can be passed to createCollection\n */\nexport function liveQueryCollectionOptions<\n TQuery extends QueryBuilder<any>,\n TContext extends Context = ExtractContext<TQuery>,\n TResult extends object = RootQueryResult<TContext>,\n>(\n config: LiveQueryCollectionConfig<TContext, TResult> & {\n query: RootQueryFn<TQuery> | RootQueryBuilder<TQuery>\n },\n): CollectionConfigForContext<TContext, TResult> & {\n utils: LiveQueryCollectionUtils\n} {\n const collectionConfigBuilder = new CollectionConfigBuilder<\n TContext,\n TResult\n >(config)\n return collectionConfigBuilder.getConfig() as CollectionConfigForContext<\n TContext,\n TResult\n > & { utils: LiveQueryCollectionUtils }\n}\n\n/**\n * Creates a live query collection directly\n *\n * @example\n * ```typescript\n * // Minimal usage - just pass a query function\n * const activeUsers = createLiveQueryCollection(\n * (q) => q\n * .from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ id: user.id, name: user.name }))\n * )\n *\n * // Full configuration with custom options\n * const searchResults = createLiveQueryCollection({\n * id: \"search-results\", // Custom ID (auto-generated if omitted)\n * query: (q) => q\n * .from({ post: postsCollection })\n * .where(({ post }) => like(post.title, `%${searchTerm}%`))\n * .select(({ post }) => ({\n * id: post.id,\n * title: post.title,\n * excerpt: post.excerpt,\n * })),\n * getKey: (item) => item.id, // Custom key function (uses stream key if omitted)\n * utils: {\n * updateSearchTerm: (newTerm: string) => {\n * // Custom utility functions\n * }\n * }\n * })\n * ```\n */\n\n// Overload 1: Accept just the query function\nexport function createLiveQueryCollection<\n TQueryFn extends (q: InitialQueryBuilder) => QueryBuilder<any>,\n TQuery extends QueryBuilder<any> = ReturnType<TQueryFn>,\n>(\n query: TQueryFn & RootQueryFn<TQuery>,\n): CollectionForContext<\n ExtractContext<TQuery>,\n RootQueryResult<ExtractContext<TQuery>>\n> & {\n utils: LiveQueryCollectionUtils\n}\n\n// Overload 2: Accept full config object with optional utilities\nexport function createLiveQueryCollection<\n TQuery extends QueryBuilder<any>,\n TContext extends Context = ExtractContext<TQuery>,\n TUtils extends UtilsRecord = {},\n>(\n config: LiveQueryCollectionConfig<TContext, RootQueryResult<TContext>> & {\n query: RootQueryFn<TQuery> | RootQueryBuilder<TQuery>\n utils?: TUtils\n },\n): CollectionForContext<TContext, RootQueryResult<TContext>> & {\n utils: LiveQueryCollectionUtils & TUtils\n}\n\n// Implementation\nexport function createLiveQueryCollection<\n TContext extends Context,\n TResult extends object = RootQueryResult<TContext>,\n TUtils extends UtilsRecord = {},\n>(\n configOrQuery:\n | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })\n | ((\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> & RootObjectResultConstraint<TContext>),\n): CollectionForContext<TContext, TResult> & {\n utils: LiveQueryCollectionUtils & TUtils\n} {\n // Determine if the argument is a function (query) or a config object\n if (typeof configOrQuery === `function`) {\n // Simple query function case\n const config: LiveQueryCollectionConfig<TContext, TResult> = {\n query: configOrQuery as (\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> & RootObjectResultConstraint<TContext>,\n }\n // The implementation accepts both overload shapes, but TypeScript cannot\n // preserve the overload-specific query-builder inference through this branch.\n const options = liveQueryCollectionOptions(config as any)\n return bridgeToCreateCollection(options) as CollectionForContext<\n TContext,\n TResult\n > & { utils: LiveQueryCollectionUtils & TUtils }\n } else {\n // Config object case\n const config = configOrQuery as LiveQueryCollectionConfig<\n TContext,\n TResult\n > & { utils?: TUtils }\n // Same overload implementation limitation as above: the config has already\n // been validated by the public signatures, but the branch loses that precision.\n const options = liveQueryCollectionOptions(config as any)\n\n // Merge custom utils if provided, preserving the getBuilder() method for dependency tracking\n if (config.utils) {\n options.utils = { ...options.utils, ...config.utils }\n }\n\n return bridgeToCreateCollection(options) as CollectionForContext<\n TContext,\n TResult\n > & { utils: LiveQueryCollectionUtils & TUtils }\n }\n}\n\n/**\n * Bridge function that handles the type compatibility between query2's TResult\n * and core collection's output type without exposing ugly type assertions to users\n */\nfunction bridgeToCreateCollection<\n TResult extends object,\n TUtils extends UtilsRecord = {},\n>(\n options: CollectionConfig<TResult> & { utils: TUtils },\n): Collection<TResult, string | number, TUtils> {\n const collection = createCollection(options as any) as unknown as Collection<\n TResult,\n string | number,\n LiveQueryCollectionUtils\n >\n\n const builder = getBuilderFromConfig(options)\n if (builder) {\n registerCollectionBuilder(collection, builder)\n }\n\n return collection as unknown as Collection<TResult, string | number, TUtils>\n}\n"],"names":[],"mappings":";;;AAuEO,SAAS,2BAKd,QAKA;AACA,QAAM,0BAA0B,IAAI,wBAGlC,MAAM;AACR,SAAO,wBAAwB,UAAA;AAIjC;AAgEO,SAAS,0BAKd,eAOA;AAEA,MAAI,OAAO,kBAAkB,YAAY;AAEvC,UAAM,SAAuD;AAAA,MAC3D,OAAO;AAAA,IAAA;AAMT,UAAM,UAAU,2BAA2B,MAAa;AACxD,WAAO,yBAAyB,OAAO;AAAA,EAIzC,OAAO;AAEL,UAAM,SAAS;AAMf,UAAM,UAAU,2BAA2B,MAAa;AAGxD,QAAI,OAAO,OAAO;AAChB,cAAQ,QAAQ,EAAE,GAAG,QAAQ,OAAO,GAAG,OAAO,MAAA;AAAA,IAChD;AAEA,WAAO,yBAAyB,OAAO;AAAA,EAIzC;AACF;AAMA,SAAS,yBAIP,SAC8C;AAC9C,QAAM,aAAa,iBAAiB,OAAc;AAMlD,QAAM,UAAU,qBAAqB,OAAO;AAC5C,MAAI,SAAS;AACX,8BAA0B,YAAY,OAAO;AAAA,EAC/C;AAEA,SAAO;AACT;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { InitialQueryBuilder, QueryBuilder } from './builder/index.js';
|
|
2
|
-
import { Context, InferResultType } from './builder/types.js';
|
|
1
|
+
import { ExtractContext, InitialQueryBuilder, QueryBuilder } from './builder/index.js';
|
|
2
|
+
import { Context, InferResultType, RootObjectResultConstraint, RootQueryBuilder, RootQueryFn } from './builder/types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Configuration options for queryOnce
|
|
5
5
|
*/
|
|
@@ -7,7 +7,7 @@ export interface QueryOnceConfig<TContext extends Context> {
|
|
|
7
7
|
/**
|
|
8
8
|
* Query builder function that defines the query
|
|
9
9
|
*/
|
|
10
|
-
query: ((q: InitialQueryBuilder) => QueryBuilder<TContext>) | QueryBuilder<TContext
|
|
10
|
+
query: ((q: InitialQueryBuilder) => QueryBuilder<TContext> & RootObjectResultConstraint<TContext>) | (QueryBuilder<TContext> & RootObjectResultConstraint<TContext>);
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* Executes a one-shot query and returns the results as an array.
|
|
@@ -37,7 +37,7 @@ export interface QueryOnceConfig<TContext extends Context> {
|
|
|
37
37
|
* )
|
|
38
38
|
* ```
|
|
39
39
|
*/
|
|
40
|
-
export declare function queryOnce<
|
|
40
|
+
export declare function queryOnce<TQueryFn extends (q: InitialQueryBuilder) => QueryBuilder<any>, TQuery extends QueryBuilder<any> = ReturnType<TQueryFn>>(queryFn: TQueryFn & RootQueryFn<TQuery>): Promise<InferResultType<ExtractContext<TQuery>>>;
|
|
41
41
|
/**
|
|
42
42
|
* Executes a one-shot query using a configuration object.
|
|
43
43
|
*
|
|
@@ -54,4 +54,6 @@ export declare function queryOnce<TContext extends Context>(queryFn: (q: Initial
|
|
|
54
54
|
* })
|
|
55
55
|
* ```
|
|
56
56
|
*/
|
|
57
|
-
export declare function queryOnce<
|
|
57
|
+
export declare function queryOnce<TQuery extends QueryBuilder<any>>(config: QueryOnceConfig<ExtractContext<TQuery>> & {
|
|
58
|
+
query: RootQueryFn<TQuery> | RootQueryBuilder<TQuery>;
|
|
59
|
+
}): Promise<InferResultType<ExtractContext<TQuery>>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-once.js","sources":["../../../src/query/query-once.ts"],"sourcesContent":["import { createLiveQueryCollection } from './live-query-collection.js'\nimport type {
|
|
1
|
+
{"version":3,"file":"query-once.js","sources":["../../../src/query/query-once.ts"],"sourcesContent":["import { createLiveQueryCollection } from './live-query-collection.js'\nimport type {\n ExtractContext,\n InitialQueryBuilder,\n QueryBuilder,\n} from './builder/index.js'\nimport type {\n Context,\n InferResultType,\n RootObjectResultConstraint,\n RootQueryBuilder,\n RootQueryFn,\n} from './builder/types.js'\n\n/**\n * Configuration options for queryOnce\n */\nexport interface QueryOnceConfig<TContext extends Context> {\n /**\n * Query builder function that defines the query\n */\n query:\n | ((\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> & RootObjectResultConstraint<TContext>)\n | (QueryBuilder<TContext> & RootObjectResultConstraint<TContext>)\n // Future: timeout, signal, etc.\n}\n\n// Overload 1: Simple query function returning array (non-single result)\n/**\n * Executes a one-shot query and returns the results as an array.\n *\n * This function creates a live query collection, preloads it, extracts the results,\n * and automatically cleans up the collection. It's ideal for:\n * - AI/LLM context building\n * - Data export\n * - Background processing\n * - Testing\n *\n * @param queryFn - A function that receives the query builder and returns a query\n * @returns A promise that resolves to an array of query results\n *\n * @example\n * ```typescript\n * // Basic query\n * const users = await queryOnce((q) =>\n * q.from({ user: usersCollection })\n * )\n *\n * // With filtering and projection\n * const activeUserNames = await queryOnce((q) =>\n * q.from({ user: usersCollection })\n * .where(({ user }) => eq(user.active, true))\n * .select(({ user }) => ({ name: user.name }))\n * )\n * ```\n */\nexport function queryOnce<\n TQueryFn extends (q: InitialQueryBuilder) => QueryBuilder<any>,\n TQuery extends QueryBuilder<any> = ReturnType<TQueryFn>,\n>(\n queryFn: TQueryFn & RootQueryFn<TQuery>,\n): Promise<InferResultType<ExtractContext<TQuery>>>\n\n// Overload 2: Config object form returning array (non-single result)\n/**\n * Executes a one-shot query using a configuration object.\n *\n * @param config - Configuration object with the query function\n * @returns A promise that resolves to an array of query results\n *\n * @example\n * ```typescript\n * const recentOrders = await queryOnce({\n * query: (q) =>\n * q.from({ order: ordersCollection })\n * .orderBy(({ order }) => desc(order.createdAt))\n * .limit(100),\n * })\n * ```\n */\nexport function queryOnce<TQuery extends QueryBuilder<any>>(\n config: QueryOnceConfig<ExtractContext<TQuery>> & {\n query: RootQueryFn<TQuery> | RootQueryBuilder<TQuery>\n },\n): Promise<InferResultType<ExtractContext<TQuery>>>\n\n// Implementation\nexport async function queryOnce<TContext extends Context>(\n configOrQuery:\n | QueryOnceConfig<TContext>\n | ((\n q: InitialQueryBuilder,\n ) => QueryBuilder<TContext> & RootObjectResultConstraint<TContext>),\n): Promise<InferResultType<TContext>> {\n // Normalize input\n const config: QueryOnceConfig<TContext> =\n typeof configOrQuery === `function`\n ? { query: configOrQuery }\n : configOrQuery\n\n const query = (q: InitialQueryBuilder) => {\n const queryConfig = config.query\n return typeof queryConfig === `function` ? queryConfig(q) : queryConfig\n }\n\n // Create collection with minimal GC time; preload handles sync start\n const collection = createLiveQueryCollection({\n query,\n gcTime: 1, // Cleanup in next tick when no subscribers (0 disables GC)\n })\n\n try {\n // Wait for initial data load\n await collection.preload()\n\n // Check if this is a single-result query (findOne was called)\n const isSingleResult =\n (collection.config as { singleResult?: boolean }).singleResult === true\n\n // Extract and return results\n if (isSingleResult) {\n const first = collection.values().next().value as\n | InferResultType<TContext>\n | undefined\n return first as InferResultType<TContext>\n }\n return collection.toArray as unknown as InferResultType<TContext>\n } finally {\n // Always cleanup, even on error\n await collection.cleanup()\n }\n}\n"],"names":[],"mappings":";AAyFA,eAAsB,UACpB,eAKoC;AAEpC,QAAM,SACJ,OAAO,kBAAkB,aACrB,EAAE,OAAO,kBACT;AAEN,QAAM,QAAQ,CAAC,MAA2B;AACxC,UAAM,cAAc,OAAO;AAC3B,WAAO,OAAO,gBAAgB,aAAa,YAAY,CAAC,IAAI;AAAA,EAC9D;AAGA,QAAM,aAAa,0BAA0B;AAAA,IAC3C;AAAA,IACA,QAAQ;AAAA;AAAA,EAAA,CACT;AAED,MAAI;AAEF,UAAM,WAAW,QAAA;AAGjB,UAAM,iBACH,WAAW,OAAsC,iBAAiB;AAGrE,QAAI,gBAAgB;AAClB,YAAM,QAAQ,WAAW,OAAA,EAAS,OAAO;AAGzC,aAAO;AAAA,IACT;AACA,WAAO,WAAW;AAAA,EACpB,UAAA;AAEE,UAAM,WAAW,QAAA;AAAA,EACnB;AACF;"}
|
|
@@ -46,8 +46,7 @@ class DeduplicatedLoadSubset {
|
|
|
46
46
|
} else {
|
|
47
47
|
const capturedGeneration = this.generation;
|
|
48
48
|
const inflightEntry = {
|
|
49
|
-
options:
|
|
50
|
-
// Store load options for subset matching of in-flight requests
|
|
49
|
+
options: trackingOptions,
|
|
51
50
|
promise: resultPromise.then((result) => {
|
|
52
51
|
if (capturedGeneration === this.generation) {
|
|
53
52
|
this.updateTracking(trackingOptions);
|
|
@@ -103,7 +102,14 @@ class DeduplicatedLoadSubset {
|
|
|
103
102
|
}
|
|
104
103
|
}
|
|
105
104
|
function cloneOptions(options) {
|
|
106
|
-
return {
|
|
105
|
+
return {
|
|
106
|
+
...options,
|
|
107
|
+
orderBy: options.orderBy?.map((clause) => ({
|
|
108
|
+
...clause,
|
|
109
|
+
compareOptions: { ...clause.compareOptions }
|
|
110
|
+
})),
|
|
111
|
+
cursor: options.cursor ? { ...options.cursor } : void 0
|
|
112
|
+
};
|
|
107
113
|
}
|
|
108
114
|
export {
|
|
109
115
|
DeduplicatedLoadSubset,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subset-dedupe.js","sources":["../../../src/query/subset-dedupe.ts"],"sourcesContent":["import {\n isPredicateSubset,\n isWhereSubset,\n minusWherePredicates,\n unionWherePredicates,\n} from './predicate-utils.js'\nimport type { BasicExpression } from './ir.js'\nimport type { LoadSubsetOptions } from '../types.js'\n\n/**\n * Deduplicated wrapper for a loadSubset function.\n * Tracks what data has been loaded and avoids redundant calls by applying\n * subset logic to predicates.\n *\n * @param opts - The options for the DeduplicatedLoadSubset\n * @param opts.loadSubset - The underlying loadSubset function to wrap\n * @param opts.onDeduplicate - An optional callback function that is invoked when a loadSubset call is deduplicated.\n * If the call is deduplicated because the requested data is being loaded by an inflight request,\n * then this callback is invoked when the inflight request completes successfully and the data is fully loaded.\n * This callback is useful if you need to track rows per query, in which case you can't ignore deduplicated calls\n * because you need to know which rows were loaded for each query.\n * @example\n * const dedupe = new DeduplicatedLoadSubset({ loadSubset: myLoadSubset, onDeduplicate: (opts) => console.log(`Call was deduplicated:`, opts) })\n *\n * // First call - fetches data\n * await dedupe.loadSubset({ where: gt(ref('age'), val(10)) })\n *\n * // Second call - subset of first, returns true immediately\n * await dedupe.loadSubset({ where: gt(ref('age'), val(20)) })\n *\n * // Clear state to start fresh\n * dedupe.reset()\n */\nexport class DeduplicatedLoadSubset {\n // The underlying loadSubset function to wrap\n private readonly _loadSubset: (\n options: LoadSubsetOptions,\n ) => true | Promise<void>\n\n // An optional callback function that is invoked when a loadSubset call is deduplicated.\n private readonly onDeduplicate:\n | ((options: LoadSubsetOptions) => void)\n | undefined\n\n // Combined where predicate for all unlimited calls (no limit)\n private unlimitedWhere: BasicExpression<boolean> | undefined = undefined\n\n // Flag to track if we've loaded all data (unlimited call with no where clause)\n private hasLoadedAllData = false\n\n // List of all limited calls (with limit, possibly with orderBy)\n // We clone options before storing to prevent mutation of stored predicates\n private limitedCalls: Array<LoadSubsetOptions> = []\n\n // Track in-flight calls to prevent concurrent duplicate requests\n // We store both the options and the promise so we can apply subset logic\n private inflightCalls: Array<{\n options: LoadSubsetOptions\n promise: Promise<void>\n }> = []\n\n // Generation counter to invalidate in-flight requests after reset()\n // When reset() is called, this increments, and any in-flight completion handlers\n // check if their captured generation matches before updating tracking state\n private generation = 0\n\n constructor(opts: {\n loadSubset: (options: LoadSubsetOptions) => true | Promise<void>\n onDeduplicate?: (options: LoadSubsetOptions) => void\n }) {\n this._loadSubset = opts.loadSubset\n this.onDeduplicate = opts.onDeduplicate\n }\n\n /**\n * Load a subset of data, with automatic deduplication based on previously\n * loaded predicates and in-flight requests.\n *\n * This method is auto-bound, so it can be safely passed as a callback without\n * losing its `this` context (e.g., `loadSubset: dedupe.loadSubset` in a sync config).\n *\n * @param options - The predicate options (where, orderBy, limit)\n * @returns true if data is already loaded, or a Promise that resolves when data is loaded\n */\n loadSubset = (options: LoadSubsetOptions): true | Promise<void> => {\n // If we've loaded all data, everything is covered\n if (this.hasLoadedAllData) {\n this.onDeduplicate?.(options)\n return true\n }\n\n // Check against unlimited combined predicate\n // If we've loaded all data matching a where clause, we don't need to refetch subsets\n if (this.unlimitedWhere !== undefined && options.where !== undefined) {\n if (isWhereSubset(options.where, this.unlimitedWhere)) {\n this.onDeduplicate?.(options)\n return true // Data already loaded via unlimited call\n }\n }\n\n // Check against limited calls\n if (options.limit !== undefined) {\n const alreadyLoaded = this.limitedCalls.some((loaded) =>\n isPredicateSubset(options, loaded),\n )\n\n if (alreadyLoaded) {\n this.onDeduplicate?.(options)\n return true // Already loaded\n }\n }\n\n // Check against in-flight calls using the same subset logic as resolved calls\n // This prevents duplicate requests when concurrent calls have subset relationships\n const matchingInflight = this.inflightCalls.find((inflight) =>\n isPredicateSubset(options, inflight.options),\n )\n\n if (matchingInflight !== undefined) {\n // An in-flight call will load data that covers this request\n // Return the same promise so this caller waits for the data to load\n // The in-flight promise already handles tracking updates when it completes\n const prom = matchingInflight.promise\n // Call `onDeduplicate` when the inflight request has loaded the data\n prom.then(() => this.onDeduplicate?.(options)).catch() // ignore errors\n return prom\n }\n\n // Not fully covered by existing data — load the missing subset.\n // We need two clones: trackingOptions preserves the original predicate for\n // accurate tracking (e.g., where=undefined means \"all data\"), while loadOptions\n // may be narrowed with a difference expression for the actual backend request.\n const trackingOptions = cloneOptions(options)\n const loadOptions = cloneOptions(options)\n if (this.unlimitedWhere !== undefined && options.limit === undefined) {\n // Compute difference to get only the missing data\n // We can only do this for unlimited queries\n // and we can only remove data that was loaded from unlimited queries\n // because with limited queries we have no way to express that we already loaded part of the matching data\n loadOptions.where =\n minusWherePredicates(loadOptions.where, this.unlimitedWhere) ??\n loadOptions.where\n }\n\n // Call underlying loadSubset to load the missing data\n const resultPromise = this._loadSubset(loadOptions)\n\n // Handle both sync (true) and async (Promise<void>) return values\n if (resultPromise === true) {\n // Sync return - update tracking with the original predicate\n this.updateTracking(trackingOptions)\n return true\n } else {\n // Async return - track the promise and update tracking after it resolves\n\n // Capture the current generation - this lets us detect if reset() was called\n // while this request was in-flight, so we can skip updating tracking state\n const capturedGeneration = this.generation\n\n // We need to create a reference to the in-flight entry so we can remove it later\n const inflightEntry = {\n options: loadOptions, // Store load options for subset matching of in-flight requests\n promise: resultPromise\n .then((result) => {\n // Only update tracking if this request is still from the current generation\n // If reset() was called, the generation will have incremented and we should\n // not repopulate the state that was just cleared\n if (capturedGeneration === this.generation) {\n this.updateTracking(trackingOptions)\n }\n return result\n })\n .finally(() => {\n // Always remove from in-flight array on completion OR rejection\n // This ensures failed requests can be retried instead of being cached forever\n const index = this.inflightCalls.indexOf(inflightEntry)\n if (index !== -1) {\n this.inflightCalls.splice(index, 1)\n }\n }),\n }\n\n // Store the in-flight entry so concurrent subset calls can wait for it\n this.inflightCalls.push(inflightEntry)\n return inflightEntry.promise\n }\n }\n\n /**\n * Reset all tracking state.\n * Clears the history of loaded predicates and in-flight calls.\n * Use this when you want to start fresh, for example after clearing the underlying data store.\n *\n * Note: Any in-flight requests will still complete, but they will not update the tracking\n * state after the reset. This prevents old requests from repopulating cleared state.\n */\n reset(): void {\n this.unlimitedWhere = undefined\n this.hasLoadedAllData = false\n this.limitedCalls = []\n this.inflightCalls = []\n // Increment generation to invalidate any in-flight completion handlers\n // This ensures requests that were started before reset() don't repopulate the state\n this.generation++\n }\n\n private updateTracking(options: LoadSubsetOptions): void {\n // Update tracking based on whether this was a limited or unlimited call\n if (options.limit === undefined) {\n // Unlimited call - update combined where predicate\n // We ignore orderBy for unlimited calls as mentioned in requirements\n if (options.where === undefined) {\n // No where clause = all data loaded\n this.hasLoadedAllData = true\n this.unlimitedWhere = undefined\n this.limitedCalls = []\n this.inflightCalls = []\n } else if (this.unlimitedWhere === undefined) {\n this.unlimitedWhere = options.where\n } else {\n this.unlimitedWhere = unionWherePredicates([\n this.unlimitedWhere,\n options.where,\n ])\n }\n } else {\n // Limited call - add to list for future subset checks\n // Options are already cloned by caller to prevent mutation issues\n this.limitedCalls.push(options)\n }\n }\n}\n\n/**\n * Clones a LoadSubsetOptions object to prevent mutation of stored predicates.\n * This is crucial because callers often reuse the same options object and mutate\n * properties like limit or where between calls. Without cloning, our stored history\n * would reflect the mutated values rather than what was actually loaded.\n */\nexport function cloneOptions(options: LoadSubsetOptions): LoadSubsetOptions {\n return { ...options }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"subset-dedupe.js","sources":["../../../src/query/subset-dedupe.ts"],"sourcesContent":["import {\n isPredicateSubset,\n isWhereSubset,\n minusWherePredicates,\n unionWherePredicates,\n} from './predicate-utils.js'\nimport type { BasicExpression } from './ir.js'\nimport type { LoadSubsetOptions } from '../types.js'\n\n/**\n * Deduplicated wrapper for a loadSubset function.\n * Tracks what data has been loaded and avoids redundant calls by applying\n * subset logic to predicates.\n *\n * @param opts - The options for the DeduplicatedLoadSubset\n * @param opts.loadSubset - The underlying loadSubset function to wrap\n * @param opts.onDeduplicate - An optional callback function that is invoked when a loadSubset call is deduplicated.\n * If the call is deduplicated because the requested data is being loaded by an inflight request,\n * then this callback is invoked when the inflight request completes successfully and the data is fully loaded.\n * This callback is useful if you need to track rows per query, in which case you can't ignore deduplicated calls\n * because you need to know which rows were loaded for each query.\n * @example\n * const dedupe = new DeduplicatedLoadSubset({ loadSubset: myLoadSubset, onDeduplicate: (opts) => console.log(`Call was deduplicated:`, opts) })\n *\n * // First call - fetches data\n * await dedupe.loadSubset({ where: gt(ref('age'), val(10)) })\n *\n * // Second call - subset of first, returns true immediately\n * await dedupe.loadSubset({ where: gt(ref('age'), val(20)) })\n *\n * // Clear state to start fresh\n * dedupe.reset()\n */\nexport class DeduplicatedLoadSubset {\n // The underlying loadSubset function to wrap\n private readonly _loadSubset: (\n options: LoadSubsetOptions,\n ) => true | Promise<void>\n\n // An optional callback function that is invoked when a loadSubset call is deduplicated.\n private readonly onDeduplicate:\n | ((options: LoadSubsetOptions) => void)\n | undefined\n\n // Combined where predicate for all unlimited calls (no limit)\n private unlimitedWhere: BasicExpression<boolean> | undefined = undefined\n\n // Flag to track if we've loaded all data (unlimited call with no where clause)\n private hasLoadedAllData = false\n\n // List of all limited calls (with limit, possibly with orderBy)\n // We clone options before storing to prevent mutation of stored predicates\n private limitedCalls: Array<LoadSubsetOptions> = []\n\n // Track in-flight calls to prevent concurrent duplicate requests\n // We store both the options and the promise so we can apply subset logic\n private inflightCalls: Array<{\n options: LoadSubsetOptions\n promise: Promise<void>\n }> = []\n\n // Generation counter to invalidate in-flight requests after reset()\n // When reset() is called, this increments, and any in-flight completion handlers\n // check if their captured generation matches before updating tracking state\n private generation = 0\n\n constructor(opts: {\n loadSubset: (options: LoadSubsetOptions) => true | Promise<void>\n onDeduplicate?: (options: LoadSubsetOptions) => void\n }) {\n this._loadSubset = opts.loadSubset\n this.onDeduplicate = opts.onDeduplicate\n }\n\n /**\n * Load a subset of data, with automatic deduplication based on previously\n * loaded predicates and in-flight requests.\n *\n * This method is auto-bound, so it can be safely passed as a callback without\n * losing its `this` context (e.g., `loadSubset: dedupe.loadSubset` in a sync config).\n *\n * @param options - The predicate options (where, orderBy, limit)\n * @returns true if data is already loaded, or a Promise that resolves when data is loaded\n */\n loadSubset = (options: LoadSubsetOptions): true | Promise<void> => {\n // If we've loaded all data, everything is covered\n if (this.hasLoadedAllData) {\n this.onDeduplicate?.(options)\n return true\n }\n\n // Check against unlimited combined predicate\n // If we've loaded all data matching a where clause, we don't need to refetch subsets\n if (this.unlimitedWhere !== undefined && options.where !== undefined) {\n if (isWhereSubset(options.where, this.unlimitedWhere)) {\n this.onDeduplicate?.(options)\n return true // Data already loaded via unlimited call\n }\n }\n\n // Check against limited calls\n if (options.limit !== undefined) {\n const alreadyLoaded = this.limitedCalls.some((loaded) =>\n isPredicateSubset(options, loaded),\n )\n\n if (alreadyLoaded) {\n this.onDeduplicate?.(options)\n return true // Already loaded\n }\n }\n\n // Check against in-flight calls using the same subset logic as resolved calls\n // This prevents duplicate requests when concurrent calls have subset relationships\n const matchingInflight = this.inflightCalls.find((inflight) =>\n isPredicateSubset(options, inflight.options),\n )\n\n if (matchingInflight !== undefined) {\n // An in-flight call will load data that covers this request\n // Return the same promise so this caller waits for the data to load\n // The in-flight promise already handles tracking updates when it completes\n const prom = matchingInflight.promise\n // Call `onDeduplicate` when the inflight request has loaded the data\n prom.then(() => this.onDeduplicate?.(options)).catch() // ignore errors\n return prom\n }\n\n // Preserve the original request for tracking and in-flight dedupe, but allow\n // the backend request to be narrowed to only the missing subset.\n const trackingOptions = cloneOptions(options)\n const loadOptions = cloneOptions(options)\n if (this.unlimitedWhere !== undefined && options.limit === undefined) {\n // Compute difference to get only the missing data\n // We can only do this for unlimited queries\n // and we can only remove data that was loaded from unlimited queries\n // because with limited queries we have no way to express that we already loaded part of the matching data\n loadOptions.where =\n minusWherePredicates(loadOptions.where, this.unlimitedWhere) ??\n loadOptions.where\n }\n\n // Call underlying loadSubset to load the missing data\n const resultPromise = this._loadSubset(loadOptions)\n\n // Handle both sync (true) and async (Promise<void>) return values\n if (resultPromise === true) {\n this.updateTracking(trackingOptions)\n return true\n } else {\n // Async return - track the promise and update tracking after it resolves\n\n // Capture the current generation - this lets us detect if reset() was called\n // while this request was in-flight, so we can skip updating tracking state\n const capturedGeneration = this.generation\n\n // We need to create a reference to the in-flight entry so we can remove it later\n const inflightEntry = {\n options: trackingOptions,\n promise: resultPromise\n .then((result) => {\n // Only update tracking if this request is still from the current generation\n // If reset() was called, the generation will have incremented and we should\n // not repopulate the state that was just cleared\n if (capturedGeneration === this.generation) {\n this.updateTracking(trackingOptions)\n }\n return result\n })\n .finally(() => {\n // Always remove from in-flight array on completion OR rejection\n // This ensures failed requests can be retried instead of being cached forever\n const index = this.inflightCalls.indexOf(inflightEntry)\n if (index !== -1) {\n this.inflightCalls.splice(index, 1)\n }\n }),\n }\n\n // Store the in-flight entry so concurrent subset calls can wait for it\n this.inflightCalls.push(inflightEntry)\n return inflightEntry.promise\n }\n }\n\n /**\n * Reset all tracking state.\n * Clears the history of loaded predicates and in-flight calls.\n * Use this when you want to start fresh, for example after clearing the underlying data store.\n *\n * Note: Any in-flight requests will still complete, but they will not update the tracking\n * state after the reset. This prevents old requests from repopulating cleared state.\n */\n reset(): void {\n this.unlimitedWhere = undefined\n this.hasLoadedAllData = false\n this.limitedCalls = []\n this.inflightCalls = []\n // Increment generation to invalidate any in-flight completion handlers\n // This ensures requests that were started before reset() don't repopulate the state\n this.generation++\n }\n\n private updateTracking(options: LoadSubsetOptions): void {\n // Update tracking based on whether this was a limited or unlimited call\n if (options.limit === undefined) {\n // Unlimited call - update combined where predicate\n // We ignore orderBy for unlimited calls as mentioned in requirements\n if (options.where === undefined) {\n // No where clause = all data loaded\n this.hasLoadedAllData = true\n this.unlimitedWhere = undefined\n this.limitedCalls = []\n this.inflightCalls = []\n } else if (this.unlimitedWhere === undefined) {\n this.unlimitedWhere = options.where\n } else {\n this.unlimitedWhere = unionWherePredicates([\n this.unlimitedWhere,\n options.where,\n ])\n }\n } else {\n // Limited call - add to list for future subset checks\n // Options are already cloned by caller to prevent mutation issues\n this.limitedCalls.push(options)\n }\n }\n}\n\n/**\n * Clones a LoadSubsetOptions object to prevent mutation of stored predicates.\n * This is crucial because callers often reuse the same options object and mutate\n * properties like limit or where between calls. Without cloning, our stored history\n * would reflect the mutated values rather than what was actually loaded.\n */\nexport function cloneOptions(options: LoadSubsetOptions): LoadSubsetOptions {\n return {\n ...options,\n orderBy: options.orderBy?.map((clause) => ({\n ...clause,\n compareOptions: { ...clause.compareOptions },\n })),\n cursor: options.cursor ? { ...options.cursor } : undefined,\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -4,7 +4,9 @@ import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
|
4
4
|
import { Transaction } from './transactions.js';
|
|
5
5
|
import { BasicExpression, OrderBy } from './query/ir.js';
|
|
6
6
|
import { EventEmitter } from './event-emitter.js';
|
|
7
|
+
import { IndexConstructor } from './indexes/base-index.js';
|
|
7
8
|
import { SingleRowRefProxy } from './query/builder/ref-proxy.js';
|
|
9
|
+
import { WithVirtualProps } from './virtual-props.js';
|
|
8
10
|
/**
|
|
9
11
|
* Interface for a collection-like object that provides the necessary methods
|
|
10
12
|
* for the change events system to work
|
|
@@ -259,6 +261,7 @@ export interface SyncConfig<T extends object = Record<string, unknown>, TKey ext
|
|
|
259
261
|
commit: () => void;
|
|
260
262
|
markReady: () => void;
|
|
261
263
|
truncate: () => void;
|
|
264
|
+
metadata?: SyncMetadataApi<TKey>;
|
|
262
265
|
}) => void | CleanupFn | SyncConfigRes;
|
|
263
266
|
/**
|
|
264
267
|
* Get the sync metadata for insert operations
|
|
@@ -274,6 +277,22 @@ export interface SyncConfig<T extends object = Record<string, unknown>, TKey ext
|
|
|
274
277
|
*/
|
|
275
278
|
rowUpdateMode?: `partial` | `full`;
|
|
276
279
|
}
|
|
280
|
+
export interface SyncMetadataApi<TKey extends string | number = string | number> {
|
|
281
|
+
row: {
|
|
282
|
+
get: (key: TKey) => unknown | undefined;
|
|
283
|
+
set: (key: TKey, metadata: unknown) => void;
|
|
284
|
+
delete: (key: TKey) => void;
|
|
285
|
+
};
|
|
286
|
+
collection: {
|
|
287
|
+
get: (key: string) => unknown | undefined;
|
|
288
|
+
set: (key: string, value: unknown) => void;
|
|
289
|
+
delete: (key: string) => void;
|
|
290
|
+
list: (prefix?: string) => ReadonlyArray<{
|
|
291
|
+
key: string;
|
|
292
|
+
value: unknown;
|
|
293
|
+
}>;
|
|
294
|
+
};
|
|
295
|
+
}
|
|
277
296
|
export interface ChangeMessage<T extends object = Record<string, unknown>, TKey extends string | number = string | number> {
|
|
278
297
|
key: TKey;
|
|
279
298
|
value: T;
|
|
@@ -390,12 +409,27 @@ export interface BaseCollectionConfig<T extends object = Record<string, unknown>
|
|
|
390
409
|
/**
|
|
391
410
|
* Auto-indexing mode for the collection.
|
|
392
411
|
* When enabled, indexes will be automatically created for simple where expressions.
|
|
393
|
-
* @default "
|
|
412
|
+
* @default "off"
|
|
394
413
|
* @description
|
|
395
|
-
* - "off": No automatic indexing
|
|
396
|
-
* - "eager": Automatically create indexes for simple where expressions in subscribeChanges
|
|
414
|
+
* - "off": No automatic indexing (default). Use explicit indexes for better bundle size.
|
|
415
|
+
* - "eager": Automatically create indexes for simple where expressions in subscribeChanges.
|
|
416
|
+
* Requires setting defaultIndexType.
|
|
397
417
|
*/
|
|
398
418
|
autoIndex?: `off` | `eager`;
|
|
419
|
+
/**
|
|
420
|
+
* Default index type to use when creating indexes without an explicit type.
|
|
421
|
+
* Required for auto-indexing. Import from '@tanstack/db'.
|
|
422
|
+
* @example
|
|
423
|
+
* ```ts
|
|
424
|
+
* import { BasicIndex } from '@tanstack/db'
|
|
425
|
+
* const collection = createCollection({
|
|
426
|
+
* defaultIndexType: BasicIndex,
|
|
427
|
+
* autoIndex: 'eager',
|
|
428
|
+
* // ...
|
|
429
|
+
* })
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
defaultIndexType?: IndexConstructor<TKey>;
|
|
399
433
|
/**
|
|
400
434
|
* Optional function to compare two items.
|
|
401
435
|
* This is used to order the items in the collection.
|
|
@@ -569,7 +603,7 @@ export type MaybeSingleResult = {
|
|
|
569
603
|
singleResult?: true;
|
|
570
604
|
};
|
|
571
605
|
export type CollectionConfigSingleRowOption<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = never, TUtils extends UtilsRecord = {}> = CollectionConfig<T, TKey, TSchema, TUtils> & MaybeSingleResult;
|
|
572
|
-
export type ChangesPayload<T extends object = Record<string, unknown
|
|
606
|
+
export type ChangesPayload<T extends object = Record<string, unknown>, TKey extends string | number = string | number> = Array<ChangeMessage<WithVirtualProps<T, TKey>, TKey>>;
|
|
573
607
|
/**
|
|
574
608
|
* An input row from a collection
|
|
575
609
|
*/
|
|
@@ -602,7 +636,7 @@ export type NamespacedAndKeyedStream = IStreamBuilder<KeyedNamespacedRow>;
|
|
|
602
636
|
/**
|
|
603
637
|
* Options for subscribing to collection changes
|
|
604
638
|
*/
|
|
605
|
-
export interface SubscribeChangesOptions<T extends object = Record<string, unknown
|
|
639
|
+
export interface SubscribeChangesOptions<T extends object = Record<string, unknown>, TKey extends string | number = string | number> {
|
|
606
640
|
/** Whether to include the current state as initial changes */
|
|
607
641
|
includeInitialState?: boolean;
|
|
608
642
|
/**
|
|
@@ -619,7 +653,7 @@ export interface SubscribeChangesOptions<T extends object = Record<string, unkno
|
|
|
619
653
|
* })
|
|
620
654
|
* ```
|
|
621
655
|
*/
|
|
622
|
-
where?: (row: SingleRowRefProxy<T
|
|
656
|
+
where?: (row: SingleRowRefProxy<WithVirtualProps<T, TKey>>) => any;
|
|
623
657
|
/** Pre-compiled expression for filtering changes */
|
|
624
658
|
whereExpression?: BasicExpression<boolean>;
|
|
625
659
|
/**
|
|
@@ -645,7 +679,7 @@ export interface SubscribeChangesOptions<T extends object = Record<string, unkno
|
|
|
645
679
|
*/
|
|
646
680
|
onLoadSubsetResult?: (result: Promise<void> | true) => void;
|
|
647
681
|
}
|
|
648
|
-
export interface SubscribeChangesSnapshotOptions<T extends object = Record<string, unknown
|
|
682
|
+
export interface SubscribeChangesSnapshotOptions<T extends object = Record<string, unknown>, TKey extends string | number = string | number> extends Omit<SubscribeChangesOptions<T, TKey>, `includeInitialState`> {
|
|
649
683
|
orderBy?: OrderBy;
|
|
650
684
|
limit?: number;
|
|
651
685
|
}
|
|
@@ -690,7 +724,7 @@ export interface CurrentStateAsChangesOptions {
|
|
|
690
724
|
* }
|
|
691
725
|
* }
|
|
692
726
|
*/
|
|
693
|
-
export type ChangeListener<T extends object = Record<string, unknown>, TKey extends string | number = string | number> = (changes: Array<ChangeMessage<T, TKey>>) => void;
|
|
727
|
+
export type ChangeListener<T extends object = Record<string, unknown>, TKey extends string | number = string | number> = (changes: Array<ChangeMessage<WithVirtualProps<T, TKey>, TKey>>) => void;
|
|
694
728
|
type BuiltIns = null | undefined | string | number | boolean | symbol | bigint | void | Date | RegExp;
|
|
695
729
|
type HasMultipleCallSignatures<T extends (...arguments_: Array<any>) => unknown> = T extends {
|
|
696
730
|
(...arguments_: infer A): unknown;
|
|
@@ -5,4 +5,20 @@
|
|
|
5
5
|
* @param compareFn Comparison function to use for ordering
|
|
6
6
|
* @returns The index where the value should be inserted to maintain order
|
|
7
7
|
*/
|
|
8
|
+
export declare function findInsertPositionInArray<T>(sortedArray: Array<T>, value: T, compareFn: (a: T, b: T) => number): number;
|
|
9
|
+
/**
|
|
10
|
+
* Finds the correct insert position for a value in a sorted tuple array using binary search
|
|
11
|
+
* @param sortedArray The sorted tuple array to search in
|
|
12
|
+
* @param value The value to find the position for
|
|
13
|
+
* @param compareFn Comparison function to use for ordering
|
|
14
|
+
* @returns The index where the value should be inserted to maintain order
|
|
15
|
+
*/
|
|
8
16
|
export declare function findInsertPosition<T>(sortedArray: Array<[T, any]>, value: T, compareFn: (a: T, b: T) => number): number;
|
|
17
|
+
/**
|
|
18
|
+
* Deletes a value from a sorted array while maintaining sort order
|
|
19
|
+
* @param sortedArray The sorted array to delete from
|
|
20
|
+
* @param value The value to delete
|
|
21
|
+
* @param compareFn Comparison function to use for ordering
|
|
22
|
+
* @returns True if the value was found and deleted, false otherwise
|
|
23
|
+
*/
|
|
24
|
+
export declare function deleteInSortedArray<T>(sortedArray: Array<T>, value: T, compareFn: (a: T, b: T) => number): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function findInsertPositionInArray(sortedArray, value, compareFn) {
|
|
2
|
+
let left = 0;
|
|
3
|
+
let right = sortedArray.length;
|
|
4
|
+
while (left < right) {
|
|
5
|
+
const mid = Math.floor((left + right) / 2);
|
|
6
|
+
const comparison = compareFn(sortedArray[mid], value);
|
|
7
|
+
if (comparison < 0) {
|
|
8
|
+
left = mid + 1;
|
|
9
|
+
} else {
|
|
10
|
+
right = mid;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return left;
|
|
14
|
+
}
|
|
15
|
+
function deleteInSortedArray(sortedArray, value, compareFn) {
|
|
16
|
+
const idx = findInsertPositionInArray(sortedArray, value, compareFn);
|
|
17
|
+
if (idx < sortedArray.length && compareFn(sortedArray[idx], value) === 0) {
|
|
18
|
+
sortedArray.splice(idx, 1);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
deleteInSortedArray,
|
|
25
|
+
findInsertPositionInArray
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=array-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"array-utils.js","sources":["../../../src/utils/array-utils.ts"],"sourcesContent":["/**\n * Finds the correct insert position for a value in a sorted array using binary search\n * @param sortedArray The sorted array to search in\n * @param value The value to find the position for\n * @param compareFn Comparison function to use for ordering\n * @returns The index where the value should be inserted to maintain order\n */\nexport function findInsertPositionInArray<T>(\n sortedArray: Array<T>,\n value: T,\n compareFn: (a: T, b: T) => number,\n): number {\n let left = 0\n let right = sortedArray.length\n\n while (left < right) {\n const mid = Math.floor((left + right) / 2)\n const comparison = compareFn(sortedArray[mid]!, value)\n\n if (comparison < 0) {\n left = mid + 1\n } else {\n right = mid\n }\n }\n\n return left\n}\n\n/**\n * Finds the correct insert position for a value in a sorted tuple array using binary search\n * @param sortedArray The sorted tuple array to search in\n * @param value The value to find the position for\n * @param compareFn Comparison function to use for ordering\n * @returns The index where the value should be inserted to maintain order\n */\nexport function findInsertPosition<T>(\n sortedArray: Array<[T, any]>,\n value: T,\n compareFn: (a: T, b: T) => number,\n): number {\n let left = 0\n let right = sortedArray.length\n\n while (left < right) {\n const mid = Math.floor((left + right) / 2)\n const comparison = compareFn(sortedArray[mid]![0], value)\n\n if (comparison < 0) {\n left = mid + 1\n } else {\n right = mid\n }\n }\n\n return left\n}\n\n/**\n * Deletes a value from a sorted array while maintaining sort order\n * @param sortedArray The sorted array to delete from\n * @param value The value to delete\n * @param compareFn Comparison function to use for ordering\n * @returns True if the value was found and deleted, false otherwise\n */\nexport function deleteInSortedArray<T>(\n sortedArray: Array<T>,\n value: T,\n compareFn: (a: T, b: T) => number,\n): boolean {\n const idx = findInsertPositionInArray(sortedArray, value, compareFn)\n if (idx < sortedArray.length && compareFn(sortedArray[idx]!, value) === 0) {\n sortedArray.splice(idx, 1)\n return true\n }\n return false\n}\n"],"names":[],"mappings":"AAOO,SAAS,0BACd,aACA,OACA,WACQ;AACR,MAAI,OAAO;AACX,MAAI,QAAQ,YAAY;AAExB,SAAO,OAAO,OAAO;AACnB,UAAM,MAAM,KAAK,OAAO,OAAO,SAAS,CAAC;AACzC,UAAM,aAAa,UAAU,YAAY,GAAG,GAAI,KAAK;AAErD,QAAI,aAAa,GAAG;AAClB,aAAO,MAAM;AAAA,IACf,OAAO;AACL,cAAQ;AAAA,IACV;AAAA,EACF;AAEA,SAAO;AACT;AAsCO,SAAS,oBACd,aACA,OACA,WACS;AACT,QAAM,MAAM,0BAA0B,aAAa,OAAO,SAAS;AACnE,MAAI,MAAM,YAAY,UAAU,UAAU,YAAY,GAAG,GAAI,KAAK,MAAM,GAAG;AACzE,gBAAY,OAAO,KAAK,CAAC;AACzB,WAAO;AAAA,EACT;AACA,SAAO;AACT;"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isTemporal } from "../utils.js";
|
|
1
2
|
const objectIds = /* @__PURE__ */ new WeakMap();
|
|
2
3
|
let nextObjectId = 1;
|
|
3
4
|
function getObjectId(obj) {
|
|
@@ -30,6 +31,13 @@ const ascComparator = (a, b, opts) => {
|
|
|
30
31
|
if (a instanceof Date && b instanceof Date) {
|
|
31
32
|
return a.getTime() - b.getTime();
|
|
32
33
|
}
|
|
34
|
+
if (isTemporal(a) && isTemporal(b)) {
|
|
35
|
+
const aStr = a.toString();
|
|
36
|
+
const bStr = b.toString();
|
|
37
|
+
if (aStr < bStr) return -1;
|
|
38
|
+
if (aStr > bStr) return 1;
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
33
41
|
const aIsObject = typeof a === `object`;
|
|
34
42
|
const bIsObject = typeof b === `object`;
|
|
35
43
|
if (aIsObject || bIsObject) {
|
|
@@ -82,6 +90,9 @@ function normalizeValue(value) {
|
|
|
82
90
|
if (value instanceof Date) {
|
|
83
91
|
return value.getTime();
|
|
84
92
|
}
|
|
93
|
+
if (isTemporal(value)) {
|
|
94
|
+
return `__temporal__${value[Symbol.toStringTag]}__${value.toString()}`;
|
|
95
|
+
}
|
|
85
96
|
const isUint8Array = typeof Buffer !== `undefined` && value instanceof Buffer || value instanceof Uint8Array;
|
|
86
97
|
if (isUint8Array) {
|
|
87
98
|
if (value.byteLength <= UINT8ARRAY_NORMALIZE_THRESHOLD) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comparison.js","sources":["../../../src/utils/comparison.ts"],"sourcesContent":["import type { CompareOptions } from '../query/builder/types'\n\n// WeakMap to store stable IDs for objects\nconst objectIds = new WeakMap<object, number>()\nlet nextObjectId = 1\n\n/**\n * Get or create a stable ID for an object\n */\nfunction getObjectId(obj: object): number {\n if (objectIds.has(obj)) {\n return objectIds.get(obj)!\n }\n const id = nextObjectId++\n objectIds.set(obj, id)\n return id\n}\n\n/**\n * Universal comparison function for all data types\n * Handles null/undefined, strings, arrays, dates, objects, and primitives\n * Always sorts null/undefined values first\n */\nexport const ascComparator = (a: any, b: any, opts: CompareOptions): number => {\n const { nulls } = opts\n\n // Handle null/undefined\n if (a == null && b == null) return 0\n if (a == null) return nulls === `first` ? -1 : 1\n if (b == null) return nulls === `first` ? 1 : -1\n\n // if a and b are both strings, compare them based on locale\n if (typeof a === `string` && typeof b === `string`) {\n if (opts.stringSort === `locale`) {\n return a.localeCompare(b, opts.locale, opts.localeOptions)\n }\n // For lexical sort we rely on direct comparison for primitive values\n }\n\n // if a and b are both arrays, compare them element by element\n if (Array.isArray(a) && Array.isArray(b)) {\n for (let i = 0; i < Math.min(a.length, b.length); i++) {\n const result = ascComparator(a[i], b[i], opts)\n if (result !== 0) {\n return result\n }\n }\n // All elements are equal up to the minimum length\n return a.length - b.length\n }\n\n // If both are dates, compare them\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() - b.getTime()\n }\n\n // If at least one of the values is an object, use stable IDs for comparison\n const aIsObject = typeof a === `object`\n const bIsObject = typeof b === `object`\n\n if (aIsObject || bIsObject) {\n // If both are objects, compare their stable IDs\n if (aIsObject && bIsObject) {\n const aId = getObjectId(a)\n const bId = getObjectId(b)\n return aId - bId\n }\n\n // If only one is an object, objects come after primitives\n if (aIsObject) return 1\n if (bIsObject) return -1\n }\n\n // For primitive values, use direct comparison\n if (a < b) return -1\n if (a > b) return 1\n return 0\n}\n\n/**\n * Descending comparator function for ordering values\n * Handles null/undefined as largest values (opposite of ascending)\n */\nexport const descComparator = (\n a: unknown,\n b: unknown,\n opts: CompareOptions,\n): number => {\n return ascComparator(b, a, {\n ...opts,\n nulls: opts.nulls === `first` ? `last` : `first`,\n })\n}\n\nexport function makeComparator(\n opts: CompareOptions,\n): (a: any, b: any) => number {\n return (a, b) => {\n if (opts.direction === `asc`) {\n return ascComparator(a, b, opts)\n } else {\n return descComparator(a, b, opts)\n }\n }\n}\n\n/** Default comparator orders values in ascending order with nulls first and locale string comparison. */\nexport const defaultComparator = makeComparator({\n direction: `asc`,\n nulls: `first`,\n stringSort: `locale`,\n})\n\n/**\n * Compare two Uint8Arrays for content equality\n */\nfunction areUint8ArraysEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.byteLength !== b.byteLength) {\n return false\n }\n for (let i = 0; i < a.byteLength; i++) {\n if (a[i] !== b[i]) {\n return false\n }\n }\n return true\n}\n\n/**\n * Threshold for normalizing Uint8Arrays to string representations.\n * Arrays larger than this will use reference equality to avoid memory overhead.\n * 128 bytes is enough for common ID formats (ULIDs are 16 bytes, UUIDs are 16 bytes)\n * while avoiding excessive string allocation for large binary data.\n */\nconst UINT8ARRAY_NORMALIZE_THRESHOLD = 128\n\n/**\n * Sentinel value representing undefined in normalized form.\n * This allows distinguishing between \"start from beginning\" (undefined parameter)\n * and \"start from the key undefined\" (actual undefined value in the tree).\n */\nexport const UNDEFINED_SENTINEL = `__TS_DB_BTREE_UNDEFINED_VALUE__`\n\n/**\n * Normalize a value for comparison and Map key usage\n * Converts values that can't be directly compared or used as Map keys\n * into comparable primitive representations\n *\n * Note: This does NOT convert undefined to a sentinel. Use normalizeForBTree\n * for BTree index operations that need to distinguish undefined values.\n */\nexport function normalizeValue(value: any): any {\n if (value instanceof Date) {\n return value.getTime()\n }\n\n // Normalize Uint8Arrays/Buffers to a string representation for Map key usage\n // This enables content-based equality for binary data like ULIDs\n const isUint8Array =\n (typeof Buffer !== `undefined` && value instanceof Buffer) ||\n value instanceof Uint8Array\n\n if (isUint8Array) {\n // Only normalize small arrays to avoid memory overhead for large binary data\n if (value.byteLength <= UINT8ARRAY_NORMALIZE_THRESHOLD) {\n // Convert to a string representation that can be used as a Map key\n // Use a special prefix to avoid collisions with user strings\n return `__u8__${Array.from(value).join(`,`)}`\n }\n // For large arrays, fall back to reference equality\n // Users working with large binary data should use a derived key if needed\n }\n\n return value\n}\n\n/**\n * Normalize a value for BTree index usage.\n * Extends normalizeValue to also convert undefined to a sentinel value.\n * This is needed because the BTree does not properly support `undefined` as a key\n * (it interprets undefined as \"start from beginning\" in nextHigherPair/nextLowerPair).\n */\nexport function normalizeForBTree(value: any): any {\n if (value === undefined) {\n return UNDEFINED_SENTINEL\n }\n return normalizeValue(value)\n}\n\n/**\n * Converts the `UNDEFINED_SENTINEL` back to `undefined`.\n * Needed such that the sentinel is converted back to `undefined` before comparison.\n */\nexport function denormalizeUndefined(value: any): any {\n if (value === UNDEFINED_SENTINEL) {\n return undefined\n }\n return value\n}\n\n/**\n * Compare two values for equality, with special handling for Uint8Arrays and Buffers\n */\nexport function areValuesEqual(a: any, b: any): boolean {\n // Fast path for reference equality\n if (a === b) {\n return true\n }\n\n // Check for Uint8Array/Buffer comparison\n const aIsUint8Array =\n (typeof Buffer !== `undefined` && a instanceof Buffer) ||\n a instanceof Uint8Array\n const bIsUint8Array =\n (typeof Buffer !== `undefined` && b instanceof Buffer) ||\n b instanceof Uint8Array\n\n // If both are Uint8Arrays, compare by content\n if (aIsUint8Array && bIsUint8Array) {\n return areUint8ArraysEqual(a, b)\n }\n\n // Different types or not Uint8Arrays\n return false\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"comparison.js","sources":["../../../src/utils/comparison.ts"],"sourcesContent":["import { isTemporal } from '../utils'\nimport type { CompareOptions } from '../query/builder/types'\n\n// WeakMap to store stable IDs for objects\nconst objectIds = new WeakMap<object, number>()\nlet nextObjectId = 1\n\n/**\n * Get or create a stable ID for an object\n */\nfunction getObjectId(obj: object): number {\n if (objectIds.has(obj)) {\n return objectIds.get(obj)!\n }\n const id = nextObjectId++\n objectIds.set(obj, id)\n return id\n}\n\n/**\n * Universal comparison function for all data types\n * Handles null/undefined, strings, arrays, dates, objects, and primitives\n * Always sorts null/undefined values first\n */\nexport const ascComparator = (a: any, b: any, opts: CompareOptions): number => {\n const { nulls } = opts\n\n // Handle null/undefined\n if (a == null && b == null) return 0\n if (a == null) return nulls === `first` ? -1 : 1\n if (b == null) return nulls === `first` ? 1 : -1\n\n // if a and b are both strings, compare them based on locale\n if (typeof a === `string` && typeof b === `string`) {\n if (opts.stringSort === `locale`) {\n return a.localeCompare(b, opts.locale, opts.localeOptions)\n }\n // For lexical sort we rely on direct comparison for primitive values\n }\n\n // if a and b are both arrays, compare them element by element\n if (Array.isArray(a) && Array.isArray(b)) {\n for (let i = 0; i < Math.min(a.length, b.length); i++) {\n const result = ascComparator(a[i], b[i], opts)\n if (result !== 0) {\n return result\n }\n }\n // All elements are equal up to the minimum length\n return a.length - b.length\n }\n\n // If both are dates, compare them\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() - b.getTime()\n }\n\n // If both are Temporal objects of the same type, compare by string representation\n if (isTemporal(a) && isTemporal(b)) {\n const aStr = a.toString()\n const bStr = b.toString()\n if (aStr < bStr) return -1\n if (aStr > bStr) return 1\n return 0\n }\n\n // If at least one of the values is an object, use stable IDs for comparison\n const aIsObject = typeof a === `object`\n const bIsObject = typeof b === `object`\n\n if (aIsObject || bIsObject) {\n // If both are objects, compare their stable IDs\n if (aIsObject && bIsObject) {\n const aId = getObjectId(a)\n const bId = getObjectId(b)\n return aId - bId\n }\n\n // If only one is an object, objects come after primitives\n if (aIsObject) return 1\n if (bIsObject) return -1\n }\n\n // For primitive values, use direct comparison\n if (a < b) return -1\n if (a > b) return 1\n return 0\n}\n\n/**\n * Descending comparator function for ordering values\n * Handles null/undefined as largest values (opposite of ascending)\n */\nexport const descComparator = (\n a: unknown,\n b: unknown,\n opts: CompareOptions,\n): number => {\n return ascComparator(b, a, {\n ...opts,\n nulls: opts.nulls === `first` ? `last` : `first`,\n })\n}\n\nexport function makeComparator(\n opts: CompareOptions,\n): (a: any, b: any) => number {\n return (a, b) => {\n if (opts.direction === `asc`) {\n return ascComparator(a, b, opts)\n } else {\n return descComparator(a, b, opts)\n }\n }\n}\n\n/** Default comparator orders values in ascending order with nulls first and locale string comparison. */\nexport const defaultComparator = makeComparator({\n direction: `asc`,\n nulls: `first`,\n stringSort: `locale`,\n})\n\n/**\n * Compare two Uint8Arrays for content equality\n */\nfunction areUint8ArraysEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.byteLength !== b.byteLength) {\n return false\n }\n for (let i = 0; i < a.byteLength; i++) {\n if (a[i] !== b[i]) {\n return false\n }\n }\n return true\n}\n\n/**\n * Threshold for normalizing Uint8Arrays to string representations.\n * Arrays larger than this will use reference equality to avoid memory overhead.\n * 128 bytes is enough for common ID formats (ULIDs are 16 bytes, UUIDs are 16 bytes)\n * while avoiding excessive string allocation for large binary data.\n */\nconst UINT8ARRAY_NORMALIZE_THRESHOLD = 128\n\n/**\n * Sentinel value representing undefined in normalized form.\n * This allows distinguishing between \"start from beginning\" (undefined parameter)\n * and \"start from the key undefined\" (actual undefined value in the tree).\n */\nexport const UNDEFINED_SENTINEL = `__TS_DB_BTREE_UNDEFINED_VALUE__`\n\n/**\n * Normalize a value for comparison and Map key usage\n * Converts values that can't be directly compared or used as Map keys\n * into comparable primitive representations\n *\n * Note: This does NOT convert undefined to a sentinel. Use normalizeForBTree\n * for BTree index operations that need to distinguish undefined values.\n */\nexport function normalizeValue(value: any): any {\n if (value instanceof Date) {\n return value.getTime()\n }\n\n if (isTemporal(value)) {\n return `__temporal__${value[Symbol.toStringTag]}__${value.toString()}`\n }\n\n // Normalize Uint8Arrays/Buffers to a string representation for Map key usage\n // This enables content-based equality for binary data like ULIDs\n const isUint8Array =\n (typeof Buffer !== `undefined` && value instanceof Buffer) ||\n value instanceof Uint8Array\n\n if (isUint8Array) {\n // Only normalize small arrays to avoid memory overhead for large binary data\n if (value.byteLength <= UINT8ARRAY_NORMALIZE_THRESHOLD) {\n // Convert to a string representation that can be used as a Map key\n // Use a special prefix to avoid collisions with user strings\n return `__u8__${Array.from(value).join(`,`)}`\n }\n // For large arrays, fall back to reference equality\n // Users working with large binary data should use a derived key if needed\n }\n\n return value\n}\n\n/**\n * Normalize a value for BTree index usage.\n * Extends normalizeValue to also convert undefined to a sentinel value.\n * This is needed because the BTree does not properly support `undefined` as a key\n * (it interprets undefined as \"start from beginning\" in nextHigherPair/nextLowerPair).\n */\nexport function normalizeForBTree(value: any): any {\n if (value === undefined) {\n return UNDEFINED_SENTINEL\n }\n return normalizeValue(value)\n}\n\n/**\n * Converts the `UNDEFINED_SENTINEL` back to `undefined`.\n * Needed such that the sentinel is converted back to `undefined` before comparison.\n */\nexport function denormalizeUndefined(value: any): any {\n if (value === UNDEFINED_SENTINEL) {\n return undefined\n }\n return value\n}\n\n/**\n * Compare two values for equality, with special handling for Uint8Arrays and Buffers\n */\nexport function areValuesEqual(a: any, b: any): boolean {\n // Fast path for reference equality\n if (a === b) {\n return true\n }\n\n // Check for Uint8Array/Buffer comparison\n const aIsUint8Array =\n (typeof Buffer !== `undefined` && a instanceof Buffer) ||\n a instanceof Uint8Array\n const bIsUint8Array =\n (typeof Buffer !== `undefined` && b instanceof Buffer) ||\n b instanceof Uint8Array\n\n // If both are Uint8Arrays, compare by content\n if (aIsUint8Array && bIsUint8Array) {\n return areUint8ArraysEqual(a, b)\n }\n\n // Different types or not Uint8Arrays\n return false\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { DEFAULT_COMPARE_OPTIONS } from "../utils.js";
|
|
2
2
|
import { ReverseIndex } from "../indexes/reverse-index.js";
|
|
3
|
+
import { hasVirtualPropPath } from "../virtual-props.js";
|
|
3
4
|
function findIndexForField(collection, fieldPath, compareOptions) {
|
|
5
|
+
if (hasVirtualPropPath(fieldPath)) {
|
|
6
|
+
return void 0;
|
|
7
|
+
}
|
|
4
8
|
const compareOpts = compareOptions ?? {
|
|
5
9
|
...DEFAULT_COMPARE_OPTIONS,
|
|
6
10
|
...collection.compareOptions
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index-optimization.js","sources":["../../../src/utils/index-optimization.ts"],"sourcesContent":["/**\n * # Index-Based Query Optimization\n *\n * This module provides utilities for optimizing query expressions by leveraging\n * available indexes to quickly find matching keys instead of scanning all data.\n *\n * This is different from the query structure optimizer in `query/optimizer.ts`\n * which rewrites query IR structure. This module focuses on using indexes during\n * query execution to speed up data filtering.\n *\n * ## Key Features:\n * - Uses indexes to find matching keys for WHERE conditions\n * - Supports AND/OR logic with set operations\n * - Handles range queries (eq, gt, gte, lt, lte)\n * - Optimizes IN array expressions\n */\n\nimport { DEFAULT_COMPARE_OPTIONS } from '../utils.js'\nimport { ReverseIndex } from '../indexes/reverse-index.js'\nimport type { CompareOptions } from '../query/builder/types.js'\nimport type { IndexInterface, IndexOperation } from '../indexes/base-index.js'\nimport type { BasicExpression } from '../query/ir.js'\nimport type { CollectionLike } from '../types.js'\n\n/**\n * Result of index-based query optimization\n */\nexport interface OptimizationResult<TKey> {\n canOptimize: boolean\n matchingKeys: Set<TKey>\n}\n\n/**\n * Finds an index that matches a given field path\n */\nexport function findIndexForField<TKey extends string | number>(\n collection: CollectionLike<any, TKey>,\n fieldPath: Array<string>,\n compareOptions?: CompareOptions,\n): IndexInterface<TKey> | undefined {\n const compareOpts = compareOptions ?? {\n ...DEFAULT_COMPARE_OPTIONS,\n ...collection.compareOptions,\n }\n\n for (const index of collection.indexes.values()) {\n if (\n index.matchesField(fieldPath) &&\n index.matchesCompareOptions(compareOpts)\n ) {\n if (!index.matchesDirection(compareOpts.direction)) {\n return new ReverseIndex(index)\n }\n return index\n }\n }\n return undefined\n}\n\n/**\n * Intersects multiple sets (AND logic)\n */\nexport function intersectSets<T>(sets: Array<Set<T>>): Set<T> {\n if (sets.length === 0) return new Set()\n if (sets.length === 1) return new Set(sets[0])\n\n let result = new Set(sets[0])\n for (let i = 1; i < sets.length; i++) {\n const newResult = new Set<T>()\n for (const item of result) {\n if (sets[i]!.has(item)) {\n newResult.add(item)\n }\n }\n result = newResult\n }\n return result\n}\n\n/**\n * Unions multiple sets (OR logic)\n */\nexport function unionSets<T>(sets: Array<Set<T>>): Set<T> {\n const result = new Set<T>()\n for (const set of sets) {\n for (const item of set) {\n result.add(item)\n }\n }\n return result\n}\n\n/**\n * Optimizes a query expression using available indexes to find matching keys\n */\nexport function optimizeExpressionWithIndexes<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n return optimizeQueryRecursive(expression, collection)\n}\n\n/**\n * Recursively optimizes query expressions\n */\nfunction optimizeQueryRecursive<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return optimizeSimpleComparison(expression, collection)\n\n case `and`:\n return optimizeAndExpression(expression, collection)\n\n case `or`:\n return optimizeOrExpression(expression, collection)\n\n case `in`:\n return optimizeInArrayExpression(expression, collection)\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an expression can be optimized\n */\nexport function canOptimizeExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return canOptimizeSimpleComparison(expression, collection)\n\n case `and`:\n return canOptimizeAndExpression(expression, collection)\n\n case `or`:\n return canOptimizeOrExpression(expression, collection)\n\n case `in`:\n return canOptimizeInArrayExpression(expression, collection)\n }\n }\n\n return false\n}\n\n/**\n * Optimizes compound range queries on the same field\n * Example: WHERE age > 5 AND age < 10\n */\nfunction optimizeCompoundRangeQuery<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // Group range operations by field\n const fieldOperations = new Map<\n string,\n Array<{\n operation: `gt` | `gte` | `lt` | `lte`\n value: any\n }>\n >()\n\n // Collect all range operations from AND arguments\n for (const arg of expression.args) {\n if (arg.type === `func` && [`gt`, `gte`, `lt`, `lte`].includes(arg.name)) {\n const rangeOp = arg as any\n if (rangeOp.args.length === 2) {\n const leftArg = rangeOp.args[0]!\n const rightArg = rangeOp.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = rangeOp.name as `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const fieldKey = fieldPath.join(`.`)\n const value = (valueArg as any).value\n\n if (!fieldOperations.has(fieldKey)) {\n fieldOperations.set(fieldKey, [])\n }\n fieldOperations.get(fieldKey)!.push({ operation, value })\n }\n }\n }\n }\n\n // Check if we have multiple operations on the same field\n for (const [fieldKey, operations] of fieldOperations) {\n if (operations.length >= 2) {\n const fieldPath = fieldKey.split(`.`)\n const index = findIndexForField(collection, fieldPath)\n\n if (index && index.supports(`gt`) && index.supports(`lt`)) {\n // Build range query options\n let from: any = undefined\n let to: any = undefined\n let fromInclusive = true\n let toInclusive = true\n\n for (const { operation, value } of operations) {\n switch (operation) {\n case `gt`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = false\n }\n break\n case `gte`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = true\n }\n break\n case `lt`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = false\n }\n break\n case `lte`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = true\n }\n break\n }\n }\n\n const matchingKeys = (index as any).rangeQuery({\n from,\n to,\n fromInclusive,\n toInclusive,\n })\n\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Optimizes simple comparison expressions (eq, gt, gte, lt, lte)\n */\nfunction optimizeSimpleComparison<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = expression.name as `eq` | `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n // eq stays the same\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(collection, fieldPath)\n\n if (index) {\n const queryValue = (valueArg as any).value\n\n // Map operation to IndexOperation enum\n const indexOperation = operation as IndexOperation\n\n // Check if the index supports this operation\n if (!index.supports(indexOperation)) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const matchingKeys = index.lookup(indexOperation, queryValue)\n return { canOptimize: true, matchingKeys }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if a simple comparison can be optimized\n */\nfunction canOptimizeSimpleComparison<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldPath: Array<string> | null = null\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n fieldPath = (leftArg as any).path\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n fieldPath = (rightArg as any).path\n }\n\n if (fieldPath) {\n const index = findIndexForField(collection, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n\n/**\n * Optimizes AND expressions\n */\nfunction optimizeAndExpression<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // First, try to optimize compound range queries on the same field\n const compoundRangeResult = optimizeCompoundRangeQuery(expression, collection)\n if (compoundRangeResult.canOptimize) {\n return compoundRangeResult\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, collection)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use intersectSets utility for AND logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const intersectedKeys = intersectSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: intersectedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an AND expression can be optimized\n */\nfunction canOptimizeAndExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, collection))\n}\n\n/**\n * Optimizes OR expressions\n */\nfunction optimizeOrExpression<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, collection)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use unionSets utility for OR logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const unionedKeys = unionSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: unionedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an OR expression can be optimized\n */\nfunction canOptimizeOrExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, collection))\n}\n\n/**\n * Optimizes IN array expressions\n */\nfunction optimizeInArrayExpression<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const values = (arrayArg as any).value\n const index = findIndexForField(collection, fieldPath)\n\n if (index) {\n // Check if the index supports IN operation\n if (index.supports(`in`)) {\n const matchingKeys = index.lookup(`in`, values)\n return { canOptimize: true, matchingKeys }\n } else if (index.supports(`eq`)) {\n // Fallback to multiple equality lookups\n const matchingKeys = new Set<TKey>()\n for (const value of values) {\n const keysForValue = index.lookup(`eq`, value)\n for (const key of keysForValue) {\n matchingKeys.add(key)\n }\n }\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an IN array expression can be optimized\n */\nfunction canOptimizeInArrayExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(collection, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n"],"names":[],"mappings":";;AAmCO,SAAS,kBACd,YACA,WACA,gBACkC;AAClC,QAAM,cAAc,kBAAkB;AAAA,IACpC,GAAG;AAAA,IACH,GAAG,WAAW;AAAA,EAAA;AAGhB,aAAW,SAAS,WAAW,QAAQ,OAAA,GAAU;AAC/C,QACE,MAAM,aAAa,SAAS,KAC5B,MAAM,sBAAsB,WAAW,GACvC;AACA,UAAI,CAAC,MAAM,iBAAiB,YAAY,SAAS,GAAG;AAClD,eAAO,IAAI,aAAa,KAAK;AAAA,MAC/B;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAKO,SAAS,cAAiB,MAA6B;AAC5D,MAAI,KAAK,WAAW,EAAG,4BAAW,IAAA;AAClC,MAAI,KAAK,WAAW,EAAG,QAAO,IAAI,IAAI,KAAK,CAAC,CAAC;AAE7C,MAAI,SAAS,IAAI,IAAI,KAAK,CAAC,CAAC;AAC5B,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,gCAAgB,IAAA;AACtB,eAAW,QAAQ,QAAQ;AACzB,UAAI,KAAK,CAAC,EAAG,IAAI,IAAI,GAAG;AACtB,kBAAU,IAAI,IAAI;AAAA,MACpB;AAAA,IACF;AACA,aAAS;AAAA,EACX;AACA,SAAO;AACT;AAKO,SAAS,UAAa,MAA6B;AACxD,QAAM,6BAAa,IAAA;AACnB,aAAW,OAAO,MAAM;AACtB,eAAW,QAAQ,KAAK;AACtB,aAAO,IAAI,IAAI;AAAA,IACjB;AAAA,EACF;AACA,SAAO;AACT;AAKO,SAAS,8BAId,YACA,YAC0B;AAC1B,SAAO,uBAAuB,YAAY,UAAU;AACtD;AAKA,SAAS,uBACP,YACA,YAC0B;AAC1B,MAAI,WAAW,SAAS,QAAQ;AAC9B,YAAQ,WAAW,MAAA;AAAA,MACjB,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,eAAO,yBAAyB,YAAY,UAAU;AAAA,MAExD,KAAK;AACH,eAAO,sBAAsB,YAAY,UAAU;AAAA,MAErD,KAAK;AACH,eAAO,qBAAqB,YAAY,UAAU;AAAA,MAEpD,KAAK;AACH,eAAO,0BAA0B,YAAY,UAAU;AAAA,IAAA;AAAA,EAE7D;AAEA,SAAO,EAAE,aAAa,OAAO,cAAc,oBAAI,MAAI;AACrD;AAoCA,SAAS,2BAIP,YACA,YAC0B;AAC1B,MAAI,WAAW,SAAS,UAAU,WAAW,KAAK,SAAS,GAAG;AAC5D,WAAO,EAAE,aAAa,OAAO,cAAc,oBAAI,MAAI;AAAA,EACrD;AAGA,QAAM,sCAAsB,IAAA;AAS5B,aAAW,OAAO,WAAW,MAAM;AACjC,QAAI,IAAI,SAAS,UAAU,CAAC,MAAM,OAAO,MAAM,KAAK,EAAE,SAAS,IAAI,IAAI,GAAG;AACxE,YAAM,UAAU;AAChB,UAAI,QAAQ,KAAK,WAAW,GAAG;AAC7B,cAAM,UAAU,QAAQ,KAAK,CAAC;AAC9B,cAAM,WAAW,QAAQ,KAAK,CAAC;AAG/B,YAAI,WAAmC;AACvC,YAAI,WAAmC;AACvC,YAAI,YAAY,QAAQ;AAExB,YAAI,QAAQ,SAAS,SAAS,SAAS,SAAS,OAAO;AAErD,qBAAW;AACX,qBAAW;AAAA,QACb,WAAW,QAAQ,SAAS,SAAS,SAAS,SAAS,OAAO;AAE5D,qBAAW;AACX,qBAAW;AAGX,kBAAQ,WAAA;AAAA,YACN,KAAK;AACH,0BAAY;AACZ;AAAA,YACF,KAAK;AACH,0BAAY;AACZ;AAAA,YACF,KAAK;AACH,0BAAY;AACZ;AAAA,YACF,KAAK;AACH,0BAAY;AACZ;AAAA,UAAA;AAAA,QAEN;AAEA,YAAI,YAAY,UAAU;AACxB,gBAAM,YAAa,SAAiB;AACpC,gBAAM,WAAW,UAAU,KAAK,GAAG;AACnC,gBAAM,QAAS,SAAiB;AAEhC,cAAI,CAAC,gBAAgB,IAAI,QAAQ,GAAG;AAClC,4BAAgB,IAAI,UAAU,EAAE;AAAA,UAClC;AACA,0BAAgB,IAAI,QAAQ,EAAG,KAAK,EAAE,WAAW,OAAO;AAAA,QAC1D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,aAAW,CAAC,UAAU,UAAU,KAAK,iBAAiB;AACpD,QAAI,WAAW,UAAU,GAAG;AAC1B,YAAM,YAAY,SAAS,MAAM,GAAG;AACpC,YAAM,QAAQ,kBAAkB,YAAY,SAAS;AAErD,UAAI,SAAS,MAAM,SAAS,IAAI,KAAK,MAAM,SAAS,IAAI,GAAG;AAEzD,YAAI,OAAY;AAChB,YAAI,KAAU;AACd,YAAI,gBAAgB;AACpB,YAAI,cAAc;AAElB,mBAAW,EAAE,WAAW,MAAA,KAAW,YAAY;AAC7C,kBAAQ,WAAA;AAAA,YACN,KAAK;AACH,kBAAI,SAAS,UAAa,QAAQ,MAAM;AACtC,uBAAO;AACP,gCAAgB;AAAA,cAClB;AACA;AAAA,YACF,KAAK;AACH,kBAAI,SAAS,UAAa,QAAQ,MAAM;AACtC,uBAAO;AACP,gCAAgB;AAAA,cAClB;AACA;AAAA,YACF,KAAK;AACH,kBAAI,OAAO,UAAa,QAAQ,IAAI;AAClC,qBAAK;AACL,8BAAc;AAAA,cAChB;AACA;AAAA,YACF,KAAK;AACH,kBAAI,OAAO,UAAa,QAAQ,IAAI;AAClC,qBAAK;AACL,8BAAc;AAAA,cAChB;AACA;AAAA,UAAA;AAAA,QAEN;AAEA,cAAM,eAAgB,MAAc,WAAW;AAAA,UAC7C;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA,CACD;AAED,eAAO,EAAE,aAAa,MAAM,aAAA;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,OAAO,cAAc,oBAAI,MAAI;AACrD;AAKA,SAAS,yBAIP,YACA,YAC0B;AAC1B,MAAI,WAAW,SAAS,UAAU,WAAW,KAAK,WAAW,GAAG;AAC9D,WAAO,EAAE,aAAa,OAAO,cAAc,oBAAI,MAAI;AAAA,EACrD;AAEA,QAAM,UAAU,WAAW,KAAK,CAAC;AACjC,QAAM,WAAW,WAAW,KAAK,CAAC;AAGlC,MAAI,WAAmC;AACvC,MAAI,WAAmC;AACvC,MAAI,YAAY,WAAW;AAE3B,MAAI,QAAQ,SAAS,SAAS,SAAS,SAAS,OAAO;AAErD,eAAW;AACX,eAAW;AAAA,EACb,WAAW,QAAQ,SAAS,SAAS,SAAS,SAAS,OAAO;AAE5D,eAAW;AACX,eAAW;AAGX,YAAQ,WAAA;AAAA,MACN,KAAK;AACH,oBAAY;AACZ;AAAA,MACF,KAAK;AACH,oBAAY;AACZ;AAAA,MACF,KAAK;AACH,oBAAY;AACZ;AAAA,MACF,KAAK;AACH,oBAAY;AACZ;AAAA,IAAA;AAAA,EAGN;AAEA,MAAI,YAAY,UAAU;AACxB,UAAM,YAAa,SAAiB;AACpC,UAAM,QAAQ,kBAAkB,YAAY,SAAS;AAErD,QAAI,OAAO;AACT,YAAM,aAAc,SAAiB;AAGrC,YAAM,iBAAiB;AAGvB,UAAI,CAAC,MAAM,SAAS,cAAc,GAAG;AACnC,eAAO,EAAE,aAAa,OAAO,cAAc,oBAAI,MAAI;AAAA,MACrD;AAEA,YAAM,eAAe,MAAM,OAAO,gBAAgB,UAAU;AAC5D,aAAO,EAAE,aAAa,MAAM,aAAA;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,OAAO,cAAc,oBAAI,MAAI;AACrD;AAoCA,SAAS,sBACP,YACA,YAC0B;AAC1B,MAAI,WAAW,SAAS,UAAU,WAAW,KAAK,SAAS,GAAG;AAC5D,WAAO,EAAE,aAAa,OAAO,cAAc,oBAAI,MAAI;AAAA,EACrD;AAGA,QAAM,sBAAsB,2BAA2B,YAAY,UAAU;AAC7E,MAAI,oBAAoB,aAAa;AACnC,WAAO;AAAA,EACT;AAEA,QAAM,UAA2C,CAAA;AAGjD,aAAW,OAAO,WAAW,MAAM;AACjC,UAAM,SAAS,uBAAuB,KAAK,UAAU;AACrD,QAAI,OAAO,aAAa;AACtB,cAAQ,KAAK,MAAM;AAAA,IACrB;AAAA,EACF;AAEA,MAAI,QAAQ,SAAS,GAAG;AAEtB,UAAM,kBAAkB,QAAQ,IAAI,CAAC,MAAM,EAAE,YAAY;AACzD,UAAM,kBAAkB,cAAc,eAAe;AACrD,WAAO,EAAE,aAAa,MAAM,cAAc,gBAAA;AAAA,EAC5C;AAEA,SAAO,EAAE,aAAa,OAAO,cAAc,oBAAI,MAAI;AACrD;AAoBA,SAAS,qBACP,YACA,YAC0B;AAC1B,MAAI,WAAW,SAAS,UAAU,WAAW,KAAK,SAAS,GAAG;AAC5D,WAAO,EAAE,aAAa,OAAO,cAAc,oBAAI,MAAI;AAAA,EACrD;AAEA,QAAM,UAA2C,CAAA;AAGjD,aAAW,OAAO,WAAW,MAAM;AACjC,UAAM,SAAS,uBAAuB,KAAK,UAAU;AACrD,QAAI,OAAO,aAAa;AACtB,cAAQ,KAAK,MAAM;AAAA,IACrB;AAAA,EACF;AAEA,MAAI,QAAQ,SAAS,GAAG;AAEtB,UAAM,kBAAkB,QAAQ,IAAI,CAAC,MAAM,EAAE,YAAY;AACzD,UAAM,cAAc,UAAU,eAAe;AAC7C,WAAO,EAAE,aAAa,MAAM,cAAc,YAAA;AAAA,EAC5C;AAEA,SAAO,EAAE,aAAa,OAAO,cAAc,oBAAI,MAAI;AACrD;AAoBA,SAAS,0BAIP,YACA,YAC0B;AAC1B,MAAI,WAAW,SAAS,UAAU,WAAW,KAAK,WAAW,GAAG;AAC9D,WAAO,EAAE,aAAa,OAAO,cAAc,oBAAI,MAAI;AAAA,EACrD;AAEA,QAAM,WAAW,WAAW,KAAK,CAAC;AAClC,QAAM,WAAW,WAAW,KAAK,CAAC;AAElC,MACE,SAAS,SAAS,SAClB,SAAS,SAAS,SAClB,MAAM,QAAS,SAAiB,KAAK,GACrC;AACA,UAAM,YAAa,SAAiB;AACpC,UAAM,SAAU,SAAiB;AACjC,UAAM,QAAQ,kBAAkB,YAAY,SAAS;AAErD,QAAI,OAAO;AAET,UAAI,MAAM,SAAS,IAAI,GAAG;AACxB,cAAM,eAAe,MAAM,OAAO,MAAM,MAAM;AAC9C,eAAO,EAAE,aAAa,MAAM,aAAA;AAAA,MAC9B,WAAW,MAAM,SAAS,IAAI,GAAG;AAE/B,cAAM,mCAAmB,IAAA;AACzB,mBAAW,SAAS,QAAQ;AAC1B,gBAAM,eAAe,MAAM,OAAO,MAAM,KAAK;AAC7C,qBAAW,OAAO,cAAc;AAC9B,yBAAa,IAAI,GAAG;AAAA,UACtB;AAAA,QACF;AACA,eAAO,EAAE,aAAa,MAAM,aAAA;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,OAAO,cAAc,oBAAI,MAAI;AACrD;"}
|
|
1
|
+
{"version":3,"file":"index-optimization.js","sources":["../../../src/utils/index-optimization.ts"],"sourcesContent":["/**\n * # Index-Based Query Optimization\n *\n * This module provides utilities for optimizing query expressions by leveraging\n * available indexes to quickly find matching keys instead of scanning all data.\n *\n * This is different from the query structure optimizer in `query/optimizer.ts`\n * which rewrites query IR structure. This module focuses on using indexes during\n * query execution to speed up data filtering.\n *\n * ## Key Features:\n * - Uses indexes to find matching keys for WHERE conditions\n * - Supports AND/OR logic with set operations\n * - Handles range queries (eq, gt, gte, lt, lte)\n * - Optimizes IN array expressions\n */\n\nimport { DEFAULT_COMPARE_OPTIONS } from '../utils.js'\nimport { ReverseIndex } from '../indexes/reverse-index.js'\nimport { hasVirtualPropPath } from '../virtual-props.js'\nimport type { CompareOptions } from '../query/builder/types.js'\nimport type { IndexInterface, IndexOperation } from '../indexes/base-index.js'\nimport type { BasicExpression } from '../query/ir.js'\nimport type { CollectionLike } from '../types.js'\n\n/**\n * Result of index-based query optimization\n */\nexport interface OptimizationResult<TKey> {\n canOptimize: boolean\n matchingKeys: Set<TKey>\n}\n\n/**\n * Finds an index that matches a given field path\n */\nexport function findIndexForField<TKey extends string | number>(\n collection: CollectionLike<any, TKey>,\n fieldPath: Array<string>,\n compareOptions?: CompareOptions,\n): IndexInterface<TKey> | undefined {\n if (hasVirtualPropPath(fieldPath)) {\n return undefined\n }\n const compareOpts = compareOptions ?? {\n ...DEFAULT_COMPARE_OPTIONS,\n ...collection.compareOptions,\n }\n\n for (const index of collection.indexes.values()) {\n if (\n index.matchesField(fieldPath) &&\n index.matchesCompareOptions(compareOpts)\n ) {\n if (!index.matchesDirection(compareOpts.direction)) {\n return new ReverseIndex(index)\n }\n return index\n }\n }\n return undefined\n}\n\n/**\n * Intersects multiple sets (AND logic)\n */\nexport function intersectSets<T>(sets: Array<Set<T>>): Set<T> {\n if (sets.length === 0) return new Set()\n if (sets.length === 1) return new Set(sets[0])\n\n let result = new Set(sets[0])\n for (let i = 1; i < sets.length; i++) {\n const newResult = new Set<T>()\n for (const item of result) {\n if (sets[i]!.has(item)) {\n newResult.add(item)\n }\n }\n result = newResult\n }\n return result\n}\n\n/**\n * Unions multiple sets (OR logic)\n */\nexport function unionSets<T>(sets: Array<Set<T>>): Set<T> {\n const result = new Set<T>()\n for (const set of sets) {\n for (const item of set) {\n result.add(item)\n }\n }\n return result\n}\n\n/**\n * Optimizes a query expression using available indexes to find matching keys\n */\nexport function optimizeExpressionWithIndexes<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n return optimizeQueryRecursive(expression, collection)\n}\n\n/**\n * Recursively optimizes query expressions\n */\nfunction optimizeQueryRecursive<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return optimizeSimpleComparison(expression, collection)\n\n case `and`:\n return optimizeAndExpression(expression, collection)\n\n case `or`:\n return optimizeOrExpression(expression, collection)\n\n case `in`:\n return optimizeInArrayExpression(expression, collection)\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an expression can be optimized\n */\nexport function canOptimizeExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type === `func`) {\n switch (expression.name) {\n case `eq`:\n case `gt`:\n case `gte`:\n case `lt`:\n case `lte`:\n return canOptimizeSimpleComparison(expression, collection)\n\n case `and`:\n return canOptimizeAndExpression(expression, collection)\n\n case `or`:\n return canOptimizeOrExpression(expression, collection)\n\n case `in`:\n return canOptimizeInArrayExpression(expression, collection)\n }\n }\n\n return false\n}\n\n/**\n * Optimizes compound range queries on the same field\n * Example: WHERE age > 5 AND age < 10\n */\nfunction optimizeCompoundRangeQuery<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // Group range operations by field\n const fieldOperations = new Map<\n string,\n Array<{\n operation: `gt` | `gte` | `lt` | `lte`\n value: any\n }>\n >()\n\n // Collect all range operations from AND arguments\n for (const arg of expression.args) {\n if (arg.type === `func` && [`gt`, `gte`, `lt`, `lte`].includes(arg.name)) {\n const rangeOp = arg as any\n if (rangeOp.args.length === 2) {\n const leftArg = rangeOp.args[0]!\n const rightArg = rangeOp.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = rangeOp.name as `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const fieldKey = fieldPath.join(`.`)\n const value = (valueArg as any).value\n\n if (!fieldOperations.has(fieldKey)) {\n fieldOperations.set(fieldKey, [])\n }\n fieldOperations.get(fieldKey)!.push({ operation, value })\n }\n }\n }\n }\n\n // Check if we have multiple operations on the same field\n for (const [fieldKey, operations] of fieldOperations) {\n if (operations.length >= 2) {\n const fieldPath = fieldKey.split(`.`)\n const index = findIndexForField(collection, fieldPath)\n\n if (index && index.supports(`gt`) && index.supports(`lt`)) {\n // Build range query options\n let from: any = undefined\n let to: any = undefined\n let fromInclusive = true\n let toInclusive = true\n\n for (const { operation, value } of operations) {\n switch (operation) {\n case `gt`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = false\n }\n break\n case `gte`:\n if (from === undefined || value > from) {\n from = value\n fromInclusive = true\n }\n break\n case `lt`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = false\n }\n break\n case `lte`:\n if (to === undefined || value < to) {\n to = value\n toInclusive = true\n }\n break\n }\n }\n\n const matchingKeys = (index as any).rangeQuery({\n from,\n to,\n fromInclusive,\n toInclusive,\n })\n\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Optimizes simple comparison expressions (eq, gt, gte, lt, lte)\n */\nfunction optimizeSimpleComparison<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldArg: BasicExpression | null = null\n let valueArg: BasicExpression | null = null\n let operation = expression.name as `eq` | `gt` | `gte` | `lt` | `lte`\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n // field op value\n fieldArg = leftArg\n valueArg = rightArg\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n // value op field - need to flip the operation\n fieldArg = rightArg\n valueArg = leftArg\n\n // Flip the operation for reverse comparison\n switch (operation) {\n case `gt`:\n operation = `lt`\n break\n case `gte`:\n operation = `lte`\n break\n case `lt`:\n operation = `gt`\n break\n case `lte`:\n operation = `gte`\n break\n // eq stays the same\n }\n }\n\n if (fieldArg && valueArg) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(collection, fieldPath)\n\n if (index) {\n const queryValue = (valueArg as any).value\n\n // Map operation to IndexOperation enum\n const indexOperation = operation as IndexOperation\n\n // Check if the index supports this operation\n if (!index.supports(indexOperation)) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const matchingKeys = index.lookup(indexOperation, queryValue)\n return { canOptimize: true, matchingKeys }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if a simple comparison can be optimized\n */\nfunction canOptimizeSimpleComparison<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const leftArg = expression.args[0]!\n const rightArg = expression.args[1]!\n\n // Check both directions: field op value AND value op field\n let fieldPath: Array<string> | null = null\n\n if (leftArg.type === `ref` && rightArg.type === `val`) {\n fieldPath = (leftArg as any).path\n } else if (leftArg.type === `val` && rightArg.type === `ref`) {\n fieldPath = (rightArg as any).path\n }\n\n if (fieldPath) {\n const index = findIndexForField(collection, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n\n/**\n * Optimizes AND expressions\n */\nfunction optimizeAndExpression<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n // First, try to optimize compound range queries on the same field\n const compoundRangeResult = optimizeCompoundRangeQuery(expression, collection)\n if (compoundRangeResult.canOptimize) {\n return compoundRangeResult\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, collection)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use intersectSets utility for AND logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const intersectedKeys = intersectSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: intersectedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an AND expression can be optimized\n */\nfunction canOptimizeAndExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, collection))\n}\n\n/**\n * Optimizes OR expressions\n */\nfunction optimizeOrExpression<T extends object, TKey extends string | number>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length < 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const results: Array<OptimizationResult<TKey>> = []\n\n // Try to optimize each part, keep the optimizable ones\n for (const arg of expression.args) {\n const result = optimizeQueryRecursive(arg, collection)\n if (result.canOptimize) {\n results.push(result)\n }\n }\n\n if (results.length > 0) {\n // Use unionSets utility for OR logic\n const allMatchingSets = results.map((r) => r.matchingKeys)\n const unionedKeys = unionSets(allMatchingSets)\n return { canOptimize: true, matchingKeys: unionedKeys }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an OR expression can be optimized\n */\nfunction canOptimizeOrExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length < 2) {\n return false\n }\n\n // If any argument can be optimized, we can gain some speedup\n return expression.args.some((arg) => canOptimizeExpression(arg, collection))\n}\n\n/**\n * Optimizes IN array expressions\n */\nfunction optimizeInArrayExpression<\n T extends object,\n TKey extends string | number,\n>(\n expression: BasicExpression,\n collection: CollectionLike<T, TKey>,\n): OptimizationResult<TKey> {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return { canOptimize: false, matchingKeys: new Set() }\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const values = (arrayArg as any).value\n const index = findIndexForField(collection, fieldPath)\n\n if (index) {\n // Check if the index supports IN operation\n if (index.supports(`in`)) {\n const matchingKeys = index.lookup(`in`, values)\n return { canOptimize: true, matchingKeys }\n } else if (index.supports(`eq`)) {\n // Fallback to multiple equality lookups\n const matchingKeys = new Set<TKey>()\n for (const value of values) {\n const keysForValue = index.lookup(`eq`, value)\n for (const key of keysForValue) {\n matchingKeys.add(key)\n }\n }\n return { canOptimize: true, matchingKeys }\n }\n }\n }\n\n return { canOptimize: false, matchingKeys: new Set() }\n}\n\n/**\n * Checks if an IN array expression can be optimized\n */\nfunction canOptimizeInArrayExpression<\n T extends object,\n TKey extends string | number,\n>(expression: BasicExpression, collection: CollectionLike<T, TKey>): boolean {\n if (expression.type !== `func` || expression.args.length !== 2) {\n return false\n }\n\n const fieldArg = expression.args[0]!\n const arrayArg = expression.args[1]!\n\n if (\n fieldArg.type === `ref` &&\n arrayArg.type === `val` &&\n Array.isArray((arrayArg as any).value)\n ) {\n const fieldPath = (fieldArg as any).path\n const index = findIndexForField(collection, fieldPath)\n return index !== undefined\n }\n\n return false\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/utils.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ import { CompareOptions } from './query/builder/types.js';
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export declare function deepEquals(a: any, b: any): boolean;
|
|
20
|
+
export interface TemporalLike {
|
|
21
|
+
[Symbol.toStringTag]: string;
|
|
22
|
+
toString: () => string;
|
|
23
|
+
equals?: (other: unknown) => boolean;
|
|
24
|
+
}
|
|
20
25
|
/** Checks if the value is a Temporal object by checking for the Temporal brand */
|
|
21
|
-
export declare function isTemporal(a:
|
|
26
|
+
export declare function isTemporal(a: unknown): a is TemporalLike;
|
|
22
27
|
export declare const DEFAULT_COMPARE_OPTIONS: CompareOptions;
|