@omer-x/next-openapi-scaffold-generator 0.3.0 → 0.4.0-alpha.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 (2) hide show
  1. package/bin/index.js +98 -89
  2. package/package.json +12 -11
package/bin/index.js CHANGED
@@ -4,8 +4,8 @@
4
4
  import prompts from "prompts";
5
5
 
6
6
  // src/core/file.ts
7
- import fs from "node:fs/promises";
8
- import path from "node:path";
7
+ import fs from "fs/promises";
8
+ import path from "path";
9
9
  async function saveFile(directory, fileName, content) {
10
10
  const dirPath = path.resolve(process.cwd(), directory);
11
11
  await fs.mkdir(dirPath, { recursive: true });
@@ -18,6 +18,19 @@ async function appendFile(directory, fileName, content) {
18
18
  const filePath = path.resolve(dirPath, fileName);
19
19
  await fs.appendFile(filePath, content + "\n", "utf8");
20
20
  }
21
+ async function checkFile(directory, fileName, content) {
22
+ const dirPath = path.resolve(process.cwd(), directory);
23
+ const filePath = path.resolve(dirPath, fileName);
24
+ try {
25
+ const fileContent = await fs.readFile(filePath, "utf8");
26
+ return fileContent.includes(content);
27
+ } catch (err) {
28
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
29
+ return false;
30
+ }
31
+ throw err;
32
+ }
33
+ }
21
34
 
22
35
  // src/core/string.ts
23
36
  import * as changeCase from "change-case";
@@ -53,6 +66,10 @@ function capitalCase2(text, plural, onlyFirst) {
53
66
  }
54
67
  return changeCase.capitalCase(properText);
55
68
  }
69
+ function constantCase2(text, plural) {
70
+ const properText = plural ? pluralize(text) : pluralize.singular(text);
71
+ return changeCase.constantCase(properText);
72
+ }
56
73
 
57
74
  // src/core/template.ts
58
75
  import Handlebars from "handlebars";
@@ -60,11 +77,11 @@ function getTemplate(input) {
60
77
  return Handlebars.compile(input);
61
78
  }
62
79
 
63
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/api-route.hbs
64
- var api_route_default = 'import defineRoute from "@omer-x/next-openapi-route-handler";\nimport z from "zod";\nimport db from "~/database";\nimport { New{{ pascalCaseSingular }}DTO, {{ pascalCaseSingular }}DTO } from "~/models/{{ kebabCaseSingular }}";\nimport create{{ pascalCaseSingular }} from "~/operations/create{{ pascalCaseSingular }}";\nimport get{{ pascalCasePlural }} from "~/operations/get{{ pascalCasePlural }}";\n\n{{{ readAllOperation }}}\n\n{{{ createOperation }}}\n';
80
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/api-route.hbs
81
+ var api_route_default = 'import defineRoute from "@omer-x/next-openapi-route-handler";\nimport { count } from "drizzle-orm";\nimport z from "zod";\nimport db from "~/database";\nimport { {{ camelCasePlural }} } from "~/database/schema/{{ kebabCasePlural }}";\nimport { New{{ pascalCaseSingular }}DTO, {{ pascalCaseSingular }}DTO } from "~/models/{{ kebabCaseSingular }}";\nimport create{{ pascalCaseSingular }} from "~/operations/create{{ pascalCaseSingular }}";\nimport get{{ pascalCasePlural }} from "~/operations/get{{ pascalCasePlural }}";\n\n{{{ readAllOperation }}}\n\n{{{ createOperation }}}\n';
65
82
 
66
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/create.hbs
67
- var create_default = 'export const { POST } = defineRoute({\n operationId: "create{{ pascalCaseSingular }}",\n method: "POST",\n summary: "Create {{ noCaseSingular }}",\n description: "Create a new {{ noCaseSingular }}",\n tags: ["{{ capitalCasePlural }}"],\n requestBody: New{{ pascalCaseSingular }}DTO,\n action: async ({ body }) => {\n const {{ camelCaseSingular }} = await create{{ pascalCaseSingular }}(db, body);\n return Response.json({{ camelCaseSingular }}, { status: 201 });\n },\n responses: {\n 201: { description: "{{ onlyFirstCapitalCaseSingular }} created successfully", content: {{ pascalCaseSingular }}DTO },\n },\n});\n';
83
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/create.hbs
84
+ var create_default = 'export const { POST } = defineRoute({\n operationId: "create{{ pascalCaseSingular }}",\n method: "POST",\n summary: "Create {{ noCaseSingular }}",\n description: "Create a new {{ noCaseSingular }}",\n tags: ["{{ capitalCasePlural }}"],\n requestBody: New{{ pascalCaseSingular }}DTO,\n action: async ({ body }) => {\n const {{ camelCaseSingular }} = await create{{ pascalCaseSingular }}(db, body);\n return Response.json({{ camelCaseSingular }}, { status: 201 });\n },\n handleErrors(errorType, issues) {\n switch (errorType) {\n case "PARSE_REQUEST_BODY":\n return Response.json({ errorCode: "BAD_REQUEST" }, { status: 400 });\n default:\n // eslint-disable-next-line no-console\n console.log(`---------------[${errorType}]---------------`);\n // eslint-disable-next-line no-console\n console.log(issues);\n // eslint-disable-next-line no-console\n console.log("".padEnd(errorType.length + 32, "-"));\n return Response.json({ errorCode: "UNKNOWN_ERROR" }, { status: 500 });\n }\n },\n responses: {\n 201: { description: "{{ onlyFirstCapitalCaseSingular }} created successfully", content: {{ pascalCaseSingular }}DTO },\n 400: { description: "Bad Request", content: z.object({ errorCode: z.literal(["BAD_REQUEST"]) }) },\n 500: { description: "Internal Server Error", content: z.object({ errorCode: z.literal(["UNKNOWN_ERROR"]) }) },\n },\n});\n';
68
85
 
69
86
  // src/templates/routes/create.ts
70
87
  function generateCreateOperationRoute(modelName2) {
@@ -78,13 +95,14 @@ function generateCreateOperationRoute(modelName2) {
78
95
  });
79
96
  }
80
97
 
81
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/read-all.hbs
82
- var read_all_default = 'export const { GET } = defineRoute({\n operationId: "get{{ pascalCasePlural }}",\n method: "GET",\n summary: "Get all {{ noCasePlural }}",\n description: "Retrieve a list of {{ noCasePlural }}",\n tags: ["{{ capitalCasePlural }}"],\n queryParams: z.object({\n select: {{ pascalCaseSingular }}DTO.keyof().array().default([])\n .describe("List of the column names"),\n }),\n action: async ({ queryParams }) => {\n return Response.json(await get{{ pascalCasePlural }}(db, queryParams.select));\n },\n responses: {\n 200: { description: "Returns a list of {{ noCasePlural }}", content: {{ pascalCaseSingular }}DTO, isArray: true },\n },\n});\n';
98
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/read-all.hbs
99
+ var read_all_default = 'export const { GET } = defineRoute({\n operationId: "get{{ pascalCasePlural }}",\n method: "GET",\n summary: "Get all {{ noCasePlural }}",\n description: "Retrieve a list of {{ noCasePlural }}",\n tags: ["{{ capitalCasePlural }}"],\n queryParams: z.object({\n offset: z.int32().min(0),\n limit: z.int32().min(0),\n select: {{ pascalCaseSingular }}DTO.keyof().array().default([])\n .describe("List of the column names"),\n }),\n action: async ({ queryParams }) => {\n const [\n [total],\n items,\n ] = await Promise.all([\n db.select({ count: count() }).from({{ camelCasePlural }}),\n get{{ pascalCasePlural }}(db, queryParams),\n ]);\n return Response.json({\n items,\n pagination: {\n offset: queryParams.offset,\n limit: queryParams.limit,\n total: total.count,\n },\n });\n },\n handleErrors(errorType, issues) {\n switch (errorType) {\n case "PARSE_SEARCH_PARAMS":\n return Response.json({ errorCode: "BAD_REQUEST" }, { status: 400 });\n default:\n // eslint-disable-next-line no-console\n console.log(`---------------[${errorType}]---------------`);\n // eslint-disable-next-line no-console\n console.log(issues);\n // eslint-disable-next-line no-console\n console.log("".padEnd(errorType.length + 32, "-"));\n return Response.json({ errorCode: "UNKNOWN_ERROR" }, { status: 500 });\n }\n },\n responses: {\n 200: {\n description: "Returns a list of {{ noCasePlural }}",\n content: z.object({\n items: {{ pascalCaseSingular }}DTO.array(),\n pagination: z.object({\n offset: z.int32().min(0),\n limit: z.int32().min(0),\n total: z.int(),\n }),\n }),\n },\n 400: { description: "Bad Request", content: z.object({ errorCode: z.literal(["BAD_REQUEST"]) }) },\n 500: { description: "Internal Server Error", content: z.object({ errorCode: z.literal(["UNKNOWN_ERROR"]) }) },\n },\n});\n';
83
100
 
84
101
  // src/templates/routes/read-all.ts
85
102
  function generateReadAllOperationRoute(modelName2) {
86
103
  const template = getTemplate(read_all_default);
87
104
  return template({
105
+ camelCasePlural: camelCase2(modelName2, true),
88
106
  capitalCasePlural: capitalCase2(modelName2, true, false),
89
107
  noCasePlural: noCase2(modelName2, true),
90
108
  pascalCaseSingular: pascalCase2(modelName2, false),
@@ -96,6 +114,7 @@ function generateReadAllOperationRoute(modelName2) {
96
114
  function generateApiRoute(modelName2) {
97
115
  const template = getTemplate(api_route_default);
98
116
  return template({
117
+ camelCasePlural: camelCase2(modelName2, true),
99
118
  kebabCasePlural: kebabCase2(modelName2, true),
100
119
  kebabCaseSingular: kebabCase2(modelName2, false),
101
120
  pascalCasePlural: pascalCase2(modelName2, true),
@@ -105,11 +124,11 @@ function generateApiRoute(modelName2) {
105
124
  });
106
125
  }
107
126
 
108
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/api-route-with-id.hbs
109
- var api_route_with_id_default = 'import defineRoute from "@omer-x/next-openapi-route-handler";\nimport db from "~/database";\nimport { {{ pascalCaseSingular }}DTO, {{ pascalCaseSingular }}PatchDTO } from "~/models/{{ kebabCaseSingular }}";\nimport delete{{ pascalCaseSingular }} from "~/operations/delete{{ pascalCaseSingular }}";\nimport get{{ pascalCaseSingular }} from "~/operations/get{{ pascalCaseSingular }}";\nimport update{{ pascalCaseSingular }} from "~/operations/update{{ pascalCaseSingular }}";\n\n{{{ readOperation }}}\n\n{{{ updateOperation }}}\n\n{{{ deleteOperation }}}\n';
127
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/api-route-with-id.hbs
128
+ var api_route_with_id_default = 'import defineRoute from "@omer-x/next-openapi-route-handler";\nimport z from "zod";\nimport db from "~/database";\nimport { {{ pascalCaseSingular }}DTO, {{ pascalCaseSingular }}PatchDTO } from "~/models/{{ kebabCaseSingular }}";\nimport delete{{ pascalCaseSingular }} from "~/operations/delete{{ pascalCaseSingular }}";\nimport get{{ pascalCaseSingular }} from "~/operations/get{{ pascalCaseSingular }}";\nimport update{{ pascalCaseSingular }} from "~/operations/update{{ pascalCaseSingular }}";\n\n{{{ readOperation }}}\n\n{{{ updateOperation }}}\n\n{{{ deleteOperation }}}\n';
110
129
 
111
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/delete.hbs
112
- var delete_default = 'export const { DELETE } = defineRoute({\n operationId: "delete{{ pascalCaseSingular }}",\n method: "DELETE",\n summary: "Delete {{ noCaseSingular }}",\n description: "Delete a specific {{ noCaseSingular }} by ID",\n tags: ["{{ capitalCasePlural }}"],\n pathParams: {{ pascalCaseSingular }}DTO.pick({ id: true }),\n action: async ({ pathParams }) => {\n const {{ camelCaseSingular }} = await delete{{ pascalCaseSingular }}(db, pathParams.id);\n if ({{ camelCaseSingular }}?.id === pathParams.id) {\n return new Response(null, { status: 204 });\n }\n return new Response(null, { status: 404 });\n },\n responses: {\n 204: { description: "{{ onlyFirstCapitalCaseSingular }} deleted successfully" },\n 404: { description: "{{ onlyFirstCapitalCaseSingular }} not found" },\n },\n});\n';
130
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/delete.hbs
131
+ var delete_default = 'export const { DELETE } = defineRoute({\n operationId: "delete{{ pascalCaseSingular }}",\n method: "DELETE",\n summary: "Delete {{ noCaseSingular }}",\n description: "Delete a specific {{ noCaseSingular }} by ID",\n tags: ["{{ capitalCasePlural }}"],\n pathParams: {{ pascalCaseSingular }}DTO.pick({ id: true }),\n action: async ({ pathParams }) => {\n const {{ camelCaseSingular }} = await delete{{ pascalCaseSingular }}(db, pathParams.id);\n if ({{ camelCaseSingular }}?.id === pathParams.id) {\n return new Response(null, { status: 204 });\n }\n return Response.json({ errorCode: "{{ constantCaseSingular }}_NOT_FOUND" }, { status: 404 });\n },\n handleErrors(errorType, issues) {\n switch (errorType) {\n case "PARSE_PATH_PARAMS":\n return Response.json({ errorCode: "{{ constantCaseSingular }}_NOT_FOUND" }, { status: 404 });\n default:\n // eslint-disable-next-line no-console\n console.log(`---------------[${errorType}]---------------`);\n // eslint-disable-next-line no-console\n console.log(issues);\n // eslint-disable-next-line no-console\n console.log("".padEnd(errorType.length + 32, "-"));\n return Response.json({ errorCode: "UNKNOWN_ERROR" }, { status: 500 });\n }\n },\n responses: {\n 204: { description: "{{ onlyFirstCapitalCaseSingular }} deleted successfully" },\n 404: { description: "{{ onlyFirstCapitalCaseSingular }} not found", content: z.object({ errorCode: z.literal(["{{ constantCaseSingular }}_NOT_FOUND"]) }) },\n 500: { description: "Internal Server Error", content: z.object({ errorCode: z.literal(["UNKNOWN_ERROR"]) }) },\n },\n});\n';
113
132
 
114
133
  // src/templates/routes/delete.ts
115
134
  function generateDeleteOperationRoute(modelName2) {
@@ -117,14 +136,15 @@ function generateDeleteOperationRoute(modelName2) {
117
136
  return template({
118
137
  camelCaseSingular: camelCase2(modelName2, false),
119
138
  capitalCasePlural: capitalCase2(modelName2, true, false),
139
+ constantCaseSingular: constantCase2(modelName2, false),
120
140
  noCaseSingular: noCase2(modelName2, false),
121
141
  onlyFirstCapitalCaseSingular: capitalCase2(modelName2, false, true),
122
142
  pascalCaseSingular: pascalCase2(modelName2, false)
123
143
  });
124
144
  }
125
145
 
126
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/read.hbs
127
- var read_default = 'export const { GET } = defineRoute({\n operationId: "get{{ pascalCaseSingular }}",\n method: "GET",\n summary: "Get {{ noCaseSingular }}",\n description: "Get a specific {{ noCaseSingular }} by ID",\n tags: ["{{ capitalCasePlural }}"],\n pathParams: {{ pascalCaseSingular }}DTO.pick({ id: true }),\n action: async ({ pathParams }) => {\n const {{ camelCaseSingular }} = await get{{ pascalCaseSingular }}(db, pathParams.id);\n if ({{ camelCaseSingular }}) {\n return Response.json({{ camelCaseSingular }});\n }\n return new Response(null, { status: 404 });\n },\n responses: {\n 200: { description: "{{ onlyFirstCapitalCaseSingular }} found", content: {{ pascalCaseSingular }}DTO },\n 404: { description: "{{ onlyFirstCapitalCaseSingular }} not found" },\n },\n});\n';
146
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/read.hbs
147
+ var read_default = 'export const { GET } = defineRoute({\n operationId: "get{{ pascalCaseSingular }}",\n method: "GET",\n summary: "Get {{ noCaseSingular }}",\n description: "Get a specific {{ noCaseSingular }} by ID",\n tags: ["{{ capitalCasePlural }}"],\n pathParams: {{ pascalCaseSingular }}DTO.pick({ id: true }),\n action: async ({ pathParams }) => {\n const {{ camelCaseSingular }} = await get{{ pascalCaseSingular }}(db, pathParams.id);\n if ({{ camelCaseSingular }}) {\n return Response.json({{ camelCaseSingular }});\n }\n return Response.json({ errorCode: "{{ constantCaseSingular }}_NOT_FOUND" }, { status: 404 });\n },\n handleErrors(errorType, issues) {\n switch (errorType) {\n case "PARSE_PATH_PARAMS":\n return Response.json({ errorCode: "{{ constantCaseSingular }}_NOT_FOUND" }, { status: 404 });\n default:\n // eslint-disable-next-line no-console\n console.log(`---------------[${errorType}]---------------`);\n // eslint-disable-next-line no-console\n console.log(issues);\n // eslint-disable-next-line no-console\n console.log("".padEnd(errorType.length + 32, "-"));\n return Response.json({ errorCode: "UNKNOWN_ERROR" }, { status: 500 });\n }\n },\n responses: {\n 200: { description: "{{ onlyFirstCapitalCaseSingular }} found", content: {{ pascalCaseSingular }}DTO },\n 404: { description: "{{ onlyFirstCapitalCaseSingular }} not found", content: z.object({ errorCode: z.literal(["{{ constantCaseSingular }}_NOT_FOUND"]) }) },\n 500: { description: "Internal Server Error", content: z.object({ errorCode: z.literal(["UNKNOWN_ERROR"]) }) },\n },\n});\n';
128
148
 
129
149
  // src/templates/routes/read.ts
130
150
  function generateReadOperationRoute(modelName2) {
@@ -132,14 +152,15 @@ function generateReadOperationRoute(modelName2) {
132
152
  return template({
133
153
  camelCaseSingular: camelCase2(modelName2, false),
134
154
  capitalCasePlural: capitalCase2(modelName2, true, false),
155
+ constantCaseSingular: constantCase2(modelName2, false),
135
156
  noCaseSingular: noCase2(modelName2, false),
136
157
  onlyFirstCapitalCaseSingular: capitalCase2(modelName2, false, true),
137
158
  pascalCaseSingular: pascalCase2(modelName2, false)
138
159
  });
139
160
  }
140
161
 
141
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/update.hbs
142
- var update_default = 'export const { PATCH } = defineRoute({\n operationId: "update{{ pascalCaseSingular }}",\n method: "PATCH",\n summary: "Update {{ noCaseSingular }}",\n description: "Update a specific {{ noCaseSingular }} by ID",\n tags: ["{{ capitalCasePlural }}"],\n pathParams: {{ pascalCaseSingular }}DTO.pick({ id: true }),\n requestBody: {{ pascalCaseSingular }}PatchDTO,\n action: async ({ pathParams, body }) => {\n const {{ camelCaseSingular }} = await update{{ pascalCaseSingular }}(db, pathParams.id, body);\n if ({{ camelCaseSingular }}?.id === pathParams.id) {\n return Response.json({{ camelCaseSingular }});\n }\n return new Response(null, { status: 404 });\n },\n responses: {\n 200: { description: "{{ onlyFirstCapitalCaseSingular }} updated successfully", content: {{ pascalCaseSingular }}DTO },\n 404: { description: "{{ onlyFirstCapitalCaseSingular }} not found" },\n },\n});\n';
162
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/update.hbs
163
+ var update_default = 'export const { PATCH } = defineRoute({\n operationId: "update{{ pascalCaseSingular }}",\n method: "PATCH",\n summary: "Update {{ noCaseSingular }}",\n description: "Update a specific {{ noCaseSingular }} by ID",\n tags: ["{{ capitalCasePlural }}"],\n pathParams: {{ pascalCaseSingular }}DTO.pick({ id: true }),\n requestBody: {{ pascalCaseSingular }}PatchDTO,\n action: async ({ pathParams, body }) => {\n const {{ camelCaseSingular }} = await update{{ pascalCaseSingular }}(db, pathParams.id, body);\n if ({{ camelCaseSingular }}.id === pathParams.id) {\n return Response.json({{ camelCaseSingular }});\n }\n return Response.json({ errorCode: "{{ constantCaseSingular }}_NOT_FOUND" }, { status: 404 });\n },\n handleErrors(errorType, issues) {\n switch (errorType) {\n case "PARSE_REQUEST_BODY":\n return Response.json({ errorCode: "BAD_REQUEST" }, { status: 400 });\n case "PARSE_PATH_PARAMS":\n return Response.json({ errorCode: "{{ constantCaseSingular }}_NOT_FOUND" }, { status: 404 });\n default:\n // eslint-disable-next-line no-console\n console.log(`---------------[${errorType}]---------------`);\n // eslint-disable-next-line no-console\n console.log(issues);\n // eslint-disable-next-line no-console\n console.log("".padEnd(errorType.length + 32, "-"));\n return Response.json({ errorCode: "UNKNOWN_ERROR" }, { status: 500 });\n }\n },\n responses: {\n 200: { description: "{{ onlyFirstCapitalCaseSingular }} updated successfully", content: {{ pascalCaseSingular }}DTO },\n 400: { description: "Bad Request", content: z.object({ errorCode: z.literal(["BAD_REQUEST"]) }) },\n 404: { description: "{{ onlyFirstCapitalCaseSingular }} not found", content: z.object({ errorCode: z.literal(["{{ constantCaseSingular }}_NOT_FOUND"]) }) },\n 500: { description: "Internal Server Error", content: z.object({ errorCode: z.literal(["UNKNOWN_ERROR"]) }) },\n },\n});\n';
143
164
 
144
165
  // src/templates/routes/update.ts
145
166
  function generateUpdateOperationRoute(modelName2) {
@@ -147,6 +168,7 @@ function generateUpdateOperationRoute(modelName2) {
147
168
  return template({
148
169
  camelCaseSingular: camelCase2(modelName2, false),
149
170
  capitalCasePlural: capitalCase2(modelName2, true, false),
171
+ constantCaseSingular: constantCase2(modelName2, false),
150
172
  noCaseSingular: noCase2(modelName2, false),
151
173
  onlyFirstCapitalCaseSingular: capitalCase2(modelName2, false, true),
152
174
  pascalCaseSingular: pascalCase2(modelName2, false)
@@ -165,98 +187,92 @@ function generateApiRouteWithId(modelName2) {
165
187
  });
166
188
  }
167
189
 
168
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/model.hbs
169
- var model_default = 'import { createInsertSchema } from "drizzle-zod";\nimport { {{ camelCasePlural }} } from "~/database/schema/{{ kebabCasePlural }}";\nimport type z from "zod";\n\nconst baseSchema = createInsertSchema({{ camelCasePlural }}, {\n id: schema => schema.id.readonly().describe("Unique identifier of the {{ noCaseSingular }}"),\n {{ firstField }}: schema => schema.{{ firstField }}.describe("............"),\n // describe other fields here\n createdAt: schema => schema.createdAt.readonly().describe("Creation date of the {{ noCaseSingular }} as an ISO 8601 date string"),\n updatedAt: schema => schema.updatedAt.readonly().describe("Modification date of the {{ noCaseSingular }} as an ISO 8601 date string"),\n});\n\nexport const {{ pascalCaseSingular }}DTO = baseSchema.required()\n .describe("Represents a {{ noCaseSingular }} definition");\n\nexport const New{{ pascalCaseSingular }}DTO = baseSchema.omit({\n id: true,\n createdAt: true,\n updatedAt: true,\n}).describe("Data Transfer Object for creating a new {{ noCaseSingular }}");\n\nexport const {{ pascalCaseSingular }}PatchDTO = New{{ pascalCaseSingular }}DTO.partial().omit({\n}).describe("Data Transfer Object for updating an existing {{ noCaseSingular }}");\n\nexport function test{{ pascalCaseSingular }}Data() {\n return {\n {{ firstField }}: "unknown",\n // add the required fields here\n } satisfies z.infer<typeof New{{ pascalCaseSingular }}DTO>;\n}\n';
190
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/model.hbs
191
+ var model_default = 'import { createInsertSchema, createSelectSchema, createUpdateSchema } from "drizzle-zod";\nimport { createSharedRefinements } from "drizzle-zod-shared-refinements";\nimport { {{ camelCasePlural }} } from "~/database/schema/{{ kebabCasePlural }}";\nimport type z from "zod";\n\nconst sharedRefinements = createSharedRefinements({{ camelCasePlural }}, {\n id: schema => schema.readonly().describe("Unique identifier of the {{ noCaseSingular }}"),\n // describe other fields here\n createdAt: schema => schema.readonly().describe("Creation date of the {{ noCaseSingular }} as an ISO 8601 date string"),\n updatedAt: schema => schema.readonly().describe("Modification date of the {{ noCaseSingular }} as an ISO 8601 date string"),\n});\n\nexport const {{ pascalCaseSingular }}DTO = createSelectSchema({{ camelCasePlural }}, sharedRefinements).describe("Represents a {{ noCaseSingular }} definition");\n\nexport const New{{ pascalCaseSingular }}DTO = createInsertSchema({{ camelCasePlural }}, sharedRefinements).omit({\n id: true,\n createdAt: true,\n updatedAt: true,\n}).describe("Data Transfer Object for creating a new {{ noCaseSingular }}");\n\nexport const {{ pascalCaseSingular }}PatchDTO = createUpdateSchema({{ camelCasePlural }}, sharedRefinements).omit({\n id: true,\n createdAt: true,\n updatedAt: true,\n}).describe("Data Transfer Object for updating an existing {{ noCaseSingular }}");\n\nexport type {{ pascalCaseSingular }} = z.infer<typeof {{ pascalCaseSingular }}DTO>;\nexport type New{{ pascalCaseSingular }} = z.infer<typeof New{{ pascalCaseSingular }}DTO>;\nexport type {{ pascalCaseSingular }}Patch = z.infer<typeof {{ pascalCaseSingular }}PatchDTO>;\n';
170
192
 
171
193
  // src/templates/model.ts
172
- function generateModel(modelName2, firstField2) {
173
- const template = getTemplate(model_default);
174
- return template({
194
+ function generateModel(modelName2) {
195
+ return getTemplate(model_default)({
175
196
  camelCasePlural: camelCase2(modelName2, true),
176
197
  kebabCasePlural: kebabCase2(modelName2, true),
177
198
  noCaseSingular: noCase2(modelName2, false),
178
- pascalCaseSingular: pascalCase2(modelName2, false),
179
- firstField: firstField2
199
+ pascalCaseSingular: pascalCase2(modelName2, false)
180
200
  });
181
201
  }
182
202
 
183
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/create.hbs
184
- var create_default2 = 'import { afterAll, describe, expect, it } from "@jest/globals";\nimport db, { evacuatePool } from "~/database";\nimport { {{ pascalCaseSingular }}DTO, test{{ pascalCaseSingular }}Data } from "~/models/{{ kebabCaseSingular }}";\nimport create{{ pascalCaseSingular }} from "./create{{ pascalCaseSingular }}";\nimport delete{{ pascalCaseSingular }} from "./delete{{ pascalCaseSingular }}";\n\ndescribe("create{{ pascalCaseSingular }}", () => {\n const schema = {{ pascalCaseSingular }}DTO.pick({ id: true });\n const data = test{{ pascalCaseSingular }}Data();\n\n it("should successfully create a {{ noCaseSingular }}", async () => {\n await db.transaction(async tx => {\n const {{ camelCaseSingular }} = await create{{ pascalCaseSingular }}(tx, data);\n const { success } = schema.safeParse({{ camelCaseSingular }});\n expect(success).toBe(true);\n await delete{{ pascalCaseSingular }}(tx, {{ camelCaseSingular }}.id);\n });\n });\n});\n\nafterAll(done => {\n evacuatePool().then(() => done());\n});\n';
203
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/create.hbs
204
+ var create_default2 = 'import { describe, expect, it, vi } from "vitest";\nimport { zocker } from "zocker";\nimport db from "~/database";\nimport { New{{ pascalCaseSingular }}DTO } from "~/models/{{ kebabCaseSingular }}";\nimport create{{ pascalCaseSingular }} from "./create{{ pascalCaseSingular }}";\n\ndescribe("create{{ pascalCaseSingular }}", () => {\n it("should insert a {{ noCaseSingular }} into the database", async () => {\n const mockData = zocker(New{{ pascalCaseSingular }}DTO).generate();\n const spy = vi.spyOn(db.$client, "query").mockImplementation(() => Promise.resolve({ rows: [], rowCount: 0 }));\n\n await create{{ pascalCaseSingular }}(db, mockData);\n\n expect(spy).toHaveBeenCalled();\n const [query, parameters] = spy.mock.calls[0] as unknown as [{ text: string }, string[]];\n const q = /^insert into "{{ snakeCasePlural }}" (.+) values (.+) returning (.+)$/;\n expect(query.text).toMatch(q);\n expect(parameters).toEqual(Object.values(mockData));\n\n spy.mockRestore();\n });\n});\n';
185
205
 
186
206
  // src/templates/operation-tests/create.ts
187
207
  function generateCreateTest(modelName2) {
188
- const template = getTemplate(create_default2);
189
- return template({
190
- camelCaseSingular: camelCase2(modelName2, false),
208
+ return getTemplate(create_default2)({
191
209
  kebabCaseSingular: kebabCase2(modelName2, false),
192
210
  noCaseSingular: noCase2(modelName2, false),
193
- pascalCaseSingular: pascalCase2(modelName2, false)
211
+ pascalCaseSingular: pascalCase2(modelName2, false),
212
+ snakeCasePlural: snakeCase2(modelName2, true)
194
213
  });
195
214
  }
196
215
 
197
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/delete.hbs
198
- var delete_default2 = 'import { afterAll, afterEach, beforeEach, describe, expect, it } from "@jest/globals";\nimport db, { evacuatePool } from "~/database";\nimport { {{ pascalCaseSingular }}DTO, test{{ pascalCaseSingular }}Data } from "~/models/{{ kebabCaseSingular }}";\nimport create{{ pascalCaseSingular }} from "./create{{ pascalCaseSingular }}";\nimport delete{{ pascalCaseSingular }} from "./delete{{ pascalCaseSingular }}";\n\nlet test{{ pascalCaseSingular }}ID = "";\n\ndescribe("delete{{ pascalCaseSingular }}", () => {\n const schema = {{ pascalCaseSingular }}DTO.pick({ id: true });\n\n it("should fail when trying to delete a nonexistent {{ noCaseSingular }}", async () => {\n const pseudo{{ pascalCaseSingular }}Id = crypto.randomUUID();\n const result = await delete{{ pascalCaseSingular }}(db, pseudo{{ pascalCaseSingular }}Id);\n const { success } = schema.safeParse(result);\n expect(success).toBe(false);\n });\n\n it("should successfully delete an existing {{ noCaseSingular }}", async () => {\n await db.transaction(async tx => {\n const result = await delete{{ pascalCaseSingular }}(tx, test{{ pascalCaseSingular }}ID);\n const { success } = schema.safeParse(result);\n expect(success).toBe(true);\n });\n });\n});\n\nbeforeEach(async () => {\n const {{ camelCaseSingular }} = await create{{ pascalCaseSingular }}(db, test{{ pascalCaseSingular }}Data());\n test{{ pascalCaseSingular }}ID = {{ camelCaseSingular }}.id;\n});\n\nafterEach(async () => {\n await delete{{ pascalCaseSingular }}(db, test{{ pascalCaseSingular }}ID);\n});\n\nafterAll(done => {\n evacuatePool().then(() => done());\n});\n';
216
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/delete.hbs
217
+ var delete_default2 = 'import { describe, expect, it, vi } from "vitest";\nimport { zocker } from "zocker";\nimport db from "~/database";\nimport { {{ pascalCaseSingular }}DTO } from "~/models/{{ kebabCaseSingular }}";\nimport delete{{ pascalCaseSingular }} from "./delete{{ pascalCaseSingular }}";\n\ndescribe("delete{{ pascalCaseSingular }}", () => {\n it("should delete a {{ noCaseSingular }} from the database", async () => {\n const mockData = zocker({{ pascalCaseSingular }}DTO.pick({ id: true })).generate();\n const spy = vi.spyOn(db.$client, "query").mockImplementation(() => Promise.resolve({ rows: [], rowCount: 0 }));\n\n await delete{{ pascalCaseSingular }}(db, mockData.id);\n\n const [query, parameters] = spy.mock.calls[0] as unknown as [{ text: string }, string[]];\n const q = /^delete from "{{ snakeCasePlural }}" where "{{ snakeCasePlural }}"."id" = \\$1 returning (.+)$/;\n expect(query.text).toMatch(q);\n expect(parameters).toEqual([mockData.id]);\n\n spy.mockRestore();\n });\n});\n';
199
218
 
200
219
  // src/templates/operation-tests/delete.ts
201
220
  function generateDeleteTest(modelName2) {
202
- const template = getTemplate(delete_default2);
203
- return template({
204
- camelCaseSingular: camelCase2(modelName2, false),
221
+ return getTemplate(delete_default2)({
205
222
  kebabCaseSingular: kebabCase2(modelName2, false),
206
223
  noCaseSingular: noCase2(modelName2, false),
207
- pascalCaseSingular: pascalCase2(modelName2, false)
224
+ pascalCaseSingular: pascalCase2(modelName2, false),
225
+ snakeCasePlural: snakeCase2(modelName2, true)
208
226
  });
209
227
  }
210
228
 
211
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/read.hbs
212
- var read_default2 = 'import { afterAll, afterEach, beforeEach, describe, expect, it } from "@jest/globals";\nimport db, { evacuatePool } from "~/database";\nimport { {{ pascalCaseSingular }}DTO, test{{ pascalCaseSingular }}Data } from "~/models/{{ kebabCaseSingular }}";\nimport create{{ pascalCaseSingular }} from "./create{{ pascalCaseSingular }}";\nimport delete{{ pascalCaseSingular }} from "./delete{{ pascalCaseSingular }}";\nimport get{{ pascalCaseSingular }} from "./get{{ pascalCaseSingular }}";\n\nlet test{{ pascalCaseSingular }}ID = "";\n\ndescribe("get{{ pascalCaseSingular }}", () => {\n const schema = {{ pascalCaseSingular }}DTO;\n\n it("should fail when trying to find a nonexistent {{ noCaseSingular }}", async () => {\n const pseudo{{ pascalCaseSingular }}Id = crypto.randomUUID();\n const result = await get{{ pascalCaseSingular }}(db, pseudo{{ pascalCaseSingular }}Id);\n const { success } = schema.safeParse(result);\n expect(success).toBe(false);\n });\n\n it("should successfully retrieve an existing {{ noCaseSingular }}", async () => {\n const result = await get{{ pascalCaseSingular }}(db, test{{ pascalCaseSingular }}ID);\n const { success } = schema.safeParse(result);\n expect(success).toBe(true);\n });\n});\n\nbeforeEach(async () => {\n const {{ camelCaseSingular }} = await create{{ pascalCaseSingular }}(db, test{{ pascalCaseSingular }}Data());\n test{{ pascalCaseSingular }}ID = {{ camelCaseSingular }}.id;\n});\n\nafterEach(async () => {\n await delete{{ pascalCaseSingular }}(db, test{{ pascalCaseSingular }}ID);\n});\n\nafterAll(done => {\n evacuatePool().then(() => done());\n});\n';
229
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/read.hbs
230
+ var read_default2 = 'import { describe, expect, it, vi } from "vitest";\nimport { zocker } from "zocker";\nimport db from "~/database";\nimport { {{ pascalCaseSingular }}DTO } from "~/models/{{ kebabCaseSingular }}";\nimport get{{ pascalCaseSingular }} from "./get{{ pascalCaseSingular }}";\n\ndescribe("get{{ pascalCaseSingular }}", () => {\n it("should retrieve a single {{ noCaseSingular }} by id", async () => {\n const mockData = zocker({{ pascalCaseSingular }}DTO.pick({ id: true })).generate();\n const spy = vi.spyOn(db.$client, "query").mockImplementation(() => Promise.resolve({ rows: [], rowCount: 0 }));\n\n await get{{ pascalCaseSingular }}(db, mockData.id);\n\n const [query, parameters] = spy.mock.calls[0] as unknown as [{ text: string }, string[]];\n const q = /^select (.+) from "{{ snakeCasePlural }}" "{{ camelCasePlural }}" where "{{ camelCasePlural }}"\\."id" = \\$1 limit \\$2$/;\n expect(query.text).toMatch(q);\n expect(parameters).toEqual([...Object.values(mockData), 1]);\n\n spy.mockRestore();\n });\n});\n';
213
231
 
214
232
  // src/templates/operation-tests/read.ts
215
233
  function generateReadTest(modelName2) {
216
- const template = getTemplate(read_default2);
217
- return template({
218
- camelCaseSingular: camelCase2(modelName2, false),
234
+ return getTemplate(read_default2)({
235
+ camelCasePlural: camelCase2(modelName2, true),
219
236
  kebabCaseSingular: kebabCase2(modelName2, false),
220
237
  noCaseSingular: noCase2(modelName2, false),
221
- pascalCaseSingular: pascalCase2(modelName2, false)
238
+ pascalCaseSingular: pascalCase2(modelName2, false),
239
+ snakeCasePlural: snakeCase2(modelName2, true)
222
240
  });
223
241
  }
224
242
 
225
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/read-all.hbs
226
- var read_all_default2 = 'import { afterAll, expect, test } from "@jest/globals";\nimport db, { evacuatePool } from "~/database";\nimport { {{ pascalCaseSingular }}DTO } from "~/models/{{ kebabCaseSingular }}";\nimport get{{ pascalCasePlural }} from "./get{{ pascalCasePlural }}";\n\ntest("get{{ pascalCasePlural }}", async () => {\n const result = await get{{ pascalCasePlural }}(db, ["id"]);\n const schema = {{ pascalCaseSingular }}DTO.partial().array();\n const { success } = schema.safeParse(result);\n expect(success).toBe(true);\n});\n\nafterAll(done => {\n evacuatePool().then(() => done());\n});\n';
243
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/read-all.hbs
244
+ var read_all_default2 = 'import { describe, expect, it, vi } from "vitest";\nimport db from "~/database";\nimport get{{ pascalCasePlural }} from "./get{{ pascalCasePlural }}";\n\ndescribe("get{{ pascalCasePlural }}", () => {\n it("should retrieve all {{ noCasePlural }} from the database", async () => {\n const spy = vi.spyOn(db.$client, "query").mockImplementation(() => Promise.resolve({ rows: [], rowCount: 0 }));\n\n await get{{ pascalCasePlural }}(db);\n\n const [query] = spy.mock.calls[0] as unknown as [{ text: string }, string[]];\n const q = /^select (.+) from "{{ snakeCasePlural }}" "{{ camelCasePlural }}" order by "{{ camelCasePlural }}"."created_at" asc$/;\n expect(query.text).toMatch(q);\n\n spy.mockRestore();\n });\n\n it("should retrieve {{ noCasePlural }} with limit and offset", async () => {\n const spy = vi.spyOn(db.$client, "query").mockImplementation(() => Promise.resolve({ rows: [], rowCount: 0 }));\n\n await get{{ pascalCasePlural }}(db, {\n limit: 50,\n offset: 50,\n });\n\n const [query, parameters] = spy.mock.calls[0] as unknown as [{ text: string }, string[]];\n const q = /^select (.+) from "{{ snakeCasePlural }}" "{{ camelCasePlural }}" order by "{{ camelCasePlural }}"."created_at" asc limit \\$1 offset \\$2$/;\n expect(query.text).toMatch(q);\n expect(parameters).toEqual([50, 50]);\n\n spy.mockRestore();\n });\n\n it("should retrieve {{ noCasePlural }} with specific fields selected", async () => {\n const spy = vi.spyOn(db.$client, "query").mockImplementation(() => Promise.resolve({ rows: [], rowCount: 0 }));\n\n await get{{ pascalCasePlural }}(db, {\n select: ["id", "createdAt"],\n });\n\n const [query] = spy.mock.calls[0] as unknown as [{ text: string }, string[]];\n const q = /^select "id", "created_at" from "{{ snakeCasePlural }}" "{{ camelCasePlural }}" order by "{{ camelCasePlural }}"."created_at" asc$/;\n expect(query.text).toMatch(q);\n\n spy.mockRestore();\n });\n});\n';
227
245
 
228
246
  // src/templates/operation-tests/read-all.ts
229
247
  function generateReadAllTest(modelName2) {
230
- const template = getTemplate(read_all_default2);
231
- return template({
248
+ return getTemplate(read_all_default2)({
249
+ camelCasePlural: camelCase2(modelName2, true),
232
250
  kebabCaseSingular: kebabCase2(modelName2, false),
251
+ noCasePlural: noCase2(modelName2, true),
233
252
  pascalCasePlural: pascalCase2(modelName2, true),
234
- pascalCaseSingular: pascalCase2(modelName2, false)
253
+ snakeCasePlural: snakeCase2(modelName2, true)
235
254
  });
236
255
  }
237
256
 
238
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/update.hbs
239
- var update_default2 = 'import { afterAll, afterEach, beforeEach, describe, expect, it } from "@jest/globals";\nimport db, { evacuatePool } from "~/database";\nimport { {{ pascalCaseSingular }}DTO, test{{ pascalCaseSingular }}Data } from "~/models/{{ kebabCaseSingular }}";\nimport create{{ pascalCaseSingular }} from "./create{{ pascalCaseSingular }}";\nimport delete{{ pascalCaseSingular }} from "./delete{{ pascalCaseSingular }}";\nimport update{{ pascalCaseSingular }} from "./update{{ pascalCaseSingular }}";\n\nlet test{{ pascalCaseSingular }}ID = "";\n\ndescribe("update{{ pascalCaseSingular }}", () => {\n const schema = {{ pascalCaseSingular }}DTO;\n\n it("should fail when trying to update a nonexistent {{ noCaseSingular }}", async () => {\n const pseudo{{ pascalCaseSingular }}Id = crypto.randomUUID();\n const result = await update{{ pascalCaseSingular }}(db, pseudo{{ pascalCaseSingular }}Id, test{{ pascalCaseSingular }}Data());\n const { success } = schema.safeParse(result);\n expect(success).toBe(false);\n });\n\n it("should successfully update an existing {{ noCaseSingular }}", async () => {\n await db.transaction(async tx => {\n const result = await update{{ pascalCaseSingular }}(tx, test{{ pascalCaseSingular }}ID, { {{ firstField }}: "testing" });\n const { success } = schema.safeParse(result);\n expect(success).toBe(true);\n expect(result?.{{ firstField }}).toBe("testing");\n });\n });\n});\n\nbeforeEach(async () => {\n const {{ camelCaseSingular }} = await create{{ pascalCaseSingular }}(db, test{{ pascalCaseSingular }}Data());\n test{{ pascalCaseSingular }}ID = {{ camelCaseSingular }}.id;\n});\n\nafterEach(async () => {\n await delete{{ pascalCaseSingular }}(db, test{{ pascalCaseSingular }}ID);\n});\n\nafterAll(done => {\n evacuatePool().then(() => done());\n});\n';
257
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/update.hbs
258
+ var update_default2 = 'import { describe, expect, it, vi } from "vitest";\nimport { zocker } from "zocker";\nimport db from "~/database";\nimport { {{ pascalCaseSingular }}PatchDTO, {{ pascalCaseSingular }}DTO } from "~/models/{{ kebabCaseSingular }}";\nimport update{{ pascalCaseSingular }} from "./update{{ pascalCaseSingular }}";\n\ndescribe("update{{ pascalCaseSingular }}", () => {\n function generateMockPatch() {\n const zock = zocker({{ pascalCaseSingular }}PatchDTO);\n let mock: ReturnType<typeof zock.generate>;\n do {\n mock = zock.generate();\n } while (Object.values(JSON.parse(JSON.stringify(mock))).length < 1); // because .set(patch) might throw "No values to set"\n return mock;\n }\n\n it("should update a {{ noCaseSingular }} in the database", async () => {\n const mockData = zocker({{ pascalCaseSingular }}DTO.pick({ id: true })).generate();\n const mockPatch = generateMockPatch();\n const spy = vi.spyOn(db.$client, "query").mockImplementation(() => Promise.resolve({ rows: [], rowCount: 0 }));\n\n await update{{ pascalCaseSingular }}(db, mockData.id, mockPatch);\n\n expect(spy).toHaveBeenCalled();\n const [query, parameters] = spy.mock.calls[0] as unknown as [{ text: string }, string[]];\n const q = /^update "{{ snakeCasePlural }}" set (.+) where (.+) returning (.+)$/;\n expect(query.text).toMatch(q);\n expect(parameters).toEqual([\n ...Object.values(mockPatch).filter(p => typeof p !== "undefined"),\n mockData.id,\n ]);\n\n spy.mockRestore();\n });\n});\n';
240
259
 
241
260
  // src/templates/operation-tests/update.ts
242
- function generateUpdateTest(modelName2, firstField2) {
243
- const template = getTemplate(update_default2);
244
- return template({
245
- camelCaseSingular: camelCase2(modelName2, false),
261
+ function generateUpdateTest(modelName2) {
262
+ return getTemplate(update_default2)({
246
263
  kebabCaseSingular: kebabCase2(modelName2, false),
247
264
  noCaseSingular: noCase2(modelName2, false),
248
265
  pascalCaseSingular: pascalCase2(modelName2, false),
249
- firstField: firstField2
266
+ snakeCasePlural: snakeCase2(modelName2, true)
250
267
  });
251
268
  }
252
269
 
253
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/create.hbs
254
- var create_default3 = 'import type database from "~/database";\nimport { {{ camelCasePlural }} } from "~/database/schema/{{ kebabCasePlural }}";\nimport type { New{{ pascalCaseSingular }}DTO } from "~/models/{{ kebabCaseSingular }}";\nimport type z from "zod";\n\nexport default async function create{{ pascalCaseSingular }}(db: typeof database, data: z.infer<typeof New{{ pascalCaseSingular }}DTO>) {\n const [{{ camelCaseSingular }}] = await db.insert({{ camelCasePlural }}).values(data).returning({ id: {{ camelCasePlural }}.id });\n return {{ camelCaseSingular }};\n}\n';
270
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/create.hbs
271
+ var create_default3 = 'import type database from "~/database";\nimport { {{ camelCasePlural }} } from "~/database/schema/{{ kebabCasePlural }}";\nimport { type New{{ pascalCaseSingular }} } from "~/models/{{ kebabCaseSingular }}";\n\nexport default async function create{{ pascalCaseSingular }}(db: Omit<typeof database, "$client">, data: New{{ pascalCaseSingular }}) {\n const [{{ camelCaseSingular }}] = await db.insert({{ camelCasePlural }}).values(data).returning({ id: {{ camelCasePlural }}.id });\n return {{ camelCaseSingular }};\n}\n';
255
272
 
256
273
  // src/templates/operations/create.ts
257
274
  function generateCreateOperation(modelName2) {
258
- const template = getTemplate(create_default3);
259
- return template({
275
+ return getTemplate(create_default3)({
260
276
  camelCasePlural: camelCase2(modelName2, true),
261
277
  camelCaseSingular: camelCase2(modelName2, false),
262
278
  kebabCasePlural: kebabCase2(modelName2, true),
@@ -265,13 +281,12 @@ function generateCreateOperation(modelName2) {
265
281
  });
266
282
  }
267
283
 
268
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/delete.hbs
269
- var delete_default3 = 'import { eq } from "drizzle-orm";\nimport type database from "~/database";\nimport { {{ camelCasePlural }} } from "~/database/schema/{{ kebabCasePlural }}";\n\nexport default async function delete{{ pascalCaseSingular }}(db: typeof database, {{ camelCaseSingular }}Id: string) {\n const results = await db.delete({{ camelCasePlural }}).where(eq({{ camelCasePlural }}.id, {{ camelCaseSingular }}Id)).returning({\n id: {{ camelCasePlural }}.id,\n });\n return results.shift();\n}\n';
284
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/delete.hbs
285
+ var delete_default3 = 'import { eq } from "drizzle-orm";\nimport type database from "~/database";\nimport { {{ camelCasePlural }} } from "~/database/schema/{{ kebabCasePlural }}";\n\nexport default async function delete{{ pascalCaseSingular }}(db: Omit<typeof database, "$client">, {{ camelCaseSingular }}Id: string) {\n const results = await db.delete({{ camelCasePlural }}).where(eq({{ camelCasePlural }}.id, {{ camelCaseSingular }}Id)).returning({\n id: {{ camelCasePlural }}.id,\n });\n return results.shift();\n}\n';
270
286
 
271
287
  // src/templates/operations/delete.ts
272
288
  function generateDeleteOperation(modelName2) {
273
- const template = getTemplate(delete_default3);
274
- return template({
289
+ return getTemplate(delete_default3)({
275
290
  camelCasePlural: camelCase2(modelName2, true),
276
291
  camelCaseSingular: camelCase2(modelName2, false),
277
292
  kebabCasePlural: kebabCase2(modelName2, true),
@@ -279,26 +294,24 @@ function generateDeleteOperation(modelName2) {
279
294
  });
280
295
  }
281
296
 
282
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/read.hbs
283
- var read_default3 = 'import type database from "~/database";\n\nexport default function get{{ pascalCaseSingular }}(db: typeof database, {{ camelCaseSingular }}Id: string) {\n return db.query.{{ camelCasePlural }}.findFirst({\n where: (table, { eq }) => eq(table.id, {{ camelCaseSingular }}Id),\n });\n}\n';
297
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/read.hbs
298
+ var read_default3 = 'import type database from "~/database";\n\nexport default function get{{ pascalCaseSingular }}(db: Omit<typeof database, "$client">, {{ camelCaseSingular }}Id: string) {\n return db.query.{{ camelCasePlural }}.findFirst({\n where: (table, { eq }) => eq(table.id, {{ camelCaseSingular }}Id),\n });\n}\n';
284
299
 
285
300
  // src/templates/operations/read.ts
286
301
  function generateReadOperation(modelName2) {
287
- const template = getTemplate(read_default3);
288
- return template({
302
+ return getTemplate(read_default3)({
289
303
  camelCasePlural: camelCase2(modelName2, true),
290
304
  camelCaseSingular: camelCase2(modelName2, false),
291
305
  pascalCaseSingular: pascalCase2(modelName2, false)
292
306
  });
293
307
  }
294
308
 
295
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/read-all.hbs
296
- var read_all_default3 = 'import selectColumns from "~/core/column";\nimport type database from "~/database";\nimport { {{ pascalCaseSingular }}DTO } from "~/models/{{ kebabCaseSingular }}";\nimport type { z } from "zod";\n\nconst selectSchema = {{ pascalCaseSingular }}DTO.keyof().array().default([]);\n\nexport default function get{{ pascalCasePlural }}(db: typeof database, select: z.infer<typeof selectSchema>) {\n return db.query.{{ camelCasePlural }}.findMany({\n columns: selectColumns(select),\n orderBy: (u, { asc }) => [asc(u.createdAt)],\n });\n}\n';
309
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/read-all.hbs
310
+ var read_all_default3 = 'import type database from "~/database";\nimport { type {{ pascalCaseSingular }} } from "~/models/{{ kebabCaseSingular }}";\nimport selectColumns from "~/utils/column";\n\ntype Options = {\n offset?: number,\n limit?: number,\n select?: (keyof {{ pascalCaseSingular }})[],\n};\n\nexport default function get{{ pascalCasePlural }}(db: Omit<typeof database, "$client">, options: Options = {}) {\n const { offset, limit, select } = options;\n return db.query.{{ camelCasePlural }}.findMany({\n columns: selectColumns(select ?? []),\n orderBy: (u, { asc }) => [asc(u.createdAt)],\n limit,\n offset,\n });\n}\n';
297
311
 
298
312
  // src/templates/operations/read-all.ts
299
313
  function generateReadAllOperation(modelName2) {
300
- const template = getTemplate(read_all_default3);
301
- return template({
314
+ return getTemplate(read_all_default3)({
302
315
  camelCasePlural: camelCase2(modelName2, true),
303
316
  kebabCaseSingular: kebabCase2(modelName2, false),
304
317
  pascalCasePlural: pascalCase2(modelName2, true),
@@ -306,13 +319,12 @@ function generateReadAllOperation(modelName2) {
306
319
  });
307
320
  }
308
321
 
309
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/update.hbs
310
- var update_default3 = 'import { eq } from "drizzle-orm";\nimport type database from "~/database";\nimport { {{ camelCasePlural }} } from "~/database/schema/{{ kebabCasePlural }}";\nimport type { {{ pascalCaseSingular }}PatchDTO } from "~/models/{{ kebabCaseSingular }}";\nimport type { z } from "zod";\n\nexport default async function update{{ pascalCaseSingular }}(db: typeof database, {{ camelCaseSingular }}Id: string, patch: z.infer<typeof {{ pascalCaseSingular }}PatchDTO>) {\n const results = await db.update({{ camelCasePlural }})\n .set(patch)\n .where(eq({{ camelCasePlural }}.id, {{ camelCaseSingular }}Id))\n .returning();\n return results.shift();\n}\n';
322
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/update.hbs
323
+ var update_default3 = 'import { eq } from "drizzle-orm";\nimport type database from "~/database";\nimport { {{ camelCasePlural }} } from "~/database/schema/{{ kebabCasePlural }}";\nimport { type {{ pascalCaseSingular }}Patch } from "~/models/{{ kebabCaseSingular }}";\n\nexport default async function update{{ pascalCaseSingular }}(db: Omit<typeof database, "$client">, {{ camelCaseSingular }}Id: string, patch: {{ pascalCaseSingular }}Patch) {\n const [updated{{ pascalCaseSingular }}] = await db.update({{ camelCasePlural }})\n .set(patch)\n .where(eq({{ camelCasePlural }}.id, {{ camelCaseSingular }}Id))\n .returning();\n return updated{{ pascalCaseSingular }};\n}\n';
311
324
 
312
325
  // src/templates/operations/update.ts
313
326
  function generateUpdateOperation(modelName2) {
314
- const template = getTemplate(update_default3);
315
- return template({
327
+ return getTemplate(update_default3)({
316
328
  camelCasePlural: camelCase2(modelName2, true),
317
329
  camelCaseSingular: camelCase2(modelName2, false),
318
330
  kebabCasePlural: kebabCase2(modelName2, true),
@@ -321,17 +333,16 @@ function generateUpdateOperation(modelName2) {
321
333
  });
322
334
  }
323
335
 
324
- // _rnokx36w3:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/schema.hbs
325
- var schema_default = 'import { relations, sql } from "drizzle-orm";\nimport { bigint, boolean, char, index, integer, pgTable, smallint, text, timestamp, uuid, varchar } from "drizzle-orm/pg-core";\n\nexport const {{ camelCasePlural }} = pgTable("{{ snakeCasePlural }}", {\n id: uuid("id").defaultRandom().primaryKey(),\n {{ firstField }}: text("{{ firstField }}").notNull(),\n // ...\n exampleInteger: integer("example_integer").notNull().default(2147483647),\n exampleSmallInt: smallint("example_small_int").notNull().default(32767),\n exampleBigInt: bigint("example_big_int", { mode: "number" }).notNull().default(Number.MAX_SAFE_INTEGER),\n exampleBiggerInt: bigint("example_bigger_int", { mode: "bigint" }).notNull().default(0n),\n exampleBoolean: boolean("example_boolean").notNull().default(false),\n exampleText: text("example_text").notNull().default("Lorem ipsum dolor sit amet"),\n exampleVarchar: varchar("example_varchar", { length: 16 }).notNull().default("hello world"),\n examplechar: char("example_char", { length: 16 }).notNull().default("hello world "),\n // ...\n createdAt: timestamp("created_at", { withTimezone: true, precision: 3 }).default(sql`CURRENT_TIMESTAMP`).notNull(),\n updatedAt: timestamp("updated_at", { withTimezone: true, precision: 3 }).default(sql`CURRENT_TIMESTAMP`).notNull(),\n}, table => ({\n createdAtIdx: index().on(table.createdAt),\n}));\n\nexport const relationsOf{{ pascalCasePlural }} = relations({{ camelCasePlural }}, ({ many, one }) => ({\n master: one(masters, {\n fields: [{{ camelCasePlural }}.masterId],\n references: [masters.id],\n }),\n slaves: many(slaves),\n}));\n';
336
+ // _ztxb3livn:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/schema.hbs
337
+ var schema_default = 'import { relations, sql } from "drizzle-orm";\nimport { bigint, boolean, char, index, integer, pgTable, smallint, text, timestamp, uuid, varchar } from "drizzle-orm/pg-core";\n\nexport const {{ camelCasePlural }} = pgTable("{{ snakeCasePlural }}", {\n id: uuid().defaultRandom().primaryKey(),\n // ...\n exampleInteger: integer().notNull().default(2147483647),\n exampleSmallInt: smallint().notNull().default(32767),\n exampleBigInt: bigint({ mode: "number" }).notNull().default(Number.MAX_SAFE_INTEGER),\n exampleBiggerInt: bigint({ mode: "bigint" }).notNull().default(0n),\n exampleBoolean: boolean().notNull().default(false),\n exampleText: text().notNull().default("Lorem ipsum dolor sit amet"),\n exampleVarchar: varchar({ length: 16 }).notNull().default("hello world"),\n examplechar: char({ length: 16 }).notNull().default("hello world "),\n // ...\n createdAt: timestamp({ mode: "string", withTimezone: true, precision: 3 }).notNull().defaultNow(),\n updatedAt: timestamp({ mode: "string", withTimezone: true, precision: 3 }).notNull().defaultNow().$onUpdate(() => sql`now()`),\n}, table => [\n index().on(table.createdAt),\n]);\n\nexport const relationsOf{{ pascalCasePlural }} = relations({{ camelCasePlural }}, ({ many, one }) => ({\n master: one(masters, {\n fields: [{{ camelCasePlural }}.masterId],\n references: [masters.id],\n }),\n slaves: many(slaves),\n}));\n';
326
338
 
327
339
  // src/templates/schema.ts
328
- function generateSchema(modelName2, firstField2) {
340
+ function generateSchema(modelName2) {
329
341
  const template = getTemplate(schema_default);
330
342
  return template({
331
343
  camelCasePlural: camelCase2(modelName2, true),
332
344
  pascalCasePlural: pascalCase2(modelName2, true),
333
- snakeCasePlural: snakeCase2(modelName2, true),
334
- firstField: firstField2
345
+ snakeCasePlural: snakeCase2(modelName2, true)
335
346
  });
336
347
  }
337
348
 
@@ -351,12 +362,6 @@ var { orm } = await prompts({
351
362
  ],
352
363
  initial: 0
353
364
  });
354
- var { firstField } = await prompts({
355
- type: "text",
356
- name: "firstField",
357
- message: "Enter name of the first field",
358
- initial: "name"
359
- });
360
365
  var { operationTests } = await prompts({
361
366
  type: "toggle",
362
367
  name: "operationTests",
@@ -365,14 +370,18 @@ var { operationTests } = await prompts({
365
370
  active: "yes",
366
371
  inactive: "no"
367
372
  });
368
- if (modelName && firstField) {
373
+ if (modelName) {
369
374
  await saveFile(`src/app/${kebabCase2(modelName, true)}`, "route.ts", generateApiRoute(modelName));
370
375
  await saveFile(`src/app/${kebabCase2(modelName, true)}/[id]`, "route.ts", generateApiRouteWithId(modelName));
371
376
  if (orm === "drizzle") {
372
- await saveFile("src/database/schema", `${kebabCase2(modelName, true)}.ts`, generateSchema(modelName, firstField));
373
- await appendFile("src/database/schema", "index.ts", `export * from "./${kebabCase2(modelName, true)}";`);
377
+ await saveFile("src/database/schema", `${kebabCase2(modelName, true)}.ts`, generateSchema(modelName));
378
+ const exportStatement = `export * from "./${kebabCase2(modelName, true)}";`;
379
+ const exported = await checkFile("src/database/schema", "index.ts", exportStatement);
380
+ if (!exported) {
381
+ await appendFile("src/database/schema", "index.ts", `export * from "./${kebabCase2(modelName, true)}";`);
382
+ }
374
383
  }
375
- await saveFile("src/models", `${kebabCase2(modelName, false)}.ts`, generateModel(modelName, firstField));
384
+ await saveFile("src/models", `${kebabCase2(modelName, false)}.ts`, generateModel(modelName));
376
385
  await saveFile("src/operations", `get${pascalCase2(modelName, true)}.ts`, generateReadAllOperation(modelName));
377
386
  await saveFile("src/operations", `create${pascalCase2(modelName, false)}.ts`, generateCreateOperation(modelName));
378
387
  await saveFile("src/operations", `get${pascalCase2(modelName, false)}.ts`, generateReadOperation(modelName));
@@ -382,7 +391,7 @@ if (modelName && firstField) {
382
391
  await saveFile("src/operations", `get${pascalCase2(modelName, true)}.test.ts`, generateReadAllTest(modelName));
383
392
  await saveFile("src/operations", `create${pascalCase2(modelName, false)}.test.ts`, generateCreateTest(modelName));
384
393
  await saveFile("src/operations", `get${pascalCase2(modelName, false)}.test.ts`, generateReadTest(modelName));
385
- await saveFile("src/operations", `update${pascalCase2(modelName, false)}.test.ts`, generateUpdateTest(modelName, firstField));
394
+ await saveFile("src/operations", `update${pascalCase2(modelName, false)}.test.ts`, generateUpdateTest(modelName));
386
395
  await saveFile("src/operations", `delete${pascalCase2(modelName, false)}.test.ts`, generateDeleteTest(modelName));
387
396
  }
388
397
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omer-x/next-openapi-scaffold-generator",
3
- "version": "0.3.0",
3
+ "version": "0.4.0-alpha.1",
4
4
  "description": "Generates model, schema and API routes for CRUD operations",
5
5
  "keywords": [
6
6
  "next",
@@ -46,8 +46,9 @@
46
46
  "node": ">=14.8.0"
47
47
  },
48
48
  "scripts": {
49
- "gh:release": "conventional-github-releaser -p angular",
50
- "test": "jest",
49
+ "lint": "eslint --flag unstable_native_nodejs_ts_config",
50
+ "test": "vitest run --coverage",
51
+ "test:watch": "vitest --coverage",
51
52
  "dev": "tsup --watch",
52
53
  "build": "tsup"
53
54
  },
@@ -58,16 +59,16 @@
58
59
  "prompts": "^2.4.2"
59
60
  },
60
61
  "devDependencies": {
61
- "@omer-x/eslint-config": "^1.0.7",
62
- "@types/node": "^22.5.0",
62
+ "@omer-x/eslint-config": "^2.2.6",
63
+ "@types/node": "^25.0.1",
63
64
  "@types/pluralize": "^0.0.33",
64
65
  "@types/prompts": "^2.4.9",
65
- "conventional-github-releaser": "^3.1.5",
66
+ "@vitest/coverage-v8": "^4.0.15",
66
67
  "esbuild-plugin-inline-import": "^1.1.0",
67
- "eslint": "^8.57.0",
68
- "ts-jest": "^29.2.4",
69
- "ts-node": "^10.9.2",
70
- "tsup": "^8.2.4",
71
- "typescript": "^5.5.4"
68
+ "eslint": "^9.39.1",
69
+ "semantic-release": "^25.0.2",
70
+ "tsup": "^8.5.1",
71
+ "typescript": "^5.9.3",
72
+ "vitest": "^4.0.15"
72
73
  }
73
74
  }