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,540 @@
|
|
|
1
|
+
import sql_runner from '#src/sql/sql-runner.js';
|
|
2
|
+
import {jsonb_stringify} from '#src/utils/json.js';
|
|
3
|
+
import {quote_identifier} from '#src/utils/assert.js';
|
|
4
|
+
import {deep_clone, has_own} from '#src/utils/object.js';
|
|
5
|
+
import {split_dot_path} from '#src/utils/object-path.js';
|
|
6
|
+
import {is_array} from '#src/utils/array.js';
|
|
7
|
+
import {is_not_object, is_object, is_string} from '#src/utils/value.js';
|
|
8
|
+
|
|
9
|
+
const runtime_state_symbol = Symbol('document_runtime_state');
|
|
10
|
+
|
|
11
|
+
export default function document_instance(model_constructor) {
|
|
12
|
+
const schema_instance = model_constructor.schema_instance;
|
|
13
|
+
const alias_path_map = build_alias_path_map(schema_instance);
|
|
14
|
+
|
|
15
|
+
model_constructor.prototype.validate = function () {
|
|
16
|
+
const validated_payload = schema_instance.validate(this.data);
|
|
17
|
+
this.data = validated_payload;
|
|
18
|
+
return this.data;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
model_constructor.prototype.get = function (path_name, runtime_options) {
|
|
22
|
+
const call_options = is_object(runtime_options) ? runtime_options : {};
|
|
23
|
+
const resolved_path = resolve_alias_path(alias_path_map, path_name);
|
|
24
|
+
const path_segments = split_dot_path(resolved_path);
|
|
25
|
+
const path_state = read_existing_path(this.data, path_segments);
|
|
26
|
+
|
|
27
|
+
if(!path_state.exists) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const field_type = resolve_schema_field_type(schema_instance, resolved_path);
|
|
32
|
+
|
|
33
|
+
if(call_options.getters === false) {
|
|
34
|
+
return path_state.value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if(!field_type || !field_type.options || typeof field_type.options.get !== 'function') {
|
|
38
|
+
return path_state.value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return field_type.apply_get(path_state.value, {
|
|
42
|
+
path: resolved_path,
|
|
43
|
+
mode: 'get'
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
model_constructor.prototype.set = function (path_name, next_value, runtime_options) {
|
|
48
|
+
ensure_runtime_state(this);
|
|
49
|
+
|
|
50
|
+
const call_options = is_object(runtime_options) ? runtime_options : {};
|
|
51
|
+
const resolved_path = resolve_alias_path(alias_path_map, path_name);
|
|
52
|
+
const path_segments = split_dot_path(resolved_path);
|
|
53
|
+
const field_type = resolve_schema_field_type(schema_instance, resolved_path);
|
|
54
|
+
const current_path_state = read_existing_path(this.data, path_segments);
|
|
55
|
+
let assigned_value = next_value;
|
|
56
|
+
|
|
57
|
+
if(field_type) {
|
|
58
|
+
if(call_options.setters !== false) {
|
|
59
|
+
assigned_value = field_type.apply_set(assigned_value, {
|
|
60
|
+
path: resolved_path,
|
|
61
|
+
mode: 'set'
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if(call_options.cast !== false && typeof field_type.cast === 'function') {
|
|
66
|
+
assigned_value = field_type.cast(assigned_value, {
|
|
67
|
+
path: resolved_path,
|
|
68
|
+
mode: 'set'
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
assert_immutable_write_allowed(this, field_type, resolved_path, current_path_state, assigned_value);
|
|
74
|
+
|
|
75
|
+
if(current_path_state.exists && Object.is(current_path_state.value, assigned_value)) {
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if(!is_object(this.data)) {
|
|
80
|
+
this.data = {};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
write_path(this.data, path_segments, assigned_value);
|
|
84
|
+
mark_document_path_modified(this, resolved_path);
|
|
85
|
+
return this;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
model_constructor.prototype.mark_modified = function (path_name) {
|
|
89
|
+
ensure_runtime_state(this);
|
|
90
|
+
|
|
91
|
+
const resolved_path = resolve_alias_path(alias_path_map, path_name);
|
|
92
|
+
split_dot_path(resolved_path);
|
|
93
|
+
mark_document_path_modified(this, resolved_path);
|
|
94
|
+
return this;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
model_constructor.prototype.is_modified = function (path_name) {
|
|
98
|
+
ensure_runtime_state(this);
|
|
99
|
+
|
|
100
|
+
const runtime_state = this[runtime_state_symbol];
|
|
101
|
+
|
|
102
|
+
if(path_name === undefined) {
|
|
103
|
+
return runtime_state.modified_paths.size > 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const resolved_path = resolve_alias_path(alias_path_map, path_name);
|
|
107
|
+
split_dot_path(resolved_path);
|
|
108
|
+
return is_path_marked_modified(runtime_state.modified_paths, resolved_path);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
model_constructor.prototype.clear_modified = function () {
|
|
112
|
+
ensure_runtime_state(this);
|
|
113
|
+
clear_document_modified_paths(this);
|
|
114
|
+
return this;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
define_alias_properties(model_constructor, alias_path_map);
|
|
118
|
+
|
|
119
|
+
model_constructor.prototype.to_object = function (serialization_options) {
|
|
120
|
+
return serialize_document(this, model_constructor, 'to_object', serialization_options);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
model_constructor.prototype.to_json = function (serialization_options) {
|
|
124
|
+
return serialize_document(this, model_constructor, 'to_json', serialization_options);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
model_constructor.prototype.toJSON = function () {
|
|
128
|
+
return this.to_json();
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
model_constructor.prototype.save = async function () {
|
|
132
|
+
ensure_runtime_state(this);
|
|
133
|
+
|
|
134
|
+
const model_options = model_constructor.model_options;
|
|
135
|
+
const table_name = model_options.table_name;
|
|
136
|
+
const data_column = model_options.data_column;
|
|
137
|
+
const table_identifier = quote_identifier(table_name);
|
|
138
|
+
const data_identifier = quote_identifier(data_column);
|
|
139
|
+
const validated_payload = this.validate();
|
|
140
|
+
|
|
141
|
+
await model_constructor.ensure_table();
|
|
142
|
+
|
|
143
|
+
const insert_statement = build_insert_statement(table_identifier, data_identifier, validated_payload);
|
|
144
|
+
const query_result = await sql_runner(insert_statement.sql_text, insert_statement.sql_params);
|
|
145
|
+
const saved_data = query_result.rows[0].data;
|
|
146
|
+
|
|
147
|
+
this.data = saved_data;
|
|
148
|
+
this.is_new = false;
|
|
149
|
+
clear_document_modified_paths(this);
|
|
150
|
+
return saved_data;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function build_insert_statement(table_identifier, data_identifier, validated_payload) {
|
|
155
|
+
const serialized_payload = jsonb_stringify(validated_payload);
|
|
156
|
+
const sql_text = 'INSERT INTO ' + table_identifier + ' (' + data_identifier + ') VALUES ($1::jsonb) RETURNING ' + data_identifier + ' AS data';
|
|
157
|
+
const sql_params = [serialized_payload];
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
sql_text: sql_text,
|
|
161
|
+
sql_params: sql_params
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function serialize_document(document_instance, model_constructor, mode_value, serialization_options) {
|
|
166
|
+
const schema_instance = model_constructor.schema_instance;
|
|
167
|
+
const call_options = is_object(serialization_options) ? serialization_options : {};
|
|
168
|
+
const schema_serialization_options = resolve_schema_serialization_options(schema_instance, mode_value);
|
|
169
|
+
const apply_getters = resolve_getters_option(call_options, schema_serialization_options);
|
|
170
|
+
let serialized_value = deep_clone(document_instance.data);
|
|
171
|
+
|
|
172
|
+
if(apply_getters) {
|
|
173
|
+
serialized_value = apply_schema_getters(serialized_value, schema_instance, mode_value);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const transform_function = resolve_transform_function(call_options, schema_serialization_options);
|
|
177
|
+
|
|
178
|
+
if(typeof transform_function !== 'function') {
|
|
179
|
+
return serialized_value;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const transform_result = transform_function.call(null, document_instance, serialized_value, {
|
|
183
|
+
mode: mode_value
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if(transform_result === undefined) {
|
|
187
|
+
return serialized_value;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return transform_result;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function resolve_schema_serialization_options(schema_instance, mode_value) {
|
|
194
|
+
if(!schema_instance || !is_object(schema_instance.options)) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const option_key = mode_value === 'to_json' ? 'to_json' : 'to_object';
|
|
199
|
+
const option_value = schema_instance.options[option_key];
|
|
200
|
+
|
|
201
|
+
if(!is_object(option_value)) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return option_value;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function resolve_getters_option(call_options, schema_serialization_options) {
|
|
209
|
+
if(call_options.getters !== undefined) {
|
|
210
|
+
return call_options.getters === true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if(schema_serialization_options && schema_serialization_options.getters !== undefined) {
|
|
214
|
+
return schema_serialization_options.getters === true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function resolve_transform_function(call_options, schema_serialization_options) {
|
|
221
|
+
if(call_options.transform === false) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if(typeof call_options.transform === 'function') {
|
|
226
|
+
return call_options.transform;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if(schema_serialization_options && typeof schema_serialization_options.transform === 'function') {
|
|
230
|
+
return schema_serialization_options.transform;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function apply_schema_getters(serialized_value, schema_instance, mode_value) {
|
|
237
|
+
if(!schema_instance || typeof schema_instance.path !== 'function') {
|
|
238
|
+
return serialized_value;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if(is_not_object(serialized_value)) {
|
|
242
|
+
return serialized_value;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const path_names = resolve_schema_paths(schema_instance);
|
|
246
|
+
let path_index = 0;
|
|
247
|
+
|
|
248
|
+
while(path_index < path_names.length) {
|
|
249
|
+
const path_name = path_names[path_index];
|
|
250
|
+
const field_type = schema_instance.path(path_name);
|
|
251
|
+
|
|
252
|
+
if(!field_type || !field_type.options || typeof field_type.options.get !== 'function') {
|
|
253
|
+
path_index += 1;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const path_segments = path_name.split('.');
|
|
258
|
+
const apply_getter = (current_path_value) => {
|
|
259
|
+
return field_type.apply_get(current_path_value, {
|
|
260
|
+
path: path_name,
|
|
261
|
+
mode: mode_value
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
const update_result = update_existing_path(serialized_value, path_segments, apply_getter);
|
|
265
|
+
|
|
266
|
+
if(!update_result.exists) {
|
|
267
|
+
path_index += 1;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
path_index += 1;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return serialized_value;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function resolve_schema_paths(schema_instance) {
|
|
278
|
+
const schema_description = schema_instance.schema_description;
|
|
279
|
+
|
|
280
|
+
if(!schema_description || !is_array(schema_description.paths)) {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const path_entries = [];
|
|
285
|
+
let path_index = 0;
|
|
286
|
+
|
|
287
|
+
while(path_index < schema_description.paths.length) {
|
|
288
|
+
const path_name = schema_description.paths[path_index];
|
|
289
|
+
|
|
290
|
+
path_entries.push({
|
|
291
|
+
path_name: path_name,
|
|
292
|
+
depth: path_name.split('.').length
|
|
293
|
+
});
|
|
294
|
+
path_index += 1;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
path_entries.sort(sort_paths_by_getter_order);
|
|
298
|
+
|
|
299
|
+
const sorted_paths = [];
|
|
300
|
+
path_index = 0;
|
|
301
|
+
|
|
302
|
+
while(path_index < path_entries.length) {
|
|
303
|
+
sorted_paths.push(path_entries[path_index].path_name);
|
|
304
|
+
path_index += 1;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return sorted_paths;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function sort_paths_by_getter_order(left_path_entry, right_path_entry) {
|
|
311
|
+
const left_depth = left_path_entry.depth;
|
|
312
|
+
const right_depth = right_path_entry.depth;
|
|
313
|
+
|
|
314
|
+
if(left_depth !== right_depth) {
|
|
315
|
+
return right_depth - left_depth;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return left_path_entry.path_name.localeCompare(right_path_entry.path_name);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function build_alias_path_map(schema_instance) {
|
|
322
|
+
const alias_path_map = Object.create(null);
|
|
323
|
+
const schema_description = schema_instance && schema_instance.schema_description;
|
|
324
|
+
|
|
325
|
+
if(!schema_description || !is_array(schema_description.paths) || typeof schema_instance.path !== 'function') {
|
|
326
|
+
return alias_path_map;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let path_index = 0;
|
|
330
|
+
|
|
331
|
+
while(path_index < schema_description.paths.length) {
|
|
332
|
+
const path_name = schema_description.paths[path_index];
|
|
333
|
+
const field_type = schema_instance.path(path_name);
|
|
334
|
+
const alias_value = field_type && field_type.options ? field_type.options.alias : undefined;
|
|
335
|
+
|
|
336
|
+
if(!is_string(alias_value) || alias_value.length === 0) {
|
|
337
|
+
path_index += 1;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if(has_own(alias_path_map, alias_value) && alias_path_map[alias_value] !== path_name) {
|
|
342
|
+
throw new Error('Duplicate alias "' + alias_value + '" for paths "' + alias_path_map[alias_value] + '" and "' + path_name + '"');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
alias_path_map[alias_value] = path_name;
|
|
346
|
+
path_index += 1;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return alias_path_map;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function define_alias_properties(model_constructor, alias_path_map) {
|
|
353
|
+
const alias_names = Object.keys(alias_path_map);
|
|
354
|
+
let alias_index = 0;
|
|
355
|
+
|
|
356
|
+
while(alias_index < alias_names.length) {
|
|
357
|
+
const alias_name = alias_names[alias_index];
|
|
358
|
+
|
|
359
|
+
if(alias_name.indexOf('.') !== -1) {
|
|
360
|
+
alias_index += 1;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if(alias_name in model_constructor.prototype) {
|
|
365
|
+
throw new Error('Alias "' + alias_name + '" conflicts with an existing document property');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
Object.defineProperty(model_constructor.prototype, alias_name, {
|
|
369
|
+
configurable: true,
|
|
370
|
+
enumerable: false,
|
|
371
|
+
get: function () {
|
|
372
|
+
return this.get(alias_name);
|
|
373
|
+
},
|
|
374
|
+
set: function (next_value) {
|
|
375
|
+
this.set(alias_name, next_value);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
alias_index += 1;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function resolve_alias_path(alias_path_map, path_name) {
|
|
383
|
+
if(has_own(alias_path_map, path_name)) {
|
|
384
|
+
return alias_path_map[path_name];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return path_name;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function resolve_schema_field_type(schema_instance, path_name) {
|
|
391
|
+
if(!schema_instance || typeof schema_instance.path !== 'function') {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return schema_instance.path(path_name);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function ensure_runtime_state(document_value) {
|
|
399
|
+
if(typeof document_value.is_new !== 'boolean') {
|
|
400
|
+
document_value.is_new = true;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if(!document_value[runtime_state_symbol]) {
|
|
404
|
+
document_value[runtime_state_symbol] = {
|
|
405
|
+
modified_paths: new Set()
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return document_value[runtime_state_symbol];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function assert_immutable_write_allowed(document_value, field_type, path_name, current_path_state, assigned_value) {
|
|
413
|
+
if(!field_type || !field_type.options || field_type.options.immutable !== true) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if(document_value.is_new === true) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const current_value = current_path_state.exists ? current_path_state.value : undefined;
|
|
422
|
+
|
|
423
|
+
if(Object.is(current_value, assigned_value)) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
throw new Error('Path "' + path_name + '" is immutable');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function mark_document_path_modified(document_value, path_name) {
|
|
431
|
+
const runtime_state = ensure_runtime_state(document_value);
|
|
432
|
+
runtime_state.modified_paths.add(path_name);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function clear_document_modified_paths(document_value) {
|
|
436
|
+
const runtime_state = ensure_runtime_state(document_value);
|
|
437
|
+
runtime_state.modified_paths.clear();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function is_path_marked_modified(modified_paths, path_name) {
|
|
441
|
+
if(modified_paths.has(path_name)) {
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const modified_path_values = Array.from(modified_paths);
|
|
446
|
+
let path_index = 0;
|
|
447
|
+
|
|
448
|
+
while(path_index < modified_path_values.length) {
|
|
449
|
+
const modified_path = modified_path_values[path_index];
|
|
450
|
+
|
|
451
|
+
if(is_parent_or_child_path(modified_path, path_name)) {
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
path_index += 1;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function is_parent_or_child_path(left_path, right_path) {
|
|
462
|
+
return left_path.indexOf(right_path + '.') === 0 || right_path.indexOf(left_path + '.') === 0;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function read_existing_path(root_object, path_segments) {
|
|
466
|
+
let current_value = root_object;
|
|
467
|
+
let segment_index = 0;
|
|
468
|
+
|
|
469
|
+
while(segment_index < path_segments.length) {
|
|
470
|
+
const segment_value = path_segments[segment_index];
|
|
471
|
+
|
|
472
|
+
if(is_not_object(current_value) || !has_own(current_value, segment_value)) {
|
|
473
|
+
return {
|
|
474
|
+
exists: false,
|
|
475
|
+
value: undefined
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
current_value = current_value[segment_value];
|
|
480
|
+
segment_index += 1;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
exists: true,
|
|
485
|
+
value: current_value
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function write_path(root_object, path_segments, next_value) {
|
|
490
|
+
let current_value = root_object;
|
|
491
|
+
let segment_index = 0;
|
|
492
|
+
|
|
493
|
+
while(segment_index < path_segments.length) {
|
|
494
|
+
const segment_value = path_segments[segment_index];
|
|
495
|
+
const is_leaf = segment_index === path_segments.length - 1;
|
|
496
|
+
|
|
497
|
+
if(is_leaf) {
|
|
498
|
+
current_value[segment_value] = next_value;
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const has_segment = has_own(current_value, segment_value);
|
|
503
|
+
const segment_value_ref = has_segment ? current_value[segment_value] : undefined;
|
|
504
|
+
const should_create_container = !has_segment || is_not_object(segment_value_ref);
|
|
505
|
+
|
|
506
|
+
if(should_create_container) {
|
|
507
|
+
current_value[segment_value] = {};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
current_value = current_value[segment_value];
|
|
511
|
+
segment_index += 1;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function update_existing_path(root_object, path_segments, update_value) {
|
|
516
|
+
let current_value = root_object;
|
|
517
|
+
let segment_index = 0;
|
|
518
|
+
|
|
519
|
+
while(segment_index < path_segments.length) {
|
|
520
|
+
const segment_value = path_segments[segment_index];
|
|
521
|
+
const is_leaf = segment_index === path_segments.length - 1;
|
|
522
|
+
|
|
523
|
+
if(is_not_object(current_value) || !has_own(current_value, segment_value)) {
|
|
524
|
+
return {
|
|
525
|
+
exists: false
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if(is_leaf) {
|
|
530
|
+
current_value[segment_value] = update_value(current_value[segment_value]);
|
|
531
|
+
return {
|
|
532
|
+
exists: true
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
current_value = current_value[segment_value];
|
|
537
|
+
segment_index += 1;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
}
|