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