@tanstack/db 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/cjs/collection.cjs.map +1 -1
  2. package/dist/cjs/collection.d.cts +9 -1
  3. package/dist/cjs/indexes/btree-index.cjs +3 -3
  4. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  5. package/dist/cjs/query/builder/index.cjs +9 -2
  6. package/dist/cjs/query/builder/index.cjs.map +1 -1
  7. package/dist/cjs/query/builder/index.d.cts +2 -2
  8. package/dist/cjs/query/builder/types.d.cts +27 -6
  9. package/dist/cjs/query/compiler/order-by.cjs +19 -21
  10. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  11. package/dist/cjs/query/ir.cjs.map +1 -1
  12. package/dist/cjs/query/ir.d.cts +2 -1
  13. package/dist/cjs/utils/comparison.cjs +29 -7
  14. package/dist/cjs/utils/comparison.cjs.map +1 -1
  15. package/dist/cjs/utils/comparison.d.cts +6 -2
  16. package/dist/esm/collection.d.ts +9 -1
  17. package/dist/esm/collection.js.map +1 -1
  18. package/dist/esm/indexes/btree-index.js +3 -3
  19. package/dist/esm/indexes/btree-index.js.map +1 -1
  20. package/dist/esm/query/builder/index.d.ts +2 -2
  21. package/dist/esm/query/builder/index.js +9 -2
  22. package/dist/esm/query/builder/index.js.map +1 -1
  23. package/dist/esm/query/builder/types.d.ts +27 -6
  24. package/dist/esm/query/compiler/order-by.js +20 -22
  25. package/dist/esm/query/compiler/order-by.js.map +1 -1
  26. package/dist/esm/query/ir.d.ts +2 -1
  27. package/dist/esm/query/ir.js.map +1 -1
  28. package/dist/esm/utils/comparison.d.ts +6 -2
  29. package/dist/esm/utils/comparison.js +30 -8
  30. package/dist/esm/utils/comparison.js.map +1 -1
  31. package/package.json +2 -2
  32. package/src/collection.ts +74 -1
  33. package/src/indexes/btree-index.ts +3 -3
  34. package/src/query/builder/index.ts +19 -2
  35. package/src/query/builder/types.ts +48 -15
  36. package/src/query/compiler/order-by.ts +22 -26
  37. package/src/query/ir.ts +2 -1
  38. package/src/utils/comparison.ts +40 -7
@@ -343,13 +343,20 @@ class BaseQueryBuilder {
343
343
  * .orderBy(({users}) => users.firstName)
344
344
  * ```
345
345
  */
346
- orderBy(callback, direction = `asc`) {
346
+ orderBy(callback, options = `asc`) {
347
347
  const aliases = this._getCurrentAliases();
348
348
  const refProxy = createRefProxy(aliases);
349
349
  const result = callback(refProxy);
350
+ const opts = typeof options === `string` ? { direction: options, nulls: `first`, stringSort: `locale` } : {
351
+ direction: options.direction ?? `asc`,
352
+ nulls: options.nulls ?? `first`,
353
+ stringSort: options.stringSort ?? `locale`,
354
+ locale: options.stringSort === `locale` ? options.locale : void 0,
355
+ localeOptions: options.stringSort === `locale` ? options.localeOptions : void 0
356
+ };
350
357
  const orderByClause = {
351
358
  expression: toExpression(result),
352
- direction
359
+ compareOptions: opts
353
360
  };
354
361
  const existingOrderBy = this.query.orderBy || [];
355
362
  return new BaseQueryBuilder({
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/query/builder/index.ts"],"sourcesContent":["import { CollectionImpl } from \"../../collection.js\"\nimport { CollectionRef, QueryRef } from \"../ir.js\"\nimport {\n InvalidSourceError,\n JoinConditionMustBeEqualityError,\n OnlyOneSourceAllowedError,\n QueryMustHaveFromClauseError,\n SubQueryMustHaveFromClauseError,\n} from \"../../errors.js\"\nimport { createRefProxy, isRefProxy, toExpression } from \"./ref-proxy.js\"\nimport type { NamespacedRow } from \"../../types.js\"\nimport type {\n Aggregate,\n BasicExpression,\n JoinClause,\n OrderBy,\n OrderByClause,\n OrderByDirection,\n QueryIR,\n} from \"../ir.js\"\nimport type {\n Context,\n GroupByCallback,\n JoinOnCallback,\n MergeContext,\n MergeContextWithJoinType,\n OrderByCallback,\n RefProxyForContext,\n ResultTypeFromSelect,\n SchemaFromSource,\n SelectObject,\n Source,\n WhereCallback,\n WithResult,\n} from \"./types.js\"\n\nexport class BaseQueryBuilder<TContext extends Context = Context> {\n private readonly query: Partial<QueryIR> = {}\n\n constructor(query: Partial<QueryIR> = {}) {\n this.query = { ...query }\n }\n\n /**\n * Creates a CollectionRef or QueryRef from a source object\n * @param source - An object with a single key-value pair\n * @param context - Context string for error messages (e.g., \"from clause\", \"join clause\")\n * @returns A tuple of [alias, ref] where alias is the source key and ref is the created reference\n */\n private _createRefForSource<TSource extends Source>(\n source: TSource,\n context: string\n ): [string, CollectionRef | QueryRef] {\n if (Object.keys(source).length !== 1) {\n throw new OnlyOneSourceAllowedError(context)\n }\n\n const alias = Object.keys(source)[0]!\n const sourceValue = source[alias]\n\n let ref: CollectionRef | QueryRef\n\n if (sourceValue instanceof CollectionImpl) {\n ref = new CollectionRef(sourceValue, alias)\n } else if (sourceValue instanceof BaseQueryBuilder) {\n const subQuery = sourceValue._getQuery()\n if (!(subQuery as Partial<QueryIR>).from) {\n throw new SubQueryMustHaveFromClauseError(context)\n }\n ref = new QueryRef(subQuery, alias)\n } else {\n throw new InvalidSourceError()\n }\n\n return [alias, ref]\n }\n\n /**\n * Specify the source table or subquery for the query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @returns A QueryBuilder with the specified source\n *\n * @example\n * ```ts\n * // Query from a collection\n * query.from({ users: usersCollection })\n *\n * // Query from a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query.from({ activeUsers })\n * ```\n */\n from<TSource extends Source>(\n source: TSource\n ): QueryBuilder<{\n baseSchema: SchemaFromSource<TSource>\n schema: SchemaFromSource<TSource>\n fromSourceName: keyof TSource & string\n hasJoins: false\n }> {\n const [, from] = this._createRefForSource(source, `from clause`)\n\n return new BaseQueryBuilder({\n ...this.query,\n from,\n }) as any\n }\n\n /**\n * Join another table or subquery to the current query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @param type - The type of join: 'inner', 'left', 'right', or 'full' (defaults to 'left')\n * @returns A QueryBuilder with the joined table available\n *\n * @example\n * ```ts\n * // Left join users with posts\n * query\n * .from({ users: usersCollection })\n * .join({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n *\n * // Inner join with explicit type\n * query\n * .from({ u: usersCollection })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId), 'inner')\n * ```\n *\n * // Join with a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query\n * .from({ activeUsers })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId))\n */\n join<\n TSource extends Source,\n TJoinType extends `inner` | `left` | `right` | `full` = `left`,\n >(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >,\n type: TJoinType = `left` as TJoinType\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, TJoinType>\n > {\n const [alias, from] = this._createRefForSource(source, `join clause`)\n\n // Create a temporary context for the callback\n const currentAliases = this._getCurrentAliases()\n const newAliases = [...currentAliases, alias]\n const refProxy = createRefProxy(newAliases) as RefProxyForContext<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n\n // Get the join condition expression\n const onExpression = onCallback(refProxy)\n\n // Extract left and right from the expression\n // For now, we'll assume it's an eq function with two arguments\n let left: BasicExpression\n let right: BasicExpression\n\n if (\n onExpression.type === `func` &&\n onExpression.name === `eq` &&\n onExpression.args.length === 2\n ) {\n left = onExpression.args[0]!\n right = onExpression.args[1]!\n } else {\n throw new JoinConditionMustBeEqualityError()\n }\n\n const joinClause: JoinClause = {\n from,\n type,\n left,\n right,\n }\n\n const existingJoins = this.query.join || []\n\n return new BaseQueryBuilder({\n ...this.query,\n join: [...existingJoins, joinClause],\n }) as any\n }\n\n /**\n * Perform a LEFT JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the left joined table available\n *\n * @example\n * ```ts\n * // Left join users with posts\n * query\n * .from({ users: usersCollection })\n * .leftJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n leftJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `left`>\n > {\n return this.join(source, onCallback, `left`)\n }\n\n /**\n * Perform a RIGHT JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the right joined table available\n *\n * @example\n * ```ts\n * // Right join users with posts\n * query\n * .from({ users: usersCollection })\n * .rightJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n rightJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `right`>\n > {\n return this.join(source, onCallback, `right`)\n }\n\n /**\n * Perform an INNER JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the inner joined table available\n *\n * @example\n * ```ts\n * // Inner join users with posts\n * query\n * .from({ users: usersCollection })\n * .innerJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n innerJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `inner`>\n > {\n return this.join(source, onCallback, `inner`)\n }\n\n /**\n * Perform a FULL JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the full joined table available\n *\n * @example\n * ```ts\n * // Full join users with posts\n * query\n * .from({ users: usersCollection })\n * .fullJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n fullJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `full`>\n > {\n return this.join(source, onCallback, `full`)\n }\n\n /**\n * Filter rows based on a condition\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the where condition applied\n *\n * @example\n * ```ts\n * // Simple condition\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n *\n * // Multiple conditions\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => and(\n * gt(users.age, 18),\n * eq(users.active, true)\n * ))\n *\n * // Multiple where calls are ANDed together\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n * .where(({users}) => eq(users.active, true))\n * ```\n */\n where(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const expression = callback(refProxy)\n\n const existingWhere = this.query.where || []\n\n return new BaseQueryBuilder({\n ...this.query,\n where: [...existingWhere, expression],\n }) as any\n }\n\n /**\n * Filter grouped rows based on aggregate conditions\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the having condition applied\n *\n * @example\n * ```ts\n * // Filter groups by count\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .having(({posts}) => gt(count(posts.id), 5))\n *\n * // Filter by average\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(avg(orders.total), 100))\n *\n * // Multiple having calls are ANDed together\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(count(orders.id), 5))\n * .having(({orders}) => gt(avg(orders.total), 100))\n * ```\n */\n having(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const expression = callback(refProxy)\n\n const existingHaving = this.query.having || []\n\n return new BaseQueryBuilder({\n ...this.query,\n having: [...existingHaving, expression],\n }) as any\n }\n\n /**\n * Select specific columns or computed values from the query\n *\n * @param callback - A function that receives table references and returns an object with selected fields or expressions\n * @returns A QueryBuilder that returns only the selected fields\n *\n * @example\n * ```ts\n * // Select specific columns\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * name: users.name,\n * email: users.email\n * }))\n *\n * // Select with computed values\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * fullName: concat(users.firstName, ' ', users.lastName),\n * ageInMonths: mul(users.age, 12)\n * }))\n *\n * // Select with aggregates (requires GROUP BY)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count(posts.id)\n * }))\n * ```\n */\n select<TSelectObject extends SelectObject>(\n callback: (refs: RefProxyForContext<TContext>) => TSelectObject\n ): QueryBuilder<WithResult<TContext, ResultTypeFromSelect<TSelectObject>>> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const selectObject = callback(refProxy)\n\n // Check if any tables were spread during the callback\n const spreadSentinels = (refProxy as any).__spreadSentinels as Set<string>\n\n // Convert the select object to use expressions, including spread sentinels\n const select: Record<string, BasicExpression | Aggregate> = {}\n\n // First, add spread sentinels for any tables that were spread\n for (const spreadAlias of spreadSentinels) {\n const sentinelKey = `__SPREAD_SENTINEL__${spreadAlias}`\n select[sentinelKey] = toExpression(spreadAlias) // Use alias as a simple reference\n }\n\n // Then add the explicit select fields\n for (const [key, value] of Object.entries(selectObject)) {\n if (isRefProxy(value)) {\n select[key] = toExpression(value)\n } else if (\n typeof value === `object` &&\n `type` in value &&\n (value.type === `agg` || value.type === `func`)\n ) {\n select[key] = value as BasicExpression | Aggregate\n } else {\n select[key] = toExpression(value)\n }\n }\n\n return new BaseQueryBuilder({\n ...this.query,\n select,\n fnSelect: undefined, // remove the fnSelect clause if it exists\n }) as any\n }\n\n /**\n * Sort the query results by one or more columns\n *\n * @param callback - A function that receives table references and returns the field to sort by\n * @param direction - Sort direction: 'asc' for ascending, 'desc' for descending (defaults to 'asc')\n * @returns A QueryBuilder with the ordering applied\n *\n * @example\n * ```ts\n * // Sort by a single column\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.name)\n *\n * // Sort descending\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.createdAt, 'desc')\n *\n * // Multiple sorts (chain orderBy calls)\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.lastName)\n * .orderBy(({users}) => users.firstName)\n * ```\n */\n orderBy(\n callback: OrderByCallback<TContext>,\n direction: OrderByDirection = `asc`\n ): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const result = callback(refProxy)\n\n // Create the new OrderBy structure with expression and direction\n const orderByClause: OrderByClause = {\n expression: toExpression(result),\n direction,\n }\n\n const existingOrderBy: OrderBy = this.query.orderBy || []\n\n return new BaseQueryBuilder({\n ...this.query,\n orderBy: [...existingOrderBy, orderByClause],\n }) as any\n }\n\n /**\n * Group rows by one or more columns for aggregation\n *\n * @param callback - A function that receives table references and returns the field(s) to group by\n * @returns A QueryBuilder with grouping applied (enables aggregate functions in SELECT and HAVING)\n *\n * @example\n * ```ts\n * // Group by a single column\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count()\n * }))\n *\n * // Group by multiple columns\n * query\n * .from({ sales: salesCollection })\n * .groupBy(({sales}) => [sales.region, sales.category])\n * .select(({sales, sum}) => ({\n * region: sales.region,\n * category: sales.category,\n * totalSales: sum(sales.amount)\n * }))\n * ```\n */\n groupBy(callback: GroupByCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const result = callback(refProxy)\n\n const newExpressions = Array.isArray(result)\n ? result.map((r) => toExpression(r))\n : [toExpression(result)]\n\n // Replace existing groupBy expressions instead of extending them\n return new BaseQueryBuilder({\n ...this.query,\n groupBy: newExpressions,\n }) as any\n }\n\n /**\n * Limit the number of rows returned by the query\n * `orderBy` is required for `limit`\n *\n * @param count - Maximum number of rows to return\n * @returns A QueryBuilder with the limit applied\n *\n * @example\n * ```ts\n * // Get top 5 posts by likes\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.likes, 'desc')\n * .limit(5)\n * ```\n */\n limit(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n limit: count,\n }) as any\n }\n\n /**\n * Skip a number of rows before returning results\n * `orderBy` is required for `offset`\n *\n * @param count - Number of rows to skip\n * @returns A QueryBuilder with the offset applied\n *\n * @example\n * ```ts\n * // Get second page of results\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.createdAt, 'desc')\n * .offset(page * pageSize)\n * .limit(pageSize)\n * ```\n */\n offset(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n offset: count,\n }) as any\n }\n\n /**\n * Specify that the query should return distinct rows.\n * Deduplicates rows based on the selected columns.\n * @returns A QueryBuilder with distinct enabled\n *\n * @example\n * ```ts\n * // Get countries our users are from\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => users.country)\n * .distinct()\n * ```\n */\n distinct(): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n distinct: true,\n }) as any\n }\n\n // Helper methods\n private _getCurrentAliases(): Array<string> {\n const aliases: Array<string> = []\n\n // Add the from alias\n if (this.query.from) {\n aliases.push(this.query.from.alias)\n }\n\n // Add join aliases\n if (this.query.join) {\n for (const join of this.query.join) {\n aliases.push(join.from.alias)\n }\n }\n\n return aliases\n }\n\n /**\n * Functional variants of the query builder\n * These are imperative function that are called for ery row.\n * Warning: that these cannot be optimized by the query compiler, and may prevent\n * some type of optimizations being possible.\n * @example\n * ```ts\n * q.fn.select((row) => ({\n * name: row.user.name.toUpperCase(),\n * age: row.user.age + 1,\n * }))\n * ```\n */\n get fn() {\n const builder = this\n return {\n /**\n * Select fields using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns the selected value\n * @returns A QueryBuilder with functional selection applied\n *\n * @example\n * ```ts\n * // Functional select (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.select(row => ({\n * name: row.users.name.toUpperCase(),\n * age: row.users.age + 1,\n * }))\n * ```\n */\n select<TFuncSelectResult>(\n callback: (row: TContext[`schema`]) => TFuncSelectResult\n ): QueryBuilder<WithResult<TContext, TFuncSelectResult>> {\n return new BaseQueryBuilder({\n ...builder.query,\n select: undefined, // remove the select clause if it exists\n fnSelect: callback,\n })\n },\n /**\n * Filter rows using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns a boolean\n * @returns A QueryBuilder with functional filtering applied\n *\n * @example\n * ```ts\n * // Functional where (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.where(row => row.users.name.startsWith('A'))\n * ```\n */\n where(\n callback: (row: TContext[`schema`]) => any\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnWhere: [\n ...(builder.query.fnWhere || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n /**\n * Filter grouped rows using a function that operates on each aggregated row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives an aggregated row and returns a boolean\n * @returns A QueryBuilder with functional having filter applied\n *\n * @example\n * ```ts\n * // Functional having (not optimized)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .fn.having(row => row.count > 5)\n * ```\n */\n having(\n callback: (row: TContext[`schema`]) => any\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnHaving: [\n ...(builder.query.fnHaving || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n }\n }\n\n _getQuery(): QueryIR {\n if (!this.query.from) {\n throw new QueryMustHaveFromClauseError()\n }\n return this.query as QueryIR\n }\n}\n\n// Internal function to build a query from a callback\n// used by liveQueryCollectionOptions.query\nexport function buildQuery<TContext extends Context>(\n fn: (builder: InitialQueryBuilder) => QueryBuilder<TContext>\n): QueryIR {\n const result = fn(new BaseQueryBuilder())\n return getQueryIR(result)\n}\n\n// Internal function to get the QueryIR from a builder\nexport function getQueryIR(\n builder: BaseQueryBuilder | QueryBuilder<any> | InitialQueryBuilder\n): QueryIR {\n return (builder as unknown as BaseQueryBuilder)._getQuery()\n}\n\n// Type-only exports for the query builder\nexport type InitialQueryBuilder = Pick<BaseQueryBuilder<Context>, `from`>\n\nexport type InitialQueryBuilderConstructor = new () => InitialQueryBuilder\n\nexport type QueryBuilder<TContext extends Context> = Omit<\n BaseQueryBuilder<TContext>,\n `from` | `_getQuery`\n>\n\n// Main query builder class alias with the constructor type modified to hide all\n// but the from method on the initial instance\nexport const Query: InitialQueryBuilderConstructor = BaseQueryBuilder\n\n// Helper type to extract context from a QueryBuilder\nexport type ExtractContext<T> =\n T extends BaseQueryBuilder<infer TContext>\n ? TContext\n : T extends QueryBuilder<infer TContext>\n ? TContext\n : never\n\n// Export the types from types.ts for convenience\nexport type { Context, Source, GetResult } from \"./types.js\"\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/query/builder/index.ts"],"sourcesContent":["import { CollectionImpl } from \"../../collection.js\"\nimport { CollectionRef, QueryRef } from \"../ir.js\"\nimport {\n InvalidSourceError,\n JoinConditionMustBeEqualityError,\n OnlyOneSourceAllowedError,\n QueryMustHaveFromClauseError,\n SubQueryMustHaveFromClauseError,\n} from \"../../errors.js\"\nimport { createRefProxy, isRefProxy, toExpression } from \"./ref-proxy.js\"\nimport type { NamespacedRow } from \"../../types.js\"\nimport type {\n Aggregate,\n BasicExpression,\n JoinClause,\n OrderBy,\n OrderByClause,\n OrderByDirection,\n QueryIR,\n} from \"../ir.js\"\nimport type {\n CompareOptions,\n Context,\n GroupByCallback,\n JoinOnCallback,\n MergeContext,\n MergeContextWithJoinType,\n OrderByCallback,\n OrderByOptions,\n RefProxyForContext,\n ResultTypeFromSelect,\n SchemaFromSource,\n SelectObject,\n Source,\n WhereCallback,\n WithResult,\n} from \"./types.js\"\n\nexport class BaseQueryBuilder<TContext extends Context = Context> {\n private readonly query: Partial<QueryIR> = {}\n\n constructor(query: Partial<QueryIR> = {}) {\n this.query = { ...query }\n }\n\n /**\n * Creates a CollectionRef or QueryRef from a source object\n * @param source - An object with a single key-value pair\n * @param context - Context string for error messages (e.g., \"from clause\", \"join clause\")\n * @returns A tuple of [alias, ref] where alias is the source key and ref is the created reference\n */\n private _createRefForSource<TSource extends Source>(\n source: TSource,\n context: string\n ): [string, CollectionRef | QueryRef] {\n if (Object.keys(source).length !== 1) {\n throw new OnlyOneSourceAllowedError(context)\n }\n\n const alias = Object.keys(source)[0]!\n const sourceValue = source[alias]\n\n let ref: CollectionRef | QueryRef\n\n if (sourceValue instanceof CollectionImpl) {\n ref = new CollectionRef(sourceValue, alias)\n } else if (sourceValue instanceof BaseQueryBuilder) {\n const subQuery = sourceValue._getQuery()\n if (!(subQuery as Partial<QueryIR>).from) {\n throw new SubQueryMustHaveFromClauseError(context)\n }\n ref = new QueryRef(subQuery, alias)\n } else {\n throw new InvalidSourceError()\n }\n\n return [alias, ref]\n }\n\n /**\n * Specify the source table or subquery for the query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @returns A QueryBuilder with the specified source\n *\n * @example\n * ```ts\n * // Query from a collection\n * query.from({ users: usersCollection })\n *\n * // Query from a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query.from({ activeUsers })\n * ```\n */\n from<TSource extends Source>(\n source: TSource\n ): QueryBuilder<{\n baseSchema: SchemaFromSource<TSource>\n schema: SchemaFromSource<TSource>\n fromSourceName: keyof TSource & string\n hasJoins: false\n }> {\n const [, from] = this._createRefForSource(source, `from clause`)\n\n return new BaseQueryBuilder({\n ...this.query,\n from,\n }) as any\n }\n\n /**\n * Join another table or subquery to the current query\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @param type - The type of join: 'inner', 'left', 'right', or 'full' (defaults to 'left')\n * @returns A QueryBuilder with the joined table available\n *\n * @example\n * ```ts\n * // Left join users with posts\n * query\n * .from({ users: usersCollection })\n * .join({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n *\n * // Inner join with explicit type\n * query\n * .from({ u: usersCollection })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId), 'inner')\n * ```\n *\n * // Join with a subquery\n * const activeUsers = query.from({ u: usersCollection }).where(({u}) => u.active)\n * query\n * .from({ activeUsers })\n * .join({ p: postsCollection }, ({u, p}) => eq(u.id, p.userId))\n */\n join<\n TSource extends Source,\n TJoinType extends `inner` | `left` | `right` | `full` = `left`,\n >(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >,\n type: TJoinType = `left` as TJoinType\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, TJoinType>\n > {\n const [alias, from] = this._createRefForSource(source, `join clause`)\n\n // Create a temporary context for the callback\n const currentAliases = this._getCurrentAliases()\n const newAliases = [...currentAliases, alias]\n const refProxy = createRefProxy(newAliases) as RefProxyForContext<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n\n // Get the join condition expression\n const onExpression = onCallback(refProxy)\n\n // Extract left and right from the expression\n // For now, we'll assume it's an eq function with two arguments\n let left: BasicExpression\n let right: BasicExpression\n\n if (\n onExpression.type === `func` &&\n onExpression.name === `eq` &&\n onExpression.args.length === 2\n ) {\n left = onExpression.args[0]!\n right = onExpression.args[1]!\n } else {\n throw new JoinConditionMustBeEqualityError()\n }\n\n const joinClause: JoinClause = {\n from,\n type,\n left,\n right,\n }\n\n const existingJoins = this.query.join || []\n\n return new BaseQueryBuilder({\n ...this.query,\n join: [...existingJoins, joinClause],\n }) as any\n }\n\n /**\n * Perform a LEFT JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the left joined table available\n *\n * @example\n * ```ts\n * // Left join users with posts\n * query\n * .from({ users: usersCollection })\n * .leftJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n leftJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `left`>\n > {\n return this.join(source, onCallback, `left`)\n }\n\n /**\n * Perform a RIGHT JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the right joined table available\n *\n * @example\n * ```ts\n * // Right join users with posts\n * query\n * .from({ users: usersCollection })\n * .rightJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n rightJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `right`>\n > {\n return this.join(source, onCallback, `right`)\n }\n\n /**\n * Perform an INNER JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the inner joined table available\n *\n * @example\n * ```ts\n * // Inner join users with posts\n * query\n * .from({ users: usersCollection })\n * .innerJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n innerJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `inner`>\n > {\n return this.join(source, onCallback, `inner`)\n }\n\n /**\n * Perform a FULL JOIN with another table or subquery\n *\n * @param source - An object with a single key-value pair where the key is the table alias and the value is a Collection or subquery\n * @param onCallback - A function that receives table references and returns the join condition\n * @returns A QueryBuilder with the full joined table available\n *\n * @example\n * ```ts\n * // Full join users with posts\n * query\n * .from({ users: usersCollection })\n * .fullJoin({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.userId))\n * ```\n */\n fullJoin<TSource extends Source>(\n source: TSource,\n onCallback: JoinOnCallback<\n MergeContext<TContext, SchemaFromSource<TSource>>\n >\n ): QueryBuilder<\n MergeContextWithJoinType<TContext, SchemaFromSource<TSource>, `full`>\n > {\n return this.join(source, onCallback, `full`)\n }\n\n /**\n * Filter rows based on a condition\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the where condition applied\n *\n * @example\n * ```ts\n * // Simple condition\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n *\n * // Multiple conditions\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => and(\n * gt(users.age, 18),\n * eq(users.active, true)\n * ))\n *\n * // Multiple where calls are ANDed together\n * query\n * .from({ users: usersCollection })\n * .where(({users}) => gt(users.age, 18))\n * .where(({users}) => eq(users.active, true))\n * ```\n */\n where(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const expression = callback(refProxy)\n\n const existingWhere = this.query.where || []\n\n return new BaseQueryBuilder({\n ...this.query,\n where: [...existingWhere, expression],\n }) as any\n }\n\n /**\n * Filter grouped rows based on aggregate conditions\n *\n * @param callback - A function that receives table references and returns an expression\n * @returns A QueryBuilder with the having condition applied\n *\n * @example\n * ```ts\n * // Filter groups by count\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .having(({posts}) => gt(count(posts.id), 5))\n *\n * // Filter by average\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(avg(orders.total), 100))\n *\n * // Multiple having calls are ANDed together\n * query\n * .from({ orders: ordersCollection })\n * .groupBy(({orders}) => orders.customerId)\n * .having(({orders}) => gt(count(orders.id), 5))\n * .having(({orders}) => gt(avg(orders.total), 100))\n * ```\n */\n having(callback: WhereCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const expression = callback(refProxy)\n\n const existingHaving = this.query.having || []\n\n return new BaseQueryBuilder({\n ...this.query,\n having: [...existingHaving, expression],\n }) as any\n }\n\n /**\n * Select specific columns or computed values from the query\n *\n * @param callback - A function that receives table references and returns an object with selected fields or expressions\n * @returns A QueryBuilder that returns only the selected fields\n *\n * @example\n * ```ts\n * // Select specific columns\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * name: users.name,\n * email: users.email\n * }))\n *\n * // Select with computed values\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => ({\n * fullName: concat(users.firstName, ' ', users.lastName),\n * ageInMonths: mul(users.age, 12)\n * }))\n *\n * // Select with aggregates (requires GROUP BY)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count(posts.id)\n * }))\n * ```\n */\n select<TSelectObject extends SelectObject>(\n callback: (refs: RefProxyForContext<TContext>) => TSelectObject\n ): QueryBuilder<WithResult<TContext, ResultTypeFromSelect<TSelectObject>>> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const selectObject = callback(refProxy)\n\n // Check if any tables were spread during the callback\n const spreadSentinels = (refProxy as any).__spreadSentinels as Set<string>\n\n // Convert the select object to use expressions, including spread sentinels\n const select: Record<string, BasicExpression | Aggregate> = {}\n\n // First, add spread sentinels for any tables that were spread\n for (const spreadAlias of spreadSentinels) {\n const sentinelKey = `__SPREAD_SENTINEL__${spreadAlias}`\n select[sentinelKey] = toExpression(spreadAlias) // Use alias as a simple reference\n }\n\n // Then add the explicit select fields\n for (const [key, value] of Object.entries(selectObject)) {\n if (isRefProxy(value)) {\n select[key] = toExpression(value)\n } else if (\n typeof value === `object` &&\n `type` in value &&\n (value.type === `agg` || value.type === `func`)\n ) {\n select[key] = value as BasicExpression | Aggregate\n } else {\n select[key] = toExpression(value)\n }\n }\n\n return new BaseQueryBuilder({\n ...this.query,\n select,\n fnSelect: undefined, // remove the fnSelect clause if it exists\n }) as any\n }\n\n /**\n * Sort the query results by one or more columns\n *\n * @param callback - A function that receives table references and returns the field to sort by\n * @param direction - Sort direction: 'asc' for ascending, 'desc' for descending (defaults to 'asc')\n * @returns A QueryBuilder with the ordering applied\n *\n * @example\n * ```ts\n * // Sort by a single column\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.name)\n *\n * // Sort descending\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.createdAt, 'desc')\n *\n * // Multiple sorts (chain orderBy calls)\n * query\n * .from({ users: usersCollection })\n * .orderBy(({users}) => users.lastName)\n * .orderBy(({users}) => users.firstName)\n * ```\n */\n orderBy(\n callback: OrderByCallback<TContext>,\n options: OrderByDirection | OrderByOptions = `asc`\n ): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const result = callback(refProxy)\n\n const opts: CompareOptions =\n typeof options === `string`\n ? { direction: options, nulls: `first`, stringSort: `locale` }\n : {\n direction: options.direction ?? `asc`,\n nulls: options.nulls ?? `first`,\n stringSort: options.stringSort ?? `locale`,\n locale:\n options.stringSort === `locale` ? options.locale : undefined,\n localeOptions:\n options.stringSort === `locale`\n ? options.localeOptions\n : undefined,\n }\n\n // Create the new OrderBy structure with expression and direction\n const orderByClause: OrderByClause = {\n expression: toExpression(result),\n compareOptions: opts,\n }\n\n const existingOrderBy: OrderBy = this.query.orderBy || []\n\n return new BaseQueryBuilder({\n ...this.query,\n orderBy: [...existingOrderBy, orderByClause],\n }) as any\n }\n\n /**\n * Group rows by one or more columns for aggregation\n *\n * @param callback - A function that receives table references and returns the field(s) to group by\n * @returns A QueryBuilder with grouping applied (enables aggregate functions in SELECT and HAVING)\n *\n * @example\n * ```ts\n * // Group by a single column\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .select(({posts, count}) => ({\n * userId: posts.userId,\n * postCount: count()\n * }))\n *\n * // Group by multiple columns\n * query\n * .from({ sales: salesCollection })\n * .groupBy(({sales}) => [sales.region, sales.category])\n * .select(({sales, sum}) => ({\n * region: sales.region,\n * category: sales.category,\n * totalSales: sum(sales.amount)\n * }))\n * ```\n */\n groupBy(callback: GroupByCallback<TContext>): QueryBuilder<TContext> {\n const aliases = this._getCurrentAliases()\n const refProxy = createRefProxy(aliases) as RefProxyForContext<TContext>\n const result = callback(refProxy)\n\n const newExpressions = Array.isArray(result)\n ? result.map((r) => toExpression(r))\n : [toExpression(result)]\n\n // Replace existing groupBy expressions instead of extending them\n return new BaseQueryBuilder({\n ...this.query,\n groupBy: newExpressions,\n }) as any\n }\n\n /**\n * Limit the number of rows returned by the query\n * `orderBy` is required for `limit`\n *\n * @param count - Maximum number of rows to return\n * @returns A QueryBuilder with the limit applied\n *\n * @example\n * ```ts\n * // Get top 5 posts by likes\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.likes, 'desc')\n * .limit(5)\n * ```\n */\n limit(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n limit: count,\n }) as any\n }\n\n /**\n * Skip a number of rows before returning results\n * `orderBy` is required for `offset`\n *\n * @param count - Number of rows to skip\n * @returns A QueryBuilder with the offset applied\n *\n * @example\n * ```ts\n * // Get second page of results\n * query\n * .from({ posts: postsCollection })\n * .orderBy(({posts}) => posts.createdAt, 'desc')\n * .offset(page * pageSize)\n * .limit(pageSize)\n * ```\n */\n offset(count: number): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n offset: count,\n }) as any\n }\n\n /**\n * Specify that the query should return distinct rows.\n * Deduplicates rows based on the selected columns.\n * @returns A QueryBuilder with distinct enabled\n *\n * @example\n * ```ts\n * // Get countries our users are from\n * query\n * .from({ users: usersCollection })\n * .select(({users}) => users.country)\n * .distinct()\n * ```\n */\n distinct(): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...this.query,\n distinct: true,\n }) as any\n }\n\n // Helper methods\n private _getCurrentAliases(): Array<string> {\n const aliases: Array<string> = []\n\n // Add the from alias\n if (this.query.from) {\n aliases.push(this.query.from.alias)\n }\n\n // Add join aliases\n if (this.query.join) {\n for (const join of this.query.join) {\n aliases.push(join.from.alias)\n }\n }\n\n return aliases\n }\n\n /**\n * Functional variants of the query builder\n * These are imperative function that are called for ery row.\n * Warning: that these cannot be optimized by the query compiler, and may prevent\n * some type of optimizations being possible.\n * @example\n * ```ts\n * q.fn.select((row) => ({\n * name: row.user.name.toUpperCase(),\n * age: row.user.age + 1,\n * }))\n * ```\n */\n get fn() {\n const builder = this\n return {\n /**\n * Select fields using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns the selected value\n * @returns A QueryBuilder with functional selection applied\n *\n * @example\n * ```ts\n * // Functional select (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.select(row => ({\n * name: row.users.name.toUpperCase(),\n * age: row.users.age + 1,\n * }))\n * ```\n */\n select<TFuncSelectResult>(\n callback: (row: TContext[`schema`]) => TFuncSelectResult\n ): QueryBuilder<WithResult<TContext, TFuncSelectResult>> {\n return new BaseQueryBuilder({\n ...builder.query,\n select: undefined, // remove the select clause if it exists\n fnSelect: callback,\n })\n },\n /**\n * Filter rows using a function that operates on each row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives a row and returns a boolean\n * @returns A QueryBuilder with functional filtering applied\n *\n * @example\n * ```ts\n * // Functional where (not optimized)\n * query\n * .from({ users: usersCollection })\n * .fn.where(row => row.users.name.startsWith('A'))\n * ```\n */\n where(\n callback: (row: TContext[`schema`]) => any\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnWhere: [\n ...(builder.query.fnWhere || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n /**\n * Filter grouped rows using a function that operates on each aggregated row\n * Warning: This cannot be optimized by the query compiler\n *\n * @param callback - A function that receives an aggregated row and returns a boolean\n * @returns A QueryBuilder with functional having filter applied\n *\n * @example\n * ```ts\n * // Functional having (not optimized)\n * query\n * .from({ posts: postsCollection })\n * .groupBy(({posts}) => posts.userId)\n * .fn.having(row => row.count > 5)\n * ```\n */\n having(\n callback: (row: TContext[`schema`]) => any\n ): QueryBuilder<TContext> {\n return new BaseQueryBuilder({\n ...builder.query,\n fnHaving: [\n ...(builder.query.fnHaving || []),\n callback as (row: NamespacedRow) => any,\n ],\n })\n },\n }\n }\n\n _getQuery(): QueryIR {\n if (!this.query.from) {\n throw new QueryMustHaveFromClauseError()\n }\n return this.query as QueryIR\n }\n}\n\n// Internal function to build a query from a callback\n// used by liveQueryCollectionOptions.query\nexport function buildQuery<TContext extends Context>(\n fn: (builder: InitialQueryBuilder) => QueryBuilder<TContext>\n): QueryIR {\n const result = fn(new BaseQueryBuilder())\n return getQueryIR(result)\n}\n\n// Internal function to get the QueryIR from a builder\nexport function getQueryIR(\n builder: BaseQueryBuilder | QueryBuilder<any> | InitialQueryBuilder\n): QueryIR {\n return (builder as unknown as BaseQueryBuilder)._getQuery()\n}\n\n// Type-only exports for the query builder\nexport type InitialQueryBuilder = Pick<BaseQueryBuilder<Context>, `from`>\n\nexport type InitialQueryBuilderConstructor = new () => InitialQueryBuilder\n\nexport type QueryBuilder<TContext extends Context> = Omit<\n BaseQueryBuilder<TContext>,\n `from` | `_getQuery`\n>\n\n// Main query builder class alias with the constructor type modified to hide all\n// but the from method on the initial instance\nexport const Query: InitialQueryBuilderConstructor = BaseQueryBuilder\n\n// Helper type to extract context from a QueryBuilder\nexport type ExtractContext<T> =\n T extends BaseQueryBuilder<infer TContext>\n ? TContext\n : T extends QueryBuilder<infer TContext>\n ? TContext\n : never\n\n// Export the types from types.ts for convenience\nexport type { Context, Source, GetResult } from \"./types.js\"\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { CollectionImpl } from '../../collection.js';
2
- import { Aggregate, BasicExpression } from '../ir.js';
2
+ import { Aggregate, BasicExpression, OrderByDirection } from '../ir.js';
3
3
  import { QueryBuilder } from './index.js';
4
+ import { ResolveType } from '../../types.js';
4
5
  export interface Context {
5
6
  baseSchema: ContextSchema;
6
7
  schema: ContextSchema;
@@ -13,17 +14,37 @@ export type ContextSchema = Record<string, unknown>;
13
14
  export type Source = {
14
15
  [alias: string]: CollectionImpl<any, any> | QueryBuilder<Context>;
15
16
  };
16
- export type InferCollectionType<T> = T extends CollectionImpl<infer U> ? U : never;
17
+ export type InferCollectionType<T> = T extends CollectionImpl<infer U, any, any, infer TSchema, any> ? ResolveType<U, TSchema, U> : never;
17
18
  export type SchemaFromSource<T extends Source> = Prettify<{
18
- [K in keyof T]: T[K] extends CollectionImpl<infer U> ? U : T[K] extends QueryBuilder<infer TContext> ? GetResult<TContext> : never;
19
+ [K in keyof T]: T[K] extends CollectionImpl<any, any, any, any, any> ? InferCollectionType<T[K]> : T[K] extends QueryBuilder<infer TContext> ? GetResult<TContext> : never;
19
20
  }>;
20
21
  export type GetAliases<TContext extends Context> = keyof TContext[`schema`];
21
22
  export type WhereCallback<TContext extends Context> = (refs: RefProxyForContext<TContext>) => any;
22
23
  export type SelectObject<T extends Record<string, BasicExpression | Aggregate | RefProxy | RefProxyFor<any>> = Record<string, BasicExpression | Aggregate | RefProxy | RefProxyFor<any>>> = T;
23
24
  export type ResultTypeFromSelect<TSelectObject> = {
24
- [K in keyof TSelectObject]: TSelectObject[K] extends RefProxy<infer T> ? T : TSelectObject[K] extends BasicExpression<infer T> ? T : TSelectObject[K] extends Aggregate<infer T> ? T : TSelectObject[K] extends RefProxyFor<infer T> ? T : never;
25
+ [K in keyof TSelectObject]: TSelectObject[K] extends RefProxy<infer T> ? T : TSelectObject[K] extends BasicExpression<infer T> ? T : TSelectObject[K] extends Aggregate<infer T> ? T : TSelectObject[K] extends RefProxyFor<infer T> ? T : TSelectObject[K] extends undefined ? undefined : TSelectObject[K] extends {
26
+ __type: infer U;
27
+ } ? U : never;
25
28
  };
26
29
  export type OrderByCallback<TContext extends Context> = (refs: RefProxyForContext<TContext>) => any;
30
+ export type OrderByOptions = {
31
+ direction?: OrderByDirection;
32
+ nulls?: `first` | `last`;
33
+ } & StringSortOpts;
34
+ export type StringSortOpts = {
35
+ stringSort?: `lexical`;
36
+ } | {
37
+ stringSort?: `locale`;
38
+ locale?: string;
39
+ localeOptions?: object;
40
+ };
41
+ export type CompareOptions = {
42
+ direction: OrderByDirection;
43
+ nulls: `first` | `last`;
44
+ stringSort: `lexical` | `locale`;
45
+ locale?: string;
46
+ localeOptions?: object;
47
+ };
27
48
  export type GroupByCallback<TContext extends Context> = (refs: RefProxyForContext<TContext>) => any;
28
49
  export type JoinOnCallback<TContext extends Context> = (refs: RefProxyForContext<TContext>) => any;
29
50
  export type RefProxyForContext<TContext extends Context> = {
@@ -33,9 +54,9 @@ type IsExactlyUndefined<T> = [T] extends [undefined] ? true : false;
33
54
  type IsOptional<T> = undefined extends T ? true : false;
34
55
  type NonUndefined<T> = T extends undefined ? never : T;
35
56
  export type RefProxyFor<T> = OmitRefProxy<IsExactlyUndefined<T> extends true ? RefProxy<T> : IsOptional<T> extends true ? NonUndefined<T> extends Record<string, any> ? {
36
- [K in keyof NonUndefined<T>]: NonUndefined<T>[K] extends Record<string, any> ? RefProxyFor<NonUndefined<T>[K] | undefined> & RefProxy<NonUndefined<T>[K] | undefined> : RefProxy<NonUndefined<T>[K] | undefined>;
57
+ [K in keyof NonUndefined<T>]-?: NonUndefined<T>[K] extends Record<string, any> ? RefProxyFor<NonUndefined<T>[K]> & RefProxy<NonUndefined<T>[K] | undefined> : RefProxy<NonUndefined<T>[K] | undefined>;
37
58
  } & RefProxy<T> : RefProxy<T> : T extends Record<string, any> ? {
38
- [K in keyof T]: T[K] extends Record<string, any> ? RefProxyFor<T[K]> & RefProxy<T[K]> : RefProxy<T[K]>;
59
+ [K in keyof T]-?: undefined extends T[K] ? T[K] extends Record<string, any> ? RefProxyFor<T[K]> & RefProxy<T[K]> : RefProxy<T[K]> : T[K] extends Record<string, any> ? RefProxyFor<T[K]> & RefProxy<T[K]> : RefProxy<T[K]>;
39
60
  } & RefProxy<T> : RefProxy<T>>;
40
61
  export type Ref<T> = RefProxyFor<T>;
41
62
  type OmitRefProxy<T> = Omit<T, `__refProxy` | `__path` | `__type`>;
@@ -1,10 +1,10 @@
1
1
  import { orderByWithFractionalIndex } from "@tanstack/db-ivm";
2
- import { descComparator, ascComparator } from "../../utils/comparison.js";
2
+ import { makeComparator, defaultComparator } from "../../utils/comparison.js";
3
3
  import { compileExpression } from "./evaluators.js";
4
4
  function processOrderBy(pipeline, orderByClause, limit, offset) {
5
5
  const compiledOrderBy = orderByClause.map((clause) => ({
6
6
  compiledExpression: compileExpression(clause.expression),
7
- direction: clause.direction
7
+ compareOptions: clause.compareOptions
8
8
  }));
9
9
  const valueExtractor = (row) => {
10
10
  const orderByContext = { ...row };
@@ -21,29 +21,27 @@ function processOrderBy(pipeline, orderByClause, limit, offset) {
21
21
  }
22
22
  return null;
23
23
  };
24
- const makeComparator = () => {
25
- return (a, b) => {
26
- if (orderByClause.length > 1) {
27
- const arrayA = a;
28
- const arrayB = b;
29
- for (let i = 0; i < orderByClause.length; i++) {
30
- const direction = orderByClause[i].direction;
31
- const compareFn = direction === `desc` ? descComparator : ascComparator;
32
- const result = compareFn(arrayA[i], arrayB[i]);
33
- if (result !== 0) {
34
- return result;
35
- }
24
+ const comparator = (a, b) => {
25
+ if (orderByClause.length > 1) {
26
+ const arrayA = a;
27
+ const arrayB = b;
28
+ for (let i = 0; i < orderByClause.length; i++) {
29
+ const clause = orderByClause[i];
30
+ const compareFn = makeComparator(clause.compareOptions);
31
+ const result = compareFn(arrayA[i], arrayB[i]);
32
+ if (result !== 0) {
33
+ return result;
36
34
  }
37
- return arrayA.length - arrayB.length;
38
- }
39
- if (orderByClause.length === 1) {
40
- const direction = orderByClause[0].direction;
41
- return direction === `desc` ? descComparator(a, b) : ascComparator(a, b);
42
35
  }
43
- return ascComparator(a, b);
44
- };
36
+ return arrayA.length - arrayB.length;
37
+ }
38
+ if (orderByClause.length === 1) {
39
+ const clause = orderByClause[0];
40
+ const compareFn = makeComparator(clause.compareOptions);
41
+ return compareFn(a, b);
42
+ }
43
+ return defaultComparator(a, b);
45
44
  };
46
- const comparator = makeComparator();
47
45
  return pipeline.pipe(
48
46
  orderByWithFractionalIndex(valueExtractor, {
49
47
  limit,
@@ -1 +1 @@
1
- {"version":3,"file":"order-by.js","sources":["../../../../src/query/compiler/order-by.ts"],"sourcesContent":["import { orderByWithFractionalIndex } from \"@tanstack/db-ivm\"\nimport { ascComparator, descComparator } from \"../../utils/comparison.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport type { OrderByClause } from \"../ir.js\"\nimport type { NamespacedAndKeyedStream, NamespacedRow } from \"../../types.js\"\nimport type { IStreamBuilder, KeyValue } from \"@tanstack/db-ivm\"\n\n/**\n * Processes the ORDER BY clause\n * Works with the new structure that has both namespaced row data and __select_results\n * Always uses fractional indexing and adds the index as __ordering_index to the result\n */\nexport function processOrderBy(\n pipeline: NamespacedAndKeyedStream,\n orderByClause: Array<OrderByClause>,\n limit?: number,\n offset?: number\n): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>> {\n // Pre-compile all order by expressions\n const compiledOrderBy = orderByClause.map((clause) => ({\n compiledExpression: compileExpression(clause.expression),\n direction: clause.direction,\n }))\n\n // Create a value extractor function for the orderBy operator\n const valueExtractor = (row: NamespacedRow & { __select_results?: any }) => {\n // For ORDER BY expressions, we need to provide access to both:\n // 1. The original namespaced row data (for direct table column references)\n // 2. The __select_results (for SELECT alias references)\n\n // Create a merged context for expression evaluation\n const orderByContext = { ...row }\n\n // If there are select results, merge them at the top level for alias access\n if (row.__select_results) {\n // Add select results as top-level properties for alias access\n Object.assign(orderByContext, row.__select_results)\n }\n\n if (orderByClause.length > 1) {\n // For multiple orderBy columns, create a composite key\n return compiledOrderBy.map((compiled) =>\n compiled.compiledExpression(orderByContext)\n )\n } else if (orderByClause.length === 1) {\n // For a single orderBy column, use the value directly\n const compiled = compiledOrderBy[0]!\n return compiled.compiledExpression(orderByContext)\n }\n\n // Default case - no ordering\n return null\n }\n\n // Create a multi-property comparator that respects the order and direction of each property\n const makeComparator = () => {\n return (a: unknown, b: unknown) => {\n // If we're comparing arrays (multiple properties), compare each property in order\n if (orderByClause.length > 1) {\n const arrayA = a as Array<unknown>\n const arrayB = b as Array<unknown>\n for (let i = 0; i < orderByClause.length; i++) {\n const direction = orderByClause[i]!.direction\n const compareFn =\n direction === `desc` ? descComparator : ascComparator\n const result = compareFn(arrayA[i], arrayB[i])\n if (result !== 0) {\n return result\n }\n }\n return arrayA.length - arrayB.length\n }\n\n // Single property comparison\n if (orderByClause.length === 1) {\n const direction = orderByClause[0]!.direction\n return direction === `desc` ? descComparator(a, b) : ascComparator(a, b)\n }\n\n return ascComparator(a, b)\n }\n }\n\n const comparator = makeComparator()\n\n // Use fractional indexing and return the tuple [value, index]\n return pipeline.pipe(\n orderByWithFractionalIndex(valueExtractor, {\n limit,\n offset,\n comparator,\n })\n // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format\n )\n}\n"],"names":[],"mappings":";;;AAYO,SAAS,eACd,UACA,eACA,OACA,QAC4D;AAE5D,QAAM,kBAAkB,cAAc,IAAI,CAAC,YAAY;AAAA,IACrD,oBAAoB,kBAAkB,OAAO,UAAU;AAAA,IACvD,WAAW,OAAO;AAAA,EAAA,EAClB;AAGF,QAAM,iBAAiB,CAAC,QAAoD;AAM1E,UAAM,iBAAiB,EAAE,GAAG,IAAA;AAG5B,QAAI,IAAI,kBAAkB;AAExB,aAAO,OAAO,gBAAgB,IAAI,gBAAgB;AAAA,IACpD;AAEA,QAAI,cAAc,SAAS,GAAG;AAE5B,aAAO,gBAAgB;AAAA,QAAI,CAAC,aAC1B,SAAS,mBAAmB,cAAc;AAAA,MAAA;AAAA,IAE9C,WAAW,cAAc,WAAW,GAAG;AAErC,YAAM,WAAW,gBAAgB,CAAC;AAClC,aAAO,SAAS,mBAAmB,cAAc;AAAA,IACnD;AAGA,WAAO;AAAA,EACT;AAGA,QAAM,iBAAiB,MAAM;AAC3B,WAAO,CAAC,GAAY,MAAe;AAEjC,UAAI,cAAc,SAAS,GAAG;AAC5B,cAAM,SAAS;AACf,cAAM,SAAS;AACf,iBAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AAC7C,gBAAM,YAAY,cAAc,CAAC,EAAG;AACpC,gBAAM,YACJ,cAAc,SAAS,iBAAiB;AAC1C,gBAAM,SAAS,UAAU,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;AAC7C,cAAI,WAAW,GAAG;AAChB,mBAAO;AAAA,UACT;AAAA,QACF;AACA,eAAO,OAAO,SAAS,OAAO;AAAA,MAChC;AAGA,UAAI,cAAc,WAAW,GAAG;AAC9B,cAAM,YAAY,cAAc,CAAC,EAAG;AACpC,eAAO,cAAc,SAAS,eAAe,GAAG,CAAC,IAAI,cAAc,GAAG,CAAC;AAAA,MACzE;AAEA,aAAO,cAAc,GAAG,CAAC;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,aAAa,eAAA;AAGnB,SAAO,SAAS;AAAA,IACd,2BAA2B,gBAAgB;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAAA;AAAA,EAAA;AAGL;"}
1
+ {"version":3,"file":"order-by.js","sources":["../../../../src/query/compiler/order-by.ts"],"sourcesContent":["import { orderByWithFractionalIndex } from \"@tanstack/db-ivm\"\nimport { defaultComparator, makeComparator } from \"../../utils/comparison.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport type { OrderByClause } from \"../ir.js\"\nimport type { NamespacedAndKeyedStream, NamespacedRow } from \"../../types.js\"\nimport type { IStreamBuilder, KeyValue } from \"@tanstack/db-ivm\"\n\n/**\n * Processes the ORDER BY clause\n * Works with the new structure that has both namespaced row data and __select_results\n * Always uses fractional indexing and adds the index as __ordering_index to the result\n */\nexport function processOrderBy(\n pipeline: NamespacedAndKeyedStream,\n orderByClause: Array<OrderByClause>,\n limit?: number,\n offset?: number\n): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>> {\n // Pre-compile all order by expressions\n const compiledOrderBy = orderByClause.map((clause) => ({\n compiledExpression: compileExpression(clause.expression),\n compareOptions: clause.compareOptions,\n }))\n\n // Create a value extractor function for the orderBy operator\n const valueExtractor = (row: NamespacedRow & { __select_results?: any }) => {\n // For ORDER BY expressions, we need to provide access to both:\n // 1. The original namespaced row data (for direct table column references)\n // 2. The __select_results (for SELECT alias references)\n\n // Create a merged context for expression evaluation\n const orderByContext = { ...row }\n\n // If there are select results, merge them at the top level for alias access\n if (row.__select_results) {\n // Add select results as top-level properties for alias access\n Object.assign(orderByContext, row.__select_results)\n }\n\n if (orderByClause.length > 1) {\n // For multiple orderBy columns, create a composite key\n return compiledOrderBy.map((compiled) =>\n compiled.compiledExpression(orderByContext)\n )\n } else if (orderByClause.length === 1) {\n // For a single orderBy column, use the value directly\n const compiled = compiledOrderBy[0]!\n return compiled.compiledExpression(orderByContext)\n }\n\n // Default case - no ordering\n return null\n }\n\n // Create a multi-property comparator that respects the order and direction of each property\n const comparator = (a: unknown, b: unknown) => {\n // If we're comparing arrays (multiple properties), compare each property in order\n if (orderByClause.length > 1) {\n const arrayA = a as Array<unknown>\n const arrayB = b as Array<unknown>\n for (let i = 0; i < orderByClause.length; i++) {\n const clause = orderByClause[i]!\n const compareFn = makeComparator(clause.compareOptions)\n const result = compareFn(arrayA[i], arrayB[i])\n if (result !== 0) {\n return result\n }\n }\n return arrayA.length - arrayB.length\n }\n\n // Single property comparison\n if (orderByClause.length === 1) {\n const clause = orderByClause[0]!\n const compareFn = makeComparator(clause.compareOptions)\n return compareFn(a, b)\n }\n\n return defaultComparator(a, b)\n }\n\n // Use fractional indexing and return the tuple [value, index]\n return pipeline.pipe(\n orderByWithFractionalIndex(valueExtractor, {\n limit,\n offset,\n comparator,\n })\n // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format\n )\n}\n"],"names":[],"mappings":";;;AAYO,SAAS,eACd,UACA,eACA,OACA,QAC4D;AAE5D,QAAM,kBAAkB,cAAc,IAAI,CAAC,YAAY;AAAA,IACrD,oBAAoB,kBAAkB,OAAO,UAAU;AAAA,IACvD,gBAAgB,OAAO;AAAA,EAAA,EACvB;AAGF,QAAM,iBAAiB,CAAC,QAAoD;AAM1E,UAAM,iBAAiB,EAAE,GAAG,IAAA;AAG5B,QAAI,IAAI,kBAAkB;AAExB,aAAO,OAAO,gBAAgB,IAAI,gBAAgB;AAAA,IACpD;AAEA,QAAI,cAAc,SAAS,GAAG;AAE5B,aAAO,gBAAgB;AAAA,QAAI,CAAC,aAC1B,SAAS,mBAAmB,cAAc;AAAA,MAAA;AAAA,IAE9C,WAAW,cAAc,WAAW,GAAG;AAErC,YAAM,WAAW,gBAAgB,CAAC;AAClC,aAAO,SAAS,mBAAmB,cAAc;AAAA,IACnD;AAGA,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,CAAC,GAAY,MAAe;AAE7C,QAAI,cAAc,SAAS,GAAG;AAC5B,YAAM,SAAS;AACf,YAAM,SAAS;AACf,eAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;AAC7C,cAAM,SAAS,cAAc,CAAC;AAC9B,cAAM,YAAY,eAAe,OAAO,cAAc;AACtD,cAAM,SAAS,UAAU,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;AAC7C,YAAI,WAAW,GAAG;AAChB,iBAAO;AAAA,QACT;AAAA,MACF;AACA,aAAO,OAAO,SAAS,OAAO;AAAA,IAChC;AAGA,QAAI,cAAc,WAAW,GAAG;AAC9B,YAAM,SAAS,cAAc,CAAC;AAC9B,YAAM,YAAY,eAAe,OAAO,cAAc;AACtD,aAAO,UAAU,GAAG,CAAC;AAAA,IACvB;AAEA,WAAO,kBAAkB,GAAG,CAAC;AAAA,EAC/B;AAGA,SAAO,SAAS;AAAA,IACd,2BAA2B,gBAAgB;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAAA;AAAA,EAAA;AAGL;"}
@@ -1,3 +1,4 @@
1
+ import { CompareOptions } from './builder/types.js';
1
2
  import { CollectionImpl } from '../collection.js';
2
3
  import { NamespacedRow } from '../types.js';
3
4
  export interface QueryIR {
@@ -32,7 +33,7 @@ export type Having = Where;
32
33
  export type OrderBy = Array<OrderByClause>;
33
34
  export type OrderByClause = {
34
35
  expression: BasicExpression;
35
- direction: OrderByDirection;
36
+ compareOptions: CompareOptions;
36
37
  };
37
38
  export type OrderByDirection = `asc` | `desc`;
38
39
  export type Limit = number;
@@ -1 +1 @@
1
- {"version":3,"file":"ir.js","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CollectionImpl } from \"../collection\"\nimport type { NamespacedRow } from \"../types\"\n\nexport interface QueryIR {\n from: From\n select?: Select\n join?: Join\n where?: Array<Where>\n groupBy?: GroupBy\n having?: Array<Having>\n orderBy?: OrderBy\n limit?: Limit\n offset?: Offset\n distinct?: true\n\n // Functional variants\n fnSelect?: (row: NamespacedRow) => any\n fnWhere?: Array<(row: NamespacedRow) => any>\n fnHaving?: Array<(row: NamespacedRow) => any>\n}\n\nexport type From = CollectionRef | QueryRef\n\nexport type Select = {\n [alias: string]: BasicExpression | Aggregate\n}\n\nexport type Join = Array<JoinClause>\n\nexport interface JoinClause {\n from: CollectionRef | QueryRef\n type: `left` | `right` | `inner` | `outer` | `full` | `cross`\n left: BasicExpression\n right: BasicExpression\n}\n\nexport type Where = BasicExpression<boolean>\n\nexport type GroupBy = Array<BasicExpression>\n\nexport type Having = Where\n\nexport type OrderBy = Array<OrderByClause>\n\nexport type OrderByClause = {\n expression: BasicExpression\n direction: OrderByDirection\n}\n\nexport type OrderByDirection = `asc` | `desc`\n\nexport type Limit = number\n\nexport type Offset = number\n\n/* Expressions */\n\nabstract class BaseExpression<T = any> {\n public abstract type: string\n /** @internal - Type brand for TypeScript inference */\n declare readonly __returnType: T\n}\n\nexport class CollectionRef extends BaseExpression {\n public type = `collectionRef` as const\n constructor(\n public collection: CollectionImpl,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class QueryRef extends BaseExpression {\n public type = `queryRef` as const\n constructor(\n public query: QueryIR,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class PropRef<T = any> extends BaseExpression<T> {\n public type = `ref` as const\n constructor(\n public path: Array<string> // path to the property in the collection, with the alias as the first element\n ) {\n super()\n }\n}\n\nexport class Value<T = any> extends BaseExpression<T> {\n public type = `val` as const\n constructor(\n public value: T // any js value\n ) {\n super()\n }\n}\n\nexport class Func<T = any> extends BaseExpression<T> {\n public type = `func` as const\n constructor(\n public name: string, // such as eq, gt, lt, upper, lower, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n\n// This is the basic expression type that is used in the majority of expression\n// builder callbacks (select, where, groupBy, having, orderBy, etc.)\n// it doesn't include aggregate functions as those are only used in the select clause\nexport type BasicExpression<T = any> = PropRef<T> | Value<T> | Func<T>\n\nexport class Aggregate<T = any> extends BaseExpression<T> {\n public type = `agg` as const\n constructor(\n public name: string, // such as count, avg, sum, min, max, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n"],"names":[],"mappings":"AA6DA,MAAe,eAAwB;AAIvC;AAEO,MAAM,sBAAsB,eAAe;AAAA,EAEhD,YACS,YACA,OACP;AACA,UAAA;AAHO,SAAA,aAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,iBAAiB,eAAe;AAAA,EAE3C,YACS,OACA,OACP;AACA,UAAA;AAHO,SAAA,QAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,gBAAyB,eAAkB;AAAA,EAEtD,YACS,MACP;AACA,UAAA;AAFO,SAAA,OAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,cAAuB,eAAkB;AAAA,EAEpD,YACS,OACP;AACA,UAAA;AAFO,SAAA,QAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,aAAsB,eAAkB;AAAA,EAEnD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAOO,MAAM,kBAA2B,eAAkB;AAAA,EAExD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;"}
1
+ {"version":3,"file":"ir.js","sources":["../../../src/query/ir.ts"],"sourcesContent":["/*\nThis is the intermediate representation of the query.\n*/\n\nimport type { CompareOptions } from \"./builder/types\"\nimport type { CollectionImpl } from \"../collection\"\nimport type { NamespacedRow } from \"../types\"\n\nexport interface QueryIR {\n from: From\n select?: Select\n join?: Join\n where?: Array<Where>\n groupBy?: GroupBy\n having?: Array<Having>\n orderBy?: OrderBy\n limit?: Limit\n offset?: Offset\n distinct?: true\n\n // Functional variants\n fnSelect?: (row: NamespacedRow) => any\n fnWhere?: Array<(row: NamespacedRow) => any>\n fnHaving?: Array<(row: NamespacedRow) => any>\n}\n\nexport type From = CollectionRef | QueryRef\n\nexport type Select = {\n [alias: string]: BasicExpression | Aggregate\n}\n\nexport type Join = Array<JoinClause>\n\nexport interface JoinClause {\n from: CollectionRef | QueryRef\n type: `left` | `right` | `inner` | `outer` | `full` | `cross`\n left: BasicExpression\n right: BasicExpression\n}\n\nexport type Where = BasicExpression<boolean>\n\nexport type GroupBy = Array<BasicExpression>\n\nexport type Having = Where\n\nexport type OrderBy = Array<OrderByClause>\n\nexport type OrderByClause = {\n expression: BasicExpression\n compareOptions: CompareOptions\n}\n\nexport type OrderByDirection = `asc` | `desc`\n\nexport type Limit = number\n\nexport type Offset = number\n\n/* Expressions */\n\nabstract class BaseExpression<T = any> {\n public abstract type: string\n /** @internal - Type brand for TypeScript inference */\n declare readonly __returnType: T\n}\n\nexport class CollectionRef extends BaseExpression {\n public type = `collectionRef` as const\n constructor(\n public collection: CollectionImpl,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class QueryRef extends BaseExpression {\n public type = `queryRef` as const\n constructor(\n public query: QueryIR,\n public alias: string\n ) {\n super()\n }\n}\n\nexport class PropRef<T = any> extends BaseExpression<T> {\n public type = `ref` as const\n constructor(\n public path: Array<string> // path to the property in the collection, with the alias as the first element\n ) {\n super()\n }\n}\n\nexport class Value<T = any> extends BaseExpression<T> {\n public type = `val` as const\n constructor(\n public value: T // any js value\n ) {\n super()\n }\n}\n\nexport class Func<T = any> extends BaseExpression<T> {\n public type = `func` as const\n constructor(\n public name: string, // such as eq, gt, lt, upper, lower, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n\n// This is the basic expression type that is used in the majority of expression\n// builder callbacks (select, where, groupBy, having, orderBy, etc.)\n// it doesn't include aggregate functions as those are only used in the select clause\nexport type BasicExpression<T = any> = PropRef<T> | Value<T> | Func<T>\n\nexport class Aggregate<T = any> extends BaseExpression<T> {\n public type = `agg` as const\n constructor(\n public name: string, // such as count, avg, sum, min, max, etc.\n public args: Array<BasicExpression>\n ) {\n super()\n }\n}\n"],"names":[],"mappings":"AA8DA,MAAe,eAAwB;AAIvC;AAEO,MAAM,sBAAsB,eAAe;AAAA,EAEhD,YACS,YACA,OACP;AACA,UAAA;AAHO,SAAA,aAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,iBAAiB,eAAe;AAAA,EAE3C,YACS,OACA,OACP;AACA,UAAA;AAHO,SAAA,QAAA;AACA,SAAA,QAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAEO,MAAM,gBAAyB,eAAkB;AAAA,EAEtD,YACS,MACP;AACA,UAAA;AAFO,SAAA,OAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,cAAuB,eAAkB;AAAA,EAEpD,YACS,OACP;AACA,UAAA;AAFO,SAAA,QAAA;AAFT,SAAO,OAAO;AAAA,EAKd;AACF;AAEO,MAAM,aAAsB,eAAkB;AAAA,EAEnD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;AAOO,MAAM,kBAA2B,eAAkB;AAAA,EAExD,YACS,MACA,MACP;AACA,UAAA;AAHO,SAAA,OAAA;AACA,SAAA,OAAA;AAHT,SAAO,OAAO;AAAA,EAMd;AACF;"}
@@ -1,11 +1,15 @@
1
+ import { CompareOptions } from '../query/builder/types.js';
1
2
  /**
2
3
  * Universal comparison function for all data types
3
4
  * Handles null/undefined, strings, arrays, dates, objects, and primitives
4
5
  * Always sorts null/undefined values first
5
6
  */
6
- export declare const ascComparator: (a: any, b: any) => number;
7
+ export declare const ascComparator: (a: any, b: any, opts: CompareOptions) => number;
7
8
  /**
8
9
  * Descending comparator function for ordering values
9
10
  * Handles null/undefined as largest values (opposite of ascending)
10
11
  */
11
- export declare const descComparator: (a: unknown, b: unknown) => number;
12
+ export declare const descComparator: (a: unknown, b: unknown, opts: CompareOptions) => number;
13
+ export declare function makeComparator(opts: CompareOptions): (a: any, b: any) => number;
14
+ /** Default comparator orders values in ascending order with nulls first and locale string comparison. */
15
+ export declare const defaultComparator: (a: any, b: any) => number;
@@ -8,16 +8,19 @@ function getObjectId(obj) {
8
8
  objectIds.set(obj, id);
9
9
  return id;
10
10
  }
11
- const ascComparator = (a, b) => {
11
+ const ascComparator = (a, b, opts) => {
12
+ const { nulls } = opts;
12
13
  if (a == null && b == null) return 0;
13
- if (a == null) return -1;
14
- if (b == null) return 1;
14
+ if (a == null) return nulls === `first` ? -1 : 1;
15
+ if (b == null) return nulls === `first` ? 1 : -1;
15
16
  if (typeof a === `string` && typeof b === `string`) {
16
- return a.localeCompare(b);
17
+ if (opts.stringSort === `locale`) {
18
+ return a.localeCompare(b, opts.locale, opts.localeOptions);
19
+ }
17
20
  }
18
21
  if (Array.isArray(a) && Array.isArray(b)) {
19
22
  for (let i = 0; i < Math.min(a.length, b.length); i++) {
20
- const result = ascComparator(a[i], b[i]);
23
+ const result = ascComparator(a[i], b[i], opts);
21
24
  if (result !== 0) {
22
25
  return result;
23
26
  }
@@ -42,11 +45,30 @@ const ascComparator = (a, b) => {
42
45
  if (a > b) return 1;
43
46
  return 0;
44
47
  };
45
- const descComparator = (a, b) => {
46
- return ascComparator(b, a);
48
+ const descComparator = (a, b, opts) => {
49
+ return ascComparator(b, a, {
50
+ ...opts,
51
+ nulls: opts.nulls === `first` ? `last` : `first`
52
+ });
47
53
  };
54
+ function makeComparator(opts) {
55
+ return (a, b) => {
56
+ if (opts.direction === `asc`) {
57
+ return ascComparator(a, b, opts);
58
+ } else {
59
+ return descComparator(a, b, opts);
60
+ }
61
+ };
62
+ }
63
+ const defaultComparator = makeComparator({
64
+ direction: `asc`,
65
+ nulls: `first`,
66
+ stringSort: `locale`
67
+ });
48
68
  export {
49
69
  ascComparator,
50
- descComparator
70
+ defaultComparator,
71
+ descComparator,
72
+ makeComparator
51
73
  };
52
74
  //# sourceMappingURL=comparison.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"comparison.js","sources":["../../../src/utils/comparison.ts"],"sourcesContent":["// 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): number => {\n // Handle null/undefined\n if (a == null && b == null) return 0\n if (a == null) return -1\n if (b == null) return 1\n\n // if a and b are both strings, compare them based on locale\n if (typeof a === `string` && typeof b === `string`) {\n return a.localeCompare(b)\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])\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 = (a: unknown, b: unknown): number => {\n return ascComparator(b, a)\n}\n"],"names":[],"mappings":"AACA,MAAM,gCAAgB,QAAA;AACtB,IAAI,eAAe;AAKnB,SAAS,YAAY,KAAqB;AACxC,MAAI,UAAU,IAAI,GAAG,GAAG;AACtB,WAAO,UAAU,IAAI,GAAG;AAAA,EAC1B;AACA,QAAM,KAAK;AACX,YAAU,IAAI,KAAK,EAAE;AACrB,SAAO;AACT;AAOO,MAAM,gBAAgB,CAAC,GAAQ,MAAmB;AAEvD,MAAI,KAAK,QAAQ,KAAK,KAAM,QAAO;AACnC,MAAI,KAAK,KAAM,QAAO;AACtB,MAAI,KAAK,KAAM,QAAO;AAGtB,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;AAClD,WAAO,EAAE,cAAc,CAAC;AAAA,EAC1B;AAGA,MAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;AACxC,aAAS,IAAI,GAAG,IAAI,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK;AACrD,YAAM,SAAS,cAAc,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AACvC,UAAI,WAAW,GAAG;AAChB,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO,EAAE,SAAS,EAAE;AAAA,EACtB;AAGA,MAAI,aAAa,QAAQ,aAAa,MAAM;AAC1C,WAAO,EAAE,YAAY,EAAE,QAAA;AAAA,EACzB;AAGA,QAAM,YAAY,OAAO,MAAM;AAC/B,QAAM,YAAY,OAAO,MAAM;AAE/B,MAAI,aAAa,WAAW;AAE1B,QAAI,aAAa,WAAW;AAC1B,YAAM,MAAM,YAAY,CAAC;AACzB,YAAM,MAAM,YAAY,CAAC;AACzB,aAAO,MAAM;AAAA,IACf;AAGA,QAAI,UAAW,QAAO;AACtB,QAAI,UAAW,QAAO;AAAA,EACxB;AAGA,MAAI,IAAI,EAAG,QAAO;AAClB,MAAI,IAAI,EAAG,QAAO;AAClB,SAAO;AACT;AAMO,MAAM,iBAAiB,CAAC,GAAY,MAAuB;AAChE,SAAO,cAAc,GAAG,CAAC;AAC3B;"}
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"],"names":[],"mappings":"AAGA,MAAM,gCAAgB,QAAA;AACtB,IAAI,eAAe;AAKnB,SAAS,YAAY,KAAqB;AACxC,MAAI,UAAU,IAAI,GAAG,GAAG;AACtB,WAAO,UAAU,IAAI,GAAG;AAAA,EAC1B;AACA,QAAM,KAAK;AACX,YAAU,IAAI,KAAK,EAAE;AACrB,SAAO;AACT;AAOO,MAAM,gBAAgB,CAAC,GAAQ,GAAQ,SAAiC;AAC7E,QAAM,EAAE,UAAU;AAGlB,MAAI,KAAK,QAAQ,KAAK,KAAM,QAAO;AACnC,MAAI,KAAK,KAAM,QAAO,UAAU,UAAU,KAAK;AAC/C,MAAI,KAAK,KAAM,QAAO,UAAU,UAAU,IAAI;AAG9C,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;AAClD,QAAI,KAAK,eAAe,UAAU;AAChC,aAAO,EAAE,cAAc,GAAG,KAAK,QAAQ,KAAK,aAAa;AAAA,IAC3D;AAAA,EAEF;AAGA,MAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;AACxC,aAAS,IAAI,GAAG,IAAI,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK;AACrD,YAAM,SAAS,cAAc,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI;AAC7C,UAAI,WAAW,GAAG;AAChB,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO,EAAE,SAAS,EAAE;AAAA,EACtB;AAGA,MAAI,aAAa,QAAQ,aAAa,MAAM;AAC1C,WAAO,EAAE,YAAY,EAAE,QAAA;AAAA,EACzB;AAGA,QAAM,YAAY,OAAO,MAAM;AAC/B,QAAM,YAAY,OAAO,MAAM;AAE/B,MAAI,aAAa,WAAW;AAE1B,QAAI,aAAa,WAAW;AAC1B,YAAM,MAAM,YAAY,CAAC;AACzB,YAAM,MAAM,YAAY,CAAC;AACzB,aAAO,MAAM;AAAA,IACf;AAGA,QAAI,UAAW,QAAO;AACtB,QAAI,UAAW,QAAO;AAAA,EACxB;AAGA,MAAI,IAAI,EAAG,QAAO;AAClB,MAAI,IAAI,EAAG,QAAO;AAClB,SAAO;AACT;AAMO,MAAM,iBAAiB,CAC5B,GACA,GACA,SACW;AACX,SAAO,cAAc,GAAG,GAAG;AAAA,IACzB,GAAG;AAAA,IACH,OAAO,KAAK,UAAU,UAAU,SAAS;AAAA,EAAA,CAC1C;AACH;AAEO,SAAS,eACd,MAC4B;AAC5B,SAAO,CAAC,GAAG,MAAM;AACf,QAAI,KAAK,cAAc,OAAO;AAC5B,aAAO,cAAc,GAAG,GAAG,IAAI;AAAA,IACjC,OAAO;AACL,aAAO,eAAe,GAAG,GAAG,IAAI;AAAA,IAClC;AAAA,EACF;AACF;AAGO,MAAM,oBAAoB,eAAe;AAAA,EAC9C,WAAW;AAAA,EACX,OAAO;AAAA,EACP,YAAY;AACd,CAAC;"}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@tanstack/db",
3
3
  "description": "A reactive client store for building super fast apps on sync",
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "dependencies": {
6
6
  "@standard-schema/spec": "^1.0.0",
7
- "@tanstack/db-ivm": "0.1.0"
7
+ "@tanstack/db-ivm": "0.1.1"
8
8
  },
9
9
  "devDependencies": {
10
10
  "@vitest/coverage-istanbul": "^3.0.9",
package/src/collection.ts CHANGED
@@ -155,8 +155,81 @@ export interface Collection<
155
155
  * sync: { sync: () => {} }
156
156
  * })
157
157
  *
158
- * // Note: You must provide either an explicit type or a schema, but not both.
158
+ * // Note: You can provide an explicit type, a schema, or both. When both are provided, the explicit type takes precedence.
159
159
  */
160
+
161
+ // Overload for when schema is provided - infers schema type
162
+ export function createCollection<
163
+ TSchema extends StandardSchemaV1,
164
+ TKey extends string | number = string | number,
165
+ TUtils extends UtilsRecord = {},
166
+ TFallback extends object = Record<string, unknown>,
167
+ >(
168
+ options: CollectionConfig<
169
+ ResolveType<unknown, TSchema, TFallback>,
170
+ TKey,
171
+ TSchema,
172
+ ResolveInsertInput<unknown, TSchema, TFallback>
173
+ > & {
174
+ schema: TSchema
175
+ utils?: TUtils
176
+ }
177
+ ): Collection<
178
+ ResolveType<unknown, TSchema, TFallback>,
179
+ TKey,
180
+ TUtils,
181
+ TSchema,
182
+ ResolveInsertInput<unknown, TSchema, TFallback>
183
+ >
184
+
185
+ // Overload for when explicit type is provided with schema - explicit type takes precedence
186
+ export function createCollection<
187
+ TExplicit extends object,
188
+ TKey extends string | number = string | number,
189
+ TUtils extends UtilsRecord = {},
190
+ TSchema extends StandardSchemaV1 = StandardSchemaV1,
191
+ TFallback extends object = Record<string, unknown>,
192
+ >(
193
+ options: CollectionConfig<
194
+ ResolveType<TExplicit, TSchema, TFallback>,
195
+ TKey,
196
+ TSchema,
197
+ ResolveInsertInput<TExplicit, TSchema, TFallback>
198
+ > & {
199
+ schema: TSchema
200
+ utils?: TUtils
201
+ }
202
+ ): Collection<
203
+ ResolveType<TExplicit, TSchema, TFallback>,
204
+ TKey,
205
+ TUtils,
206
+ TSchema,
207
+ ResolveInsertInput<TExplicit, TSchema, TFallback>
208
+ >
209
+
210
+ // Overload for when explicit type is provided or no schema
211
+ export function createCollection<
212
+ TExplicit = unknown,
213
+ TKey extends string | number = string | number,
214
+ TUtils extends UtilsRecord = {},
215
+ TSchema extends StandardSchemaV1 = StandardSchemaV1,
216
+ TFallback extends object = Record<string, unknown>,
217
+ >(
218
+ options: CollectionConfig<
219
+ ResolveType<TExplicit, TSchema, TFallback>,
220
+ TKey,
221
+ TSchema,
222
+ ResolveInsertInput<TExplicit, TSchema, TFallback>
223
+ > & { utils?: TUtils }
224
+ ): Collection<
225
+ ResolveType<TExplicit, TSchema, TFallback>,
226
+ TKey,
227
+ TUtils,
228
+ TSchema,
229
+ ResolveInsertInput<TExplicit, TSchema, TFallback>
230
+ >
231
+
232
+ // Implementation
160
233
  export function createCollection<
161
234
  TExplicit = unknown,
162
235
  TKey extends string | number = string | number,
@@ -1,5 +1,5 @@
1
- import { ascComparator } from "../utils/comparison.js"
2
1
  import { BTree } from "../utils/btree.js"
2
+ import { defaultComparator } from "../utils/comparison.js"
3
3
  import { BaseIndex } from "./base-index.js"
4
4
  import type { BasicExpression } from "../query/ir.js"
5
5
  import type { IndexOperation } from "./base-index.js"
@@ -43,7 +43,7 @@ export class BTreeIndex<
43
43
  private orderedEntries: BTree<any, undefined> // we don't associate values with the keys of the B+ tree (the keys are indexed values)
44
44
  private valueMap = new Map<any, Set<TKey>>() // instead we store a mapping of indexed values to a set of PKs
45
45
  private indexedKeys = new Set<TKey>()
46
- private compareFn: (a: any, b: any) => number = ascComparator
46
+ private compareFn: (a: any, b: any) => number = defaultComparator
47
47
 
48
48
  constructor(
49
49
  id: number,
@@ -52,7 +52,7 @@ export class BTreeIndex<
52
52
  options?: any
53
53
  ) {
54
54
  super(id, expression, name, options)
55
- this.compareFn = options?.compareFn ?? ascComparator
55
+ this.compareFn = options?.compareFn ?? defaultComparator
56
56
  this.orderedEntries = new BTree(this.compareFn)
57
57
  }
58
58