xcraft-core-pickaxe 0.1.0 → 0.1.1
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/lib/examples.js +44 -14
- package/lib/operator-to-sql.js +65 -9
- package/lib/operators.js +37 -2
- package/lib/picks.js +160 -53
- package/lib/query-builder.js +115 -52
- package/lib/query-to-sql.js +1 -8
- package/package.json +1 -1
package/lib/examples.js
CHANGED
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
dateTime,
|
|
10
10
|
any,
|
|
11
11
|
value,
|
|
12
|
+
record,
|
|
12
13
|
} = require('xcraft-core-stones');
|
|
13
14
|
const $ = require('./operators.js');
|
|
14
15
|
const {queryToSql} = require('./query-to-sql.js');
|
|
@@ -24,6 +25,7 @@ class TestUserShape {
|
|
|
24
25
|
streetName = string;
|
|
25
26
|
townName = string;
|
|
26
27
|
};
|
|
28
|
+
skills = record(string, number);
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
const TestUserShape2 = {
|
|
@@ -110,21 +112,49 @@ example3: {
|
|
|
110
112
|
console.log(queryToSql(builder.query));
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
/**
|
|
116
|
+
* @template {AnyObjectShape} T
|
|
117
|
+
* @param {T} shape
|
|
118
|
+
* @returns {ScopedFromQuery<GetShape<T>>}
|
|
119
|
+
*/
|
|
120
|
+
function queryAction(shape) {
|
|
121
|
+
const builder = new QueryBuilder()
|
|
122
|
+
.db('test_db')
|
|
123
|
+
.from('test_table', ActionShape(shape))
|
|
124
|
+
.scope((row) => row.field('action').get('payload').get('state'));
|
|
125
|
+
return /** @type {ScopedFromQuery<GetShape<T>>} */ (builder);
|
|
126
|
+
}
|
|
127
|
+
|
|
113
128
|
example4: {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
const builder = queryAction(TestUserShape)
|
|
130
|
+
.fields(['firstname', 'age'])
|
|
131
|
+
.where((user) => user.get('address').get('streetName').eq('toto'))
|
|
132
|
+
.orderBy((user) => [user.get('age'), user.get('firstname')]);
|
|
133
|
+
|
|
134
|
+
console.log(queryToSql(builder.query));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
example5: {
|
|
138
|
+
const builder = queryAction(TestUserShape)
|
|
139
|
+
.fields(['firstname', 'age'])
|
|
140
|
+
.where((user, $) =>
|
|
141
|
+
$.and(
|
|
142
|
+
user.get('mails').length.gt(0),
|
|
143
|
+
user.get('mails').some((mail) => mail.like('%@example.com'))
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
console.log(queryToSql(builder.query));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
example6: {
|
|
151
|
+
const builder = queryAction(TestUserShape)
|
|
152
|
+
.fields(['firstname', 'age'])
|
|
153
|
+
.where((user, $) =>
|
|
154
|
+
user
|
|
155
|
+
.get('skills')
|
|
156
|
+
.some((value, key) => $.or(value.eq(42), key.eq('test')))
|
|
157
|
+
);
|
|
128
158
|
|
|
129
159
|
console.log(queryToSql(builder.query));
|
|
130
160
|
}
|
package/lib/operator-to-sql.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
const {isAnyPick} = require('./picks.js');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import("./operators.js").Operator} Operator
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {import('./picks.js').AnyPick} AnyPick
|
|
10
|
+
*/
|
|
11
|
+
|
|
3
12
|
function escape(value) {
|
|
4
13
|
if (typeof value === 'string') {
|
|
5
14
|
return `'${value.replace(/'/g, "''")}'`;
|
|
@@ -9,6 +18,9 @@ function escape(value) {
|
|
|
9
18
|
|
|
10
19
|
const operators = {
|
|
11
20
|
value({value}, values) {
|
|
21
|
+
if (typeof value === 'boolean') {
|
|
22
|
+
return value ? 'TRUE' : 'FALSE';
|
|
23
|
+
}
|
|
12
24
|
values.push(value);
|
|
13
25
|
return '?';
|
|
14
26
|
},
|
|
@@ -19,13 +31,22 @@ const operators = {
|
|
|
19
31
|
|
|
20
32
|
field({field}, values) {
|
|
21
33
|
// Note: field is not validated
|
|
22
|
-
|
|
34
|
+
if (typeof field === 'string') {
|
|
35
|
+
return `${field}`;
|
|
36
|
+
}
|
|
37
|
+
return sql(field, values);
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
as({value, name}, values) {
|
|
41
|
+
return `${sql(value, values)} AS ${escape(name)}`;
|
|
23
42
|
},
|
|
24
43
|
|
|
25
44
|
get({value, path}, values) {
|
|
26
45
|
// TODO: try values.push(path) => '?'
|
|
27
46
|
// return `json_extract(${field}, ${sql(path, values)})`;
|
|
28
|
-
return `json_extract(${sql(value)}, '$.' || ${escape(
|
|
47
|
+
return `json_extract(${sql(value, values)}, '$.' || ${escape(
|
|
48
|
+
path.join('.')
|
|
49
|
+
)})`;
|
|
29
50
|
},
|
|
30
51
|
|
|
31
52
|
not({value}, values) {
|
|
@@ -93,10 +114,14 @@ const operators = {
|
|
|
93
114
|
.join(' OR ')})`;
|
|
94
115
|
},
|
|
95
116
|
|
|
117
|
+
length({list}, values) {
|
|
118
|
+
return `json_array_length(${sql(list, values)})`;
|
|
119
|
+
},
|
|
120
|
+
|
|
96
121
|
includes({list, value}, values) {
|
|
97
122
|
return `EXISTS (
|
|
98
123
|
SELECT *
|
|
99
|
-
FROM json_each(${list})
|
|
124
|
+
FROM json_each(${sql(list, values)})
|
|
100
125
|
WHERE json_each.value = ${sql(value, values)}
|
|
101
126
|
)`;
|
|
102
127
|
},
|
|
@@ -104,24 +129,55 @@ const operators = {
|
|
|
104
129
|
some({list, condition}, values) {
|
|
105
130
|
return `EXISTS (
|
|
106
131
|
SELECT *
|
|
107
|
-
FROM json_each(${list})
|
|
132
|
+
FROM json_each(${sql(list, values)})
|
|
108
133
|
WHERE ${sql(condition, values)}
|
|
109
134
|
)`;
|
|
110
135
|
},
|
|
111
136
|
|
|
112
|
-
|
|
137
|
+
eachValue(_, values) {
|
|
113
138
|
return 'json_each.value';
|
|
114
139
|
},
|
|
115
140
|
|
|
116
|
-
|
|
141
|
+
eachKey(_, values) {
|
|
142
|
+
return 'json_each.key';
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
keys({obj}, values) {
|
|
146
|
+
return `(SELECT json_group_array(json_each.key) FROM json_each(${sql(
|
|
147
|
+
obj,
|
|
148
|
+
values
|
|
149
|
+
)}))`;
|
|
150
|
+
},
|
|
117
151
|
|
|
118
|
-
|
|
152
|
+
values({obj}, values) {
|
|
153
|
+
return `(SELECT json_group_array(json_each.value) FROM json_each(${sql(
|
|
154
|
+
obj,
|
|
155
|
+
values
|
|
156
|
+
)}))`;
|
|
157
|
+
},
|
|
119
158
|
};
|
|
120
159
|
|
|
121
|
-
|
|
160
|
+
/**
|
|
161
|
+
* @param {Operator | AnyPick} operatorOrPick
|
|
162
|
+
* @returns {Operator}
|
|
163
|
+
*/
|
|
164
|
+
function getOperator(operatorOrPick) {
|
|
165
|
+
if (isAnyPick(operatorOrPick)) {
|
|
166
|
+
return operatorOrPick.value;
|
|
167
|
+
}
|
|
168
|
+
return operatorOrPick;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {Operator | AnyPick} operatorOrPick
|
|
173
|
+
* @param {any[]} values
|
|
174
|
+
* @returns {string}
|
|
175
|
+
*/
|
|
176
|
+
function sql(operatorOrPick, values) {
|
|
177
|
+
const operator = getOperator(operatorOrPick);
|
|
122
178
|
const operatorName = operator.operator;
|
|
123
179
|
if (!(operatorName in operators)) {
|
|
124
|
-
throw new Error(`Unknown operator '${operator}'`);
|
|
180
|
+
throw new Error(`Unknown operator '${JSON.stringify(operator)}'`);
|
|
125
181
|
}
|
|
126
182
|
return operators[operatorName](operator, values);
|
|
127
183
|
}
|
package/lib/operators.js
CHANGED
|
@@ -36,6 +36,14 @@ const operators = {
|
|
|
36
36
|
});
|
|
37
37
|
},
|
|
38
38
|
|
|
39
|
+
as(value, name) {
|
|
40
|
+
return /** @type {const} */ ({
|
|
41
|
+
operator: 'as',
|
|
42
|
+
value,
|
|
43
|
+
name,
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
|
|
39
47
|
get(value, path) {
|
|
40
48
|
return /** @type {const} */ ({
|
|
41
49
|
operator: 'get',
|
|
@@ -152,6 +160,13 @@ const operators = {
|
|
|
152
160
|
});
|
|
153
161
|
},
|
|
154
162
|
|
|
163
|
+
length(list) {
|
|
164
|
+
return /** @type {const} */ ({
|
|
165
|
+
operator: 'length',
|
|
166
|
+
list,
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
|
|
155
170
|
includes(list, value) {
|
|
156
171
|
value = op(value);
|
|
157
172
|
return /** @type {const} */ ({
|
|
@@ -169,9 +184,29 @@ const operators = {
|
|
|
169
184
|
});
|
|
170
185
|
},
|
|
171
186
|
|
|
172
|
-
|
|
187
|
+
eachValue() {
|
|
188
|
+
return /** @type {const} */ ({
|
|
189
|
+
operator: 'eachValue',
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
eachKey() {
|
|
194
|
+
return /** @type {const} */ ({
|
|
195
|
+
operator: 'eachKey',
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
keys(obj) {
|
|
200
|
+
return /** @type {const} */ ({
|
|
201
|
+
operator: 'keys',
|
|
202
|
+
obj,
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
values(obj) {
|
|
173
207
|
return /** @type {const} */ ({
|
|
174
|
-
operator: '
|
|
208
|
+
operator: 'values',
|
|
209
|
+
obj,
|
|
175
210
|
});
|
|
176
211
|
},
|
|
177
212
|
};
|
package/lib/picks.js
CHANGED
|
@@ -6,6 +6,12 @@ const {
|
|
|
6
6
|
ObjectType,
|
|
7
7
|
Type,
|
|
8
8
|
getTypeInstance,
|
|
9
|
+
array,
|
|
10
|
+
ObjectMapType,
|
|
11
|
+
RecordType,
|
|
12
|
+
number,
|
|
13
|
+
StringType,
|
|
14
|
+
string,
|
|
9
15
|
} = require('xcraft-core-stones');
|
|
10
16
|
|
|
11
17
|
const $o = require('./operators.js');
|
|
@@ -17,13 +23,6 @@ const $o = require('./operators.js');
|
|
|
17
23
|
* @typedef {import('./operators.js').Operator} Operator
|
|
18
24
|
*/
|
|
19
25
|
|
|
20
|
-
/**
|
|
21
|
-
* @typedef {boolean | number | bigint | string | symbol | undefined | null} primitive
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* @typedef {primitive | Function | Date | Error | RegExp} Builtin
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
26
|
/**
|
|
28
27
|
* @typedef {(keyof any)} PathElement
|
|
29
28
|
*/
|
|
@@ -32,26 +31,29 @@ const $o = require('./operators.js');
|
|
|
32
31
|
*/
|
|
33
32
|
|
|
34
33
|
/**
|
|
35
|
-
* @
|
|
34
|
+
* @template T
|
|
35
|
+
* @template {keyof T} K
|
|
36
|
+
* @typedef {Pick<T, K>[keyof Pick<T, K>]} SelectValues
|
|
36
37
|
*/
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
|
-
* @
|
|
40
|
-
*
|
|
40
|
+
* @typedef {{
|
|
41
|
+
* field: (keyof any) | SelectValues<Operators, "eachValue" | "eachKey" | "keys" | "values" | "length">,
|
|
42
|
+
* path: Path
|
|
43
|
+
* }} Context
|
|
41
44
|
*/
|
|
42
45
|
|
|
43
46
|
/**
|
|
44
|
-
* @typedef {ValuePick<any> | ArrayPick<any> | ObjectPick<any>} AnyPick
|
|
47
|
+
* @typedef {ValuePick<any> | ArrayPick<any> | ObjectPick<any> | RecordPick<any,any>} AnyPick
|
|
45
48
|
*/
|
|
46
49
|
|
|
47
50
|
/**
|
|
48
|
-
* @template T
|
|
49
|
-
* @typedef {
|
|
51
|
+
* @template {Type} T
|
|
52
|
+
* @typedef { [T] extends [ArrayType<infer V>] ? ArrayPick<V> : [T] extends [ObjectType<infer S>] ? ObjectPick<S> : [T] extends [ObjectMapType<infer V>] ? RecordPick<StringType,V> : [T] extends [RecordType<infer K,infer V>] ? RecordPick<K,V> : ValuePick<T>} PickOfType
|
|
50
53
|
*/
|
|
51
|
-
|
|
52
54
|
/**
|
|
53
|
-
* @template {
|
|
54
|
-
* @typedef {
|
|
55
|
+
* @template {AnyTypeOrShape} T
|
|
56
|
+
* @typedef {PickOfType<GetType<T>>} PickOf
|
|
55
57
|
*/
|
|
56
58
|
|
|
57
59
|
/**
|
|
@@ -67,16 +69,16 @@ function contextWithPath(context, path) {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
/**
|
|
70
|
-
* @template T
|
|
72
|
+
* @template {AnyTypeOrShape} T
|
|
71
73
|
*/
|
|
72
74
|
class ValuePick {
|
|
73
|
-
/** @type {
|
|
75
|
+
/** @type {T} */
|
|
74
76
|
#type;
|
|
75
77
|
/** @type {Context} */
|
|
76
78
|
#context;
|
|
77
79
|
|
|
78
80
|
/**
|
|
79
|
-
* @param {
|
|
81
|
+
* @param {T} type
|
|
80
82
|
* @param {Context} context
|
|
81
83
|
*/
|
|
82
84
|
constructor(type, context) {
|
|
@@ -97,56 +99,56 @@ class ValuePick {
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
/**
|
|
100
|
-
* @param {T | ValuePick<T>} value
|
|
102
|
+
* @param {t<T> | ValuePick<T>} value
|
|
101
103
|
*/
|
|
102
104
|
eq(value) {
|
|
103
105
|
return $o.eq(this.value, value);
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
/**
|
|
107
|
-
* @param {T | ValuePick<T>} value
|
|
109
|
+
* @param {t<T> | ValuePick<T>} value
|
|
108
110
|
*/
|
|
109
111
|
neq(value) {
|
|
110
112
|
return $o.neq(this.value, value);
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
/**
|
|
114
|
-
* @param {T | ValuePick<T>} value
|
|
116
|
+
* @param {t<T> | ValuePick<T>} value
|
|
115
117
|
*/
|
|
116
118
|
gte(value) {
|
|
117
119
|
return $o.gte(this.value, value);
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
/**
|
|
121
|
-
* @param {T | ValuePick<T>} value
|
|
123
|
+
* @param {t<T> | ValuePick<T>} value
|
|
122
124
|
*/
|
|
123
125
|
gt(value) {
|
|
124
126
|
return $o.gt(this.value, value);
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
/**
|
|
128
|
-
* @param {T | ValuePick<T>} value
|
|
130
|
+
* @param {t<T> | ValuePick<T>} value
|
|
129
131
|
*/
|
|
130
132
|
lte(value) {
|
|
131
133
|
return $o.lte(this.value, value);
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
/**
|
|
135
|
-
* @param {T | ValuePick<T>} value
|
|
137
|
+
* @param {t<T> | ValuePick<T>} value
|
|
136
138
|
*/
|
|
137
139
|
lt(value) {
|
|
138
140
|
return $o.lt(this.value, value);
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
/**
|
|
142
|
-
* @param {(T | ValuePick<T>)[]} list
|
|
144
|
+
* @param {(t<T> | ValuePick<T>)[]} list
|
|
143
145
|
*/
|
|
144
146
|
in(list) {
|
|
145
147
|
return $o.in(this.value, list);
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
/**
|
|
149
|
-
* @param {string | ValuePick<
|
|
151
|
+
* @param {string | ValuePick<StringType>} value
|
|
150
152
|
*/
|
|
151
153
|
like(value) {
|
|
152
154
|
return $o.like(this.value, value);
|
|
@@ -154,16 +156,16 @@ class ValuePick {
|
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
/**
|
|
157
|
-
* @template {
|
|
159
|
+
* @template {ObjectShape} T
|
|
158
160
|
*/
|
|
159
161
|
class ObjectPick {
|
|
160
|
-
/** @type {
|
|
162
|
+
/** @type {ObjectType<T>} */
|
|
161
163
|
#type;
|
|
162
164
|
/** @type {Context} */
|
|
163
165
|
#context;
|
|
164
166
|
|
|
165
167
|
/**
|
|
166
|
-
* @param {
|
|
168
|
+
* @param {ObjectType<T>} type
|
|
167
169
|
* @param {Context} context
|
|
168
170
|
*/
|
|
169
171
|
constructor(type, context) {
|
|
@@ -189,24 +191,117 @@ class ObjectPick {
|
|
|
189
191
|
* @returns {PickOf<T[K]>}
|
|
190
192
|
*/
|
|
191
193
|
get(key) {
|
|
192
|
-
return
|
|
193
|
-
|
|
194
|
+
return makePick(
|
|
195
|
+
this.#type.properties[key],
|
|
194
196
|
contextWithPath(this.#context, key)
|
|
195
197
|
);
|
|
196
198
|
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {t<T[keyof T]> | ValuePick<T[keyof T]>} value
|
|
202
|
+
*/
|
|
203
|
+
includes(value) {
|
|
204
|
+
return $o.includes(this.value, value);
|
|
205
|
+
}
|
|
197
206
|
}
|
|
198
207
|
|
|
199
208
|
/**
|
|
200
|
-
* @template
|
|
209
|
+
* @template {AnyTypeOrShape} K
|
|
210
|
+
* @template {AnyTypeOrShape} V
|
|
211
|
+
*/
|
|
212
|
+
class RecordPick {
|
|
213
|
+
/** @type {RecordType<K,V>} */
|
|
214
|
+
#type;
|
|
215
|
+
/** @type {Context} */
|
|
216
|
+
#context;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @param {RecordType<K,V>} type
|
|
220
|
+
* @param {Context} context
|
|
221
|
+
*/
|
|
222
|
+
constructor(type, context) {
|
|
223
|
+
this.#type = type;
|
|
224
|
+
this.#context = context;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
get value() {
|
|
228
|
+
const {field, path} = this.#context;
|
|
229
|
+
if (path.length > 0) {
|
|
230
|
+
return $o.get($o.field(field), path);
|
|
231
|
+
}
|
|
232
|
+
return $o.field(field);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
get type() {
|
|
236
|
+
return this.#type;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @param {t<K>} key
|
|
241
|
+
* @returns {PickOf<V>}
|
|
242
|
+
*/
|
|
243
|
+
get(key) {
|
|
244
|
+
return makePick(this.#type.valuesType, contextWithPath(this.#context, key));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @param {t<V> | ValuePick<V>} value
|
|
249
|
+
*/
|
|
250
|
+
includes(value) {
|
|
251
|
+
return $o.includes(this.value, value);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @param {(value: PickOf<V>, key: PickOf<K>) => Operator} func
|
|
256
|
+
*/
|
|
257
|
+
some(func) {
|
|
258
|
+
return $o.some(
|
|
259
|
+
this.value,
|
|
260
|
+
func(
|
|
261
|
+
makePick(this.#type.valuesType, {
|
|
262
|
+
field: $o.eachValue(),
|
|
263
|
+
path: [],
|
|
264
|
+
}),
|
|
265
|
+
makePick(this.#type.keysType, {
|
|
266
|
+
field: $o.eachKey(),
|
|
267
|
+
path: [],
|
|
268
|
+
})
|
|
269
|
+
)
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @returns {ArrayPick<K>}
|
|
275
|
+
*/
|
|
276
|
+
keys() {
|
|
277
|
+
return new ArrayPick(array(this.#type.keysType), {
|
|
278
|
+
field: $o.keys(this.value),
|
|
279
|
+
path: [],
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @returns {ArrayPick<V>}
|
|
285
|
+
*/
|
|
286
|
+
values() {
|
|
287
|
+
return new ArrayPick(array(this.#type.valuesType), {
|
|
288
|
+
field: $o.values(this.value),
|
|
289
|
+
path: [],
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @template {AnyTypeOrShape} T
|
|
201
296
|
*/
|
|
202
297
|
class ArrayPick {
|
|
203
|
-
/** @type {ArrayType<
|
|
298
|
+
/** @type {ArrayType<T>} */
|
|
204
299
|
#type;
|
|
205
300
|
/** @type {Context} */
|
|
206
301
|
#context;
|
|
207
302
|
|
|
208
303
|
/**
|
|
209
|
-
* @param {ArrayType<
|
|
304
|
+
* @param {ArrayType<T>} type
|
|
210
305
|
* @param {Context} context
|
|
211
306
|
*/
|
|
212
307
|
constructor(type, context) {
|
|
@@ -228,14 +323,21 @@ class ArrayPick {
|
|
|
228
323
|
* @returns {PickOf<T>}
|
|
229
324
|
*/
|
|
230
325
|
get(index) {
|
|
231
|
-
return
|
|
232
|
-
|
|
326
|
+
return makePick(
|
|
327
|
+
this.#type.valuesType,
|
|
233
328
|
contextWithPath(this.#context, index)
|
|
234
329
|
);
|
|
235
330
|
}
|
|
236
331
|
|
|
332
|
+
get length() {
|
|
333
|
+
return new ValuePick(number, {
|
|
334
|
+
field: $o.length(this.value),
|
|
335
|
+
path: [],
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
237
339
|
/**
|
|
238
|
-
* @param {T | ValuePick<T>} value
|
|
340
|
+
* @param {t<T> | ValuePick<T>} value
|
|
239
341
|
*/
|
|
240
342
|
includes(value) {
|
|
241
343
|
return $o.includes(this.value, value);
|
|
@@ -248,8 +350,8 @@ class ArrayPick {
|
|
|
248
350
|
return $o.some(
|
|
249
351
|
this.value,
|
|
250
352
|
func(
|
|
251
|
-
|
|
252
|
-
field: $o.
|
|
353
|
+
makePick(this.#type.valuesType, {
|
|
354
|
+
field: $o.eachValue(),
|
|
253
355
|
path: [],
|
|
254
356
|
})
|
|
255
357
|
)
|
|
@@ -265,19 +367,20 @@ function isAnyPick(value) {
|
|
|
265
367
|
return (
|
|
266
368
|
value instanceof ValuePick ||
|
|
267
369
|
value instanceof ObjectPick ||
|
|
370
|
+
value instanceof RecordPick ||
|
|
268
371
|
value instanceof ArrayPick
|
|
269
372
|
);
|
|
270
373
|
}
|
|
271
374
|
|
|
272
375
|
/**
|
|
273
|
-
* @template {
|
|
376
|
+
* @template {ObjectShape} T
|
|
274
377
|
*/
|
|
275
378
|
class RowPick {
|
|
276
|
-
/** @type {
|
|
379
|
+
/** @type {ObjectType<T>} */
|
|
277
380
|
#type;
|
|
278
381
|
|
|
279
382
|
/**
|
|
280
|
-
* @param {
|
|
383
|
+
* @param {ObjectType<T>} type
|
|
281
384
|
*/
|
|
282
385
|
constructor(type) {
|
|
283
386
|
this.#type = type;
|
|
@@ -293,18 +396,15 @@ class RowPick {
|
|
|
293
396
|
field: fieldName,
|
|
294
397
|
path: [],
|
|
295
398
|
};
|
|
296
|
-
return
|
|
297
|
-
getTypeInstance(this.#type.properties[fieldName]),
|
|
298
|
-
context
|
|
299
|
-
);
|
|
399
|
+
return makePick(this.#type.properties[fieldName], context);
|
|
300
400
|
}
|
|
301
401
|
}
|
|
302
402
|
|
|
303
403
|
/**
|
|
304
|
-
* @template T
|
|
305
|
-
* @param {
|
|
404
|
+
* @template {Type} T
|
|
405
|
+
* @param {T} type
|
|
306
406
|
* @param {Context} context
|
|
307
|
-
* @returns {
|
|
407
|
+
* @returns {PickOfType<T>}
|
|
308
408
|
*/
|
|
309
409
|
function makeTypePick(type, context) {
|
|
310
410
|
if (type instanceof ArrayType) {
|
|
@@ -313,6 +413,13 @@ function makeTypePick(type, context) {
|
|
|
313
413
|
if (type instanceof ObjectType) {
|
|
314
414
|
return /** @type {any} */ (new ObjectPick(type, context));
|
|
315
415
|
}
|
|
416
|
+
if (type instanceof ObjectMapType) {
|
|
417
|
+
const newType = new RecordType(string, type.valuesType);
|
|
418
|
+
return /** @type {any} */ (new RecordPick(newType, context));
|
|
419
|
+
}
|
|
420
|
+
if (type instanceof RecordType) {
|
|
421
|
+
return /** @type {any} */ (new RecordPick(type, context));
|
|
422
|
+
}
|
|
316
423
|
return /** @type {any} */ (new ValuePick(type, context));
|
|
317
424
|
}
|
|
318
425
|
|
|
@@ -320,7 +427,7 @@ function makeTypePick(type, context) {
|
|
|
320
427
|
* @template {AnyTypeOrShape} T
|
|
321
428
|
* @param {T} typeOrShape
|
|
322
429
|
* @param {Context} context
|
|
323
|
-
* @returns {PickOf<
|
|
430
|
+
* @returns {PickOf<T>}
|
|
324
431
|
*/
|
|
325
432
|
function makePick(typeOrShape, context) {
|
|
326
433
|
const type = getTypeInstance(typeOrShape);
|
|
@@ -328,8 +435,8 @@ function makePick(typeOrShape, context) {
|
|
|
328
435
|
}
|
|
329
436
|
|
|
330
437
|
/**
|
|
331
|
-
* @template {
|
|
332
|
-
* @param {
|
|
438
|
+
* @template {ObjectShape} T
|
|
439
|
+
* @param {ObjectType<T>} type
|
|
333
440
|
* @returns {RowPick<T>}
|
|
334
441
|
*/
|
|
335
442
|
function rowPick(type) {
|
package/lib/query-builder.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
const {ObjectType,
|
|
3
|
+
const {ObjectType, ArrayType, toObjectType} = require('xcraft-core-stones');
|
|
4
4
|
const operators = require('./operators.js');
|
|
5
5
|
const {rowPick, ValuePick, RowPick, ObjectPick} = require('./picks.js');
|
|
6
6
|
const {queryToSql} = require('./query-to-sql.js');
|
|
7
|
-
|
|
8
7
|
/**
|
|
9
8
|
* @typedef {import("./operators.js").Operator} Operator
|
|
10
9
|
*/
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import("./operators.js").Operators} Operators
|
|
12
|
+
*/
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
|
-
* @template T
|
|
15
|
+
* @template {AnyTypeOrShape} T
|
|
14
16
|
* @typedef {import("./picks.js").PickOf<T>} PickOf
|
|
15
17
|
*/
|
|
16
18
|
/**
|
|
@@ -19,17 +21,13 @@ const {queryToSql} = require('./query-to-sql.js');
|
|
|
19
21
|
/**
|
|
20
22
|
* @typedef {import("./picks.js").Path} Path
|
|
21
23
|
*/
|
|
22
|
-
/**
|
|
23
|
-
* @template {{}} T
|
|
24
|
-
* @typedef {import("./picks.js").ObjectTypeOf<T>} ObjectTypeOf
|
|
25
|
-
*/
|
|
26
24
|
|
|
27
25
|
/**
|
|
28
26
|
* @typedef {{
|
|
29
27
|
* db: string,
|
|
30
28
|
* from: string,
|
|
31
29
|
* scope?: ObjectPick<any>
|
|
32
|
-
* select:
|
|
30
|
+
* select: Record<string, AnyPick>,
|
|
33
31
|
* selectOneField?: boolean,
|
|
34
32
|
* where?: Operator,
|
|
35
33
|
* orderBy?: ValuePick<any> | ValuePick<any>[]
|
|
@@ -64,9 +62,73 @@ class FinalQuery {
|
|
|
64
62
|
this.#queryParts = queryParts;
|
|
65
63
|
}
|
|
66
64
|
|
|
65
|
+
#useRaw() {
|
|
66
|
+
return Boolean(this.#queryParts.selectOneField);
|
|
67
|
+
}
|
|
68
|
+
|
|
67
69
|
#getStatement() {
|
|
68
70
|
const {sql, values} = queryToSql(this.#queryParts);
|
|
69
|
-
return this.#database.prepare(sql).bind(values);
|
|
71
|
+
return this.#database.prepare(sql).bind(values).raw(this.#useRaw());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#parseValue(value) {
|
|
75
|
+
return JSON.parse(value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#identity(value) {
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#getMapper(type) {
|
|
83
|
+
if (type instanceof ArrayType || type instanceof ObjectType) {
|
|
84
|
+
return this.#parseValue;
|
|
85
|
+
}
|
|
86
|
+
return this.#identity;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#getMappers() {
|
|
90
|
+
const select = this.#queryParts.select;
|
|
91
|
+
const mappers = Object.fromEntries(
|
|
92
|
+
Object.entries(select).map(([name, selectValue]) => [
|
|
93
|
+
name,
|
|
94
|
+
this.#getMapper(selectValue.type),
|
|
95
|
+
])
|
|
96
|
+
);
|
|
97
|
+
return mappers;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#getRawMappers() {
|
|
101
|
+
const select = this.#queryParts.select;
|
|
102
|
+
const mappers = Object.values(select).map((selectValue) =>
|
|
103
|
+
this.#getMapper(selectValue.type)
|
|
104
|
+
);
|
|
105
|
+
return mappers;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#mapRow(row, mappers) {
|
|
109
|
+
for (const [name, value] of Object.entries(row)) {
|
|
110
|
+
row[name] = mappers[name](value);
|
|
111
|
+
}
|
|
112
|
+
return row;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#mapRawRow(row, mappers) {
|
|
116
|
+
for (const [i, value] of row.entries()) {
|
|
117
|
+
row[i] = mappers[i](value);
|
|
118
|
+
}
|
|
119
|
+
return row;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#getMapRow() {
|
|
123
|
+
if (this.#useRaw()) {
|
|
124
|
+
const mappers = this.#getRawMappers();
|
|
125
|
+
if (this.#queryParts.selectOneField) {
|
|
126
|
+
return (row) => (row ? this.#mapRawRow(row, mappers)[0] : row);
|
|
127
|
+
}
|
|
128
|
+
return (row) => row && this.#mapRawRow(row, mappers);
|
|
129
|
+
}
|
|
130
|
+
const mappers = this.#getMappers();
|
|
131
|
+
return (row) => row && this.#mapRow(row, mappers);
|
|
70
132
|
}
|
|
71
133
|
|
|
72
134
|
/**
|
|
@@ -80,39 +142,32 @@ class FinalQuery {
|
|
|
80
142
|
* @returns {R}
|
|
81
143
|
*/
|
|
82
144
|
get() {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
return result;
|
|
145
|
+
const mapRow = this.#getMapRow();
|
|
146
|
+
const row = this.#getStatement().get();
|
|
147
|
+
return mapRow(row);
|
|
88
148
|
}
|
|
89
149
|
|
|
90
150
|
/**
|
|
91
151
|
* @returns {R[]}
|
|
92
152
|
*/
|
|
93
153
|
all() {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
return this.#getStatement().all();
|
|
154
|
+
const mapRow = this.#getMapRow();
|
|
155
|
+
return Array.from(this.#getStatement().iterate(), mapRow);
|
|
98
156
|
}
|
|
99
157
|
|
|
100
158
|
/**
|
|
101
159
|
* @returns {Generator<R>}
|
|
102
160
|
*/
|
|
103
161
|
*iterate() {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
return;
|
|
162
|
+
const mapRow = this.#getMapRow();
|
|
163
|
+
for (const row of this.#getStatement().iterate()) {
|
|
164
|
+
yield mapRow(row);
|
|
109
165
|
}
|
|
110
|
-
yield* this.#getStatement().iterate();
|
|
111
166
|
}
|
|
112
167
|
}
|
|
113
168
|
|
|
114
169
|
/**
|
|
115
|
-
* @template {
|
|
170
|
+
* @template {ObjectShape} T
|
|
116
171
|
* @template R
|
|
117
172
|
* @extends {FinalQuery<R>}
|
|
118
173
|
*/
|
|
@@ -123,7 +178,7 @@ class ScopedSelectQuery extends FinalQuery {
|
|
|
123
178
|
|
|
124
179
|
/**
|
|
125
180
|
* @param {*} database
|
|
126
|
-
* @param {
|
|
181
|
+
* @param {ObjectType<T>} type
|
|
127
182
|
* @param {QueryParts<'db' | 'from' | 'select' | 'scope'>} queryParts
|
|
128
183
|
*/
|
|
129
184
|
constructor(database, type, queryParts) {
|
|
@@ -161,7 +216,7 @@ class ScopedSelectQuery extends FinalQuery {
|
|
|
161
216
|
}
|
|
162
217
|
|
|
163
218
|
/**
|
|
164
|
-
* @template {
|
|
219
|
+
* @template {ObjectShape} T
|
|
165
220
|
* @template R
|
|
166
221
|
* @extends {FinalQuery<R>}
|
|
167
222
|
*/
|
|
@@ -172,7 +227,7 @@ class SelectQuery extends FinalQuery {
|
|
|
172
227
|
|
|
173
228
|
/**
|
|
174
229
|
* @param {*} database
|
|
175
|
-
* @param {
|
|
230
|
+
* @param {ObjectType<T>} type
|
|
176
231
|
* @param {QueryParts<'db' | 'from' | 'select'>} queryParts
|
|
177
232
|
*/
|
|
178
233
|
constructor(database, type, queryParts) {
|
|
@@ -211,7 +266,7 @@ class SelectQuery extends FinalQuery {
|
|
|
211
266
|
}
|
|
212
267
|
|
|
213
268
|
/**
|
|
214
|
-
* @template {
|
|
269
|
+
* @template {ObjectShape} T
|
|
215
270
|
*/
|
|
216
271
|
class ScopedFromQuery {
|
|
217
272
|
#database;
|
|
@@ -220,7 +275,7 @@ class ScopedFromQuery {
|
|
|
220
275
|
|
|
221
276
|
/**
|
|
222
277
|
* @param {*} database
|
|
223
|
-
* @param {
|
|
278
|
+
* @param {ObjectType<T>} type
|
|
224
279
|
* @param {QueryParts<'db' | 'from' | 'scope'>} queryParts
|
|
225
280
|
*/
|
|
226
281
|
constructor(database, type, queryParts) {
|
|
@@ -233,13 +288,13 @@ class ScopedFromQuery {
|
|
|
233
288
|
* Select field
|
|
234
289
|
* @template {keyof T} F
|
|
235
290
|
* @param {F} value
|
|
236
|
-
* @returns {ScopedSelectQuery<T, T[F]
|
|
291
|
+
* @returns {ScopedSelectQuery<T, t<T[F]>>}
|
|
237
292
|
*/
|
|
238
293
|
field(value) {
|
|
239
294
|
const scope = this.#queryParts.scope;
|
|
240
295
|
const queryParts = {
|
|
241
296
|
...this.#queryParts,
|
|
242
|
-
select: scope.get(value),
|
|
297
|
+
select: {[value]: scope.get(value)},
|
|
243
298
|
selectOneField: true,
|
|
244
299
|
};
|
|
245
300
|
return new ScopedSelectQuery(this.#database, this.#type, queryParts);
|
|
@@ -249,26 +304,30 @@ class ScopedFromQuery {
|
|
|
249
304
|
* Select fields
|
|
250
305
|
* @template {(keyof T)[]} F
|
|
251
306
|
* @param {F} values
|
|
252
|
-
* @returns {ScopedSelectQuery<T,
|
|
307
|
+
* @returns {ScopedSelectQuery<T, t<Pick<T, F[number]>>>}
|
|
253
308
|
*/
|
|
254
309
|
fields(values) {
|
|
255
310
|
const scope = this.#queryParts.scope;
|
|
256
311
|
const queryParts = {
|
|
257
312
|
...this.#queryParts,
|
|
258
|
-
select:
|
|
313
|
+
select: Object.fromEntries(
|
|
314
|
+
values.map((value) => [value, scope.get(value)])
|
|
315
|
+
),
|
|
259
316
|
};
|
|
260
317
|
return new ScopedSelectQuery(this.#database, this.#type, queryParts);
|
|
261
318
|
}
|
|
262
319
|
|
|
263
320
|
/**
|
|
264
|
-
* @template {Record<string,
|
|
265
|
-
* @param {(obj: ObjectPick<T>) =>
|
|
266
|
-
* @returns {ScopedSelectQuery<T, R
|
|
321
|
+
* @template {Record<string, AnyPick>} R
|
|
322
|
+
* @param {(obj: ObjectPick<T>) => R} fct
|
|
323
|
+
* @returns {ScopedSelectQuery<T, t<{[K in keyof R]: R[K]["type"]}>>}
|
|
267
324
|
*/
|
|
268
325
|
select(fct) {
|
|
269
326
|
const scope = this.#queryParts.scope;
|
|
270
|
-
const
|
|
271
|
-
|
|
327
|
+
const queryParts = {
|
|
328
|
+
...this.#queryParts,
|
|
329
|
+
select: fct(scope),
|
|
330
|
+
};
|
|
272
331
|
return new ScopedSelectQuery(this.#database, this.#type, queryParts);
|
|
273
332
|
}
|
|
274
333
|
|
|
@@ -287,7 +346,7 @@ class ScopedFromQuery {
|
|
|
287
346
|
}
|
|
288
347
|
|
|
289
348
|
/**
|
|
290
|
-
* @template {
|
|
349
|
+
* @template {ObjectShape} T
|
|
291
350
|
*/
|
|
292
351
|
class FromQuery {
|
|
293
352
|
#database;
|
|
@@ -296,7 +355,7 @@ class FromQuery {
|
|
|
296
355
|
|
|
297
356
|
/**
|
|
298
357
|
* @param {*} database
|
|
299
|
-
* @param {
|
|
358
|
+
* @param {ObjectType<T>} type
|
|
300
359
|
* @param {QueryParts<'db' | 'from'>} queryParts
|
|
301
360
|
*/
|
|
302
361
|
constructor(database, type, queryParts) {
|
|
@@ -306,7 +365,7 @@ class FromQuery {
|
|
|
306
365
|
}
|
|
307
366
|
|
|
308
367
|
/**
|
|
309
|
-
* @template {
|
|
368
|
+
* @template {ObjectShape} U
|
|
310
369
|
* @param {(obj: RowPick<T>) => ObjectPick<U>} fct
|
|
311
370
|
* @returns {ScopedFromQuery<U>}
|
|
312
371
|
*/
|
|
@@ -323,25 +382,29 @@ class FromQuery {
|
|
|
323
382
|
* Select fields
|
|
324
383
|
* @template {(keyof T)[]} F
|
|
325
384
|
* @param {F} fields
|
|
326
|
-
* @returns {SelectQuery<T,
|
|
385
|
+
* @returns {SelectQuery<T, t<Pick<T, F[number]>>>}
|
|
327
386
|
*/
|
|
328
387
|
fields(fields) {
|
|
329
388
|
const row = rowPick(this.#type);
|
|
330
389
|
const queryParts = {
|
|
331
390
|
...this.#queryParts,
|
|
332
|
-
select:
|
|
391
|
+
select: Object.fromEntries(
|
|
392
|
+
fields.map((fieldName) => [fieldName, row.field(fieldName)])
|
|
393
|
+
),
|
|
333
394
|
};
|
|
334
395
|
return new SelectQuery(this.#database, this.#type, queryParts);
|
|
335
396
|
}
|
|
336
397
|
|
|
337
398
|
/**
|
|
338
|
-
* @template {Record<string,
|
|
339
|
-
* @param {(obj: RowPick<T>) =>
|
|
340
|
-
* @returns {SelectQuery<T, R
|
|
399
|
+
* @template {Record<string, AnyPick>} R
|
|
400
|
+
* @param {(obj: RowPick<T>) => R} fct
|
|
401
|
+
* @returns {SelectQuery<T, t<{[K in keyof R]: R[K]["type"]}>>}
|
|
341
402
|
*/
|
|
342
403
|
select(fct) {
|
|
343
|
-
const
|
|
344
|
-
|
|
404
|
+
const queryParts = {
|
|
405
|
+
...this.#queryParts,
|
|
406
|
+
select: fct(rowPick(this.#type)),
|
|
407
|
+
};
|
|
345
408
|
return new SelectQuery(this.#database, this.#type, queryParts);
|
|
346
409
|
}
|
|
347
410
|
|
|
@@ -378,15 +441,15 @@ class DbQuery {
|
|
|
378
441
|
* @template {AnyObjectShape} T
|
|
379
442
|
* @param {string} tableName
|
|
380
443
|
* @param {T} shape
|
|
381
|
-
* @returns {FromQuery<
|
|
444
|
+
* @returns {FromQuery<GetShape<T>>}
|
|
382
445
|
*/
|
|
383
446
|
from(tableName, shape) {
|
|
384
|
-
const type =
|
|
447
|
+
const type = toObjectType(shape);
|
|
385
448
|
const queryParts = {
|
|
386
449
|
...this.#queryParts,
|
|
387
450
|
from: tableName,
|
|
388
451
|
};
|
|
389
|
-
return
|
|
452
|
+
return new FromQuery(this.#database, type, queryParts);
|
|
390
453
|
}
|
|
391
454
|
}
|
|
392
455
|
|
package/lib/query-to-sql.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const {sql} = require('./operator-to-sql.js');
|
|
7
|
-
const {isAnyPick} = require('./picks.js');
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* @param {QueryObj["select"]} select
|
|
@@ -12,14 +11,8 @@ const {isAnyPick} = require('./picks.js');
|
|
|
12
11
|
* @returns {string}
|
|
13
12
|
*/
|
|
14
13
|
function selectFields(select, values) {
|
|
15
|
-
if (Array.isArray(select)) {
|
|
16
|
-
return select.map((rep) => sql(rep.value, values)).join(', ');
|
|
17
|
-
}
|
|
18
|
-
if (isAnyPick(select)) {
|
|
19
|
-
return sql(select.value, values);
|
|
20
|
-
}
|
|
21
14
|
return Object.entries(select)
|
|
22
|
-
.map(([name,
|
|
15
|
+
.map(([name, value]) => `${sql(value, values)} AS ${name}`)
|
|
23
16
|
.join(', ');
|
|
24
17
|
}
|
|
25
18
|
|