@omer-x/next-openapi-scaffold-generator 0.2.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 +149 -97
  2. package/package.json +15 -12
package/bin/index.js CHANGED
@@ -1,12 +1,36 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import prompts from "prompts";
5
+
1
6
  // src/core/file.ts
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
7
+ import fs from "fs/promises";
8
+ import path from "path";
4
9
  async function saveFile(directory, fileName, content) {
5
10
  const dirPath = path.resolve(process.cwd(), directory);
6
11
  await fs.mkdir(dirPath, { recursive: true });
7
12
  const filePath = path.resolve(dirPath, fileName);
8
13
  await fs.writeFile(filePath, content, "utf8");
9
14
  }
15
+ async function appendFile(directory, fileName, content) {
16
+ const dirPath = path.resolve(process.cwd(), directory);
17
+ await fs.mkdir(dirPath, { recursive: true });
18
+ const filePath = path.resolve(dirPath, fileName);
19
+ await fs.appendFile(filePath, content + "\n", "utf8");
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
+ }
10
34
 
11
35
  // src/core/string.ts
12
36
  import * as changeCase from "change-case";
@@ -42,28 +66,22 @@ function capitalCase2(text, plural, onlyFirst) {
42
66
  }
43
67
  return changeCase.capitalCase(properText);
44
68
  }
45
-
46
- // src/prompt.ts
47
- import { stdin as input, stdout as output } from "node:process";
48
- import * as readline from "node:readline/promises";
49
- var rl = readline.createInterface({ input, output });
50
- async function askQuestions(queries) {
51
- const answers = await Promise.all(queries.map((query) => rl.question(query)));
52
- rl.close();
53
- return answers;
69
+ function constantCase2(text, plural) {
70
+ const properText = plural ? pluralize(text) : pluralize.singular(text);
71
+ return changeCase.constantCase(properText);
54
72
  }
55
73
 
56
74
  // src/core/template.ts
57
75
  import Handlebars from "handlebars";
58
- function getTemplate(input2) {
59
- return Handlebars.compile(input2);
76
+ function getTemplate(input) {
77
+ return Handlebars.compile(input);
60
78
  }
61
79
 
62
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/api-route.hbs
63
- 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';
64
82
 
65
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/create.hbs
66
- 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';
67
85
 
68
86
  // src/templates/routes/create.ts
69
87
  function generateCreateOperationRoute(modelName2) {
@@ -77,13 +95,14 @@ function generateCreateOperationRoute(modelName2) {
77
95
  });
78
96
  }
79
97
 
80
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/read-all.hbs
81
- 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';
82
100
 
83
101
  // src/templates/routes/read-all.ts
84
102
  function generateReadAllOperationRoute(modelName2) {
85
103
  const template = getTemplate(read_all_default);
86
104
  return template({
105
+ camelCasePlural: camelCase2(modelName2, true),
87
106
  capitalCasePlural: capitalCase2(modelName2, true, false),
88
107
  noCasePlural: noCase2(modelName2, true),
89
108
  pascalCaseSingular: pascalCase2(modelName2, false),
@@ -95,6 +114,7 @@ function generateReadAllOperationRoute(modelName2) {
95
114
  function generateApiRoute(modelName2) {
96
115
  const template = getTemplate(api_route_default);
97
116
  return template({
117
+ camelCasePlural: camelCase2(modelName2, true),
98
118
  kebabCasePlural: kebabCase2(modelName2, true),
99
119
  kebabCaseSingular: kebabCase2(modelName2, false),
100
120
  pascalCasePlural: pascalCase2(modelName2, true),
@@ -104,11 +124,11 @@ function generateApiRoute(modelName2) {
104
124
  });
105
125
  }
106
126
 
107
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/api-route-with-id.hbs
108
- 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';
109
129
 
110
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/delete.hbs
111
- 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';
112
132
 
113
133
  // src/templates/routes/delete.ts
114
134
  function generateDeleteOperationRoute(modelName2) {
@@ -116,14 +136,15 @@ function generateDeleteOperationRoute(modelName2) {
116
136
  return template({
117
137
  camelCaseSingular: camelCase2(modelName2, false),
118
138
  capitalCasePlural: capitalCase2(modelName2, true, false),
139
+ constantCaseSingular: constantCase2(modelName2, false),
119
140
  noCaseSingular: noCase2(modelName2, false),
120
141
  onlyFirstCapitalCaseSingular: capitalCase2(modelName2, false, true),
121
142
  pascalCaseSingular: pascalCase2(modelName2, false)
122
143
  });
123
144
  }
124
145
 
125
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/read.hbs
126
- 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';
127
148
 
128
149
  // src/templates/routes/read.ts
129
150
  function generateReadOperationRoute(modelName2) {
@@ -131,14 +152,15 @@ function generateReadOperationRoute(modelName2) {
131
152
  return template({
132
153
  camelCaseSingular: camelCase2(modelName2, false),
133
154
  capitalCasePlural: capitalCase2(modelName2, true, false),
155
+ constantCaseSingular: constantCase2(modelName2, false),
134
156
  noCaseSingular: noCase2(modelName2, false),
135
157
  onlyFirstCapitalCaseSingular: capitalCase2(modelName2, false, true),
136
158
  pascalCaseSingular: pascalCase2(modelName2, false)
137
159
  });
138
160
  }
139
161
 
140
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/routes/update.hbs
141
- 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';
142
164
 
143
165
  // src/templates/routes/update.ts
144
166
  function generateUpdateOperationRoute(modelName2) {
@@ -146,6 +168,7 @@ function generateUpdateOperationRoute(modelName2) {
146
168
  return template({
147
169
  camelCaseSingular: camelCase2(modelName2, false),
148
170
  capitalCasePlural: capitalCase2(modelName2, true, false),
171
+ constantCaseSingular: constantCase2(modelName2, false),
149
172
  noCaseSingular: noCase2(modelName2, false),
150
173
  onlyFirstCapitalCaseSingular: capitalCase2(modelName2, false, true),
151
174
  pascalCaseSingular: pascalCase2(modelName2, false)
@@ -164,135 +187,131 @@ function generateApiRouteWithId(modelName2) {
164
187
  });
165
188
  }
166
189
 
167
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/model.hbs
168
- var model_default = 'import { createInsertSchema } from "drizzle-zod";\nimport { {{ camelCasePlural }} } from "~/database/schema";\nimport type z from "zod";\n\nconst baseSchema = createInsertSchema({{ camelCasePlural }}, {\n id: schema => schema.id.readonly().describe("Unique identifier of the {{ noCaseSingular }}"),\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 // 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';
169
192
 
170
193
  // src/templates/model.ts
171
194
  function generateModel(modelName2) {
172
- const template = getTemplate(model_default);
173
- return template({
195
+ return getTemplate(model_default)({
174
196
  camelCasePlural: camelCase2(modelName2, true),
197
+ kebabCasePlural: kebabCase2(modelName2, true),
175
198
  noCaseSingular: noCase2(modelName2, false),
176
199
  pascalCaseSingular: pascalCase2(modelName2, false)
177
200
  });
178
201
  }
179
202
 
180
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/create.hbs
181
- 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';
182
205
 
183
206
  // src/templates/operation-tests/create.ts
184
207
  function generateCreateTest(modelName2) {
185
- const template = getTemplate(create_default2);
186
- return template({
187
- camelCaseSingular: camelCase2(modelName2, false),
208
+ return getTemplate(create_default2)({
188
209
  kebabCaseSingular: kebabCase2(modelName2, false),
189
210
  noCaseSingular: noCase2(modelName2, false),
190
- pascalCaseSingular: pascalCase2(modelName2, false)
211
+ pascalCaseSingular: pascalCase2(modelName2, false),
212
+ snakeCasePlural: snakeCase2(modelName2, true)
191
213
  });
192
214
  }
193
215
 
194
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/delete.hbs
195
- 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';
196
218
 
197
219
  // src/templates/operation-tests/delete.ts
198
220
  function generateDeleteTest(modelName2) {
199
- const template = getTemplate(delete_default2);
200
- return template({
201
- camelCaseSingular: camelCase2(modelName2, false),
221
+ return getTemplate(delete_default2)({
202
222
  kebabCaseSingular: kebabCase2(modelName2, false),
203
223
  noCaseSingular: noCase2(modelName2, false),
204
- pascalCaseSingular: pascalCase2(modelName2, false)
224
+ pascalCaseSingular: pascalCase2(modelName2, false),
225
+ snakeCasePlural: snakeCase2(modelName2, true)
205
226
  });
206
227
  }
207
228
 
208
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/read.hbs
209
- 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';
210
231
 
211
232
  // src/templates/operation-tests/read.ts
212
233
  function generateReadTest(modelName2) {
213
- const template = getTemplate(read_default2);
214
- return template({
215
- camelCaseSingular: camelCase2(modelName2, false),
234
+ return getTemplate(read_default2)({
235
+ camelCasePlural: camelCase2(modelName2, true),
216
236
  kebabCaseSingular: kebabCase2(modelName2, false),
217
237
  noCaseSingular: noCase2(modelName2, false),
218
- pascalCaseSingular: pascalCase2(modelName2, false)
238
+ pascalCaseSingular: pascalCase2(modelName2, false),
239
+ snakeCasePlural: snakeCase2(modelName2, true)
219
240
  });
220
241
  }
221
242
 
222
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/read-all.hbs
223
- 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';
224
245
 
225
246
  // src/templates/operation-tests/read-all.ts
226
247
  function generateReadAllTest(modelName2) {
227
- const template = getTemplate(read_all_default2);
228
- return template({
248
+ return getTemplate(read_all_default2)({
249
+ camelCasePlural: camelCase2(modelName2, true),
229
250
  kebabCaseSingular: kebabCase2(modelName2, false),
251
+ noCasePlural: noCase2(modelName2, true),
230
252
  pascalCasePlural: pascalCase2(modelName2, true),
231
- pascalCaseSingular: pascalCase2(modelName2, false)
253
+ snakeCasePlural: snakeCase2(modelName2, true)
232
254
  });
233
255
  }
234
256
 
235
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operation-tests/update.hbs
236
- 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("new.email@example.com"));\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, test{{ pascalCaseSingular }}Data("new.email@example.com"));\n const { success } = schema.safeParse(result);\n expect(success).toBe(true);\n expect(result?.email).toBe("new.email@example.com");\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';
237
259
 
238
260
  // src/templates/operation-tests/update.ts
239
261
  function generateUpdateTest(modelName2) {
240
- const template = getTemplate(update_default2);
241
- return template({
242
- camelCaseSingular: camelCase2(modelName2, false),
262
+ return getTemplate(update_default2)({
243
263
  kebabCaseSingular: kebabCase2(modelName2, false),
244
264
  noCaseSingular: noCase2(modelName2, false),
245
- pascalCaseSingular: pascalCase2(modelName2, false)
265
+ pascalCaseSingular: pascalCase2(modelName2, false),
266
+ snakeCasePlural: snakeCase2(modelName2, true)
246
267
  });
247
268
  }
248
269
 
249
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/create.hbs
250
- var create_default3 = 'import type database from "~/database";\nimport { {{ camelCasePlural }} } from "~/database/schema";\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';
251
272
 
252
273
  // src/templates/operations/create.ts
253
274
  function generateCreateOperation(modelName2) {
254
- const template = getTemplate(create_default3);
255
- return template({
275
+ return getTemplate(create_default3)({
256
276
  camelCasePlural: camelCase2(modelName2, true),
257
277
  camelCaseSingular: camelCase2(modelName2, false),
278
+ kebabCasePlural: kebabCase2(modelName2, true),
258
279
  kebabCaseSingular: kebabCase2(modelName2, false),
259
280
  pascalCaseSingular: pascalCase2(modelName2, false)
260
281
  });
261
282
  }
262
283
 
263
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/delete.hbs
264
- var delete_default3 = 'import { eq } from "drizzle-orm";\nimport type database from "~/database";\nimport { {{ camelCasePlural }} } from "~/database/schema";\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';
265
286
 
266
287
  // src/templates/operations/delete.ts
267
288
  function generateDeleteOperation(modelName2) {
268
- const template = getTemplate(delete_default3);
269
- return template({
289
+ return getTemplate(delete_default3)({
270
290
  camelCasePlural: camelCase2(modelName2, true),
271
291
  camelCaseSingular: camelCase2(modelName2, false),
292
+ kebabCasePlural: kebabCase2(modelName2, true),
272
293
  pascalCaseSingular: pascalCase2(modelName2, false)
273
294
  });
274
295
  }
275
296
 
276
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/read.hbs
277
- 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';
278
299
 
279
300
  // src/templates/operations/read.ts
280
301
  function generateReadOperation(modelName2) {
281
- const template = getTemplate(read_default3);
282
- return template({
302
+ return getTemplate(read_default3)({
283
303
  camelCasePlural: camelCase2(modelName2, true),
284
304
  camelCaseSingular: camelCase2(modelName2, false),
285
305
  pascalCaseSingular: pascalCase2(modelName2, false)
286
306
  });
287
307
  }
288
308
 
289
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/read-all.hbs
290
- 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';
291
311
 
292
312
  // src/templates/operations/read-all.ts
293
313
  function generateReadAllOperation(modelName2) {
294
- const template = getTemplate(read_all_default3);
295
- return template({
314
+ return getTemplate(read_all_default3)({
296
315
  camelCasePlural: camelCase2(modelName2, true),
297
316
  kebabCaseSingular: kebabCase2(modelName2, false),
298
317
  pascalCasePlural: pascalCase2(modelName2, true),
@@ -300,22 +319,22 @@ function generateReadAllOperation(modelName2) {
300
319
  });
301
320
  }
302
321
 
303
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/operations/update.hbs
304
- var update_default3 = 'import { eq } from "drizzle-orm";\nimport type database from "~/database";\nimport { {{ camelCasePlural }} } from "~/database/schema";\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';
305
324
 
306
325
  // src/templates/operations/update.ts
307
326
  function generateUpdateOperation(modelName2) {
308
- const template = getTemplate(update_default3);
309
- return template({
327
+ return getTemplate(update_default3)({
310
328
  camelCasePlural: camelCase2(modelName2, true),
311
329
  camelCaseSingular: camelCase2(modelName2, false),
330
+ kebabCasePlural: kebabCase2(modelName2, true),
312
331
  kebabCaseSingular: kebabCase2(modelName2, false),
313
332
  pascalCaseSingular: pascalCase2(modelName2, false)
314
333
  });
315
334
  }
316
335
 
317
- // _8rxnfpgcy:/home/runner/work/next-openapi-scaffold-generator/next-openapi-scaffold-generator/src/templates/schema.hbs
318
- var schema_default = 'import { relations, sql } from "drizzle-orm";\nimport { bigint, boolean, char, index, integer, pgTable, primaryKey, smallint, text, timestamp, uuid, varchar } from "drizzle-orm/pg-core";\n\nexport const {{ camelCasePlural }} = pgTable("{{ snakeCasePlural }}", {\n id: uuid("id").defaultRandom().primaryKey(),\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(0),\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';
319
338
 
320
339
  // src/templates/schema.ts
321
340
  function generateSchema(modelName2) {
@@ -328,18 +347,51 @@ function generateSchema(modelName2) {
328
347
  }
329
348
 
330
349
  // src/index.ts
331
- var [modelName] = await askQuestions(["Enter Model Name: "]);
332
- await saveFile(`src/app/${kebabCase2(modelName, true)}`, "route.ts", generateApiRoute(modelName));
333
- await saveFile(`src/app/${kebabCase2(modelName, true)}/[id]`, "route.ts", generateApiRouteWithId(modelName));
334
- await saveFile("src/database/schema", `${kebabCase2(modelName, true)}.ts`, generateSchema(modelName));
335
- await saveFile("src/models", `${kebabCase2(modelName, false)}.ts`, generateModel(modelName));
336
- await saveFile("src/operations", `get${pascalCase2(modelName, true)}.ts`, generateReadAllOperation(modelName));
337
- await saveFile("src/operations", `get${pascalCase2(modelName, true)}.test.ts`, generateReadAllTest(modelName));
338
- await saveFile("src/operations", `create${pascalCase2(modelName, false)}.ts`, generateCreateOperation(modelName));
339
- await saveFile("src/operations", `create${pascalCase2(modelName, false)}.test.ts`, generateCreateTest(modelName));
340
- await saveFile("src/operations", `get${pascalCase2(modelName, false)}.ts`, generateReadOperation(modelName));
341
- await saveFile("src/operations", `get${pascalCase2(modelName, false)}.test.ts`, generateReadTest(modelName));
342
- await saveFile("src/operations", `update${pascalCase2(modelName, false)}.ts`, generateUpdateOperation(modelName));
343
- await saveFile("src/operations", `update${pascalCase2(modelName, false)}.test.ts`, generateUpdateTest(modelName));
344
- await saveFile("src/operations", `delete${pascalCase2(modelName, false)}.ts`, generateDeleteOperation(modelName));
345
- await saveFile("src/operations", `delete${pascalCase2(modelName, false)}.test.ts`, generateDeleteTest(modelName));
350
+ var { modelName } = await prompts({
351
+ type: "text",
352
+ name: "modelName",
353
+ message: "Enter model name"
354
+ });
355
+ var { orm } = await prompts({
356
+ type: "select",
357
+ name: "orm",
358
+ message: "Pick an ORM",
359
+ choices: [
360
+ { title: "Drizzle", description: "a headless TypeScript ORM with a head. \u{1F432}", value: "drizzle" },
361
+ { title: "none", value: null, disabled: true }
362
+ ],
363
+ initial: 0
364
+ });
365
+ var { operationTests } = await prompts({
366
+ type: "toggle",
367
+ name: "operationTests",
368
+ message: "Do you want to include operation tests?",
369
+ initial: true,
370
+ active: "yes",
371
+ inactive: "no"
372
+ });
373
+ if (modelName) {
374
+ await saveFile(`src/app/${kebabCase2(modelName, true)}`, "route.ts", generateApiRoute(modelName));
375
+ await saveFile(`src/app/${kebabCase2(modelName, true)}/[id]`, "route.ts", generateApiRouteWithId(modelName));
376
+ if (orm === "drizzle") {
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
+ }
383
+ }
384
+ await saveFile("src/models", `${kebabCase2(modelName, false)}.ts`, generateModel(modelName));
385
+ await saveFile("src/operations", `get${pascalCase2(modelName, true)}.ts`, generateReadAllOperation(modelName));
386
+ await saveFile("src/operations", `create${pascalCase2(modelName, false)}.ts`, generateCreateOperation(modelName));
387
+ await saveFile("src/operations", `get${pascalCase2(modelName, false)}.ts`, generateReadOperation(modelName));
388
+ await saveFile("src/operations", `update${pascalCase2(modelName, false)}.ts`, generateUpdateOperation(modelName));
389
+ await saveFile("src/operations", `delete${pascalCase2(modelName, false)}.ts`, generateDeleteOperation(modelName));
390
+ if (operationTests) {
391
+ await saveFile("src/operations", `get${pascalCase2(modelName, true)}.test.ts`, generateReadAllTest(modelName));
392
+ await saveFile("src/operations", `create${pascalCase2(modelName, false)}.test.ts`, generateCreateTest(modelName));
393
+ await saveFile("src/operations", `get${pascalCase2(modelName, false)}.test.ts`, generateReadTest(modelName));
394
+ await saveFile("src/operations", `update${pascalCase2(modelName, false)}.test.ts`, generateUpdateTest(modelName));
395
+ await saveFile("src/operations", `delete${pascalCase2(modelName, false)}.test.ts`, generateDeleteTest(modelName));
396
+ }
397
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omer-x/next-openapi-scaffold-generator",
3
- "version": "0.2.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,26 +46,29 @@
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
  },
54
55
  "dependencies": {
55
56
  "change-case": "^5.4.4",
56
57
  "handlebars": "^4.7.8",
57
- "pluralize": "^8.0.0"
58
+ "pluralize": "^8.0.0",
59
+ "prompts": "^2.4.2"
58
60
  },
59
61
  "devDependencies": {
60
- "@omer-x/eslint-config": "^1.0.7",
61
- "@types/node": "^22.5.0",
62
+ "@omer-x/eslint-config": "^2.2.6",
63
+ "@types/node": "^25.0.1",
62
64
  "@types/pluralize": "^0.0.33",
63
- "conventional-github-releaser": "^3.1.5",
65
+ "@types/prompts": "^2.4.9",
66
+ "@vitest/coverage-v8": "^4.0.15",
64
67
  "esbuild-plugin-inline-import": "^1.1.0",
65
- "eslint": "^8.57.0",
66
- "ts-jest": "^29.2.4",
67
- "ts-node": "^10.9.2",
68
- "tsup": "^8.2.4",
69
- "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"
70
73
  }
71
74
  }