@unispechq/unispec-core 0.2.10 → 0.2.12
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/converters/index.js +20 -13
- package/dist/cjs/diff/index.js +18 -5
- package/dist/cjs/loader/index.js +61 -6
- package/dist/cjs/normalizer/index.js +22 -8
- package/dist/cjs/schemas/index.js +13 -10
- package/dist/cjs/validator/generated-schemas.js +827 -0
- package/dist/cjs/validator/index.js +8 -90
- package/dist/converters/index.d.ts +1 -0
- package/dist/converters/index.js +20 -13
- package/dist/diff/index.d.ts +18 -5
- package/dist/diff/index.js +18 -5
- package/dist/loader/index.d.ts +3 -4
- package/dist/loader/index.js +28 -6
- package/dist/normalizer/index.d.ts +16 -2
- package/dist/normalizer/index.js +22 -8
- package/dist/schemas/index.js +13 -10
- package/dist/types/index.d.ts +7 -1
- package/dist/validator/generated-schemas.d.ts +842 -0
- package/dist/validator/generated-schemas.js +824 -0
- package/dist/validator/index.js +8 -57
- package/package.json +8 -3
|
@@ -4,16 +4,16 @@ exports.toOpenAPI = toOpenAPI;
|
|
|
4
4
|
exports.toGraphQLSDL = toGraphQLSDL;
|
|
5
5
|
exports.toWebSocketModel = toWebSocketModel;
|
|
6
6
|
function toOpenAPI(doc) {
|
|
7
|
-
const rest = (doc.protocols?.rest ?? {});
|
|
7
|
+
const rest = (doc.service?.protocols?.rest ?? {});
|
|
8
8
|
const info = {
|
|
9
|
-
title: doc.name,
|
|
10
|
-
description: doc.description,
|
|
9
|
+
title: doc.service.name,
|
|
10
|
+
description: doc.service.description,
|
|
11
11
|
};
|
|
12
12
|
const servers = [];
|
|
13
13
|
const paths = {};
|
|
14
14
|
const components = {};
|
|
15
|
-
// Map doc.schemas into OpenAPI components.schemas
|
|
16
|
-
const schemas = (doc.schemas ?? {});
|
|
15
|
+
// Map doc.service.schemas into OpenAPI components.schemas
|
|
16
|
+
const schemas = (doc.service?.schemas ?? {});
|
|
17
17
|
const componentsSchemas = {};
|
|
18
18
|
for (const [name, def] of Object.entries(schemas)) {
|
|
19
19
|
componentsSchemas[name] = def.jsonSchema;
|
|
@@ -138,13 +138,16 @@ function toGraphQLSDL(doc) {
|
|
|
138
138
|
// via a Query field. This does not attempt to interpret the full GraphQL
|
|
139
139
|
// protocol structure yet, but provides a stable, deterministic SDL shape
|
|
140
140
|
// based on top-level UniSpec document fields.
|
|
141
|
-
const graphql = doc.protocols?.graphql;
|
|
141
|
+
const graphql = doc.service?.protocols?.graphql;
|
|
142
142
|
const customSDL = graphql?.schema;
|
|
143
143
|
if (typeof customSDL === "string" && customSDL.trim()) {
|
|
144
|
-
return {
|
|
144
|
+
return {
|
|
145
|
+
sdl: customSDL,
|
|
146
|
+
url: graphql?.url
|
|
147
|
+
};
|
|
145
148
|
}
|
|
146
|
-
const title = doc.name;
|
|
147
|
-
const description = doc.description ?? "";
|
|
149
|
+
const title = doc.service.name;
|
|
150
|
+
const description = doc.service.description ?? "";
|
|
148
151
|
const lines = [];
|
|
149
152
|
if (title || description) {
|
|
150
153
|
lines.push("\"\"");
|
|
@@ -165,14 +168,17 @@ function toGraphQLSDL(doc) {
|
|
|
165
168
|
lines.push(" _serviceInfo: String!\n");
|
|
166
169
|
lines.push("}");
|
|
167
170
|
const sdl = lines.join("\n");
|
|
168
|
-
return {
|
|
171
|
+
return {
|
|
172
|
+
sdl,
|
|
173
|
+
url: graphql?.url
|
|
174
|
+
};
|
|
169
175
|
}
|
|
170
176
|
function toWebSocketModel(doc) {
|
|
171
177
|
// Base WebSocket model intended for a modern, dashboard-oriented UI.
|
|
172
178
|
// It exposes service metadata, a normalized list of channels and the raw
|
|
173
179
|
// websocket protocol object, while also embedding the original UniSpec
|
|
174
180
|
// document under a technical key for debugging and introspection.
|
|
175
|
-
const websocket = (doc.protocols?.websocket ?? {});
|
|
181
|
+
const websocket = (doc.service?.protocols?.websocket ?? {});
|
|
176
182
|
const channelsArray = Array.isArray(websocket.channels) ? websocket.channels : [];
|
|
177
183
|
const channels = [...channelsArray]
|
|
178
184
|
.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""))
|
|
@@ -186,10 +192,11 @@ function toWebSocketModel(doc) {
|
|
|
186
192
|
}));
|
|
187
193
|
return {
|
|
188
194
|
service: {
|
|
189
|
-
name: doc.name,
|
|
195
|
+
name: doc.service.name,
|
|
190
196
|
title: undefined,
|
|
191
|
-
description: doc.description,
|
|
197
|
+
description: doc.service.description,
|
|
192
198
|
},
|
|
199
|
+
url: websocket.url,
|
|
193
200
|
channels,
|
|
194
201
|
rawProtocol: websocket,
|
|
195
202
|
"x-unispec-ws": doc,
|
package/dist/cjs/diff/index.js
CHANGED
|
@@ -221,12 +221,25 @@ function annotateGraphQLChange(change) {
|
|
|
221
221
|
return annotated;
|
|
222
222
|
}
|
|
223
223
|
/**
|
|
224
|
-
*
|
|
224
|
+
* Compare two UniSpec documents and return detected changes.
|
|
225
225
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* -
|
|
229
|
-
* -
|
|
226
|
+
* This function performs a deep comparison between two UniSpec documents
|
|
227
|
+
* and categorizes changes by severity and protocol. Useful for:
|
|
228
|
+
* - API change detection in CI/CD pipelines
|
|
229
|
+
* - Breaking change analysis
|
|
230
|
+
* - Version compatibility checks
|
|
231
|
+
* - Audit trails for API evolution
|
|
232
|
+
*
|
|
233
|
+
* Features:
|
|
234
|
+
* - Detects added/removed fields at any depth
|
|
235
|
+
* - Special handling for named collections (routes, operations, channels)
|
|
236
|
+
* - Severity classification: "breaking" | "non-breaking" | "unknown"
|
|
237
|
+
* - Protocol categorization: "rest" | "graphql" | "websocket"
|
|
238
|
+
* - Detailed change descriptions with JSON paths
|
|
239
|
+
*
|
|
240
|
+
* @param oldDoc - The previous version of the UniSpec document
|
|
241
|
+
* @param newDoc - The current version of the UniSpec document
|
|
242
|
+
* @returns Object containing all detected changes
|
|
230
243
|
*/
|
|
231
244
|
function diffUniSpec(oldDoc, newDoc) {
|
|
232
245
|
const changes = [];
|
package/dist/cjs/loader/index.js
CHANGED
|
@@ -1,22 +1,77 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.loadUniSpec = loadUniSpec;
|
|
4
37
|
/**
|
|
5
38
|
* Load a UniSpec document from a raw input value.
|
|
6
|
-
*
|
|
39
|
+
* Supports:
|
|
7
40
|
* - JavaScript objects (treated as already parsed UniSpec)
|
|
8
41
|
* - JSON strings
|
|
9
|
-
*
|
|
10
|
-
* YAML and filesystem helpers will be added later, keeping this API stable.
|
|
42
|
+
* - YAML strings
|
|
11
43
|
*/
|
|
12
|
-
async function loadUniSpec(input,
|
|
44
|
+
async function loadUniSpec(input, options = {}) {
|
|
13
45
|
if (typeof input === "string") {
|
|
14
46
|
const trimmed = input.trim();
|
|
15
47
|
if (!trimmed) {
|
|
16
48
|
throw new Error("Cannot load UniSpec: input string is empty");
|
|
17
49
|
}
|
|
18
|
-
//
|
|
19
|
-
|
|
50
|
+
// Try JSON first (faster and more common)
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(trimmed);
|
|
53
|
+
}
|
|
54
|
+
catch (jsonError) {
|
|
55
|
+
// If JSON fails, try YAML
|
|
56
|
+
try {
|
|
57
|
+
// Dynamic import to avoid bundling yaml parser if not needed
|
|
58
|
+
const yamlModule = await Promise.resolve().then(() => __importStar(require("js-yaml")));
|
|
59
|
+
if (!yamlModule.load) {
|
|
60
|
+
throw new Error("js-yaml module not available");
|
|
61
|
+
}
|
|
62
|
+
const doc = yamlModule.load(trimmed, {
|
|
63
|
+
filename: options.filename,
|
|
64
|
+
// Basic security options
|
|
65
|
+
schema: yamlModule.FAILSAFE_SCHEMA
|
|
66
|
+
});
|
|
67
|
+
return doc;
|
|
68
|
+
}
|
|
69
|
+
catch (yamlError) {
|
|
70
|
+
const jsonMsg = jsonError instanceof Error ? jsonError.message : String(jsonError);
|
|
71
|
+
const yamlMsg = yamlError instanceof Error ? yamlError.message : String(yamlError);
|
|
72
|
+
throw new Error(`Failed to parse input as JSON or YAML. JSON error: ${jsonMsg}. YAML error: ${yamlMsg}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
20
75
|
}
|
|
21
76
|
return input;
|
|
22
77
|
}
|
|
@@ -19,10 +19,10 @@ function normalizeValue(value) {
|
|
|
19
19
|
return value;
|
|
20
20
|
}
|
|
21
21
|
function normalizeRestRoutes(doc) {
|
|
22
|
-
if (!doc || !doc.protocols) {
|
|
22
|
+
if (!doc || !doc.service?.protocols) {
|
|
23
23
|
return doc;
|
|
24
24
|
}
|
|
25
|
-
const protocols = doc.protocols;
|
|
25
|
+
const protocols = doc.service.protocols;
|
|
26
26
|
const rest = protocols.rest;
|
|
27
27
|
if (!rest || !Array.isArray(rest.routes)) {
|
|
28
28
|
return doc;
|
|
@@ -37,10 +37,10 @@ function normalizeRestRoutes(doc) {
|
|
|
37
37
|
return doc;
|
|
38
38
|
}
|
|
39
39
|
function normalizeWebSocket(doc) {
|
|
40
|
-
if (!doc || !doc.protocols) {
|
|
40
|
+
if (!doc || !doc.service?.protocols) {
|
|
41
41
|
return doc;
|
|
42
42
|
}
|
|
43
|
-
const protocols = doc.protocols;
|
|
43
|
+
const protocols = doc.service.protocols;
|
|
44
44
|
const websocket = protocols.websocket;
|
|
45
45
|
if (!websocket || !Array.isArray(websocket.channels)) {
|
|
46
46
|
return doc;
|
|
@@ -68,10 +68,10 @@ function normalizeWebSocket(doc) {
|
|
|
68
68
|
return doc;
|
|
69
69
|
}
|
|
70
70
|
function normalizeGraphqlOperations(doc) {
|
|
71
|
-
if (!doc || !doc.protocols) {
|
|
71
|
+
if (!doc || !doc.service?.protocols) {
|
|
72
72
|
return doc;
|
|
73
73
|
}
|
|
74
|
-
const protocols = doc.protocols;
|
|
74
|
+
const protocols = doc.service.protocols;
|
|
75
75
|
const graphql = protocols.graphql;
|
|
76
76
|
if (!graphql) {
|
|
77
77
|
return doc;
|
|
@@ -97,9 +97,23 @@ function normalizeGraphqlOperations(doc) {
|
|
|
97
97
|
/**
|
|
98
98
|
* Normalize a UniSpec document into a canonical, deterministic form.
|
|
99
99
|
*
|
|
100
|
+
* This function ensures consistent representation of UniSpec documents
|
|
101
|
+
* by sorting object keys and protocol-specific structures in a predictable order.
|
|
102
|
+
* Useful for:
|
|
103
|
+
* - Generating stable diffs between documents
|
|
104
|
+
* - Creating consistent output for caching
|
|
105
|
+
* - Ensuring reproducible builds
|
|
106
|
+
*
|
|
100
107
|
* Current behavior:
|
|
101
|
-
* - Recursively sorts object keys lexicographically
|
|
102
|
-
* -
|
|
108
|
+
* - Recursively sorts object keys lexicographically
|
|
109
|
+
* - Sorts REST routes by name or path+method
|
|
110
|
+
* - Sorts GraphQL operations by name within each operation type
|
|
111
|
+
* - Sorts WebSocket channels and messages by name
|
|
112
|
+
* - Preserves all values as-is
|
|
113
|
+
*
|
|
114
|
+
* @param doc - The UniSpec document to normalize
|
|
115
|
+
* @param options - Normalization options (currently unused, reserved for future)
|
|
116
|
+
* @returns The normalized UniSpec document
|
|
103
117
|
*/
|
|
104
118
|
function normalizeUniSpec(doc, _options = {}) {
|
|
105
119
|
const normalized = normalizeValue(doc);
|
|
@@ -32,21 +32,24 @@ function resolveSchemaRef(doc, ref) {
|
|
|
32
32
|
const name = normalizeSchemaRef(ref);
|
|
33
33
|
if (!name)
|
|
34
34
|
return undefined;
|
|
35
|
-
return doc.schemas?.[name];
|
|
35
|
+
return doc.service?.schemas?.[name];
|
|
36
36
|
}
|
|
37
37
|
function registerSchema(doc, name, jsonSchema) {
|
|
38
|
-
if (!doc.
|
|
39
|
-
doc.
|
|
38
|
+
if (!doc.service) {
|
|
39
|
+
doc.service = { name: "" };
|
|
40
|
+
}
|
|
41
|
+
if (!doc.service.schemas) {
|
|
42
|
+
doc.service.schemas = {};
|
|
40
43
|
}
|
|
41
44
|
const definition = {
|
|
42
45
|
jsonSchema,
|
|
43
46
|
};
|
|
44
|
-
doc.schemas[name] = definition;
|
|
47
|
+
doc.service.schemas[name] = definition;
|
|
45
48
|
return definition;
|
|
46
49
|
}
|
|
47
50
|
function updateSchemaRefs(doc, mapping) {
|
|
48
|
-
const rest = doc.protocols?.rest;
|
|
49
|
-
const websocket = doc.protocols?.websocket;
|
|
51
|
+
const rest = doc.service?.protocols?.rest;
|
|
52
|
+
const websocket = doc.service?.protocols?.websocket;
|
|
50
53
|
if (rest?.routes) {
|
|
51
54
|
for (const route of rest.routes) {
|
|
52
55
|
for (const params of [route.pathParams, route.queryParams, route.headers]) {
|
|
@@ -106,13 +109,13 @@ function updateSchemaRefs(doc, mapping) {
|
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
function dedupeSchemas(doc) {
|
|
109
|
-
if (!doc.schemas)
|
|
112
|
+
if (!doc.service?.schemas)
|
|
110
113
|
return;
|
|
111
|
-
const names = Object.keys(doc.schemas);
|
|
114
|
+
const names = Object.keys(doc.service.schemas);
|
|
112
115
|
const hashToName = new Map();
|
|
113
116
|
const renameMap = {};
|
|
114
117
|
for (const name of names) {
|
|
115
|
-
const schema = doc.schemas[name];
|
|
118
|
+
const schema = doc.service.schemas[name];
|
|
116
119
|
const hash = stableStringify(schema?.jsonSchema ?? null);
|
|
117
120
|
const existing = hashToName.get(hash);
|
|
118
121
|
if (!existing) {
|
|
@@ -126,6 +129,6 @@ function dedupeSchemas(doc) {
|
|
|
126
129
|
return;
|
|
127
130
|
updateSchemaRefs(doc, renameMap);
|
|
128
131
|
for (const dup of duplicates) {
|
|
129
|
-
delete doc.schemas[dup];
|
|
132
|
+
delete doc.service.schemas[dup];
|
|
130
133
|
}
|
|
131
134
|
}
|