@omer-x/next-openapi-json-generator 2.0.2 → 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 +43 -133
  2. package/dist/index.js +43 -133
  3. package/package.json +14 -11
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,125 +282,6 @@ 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
- });
297
- }
298
- case "function":
299
- case "symbol":
300
- return false;
301
- default:
302
- return a === b;
303
- }
304
- }
305
-
306
- // src/core/zod-to-openapi.ts
307
- var import_zod2 = require("zod");
308
- function convertToOpenAPI(schema, isArray) {
309
- return import_zod2.z.toJSONSchema(isArray ? schema.array() : schema);
310
- }
311
-
312
- // src/core/mask.ts
313
- function maskWithReference(schema, storedSchemas, self) {
314
- if (self) {
315
- for (const [schemaName, zodSchema] of Object.entries(storedSchemas)) {
316
- if (deepEqual(schema, convertToOpenAPI(zodSchema, false))) {
317
- return {
318
- $ref: `#/components/schemas/${schemaName}`
319
- };
320
- }
321
- }
322
- }
323
- if ("$ref" in schema) return schema;
324
- if (schema.oneOf) {
325
- return {
326
- ...schema,
327
- oneOf: schema.oneOf.map((i) => maskWithReference(i, storedSchemas, true))
328
- };
329
- }
330
- if (schema.anyOf) {
331
- return {
332
- ...schema,
333
- anyOf: schema.anyOf.map((i) => maskWithReference(i, storedSchemas, true))
334
- };
335
- }
336
- switch (schema.type) {
337
- case "object":
338
- return {
339
- ...schema,
340
- properties: Object.entries(schema.properties ?? {}).reduce((props, [propName, prop]) => ({
341
- ...props,
342
- [propName]: maskWithReference(prop, storedSchemas, true)
343
- }), {})
344
- };
345
- case "array":
346
- if (Array.isArray(schema.items)) {
347
- return {
348
- ...schema,
349
- items: schema.items.map((i) => maskWithReference(i, storedSchemas, true))
350
- };
351
- }
352
- return {
353
- ...schema,
354
- items: maskWithReference(schema.items, storedSchemas, true)
355
- };
356
- }
357
- return schema;
358
- }
359
-
360
- // src/core/operation-mask.ts
361
- function maskSchema(storedSchemas, schema) {
362
- if (!schema) return schema;
363
- return maskWithReference(schema, storedSchemas, true);
364
- }
365
- function maskParameterSchema(param, storedSchemas) {
366
- if ("$ref" in param) return param;
367
- return { ...param, schema: maskSchema(storedSchemas, param.schema) };
368
- }
369
- function maskContentSchema(storedSchemas, bodyContent) {
370
- if (!bodyContent) return bodyContent;
371
- return Object.entries(bodyContent).reduce((collection, [contentType, content]) => ({
372
- ...collection,
373
- [contentType]: {
374
- ...content,
375
- schema: maskSchema(storedSchemas, content.schema)
376
- }
377
- }), {});
378
- }
379
- function maskRequestBodySchema(storedSchemas, body) {
380
- if (!body || "$ref" in body) return body;
381
- return { ...body, content: maskContentSchema(storedSchemas, body.content) };
382
- }
383
- function maskResponseSchema(storedSchemas, response) {
384
- if ("$ref" in response) return response;
385
- return { ...response, content: maskContentSchema(storedSchemas, response.content) };
386
- }
387
- function maskSchemasInResponses(storedSchemas, responses) {
388
- if (!responses) return responses;
389
- return Object.entries(responses).reduce((collection, [key, response]) => ({
390
- ...collection,
391
- [key]: maskResponseSchema(storedSchemas, response)
392
- }), {});
393
- }
394
- function maskOperationSchemas(operation, storedSchemas) {
395
- return {
396
- ...operation,
397
- parameters: operation.parameters?.map((p) => maskParameterSchema(p, storedSchemas)),
398
- requestBody: maskRequestBodySchema(storedSchemas, operation.requestBody),
399
- responses: maskSchemasInResponses(storedSchemas, operation.responses)
400
- };
401
- }
402
-
403
285
  // src/core/route.ts
404
286
  function createRouteRecord(method, filePath, rootPath, apiData) {
405
287
  return {
@@ -408,29 +290,56 @@ function createRouteRecord(method, filePath, rootPath, apiData) {
408
290
  apiData
409
291
  };
410
292
  }
411
- function bundlePaths(source, storedSchemas) {
293
+ function bundlePaths(source) {
412
294
  source.sort((a, b) => a.path.localeCompare(b.path));
413
295
  return source.reduce((collection, route) => ({
414
296
  ...collection,
415
297
  [route.path]: {
416
298
  ...collection[route.path],
417
- [route.method]: maskOperationSchemas(route.apiData, storedSchemas)
299
+ [route.method]: route.apiData
418
300
  }
419
301
  }), {});
420
302
  }
421
303
 
304
+ // src/core/zod-to-openapi.ts
305
+ var import_zod2 = require("zod");
306
+ function fixSchema(schema) {
307
+ if ("unwrap" in schema && typeof schema.unwrap === "function") {
308
+ switch (schema._zod.def.type) {
309
+ case "nullable":
310
+ return fixSchema(schema.unwrap()).nullable();
311
+ case "optional":
312
+ return fixSchema(schema.unwrap()).optional();
313
+ case "readonly":
314
+ return fixSchema(schema.unwrap()).readonly();
315
+ case "array":
316
+ return fixSchema(schema.unwrap()).array();
317
+ default:
318
+ throw new Error(`${schema._zod.def.type} type is not covered in fixSchema (@omer-x/next-openapi-json-generator")`);
319
+ }
320
+ }
321
+ if (schema._zod.def.type === "date") {
322
+ return import_zod2.z.iso.datetime();
323
+ }
324
+ if (schema._zod.def.type === "object") {
325
+ const { shape } = schema;
326
+ const entries = Object.entries(shape);
327
+ const alteredEntries = entries.map(([propName, prop]) => [propName, fixSchema(prop)]);
328
+ const newShape = Object.fromEntries(alteredEntries);
329
+ return import_zod2.z.object(newShape);
330
+ }
331
+ return schema;
332
+ }
333
+ function convertToOpenAPI(schema, isArray) {
334
+ return import_zod2.z.toJSONSchema(fixSchema(isArray ? schema.array() : schema));
335
+ }
336
+
422
337
  // src/core/schema.ts
423
338
  function bundleSchemas(schemas) {
424
- const bundledSchemas = Object.keys(schemas).reduce((collection, schemaName) => {
425
- return {
426
- ...collection,
427
- [schemaName]: convertToOpenAPI(schemas[schemaName], false)
428
- };
429
- }, {});
430
- return Object.entries(bundledSchemas).reduce((bundle, [schemaName, schema]) => ({
431
- ...bundle,
432
- [schemaName]: maskWithReference(schema, schemas, false)
433
- }), {});
339
+ const entries = Object.entries(schemas).map(([schemaName, schema]) => {
340
+ return [schemaName, convertToOpenAPI(schema, false)];
341
+ });
342
+ return Object.fromEntries(entries);
434
343
  }
435
344
 
436
345
  // src/core/generateOpenApiSpec.ts
@@ -468,13 +377,13 @@ async function generateOpenApiSpec(schemas, {
468
377
  }
469
378
  const metadata = (0, import_package_metadata.default)();
470
379
  const pathsAndComponents = {
471
- paths: bundlePaths(validRoutes, schemas),
380
+ paths: bundlePaths(validRoutes),
472
381
  components: {
473
382
  schemas: bundleSchemas(schemas),
474
383
  securitySchemes
475
384
  }
476
385
  };
477
- return JSON.parse(JSON.stringify({
386
+ const spec = JSON.parse(JSON.stringify({
478
387
  openapi: "3.1.0",
479
388
  info: {
480
389
  title: metadata.serviceName,
@@ -486,6 +395,7 @@ async function generateOpenApiSpec(schemas, {
486
395
  security,
487
396
  tags: []
488
397
  }));
398
+ return (0, import_openapi_optimizer.optimizeOpenApiSpec)(spec);
489
399
  }
490
400
 
491
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,125 +253,6 @@ 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
- });
268
- }
269
- case "function":
270
- case "symbol":
271
- return false;
272
- default:
273
- return a === b;
274
- }
275
- }
276
-
277
- // src/core/zod-to-openapi.ts
278
- import { z as z2 } from "zod";
279
- function convertToOpenAPI(schema, isArray) {
280
- return z2.toJSONSchema(isArray ? schema.array() : schema);
281
- }
282
-
283
- // src/core/mask.ts
284
- function maskWithReference(schema, storedSchemas, self) {
285
- if (self) {
286
- for (const [schemaName, zodSchema] of Object.entries(storedSchemas)) {
287
- if (deepEqual(schema, convertToOpenAPI(zodSchema, false))) {
288
- return {
289
- $ref: `#/components/schemas/${schemaName}`
290
- };
291
- }
292
- }
293
- }
294
- if ("$ref" in schema) return schema;
295
- if (schema.oneOf) {
296
- return {
297
- ...schema,
298
- oneOf: schema.oneOf.map((i) => maskWithReference(i, storedSchemas, true))
299
- };
300
- }
301
- if (schema.anyOf) {
302
- return {
303
- ...schema,
304
- anyOf: schema.anyOf.map((i) => maskWithReference(i, storedSchemas, true))
305
- };
306
- }
307
- switch (schema.type) {
308
- case "object":
309
- return {
310
- ...schema,
311
- properties: Object.entries(schema.properties ?? {}).reduce((props, [propName, prop]) => ({
312
- ...props,
313
- [propName]: maskWithReference(prop, storedSchemas, true)
314
- }), {})
315
- };
316
- case "array":
317
- if (Array.isArray(schema.items)) {
318
- return {
319
- ...schema,
320
- items: schema.items.map((i) => maskWithReference(i, storedSchemas, true))
321
- };
322
- }
323
- return {
324
- ...schema,
325
- items: maskWithReference(schema.items, storedSchemas, true)
326
- };
327
- }
328
- return schema;
329
- }
330
-
331
- // src/core/operation-mask.ts
332
- function maskSchema(storedSchemas, schema) {
333
- if (!schema) return schema;
334
- return maskWithReference(schema, storedSchemas, true);
335
- }
336
- function maskParameterSchema(param, storedSchemas) {
337
- if ("$ref" in param) return param;
338
- return { ...param, schema: maskSchema(storedSchemas, param.schema) };
339
- }
340
- function maskContentSchema(storedSchemas, bodyContent) {
341
- if (!bodyContent) return bodyContent;
342
- return Object.entries(bodyContent).reduce((collection, [contentType, content]) => ({
343
- ...collection,
344
- [contentType]: {
345
- ...content,
346
- schema: maskSchema(storedSchemas, content.schema)
347
- }
348
- }), {});
349
- }
350
- function maskRequestBodySchema(storedSchemas, body) {
351
- if (!body || "$ref" in body) return body;
352
- return { ...body, content: maskContentSchema(storedSchemas, body.content) };
353
- }
354
- function maskResponseSchema(storedSchemas, response) {
355
- if ("$ref" in response) return response;
356
- return { ...response, content: maskContentSchema(storedSchemas, response.content) };
357
- }
358
- function maskSchemasInResponses(storedSchemas, responses) {
359
- if (!responses) return responses;
360
- return Object.entries(responses).reduce((collection, [key, response]) => ({
361
- ...collection,
362
- [key]: maskResponseSchema(storedSchemas, response)
363
- }), {});
364
- }
365
- function maskOperationSchemas(operation, storedSchemas) {
366
- return {
367
- ...operation,
368
- parameters: operation.parameters?.map((p) => maskParameterSchema(p, storedSchemas)),
369
- requestBody: maskRequestBodySchema(storedSchemas, operation.requestBody),
370
- responses: maskSchemasInResponses(storedSchemas, operation.responses)
371
- };
372
- }
373
-
374
256
  // src/core/route.ts
375
257
  function createRouteRecord(method, filePath, rootPath, apiData) {
376
258
  return {
@@ -379,29 +261,56 @@ function createRouteRecord(method, filePath, rootPath, apiData) {
379
261
  apiData
380
262
  };
381
263
  }
382
- function bundlePaths(source, storedSchemas) {
264
+ function bundlePaths(source) {
383
265
  source.sort((a, b) => a.path.localeCompare(b.path));
384
266
  return source.reduce((collection, route) => ({
385
267
  ...collection,
386
268
  [route.path]: {
387
269
  ...collection[route.path],
388
- [route.method]: maskOperationSchemas(route.apiData, storedSchemas)
270
+ [route.method]: route.apiData
389
271
  }
390
272
  }), {});
391
273
  }
392
274
 
275
+ // src/core/zod-to-openapi.ts
276
+ import { z as z2 } from "zod";
277
+ function fixSchema(schema) {
278
+ if ("unwrap" in schema && typeof schema.unwrap === "function") {
279
+ switch (schema._zod.def.type) {
280
+ case "nullable":
281
+ return fixSchema(schema.unwrap()).nullable();
282
+ case "optional":
283
+ return fixSchema(schema.unwrap()).optional();
284
+ case "readonly":
285
+ return fixSchema(schema.unwrap()).readonly();
286
+ case "array":
287
+ return fixSchema(schema.unwrap()).array();
288
+ default:
289
+ throw new Error(`${schema._zod.def.type} type is not covered in fixSchema (@omer-x/next-openapi-json-generator")`);
290
+ }
291
+ }
292
+ if (schema._zod.def.type === "date") {
293
+ return z2.iso.datetime();
294
+ }
295
+ if (schema._zod.def.type === "object") {
296
+ const { shape } = schema;
297
+ const entries = Object.entries(shape);
298
+ const alteredEntries = entries.map(([propName, prop]) => [propName, fixSchema(prop)]);
299
+ const newShape = Object.fromEntries(alteredEntries);
300
+ return z2.object(newShape);
301
+ }
302
+ return schema;
303
+ }
304
+ function convertToOpenAPI(schema, isArray) {
305
+ return z2.toJSONSchema(fixSchema(isArray ? schema.array() : schema));
306
+ }
307
+
393
308
  // src/core/schema.ts
394
309
  function bundleSchemas(schemas) {
395
- const bundledSchemas = Object.keys(schemas).reduce((collection, schemaName) => {
396
- return {
397
- ...collection,
398
- [schemaName]: convertToOpenAPI(schemas[schemaName], false)
399
- };
400
- }, {});
401
- return Object.entries(bundledSchemas).reduce((bundle, [schemaName, schema]) => ({
402
- ...bundle,
403
- [schemaName]: maskWithReference(schema, schemas, false)
404
- }), {});
310
+ const entries = Object.entries(schemas).map(([schemaName, schema]) => {
311
+ return [schemaName, convertToOpenAPI(schema, false)];
312
+ });
313
+ return Object.fromEntries(entries);
405
314
  }
406
315
 
407
316
  // src/core/generateOpenApiSpec.ts
@@ -439,13 +348,13 @@ async function generateOpenApiSpec(schemas, {
439
348
  }
440
349
  const metadata = getPackageMetadata();
441
350
  const pathsAndComponents = {
442
- paths: bundlePaths(validRoutes, schemas),
351
+ paths: bundlePaths(validRoutes),
443
352
  components: {
444
353
  schemas: bundleSchemas(schemas),
445
354
  securitySchemes
446
355
  }
447
356
  };
448
- return JSON.parse(JSON.stringify({
357
+ const spec = JSON.parse(JSON.stringify({
449
358
  openapi: "3.1.0",
450
359
  info: {
451
360
  title: metadata.serviceName,
@@ -457,6 +366,7 @@ async function generateOpenApiSpec(schemas, {
457
366
  security,
458
367
  tags: []
459
368
  }));
369
+ return optimizeOpenApiSpec(spec);
460
370
  }
461
371
 
462
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.2",
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
- "minimatch": "^10.0.3"
59
+ "minimatch": "^10.1.1"
57
60
  },
58
61
  "devDependencies": {
59
- "@omer-x/eslint-config": "^2.1.3",
60
- "@types/node": "^24.0.14",
61
- "@vitest/coverage-v8": "^3.2.4",
62
- "eslint": "^9.31.0",
63
- "semantic-release": "^24.2.7",
64
- "tsup": "^8.5.0",
65
- "vitest": "^3.2.4"
62
+ "@omer-x/eslint-config": "^2.2.6",
63
+ "@types/node": "^25.0.3",
64
+ "@vitest/coverage-v8": "^4.0.16",
65
+ "eslint": "^9.39.2",
66
+ "semantic-release": "^25.0.2",
67
+ "tsup": "^8.5.1",
68
+ "vitest": "^4.0.16"
66
69
  }
67
70
  }