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 +63 -0
- package/dist/lib/route-processor.js +1 -1
- package/dist/lib/utils.js +9 -0
- package/dist/lib/zod-converter.js +68 -2
- package/package.json +1 -1
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
|
|
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