kysely-rizzolver 0.0.2
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/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 {};
|