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,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
|
+
};
|