@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.
@@ -1,37 +1,4 @@
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
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
@@ -39,58 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
6
  exports.validateUniSpec = validateUniSpec;
40
7
  exports.validateUniSpecTests = validateUniSpecTests;
41
8
  const _2020_js_1 = __importDefault(require("ajv/dist/2020.js"));
9
+ const generated_schemas_js_1 = require("./generated-schemas.js");
42
10
  let cached = null;
43
- async function tryCreateNodeSchemaProvider() {
44
- try {
45
- const fs = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
46
- const path = await Promise.resolve().then(() => __importStar(require("node:path")));
47
- const module = await Promise.resolve().then(() => __importStar(require("node:module")));
48
- const requireBasePath = typeof __filename === "string" && __filename.length > 0
49
- ? __filename
50
- : path.join(process.cwd(), "__unispec_core_require__.js");
51
- const require = module.createRequire(requireBasePath);
52
- const schemaIndexPath = require.resolve("@unispechq/unispec-schema/schema");
53
- const schemaRoot = path.dirname(schemaIndexPath);
54
- return {
55
- getSchema: async (schemaPath) => {
56
- const fullPath = path.join(schemaRoot, schemaPath);
57
- const json = await fs.readFile(fullPath, "utf8");
58
- return JSON.parse(json);
59
- },
60
- };
61
- }
62
- catch {
63
- return undefined;
64
- }
65
- }
66
11
  async function loadDefaultSchemas() {
67
- const schemaProvider = await tryCreateNodeSchemaProvider();
68
- if (!schemaProvider) {
69
- throw new Error("UniSpec validator: default schema loading is not available in this environment. " +
70
- "Provide ValidateOptions.schemas (recommended for browsers/edge runtimes) or ValidateOptions.schemaProvider.");
71
- }
72
- const { manifest, unispec } = (await Promise.resolve().then(() => __importStar(require("@unispechq/unispec-schema"))));
73
- const types = manifest?.types ?? {};
74
- const typeSchemaPaths = Object.values(types).map((rel) => String(rel));
75
- const subschemas = await Promise.all(typeSchemaPaths.map(async (relPath) => {
76
- try {
77
- return await schemaProvider.getSchema(relPath);
78
- }
79
- catch {
80
- return null;
81
- }
82
- }));
83
- let unispecTests;
84
- try {
85
- unispecTests = await schemaProvider.getSchema("unispec-tests.schema.json");
86
- }
87
- catch {
88
- unispecTests = undefined;
89
- }
12
+ // Always use generated schemas - they work in all environments
90
13
  return {
91
- unispec: unispec,
92
- unispecTests,
93
- subschemas: subschemas.filter((s) => Boolean(s)),
14
+ unispec: generated_schemas_js_1.GENERATED_SCHEMAS.unispec,
15
+ unispecTests: generated_schemas_js_1.GENERATED_SCHEMAS.unispecTests,
16
+ subschemas: generated_schemas_js_1.GENERATED_SCHEMAS.subschemas,
94
17
  };
95
18
  }
96
19
  function createAjv(options) {
@@ -138,15 +61,10 @@ function mapAjvErrors(errors) {
138
61
  */
139
62
  async function validateUniSpec(doc, options = {}) {
140
63
  const { validateUniSpecFn } = await getValidator(options);
64
+ // Ensure the document has required fields for validation
141
65
  const docForValidation = {
142
- unispecVersion: "0.0.0",
143
- service: {
144
- name: doc.name,
145
- description: doc.description,
146
- version: doc.version,
147
- protocols: doc.protocols,
148
- schemas: doc.schemas,
149
- },
66
+ unispecVersion: doc.unispecVersion || "1.0.0",
67
+ service: doc.service,
150
68
  extensions: doc.extensions,
151
69
  };
152
70
  const valid = validateUniSpecFn(docForValidation);
@@ -4,6 +4,7 @@ export interface OpenAPIDocument {
4
4
  }
5
5
  export interface GraphQLSDLOutput {
6
6
  sdl: string;
7
+ url?: string;
7
8
  }
8
9
  export interface WebSocketModel {
9
10
  [key: string]: unknown;
@@ -1,14 +1,14 @@
1
1
  export function toOpenAPI(doc) {
2
- const rest = (doc.protocols?.rest ?? {});
2
+ const rest = (doc.service?.protocols?.rest ?? {});
3
3
  const info = {
4
- title: doc.name,
5
- description: doc.description,
4
+ title: doc.service.name,
5
+ description: doc.service.description,
6
6
  };
7
7
  const servers = [];
8
8
  const paths = {};
9
9
  const components = {};
10
- // Map doc.schemas into OpenAPI components.schemas
11
- const schemas = (doc.schemas ?? {});
10
+ // Map doc.service.schemas into OpenAPI components.schemas
11
+ const schemas = (doc.service?.schemas ?? {});
12
12
  const componentsSchemas = {};
13
13
  for (const [name, def] of Object.entries(schemas)) {
14
14
  componentsSchemas[name] = def.jsonSchema;
@@ -133,13 +133,16 @@ export function toGraphQLSDL(doc) {
133
133
  // via a Query field. This does not attempt to interpret the full GraphQL
134
134
  // protocol structure yet, but provides a stable, deterministic SDL shape
135
135
  // based on top-level UniSpec document fields.
136
- const graphql = doc.protocols?.graphql;
136
+ const graphql = doc.service?.protocols?.graphql;
137
137
  const customSDL = graphql?.schema;
138
138
  if (typeof customSDL === "string" && customSDL.trim()) {
139
- return { sdl: customSDL };
139
+ return {
140
+ sdl: customSDL,
141
+ url: graphql?.url
142
+ };
140
143
  }
141
- const title = doc.name;
142
- const description = doc.description ?? "";
144
+ const title = doc.service.name;
145
+ const description = doc.service.description ?? "";
143
146
  const lines = [];
144
147
  if (title || description) {
145
148
  lines.push("\"\"");
@@ -160,14 +163,17 @@ export function toGraphQLSDL(doc) {
160
163
  lines.push(" _serviceInfo: String!\n");
161
164
  lines.push("}");
162
165
  const sdl = lines.join("\n");
163
- return { sdl };
166
+ return {
167
+ sdl,
168
+ url: graphql?.url
169
+ };
164
170
  }
165
171
  export function toWebSocketModel(doc) {
166
172
  // Base WebSocket model intended for a modern, dashboard-oriented UI.
167
173
  // It exposes service metadata, a normalized list of channels and the raw
168
174
  // websocket protocol object, while also embedding the original UniSpec
169
175
  // document under a technical key for debugging and introspection.
170
- const websocket = (doc.protocols?.websocket ?? {});
176
+ const websocket = (doc.service?.protocols?.websocket ?? {});
171
177
  const channelsArray = Array.isArray(websocket.channels) ? websocket.channels : [];
172
178
  const channels = [...channelsArray]
173
179
  .sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""))
@@ -181,10 +187,11 @@ export function toWebSocketModel(doc) {
181
187
  }));
182
188
  return {
183
189
  service: {
184
- name: doc.name,
190
+ name: doc.service.name,
185
191
  title: undefined,
186
- description: doc.description,
192
+ description: doc.service.description,
187
193
  },
194
+ url: websocket.url,
188
195
  channels,
189
196
  rawProtocol: websocket,
190
197
  "x-unispec-ws": doc,
@@ -11,11 +11,24 @@ export interface DiffResult {
11
11
  changes: UniSpecChange[];
12
12
  }
13
13
  /**
14
- * Compute a structural diff between two UniSpec documents.
14
+ * Compare two UniSpec documents and return detected changes.
15
15
  *
16
- * Current behavior:
17
- * - Tracks added, removed, and changed fields and array items.
18
- * - Uses JSON Pointer-like paths rooted at "" (e.g., "/info/title").
19
- * - Marks all changes with severity "unknown" for now.
16
+ * This function performs a deep comparison between two UniSpec documents
17
+ * and categorizes changes by severity and protocol. Useful for:
18
+ * - API change detection in CI/CD pipelines
19
+ * - Breaking change analysis
20
+ * - Version compatibility checks
21
+ * - Audit trails for API evolution
22
+ *
23
+ * Features:
24
+ * - Detects added/removed fields at any depth
25
+ * - Special handling for named collections (routes, operations, channels)
26
+ * - Severity classification: "breaking" | "non-breaking" | "unknown"
27
+ * - Protocol categorization: "rest" | "graphql" | "websocket"
28
+ * - Detailed change descriptions with JSON paths
29
+ *
30
+ * @param oldDoc - The previous version of the UniSpec document
31
+ * @param newDoc - The current version of the UniSpec document
32
+ * @returns Object containing all detected changes
20
33
  */
21
34
  export declare function diffUniSpec(oldDoc: UniSpecDocument, newDoc: UniSpecDocument): DiffResult;
@@ -218,12 +218,25 @@ function annotateGraphQLChange(change) {
218
218
  return annotated;
219
219
  }
220
220
  /**
221
- * Compute a structural diff between two UniSpec documents.
221
+ * Compare two UniSpec documents and return detected changes.
222
222
  *
223
- * Current behavior:
224
- * - Tracks added, removed, and changed fields and array items.
225
- * - Uses JSON Pointer-like paths rooted at "" (e.g., "/info/title").
226
- * - Marks all changes with severity "unknown" for now.
223
+ * This function performs a deep comparison between two UniSpec documents
224
+ * and categorizes changes by severity and protocol. Useful for:
225
+ * - API change detection in CI/CD pipelines
226
+ * - Breaking change analysis
227
+ * - Version compatibility checks
228
+ * - Audit trails for API evolution
229
+ *
230
+ * Features:
231
+ * - Detects added/removed fields at any depth
232
+ * - Special handling for named collections (routes, operations, channels)
233
+ * - Severity classification: "breaking" | "non-breaking" | "unknown"
234
+ * - Protocol categorization: "rest" | "graphql" | "websocket"
235
+ * - Detailed change descriptions with JSON paths
236
+ *
237
+ * @param oldDoc - The previous version of the UniSpec document
238
+ * @param newDoc - The current version of the UniSpec document
239
+ * @returns Object containing all detected changes
227
240
  */
228
241
  export function diffUniSpec(oldDoc, newDoc) {
229
242
  const changes = [];
@@ -4,10 +4,9 @@ export interface LoadOptions {
4
4
  }
5
5
  /**
6
6
  * Load a UniSpec document from a raw input value.
7
- * Currently supports:
7
+ * Supports:
8
8
  * - JavaScript objects (treated as already parsed UniSpec)
9
9
  * - JSON strings
10
- *
11
- * YAML and filesystem helpers will be added later, keeping this API stable.
10
+ * - YAML strings
12
11
  */
13
- export declare function loadUniSpec(input: string | object, _options?: LoadOptions): Promise<UniSpecDocument>;
12
+ export declare function loadUniSpec(input: string | object, options?: LoadOptions): Promise<UniSpecDocument>;
@@ -1,19 +1,41 @@
1
1
  /**
2
2
  * Load a UniSpec document from a raw input value.
3
- * Currently supports:
3
+ * Supports:
4
4
  * - JavaScript objects (treated as already parsed UniSpec)
5
5
  * - JSON strings
6
- *
7
- * YAML and filesystem helpers will be added later, keeping this API stable.
6
+ * - YAML strings
8
7
  */
9
- export async function loadUniSpec(input, _options = {}) {
8
+ export async function loadUniSpec(input, options = {}) {
10
9
  if (typeof input === "string") {
11
10
  const trimmed = input.trim();
12
11
  if (!trimmed) {
13
12
  throw new Error("Cannot load UniSpec: input string is empty");
14
13
  }
15
- // For now we assume JSON; YAML support will be added later.
16
- return JSON.parse(trimmed);
14
+ // Try JSON first (faster and more common)
15
+ try {
16
+ return JSON.parse(trimmed);
17
+ }
18
+ catch (jsonError) {
19
+ // If JSON fails, try YAML
20
+ try {
21
+ // Dynamic import to avoid bundling yaml parser if not needed
22
+ const yamlModule = await import("js-yaml");
23
+ if (!yamlModule.load) {
24
+ throw new Error("js-yaml module not available");
25
+ }
26
+ const doc = yamlModule.load(trimmed, {
27
+ filename: options.filename,
28
+ // Basic security options
29
+ schema: yamlModule.FAILSAFE_SCHEMA
30
+ });
31
+ return doc;
32
+ }
33
+ catch (yamlError) {
34
+ const jsonMsg = jsonError instanceof Error ? jsonError.message : String(jsonError);
35
+ const yamlMsg = yamlError instanceof Error ? yamlError.message : String(yamlError);
36
+ throw new Error(`Failed to parse input as JSON or YAML. JSON error: ${jsonMsg}. YAML error: ${yamlMsg}`);
37
+ }
38
+ }
17
39
  }
18
40
  return input;
19
41
  }
@@ -4,8 +4,22 @@ export interface NormalizeOptions {
4
4
  /**
5
5
  * Normalize a UniSpec document into a canonical, deterministic form.
6
6
  *
7
+ * This function ensures consistent representation of UniSpec documents
8
+ * by sorting object keys and protocol-specific structures in a predictable order.
9
+ * Useful for:
10
+ * - Generating stable diffs between documents
11
+ * - Creating consistent output for caching
12
+ * - Ensuring reproducible builds
13
+ *
7
14
  * Current behavior:
8
- * - Recursively sorts object keys lexicographically.
9
- * - Preserves values as-is.
15
+ * - Recursively sorts object keys lexicographically
16
+ * - Sorts REST routes by name or path+method
17
+ * - Sorts GraphQL operations by name within each operation type
18
+ * - Sorts WebSocket channels and messages by name
19
+ * - Preserves all values as-is
20
+ *
21
+ * @param doc - The UniSpec document to normalize
22
+ * @param options - Normalization options (currently unused, reserved for future)
23
+ * @returns The normalized UniSpec document
10
24
  */
11
25
  export declare function normalizeUniSpec(doc: UniSpecDocument, _options?: NormalizeOptions): UniSpecDocument;
@@ -16,10 +16,10 @@ function normalizeValue(value) {
16
16
  return value;
17
17
  }
18
18
  function normalizeRestRoutes(doc) {
19
- if (!doc || !doc.protocols) {
19
+ if (!doc || !doc.service?.protocols) {
20
20
  return doc;
21
21
  }
22
- const protocols = doc.protocols;
22
+ const protocols = doc.service.protocols;
23
23
  const rest = protocols.rest;
24
24
  if (!rest || !Array.isArray(rest.routes)) {
25
25
  return doc;
@@ -34,10 +34,10 @@ function normalizeRestRoutes(doc) {
34
34
  return doc;
35
35
  }
36
36
  function normalizeWebSocket(doc) {
37
- if (!doc || !doc.protocols) {
37
+ if (!doc || !doc.service?.protocols) {
38
38
  return doc;
39
39
  }
40
- const protocols = doc.protocols;
40
+ const protocols = doc.service.protocols;
41
41
  const websocket = protocols.websocket;
42
42
  if (!websocket || !Array.isArray(websocket.channels)) {
43
43
  return doc;
@@ -65,10 +65,10 @@ function normalizeWebSocket(doc) {
65
65
  return doc;
66
66
  }
67
67
  function normalizeGraphqlOperations(doc) {
68
- if (!doc || !doc.protocols) {
68
+ if (!doc || !doc.service?.protocols) {
69
69
  return doc;
70
70
  }
71
- const protocols = doc.protocols;
71
+ const protocols = doc.service.protocols;
72
72
  const graphql = protocols.graphql;
73
73
  if (!graphql) {
74
74
  return doc;
@@ -94,9 +94,23 @@ function normalizeGraphqlOperations(doc) {
94
94
  /**
95
95
  * Normalize a UniSpec document into a canonical, deterministic form.
96
96
  *
97
+ * This function ensures consistent representation of UniSpec documents
98
+ * by sorting object keys and protocol-specific structures in a predictable order.
99
+ * Useful for:
100
+ * - Generating stable diffs between documents
101
+ * - Creating consistent output for caching
102
+ * - Ensuring reproducible builds
103
+ *
97
104
  * Current behavior:
98
- * - Recursively sorts object keys lexicographically.
99
- * - Preserves values as-is.
105
+ * - Recursively sorts object keys lexicographically
106
+ * - Sorts REST routes by name or path+method
107
+ * - Sorts GraphQL operations by name within each operation type
108
+ * - Sorts WebSocket channels and messages by name
109
+ * - Preserves all values as-is
110
+ *
111
+ * @param doc - The UniSpec document to normalize
112
+ * @param options - Normalization options (currently unused, reserved for future)
113
+ * @returns The normalized UniSpec document
100
114
  */
101
115
  export function normalizeUniSpec(doc, _options = {}) {
102
116
  const normalized = normalizeValue(doc);
@@ -27,21 +27,24 @@ export function resolveSchemaRef(doc, ref) {
27
27
  const name = normalizeSchemaRef(ref);
28
28
  if (!name)
29
29
  return undefined;
30
- return doc.schemas?.[name];
30
+ return doc.service?.schemas?.[name];
31
31
  }
32
32
  export function registerSchema(doc, name, jsonSchema) {
33
- if (!doc.schemas) {
34
- doc.schemas = {};
33
+ if (!doc.service) {
34
+ doc.service = { name: "" };
35
+ }
36
+ if (!doc.service.schemas) {
37
+ doc.service.schemas = {};
35
38
  }
36
39
  const definition = {
37
40
  jsonSchema,
38
41
  };
39
- doc.schemas[name] = definition;
42
+ doc.service.schemas[name] = definition;
40
43
  return definition;
41
44
  }
42
45
  function updateSchemaRefs(doc, mapping) {
43
- const rest = doc.protocols?.rest;
44
- const websocket = doc.protocols?.websocket;
46
+ const rest = doc.service?.protocols?.rest;
47
+ const websocket = doc.service?.protocols?.websocket;
45
48
  if (rest?.routes) {
46
49
  for (const route of rest.routes) {
47
50
  for (const params of [route.pathParams, route.queryParams, route.headers]) {
@@ -101,13 +104,13 @@ function updateSchemaRefs(doc, mapping) {
101
104
  }
102
105
  }
103
106
  export function dedupeSchemas(doc) {
104
- if (!doc.schemas)
107
+ if (!doc.service?.schemas)
105
108
  return;
106
- const names = Object.keys(doc.schemas);
109
+ const names = Object.keys(doc.service.schemas);
107
110
  const hashToName = new Map();
108
111
  const renameMap = {};
109
112
  for (const name of names) {
110
- const schema = doc.schemas[name];
113
+ const schema = doc.service.schemas[name];
111
114
  const hash = stableStringify(schema?.jsonSchema ?? null);
112
115
  const existing = hashToName.get(hash);
113
116
  if (!existing) {
@@ -121,6 +124,6 @@ export function dedupeSchemas(doc) {
121
124
  return;
122
125
  updateSchemaRefs(doc, renameMap);
123
126
  for (const dup of duplicates) {
124
- delete doc.schemas[dup];
127
+ delete doc.service.schemas[dup];
125
128
  }
126
129
  }
@@ -5,6 +5,7 @@ export interface UniSpecGraphQLOperation {
5
5
  deprecationReason?: string;
6
6
  }
7
7
  export interface UniSpecGraphQLProtocol {
8
+ url?: string;
8
9
  schema?: string;
9
10
  queries?: UniSpecGraphQLOperation[];
10
11
  mutations?: UniSpecGraphQLOperation[];
@@ -26,6 +27,7 @@ export interface UniSpecWebSocketChannel {
26
27
  extensions?: Record<string, unknown>;
27
28
  }
28
29
  export interface UniSpecWebSocketProtocol {
30
+ url?: string;
29
31
  channels?: UniSpecWebSocketChannel[];
30
32
  securitySchemes?: Record<string, Record<string, unknown>>;
31
33
  }
@@ -86,12 +88,16 @@ export interface UniSpecServiceProtocols {
86
88
  websocket?: UniSpecWebSocketProtocol;
87
89
  }
88
90
  export interface UniSpecDocument {
91
+ unispecVersion: string;
92
+ service: UniSpecService;
93
+ extensions?: Record<string, unknown>;
94
+ }
95
+ export interface UniSpecService {
89
96
  name: string;
90
97
  description?: string;
91
98
  version?: string;
92
99
  protocols?: UniSpecServiceProtocols;
93
100
  schemas?: UniSpecSchemas;
94
- extensions?: Record<string, unknown>;
95
101
  }
96
102
  export interface ValidationError {
97
103
  message: string;