bun-query-builder 0.1.25 → 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/actions/index.d.ts +1 -0
- package/dist/actions/introspect-db.d.ts +32 -0
- package/dist/actions/migrate-rollback.d.ts +14 -0
- package/dist/bin/cli.js +862 -216
- package/dist/client.d.ts +40 -10
- package/dist/config.d.ts +1 -15
- package/dist/db.d.ts +54 -0
- package/dist/orm.d.ts +30 -1
- package/dist/relation-utils.d.ts +27 -0
- package/dist/schema.d.ts +65 -3
- package/dist/src/index.js +858 -215
- package/dist/type-inference.d.ts +40 -0
- package/dist/types.d.ts +21 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -103,8 +103,8 @@ export declare interface BaseSelectQueryBuilder<DB extends DatabaseSchema<any>,
|
|
|
103
103
|
groupByRaw: (fragment: SqlFragment) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
104
104
|
having: (expr: WhereExpression<any>) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
105
105
|
havingRaw: (fragment: SqlFragment) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
106
|
-
addSelect: (...columns: (keyof DB[TTable]['columns'] & string | string)[]) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
107
|
-
select?: (columns: (keyof DB[TTable]['columns'] & string | string)[]) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
106
|
+
addSelect: (...columns: ((keyof DB[TTable]['columns'] & string) | string | SqlFragment)[]) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
107
|
+
select?: (columns: string | SqlFragment | ((keyof DB[TTable]['columns'] & string) | string | SqlFragment)[]) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
108
108
|
selectAll?: () => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
109
109
|
orderByRaw: (fragment: SqlFragment) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
110
110
|
union: (other: { toSQL: () => any }) => SelectQueryBuilder<DB, TTable, TSelected, TJoined>
|
|
@@ -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:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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>
|
|
@@ -148,7 +157,7 @@ export declare interface BaseSelectQueryBuilder<DB extends DatabaseSchema<any>,
|
|
|
148
157
|
explain: () => Promise<any[]>
|
|
149
158
|
simple: () => any
|
|
150
159
|
toText?: () => string
|
|
151
|
-
paginate: (perPage: number, page?: number) => Promise<{ data: SelectedRow<DB, TTable, TSelected>[], meta: { perPage: number, page: number, total: number, lastPage: number } }>
|
|
160
|
+
paginate: (perPage: number, page?: number, opts?: { tx?: { unsafe: (sql: string, params?: any[]) => any } }) => Promise<{ data: SelectedRow<DB, TTable, TSelected>[], meta: { perPage: number, page: number, total: number, lastPage: number } }>
|
|
152
161
|
simplePaginate: (perPage: number, page?: number) => Promise<{ data: SelectedRow<DB, TTable, TSelected>[], meta: { perPage: number, page: number, hasMore: boolean } }>
|
|
153
162
|
toSQL: () => string
|
|
154
163
|
execute: () => Promise<SelectedRow<DB, TTable, TSelected>[]>
|
|
@@ -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/config.d.ts
CHANGED
|
@@ -13,18 +13,4 @@ export declare function getPlaceholders(count: number, startIndex?: number): str
|
|
|
13
13
|
export declare function getConfig(): Promise<QueryBuilderConfig>;
|
|
14
14
|
export declare function setConfig(userConfig: Partial<QueryBuilderConfig>): void;
|
|
15
15
|
export declare const defaultConfig: QueryBuilderConfig;
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
// Why `let` + an explicit `Object.assign(config, defaultConfig)` instead of
|
|
19
|
-
// the obvious `export const config = defaultConfig`: when downstream code
|
|
20
|
-
// (e.g. `bunfig`, our config loader) introduces a top-level `await`, Bun's
|
|
21
|
-
// bundler may wrap this module's initializer in `__esm(async () => {...})`.
|
|
22
|
-
// Static `const x = y` exports inside such a wrapper are reassigned only
|
|
23
|
-
// once the async init runs, so callers that reach `setConfig(...)` before
|
|
24
|
-
// that init (e.g. another module's top-level `setConfig` call mid-graph)
|
|
25
|
-
// see `config` as `undefined` and `Object.assign(undefined, ...)` throws.
|
|
26
|
-
// Initializing as `let config = {...defaultConfig}` outside the wrapper, and
|
|
27
|
-
// having `setConfig` re-hydrate from defaults if it's somehow still empty,
|
|
28
|
-
// keeps the surface synchronous for every consumer regardless of how the
|
|
29
|
-
// module ends up bundled.
|
|
30
|
-
export declare let config: QueryBuilderConfig;
|
|
16
|
+
export declare const config: QueryBuilderConfig;
|
package/dist/db.d.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { Database } from 'bun:sqlite';
|
|
2
2
|
import { SQL } from 'bun';
|
|
3
|
+
import type { PoolConfig } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Map the qb-level `pool` config (ms-based, ergonomic) onto the Bun SQL
|
|
6
|
+
* driver's native option names (second resolution). Only the knobs Bun's
|
|
7
|
+
* `SQL` actually honors are emitted — `min`/`autoReconnect` are accepted on
|
|
8
|
+
* `PoolConfig` for forward-compatibility but the driver manages them itself,
|
|
9
|
+
* so they are intentionally not passed through. See
|
|
10
|
+
* stacksjs/bun-query-builder#1014.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolvePoolOptions(pool?: PoolConfig): {
|
|
13
|
+
max?: number
|
|
14
|
+
idleTimeout?: number
|
|
15
|
+
connectionTimeout?: number
|
|
16
|
+
maxLifetime?: number
|
|
17
|
+
};
|
|
3
18
|
/**
|
|
4
19
|
* Returns a Bun SQL instance configured for the current dialect and database settings.
|
|
5
20
|
* For SQLite, uses bun:sqlite directly for better compiled binary support.
|
|
@@ -16,6 +31,37 @@ export declare function resetConnection(): void;
|
|
|
16
31
|
export declare function withFreshConnection<T>(fn: (sql: SQL) => Promise<T>): Promise<T>;
|
|
17
32
|
// Export a lazy proxy - no connection is made until first use
|
|
18
33
|
export declare const bunSql: SQL;
|
|
34
|
+
/**
|
|
35
|
+
* The query object returned by `connection.unsafe(...)` / a tagged template.
|
|
36
|
+
* Both Bun's native `SQL` query and our `createSQLiteSQL` wrapper satisfy this.
|
|
37
|
+
* See stacksjs/bun-query-builder#1044.
|
|
38
|
+
*/
|
|
39
|
+
export declare interface DriverQuery {
|
|
40
|
+
execute: () => Promise<any>
|
|
41
|
+
values?: () => any
|
|
42
|
+
raw?: () => any
|
|
43
|
+
toString: () => string
|
|
44
|
+
cancel?: () => void
|
|
45
|
+
readonly sql?: string
|
|
46
|
+
[key: string]: any
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The shared connection surface used across the dispatch path — both the
|
|
50
|
+
* `bun:sqlite` wrapper and Bun's native `SQL` satisfy it. Typing `_sql` against
|
|
51
|
+
* this (instead of `any`) is what lets the ~hundreds of `(_sql as any).unsafe`
|
|
52
|
+
* casts be dropped. See stacksjs/bun-query-builder#1044.
|
|
53
|
+
*/
|
|
54
|
+
export declare interface DriverConnection {
|
|
55
|
+
(strings: TemplateStringsArray, ...values: any[]): DriverQuery
|
|
56
|
+
(value: any): any
|
|
57
|
+
unsafe: (sql: string, values?: any[]) => AwaitableDriverQuery
|
|
58
|
+
query?: (sql: string, params?: any[]) => any
|
|
59
|
+
close?: () => Promise<void> | void
|
|
60
|
+
_prepareStatement?: (sql: string) => any
|
|
61
|
+
[key: string]: any
|
|
62
|
+
}
|
|
63
|
+
/** An `unsafe(...)` result: a query object that is ALSO directly awaitable. */
|
|
64
|
+
export type AwaitableDriverQuery = DriverQuery & PromiseLike<any>;
|
|
19
65
|
/**
|
|
20
66
|
* SQLite wrapper that provides a SQL-like tagged template literal interface
|
|
21
67
|
* using bun:sqlite's Database class for better compiled binary support.
|
|
@@ -27,5 +73,13 @@ declare class SQLiteWrapper {
|
|
|
27
73
|
close(): void;
|
|
28
74
|
get database(): Database;
|
|
29
75
|
}
|
|
76
|
+
// NOTE: this module no longer installs a process-wide `unhandledRejection`
|
|
77
|
+
// handler. A library has no business doing so: ANY such listener suppresses the
|
|
78
|
+
// runtime's default crash for EVERY unhandled rejection in the consumer's
|
|
79
|
+
// process (the old handler's body matched only DB errors but silently swallowed
|
|
80
|
+
// the rest), masking genuine production bugs. Expected "database does not exist"
|
|
81
|
+
// errors during tests are surfaced/awaited at query time now (#1022); a test
|
|
82
|
+
// harness that needs to tolerate a missing DB should install its own handler.
|
|
83
|
+
// See stacksjs/bun-query-builder#1040.
|
|
30
84
|
// Also export the SQL class for advanced usage
|
|
31
85
|
export { SQL } from 'bun';
|
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,
|
|
@@ -13,6 +14,18 @@ export type {
|
|
|
13
14
|
FillableKeys,
|
|
14
15
|
HiddenKeys,
|
|
15
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Extract an affected-row count from a Bun SQL driver result.
|
|
19
|
+
*
|
|
20
|
+
* Verified against live Postgres (Bun 1.3): a non-RETURNING `UPDATE`/`DELETE`
|
|
21
|
+
* returns an empty array carrying `{ count: <affected>, affectedRows: null,
|
|
22
|
+
* command: 'UPDATE' }`. So `count` must be checked BEFORE the `Array.length`
|
|
23
|
+
* fallback (which would be 0) — see stacksjs/bun-query-builder#1032. MySQL
|
|
24
|
+
* surfaces `affectedRows` instead.
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractChanges(res: any): number;
|
|
27
|
+
/** Extract a generated primary key from a Bun SQL driver result. */
|
|
28
|
+
export declare function extractInsertId(res: any): number | bigint | null;
|
|
16
29
|
export declare function configureOrm(options: { database?: string | Database; verbose?: boolean }): void;
|
|
17
30
|
/**
|
|
18
31
|
* Return the underlying `bun:sqlite` Database backing the model layer.
|
|
@@ -251,6 +264,18 @@ export type InferRelationNames<TDef> = | InferBelongsToNames<TDef>
|
|
|
251
264
|
| InferBelongsToManyNames<TDef>
|
|
252
265
|
| InferHasOneThroughNames<TDef>
|
|
253
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;
|
|
254
279
|
declare type WhereOperator = '=' | '!=' | '<' | '>' | '<=' | '>=' | 'like' | 'not like' | 'in' | 'not in';
|
|
255
280
|
// --- Dialect-aware execution layer -------------------------------------------
|
|
256
281
|
//
|
|
@@ -294,7 +319,7 @@ declare class ModelInstance<TDef extends ModelDefinition, TSelected extends Colu
|
|
|
294
319
|
set<K extends ColumnName<TDef>>(key: K, value: K extends keyof ModelAttributes<TDef> ? ModelAttributes<TDef>[K] : unknown): void;
|
|
295
320
|
only<K extends TSelected>(keys: ReadonlyArray<K>): Partial<ModelAttributes<TDef>>;
|
|
296
321
|
except<K extends TSelected>(keys: ReadonlyArray<K>): Partial<ModelAttributes<TDef>>;
|
|
297
|
-
getRelation(name:
|
|
322
|
+
getRelation<R extends InferRelationNames<TDef> & string>(name: R): LoadedRelationValue<TDef, R>;
|
|
298
323
|
setRelation(name: string, data: ModelInstance<any, any>[] | ModelInstance<any, any> | null): void;
|
|
299
324
|
getLoadedRelations(): Record<string, ModelInstance<any, any>[] | ModelInstance<any, any> | null>;
|
|
300
325
|
get attributes(): Pick<ModelAttributes<TDef>, TSelected & keyof ModelAttributes<TDef>>;
|
|
@@ -309,6 +334,8 @@ declare class ModelInstance<TDef extends ModelDefinition, TSelected extends Colu
|
|
|
309
334
|
update(data: Partial<Pick<InferModelAttributes<TDef>, FillableKeys<TDef>>>): Promise<this>;
|
|
310
335
|
fresh(): Promise<ModelInstance<TDef, TSelected> | null>;
|
|
311
336
|
delete(): Promise<boolean>;
|
|
337
|
+
restore(): Promise<this>;
|
|
338
|
+
trashed(): boolean;
|
|
312
339
|
refresh(): Promise<this | null>;
|
|
313
340
|
replicate(): ModelInstance<TDef, TSelected>;
|
|
314
341
|
toArray(): Record<string, unknown>;
|
|
@@ -352,6 +379,8 @@ export declare class BelongsToManyRelationBuilder<TRel extends ModelDefinition>
|
|
|
352
379
|
*/
|
|
353
380
|
declare class ModelQueryBuilder<TDef extends ModelDefinition, TSelected extends ColumnName<TDef> = ColumnName<TDef>> {
|
|
354
381
|
constructor(definition: TDef);
|
|
382
|
+
withTrashed(): ModelQueryBuilder<TDef, TSelected>;
|
|
383
|
+
onlyTrashed(): ModelQueryBuilder<TDef, TSelected>;
|
|
355
384
|
where<K extends ColumnName<TDef>>(column: K, value: K extends keyof ModelAttributes<TDef> ? ModelAttributes<TDef>[K] : unknown): ModelQueryBuilder<TDef, TSelected>;
|
|
356
385
|
where<K extends ColumnName<TDef>>(column: K, operator: WhereOperator, value: K extends keyof ModelAttributes<TDef> ? ModelAttributes<TDef>[K] : unknown): ModelQueryBuilder<TDef, TSelected>;
|
|
357
386
|
where<K extends ColumnName<TDef>>(column: K, operatorOrValue: WhereOperator | unknown, value?: unknown): ModelQueryBuilder<TDef, TSelected>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { OnForeignKeyAction } from './schema';
|
|
2
|
+
/**
|
|
3
|
+
* Unwrap a single relation entry to a descriptor, or null if it isn't a valid
|
|
4
|
+
* relation entry. Accepts:
|
|
5
|
+
* - `'Model'`
|
|
6
|
+
* - `{ model: 'Model', foreignKey?, onDelete? }`
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizeRelationEntry(entry: unknown): NormalizedRelation | null;
|
|
9
|
+
/**
|
|
10
|
+
* Flatten a relation declaration into descriptors. Accepts every supported
|
|
11
|
+
* shape: string array, object-in-array, record (name->Model) and record
|
|
12
|
+
* (name->config). Invalid entries are dropped.
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizeRelationList(rel: unknown): NormalizedRelation[];
|
|
15
|
+
/**
|
|
16
|
+
* A relation entry reduced to the fields the schema/migration layers need.
|
|
17
|
+
*
|
|
18
|
+
* Single source of truth for unwrapping the supported relation-declaration
|
|
19
|
+
* shapes. Previously `meta.ts` (toRecord) and `migrations.ts`
|
|
20
|
+
* (normalizeBelongsTo) each re-implemented this — the exact shape that caused
|
|
21
|
+
* stacksjs/bun-query-builder#1023, and the fix had to be applied twice. See #1042.
|
|
22
|
+
*/
|
|
23
|
+
export declare interface NormalizedRelation {
|
|
24
|
+
model: string
|
|
25
|
+
foreignKey?: string
|
|
26
|
+
onDelete?: OnForeignKeyAction
|
|
27
|
+
}
|
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
|
}
|