@speclynx/apidom-overlay 4.3.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.
- package/LICENSES/AFL-3.0.txt +182 -0
- package/LICENSES/Apache-2.0.txt +202 -0
- package/LICENSES/BSD-3-Clause.txt +26 -0
- package/LICENSES/MIT.txt +9 -0
- package/NOTICE +74 -0
- package/README.md +150 -0
- package/dist/apidom-overlay.browser.js +100884 -0
- package/dist/apidom-overlay.browser.min.js +1 -0
- package/package.json +73 -0
- package/src/apply-uri.cjs +80 -0
- package/src/apply-uri.mjs +74 -0
- package/src/apply.cjs +243 -0
- package/src/apply.mjs +232 -0
- package/src/errors/OverlayError.cjs +22 -0
- package/src/errors/OverlayError.mjs +19 -0
- package/src/index.cjs +14 -0
- package/src/index.mjs +4 -0
- package/src/validate.cjs +81 -0
- package/src/validate.mjs +73 -0
- package/types/apidom-overlay.d.ts +92 -0
package/src/apply.mjs
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { isObjectElement, isArrayElement, cloneDeep, ParseResultElement } from '@speclynx/apidom-datamodel';
|
|
2
|
+
import { deepmerge, toValue } from '@speclynx/apidom-core';
|
|
3
|
+
import { evaluate, NormalizedPath } from '@speclynx/apidom-json-path';
|
|
4
|
+
import { isOverlay1Element } from '@speclynx/apidom-ns-overlay-1';
|
|
5
|
+
import OverlayError from "./errors/OverlayError.mjs";
|
|
6
|
+
import { validateAction, validateTargetNodes } from "./validate.mjs";
|
|
7
|
+
/**
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
const defaultApplyOptions = {
|
|
11
|
+
immutable: true,
|
|
12
|
+
strict: false
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default customMerge that enforces Overlay spec type compatibility.
|
|
17
|
+
* Throws OverlayError on incompatible type combinations.
|
|
18
|
+
*/
|
|
19
|
+
const overlayCustomMerge = (_keyElement, options) => {
|
|
20
|
+
return (target, source) => {
|
|
21
|
+
const targetIsObject = isObjectElement(target);
|
|
22
|
+
const sourceIsObject = isObjectElement(source);
|
|
23
|
+
const targetIsArray = isArrayElement(target);
|
|
24
|
+
const sourceIsArray = isArrayElement(source);
|
|
25
|
+
if (targetIsObject !== sourceIsObject || targetIsArray !== sourceIsArray) {
|
|
26
|
+
throw new OverlayError(`Incompatible types in overlay merge: cannot merge ` + `${targetIsObject ? 'object' : targetIsArray ? 'array' : 'primitive'} with ` + `${sourceIsObject ? 'object' : sourceIsArray ? 'array' : 'primitive'}`);
|
|
27
|
+
}
|
|
28
|
+
return deepmerge(target, source, options);
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Merges a value into a target node following Overlay spec semantics.
|
|
34
|
+
*/
|
|
35
|
+
const mergeValue = (targetElement, sourceElement, options = defaultApplyOptions) => {
|
|
36
|
+
const targetIsObject = isObjectElement(targetElement);
|
|
37
|
+
const targetIsArray = isArrayElement(targetElement);
|
|
38
|
+
const sourceIsObject = isObjectElement(sourceElement);
|
|
39
|
+
const sourceIsArray = isArrayElement(sourceElement);
|
|
40
|
+
const clone = options.immutable;
|
|
41
|
+
if (targetIsObject && sourceIsObject || targetIsArray && sourceIsArray) {
|
|
42
|
+
const mergeOpts = {
|
|
43
|
+
clone,
|
|
44
|
+
customMerge: overlayCustomMerge,
|
|
45
|
+
...options.deepmerge
|
|
46
|
+
};
|
|
47
|
+
return deepmerge(targetElement, sourceElement, mergeOpts);
|
|
48
|
+
}
|
|
49
|
+
if (!targetIsObject && !targetIsArray && !sourceIsObject && !sourceIsArray) {
|
|
50
|
+
return clone ? cloneDeep(sourceElement) : sourceElement;
|
|
51
|
+
}
|
|
52
|
+
throw new OverlayError(`Incompatible types in overlay merge: cannot merge ` + `${targetIsObject ? 'object' : targetIsArray ? 'array' : 'primitive'} with ` + `${sourceIsObject ? 'object' : sourceIsArray ? 'array' : 'primitive'}`);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolves a normalized path to its parent element and child key.
|
|
57
|
+
*/
|
|
58
|
+
const resolveParent = (normalizedPath, root) => {
|
|
59
|
+
const selectors = NormalizedPath.to(normalizedPath);
|
|
60
|
+
if (selectors.length === 0) return null; // root — no parent
|
|
61
|
+
const key = selectors[selectors.length - 1];
|
|
62
|
+
const parentPath = NormalizedPath.from(selectors.slice(0, -1));
|
|
63
|
+
const [parent] = evaluate(root, parentPath);
|
|
64
|
+
return {
|
|
65
|
+
parent,
|
|
66
|
+
key
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Applies an update action to matched nodes.
|
|
72
|
+
* @public
|
|
73
|
+
*/
|
|
74
|
+
export const applyUpdateAction = (normalizedPaths, updateValue, targetElement, options = defaultApplyOptions) => {
|
|
75
|
+
let result = options.immutable ? cloneDeep(targetElement) : targetElement;
|
|
76
|
+
for (const normalizedPath of normalizedPaths) {
|
|
77
|
+
const resolved = resolveParent(normalizedPath, result);
|
|
78
|
+
if (resolved === null) {
|
|
79
|
+
// targeting root
|
|
80
|
+
result = mergeValue(result, updateValue, options);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const {
|
|
84
|
+
parent,
|
|
85
|
+
key
|
|
86
|
+
} = resolved;
|
|
87
|
+
if (isObjectElement(parent)) {
|
|
88
|
+
const current = parent.get(key);
|
|
89
|
+
if (current !== undefined) {
|
|
90
|
+
parent.set(key, mergeValue(current, updateValue, options));
|
|
91
|
+
}
|
|
92
|
+
} else if (isArrayElement(parent)) {
|
|
93
|
+
const current = parent.get(key);
|
|
94
|
+
if (current !== undefined) {
|
|
95
|
+
parent.content[key] = mergeValue(current, updateValue, options);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Applies a copy action to matched nodes.
|
|
104
|
+
* @public
|
|
105
|
+
*/
|
|
106
|
+
export const applyCopyAction = (normalizedPaths, copyExpression, targetElement, options = defaultApplyOptions) => {
|
|
107
|
+
const sourceMatches = evaluate(targetElement, copyExpression);
|
|
108
|
+
if (sourceMatches.length === 0) {
|
|
109
|
+
throw new OverlayError(`Copy source JSONPath "${copyExpression}" selected zero nodes`);
|
|
110
|
+
}
|
|
111
|
+
if (sourceMatches.length > 1) {
|
|
112
|
+
throw new OverlayError(`Copy source JSONPath "${copyExpression}" must select a single node, but selected ${sourceMatches.length}`);
|
|
113
|
+
}
|
|
114
|
+
const sourceValue = sourceMatches[0];
|
|
115
|
+
return applyUpdateAction(normalizedPaths, sourceValue, targetElement, options);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Applies a remove action to matched nodes.
|
|
120
|
+
* @public
|
|
121
|
+
*/
|
|
122
|
+
export const applyRemoveAction = (normalizedPaths, targetElement, options = defaultApplyOptions) => {
|
|
123
|
+
const result = options.immutable ? cloneDeep(targetElement) : targetElement;
|
|
124
|
+
// reverse document order so higher array indices are removed first (avoids index shifting)
|
|
125
|
+
const sorted = [...normalizedPaths].reverse();
|
|
126
|
+
for (const normalizedPath of sorted) {
|
|
127
|
+
const resolved = resolveParent(normalizedPath, result);
|
|
128
|
+
if (resolved === null) {
|
|
129
|
+
throw new OverlayError('Cannot remove the root node');
|
|
130
|
+
}
|
|
131
|
+
const {
|
|
132
|
+
parent,
|
|
133
|
+
key
|
|
134
|
+
} = resolved;
|
|
135
|
+
if (isObjectElement(parent)) {
|
|
136
|
+
parent.remove(key);
|
|
137
|
+
} else if (isArrayElement(parent)) {
|
|
138
|
+
parent.content.splice(key, 1);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Applies a single overlay action to a target element.
|
|
146
|
+
* Immutable — returns a new element with the action applied.
|
|
147
|
+
*
|
|
148
|
+
* @public
|
|
149
|
+
*/
|
|
150
|
+
export const applyAction = (actionElement, targetElement, options = defaultApplyOptions) => {
|
|
151
|
+
const actionValidation = validateAction(actionElement);
|
|
152
|
+
if (!actionValidation.valid) throw actionValidation.error;
|
|
153
|
+
const targetExpression = toValue(actionElement.target);
|
|
154
|
+
const matches = [];
|
|
155
|
+
const normalizedPaths = [];
|
|
156
|
+
evaluate(targetElement, targetExpression, {
|
|
157
|
+
callback: (value, normalizedPath) => {
|
|
158
|
+
matches.push(value);
|
|
159
|
+
normalizedPaths.push(normalizedPath);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
if (matches.length === 0) {
|
|
163
|
+
if (options.strict) {
|
|
164
|
+
throw new OverlayError(`Action target "${targetExpression}" matched zero nodes (strict mode)`, {
|
|
165
|
+
action: actionElement
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return targetElement;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
// remove action
|
|
172
|
+
const removeValue = actionElement.removeField;
|
|
173
|
+
if (removeValue !== undefined && toValue(removeValue) === true) {
|
|
174
|
+
return applyRemoveAction(normalizedPaths, targetElement, options);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// update action
|
|
178
|
+
if (actionElement.update !== undefined) {
|
|
179
|
+
const nodesValidation = validateTargetNodes(matches);
|
|
180
|
+
if (!nodesValidation.valid) throw nodesValidation.error;
|
|
181
|
+
return applyUpdateAction(normalizedPaths, actionElement.update, targetElement, options);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// copy action
|
|
185
|
+
if (actionElement.copy !== undefined) {
|
|
186
|
+
const nodesValidation = validateTargetNodes(matches);
|
|
187
|
+
if (!nodesValidation.valid) throw nodesValidation.error;
|
|
188
|
+
const copyExpression = toValue(actionElement.copy);
|
|
189
|
+
return applyCopyAction(normalizedPaths, copyExpression, targetElement, options);
|
|
190
|
+
}
|
|
191
|
+
return targetElement;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
// enrich OverlayError with action context, preserving original stack
|
|
194
|
+
if (error instanceof OverlayError && !error.action) {
|
|
195
|
+
error.action = actionElement;
|
|
196
|
+
}
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Applies an entire overlay document to a target element.
|
|
203
|
+
* Immutable — returns a new element with all actions applied sequentially.
|
|
204
|
+
*
|
|
205
|
+
* @public
|
|
206
|
+
*/
|
|
207
|
+
export const applyOverlayApiDOM = (overlayElement, targetElement, options = defaultApplyOptions) => {
|
|
208
|
+
const overlay = overlayElement instanceof ParseResultElement ? overlayElement.api : overlayElement;
|
|
209
|
+
if (!isOverlay1Element(overlay)) {
|
|
210
|
+
throw new OverlayError('First argument must be an Overlay 1.x document');
|
|
211
|
+
}
|
|
212
|
+
let target;
|
|
213
|
+
if (targetElement instanceof ParseResultElement) {
|
|
214
|
+
if (targetElement.api === undefined) {
|
|
215
|
+
throw new OverlayError('Target ParseResultElement contains no API element');
|
|
216
|
+
}
|
|
217
|
+
target = targetElement.api;
|
|
218
|
+
} else {
|
|
219
|
+
target = targetElement;
|
|
220
|
+
}
|
|
221
|
+
if (!isArrayElement(overlay.actions)) {
|
|
222
|
+
return targetElement;
|
|
223
|
+
}
|
|
224
|
+
for (const action of overlay.actions) {
|
|
225
|
+
target = applyAction(action, target, options);
|
|
226
|
+
}
|
|
227
|
+
if (targetElement instanceof ParseResultElement) {
|
|
228
|
+
targetElement.replaceResult(target);
|
|
229
|
+
return targetElement;
|
|
230
|
+
}
|
|
231
|
+
return target;
|
|
232
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.default = void 0;
|
|
5
|
+
var _apidomError = require("@speclynx/apidom-error");
|
|
6
|
+
/**
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
class OverlayError extends _apidomError.ApiDOMStructuredError {
|
|
14
|
+
action;
|
|
15
|
+
member;
|
|
16
|
+
constructor(message, options) {
|
|
17
|
+
super(message, options);
|
|
18
|
+
this.action = options?.action;
|
|
19
|
+
this.member = options?.member;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
var _default = exports.default = OverlayError;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ApiDOMStructuredError } from '@speclynx/apidom-error';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @public
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
class OverlayError extends ApiDOMStructuredError {
|
|
11
|
+
action;
|
|
12
|
+
member;
|
|
13
|
+
constructor(message, options) {
|
|
14
|
+
super(message, options);
|
|
15
|
+
this.action = options?.action;
|
|
16
|
+
this.member = options?.member;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export default OverlayError;
|
package/src/index.cjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
4
|
+
exports.__esModule = true;
|
|
5
|
+
exports.validateAction = exports.default = exports.applyOverlayApiDOM = exports.applyAction = exports.OverlayError = void 0;
|
|
6
|
+
var _apply = require("./apply.cjs");
|
|
7
|
+
exports.applyAction = _apply.applyAction;
|
|
8
|
+
exports.applyOverlayApiDOM = _apply.applyOverlayApiDOM;
|
|
9
|
+
var _validate = require("./validate.cjs");
|
|
10
|
+
exports.validateAction = _validate.validateAction;
|
|
11
|
+
var _OverlayError = _interopRequireDefault(require("./errors/OverlayError.cjs"));
|
|
12
|
+
exports.OverlayError = _OverlayError.default;
|
|
13
|
+
var _applyUri = _interopRequireDefault(require("./apply-uri.cjs"));
|
|
14
|
+
exports.default = _applyUri.default;
|
package/src/index.mjs
ADDED
package/src/validate.cjs
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
4
|
+
exports.__esModule = true;
|
|
5
|
+
exports.validateTargetNodes = exports.validateAction = void 0;
|
|
6
|
+
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
7
|
+
var _apidomNsOverlay = require("@speclynx/apidom-ns-overlay-1");
|
|
8
|
+
var _OverlayError = _interopRequireDefault(require("./errors/OverlayError.cjs"));
|
|
9
|
+
/**
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const valid = {
|
|
14
|
+
valid: true
|
|
15
|
+
};
|
|
16
|
+
const invalid = error => ({
|
|
17
|
+
valid: false,
|
|
18
|
+
error
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validates that an ActionElement has the correct shape per the Overlay spec.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
const validateAction = actionElement => {
|
|
27
|
+
if (!(0, _apidomNsOverlay.isActionElement)(actionElement)) {
|
|
28
|
+
return invalid(new _OverlayError.default('Action must be an ActionElement', {
|
|
29
|
+
action: actionElement
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
if (!(0, _apidomDatamodel.isStringElement)(actionElement.target)) {
|
|
33
|
+
return invalid(new _OverlayError.default('Action "target" field is required and must be a string', {
|
|
34
|
+
action: actionElement,
|
|
35
|
+
member: actionElement.getMember('target')
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
if (actionElement.update !== undefined && actionElement.copy !== undefined) {
|
|
39
|
+
return invalid(new _OverlayError.default('Action must not have both "update" and "copy" fields', {
|
|
40
|
+
action: actionElement
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
if (actionElement.copy !== undefined && !(0, _apidomDatamodel.isStringElement)(actionElement.copy)) {
|
|
44
|
+
return invalid(new _OverlayError.default('Action "copy" field must be a string (JSONPath expression)', {
|
|
45
|
+
action: actionElement,
|
|
46
|
+
member: actionElement.getMember('copy')
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
if (actionElement.removeField !== undefined && !(0, _apidomDatamodel.isBooleanElement)(actionElement.removeField)) {
|
|
50
|
+
return invalid(new _OverlayError.default('Action "remove" field must be a boolean', {
|
|
51
|
+
action: actionElement,
|
|
52
|
+
member: actionElement.getMember('remove')
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
return valid;
|
|
56
|
+
};
|
|
57
|
+
exports.validateAction = validateAction;
|
|
58
|
+
const categorizeNode = node => {
|
|
59
|
+
if ((0, _apidomDatamodel.isObjectElement)(node)) return 'object';
|
|
60
|
+
if ((0, _apidomDatamodel.isArrayElement)(node)) return 'array';
|
|
61
|
+
return 'primitive';
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Validates that all matched target nodes are the same type
|
|
66
|
+
* when there are 2 or more matches (spec requirement for update/copy).
|
|
67
|
+
*
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
const validateTargetNodes = nodes => {
|
|
71
|
+
if (nodes.length < 2) return valid;
|
|
72
|
+
const firstCategory = categorizeNode(nodes[0]);
|
|
73
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
74
|
+
const category = categorizeNode(nodes[i]);
|
|
75
|
+
if (category !== firstCategory) {
|
|
76
|
+
return invalid(new _OverlayError.default(`All target nodes must be the same type when selecting multiple nodes. ` + `Expected "${firstCategory}" but found "${category}" at index ${i}`));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return valid;
|
|
80
|
+
};
|
|
81
|
+
exports.validateTargetNodes = validateTargetNodes;
|
package/src/validate.mjs
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { isStringElement, isBooleanElement, isObjectElement, isArrayElement } from '@speclynx/apidom-datamodel';
|
|
2
|
+
import { isActionElement } from '@speclynx/apidom-ns-overlay-1';
|
|
3
|
+
import OverlayError from "./errors/OverlayError.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
const valid = {
|
|
8
|
+
valid: true
|
|
9
|
+
};
|
|
10
|
+
const invalid = error => ({
|
|
11
|
+
valid: false,
|
|
12
|
+
error
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validates that an ActionElement has the correct shape per the Overlay spec.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export const validateAction = actionElement => {
|
|
21
|
+
if (!isActionElement(actionElement)) {
|
|
22
|
+
return invalid(new OverlayError('Action must be an ActionElement', {
|
|
23
|
+
action: actionElement
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
if (!isStringElement(actionElement.target)) {
|
|
27
|
+
return invalid(new OverlayError('Action "target" field is required and must be a string', {
|
|
28
|
+
action: actionElement,
|
|
29
|
+
member: actionElement.getMember('target')
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
if (actionElement.update !== undefined && actionElement.copy !== undefined) {
|
|
33
|
+
return invalid(new OverlayError('Action must not have both "update" and "copy" fields', {
|
|
34
|
+
action: actionElement
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
if (actionElement.copy !== undefined && !isStringElement(actionElement.copy)) {
|
|
38
|
+
return invalid(new OverlayError('Action "copy" field must be a string (JSONPath expression)', {
|
|
39
|
+
action: actionElement,
|
|
40
|
+
member: actionElement.getMember('copy')
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
if (actionElement.removeField !== undefined && !isBooleanElement(actionElement.removeField)) {
|
|
44
|
+
return invalid(new OverlayError('Action "remove" field must be a boolean', {
|
|
45
|
+
action: actionElement,
|
|
46
|
+
member: actionElement.getMember('remove')
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
return valid;
|
|
50
|
+
};
|
|
51
|
+
const categorizeNode = node => {
|
|
52
|
+
if (isObjectElement(node)) return 'object';
|
|
53
|
+
if (isArrayElement(node)) return 'array';
|
|
54
|
+
return 'primitive';
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validates that all matched target nodes are the same type
|
|
59
|
+
* when there are 2 or more matches (spec requirement for update/copy).
|
|
60
|
+
*
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
export const validateTargetNodes = nodes => {
|
|
64
|
+
if (nodes.length < 2) return valid;
|
|
65
|
+
const firstCategory = categorizeNode(nodes[0]);
|
|
66
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
67
|
+
const category = categorizeNode(nodes[i]);
|
|
68
|
+
if (category !== firstCategory) {
|
|
69
|
+
return invalid(new OverlayError(`All target nodes must be the same type when selecting multiple nodes. ` + `Expected "${firstCategory}" but found "${category}" at index ${i}`));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return valid;
|
|
73
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ActionElement } from '@speclynx/apidom-ns-overlay-1';
|
|
2
|
+
import { ApiDOMReferenceOptions } from '@speclynx/apidom-reference';
|
|
3
|
+
import { ApiDOMStructuredError } from '@speclynx/apidom-error';
|
|
4
|
+
import { DeepMergeUserOptions } from '@speclynx/apidom-core';
|
|
5
|
+
import { Element as Element_2 } from '@speclynx/apidom-datamodel';
|
|
6
|
+
import { MemberElement } from '@speclynx/apidom-datamodel';
|
|
7
|
+
import { Overlay1Element } from '@speclynx/apidom-ns-overlay-1';
|
|
8
|
+
import { ParseResultElement } from '@speclynx/apidom-datamodel';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Applies a single overlay action to a target element.
|
|
12
|
+
* Immutable — returns a new element with the action applied.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export declare const applyAction: (actionElement: ActionElement, targetElement: Element_2, options?: ApplyOptions) => Element_2;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export declare interface ApplyOptions {
|
|
22
|
+
readonly deepmerge?: DeepMergeUserOptions;
|
|
23
|
+
readonly strict?: boolean;
|
|
24
|
+
readonly immutable?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Applies an overlay document from a file/URL to its target document.
|
|
29
|
+
*
|
|
30
|
+
* Parses the overlay, resolves the target from `targetURI` or the overlay's
|
|
31
|
+
* `extends` field, parses the target with style preservation for round-trip fidelity,
|
|
32
|
+
* and applies all overlay actions.
|
|
33
|
+
*
|
|
34
|
+
* @param overlayURI - URI to the overlay document (file path or URL)
|
|
35
|
+
* @param targetURI - URI to the target document; if omitted, uses the overlay's `extends` field
|
|
36
|
+
* @param options - apidom-reference options + overlay merge options
|
|
37
|
+
* @returns ParseResultElement with the modified target document
|
|
38
|
+
*
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
declare const applyOverlay: (overlayURI: string, targetURI?: string, options?: ApplyOverlayOptions) => Promise<ParseResultElement>;
|
|
42
|
+
export default applyOverlay;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Applies an entire overlay document to a target element.
|
|
46
|
+
* Immutable — returns a new element with all actions applied sequentially.
|
|
47
|
+
*
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
export declare const applyOverlayApiDOM: <T extends Element_2 | ParseResultElement>(overlayElement: Overlay1Element | ParseResultElement, targetElement: T, options?: ApplyOptions) => T;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
export declare interface ApplyOverlayOptions extends Partial<ApiDOMReferenceOptions>, ApplyOptions {
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
61
|
+
export declare class OverlayError extends ApiDOMStructuredError {
|
|
62
|
+
action?: ActionElement;
|
|
63
|
+
member?: MemberElement;
|
|
64
|
+
constructor(message?: string, options?: OverlayErrorOptions);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
export declare interface OverlayErrorOptions {
|
|
71
|
+
readonly cause?: Error;
|
|
72
|
+
readonly action?: ActionElement;
|
|
73
|
+
readonly member?: MemberElement;
|
|
74
|
+
readonly [key: string]: unknown;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validates that an ActionElement has the correct shape per the Overlay spec.
|
|
79
|
+
*
|
|
80
|
+
* @public
|
|
81
|
+
*/
|
|
82
|
+
export declare const validateAction: (actionElement: ActionElement) => ValidationResult;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
export declare interface ValidationResult {
|
|
88
|
+
readonly valid: boolean;
|
|
89
|
+
readonly error?: OverlayError;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { }
|