@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.
Files changed (306) hide show
  1. package/dist/cjs/diff/annotators.js +36 -9
  2. package/dist/cjs/src/cache/cache-factory.js +72 -0
  3. package/dist/cjs/src/cache/cache-manager.js +128 -0
  4. package/dist/cjs/src/cache/constants.js +25 -0
  5. package/dist/cjs/src/cache/hash-utils.js +19 -0
  6. package/dist/cjs/src/cache/hashing.js +230 -0
  7. package/dist/cjs/src/cache/index.js +24 -0
  8. package/dist/cjs/src/cache/lru-cache.js +144 -0
  9. package/dist/cjs/src/cache/types.js +5 -0
  10. package/dist/cjs/src/diff/annotators.js +160 -0
  11. package/dist/cjs/src/diff/change-reports.js +369 -0
  12. package/dist/cjs/src/diff/core.js +158 -0
  13. package/dist/cjs/src/diff/enhanced-diff.js +65 -0
  14. package/dist/cjs/src/diff/impact-strategies-refactored.js +230 -0
  15. package/dist/cjs/src/diff/impact-strategies.js +219 -0
  16. package/dist/cjs/src/diff/index.js +27 -0
  17. package/dist/cjs/src/diff/metrics-calculator.js +69 -0
  18. package/dist/cjs/src/diff/risk-calculator.js +58 -0
  19. package/dist/cjs/src/diff/suggestion-generator.js +78 -0
  20. package/dist/cjs/src/diff/types.js +11 -0
  21. package/dist/cjs/src/errors/base-error.js +33 -0
  22. package/dist/cjs/src/errors/config-error.js +11 -0
  23. package/dist/cjs/src/errors/error-factory.js +48 -0
  24. package/dist/cjs/src/errors/index.js +19 -0
  25. package/dist/cjs/src/errors/loader-error.js +11 -0
  26. package/dist/cjs/src/errors/reference-error.js +11 -0
  27. package/dist/cjs/src/errors/schema-error.js +11 -0
  28. package/dist/cjs/src/errors/security-error.js +11 -0
  29. package/dist/cjs/src/errors/semantic-error.js +11 -0
  30. package/dist/cjs/src/generated-schemas.js +2100 -0
  31. package/dist/cjs/src/index.js +59 -0
  32. package/dist/cjs/src/loader/index.js +13 -0
  33. package/dist/cjs/src/loader/security-validator.js +53 -0
  34. package/dist/cjs/src/loader/types.js +11 -0
  35. package/dist/cjs/src/loader/unispec-loader.js +84 -0
  36. package/dist/cjs/src/loader/yaml-loader.js +76 -0
  37. package/dist/cjs/src/normalizer/core.js +37 -0
  38. package/dist/cjs/src/normalizer/graphql-normalizer.js +67 -0
  39. package/dist/cjs/src/normalizer/index.js +7 -0
  40. package/dist/cjs/src/normalizer/rest-normalizer.js +51 -0
  41. package/dist/cjs/src/normalizer/types.js +2 -0
  42. package/dist/cjs/src/normalizer/utils.js +49 -0
  43. package/dist/cjs/src/normalizer/websocket-normalizer.js +81 -0
  44. package/dist/cjs/src/optimizer/core.js +140 -0
  45. package/dist/cjs/src/optimizer/index.js +17 -0
  46. package/dist/cjs/src/optimizer/optimization-functions.js +185 -0
  47. package/dist/cjs/src/optimizer/types.js +2 -0
  48. package/dist/cjs/src/optimizer/utils.js +32 -0
  49. package/dist/cjs/src/schemas/dedupe.js +113 -0
  50. package/dist/cjs/src/schemas/index.js +14 -0
  51. package/dist/cjs/src/schemas/resolver.js +42 -0
  52. package/dist/cjs/src/schemas/utils.js +53 -0
  53. package/dist/cjs/src/types/index.js +2 -0
  54. package/dist/cjs/src/validator/ajv-validator.js +82 -0
  55. package/dist/cjs/src/validator/config-validator-main.js +34 -0
  56. package/dist/cjs/src/validator/config-validator.js +17 -0
  57. package/dist/cjs/src/validator/index.js +23 -0
  58. package/dist/cjs/src/validator/object-traversal.js +112 -0
  59. package/dist/cjs/src/validator/reference-validator.js +233 -0
  60. package/dist/cjs/src/validator/schema-references.js +116 -0
  61. package/dist/cjs/src/validator/semantic-validator.js +328 -0
  62. package/dist/cjs/src/validator/tests-validator.js +16 -0
  63. package/dist/cjs/src/validator/types.js +2 -0
  64. package/dist/cjs/src/validator/unispec-validator.js +80 -0
  65. package/dist/cjs/src/validator/validator-factory.js +77 -0
  66. package/dist/cjs/src/versions.js +147 -0
  67. package/dist/cjs/tests/cache/cache.test.js +274 -0
  68. package/dist/cjs/tests/cache/utils.js +32 -0
  69. package/dist/cjs/tests/concurrency-normalizer-optimizer.test.js +1 -0
  70. package/dist/cjs/tests/diff/diff-annotators.test.js +280 -0
  71. package/dist/cjs/tests/diff/diff-comprehensive.test.js +262 -0
  72. package/dist/cjs/tests/diff/diff-extended.test.js +235 -0
  73. package/dist/cjs/tests/diff/diff.test.js +189 -0
  74. package/dist/cjs/tests/diff/utils.js +8 -0
  75. package/dist/cjs/tests/errors/errors-integration.test.js +173 -0
  76. package/dist/cjs/tests/errors/errors.test.js +280 -0
  77. package/dist/cjs/tests/errors/utils.js +7 -0
  78. package/dist/cjs/tests/loader/integration.test.js +216 -0
  79. package/dist/cjs/tests/loader/loader.test.js +341 -0
  80. package/dist/cjs/tests/normalizer/normalizer-comprehensive.test.js +648 -0
  81. package/dist/cjs/tests/normalizer/normalizer-invalid.test.js +258 -0
  82. package/dist/cjs/tests/normalizer/normalizer-valid.test.js +238 -0
  83. package/dist/cjs/tests/normalizer/utils.js +47 -0
  84. package/dist/cjs/tests/optimizer/compress-references.test.js +304 -0
  85. package/dist/cjs/tests/optimizer/deduplication.test.js +132 -0
  86. package/dist/cjs/tests/optimizer/integration.test.js +131 -0
  87. package/dist/cjs/tests/optimizer/optimization-report.test.js +222 -0
  88. package/dist/cjs/tests/optimizer/optimize-document.test.js +187 -0
  89. package/dist/cjs/tests/optimizer/orphaned-schemas.test.js +194 -0
  90. package/dist/cjs/tests/optimizer/sort-schemas.test.js +131 -0
  91. package/dist/cjs/tests/optimizer/utils.js +209 -0
  92. package/dist/cjs/tests/schemas/schemas-edge-cases.test.js +223 -0
  93. package/dist/cjs/tests/schemas/schemas.test.js +400 -0
  94. package/dist/cjs/tests/schemas/utils.js +7 -0
  95. package/dist/cjs/tests/utils.js +131 -0
  96. package/dist/cjs/tests/validator/config-validator.test.js +78 -0
  97. package/dist/cjs/tests/validator/debug-config.js +1 -0
  98. package/dist/cjs/tests/validator/debug-missing-service.js +1 -0
  99. package/dist/cjs/tests/validator/debug-other-configs.js +1 -0
  100. package/dist/cjs/tests/validator/debug-references.js +1 -0
  101. package/dist/cjs/tests/validator/unispec-validator.test.js +103 -0
  102. package/dist/cjs/tests/validator/utils.js +25 -0
  103. package/dist/diff/annotators.js +36 -9
  104. package/dist/src/cache/cache-factory.d.ts +31 -0
  105. package/dist/src/cache/cache-factory.js +65 -0
  106. package/dist/src/cache/cache-manager.d.ts +62 -0
  107. package/dist/src/cache/cache-manager.js +124 -0
  108. package/dist/src/cache/constants.d.ts +21 -0
  109. package/dist/src/cache/constants.js +22 -0
  110. package/dist/src/cache/hash-utils.d.ts +11 -0
  111. package/dist/src/cache/hash-utils.js +15 -0
  112. package/dist/src/cache/hashing.d.ts +28 -0
  113. package/dist/src/cache/hashing.js +193 -0
  114. package/dist/src/cache/index.d.ts +6 -0
  115. package/dist/src/cache/index.js +10 -0
  116. package/dist/src/cache/lru-cache.d.ts +44 -0
  117. package/dist/src/cache/lru-cache.js +140 -0
  118. package/dist/src/cache/types.d.ts +24 -0
  119. package/dist/src/cache/types.js +4 -0
  120. package/dist/src/diff/annotators.d.ts +4 -0
  121. package/dist/src/diff/annotators.js +155 -0
  122. package/dist/src/diff/change-reports.d.ts +37 -0
  123. package/dist/src/diff/change-reports.js +366 -0
  124. package/dist/src/diff/core.d.ts +26 -0
  125. package/dist/src/diff/core.js +155 -0
  126. package/dist/src/diff/enhanced-diff.d.ts +51 -0
  127. package/dist/src/diff/enhanced-diff.js +62 -0
  128. package/dist/src/diff/impact-strategies-refactored.d.ts +69 -0
  129. package/dist/src/diff/impact-strategies-refactored.js +223 -0
  130. package/dist/src/diff/impact-strategies.d.ts +41 -0
  131. package/dist/src/diff/impact-strategies.js +212 -0
  132. package/dist/src/diff/index.d.ts +8 -0
  133. package/dist/src/diff/index.js +11 -0
  134. package/dist/src/diff/metrics-calculator.d.ts +23 -0
  135. package/dist/src/diff/metrics-calculator.js +65 -0
  136. package/dist/src/diff/risk-calculator.d.ts +23 -0
  137. package/dist/src/diff/risk-calculator.js +55 -0
  138. package/dist/src/diff/suggestion-generator.d.ts +18 -0
  139. package/dist/src/diff/suggestion-generator.js +74 -0
  140. package/dist/src/diff/types.d.ts +24 -0
  141. package/dist/src/diff/types.js +8 -0
  142. package/dist/src/errors/base-error.d.ts +20 -0
  143. package/dist/src/errors/base-error.js +29 -0
  144. package/dist/src/errors/config-error.d.ts +4 -0
  145. package/dist/src/errors/config-error.js +7 -0
  146. package/dist/src/errors/error-factory.d.ts +22 -0
  147. package/dist/src/errors/error-factory.js +45 -0
  148. package/dist/src/errors/index.d.ts +8 -0
  149. package/dist/src/errors/index.js +8 -0
  150. package/dist/src/errors/loader-error.d.ts +4 -0
  151. package/dist/src/errors/loader-error.js +7 -0
  152. package/dist/src/errors/reference-error.d.ts +4 -0
  153. package/dist/src/errors/reference-error.js +7 -0
  154. package/dist/src/errors/schema-error.d.ts +4 -0
  155. package/dist/src/errors/schema-error.js +7 -0
  156. package/dist/src/errors/security-error.d.ts +4 -0
  157. package/dist/src/errors/security-error.js +7 -0
  158. package/dist/src/errors/semantic-error.d.ts +4 -0
  159. package/dist/src/errors/semantic-error.js +7 -0
  160. package/dist/src/generated-schemas.d.ts +2073 -0
  161. package/dist/src/generated-schemas.js +2097 -0
  162. package/dist/src/index.d.ts +13 -0
  163. package/dist/src/index.js +43 -0
  164. package/dist/src/loader/index.d.ts +5 -0
  165. package/dist/src/loader/index.js +5 -0
  166. package/dist/src/loader/security-validator.d.ts +5 -0
  167. package/dist/src/loader/security-validator.js +50 -0
  168. package/dist/src/loader/types.d.ts +30 -0
  169. package/dist/src/loader/types.js +8 -0
  170. package/dist/src/loader/unispec-loader.d.ts +10 -0
  171. package/dist/src/loader/unispec-loader.js +81 -0
  172. package/dist/src/loader/yaml-loader.d.ts +10 -0
  173. package/dist/src/loader/yaml-loader.js +39 -0
  174. package/dist/src/normalizer/core.d.ts +24 -0
  175. package/dist/src/normalizer/core.js +34 -0
  176. package/dist/src/normalizer/graphql-normalizer.d.ts +8 -0
  177. package/dist/src/normalizer/graphql-normalizer.js +64 -0
  178. package/dist/src/normalizer/index.d.ts +2 -0
  179. package/dist/src/normalizer/index.js +3 -0
  180. package/dist/src/normalizer/rest-normalizer.d.ts +8 -0
  181. package/dist/src/normalizer/rest-normalizer.js +48 -0
  182. package/dist/src/normalizer/types.d.ts +7 -0
  183. package/dist/src/normalizer/types.js +1 -0
  184. package/dist/src/normalizer/utils.d.ts +17 -0
  185. package/dist/src/normalizer/utils.js +45 -0
  186. package/dist/src/normalizer/websocket-normalizer.d.ts +8 -0
  187. package/dist/src/normalizer/websocket-normalizer.js +78 -0
  188. package/dist/src/optimizer/core.d.ts +17 -0
  189. package/dist/src/optimizer/core.js +136 -0
  190. package/dist/src/optimizer/index.d.ts +4 -0
  191. package/dist/src/optimizer/index.js +7 -0
  192. package/dist/src/optimizer/optimization-functions.d.ts +32 -0
  193. package/dist/src/optimizer/optimization-functions.js +179 -0
  194. package/dist/src/optimizer/types.d.ts +28 -0
  195. package/dist/src/optimizer/types.js +1 -0
  196. package/dist/src/optimizer/utils.d.ts +7 -0
  197. package/dist/src/optimizer/utils.js +29 -0
  198. package/dist/src/schemas/dedupe.d.ts +9 -0
  199. package/dist/src/schemas/dedupe.js +110 -0
  200. package/dist/src/schemas/index.d.ts +3 -0
  201. package/dist/src/schemas/index.js +6 -0
  202. package/dist/src/schemas/resolver.d.ts +19 -0
  203. package/dist/src/schemas/resolver.js +38 -0
  204. package/dist/src/schemas/utils.d.ts +20 -0
  205. package/dist/src/schemas/utils.js +49 -0
  206. package/dist/src/types/index.d.ts +434 -0
  207. package/dist/src/types/index.js +1 -0
  208. package/dist/src/validator/ajv-validator.d.ts +15 -0
  209. package/dist/src/validator/ajv-validator.js +75 -0
  210. package/dist/src/validator/config-validator-main.d.ts +10 -0
  211. package/dist/src/validator/config-validator-main.js +31 -0
  212. package/dist/src/validator/config-validator.d.ts +5 -0
  213. package/dist/src/validator/config-validator.js +14 -0
  214. package/dist/src/validator/index.d.ts +10 -0
  215. package/dist/src/validator/index.js +11 -0
  216. package/dist/src/validator/object-traversal.d.ts +52 -0
  217. package/dist/src/validator/object-traversal.js +104 -0
  218. package/dist/src/validator/reference-validator.d.ts +31 -0
  219. package/dist/src/validator/reference-validator.js +230 -0
  220. package/dist/src/validator/schema-references.d.ts +23 -0
  221. package/dist/src/validator/schema-references.js +111 -0
  222. package/dist/src/validator/semantic-validator.d.ts +26 -0
  223. package/dist/src/validator/semantic-validator.js +325 -0
  224. package/dist/src/validator/tests-validator.d.ts +9 -0
  225. package/dist/src/validator/tests-validator.js +13 -0
  226. package/dist/src/validator/types.d.ts +29 -0
  227. package/dist/src/validator/types.js +1 -0
  228. package/dist/src/validator/unispec-validator.d.ts +15 -0
  229. package/dist/src/validator/unispec-validator.js +77 -0
  230. package/dist/src/validator/validator-factory.d.ts +10 -0
  231. package/dist/src/validator/validator-factory.js +73 -0
  232. package/dist/src/versions.d.ts +10 -0
  233. package/dist/src/versions.js +143 -0
  234. package/dist/tests/cache/cache.test.d.ts +1 -0
  235. package/dist/tests/cache/cache.test.js +269 -0
  236. package/dist/tests/cache/utils.d.ts +4 -0
  237. package/dist/tests/cache/utils.js +24 -0
  238. package/dist/tests/concurrency-normalizer-optimizer.test.d.ts +0 -0
  239. package/dist/tests/concurrency-normalizer-optimizer.test.js +1 -0
  240. package/dist/tests/diff/diff-annotators.test.d.ts +1 -0
  241. package/dist/tests/diff/diff-annotators.test.js +275 -0
  242. package/dist/tests/diff/diff-comprehensive.test.d.ts +1 -0
  243. package/dist/tests/diff/diff-comprehensive.test.js +257 -0
  244. package/dist/tests/diff/diff-extended.test.d.ts +1 -0
  245. package/dist/tests/diff/diff-extended.test.js +230 -0
  246. package/dist/tests/diff/diff.test.d.ts +1 -0
  247. package/dist/tests/diff/diff.test.js +184 -0
  248. package/dist/tests/diff/utils.d.ts +2 -0
  249. package/dist/tests/diff/utils.js +3 -0
  250. package/dist/tests/errors/errors-integration.test.d.ts +1 -0
  251. package/dist/tests/errors/errors-integration.test.js +168 -0
  252. package/dist/tests/errors/errors.test.d.ts +1 -0
  253. package/dist/tests/errors/errors.test.js +275 -0
  254. package/dist/tests/errors/utils.d.ts +2 -0
  255. package/dist/tests/errors/utils.js +3 -0
  256. package/dist/tests/loader/integration.test.d.ts +1 -0
  257. package/dist/tests/loader/integration.test.js +211 -0
  258. package/dist/tests/loader/loader.test.d.ts +1 -0
  259. package/dist/tests/loader/loader.test.js +336 -0
  260. package/dist/tests/normalizer/normalizer-comprehensive.test.d.ts +1 -0
  261. package/dist/tests/normalizer/normalizer-comprehensive.test.js +643 -0
  262. package/dist/tests/normalizer/normalizer-invalid.test.d.ts +1 -0
  263. package/dist/tests/normalizer/normalizer-invalid.test.js +253 -0
  264. package/dist/tests/normalizer/normalizer-valid.test.d.ts +1 -0
  265. package/dist/tests/normalizer/normalizer-valid.test.js +233 -0
  266. package/dist/tests/normalizer/utils.d.ts +18 -0
  267. package/dist/tests/normalizer/utils.js +36 -0
  268. package/dist/tests/optimizer/compress-references.test.d.ts +1 -0
  269. package/dist/tests/optimizer/compress-references.test.js +299 -0
  270. package/dist/tests/optimizer/deduplication.test.d.ts +1 -0
  271. package/dist/tests/optimizer/deduplication.test.js +127 -0
  272. package/dist/tests/optimizer/integration.test.d.ts +1 -0
  273. package/dist/tests/optimizer/integration.test.js +126 -0
  274. package/dist/tests/optimizer/optimization-report.test.d.ts +1 -0
  275. package/dist/tests/optimizer/optimization-report.test.js +217 -0
  276. package/dist/tests/optimizer/optimize-document.test.d.ts +1 -0
  277. package/dist/tests/optimizer/optimize-document.test.js +182 -0
  278. package/dist/tests/optimizer/orphaned-schemas.test.d.ts +1 -0
  279. package/dist/tests/optimizer/orphaned-schemas.test.js +189 -0
  280. package/dist/tests/optimizer/sort-schemas.test.d.ts +1 -0
  281. package/dist/tests/optimizer/sort-schemas.test.js +126 -0
  282. package/dist/tests/optimizer/utils.d.ts +8 -0
  283. package/dist/tests/optimizer/utils.js +199 -0
  284. package/dist/tests/schemas/schemas-edge-cases.test.d.ts +1 -0
  285. package/dist/tests/schemas/schemas-edge-cases.test.js +218 -0
  286. package/dist/tests/schemas/schemas.test.d.ts +1 -0
  287. package/dist/tests/schemas/schemas.test.js +395 -0
  288. package/dist/tests/schemas/utils.d.ts +2 -0
  289. package/dist/tests/schemas/utils.js +3 -0
  290. package/dist/tests/utils.d.ts +10 -0
  291. package/dist/tests/utils.js +118 -0
  292. package/dist/tests/validator/config-validator.test.d.ts +1 -0
  293. package/dist/tests/validator/config-validator.test.js +73 -0
  294. package/dist/tests/validator/debug-config.d.ts +0 -0
  295. package/dist/tests/validator/debug-config.js +1 -0
  296. package/dist/tests/validator/debug-missing-service.d.ts +0 -0
  297. package/dist/tests/validator/debug-missing-service.js +1 -0
  298. package/dist/tests/validator/debug-other-configs.d.ts +0 -0
  299. package/dist/tests/validator/debug-other-configs.js +1 -0
  300. package/dist/tests/validator/debug-references.d.ts +0 -0
  301. package/dist/tests/validator/debug-references.js +1 -0
  302. package/dist/tests/validator/unispec-validator.test.d.ts +1 -0
  303. package/dist/tests/validator/unispec-validator.test.js +98 -0
  304. package/dist/tests/validator/utils.d.ts +6 -0
  305. package/dist/tests/validator/utils.js +20 -0
  306. package/package.json +4 -3
@@ -0,0 +1,643 @@
1
+ import assert from "node:assert";
2
+ import path from "node:path";
3
+ import { describe, it } from "node:test";
4
+ import { normalizeUniSpec } from "../../src/normalizer/index.js";
5
+ import { createTestDocument, examplesDir, loadExample } from "./utils.js";
6
+ describe("normalizer module - comprehensive protocol tests", () => {
7
+ describe("REST protocol normalization", () => {
8
+ it("should sort REST routes by name when available", () => {
9
+ const doc = createTestDocument({
10
+ service: {
11
+ name: "test-service",
12
+ protocols: {
13
+ rest: {
14
+ routes: [
15
+ {
16
+ name: "zebraRoute",
17
+ method: "GET",
18
+ path: "/zebra",
19
+ responses: { "200": { description: "OK" } },
20
+ },
21
+ {
22
+ name: "alphaRoute",
23
+ method: "POST",
24
+ path: "/alpha",
25
+ responses: { "201": { description: "Created" } },
26
+ },
27
+ {
28
+ name: "betaRoute",
29
+ method: "PUT",
30
+ path: "/beta",
31
+ responses: { "200": { description: "OK" } },
32
+ },
33
+ ],
34
+ },
35
+ },
36
+ },
37
+ });
38
+ const normalized = normalizeUniSpec(doc);
39
+ // Routes should be sorted by name
40
+ const routes = normalized.service?.protocols?.rest?.routes || [];
41
+ const routeNames = routes.map((r) => r.name).filter(Boolean);
42
+ assert.deepStrictEqual(routeNames, [
43
+ "alphaRoute",
44
+ "betaRoute",
45
+ "zebraRoute",
46
+ ]);
47
+ });
48
+ it("should sort REST routes by path+method when name is missing", () => {
49
+ const doc = createTestDocument({
50
+ service: {
51
+ name: "test-service",
52
+ protocols: {
53
+ rest: {
54
+ routes: [
55
+ {
56
+ name: "",
57
+ method: "GET",
58
+ path: "/users",
59
+ responses: { "200": { description: "OK" } },
60
+ },
61
+ {
62
+ name: "",
63
+ method: "POST",
64
+ path: "/users",
65
+ responses: { "201": { description: "Created" } },
66
+ },
67
+ {
68
+ name: "",
69
+ method: "DELETE",
70
+ path: "/users/{id}",
71
+ responses: { "204": { description: "No Content" } },
72
+ },
73
+ ],
74
+ },
75
+ },
76
+ },
77
+ });
78
+ const normalized = normalizeUniSpec(doc);
79
+ // Routes should be sorted by path+method
80
+ const routes = normalized.service?.protocols?.rest?.routes || [];
81
+ const routeKeys = routes.map((r) => r.name ? `${r.path} ${r.method}` : `${r.path} ${r.method}`);
82
+ assert.deepStrictEqual(routeKeys, [
83
+ "/users GET",
84
+ "/users POST",
85
+ "/users/{id} DELETE",
86
+ ]);
87
+ });
88
+ it("should handle mixed named and unnamed routes", () => {
89
+ const doc = createTestDocument({
90
+ service: {
91
+ name: "test-service",
92
+ protocols: {
93
+ rest: {
94
+ routes: [
95
+ {
96
+ name: "getUser",
97
+ method: "GET",
98
+ path: "/users/{id}",
99
+ responses: { "200": { description: "OK" } },
100
+ },
101
+ {
102
+ name: "",
103
+ method: "POST",
104
+ path: "/users",
105
+ responses: { "201": { description: "Created" } },
106
+ },
107
+ {
108
+ name: "deleteUser",
109
+ method: "DELETE",
110
+ path: "/users/{id}",
111
+ responses: { "204": { description: "No Content" } },
112
+ },
113
+ ],
114
+ },
115
+ },
116
+ },
117
+ });
118
+ const normalized = normalizeUniSpec(doc);
119
+ // Routes should be sorted by name or path+method
120
+ const routes = normalized.service?.protocols?.rest?.routes || [];
121
+ const routeKeys = routes.map((r) => r.name || `${r.path} ${r.method}`);
122
+ assert.deepStrictEqual(routeKeys, [
123
+ "/users POST",
124
+ "deleteUser",
125
+ "getUser",
126
+ ]);
127
+ });
128
+ });
129
+ describe("GraphQL protocol normalization", () => {
130
+ it("should sort GraphQL queries by name", () => {
131
+ const doc = createTestDocument({
132
+ service: {
133
+ name: "test-service",
134
+ protocols: {
135
+ graphql: {
136
+ queries: [
137
+ {
138
+ name: "zebraQuery",
139
+ description: "Get zebra",
140
+ args: [],
141
+ returnType: "Zebra",
142
+ },
143
+ {
144
+ name: "alphaQuery",
145
+ description: "Get alpha",
146
+ args: [],
147
+ returnType: "Alpha",
148
+ },
149
+ {
150
+ name: "betaQuery",
151
+ description: "Get beta",
152
+ args: [],
153
+ returnType: "Beta",
154
+ },
155
+ ],
156
+ mutations: [],
157
+ subscriptions: [],
158
+ schema: "type Query { alphaQuery: Alpha betaQuery: Beta zebraQuery: Zebra }",
159
+ },
160
+ },
161
+ },
162
+ });
163
+ const normalized = normalizeUniSpec(doc);
164
+ // Queries should be sorted by name
165
+ const queries = normalized.service?.protocols?.graphql?.queries || [];
166
+ const queryNames = queries.map((q) => q.name).filter(Boolean);
167
+ assert.deepStrictEqual(queryNames, [
168
+ "alphaQuery",
169
+ "betaQuery",
170
+ "zebraQuery",
171
+ ]);
172
+ });
173
+ it("should sort GraphQL mutations by name", () => {
174
+ const doc = createTestDocument({
175
+ service: {
176
+ name: "test-service",
177
+ protocols: {
178
+ graphql: {
179
+ queries: [],
180
+ mutations: [
181
+ {
182
+ name: "zebraMutation",
183
+ description: "Create zebra",
184
+ args: [],
185
+ returnType: "Zebra",
186
+ },
187
+ {
188
+ name: "alphaMutation",
189
+ description: "Create alpha",
190
+ args: [],
191
+ returnType: "Alpha",
192
+ },
193
+ ],
194
+ subscriptions: [],
195
+ schema: "type Mutation { alphaMutation: Alpha zebraMutation: Zebra }",
196
+ },
197
+ },
198
+ },
199
+ });
200
+ const normalized = normalizeUniSpec(doc);
201
+ // Mutations should be sorted by name
202
+ const mutations = normalized.service?.protocols?.graphql?.mutations || [];
203
+ const mutationNames = mutations.map((m) => m.name).filter(Boolean);
204
+ assert.deepStrictEqual(mutationNames, ["alphaMutation", "zebraMutation"]);
205
+ });
206
+ it("should sort GraphQL subscriptions by name", () => {
207
+ const doc = createTestDocument({
208
+ service: {
209
+ name: "test-service",
210
+ protocols: {
211
+ graphql: {
212
+ queries: [],
213
+ mutations: [],
214
+ subscriptions: [
215
+ {
216
+ name: "zebraSubscription",
217
+ description: "Subscribe to zebra",
218
+ args: [],
219
+ returnType: "Zebra",
220
+ },
221
+ {
222
+ name: "alphaSubscription",
223
+ description: "Subscribe to alpha",
224
+ args: [],
225
+ returnType: "Alpha",
226
+ },
227
+ ],
228
+ schema: "type Subscription { alphaSubscription: Alpha zebraSubscription: Zebra }",
229
+ },
230
+ },
231
+ },
232
+ });
233
+ const normalized = normalizeUniSpec(doc);
234
+ // Subscriptions should be sorted by name
235
+ const subscriptions = normalized.service?.protocols?.graphql?.subscriptions || [];
236
+ const subscriptionNames = subscriptions
237
+ .map((s) => s.name)
238
+ .filter(Boolean);
239
+ assert.deepStrictEqual(subscriptionNames, [
240
+ "alphaSubscription",
241
+ "zebraSubscription",
242
+ ]);
243
+ });
244
+ it("should handle operations with missing names", () => {
245
+ const doc = createTestDocument({
246
+ service: {
247
+ name: "test-service",
248
+ protocols: {
249
+ graphql: {
250
+ queries: [
251
+ {
252
+ name: "",
253
+ description: "Query without name",
254
+ args: [],
255
+ returnType: "String",
256
+ },
257
+ {
258
+ name: "namedQuery",
259
+ description: "Named query",
260
+ args: [],
261
+ returnType: "String",
262
+ },
263
+ ],
264
+ mutations: [],
265
+ subscriptions: [],
266
+ schema: "type Query { namedQuery: String }",
267
+ },
268
+ },
269
+ },
270
+ });
271
+ const normalized = normalizeUniSpec(doc);
272
+ // Should handle missing names gracefully
273
+ const queries = normalized.service?.protocols?.graphql?.queries || [];
274
+ assert.strictEqual(queries.length, 2);
275
+ // Named query should come after unnamed (empty string sorts first)
276
+ const queryNames = queries.map((q) => q.name || "");
277
+ assert.deepStrictEqual(queryNames, ["", "namedQuery"]);
278
+ });
279
+ });
280
+ describe("WebSocket protocol normalization", () => {
281
+ it("should sort WebSocket channels by name", () => {
282
+ const doc = createTestDocument({
283
+ service: {
284
+ name: "test-service",
285
+ protocols: {
286
+ websocket: {
287
+ channels: [
288
+ {
289
+ name: "zebraChannel",
290
+ description: "Zebra channel",
291
+ messages: [],
292
+ },
293
+ {
294
+ name: "alphaChannel",
295
+ description: "Alpha channel",
296
+ messages: [],
297
+ },
298
+ {
299
+ name: "betaChannel",
300
+ description: "Beta channel",
301
+ messages: [],
302
+ },
303
+ ],
304
+ },
305
+ },
306
+ },
307
+ });
308
+ const normalized = normalizeUniSpec(doc);
309
+ // Channels should be sorted by name
310
+ const channels = normalized.service?.protocols?.websocket?.channels || [];
311
+ const channelNames = channels.map((c) => c.name).filter(Boolean);
312
+ assert.deepStrictEqual(channelNames, [
313
+ "alphaChannel",
314
+ "betaChannel",
315
+ "zebraChannel",
316
+ ]);
317
+ });
318
+ it("should sort messages within each channel by name", () => {
319
+ const doc = createTestDocument({
320
+ service: {
321
+ name: "test-service",
322
+ protocols: {
323
+ websocket: {
324
+ channels: [
325
+ {
326
+ name: "notifications",
327
+ description: "Notification channel",
328
+ messages: [
329
+ {
330
+ name: "zebraMessage",
331
+ description: "Zebra notification",
332
+ direction: "subscribe",
333
+ },
334
+ {
335
+ name: "alphaMessage",
336
+ description: "Alpha notification",
337
+ direction: "publish",
338
+ },
339
+ {
340
+ name: "betaMessage",
341
+ description: "Beta notification",
342
+ direction: "both",
343
+ },
344
+ ],
345
+ },
346
+ ],
347
+ },
348
+ },
349
+ },
350
+ });
351
+ const normalized = normalizeUniSpec(doc);
352
+ // Messages should be sorted by name within the channel
353
+ const channel = normalized.service?.protocols?.websocket?.channels?.find((c) => c.name === "notifications");
354
+ assert.ok(channel);
355
+ const messages = channel?.messages || [];
356
+ const messageNames = messages.map((m) => m.name).filter(Boolean);
357
+ assert.deepStrictEqual(messageNames, [
358
+ "alphaMessage",
359
+ "betaMessage",
360
+ "zebraMessage",
361
+ ]);
362
+ });
363
+ it("should handle channels with missing names", () => {
364
+ const doc = createTestDocument({
365
+ service: {
366
+ name: "test-service",
367
+ protocols: {
368
+ websocket: {
369
+ channels: [
370
+ {
371
+ name: "",
372
+ description: "Channel without name",
373
+ messages: [],
374
+ },
375
+ {
376
+ name: "namedChannel",
377
+ description: "Named channel",
378
+ messages: [],
379
+ },
380
+ ],
381
+ },
382
+ },
383
+ },
384
+ });
385
+ const normalized = normalizeUniSpec(doc);
386
+ // Should handle missing names gracefully
387
+ const channels = normalized.service?.protocols?.websocket?.channels || [];
388
+ assert.strictEqual(channels.length, 2);
389
+ // Named channel should come after unnamed
390
+ const channelNames = channels.map((c) => c.name || "");
391
+ assert.deepStrictEqual(channelNames, ["", "namedChannel"]);
392
+ });
393
+ it("should handle messages with missing names", () => {
394
+ const doc = createTestDocument({
395
+ service: {
396
+ name: "test-service",
397
+ protocols: {
398
+ websocket: {
399
+ channels: [
400
+ {
401
+ name: "testChannel",
402
+ description: "Test channel",
403
+ messages: [
404
+ {
405
+ name: "",
406
+ description: "Message without name",
407
+ direction: "subscribe",
408
+ },
409
+ {
410
+ name: "namedMessage",
411
+ description: "Named message",
412
+ direction: "publish",
413
+ },
414
+ ],
415
+ },
416
+ ],
417
+ },
418
+ },
419
+ },
420
+ });
421
+ const normalized = normalizeUniSpec(doc);
422
+ // Should handle missing names gracefully
423
+ const channel = normalized.service?.protocols?.websocket?.channels?.find((c) => c.name === "testChannel");
424
+ assert.ok(channel);
425
+ const messages = channel?.messages || [];
426
+ assert.strictEqual(messages.length, 2);
427
+ // Named message should come after unnamed
428
+ const messageNames = messages.map((m) => m.name || "");
429
+ assert.deepStrictEqual(messageNames, ["", "namedMessage"]);
430
+ });
431
+ });
432
+ describe("Mixed protocol normalization", () => {
433
+ it("should normalize all protocols simultaneously", () => {
434
+ const doc = createTestDocument({
435
+ service: {
436
+ name: "test-service",
437
+ protocols: {
438
+ rest: {
439
+ routes: [
440
+ {
441
+ name: "zebraRoute",
442
+ method: "GET",
443
+ path: "/zebra",
444
+ responses: { "200": { description: "OK" } },
445
+ },
446
+ {
447
+ name: "alphaRoute",
448
+ method: "POST",
449
+ path: "/alpha",
450
+ responses: { "201": { description: "Created" } },
451
+ },
452
+ ],
453
+ },
454
+ graphql: {
455
+ queries: [
456
+ {
457
+ name: "zebraQuery",
458
+ description: "Get zebra",
459
+ args: [],
460
+ returnType: "Zebra",
461
+ },
462
+ {
463
+ name: "alphaQuery",
464
+ description: "Get alpha",
465
+ args: [],
466
+ returnType: "Alpha",
467
+ },
468
+ ],
469
+ mutations: [],
470
+ subscriptions: [],
471
+ schema: "type Query { alphaQuery: Alpha zebraQuery: Zebra }",
472
+ },
473
+ websocket: {
474
+ channels: [
475
+ {
476
+ name: "zebraChannel",
477
+ description: "Zebra channel",
478
+ messages: [],
479
+ },
480
+ {
481
+ name: "alphaChannel",
482
+ description: "Alpha channel",
483
+ messages: [],
484
+ },
485
+ ],
486
+ },
487
+ },
488
+ },
489
+ });
490
+ const normalized = normalizeUniSpec(doc);
491
+ // All protocols should be normalized
492
+ const protocols = normalized.service?.protocols;
493
+ // Check REST routes
494
+ const restRoutes = protocols?.rest?.routes || [];
495
+ const restRouteNames = restRoutes.map((r) => r.name).filter(Boolean);
496
+ assert.deepStrictEqual(restRouteNames, ["alphaRoute", "zebraRoute"]);
497
+ // Check GraphQL queries
498
+ const graphqlQueries = protocols?.graphql?.queries || [];
499
+ const graphqlQueryNames = graphqlQueries
500
+ .map((q) => q.name)
501
+ .filter(Boolean);
502
+ assert.deepStrictEqual(graphqlQueryNames, ["alphaQuery", "zebraQuery"]);
503
+ // Check WebSocket channels
504
+ const wsChannels = protocols?.websocket?.channels || [];
505
+ const wsChannelNames = wsChannels.map((c) => c.name).filter(Boolean);
506
+ assert.deepStrictEqual(wsChannelNames, ["alphaChannel", "zebraChannel"]);
507
+ });
508
+ });
509
+ describe("Structural normalization", () => {
510
+ it("should sort all object keys lexicographically", () => {
511
+ const doc = {
512
+ unispecVersion: "1.0.0",
513
+ service: {
514
+ name: "test-service",
515
+ description: "Test service for sorting",
516
+ protocols: {
517
+ rest: {
518
+ routes: [
519
+ {
520
+ name: "zRoute",
521
+ method: "GET",
522
+ path: "/z",
523
+ responses: { "200": { description: "OK" } },
524
+ },
525
+ {
526
+ name: "aRoute",
527
+ method: "POST",
528
+ path: "/a",
529
+ responses: { "201": { description: "Created" } },
530
+ },
531
+ ],
532
+ },
533
+ },
534
+ },
535
+ };
536
+ const normalized = normalizeUniSpec(doc);
537
+ // Top-level keys should be sorted
538
+ const docKeys = Object.keys(normalized);
539
+ assert.deepStrictEqual(docKeys, ["service", "unispecVersion"]);
540
+ // Service keys should be sorted lexicographically
541
+ const serviceKeys = Object.keys(normalized.service || {});
542
+ const expectedServiceKeys = ["description", "name", "protocols"];
543
+ assert.deepStrictEqual(serviceKeys, expectedServiceKeys);
544
+ // Protocol keys should be sorted
545
+ const protocolKeys = Object.keys(normalized.service?.protocols || {});
546
+ assert.deepStrictEqual(protocolKeys, ["rest"]);
547
+ });
548
+ it("should handle nested objects recursively", () => {
549
+ const doc = createTestDocument({
550
+ service: {
551
+ name: "test-service",
552
+ protocols: {
553
+ rest: {
554
+ routes: [
555
+ {
556
+ name: "testRoute",
557
+ method: "POST",
558
+ path: "/test",
559
+ requestBody: {
560
+ description: "Request body",
561
+ content: {
562
+ "application/json": {
563
+ schemaRef: "ZSchema",
564
+ },
565
+ "text/plain": {
566
+ schemaRef: "ASchema",
567
+ },
568
+ },
569
+ },
570
+ responses: {
571
+ "201": {
572
+ description: "Created",
573
+ },
574
+ "200": {
575
+ description: "OK",
576
+ },
577
+ },
578
+ },
579
+ ],
580
+ },
581
+ },
582
+ },
583
+ });
584
+ const normalized = normalizeUniSpec(doc);
585
+ // Nested object keys should be sorted
586
+ const route = normalized.service?.protocols?.rest?.routes?.[0];
587
+ assert.ok(route);
588
+ // requestBody keys should be sorted lexicographically
589
+ const requestBodyKeys = Object.keys(route.requestBody || {});
590
+ assert.deepStrictEqual(requestBodyKeys, ["content", "description"]);
591
+ // response keys should be sorted
592
+ const responseKeys = Object.keys(route.responses || {});
593
+ assert.deepStrictEqual(responseKeys, ["200", "201"]);
594
+ });
595
+ });
596
+ describe("Real-world examples", () => {
597
+ it("should handle complex real-world example from mixed-complete.json", () => {
598
+ const doc = loadExample(path.join(examplesDir, "valid", "spec", "mixed-complete.json"));
599
+ // Should not crash
600
+ const normalized = normalizeUniSpec(doc);
601
+ assert.ok(normalized);
602
+ // Should have sorted structure
603
+ const docKeys = Object.keys(normalized);
604
+ assert.deepStrictEqual(docKeys, ["service", "unispecVersion"]);
605
+ // Should normalize all protocols present
606
+ const protocols = normalized.service?.protocols;
607
+ if (protocols?.rest?.routes) {
608
+ const routeKeys = protocols.rest.routes.map((r) => r.name || `${r.path} ${r.method}`);
609
+ const sortedRouteKeys = [...routeKeys].sort();
610
+ assert.deepStrictEqual(routeKeys, sortedRouteKeys);
611
+ }
612
+ if (protocols?.graphql) {
613
+ ["queries", "mutations", "subscriptions"].forEach((operationType) => {
614
+ const operations = protocols[operationType];
615
+ if (Array.isArray(operations)) {
616
+ const names = operations
617
+ .map((op) => op.name || "")
618
+ .filter(Boolean);
619
+ const sortedNames = [...names].sort();
620
+ assert.deepStrictEqual(names, sortedNames);
621
+ }
622
+ });
623
+ }
624
+ if (protocols?.websocket?.channels) {
625
+ const channelNames = protocols.websocket.channels
626
+ .map((c) => c.name || "")
627
+ .filter(Boolean);
628
+ const sortedChannelNames = [...channelNames].sort();
629
+ assert.deepStrictEqual(channelNames, sortedChannelNames);
630
+ // Check message sorting within channels
631
+ protocols.websocket.channels.forEach((channel) => {
632
+ if (channel.messages) {
633
+ const messageNames = channel.messages
634
+ .map((m) => m.name || "")
635
+ .filter(Boolean);
636
+ const sortedMessageNames = [...messageNames].sort();
637
+ assert.deepStrictEqual(messageNames, sortedMessageNames);
638
+ }
639
+ });
640
+ }
641
+ });
642
+ });
643
+ });
@@ -0,0 +1 @@
1
+ export {};