@fgv/ts-json 5.0.1-8 → 5.0.1

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.
@@ -0,0 +1,416 @@
1
+ /*
2
+ * Copyright (c) 2020 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import { captureResult, fail, failWithDetail, mapDetailedResults, mapResults, succeed, succeedWithDetail } from '@fgv/ts-utils';
23
+ import { ConditionalJsonEditorRule, MultiValueJsonEditorRule, ReferenceJsonEditorRule, TemplatedJsonEditorRule } from './rules';
24
+ import { isJsonArray, isJsonObject, isJsonPrimitive } from '@fgv/ts-json-base';
25
+ import { JsonEditorState } from './jsonEditorState';
26
+ /**
27
+ * A {@link JsonEditor | JsonEditor} can be used to edit JSON objects in place or to
28
+ * clone any JSON value, applying a default context and optional set of editor rules that
29
+ * were supplied at initialization.
30
+ * @public
31
+ */
32
+ export class JsonEditor {
33
+ /**
34
+ * Protected constructor for {@link JsonEditor | JsonEditor} and derived classes.
35
+ * External consumers should instantiate via the {@link JsonEditor.create | create static method}.
36
+ * @param options - Optional partial {@link IJsonEditorOptions | editor options} for the
37
+ * constructed editor.
38
+ * @param rules - Any {@link IJsonEditorRule | editor rules} to be applied by the editor.
39
+ * @internal
40
+ */
41
+ constructor(options, rules) {
42
+ this.options = JsonEditor._getDefaultOptions(options).orThrow();
43
+ this._rules = rules || JsonEditor.getDefaultRules(this.options).orThrow();
44
+ }
45
+ /**
46
+ * Default singleton {@link JsonEditor | JsonEditor} for simple use. Applies all rules
47
+ * but with no default context.
48
+ */
49
+ static get default() {
50
+ if (!JsonEditor._default) {
51
+ const rules = this.getDefaultRules().orDefault();
52
+ JsonEditor._default = new JsonEditor(undefined, rules);
53
+ }
54
+ return JsonEditor._default;
55
+ }
56
+ /**
57
+ * Constructs a new {@link JsonEditor | JsonEditor}.
58
+ * @param options - Optional partial {@link IJsonEditorOptions | editor options} for the
59
+ * constructed editor.
60
+ * @param rules - Optional set of {@link IJsonEditorRule | editor rules} to be applied by the editor.
61
+ * @readonly A new {@link JsonEditor | JsonEditor}.
62
+ */
63
+ static create(options, rules) {
64
+ return captureResult(() => new JsonEditor(options, rules));
65
+ }
66
+ /**
67
+ * Gets the default set of rules to be applied for a given set of options.
68
+ * By default, all available rules (templates, conditionals, multi-value and references)
69
+ * are applied.
70
+ * @param options - Optional partial {@link IJsonEditorOptions | editor options} for
71
+ * all rules.
72
+ * @returns Default {@link IJsonEditorRule | editor rules} with any supplied options
73
+ * applied.
74
+ */
75
+ static getDefaultRules(options) {
76
+ return mapResults([
77
+ TemplatedJsonEditorRule.create(options),
78
+ ConditionalJsonEditorRule.create(options),
79
+ MultiValueJsonEditorRule.create(options),
80
+ ReferenceJsonEditorRule.create(options)
81
+ ]);
82
+ }
83
+ /**
84
+ * Creates a complete IJsonEditorOptions object from partial options, filling in
85
+ * default values for any missing properties. This ensures all editor instances
86
+ * have consistent, complete configuration including validation rules and merge behavior.
87
+ * @param options - Optional partial editor options to merge with defaults
88
+ * @returns Success with complete editor options, or Failure if validation fails
89
+ * @internal
90
+ */
91
+ static _getDefaultOptions(options) {
92
+ var _a;
93
+ const context = options === null || options === void 0 ? void 0 : options.context;
94
+ let validation = options === null || options === void 0 ? void 0 : options.validation;
95
+ if (validation === undefined) {
96
+ validation = {
97
+ onInvalidPropertyName: 'error',
98
+ onInvalidPropertyValue: 'error',
99
+ onUndefinedPropertyValue: 'ignore'
100
+ };
101
+ }
102
+ let merge = options === null || options === void 0 ? void 0 : options.merge;
103
+ if (merge === undefined) {
104
+ merge = {
105
+ arrayMergeBehavior: 'append',
106
+ nullAsDelete: false
107
+ };
108
+ }
109
+ else {
110
+ merge = {
111
+ arrayMergeBehavior: merge.arrayMergeBehavior,
112
+ nullAsDelete: (_a = merge.nullAsDelete) !== null && _a !== void 0 ? _a : false
113
+ };
114
+ }
115
+ return succeed({ context, validation, merge });
116
+ }
117
+ /**
118
+ * Merges a supplied source object into a supplied target, updating the target object.
119
+ * @param target - The target `JsonObject` to be updated
120
+ * @param src - The source `JsonObject` to be merged
121
+ * @param runtimeContext - An optional {@link IJsonContext | IJsonContext} supplying variables
122
+ * and references.
123
+ * @returns `Success` with the original source `JsonObject` if merge was successful.
124
+ * Returns `Failure` with details if an error occurs.
125
+ */
126
+ mergeObjectInPlace(target, src, runtimeContext) {
127
+ const state = new JsonEditorState(this, this.options, runtimeContext);
128
+ return this._mergeObjectInPlace(target, src, state).onSuccess((merged) => {
129
+ return this._finalizeAndMerge(merged, state);
130
+ });
131
+ }
132
+ /**
133
+ * Merges multiple supplied source objects into a supplied target, updating the target
134
+ * object and using the default context supplied at creation time.
135
+ * @param target - The target `JsonObject` to be updated
136
+ * @param srcObjects - `JsonObject`s to be merged into the target object, in the order
137
+ * supplied.
138
+ * @returns `Success` with the original source `JsonObject` if merge was successful.
139
+ * Returns `Failure` with details if an error occurs.
140
+ */
141
+ mergeObjectsInPlace(target, srcObjects) {
142
+ return this.mergeObjectsInPlaceWithContext(this.options.context, target, srcObjects);
143
+ }
144
+ /**
145
+ * Merges multiple supplied source objects into a supplied target, updating the target
146
+ * object and using an optional {@link IJsonContext | context} supplied in the call.
147
+ * @param context - An optional {@link IJsonContext | IJsonContext} supplying variables and
148
+ * references.
149
+ * @param base - The base `JsonObject` to be updated
150
+ * @param srcObjects - Objects to be merged into the target object, in the order supplied.
151
+ * @returns `Success` with the original source `JsonObject` if merge was successful.
152
+ * Returns `Failure` with details if an error occurs.
153
+ */
154
+ mergeObjectsInPlaceWithContext(context, base, srcObjects) {
155
+ for (const src of srcObjects) {
156
+ const mergeResult = this.mergeObjectInPlace(base, src, context);
157
+ if (mergeResult.isFailure()) {
158
+ return mergeResult.withFailureDetail('error');
159
+ }
160
+ }
161
+ return succeedWithDetail(base);
162
+ }
163
+ /**
164
+ * Deep clones a supplied `JsonValue`, applying all editor rules and a default
165
+ * or optionally supplied context
166
+ * @param src - The `JsonValue` to be cloned.
167
+ * @param context - An optional {@link IJsonContext | JSON context} supplying variables and references.
168
+ */
169
+ clone(src, context) {
170
+ const state = new JsonEditorState(this, this.options, context);
171
+ let value = src;
172
+ let valueResult = this._editValue(src, state);
173
+ while (valueResult.isSuccess()) {
174
+ value = valueResult.value;
175
+ valueResult = this._editValue(value, state);
176
+ }
177
+ if (valueResult.detail === 'error' || valueResult.detail === 'ignore') {
178
+ return valueResult;
179
+ }
180
+ if (isJsonPrimitive(value) || value === null) {
181
+ return succeedWithDetail(value, 'edited');
182
+ }
183
+ else if (isJsonObject(value)) {
184
+ return this._cloneObjectWithoutNullAsDelete({}, value, state);
185
+ }
186
+ else if (isJsonArray(value)) {
187
+ return this._cloneArray(value, state.context);
188
+ }
189
+ else if (value === undefined) {
190
+ return state.failValidation('undefinedPropertyValue');
191
+ }
192
+ return state.failValidation('invalidPropertyValue', `Cannot convert invalid JSON: "${JSON.stringify(value)}"`);
193
+ }
194
+ /**
195
+ * Merges properties from a source object into a target object, applying editor rules and
196
+ * null-as-delete logic. This is the core merge implementation that handles property-by-property
197
+ * merging with rule processing and deferred property handling.
198
+ * @param target - The target object to merge properties into
199
+ * @param src - The source object containing properties to merge
200
+ * @param state - The editor state containing options and context
201
+ * @returns Success with the modified target object, or Failure with error details
202
+ * @internal
203
+ */
204
+ _mergeObjectInPlace(target, src, state) {
205
+ for (const key in src) {
206
+ if (src.hasOwnProperty(key)) {
207
+ // Skip dangerous property names to prevent prototype pollution
208
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
209
+ continue;
210
+ }
211
+ const propResult = this._editProperty(key, src[key], state);
212
+ if (propResult.isSuccess()) {
213
+ if (propResult.detail === 'deferred') {
214
+ state.defer(propResult.value);
215
+ }
216
+ else {
217
+ const mergeResult = this._mergeObjectInPlace(target, propResult.value, state);
218
+ if (mergeResult.isFailure()) {
219
+ return mergeResult;
220
+ }
221
+ }
222
+ }
223
+ else if (propResult.detail === 'inapplicable') {
224
+ const valueResult = this.clone(src[key], state.context).onSuccess((cloned) => {
225
+ return this._mergeClonedProperty(target, key, cloned, state);
226
+ });
227
+ if (valueResult.isFailure() && valueResult.detail === 'error') {
228
+ return fail(`${key}: ${valueResult.message}`);
229
+ }
230
+ }
231
+ else if (propResult.detail !== 'ignore') {
232
+ return fail(`${key}: ${propResult.message}`);
233
+ }
234
+ }
235
+ else {
236
+ return fail(`${key}: Cannot merge inherited properties`);
237
+ }
238
+ }
239
+ return succeed(target);
240
+ }
241
+ /**
242
+ * Creates a deep clone of a JSON array by recursively cloning each element.
243
+ * Each array element is cloned using the main clone method, preserving the
244
+ * editor's rules and validation settings.
245
+ * @param src - The source JSON array to clone
246
+ * @param context - Optional JSON context for cloning operations
247
+ * @returns Success with the cloned array, or Failure with error details
248
+ * @internal
249
+ */
250
+ _cloneArray(src, context) {
251
+ const results = src.map((v) => {
252
+ return this.clone(v, context);
253
+ });
254
+ return mapDetailedResults(results, ['ignore'])
255
+ .onSuccess((converted) => {
256
+ return succeed(converted);
257
+ })
258
+ .withFailureDetail('error');
259
+ }
260
+ /**
261
+ * Merges a single cloned property value into a target object. This method handles
262
+ * the core merge logic including null-as-delete behavior, array merging, and
263
+ * recursive object merging. The null-as-delete check occurs before primitive
264
+ * handling to ensure null values can signal property deletion.
265
+ * @param target - The target object to merge the property into
266
+ * @param key - The property key being merged
267
+ * @param newValue - The cloned value to merge (from source object)
268
+ * @param state - The editor state containing merge options and context
269
+ * @returns Success with the merged value, or Failure with error details
270
+ * @internal
271
+ */
272
+ _mergeClonedProperty(target, key, newValue, state) {
273
+ var _a, _b, _c;
274
+ // Skip dangerous property names to prevent prototype pollution
275
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
276
+ return succeedWithDetail(newValue, 'edited');
277
+ }
278
+ const existing = target[key];
279
+ // Handle null-as-delete behavior before primitive check
280
+ /* c8 ignore next 1 - ? is defense in depth */
281
+ if (newValue === null && ((_a = state.options.merge) === null || _a === void 0 ? void 0 : _a.nullAsDelete) === true) {
282
+ delete target[key];
283
+ return succeedWithDetail(null, 'edited');
284
+ }
285
+ // merge is called right after clone so this should never happen
286
+ // since clone itself will have failed
287
+ if (isJsonPrimitive(newValue)) {
288
+ target[key] = newValue;
289
+ return succeedWithDetail(newValue, 'edited');
290
+ }
291
+ if (isJsonObject(newValue)) {
292
+ if (isJsonObject(existing)) {
293
+ return this.mergeObjectInPlace(existing, newValue, state.context).withFailureDetail('error');
294
+ }
295
+ target[key] = newValue;
296
+ return succeedWithDetail(newValue, 'edited');
297
+ }
298
+ /* c8 ignore else */
299
+ if (isJsonArray(newValue)) {
300
+ if (isJsonArray(existing)) {
301
+ /* c8 ignore next 1 - ?? is defense in depth */
302
+ const arrayMergeBehavior = (_c = (_b = state.options.merge) === null || _b === void 0 ? void 0 : _b.arrayMergeBehavior) !== null && _c !== void 0 ? _c : 'append';
303
+ switch (arrayMergeBehavior) {
304
+ case 'append':
305
+ target[key] = existing.concat(...newValue);
306
+ break;
307
+ case 'replace':
308
+ target[key] = newValue;
309
+ break;
310
+ /* c8 ignore next 2 - exhaustive switch for ArrayMergeBehavior type */
311
+ default:
312
+ return failWithDetail(`Invalid array merge behavior: ${arrayMergeBehavior}`, 'error');
313
+ }
314
+ return succeedWithDetail(target[key], 'edited');
315
+ }
316
+ target[key] = newValue;
317
+ return succeedWithDetail(newValue, 'edited');
318
+ }
319
+ /* c8 ignore start */
320
+ return failWithDetail(`Invalid JSON: ${JSON.stringify(newValue)}`, 'error');
321
+ } /* c8 ignore stop */
322
+ /**
323
+ * Applies editor rules to a single property during merge operations. This method
324
+ * iterates through all configured editor rules to process the property, handling
325
+ * templates, conditionals, multi-value properties, and references.
326
+ * @param key - The property key to edit
327
+ * @param value - The property value to edit
328
+ * @param state - The editor state containing rules and context
329
+ * @returns Success with transformed property object, or Failure if rules cannot process
330
+ * @internal
331
+ */
332
+ _editProperty(key, value, state) {
333
+ for (const rule of this._rules) {
334
+ const ruleResult = rule.editProperty(key, value, state);
335
+ if (ruleResult.isSuccess() || ruleResult.detail !== 'inapplicable') {
336
+ return ruleResult;
337
+ }
338
+ }
339
+ return failWithDetail('inapplicable', 'inapplicable');
340
+ }
341
+ /**
342
+ * Applies editor rules to a single JSON value during clone operations. This method
343
+ * iterates through all configured editor rules to process the value, handling
344
+ * templates, conditionals, multi-value expressions, and references.
345
+ * @param value - The JSON value to edit and transform
346
+ * @param state - The editor state containing rules and context
347
+ * @returns Success with transformed value, or Failure if rules cannot process
348
+ * @internal
349
+ */
350
+ _editValue(value, state) {
351
+ for (const rule of this._rules) {
352
+ const ruleResult = rule.editValue(value, state);
353
+ if (ruleResult.isSuccess() || ruleResult.detail !== 'inapplicable') {
354
+ return ruleResult;
355
+ }
356
+ }
357
+ return failWithDetail('inapplicable', 'inapplicable');
358
+ }
359
+ /**
360
+ * Clone an object without applying null-as-delete behavior.
361
+ * This preserves null values during cloning so they can be used for deletion signaling during merge.
362
+ * @param target - The target object to clone into
363
+ * @param src - The source object to clone
364
+ * @param state - The editor state
365
+ * @returns The cloned object
366
+ * @internal
367
+ */
368
+ _cloneObjectWithoutNullAsDelete(target, src, state) {
369
+ var _a, _b;
370
+ // Temporarily disable null-as-delete during cloning
371
+ const modifiedOptions = {
372
+ context: state.options.context,
373
+ validation: state.options.validation,
374
+ merge: {
375
+ /* c8 ignore next 1 - ? is defense in depth */
376
+ arrayMergeBehavior: (_b = (_a = state.options.merge) === null || _a === void 0 ? void 0 : _a.arrayMergeBehavior) !== null && _b !== void 0 ? _b : 'append',
377
+ nullAsDelete: false
378
+ }
379
+ };
380
+ const modifiedState = new JsonEditorState(state.editor, modifiedOptions, state.context);
381
+ return this._mergeObjectInPlace(target, src, modifiedState)
382
+ .onSuccess((merged) => {
383
+ return this._finalizeAndMerge(merged, modifiedState);
384
+ })
385
+ .withFailureDetail('error');
386
+ }
387
+ /**
388
+ * Finalizes the merge operation by processing any deferred properties and merging
389
+ * them into the target object. Deferred properties are those that require special
390
+ * processing after the initial merge phase, such as references that depend on
391
+ * other properties being resolved first.
392
+ * @param target - The target object that has been merged
393
+ * @param state - The editor state containing deferred properties and rules
394
+ * @returns Success with the finalized target object, or Failure with error details
395
+ * @internal
396
+ */
397
+ _finalizeAndMerge(target, state) {
398
+ const deferred = state.deferred;
399
+ if (deferred.length > 0) {
400
+ for (const rule of this._rules) {
401
+ const ruleResult = rule.finalizeProperties(deferred, state);
402
+ if (ruleResult.isSuccess()) {
403
+ return this.mergeObjectsInPlaceWithContext(state.context, target, ruleResult.value).withFailureDetail('error');
404
+ }
405
+ else if (ruleResult.detail === 'ignore') {
406
+ succeedWithDetail(target, 'edited');
407
+ }
408
+ else if (ruleResult.detail !== 'inapplicable') {
409
+ return failWithDetail(ruleResult.message, ruleResult.detail);
410
+ }
411
+ }
412
+ }
413
+ return succeedWithDetail(target, 'edited');
414
+ }
415
+ }
416
+ //# sourceMappingURL=jsonEditor.js.map
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Copyright (c) 2020 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import { failWithDetail } from '@fgv/ts-utils';
23
+ /**
24
+ * Default base implementation of {@link IJsonEditorRule | IJsonEditorRule} returns inapplicable for all operations so that
25
+ * derived classes need only implement the operations they actually support.
26
+ * @public
27
+ */
28
+ export class JsonEditorRuleBase {
29
+ /**
30
+ * {@inheritdoc IJsonEditorRule.editProperty}
31
+ */
32
+ /* c8 ignore start */
33
+ editProperty(__key, __value, __state) {
34
+ return failWithDetail('inapplicable', 'inapplicable');
35
+ }
36
+ /* c8 ignore stop */
37
+ /**
38
+ * {@inheritdoc IJsonEditorRule.editValue}
39
+ */
40
+ editValue(__value, __state) {
41
+ return failWithDetail('inapplicable', 'inapplicable');
42
+ }
43
+ /**
44
+ * {@inheritdoc IJsonEditorRule.finalizeProperties}
45
+ */
46
+ finalizeProperties(__deferred, __state) {
47
+ return failWithDetail('inapplicable', 'inapplicable');
48
+ }
49
+ }
50
+ //# sourceMappingURL=jsonEditorRule.js.map
@@ -0,0 +1,175 @@
1
+ /*
2
+ * Copyright (c) 2020 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import { failWithDetail, succeed } from '@fgv/ts-utils';
23
+ import { JsonContextHelper } from '../context';
24
+ /**
25
+ * Represents the internal state of a {@link JsonEditor | JsonEditor}.
26
+ * @public
27
+ */
28
+ export class JsonEditorState {
29
+ /**
30
+ * Constructs a new {@link JsonEditorState | JsonEditorState}.
31
+ * @param editor - The {@link IJsonCloneEditor | editor} to which this state
32
+ * applies.
33
+ * @param baseOptions - The {@link IJsonEditorOptions | editor options} that
34
+ * apply to this rule.
35
+ * @param runtimeContext - An optional {@link IJsonContext | JSON context} to be used
36
+ * for json value conversion.
37
+ */
38
+ constructor(editor, baseOptions, runtimeContext) {
39
+ /**
40
+ * Any deferred JSON objects to be merged during finalization.
41
+ * @internal
42
+ */
43
+ this._deferred = [];
44
+ this.editor = editor;
45
+ this.options = JsonEditorState._getEffectiveOptions(baseOptions, runtimeContext).orThrow();
46
+ this._id = JsonEditorState._nextId++;
47
+ }
48
+ /**
49
+ * The optional {@link IJsonContext | JSON context} for this state.
50
+ */
51
+ get context() {
52
+ return this.options.context;
53
+ }
54
+ /**
55
+ * An array of JSON objects that were deferred for merge during
56
+ * finalization.
57
+ */
58
+ get deferred() {
59
+ return this._deferred;
60
+ }
61
+ /**
62
+ * Merges an optional {@link IJsonContext | JSON context} into a supplied set
63
+ * of {@link IJsonEditorOptions | JSON editor options}.
64
+ * @param options - The {@link IJsonEditorOptions | IJsonEditorOptions} into
65
+ * which the the new context is to be merged.
66
+ * @param context - The {@link IJsonContext | JSON context} to be merged into the
67
+ * editor options.
68
+ * @returns `Success` with the supplied {@link IJsonEditorOptions | options} if
69
+ * there was nothing to merge, or aa new {@link IJsonEditorOptions | IJsonEditorOptions}
70
+ * constructed from the base options merged with the supplied context. Returns `Failure`
71
+ * with more information if an error occurs.
72
+ * @internal
73
+ */
74
+ static _getEffectiveOptions(options, context) {
75
+ if (!context) {
76
+ return succeed(options);
77
+ }
78
+ return JsonContextHelper.mergeContext(options.context, context).onSuccess((merged) => {
79
+ return succeed({ context: merged, validation: options.validation, merge: options.merge });
80
+ });
81
+ }
82
+ /**
83
+ * Adds a supplied `JsonObject` to the deferred list.
84
+ * @param obj - The `JsonObject` to be deferred.
85
+ */
86
+ defer(obj) {
87
+ this._deferred.push(obj);
88
+ }
89
+ /**
90
+ * Gets a {@link TemplateVars | TemplateVars} from the context of this {@link JsonEditorState | JsonEditorState},
91
+ * or from an optional supplied {@link IJsonContext | IJsonContext} if the current state has no default
92
+ * context.
93
+ * @param defaultContext - An optional default {@link IJsonContext | IJsonContext} to use as `TemplateVars`
94
+ * if the current state does not have context.
95
+ * @returns A {@link TemplateVars | TemplateVars} reflecting the appropriate {@link IJsonContext | JSON context}, or
96
+ * `undefined` if no vars are found.
97
+ */
98
+ getVars(defaultContext) {
99
+ var _a, _b;
100
+ /* c8 ignore next */ // c8 seems to be struggling atm
101
+ return (_b = (_a = this.options.context) === null || _a === void 0 ? void 0 : _a.vars) !== null && _b !== void 0 ? _b : defaultContext === null || defaultContext === void 0 ? void 0 : defaultContext.vars;
102
+ }
103
+ /**
104
+ * Gets an {@link IJsonReferenceMap | reference map} containing any other values
105
+ * referenced during the operation.
106
+ * @param defaultContext - An optional default {@link IJsonContext | IJsonContext} to use as
107
+ * {@link TemplateVars | TemplateVars} if the current state does not have context.
108
+ * @returns An {@link IJsonReferenceMap | IJsonReferenceMap} containing any values referenced
109
+ * during this operation.
110
+ */
111
+ getRefs(defaultContext) {
112
+ var _a, _b;
113
+ /* c8 ignore next */ // c8 seems to be struggling atm
114
+ return (_b = (_a = this.options.context) === null || _a === void 0 ? void 0 : _a.refs) !== null && _b !== void 0 ? _b : defaultContext === null || defaultContext === void 0 ? void 0 : defaultContext.refs;
115
+ }
116
+ /**
117
+ * Gets the context of this {@link JsonEditorState | JsonEditorState} or an optionally
118
+ * supplied default context if this state has no context.
119
+ * @param defaultContext - The default {@link IJsonContext | JSON context} to use as default
120
+ * if this state has no context.
121
+ * @returns The appropriate {@link IJsonContext | IJsonContext} or `undefined` if no context
122
+ * is available.
123
+ */
124
+ getContext(defaultContext) {
125
+ return JsonContextHelper.mergeContext(defaultContext, this.options.context).orDefault();
126
+ }
127
+ /**
128
+ * Constructs a new {@link IJsonContext | IJsonContext} by merging supplied variables
129
+ * and references into a supplied existing context.
130
+ * @param baseContext - The {@link IJsonContext | IJsonContext} into which variables
131
+ * and references are to be merged, or `undefined` to start with a default empty context.
132
+ * @param add - The {@link VariableValue | variable values} and/or
133
+ * {@link IJsonReferenceMap | JSON entity references} to be merged into the base context.
134
+ * @returns A new {@link IJsonContext | IJsonContext} created by merging the supplied values.
135
+ */
136
+ extendContext(baseContext, add) {
137
+ const context = this.getContext(baseContext);
138
+ return JsonContextHelper.extendContext(context, add);
139
+ }
140
+ /**
141
+ * Helper method to constructs `DetailedFailure` with appropriate details and messaging
142
+ * for various validation failures.
143
+ * @param rule - The {@link JsonEditorValidationRules | validation rule} that failed.
144
+ * @param message - A string message describing the failed validation.
145
+ * @param validation - The {@link IJsonEditorValidationOptions | validation options}
146
+ * in effect.
147
+ * @returns A `DetailedFailure` with appropriate detail and message.
148
+ */
149
+ failValidation(rule, message, validation) {
150
+ let detail = 'error';
151
+ const effective = validation !== null && validation !== void 0 ? validation : this.options.validation;
152
+ switch (rule) {
153
+ case 'invalidPropertyName':
154
+ detail = effective.onInvalidPropertyName !== 'ignore' ? 'error' : 'inapplicable';
155
+ break;
156
+ case 'invalidPropertyValue':
157
+ detail = effective.onInvalidPropertyValue !== 'ignore' ? 'error' : 'ignore';
158
+ break;
159
+ case 'undefinedPropertyValue':
160
+ detail = effective.onUndefinedPropertyValue !== 'error' ? 'ignore' : 'error';
161
+ /* c8 ignore next */
162
+ message = message !== null && message !== void 0 ? message : 'Cannot convert undefined to JSON';
163
+ break;
164
+ }
165
+ /* c8 ignore next */
166
+ return failWithDetail(message !== null && message !== void 0 ? message : rule, detail);
167
+ }
168
+ }
169
+ /**
170
+ * Static global counter used to assign each {@link JsonEditorState | JsonEditorState}
171
+ * a unique identifier.
172
+ * @internal
173
+ */
174
+ JsonEditorState._nextId = 0;
175
+ //# sourceMappingURL=jsonEditorState.js.map