import { Collection, CollectionImpl } from '../../collection/index.js'; import { SingleResult, StringCollationConfig } from '../../types.js'; import { Aggregate, BasicExpression, Func, OrderByDirection, PropRef, Value } from '../ir.js'; import { InitialQueryBuilder, QueryBuilder } from './index.js'; import { VirtualRowProps, WithVirtualProps } from '../../virtual-props.js'; import { CaseWhenWrapper, ConcatToArrayWrapper, ToArrayWrapper } from './functions.js'; /** * Context - The central state container for query builder operations * * This interface tracks all the information needed to build and type-check queries: * * **Schema Management**: * - `baseSchema`: The original tables/collections from the `from()` clause * - `schema`: Current available tables (expands with joins, contracts with subqueries) * * **Query State**: * - `fromSourceName`: Which table was used in `from()` or the first * `unionAll()` source - needed for optionality logic * - `hasJoins`: Whether any joins have been added (affects result type inference) * - `joinTypes`: Maps table aliases to their join types for optionality calculations * * **Result Tracking**: * - `result`: The final shape after `select()` - undefined until select is called * * The context evolves through the query builder chain: * 1. `from()` sets baseSchema and schema to the same thing * 2. `join()` expands schema and sets hasJoins/joinTypes * 3. `select()` sets result to the projected shape */ export interface Context { baseSchema: ContextSchema; schema: ContextSchema; refsSchema?: ContextSchema; fromSourceName: string; fromSourceNames?: ReadonlyArray; hasUnionFrom?: true; hasJoins?: boolean; joinTypes?: Record; result?: any; hasResult?: true; singleResult?: boolean; } /** * ContextSchema - The shape of available tables/collections in a query context * * This is simply a record mapping table aliases to their TypeScript types. * It evolves as the query progresses: * - Initial: Just the `from()` table * - After joins: Includes all joined tables with proper optionality * - In subqueries: May be a subset of the outer query's schema */ export type ContextSchema = Record; /** * Source - Input definition for query builder `from()` and `unionAll()` clauses * * Maps table aliases to either: * - `CollectionImpl`: A database collection/table * - `QueryBuilder`: A subquery that can be used as a table * * Example: `{ users: usersCollection }` */ export type Source = { [alias: string]: CollectionImpl | QueryBuilder; }; /** * InferCollectionType - Extracts the TypeScript type from a CollectionImpl * * This helper ensures we get the same type that was used when creating the collection itself. * This can be an explicit type passed by the user or the schema output type. */ export type InferCollectionType = T extends CollectionImpl ? WithVirtualProps : never; /** * SchemaFromSource - Converts a Source definition into a ContextSchema * * This transforms the input to `from()` into the schema format used throughout * the query builder. For each alias in the source: * - Collections → their inferred TypeScript type * - Subqueries → their result type (what they would return if executed) * * The `Prettify` wrapper ensures clean type display in IDEs. */ export type SchemaFromSource = Prettify<{ [K in keyof T]: T[K] extends CollectionImpl ? InferCollectionType : T[K] extends QueryBuilder ? GetResult : never; }>; export type UnionRefsSchema = Prettify<{ [K in keyof TSchema]: TSchema[K] | undefined; }>; type IsUnion = T extends unknown ? [U] extends [T] ? false : true : false; export type SingleSource = IsUnion extends true ? never : TSource; export type ContextFromSource = { baseSchema: SchemaFromSource; schema: SchemaFromSource; fromSourceName: keyof TSource & string; hasJoins: false; }; export type ContextFromUnionSource = IsUnion extends true ? { baseSchema: SchemaFromSource; schema: UnionRefsSchema>; fromSourceName: keyof TSource & string; fromSourceNames: ReadonlyArray; hasUnionFrom: true; hasJoins: false; } : ContextFromSource; type ResultFromBranch = TBranch extends QueryBuilder ? GetResult : never; type UnionBranchResult>> = ResultFromBranch; type UnionBranchSchema>> = UnionBranchResult extends infer TResult ? { [K in KeysOfUnion]: ValueOfUnion; } : never; export type ContextFromUnionBranches, ...Array>]> = { baseSchema: UnionBranchSchema & ContextSchema; schema: UnionBranchSchema & ContextSchema; refsSchema: UnionBranchSchema; fromSourceName: keyof UnionBranchSchema & string; hasJoins: false; result: PrettifyIfPlainObject>; hasResult: true; }; /** * GetAliases - Extracts all table aliases available in a query context * * Simple utility type that returns the keys of the schema, representing * all table/source aliases that can be referenced in the current query. */ export type GetAliases = keyof TContext[`schema`]; /** * WhereCallback - Type for where/having clause callback functions * * These callbacks receive a `refs` object containing RefProxy instances for * all available tables. The callback should return a boolean expression * that will be used to filter query results. * * Example: `(refs) => eq(refs.users.age, 25)` */ export type WhereCallback = (refs: RefsForContext) => any; /** * SelectValue - Union of all valid values in a select clause * * This type defines what can be used as values in the object passed to `select()`. * * **Core Expression Types**: * - `BasicExpression`: Function calls like `upper(users.name)` * - `Aggregate`: Aggregations like `count()`, `avg()` * - `RefProxy/Ref`: Direct field references like `users.name` * * **JavaScript Literals** (for constant values in projections): * - `string`: String literals like `'active'`, `'N/A'` * - `number`: Numeric literals like `0`, `42`, `3.14` * - `boolean`: Boolean literals `true`, `false` * - `null`: Explicit null values * * **Advanced Features**: * - `undefined`: Allows optional projection values * - `{ [key: string]: SelectValue }`: Nested object projection * * The clean Ref type ensures no internal properties are visible to users. * * Examples: * ```typescript * select({ * id: users.id, * name: users.name, * status: 'active', // string literal * priority: 1, // number literal * verified: true, // boolean literal * notes: null, // explicit null * profile: { * name: users.name, * email: users.email * } * }) * ``` */ type SelectValue = BasicExpression | Aggregate | Ref | RefLeaf | string | number | boolean | null | undefined | { [key: string]: SelectValue; } | Array> | ToArrayWrapper | ConcatToArrayWrapper | CaseWhenWrapper | QueryBuilder; type SelectShape = { [key: string]: SelectValue | SelectShape; }; export type ScalarSelectValue = BasicExpression | Aggregate | Ref | RefLeaf | string | number | boolean | null | undefined; export type StringifiableScalar = string | number | boolean | null | undefined; /** * SelectObject - Wrapper type for select clause objects * * This ensures that objects passed to `select()` have valid SelectValue types * for all their properties. It's a simple wrapper that provides better error * messages when invalid selections are attempted. */ export type SelectObject = T; type RefBrandKeys = typeof RefBrand | typeof NullableBrand; type HasNamedSelectKeys = Exclude extends never ? false : true; type IsScalarSelectLike = T extends BasicExpression | Aggregate ? true : T extends string | number | boolean | null | undefined ? true : typeof RefBrand extends keyof T ? HasNamedSelectKeys extends true ? false : true : false; export type NonScalarSelectObject = T extends SelectObject ? IsScalarSelectLike extends true ? never : T : never; export type ResultTypeFromSelectValue = IsAny extends true ? any : WithoutRefBrand extends true ? ExtractExpressionType : TSelectValue extends ToArrayWrapper ? Array : TSelectValue extends ConcatToArrayWrapper ? string : TSelectValue extends { readonly __brand: `CaseWhenWrapper`; readonly _result?: infer T; } ? ResultTypeFromCaseWhen : TSelectValue extends QueryBuilder ? Collection> : TSelectValue extends Ref ? ExtractRef : TSelectValue extends RefLeaf ? IsNullableRef extends true ? T | undefined : T : TSelectValue extends RefLeaf | undefined ? T | undefined : TSelectValue extends RefLeaf | null ? IsNullableRef> extends true ? T | null | undefined : T | null : TSelectValue extends Ref | undefined ? ExtractRef> | undefined : TSelectValue extends Ref | null ? ExtractRef> | null : TSelectValue extends Aggregate ? T : TSelectValue extends string | number | boolean | null | undefined ? TSelectValue : TSelectValue extends Record ? ResultTypeFromSelect : never>; /** * ResultTypeFromSelect - Infers the result type from a select object * * This complex type transforms the input to `select()` into the actual TypeScript * type that the query will return. It handles all the different kinds of values * that can appear in a select clause: * * **Ref/RefProxy Extraction**: * - `RefProxy` → `T`: Extracts the underlying type * - `Ref | undefined` → `T | undefined`: Preserves optionality * - `Ref | null` → `T | null`: Preserves nullability * * **Expression Types**: * - `BasicExpression` → `T`: Function results like `upper()` → `string` * - `Aggregate` → `T`: Aggregation results like `count()` → `number` * * **JavaScript Literals** (pass through as-is): * - `string` → `string`: String literals remain strings * - `number` → `number`: Numeric literals remain numbers * - `boolean` → `boolean`: Boolean literals remain booleans * - `null` → `null`: Explicit null remains null * - `undefined` → `undefined`: Direct undefined values * * **Nested Objects** (recursive): * - Plain objects are recursively processed to handle nested projections * - RefProxy objects are detected and their types extracted * * Example transformation: * ```typescript * // Input: * { id: Ref, name: Ref, status: 'active', count: 42, profile: { bio: Ref } } * * // Output: * { id: number, name: string, status: 'active', count: 42, profile: { bio: string } } * ``` */ export type ResultTypeFromSelect = IsAny extends true ? any : WithoutRefBrand extends true ? ExtractExpressionType : TSelectObject[K] extends ToArrayWrapper ? Array : TSelectObject[K] extends ConcatToArrayWrapper ? string : TSelectObject[K] extends { readonly __brand: `CaseWhenWrapper`; readonly _result?: infer T; } ? ResultTypeFromCaseWhen : TSelectObject[K] extends QueryBuilder ? Collection> : TSelectObject[K] extends Ref ? ExtractRef : TSelectObject[K] extends RefLeaf ? IsNullableRef extends true ? T | undefined : T : TSelectObject[K] extends RefLeaf | undefined ? T | undefined : TSelectObject[K] extends RefLeaf | null ? IsNullableRef> extends true ? T | null | undefined : T | null : TSelectObject[K] extends Ref | undefined ? ExtractRef> | undefined : TSelectObject[K] extends Ref | null ? ExtractRef> | null : TSelectObject[K] extends Aggregate ? T : TSelectObject[K] extends string | number | boolean | null | undefined ? TSelectObject[K] : TSelectObject[K] extends Record ? ResultTypeFromSelect : never; }>>; export type SelectResult = IsPlainObject extends true ? ResultTypeFromSelect : ResultTypeFromSelectValue; type ResultTypeFromCaseWhen = T extends unknown ? ResultTypeFromSelectValue : never; type ExtractRef = Prettify>>; type ExtractExpressionType = T extends PropRef ? U : T extends Value ? U : T extends Func ? U : T extends Aggregate ? U : T extends BasicExpression ? U : T; type NeedsExtraction = T extends PropRef | Value | Func | Aggregate | BasicExpression ? true : false; /** * OrderByCallback - Type for orderBy clause callback functions * * Similar to WhereCallback, these receive refs for all available tables * and should return expressions that will be used for sorting. * * Example: `(refs) => refs.users.createdAt` */ export type OrderByCallback = (refs: RefsForContext) => any; /** * OrderByOptions - Configuration for orderBy operations * * Combines direction and null handling with string-specific sorting options. * The intersection with StringSortOpts allows for either simple lexical sorting * or locale-aware sorting with customizable options. */ export type OrderByOptions = { direction?: OrderByDirection; nulls?: `first` | `last`; } & StringCollationConfig; /** * CompareOptions - Final resolved options for comparison operations * * This is the internal type used after all orderBy options have been resolved * to their concrete values. Unlike OrderByOptions, all fields are required * since defaults have been applied. */ export type CompareOptions = StringCollationConfig & { direction: OrderByDirection; nulls: `first` | `last`; }; /** * GroupByCallback - Type for groupBy clause callback functions * * These callbacks receive refs for all available tables and should return * expressions that will be used for grouping query results. * * Example: `(refs) => refs.orders.status` */ export type GroupByCallback = (refs: RefsForContext) => any; /** * JoinOnCallback - Type for join condition callback functions * * These callbacks receive refs for all available tables (including the newly * joined table) and should return a boolean expression defining the join condition. * * Important: The newly joined table is NOT marked as optional in this callback, * even for left/right/full joins, because optionality is applied AFTER the join * condition is evaluated. * * Example: `(refs) => eq(refs.users.id, refs.orders.userId)` */ export type JoinOnCallback = (refs: RefsForContext) => any; /** * FunctionalHavingRow - Type for the row parameter in functional having callbacks * * Functional having callbacks receive a namespaced row that includes: * - Table data from the schema (when available) * - $selected: The SELECT result fields (when select() has been called) * * After `select()` is called, this type includes `$selected` which provides access * to the SELECT result fields via `$selected.fieldName` syntax. * * Note: When used with GROUP BY, functional having receives `{ $selected: ... }` with the * aggregated SELECT results. When used without GROUP BY, it receives the full namespaced row * which includes both table data and `$selected`. * * Example: `({ $selected }) => $selected.sessionCount > 2` * Example (no GROUP BY): `(row) => row.user.salary > 70000 && row.$selected.user_count > 2` */ export type FunctionalHavingRow = TContext[`schema`] & (TContext[`hasResult`] extends true ? { $selected: TContext[`result`]; } : {}); /** * RefsForContext - Creates ref proxies for all tables/collections in a query context * * This is the main entry point for creating ref objects in query builder callbacks. * For nullable join sides (left/right/full joins), it produces `Ref` instead * of `Ref | undefined`. This accurately reflects that the proxy object is always * present at build time (it's a truthy proxy that records property access paths), * while the `Nullable` flag ensures the result type correctly includes `| undefined`. * * Examples: * - Required field: `Ref` → user.name works, result is T * - Nullable join side: `Ref` → user.name works, result is T | undefined * * After `select()` is called, this type also includes `$selected` which provides access * to the SELECT result fields via `$selected.fieldName` syntax. */ type KeysOfUnion = T extends unknown ? keyof T : never; type ValueOfUnion = T extends unknown ? K extends keyof T ? T[K] : never : never; type RefForContextValue = IsPlainObject extends true ? Ref : RefLeaf; type RefsSchemaForContext = IsExactlyUndefined extends true ? TContext[`schema`] : NonUndefined extends ContextSchema ? NonUndefined : TContext[`schema`]; export type RefsForContext = { [K in KeysOfUnion>]: IsNonExactOptional, K>> extends true ? IsNonExactNullable, K>> extends true ? RefForContextValue, K>>, true> : RefForContextValue, K>>, true> : IsNonExactNullable, K>> extends true ? RefForContextValue, K>>, true> : RefForContextValue, K>>; } & (TContext[`hasResult`] extends true ? { $selected: Ref; } : {}); /** * Type Detection Helpers * * These helpers distinguish between different kinds of optionality/nullability: * - IsExactlyUndefined: T is literally `undefined` (not `string | undefined`) * - IsOptional: T includes undefined (like `string | undefined`) * - IsExactlyNull: T is literally `null` (not `string | null`) * - IsNullable: T includes null (like `string | null`) * - IsNonExactOptional: T includes undefined but is not exactly undefined * - IsNonExactNullable: T includes null but is not exactly null * * The [T] extends [undefined] pattern prevents distributive conditional types, * ensuring we check the exact type rather than distributing over union members. */ type IsExactlyUndefined = [T] extends [undefined] ? true : false; type IsExactlyNull = [T] extends [null] ? true : false; type IsOptional = undefined extends T ? true : false; type IsNullable = null extends T ? true : false; type IsNonExactOptional = IsOptional extends true ? IsExactlyUndefined extends false ? true : false : false; type IsNonExactNullable = IsNullable extends true ? IsExactlyNull extends false ? true : false : false; /** * Type Extraction Helpers * * These helpers extract the "useful" part of a type by removing null/undefined: * - NonUndefined: `string | undefined` → `string` (preserves null if present) * - NonNull: `string | null` → `string` (preserves undefined if present) * * These are used when we need to handle optional and nullable types separately. * For cases where both null and undefined should be removed, use TypeScript's * built-in NonNullable instead. */ type NonUndefined = T extends undefined ? never : T; type NonNull = T extends null ? never : T; /** * Virtual properties available on all Ref types in query builders. * These allow querying on sync status, origin, key, and collection ID. * * @example * ```typescript * // Filter by sync status * .where(({ user }) => eq(user.$synced, true)) * * // Filter by origin * .where(({ order }) => eq(order.$origin, 'local')) * * // Access key in select * .select(({ user }) => ({ * key: user.$key, * collectionId: user.$collectionId, * })) * ``` */ type VirtualPropsRef = { readonly [K in keyof VirtualRowProps]: RefLeaf[K]>; }; /** * Ref - The user-facing ref interface for the query builder * * This is a clean type that represents a reference to a value in the query, * designed for optimal IDE experience without internal implementation details. * It provides a recursive interface that allows nested property access while * preserving optionality and nullability correctly. * * The `Nullable` parameter indicates whether this ref comes from a nullable * join side (left/right/full). When `true`, the `Nullable` flag propagates * through all nested property accesses, ensuring the result type includes * `| undefined` for all fields accessed through this ref. * * Includes virtual properties ($synced, $origin, $key, $collectionId) for * querying on sync status and row metadata. * * Example usage: * ```typescript * // Clean interface - no internal properties visible * const users: Ref<{ id: number; profile?: { bio: string } }> = { ... } * users.id // Ref - clean display * users.profile?.bio // Ref - nested optional access works * users.$synced // RefLeaf - virtual property access * * // Nullable ref (left/right/full join side): * select(({ dept }) => ({ name: dept.name })) // result: string | undefined * * // Spread operations work cleanly: * select(({ user }) => ({ ...user })) // Returns User type, not Ref types * ``` */ export type Ref = { [K in keyof T]: IsNonExactOptional extends true ? IsNonExactNullable extends true ? IsPlainObject> extends true ? Ref, Nullable> | undefined : RefLeaf, Nullable> | undefined : IsPlainObject> extends true ? Ref, Nullable> | undefined : RefLeaf, Nullable> | undefined : IsNonExactNullable extends true ? IsPlainObject> extends true ? Ref, Nullable> | null : RefLeaf, Nullable> | null : IsPlainObject extends true ? Ref : RefLeaf; } & RefLeaf & VirtualPropsRef; /** * Ref - The user-facing ref type with clean IDE display * * An opaque branded type that represents a reference to a value in a query. * This shows as `Ref` in the IDE without exposing internal structure. * * Example usage: * - Ref displays as `Ref` in IDE * - Ref displays as `Ref` in IDE * - No internal properties like __refProxy, __path, __type are visible */ declare const RefBrand: unique symbol; declare const NullableBrand: unique symbol; export type RefLeaf = { readonly [RefBrand]?: T; } & ([Nullable] extends [true] ? { readonly [NullableBrand]?: true; } : {}); type IsNullableRef = typeof NullableBrand extends keyof T ? true : false; type WithoutRefBrand = IsPlainObject extends true ? Omit : T; /** * PreserveSingleResultFlag - Conditionally includes the singleResult flag * * This helper type ensures the singleResult flag is only added to the context when it's * explicitly true. It uses a non-distributive conditional (tuple wrapper) to prevent * unexpected behavior when TFlag is a union type. * * @template TFlag - The singleResult flag value to check * @returns { singleResult: true } if TFlag is true, otherwise {} */ type PreserveSingleResultFlag = [TFlag] extends [true] ? { singleResult: true; } : {}; type PreserveHasResultFlag = [TFlag] extends [true] ? { hasResult: true; } : {}; type PreserveUnionFromFlag = [TFlag] extends [true] ? { hasUnionFrom: true; } : {}; type PreserveFromSourceNames = [TNames] extends [ReadonlyArray] ? { fromSourceNames: TNames; } : {}; /** * MergeContextWithJoinType - Creates a new context after a join operation * * This is the core type that handles the complex logic of merging schemas * when tables are joined, applying the correct optionality based on join type. * * **Key Responsibilities**: * 1. **Schema Merging**: Combines existing schema with newly joined tables * 2. **Optionality Logic**: Applies join-specific optionality rules: * - `LEFT JOIN`: New table becomes optional * - `RIGHT JOIN`: Existing tables become optional * - `FULL JOIN`: Both existing and new become optional * - `INNER JOIN`: No tables become optional * 3. **State Tracking**: Updates hasJoins and joinTypes for future operations * * **Context Evolution**: * - `baseSchema`: Unchanged (always the original `from()` tables) * - `schema`: Expanded with new tables and proper optionality * - `hasJoins`: Set to true * - `joinTypes`: Updated to track this join type * - `result`: Preserved from previous operations * - `singleResult`: Preserved only if already true (via PreserveSingleResultFlag) */ export type MergeContextWithJoinType = { baseSchema: TContext[`baseSchema`]; schema: ApplyJoinOptionalityToMergedSchema>; refsSchema: ApplyJoinOptionalityToMergedSchema, TNewSchema, TJoinType, FromSourceNamesForOptionality>; fromSourceName: TContext[`fromSourceName`]; hasJoins: true; joinTypes: (TContext[`joinTypes`] extends Record ? TContext[`joinTypes`] : {}) & { [K in keyof TNewSchema & string]: TJoinType; }; result: TContext[`result`]; } & PreserveSingleResultFlag & PreserveHasResultFlag & PreserveUnionFromFlag & PreserveFromSourceNames; /** * ApplyJoinOptionalityToMergedSchema - Applies optionality rules when merging schemas * * This type implements the SQL join optionality semantics: * * **For Existing Tables**: * - `RIGHT JOIN` or `FULL JOIN`: Main table (from fromSourceName) becomes optional * - Other join types: Existing tables keep their current optionality * - Previously joined tables: Keep their already-applied optionality * * **For New Tables**: * - `LEFT JOIN` or `FULL JOIN`: New table becomes optional * - `INNER JOIN` or `RIGHT JOIN`: New table remains required * * **Examples**: * ```sql * FROM users LEFT JOIN orders -- orders becomes optional * FROM users RIGHT JOIN orders -- users becomes optional * FROM users FULL JOIN orders -- both become optional * FROM users INNER JOIN orders -- both remain required * ``` * * The intersection (&) ensures both existing and new schemas are merged * into a single type while preserving all table references. */ export type ApplyJoinOptionalityToMergedSchema = { [K in keyof TExistingSchema]: K extends TFromSourceNames ? TJoinType extends `right` | `full` ? TExistingSchema[K] | undefined : TExistingSchema[K] : TExistingSchema[K]; } & { [K in keyof TNewSchema]: TJoinType extends `left` | `full` ? // New table becomes optional for left and full joins TNewSchema[K] | undefined : TNewSchema[K]; }; /** * Utility type to infer the query result size (single row or an array) */ export type InferResultType = TContext extends SingleResult ? GetResult | undefined : Array>; type WithVirtualPropsIfObject = TResult extends object ? WithVirtualProps : TResult; type PrettifyIfPlainObject = IsPlainObject extends true ? Prettify : T; type FromSourceNamesForOptionality = TContext[`fromSourceNames`] extends ReadonlyArray ? TName & string : TContext[`fromSourceName`]; type HasRightOrFullJoin = TContext[`joinTypes`] extends Record ? Extract extends never ? false : true : false; type JoinedOnlyUnionFromResult = Prettify<{ [P in keyof TBaseSchema]?: undefined; } & { [P in Exclude]: TSchema[P]; }>; type UnionFromResult = { [K in keyof TBaseSchema]: Prettify<{ [P in K]: NonUndefined]>; } & { [P in Exclude]?: undefined; } & { [P in Exclude]: TSchema[P]; }>; }[keyof TBaseSchema] | (THasJoinedOnlyBranch extends true ? JoinedOnlyUnionFromResult : never); type ResultValue = TContext[`hasResult`] extends true ? WithVirtualPropsIfObject : TContext[`hasUnionFrom`] extends true ? UnionFromResult> : TContext[`hasJoins`] extends true ? TContext[`schema`] : TContext[`schema`][TContext[`fromSourceName`]]; /** * GetResult - Determines the final result type of a query * * This type implements the logic for what a query returns based on its current state: * * **Priority Order**: * 1. **Explicit Result**: If `select()` was called, use the projected type * 2. **Join Query**: If joins exist, return all tables with proper optionality * 3. **Single Table**: Return just the main table from `from()` * * **Examples**: * ```typescript * // Single table query: * from({ users }).where(...) // → User[] * * // Join query without select: * from({ users }).leftJoin({ orders }, ...) // → { users: User, orders: Order | undefined }[] * * // Query with select: * from({ users }).select({ id: users.id, name: users.name }) // → { id: number, name: string }[] * ``` * * The `Prettify` wrapper ensures clean type display in IDEs by flattening * complex intersection types into readable object types. */ export type GetRawResult = ResultValue; export type GetResult = Prettify>; type IsExactlyContext = [Context] extends [TContext] ? [TContext] extends [Context] ? true : false : false; type RootScalarResultError = { readonly __tanstackDbRootQueryError__: `Top-level scalar results are not supported by createLiveQueryCollection() or queryOnce(). Return an object, or use the scalar query inside toArray(...) or concat(toArray(...)).`; }; export type RootObjectResultConstraint = IsExactlyContext extends true ? unknown : GetResult extends object ? unknown : RootScalarResultError; type ContextFromQueryBuilder> = TQuery extends QueryBuilder ? TContext : never; export type RootQueryBuilder> = TQuery & RootObjectResultConstraint>; export type RootQueryFn> = (q: InitialQueryBuilder) => RootQueryBuilder; export type RootQueryResult = IsExactlyContext extends true ? any : GetResult extends object ? GetResult : never; /** * ApplyJoinOptionalityToSchema - Legacy helper for complex join scenarios * * This type was designed to handle complex scenarios with multiple joins * where the optionality of tables might be affected by subsequent joins. * Currently used in advanced join logic, but most cases are handled by * the simpler `ApplyJoinOptionalityToMergedSchema`. * * **Logic**: * 1. **Main Table**: Becomes optional if ANY right or full join exists in the chain * 2. **Joined Tables**: Check their specific join type for optionality * 3. **Complex Cases**: Handle scenarios where subsequent joins affect earlier tables * * This is primarily used for edge cases and may be simplified in future versions * as the simpler merge-based approach covers most real-world scenarios. */ export type ApplyJoinOptionalityToSchema, TFromSourceName extends string> = { [K in keyof TSchema]: K extends TFromSourceName ? HasJoinType extends true ? TSchema[K] | undefined : TSchema[K] : K extends keyof TJoinTypes ? TJoinTypes[K] extends `left` | `full` ? TSchema[K] | undefined : IsTableMadeOptionalBySubsequentJoins extends true ? TSchema[K] | undefined : TSchema[K] : TSchema[K]; }; /** * IsTableMadeOptionalBySubsequentJoins - Checks if later joins affect table optionality * * This helper determines if a table that was initially required becomes optional * due to joins that happen later in the query chain. * * **Current Implementation**: * - Main table: Becomes optional if any right/full joins exist * - Joined tables: Not affected by subsequent joins (simplified model) * * This is a conservative approach that may be extended in the future to handle * more complex join interaction scenarios. */ type IsTableMadeOptionalBySubsequentJoins, TFromSourceName extends string> = TTableAlias extends TFromSourceName ? HasJoinType : false; /** * HasJoinType - Utility to check if any join in a chain matches target types * * This type searches through all recorded join types to see if any match * the specified target types. It's used to implement logic like "becomes optional * if ANY right or full join exists in the chain". * * **How it works**: * 1. Maps over all join types, checking each against target types * 2. Creates a union of boolean results * 3. Uses `true extends Union` pattern to check if any were true * * **Example**: * ```typescript * HasJoinType<{ orders: 'left', products: 'right' }, 'right' | 'full'> * // → true (because products is a right join) * ``` */ export type HasJoinType, TTargetTypes extends string> = true extends { [K in keyof TJoinTypes]: TJoinTypes[K] extends TTargetTypes ? true : false; }[keyof TJoinTypes] ? true : false; /** * MergeContextForJoinCallback - Special context for join condition callbacks * * This type creates a context specifically for the `onCallback` parameter of join operations. * The key difference from `MergeContextWithJoinType` is that NO optionality is applied here. * * **Why No Optionality?** * In SQL, join conditions are evaluated BEFORE optionality is determined. Both tables * must be treated as available (non-optional) within the join condition itself. * Optionality is only applied to the result AFTER the join logic executes. * * **Example**: * ```typescript * .from({ users }) * .leftJoin({ orders }, ({ users, orders }) => { * // users is NOT optional here - we can access users.id directly * // orders is NOT optional here - we can access orders.userId directly * return eq(users.id, orders.userId) * }) * .where(({ orders }) => { * // NOW orders is optional because it's after the LEFT JOIN * return orders?.status === 'pending' * }) * ``` * * The simple intersection (&) merges schemas without any optionality transformation. */ export type MergeContextForJoinCallback = { baseSchema: TContext[`baseSchema`]; schema: TContext[`schema`] & TNewSchema; refsSchema: RefsSchemaForContext & TNewSchema; fromSourceName: TContext[`fromSourceName`]; hasJoins: true; joinTypes: TContext[`joinTypes`] extends Record ? TContext[`joinTypes`] : {}; result: TContext[`result`]; } & PreserveHasResultFlag & PreserveUnionFromFlag & PreserveFromSourceNames; /** * WithResult - Updates a context with a new result type after select() * * This utility type is used internally when the `select()` method is called * to update the context with the projected result type. It preserves all * other context properties while replacing the `result` field. * * **Usage**: * When `select()` is called, the query builder uses this type to create * a new context where `result` contains the shape of the selected fields. * * The double `Prettify` ensures both the overall context and the nested * result type display cleanly in IDEs. */ export type WithResult = Prettify & { result: PrettifyIfPlainObject; hasResult: true; }>; /** * Prettify - Utility type for clean IDE display */ export type Prettify = { [K in keyof T]: T[K]; } & {}; /** * IsPlainObject - Utility type to check if T is a plain object * * Returns `false` for: * - Arrays (ReadonlyArray) * - JavaScript built-ins (Date, Map, Set, etc.) * - Objects with `Symbol.toStringTag` (class instances like Temporal types, * TypedArrays not already in JsBuiltIns, etc.) — these are not plain data objects */ type IsPlainObject = T extends unknown ? T extends object ? T extends ReadonlyArray ? false : T extends JsBuiltIns ? false : T extends { readonly [Symbol.toStringTag]: string; } ? false : true : false : false; type IsAny = 0 extends 1 & T ? true : false; /** * JsBuiltIns - List of JavaScript built-ins */ type JsBuiltIns = ArrayBuffer | ArrayBufferLike | AsyncGenerator | BigInt64Array | BigUint64Array | DataView | Date | Error | Float32Array | Float64Array | Function | Generator | Int16Array | Int32Array | Int8Array | Map | Promise | RegExp | Set | string | Uint16Array | Uint32Array | Uint8Array | Uint8ClampedArray | WeakMap | WeakSet; export {};