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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/docs/api.md +152 -0
  4. package/docs/examples.md +612 -0
  5. package/docs/local-integration-testing.md +17 -0
  6. package/docs/query-translation.md +98 -0
  7. package/index.js +2 -0
  8. package/package.json +58 -0
  9. package/src/connection/connect.js +56 -0
  10. package/src/connection/disconnect.js +16 -0
  11. package/src/connection/pool-store.js +46 -0
  12. package/src/connection/server-capabilities.js +59 -0
  13. package/src/constants/defaults.js +20 -0
  14. package/src/constants/id-strategies.js +29 -0
  15. package/src/debug/debug-logger.js +15 -0
  16. package/src/errors/query-error.js +23 -0
  17. package/src/errors/validation-error.js +23 -0
  18. package/src/field-types/base-field-type.js +140 -0
  19. package/src/field-types/builtins/advanced.js +365 -0
  20. package/src/field-types/builtins/index.js +585 -0
  21. package/src/field-types/registry.js +122 -0
  22. package/src/index.js +36 -0
  23. package/src/migration/ensure-index.js +155 -0
  24. package/src/migration/ensure-schema.js +16 -0
  25. package/src/migration/ensure-table.js +31 -0
  26. package/src/migration/schema-indexes-resolver.js +6 -0
  27. package/src/model/document-instance.js +540 -0
  28. package/src/model/model-factory.js +555 -0
  29. package/src/query/limit-skip-compiler.js +31 -0
  30. package/src/query/operators/all.js +10 -0
  31. package/src/query/operators/contains.js +7 -0
  32. package/src/query/operators/elem-match.js +3 -0
  33. package/src/query/operators/eq.js +6 -0
  34. package/src/query/operators/gt.js +16 -0
  35. package/src/query/operators/gte.js +16 -0
  36. package/src/query/operators/has-all-keys.js +11 -0
  37. package/src/query/operators/has-any-keys.js +11 -0
  38. package/src/query/operators/has-key.js +6 -0
  39. package/src/query/operators/in.js +12 -0
  40. package/src/query/operators/index.js +60 -0
  41. package/src/query/operators/jsonpath-exists.js +15 -0
  42. package/src/query/operators/jsonpath-match.js +15 -0
  43. package/src/query/operators/lt.js +16 -0
  44. package/src/query/operators/lte.js +16 -0
  45. package/src/query/operators/ne.js +6 -0
  46. package/src/query/operators/nin.js +12 -0
  47. package/src/query/operators/regex.js +8 -0
  48. package/src/query/operators/size.js +16 -0
  49. package/src/query/path-parser.js +43 -0
  50. package/src/query/query-builder.js +93 -0
  51. package/src/query/sort-compiler.js +30 -0
  52. package/src/query/where-compiler.js +477 -0
  53. package/src/schema/field-definition-parser.js +218 -0
  54. package/src/schema/path-introspection.js +82 -0
  55. package/src/schema/schema-compiler.js +212 -0
  56. package/src/schema/schema.js +234 -0
  57. package/src/sql/parameter-binder.js +13 -0
  58. package/src/sql/sql-runner.js +31 -0
  59. package/src/utils/array.js +31 -0
  60. package/src/utils/assert.js +27 -0
  61. package/src/utils/json-safe.js +9 -0
  62. package/src/utils/json.js +21 -0
  63. package/src/utils/object-path.js +33 -0
  64. package/src/utils/object.js +168 -0
  65. package/src/utils/value.js +30 -0
@@ -0,0 +1,555 @@
1
+ import defaults from '#src/constants/defaults.js';
2
+ import QueryError from '#src/errors/query-error.js';
3
+
4
+ import {get_connection_options, get_server_capabilities, has_pool} from '#src/connection/pool-store.js';
5
+ import {assert_id_strategy_capability} from '#src/connection/server-capabilities.js';
6
+ import ensure_index from '#src/migration/ensure-index.js';
7
+ import ensure_schema from '#src/migration/ensure-schema.js';
8
+ import resolve_schema_indexes from '#src/migration/schema-indexes-resolver.js';
9
+ import ensure_table from '#src/migration/ensure-table.js';
10
+
11
+ import document_instance from '#src/model/document-instance.js';
12
+ import IdStrategies, {assert_valid_id_strategy} from '#src/constants/id-strategies.js';
13
+ import QueryBuilder from '#src/query/query-builder.js';
14
+ import where_compiler from '#src/query/where-compiler.js';
15
+
16
+ import {bind_parameter, create_parameter_state} from '#src/sql/parameter-binder.js';
17
+ import sql_runner from '#src/sql/sql-runner.js';
18
+
19
+ import {assert_condition, assert_identifier, quote_identifier} from '#src/utils/assert.js';
20
+ import {has_own} from '#src/utils/object.js';
21
+ import {build_path_literal} from '#src/utils/object-path.js';
22
+ import {jsonb_stringify} from '#src/utils/json.js';
23
+ import {is_not_object} from '#src/utils/value.js';
24
+
25
+ const update_path_root_segment_pattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
26
+ const update_path_nested_segment_pattern = /^(?:[a-zA-Z_][a-zA-Z0-9_]*|[0-9]+)$/;
27
+
28
+ export default function model(schema_instance, model_configuration) {
29
+ assert_condition(schema_instance && typeof schema_instance.validate === 'function', 'schema_instance is required');
30
+ assert_condition(is_not_object(model_configuration) === false, 'model options are required');
31
+
32
+ const model_options = Object.assign({}, defaults.model_options, model_configuration);
33
+ assert_identifier(model_options.table_name, 'table_name');
34
+ assert_identifier(model_options.data_column, 'data_column');
35
+
36
+ let indexes_ensured = false;
37
+
38
+ function Model(data) {
39
+ this.data = data || {};
40
+ }
41
+
42
+ Model.model_name = model_options.table_name;
43
+ Model.schema_instance = schema_instance;
44
+ Model.model_options = model_options;
45
+ Model.resolve_id_strategy = function () {
46
+ const connection_options = get_connection_options();
47
+ const model_id_strategy = model_options.id_strategy;
48
+ const server_id_strategy = connection_options.id_strategy;
49
+ const final_id_strategy = model_id_strategy ?? server_id_strategy;
50
+
51
+ assert_valid_id_strategy(final_id_strategy);
52
+
53
+ return final_id_strategy;
54
+ };
55
+
56
+ Model.resolve_auto_index = function () {
57
+ const connection_options = get_connection_options();
58
+ const model_auto_index = model_options.auto_index;
59
+ const server_auto_index = connection_options.auto_index;
60
+ const final_auto_index = model_auto_index ?? server_auto_index;
61
+
62
+ assert_condition(typeof final_auto_index === 'boolean', 'auto_index must be a boolean');
63
+
64
+ return final_auto_index;
65
+ };
66
+
67
+ Model.assert_id_strategy_supported = function () {
68
+ const final_id_strategy = Model.resolve_id_strategy();
69
+ assert_model_id_strategy_supported(final_id_strategy);
70
+ return final_id_strategy;
71
+ };
72
+
73
+ document_instance(Model);
74
+
75
+ Model.ensure_table = async function () {
76
+ const final_table_name = model_options.table_name;
77
+ const final_id_strategy = Model.assert_id_strategy_supported();
78
+
79
+ await ensure_table(final_table_name, model_options.data_column, final_id_strategy);
80
+
81
+ if(indexes_ensured || !Model.resolve_auto_index()) {
82
+ return;
83
+ }
84
+
85
+ await ensure_schema_indexes(final_table_name);
86
+ indexes_ensured = true;
87
+ };
88
+
89
+ Model.ensure_index = async function () {
90
+ const final_table_name = model_options.table_name;
91
+ const final_id_strategy = Model.assert_id_strategy_supported();
92
+
93
+ await ensure_table(final_table_name, model_options.data_column, final_id_strategy);
94
+ await ensure_schema_indexes(final_table_name);
95
+
96
+ indexes_ensured = true;
97
+ };
98
+
99
+ Model.ensure_schema = async function () {
100
+ const final_id_strategy = Model.assert_id_strategy_supported();
101
+ await ensure_schema(model_options.table_name, model_options.data_column, schema_instance, final_id_strategy);
102
+ indexes_ensured = true;
103
+ };
104
+
105
+ Model.find = function (query_filter, projection_value) {
106
+ return new QueryBuilder(Model, 'find', query_filter || {}, projection_value || null);
107
+ };
108
+
109
+ Model.find_one = function (query_filter) {
110
+ return new QueryBuilder(Model, 'find_one', query_filter || {}, null);
111
+ };
112
+
113
+ Model.count_documents = function (query_filter) {
114
+ return new QueryBuilder(Model, 'count_documents', query_filter || {}, null);
115
+ };
116
+
117
+ Model.update_one = async function (query_filter, update_definition) {
118
+ const update_object = update_definition || {};
119
+ const set_definition = update_object.$set;
120
+ const insert_definition = update_object.$insert;
121
+ const set_lax_definition = update_object.$set_lax;
122
+
123
+ assert_supported_update_definition(set_definition, insert_definition, set_lax_definition);
124
+
125
+ await Model.ensure_table();
126
+
127
+ const table_identifier = quote_identifier(model_options.table_name);
128
+ const data_identifier = quote_identifier(model_options.data_column);
129
+ const where_result = where_compiler(query_filter || {}, {
130
+ data_column: model_options.data_column,
131
+ schema: schema_instance
132
+ });
133
+ const parameter_state = create_parameter_state(where_result.next_index);
134
+ let data_expression = data_identifier;
135
+
136
+ data_expression = apply_set_updates(data_expression, set_definition, parameter_state);
137
+ data_expression = apply_insert_updates(data_expression, insert_definition, parameter_state);
138
+ data_expression = apply_set_lax_updates(data_expression, set_lax_definition, parameter_state);
139
+
140
+ const sql_text =
141
+ 'WITH target_row AS (' + 'SELECT id FROM ' + table_identifier + ' WHERE ' + where_result.sql + ' LIMIT 1' + ') ' +
142
+ 'UPDATE ' + table_identifier + ' AS target_table ' +
143
+ 'SET ' + data_identifier + ' = ' + data_expression + ', updated_at = NOW() ' +
144
+ 'FROM target_row ' +
145
+ 'WHERE target_table.id = target_row.id ' +
146
+ 'RETURNING target_table.' + data_identifier + ' AS data';
147
+ const sql_params = where_result.params.concat(parameter_state.params);
148
+ const query_result = await sql_runner(sql_text, sql_params);
149
+
150
+ if(query_result.rows.length === 0) {
151
+ return null;
152
+ }
153
+
154
+ return query_result.rows[0].data;
155
+ };
156
+
157
+ Model.delete_one = async function (query_filter) {
158
+ const table_identifier = quote_identifier(model_options.table_name);
159
+ const data_identifier = quote_identifier(model_options.data_column);
160
+ const where_result = where_compiler(query_filter || {}, {
161
+ data_column: model_options.data_column,
162
+ schema: schema_instance
163
+ });
164
+
165
+ const sql_text =
166
+ 'WITH target_row AS (' + 'SELECT id FROM ' + table_identifier + ' WHERE ' + where_result.sql + ' LIMIT 1' + ') ' +
167
+ 'DELETE FROM ' + table_identifier + ' AS target_table ' +
168
+ 'USING target_row ' +
169
+ 'WHERE target_table.id = target_row.id ' +
170
+ 'RETURNING target_table.' + data_identifier + ' AS data';
171
+ let query_result;
172
+
173
+ try {
174
+ query_result = await sql_runner(sql_text, where_result.params);
175
+ } catch(error) {
176
+ if(is_missing_relation_query_error(error)) {
177
+ return null;
178
+ }
179
+
180
+ throw error;
181
+ }
182
+
183
+ if(query_result.rows.length === 0) {
184
+ return null;
185
+ }
186
+
187
+ return query_result.rows[0].data;
188
+ };
189
+
190
+ function cast_update_value(path_value, next_value) {
191
+ const field_type = resolve_update_field_type(path_value);
192
+
193
+ if(!field_type || typeof field_type.cast !== 'function') {
194
+ return next_value;
195
+ }
196
+
197
+ let casted_value = next_value;
198
+
199
+ if(typeof field_type.apply_set === 'function') {
200
+ casted_value = field_type.apply_set(casted_value, {path: path_value, mode: 'update'});
201
+ }
202
+
203
+ return field_type.cast(casted_value, {path: path_value, mode: 'update'});
204
+ }
205
+
206
+ function resolve_update_field_type(path_value) {
207
+ if(!schema_instance || typeof schema_instance.path !== 'function') {
208
+ return null;
209
+ }
210
+
211
+ return schema_instance.path(path_value);
212
+ }
213
+
214
+ function assert_supported_update_definition(set_definition, insert_definition, set_lax_definition) {
215
+ const has_set_updates = set_definition !== undefined;
216
+ const has_insert_updates = insert_definition !== undefined;
217
+ const has_set_lax_updates = set_lax_definition !== undefined;
218
+
219
+ if(!has_set_updates && !has_insert_updates && !has_set_lax_updates) {
220
+ throw new QueryError('update_one requires at least one supported update operator', {
221
+ allowed: ['$set', '$insert', '$set_lax']
222
+ });
223
+ }
224
+
225
+ if(has_set_updates && is_not_object(set_definition)) {
226
+ throw new QueryError('update_definition.$set must be an object', {
227
+ allowed: ['$set', '$insert', '$set_lax']
228
+ });
229
+ }
230
+
231
+ if(has_insert_updates && is_not_object(insert_definition)) {
232
+ throw new QueryError('update_definition.$insert must be an object', {
233
+ allowed: ['$set', '$insert', '$set_lax']
234
+ });
235
+ }
236
+
237
+ if(has_set_lax_updates && is_not_object(set_lax_definition)) {
238
+ throw new QueryError('update_definition.$set_lax must be an object', {
239
+ allowed: ['$set', '$insert', '$set_lax']
240
+ });
241
+ }
242
+
243
+ const update_path_entries = collect_update_path_entries([
244
+ {operator_name: '$set', definition: set_definition},
245
+ {operator_name: '$insert', definition: insert_definition},
246
+ {operator_name: '$set_lax', definition: set_lax_definition}
247
+ ]);
248
+
249
+ assert_no_conflicting_update_paths(update_path_entries);
250
+ }
251
+
252
+ function apply_set_updates(data_expression, set_definition, parameter_state) {
253
+ if(set_definition === undefined) {
254
+ return data_expression;
255
+ }
256
+
257
+ const set_entries = Object.entries(set_definition);
258
+ let set_index = 0;
259
+ let next_expression = data_expression;
260
+
261
+ while(set_index < set_entries.length) {
262
+ const set_entry = set_entries[set_index];
263
+ const path_value = set_entry[0];
264
+ const next_value = set_entry[1];
265
+ const casted_value = cast_update_value(path_value, next_value);
266
+ const path_literal = build_update_path_literal(path_value);
267
+ const placeholder = bind_parameter(parameter_state, jsonb_stringify(casted_value));
268
+
269
+ next_expression = 'jsonb_set(' + next_expression + ", '" + path_literal + "', " + placeholder + '::jsonb, true)';
270
+ set_index += 1;
271
+ }
272
+
273
+ return next_expression;
274
+ }
275
+
276
+ function apply_insert_updates(data_expression, insert_definition, parameter_state) {
277
+ if(insert_definition === undefined) {
278
+ return data_expression;
279
+ }
280
+
281
+ const insert_entries = Object.entries(insert_definition);
282
+ let insert_index = 0;
283
+ let next_expression = data_expression;
284
+
285
+ while(insert_index < insert_entries.length) {
286
+ const insert_entry = insert_entries[insert_index];
287
+ const path_value = insert_entry[0];
288
+ const insert_config = normalize_insert_update(path_value, insert_entry[1]);
289
+ const casted_value = cast_update_value(path_value, insert_config.value);
290
+ const path_literal = build_update_path_literal(path_value);
291
+ const value_placeholder = bind_parameter(parameter_state, jsonb_stringify(casted_value));
292
+ const insert_after_sql = insert_config.insert_after ? 'true' : 'false';
293
+
294
+ next_expression =
295
+ 'jsonb_insert(' + next_expression + ", '" + path_literal + "', " + value_placeholder + '::jsonb, ' + insert_after_sql + ')';
296
+ insert_index += 1;
297
+ }
298
+
299
+ return next_expression;
300
+ }
301
+
302
+ function apply_set_lax_updates(data_expression, set_lax_definition, parameter_state) {
303
+ if(set_lax_definition === undefined) {
304
+ return data_expression;
305
+ }
306
+
307
+ const set_lax_entries = Object.entries(set_lax_definition);
308
+ let set_lax_index = 0;
309
+ let next_expression = data_expression;
310
+
311
+ while(set_lax_index < set_lax_entries.length) {
312
+ const set_lax_entry = set_lax_entries[set_lax_index];
313
+ const path_value = set_lax_entry[0];
314
+ const set_lax_config = normalize_set_lax_update(path_value, set_lax_entry[1]);
315
+ const casted_value = cast_update_value(path_value, set_lax_config.value);
316
+ const path_literal = build_update_path_literal(path_value);
317
+ const value_placeholder = bind_parameter(parameter_state, cast_update_parameter_value(casted_value));
318
+ const create_if_missing_sql = set_lax_config.create_if_missing ? 'true' : 'false';
319
+
320
+ next_expression =
321
+ 'jsonb_set_lax(' + next_expression + ", '" + path_literal + "', " + value_placeholder + '::jsonb, ' +
322
+ create_if_missing_sql + ", '" + set_lax_config.null_value_treatment + "')";
323
+ set_lax_index += 1;
324
+ }
325
+
326
+ return next_expression;
327
+ }
328
+
329
+ function build_update_path_literal(path_value) {
330
+ return build_path_literal(split_update_path(path_value));
331
+ }
332
+
333
+ function collect_update_path_entries(update_operator_entries) {
334
+ const update_path_entries = [];
335
+ let operator_index = 0;
336
+
337
+ while(operator_index < update_operator_entries.length) {
338
+ const operator_entry = update_operator_entries[operator_index];
339
+ const definition = operator_entry.definition;
340
+
341
+ if(definition === undefined) {
342
+ operator_index += 1;
343
+ continue;
344
+ }
345
+
346
+ const path_entries = Object.entries(definition);
347
+ let path_index = 0;
348
+
349
+ while(path_index < path_entries.length) {
350
+ const path_entry = path_entries[path_index];
351
+ const path_value = path_entry[0];
352
+
353
+ split_update_path(path_value, operator_entry.operator_name);
354
+ update_path_entries.push({
355
+ operator_name: operator_entry.operator_name,
356
+ path: path_value
357
+ });
358
+
359
+ path_index += 1;
360
+ }
361
+
362
+ operator_index += 1;
363
+ }
364
+
365
+ return update_path_entries;
366
+ }
367
+
368
+ function assert_no_conflicting_update_paths(update_path_entries) {
369
+ let left_index = 0;
370
+
371
+ while(left_index < update_path_entries.length) {
372
+ let right_index = left_index + 1;
373
+
374
+ while(right_index < update_path_entries.length) {
375
+ const left_entry = update_path_entries[left_index];
376
+ const right_entry = update_path_entries[right_index];
377
+
378
+ if(do_update_paths_conflict(left_entry.path, right_entry.path)) {
379
+ throw new QueryError('Conflicting update paths are not allowed in a single update_one call', {
380
+ left: left_entry,
381
+ right: right_entry
382
+ });
383
+ }
384
+
385
+ right_index += 1;
386
+ }
387
+
388
+ left_index += 1;
389
+ }
390
+ }
391
+
392
+ function do_update_paths_conflict(left_path, right_path) {
393
+ if(left_path === right_path) {
394
+ return true;
395
+ }
396
+
397
+ return left_path.indexOf(right_path + '.') === 0 || right_path.indexOf(left_path + '.') === 0;
398
+ }
399
+
400
+ function split_update_path(path_value, operator_name) {
401
+ const path_segments = path_value.split('.');
402
+ let segment_index = 0;
403
+
404
+ while(segment_index < path_segments.length) {
405
+ const segment_value = path_segments[segment_index];
406
+ const is_root = segment_index === 0;
407
+
408
+ if(segment_value.length === 0) {
409
+ throw new QueryError('Update path contains an empty segment', {
410
+ operator: operator_name,
411
+ path: path_value
412
+ });
413
+ }
414
+
415
+ if(is_root && !update_path_root_segment_pattern.test(segment_value)) {
416
+ throw new QueryError('Update path root segment has invalid characters', {
417
+ operator: operator_name,
418
+ path: path_value
419
+ });
420
+ }
421
+
422
+ if(!is_root && !update_path_nested_segment_pattern.test(segment_value)) {
423
+ throw new QueryError('Update path contains an invalid nested segment', {
424
+ operator: operator_name,
425
+ path: path_value
426
+ });
427
+ }
428
+
429
+ segment_index += 1;
430
+ }
431
+
432
+ return path_segments;
433
+ }
434
+
435
+ function cast_update_parameter_value(casted_value) {
436
+ if(casted_value === null) {
437
+ return null;
438
+ }
439
+
440
+ return jsonb_stringify(casted_value);
441
+ }
442
+
443
+ function normalize_insert_update(path_value, insert_definition_value) {
444
+ if(is_not_object(insert_definition_value)) {
445
+ return {
446
+ value: insert_definition_value,
447
+ insert_after: false
448
+ };
449
+ }
450
+
451
+ assert_condition(has_own(insert_definition_value, 'value'), '$insert entry for path "' + path_value + '" must include value');
452
+
453
+ if(insert_definition_value.insert_after !== undefined) {
454
+ assert_condition(typeof insert_definition_value.insert_after === 'boolean', '$insert.insert_after for path "' + path_value + '" must be a boolean');
455
+ }
456
+
457
+ return {
458
+ value: insert_definition_value.value,
459
+ insert_after: insert_definition_value.insert_after === true
460
+ };
461
+ }
462
+
463
+ function normalize_set_lax_update(path_value, set_lax_definition_value) {
464
+ if(is_not_object(set_lax_definition_value)) {
465
+ return {
466
+ value: set_lax_definition_value,
467
+ create_if_missing: true,
468
+ null_value_treatment: 'use_json_null'
469
+ };
470
+ }
471
+
472
+ assert_condition(has_own(set_lax_definition_value, 'value'), '$set_lax entry for path "' + path_value + '" must include value');
473
+
474
+ if(set_lax_definition_value.create_if_missing !== undefined) {
475
+ assert_condition(typeof set_lax_definition_value.create_if_missing === 'boolean', '$set_lax.create_if_missing for path "' + path_value + '" must be a boolean');
476
+ }
477
+
478
+ const null_value_treatment = set_lax_definition_value.null_value_treatment ?? 'use_json_null';
479
+ assert_valid_set_lax_null_treatment(path_value, null_value_treatment);
480
+
481
+ return {
482
+ value: set_lax_definition_value.value,
483
+ create_if_missing: set_lax_definition_value.create_if_missing !== false,
484
+ null_value_treatment: null_value_treatment
485
+ };
486
+ }
487
+
488
+ function assert_valid_set_lax_null_treatment(path_value, null_value_treatment) {
489
+ const allowed_null_treatments = ['raise_exception', 'use_json_null', 'delete_key', 'return_target'];
490
+ assert_condition(
491
+ allowed_null_treatments.includes(null_value_treatment),
492
+ '$set_lax.null_value_treatment for path "' + path_value + '" must be one of: ' + allowed_null_treatments.join(', ')
493
+ );
494
+ }
495
+
496
+ function is_missing_relation_query_error(error) {
497
+ if(!(error instanceof QueryError)) {
498
+ return false;
499
+ }
500
+
501
+ const cause_message = error.details && error.details.cause;
502
+
503
+ if(typeof cause_message !== 'string') {
504
+ return false;
505
+ }
506
+
507
+ return cause_message.indexOf('relation ') !== -1 && cause_message.indexOf('does not exist') !== -1;
508
+ }
509
+
510
+ function assert_model_id_strategy_supported(final_id_strategy) {
511
+ if(final_id_strategy !== IdStrategies.uuidv7) {
512
+ return;
513
+ }
514
+
515
+ if(!has_pool()) {
516
+ return;
517
+ }
518
+
519
+ const server_capabilities = get_server_capabilities();
520
+ assert_id_strategy_capability(final_id_strategy, server_capabilities);
521
+ }
522
+
523
+ async function ensure_schema_indexes(final_table_name) {
524
+ const schema_indexes = resolve_schema_indexes(schema_instance);
525
+ let index_position = 0;
526
+
527
+ while(index_position < schema_indexes.length) {
528
+ const index_definition = schema_indexes[index_position];
529
+
530
+ await ensure_index(
531
+ final_table_name,
532
+ index_definition.index_spec,
533
+ model_options.data_column,
534
+ index_definition.index_options
535
+ );
536
+
537
+ index_position += 1;
538
+ }
539
+ }
540
+
541
+ if(has_pool()) {
542
+ const connection_options = get_connection_options();
543
+ const eager_id_strategy = model_options.id_strategy ?? connection_options.id_strategy;
544
+
545
+ if(eager_id_strategy === IdStrategies.uuidv7) {
546
+ Model.assert_id_strategy_supported();
547
+ }
548
+ }
549
+
550
+ return Model;
551
+ }
552
+
553
+
554
+
555
+
@@ -0,0 +1,31 @@
1
+ import {is_nan} from '#src/utils/value.js';
2
+
3
+ export default function limit_skip_compiler(limit_value, skip_value) {
4
+ let sql_fragment = '';
5
+
6
+ if(is_valid_integer(limit_value)) {
7
+ sql_fragment += ' LIMIT ' + normalize_integer(limit_value);
8
+ }
9
+
10
+ if(is_valid_integer(skip_value)) {
11
+ sql_fragment += ' OFFSET ' + normalize_integer(skip_value);
12
+ }
13
+
14
+ return sql_fragment;
15
+ }
16
+
17
+ function is_valid_integer(value) {
18
+ if(value === null || value === undefined) {
19
+ return false;
20
+ }
21
+
22
+ if(is_nan(value)) {
23
+ return false;
24
+ }
25
+
26
+ return Number(value) >= 0;
27
+ }
28
+
29
+ function normalize_integer(value) {
30
+ return Math.floor(Number(value));
31
+ }
@@ -0,0 +1,10 @@
1
+ import {bind_parameter} from '#src/sql/parameter-binder.js';
2
+ import {jsonb_stringify} from '#src/utils/json.js';
3
+ import {to_array} from '#src/utils/array.js';
4
+
5
+ export default function all_operator(jsonb_expression, comparison_value, parameter_state) {
6
+ const value_list = to_array(comparison_value);
7
+ const placeholder = bind_parameter(parameter_state, jsonb_stringify(value_list));
8
+
9
+ return jsonb_expression + ' @> ' + placeholder + '::jsonb';
10
+ }
@@ -0,0 +1,7 @@
1
+ import {bind_parameter} from '#src/sql/parameter-binder.js';
2
+ import {jsonb_stringify} from '#src/utils/json.js';
3
+
4
+ export default function contains_operator(jsonb_expression, comparison_value, parameter_state) {
5
+ const placeholder = bind_parameter(parameter_state, jsonb_stringify(comparison_value));
6
+ return jsonb_expression + ' @> ' + placeholder + '::jsonb';
7
+ }
@@ -0,0 +1,3 @@
1
+ export default function elem_match_operator(array_expression, predicate_expression) {
2
+ return 'EXISTS (SELECT 1 FROM jsonb_array_elements(' + array_expression + ') AS elem WHERE ' + predicate_expression + ')';
3
+ }
@@ -0,0 +1,6 @@
1
+ import {bind_parameter} from '#src/sql/parameter-binder.js';
2
+
3
+ export default function eq_operator(sql_expression, comparison_value, parameter_state) {
4
+ const placeholder = bind_parameter(parameter_state, String(comparison_value));
5
+ return sql_expression + ' = ' + placeholder;
6
+ }
@@ -0,0 +1,16 @@
1
+ import QueryError from '#src/errors/query-error.js';
2
+ import {bind_parameter} from '#src/sql/parameter-binder.js';
3
+ import {is_nan} from '#src/utils/value.js';
4
+
5
+ export default function gt_operator(sql_expression, comparison_value, parameter_state) {
6
+ if(is_nan(comparison_value)) {
7
+ throw new QueryError('Invalid value for $gt operator', {
8
+ operator: '$gt',
9
+ value: comparison_value
10
+ });
11
+ }
12
+
13
+ const numeric_value = typeof comparison_value === 'bigint' ? comparison_value.toString() : Number(comparison_value);
14
+ const placeholder = bind_parameter(parameter_state, numeric_value);
15
+ return '(' + sql_expression + ')::numeric > ' + placeholder;
16
+ }
@@ -0,0 +1,16 @@
1
+ import QueryError from '#src/errors/query-error.js';
2
+ import {bind_parameter} from '#src/sql/parameter-binder.js';
3
+ import {is_nan} from '#src/utils/value.js';
4
+
5
+ export default function gte_operator(sql_expression, comparison_value, parameter_state) {
6
+ if(is_nan(comparison_value)) {
7
+ throw new QueryError('Invalid value for $gte operator', {
8
+ operator: '$gte',
9
+ value: comparison_value
10
+ });
11
+ }
12
+
13
+ const numeric_value = typeof comparison_value === 'bigint' ? comparison_value.toString() : Number(comparison_value);
14
+ const placeholder = bind_parameter(parameter_state, numeric_value);
15
+ return '(' + sql_expression + ')::numeric >= ' + placeholder;
16
+ }
@@ -0,0 +1,11 @@
1
+ import {bind_parameter} from '#src/sql/parameter-binder.js';
2
+ import {to_array} from '#src/utils/array.js';
3
+
4
+ export default function has_all_keys_operator(jsonb_expression, comparison_value, parameter_state) {
5
+ const key_list = to_array(comparison_value).map(function map_key(key_value) {
6
+ return String(key_value);
7
+ });
8
+
9
+ const placeholder = bind_parameter(parameter_state, key_list);
10
+ return jsonb_expression + ' ?& ' + placeholder + '::text[]';
11
+ }
@@ -0,0 +1,11 @@
1
+ import {bind_parameter} from '#src/sql/parameter-binder.js';
2
+ import {to_array} from '#src/utils/array.js';
3
+
4
+ export default function has_any_keys_operator(jsonb_expression, comparison_value, parameter_state) {
5
+ const key_list = to_array(comparison_value).map(function map_key(key_value) {
6
+ return String(key_value);
7
+ });
8
+
9
+ const placeholder = bind_parameter(parameter_state, key_list);
10
+ return jsonb_expression + ' ?| ' + placeholder + '::text[]';
11
+ }
@@ -0,0 +1,6 @@
1
+ import {bind_parameter} from '#src/sql/parameter-binder.js';
2
+
3
+ export default function has_key_operator(jsonb_expression, comparison_value, parameter_state) {
4
+ const placeholder = bind_parameter(parameter_state, String(comparison_value));
5
+ return jsonb_expression + ' ? ' + placeholder;
6
+ }