@speclynx/apidom-overlay 4.9.0 → 4.10.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/CHANGELOG.md +10 -0
- package/README.md +108 -3
- package/dist/apidom-overlay.browser.js +999 -184
- package/dist/apidom-overlay.browser.min.js +1 -1
- package/package.json +9 -9
- package/src/diff/index.cjs +12 -0
- package/src/diff/index.mjs +3 -0
- package/src/diff/realms/apidom.cjs +177 -0
- package/src/diff/realms/apidom.mjs +170 -0
- package/src/diff/realms/pojo.cjs +15 -0
- package/src/diff/realms/pojo.mjs +10 -0
- package/src/diff/realms/uri.cjs +66 -0
- package/src/diff/realms/uri.mjs +60 -0
- package/src/index.cjs +6 -1
- package/src/index.mjs +1 -0
- package/types/apidom-overlay.d.ts +75 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@speclynx/apidom-overlay",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.10.0",
|
|
4
4
|
"description": "Apply Overlay 1.x.y documents to API definitions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"apidom",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"license": "Apache-2.0",
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@babel/runtime-corejs3": "^7.28.4",
|
|
56
|
-
"@speclynx/apidom-core": "4.
|
|
57
|
-
"@speclynx/apidom-datamodel": "4.
|
|
58
|
-
"@speclynx/apidom-error": "4.
|
|
59
|
-
"@speclynx/apidom-json-path": "4.
|
|
60
|
-
"@speclynx/apidom-ns-overlay-1": "4.
|
|
61
|
-
"@speclynx/apidom-reference": "4.
|
|
62
|
-
"type-fest": "^5.
|
|
56
|
+
"@speclynx/apidom-core": "4.10.0",
|
|
57
|
+
"@speclynx/apidom-datamodel": "4.10.0",
|
|
58
|
+
"@speclynx/apidom-error": "4.10.0",
|
|
59
|
+
"@speclynx/apidom-json-path": "4.10.0",
|
|
60
|
+
"@speclynx/apidom-ns-overlay-1": "4.10.0",
|
|
61
|
+
"@speclynx/apidom-reference": "4.10.0",
|
|
62
|
+
"type-fest": "^5.6.0"
|
|
63
63
|
},
|
|
64
64
|
"files": [
|
|
65
65
|
"src/**/*.mjs",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"README.md",
|
|
72
72
|
"CHANGELOG.md"
|
|
73
73
|
],
|
|
74
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "cc5b227c468c8bd1a56611f535a71a4018beebf8"
|
|
75
75
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
|
|
4
|
+
exports.__esModule = true;
|
|
5
|
+
exports.diffPOJO = exports.diffOverlay = exports.diffApiDOM = exports.defaultDiffOverlayOptions = void 0;
|
|
6
|
+
var _apidom = require("./realms/apidom.cjs");
|
|
7
|
+
exports.diffApiDOM = _apidom.diff;
|
|
8
|
+
var _pojo = require("./realms/pojo.cjs");
|
|
9
|
+
exports.diffPOJO = _pojo.diff;
|
|
10
|
+
var _uri = _interopRequireWildcard(require("./realms/uri.cjs"));
|
|
11
|
+
exports.diffOverlay = _uri.default;
|
|
12
|
+
exports.defaultDiffOverlayOptions = _uri.defaultOptions;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
4
|
+
exports.__esModule = true;
|
|
5
|
+
exports.diff = void 0;
|
|
6
|
+
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
7
|
+
var _apidomJsonPath = require("@speclynx/apidom-json-path");
|
|
8
|
+
var _apidomNsOverlay = require("@speclynx/apidom-ns-overlay-1");
|
|
9
|
+
var _OverlayError = _interopRequireDefault(require("../../errors/OverlayError.cjs"));
|
|
10
|
+
/**
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const defaultDiffOptions = {
|
|
15
|
+
overlay: '1.1.0',
|
|
16
|
+
info: {
|
|
17
|
+
title: 'API diff',
|
|
18
|
+
version: '0.0.0'
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const createUpdateAction = (segments, value) => {
|
|
22
|
+
const action = new _apidomNsOverlay.ActionElement();
|
|
23
|
+
action.target = new _apidomDatamodel.StringElement(_apidomJsonPath.NormalizedPath.from(segments));
|
|
24
|
+
action.update = value;
|
|
25
|
+
return action;
|
|
26
|
+
};
|
|
27
|
+
const createRemoveAction = segments => {
|
|
28
|
+
const action = new _apidomNsOverlay.ActionElement();
|
|
29
|
+
action.target = new _apidomDatamodel.StringElement(_apidomJsonPath.NormalizedPath.from(segments));
|
|
30
|
+
action.removeField = new _apidomDatamodel.BooleanElement(true);
|
|
31
|
+
return action;
|
|
32
|
+
};
|
|
33
|
+
const diffElements = (left, right, segments, actions) => {
|
|
34
|
+
if (left.equals(right)) return;
|
|
35
|
+
const leftKind = left.primitive();
|
|
36
|
+
const rightKind = right.primitive();
|
|
37
|
+
if (leftKind !== rightKind) {
|
|
38
|
+
if (segments.length === 0) {
|
|
39
|
+
throw new _OverlayError.default('Cannot diff documents with incompatible root types');
|
|
40
|
+
}
|
|
41
|
+
// structural type change: remove old, re-add new via parent update.
|
|
42
|
+
// only reachable from diffObjects — segments tail is always a string key
|
|
43
|
+
actions.push(createRemoveAction(segments));
|
|
44
|
+
const key = segments[segments.length - 1];
|
|
45
|
+
const wrapper = new _apidomDatamodel.ObjectElement();
|
|
46
|
+
wrapper.set(key, right);
|
|
47
|
+
actions.push(createUpdateAction(segments.slice(0, -1), wrapper));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if ((0, _apidomDatamodel.isObjectElement)(left) && (0, _apidomDatamodel.isObjectElement)(right)) {
|
|
51
|
+
diffObjects(left, right, segments, actions);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if ((0, _apidomDatamodel.isArrayElement)(left) && (0, _apidomDatamodel.isArrayElement)(right)) {
|
|
55
|
+
diffArrays(left, right, segments, actions);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// primitive value changed
|
|
60
|
+
actions.push(createUpdateAction(segments, right));
|
|
61
|
+
};
|
|
62
|
+
const diffObjects = (left, right, segments, actions) => {
|
|
63
|
+
const leftKeys = new Set(left.keys());
|
|
64
|
+
const rightKeys = new Set(right.keys());
|
|
65
|
+
|
|
66
|
+
// removed keys
|
|
67
|
+
for (const key of leftKeys) {
|
|
68
|
+
if (!rightKeys.has(key)) {
|
|
69
|
+
actions.push(createRemoveAction([...segments, key]));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// added keys — batched into a single parent update to minimize action count
|
|
74
|
+
const added = [];
|
|
75
|
+
for (const key of rightKeys) {
|
|
76
|
+
if (!leftKeys.has(key)) added.push(key);
|
|
77
|
+
}
|
|
78
|
+
if (added.length > 0) {
|
|
79
|
+
const patch = new _apidomDatamodel.ObjectElement();
|
|
80
|
+
for (const key of added) {
|
|
81
|
+
patch.set(key, right.get(key));
|
|
82
|
+
}
|
|
83
|
+
actions.push(createUpdateAction(segments, patch));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// common keys — recurse into changed values
|
|
87
|
+
for (const key of leftKeys) {
|
|
88
|
+
if (!rightKeys.has(key)) continue;
|
|
89
|
+
diffElements(left.get(key), right.get(key), [...segments, key], actions);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const diffArrays = (left, right, segments, actions) => {
|
|
93
|
+
const leftLen = left.length;
|
|
94
|
+
const rightLen = right.length;
|
|
95
|
+
const minLen = Math.min(leftLen, rightLen);
|
|
96
|
+
for (let i = 0; i < minLen; i++) {
|
|
97
|
+
const leftItem = left.get(i);
|
|
98
|
+
const rightItem = right.get(i);
|
|
99
|
+
if (leftItem.equals(rightItem)) continue;
|
|
100
|
+
if (leftItem.primitive() !== rightItem.primitive()) {
|
|
101
|
+
// structural type change at index i — reconstruct tail:
|
|
102
|
+
// remove from end down to i (reverse order preserves index stability),
|
|
103
|
+
// then batch-append right[i..] as a single ArrayElement (Overlay concat semantics)
|
|
104
|
+
for (let j = leftLen - 1; j >= i; j--) {
|
|
105
|
+
actions.push(createRemoveAction([...segments, j]));
|
|
106
|
+
}
|
|
107
|
+
const tail = new _apidomDatamodel.ArrayElement();
|
|
108
|
+
for (let j = i; j < rightLen; j++) {
|
|
109
|
+
tail.push(right.get(j));
|
|
110
|
+
}
|
|
111
|
+
actions.push(createUpdateAction(segments, tail));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
diffElements(leftItem, rightItem, [...segments, i], actions);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// right is longer — batch all new items into a single ArrayElement update.
|
|
118
|
+
// wrapping is required: applyUpdateAction uses deepmerge(array, array) for array targets,
|
|
119
|
+
// so the update value must be an ArrayElement to trigger concat semantics.
|
|
120
|
+
if (leftLen < rightLen) {
|
|
121
|
+
const tail = new _apidomDatamodel.ArrayElement();
|
|
122
|
+
for (let i = leftLen; i < rightLen; i++) {
|
|
123
|
+
tail.push(right.get(i));
|
|
124
|
+
}
|
|
125
|
+
actions.push(createUpdateAction(segments, tail));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// left is longer — remove trailing items in reverse order for index stability
|
|
129
|
+
for (let i = leftLen - 1; i >= rightLen; i--) {
|
|
130
|
+
actions.push(createRemoveAction([...segments, i]));
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Produces an {@link Overlay1Element} that, when applied to `leftElement`, yields `rightElement`.
|
|
136
|
+
*
|
|
137
|
+
* Known limitations:
|
|
138
|
+
* - Identical documents produce an overlay with an empty `actions` array. Overlay 1.1.0 §3
|
|
139
|
+
* requires `actions` to be non-empty; callers should guard against the no-diff case.
|
|
140
|
+
* - Root-level structural type change (e.g. object root → array root) throws {@link OverlayError}
|
|
141
|
+
* because Overlay has no mechanism to replace the root node.
|
|
142
|
+
* - Array insert-at-position is not supported by Overlay 1.x; positional diffing cascades
|
|
143
|
+
* replaces and appends, which is always correct but may be verbose for large reorderings.
|
|
144
|
+
* - Array items with a structural type change are handled via tail-reconstruct
|
|
145
|
+
* (remove tail + re-append from right), producing more actions than a minimal diff.
|
|
146
|
+
* - The overlay carries no "before" value, so it cannot be reversed without the original document.
|
|
147
|
+
* - Action `update` values reference elements from `rightElement` directly. Mutating `rightElement`
|
|
148
|
+
* after calling `diff` will affect the returned overlay.
|
|
149
|
+
*
|
|
150
|
+
* @public
|
|
151
|
+
*/
|
|
152
|
+
const diff = (leftElement, rightElement, options = {}) => {
|
|
153
|
+
const overlayVersion = options.overlay ?? defaultDiffOptions.overlay;
|
|
154
|
+
const infoTitle = options.info?.title ?? defaultDiffOptions.info.title;
|
|
155
|
+
const infoVersion = options.info?.version ?? defaultDiffOptions.info.version;
|
|
156
|
+
const infoDescription = options.info?.description;
|
|
157
|
+
const actions = [];
|
|
158
|
+
diffElements(leftElement, rightElement, [], actions);
|
|
159
|
+
if (actions.length === 0 && options.onEmptyDiff === 'throw') {
|
|
160
|
+
throw new _OverlayError.default('Diff produced no actions: documents are identical');
|
|
161
|
+
}
|
|
162
|
+
const overlay = new _apidomNsOverlay.Overlay1Element();
|
|
163
|
+
overlay.overlay = new _apidomNsOverlay.OverlayElement(overlayVersion);
|
|
164
|
+
const info = new _apidomNsOverlay.InfoElement();
|
|
165
|
+
info.title = new _apidomDatamodel.StringElement(infoTitle);
|
|
166
|
+
info.version = new _apidomDatamodel.StringElement(infoVersion);
|
|
167
|
+
if (infoDescription !== undefined) {
|
|
168
|
+
info.description = new _apidomDatamodel.StringElement(infoDescription);
|
|
169
|
+
}
|
|
170
|
+
overlay.info = info;
|
|
171
|
+
if (options.extends !== undefined) {
|
|
172
|
+
overlay.extends = new _apidomDatamodel.StringElement(options.extends);
|
|
173
|
+
}
|
|
174
|
+
overlay.actions = new _apidomNsOverlay.ActionsElement(actions);
|
|
175
|
+
return overlay;
|
|
176
|
+
};
|
|
177
|
+
exports.diff = diff;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { ObjectElement, ArrayElement, StringElement, BooleanElement, isObjectElement, isArrayElement } from '@speclynx/apidom-datamodel';
|
|
2
|
+
import { NormalizedPath } from '@speclynx/apidom-json-path';
|
|
3
|
+
import { ActionElement, Overlay1Element, InfoElement, OverlayElement, ActionsElement } from '@speclynx/apidom-ns-overlay-1';
|
|
4
|
+
import OverlayError from "../../errors/OverlayError.mjs";
|
|
5
|
+
/**
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
const defaultDiffOptions = {
|
|
9
|
+
overlay: '1.1.0',
|
|
10
|
+
info: {
|
|
11
|
+
title: 'API diff',
|
|
12
|
+
version: '0.0.0'
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const createUpdateAction = (segments, value) => {
|
|
16
|
+
const action = new ActionElement();
|
|
17
|
+
action.target = new StringElement(NormalizedPath.from(segments));
|
|
18
|
+
action.update = value;
|
|
19
|
+
return action;
|
|
20
|
+
};
|
|
21
|
+
const createRemoveAction = segments => {
|
|
22
|
+
const action = new ActionElement();
|
|
23
|
+
action.target = new StringElement(NormalizedPath.from(segments));
|
|
24
|
+
action.removeField = new BooleanElement(true);
|
|
25
|
+
return action;
|
|
26
|
+
};
|
|
27
|
+
const diffElements = (left, right, segments, actions) => {
|
|
28
|
+
if (left.equals(right)) return;
|
|
29
|
+
const leftKind = left.primitive();
|
|
30
|
+
const rightKind = right.primitive();
|
|
31
|
+
if (leftKind !== rightKind) {
|
|
32
|
+
if (segments.length === 0) {
|
|
33
|
+
throw new OverlayError('Cannot diff documents with incompatible root types');
|
|
34
|
+
}
|
|
35
|
+
// structural type change: remove old, re-add new via parent update.
|
|
36
|
+
// only reachable from diffObjects — segments tail is always a string key
|
|
37
|
+
actions.push(createRemoveAction(segments));
|
|
38
|
+
const key = segments[segments.length - 1];
|
|
39
|
+
const wrapper = new ObjectElement();
|
|
40
|
+
wrapper.set(key, right);
|
|
41
|
+
actions.push(createUpdateAction(segments.slice(0, -1), wrapper));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (isObjectElement(left) && isObjectElement(right)) {
|
|
45
|
+
diffObjects(left, right, segments, actions);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (isArrayElement(left) && isArrayElement(right)) {
|
|
49
|
+
diffArrays(left, right, segments, actions);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// primitive value changed
|
|
54
|
+
actions.push(createUpdateAction(segments, right));
|
|
55
|
+
};
|
|
56
|
+
const diffObjects = (left, right, segments, actions) => {
|
|
57
|
+
const leftKeys = new Set(left.keys());
|
|
58
|
+
const rightKeys = new Set(right.keys());
|
|
59
|
+
|
|
60
|
+
// removed keys
|
|
61
|
+
for (const key of leftKeys) {
|
|
62
|
+
if (!rightKeys.has(key)) {
|
|
63
|
+
actions.push(createRemoveAction([...segments, key]));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// added keys — batched into a single parent update to minimize action count
|
|
68
|
+
const added = [];
|
|
69
|
+
for (const key of rightKeys) {
|
|
70
|
+
if (!leftKeys.has(key)) added.push(key);
|
|
71
|
+
}
|
|
72
|
+
if (added.length > 0) {
|
|
73
|
+
const patch = new ObjectElement();
|
|
74
|
+
for (const key of added) {
|
|
75
|
+
patch.set(key, right.get(key));
|
|
76
|
+
}
|
|
77
|
+
actions.push(createUpdateAction(segments, patch));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// common keys — recurse into changed values
|
|
81
|
+
for (const key of leftKeys) {
|
|
82
|
+
if (!rightKeys.has(key)) continue;
|
|
83
|
+
diffElements(left.get(key), right.get(key), [...segments, key], actions);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const diffArrays = (left, right, segments, actions) => {
|
|
87
|
+
const leftLen = left.length;
|
|
88
|
+
const rightLen = right.length;
|
|
89
|
+
const minLen = Math.min(leftLen, rightLen);
|
|
90
|
+
for (let i = 0; i < minLen; i++) {
|
|
91
|
+
const leftItem = left.get(i);
|
|
92
|
+
const rightItem = right.get(i);
|
|
93
|
+
if (leftItem.equals(rightItem)) continue;
|
|
94
|
+
if (leftItem.primitive() !== rightItem.primitive()) {
|
|
95
|
+
// structural type change at index i — reconstruct tail:
|
|
96
|
+
// remove from end down to i (reverse order preserves index stability),
|
|
97
|
+
// then batch-append right[i..] as a single ArrayElement (Overlay concat semantics)
|
|
98
|
+
for (let j = leftLen - 1; j >= i; j--) {
|
|
99
|
+
actions.push(createRemoveAction([...segments, j]));
|
|
100
|
+
}
|
|
101
|
+
const tail = new ArrayElement();
|
|
102
|
+
for (let j = i; j < rightLen; j++) {
|
|
103
|
+
tail.push(right.get(j));
|
|
104
|
+
}
|
|
105
|
+
actions.push(createUpdateAction(segments, tail));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
diffElements(leftItem, rightItem, [...segments, i], actions);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// right is longer — batch all new items into a single ArrayElement update.
|
|
112
|
+
// wrapping is required: applyUpdateAction uses deepmerge(array, array) for array targets,
|
|
113
|
+
// so the update value must be an ArrayElement to trigger concat semantics.
|
|
114
|
+
if (leftLen < rightLen) {
|
|
115
|
+
const tail = new ArrayElement();
|
|
116
|
+
for (let i = leftLen; i < rightLen; i++) {
|
|
117
|
+
tail.push(right.get(i));
|
|
118
|
+
}
|
|
119
|
+
actions.push(createUpdateAction(segments, tail));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// left is longer — remove trailing items in reverse order for index stability
|
|
123
|
+
for (let i = leftLen - 1; i >= rightLen; i--) {
|
|
124
|
+
actions.push(createRemoveAction([...segments, i]));
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Produces an {@link Overlay1Element} that, when applied to `leftElement`, yields `rightElement`.
|
|
130
|
+
*
|
|
131
|
+
* Known limitations:
|
|
132
|
+
* - Identical documents produce an overlay with an empty `actions` array. Overlay 1.1.0 §3
|
|
133
|
+
* requires `actions` to be non-empty; callers should guard against the no-diff case.
|
|
134
|
+
* - Root-level structural type change (e.g. object root → array root) throws {@link OverlayError}
|
|
135
|
+
* because Overlay has no mechanism to replace the root node.
|
|
136
|
+
* - Array insert-at-position is not supported by Overlay 1.x; positional diffing cascades
|
|
137
|
+
* replaces and appends, which is always correct but may be verbose for large reorderings.
|
|
138
|
+
* - Array items with a structural type change are handled via tail-reconstruct
|
|
139
|
+
* (remove tail + re-append from right), producing more actions than a minimal diff.
|
|
140
|
+
* - The overlay carries no "before" value, so it cannot be reversed without the original document.
|
|
141
|
+
* - Action `update` values reference elements from `rightElement` directly. Mutating `rightElement`
|
|
142
|
+
* after calling `diff` will affect the returned overlay.
|
|
143
|
+
*
|
|
144
|
+
* @public
|
|
145
|
+
*/
|
|
146
|
+
export const diff = (leftElement, rightElement, options = {}) => {
|
|
147
|
+
const overlayVersion = options.overlay ?? defaultDiffOptions.overlay;
|
|
148
|
+
const infoTitle = options.info?.title ?? defaultDiffOptions.info.title;
|
|
149
|
+
const infoVersion = options.info?.version ?? defaultDiffOptions.info.version;
|
|
150
|
+
const infoDescription = options.info?.description;
|
|
151
|
+
const actions = [];
|
|
152
|
+
diffElements(leftElement, rightElement, [], actions);
|
|
153
|
+
if (actions.length === 0 && options.onEmptyDiff === 'throw') {
|
|
154
|
+
throw new OverlayError('Diff produced no actions: documents are identical');
|
|
155
|
+
}
|
|
156
|
+
const overlay = new Overlay1Element();
|
|
157
|
+
overlay.overlay = new OverlayElement(overlayVersion);
|
|
158
|
+
const info = new InfoElement();
|
|
159
|
+
info.title = new StringElement(infoTitle);
|
|
160
|
+
info.version = new StringElement(infoVersion);
|
|
161
|
+
if (infoDescription !== undefined) {
|
|
162
|
+
info.description = new StringElement(infoDescription);
|
|
163
|
+
}
|
|
164
|
+
overlay.info = info;
|
|
165
|
+
if (options.extends !== undefined) {
|
|
166
|
+
overlay.extends = new StringElement(options.extends);
|
|
167
|
+
}
|
|
168
|
+
overlay.actions = new ActionsElement(actions);
|
|
169
|
+
return overlay;
|
|
170
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
exports.__esModule = true;
|
|
4
|
+
exports.diff = void 0;
|
|
5
|
+
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
6
|
+
var _apidomCore = require("@speclynx/apidom-core");
|
|
7
|
+
var _apidom = require("./apidom.cjs");
|
|
8
|
+
/**
|
|
9
|
+
* Computes the diff between two plain JavaScript objects and returns an Overlay document
|
|
10
|
+
* as a plain JavaScript object.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
const diff = (left, right, options) => (0, _apidomCore.toValue)((0, _apidom.diff)((0, _apidomDatamodel.refract)(left), (0, _apidomDatamodel.refract)(right), options));
|
|
15
|
+
exports.diff = diff;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { refract } from '@speclynx/apidom-datamodel';
|
|
2
|
+
import { toValue } from '@speclynx/apidom-core';
|
|
3
|
+
import { diff as diffApiDOM } from "./apidom.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* Computes the diff between two plain JavaScript objects and returns an Overlay document
|
|
6
|
+
* as a plain JavaScript object.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export const diff = (left, right, options) => toValue(diffApiDOM(refract(left), refract(right), options));
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
4
|
+
exports.__esModule = true;
|
|
5
|
+
exports.defaultOptions = exports.default = void 0;
|
|
6
|
+
var _apidomReference = require("@speclynx/apidom-reference");
|
|
7
|
+
var _apidom = require("./apidom.cjs");
|
|
8
|
+
var _OverlayError = _interopRequireDefault(require("../../errors/OverlayError.cjs"));
|
|
9
|
+
/**
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// intentionally omits overlay-json-1/overlay-yaml-1 parser opts present in apply/realms/uri.ts
|
|
14
|
+
// because diff inputs are regular API documents, not overlay documents
|
|
15
|
+
const defaultReferenceOptions = {
|
|
16
|
+
parse: {
|
|
17
|
+
parserOpts: {
|
|
18
|
+
strict: false
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
resolve: {
|
|
22
|
+
resolverOpts: {
|
|
23
|
+
fileAllowList: ['*']
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
const defaultOptions = exports.defaultOptions = {
|
|
32
|
+
reference: defaultReferenceOptions
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Computes the diff between two API documents identified by URIs and returns
|
|
37
|
+
* an {@link Overlay1Element} that, when applied to the left document, yields the right.
|
|
38
|
+
*
|
|
39
|
+
* When `options.extends` is not provided, it is automatically set to `leftURI`
|
|
40
|
+
* so the generated overlay can be applied back to the original document.
|
|
41
|
+
*
|
|
42
|
+
* @param leftURI - URI to the base (source) document
|
|
43
|
+
* @param rightURI - URI to the target (changed) document
|
|
44
|
+
* @param options - diff options; reference options live under `options.reference`
|
|
45
|
+
* @returns Overlay1Element representing the diff
|
|
46
|
+
*
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
const diff = async (leftURI, rightURI, options = defaultOptions) => {
|
|
50
|
+
const mergedRefOptions = (0, _apidomReference.mergeOptions)(defaultReferenceOptions, options.reference ?? {});
|
|
51
|
+
const [leftParseResult, rightParseResult] = await Promise.all([(0, _apidomReference.parse)(leftURI, mergedRefOptions), (0, _apidomReference.parse)(rightURI, mergedRefOptions)]);
|
|
52
|
+
const leftElement = leftParseResult.api;
|
|
53
|
+
const rightElement = rightParseResult.api;
|
|
54
|
+
if (leftElement == null) {
|
|
55
|
+
throw new _OverlayError.default(`Failed to parse document "${leftURI}"`);
|
|
56
|
+
}
|
|
57
|
+
if (rightElement == null) {
|
|
58
|
+
throw new _OverlayError.default(`Failed to parse document "${rightURI}"`);
|
|
59
|
+
}
|
|
60
|
+
const diffOptions = {
|
|
61
|
+
...options,
|
|
62
|
+
extends: options.extends ?? leftURI
|
|
63
|
+
};
|
|
64
|
+
return (0, _apidom.diff)(leftElement, rightElement, diffOptions);
|
|
65
|
+
};
|
|
66
|
+
var _default = exports.default = diff;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { parse, mergeOptions } from '@speclynx/apidom-reference';
|
|
2
|
+
import { diff as diffApiDOM } from "./apidom.mjs";
|
|
3
|
+
import OverlayError from "../../errors/OverlayError.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
// intentionally omits overlay-json-1/overlay-yaml-1 parser opts present in apply/realms/uri.ts
|
|
8
|
+
// because diff inputs are regular API documents, not overlay documents
|
|
9
|
+
const defaultReferenceOptions = {
|
|
10
|
+
parse: {
|
|
11
|
+
parserOpts: {
|
|
12
|
+
strict: false
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
resolve: {
|
|
16
|
+
resolverOpts: {
|
|
17
|
+
fileAllowList: ['*']
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
export const defaultOptions = {
|
|
26
|
+
reference: defaultReferenceOptions
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Computes the diff between two API documents identified by URIs and returns
|
|
31
|
+
* an {@link Overlay1Element} that, when applied to the left document, yields the right.
|
|
32
|
+
*
|
|
33
|
+
* When `options.extends` is not provided, it is automatically set to `leftURI`
|
|
34
|
+
* so the generated overlay can be applied back to the original document.
|
|
35
|
+
*
|
|
36
|
+
* @param leftURI - URI to the base (source) document
|
|
37
|
+
* @param rightURI - URI to the target (changed) document
|
|
38
|
+
* @param options - diff options; reference options live under `options.reference`
|
|
39
|
+
* @returns Overlay1Element representing the diff
|
|
40
|
+
*
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
const diff = async (leftURI, rightURI, options = defaultOptions) => {
|
|
44
|
+
const mergedRefOptions = mergeOptions(defaultReferenceOptions, options.reference ?? {});
|
|
45
|
+
const [leftParseResult, rightParseResult] = await Promise.all([parse(leftURI, mergedRefOptions), parse(rightURI, mergedRefOptions)]);
|
|
46
|
+
const leftElement = leftParseResult.api;
|
|
47
|
+
const rightElement = rightParseResult.api;
|
|
48
|
+
if (leftElement == null) {
|
|
49
|
+
throw new OverlayError(`Failed to parse document "${leftURI}"`);
|
|
50
|
+
}
|
|
51
|
+
if (rightElement == null) {
|
|
52
|
+
throw new OverlayError(`Failed to parse document "${rightURI}"`);
|
|
53
|
+
}
|
|
54
|
+
const diffOptions = {
|
|
55
|
+
...options,
|
|
56
|
+
extends: options.extends ?? leftURI
|
|
57
|
+
};
|
|
58
|
+
return diffApiDOM(leftElement, rightElement, diffOptions);
|
|
59
|
+
};
|
|
60
|
+
export default diff;
|
package/src/index.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
4
4
|
var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
|
|
5
5
|
exports.__esModule = true;
|
|
6
|
-
exports.validateAction = exports.defaultApplyOverlayOptions = exports.applyOverlayPOJO = exports.applyOverlayApiDOM = exports.applyOverlay = exports.applyActionPOJO = exports.applyActionApiDOM = exports.OverlayError = void 0;
|
|
6
|
+
exports.validateAction = exports.diffPOJO = exports.diffOverlay = exports.diffApiDOM = exports.defaultDiffOverlayOptions = exports.defaultApplyOverlayOptions = exports.applyOverlayPOJO = exports.applyOverlayApiDOM = exports.applyOverlay = exports.applyActionPOJO = exports.applyActionApiDOM = exports.OverlayError = void 0;
|
|
7
7
|
var _apidom = require("./apply/realms/apidom.cjs");
|
|
8
8
|
exports.applyActionApiDOM = _apidom.applyAction;
|
|
9
9
|
exports.applyOverlayApiDOM = _apidom.applyOverlay;
|
|
@@ -13,6 +13,11 @@ exports.defaultApplyOverlayOptions = _uri.defaultOptions;
|
|
|
13
13
|
var _pojo = require("./apply/realms/pojo.cjs");
|
|
14
14
|
exports.applyActionPOJO = _pojo.applyAction;
|
|
15
15
|
exports.applyOverlayPOJO = _pojo.applyOverlay;
|
|
16
|
+
var _index = require("./diff/index.cjs");
|
|
17
|
+
exports.diffApiDOM = _index.diffApiDOM;
|
|
18
|
+
exports.diffPOJO = _index.diffPOJO;
|
|
19
|
+
exports.diffOverlay = _index.diffOverlay;
|
|
20
|
+
exports.defaultDiffOverlayOptions = _index.defaultDiffOverlayOptions;
|
|
16
21
|
var _validate = require("./validate.cjs");
|
|
17
22
|
exports.validateAction = _validate.validateAction;
|
|
18
23
|
var _OverlayError = _interopRequireDefault(require("./errors/OverlayError.cjs"));
|
package/src/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { applyAction as applyActionApiDOM, applyOverlay as applyOverlayApiDOM } from "./apply/realms/apidom.mjs";
|
|
2
2
|
export { default as applyOverlay, defaultOptions as defaultApplyOverlayOptions } from "./apply/realms/uri.mjs";
|
|
3
3
|
export { applyAction as applyActionPOJO, applyOverlay as applyOverlayPOJO } from "./apply/realms/pojo.mjs";
|
|
4
|
+
export { diffApiDOM, diffPOJO, diffOverlay, defaultDiffOverlayOptions } from "./diff/index.mjs";
|
|
4
5
|
export { validateAction } from "./validate.mjs";
|
|
5
6
|
export { default as OverlayError } from "./errors/OverlayError.mjs";
|
|
@@ -95,6 +95,81 @@ export declare const applyOverlayPOJO: (overlay: Record<string, unknown>, target
|
|
|
95
95
|
*/
|
|
96
96
|
export declare const defaultApplyOverlayOptions: ApplyOverlayOptions;
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* @public
|
|
100
|
+
*/
|
|
101
|
+
export declare const defaultDiffOverlayOptions: DiffOverlayOptions;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Produces an {@link Overlay1Element} that, when applied to `leftElement`, yields `rightElement`.
|
|
105
|
+
*
|
|
106
|
+
* Known limitations:
|
|
107
|
+
* - Identical documents produce an overlay with an empty `actions` array. Overlay 1.1.0 §3
|
|
108
|
+
* requires `actions` to be non-empty; callers should guard against the no-diff case.
|
|
109
|
+
* - Root-level structural type change (e.g. object root → array root) throws {@link OverlayError}
|
|
110
|
+
* because Overlay has no mechanism to replace the root node.
|
|
111
|
+
* - Array insert-at-position is not supported by Overlay 1.x; positional diffing cascades
|
|
112
|
+
* replaces and appends, which is always correct but may be verbose for large reorderings.
|
|
113
|
+
* - Array items with a structural type change are handled via tail-reconstruct
|
|
114
|
+
* (remove tail + re-append from right), producing more actions than a minimal diff.
|
|
115
|
+
* - The overlay carries no "before" value, so it cannot be reversed without the original document.
|
|
116
|
+
* - Action `update` values reference elements from `rightElement` directly. Mutating `rightElement`
|
|
117
|
+
* after calling `diff` will affect the returned overlay.
|
|
118
|
+
*
|
|
119
|
+
* @public
|
|
120
|
+
*/
|
|
121
|
+
export declare const diffApiDOM: (leftElement: Element_2, rightElement: Element_2, options?: DiffOptions) => Overlay1Element;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @public
|
|
125
|
+
*/
|
|
126
|
+
export declare interface DiffOptions {
|
|
127
|
+
readonly overlay?: string;
|
|
128
|
+
readonly info?: {
|
|
129
|
+
title?: string;
|
|
130
|
+
description?: string;
|
|
131
|
+
version?: string;
|
|
132
|
+
};
|
|
133
|
+
readonly extends?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Controls behavior when the two documents are identical and the diff produces no actions.
|
|
136
|
+
* - `'allow'` (default) — returns an overlay with an empty `actions` array
|
|
137
|
+
* - `'throw'` — throws {@link OverlayError}
|
|
138
|
+
*/
|
|
139
|
+
readonly onEmptyDiff?: 'allow' | 'throw';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Computes the diff between two API documents identified by URIs and returns
|
|
144
|
+
* an {@link Overlay1Element} that, when applied to the left document, yields the right.
|
|
145
|
+
*
|
|
146
|
+
* When `options.extends` is not provided, it is automatically set to `leftURI`
|
|
147
|
+
* so the generated overlay can be applied back to the original document.
|
|
148
|
+
*
|
|
149
|
+
* @param leftURI - URI to the base (source) document
|
|
150
|
+
* @param rightURI - URI to the target (changed) document
|
|
151
|
+
* @param options - diff options; reference options live under `options.reference`
|
|
152
|
+
* @returns Overlay1Element representing the diff
|
|
153
|
+
*
|
|
154
|
+
* @public
|
|
155
|
+
*/
|
|
156
|
+
export declare const diffOverlay: (leftURI: string, rightURI: string, options?: DiffOverlayOptions) => Promise<Overlay1Element>;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @public
|
|
160
|
+
*/
|
|
161
|
+
export declare interface DiffOverlayOptions extends DiffOptions {
|
|
162
|
+
readonly reference?: PartialDeep<ApiDOMReferenceOptions>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Computes the diff between two plain JavaScript objects and returns an Overlay document
|
|
167
|
+
* as a plain JavaScript object.
|
|
168
|
+
*
|
|
169
|
+
* @public
|
|
170
|
+
*/
|
|
171
|
+
export declare const diffPOJO: (left: Record<string, unknown>, right: Record<string, unknown>, options?: DiffOptions) => unknown;
|
|
172
|
+
|
|
98
173
|
/**
|
|
99
174
|
* @public
|
|
100
175
|
*/
|