@omer-x/next-openapi-json-generator 2.0.3 → 2.1.0-rc.1

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 (3) hide show
  1. package/dist/index.cjs +24 -141
  2. package/dist/index.js +24 -141
  3. package/package.json +9 -6
package/dist/index.cjs CHANGED
@@ -36,6 +36,7 @@ module.exports = __toCommonJS(index_exports);
36
36
 
37
37
  // src/core/generateOpenApiSpec.ts
38
38
  var import_node_path4 = __toESM(require("path"), 1);
39
+ var import_openapi_optimizer = require("@omer-x/openapi-optimizer");
39
40
  var import_package_metadata = __toESM(require("@omer-x/package-metadata"), 1);
40
41
 
41
42
  // src/utils/object.ts
@@ -281,26 +282,23 @@ function getRoutePathName(filePath, rootPath) {
281
282
  return "/" + import_node_path3.default.relative(rootPath, dirName).replaceAll("[", "{").replaceAll("]", "}").replaceAll("\\", "/");
282
283
  }
283
284
 
284
- // src/utils/deepEqual.ts
285
- function deepEqual(a, b) {
286
- if (typeof a !== typeof b) return false;
287
- switch (typeof a) {
288
- case "object": {
289
- if (!a || !b) return a === b;
290
- if (Array.isArray(a) && Array.isArray(b)) {
291
- return a.every((item, index) => deepEqual(item, b[index]));
292
- }
293
- if (Object.keys(a).length !== Object.keys(b).length) return false;
294
- return Object.entries(a).every(([key, value]) => {
295
- return deepEqual(value, b[key]);
296
- });
285
+ // src/core/route.ts
286
+ function createRouteRecord(method, filePath, rootPath, apiData) {
287
+ return {
288
+ method: method.toLocaleLowerCase(),
289
+ path: getRoutePathName(filePath, rootPath),
290
+ apiData
291
+ };
292
+ }
293
+ function bundlePaths(source) {
294
+ source.sort((a, b) => a.path.localeCompare(b.path));
295
+ return source.reduce((collection, route) => ({
296
+ ...collection,
297
+ [route.path]: {
298
+ ...collection[route.path],
299
+ [route.method]: route.apiData
297
300
  }
298
- case "function":
299
- case "symbol":
300
- return false;
301
- default:
302
- return a === b;
303
- }
301
+ }), {});
304
302
  }
305
303
 
306
304
  // src/core/zod-to-openapi.ts
@@ -336,128 +334,12 @@ function convertToOpenAPI(schema, isArray) {
336
334
  return import_zod2.z.toJSONSchema(fixSchema(isArray ? schema.array() : schema));
337
335
  }
338
336
 
339
- // src/core/mask.ts
340
- function maskWithReference(schema, storedSchemas, self) {
341
- if (self) {
342
- for (const [schemaName, zodSchema] of Object.entries(storedSchemas)) {
343
- if (deepEqual(schema, convertToOpenAPI(zodSchema, false))) {
344
- return {
345
- $ref: `#/components/schemas/${schemaName}`
346
- };
347
- }
348
- }
349
- }
350
- if ("$ref" in schema) return schema;
351
- if (schema.oneOf) {
352
- return {
353
- ...schema,
354
- oneOf: schema.oneOf.map((i) => maskWithReference(i, storedSchemas, true))
355
- };
356
- }
357
- if (schema.anyOf) {
358
- return {
359
- ...schema,
360
- anyOf: schema.anyOf.map((i) => maskWithReference(i, storedSchemas, true))
361
- };
362
- }
363
- switch (schema.type) {
364
- case "object":
365
- return {
366
- ...schema,
367
- properties: Object.entries(schema.properties ?? {}).reduce((props, [propName, prop]) => ({
368
- ...props,
369
- [propName]: maskWithReference(prop, storedSchemas, true)
370
- }), {})
371
- };
372
- case "array":
373
- if (Array.isArray(schema.items)) {
374
- return {
375
- ...schema,
376
- items: schema.items.map((i) => maskWithReference(i, storedSchemas, true))
377
- };
378
- }
379
- return {
380
- ...schema,
381
- items: maskWithReference(schema.items, storedSchemas, true)
382
- };
383
- }
384
- return schema;
385
- }
386
-
387
- // src/core/operation-mask.ts
388
- function maskSchema(storedSchemas, schema) {
389
- if (!schema) return schema;
390
- return maskWithReference(schema, storedSchemas, true);
391
- }
392
- function maskParameterSchema(param, storedSchemas) {
393
- if ("$ref" in param) return param;
394
- return { ...param, schema: maskSchema(storedSchemas, param.schema) };
395
- }
396
- function maskContentSchema(storedSchemas, bodyContent) {
397
- if (!bodyContent) return bodyContent;
398
- return Object.entries(bodyContent).reduce((collection, [contentType, content]) => ({
399
- ...collection,
400
- [contentType]: {
401
- ...content,
402
- schema: maskSchema(storedSchemas, content.schema)
403
- }
404
- }), {});
405
- }
406
- function maskRequestBodySchema(storedSchemas, body) {
407
- if (!body || "$ref" in body) return body;
408
- return { ...body, content: maskContentSchema(storedSchemas, body.content) };
409
- }
410
- function maskResponseSchema(storedSchemas, response) {
411
- if ("$ref" in response) return response;
412
- return { ...response, content: maskContentSchema(storedSchemas, response.content) };
413
- }
414
- function maskSchemasInResponses(storedSchemas, responses) {
415
- if (!responses) return responses;
416
- return Object.entries(responses).reduce((collection, [key, response]) => ({
417
- ...collection,
418
- [key]: maskResponseSchema(storedSchemas, response)
419
- }), {});
420
- }
421
- function maskOperationSchemas(operation, storedSchemas) {
422
- return {
423
- ...operation,
424
- parameters: operation.parameters?.map((p) => maskParameterSchema(p, storedSchemas)),
425
- requestBody: maskRequestBodySchema(storedSchemas, operation.requestBody),
426
- responses: maskSchemasInResponses(storedSchemas, operation.responses)
427
- };
428
- }
429
-
430
- // src/core/route.ts
431
- function createRouteRecord(method, filePath, rootPath, apiData) {
432
- return {
433
- method: method.toLocaleLowerCase(),
434
- path: getRoutePathName(filePath, rootPath),
435
- apiData
436
- };
437
- }
438
- function bundlePaths(source, storedSchemas) {
439
- source.sort((a, b) => a.path.localeCompare(b.path));
440
- return source.reduce((collection, route) => ({
441
- ...collection,
442
- [route.path]: {
443
- ...collection[route.path],
444
- [route.method]: maskOperationSchemas(route.apiData, storedSchemas)
445
- }
446
- }), {});
447
- }
448
-
449
337
  // src/core/schema.ts
450
338
  function bundleSchemas(schemas) {
451
- const bundledSchemas = Object.keys(schemas).reduce((collection, schemaName) => {
452
- return {
453
- ...collection,
454
- [schemaName]: convertToOpenAPI(schemas[schemaName], false)
455
- };
456
- }, {});
457
- return Object.entries(bundledSchemas).reduce((bundle, [schemaName, schema]) => ({
458
- ...bundle,
459
- [schemaName]: maskWithReference(schema, schemas, false)
460
- }), {});
339
+ const entries = Object.entries(schemas).map(([schemaName, schema]) => {
340
+ return [schemaName, convertToOpenAPI(schema, false)];
341
+ });
342
+ return Object.fromEntries(entries);
461
343
  }
462
344
 
463
345
  // src/core/generateOpenApiSpec.ts
@@ -495,13 +377,13 @@ async function generateOpenApiSpec(schemas, {
495
377
  }
496
378
  const metadata = (0, import_package_metadata.default)();
497
379
  const pathsAndComponents = {
498
- paths: bundlePaths(validRoutes, schemas),
380
+ paths: bundlePaths(validRoutes),
499
381
  components: {
500
382
  schemas: bundleSchemas(schemas),
501
383
  securitySchemes
502
384
  }
503
385
  };
504
- return JSON.parse(JSON.stringify({
386
+ const spec = JSON.parse(JSON.stringify({
505
387
  openapi: "3.1.0",
506
388
  info: {
507
389
  title: metadata.serviceName,
@@ -513,6 +395,7 @@ async function generateOpenApiSpec(schemas, {
513
395
  security,
514
396
  tags: []
515
397
  }));
398
+ return (0, import_openapi_optimizer.optimizeOpenApiSpec)(spec);
516
399
  }
517
400
 
518
401
  // src/index.ts
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
 
8
8
  // src/core/generateOpenApiSpec.ts
9
9
  import path4 from "path";
10
+ import { optimizeOpenApiSpec } from "@omer-x/openapi-optimizer";
10
11
  import getPackageMetadata from "@omer-x/package-metadata";
11
12
 
12
13
  // src/utils/object.ts
@@ -252,26 +253,23 @@ function getRoutePathName(filePath, rootPath) {
252
253
  return "/" + path3.relative(rootPath, dirName).replaceAll("[", "{").replaceAll("]", "}").replaceAll("\\", "/");
253
254
  }
254
255
 
255
- // src/utils/deepEqual.ts
256
- function deepEqual(a, b) {
257
- if (typeof a !== typeof b) return false;
258
- switch (typeof a) {
259
- case "object": {
260
- if (!a || !b) return a === b;
261
- if (Array.isArray(a) && Array.isArray(b)) {
262
- return a.every((item, index) => deepEqual(item, b[index]));
263
- }
264
- if (Object.keys(a).length !== Object.keys(b).length) return false;
265
- return Object.entries(a).every(([key, value]) => {
266
- return deepEqual(value, b[key]);
267
- });
256
+ // src/core/route.ts
257
+ function createRouteRecord(method, filePath, rootPath, apiData) {
258
+ return {
259
+ method: method.toLocaleLowerCase(),
260
+ path: getRoutePathName(filePath, rootPath),
261
+ apiData
262
+ };
263
+ }
264
+ function bundlePaths(source) {
265
+ source.sort((a, b) => a.path.localeCompare(b.path));
266
+ return source.reduce((collection, route) => ({
267
+ ...collection,
268
+ [route.path]: {
269
+ ...collection[route.path],
270
+ [route.method]: route.apiData
268
271
  }
269
- case "function":
270
- case "symbol":
271
- return false;
272
- default:
273
- return a === b;
274
- }
272
+ }), {});
275
273
  }
276
274
 
277
275
  // src/core/zod-to-openapi.ts
@@ -307,128 +305,12 @@ function convertToOpenAPI(schema, isArray) {
307
305
  return z2.toJSONSchema(fixSchema(isArray ? schema.array() : schema));
308
306
  }
309
307
 
310
- // src/core/mask.ts
311
- function maskWithReference(schema, storedSchemas, self) {
312
- if (self) {
313
- for (const [schemaName, zodSchema] of Object.entries(storedSchemas)) {
314
- if (deepEqual(schema, convertToOpenAPI(zodSchema, false))) {
315
- return {
316
- $ref: `#/components/schemas/${schemaName}`
317
- };
318
- }
319
- }
320
- }
321
- if ("$ref" in schema) return schema;
322
- if (schema.oneOf) {
323
- return {
324
- ...schema,
325
- oneOf: schema.oneOf.map((i) => maskWithReference(i, storedSchemas, true))
326
- };
327
- }
328
- if (schema.anyOf) {
329
- return {
330
- ...schema,
331
- anyOf: schema.anyOf.map((i) => maskWithReference(i, storedSchemas, true))
332
- };
333
- }
334
- switch (schema.type) {
335
- case "object":
336
- return {
337
- ...schema,
338
- properties: Object.entries(schema.properties ?? {}).reduce((props, [propName, prop]) => ({
339
- ...props,
340
- [propName]: maskWithReference(prop, storedSchemas, true)
341
- }), {})
342
- };
343
- case "array":
344
- if (Array.isArray(schema.items)) {
345
- return {
346
- ...schema,
347
- items: schema.items.map((i) => maskWithReference(i, storedSchemas, true))
348
- };
349
- }
350
- return {
351
- ...schema,
352
- items: maskWithReference(schema.items, storedSchemas, true)
353
- };
354
- }
355
- return schema;
356
- }
357
-
358
- // src/core/operation-mask.ts
359
- function maskSchema(storedSchemas, schema) {
360
- if (!schema) return schema;
361
- return maskWithReference(schema, storedSchemas, true);
362
- }
363
- function maskParameterSchema(param, storedSchemas) {
364
- if ("$ref" in param) return param;
365
- return { ...param, schema: maskSchema(storedSchemas, param.schema) };
366
- }
367
- function maskContentSchema(storedSchemas, bodyContent) {
368
- if (!bodyContent) return bodyContent;
369
- return Object.entries(bodyContent).reduce((collection, [contentType, content]) => ({
370
- ...collection,
371
- [contentType]: {
372
- ...content,
373
- schema: maskSchema(storedSchemas, content.schema)
374
- }
375
- }), {});
376
- }
377
- function maskRequestBodySchema(storedSchemas, body) {
378
- if (!body || "$ref" in body) return body;
379
- return { ...body, content: maskContentSchema(storedSchemas, body.content) };
380
- }
381
- function maskResponseSchema(storedSchemas, response) {
382
- if ("$ref" in response) return response;
383
- return { ...response, content: maskContentSchema(storedSchemas, response.content) };
384
- }
385
- function maskSchemasInResponses(storedSchemas, responses) {
386
- if (!responses) return responses;
387
- return Object.entries(responses).reduce((collection, [key, response]) => ({
388
- ...collection,
389
- [key]: maskResponseSchema(storedSchemas, response)
390
- }), {});
391
- }
392
- function maskOperationSchemas(operation, storedSchemas) {
393
- return {
394
- ...operation,
395
- parameters: operation.parameters?.map((p) => maskParameterSchema(p, storedSchemas)),
396
- requestBody: maskRequestBodySchema(storedSchemas, operation.requestBody),
397
- responses: maskSchemasInResponses(storedSchemas, operation.responses)
398
- };
399
- }
400
-
401
- // src/core/route.ts
402
- function createRouteRecord(method, filePath, rootPath, apiData) {
403
- return {
404
- method: method.toLocaleLowerCase(),
405
- path: getRoutePathName(filePath, rootPath),
406
- apiData
407
- };
408
- }
409
- function bundlePaths(source, storedSchemas) {
410
- source.sort((a, b) => a.path.localeCompare(b.path));
411
- return source.reduce((collection, route) => ({
412
- ...collection,
413
- [route.path]: {
414
- ...collection[route.path],
415
- [route.method]: maskOperationSchemas(route.apiData, storedSchemas)
416
- }
417
- }), {});
418
- }
419
-
420
308
  // src/core/schema.ts
421
309
  function bundleSchemas(schemas) {
422
- const bundledSchemas = Object.keys(schemas).reduce((collection, schemaName) => {
423
- return {
424
- ...collection,
425
- [schemaName]: convertToOpenAPI(schemas[schemaName], false)
426
- };
427
- }, {});
428
- return Object.entries(bundledSchemas).reduce((bundle, [schemaName, schema]) => ({
429
- ...bundle,
430
- [schemaName]: maskWithReference(schema, schemas, false)
431
- }), {});
310
+ const entries = Object.entries(schemas).map(([schemaName, schema]) => {
311
+ return [schemaName, convertToOpenAPI(schema, false)];
312
+ });
313
+ return Object.fromEntries(entries);
432
314
  }
433
315
 
434
316
  // src/core/generateOpenApiSpec.ts
@@ -466,13 +348,13 @@ async function generateOpenApiSpec(schemas, {
466
348
  }
467
349
  const metadata = getPackageMetadata();
468
350
  const pathsAndComponents = {
469
- paths: bundlePaths(validRoutes, schemas),
351
+ paths: bundlePaths(validRoutes),
470
352
  components: {
471
353
  schemas: bundleSchemas(schemas),
472
354
  securitySchemes
473
355
  }
474
356
  };
475
- return JSON.parse(JSON.stringify({
357
+ const spec = JSON.parse(JSON.stringify({
476
358
  openapi: "3.1.0",
477
359
  info: {
478
360
  title: metadata.serviceName,
@@ -484,6 +366,7 @@ async function generateOpenApiSpec(schemas, {
484
366
  security,
485
367
  tags: []
486
368
  }));
369
+ return optimizeOpenApiSpec(spec);
487
370
  }
488
371
 
489
372
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omer-x/next-openapi-json-generator",
3
- "version": "2.0.3",
3
+ "version": "2.1.0-rc.1",
4
4
  "description": "a Next.js plugin to generate OpenAPI documentation from route handlers",
5
5
  "keywords": [
6
6
  "next.js",
@@ -40,6 +40,8 @@
40
40
  }
41
41
  },
42
42
  "scripts": {
43
+ "lint": "eslint --flag unstable_native_nodejs_ts_config",
44
+ "lint:fix": "eslint --fix --flag unstable_native_nodejs_ts_config",
43
45
  "test": "vitest run --coverage",
44
46
  "test:watch": "vitest --coverage",
45
47
  "dev": "tsup --watch",
@@ -48,20 +50,21 @@
48
50
  "peerDependencies": {
49
51
  "@omer-x/next-openapi-route-handler": "^2",
50
52
  "@omer-x/openapi-types": "^1",
51
- "zod": "^4",
52
- "typescript": "^5"
53
+ "typescript": "^5",
54
+ "zod": "^4"
53
55
  },
54
56
  "dependencies": {
57
+ "@omer-x/openapi-optimizer": "alpha",
55
58
  "@omer-x/package-metadata": "^1.0.2",
56
59
  "minimatch": "^10.1.1"
57
60
  },
58
61
  "devDependencies": {
59
62
  "@omer-x/eslint-config": "^2.2.6",
60
- "@types/node": "^25.0.1",
61
- "@vitest/coverage-v8": "^4.0.15",
63
+ "@types/node": "^25.0.3",
64
+ "@vitest/coverage-v8": "^4.0.16",
62
65
  "eslint": "^9.39.2",
63
66
  "semantic-release": "^25.0.2",
64
67
  "tsup": "^8.5.1",
65
- "vitest": "^4.0.15"
68
+ "vitest": "^4.0.16"
66
69
  }
67
70
  }