@tanstack/db 0.1.11 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/cjs/errors.cjs +18 -6
  2. package/dist/cjs/errors.cjs.map +1 -1
  3. package/dist/cjs/errors.d.cts +9 -3
  4. package/dist/cjs/index.cjs +3 -1
  5. package/dist/cjs/index.cjs.map +1 -1
  6. package/dist/cjs/query/builder/functions.cjs +4 -1
  7. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  8. package/dist/cjs/query/builder/functions.d.cts +38 -21
  9. package/dist/cjs/query/builder/index.cjs +25 -16
  10. package/dist/cjs/query/builder/index.cjs.map +1 -1
  11. package/dist/cjs/query/builder/index.d.cts +8 -8
  12. package/dist/cjs/query/builder/ref-proxy.cjs +12 -8
  13. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  14. package/dist/cjs/query/builder/ref-proxy.d.cts +2 -1
  15. package/dist/cjs/query/builder/types.d.cts +493 -28
  16. package/dist/cjs/query/compiler/evaluators.cjs +29 -0
  17. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  18. package/dist/cjs/query/compiler/group-by.cjs +4 -2
  19. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  20. package/dist/cjs/query/compiler/index.cjs +13 -4
  21. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  22. package/dist/cjs/query/compiler/joins.cjs +70 -61
  23. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  24. package/dist/cjs/query/compiler/select.cjs +131 -42
  25. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  26. package/dist/cjs/query/compiler/select.d.cts +1 -5
  27. package/dist/cjs/query/ir.cjs +4 -0
  28. package/dist/cjs/query/ir.cjs.map +1 -1
  29. package/dist/cjs/query/ir.d.cts +6 -1
  30. package/dist/cjs/query/optimizer.cjs +61 -20
  31. package/dist/cjs/query/optimizer.cjs.map +1 -1
  32. package/dist/esm/errors.d.ts +9 -3
  33. package/dist/esm/errors.js +18 -6
  34. package/dist/esm/errors.js.map +1 -1
  35. package/dist/esm/index.js +4 -2
  36. package/dist/esm/query/builder/functions.d.ts +38 -21
  37. package/dist/esm/query/builder/functions.js +4 -1
  38. package/dist/esm/query/builder/functions.js.map +1 -1
  39. package/dist/esm/query/builder/index.d.ts +8 -8
  40. package/dist/esm/query/builder/index.js +27 -18
  41. package/dist/esm/query/builder/index.js.map +1 -1
  42. package/dist/esm/query/builder/ref-proxy.d.ts +2 -1
  43. package/dist/esm/query/builder/ref-proxy.js +12 -8
  44. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  45. package/dist/esm/query/builder/types.d.ts +493 -28
  46. package/dist/esm/query/compiler/evaluators.js +29 -0
  47. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  48. package/dist/esm/query/compiler/group-by.js +4 -2
  49. package/dist/esm/query/compiler/group-by.js.map +1 -1
  50. package/dist/esm/query/compiler/index.js +15 -6
  51. package/dist/esm/query/compiler/index.js.map +1 -1
  52. package/dist/esm/query/compiler/joins.js +71 -62
  53. package/dist/esm/query/compiler/joins.js.map +1 -1
  54. package/dist/esm/query/compiler/select.d.ts +1 -5
  55. package/dist/esm/query/compiler/select.js +131 -42
  56. package/dist/esm/query/compiler/select.js.map +1 -1
  57. package/dist/esm/query/ir.d.ts +6 -1
  58. package/dist/esm/query/ir.js +4 -0
  59. package/dist/esm/query/ir.js.map +1 -1
  60. package/dist/esm/query/optimizer.js +62 -21
  61. package/dist/esm/query/optimizer.js.map +1 -1
  62. package/package.json +2 -2
  63. package/src/errors.ts +17 -10
  64. package/src/query/builder/functions.ts +176 -108
  65. package/src/query/builder/index.ts +68 -48
  66. package/src/query/builder/ref-proxy.ts +14 -20
  67. package/src/query/builder/types.ts +622 -110
  68. package/src/query/compiler/evaluators.ts +30 -0
  69. package/src/query/compiler/group-by.ts +6 -1
  70. package/src/query/compiler/index.ts +23 -6
  71. package/src/query/compiler/joins.ts +132 -104
  72. package/src/query/compiler/select.ts +206 -113
  73. package/src/query/ir.ts +14 -1
  74. package/src/query/optimizer.ts +131 -59
@@ -1,8 +1,37 @@
1
1
  import type { CollectionImpl } from "../../collection.js"
2
- import type { Aggregate, BasicExpression, OrderByDirection } from "../ir.js"
2
+ import type {
3
+ Aggregate,
4
+ BasicExpression,
5
+ Func,
6
+ OrderByDirection,
7
+ PropRef,
8
+ Value,
9
+ } from "../ir.js"
3
10
  import type { QueryBuilder } from "./index.js"
4
11
  import type { ResolveType } from "../../types.js"
5
12
 
13
+ /**
14
+ * Context - The central state container for query builder operations
15
+ *
16
+ * This interface tracks all the information needed to build and type-check queries:
17
+ *
18
+ * **Schema Management**:
19
+ * - `baseSchema`: The original tables/collections from the `from()` clause
20
+ * - `schema`: Current available tables (expands with joins, contracts with subqueries)
21
+ *
22
+ * **Query State**:
23
+ * - `fromSourceName`: Which table was used in `from()` - needed for optionality logic
24
+ * - `hasJoins`: Whether any joins have been added (affects result type inference)
25
+ * - `joinTypes`: Maps table aliases to their join types for optionality calculations
26
+ *
27
+ * **Result Tracking**:
28
+ * - `result`: The final shape after `select()` - undefined until select is called
29
+ *
30
+ * The context evolves through the query builder chain:
31
+ * 1. `from()` sets baseSchema and schema to the same thing
32
+ * 2. `join()` expands schema and sets hasJoins/joinTypes
33
+ * 3. `select()` sets result to the projected shape
34
+ */
6
35
  export interface Context {
7
36
  // The collections available in the base schema
8
37
  baseSchema: ContextSchema
@@ -21,20 +50,57 @@ export interface Context {
21
50
  result?: any
22
51
  }
23
52
 
53
+ /**
54
+ * ContextSchema - The shape of available tables/collections in a query context
55
+ *
56
+ * This is simply a record mapping table aliases to their TypeScript types.
57
+ * It evolves as the query progresses:
58
+ * - Initial: Just the `from()` table
59
+ * - After joins: Includes all joined tables with proper optionality
60
+ * - In subqueries: May be a subset of the outer query's schema
61
+ */
24
62
  export type ContextSchema = Record<string, unknown>
25
63
 
64
+ /**
65
+ * Source - Input definition for query builder `from()` clause
66
+ *
67
+ * Maps table aliases to either:
68
+ * - `CollectionImpl`: A database collection/table
69
+ * - `QueryBuilder`: A subquery that can be used as a table
70
+ *
71
+ * Example: `{ users: usersCollection, orders: ordersCollection }`
72
+ */
26
73
  export type Source = {
27
74
  [alias: string]: CollectionImpl<any, any> | QueryBuilder<Context>
28
75
  }
29
76
 
30
- // Helper type to infer collection type from CollectionImpl
31
- // This uses ResolveType directly to ensure consistency with collection creation logic
77
+ /**
78
+ * InferCollectionType - Extracts the TypeScript type from a CollectionImpl
79
+ *
80
+ * This helper ensures we get the same type that would be used when creating
81
+ * the collection itself. It uses the internal `ResolveType` logic to maintain
82
+ * consistency between collection creation and query type inference.
83
+ *
84
+ * The complex generic parameters extract:
85
+ * - U: The base document type
86
+ * - TSchema: The schema definition
87
+ * - The resolved type combines these with any transforms
88
+ */
32
89
  export type InferCollectionType<T> =
33
90
  T extends CollectionImpl<infer U, any, any, infer TSchema, any>
34
91
  ? ResolveType<U, TSchema, U>
35
92
  : never
36
93
 
37
- // Helper type to create schema from source
94
+ /**
95
+ * SchemaFromSource - Converts a Source definition into a ContextSchema
96
+ *
97
+ * This transforms the input to `from()` into the schema format used throughout
98
+ * the query builder. For each alias in the source:
99
+ * - Collections → their inferred TypeScript type
100
+ * - Subqueries → their result type (what they would return if executed)
101
+ *
102
+ * The `Prettify` wrapper ensures clean type display in IDEs.
103
+ */
38
104
  export type SchemaFromSource<T extends Source> = Prettify<{
39
105
  [K in keyof T]: T[K] extends CollectionImpl<any, any, any, any, any>
40
106
  ? InferCollectionType<T[K]>
@@ -43,49 +109,217 @@ export type SchemaFromSource<T extends Source> = Prettify<{
43
109
  : never
44
110
  }>
45
111
 
46
- // Helper type to get all aliases from a context
112
+ /**
113
+ * GetAliases - Extracts all table aliases available in a query context
114
+ *
115
+ * Simple utility type that returns the keys of the schema, representing
116
+ * all table/collection aliases that can be referenced in the current query.
117
+ */
47
118
  export type GetAliases<TContext extends Context> = keyof TContext[`schema`]
48
119
 
49
- // Callback type for where/having clauses
120
+ /**
121
+ * WhereCallback - Type for where/having clause callback functions
122
+ *
123
+ * These callbacks receive a `refs` object containing RefProxy instances for
124
+ * all available tables. The callback should return a boolean expression
125
+ * that will be used to filter query results.
126
+ *
127
+ * Example: `(refs) => eq(refs.users.age, 25)`
128
+ */
50
129
  export type WhereCallback<TContext extends Context> = (
51
- refs: RefProxyForContext<TContext>
130
+ refs: RefsForContext<TContext>
52
131
  ) => any
53
132
 
54
- // Callback return type for select clauses
55
- export type SelectObject<
56
- T extends Record<
57
- string,
58
- BasicExpression | Aggregate | RefProxy | RefProxyFor<any>
59
- > = Record<string, BasicExpression | Aggregate | RefProxy | RefProxyFor<any>>,
60
- > = T
61
-
62
- // Helper type to get the result type from a select object
63
- export type ResultTypeFromSelect<TSelectObject> = {
64
- [K in keyof TSelectObject]: TSelectObject[K] extends RefProxy<infer T>
65
- ? T
66
- : TSelectObject[K] extends BasicExpression<infer T>
67
- ? T
68
- : TSelectObject[K] extends Aggregate<infer T>
69
- ? T
70
- : TSelectObject[K] extends RefProxyFor<infer T>
133
+ /**
134
+ * SelectValue - Union of all valid values in a select clause
135
+ *
136
+ * This type defines what can be used as values in the object passed to `select()`.
137
+ *
138
+ * **Core Expression Types**:
139
+ * - `BasicExpression`: Function calls like `upper(users.name)`
140
+ * - `Aggregate`: Aggregations like `count()`, `avg()`
141
+ * - `RefProxy/Ref`: Direct field references like `users.name`
142
+ *
143
+ * **JavaScript Literals** (for constant values in projections):
144
+ * - `string`: String literals like `'active'`, `'N/A'`
145
+ * - `number`: Numeric literals like `0`, `42`, `3.14`
146
+ * - `boolean`: Boolean literals `true`, `false`
147
+ * - `null`: Explicit null values
148
+ *
149
+ * **Advanced Features**:
150
+ * - `undefined`: Allows optional projection values
151
+ * - `{ [key: string]: SelectValue }`: Nested object projection
152
+ *
153
+ * The clean Ref type ensures no internal properties are visible to users.
154
+ *
155
+ * Examples:
156
+ * ```typescript
157
+ * select({
158
+ * id: users.id,
159
+ * name: users.name,
160
+ * status: 'active', // string literal
161
+ * priority: 1, // number literal
162
+ * verified: true, // boolean literal
163
+ * notes: null, // explicit null
164
+ * profile: {
165
+ * name: users.name,
166
+ * email: users.email
167
+ * }
168
+ * })
169
+ * ```
170
+ */
171
+ type SelectValue =
172
+ | BasicExpression
173
+ | Aggregate
174
+ | Ref
175
+ | RefLeaf<any>
176
+ | string // String literals
177
+ | number // Numeric literals
178
+ | boolean // Boolean literals
179
+ | null // Explicit null
180
+ | undefined // Optional values
181
+ | { [key: string]: SelectValue }
182
+ | Array<RefLeaf<any>>
183
+
184
+ // Recursive shape for select objects allowing nested projections
185
+ type SelectShape = { [key: string]: SelectValue | SelectShape }
186
+
187
+ /**
188
+ * SelectObject - Wrapper type for select clause objects
189
+ *
190
+ * This ensures that objects passed to `select()` have valid SelectValue types
191
+ * for all their properties. It's a simple wrapper that provides better error
192
+ * messages when invalid selections are attempted.
193
+ */
194
+ export type SelectObject<T extends SelectShape = SelectShape> = T
195
+
196
+ /**
197
+ * ResultTypeFromSelect - Infers the result type from a select object
198
+ *
199
+ * This complex type transforms the input to `select()` into the actual TypeScript
200
+ * type that the query will return. It handles all the different kinds of values
201
+ * that can appear in a select clause:
202
+ *
203
+ * **Ref/RefProxy Extraction**:
204
+ * - `RefProxy<T>` → `T`: Extracts the underlying type
205
+ * - `Ref<T> | undefined` → `T | undefined`: Preserves optionality
206
+ * - `Ref<T> | null` → `T | null`: Preserves nullability
207
+ *
208
+ * **Expression Types**:
209
+ * - `BasicExpression<T>` → `T`: Function results like `upper()` → `string`
210
+ * - `Aggregate<T>` → `T`: Aggregation results like `count()` → `number`
211
+ *
212
+ * **JavaScript Literals** (pass through as-is):
213
+ * - `string` → `string`: String literals remain strings
214
+ * - `number` → `number`: Numeric literals remain numbers
215
+ * - `boolean` → `boolean`: Boolean literals remain booleans
216
+ * - `null` → `null`: Explicit null remains null
217
+ * - `undefined` → `undefined`: Direct undefined values
218
+ *
219
+ * **Nested Objects** (recursive):
220
+ * - Plain objects are recursively processed to handle nested projections
221
+ * - RefProxy objects are detected and their types extracted
222
+ *
223
+ * Example transformation:
224
+ * ```typescript
225
+ * // Input:
226
+ * { id: Ref<number>, name: Ref<string>, status: 'active', count: 42, profile: { bio: Ref<string> } }
227
+ *
228
+ * // Output:
229
+ * { id: number, name: string, status: 'active', count: 42, profile: { bio: string } }
230
+ * ```
231
+ */
232
+ export type ResultTypeFromSelect<TSelectObject> = WithoutRefBrand<
233
+ Prettify<{
234
+ [K in keyof TSelectObject]: NeedsExtraction<TSelectObject[K]> extends true
235
+ ? ExtractExpressionType<TSelectObject[K]>
236
+ : TSelectObject[K] extends Ref<infer _T>
237
+ ? ExtractRef<TSelectObject[K]>
238
+ : TSelectObject[K] extends RefLeaf<infer T>
71
239
  ? T
72
- : TSelectObject[K] extends undefined
73
- ? undefined
74
- : TSelectObject[K] extends { __type: infer U }
75
- ? U
76
- : never
77
- }
240
+ : TSelectObject[K] extends RefLeaf<infer T> | undefined
241
+ ? T | undefined
242
+ : TSelectObject[K] extends RefLeaf<infer T> | null
243
+ ? T | null
244
+ : TSelectObject[K] extends Ref<infer _T> | undefined
245
+ ? ExtractRef<TSelectObject[K]> | undefined
246
+ : TSelectObject[K] extends Ref<infer _T> | null
247
+ ? ExtractRef<TSelectObject[K]> | null
248
+ : TSelectObject[K] extends Aggregate<infer T>
249
+ ? T
250
+ : TSelectObject[K] extends
251
+ | string
252
+ | number
253
+ | boolean
254
+ | null
255
+ | undefined
256
+ ? TSelectObject[K]
257
+ : TSelectObject[K] extends Record<string, any>
258
+ ? ResultTypeFromSelect<TSelectObject[K]>
259
+ : never
260
+ }>
261
+ >
262
+
263
+ // Extract Ref or subobject with a spread or a Ref
264
+ type ExtractRef<T> = Prettify<ResultTypeFromSelect<WithoutRefBrand<T>>>
265
+
266
+ // Helper type to extract the underlying type from various expression types
267
+ type ExtractExpressionType<T> =
268
+ T extends PropRef<infer U>
269
+ ? U
270
+ : T extends Value<infer U>
271
+ ? U
272
+ : T extends Func<infer U>
273
+ ? U
274
+ : T extends Aggregate<infer U>
275
+ ? U
276
+ : T extends BasicExpression<infer U>
277
+ ? U
278
+ : T
279
+
280
+ // Helper type to check if a type needs expression type extraction
281
+ type NeedsExtraction<T> = T extends
282
+ | PropRef<any>
283
+ | Value<any>
284
+ | Func<any>
285
+ | Aggregate<any>
286
+ | BasicExpression<any>
287
+ ? true
288
+ : false
78
289
 
79
- // Callback type for orderBy clauses
290
+ /**
291
+ * OrderByCallback - Type for orderBy clause callback functions
292
+ *
293
+ * Similar to WhereCallback, these receive refs for all available tables
294
+ * and should return expressions that will be used for sorting.
295
+ *
296
+ * Example: `(refs) => refs.users.createdAt`
297
+ */
80
298
  export type OrderByCallback<TContext extends Context> = (
81
- refs: RefProxyForContext<TContext>
299
+ refs: RefsForContext<TContext>
82
300
  ) => any
83
301
 
302
+ /**
303
+ * OrderByOptions - Configuration for orderBy operations
304
+ *
305
+ * Combines direction and null handling with string-specific sorting options.
306
+ * The intersection with StringSortOpts allows for either simple lexical sorting
307
+ * or locale-aware sorting with customizable options.
308
+ */
84
309
  export type OrderByOptions = {
85
310
  direction?: OrderByDirection
86
311
  nulls?: `first` | `last`
87
312
  } & StringSortOpts
88
313
 
314
+ /**
315
+ * StringSortOpts - Options for string sorting behavior
316
+ *
317
+ * This discriminated union allows for two types of string sorting:
318
+ * - **Lexical**: Simple character-by-character comparison (default)
319
+ * - **Locale**: Locale-aware sorting with optional customization
320
+ *
321
+ * The union ensures that locale options are only available when locale sorting is selected.
322
+ */
89
323
  export type StringSortOpts =
90
324
  | {
91
325
  stringSort?: `lexical`
@@ -96,6 +330,13 @@ export type StringSortOpts =
96
330
  localeOptions?: object
97
331
  }
98
332
 
333
+ /**
334
+ * CompareOptions - Final resolved options for comparison operations
335
+ *
336
+ * This is the internal type used after all orderBy options have been resolved
337
+ * to their concrete values. Unlike OrderByOptions, all fields are required
338
+ * since defaults have been applied.
339
+ */
99
340
  export type CompareOptions = {
100
341
  direction: OrderByDirection
101
342
  nulls: `first` | `last`
@@ -104,91 +345,219 @@ export type CompareOptions = {
104
345
  localeOptions?: object
105
346
  }
106
347
 
107
- // Callback type for groupBy clauses
348
+ /**
349
+ * GroupByCallback - Type for groupBy clause callback functions
350
+ *
351
+ * These callbacks receive refs for all available tables and should return
352
+ * expressions that will be used for grouping query results.
353
+ *
354
+ * Example: `(refs) => refs.orders.status`
355
+ */
108
356
  export type GroupByCallback<TContext extends Context> = (
109
- refs: RefProxyForContext<TContext>
357
+ refs: RefsForContext<TContext>
110
358
  ) => any
111
359
 
112
- // Callback type for join on clauses
360
+ /**
361
+ * JoinOnCallback - Type for join condition callback functions
362
+ *
363
+ * These callbacks receive refs for all available tables (including the newly
364
+ * joined table) and should return a boolean expression defining the join condition.
365
+ *
366
+ * Important: The newly joined table is NOT marked as optional in this callback,
367
+ * even for left/right/full joins, because optionality is applied AFTER the join
368
+ * condition is evaluated.
369
+ *
370
+ * Example: `(refs) => eq(refs.users.id, refs.orders.userId)`
371
+ */
113
372
  export type JoinOnCallback<TContext extends Context> = (
114
- refs: RefProxyForContext<TContext>
373
+ refs: RefsForContext<TContext>
115
374
  ) => any
116
375
 
117
- // Type for creating RefProxy objects based on context
118
- export type RefProxyForContext<TContext extends Context> = {
119
- [K in keyof TContext[`schema`]]: RefProxyFor<TContext[`schema`][K]>
376
+ /**
377
+ * RefProxyForContext - Creates ref proxies for all tables/collections in a query context
378
+ *
379
+ * This is the main entry point for creating ref objects in query builder callbacks.
380
+ * It handles optionality by placing undefined/null OUTSIDE the RefProxy to enable
381
+ * JavaScript's optional chaining operator (?.):
382
+ *
383
+ * Examples:
384
+ * - Required field: `RefProxy<User>` → user.name works
385
+ * - Optional field: `RefProxy<User> | undefined` → user?.name works
386
+ * - Nullable field: `RefProxy<User> | null` → user?.name works
387
+ * - Both optional and nullable: `RefProxy<User> | undefined` → user?.name works
388
+ *
389
+ * The key insight is that `RefProxy<User | undefined>` would NOT allow `user?.name`
390
+ * because the undefined is "inside" the proxy, but `RefProxy<User> | undefined`
391
+ * does allow it because the undefined is "outside" the proxy.
392
+ *
393
+ * The logic prioritizes optional chaining by always placing `undefined` outside when
394
+ * a type is both optional and nullable (e.g., `string | null | undefined`).
395
+ */
396
+ export type RefsForContext<TContext extends Context> = {
397
+ [K in keyof TContext[`schema`]]: IsNonExactOptional<
398
+ TContext[`schema`][K]
399
+ > extends true
400
+ ? IsNonExactNullable<TContext[`schema`][K]> extends true
401
+ ? // T is both non-exact optional and non-exact nullable (e.g., string | null | undefined)
402
+ // Extract the non-undefined and non-null part and place undefined outside
403
+ Ref<NonNullable<TContext[`schema`][K]>> | undefined
404
+ : // T is optional (T | undefined) but not exactly undefined, and not nullable
405
+ // Extract the non-undefined part and place undefined outside
406
+ Ref<NonUndefined<TContext[`schema`][K]>> | undefined
407
+ : IsNonExactNullable<TContext[`schema`][K]> extends true
408
+ ? // T is nullable (T | null) but not exactly null, and not optional
409
+ // Extract the non-null part and place null outside
410
+ Ref<NonNull<TContext[`schema`][K]>> | null
411
+ : // T is exactly undefined, exactly null, or neither optional nor nullable
412
+ // Wrap in RefProxy as-is (includes exact undefined, exact null, and normal types)
413
+ Ref<TContext[`schema`][K]>
120
414
  }
121
415
 
416
+ /**
417
+ * Type Detection Helpers
418
+ *
419
+ * These helpers distinguish between different kinds of optionality/nullability:
420
+ * - IsExactlyUndefined: T is literally `undefined` (not `string | undefined`)
421
+ * - IsOptional: T includes undefined (like `string | undefined`)
422
+ * - IsExactlyNull: T is literally `null` (not `string | null`)
423
+ * - IsNullable: T includes null (like `string | null`)
424
+ * - IsNonExactOptional: T includes undefined but is not exactly undefined
425
+ * - IsNonExactNullable: T includes null but is not exactly null
426
+ *
427
+ * The [T] extends [undefined] pattern prevents distributive conditional types,
428
+ * ensuring we check the exact type rather than distributing over union members.
429
+ */
430
+
122
431
  // Helper type to check if T is exactly undefined
123
432
  type IsExactlyUndefined<T> = [T] extends [undefined] ? true : false
124
433
 
434
+ // Helper type to check if T is exactly null
435
+ type IsExactlyNull<T> = [T] extends [null] ? true : false
436
+
125
437
  // Helper type to check if T includes undefined (is optional)
126
438
  type IsOptional<T> = undefined extends T ? true : false
127
439
 
440
+ // Helper type to check if T includes null (is nullable)
441
+ type IsNullable<T> = null extends T ? true : false
442
+
443
+ // Helper type to check if T is optional but not exactly undefined
444
+ type IsNonExactOptional<T> =
445
+ IsOptional<T> extends true
446
+ ? IsExactlyUndefined<T> extends false
447
+ ? true
448
+ : false
449
+ : false
450
+
451
+ // Helper type to check if T is nullable but not exactly null
452
+ type IsNonExactNullable<T> =
453
+ IsNullable<T> extends true
454
+ ? IsExactlyNull<T> extends false
455
+ ? true
456
+ : false
457
+ : false
458
+
459
+ /**
460
+ * Type Extraction Helpers
461
+ *
462
+ * These helpers extract the "useful" part of a type by removing null/undefined:
463
+ * - NonUndefined: `string | undefined` → `string` (preserves null if present)
464
+ * - NonNull: `string | null` → `string` (preserves undefined if present)
465
+ *
466
+ * These are used when we need to handle optional and nullable types separately.
467
+ * For cases where both null and undefined should be removed, use TypeScript's
468
+ * built-in NonNullable<T> instead.
469
+ */
470
+
128
471
  // Helper type to extract non-undefined type
129
472
  type NonUndefined<T> = T extends undefined ? never : T
130
473
 
131
- // Helper type to create RefProxy for a specific type with optionality passthrough
132
- // This is used to create the RefProxy object that is used in the query builder.
133
- // Much of the complexity here is due to the fact that we need to handle optionality
134
- // from joins. A left join will make the joined table optional, a right join will make
135
- // the main table optional etc. This is applied to the schema, with the new namespaced
136
- // source being `SourceType | undefined`.
137
- // We then follow this through the ref proxy system so that accessing a property on
138
- // and optional source will itsself be optional.
139
- // If for example we join in `joinedTable` with a left join, then
140
- // `where(({ joinedTable }) => joinedTable.name === `John`)`
141
- // we want the the type of `name` to be `RefProxy<string | undefined>` to indicate that
142
- // the `name` property is optional, as the joinedTable is also optional.
143
- export type RefProxyFor<T> = OmitRefProxy<
144
- IsExactlyUndefined<T> extends true
145
- ? // T is exactly undefined
146
- RefProxy<T>
147
- : IsOptional<T> extends true
148
- ? // T is optional (T | undefined) but not exactly undefined
149
- NonUndefined<T> extends Record<string, any>
150
- ? {
151
- [K in keyof NonUndefined<T>]-?: NonUndefined<T>[K] extends Record<
152
- string,
153
- any
154
- >
155
- ? RefProxyFor<NonUndefined<T>[K]> &
156
- RefProxy<NonUndefined<T>[K] | undefined>
157
- : RefProxy<NonUndefined<T>[K] | undefined>
158
- } & RefProxy<T>
159
- : RefProxy<T>
160
- : // T is not optional
161
- T extends Record<string, any>
162
- ? {
163
- // Make all properties required, but for optional ones, include undefined in the RefProxy type
164
- [K in keyof T]-?: undefined extends T[K]
165
- ? T[K] extends Record<string, any>
166
- ? RefProxyFor<T[K]> & RefProxy<T[K]>
167
- : RefProxy<T[K]>
168
- : T[K] extends Record<string, any>
169
- ? RefProxyFor<T[K]> & RefProxy<T[K]>
170
- : RefProxy<T[K]>
171
- } & RefProxy<T>
172
- : RefProxy<T>
173
- >
174
-
175
- // This is the public type that is exported from the query builder
176
- // and is used when constructing reusable query callbacks.
177
- export type Ref<T> = RefProxyFor<T>
178
-
179
- type OmitRefProxy<T> = Omit<T, `__refProxy` | `__path` | `__type`>
180
-
181
- // The core RefProxy interface
182
- export interface RefProxy<T = any> {
183
- /** @internal */
184
- readonly __refProxy: true
185
- /** @internal */
186
- readonly __path: Array<string>
187
- /** @internal */
188
- readonly __type: T
189
- }
190
-
191
- // Helper type to apply join optionality immediately when merging contexts
474
+ // Helper type to extract non-null type
475
+ type NonNull<T> = T extends null ? never : T
476
+
477
+ /**
478
+ * Ref - The user-facing ref interface for the query builder
479
+ *
480
+ * This is a clean type that represents a reference to a value in the query,
481
+ * designed for optimal IDE experience without internal implementation details.
482
+ * It provides a recursive interface that allows nested property access while
483
+ * preserving optionality and nullability correctly.
484
+ *
485
+ * When spread in select clauses, it correctly produces the underlying data type
486
+ * without Ref wrappers, enabling clean spread operations.
487
+ *
488
+ * Example usage:
489
+ * ```typescript
490
+ * // Clean interface - no internal properties visible
491
+ * const users: Ref<{ id: number; profile?: { bio: string } }> = { ... }
492
+ * users.id // Ref<number> - clean display
493
+ * users.profile?.bio // Ref<string> - nested optional access works
494
+ *
495
+ * // Spread operations work cleanly:
496
+ * select(({ user }) => ({ ...user })) // Returns User type, not Ref types
497
+ * ```
498
+ */
499
+ export type Ref<T = any> = {
500
+ [K in keyof T]: IsNonExactOptional<T[K]> extends true
501
+ ? IsNonExactNullable<T[K]> extends true
502
+ ? // Both optional and nullable
503
+ NonNullable<T[K]> extends Record<string, any>
504
+ ? Ref<NonNullable<T[K]>> | undefined
505
+ : RefLeaf<NonNullable<T[K]>> | undefined
506
+ : // Optional only
507
+ NonUndefined<T[K]> extends Record<string, any>
508
+ ? Ref<NonUndefined<T[K]>> | undefined
509
+ : RefLeaf<NonUndefined<T[K]>> | undefined
510
+ : IsNonExactNullable<T[K]> extends true
511
+ ? // Nullable only
512
+ NonNull<T[K]> extends Record<string, any>
513
+ ? Ref<NonNull<T[K]>> | null
514
+ : RefLeaf<NonNull<T[K]>> | null
515
+ : // Required
516
+ T[K] extends Record<string, any>
517
+ ? Ref<T[K]>
518
+ : RefLeaf<T[K]>
519
+ } & RefLeaf<T>
520
+
521
+ /**
522
+ * Ref - The user-facing ref type with clean IDE display
523
+ *
524
+ * An opaque branded type that represents a reference to a value in a query.
525
+ * This shows as `Ref<T>` in the IDE without exposing internal structure.
526
+ *
527
+ * Example usage:
528
+ * - Ref<number> displays as `Ref<number>` in IDE
529
+ * - Ref<string> displays as `Ref<string>` in IDE
530
+ * - No internal properties like __refProxy, __path, __type are visible
531
+ */
532
+ declare const RefBrand: unique symbol
533
+ export type RefLeaf<T = any> = { readonly [RefBrand]?: T }
534
+
535
+ // Helper type to remove RefBrand from objects
536
+ type WithoutRefBrand<T> =
537
+ T extends Record<string, any> ? Omit<T, typeof RefBrand> : T
538
+
539
+ /**
540
+ * MergeContextWithJoinType - Creates a new context after a join operation
541
+ *
542
+ * This is the core type that handles the complex logic of merging schemas
543
+ * when tables are joined, applying the correct optionality based on join type.
544
+ *
545
+ * **Key Responsibilities**:
546
+ * 1. **Schema Merging**: Combines existing schema with newly joined tables
547
+ * 2. **Optionality Logic**: Applies join-specific optionality rules:
548
+ * - `LEFT JOIN`: New table becomes optional
549
+ * - `RIGHT JOIN`: Existing tables become optional
550
+ * - `FULL JOIN`: Both existing and new become optional
551
+ * - `INNER JOIN`: No tables become optional
552
+ * 3. **State Tracking**: Updates hasJoins and joinTypes for future operations
553
+ *
554
+ * **Context Evolution**:
555
+ * - `baseSchema`: Unchanged (always the original `from()` tables)
556
+ * - `schema`: Expanded with new tables and proper optionality
557
+ * - `hasJoins`: Set to true
558
+ * - `joinTypes`: Updated to track this join type
559
+ * - `result`: Preserved from previous operations
560
+ */
192
561
  export type MergeContextWithJoinType<
193
562
  TContext extends Context,
194
563
  TNewSchema extends ContextSchema,
@@ -213,7 +582,31 @@ export type MergeContextWithJoinType<
213
582
  result: TContext[`result`]
214
583
  }
215
584
 
216
- // Helper type to apply join optionality when merging new schema
585
+ /**
586
+ * ApplyJoinOptionalityToMergedSchema - Applies optionality rules when merging schemas
587
+ *
588
+ * This type implements the SQL join optionality semantics:
589
+ *
590
+ * **For Existing Tables**:
591
+ * - `RIGHT JOIN` or `FULL JOIN`: Main table (from fromSourceName) becomes optional
592
+ * - Other join types: Existing tables keep their current optionality
593
+ * - Previously joined tables: Keep their already-applied optionality
594
+ *
595
+ * **For New Tables**:
596
+ * - `LEFT JOIN` or `FULL JOIN`: New table becomes optional
597
+ * - `INNER JOIN` or `RIGHT JOIN`: New table remains required
598
+ *
599
+ * **Examples**:
600
+ * ```sql
601
+ * FROM users LEFT JOIN orders -- orders becomes optional
602
+ * FROM users RIGHT JOIN orders -- users becomes optional
603
+ * FROM users FULL JOIN orders -- both become optional
604
+ * FROM users INNER JOIN orders -- both remain required
605
+ * ```
606
+ *
607
+ * The intersection (&) ensures both existing and new schemas are merged
608
+ * into a single type while preserving all table references.
609
+ */
217
610
  export type ApplyJoinOptionalityToMergedSchema<
218
611
  TExistingSchema extends ContextSchema,
219
612
  TNewSchema extends ContextSchema,
@@ -237,7 +630,31 @@ export type ApplyJoinOptionalityToMergedSchema<
237
630
  TNewSchema[K]
238
631
  }
239
632
 
240
- // Helper type to get the result type from a context
633
+ /**
634
+ * GetResult - Determines the final result type of a query
635
+ *
636
+ * This type implements the logic for what a query returns based on its current state:
637
+ *
638
+ * **Priority Order**:
639
+ * 1. **Explicit Result**: If `select()` was called, use the projected type
640
+ * 2. **Join Query**: If joins exist, return all tables with proper optionality
641
+ * 3. **Single Table**: Return just the main table from `from()`
642
+ *
643
+ * **Examples**:
644
+ * ```typescript
645
+ * // Single table query:
646
+ * from({ users }).where(...) // → User[]
647
+ *
648
+ * // Join query without select:
649
+ * from({ users }).leftJoin({ orders }, ...) // → { users: User, orders: Order | undefined }[]
650
+ *
651
+ * // Query with select:
652
+ * from({ users }).select({ id: users.id, name: users.name }) // → { id: number, name: string }[]
653
+ * ```
654
+ *
655
+ * The `Prettify` wrapper ensures clean type display in IDEs by flattening
656
+ * complex intersection types into readable object types.
657
+ */
241
658
  export type GetResult<TContext extends Context> = Prettify<
242
659
  TContext[`result`] extends object
243
660
  ? TContext[`result`]
@@ -248,7 +665,22 @@ export type GetResult<TContext extends Context> = Prettify<
248
665
  TContext[`schema`][TContext[`fromSourceName`]]
249
666
  >
250
667
 
251
- // Helper type to apply join optionality to the schema based on joinTypes
668
+ /**
669
+ * ApplyJoinOptionalityToSchema - Legacy helper for complex join scenarios
670
+ *
671
+ * This type was designed to handle complex scenarios with multiple joins
672
+ * where the optionality of tables might be affected by subsequent joins.
673
+ * Currently used in advanced join logic, but most cases are handled by
674
+ * the simpler `ApplyJoinOptionalityToMergedSchema`.
675
+ *
676
+ * **Logic**:
677
+ * 1. **Main Table**: Becomes optional if ANY right or full join exists in the chain
678
+ * 2. **Joined Tables**: Check their specific join type for optionality
679
+ * 3. **Complex Cases**: Handle scenarios where subsequent joins affect earlier tables
680
+ *
681
+ * This is primarily used for edge cases and may be simplified in future versions
682
+ * as the simpler merge-based approach covers most real-world scenarios.
683
+ */
252
684
  export type ApplyJoinOptionalityToSchema<
253
685
  TSchema extends ContextSchema,
254
686
  TJoinTypes extends Record<string, string>,
@@ -275,7 +707,19 @@ export type ApplyJoinOptionalityToSchema<
275
707
  : TSchema[K]
276
708
  }
277
709
 
278
- // Helper type to check if a table becomes optional due to subsequent joins
710
+ /**
711
+ * IsTableMadeOptionalBySubsequentJoins - Checks if later joins affect table optionality
712
+ *
713
+ * This helper determines if a table that was initially required becomes optional
714
+ * due to joins that happen later in the query chain.
715
+ *
716
+ * **Current Implementation**:
717
+ * - Main table: Becomes optional if any right/full joins exist
718
+ * - Joined tables: Not affected by subsequent joins (simplified model)
719
+ *
720
+ * This is a conservative approach that may be extended in the future to handle
721
+ * more complex join interaction scenarios.
722
+ */
279
723
  type IsTableMadeOptionalBySubsequentJoins<
280
724
  TTableAlias extends string | number | symbol,
281
725
  TJoinTypes extends Record<string, string>,
@@ -286,7 +730,24 @@ type IsTableMadeOptionalBySubsequentJoins<
286
730
  : // Joined tables are not affected by subsequent joins in our current implementation
287
731
  false
288
732
 
289
- // Helper type to check if any join has one of the specified types
733
+ /**
734
+ * HasJoinType - Utility to check if any join in a chain matches target types
735
+ *
736
+ * This type searches through all recorded join types to see if any match
737
+ * the specified target types. It's used to implement logic like "becomes optional
738
+ * if ANY right or full join exists in the chain".
739
+ *
740
+ * **How it works**:
741
+ * 1. Maps over all join types, checking each against target types
742
+ * 2. Creates a union of boolean results
743
+ * 3. Uses `true extends Union` pattern to check if any were true
744
+ *
745
+ * **Example**:
746
+ * ```typescript
747
+ * HasJoinType<{ orders: 'left', products: 'right' }, 'right' | 'full'>
748
+ * // → true (because products is a right join)
749
+ * ```
750
+ */
290
751
  export type HasJoinType<
291
752
  TJoinTypes extends Record<string, string>,
292
753
  TTargetTypes extends string,
@@ -296,20 +757,71 @@ export type HasJoinType<
296
757
  ? true
297
758
  : false
298
759
 
299
- // Helper type to merge contexts (for joins) - backward compatibility
300
- export type MergeContext<
760
+ /**
761
+ * MergeContextForJoinCallback - Special context for join condition callbacks
762
+ *
763
+ * This type creates a context specifically for the `onCallback` parameter of join operations.
764
+ * The key difference from `MergeContextWithJoinType` is that NO optionality is applied here.
765
+ *
766
+ * **Why No Optionality?**
767
+ * In SQL, join conditions are evaluated BEFORE optionality is determined. Both tables
768
+ * must be treated as available (non-optional) within the join condition itself.
769
+ * Optionality is only applied to the result AFTER the join logic executes.
770
+ *
771
+ * **Example**:
772
+ * ```typescript
773
+ * .from({ users })
774
+ * .leftJoin({ orders }, ({ users, orders }) => {
775
+ * // users is NOT optional here - we can access users.id directly
776
+ * // orders is NOT optional here - we can access orders.userId directly
777
+ * return eq(users.id, orders.userId)
778
+ * })
779
+ * .where(({ orders }) => {
780
+ * // NOW orders is optional because it's after the LEFT JOIN
781
+ * return orders?.status === 'pending'
782
+ * })
783
+ * ```
784
+ *
785
+ * The simple intersection (&) merges schemas without any optionality transformation.
786
+ */
787
+ export type MergeContextForJoinCallback<
301
788
  TContext extends Context,
302
789
  TNewSchema extends ContextSchema,
303
- > = MergeContextWithJoinType<TContext, TNewSchema, `left`>
790
+ > = {
791
+ baseSchema: TContext[`baseSchema`]
792
+ // Merge schemas without applying join optionality - both are non-optional in join condition
793
+ schema: TContext[`schema`] & TNewSchema
794
+ fromSourceName: TContext[`fromSourceName`]
795
+ hasJoins: true
796
+ joinTypes: TContext[`joinTypes`] extends Record<string, any>
797
+ ? TContext[`joinTypes`]
798
+ : {}
799
+ result: TContext[`result`]
800
+ }
304
801
 
305
- // Helper type for updating context with result type
802
+ /**
803
+ * WithResult - Updates a context with a new result type after select()
804
+ *
805
+ * This utility type is used internally when the `select()` method is called
806
+ * to update the context with the projected result type. It preserves all
807
+ * other context properties while replacing the `result` field.
808
+ *
809
+ * **Usage**:
810
+ * When `select()` is called, the query builder uses this type to create
811
+ * a new context where `result` contains the shape of the selected fields.
812
+ *
813
+ * The double `Prettify` ensures both the overall context and the nested
814
+ * result type display cleanly in IDEs.
815
+ */
306
816
  export type WithResult<TContext extends Context, TResult> = Prettify<
307
817
  Omit<TContext, `result`> & {
308
818
  result: Prettify<TResult>
309
819
  }
310
820
  >
311
821
 
312
- // Helper type to simplify complex types for better editor hints
822
+ /**
823
+ * Prettify - Utility type for clean IDE display
824
+ */
313
825
  export type Prettify<T> = {
314
826
  [K in keyof T]: T[K]
315
827
  } & {}