next-openapi-gen 0.8.7 → 0.8.9

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/README.md CHANGED
@@ -201,6 +201,7 @@ export async function POST(request: NextRequest) {
201
201
  | Tag | Description |
202
202
  | ---------------------- | ------------------------------------------------------------------------------------------------------------------------ |
203
203
  | `@description` | Endpoint description |
204
+ | `@operationId` | Custom operation ID (overrides auto-generated ID) |
204
205
  | `@pathParams` | Path parameters type/schema |
205
206
  | `@params` | Query parameters type/schema |
206
207
  | `@body` | Request body type/schema |
@@ -424,6 +425,23 @@ export async function GET() {
424
425
  }
425
426
  ```
426
427
 
428
+ ### Custom Operation ID
429
+
430
+ ```typescript
431
+ // src/app/api/users/[id]/route.ts
432
+
433
+ /**
434
+ * Get user by ID
435
+ * @operationId getUserById
436
+ * @pathParams UserParams
437
+ * @response UserResponse
438
+ */
439
+ export async function GET() {
440
+ // ...
441
+ }
442
+ // Generates: operationId: "getUserById" instead of auto-generated "get-users-{id}"
443
+ ```
444
+
427
445
  ### File Uploads / Multipart Form Data
428
446
 
429
447
  ```typescript
@@ -766,6 +784,51 @@ Factory functions work with any naming convention and support:
766
784
  - Imported schemas
767
785
  - Multiple factory patterns in the same project
768
786
 
787
+ ### Schema Composition with `.extend()`
788
+
789
+ Zod's `.extend()` method allows you to build upon existing schemas:
790
+
791
+ ```typescript
792
+ // src/schemas/user.ts
793
+
794
+ // Base user schema
795
+ export const BaseUserSchema = z.object({
796
+ id: z.string().uuid().describe("User ID"),
797
+ email: z.string().email().describe("Email address"),
798
+ });
799
+
800
+ // Extend with additional fields
801
+ export const UserProfileSchema = BaseUserSchema.extend({
802
+ name: z.string().describe("Full name"),
803
+ bio: z.string().optional().describe("User biography"),
804
+ });
805
+
806
+ // Multiple levels of extension
807
+ export const AdminUserSchema = UserProfileSchema.extend({
808
+ role: z.enum(["admin", "moderator"]).describe("Admin role"),
809
+ permissions: z.array(z.string()).describe("Permission list"),
810
+ });
811
+
812
+ export const UserIdParams = z.object({
813
+ id: z.string().uuid().describe("User ID"),
814
+ });
815
+
816
+ // src/app/api/users/[id]/route.ts
817
+
818
+ /**
819
+ * Get user profile
820
+ * @pathParams UserIdParams
821
+ * @response UserProfileSchema
822
+ * @openapi
823
+ */
824
+ export async function GET(
825
+ request: NextRequest,
826
+ { params }: { params: { id: string } }
827
+ ) {
828
+ // Returns: { id, email, name, bio? }
829
+ }
830
+ ```
831
+
769
832
  ### Drizzle-Zod Support
770
833
 
771
834
  The library fully supports **drizzle-zod** for generating Zod schemas from Drizzle ORM table definitions. This provides a single source of truth for your database schema, validation, and API documentation.
@@ -248,7 +248,7 @@ export class RouteProcessor {
248
248
  const method = varName.toLowerCase();
249
249
  const routePath = this.getRoutePath(filePath);
250
250
  const rootPath = capitalize(routePath.split("/")[1]);
251
- const operationId = getOperationId(routePath, method);
251
+ const operationId = dataTypes.operationId || getOperationId(routePath, method);
252
252
  const { tag, summary, description, auth, isOpenApi, deprecated, bodyDescription, responseDescription, } = dataTypes;
253
253
  if (this.config.includeOpenApiRoutes && !isOpenApi) {
254
254
  // If flag is enabled and there is no @openapi tag, then skip path
package/dist/lib/utils.js CHANGED
@@ -34,6 +34,7 @@ export function extractJSDocComments(path) {
34
34
  let responseSet = "";
35
35
  let addResponses = "";
36
36
  let successCode = "";
37
+ let operationId = "";
37
38
  if (comments) {
38
39
  comments.forEach((comment) => {
39
40
  const commentValue = cleanComment(comment.value);
@@ -124,6 +125,13 @@ export function extractJSDocComments(path) {
124
125
  addResponses = match[1].trim();
125
126
  }
126
127
  }
128
+ if (commentValue.includes("@operationId")) {
129
+ const regex = /@operationId\s+(\S+)/;
130
+ const match = commentValue.match(regex);
131
+ if (match && match[1]) {
132
+ operationId = match[1].trim();
133
+ }
134
+ }
127
135
  if (commentValue.includes("@response")) {
128
136
  // Updated regex to support generic types
129
137
  const responseMatch = commentValue.match(/@response\s+(?:(\d+):)?([^@\n\r]+)(?:\s+(.*))?/);
@@ -156,6 +164,7 @@ export function extractJSDocComments(path) {
156
164
  responseSet,
157
165
  addResponses,
158
166
  successCode,
167
+ operationId,
159
168
  };
160
169
  }
161
170
  export function extractTypeFromComment(commentValue, tag) {
@@ -400,6 +400,54 @@ export class ZodSchemaConverter {
400
400
  });
401
401
  }
402
402
  break;
403
+ case "extend":
404
+ // Extend the schema with new properties
405
+ if (node.arguments.length > 0 &&
406
+ t.isObjectExpression(node.arguments[0])) {
407
+ const extensionProperties = {};
408
+ const extensionRequired = [];
409
+ node.arguments[0].properties.forEach((prop) => {
410
+ if (t.isObjectProperty(prop)) {
411
+ const key = t.isIdentifier(prop.key)
412
+ ? prop.key.name
413
+ : t.isStringLiteral(prop.key)
414
+ ? prop.key.value
415
+ : null;
416
+ if (key) {
417
+ // Process the Zod type for this property
418
+ const propSchema = this.processZodNode(prop.value);
419
+ if (propSchema) {
420
+ extensionProperties[key] = propSchema;
421
+ // Check if the schema itself has nullable set (which processZodNode sets for optional fields)
422
+ const isOptional = propSchema.nullable === true;
423
+ if (!isOptional) {
424
+ extensionRequired.push(key);
425
+ }
426
+ }
427
+ }
428
+ }
429
+ });
430
+ // Merge with existing schema
431
+ if (schema.properties) {
432
+ schema.properties = {
433
+ ...schema.properties,
434
+ ...extensionProperties,
435
+ };
436
+ }
437
+ else {
438
+ schema.properties = extensionProperties;
439
+ }
440
+ // Merge required arrays
441
+ if (extensionRequired.length > 0) {
442
+ schema.required = [
443
+ ...(schema.required || []),
444
+ ...extensionRequired,
445
+ ];
446
+ // Deduplicate
447
+ schema.required = [...new Set(schema.required)];
448
+ }
449
+ }
450
+ break;
403
451
  }
404
452
  return schema;
405
453
  };
@@ -989,8 +1037,9 @@ export class ZodSchemaConverter {
989
1037
  properties,
990
1038
  };
991
1039
  if (required.length > 0) {
1040
+ // Deduplicate required array using Set
992
1041
  // @ts-ignore
993
- schema.required = required;
1042
+ schema.required = [...new Set(required)];
994
1043
  }
995
1044
  return schema;
996
1045
  }
@@ -1371,7 +1420,24 @@ export class ZodSchemaConverter {
1371
1420
  if (node.arguments.length > 0 &&
1372
1421
  t.isObjectExpression(node.arguments[0])) {
1373
1422
  // Get the base schema by processing the object that extend is called on
1374
- const baseSchema = this.processZodNode(node.callee.object);
1423
+ const baseSchemaResult = this.processZodNode(node.callee.object);
1424
+ // If it's a reference, resolve it to the actual schema
1425
+ let baseSchema = baseSchemaResult;
1426
+ if (baseSchemaResult && baseSchemaResult.$ref) {
1427
+ const schemaName = baseSchemaResult.$ref.replace("#/components/schemas/", "");
1428
+ // Try to convert the base schema if not already processed
1429
+ if (!this.zodSchemas[schemaName]) {
1430
+ logger.debug(`[extend] Base schema ${schemaName} not found, attempting to convert it`);
1431
+ this.convertZodSchemaToOpenApi(schemaName);
1432
+ }
1433
+ // Now retrieve the converted schema
1434
+ if (this.zodSchemas[schemaName]) {
1435
+ baseSchema = this.zodSchemas[schemaName];
1436
+ }
1437
+ else {
1438
+ logger.debug(`Could not resolve reference for extend: ${schemaName}`);
1439
+ }
1440
+ }
1375
1441
  // Process the extension object
1376
1442
  const extendNode = {
1377
1443
  type: "CallExpression",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-openapi-gen",
3
- "version": "0.8.7",
3
+ "version": "0.8.9",
4
4
  "description": "Automatically generate OpenAPI 3.0 documentation from Next.js projects, with support for Zod schemas and TypeScript types.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",