@unispechq/unispec-core 0.3.0 → 0.3.2
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/dist/cjs/diff/annotators.js +36 -9
- package/dist/cjs/src/cache/cache-factory.js +72 -0
- package/dist/cjs/src/cache/cache-manager.js +128 -0
- package/dist/cjs/src/cache/constants.js +25 -0
- package/dist/cjs/src/cache/hash-utils.js +19 -0
- package/dist/cjs/src/cache/hashing.js +230 -0
- package/dist/cjs/src/cache/index.js +24 -0
- package/dist/cjs/src/cache/lru-cache.js +144 -0
- package/dist/cjs/src/cache/types.js +5 -0
- package/dist/cjs/src/diff/annotators.js +160 -0
- package/dist/cjs/src/diff/change-reports.js +369 -0
- package/dist/cjs/src/diff/core.js +158 -0
- package/dist/cjs/src/diff/enhanced-diff.js +65 -0
- package/dist/cjs/src/diff/impact-strategies-refactored.js +230 -0
- package/dist/cjs/src/diff/impact-strategies.js +219 -0
- package/dist/cjs/src/diff/index.js +27 -0
- package/dist/cjs/src/diff/metrics-calculator.js +69 -0
- package/dist/cjs/src/diff/risk-calculator.js +58 -0
- package/dist/cjs/src/diff/suggestion-generator.js +78 -0
- package/dist/cjs/src/diff/types.js +11 -0
- package/dist/cjs/src/errors/base-error.js +33 -0
- package/dist/cjs/src/errors/config-error.js +11 -0
- package/dist/cjs/src/errors/error-factory.js +48 -0
- package/dist/cjs/src/errors/index.js +19 -0
- package/dist/cjs/src/errors/loader-error.js +11 -0
- package/dist/cjs/src/errors/reference-error.js +11 -0
- package/dist/cjs/src/errors/schema-error.js +11 -0
- package/dist/cjs/src/errors/security-error.js +11 -0
- package/dist/cjs/src/errors/semantic-error.js +11 -0
- package/dist/cjs/src/generated-schemas.js +2100 -0
- package/dist/cjs/src/index.js +59 -0
- package/dist/cjs/src/loader/index.js +13 -0
- package/dist/cjs/src/loader/security-validator.js +53 -0
- package/dist/cjs/src/loader/types.js +11 -0
- package/dist/cjs/src/loader/unispec-loader.js +84 -0
- package/dist/cjs/src/loader/yaml-loader.js +76 -0
- package/dist/cjs/src/normalizer/core.js +37 -0
- package/dist/cjs/src/normalizer/graphql-normalizer.js +67 -0
- package/dist/cjs/src/normalizer/index.js +7 -0
- package/dist/cjs/src/normalizer/rest-normalizer.js +51 -0
- package/dist/cjs/src/normalizer/types.js +2 -0
- package/dist/cjs/src/normalizer/utils.js +49 -0
- package/dist/cjs/src/normalizer/websocket-normalizer.js +81 -0
- package/dist/cjs/src/optimizer/core.js +140 -0
- package/dist/cjs/src/optimizer/index.js +17 -0
- package/dist/cjs/src/optimizer/optimization-functions.js +185 -0
- package/dist/cjs/src/optimizer/types.js +2 -0
- package/dist/cjs/src/optimizer/utils.js +32 -0
- package/dist/cjs/src/schemas/dedupe.js +113 -0
- package/dist/cjs/src/schemas/index.js +14 -0
- package/dist/cjs/src/schemas/resolver.js +42 -0
- package/dist/cjs/src/schemas/utils.js +53 -0
- package/dist/cjs/src/types/index.js +2 -0
- package/dist/cjs/src/validator/ajv-validator.js +82 -0
- package/dist/cjs/src/validator/config-validator-main.js +34 -0
- package/dist/cjs/src/validator/config-validator.js +17 -0
- package/dist/cjs/src/validator/index.js +23 -0
- package/dist/cjs/src/validator/object-traversal.js +112 -0
- package/dist/cjs/src/validator/reference-validator.js +233 -0
- package/dist/cjs/src/validator/schema-references.js +116 -0
- package/dist/cjs/src/validator/semantic-validator.js +328 -0
- package/dist/cjs/src/validator/tests-validator.js +16 -0
- package/dist/cjs/src/validator/types.js +2 -0
- package/dist/cjs/src/validator/unispec-validator.js +80 -0
- package/dist/cjs/src/validator/validator-factory.js +77 -0
- package/dist/cjs/src/versions.js +147 -0
- package/dist/cjs/tests/cache/cache.test.js +274 -0
- package/dist/cjs/tests/cache/utils.js +32 -0
- package/dist/cjs/tests/concurrency-normalizer-optimizer.test.js +1 -0
- package/dist/cjs/tests/diff/diff-annotators.test.js +280 -0
- package/dist/cjs/tests/diff/diff-comprehensive.test.js +262 -0
- package/dist/cjs/tests/diff/diff-extended.test.js +235 -0
- package/dist/cjs/tests/diff/diff.test.js +189 -0
- package/dist/cjs/tests/diff/utils.js +8 -0
- package/dist/cjs/tests/errors/errors-integration.test.js +173 -0
- package/dist/cjs/tests/errors/errors.test.js +280 -0
- package/dist/cjs/tests/errors/utils.js +7 -0
- package/dist/cjs/tests/loader/integration.test.js +216 -0
- package/dist/cjs/tests/loader/loader.test.js +341 -0
- package/dist/cjs/tests/normalizer/normalizer-comprehensive.test.js +648 -0
- package/dist/cjs/tests/normalizer/normalizer-invalid.test.js +258 -0
- package/dist/cjs/tests/normalizer/normalizer-valid.test.js +238 -0
- package/dist/cjs/tests/normalizer/utils.js +47 -0
- package/dist/cjs/tests/optimizer/compress-references.test.js +304 -0
- package/dist/cjs/tests/optimizer/deduplication.test.js +132 -0
- package/dist/cjs/tests/optimizer/integration.test.js +131 -0
- package/dist/cjs/tests/optimizer/optimization-report.test.js +222 -0
- package/dist/cjs/tests/optimizer/optimize-document.test.js +187 -0
- package/dist/cjs/tests/optimizer/orphaned-schemas.test.js +194 -0
- package/dist/cjs/tests/optimizer/sort-schemas.test.js +131 -0
- package/dist/cjs/tests/optimizer/utils.js +209 -0
- package/dist/cjs/tests/schemas/schemas-edge-cases.test.js +223 -0
- package/dist/cjs/tests/schemas/schemas.test.js +400 -0
- package/dist/cjs/tests/schemas/utils.js +7 -0
- package/dist/cjs/tests/utils.js +131 -0
- package/dist/cjs/tests/validator/config-validator.test.js +78 -0
- package/dist/cjs/tests/validator/debug-config.js +1 -0
- package/dist/cjs/tests/validator/debug-missing-service.js +1 -0
- package/dist/cjs/tests/validator/debug-other-configs.js +1 -0
- package/dist/cjs/tests/validator/debug-references.js +1 -0
- package/dist/cjs/tests/validator/unispec-validator.test.js +103 -0
- package/dist/cjs/tests/validator/utils.js +25 -0
- package/dist/diff/annotators.js +36 -9
- package/dist/src/cache/cache-factory.d.ts +31 -0
- package/dist/src/cache/cache-factory.js +65 -0
- package/dist/src/cache/cache-manager.d.ts +62 -0
- package/dist/src/cache/cache-manager.js +124 -0
- package/dist/src/cache/constants.d.ts +21 -0
- package/dist/src/cache/constants.js +22 -0
- package/dist/src/cache/hash-utils.d.ts +11 -0
- package/dist/src/cache/hash-utils.js +15 -0
- package/dist/src/cache/hashing.d.ts +28 -0
- package/dist/src/cache/hashing.js +193 -0
- package/dist/src/cache/index.d.ts +6 -0
- package/dist/src/cache/index.js +10 -0
- package/dist/src/cache/lru-cache.d.ts +44 -0
- package/dist/src/cache/lru-cache.js +140 -0
- package/dist/src/cache/types.d.ts +24 -0
- package/dist/src/cache/types.js +4 -0
- package/dist/src/diff/annotators.d.ts +4 -0
- package/dist/src/diff/annotators.js +155 -0
- package/dist/src/diff/change-reports.d.ts +37 -0
- package/dist/src/diff/change-reports.js +366 -0
- package/dist/src/diff/core.d.ts +26 -0
- package/dist/src/diff/core.js +155 -0
- package/dist/src/diff/enhanced-diff.d.ts +51 -0
- package/dist/src/diff/enhanced-diff.js +62 -0
- package/dist/src/diff/impact-strategies-refactored.d.ts +69 -0
- package/dist/src/diff/impact-strategies-refactored.js +223 -0
- package/dist/src/diff/impact-strategies.d.ts +41 -0
- package/dist/src/diff/impact-strategies.js +212 -0
- package/dist/src/diff/index.d.ts +8 -0
- package/dist/src/diff/index.js +11 -0
- package/dist/src/diff/metrics-calculator.d.ts +23 -0
- package/dist/src/diff/metrics-calculator.js +65 -0
- package/dist/src/diff/risk-calculator.d.ts +23 -0
- package/dist/src/diff/risk-calculator.js +55 -0
- package/dist/src/diff/suggestion-generator.d.ts +18 -0
- package/dist/src/diff/suggestion-generator.js +74 -0
- package/dist/src/diff/types.d.ts +24 -0
- package/dist/src/diff/types.js +8 -0
- package/dist/src/errors/base-error.d.ts +20 -0
- package/dist/src/errors/base-error.js +29 -0
- package/dist/src/errors/config-error.d.ts +4 -0
- package/dist/src/errors/config-error.js +7 -0
- package/dist/src/errors/error-factory.d.ts +22 -0
- package/dist/src/errors/error-factory.js +45 -0
- package/dist/src/errors/index.d.ts +8 -0
- package/dist/src/errors/index.js +8 -0
- package/dist/src/errors/loader-error.d.ts +4 -0
- package/dist/src/errors/loader-error.js +7 -0
- package/dist/src/errors/reference-error.d.ts +4 -0
- package/dist/src/errors/reference-error.js +7 -0
- package/dist/src/errors/schema-error.d.ts +4 -0
- package/dist/src/errors/schema-error.js +7 -0
- package/dist/src/errors/security-error.d.ts +4 -0
- package/dist/src/errors/security-error.js +7 -0
- package/dist/src/errors/semantic-error.d.ts +4 -0
- package/dist/src/errors/semantic-error.js +7 -0
- package/dist/src/generated-schemas.d.ts +2073 -0
- package/dist/src/generated-schemas.js +2097 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +43 -0
- package/dist/src/loader/index.d.ts +5 -0
- package/dist/src/loader/index.js +5 -0
- package/dist/src/loader/security-validator.d.ts +5 -0
- package/dist/src/loader/security-validator.js +50 -0
- package/dist/src/loader/types.d.ts +30 -0
- package/dist/src/loader/types.js +8 -0
- package/dist/src/loader/unispec-loader.d.ts +10 -0
- package/dist/src/loader/unispec-loader.js +81 -0
- package/dist/src/loader/yaml-loader.d.ts +10 -0
- package/dist/src/loader/yaml-loader.js +39 -0
- package/dist/src/normalizer/core.d.ts +24 -0
- package/dist/src/normalizer/core.js +34 -0
- package/dist/src/normalizer/graphql-normalizer.d.ts +8 -0
- package/dist/src/normalizer/graphql-normalizer.js +64 -0
- package/dist/src/normalizer/index.d.ts +2 -0
- package/dist/src/normalizer/index.js +3 -0
- package/dist/src/normalizer/rest-normalizer.d.ts +8 -0
- package/dist/src/normalizer/rest-normalizer.js +48 -0
- package/dist/src/normalizer/types.d.ts +7 -0
- package/dist/src/normalizer/types.js +1 -0
- package/dist/src/normalizer/utils.d.ts +17 -0
- package/dist/src/normalizer/utils.js +45 -0
- package/dist/src/normalizer/websocket-normalizer.d.ts +8 -0
- package/dist/src/normalizer/websocket-normalizer.js +78 -0
- package/dist/src/optimizer/core.d.ts +17 -0
- package/dist/src/optimizer/core.js +136 -0
- package/dist/src/optimizer/index.d.ts +4 -0
- package/dist/src/optimizer/index.js +7 -0
- package/dist/src/optimizer/optimization-functions.d.ts +32 -0
- package/dist/src/optimizer/optimization-functions.js +179 -0
- package/dist/src/optimizer/types.d.ts +28 -0
- package/dist/src/optimizer/types.js +1 -0
- package/dist/src/optimizer/utils.d.ts +7 -0
- package/dist/src/optimizer/utils.js +29 -0
- package/dist/src/schemas/dedupe.d.ts +9 -0
- package/dist/src/schemas/dedupe.js +110 -0
- package/dist/src/schemas/index.d.ts +3 -0
- package/dist/src/schemas/index.js +6 -0
- package/dist/src/schemas/resolver.d.ts +19 -0
- package/dist/src/schemas/resolver.js +38 -0
- package/dist/src/schemas/utils.d.ts +20 -0
- package/dist/src/schemas/utils.js +49 -0
- package/dist/src/types/index.d.ts +434 -0
- package/dist/src/types/index.js +1 -0
- package/dist/src/validator/ajv-validator.d.ts +15 -0
- package/dist/src/validator/ajv-validator.js +75 -0
- package/dist/src/validator/config-validator-main.d.ts +10 -0
- package/dist/src/validator/config-validator-main.js +31 -0
- package/dist/src/validator/config-validator.d.ts +5 -0
- package/dist/src/validator/config-validator.js +14 -0
- package/dist/src/validator/index.d.ts +10 -0
- package/dist/src/validator/index.js +11 -0
- package/dist/src/validator/object-traversal.d.ts +52 -0
- package/dist/src/validator/object-traversal.js +104 -0
- package/dist/src/validator/reference-validator.d.ts +31 -0
- package/dist/src/validator/reference-validator.js +230 -0
- package/dist/src/validator/schema-references.d.ts +23 -0
- package/dist/src/validator/schema-references.js +111 -0
- package/dist/src/validator/semantic-validator.d.ts +26 -0
- package/dist/src/validator/semantic-validator.js +325 -0
- package/dist/src/validator/tests-validator.d.ts +9 -0
- package/dist/src/validator/tests-validator.js +13 -0
- package/dist/src/validator/types.d.ts +29 -0
- package/dist/src/validator/types.js +1 -0
- package/dist/src/validator/unispec-validator.d.ts +15 -0
- package/dist/src/validator/unispec-validator.js +77 -0
- package/dist/src/validator/validator-factory.d.ts +10 -0
- package/dist/src/validator/validator-factory.js +73 -0
- package/dist/src/versions.d.ts +10 -0
- package/dist/src/versions.js +143 -0
- package/dist/tests/cache/cache.test.d.ts +1 -0
- package/dist/tests/cache/cache.test.js +269 -0
- package/dist/tests/cache/utils.d.ts +4 -0
- package/dist/tests/cache/utils.js +24 -0
- package/dist/tests/concurrency-normalizer-optimizer.test.d.ts +0 -0
- package/dist/tests/concurrency-normalizer-optimizer.test.js +1 -0
- package/dist/tests/diff/diff-annotators.test.d.ts +1 -0
- package/dist/tests/diff/diff-annotators.test.js +275 -0
- package/dist/tests/diff/diff-comprehensive.test.d.ts +1 -0
- package/dist/tests/diff/diff-comprehensive.test.js +257 -0
- package/dist/tests/diff/diff-extended.test.d.ts +1 -0
- package/dist/tests/diff/diff-extended.test.js +230 -0
- package/dist/tests/diff/diff.test.d.ts +1 -0
- package/dist/tests/diff/diff.test.js +184 -0
- package/dist/tests/diff/utils.d.ts +2 -0
- package/dist/tests/diff/utils.js +3 -0
- package/dist/tests/errors/errors-integration.test.d.ts +1 -0
- package/dist/tests/errors/errors-integration.test.js +168 -0
- package/dist/tests/errors/errors.test.d.ts +1 -0
- package/dist/tests/errors/errors.test.js +275 -0
- package/dist/tests/errors/utils.d.ts +2 -0
- package/dist/tests/errors/utils.js +3 -0
- package/dist/tests/loader/integration.test.d.ts +1 -0
- package/dist/tests/loader/integration.test.js +211 -0
- package/dist/tests/loader/loader.test.d.ts +1 -0
- package/dist/tests/loader/loader.test.js +336 -0
- package/dist/tests/normalizer/normalizer-comprehensive.test.d.ts +1 -0
- package/dist/tests/normalizer/normalizer-comprehensive.test.js +643 -0
- package/dist/tests/normalizer/normalizer-invalid.test.d.ts +1 -0
- package/dist/tests/normalizer/normalizer-invalid.test.js +253 -0
- package/dist/tests/normalizer/normalizer-valid.test.d.ts +1 -0
- package/dist/tests/normalizer/normalizer-valid.test.js +233 -0
- package/dist/tests/normalizer/utils.d.ts +18 -0
- package/dist/tests/normalizer/utils.js +36 -0
- package/dist/tests/optimizer/compress-references.test.d.ts +1 -0
- package/dist/tests/optimizer/compress-references.test.js +299 -0
- package/dist/tests/optimizer/deduplication.test.d.ts +1 -0
- package/dist/tests/optimizer/deduplication.test.js +127 -0
- package/dist/tests/optimizer/integration.test.d.ts +1 -0
- package/dist/tests/optimizer/integration.test.js +126 -0
- package/dist/tests/optimizer/optimization-report.test.d.ts +1 -0
- package/dist/tests/optimizer/optimization-report.test.js +217 -0
- package/dist/tests/optimizer/optimize-document.test.d.ts +1 -0
- package/dist/tests/optimizer/optimize-document.test.js +182 -0
- package/dist/tests/optimizer/orphaned-schemas.test.d.ts +1 -0
- package/dist/tests/optimizer/orphaned-schemas.test.js +189 -0
- package/dist/tests/optimizer/sort-schemas.test.d.ts +1 -0
- package/dist/tests/optimizer/sort-schemas.test.js +126 -0
- package/dist/tests/optimizer/utils.d.ts +8 -0
- package/dist/tests/optimizer/utils.js +199 -0
- package/dist/tests/schemas/schemas-edge-cases.test.d.ts +1 -0
- package/dist/tests/schemas/schemas-edge-cases.test.js +218 -0
- package/dist/tests/schemas/schemas.test.d.ts +1 -0
- package/dist/tests/schemas/schemas.test.js +395 -0
- package/dist/tests/schemas/utils.d.ts +2 -0
- package/dist/tests/schemas/utils.js +3 -0
- package/dist/tests/utils.d.ts +10 -0
- package/dist/tests/utils.js +118 -0
- package/dist/tests/validator/config-validator.test.d.ts +1 -0
- package/dist/tests/validator/config-validator.test.js +73 -0
- package/dist/tests/validator/debug-config.d.ts +0 -0
- package/dist/tests/validator/debug-config.js +1 -0
- package/dist/tests/validator/debug-missing-service.d.ts +0 -0
- package/dist/tests/validator/debug-missing-service.js +1 -0
- package/dist/tests/validator/debug-other-configs.d.ts +0 -0
- package/dist/tests/validator/debug-other-configs.js +1 -0
- package/dist/tests/validator/debug-references.d.ts +0 -0
- package/dist/tests/validator/debug-references.js +1 -0
- package/dist/tests/validator/unispec-validator.test.d.ts +1 -0
- package/dist/tests/validator/unispec-validator.test.js +98 -0
- package/dist/tests/validator/utils.d.ts +6 -0
- package/dist/tests/validator/utils.js +20 -0
- package/package.json +4 -3
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffUniSpec = diffUniSpec;
|
|
4
|
+
const annotators_1 = require("./annotators.js");
|
|
5
|
+
const types_1 = require("./types.js");
|
|
6
|
+
/**
|
|
7
|
+
* Check if a value is a plain object (not array, null, or other object types).
|
|
8
|
+
*
|
|
9
|
+
* @param value - The value to check
|
|
10
|
+
* @returns true if the value is a plain object, false otherwise
|
|
11
|
+
*/
|
|
12
|
+
function isPlainObject(value) {
|
|
13
|
+
return Object.prototype.toString.call(value) === "[object Object]";
|
|
14
|
+
}
|
|
15
|
+
function diffValues(oldVal, newVal, basePath, out, options = {}) {
|
|
16
|
+
if (oldVal === newVal) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// Both plain objects → recurse by keys
|
|
20
|
+
if (isPlainObject(oldVal) && isPlainObject(newVal)) {
|
|
21
|
+
const oldKeys = new Set(Object.keys(oldVal));
|
|
22
|
+
const newKeys = new Set(Object.keys(newVal));
|
|
23
|
+
// Removed keys
|
|
24
|
+
for (const key of oldKeys) {
|
|
25
|
+
if (!newKeys.has(key)) {
|
|
26
|
+
out.push({
|
|
27
|
+
path: `${basePath}/${key}`,
|
|
28
|
+
description: "Field removed",
|
|
29
|
+
severity: "unknown",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Added / changed keys
|
|
34
|
+
for (const key of newKeys) {
|
|
35
|
+
const childPath = `${basePath}/${key}`;
|
|
36
|
+
if (!oldKeys.has(key)) {
|
|
37
|
+
out.push({
|
|
38
|
+
path: childPath,
|
|
39
|
+
description: "Field added",
|
|
40
|
+
severity: "unknown",
|
|
41
|
+
});
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const shouldRecurse = options.deepComparison !== false;
|
|
45
|
+
if (shouldRecurse) {
|
|
46
|
+
diffValues(oldVal[key], newVal[key], childPath, out, options);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Arrays
|
|
52
|
+
if (Array.isArray(oldVal) && Array.isArray(newVal)) {
|
|
53
|
+
// Special handling for UniSpec collections identified by "name"
|
|
54
|
+
const _namedCollectionPaths = options.namedCollectionPaths || types_1.DEFAULT_NAMED_COLLECTION_PATHS;
|
|
55
|
+
const isNamedCollection = (options.namedCollectionPaths || [...types_1.DEFAULT_NAMED_COLLECTION_PATHS]).includes(basePath) || basePath.endsWith("/messages"); // Handle messages in channels
|
|
56
|
+
if (isNamedCollection) {
|
|
57
|
+
const oldByName = new Map();
|
|
58
|
+
const newByName = new Map();
|
|
59
|
+
for (const item of oldVal) {
|
|
60
|
+
if (item && typeof item === "object" && typeof item.name === "string") {
|
|
61
|
+
oldByName.set(item.name, item);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const item of newVal) {
|
|
65
|
+
if (item && typeof item === "object" && typeof item.name === "string") {
|
|
66
|
+
newByName.set(item.name, item);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Removed
|
|
70
|
+
for (const [name, oldItem] of oldByName.entries()) {
|
|
71
|
+
if (!newByName.has(name)) {
|
|
72
|
+
out.push({
|
|
73
|
+
path: `${basePath}/${name}`,
|
|
74
|
+
description: "Item removed",
|
|
75
|
+
severity: "unknown",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const newItem = newByName.get(name);
|
|
80
|
+
const shouldRecurse = options.deepComparison !== false;
|
|
81
|
+
if (shouldRecurse) {
|
|
82
|
+
diffValues(oldItem, newItem, `${basePath}/${name}`, out, options);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Added
|
|
87
|
+
for (const [name, _newItem] of newByName.entries()) {
|
|
88
|
+
if (!oldByName.has(name)) {
|
|
89
|
+
out.push({
|
|
90
|
+
path: `${basePath}/${name}`,
|
|
91
|
+
description: "Item added",
|
|
92
|
+
severity: "unknown",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Regular array comparison
|
|
99
|
+
const maxLength = Math.max(oldVal.length, newVal.length);
|
|
100
|
+
for (let i = 0; i < maxLength; i++) {
|
|
101
|
+
if (i >= oldVal.length) {
|
|
102
|
+
out.push({
|
|
103
|
+
path: `${basePath}/${i}`,
|
|
104
|
+
description: "Item added",
|
|
105
|
+
severity: "unknown",
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else if (i >= newVal.length) {
|
|
109
|
+
out.push({
|
|
110
|
+
path: `${basePath}/${i}`,
|
|
111
|
+
description: "Item removed",
|
|
112
|
+
severity: "unknown",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
const shouldRecurse = options.deepComparison !== false;
|
|
117
|
+
if (shouldRecurse) {
|
|
118
|
+
diffValues(oldVal[i], newVal[i], `${basePath}/${i}`, out, options);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Primitive values or different types
|
|
124
|
+
out.push({
|
|
125
|
+
path: basePath,
|
|
126
|
+
description: "Value changed",
|
|
127
|
+
severity: "unknown",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Compare two UniSpec documents and return detected changes.
|
|
132
|
+
*
|
|
133
|
+
* This function performs a deep comparison between two UniSpec documents
|
|
134
|
+
* and categorizes changes by severity and protocol. Useful for:
|
|
135
|
+
* - API change detection in CI/CD pipelines
|
|
136
|
+
* - Breaking change analysis
|
|
137
|
+
* - Version compatibility checks
|
|
138
|
+
* - Audit trails for API evolution
|
|
139
|
+
*
|
|
140
|
+
* Features:
|
|
141
|
+
* - Detects added/removed fields at any depth
|
|
142
|
+
* - Special handling for named collections (routes, operations, channels)
|
|
143
|
+
* - Severity classification: "breaking" | "non-breaking" | "unknown"
|
|
144
|
+
* - Protocol categorization: "rest" | "graphql" | "websocket"
|
|
145
|
+
* - Detailed change descriptions with JSON paths
|
|
146
|
+
* - Configurable named collection paths and comparison depth
|
|
147
|
+
*
|
|
148
|
+
* @param oldDoc - The previous version of the UniSpec document
|
|
149
|
+
* @param newDoc - The current version of the UniSpec document
|
|
150
|
+
* @param options - Configuration options for diff behavior
|
|
151
|
+
* @returns Object containing all detected changes
|
|
152
|
+
*/
|
|
153
|
+
function diffUniSpec(oldDoc, newDoc, options = {}) {
|
|
154
|
+
const changes = [];
|
|
155
|
+
diffValues(oldDoc, newDoc, "", changes, options);
|
|
156
|
+
const annotated = changes.map((change) => (0, annotators_1.annotateWebSocketChange)((0, annotators_1.annotateGraphQLChange)((0, annotators_1.annotateRestChange)(change))));
|
|
157
|
+
return { changes: annotated };
|
|
158
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffUniSpecEnhanced = diffUniSpecEnhanced;
|
|
4
|
+
const core_1 = require("./core.js");
|
|
5
|
+
const impact_strategies_refactored_1 = require("./impact-strategies-refactored.js");
|
|
6
|
+
const metrics_calculator_1 = require("./metrics-calculator.js");
|
|
7
|
+
const risk_calculator_1 = require("./risk-calculator.js");
|
|
8
|
+
const suggestion_generator_1 = require("./suggestion-generator.js");
|
|
9
|
+
/**
|
|
10
|
+
* Service instances for better separation of concerns.
|
|
11
|
+
*/
|
|
12
|
+
const suggestionGenerator = new suggestion_generator_1.SuggestionGeneratorService();
|
|
13
|
+
const metricsCalculator = new metrics_calculator_1.MetricsCalculatorService();
|
|
14
|
+
/**
|
|
15
|
+
* Perform enhanced diff analysis with impact assessment and compatibility metrics.
|
|
16
|
+
*
|
|
17
|
+
* This function extends the basic diff functionality with:
|
|
18
|
+
* - Detailed impact analysis for each change
|
|
19
|
+
* - Compatibility metrics
|
|
20
|
+
* - Migration suggestions
|
|
21
|
+
* - Risk assessment
|
|
22
|
+
*
|
|
23
|
+
* @param oldDoc - The previous version of the UniSpec document
|
|
24
|
+
* @param newDoc - The current version of the UniSpec document
|
|
25
|
+
* @returns Enhanced diff result with comprehensive analysis
|
|
26
|
+
*/
|
|
27
|
+
function diffUniSpecEnhanced(oldDoc, newDoc) {
|
|
28
|
+
// Get basic diff
|
|
29
|
+
const basicDiff = (0, core_1.diffUniSpec)(oldDoc, newDoc);
|
|
30
|
+
// Enhance each change with impact analysis using strategy pattern
|
|
31
|
+
const enhancedChanges = basicDiff.changes.map((change) => {
|
|
32
|
+
// Get appropriate strategy for the protocol
|
|
33
|
+
const strategy = impact_strategies_refactored_1.ImpactAnalysisStrategyFactory.getStrategy(change.protocol);
|
|
34
|
+
// Analyze impact using strategy
|
|
35
|
+
const impact = strategy.analyze(change);
|
|
36
|
+
// Generate suggestions
|
|
37
|
+
const suggestions = suggestionGenerator.generateSuggestions(change, impact);
|
|
38
|
+
// Calculate risk level using calculator
|
|
39
|
+
const riskLevel = risk_calculator_1.RiskLevelCalculator.calculate(impact);
|
|
40
|
+
return {
|
|
41
|
+
...change,
|
|
42
|
+
impact,
|
|
43
|
+
suggestions,
|
|
44
|
+
riskLevel,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
// Calculate metrics
|
|
48
|
+
const metrics = metricsCalculator.calculateMetrics(enhancedChanges);
|
|
49
|
+
// Generate summary
|
|
50
|
+
const summary = {
|
|
51
|
+
totalChanges: enhancedChanges.length,
|
|
52
|
+
criticalChanges: enhancedChanges.filter((c) => c.riskLevel === "critical")
|
|
53
|
+
.length,
|
|
54
|
+
highRiskChanges: enhancedChanges.filter((c) => c.riskLevel === "high")
|
|
55
|
+
.length,
|
|
56
|
+
mediumRiskChanges: enhancedChanges.filter((c) => c.riskLevel === "medium")
|
|
57
|
+
.length,
|
|
58
|
+
lowRiskChanges: enhancedChanges.filter((c) => c.riskLevel === "low").length,
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
changes: enhancedChanges,
|
|
62
|
+
metrics,
|
|
63
|
+
summary,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImpactAnalysisStrategyFactory = exports.DefaultImpactStrategy = exports.WebSocketImpactStrategy = exports.GraphQLOperationImpactStrategy = exports.RestRouteImpactStrategy = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Base class for impact analysis strategies with common functionality.
|
|
6
|
+
*/
|
|
7
|
+
class BaseImpactStrategy {
|
|
8
|
+
createDefaultImpact() {
|
|
9
|
+
return {
|
|
10
|
+
backwardCompatibility: "unknown",
|
|
11
|
+
clientImpact: "none",
|
|
12
|
+
serverImpact: "none",
|
|
13
|
+
migrationComplexity: "none",
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
setBreakingChange(impact, clientImpact = "high", serverImpact = "medium", migrationComplexity = "moderate") {
|
|
17
|
+
impact.backwardCompatibility = "incompatible";
|
|
18
|
+
impact.clientImpact = clientImpact;
|
|
19
|
+
impact.serverImpact = serverImpact;
|
|
20
|
+
impact.migrationComplexity = migrationComplexity;
|
|
21
|
+
return impact;
|
|
22
|
+
}
|
|
23
|
+
setNonBreakingChange(impact, clientImpact = "none", serverImpact = "low", migrationComplexity = "none") {
|
|
24
|
+
impact.backwardCompatibility = "compatible";
|
|
25
|
+
impact.clientImpact = clientImpact;
|
|
26
|
+
impact.serverImpact = serverImpact;
|
|
27
|
+
impact.migrationComplexity = migrationComplexity;
|
|
28
|
+
return impact;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* REST route impact analysis strategy.
|
|
33
|
+
*/
|
|
34
|
+
class RestRouteImpactStrategy extends BaseImpactStrategy {
|
|
35
|
+
analyze(change) {
|
|
36
|
+
const impact = this.createDefaultImpact();
|
|
37
|
+
// Route removal is always breaking
|
|
38
|
+
if (this.isRouteRemoval(change)) {
|
|
39
|
+
return this.setBreakingChange(impact, "high", "medium", "complex");
|
|
40
|
+
}
|
|
41
|
+
// Route addition is non-breaking
|
|
42
|
+
if (this.isRouteAddition(change)) {
|
|
43
|
+
return this.setNonBreakingChange(impact, "none", "low", "none");
|
|
44
|
+
}
|
|
45
|
+
// Method change is breaking
|
|
46
|
+
if (this.isMethodChange(change)) {
|
|
47
|
+
return this.setBreakingChange(impact, "high", "medium", "moderate");
|
|
48
|
+
}
|
|
49
|
+
// Path change is breaking
|
|
50
|
+
if (this.isPathChange(change)) {
|
|
51
|
+
return this.setBreakingChange(impact, "high", "medium", "moderate");
|
|
52
|
+
}
|
|
53
|
+
// Required parameter change is breaking
|
|
54
|
+
if (this.isRequiredParameterChange(change)) {
|
|
55
|
+
return this.setBreakingChange(impact, "medium", "low", "simple");
|
|
56
|
+
}
|
|
57
|
+
// Response status code removal is breaking
|
|
58
|
+
if (this.isResponseRemoval(change)) {
|
|
59
|
+
return this.setBreakingChange(impact, "medium", "low", "simple");
|
|
60
|
+
}
|
|
61
|
+
// Adding optional parameter is non-breaking
|
|
62
|
+
if (this.isOptionalParameterAddition(change)) {
|
|
63
|
+
return this.setNonBreakingChange(impact, "none", "low", "none");
|
|
64
|
+
}
|
|
65
|
+
return impact;
|
|
66
|
+
}
|
|
67
|
+
isRouteRemoval(change) {
|
|
68
|
+
return (change.description === "Item removed" &&
|
|
69
|
+
!!change.kind?.includes("route.removed"));
|
|
70
|
+
}
|
|
71
|
+
isRouteAddition(change) {
|
|
72
|
+
return (change.description === "Item added" &&
|
|
73
|
+
!!change.kind?.includes("route.added"));
|
|
74
|
+
}
|
|
75
|
+
isMethodChange(change) {
|
|
76
|
+
return (change.path.includes("/method") && change.description === "Value changed");
|
|
77
|
+
}
|
|
78
|
+
isPathChange(change) {
|
|
79
|
+
return (change.path.includes("/path") && change.description === "Value changed");
|
|
80
|
+
}
|
|
81
|
+
isRequiredParameterChange(change) {
|
|
82
|
+
return (change.path.includes("/required") &&
|
|
83
|
+
change.description === "Value changed");
|
|
84
|
+
}
|
|
85
|
+
isResponseRemoval(change) {
|
|
86
|
+
return (change.path.includes("/responses/") &&
|
|
87
|
+
change.description === "Item removed");
|
|
88
|
+
}
|
|
89
|
+
isOptionalParameterAddition(change) {
|
|
90
|
+
return (change.path.includes("/queryParams/") &&
|
|
91
|
+
change.description === "Item added");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.RestRouteImpactStrategy = RestRouteImpactStrategy;
|
|
95
|
+
/**
|
|
96
|
+
* GraphQL operation impact analysis strategy.
|
|
97
|
+
*/
|
|
98
|
+
class GraphQLOperationImpactStrategy extends BaseImpactStrategy {
|
|
99
|
+
analyze(change) {
|
|
100
|
+
const impact = this.createDefaultImpact();
|
|
101
|
+
// Operation removal is breaking
|
|
102
|
+
if (this.isOperationRemoval(change)) {
|
|
103
|
+
return this.setBreakingChange(impact, "high", "medium", "complex");
|
|
104
|
+
}
|
|
105
|
+
// Operation addition is non-breaking
|
|
106
|
+
if (this.isOperationAddition(change)) {
|
|
107
|
+
return this.setNonBreakingChange(impact, "none", "low", "none");
|
|
108
|
+
}
|
|
109
|
+
// Return type change is breaking
|
|
110
|
+
if (this.isReturnTypeChange(change)) {
|
|
111
|
+
return this.setBreakingChange(impact, "high", "medium", "moderate");
|
|
112
|
+
}
|
|
113
|
+
// Adding optional argument is non-breaking
|
|
114
|
+
if (this.isOptionalArgumentAddition(change)) {
|
|
115
|
+
return this.setNonBreakingChange(impact, "none", "low", "none");
|
|
116
|
+
}
|
|
117
|
+
// Making argument required is breaking
|
|
118
|
+
if (this.isRequiredArgumentChange(change)) {
|
|
119
|
+
return this.setBreakingChange(impact, "medium", "low", "simple");
|
|
120
|
+
}
|
|
121
|
+
return impact;
|
|
122
|
+
}
|
|
123
|
+
isOperationRemoval(change) {
|
|
124
|
+
return (change.description === "Item removed" &&
|
|
125
|
+
!!change.kind?.includes("operation.removed"));
|
|
126
|
+
}
|
|
127
|
+
isOperationAddition(change) {
|
|
128
|
+
return (change.description === "Item added" &&
|
|
129
|
+
!!change.kind?.includes("operation.added"));
|
|
130
|
+
}
|
|
131
|
+
isReturnTypeChange(change) {
|
|
132
|
+
return (change.path.includes("/returnType") &&
|
|
133
|
+
change.description === "Value changed");
|
|
134
|
+
}
|
|
135
|
+
isOptionalArgumentAddition(change) {
|
|
136
|
+
return (change.path.includes("/args/") && change.description === "Item added");
|
|
137
|
+
}
|
|
138
|
+
isRequiredArgumentChange(change) {
|
|
139
|
+
return (change.path.includes("/args/") &&
|
|
140
|
+
change.path.includes("/required") &&
|
|
141
|
+
change.description === "Value changed");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.GraphQLOperationImpactStrategy = GraphQLOperationImpactStrategy;
|
|
145
|
+
/**
|
|
146
|
+
* WebSocket impact analysis strategy.
|
|
147
|
+
*/
|
|
148
|
+
class WebSocketImpactStrategy extends BaseImpactStrategy {
|
|
149
|
+
analyze(change) {
|
|
150
|
+
const impact = this.createDefaultImpact();
|
|
151
|
+
// Channel removal is breaking
|
|
152
|
+
if (this.isChannelRemoval(change)) {
|
|
153
|
+
return this.setBreakingChange(impact, "high", "medium", "complex");
|
|
154
|
+
}
|
|
155
|
+
// Channel addition is non-breaking
|
|
156
|
+
if (this.isChannelAddition(change)) {
|
|
157
|
+
return this.setNonBreakingChange(impact, "none", "low", "none");
|
|
158
|
+
}
|
|
159
|
+
// Message removal is breaking
|
|
160
|
+
if (this.isMessageRemoval(change)) {
|
|
161
|
+
return this.setBreakingChange(impact, "medium", "low", "moderate");
|
|
162
|
+
}
|
|
163
|
+
// Message addition is non-breaking
|
|
164
|
+
if (this.isMessageAddition(change)) {
|
|
165
|
+
return this.setNonBreakingChange(impact, "none", "low", "none");
|
|
166
|
+
}
|
|
167
|
+
// Message schema change is breaking
|
|
168
|
+
if (this.isMessageSchemaChange(change)) {
|
|
169
|
+
return this.setBreakingChange(impact, "medium", "low", "moderate");
|
|
170
|
+
}
|
|
171
|
+
return impact;
|
|
172
|
+
}
|
|
173
|
+
isChannelRemoval(change) {
|
|
174
|
+
return (change.description === "Item removed" &&
|
|
175
|
+
!!change.kind?.includes("channel.removed"));
|
|
176
|
+
}
|
|
177
|
+
isChannelAddition(change) {
|
|
178
|
+
return (change.description === "Item added" &&
|
|
179
|
+
!!change.kind?.includes("channel.added"));
|
|
180
|
+
}
|
|
181
|
+
isMessageRemoval(change) {
|
|
182
|
+
return (change.description === "Item removed" &&
|
|
183
|
+
!!change.kind?.includes("message.removed"));
|
|
184
|
+
}
|
|
185
|
+
isMessageAddition(change) {
|
|
186
|
+
return (change.description === "Item added" &&
|
|
187
|
+
!!change.kind?.includes("message.added"));
|
|
188
|
+
}
|
|
189
|
+
isMessageSchemaChange(change) {
|
|
190
|
+
return (change.path.includes("/schemaRef") &&
|
|
191
|
+
change.description === "Value changed");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
exports.WebSocketImpactStrategy = WebSocketImpactStrategy;
|
|
195
|
+
/**
|
|
196
|
+
* Default impact analysis strategy for unknown changes.
|
|
197
|
+
*/
|
|
198
|
+
class DefaultImpactStrategy extends BaseImpactStrategy {
|
|
199
|
+
analyze(_change) {
|
|
200
|
+
return this.createDefaultImpact();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
exports.DefaultImpactStrategy = DefaultImpactStrategy;
|
|
204
|
+
/**
|
|
205
|
+
* Factory for creating impact analysis strategies.
|
|
206
|
+
*/
|
|
207
|
+
const ImpactAnalysisStrategyFactory = {
|
|
208
|
+
strategies: new Map([
|
|
209
|
+
["rest", new RestRouteImpactStrategy()],
|
|
210
|
+
["graphql", new GraphQLOperationImpactStrategy()],
|
|
211
|
+
["websocket", new WebSocketImpactStrategy()],
|
|
212
|
+
]),
|
|
213
|
+
getStrategy(protocol) {
|
|
214
|
+
if (!protocol) {
|
|
215
|
+
return new DefaultImpactStrategy();
|
|
216
|
+
}
|
|
217
|
+
const strategy = this.strategies.get(protocol);
|
|
218
|
+
return strategy || new DefaultImpactStrategy();
|
|
219
|
+
},
|
|
220
|
+
registerStrategy(protocol, strategy) {
|
|
221
|
+
this.strategies.set(protocol, strategy);
|
|
222
|
+
},
|
|
223
|
+
getAvailableStrategies() {
|
|
224
|
+
return Array.from(this.strategies.keys());
|
|
225
|
+
},
|
|
226
|
+
clearStrategies() {
|
|
227
|
+
this.strategies.clear();
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
exports.ImpactAnalysisStrategyFactory = ImpactAnalysisStrategyFactory;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImpactAnalysisStrategyFactory = exports.DefaultImpactStrategy = exports.WebSocketImpactStrategy = exports.GraphQLOperationImpactStrategy = exports.RestRouteImpactStrategy = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* REST route impact analysis strategy.
|
|
6
|
+
*/
|
|
7
|
+
class RestRouteImpactStrategy {
|
|
8
|
+
analyze(change) {
|
|
9
|
+
const impact = {
|
|
10
|
+
backwardCompatibility: "unknown",
|
|
11
|
+
clientImpact: "none",
|
|
12
|
+
serverImpact: "none",
|
|
13
|
+
migrationComplexity: "none",
|
|
14
|
+
};
|
|
15
|
+
// Route removal is always breaking
|
|
16
|
+
if (change.description === "Item removed" &&
|
|
17
|
+
change.kind?.includes("route.removed")) {
|
|
18
|
+
impact.backwardCompatibility = "incompatible";
|
|
19
|
+
impact.clientImpact = "high";
|
|
20
|
+
impact.serverImpact = "medium";
|
|
21
|
+
impact.migrationComplexity = "complex";
|
|
22
|
+
}
|
|
23
|
+
// Route addition is non-breaking
|
|
24
|
+
if (change.description === "Item added" &&
|
|
25
|
+
change.kind?.includes("route.added")) {
|
|
26
|
+
impact.backwardCompatibility = "compatible";
|
|
27
|
+
impact.clientImpact = "none";
|
|
28
|
+
impact.serverImpact = "low";
|
|
29
|
+
impact.migrationComplexity = "none";
|
|
30
|
+
}
|
|
31
|
+
// Method change is breaking
|
|
32
|
+
if (change.path.includes("/method") &&
|
|
33
|
+
change.description === "Value changed") {
|
|
34
|
+
impact.backwardCompatibility = "incompatible";
|
|
35
|
+
impact.clientImpact = "high";
|
|
36
|
+
impact.serverImpact = "medium";
|
|
37
|
+
impact.migrationComplexity = "moderate";
|
|
38
|
+
}
|
|
39
|
+
// Path change is breaking
|
|
40
|
+
if (change.path.includes("/path") &&
|
|
41
|
+
change.description === "Value changed") {
|
|
42
|
+
impact.backwardCompatibility = "incompatible";
|
|
43
|
+
impact.clientImpact = "high";
|
|
44
|
+
impact.serverImpact = "medium";
|
|
45
|
+
impact.migrationComplexity = "moderate";
|
|
46
|
+
}
|
|
47
|
+
// Required parameter change is breaking
|
|
48
|
+
if (change.path.includes("/required") &&
|
|
49
|
+
change.description === "Value changed") {
|
|
50
|
+
impact.backwardCompatibility = "incompatible";
|
|
51
|
+
impact.clientImpact = "medium";
|
|
52
|
+
impact.serverImpact = "low";
|
|
53
|
+
impact.migrationComplexity = "simple";
|
|
54
|
+
}
|
|
55
|
+
// Response status code removal is breaking
|
|
56
|
+
if (change.path.includes("/responses/") &&
|
|
57
|
+
change.description === "Item removed") {
|
|
58
|
+
impact.backwardCompatibility = "incompatible";
|
|
59
|
+
impact.clientImpact = "medium";
|
|
60
|
+
impact.serverImpact = "low";
|
|
61
|
+
impact.migrationComplexity = "simple";
|
|
62
|
+
}
|
|
63
|
+
// Adding optional parameter is non-breaking
|
|
64
|
+
if (change.path.includes("/queryParams/") &&
|
|
65
|
+
change.description === "Item added") {
|
|
66
|
+
impact.backwardCompatibility = "compatible";
|
|
67
|
+
impact.clientImpact = "none";
|
|
68
|
+
impact.serverImpact = "low";
|
|
69
|
+
impact.migrationComplexity = "none";
|
|
70
|
+
}
|
|
71
|
+
return impact;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.RestRouteImpactStrategy = RestRouteImpactStrategy;
|
|
75
|
+
/**
|
|
76
|
+
* GraphQL operation impact analysis strategy.
|
|
77
|
+
*/
|
|
78
|
+
class GraphQLOperationImpactStrategy {
|
|
79
|
+
analyze(change) {
|
|
80
|
+
const impact = {
|
|
81
|
+
backwardCompatibility: "unknown",
|
|
82
|
+
clientImpact: "none",
|
|
83
|
+
serverImpact: "none",
|
|
84
|
+
migrationComplexity: "none",
|
|
85
|
+
};
|
|
86
|
+
// Operation removal is breaking
|
|
87
|
+
if (change.description === "Item removed" &&
|
|
88
|
+
change.kind?.includes("operation.removed")) {
|
|
89
|
+
impact.backwardCompatibility = "incompatible";
|
|
90
|
+
impact.clientImpact = "high";
|
|
91
|
+
impact.serverImpact = "medium";
|
|
92
|
+
impact.migrationComplexity = "complex";
|
|
93
|
+
}
|
|
94
|
+
// Operation addition is non-breaking
|
|
95
|
+
if (change.description === "Item added" &&
|
|
96
|
+
change.kind?.includes("operation.added")) {
|
|
97
|
+
impact.backwardCompatibility = "compatible";
|
|
98
|
+
impact.clientImpact = "none";
|
|
99
|
+
impact.serverImpact = "low";
|
|
100
|
+
impact.migrationComplexity = "none";
|
|
101
|
+
}
|
|
102
|
+
// Return type change is breaking
|
|
103
|
+
if (change.path.includes("/returnType") &&
|
|
104
|
+
change.description === "Value changed") {
|
|
105
|
+
impact.backwardCompatibility = "incompatible";
|
|
106
|
+
impact.clientImpact = "high";
|
|
107
|
+
impact.serverImpact = "medium";
|
|
108
|
+
impact.migrationComplexity = "moderate";
|
|
109
|
+
}
|
|
110
|
+
// Adding optional argument is non-breaking
|
|
111
|
+
if (change.path.includes("/args/") && change.description === "Item added") {
|
|
112
|
+
impact.backwardCompatibility = "compatible";
|
|
113
|
+
impact.clientImpact = "none";
|
|
114
|
+
impact.serverImpact = "low";
|
|
115
|
+
impact.migrationComplexity = "none";
|
|
116
|
+
}
|
|
117
|
+
// Making argument required is breaking
|
|
118
|
+
if (change.path.includes("/args/") &&
|
|
119
|
+
change.path.includes("/required") &&
|
|
120
|
+
change.description === "Value changed") {
|
|
121
|
+
impact.backwardCompatibility = "incompatible";
|
|
122
|
+
impact.clientImpact = "medium";
|
|
123
|
+
impact.serverImpact = "low";
|
|
124
|
+
impact.migrationComplexity = "simple";
|
|
125
|
+
}
|
|
126
|
+
return impact;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.GraphQLOperationImpactStrategy = GraphQLOperationImpactStrategy;
|
|
130
|
+
/**
|
|
131
|
+
* WebSocket impact analysis strategy.
|
|
132
|
+
*/
|
|
133
|
+
class WebSocketImpactStrategy {
|
|
134
|
+
analyze(change) {
|
|
135
|
+
const impact = {
|
|
136
|
+
backwardCompatibility: "unknown",
|
|
137
|
+
clientImpact: "none",
|
|
138
|
+
serverImpact: "none",
|
|
139
|
+
migrationComplexity: "none",
|
|
140
|
+
};
|
|
141
|
+
// Channel removal is breaking
|
|
142
|
+
if (change.description === "Item removed" &&
|
|
143
|
+
change.kind?.includes("channel.removed")) {
|
|
144
|
+
impact.backwardCompatibility = "incompatible";
|
|
145
|
+
impact.clientImpact = "high";
|
|
146
|
+
impact.serverImpact = "medium";
|
|
147
|
+
impact.migrationComplexity = "complex";
|
|
148
|
+
}
|
|
149
|
+
// Channel addition is non-breaking
|
|
150
|
+
if (change.description === "Item added" &&
|
|
151
|
+
change.kind?.includes("channel.added")) {
|
|
152
|
+
impact.backwardCompatibility = "compatible";
|
|
153
|
+
impact.clientImpact = "none";
|
|
154
|
+
impact.serverImpact = "low";
|
|
155
|
+
impact.migrationComplexity = "none";
|
|
156
|
+
}
|
|
157
|
+
// Message removal is breaking
|
|
158
|
+
if (change.description === "Item removed" &&
|
|
159
|
+
change.kind?.includes("message.removed")) {
|
|
160
|
+
impact.backwardCompatibility = "incompatible";
|
|
161
|
+
impact.clientImpact = "medium";
|
|
162
|
+
impact.serverImpact = "low";
|
|
163
|
+
impact.migrationComplexity = "moderate";
|
|
164
|
+
}
|
|
165
|
+
// Message addition is non-breaking
|
|
166
|
+
if (change.description === "Item added" &&
|
|
167
|
+
change.kind?.includes("message.added")) {
|
|
168
|
+
impact.backwardCompatibility = "compatible";
|
|
169
|
+
impact.clientImpact = "none";
|
|
170
|
+
impact.serverImpact = "low";
|
|
171
|
+
impact.migrationComplexity = "none";
|
|
172
|
+
}
|
|
173
|
+
// Message schema change is breaking
|
|
174
|
+
if (change.path.includes("/schemaRef") &&
|
|
175
|
+
change.description === "Value changed") {
|
|
176
|
+
impact.backwardCompatibility = "incompatible";
|
|
177
|
+
impact.clientImpact = "medium";
|
|
178
|
+
impact.serverImpact = "low";
|
|
179
|
+
impact.migrationComplexity = "moderate";
|
|
180
|
+
}
|
|
181
|
+
return impact;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
exports.WebSocketImpactStrategy = WebSocketImpactStrategy;
|
|
185
|
+
/**
|
|
186
|
+
* Default impact analysis strategy for unknown changes.
|
|
187
|
+
*/
|
|
188
|
+
class DefaultImpactStrategy {
|
|
189
|
+
analyze(_change) {
|
|
190
|
+
return {
|
|
191
|
+
backwardCompatibility: "unknown",
|
|
192
|
+
clientImpact: "none",
|
|
193
|
+
serverImpact: "none",
|
|
194
|
+
migrationComplexity: "none",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
exports.DefaultImpactStrategy = DefaultImpactStrategy;
|
|
199
|
+
/**
|
|
200
|
+
* Factory for creating impact analysis strategies.
|
|
201
|
+
*/
|
|
202
|
+
const ImpactAnalysisStrategyFactory = {
|
|
203
|
+
strategies: new Map([
|
|
204
|
+
["rest", new RestRouteImpactStrategy()],
|
|
205
|
+
["graphql", new GraphQLOperationImpactStrategy()],
|
|
206
|
+
["websocket", new WebSocketImpactStrategy()],
|
|
207
|
+
]),
|
|
208
|
+
getStrategy(protocol) {
|
|
209
|
+
if (!protocol) {
|
|
210
|
+
return new DefaultImpactStrategy();
|
|
211
|
+
}
|
|
212
|
+
const strategy = this.strategies.get(protocol);
|
|
213
|
+
return strategy || new DefaultImpactStrategy();
|
|
214
|
+
},
|
|
215
|
+
registerStrategy(protocol, strategy) {
|
|
216
|
+
this.strategies.set(protocol, strategy);
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
exports.ImpactAnalysisStrategyFactory = ImpactAnalysisStrategyFactory;
|