kysely-rizzolver 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/dist/cjs/fetch-result-factory.d.ts +52 -0
- package/dist/cjs/fetch-result-factory.js +88 -0
- package/dist/cjs/fetch-result.d.ts +111 -0
- package/dist/cjs/fetch-result.js +117 -0
- package/dist/cjs/index.d.ts +10 -0
- package/dist/cjs/index.js +21 -0
- package/dist/cjs/kysely-rizzolver.d.ts +138 -0
- package/dist/cjs/kysely-rizzolver.js +147 -0
- package/dist/cjs/model-collection.d.ts +26 -0
- package/dist/cjs/model-collection.js +24 -0
- package/dist/cjs/query-builder.d.ts +156 -0
- package/dist/cjs/query-builder.js +92 -0
- package/dist/cjs/selector.d.ts +72 -0
- package/dist/cjs/selector.js +45 -0
- package/dist/cjs/type-helpers.d.ts +11 -0
- package/dist/cjs/type-helpers.js +2 -0
- package/dist/esm/fetch-result-factory.js +85 -0
- package/dist/esm/fetch-result.js +105 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/kysely-rizzolver.js +143 -0
- package/dist/esm/model-collection.js +21 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/query-builder.js +89 -0
- package/dist/esm/selector.js +42 -0
- package/dist/esm/type-helpers.js +1 -0
- package/dist/types/fetch-result-factory.d.ts +52 -0
- package/dist/types/fetch-result.d.ts +111 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/kysely-rizzolver.d.ts +138 -0
- package/dist/types/model-collection.d.ts +26 -0
- package/dist/types/query-builder.d.ts +156 -0
- package/dist/types/selector.d.ts +72 -0
- package/dist/types/type-helpers.d.ts +11 -0
- package/package.json +32 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
import type { Selectable } from 'kysely';
|
2
|
+
import type { ModelCollection } from './model-collection.js';
|
3
|
+
/**
|
4
|
+
* A {@link FetchResult} is a result of a fetch operation. It can be one of
|
5
|
+
* three types:
|
6
|
+
* - {@link FetchOneResult} - A result of a fetch operation that is expected to
|
7
|
+
* return up to one row,
|
8
|
+
* - {@link FetchOneXResult} - A result of a fetch operation that is expected to
|
9
|
+
* return exactly one row,
|
10
|
+
* - {@link FetchSomeResult} - A result of a fetch operation that is expected to
|
11
|
+
* return any number of rows.
|
12
|
+
*/
|
13
|
+
export type FetchResult<DB, T extends keyof DB & string, R extends Partial<Selectable<DB[T]>>> = FetchOneResult<DB, T, R> | FetchOneXResult<DB, T, R> | FetchSomeResult<DB, T, R>;
|
14
|
+
/**
|
15
|
+
* A {@link FetchOneResult} is a result of a fetch operation that is expected to
|
16
|
+
* return up to one row.
|
17
|
+
*/
|
18
|
+
export type FetchOneResult<DB, T extends keyof DB & string, R extends Partial<Selectable<DB[T]>> | undefined> = {
|
19
|
+
fetchType: 'fetchOne';
|
20
|
+
table: T;
|
21
|
+
result: R | undefined;
|
22
|
+
models?: ModelCollection<DB>;
|
23
|
+
/**
|
24
|
+
* Returns this result as a {@link FetchOneXResult}.
|
25
|
+
*
|
26
|
+
* @throws If the result is null or undefined.
|
27
|
+
*/
|
28
|
+
asFetchOneX(): FetchOneXResult<DB, T, R & {}>;
|
29
|
+
};
|
30
|
+
/**
|
31
|
+
* A {@link FetchOneXResult} is a result of a fetch operation that is expected
|
32
|
+
* to return exactly one row.
|
33
|
+
*/
|
34
|
+
export type FetchOneXResult<DB, T extends keyof DB & string, R extends Partial<Selectable<DB[T]>>> = {
|
35
|
+
fetchType: 'fetchOne';
|
36
|
+
table: T;
|
37
|
+
result: R;
|
38
|
+
models?: ModelCollection<DB>;
|
39
|
+
/**
|
40
|
+
* Returns self. This is a no-op, but it's here to make it possible to
|
41
|
+
* cast this object back to a {@link FetchOneXResult}.
|
42
|
+
*/
|
43
|
+
asFetchOneX(): FetchOneXResult<DB, T, R>;
|
44
|
+
};
|
45
|
+
/**
|
46
|
+
* A {@link FetchSomeResult} is a result of a fetch operation that is expected
|
47
|
+
* to return any number of rows.
|
48
|
+
*/
|
49
|
+
export type FetchSomeResult<DB, T extends keyof DB & string, R extends Partial<Selectable<DB[T]>>> = {
|
50
|
+
fetchType: 'fetchSome';
|
51
|
+
table: T;
|
52
|
+
result: R[];
|
53
|
+
models?: ModelCollection<DB>;
|
54
|
+
};
|
55
|
+
/**
|
56
|
+
* Used to type juggle between {@link FetchResult} and its subtypes.
|
57
|
+
*/
|
58
|
+
export type AsFetchOneResult<T extends FetchResult<any, string, Selectable<any>>> = T extends FetchResult<infer DB, infer T, infer R> ? FetchOneResult<DB, T, R> : never;
|
59
|
+
/**
|
60
|
+
* Used to type juggle between {@link FetchResult} and its subtypes.
|
61
|
+
*/
|
62
|
+
export type AsFetchOneXResult<T extends FetchResult<any, string, Selectable<any>>> = T extends FetchResult<infer DB, infer T, infer R> ? FetchOneXResult<DB, T, R> : never;
|
63
|
+
/**
|
64
|
+
* Used to type juggle between {@link FetchResult} and its subtypes.
|
65
|
+
*/
|
66
|
+
export type AsFetchSomeResult<T extends FetchResult<any, string, Selectable<any>>> = T extends FetchResult<infer DB, infer T, infer R> ? FetchSomeResult<DB, T, R> : never;
|
67
|
+
/**
|
68
|
+
* Creates a new {@link FetchOneResult} instance.
|
69
|
+
*/
|
70
|
+
export declare function newFetchOneResult<DB, T extends keyof DB & string, R extends Partial<Selectable<DB[T]>>>(table: T, result: R | undefined, models?: ModelCollection<DB>): FetchOneResult<DB, T, R>;
|
71
|
+
/**
|
72
|
+
* Creates a new {@link FetchOneXResult} instance.
|
73
|
+
*
|
74
|
+
* Note: it may be counterintuitive, but this function accepts `undefined` as
|
75
|
+
* input. I found it is way more convenient to assert the type once in this
|
76
|
+
* funciton rather than in every caller.
|
77
|
+
*/
|
78
|
+
export declare function newFetchOneXResult<DB, T extends keyof DB & string, R extends Partial<Selectable<DB[T]>>>(table: T, result: R | undefined, models?: ModelCollection<DB>): FetchOneXResult<DB, T, R>;
|
79
|
+
/**
|
80
|
+
* Creates a new {@link FetchSomeResult} instance.
|
81
|
+
*/
|
82
|
+
export declare function newFetchSomeResult<DB, T extends keyof DB & string, R extends Partial<Selectable<DB[T]>>>(table: T, result: R[], models?: ModelCollection<DB>): FetchSomeResult<DB, T, R>;
|
83
|
+
export declare function isFetchResult(result: unknown): result is FetchResult<any, any, any>;
|
84
|
+
/**
|
85
|
+
* Checks if `value` is a {@link FetchOneResult}.
|
86
|
+
*/
|
87
|
+
export declare function isFetchOneResult<DB, T extends keyof DB & string, R extends Partial<Selectable<DB[T]>>>(value: FetchOneResult<DB, T, R> | FetchOneXResult<DB, T, R>): value is typeof value;
|
88
|
+
export declare function isFetchOneResult(value: unknown): value is FetchOneResult<any, string, Selectable<any>>;
|
89
|
+
/**
|
90
|
+
* Checks if `value` is a {@link FetchOneXResult}.
|
91
|
+
*/
|
92
|
+
export declare function isFetchOneXResult<DB, T extends keyof DB & string, R extends Partial<Selectable<DB[T]>>>(value: FetchOneResult<DB, T, R>): value is FetchOneXResult<DB, T, R>;
|
93
|
+
export declare function isFetchOneXResult<DB, T extends keyof DB & string, R extends Partial<Selectable<DB[T]>>>(value: FetchOneXResult<DB, T, R>): value is typeof value;
|
94
|
+
export declare function isFetchOneXResult(value: unknown): value is FetchOneXResult<any, string, Selectable<any>>;
|
95
|
+
/**
|
96
|
+
* Checks if `value` is a {@link FetchSomeResult}.
|
97
|
+
*/
|
98
|
+
export declare function isFetchSomeResult<DB, T extends keyof DB & string, R extends Partial<Selectable<DB[T]>>>(value: FetchSomeResult<DB, T, R>): value is typeof value;
|
99
|
+
export declare function isFetchSomeResult(value: unknown): value is FetchSomeResult<any, string, Selectable<any>>;
|
100
|
+
/**
|
101
|
+
* Asserts that `value` is a {@link FetchOneResult}.
|
102
|
+
*/
|
103
|
+
export declare function assertIsFetchOneResult(value: unknown): asserts value is FetchOneResult<any, string, Selectable<any>>;
|
104
|
+
/**
|
105
|
+
* Asserts that `value` is a {@link FetchOneXResult}.
|
106
|
+
*/
|
107
|
+
export declare function assertIsFetchOneXResult(value: unknown): asserts value is FetchOneXResult<any, string, Selectable<any>>;
|
108
|
+
/**
|
109
|
+
* Asserts that `value` is a {@link FetchSomeResult}.
|
110
|
+
*/
|
111
|
+
export declare function assertIsFetchSomeResult(value: unknown): asserts value is FetchSomeResult<any, string, Selectable<any>>;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
export { KyselyRizzolver } from './kysely-rizzolver.js';
|
2
|
+
export type { AllTableFields, AnyTableField, KyselyRizzolverBuilderForSchema, KyselyRizzolverBuilderNoSchema, KyDB as RizzolverDB, TableName } from './kysely-rizzolver.js';
|
3
|
+
export { assertIsFetchOneResult, assertIsFetchOneXResult, assertIsFetchSomeResult, isFetchOneResult, isFetchOneXResult, isFetchSomeResult, newFetchOneResult, newFetchOneXResult, newFetchSomeResult } from './fetch-result.js';
|
4
|
+
export type { AsFetchOneResult, AsFetchOneXResult, AsFetchSomeResult, FetchOneResult, FetchOneXResult, FetchResult, FetchSomeResult } from './fetch-result.js';
|
5
|
+
export { newSelector } from './selector.js';
|
6
|
+
export type { Selector } from './selector.js';
|
7
|
+
export { newQueryBuilder } from './query-builder.js';
|
8
|
+
export type { QueryBuilder } from './query-builder.js';
|
9
|
+
export { newModelCollection } from './model-collection.js';
|
10
|
+
export type { ModelCollection } from './model-collection.js';
|
@@ -0,0 +1,138 @@
|
|
1
|
+
import { type FetchResultFactory } from './fetch-result-factory.js';
|
2
|
+
import { type QueryBuilder } from './query-builder.js';
|
3
|
+
import { type Selector } from './selector.js';
|
4
|
+
import { type ModelCollection } from './model-collection.js';
|
5
|
+
export interface KyselyRizzolverBase<DB, T extends Record<keyof DB & string, readonly string[]>> {
|
6
|
+
readonly fields: T;
|
7
|
+
readonly fetches: FetchResultFactory<DB>;
|
8
|
+
}
|
9
|
+
/**
|
10
|
+
* A {@link KyselyRizzolver} is a class that is used to define the structure of
|
11
|
+
* a database schema.
|
12
|
+
*
|
13
|
+
* It streamlines instatiating type-safe {@link QueryBuilder}s,
|
14
|
+
* {@link Selector}s, {@link ModelCollection}s and {@link FetchResult}s.
|
15
|
+
*
|
16
|
+
* Define a new {@link KyselyRizzolver} using the
|
17
|
+
* {@link KyselyRizzolver.builder|.builderForSchema()} or
|
18
|
+
* {@link KyselyRizzolver.builderNoSchema|.builderNoSchema()}.
|
19
|
+
*/
|
20
|
+
export declare class KyselyRizzolver<DB, T extends Record<keyof DB & string, readonly string[]>> implements KyselyRizzolverBase<DB, T> {
|
21
|
+
readonly fields: T;
|
22
|
+
readonly fetches: FetchResultFactory<DB>;
|
23
|
+
constructor(fields: T);
|
24
|
+
/**
|
25
|
+
* Intantiates a new {@link Selector} for the given table.
|
26
|
+
*/
|
27
|
+
newSelector<Table extends keyof DB & string, Alias extends string>(table: Table, alias: Alias): Selector<this, Table, Alias, AllTableFields<this, Table>>;
|
28
|
+
/**
|
29
|
+
* Instantiates a new {@link QueryBuilder}.
|
30
|
+
*/
|
31
|
+
newQueryBuilder(): QueryBuilder<this, {}>;
|
32
|
+
/**
|
33
|
+
* Instantiates a new {@link ModelCollection}.
|
34
|
+
*/
|
35
|
+
newModelCollection(): ModelCollection<DB>;
|
36
|
+
/**
|
37
|
+
* Starts building a new {@link KyselyRizzolver} using a builder pattern for
|
38
|
+
* a schema.
|
39
|
+
*
|
40
|
+
* Call {@link KyselyRizzolverBuilderForSchema.table|.table()} for each
|
41
|
+
* table that exists on the `DB` type parameter with all of their column
|
42
|
+
* names as a const array. After all tables have been added, call
|
43
|
+
* {@link KyselyRizzolverBuilderForSchema.build|.build()} to get a new
|
44
|
+
* {@link KyselyRizzolver} instance.
|
45
|
+
*
|
46
|
+
* Example:
|
47
|
+
* ```
|
48
|
+
* const rizzolver = KyselyRizzolver.builderForSchema<DB>()
|
49
|
+
* .table('user', ['id', 'name', 'email'] as const)
|
50
|
+
* .table('post', ['id', 'title', 'content', 'authorId'] as const)
|
51
|
+
* .build();
|
52
|
+
* ```
|
53
|
+
*
|
54
|
+
* Note: The `as const` assertion is necessary for correct type inference.
|
55
|
+
*/
|
56
|
+
static builderForSchema<DB>(): KyselyRizzolverBuilderForSchema<DB, {}>;
|
57
|
+
/**
|
58
|
+
* Starts building a new {@link KyselyRizzolver} using a builder pattern
|
59
|
+
* without a schema.
|
60
|
+
*
|
61
|
+
* Call {@link KyselyRizzolverBuilderNoSchema.table|.table()} for each
|
62
|
+
* table with all of their column names as a const array.
|
63
|
+
*
|
64
|
+
* Example:
|
65
|
+
* ```
|
66
|
+
* const rizzolver = KyselyRizzolver.builder()
|
67
|
+
* .table('user', ['id', 'name', 'email'] as const) // note `as const` is necessary
|
68
|
+
* .table('post', ['id', 'title', 'content', 'authorId'] as const)
|
69
|
+
* .build();
|
70
|
+
* ```
|
71
|
+
*
|
72
|
+
* Since this version of builder is schemaless, it cannot infer the value
|
73
|
+
* types for the columns. The `user` type will be `{ id: unknown, name:
|
74
|
+
* unknown, email: unknown }`.
|
75
|
+
*
|
76
|
+
* You may call
|
77
|
+
* {@link KyselyRizzolverBuilderNoSchema.asModel|.asModel\<M\>()}
|
78
|
+
* immediately after the .table() call to provide the types, where `M` is an
|
79
|
+
* type like `{ column1: type1, column2: type2, ... }`.
|
80
|
+
*
|
81
|
+
* Example:
|
82
|
+
* ```
|
83
|
+
* const rizzolver = KyselyRizzolver.builder()
|
84
|
+
* .table('user', ['id', 'name', 'email'] as const)
|
85
|
+
* .asModel<{id: number, name: string, email: string}>()
|
86
|
+
* .table('post', ['id', 'title', 'content', 'authorId'] as const)
|
87
|
+
* .asModel<{id: number, title: string, content: string, authorId: number}>()
|
88
|
+
* .build();
|
89
|
+
* ```
|
90
|
+
*
|
91
|
+
* p.s. if your .table() and .asModel() columns differ, it will let you know
|
92
|
+
* at compile time ;)
|
93
|
+
*
|
94
|
+
* Once all tables have been added, call
|
95
|
+
* {@link KyselyRizzolverBuilderNoSchema.build|.build()} to get a new
|
96
|
+
* {@link KyselyRizzolver} instance.
|
97
|
+
*/
|
98
|
+
static builderNoSchema(): KyselyRizzolverBuilderNoSchema<{}, null>;
|
99
|
+
}
|
100
|
+
export type KyselyRizzolverBuilderForSchema<DB, T extends Partial<Record<keyof DB & string, readonly string[]>>> = {
|
101
|
+
table<K extends Exclude<keyof DB & string, keyof T>, U extends readonly (keyof DB[K])[]>(name: K, fields: U & ([keyof DB[K]] extends [U[number]] ? unknown : `Missing key: ${Exclude<keyof DB[K] & string, U[number]>}`)): KyselyRizzolverBuilderForSchema<DB, T & {
|
102
|
+
[key in K]: U;
|
103
|
+
}>;
|
104
|
+
build(): T extends Record<keyof DB & string, readonly string[]> ? KyselyRizzolver<DB, T> : never;
|
105
|
+
};
|
106
|
+
export type KyselyRizzolverBuilderNoSchema<T extends Record<string, {
|
107
|
+
model: any;
|
108
|
+
columns: readonly string[];
|
109
|
+
}>, Last extends keyof T | null> = {
|
110
|
+
table<K extends string, U extends readonly string[]>(name: K, fields: U): KyselyRizzolverBuilderNoSchema<T & {
|
111
|
+
[k in K]: {
|
112
|
+
model: Record<U[number], unknown>;
|
113
|
+
columns: U;
|
114
|
+
};
|
115
|
+
}, K>;
|
116
|
+
asModel<M>(): Last extends keyof T ? keyof M extends T[Last]['columns'][number] ? T[Last]['columns'][number] extends keyof M ? KyselyRizzolverBuilderNoSchema<T & {
|
117
|
+
[k in Last]: {
|
118
|
+
model: M;
|
119
|
+
columns: T[k]['columns'];
|
120
|
+
};
|
121
|
+
}, never> : `column '${Exclude<T[Last]['columns'][number], keyof M & string>}' defined in table() but missing from asModel()` : `column '${Exclude<keyof M & string, T[Last]['columns'][number]>}' defined in asModel() but missing from table()` : `asModel() must be called after table()`;
|
122
|
+
build(): KyselyRizzolver<{
|
123
|
+
[k in keyof T]: T[k]['model'];
|
124
|
+
}, {
|
125
|
+
[k in keyof T]: T[k]['columns'];
|
126
|
+
}>;
|
127
|
+
};
|
128
|
+
export type KyDB<KY extends KyselyRizzolverBase<any, any>> = KY extends KyselyRizzolverBase<infer DB, any> ? DB : never;
|
129
|
+
export type TableName<T extends KyselyRizzolverBase<any, any>> = keyof KyDB<T> & string;
|
130
|
+
/**
|
131
|
+
* A union of all the known fields of a table.
|
132
|
+
*/
|
133
|
+
export type AnyTableField<KY extends KyselyRizzolverBase<any, any>, Table extends TableName<KY>> = keyof KyDB<KY>[Table] & string;
|
134
|
+
/**
|
135
|
+
* An array of all the known fields of a table, in a type that is compatible
|
136
|
+
* with that table's ${@link Selectable} type.
|
137
|
+
*/
|
138
|
+
export type AllTableFields<KY extends KyselyRizzolverBase<any, any>, Table extends TableName<KY>> = KY extends KyselyRizzolverBase<any, infer T> ? T[Table] extends infer U ? U extends readonly AnyTableField<KY, Table>[] ? U : never : never : never;
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import type { Selectable } from 'kysely';
|
2
|
+
/**
|
3
|
+
* A {@link ModelCollection} is a collection of {@link Selectable} instances,
|
4
|
+
* grouped by their table name and keyed by their id.
|
5
|
+
*
|
6
|
+
* This type purposefully discards specific type information to make it simpler
|
7
|
+
* to create and pass it around without worrying about type compatibility and
|
8
|
+
* casting.
|
9
|
+
*
|
10
|
+
* It does not preserve information on which tables are present in the
|
11
|
+
* collection, or which fields on their {@link Selectable}s have been gathered.
|
12
|
+
*
|
13
|
+
* If you need to preserve this information, you should just pass the result of
|
14
|
+
* the {@link QueryBuilder.run} method where needed. However, doing this is not
|
15
|
+
* recommended, as your types can easily become too nested to the point where
|
16
|
+
* TypeScript can't infer them correctly.
|
17
|
+
*/
|
18
|
+
export type ModelCollection<DB> = {
|
19
|
+
add<Table extends keyof DB & string>(table: Table, selectable?: Selectable<DB[Table]>): ModelCollection<DB>;
|
20
|
+
get collection(): Data<DB>;
|
21
|
+
};
|
22
|
+
export declare function newModelCollection<DB>(init?: Data<DB>): ModelCollection<DB>;
|
23
|
+
type Data<DB> = {
|
24
|
+
[M in keyof DB & string]?: Partial<Record<number, Selectable<DB[M]>>>;
|
25
|
+
};
|
26
|
+
export {};
|
@@ -0,0 +1,156 @@
|
|
1
|
+
import { type FetchOneResult, type FetchOneXResult, type FetchSomeResult } from './fetch-result.js';
|
2
|
+
import type { AllTableFields, AnyTableField, KyDB, KyselyRizzolverBase, TableName } from './kysely-rizzolver.js';
|
3
|
+
import { type Selector, type SelectorResult } from './selector.js';
|
4
|
+
import { type ModelCollection } from './model-collection.js';
|
5
|
+
import type { UnionToTuple } from './type-helpers.js';
|
6
|
+
/**
|
7
|
+
* {@link QueryBuilder} makes it easier to work with multiple tables in a query
|
8
|
+
* and parse the results into their respective Kysely {@link Selectable}
|
9
|
+
* instances in a type-safe way.
|
10
|
+
*
|
11
|
+
* It works by adding {@link Selector}s using the {@link QueryBuilder.add}
|
12
|
+
* method, followed by a call to {@link QueryBuilder.run} to parse the results
|
13
|
+
* of the query using a simple builder pattern.
|
14
|
+
*/
|
15
|
+
export interface QueryBuilder<KY extends KyselyRizzolverBase<any, any>, T extends Data<KY>> {
|
16
|
+
/**
|
17
|
+
* The record of {@link Selector} this query builder has, keyed by their alias.
|
18
|
+
*/
|
19
|
+
selectors: T;
|
20
|
+
/**
|
21
|
+
* Given a {@link Selector} alias `k`, Gets a `"table as alias"` expression
|
22
|
+
* for that selector.
|
23
|
+
*
|
24
|
+
* This is a shorthand for `this.selectors[k].selectTable`
|
25
|
+
*
|
26
|
+
* Example:
|
27
|
+
* ```
|
28
|
+
* const qb = rizzolver
|
29
|
+
* .newQueryBuilder()
|
30
|
+
* .add('user', 'u');
|
31
|
+
*
|
32
|
+
* const userTable = qb.table('u'); // => 'user as u'
|
33
|
+
* ```
|
34
|
+
*/
|
35
|
+
table<K extends keyof T>(tableAlias: K): T[K]['selectTable'];
|
36
|
+
/**
|
37
|
+
* Gets a const array of all the fields of all the {@link Selector}s this
|
38
|
+
* query builder has. The order is arbitrary and should not be relied upon.
|
39
|
+
*/
|
40
|
+
allFields: FieldsOf<T, keyof T>;
|
41
|
+
/**
|
42
|
+
* Given one of more {@link Selector} aliases, gets an array of
|
43
|
+
* `"table.field as alias"` expressions for all of their fields.
|
44
|
+
*
|
45
|
+
* Example:
|
46
|
+
* ```
|
47
|
+
* const qb = rizzolver
|
48
|
+
* .newQueryBuilder()
|
49
|
+
* .add('user', 'u')
|
50
|
+
* .add('address', 'a');
|
51
|
+
*
|
52
|
+
* const userFields = qb.fieldsOf('u');
|
53
|
+
* // => ['u.id as _u_id', 'u.first_name as _u_first_name', 'u.last_name as _u_last_name']
|
54
|
+
* const addressFields = qb.fieldsOf('a');
|
55
|
+
* // => ['a.id as _a_id', 'a.street as _a_street', 'a.city as _a_city']
|
56
|
+
* const allFields = qb.fieldsOf('u', 'a');
|
57
|
+
* // equivalent to [...qb.fieldsOf('u'), ...qb.fieldsOf('a')]
|
58
|
+
* // => ['u.id as _u_id', 'u.first_name as _u_first_name', 'u.last_name as _u_last_name', 'a.id as _a_id', 'a.street as _a_street', 'a.city as _a_city']
|
59
|
+
* ```
|
60
|
+
*/
|
61
|
+
fieldsOf<K extends keyof T>(...tableAliases: K[]): FieldsOf<T, K>;
|
62
|
+
field<K extends keyof T & string, F extends T[K]['input']['tableFields']>(field: `${K}.${F}`): {
|
63
|
+
value: `_${K}_${F}`;
|
64
|
+
from<A extends string>(alias: A): `${A}._${K}_${F}`;
|
65
|
+
};
|
66
|
+
/**
|
67
|
+
* Adds a new {@link Selector} to the query builder.
|
68
|
+
*/
|
69
|
+
add<Table extends TableName<KY>, Alias extends string, Keys extends readonly AnyTableField<KY, Table>[] = AllTableFields<KY, Table>>(selector: Selector<KY, Table, Alias, Keys>): MoreData<KY, T, Table, Alias, Keys>;
|
70
|
+
/**
|
71
|
+
* Adds a new {@link Selector} to the query builder.
|
72
|
+
*/
|
73
|
+
add<Table extends TableName<KY>, Alias extends string, Keys extends readonly AnyTableField<KY, Table>[] = AllTableFields<KY, Table>>(table: Table, alias: Alias, keys?: Keys): MoreData<KY, T, Table, Alias, Keys>;
|
74
|
+
/**
|
75
|
+
* Parses the results of a query into the {@link Selectable} instances
|
76
|
+
* defined by the {@link Selector}s this query builder has.
|
77
|
+
*
|
78
|
+
* Example:
|
79
|
+
* ```
|
80
|
+
* const result = await rizzolver
|
81
|
+
* .newQueryBuilder()
|
82
|
+
* .add('user', 'u')
|
83
|
+
* .add('address', 'a')
|
84
|
+
* .run((qb) =>
|
85
|
+
* db
|
86
|
+
* .selectFrom(qb.table('u'))
|
87
|
+
* .leftJoin(qb.table('a'), (join) => join.onRef('u.address_id', '=', 'a.id'))
|
88
|
+
* .select(qb.allFields)
|
89
|
+
* .execute()
|
90
|
+
* );
|
91
|
+
*
|
92
|
+
* const parsedRows = result.rows;
|
93
|
+
* // => [
|
94
|
+
* // {
|
95
|
+
* // row: { id: 1, first_name: 'John', last_name: 'Doe' },
|
96
|
+
* // selectors: {
|
97
|
+
* // u: { id: 1, first_name: 'John', last_name: 'Doe' },
|
98
|
+
* // a: { id: 10, street: '123 Main St', city: 'Springfield' }
|
99
|
+
* // }
|
100
|
+
* // },
|
101
|
+
* // {
|
102
|
+
* // row: { id: 2, first_name: 'Jane', last_name: 'Smith' },
|
103
|
+
* // selectors: {
|
104
|
+
* // u: { id: 2, first_name: 'Jane', last_name: 'Smith' },
|
105
|
+
* // a: undefined, // Jane has no address
|
106
|
+
* // }
|
107
|
+
* // },
|
108
|
+
* // ]
|
109
|
+
* ```
|
110
|
+
*
|
111
|
+
* To make it easier to consume the results, the query builder also provides
|
112
|
+
* methods to create {@link FetchOneResult}, {@link FetchOneXResult}, and
|
113
|
+
* {@link FetchSomeResult} instances for each selector by its alias:
|
114
|
+
* ```
|
115
|
+
* const result = await rizzolver
|
116
|
+
* .newQueryBuilder()
|
117
|
+
* .add('user', 'u')
|
118
|
+
* .run(...)
|
119
|
+
* .newFetchOneResult('u'); // => FetchOneResult<DB, 'user', Selectable<User>>
|
120
|
+
* ```
|
121
|
+
*/
|
122
|
+
run<Row extends Record<string, unknown>>(rows: Row[]): DeferredResult<KY, T, Row>;
|
123
|
+
run<Row extends Record<string, unknown>>(callback: (qb: this) => Promise<Row[]>): DeferredResult<KY, T, Row>;
|
124
|
+
}
|
125
|
+
export declare function newQueryBuilder<KY extends KyselyRizzolverBase<any, any>>(rizzolver: KY): QueryBuilder<KY, {}>;
|
126
|
+
type Data<KY extends KyselyRizzolverBase<any, any>> = {
|
127
|
+
[alias: string]: Selector<KY, any, string, any>;
|
128
|
+
};
|
129
|
+
type MoreData<KY extends KyselyRizzolverBase<any, any>, T extends Data<KY>, Table extends TableName<KY>, A extends string, K extends readonly AnyTableField<KY, Table>[] = AllTableFields<KY, Table>> = QueryBuilder<KY, T & {
|
130
|
+
[k in A]: Selector<KY, Table, A, K>;
|
131
|
+
}>;
|
132
|
+
interface DeferredResult<KY extends KyselyRizzolverBase<any, any>, T extends Data<KY>, Row extends Record<string, unknown>> extends Promise<Result<KY, T, Row>> {
|
133
|
+
newFetchOneResult<K extends keyof T>(selectorAlias: K): Promise<FetchOneResult<KyDB<KY>, T[K]['input']['table'], SelectorResult<KY, any, T[K]['input']['table'], T[K]['input']['tableFields']>[number]['model'] & {}>>;
|
134
|
+
newFetchOneXResult<K extends keyof T>(selectorAlias: K): Promise<FetchOneXResult<KyDB<KY>, T[K]['input']['table'], SelectorResult<KY, any, T[K]['input']['table'], T[K]['input']['tableFields']>[number]['model'] & {}>>;
|
135
|
+
newFetchSomeResult<K extends keyof T>(selectorAlias: K): Promise<FetchSomeResult<KyDB<KY>, T[K]['input']['table'], SelectorResult<KY, any, T[K]['input']['table'], T[K]['input']['tableFields']>[number]['model'] & {}>>;
|
136
|
+
}
|
137
|
+
type Result<KY extends KyselyRizzolverBase<any, any>, T extends Data<KY>, Row extends Record<string, unknown>> = {
|
138
|
+
first: {
|
139
|
+
row: Row;
|
140
|
+
selectors: {
|
141
|
+
[k in keyof T]: ReturnType<T[k]['select']>[number]['model'];
|
142
|
+
};
|
143
|
+
} | undefined;
|
144
|
+
rows: {
|
145
|
+
row: Row;
|
146
|
+
selectors: {
|
147
|
+
[k in keyof T]: ReturnType<T[k]['select']>[number]['model'];
|
148
|
+
};
|
149
|
+
}[];
|
150
|
+
models: ModelCollection<KyDB<KY>>;
|
151
|
+
newFetchOneResult<K extends keyof T>(selectorAlias: K): FetchOneResult<KyDB<KY>, T[K]['input']['table'], SelectorResult<KY, any, T[K]['input']['table'], T[K]['input']['tableFields']>[number]['model'] & {}>;
|
152
|
+
newFetchOneXResult<K extends keyof T>(selectorAlias: K): FetchOneXResult<KyDB<KY>, T[K]['input']['table'], SelectorResult<KY, any, T[K]['input']['table'], T[K]['input']['tableFields']>[number]['model'] & {}>;
|
153
|
+
newFetchSomeResult<K extends keyof T>(selectorAlias: K): FetchSomeResult<KyDB<KY>, T[K]['input']['table'], SelectorResult<KY, any, T[K]['input']['table'], T[K]['input']['tableFields']>[number]['model'] & {}>;
|
154
|
+
};
|
155
|
+
type FieldsOf<T, K extends keyof T> = UnionToTuple<Pick<T, K> extends infer U ? U[keyof U] extends infer S ? S extends Selector<infer KY, infer Table, infer Alias, infer Keys> ? Selector<KY, Table, Alias, Keys>['selectFields'][number] : never : never : never>;
|
156
|
+
export {};
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import type { Selectable } from 'kysely';
|
2
|
+
import type { AllTableFields, AnyTableField, KyDB, KyselyRizzolverBase, TableName } from './kysely-rizzolver.js';
|
3
|
+
/**
|
4
|
+
* A {@link Selector} makes it easier to build select expressions for a
|
5
|
+
* table in a type-safe way. It can process the results of queries into
|
6
|
+
* Kysely's {@link Selectable} instances.
|
7
|
+
*
|
8
|
+
* {@link Selector} is a low level utility that is used by
|
9
|
+
* {@link QueryBuilder} to work with multiple selectors.
|
10
|
+
*/
|
11
|
+
export type Selector<KY extends KyselyRizzolverBase<any, any>, Table extends TableName<KY>, Alias extends string, TableFields extends readonly AnyTableField<KY, Table>[] = AllTableFields<KY, Table>> = {
|
12
|
+
input: {
|
13
|
+
/**
|
14
|
+
* The table name that's being selected from.
|
15
|
+
*/
|
16
|
+
table: Table;
|
17
|
+
/**
|
18
|
+
* The alias for the table.
|
19
|
+
*/
|
20
|
+
alias: Alias;
|
21
|
+
/**
|
22
|
+
* An array of the fields to be selected from the table.
|
23
|
+
*
|
24
|
+
* This can be omitted, in which case it will default to all the fields of
|
25
|
+
* the table.
|
26
|
+
*/
|
27
|
+
tableFields: TableFields;
|
28
|
+
};
|
29
|
+
/**
|
30
|
+
* A `"table as alias"` expression for using in select expressions.
|
31
|
+
*/
|
32
|
+
selectTable: TableAlias<KY, Table, Alias>;
|
33
|
+
/**
|
34
|
+
* An array of `"table.field as alias"` expressions for using in select expressions.
|
35
|
+
*/
|
36
|
+
selectFields: FieldsAsAliases<Alias, TableFields>;
|
37
|
+
/**
|
38
|
+
* A utility method that allows you to reference a specific field.
|
39
|
+
*/
|
40
|
+
field<Field extends TableFields[number]>(field: Field): {
|
41
|
+
str: `_${Alias}_${Field}`;
|
42
|
+
/**
|
43
|
+
* A utility method that allows you to reference the field from a different table alias.
|
44
|
+
*
|
45
|
+
* This is useful if you need to reference the field from a subquery for example.
|
46
|
+
*/
|
47
|
+
from<A extends string>(alias: A): `${A}.${FieldAlias<Alias, Field>}`;
|
48
|
+
};
|
49
|
+
/**
|
50
|
+
* Parses the results of a query into the model defined by this selector for each row.
|
51
|
+
*
|
52
|
+
* Example:
|
53
|
+
* ```
|
54
|
+
* const selector = newSelector('user', 'u');
|
55
|
+
* const data = await db.selectFrom(selector.tableAlias).selectAll().execute();
|
56
|
+
* const parsed = selector.select(data);
|
57
|
+
* // => type would be { row: Row, model: Selectable<User> | undefined }[]
|
58
|
+
* ```
|
59
|
+
*/
|
60
|
+
select<Row extends Record<string, unknown>>(rows: Row[]): SelectorResult<KY, Row, Table, TableFields>;
|
61
|
+
};
|
62
|
+
export declare function newSelector<KY extends KyselyRizzolverBase<any, any>, Table extends TableName<KY>, Alias extends string>(rizzolver: KY, tableName: Table, alias: Alias): Selector<KY, Table, Alias, AllTableFields<KY, Table>>;
|
63
|
+
export declare function newSelector<KY extends KyselyRizzolverBase<any, any>, Table extends TableName<KY>, Alias extends string, Keys extends readonly AnyTableField<KY, Table>[]>(rizzolver: KY, tableName: Table, alias: Alias, keys?: Keys): Selector<KY, Table, Alias, Keys>;
|
64
|
+
export type SelectorResult<KY extends KyselyRizzolverBase<any, any>, Row extends Record<string, unknown>, Table extends TableName<KY>, TableFields extends readonly AnyTableField<KY, Table>[] = AllTableFields<KY, Table>> = {
|
65
|
+
row: Row;
|
66
|
+
model: Selectable<Pick<KyDB<KY>[Table], TableFields[number]>> | undefined;
|
67
|
+
}[];
|
68
|
+
type TableAlias<KY extends KyselyRizzolverBase<any, any>, Table extends TableName<KY>, Alias extends string> = `${Table} as ${Alias}`;
|
69
|
+
type FieldAlias<TableAlias extends string, TableField extends string> = `_${TableAlias}_${TableField}`;
|
70
|
+
type FieldAsAlias<TableAlias extends string, TableField extends string> = `${TableAlias}.${TableField} as ${FieldAlias<TableAlias, TableField>}`;
|
71
|
+
type FieldsAsAliases<TableAlias extends string, TableFields extends readonly string[]> = TableFields extends readonly [infer TableField extends string, ...infer Tail extends string[]] ? [FieldAsAlias<TableAlias, TableField>, ...FieldsAsAliases<TableAlias, Tail>] : [];
|
72
|
+
export {};
|
@@ -0,0 +1,11 @@
|
|
1
|
+
/**
|
2
|
+
* I can't explain this.
|
3
|
+
*
|
4
|
+
* I badgered ChatGPT until it worked. it probably just referenced type-fest.
|
5
|
+
*/
|
6
|
+
type LastInUnion<U> = (U extends any ? () => U : never) extends infer F ? (F extends any ? (k: F) => void : never) extends (k: infer I) => void ? I extends () => infer R ? R : never : never : never;
|
7
|
+
/**
|
8
|
+
* Recursively converts a union type to a tuple by splitting it into its head and tail.
|
9
|
+
*/
|
10
|
+
export type UnionToTuple<U, Last = LastInUnion<U>> = [U] extends [never] ? [] : [...UnionToTuple<Exclude<U, Last>>, Last];
|
11
|
+
export {};
|
package/package.json
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
{
|
2
|
+
"name": "kysely-rizzolver",
|
3
|
+
"version": "0.0.2",
|
4
|
+
"description": "Complex Kysely queries, maximum rizz, type-safe every time.",
|
5
|
+
"author": "YLivay",
|
6
|
+
"repository": {
|
7
|
+
"type": "git",
|
8
|
+
"url": "git://github.com/YLivay/kysely-rizzolver.git"
|
9
|
+
},
|
10
|
+
"license": "MIT",
|
11
|
+
"main": "dist/cjs/index.js",
|
12
|
+
"module": "dist/esm/index.js",
|
13
|
+
"exports": {
|
14
|
+
".": {
|
15
|
+
"import": "./dist/esm/index.js",
|
16
|
+
"require": "./dist/cjs/index.js",
|
17
|
+
"default": "./dist/cjs/index.js"
|
18
|
+
}
|
19
|
+
},
|
20
|
+
"scripts": {
|
21
|
+
"clean": "rm -rf dist",
|
22
|
+
"build": "npm run clean && (npm run build:esm & npm run build:cjs)",
|
23
|
+
"build:esm": "tsc -p tsconfig.json && echo '{\"type\": \"module\"}' > dist/esm/package.json",
|
24
|
+
"build:cjs": "tsc -p tsconfig-cjs.json"
|
25
|
+
},
|
26
|
+
"peerDependencies": {
|
27
|
+
"kysely": "^0.27.5"
|
28
|
+
},
|
29
|
+
"devDependencies": {
|
30
|
+
"typescript": "^5.7.3"
|
31
|
+
}
|
32
|
+
}
|