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,82 @@
1
+ /*
2
+ - Assumptions and trade-offs:
3
+ - Introspection resolves declared FieldType paths plus nested object path metadata.
4
+ */
5
+ export function create_path_introspection(parsed_schema) {
6
+ const parsed_value = parsed_schema ?? {};
7
+ const field_types = resolve_field_types_map(parsed_value);
8
+ const object_paths = resolve_object_paths(parsed_value);
9
+
10
+ return {
11
+ field_types,
12
+ object_paths
13
+ };
14
+ }
15
+
16
+ export function get_path_field_type(path_introspection, path_name) {
17
+ if(!path_introspection || !path_name) {
18
+ return null;
19
+ }
20
+
21
+ const field_types = resolve_field_types_map(path_introspection);
22
+ return field_types[path_name] ?? null;
23
+ }
24
+
25
+ export function get_path_type(path_introspection, path_name) {
26
+ const field_type = get_path_field_type(path_introspection, path_name);
27
+
28
+ if(field_type) {
29
+ return field_type.instance;
30
+ }
31
+
32
+ const object_paths = resolve_object_paths(path_introspection);
33
+
34
+ if(object_paths.has(path_name)) {
35
+ return 'object';
36
+ }
37
+
38
+ return null;
39
+ }
40
+
41
+ export function is_array_root(path_introspection, path_name) {
42
+ if(!path_introspection || !path_name) {
43
+ return false;
44
+ }
45
+
46
+ const root_path = String(path_name).split('.')[0];
47
+ const root_field_type = get_path_field_type(path_introspection, root_path);
48
+
49
+ if(!root_field_type) {
50
+ return false;
51
+ }
52
+
53
+ return root_field_type.instance === 'Array';
54
+ }
55
+
56
+ function resolve_field_types_map(value) {
57
+ if(!value || typeof value !== 'object') {
58
+ return Object.create(null);
59
+ }
60
+
61
+ const field_types = value.field_types;
62
+
63
+ if(field_types && typeof field_types === 'object') {
64
+ return field_types;
65
+ }
66
+
67
+ return Object.create(null);
68
+ }
69
+
70
+ function resolve_object_paths(value) {
71
+ if(!value) {
72
+ return new Set();
73
+ }
74
+
75
+ const object_paths = value.object_paths;
76
+
77
+ if(object_paths instanceof Set) {
78
+ return object_paths;
79
+ }
80
+
81
+ return new Set();
82
+ }
@@ -0,0 +1,212 @@
1
+ /*
2
+ Assumptions and trade-offs:
3
+ - Validation/casting is FieldType-driven and unknown payload keys are preserved.
4
+ - Error aggregation mirrors abortEarly=false behavior with deterministic per-path detail records.
5
+ */
6
+ import field_definition_parser from '#src/schema/field-definition-parser.js';
7
+ import {
8
+ create_path_introspection,
9
+ get_path_field_type,
10
+ get_path_type as resolve_path_type,
11
+ is_array_root as resolve_is_array_root
12
+ } from '#src/schema/path-introspection.js';
13
+ import {has_own} from '#src/utils/object.js';
14
+ export default function schema_compiler(schema_definition) {
15
+ const parsed_schema = field_definition_parser(schema_definition || {});
16
+ const path_introspection = create_path_introspection(parsed_schema);
17
+ const field_types = path_introspection.field_types;
18
+ const sorted_paths = Object.keys(field_types).sort(sort_paths_by_depth);
19
+
20
+ return {
21
+ validate: function (payload) {
22
+ return validate_payload(payload, sorted_paths, field_types);
23
+ },
24
+ path: function (path_name) {
25
+ return get_path_field_type(path_introspection, path_name);
26
+ },
27
+ get_path_type: function (path_name) {
28
+ return resolve_path_type(path_introspection, path_name);
29
+ },
30
+ is_array_root: function (path_name) {
31
+ return resolve_is_array_root(path_introspection, path_name);
32
+ },
33
+ describe: function () {
34
+ return {
35
+ paths: Object.keys(field_types),
36
+ objects: Array.from(path_introspection.object_paths)
37
+ };
38
+ }
39
+ };
40
+ }
41
+
42
+ function validate_payload(payload, sorted_paths, field_types) {
43
+ if(payload === null || typeof payload !== 'object' || Array.isArray(payload)) {
44
+ return {
45
+ value: payload,
46
+ error: {
47
+ details: [{
48
+ path: '',
49
+ code: 'validation_error',
50
+ message: 'Schema validation failed',
51
+ type: 'object'
52
+ }]
53
+ }
54
+ };
55
+ }
56
+
57
+ const result_payload = clone_value(payload);
58
+ const detail_list = [];
59
+ let path_index = 0;
60
+
61
+ while(path_index < sorted_paths.length) {
62
+ const path_name = sorted_paths[path_index];
63
+ const field_type = field_types[path_name];
64
+ const path_segments = path_name.split('.');
65
+ const current_path_state = read_path(result_payload, path_segments);
66
+ const current_value = current_path_state.exists ? current_path_state.value : undefined;
67
+ let normalized_value = undefined;
68
+
69
+ try {
70
+ normalized_value = field_type.normalize(current_value, {path: path_name});
71
+ } catch(error) {
72
+ detail_list.push(format_field_error(path_name, error));
73
+ path_index += 1;
74
+ continue;
75
+ }
76
+
77
+ if(normalized_value === undefined) {
78
+ path_index += 1;
79
+ continue;
80
+ }
81
+
82
+ write_path(result_payload, path_segments, normalized_value);
83
+ path_index += 1;
84
+ }
85
+
86
+ if(detail_list.length > 0) {
87
+ return {
88
+ value: result_payload,
89
+ error: {
90
+ details: detail_list
91
+ }
92
+ };
93
+ }
94
+
95
+ return {
96
+ value: result_payload,
97
+ error: null
98
+ };
99
+ }
100
+
101
+ function sort_paths_by_depth(left_path, right_path) {
102
+ const left_depth = left_path.split('.').length;
103
+ const right_depth = right_path.split('.').length;
104
+
105
+ if(left_depth !== right_depth) {
106
+ return left_depth - right_depth;
107
+ }
108
+
109
+ return left_path.localeCompare(right_path);
110
+ }
111
+
112
+ function format_field_error(path_name, error) {
113
+ const error_message = error && error.message ? error.message : 'Schema validation failed';
114
+ const error_code = error && error.code ? error.code : 'validation_error';
115
+
116
+ return {
117
+ path: path_name,
118
+ code: error_code,
119
+ message: error_message,
120
+ type: error_code
121
+ };
122
+ }
123
+
124
+ function read_path(root_object, path_segments) {
125
+ let current_value = root_object;
126
+ let segment_index = 0;
127
+
128
+ while(segment_index < path_segments.length) {
129
+ const segment_value = path_segments[segment_index];
130
+
131
+ if(current_value === null || typeof current_value !== 'object' || !has_own(current_value, segment_value)) {
132
+ return {
133
+ exists: false,
134
+ value: undefined
135
+ };
136
+ }
137
+
138
+ current_value = current_value[segment_value];
139
+ segment_index += 1;
140
+ }
141
+
142
+ return {
143
+ exists: true,
144
+ value: current_value
145
+ };
146
+ }
147
+
148
+ function write_path(root_object, path_segments, next_value) {
149
+ let current_value = root_object;
150
+ let segment_index = 0;
151
+
152
+ while(segment_index < path_segments.length) {
153
+ const segment_value = path_segments[segment_index];
154
+ const is_leaf = segment_index === path_segments.length - 1;
155
+
156
+ if(is_leaf) {
157
+ current_value[segment_value] = next_value;
158
+ return;
159
+ }
160
+
161
+ // TODO: implement a code review agent that catches these
162
+ if(
163
+ !has_own(current_value, segment_value) ||
164
+ current_value[segment_value] === null ||
165
+ typeof current_value[segment_value] !== 'object' ||
166
+ Array.isArray(current_value[segment_value])
167
+ ) {
168
+ current_value[segment_value] = {};
169
+ }
170
+
171
+ current_value = current_value[segment_value];
172
+ segment_index += 1;
173
+ }
174
+ }
175
+
176
+ function clone_value(value) {
177
+ if(Array.isArray(value)) {
178
+ const cloned_array = [];
179
+ let value_index = 0;
180
+
181
+ while(value_index < value.length) {
182
+ cloned_array.push(clone_value(value[value_index]));
183
+ value_index += 1;
184
+ }
185
+
186
+ return cloned_array;
187
+ }
188
+
189
+ if(value instanceof Date) {
190
+ return new Date(value.getTime());
191
+ }
192
+
193
+ if(Buffer.isBuffer(value)) {
194
+ return Buffer.from(value);
195
+ }
196
+
197
+ if(value && typeof value === 'object') {
198
+ const cloned_object = {};
199
+ const object_entries = Object.entries(value);
200
+ let entry_index = 0;
201
+
202
+ while(entry_index < object_entries.length) {
203
+ const object_entry = object_entries[entry_index];
204
+ cloned_object[object_entry[0]] = clone_value(object_entry[1]);
205
+ entry_index += 1;
206
+ }
207
+
208
+ return cloned_object;
209
+ }
210
+
211
+ return value;
212
+ }
@@ -0,0 +1,234 @@
1
+ import defaults from '#src/constants/defaults.js';
2
+
3
+ import ValidationError from '#src/errors/validation-error.js';
4
+ import schema_compiler from '#src/schema/schema-compiler.js';
5
+
6
+ import {default_field_type_registry} from '#src/field-types/registry.js';
7
+ import {assert_condition, assert_identifier, assert_path} from '#src/utils/assert.js';
8
+ import {is_object, is_string} from '#src/utils/value.js';
9
+ import {has_own} from '#src/utils/object.js';
10
+
11
+ export default function Schema(schema_def, options) {
12
+ this.schema_def = schema_def || {};
13
+ this.options = Object.assign({}, defaults.schema_options, options || {});
14
+ this.compiled_schema = schema_compiler(this.schema_def);
15
+ this.schema_description = this.compiled_schema.describe();
16
+ this.indexes = [];
17
+
18
+ register_path_level_indexes(this, this.schema_def, '', default_field_type_registry);
19
+ }
20
+
21
+ Schema.prototype.validate = function (payload) {
22
+ const validation_result = this.compiled_schema.validate(payload);
23
+
24
+ if(validation_result.error) {
25
+ throw new ValidationError('Schema validation failed', validation_result.error.details);
26
+ }
27
+
28
+ return validation_result.value;
29
+ };
30
+
31
+ Schema.prototype.path = function (path_name) {
32
+ if(!this.compiled_schema || typeof this.compiled_schema.path !== 'function') {
33
+ return null;
34
+ }
35
+
36
+ return this.compiled_schema.path(path_name);
37
+ };
38
+
39
+ Schema.prototype.get_path_type = function (path_name) {
40
+ if(!this.compiled_schema || typeof this.compiled_schema.get_path_type !== 'function') {
41
+ return null;
42
+ }
43
+
44
+ return this.compiled_schema.get_path_type(path_name);
45
+ };
46
+
47
+ Schema.prototype.is_array_root = function (path_name) {
48
+ if(!this.compiled_schema || typeof this.compiled_schema.is_array_root !== 'function') {
49
+ return false;
50
+ }
51
+
52
+ return this.compiled_schema.is_array_root(path_name);
53
+ };
54
+
55
+ Schema.prototype.create_index = function (index_spec, index_options) {
56
+ const normalized_index_spec = normalize_index_spec(index_spec);
57
+ const normalized_index_options = normalize_index_options(index_options);
58
+
59
+ this.indexes.push({
60
+ index_spec: normalized_index_spec,
61
+ index_options: normalized_index_options
62
+ });
63
+
64
+ return this;
65
+ };
66
+
67
+ Schema.prototype.get_indexes = function () {
68
+ const index_definitions = [];
69
+ let index_position = 0;
70
+
71
+ while(index_position < this.indexes.length) {
72
+ const index_definition = this.indexes[index_position];
73
+ const index_spec = clone_index_spec(index_definition.index_spec);
74
+ const index_options = Object.assign({}, index_definition.index_options);
75
+
76
+ index_definitions.push({
77
+ index_spec: index_spec,
78
+ index_options: index_options
79
+ });
80
+ index_position += 1;
81
+ }
82
+
83
+ return index_definitions;
84
+ };
85
+
86
+ function register_path_level_indexes(schema_instance, schema_definition, parent_path, field_registry) {
87
+ const schema_entries = Object.entries(schema_definition);
88
+ let entry_position = 0;
89
+
90
+ while(entry_position < schema_entries.length) {
91
+ const schema_entry = schema_entries[entry_position];
92
+ const path_segment = schema_entry[0];
93
+ const field_definition = schema_entry[1];
94
+ const path_value = parent_path ? parent_path + '.' + path_segment : path_segment;
95
+
96
+ if(is_explicit_field_definition(field_definition, field_registry)) {
97
+ register_index_option(schema_instance, path_value, field_definition.index);
98
+ entry_position += 1;
99
+ continue;
100
+ }
101
+
102
+ if(is_plain_object(field_definition)) {
103
+ register_path_level_indexes(schema_instance, field_definition, path_value, field_registry);
104
+ }
105
+
106
+ entry_position += 1;
107
+ }
108
+ }
109
+
110
+ function is_explicit_field_definition(field_definition, field_registry) {
111
+ if(!is_plain_object(field_definition) || !has_own(field_definition, 'type')) {
112
+ return false;
113
+ }
114
+
115
+ const type_key = field_definition.type;
116
+
117
+ if(Array.isArray(type_key)) {
118
+ return true;
119
+ }
120
+
121
+ return field_registry.has_field_type(type_key);
122
+ }
123
+
124
+ function register_index_option(schema_instance, path_value, index_option) {
125
+ if(index_option === undefined || index_option === false) {
126
+ return;
127
+ }
128
+
129
+ if(index_option === true) {
130
+ schema_instance.create_index(path_value);
131
+ return;
132
+ }
133
+
134
+ if(index_option === 1 || index_option === -1) {
135
+ schema_instance.create_index({
136
+ [path_value]: index_option
137
+ });
138
+ return;
139
+ }
140
+
141
+ if(is_plain_object(index_option)) {
142
+ const index_options = Object.assign({}, index_option);
143
+ const direction_value = resolve_path_level_index_direction(path_value, index_options);
144
+
145
+ schema_instance.create_index({
146
+ [path_value]: direction_value
147
+ }, index_options);
148
+ return;
149
+ }
150
+
151
+ throw new Error('index option at path "' + path_value + '" must be true, false, 1, -1, or an options object');
152
+ }
153
+
154
+ function resolve_path_level_index_direction(path_value, index_options) {
155
+ let direction_value = 1;
156
+
157
+ if(has_own(index_options, 'direction')) {
158
+ direction_value = index_options.direction;
159
+ delete index_options.direction;
160
+ }
161
+
162
+ if(has_own(index_options, 'order')) {
163
+ direction_value = index_options.order;
164
+ delete index_options.order;
165
+ }
166
+
167
+ assert_condition(direction_value === 1 || direction_value === -1, 'index direction for path "' + path_value + '" must be 1 or -1');
168
+
169
+ return direction_value;
170
+ }
171
+
172
+ function normalize_index_spec(index_spec) {
173
+ if(is_string(index_spec)) {
174
+ assert_path(index_spec, 'index path');
175
+ return index_spec;
176
+ }
177
+
178
+ assert_condition(is_object(index_spec), 'index_spec must be a path string or an object map of paths to sort directions');
179
+
180
+ const index_entries = Object.entries(index_spec);
181
+ assert_condition(index_entries.length > 0, 'index_spec object must define at least one indexed path');
182
+
183
+ const normalized_spec = {};
184
+ let entry_position = 0;
185
+
186
+ while(entry_position < index_entries.length) {
187
+ const index_entry = index_entries[entry_position];
188
+ const path_value = index_entry[0];
189
+ const direction_value = index_entry[1];
190
+
191
+ assert_path(path_value, 'index path');
192
+ assert_condition(
193
+ direction_value === 1 || direction_value === -1,
194
+ 'index direction for path "' + path_value + '" must be 1 or -1'
195
+ );
196
+ normalized_spec[path_value] = direction_value;
197
+ entry_position += 1;
198
+ }
199
+
200
+ return normalized_spec;
201
+ }
202
+
203
+ function normalize_index_options(index_options) {
204
+ if(index_options === undefined) {
205
+ return {};
206
+ }
207
+
208
+ assert_condition(is_object(index_options), 'index_options must be an object');
209
+
210
+ const normalized_options = Object.assign({}, index_options);
211
+
212
+ if(normalized_options.unique !== undefined) {
213
+ assert_condition(typeof normalized_options.unique === 'boolean', 'index_options.unique must be a boolean');
214
+ }
215
+
216
+ if(normalized_options.name !== undefined) {
217
+ assert_identifier(normalized_options.name, 'index_options.name');
218
+ }
219
+
220
+ return normalized_options;
221
+ }
222
+
223
+ function clone_index_spec(index_spec) {
224
+ if(is_string(index_spec)) {
225
+ return index_spec;
226
+ }
227
+
228
+ return Object.assign({}, index_spec);
229
+ }
230
+
231
+ function is_plain_object(value) {
232
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
233
+ }
234
+
@@ -0,0 +1,13 @@
1
+ export function create_parameter_state(start_index) {
2
+ return {
3
+ params: [],
4
+ current_index: start_index || 1
5
+ };
6
+ }
7
+
8
+ export function bind_parameter(parameter_state, value) {
9
+ const placeholder = '$' + parameter_state.current_index;
10
+ parameter_state.params.push(value);
11
+ parameter_state.current_index += 1;
12
+ return placeholder;
13
+ }
@@ -0,0 +1,31 @@
1
+ import debug_logger from '#src/debug/debug-logger.js';
2
+ import QueryError from '#src/errors/query-error.js';
3
+ import {get_debug_mode, get_pool} from '#src/connection/pool-store.js';
4
+
5
+ export default async function sql_runner(sql_text, sql_params) {
6
+ const params = sql_params || [];
7
+ const debug_mode = get_debug_mode();
8
+ const pool_instance = get_pool();
9
+
10
+ debug_logger(debug_mode, 'sql_query', {
11
+ sql_text: sql_text,
12
+ params: params
13
+ });
14
+
15
+ try {
16
+ return await pool_instance.query(sql_text, params);
17
+ } catch(error) {
18
+ debug_logger(debug_mode, 'sql_error', {
19
+ sql_text: sql_text,
20
+ params: params,
21
+ message: error.message
22
+ });
23
+
24
+ throw new QueryError('SQL execution failed', {
25
+ sql_text: sql_text,
26
+ params: params,
27
+ cause: error.message
28
+ });
29
+ }
30
+ }
31
+
@@ -0,0 +1,31 @@
1
+ const is_not_array = (value) => {
2
+ return !Array.isArray(value);
3
+ };
4
+
5
+ const is_array = (value) => {
6
+ return Array.isArray(value);
7
+ };
8
+
9
+ const to_array = (object) => {
10
+ if(Array.isArray(object)) {
11
+ return object;
12
+ }
13
+
14
+ if(object instanceof Set) {
15
+ return Array.from(object);
16
+ }
17
+
18
+ return object != null ? [object] : [];
19
+ };
20
+
21
+ export {
22
+ to_array,
23
+ is_array,
24
+ is_not_array
25
+ };
26
+
27
+ export default {
28
+ to_array,
29
+ is_array,
30
+ is_not_array
31
+ };
@@ -0,0 +1,27 @@
1
+ const identifier_pattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
2
+ const path_pattern = /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
3
+
4
+ export function assert_condition(condition_value, message) {
5
+ if(!condition_value) {
6
+ throw new Error(message || 'Assertion failed');
7
+ }
8
+ }
9
+
10
+ export function assert_identifier(identifier_value, label) {
11
+ const label_value = label || 'identifier';
12
+
13
+ assert_condition(typeof identifier_value === 'string', label_value + ' must be a string');
14
+ assert_condition(identifier_pattern.test(identifier_value), label_value + ' has invalid characters');
15
+ }
16
+
17
+ export function assert_path(path_value, label) {
18
+ const label_value = label || 'path';
19
+
20
+ assert_condition(typeof path_value === 'string', label_value + ' must be a string');
21
+ assert_condition(path_pattern.test(path_value), label_value + ' has invalid characters');
22
+ }
23
+
24
+ export function quote_identifier(identifier_value) {
25
+ assert_identifier(identifier_value, 'identifier');
26
+ return '"' + identifier_value + '"';
27
+ }
@@ -0,0 +1,9 @@
1
+ export function safe_json_stringify(value) {
2
+ try {
3
+ return JSON.stringify(value);
4
+ } catch(error) {
5
+ return '"[unserializable]"';
6
+ }
7
+ }
8
+
9
+ export default safe_json_stringify;
@@ -0,0 +1,21 @@
1
+ function jsonb_bigint_replacer(key, value) {
2
+ if(typeof value === 'bigint') {
3
+ return value.toString();
4
+ }
5
+
6
+ return value;
7
+ }
8
+
9
+ function jsonb_stringify(value) {
10
+ return JSON.stringify(value, jsonb_bigint_replacer);
11
+ }
12
+
13
+ export {
14
+ jsonb_stringify,
15
+ jsonb_bigint_replacer
16
+ };
17
+
18
+ export default {
19
+ jsonb_stringify,
20
+ jsonb_bigint_replacer
21
+ };
@@ -0,0 +1,33 @@
1
+ import {assert_path} from '#src/utils/assert.js';
2
+
3
+ export function split_dot_path(path_value) {
4
+ assert_path(path_value, 'path');
5
+ return path_value.split('.');
6
+ }
7
+
8
+ export function build_path_literal(path_segments) {
9
+ return '{' + path_segments.join(',') + '}';
10
+ }
11
+
12
+ export function build_nested_object(path_value, leaf_value) {
13
+ const path_segments = split_dot_path(path_value);
14
+ const root_object = {};
15
+ let current_object = root_object;
16
+ let path_index = 0;
17
+
18
+ while(path_index < path_segments.length) {
19
+ const path_segment = path_segments[path_index];
20
+ const is_leaf = path_index === path_segments.length - 1;
21
+
22
+ if(is_leaf) {
23
+ current_object[path_segment] = leaf_value;
24
+ break;
25
+ }
26
+
27
+ current_object[path_segment] = {};
28
+ current_object = current_object[path_segment];
29
+ path_index += 1;
30
+ }
31
+
32
+ return root_object;
33
+ }