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,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,147 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.KyselyRizzolver = void 0;
|
4
|
+
const fetch_result_factory_js_1 = require("./fetch-result-factory.js");
|
5
|
+
const query_builder_js_1 = require("./query-builder.js");
|
6
|
+
const selector_js_1 = require("./selector.js");
|
7
|
+
const model_collection_js_1 = require("./model-collection.js");
|
8
|
+
/**
|
9
|
+
* A {@link KyselyRizzolver} is a class that is used to define the structure of
|
10
|
+
* a database schema.
|
11
|
+
*
|
12
|
+
* It streamlines instatiating type-safe {@link QueryBuilder}s,
|
13
|
+
* {@link Selector}s, {@link ModelCollection}s and {@link FetchResult}s.
|
14
|
+
*
|
15
|
+
* Define a new {@link KyselyRizzolver} using the
|
16
|
+
* {@link KyselyRizzolver.builder|.builderForSchema()} or
|
17
|
+
* {@link KyselyRizzolver.builderNoSchema|.builderNoSchema()}.
|
18
|
+
*/
|
19
|
+
class KyselyRizzolver {
|
20
|
+
fields;
|
21
|
+
fetches;
|
22
|
+
constructor(fields) {
|
23
|
+
this.fields = fields;
|
24
|
+
this.fetches = (0, fetch_result_factory_js_1.newFetchResultFactory)();
|
25
|
+
}
|
26
|
+
/**
|
27
|
+
* Intantiates a new {@link Selector} for the given table.
|
28
|
+
*/
|
29
|
+
newSelector(table, alias) {
|
30
|
+
return (0, selector_js_1.newSelector)(this, table, alias);
|
31
|
+
}
|
32
|
+
/**
|
33
|
+
* Instantiates a new {@link QueryBuilder}.
|
34
|
+
*/
|
35
|
+
newQueryBuilder() {
|
36
|
+
return (0, query_builder_js_1.newQueryBuilder)(this);
|
37
|
+
}
|
38
|
+
/**
|
39
|
+
* Instantiates a new {@link ModelCollection}.
|
40
|
+
*/
|
41
|
+
newModelCollection() {
|
42
|
+
return (0, model_collection_js_1.newModelCollection)();
|
43
|
+
}
|
44
|
+
/**
|
45
|
+
* Starts building a new {@link KyselyRizzolver} using a builder pattern for
|
46
|
+
* a schema.
|
47
|
+
*
|
48
|
+
* Call {@link KyselyRizzolverBuilderForSchema.table|.table()} for each
|
49
|
+
* table that exists on the `DB` type parameter with all of their column
|
50
|
+
* names as a const array. After all tables have been added, call
|
51
|
+
* {@link KyselyRizzolverBuilderForSchema.build|.build()} to get a new
|
52
|
+
* {@link KyselyRizzolver} instance.
|
53
|
+
*
|
54
|
+
* Example:
|
55
|
+
* ```
|
56
|
+
* const rizzolver = KyselyRizzolver.builderForSchema<DB>()
|
57
|
+
* .table('user', ['id', 'name', 'email'] as const)
|
58
|
+
* .table('post', ['id', 'title', 'content', 'authorId'] as const)
|
59
|
+
* .build();
|
60
|
+
* ```
|
61
|
+
*
|
62
|
+
* Note: The `as const` assertion is necessary for correct type inference.
|
63
|
+
*/
|
64
|
+
static builderForSchema() {
|
65
|
+
return _newKyselyRizzolverBuilderForSchema({});
|
66
|
+
}
|
67
|
+
/**
|
68
|
+
* Starts building a new {@link KyselyRizzolver} using a builder pattern
|
69
|
+
* without a schema.
|
70
|
+
*
|
71
|
+
* Call {@link KyselyRizzolverBuilderNoSchema.table|.table()} for each
|
72
|
+
* table with all of their column names as a const array.
|
73
|
+
*
|
74
|
+
* Example:
|
75
|
+
* ```
|
76
|
+
* const rizzolver = KyselyRizzolver.builder()
|
77
|
+
* .table('user', ['id', 'name', 'email'] as const) // note `as const` is necessary
|
78
|
+
* .table('post', ['id', 'title', 'content', 'authorId'] as const)
|
79
|
+
* .build();
|
80
|
+
* ```
|
81
|
+
*
|
82
|
+
* Since this version of builder is schemaless, it cannot infer the value
|
83
|
+
* types for the columns. The `user` type will be `{ id: unknown, name:
|
84
|
+
* unknown, email: unknown }`.
|
85
|
+
*
|
86
|
+
* You may call
|
87
|
+
* {@link KyselyRizzolverBuilderNoSchema.asModel|.asModel\<M\>()}
|
88
|
+
* immediately after the .table() call to provide the types, where `M` is an
|
89
|
+
* type like `{ column1: type1, column2: type2, ... }`.
|
90
|
+
*
|
91
|
+
* Example:
|
92
|
+
* ```
|
93
|
+
* const rizzolver = KyselyRizzolver.builder()
|
94
|
+
* .table('user', ['id', 'name', 'email'] as const)
|
95
|
+
* .asModel<{id: number, name: string, email: string}>()
|
96
|
+
* .table('post', ['id', 'title', 'content', 'authorId'] as const)
|
97
|
+
* .asModel<{id: number, title: string, content: string, authorId: number}>()
|
98
|
+
* .build();
|
99
|
+
* ```
|
100
|
+
*
|
101
|
+
* p.s. if your .table() and .asModel() columns differ, it will let you know
|
102
|
+
* at compile time ;)
|
103
|
+
*
|
104
|
+
* Once all tables have been added, call
|
105
|
+
* {@link KyselyRizzolverBuilderNoSchema.build|.build()} to get a new
|
106
|
+
* {@link KyselyRizzolver} instance.
|
107
|
+
*/
|
108
|
+
static builderNoSchema() {
|
109
|
+
return _newKyselyRizzolverBuilderNoSchema({}, null);
|
110
|
+
}
|
111
|
+
}
|
112
|
+
exports.KyselyRizzolver = KyselyRizzolver;
|
113
|
+
function _newKyselyRizzolverBuilderForSchema(fields) {
|
114
|
+
return {
|
115
|
+
table(tableName, tableFields) {
|
116
|
+
return _newKyselyRizzolverBuilderForSchema({
|
117
|
+
...fields,
|
118
|
+
[tableName]: tableFields
|
119
|
+
});
|
120
|
+
},
|
121
|
+
build() {
|
122
|
+
return new KyselyRizzolver(fields);
|
123
|
+
}
|
124
|
+
};
|
125
|
+
}
|
126
|
+
function _newKyselyRizzolverBuilderNoSchema(fields, last) {
|
127
|
+
return {
|
128
|
+
table(tableName, tableFields) {
|
129
|
+
return _newKyselyRizzolverBuilderNoSchema({
|
130
|
+
...fields,
|
131
|
+
[tableName]: {
|
132
|
+
model: null,
|
133
|
+
columns: tableFields
|
134
|
+
}
|
135
|
+
}, tableName);
|
136
|
+
},
|
137
|
+
asModel() {
|
138
|
+
if (!last) {
|
139
|
+
throw new Error('asModel() must be called after table()');
|
140
|
+
}
|
141
|
+
return _newKyselyRizzolverBuilderNoSchema(fields, null);
|
142
|
+
},
|
143
|
+
build() {
|
144
|
+
return new KyselyRizzolver(Object.fromEntries(Object.entries(fields).map((entry) => [entry[0], entry[1].columns])));
|
145
|
+
}
|
146
|
+
};
|
147
|
+
}
|
@@ -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,24 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.newModelCollection = newModelCollection;
|
4
|
+
function newModelCollection(init = {}) {
|
5
|
+
const collection = { ...init };
|
6
|
+
return {
|
7
|
+
add(table, selectable) {
|
8
|
+
if (!selectable ||
|
9
|
+
!('id' in selectable) ||
|
10
|
+
!selectable.id ||
|
11
|
+
typeof selectable.id !== 'number') {
|
12
|
+
return this;
|
13
|
+
}
|
14
|
+
if (!(table in collection)) {
|
15
|
+
collection[table] = {};
|
16
|
+
}
|
17
|
+
collection[table][selectable.id] = selectable;
|
18
|
+
return this;
|
19
|
+
},
|
20
|
+
get collection() {
|
21
|
+
return collection;
|
22
|
+
}
|
23
|
+
};
|
24
|
+
}
|
@@ -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,92 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.newQueryBuilder = newQueryBuilder;
|
4
|
+
const fetch_result_js_1 = require("./fetch-result.js");
|
5
|
+
const selector_js_1 = require("./selector.js");
|
6
|
+
const model_collection_js_1 = require("./model-collection.js");
|
7
|
+
function newQueryBuilder(rizzolver) {
|
8
|
+
const selectors = {};
|
9
|
+
return {
|
10
|
+
selectors,
|
11
|
+
table(tableAlias) {
|
12
|
+
return selectors[tableAlias].selectTable;
|
13
|
+
},
|
14
|
+
allFields: Object.values(selectors)
|
15
|
+
.map((selector) => selector.selectFields)
|
16
|
+
.flat(),
|
17
|
+
fieldsOf(...tableAliases) {
|
18
|
+
return tableAliases.map((alias) => selectors[alias].selectFields).flat();
|
19
|
+
},
|
20
|
+
field(field) {
|
21
|
+
const [t, f] = field.split('.');
|
22
|
+
const alias = `_${t}_${f}`;
|
23
|
+
return {
|
24
|
+
value: alias,
|
25
|
+
from(a) {
|
26
|
+
return `${a}.${alias}`;
|
27
|
+
}
|
28
|
+
};
|
29
|
+
},
|
30
|
+
add(selectorOrTable, alias, keys) {
|
31
|
+
let selector;
|
32
|
+
if (typeof selectorOrTable === 'string') {
|
33
|
+
if (!alias) {
|
34
|
+
throw new Error('Must provide an alias when calling QueryBuilder.add with a table name');
|
35
|
+
}
|
36
|
+
selector = (0, selector_js_1.newSelector)(rizzolver, selectorOrTable, alias, (keys ?? rizzolver.fields[selectorOrTable]));
|
37
|
+
}
|
38
|
+
else {
|
39
|
+
if (alias || keys) {
|
40
|
+
throw new Error('Must not provide an alias or keys when calling QueryBuilder.add with a selector');
|
41
|
+
}
|
42
|
+
selector = selectorOrTable;
|
43
|
+
}
|
44
|
+
selectors[selector.input.alias] = selector;
|
45
|
+
return this;
|
46
|
+
},
|
47
|
+
run(rowsOrCallback) {
|
48
|
+
const promise = (async () => {
|
49
|
+
const rows = Array.isArray(rowsOrCallback) ? rowsOrCallback : await rowsOrCallback(this);
|
50
|
+
const modelCollection = (0, model_collection_js_1.newModelCollection)();
|
51
|
+
const selectorResults = {};
|
52
|
+
for (const [alias, selector] of Object.entries(selectors)) {
|
53
|
+
const selectorResult = selector.select(rows);
|
54
|
+
for (const { model } of selectorResult) {
|
55
|
+
if (!model) {
|
56
|
+
continue;
|
57
|
+
}
|
58
|
+
modelCollection.add(selector.input.table, model);
|
59
|
+
}
|
60
|
+
selectorResults[alias] = selectorResult;
|
61
|
+
}
|
62
|
+
const result = [];
|
63
|
+
for (let i = 0; i < rows.length; i++) {
|
64
|
+
const row = rows[i];
|
65
|
+
const selectedModels = {};
|
66
|
+
for (const [alias, selectorResult] of Object.entries(selectorResults)) {
|
67
|
+
selectedModels[alias] = selectorResult[i].model;
|
68
|
+
}
|
69
|
+
result.push({ row, selectors: selectedModels });
|
70
|
+
}
|
71
|
+
return {
|
72
|
+
first: result.length ? result[0] : undefined,
|
73
|
+
rows: result,
|
74
|
+
models: modelCollection,
|
75
|
+
newFetchOneResult(selectorAlias) {
|
76
|
+
return (0, fetch_result_js_1.newFetchOneResult)(selectors[selectorAlias].input.table, (result.length ? result[0] : undefined)?.selectors[selectorAlias], modelCollection);
|
77
|
+
},
|
78
|
+
newFetchOneXResult(selectorAlias) {
|
79
|
+
return (0, fetch_result_js_1.newFetchOneXResult)(selectors[selectorAlias].input.table, (result.length ? result[0] : undefined)?.selectors[selectorAlias], modelCollection);
|
80
|
+
},
|
81
|
+
newFetchSomeResult(selectorAlias) {
|
82
|
+
return (0, fetch_result_js_1.newFetchSomeResult)(selectors[selectorAlias].input.table, result.map((r) => r.selectors[selectorAlias]).filter((r) => !!r), modelCollection);
|
83
|
+
}
|
84
|
+
};
|
85
|
+
})();
|
86
|
+
promise.newFetchOneResult = (selectorAlias) => promise.then((result) => result.newFetchOneResult(selectorAlias));
|
87
|
+
promise.newFetchOneXResult = (selectorAlias) => promise.then((result) => result.newFetchOneXResult(selectorAlias));
|
88
|
+
promise.newFetchSomeResult = (selectorAlias) => promise.then((result) => result.newFetchSomeResult(selectorAlias));
|
89
|
+
return promise;
|
90
|
+
}
|
91
|
+
};
|
92
|
+
}
|
@@ -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 {};
|