bun-query-builder 0.1.26 → 0.1.27

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/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,27 @@ 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, so
417
+ * existing untyped schemas keep compiling.
418
+ */
419
+ export type TableRelationName<DB extends DatabaseSchema<any>, TTable extends keyof DB & string> = DB[TTable] extends { relations?: infer R }
420
+ ? [keyof NonNullable<R>] extends [never] ? string : keyof NonNullable<R> & string
421
+ : string;
422
+ /**
423
+ * # `WithRelationArg<DB, TTable>`
424
+ *
425
+ * Argument accepted by `.with()`: a declared relation name, a dotted nested
426
+ * path rooted at a declared relation (`'posts.comments'`), or a record
427
+ * mapping relation names to constraint callbacks.
428
+ */
429
+ export type WithRelationArg<DB extends DatabaseSchema<any>, TTable extends keyof DB & string> = | TableRelationName<DB, TTable>
430
+ | `${TableRelationName<DB, TTable>}.${string}`
431
+ | Partial<Record<TableRelationName<DB, TTable>, (qb: any) => any>>;
402
432
  // Convert snake_case to PascalCase at the type level (e.g. created_at -> CreatedAt)
403
433
  declare type SnakeToPascal<S extends string> = S extends `${infer H}_${infer T}`
404
434
  ? `${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,
@@ -263,6 +264,18 @@ export type InferRelationNames<TDef> = | InferBelongsToNames<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
  }