bun-query-builder 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/browser.d.ts CHANGED
@@ -201,9 +201,12 @@ declare type BrowserAttributeKeys<TDef extends BrowserModelDefinition> = keyof T
201
201
  declare type InferBrowserAttributeType<TAttr> = TAttr extends { type: infer T } ? InferType<T> :
202
202
  TAttr extends { factory: (faker: unknown) => infer R } ? R :
203
203
  unknown;
204
- // Build the full attributes type from definition
204
+ // Build the full attributes type from definition. Columns declared
205
+ // `nullable: true` admit null — mirrors InferAttributes in type-inference.ts.
205
206
  declare type InferBrowserModelAttributes<TDef extends BrowserModelDefinition> = {
206
- [K in BrowserAttributeKeys<TDef>]: InferBrowserAttributeType<TDef['attributes'][K]>
207
+ [K in BrowserAttributeKeys<TDef>]: TDef['attributes'][K] extends { nullable: true }
208
+ ? InferBrowserAttributeType<TDef['attributes'][K]> | null
209
+ : InferBrowserAttributeType<TDef['attributes'][K]>
207
210
  }
208
211
  // System fields added by traits
209
212
  declare type BrowserSystemFields<TDef extends BrowserModelDefinition> = { id: number } &
package/dist/client.d.ts CHANGED
@@ -118,13 +118,22 @@ export declare interface BaseSelectQueryBuilder<DB extends DatabaseSchema<any>,
118
118
  whereJsonContainsKey?: (path: string) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
119
119
  whereJsonDoesntContainKey?: (path: string) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
120
120
  whereJsonLength?: (path: string, opOrLen: WhereOperator | number, len?: number) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
121
- with?: (...relations: string[]) => SelectQueryBuilder<DB, TTable, TSelected, any>
122
- withPivot?: (relation: string, ...columns: string[]) => SelectQueryBuilder<DB, TTable, TSelected, any>
123
- wherePivot?: (relation: string, column: string, opOrValue: any, value?: any) => SelectQueryBuilder<DB, TTable, TSelected, any>
124
- wherePivotIn?: (relation: string, column: string, values: any[]) => SelectQueryBuilder<DB, TTable, TSelected, any>
125
- wherePivotNotIn?: (relation: string, column: string, values: any[]) => SelectQueryBuilder<DB, TTable, TSelected, any>
126
- wherePivotNull?: (relation: string, column: string) => SelectQueryBuilder<DB, TTable, TSelected, any>
127
- wherePivotNotNull?: (relation: string, column: string) => SelectQueryBuilder<DB, TTable, TSelected, any>
121
+ with?: (...relations: WithRelationArg<DB, TTable>[]) => SelectQueryBuilder<DB, TTable, TSelected, any>
122
+ whereHas?: (relation: TableRelationName<DB, TTable>, callback?: (qb: any) => any) => SelectQueryBuilder<DB, TTable, TSelected, any>
123
+ whereDoesntHave?: (relation: TableRelationName<DB, TTable>, callback?: (qb: any) => any) => SelectQueryBuilder<DB, TTable, TSelected, any>
124
+ has?: (relation: TableRelationName<DB, TTable>) => SelectQueryBuilder<DB, TTable, TSelected, any>
125
+ doesntHave?: (relation: TableRelationName<DB, TTable>) => SelectQueryBuilder<DB, TTable, TSelected, any>
126
+ withCount?: (...relations: TableRelationName<DB, TTable>[]) => SelectQueryBuilder<DB, TTable, TSelected, any>
127
+ withSum?: (relation: TableRelationName<DB, TTable>, column: string) => SelectQueryBuilder<DB, TTable, TSelected, any>
128
+ withAvg?: (relation: TableRelationName<DB, TTable>, column: string) => SelectQueryBuilder<DB, TTable, TSelected, any>
129
+ withMax?: (relation: TableRelationName<DB, TTable>, column: string) => SelectQueryBuilder<DB, TTable, TSelected, any>
130
+ withMin?: (relation: TableRelationName<DB, TTable>, column: string) => SelectQueryBuilder<DB, TTable, TSelected, any>
131
+ withPivot?: (relation: TableRelationName<DB, TTable>, ...columns: string[]) => SelectQueryBuilder<DB, TTable, TSelected, any>
132
+ wherePivot?: (relation: TableRelationName<DB, TTable>, column: string, opOrValue: any, value?: any) => SelectQueryBuilder<DB, TTable, TSelected, any>
133
+ wherePivotIn?: (relation: TableRelationName<DB, TTable>, column: string, values: any[]) => SelectQueryBuilder<DB, TTable, TSelected, any>
134
+ wherePivotNotIn?: (relation: TableRelationName<DB, TTable>, column: string, values: any[]) => SelectQueryBuilder<DB, TTable, TSelected, any>
135
+ wherePivotNull?: (relation: TableRelationName<DB, TTable>, column: string) => SelectQueryBuilder<DB, TTable, TSelected, any>
136
+ wherePivotNotNull?: (relation: TableRelationName<DB, TTable>, column: string) => SelectQueryBuilder<DB, TTable, TSelected, any>
128
137
  applyPivotColumns?: () => SelectQueryBuilder<DB, TTable, TSelected, any>
129
138
  lockForUpdate: () => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
130
139
  sharedLock: () => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
@@ -399,6 +408,34 @@ export type SelectedRow<DB extends DatabaseSchema<any>, _TTable extends keyof DB
399
408
  declare type JoinColumn<DB extends DatabaseSchema<any>, TTables extends string> = TTables extends any
400
409
  ? `${TTables}.${keyof DB[TTables]['columns'] & string}`
401
410
  : never;
411
+ /**
412
+ * # `TableRelationName<DB, TTable>`
413
+ *
414
+ * The relation names declared for a table, read from the type-level
415
+ * `relations` map that `DatabaseSchema` carries. Falls back to `string`
416
+ * for hand-written schema types that don't declare relation metadata
417
+ * (inferred `R` is `unknown` when the property is absent), so existing
418
+ * untyped schemas keep compiling. A table that declares ZERO relations
419
+ * yields `never` — every relation name is rejected.
420
+ */
421
+ export type TableRelationName<DB extends DatabaseSchema<any>, TTable extends keyof DB & string> = DB[TTable] extends { relations?: infer R }
422
+ ? unknown extends R ? string : keyof NonNullable<R> & string
423
+ : string;
424
+ /**
425
+ * # `WithRelationArg<DB, TTable>`
426
+ *
427
+ * Argument accepted by `.with()`: a declared relation name, a dotted nested
428
+ * path rooted at a declared relation (`'posts.comments'`), or a record
429
+ * mapping relation names to constraint callbacks. A table with zero declared
430
+ * relations accepts nothing (the bare `Partial<Record<never, ...>>` would be
431
+ * `{}`, which strings are assignable to — hence the explicit never guard).
432
+ */
433
+ export type WithRelationArg<DB extends DatabaseSchema<any>, TTable extends keyof DB & string> = [TableRelationName<DB, TTable>] extends [never]
434
+ ? never
435
+ :
436
+ | TableRelationName<DB, TTable>
437
+ | `${TableRelationName<DB, TTable>}.${string}`
438
+ | Partial<Record<TableRelationName<DB, TTable>, (qb: any) => any>>;
402
439
  // Convert snake_case to PascalCase at the type level (e.g. created_at -> CreatedAt)
403
440
  declare type SnakeToPascal<S extends string> = S extends `${infer H}_${infer T}`
404
441
  ? `${Capitalize<H>}${SnakeToPascal<T>}`
package/dist/orm.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Database, type SQLQueryBindings } from 'bun:sqlite';
2
2
  import type { Faker } from '@stacksjs/ts-faker';
3
+ import type { RelationCardinality } from './type-inference';
3
4
  import type { SupportedDialect } from './types';
4
5
  export type {
5
6
  ModelInstance,
@@ -195,12 +196,17 @@ declare type AttributeKeys<TDef extends ModelDefinition> = keyof TDef['attribute
195
196
  declare type InferAttributeType<TAttr> = TAttr extends { type: infer T } ? InferType<T> :
196
197
  TAttr extends { factory: (faker: Faker) => infer R } ? R :
197
198
  unknown;
198
- // Build the full attributes type from definition
199
+ // Build the full attributes type from definition. Columns declared
200
+ // `nullable: true` admit null — mirrors InferAttributes in type-inference.ts.
199
201
  declare type InferModelAttributes<TDef extends ModelDefinition> = {
200
- [K in AttributeKeys<TDef>]: InferAttributeType<TDef['attributes'][K]>
202
+ [K in AttributeKeys<TDef>]: TDef['attributes'][K] extends { nullable: true }
203
+ ? InferAttributeType<TDef['attributes'][K]> | null
204
+ : InferAttributeType<TDef['attributes'][K]>
201
205
  }
202
- // System fields added by traits
203
- declare type SystemFields<TDef extends ModelDefinition> = { id: number } &
206
+ // System fields added by traits. The primary-key column honors the model's
207
+ // declared `primaryKey` (default 'id') a custom-pk model exposes THAT
208
+ // column, not a phantom 'id'. Mirrors InferAttributes in type-inference.ts.
209
+ declare type SystemFields<TDef extends ModelDefinition> = { [K in TDef extends { primaryKey: infer PK extends string } ? PK : 'id']: number } &
204
210
  (TDef['traits'] extends { useUuid: true } ? { uuid: string } : {}) &
205
211
  (TDef['traits'] extends { useTimestamps: true } ? { created_at: string; updated_at: string | null } : {}) &
206
212
  (TDef['traits'] extends { timestampable: true | object } ? { created_at: string; updated_at: string | null } : {}) &
@@ -212,7 +218,7 @@ declare type SystemFields<TDef extends ModelDefinition> = { id: number } &
212
218
  declare type ModelAttributes<TDef extends ModelDefinition> = InferModelAttributes<TDef> & SystemFields<TDef>;
213
219
  // All valid column names
214
220
  declare type ColumnName<TDef extends ModelDefinition> = | AttributeKeys<TDef>
215
- | 'id'
221
+ | (TDef extends { primaryKey: infer PK extends string } ? PK : 'id')
216
222
  | (TDef['traits'] extends { useUuid: true } ? 'uuid' : never)
217
223
  | (TDef['traits'] extends { useTimestamps: true } ? 'created_at' | 'updated_at' : never)
218
224
  | (TDef['traits'] extends { timestampable: true | object } ? 'created_at' | 'updated_at' : never)
@@ -232,37 +238,44 @@ declare type FillableKeys<TDef extends ModelDefinition> = {
232
238
  declare type NumericColumns<TDef extends ModelDefinition> = {
233
239
  [K in AttributeKeys<TDef>]: TDef['attributes'][K] extends { type: 'number' } ? K : never
234
240
  }[AttributeKeys<TDef>];
235
- // Infer relation names from model definition (supports both array and object syntax)
236
- declare type InferBelongsToNames<TDef> = (TDef extends { belongsTo: readonly (infer R)[] }
237
- ? R extends string ? Lowercase<R> : never : never)
238
- | (TDef extends { belongsTo: Readonly<Record<infer K, unknown>> }
239
- ? K extends string ? K : never : never);
240
- declare type InferHasManyNames<TDef> = (TDef extends { hasMany: readonly (infer R)[] }
241
- ? R extends string ? Lowercase<R> : never : never)
242
- | (TDef extends { hasMany: Readonly<Record<infer K, unknown>> }
243
- ? K extends string ? K : never : never);
244
- declare type InferHasOneNames<TDef> = (TDef extends { hasOne: readonly (infer R)[] }
245
- ? R extends string ? Lowercase<R> : never : never)
246
- | (TDef extends { hasOne: Readonly<Record<infer K, unknown>> }
247
- ? K extends string ? K : never : never);
248
- declare type InferBelongsToManyNames<TDef> = (TDef extends { belongsToMany: readonly (infer R)[] }
249
- ? R extends string ? Lowercase<R> : R extends { model: infer M extends string } ? Lowercase<M> : never : never)
250
- | (TDef extends { belongsToMany: Readonly<Record<infer K, unknown>> }
251
- ? K extends string ? K : never : never);
252
- declare type InferHasOneThroughNames<TDef> = (TDef extends { hasOneThrough: readonly (infer R)[] }
253
- ? R extends string ? Lowercase<R> : R extends { model: infer M extends string } ? Lowercase<M> : never : never)
254
- | (TDef extends { hasOneThrough: Readonly<Record<infer K, unknown>> }
255
- ? K extends string ? K : never : never);
256
- declare type InferHasManyThroughNames<TDef> = (TDef extends { hasManyThrough: readonly (infer R)[] }
257
- ? R extends string ? Lowercase<R> : R extends { model: infer M extends string } ? Lowercase<M> : never : never)
258
- | (TDef extends { hasManyThrough: Readonly<Record<infer K, unknown>> }
259
- ? K extends string ? K : never : never);
241
+ /**
242
+ * Relation names of one relation declaration. Array form lowercases the
243
+ * (unwrapped) model name; record form uses the keys. The array case MUST be
244
+ * checked first: a tuple also structurally matches `Readonly<Record<...>>`
245
+ * and would otherwise leak its own keys ('length', indices, ...) into the
246
+ * relation-name union.
247
+ */
248
+ declare type RelationKeyOf<V> = V extends readonly (infer E)[]
249
+ ? E extends string ? Lowercase<E>
250
+ : E extends { model: infer M extends string } ? Lowercase<M>
251
+ : never
252
+ : V extends Readonly<Record<infer K, unknown>>
253
+ ? K & string
254
+ : never;
255
+ declare type InferBelongsToNames<TDef> = TDef extends { belongsTo: infer V } ? RelationKeyOf<V> : never;
256
+ declare type InferHasManyNames<TDef> = TDef extends { hasMany: infer V } ? RelationKeyOf<V> : never;
257
+ declare type InferHasOneNames<TDef> = TDef extends { hasOne: infer V } ? RelationKeyOf<V> : never;
258
+ declare type InferBelongsToManyNames<TDef> = TDef extends { belongsToMany: infer V } ? RelationKeyOf<V> : never;
259
+ declare type InferHasOneThroughNames<TDef> = TDef extends { hasOneThrough: infer V } ? RelationKeyOf<V> : never;
260
+ declare type InferHasManyThroughNames<TDef> = TDef extends { hasManyThrough: infer V } ? RelationKeyOf<V> : never;
260
261
  export type InferRelationNames<TDef> = | InferBelongsToNames<TDef>
261
262
  | InferHasManyNames<TDef>
262
263
  | InferHasOneNames<TDef>
263
264
  | InferBelongsToManyNames<TDef>
264
265
  | InferHasOneThroughNames<TDef>
265
266
  | InferHasManyThroughNames<TDef>;
267
+ /**
268
+ * Cardinality-aware value of a loaded relation as returned by
269
+ * `ModelInstance.getRelation()`. Relations declared as to-many (hasMany,
270
+ * belongsToMany, hasManyThrough) yield arrays; to-one relations (hasOne,
271
+ * belongsTo, hasOneThrough) yield a single instance or null. `undefined`
272
+ * means the relation was not eager-loaded.
273
+ */
274
+ declare type LoadedRelationValue<TDef, R extends string> = 'one' extends RelationCardinality<TDef, R>
275
+ ? ModelInstance<any, any> | null | undefined
276
+ : 'many' extends RelationCardinality<TDef, R>
277
+ ? ModelInstance<any, any>[] | undefined
278
+ : ModelInstance<any, any>[] | ModelInstance<any, any> | null | undefined;
266
279
  declare type WhereOperator = '=' | '!=' | '<' | '>' | '<=' | '>=' | 'like' | 'not like' | 'in' | 'not in';
267
280
  // --- Dialect-aware execution layer -------------------------------------------
268
281
  //
@@ -306,7 +319,7 @@ declare class ModelInstance<TDef extends ModelDefinition, TSelected extends Colu
306
319
  set<K extends ColumnName<TDef>>(key: K, value: K extends keyof ModelAttributes<TDef> ? ModelAttributes<TDef>[K] : unknown): void;
307
320
  only<K extends TSelected>(keys: ReadonlyArray<K>): Partial<ModelAttributes<TDef>>;
308
321
  except<K extends TSelected>(keys: ReadonlyArray<K>): Partial<ModelAttributes<TDef>>;
309
- getRelation(name: string): ModelInstance<any, any>[] | ModelInstance<any, any> | null | undefined;
322
+ getRelation<R extends InferRelationNames<TDef> & string>(name: R): LoadedRelationValue<TDef, R>;
310
323
  setRelation(name: string, data: ModelInstance<any, any>[] | ModelInstance<any, any> | null): void;
311
324
  getLoadedRelations(): Record<string, ModelInstance<any, any>[] | ModelInstance<any, any> | null>;
312
325
  get attributes(): Pick<ModelAttributes<TDef>, TSelected & keyof ModelAttributes<TDef>>;
package/dist/schema.d.ts CHANGED
@@ -258,6 +258,61 @@ export type InferTableName<M extends ModelDefinition> = M extends {
258
258
  : M extends { name: infer N extends string }
259
259
  ? `${Lowercase<N>}s`
260
260
  : string;
261
+ /**
262
+ * Resolve a models-record entry to the raw definition. `defineModel()` (and
263
+ * `createModel`/`createBrowserModel`) wrap definitions in an object exposing
264
+ * `getDefinition()` / `definition`; `buildDatabaseSchema` unwraps these at
265
+ * runtime, so the type level must unwrap them too or the schema degrades to
266
+ * an untyped index signature.
267
+ */
268
+ declare type UnwrapModelDefinition<M> = M extends { getDefinition: () => infer D } ? D :
269
+ M extends { definition: infer D } ? D :
270
+ M;
271
+ /**
272
+ * Unwrap a relation entry to the related model's name. Entries may be a plain
273
+ * model-name string, a `{ model: 'X' }` config (belongsToMany Option A/B), or
274
+ * a `{ through, target }` through-relation descriptor.
275
+ */
276
+ declare type RelationEntryModelName<E> = E extends string ? E :
277
+ E extends { model: infer M extends string } ? M :
278
+ E extends { target: infer T extends string } ? T :
279
+ never;
280
+ /**
281
+ * Normalize one relation declaration (array or record form) into a
282
+ * `relationName -> relatedModelName` record, mirroring `buildSchemaMeta`:
283
+ * array entries use the (unwrapped) model name as the relation name; record
284
+ * entries use the key.
285
+ */
286
+ declare type RelationRecordOf<V> = [V] extends [never]
287
+ ? {} // absent relation kind — must yield {} (not never) so intersections survive
288
+ : V extends readonly (infer E)[]
289
+ ? { [K in RelationEntryModelName<E> & string]: K }
290
+ : V extends Readonly<Record<string, unknown>>
291
+ ? { [K in keyof V & string]: RelationEntryModelName<V[K]> }
292
+ : {}
293
+ /** All relations of a model as a `relationName -> relatedModelName` record. */
294
+ declare type ModelRelationsRecord<M> = RelationRecordOf<M extends { hasOne: infer V } ? V : never>
295
+ & RelationRecordOf<M extends { hasMany: infer V } ? V : never>
296
+ & RelationRecordOf<M extends { belongsTo: infer V } ? V : never>
297
+ & RelationRecordOf<M extends { belongsToMany: infer V } ? V : never>
298
+ & RelationRecordOf<M extends { hasOneThrough: infer V } ? V : never>
299
+ & RelationRecordOf<M extends { hasManyThrough: infer V } ? V : never>
300
+ & RelationRecordOf<M extends { morphOne: infer V } ? V : never>
301
+ & RelationRecordOf<M extends { morphMany: infer V } ? V : never>
302
+ & RelationRecordOf<M extends { morphToMany: infer V } ? V : never>
303
+ & RelationRecordOf<M extends { morphedByMany: infer V } ? V : never>;
304
+ /** Resolve a related model name to its table name within the models record. */
305
+ declare type RelatedTableName<MRecord extends ModelRecord, ModelName> = ModelName extends keyof MRecord ? InferTableName<UnwrapModelDefinition<MRecord[ModelName]>> : string;
306
+ /**
307
+ * # `InferTableRelations<M, MRecord>`
308
+ *
309
+ * `relationName -> relatedTableName` record for one model, resolved against
310
+ * the full models record. Powers the type-level narrowing of `.with()`,
311
+ * `.whereHas()`, `.withCount()`, etc. on the query builder.
312
+ */
313
+ export type InferTableRelations<M, MRecord extends ModelRecord> = {
314
+ [K in keyof ModelRelationsRecord<UnwrapModelDefinition<M>> & string]: RelatedTableName<MRecord, ModelRelationsRecord<UnwrapModelDefinition<M>>[K]>
315
+ }
261
316
  /**
262
317
  * # `DatabaseSchema<Models>`
263
318
  *
@@ -265,6 +320,11 @@ export type InferTableName<M extends ModelDefinition> = M extends {
265
320
  * table columns and primary key. This is the primary input for the query
266
321
  * builder's type-safety.
267
322
  *
323
+ * The `relations` field is type-level only (phantom): `buildDatabaseSchema`
324
+ * never materializes it at runtime. It maps relation names to related table
325
+ * names so builder methods like `.with()` can narrow their accepted relation
326
+ * names per table.
327
+ *
268
328
  * @example
269
329
  * ```ts
270
330
  * const models = defineModels({ User, Post })
@@ -272,8 +332,10 @@ export type InferTableName<M extends ModelDefinition> = M extends {
272
332
  * ```
273
333
  */
274
334
  export type DatabaseSchema<MRecord extends ModelRecord> = {
275
- [MName in keyof MRecord & string as InferTableName<MRecord[MName]>]: {
276
- columns: InferAttributes<MRecord[MName]>
277
- primaryKey: InferPrimaryKey<MRecord[MName]>
335
+ [MName in keyof MRecord & string as InferTableName<UnwrapModelDefinition<MRecord[MName]>>]: {
336
+ columns: InferAttributes<UnwrapModelDefinition<MRecord[MName]>>
337
+ primaryKey: InferPrimaryKey<UnwrapModelDefinition<MRecord[MName]>>
338
+ /** Phantom, type-level only: relation name -> related table name. */
339
+ relations?: InferTableRelations<MRecord[MName], MRecord>
278
340
  };
279
341
  }