jsonbadger 0.5.0
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 +114 -0
- package/docs/api.md +152 -0
- package/docs/examples.md +612 -0
- package/docs/local-integration-testing.md +17 -0
- package/docs/query-translation.md +98 -0
- package/index.js +2 -0
- package/package.json +58 -0
- package/src/connection/connect.js +56 -0
- package/src/connection/disconnect.js +16 -0
- package/src/connection/pool-store.js +46 -0
- package/src/connection/server-capabilities.js +59 -0
- package/src/constants/defaults.js +20 -0
- package/src/constants/id-strategies.js +29 -0
- package/src/debug/debug-logger.js +15 -0
- package/src/errors/query-error.js +23 -0
- package/src/errors/validation-error.js +23 -0
- package/src/field-types/base-field-type.js +140 -0
- package/src/field-types/builtins/advanced.js +365 -0
- package/src/field-types/builtins/index.js +585 -0
- package/src/field-types/registry.js +122 -0
- package/src/index.js +36 -0
- package/src/migration/ensure-index.js +155 -0
- package/src/migration/ensure-schema.js +16 -0
- package/src/migration/ensure-table.js +31 -0
- package/src/migration/schema-indexes-resolver.js +6 -0
- package/src/model/document-instance.js +540 -0
- package/src/model/model-factory.js +555 -0
- package/src/query/limit-skip-compiler.js +31 -0
- package/src/query/operators/all.js +10 -0
- package/src/query/operators/contains.js +7 -0
- package/src/query/operators/elem-match.js +3 -0
- package/src/query/operators/eq.js +6 -0
- package/src/query/operators/gt.js +16 -0
- package/src/query/operators/gte.js +16 -0
- package/src/query/operators/has-all-keys.js +11 -0
- package/src/query/operators/has-any-keys.js +11 -0
- package/src/query/operators/has-key.js +6 -0
- package/src/query/operators/in.js +12 -0
- package/src/query/operators/index.js +60 -0
- package/src/query/operators/jsonpath-exists.js +15 -0
- package/src/query/operators/jsonpath-match.js +15 -0
- package/src/query/operators/lt.js +16 -0
- package/src/query/operators/lte.js +16 -0
- package/src/query/operators/ne.js +6 -0
- package/src/query/operators/nin.js +12 -0
- package/src/query/operators/regex.js +8 -0
- package/src/query/operators/size.js +16 -0
- package/src/query/path-parser.js +43 -0
- package/src/query/query-builder.js +93 -0
- package/src/query/sort-compiler.js +30 -0
- package/src/query/where-compiler.js +477 -0
- package/src/schema/field-definition-parser.js +218 -0
- package/src/schema/path-introspection.js +82 -0
- package/src/schema/schema-compiler.js +212 -0
- package/src/schema/schema.js +234 -0
- package/src/sql/parameter-binder.js +13 -0
- package/src/sql/sql-runner.js +31 -0
- package/src/utils/array.js +31 -0
- package/src/utils/assert.js +27 -0
- package/src/utils/json-safe.js +9 -0
- package/src/utils/json.js +21 -0
- package/src/utils/object-path.js +33 -0
- package/src/utils/object.js +168 -0
- package/src/utils/value.js +30 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import {
|
|
2
|
+
all_operator,
|
|
3
|
+
contains_operator,
|
|
4
|
+
elem_match_operator,
|
|
5
|
+
eq_operator,
|
|
6
|
+
gt_operator,
|
|
7
|
+
gte_operator,
|
|
8
|
+
has_all_keys_operator,
|
|
9
|
+
has_any_keys_operator,
|
|
10
|
+
has_key_operator,
|
|
11
|
+
in_operator,
|
|
12
|
+
jsonpath_exists_operator,
|
|
13
|
+
jsonpath_match_operator,
|
|
14
|
+
lt_operator,
|
|
15
|
+
lte_operator,
|
|
16
|
+
ne_operator,
|
|
17
|
+
nin_operator,
|
|
18
|
+
regex_operator,
|
|
19
|
+
size_operator
|
|
20
|
+
} from '#src/query/operators/index.js';
|
|
21
|
+
|
|
22
|
+
import {build_elem_text_expression, build_json_expression, build_text_expression, parse_path} from '#src/query/path-parser.js';
|
|
23
|
+
|
|
24
|
+
import {create_parameter_state} from '#src/sql/parameter-binder.js';
|
|
25
|
+
|
|
26
|
+
import {quote_identifier} from '#src/utils/assert.js';
|
|
27
|
+
import {build_nested_object, split_dot_path} from '#src/utils/object-path.js';
|
|
28
|
+
import {is_object} from '#src/utils/value.js';
|
|
29
|
+
|
|
30
|
+
// TODO: might as well split into multiple files within the same directory
|
|
31
|
+
|
|
32
|
+
function where_compiler(query_filter, compile_options, start_index) {
|
|
33
|
+
const filter_object = query_filter ?? {};
|
|
34
|
+
const options = compile_options ?? {};
|
|
35
|
+
const parameter_state = create_parameter_state(start_index);
|
|
36
|
+
const clause_list = [];
|
|
37
|
+
const filter_entries = Object.entries(filter_object);
|
|
38
|
+
let entry_index = 0;
|
|
39
|
+
|
|
40
|
+
while(entry_index < filter_entries.length) {
|
|
41
|
+
const entry_pair = filter_entries[entry_index];
|
|
42
|
+
const path_value = entry_pair[0];
|
|
43
|
+
const comparison_value = entry_pair[1];
|
|
44
|
+
const clause = compile_field_clause(path_value, comparison_value, options, parameter_state);
|
|
45
|
+
|
|
46
|
+
if(clause) {
|
|
47
|
+
clause_list.push(clause);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
entry_index += 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
sql: clause_list.length > 0 ? clause_list.join(' AND ') : 'TRUE',
|
|
55
|
+
params: parameter_state.params,
|
|
56
|
+
next_index: parameter_state.current_index
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function compile_field_clause(path_value, comparison_value, compile_options, parameter_state) {
|
|
61
|
+
const data_column_name = compile_options?.data_column ?? 'data';
|
|
62
|
+
const data_column_reference = quote_identifier(data_column_name);
|
|
63
|
+
const schema_instance = compile_options?.schema ?? null;
|
|
64
|
+
const is_array_path = should_use_array_contains(path_value, comparison_value, schema_instance);
|
|
65
|
+
|
|
66
|
+
if(comparison_value instanceof RegExp) {
|
|
67
|
+
const text_expression = build_text_expression(data_column_reference, path_value);
|
|
68
|
+
return regex_operator(text_expression, comparison_value.source, comparison_value.flags, parameter_state);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if(is_object(comparison_value)) {
|
|
72
|
+
if(comparison_value.$elem_match !== undefined) {
|
|
73
|
+
return compile_elem_match_clause(path_value, comparison_value.$elem_match, data_column_reference, parameter_state);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if(has_operator_entries(comparison_value)) {
|
|
77
|
+
return compile_operator_object(path_value, comparison_value, data_column_reference, schema_instance, parameter_state);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const nested_object = build_nested_object(path_value, comparison_value);
|
|
81
|
+
return contains_operator(data_column_reference, nested_object, parameter_state);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if(is_array_path) {
|
|
85
|
+
const casted_array_value = cast_array_contains_value(path_value, comparison_value, schema_instance);
|
|
86
|
+
const nested_object = build_nested_object(path_value, [casted_array_value]);
|
|
87
|
+
return contains_operator(data_column_reference, nested_object, parameter_state);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const text_expression = build_text_expression(data_column_reference, path_value);
|
|
91
|
+
const casted_comparison_value = cast_query_value(path_value, comparison_value, schema_instance);
|
|
92
|
+
return eq_operator(text_expression, casted_comparison_value, parameter_state);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function compile_operator_object(path_value, operator_definition, data_column_reference, schema_instance, parameter_state) {
|
|
96
|
+
const clause_list = [];
|
|
97
|
+
const operator_entries = Object.entries(operator_definition);
|
|
98
|
+
const path_info = parse_path(path_value);
|
|
99
|
+
const regex_options = operator_definition.$options || '';
|
|
100
|
+
const has_array_root = is_array_root(schema_instance, path_info.root_path) && path_info.child_segments.length > 0;
|
|
101
|
+
let entry_index = 0;
|
|
102
|
+
|
|
103
|
+
while(entry_index < operator_entries.length) {
|
|
104
|
+
const operator_entry = operator_entries[entry_index];
|
|
105
|
+
const operator_name = operator_entry[0];
|
|
106
|
+
const operator_value = operator_entry[1];
|
|
107
|
+
|
|
108
|
+
if(operator_name === '$options') {
|
|
109
|
+
entry_index += 1;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if(has_array_root) {
|
|
114
|
+
clause_list.push(
|
|
115
|
+
compile_nested_array_operator(
|
|
116
|
+
operator_name,
|
|
117
|
+
operator_value,
|
|
118
|
+
regex_options,
|
|
119
|
+
path_info,
|
|
120
|
+
data_column_reference,
|
|
121
|
+
parameter_state
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
entry_index += 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
clause_list.push(
|
|
129
|
+
compile_standard_operator(
|
|
130
|
+
operator_name,
|
|
131
|
+
operator_value,
|
|
132
|
+
regex_options,
|
|
133
|
+
path_value,
|
|
134
|
+
data_column_reference,
|
|
135
|
+
schema_instance,
|
|
136
|
+
parameter_state
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
entry_index += 1;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return clause_list.join(' AND ');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function compile_standard_operator(operator_name, operator_value, regex_options, path_value, data_column_reference, schema_instance, parameter_state) {
|
|
146
|
+
const text_expression = build_text_expression(data_column_reference, path_value);
|
|
147
|
+
const json_expression = build_json_expression(data_column_reference, path_value);
|
|
148
|
+
const casted_operator_value = cast_operator_value(path_value, operator_name, operator_value, schema_instance);
|
|
149
|
+
|
|
150
|
+
if(operator_name === '$eq') {
|
|
151
|
+
return eq_operator(text_expression, casted_operator_value, parameter_state);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if(operator_name === '$ne') {
|
|
155
|
+
return ne_operator(text_expression, casted_operator_value, parameter_state);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if(operator_name === '$gt') {
|
|
159
|
+
return gt_operator(text_expression, casted_operator_value, parameter_state);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if(operator_name === '$gte') {
|
|
163
|
+
return gte_operator(text_expression, casted_operator_value, parameter_state);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if(operator_name === '$lt') {
|
|
167
|
+
return lt_operator(text_expression, casted_operator_value, parameter_state);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if(operator_name === '$lte') {
|
|
171
|
+
return lte_operator(text_expression, casted_operator_value, parameter_state);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if(operator_name === '$in') {
|
|
175
|
+
return in_operator(text_expression, casted_operator_value, parameter_state);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if(operator_name === '$nin') {
|
|
179
|
+
return nin_operator(text_expression, casted_operator_value, parameter_state);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if(operator_name === '$regex') {
|
|
183
|
+
return regex_operator(text_expression, operator_value, regex_options, parameter_state);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if(operator_name === '$contains') {
|
|
187
|
+
const nested_object = build_nested_object(path_value, operator_value);
|
|
188
|
+
return contains_operator(data_column_reference, nested_object, parameter_state);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if(operator_name === '$has_key') {
|
|
192
|
+
return has_key_operator(json_expression, operator_value, parameter_state);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if(operator_name === '$has_any_keys') {
|
|
196
|
+
return has_any_keys_operator(json_expression, operator_value, parameter_state);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if(operator_name === '$has_all_keys') {
|
|
200
|
+
return has_all_keys_operator(json_expression, operator_value, parameter_state);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if(operator_name === '$json_path_exists') {
|
|
204
|
+
return jsonpath_exists_operator(json_expression, operator_value, parameter_state);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if(operator_name === '$json_path_match') {
|
|
208
|
+
return jsonpath_match_operator(json_expression, operator_value, parameter_state);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if(operator_name === '$all') {
|
|
212
|
+
return all_operator(json_expression, casted_operator_value, parameter_state);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if(operator_name === '$size') {
|
|
216
|
+
return size_operator(json_expression, operator_value, parameter_state);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
throw new Error('Unsupported operator: ' + operator_name);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function compile_nested_array_operator(operator_name, operator_value, regex_options, path_info, data_column_reference, parameter_state) {
|
|
223
|
+
const array_expression = build_json_expression(data_column_reference, path_info.root_path);
|
|
224
|
+
const elem_expression = build_elem_text_expression('elem', path_info.child_segments);
|
|
225
|
+
const predicate = compile_scalar_operator(operator_name, operator_value, regex_options, elem_expression, parameter_state);
|
|
226
|
+
|
|
227
|
+
return elem_match_operator(array_expression, predicate);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function compile_elem_match_clause(path_value, elem_match_value, data_column_reference, parameter_state) {
|
|
231
|
+
const array_expression = build_json_expression(data_column_reference, path_value);
|
|
232
|
+
const predicate_list = [];
|
|
233
|
+
|
|
234
|
+
if(elem_match_value instanceof RegExp) {
|
|
235
|
+
predicate_list.push(regex_operator("elem #>> '{}'", elem_match_value.source, elem_match_value.flags, parameter_state));
|
|
236
|
+
return elem_match_operator(array_expression, predicate_list.join(' AND '));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if(!is_object(elem_match_value)) {
|
|
240
|
+
predicate_list.push(eq_operator("elem #>> '{}'", elem_match_value, parameter_state));
|
|
241
|
+
return elem_match_operator(array_expression, predicate_list.join(' AND '));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if(has_operator_entries(elem_match_value)) {
|
|
245
|
+
const operator_entries = Object.entries(elem_match_value);
|
|
246
|
+
const option_value = elem_match_value.$options || '';
|
|
247
|
+
let operator_index = 0;
|
|
248
|
+
|
|
249
|
+
while(operator_index < operator_entries.length) {
|
|
250
|
+
const operator_entry = operator_entries[operator_index];
|
|
251
|
+
const operator_name = operator_entry[0];
|
|
252
|
+
const operator_value = operator_entry[1];
|
|
253
|
+
|
|
254
|
+
if(operator_name === '$options') {
|
|
255
|
+
operator_index += 1;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
predicate_list.push(
|
|
260
|
+
compile_scalar_operator(operator_name, operator_value, option_value, "elem #>> '{}'", parameter_state)
|
|
261
|
+
);
|
|
262
|
+
operator_index += 1;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return elem_match_operator(array_expression, predicate_list.join(' AND '));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const elem_entries = Object.entries(elem_match_value);
|
|
269
|
+
let entry_index = 0;
|
|
270
|
+
|
|
271
|
+
while(entry_index < elem_entries.length) {
|
|
272
|
+
const entry_pair = elem_entries[entry_index];
|
|
273
|
+
const nested_path = entry_pair[0];
|
|
274
|
+
const nested_value = entry_pair[1];
|
|
275
|
+
const nested_segments = split_dot_path(nested_path);
|
|
276
|
+
const elem_expression = build_elem_text_expression('elem', nested_segments);
|
|
277
|
+
|
|
278
|
+
if(nested_value instanceof RegExp) {
|
|
279
|
+
predicate_list.push(regex_operator(elem_expression, nested_value.source, nested_value.flags, parameter_state));
|
|
280
|
+
entry_index += 1;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if(is_object(nested_value) && has_operator_entries(nested_value)) {
|
|
285
|
+
const nested_operator_entries = Object.entries(nested_value);
|
|
286
|
+
const option_value = nested_value.$options || '';
|
|
287
|
+
let operator_index = 0;
|
|
288
|
+
|
|
289
|
+
while(operator_index < nested_operator_entries.length) {
|
|
290
|
+
const operator_entry = nested_operator_entries[operator_index];
|
|
291
|
+
const operator_name = operator_entry[0];
|
|
292
|
+
const operator_value = operator_entry[1];
|
|
293
|
+
|
|
294
|
+
if(operator_name === '$options') {
|
|
295
|
+
operator_index += 1;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
predicate_list.push(
|
|
300
|
+
compile_scalar_operator(operator_name, operator_value, option_value, elem_expression, parameter_state)
|
|
301
|
+
);
|
|
302
|
+
operator_index += 1;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
entry_index += 1;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
predicate_list.push(eq_operator(elem_expression, nested_value, parameter_state));
|
|
310
|
+
entry_index += 1;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return elem_match_operator(array_expression, predicate_list.join(' AND '));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function compile_scalar_operator(operator_name, operator_value, regex_options, elem_expression, parameter_state) {
|
|
317
|
+
if(operator_name === '$eq') {
|
|
318
|
+
return eq_operator(elem_expression, operator_value, parameter_state);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if(operator_name === '$ne') {
|
|
322
|
+
return ne_operator(elem_expression, operator_value, parameter_state);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if(operator_name === '$gt') {
|
|
326
|
+
return gt_operator(elem_expression, operator_value, parameter_state);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if(operator_name === '$gte') {
|
|
330
|
+
return gte_operator(elem_expression, operator_value, parameter_state);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if(operator_name === '$lt') {
|
|
334
|
+
return lt_operator(elem_expression, operator_value, parameter_state);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if(operator_name === '$lte') {
|
|
338
|
+
return lte_operator(elem_expression, operator_value, parameter_state);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if(operator_name === '$in') {
|
|
342
|
+
return in_operator(elem_expression, operator_value, parameter_state);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if(operator_name === '$nin') {
|
|
346
|
+
return nin_operator(elem_expression, operator_value, parameter_state);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if(operator_name === '$regex') {
|
|
350
|
+
return regex_operator(elem_expression, operator_value, regex_options, parameter_state);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
throw new Error('Unsupported operator inside $elem_match: ' + operator_name);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function cast_operator_value(path_value, operator_name, operator_value, schema_instance) {
|
|
357
|
+
if(
|
|
358
|
+
operator_name === '$eq' ||
|
|
359
|
+
operator_name === '$ne' ||
|
|
360
|
+
operator_name === '$gt' ||
|
|
361
|
+
operator_name === '$gte' ||
|
|
362
|
+
operator_name === '$lt' ||
|
|
363
|
+
operator_name === '$lte'
|
|
364
|
+
) {
|
|
365
|
+
return cast_query_value(path_value, operator_value, schema_instance);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if(operator_name === '$in' || operator_name === '$nin') {
|
|
369
|
+
if(!Array.isArray(operator_value)) {
|
|
370
|
+
return [cast_query_value(path_value, operator_value, schema_instance)];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const casted_values = [];
|
|
374
|
+
let value_index = 0;
|
|
375
|
+
|
|
376
|
+
while(value_index < operator_value.length) {
|
|
377
|
+
casted_values.push(cast_query_value(path_value, operator_value[value_index], schema_instance));
|
|
378
|
+
value_index += 1;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return casted_values;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if(operator_name === '$all') {
|
|
385
|
+
if(!Array.isArray(operator_value)) {
|
|
386
|
+
return operator_value;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const casted_values = [];
|
|
390
|
+
let value_index = 0;
|
|
391
|
+
|
|
392
|
+
while(value_index < operator_value.length) {
|
|
393
|
+
casted_values.push(cast_array_contains_value(path_value, operator_value[value_index], schema_instance));
|
|
394
|
+
value_index += 1;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return casted_values;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return operator_value;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function cast_query_value(path_value, comparison_value, schema_instance) {
|
|
404
|
+
if(comparison_value === undefined || comparison_value === null) {
|
|
405
|
+
return comparison_value;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const field_type = resolve_query_field_type(schema_instance, path_value);
|
|
409
|
+
|
|
410
|
+
if(!field_type || typeof field_type.cast !== 'function') {
|
|
411
|
+
return comparison_value;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return field_type.cast(comparison_value, {
|
|
415
|
+
path: path_value,
|
|
416
|
+
mode: 'query'
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function cast_array_contains_value(path_value, comparison_value, schema_instance) {
|
|
421
|
+
const field_type = resolve_query_field_type(schema_instance, path_value);
|
|
422
|
+
|
|
423
|
+
if(!field_type || field_type.instance !== 'Array' || !field_type.of_field_type || typeof field_type.of_field_type.cast !== 'function') {
|
|
424
|
+
return comparison_value;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return field_type.of_field_type.cast(comparison_value, {
|
|
428
|
+
path: path_value,
|
|
429
|
+
mode: 'query'
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function resolve_query_field_type(schema_instance, path_value) {
|
|
434
|
+
if(!schema_instance || typeof schema_instance.path !== 'function') {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return schema_instance.path(path_value);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function should_use_array_contains(path_value, comparison_value, schema_instance) {
|
|
442
|
+
if(!schema_instance || typeof schema_instance.is_array_root !== 'function') {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if(is_object(comparison_value) || comparison_value instanceof RegExp) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return schema_instance.is_array_root(path_value);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function is_array_root(schema_instance, root_path) {
|
|
454
|
+
if(!schema_instance || typeof schema_instance.is_array_root !== 'function') {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return schema_instance.is_array_root(root_path);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function has_operator_entries(object_value) {
|
|
462
|
+
const keys = Object.keys(object_value);
|
|
463
|
+
let key_index = 0;
|
|
464
|
+
|
|
465
|
+
while(key_index < keys.length) {
|
|
466
|
+
if(keys[key_index][0] === '$') {
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
key_index += 1;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export default where_compiler;
|
|
477
|
+
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Assumptions and trade-offs:
|
|
3
|
+
- Type-key rules are strict: explicit declarations win when `type` is a supported type reference.
|
|
4
|
+
- Plain-object `of` definitions for Array/Map are normalized to Mixed in this phase.
|
|
5
|
+
*/
|
|
6
|
+
import {default_field_type_registry} from '#src/field-types/registry.js';
|
|
7
|
+
import {is_array, is_not_array} from '#src/utils/array.js';
|
|
8
|
+
|
|
9
|
+
export default function field_definition_parser(schema_definition, registry_instance) {
|
|
10
|
+
const parse_state = {
|
|
11
|
+
field_types: Object.create(null),
|
|
12
|
+
object_paths: new Set()
|
|
13
|
+
};
|
|
14
|
+
const schema_root = schema_definition || {};
|
|
15
|
+
const field_registry = registry_instance || default_field_type_registry;
|
|
16
|
+
|
|
17
|
+
parse_schema_object(schema_root, '', field_registry, parse_state);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
field_types: parse_state.field_types,
|
|
21
|
+
object_paths: parse_state.object_paths
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parse_schema_object(schema_object, parent_path, field_registry, parse_state) {
|
|
26
|
+
if(!is_plain_object(schema_object)) {
|
|
27
|
+
throw new Error('Schema definition must be an object');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const schema_entries = Object.entries(schema_object);
|
|
31
|
+
let entry_index = 0;
|
|
32
|
+
|
|
33
|
+
while(entry_index < schema_entries.length) {
|
|
34
|
+
const schema_entry = schema_entries[entry_index];
|
|
35
|
+
const path_segment = schema_entry[0];
|
|
36
|
+
const field_definition = schema_entry[1];
|
|
37
|
+
const path_value = parent_path ? parent_path + '.' + path_segment : path_segment;
|
|
38
|
+
const parsed_field = parse_field_definition(path_value, field_definition, field_registry);
|
|
39
|
+
|
|
40
|
+
if(parsed_field.kind === 'field_type') {
|
|
41
|
+
parse_state.field_types[path_value] = parsed_field.field_type;
|
|
42
|
+
entry_index += 1;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
parse_state.object_paths.add(path_value);
|
|
47
|
+
parse_schema_object(parsed_field.nested_schema, path_value, field_registry, parse_state);
|
|
48
|
+
entry_index += 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parse_field_definition(path_value, field_definition, field_registry) {
|
|
53
|
+
if(is_array(field_definition)) {
|
|
54
|
+
return {
|
|
55
|
+
kind: 'field_type',
|
|
56
|
+
field_type: create_array_field_type(path_value, field_definition, {}, field_registry)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if(field_registry.has_field_type(field_definition)) {
|
|
61
|
+
return {
|
|
62
|
+
kind: 'field_type',
|
|
63
|
+
field_type: field_registry.create_field_type(path_value, field_definition, {})
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if(is_plain_object(field_definition)) {
|
|
68
|
+
const definition_keys = Object.keys(field_definition);
|
|
69
|
+
|
|
70
|
+
if(definition_keys.length === 0) {
|
|
71
|
+
return {
|
|
72
|
+
kind: 'field_type',
|
|
73
|
+
field_type: field_registry.create_field_type(path_value, 'Mixed', {})
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if(should_use_explicit_type(field_definition, field_registry)) {
|
|
78
|
+
return {
|
|
79
|
+
kind: 'field_type',
|
|
80
|
+
field_type: create_explicit_field_type(path_value, field_definition, field_registry)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
kind: 'nested',
|
|
86
|
+
nested_schema: field_definition
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
throw new Error('Invalid field definition at path "' + path_value + '"');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function should_use_explicit_type(field_definition, field_registry) {
|
|
94
|
+
if(!Object.prototype.hasOwnProperty.call(field_definition, 'type')) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const type_key = field_definition.type;
|
|
99
|
+
|
|
100
|
+
if(is_array(type_key)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return field_registry.has_field_type(type_key);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function create_explicit_field_type(path_value, field_definition, field_registry) {
|
|
108
|
+
const type_key = field_definition.type;
|
|
109
|
+
const of_definition = field_definition.of;
|
|
110
|
+
const field_options = Object.assign({}, field_definition);
|
|
111
|
+
|
|
112
|
+
// Remove schema-definition syntax keys after extracting them.
|
|
113
|
+
// Runtime FieldType instances should receive internal option keys (for example, `of_field_type`),
|
|
114
|
+
// not parser syntax keys like `type` or `of`.
|
|
115
|
+
delete field_options.type;
|
|
116
|
+
delete field_options.of;
|
|
117
|
+
|
|
118
|
+
if(is_array(type_key)) {
|
|
119
|
+
return create_array_field_type(path_value, type_key, field_options, field_registry);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const type_name = field_registry.resolve_field_type_name(type_key);
|
|
123
|
+
|
|
124
|
+
if(!type_name) {
|
|
125
|
+
throw new Error('Unsupported field type at path "' + path_value + '"');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if(type_name === 'Array') {
|
|
129
|
+
const explicit_array_definition = of_definition !== undefined ? [of_definition] : [];
|
|
130
|
+
return create_array_field_type(path_value, explicit_array_definition, field_options, field_registry);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if(type_name === 'Map') {
|
|
134
|
+
field_options.of_field_type = create_of_field_type(path_value, of_definition, field_registry);
|
|
135
|
+
return field_registry.create_field_type(path_value, type_name, field_options);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if(type_name === 'Union') {
|
|
139
|
+
field_options.of_field_types = create_union_field_types(path_value, of_definition, field_registry);
|
|
140
|
+
return field_registry.create_field_type(path_value, type_name, field_options);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return field_registry.create_field_type(path_value, type_name, field_options);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function create_array_field_type(path_value, array_definition, field_options, field_registry) {
|
|
147
|
+
if(array_definition.length > 1) {
|
|
148
|
+
throw new Error('Array type definition at path "' + path_value + '" must contain at most one item type');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const array_options = Object.assign({}, field_options || {});
|
|
152
|
+
const item_definition = array_definition.length === 1 ? array_definition[0] : undefined;
|
|
153
|
+
|
|
154
|
+
array_options.of_field_type = create_of_field_type(path_value, item_definition, field_registry);
|
|
155
|
+
return field_registry.create_field_type(path_value, 'Array', array_options);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function create_union_field_types(path_value, union_of_definition, field_registry) {
|
|
159
|
+
if(is_not_array(union_of_definition) || union_of_definition.length === 0) {
|
|
160
|
+
throw new Error('Union type definition at path "' + path_value + '" must define a non-empty "of" array');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const union_field_types = [];
|
|
164
|
+
let type_index = 0;
|
|
165
|
+
|
|
166
|
+
while(type_index < union_of_definition.length) {
|
|
167
|
+
union_field_types.push(create_union_candidate_field_type(path_value, union_of_definition[type_index], field_registry));
|
|
168
|
+
type_index += 1;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return union_field_types;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function create_union_candidate_field_type(path_value, union_definition, field_registry) {
|
|
175
|
+
if(is_array(union_definition)) {
|
|
176
|
+
return create_array_field_type(path_value, union_definition, {}, field_registry);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if(field_registry.has_field_type(union_definition)) {
|
|
180
|
+
return field_registry.create_field_type(path_value, union_definition, {});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if(is_plain_object(union_definition) && should_use_explicit_type(union_definition, field_registry)) {
|
|
184
|
+
return create_explicit_field_type(path_value, union_definition, field_registry);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
throw new Error('Unsupported union type at path "' + path_value + '"');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function create_of_field_type(path_value, of_definition, field_registry) {
|
|
191
|
+
const item_path = path_value + '.$';
|
|
192
|
+
|
|
193
|
+
if(of_definition === undefined) {
|
|
194
|
+
return field_registry.create_field_type(item_path, 'Mixed', {});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if(is_array(of_definition)) {
|
|
198
|
+
return create_array_field_type(item_path, of_definition, {}, field_registry);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if(field_registry.has_field_type(of_definition)) {
|
|
202
|
+
return field_registry.create_field_type(item_path, of_definition, {});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if(is_plain_object(of_definition)) {
|
|
206
|
+
if(should_use_explicit_type(of_definition, field_registry)) {
|
|
207
|
+
return create_explicit_field_type(item_path, of_definition, field_registry);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return field_registry.create_field_type(item_path, 'Mixed', {});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
throw new Error('Unsupported "of" definition at path "' + path_value + '"');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function is_plain_object(value) {
|
|
217
|
+
return value !== null && typeof value === 'object' && is_not_array(value);
|
|
218
|
+
}
|