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
package/src/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import connect from '#src/connection/connect.js';
|
|
2
|
+
import disconnect from '#src/connection/disconnect.js';
|
|
3
|
+
import IdStrategies from '#src/constants/id-strategies.js';
|
|
4
|
+
import Schema from '#src/schema/schema.js';
|
|
5
|
+
import model from '#src/model/model-factory.js';
|
|
6
|
+
import {create_field_type, register_field_type, resolve_field_type} from '#src/field-types/registry.js';
|
|
7
|
+
import {boolean_convert_to_false, boolean_convert_to_true, uuidv7_type_reference} from '#src/field-types/builtins/index.js';
|
|
8
|
+
|
|
9
|
+
const jsonbadger = {
|
|
10
|
+
connect,
|
|
11
|
+
disconnect,
|
|
12
|
+
model,
|
|
13
|
+
create_field_type,
|
|
14
|
+
register_field_type,
|
|
15
|
+
resolve_field_type,
|
|
16
|
+
Schema,
|
|
17
|
+
IdStrategies,
|
|
18
|
+
field_types: {
|
|
19
|
+
UUIDv7: uuidv7_type_reference,
|
|
20
|
+
boolean_convert_to_true,
|
|
21
|
+
boolean_convert_to_false
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
connect,
|
|
27
|
+
disconnect,
|
|
28
|
+
model,
|
|
29
|
+
create_field_type,
|
|
30
|
+
register_field_type,
|
|
31
|
+
resolve_field_type,
|
|
32
|
+
Schema,
|
|
33
|
+
IdStrategies
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default jsonbadger;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {assert_condition, assert_identifier, assert_path, quote_identifier} from '#src/utils/assert.js';
|
|
2
|
+
import {build_path_literal, split_dot_path} from '#src/utils/object-path.js';
|
|
3
|
+
import sql_runner from '#src/sql/sql-runner.js';
|
|
4
|
+
import {is_object, is_string} from '#src/utils/value.js';
|
|
5
|
+
|
|
6
|
+
export default async function ensure_index(table_name, index_spec, data_column, index_options) {
|
|
7
|
+
assert_identifier(table_name, 'table_name');
|
|
8
|
+
assert_identifier(data_column, 'data_column');
|
|
9
|
+
|
|
10
|
+
const normalized_index_spec = normalize_index_spec(index_spec);
|
|
11
|
+
const normalized_index_options = normalize_index_options(index_options);
|
|
12
|
+
|
|
13
|
+
const table_identifier = quote_identifier(table_name);
|
|
14
|
+
const data_identifier = quote_identifier(data_column);
|
|
15
|
+
const index_name = resolve_index_name(table_name, data_column, normalized_index_spec, normalized_index_options);
|
|
16
|
+
const index_identifier = quote_identifier(index_name);
|
|
17
|
+
|
|
18
|
+
if(is_string(normalized_index_spec)) {
|
|
19
|
+
assert_condition(
|
|
20
|
+
normalized_index_options.unique !== true,
|
|
21
|
+
'unique index is not supported for single-path GIN indexes'
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const expression = build_json_path_expression(data_identifier, normalized_index_spec);
|
|
25
|
+
const sql_text =
|
|
26
|
+
'CREATE INDEX IF NOT EXISTS ' + index_identifier +
|
|
27
|
+
' ON ' + table_identifier + ' USING GIN ((' + expression + '));';
|
|
28
|
+
|
|
29
|
+
await sql_runner(sql_text, []);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const sorted_path_entries = Object.entries(normalized_index_spec);
|
|
34
|
+
const expression_list = [];
|
|
35
|
+
let entry_position = 0;
|
|
36
|
+
|
|
37
|
+
while(entry_position < sorted_path_entries.length) {
|
|
38
|
+
const path_entry = sorted_path_entries[entry_position];
|
|
39
|
+
const path_value = path_entry[0];
|
|
40
|
+
const direction_value = path_entry[1];
|
|
41
|
+
const direction_sql = direction_value === -1 ? 'DESC' : 'ASC';
|
|
42
|
+
const expression = build_text_path_expression(data_identifier, path_value);
|
|
43
|
+
|
|
44
|
+
expression_list.push('(' + expression + ') ' + direction_sql);
|
|
45
|
+
entry_position += 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const unique_prefix = normalized_index_options.unique ? 'UNIQUE ' : '';
|
|
49
|
+
const sql_text =
|
|
50
|
+
'CREATE ' + unique_prefix + 'INDEX IF NOT EXISTS ' + index_identifier +
|
|
51
|
+
' ON ' + table_identifier + ' (' + expression_list.join(', ') + ');';
|
|
52
|
+
|
|
53
|
+
await sql_runner(sql_text, []);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalize_index_spec(index_spec) {
|
|
57
|
+
if(is_string(index_spec)) {
|
|
58
|
+
assert_path(index_spec, 'index path');
|
|
59
|
+
return index_spec;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
assert_condition(is_object(index_spec), 'index_spec must be a path string or an object map of paths to sort directions');
|
|
63
|
+
|
|
64
|
+
const index_entries = Object.entries(index_spec);
|
|
65
|
+
assert_condition(index_entries.length > 0, 'index_spec object must define at least one indexed path');
|
|
66
|
+
|
|
67
|
+
const normalized_index_spec = {};
|
|
68
|
+
let entry_position = 0;
|
|
69
|
+
|
|
70
|
+
while(entry_position < index_entries.length) {
|
|
71
|
+
const index_entry = index_entries[entry_position];
|
|
72
|
+
const path_value = index_entry[0];
|
|
73
|
+
const direction_value = index_entry[1];
|
|
74
|
+
|
|
75
|
+
assert_path(path_value, 'index path');
|
|
76
|
+
assert_condition(direction_value === 1 || direction_value === -1, 'index direction for path "' + path_value + '" must be 1 or -1');
|
|
77
|
+
|
|
78
|
+
normalized_index_spec[path_value] = direction_value;
|
|
79
|
+
entry_position += 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return normalized_index_spec;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalize_index_options(index_options) {
|
|
86
|
+
if(index_options === undefined) {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
assert_condition(is_object(index_options), 'index_options must be an object');
|
|
91
|
+
|
|
92
|
+
const normalized_options = Object.assign({}, index_options);
|
|
93
|
+
|
|
94
|
+
if(normalized_options.unique !== undefined) {
|
|
95
|
+
assert_condition(typeof normalized_options.unique === 'boolean', 'index_options.unique must be a boolean');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if(normalized_options.name !== undefined) {
|
|
99
|
+
assert_identifier(normalized_options.name, 'index_options.name');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return normalized_options;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resolve_index_name(table_name, data_column, index_spec, index_options) {
|
|
106
|
+
if(index_options.name) {
|
|
107
|
+
return index_options.name;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if(is_string(index_spec)) {
|
|
111
|
+
const path_suffix = index_spec.replace(/\./g, '_');
|
|
112
|
+
return build_index_name(table_name, data_column, path_suffix + '_gin');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const index_entries = Object.entries(index_spec);
|
|
116
|
+
const path_suffix_parts = [];
|
|
117
|
+
let entry_position = 0;
|
|
118
|
+
|
|
119
|
+
while(entry_position < index_entries.length) {
|
|
120
|
+
const index_entry = index_entries[entry_position];
|
|
121
|
+
const path_value = index_entry[0];
|
|
122
|
+
const direction_value = index_entry[1];
|
|
123
|
+
const direction_suffix = direction_value === -1 ? 'desc' : 'asc';
|
|
124
|
+
path_suffix_parts.push(path_value.replace(/\./g, '_') + '_' + direction_suffix);
|
|
125
|
+
entry_position += 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return build_index_name(table_name, data_column, path_suffix_parts.join('_') + '_btree');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function build_json_path_expression(data_identifier, path_value) {
|
|
132
|
+
const path_segments = split_dot_path(path_value);
|
|
133
|
+
|
|
134
|
+
if(path_segments.length === 1) {
|
|
135
|
+
return data_identifier + " -> '" + path_segments[0] + "'";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return data_identifier + " #> '" + build_path_literal(path_segments) + "'";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function build_text_path_expression(data_identifier, path_value) {
|
|
142
|
+
const path_segments = split_dot_path(path_value);
|
|
143
|
+
|
|
144
|
+
if(path_segments.length === 1) {
|
|
145
|
+
return data_identifier + " ->> '" + path_segments[0] + "'";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return data_identifier + " #>> '" + build_path_literal(path_segments) + "'";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function build_index_name(table_name, data_column, path_suffix) {
|
|
152
|
+
const raw_name = 'idx_' + table_name + '_' + data_column + '_' + path_suffix;
|
|
153
|
+
const normalized_name = raw_name.toLowerCase().replace(/[^a-z0-9_]/g, '_');
|
|
154
|
+
return normalized_name.slice(0, 63);
|
|
155
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import ensure_index from '#src/migration/ensure-index.js';
|
|
2
|
+
import resolve_schema_indexes from '#src/migration/schema-indexes-resolver.js';
|
|
3
|
+
import ensure_table from '#src/migration/ensure-table.js';
|
|
4
|
+
|
|
5
|
+
export default async function ensure_schema(table_name, data_column, collection_schema, id_strategy) {
|
|
6
|
+
await ensure_table(table_name, data_column, id_strategy);
|
|
7
|
+
|
|
8
|
+
const schema_indexes = resolve_schema_indexes(collection_schema);
|
|
9
|
+
let index_position = 0;
|
|
10
|
+
|
|
11
|
+
while(index_position < schema_indexes.length) {
|
|
12
|
+
const index_definition = schema_indexes[index_position];
|
|
13
|
+
await ensure_index(table_name, index_definition.index_spec, data_column, index_definition.index_options);
|
|
14
|
+
index_position += 1;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {assert_identifier, quote_identifier} from '#src/utils/assert.js';
|
|
2
|
+
import IdStrategies, {assert_valid_id_strategy} from '#src/constants/id-strategies.js';
|
|
3
|
+
import sql_runner from '#src/sql/sql-runner.js';
|
|
4
|
+
|
|
5
|
+
const ID_COLUMN_SQL_BY_STRATEGY = {
|
|
6
|
+
[IdStrategies.bigserial]: 'id BIGSERIAL PRIMARY KEY',
|
|
7
|
+
[IdStrategies.uuidv7]: 'id UUID PRIMARY KEY DEFAULT uuidv7()'
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default async function ensure_table(table_name, data_column, id_strategy) {
|
|
11
|
+
assert_identifier(table_name, 'table_name');
|
|
12
|
+
assert_identifier(data_column, 'data_column');
|
|
13
|
+
assert_valid_id_strategy(id_strategy);
|
|
14
|
+
|
|
15
|
+
const final_id_strategy = id_strategy;
|
|
16
|
+
const table_identifier = quote_identifier(table_name);
|
|
17
|
+
const data_identifier = quote_identifier(data_column);
|
|
18
|
+
const id_column_sql = resolve_id_column_sql(final_id_strategy);
|
|
19
|
+
const sql_text =
|
|
20
|
+
'CREATE TABLE IF NOT EXISTS ' + table_identifier + ' (' +
|
|
21
|
+
id_column_sql + ', ' + data_identifier + ' JSONB NOT NULL, ' +
|
|
22
|
+
'created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), ' +
|
|
23
|
+
'updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()' +
|
|
24
|
+
');';
|
|
25
|
+
|
|
26
|
+
await sql_runner(sql_text, []);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolve_id_column_sql(id_strategy) {
|
|
30
|
+
return ID_COLUMN_SQL_BY_STRATEGY[id_strategy];
|
|
31
|
+
}
|