nextjs-drizzle-gen 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +7 -0
  2. package/dist/index.js +179 -122
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,7 +1,14 @@
1
1
  # nextjs-drizzle-gen
2
2
 
3
+ [![CI](https://github.com/mantaskaveckas/nextjs-drizzle-gen/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mantaskaveckas/nextjs-drizzle-gen/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/nextjs-drizzle-gen)](https://www.npmjs.com/package/nextjs-drizzle-gen)
5
+ [![npm downloads](https://img.shields.io/npm/dm/nextjs-drizzle-gen)](https://www.npmjs.com/package/nextjs-drizzle-gen)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
3
8
  Rails-like generators for Next.js + Drizzle ORM projects. Generate models, server actions, CRUD pages, and API routes with a single command.
4
9
 
10
+ [**Documentation**](https://mantaskaveckas.github.io/nextjs-drizzle-gen/)
11
+
5
12
  ## Installation
6
13
 
7
14
  ```bash
package/dist/index.js CHANGED
@@ -4,11 +4,9 @@
4
4
  import { program } from "commander";
5
5
 
6
6
  // src/generators/model.ts
7
- import * as path2 from "path";
7
+ import * as path4 from "path";
8
8
 
9
- // src/utils.ts
10
- import * as fs from "fs";
11
- import * as path from "path";
9
+ // src/lib/types.ts
12
10
  var VALID_FIELD_TYPES = [
13
11
  "string",
14
12
  "text",
@@ -25,6 +23,11 @@ var VALID_FIELD_TYPES = [
25
23
  "json",
26
24
  "uuid"
27
25
  ];
26
+
27
+ // src/lib/config.ts
28
+ import * as fs from "fs";
29
+ import * as path from "path";
30
+ var cachedProjectConfig = null;
28
31
  function detectDialect() {
29
32
  const configPath = path.join(process.cwd(), "drizzle.config.ts");
30
33
  if (!fs.existsSync(configPath)) {
@@ -43,7 +46,6 @@ function detectDialect() {
43
46
  }
44
47
  return "sqlite";
45
48
  }
46
- var cachedProjectConfig = null;
47
49
  function detectProjectConfig() {
48
50
  if (cachedProjectConfig) {
49
51
  return cachedProjectConfig;
@@ -98,37 +100,40 @@ function getDbPath() {
98
100
  const config = detectProjectConfig();
99
101
  return path.join(process.cwd(), config.dbPath);
100
102
  }
103
+
104
+ // src/lib/logger.ts
105
+ import * as path2 from "path";
101
106
  var log = {
102
107
  create: (filePath) => {
103
- const relative2 = path.relative(process.cwd(), filePath);
108
+ const relative2 = path2.relative(process.cwd(), filePath);
104
109
  console.log(` \x1B[32mcreate\x1B[0m ${relative2}`);
105
110
  },
106
111
  force: (filePath) => {
107
- const relative2 = path.relative(process.cwd(), filePath);
112
+ const relative2 = path2.relative(process.cwd(), filePath);
108
113
  console.log(` \x1B[33mforce\x1B[0m ${relative2}`);
109
114
  },
110
115
  skip: (filePath) => {
111
- const relative2 = path.relative(process.cwd(), filePath);
116
+ const relative2 = path2.relative(process.cwd(), filePath);
112
117
  console.log(` \x1B[33mskip\x1B[0m ${relative2}`);
113
118
  },
114
119
  remove: (filePath) => {
115
- const relative2 = path.relative(process.cwd(), filePath);
120
+ const relative2 = path2.relative(process.cwd(), filePath);
116
121
  console.log(` \x1B[31mremove\x1B[0m ${relative2}`);
117
122
  },
118
123
  notFound: (filePath) => {
119
- const relative2 = path.relative(process.cwd(), filePath);
124
+ const relative2 = path2.relative(process.cwd(), filePath);
120
125
  console.log(` \x1B[33mnot found\x1B[0m ${relative2}`);
121
126
  },
122
127
  wouldCreate: (filePath) => {
123
- const relative2 = path.relative(process.cwd(), filePath);
128
+ const relative2 = path2.relative(process.cwd(), filePath);
124
129
  console.log(`\x1B[36mwould create\x1B[0m ${relative2}`);
125
130
  },
126
131
  wouldForce: (filePath) => {
127
- const relative2 = path.relative(process.cwd(), filePath);
132
+ const relative2 = path2.relative(process.cwd(), filePath);
128
133
  console.log(` \x1B[36mwould force\x1B[0m ${relative2}`);
129
134
  },
130
135
  wouldRemove: (filePath) => {
131
- const relative2 = path.relative(process.cwd(), filePath);
136
+ const relative2 = path2.relative(process.cwd(), filePath);
132
137
  console.log(`\x1B[36mwould remove\x1B[0m ${relative2}`);
133
138
  },
134
139
  error: (message) => {
@@ -138,6 +143,62 @@ var log = {
138
143
  console.log(message);
139
144
  }
140
145
  };
146
+
147
+ // src/lib/strings.ts
148
+ function toPascalCase(str) {
149
+ return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
150
+ }
151
+ function toCamelCase(str) {
152
+ const pascal = toPascalCase(str);
153
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
154
+ }
155
+ function toSnakeCase(str) {
156
+ return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
157
+ }
158
+ function toKebabCase(str) {
159
+ return toSnakeCase(str).replace(/_/g, "-");
160
+ }
161
+ function pluralize(str) {
162
+ if (str.endsWith("y") && !/[aeiou]y$/.test(str)) {
163
+ return str.slice(0, -1) + "ies";
164
+ }
165
+ if (str.endsWith("s") || str.endsWith("x") || str.endsWith("ch") || str.endsWith("sh")) {
166
+ return str + "es";
167
+ }
168
+ return str + "s";
169
+ }
170
+ function singularize(str) {
171
+ if (str.endsWith("ies")) {
172
+ return str.slice(0, -3) + "y";
173
+ }
174
+ if (str.endsWith("es") && (str.endsWith("xes") || str.endsWith("ches") || str.endsWith("shes") || str.endsWith("sses"))) {
175
+ return str.slice(0, -2);
176
+ }
177
+ if (str.endsWith("s") && !str.endsWith("ss")) {
178
+ return str.slice(0, -1);
179
+ }
180
+ return str;
181
+ }
182
+ function createModelContext(name) {
183
+ const singularName = singularize(name);
184
+ const pluralName = pluralize(singularName);
185
+ return {
186
+ name,
187
+ singularName,
188
+ pluralName,
189
+ pascalName: toPascalCase(singularName),
190
+ pascalPlural: toPascalCase(pluralName),
191
+ camelName: toCamelCase(singularName),
192
+ camelPlural: toCamelCase(pluralName),
193
+ snakeName: toSnakeCase(singularName),
194
+ snakePlural: toSnakeCase(pluralName),
195
+ kebabName: toKebabCase(singularName),
196
+ kebabPlural: toKebabCase(pluralName),
197
+ tableName: pluralize(toSnakeCase(singularName))
198
+ };
199
+ }
200
+
201
+ // src/lib/validation.ts
141
202
  function validateModelName(name) {
142
203
  if (!name) {
143
204
  throw new Error("Model name is required");
@@ -186,6 +247,8 @@ function validateFieldDefinition(fieldDef) {
186
247
  }
187
248
  }
188
249
  }
250
+
251
+ // src/lib/parsing.ts
189
252
  function parseFields(fields) {
190
253
  return fields.map((field) => {
191
254
  validateFieldDefinition(field);
@@ -226,58 +289,8 @@ function parseFields(fields) {
226
289
  return { name, type, isReference: false, isEnum: false, nullable, unique };
227
290
  });
228
291
  }
229
- function toPascalCase(str) {
230
- return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
231
- }
232
- function toCamelCase(str) {
233
- const pascal = toPascalCase(str);
234
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
235
- }
236
- function toSnakeCase(str) {
237
- return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
238
- }
239
- function toKebabCase(str) {
240
- return toSnakeCase(str).replace(/_/g, "-");
241
- }
242
- function pluralize(str) {
243
- if (str.endsWith("y") && !/[aeiou]y$/.test(str)) {
244
- return str.slice(0, -1) + "ies";
245
- }
246
- if (str.endsWith("s") || str.endsWith("x") || str.endsWith("ch") || str.endsWith("sh")) {
247
- return str + "es";
248
- }
249
- return str + "s";
250
- }
251
- function singularize(str) {
252
- if (str.endsWith("ies")) {
253
- return str.slice(0, -3) + "y";
254
- }
255
- if (str.endsWith("es") && (str.endsWith("xes") || str.endsWith("ches") || str.endsWith("shes") || str.endsWith("sses"))) {
256
- return str.slice(0, -2);
257
- }
258
- if (str.endsWith("s") && !str.endsWith("ss")) {
259
- return str.slice(0, -1);
260
- }
261
- return str;
262
- }
263
- function createModelContext(name) {
264
- const singularName = singularize(name);
265
- const pluralName = pluralize(singularName);
266
- return {
267
- name,
268
- singularName,
269
- pluralName,
270
- pascalName: toPascalCase(singularName),
271
- pascalPlural: toPascalCase(pluralName),
272
- camelName: toCamelCase(singularName),
273
- camelPlural: toCamelCase(pluralName),
274
- snakeName: toSnakeCase(singularName),
275
- snakePlural: toSnakeCase(pluralName),
276
- kebabName: toKebabCase(singularName),
277
- kebabPlural: toKebabCase(pluralName),
278
- tableName: pluralize(toSnakeCase(singularName))
279
- };
280
- }
292
+
293
+ // src/lib/drizzle.ts
281
294
  var SQLITE_TYPE_MAP = {
282
295
  string: "text",
283
296
  text: "text",
@@ -449,8 +462,12 @@ function getRequiredImports(fields, dialect, options = {}) {
449
462
  }
450
463
  return Array.from(types);
451
464
  }
465
+
466
+ // src/lib/files.ts
467
+ import * as fs2 from "fs";
468
+ import * as path3 from "path";
452
469
  function writeFile(filePath, content, options = {}) {
453
- const exists = fs.existsSync(filePath);
470
+ const exists = fs2.existsSync(filePath);
454
471
  if (exists && !options.force) {
455
472
  log.skip(filePath);
456
473
  return false;
@@ -463,11 +480,11 @@ function writeFile(filePath, content, options = {}) {
463
480
  }
464
481
  return true;
465
482
  }
466
- const dir = path.dirname(filePath);
467
- if (!fs.existsSync(dir)) {
468
- fs.mkdirSync(dir, { recursive: true });
483
+ const dir = path3.dirname(filePath);
484
+ if (!fs2.existsSync(dir)) {
485
+ fs2.mkdirSync(dir, { recursive: true });
469
486
  }
470
- fs.writeFileSync(filePath, content);
487
+ fs2.writeFileSync(filePath, content);
471
488
  if (exists && options.force) {
472
489
  log.force(filePath);
473
490
  } else {
@@ -476,7 +493,7 @@ function writeFile(filePath, content, options = {}) {
476
493
  return true;
477
494
  }
478
495
  function deleteDirectory(dirPath, options = {}) {
479
- if (!fs.existsSync(dirPath)) {
496
+ if (!fs2.existsSync(dirPath)) {
480
497
  log.notFound(dirPath);
481
498
  return false;
482
499
  }
@@ -484,23 +501,25 @@ function deleteDirectory(dirPath, options = {}) {
484
501
  log.wouldRemove(dirPath);
485
502
  return true;
486
503
  }
487
- fs.rmSync(dirPath, { recursive: true });
504
+ fs2.rmSync(dirPath, { recursive: true });
488
505
  log.remove(dirPath);
489
506
  return true;
490
507
  }
491
508
  function fileExists(filePath) {
492
- return fs.existsSync(filePath);
509
+ return fs2.existsSync(filePath);
493
510
  }
494
511
  function readFile(filePath) {
495
- return fs.readFileSync(filePath, "utf-8");
512
+ return fs2.readFileSync(filePath, "utf-8");
496
513
  }
497
514
  function modelExistsInSchema(tableName) {
498
- const schemaPath = path.join(process.cwd(), "db", "schema.ts");
499
- if (!fs.existsSync(schemaPath)) {
515
+ const schemaPath = path3.join(getDbPath(), "schema.ts");
516
+ if (!fs2.existsSync(schemaPath)) {
500
517
  return false;
501
518
  }
502
- const content = fs.readFileSync(schemaPath, "utf-8");
503
- const pattern = new RegExp(`sqliteTable\\s*\\(\\s*["']${tableName}["']`);
519
+ const content = fs2.readFileSync(schemaPath, "utf-8");
520
+ const pattern = new RegExp(
521
+ `(?:sqliteTable|pgTable|mysqlTable)\\s*\\(\\s*["']${tableName}["']`
522
+ );
504
523
  return pattern.test(content);
505
524
  }
506
525
 
@@ -515,7 +534,7 @@ function generateModel(name, fieldArgs, options = {}) {
515
534
  `Model "${ctx.pascalName}" already exists in schema. Use --force to regenerate.`
516
535
  );
517
536
  }
518
- const schemaPath = path2.join(getDbPath(), "schema.ts");
537
+ const schemaPath = path4.join(getDbPath(), "schema.ts");
519
538
  if (fileExists(schemaPath) && !modelExistsInSchema(ctx.tableName)) {
520
539
  appendToSchema(schemaPath, ctx.camelPlural, ctx.tableName, fields, dialect, options);
521
540
  } else if (!fileExists(schemaPath)) {
@@ -625,22 +644,23 @@ function appendToSchema(schemaPath, modelName, tableName, fields, dialect, optio
625
644
  }
626
645
 
627
646
  // src/generators/actions.ts
628
- import * as path3 from "path";
647
+ import * as path5 from "path";
629
648
  function generateActions(name, options = {}) {
630
649
  validateModelName(name);
631
650
  const ctx = createModelContext(name);
632
- const actionsPath = path3.join(
651
+ const actionsPath = path5.join(
633
652
  getAppPath(),
634
653
  ctx.kebabPlural,
635
654
  "actions.ts"
636
655
  );
637
- const content = generateActionsContent(ctx);
656
+ const content = generateActionsContent(ctx, options);
638
657
  writeFile(actionsPath, content, options);
639
658
  }
640
- function generateActionsContent(ctx) {
659
+ function generateActionsContent(ctx, options = {}) {
641
660
  const { pascalName, pascalPlural, camelPlural, kebabPlural } = ctx;
642
661
  const dbImport = getDbImport();
643
662
  const schemaImport = getSchemaImport();
663
+ const idType = options.uuid ? "string" : "number";
644
664
  return `"use server";
645
665
 
646
666
  import { db } from "${dbImport}";
@@ -655,7 +675,7 @@ export async function get${pascalPlural}() {
655
675
  return db.select().from(${camelPlural}).orderBy(desc(${camelPlural}.createdAt));
656
676
  }
657
677
 
658
- export async function get${pascalName}(id: number) {
678
+ export async function get${pascalName}(id: ${idType}) {
659
679
  const result = await db
660
680
  .select()
661
681
  .from(${camelPlural})
@@ -671,7 +691,7 @@ export async function create${pascalName}(data: Omit<New${pascalName}, "id" | "c
671
691
  }
672
692
 
673
693
  export async function update${pascalName}(
674
- id: number,
694
+ id: ${idType},
675
695
  data: Partial<Omit<New${pascalName}, "id" | "createdAt" | "updatedAt">>
676
696
  ) {
677
697
  const result = await db
@@ -683,7 +703,7 @@ export async function update${pascalName}(
683
703
  return result[0];
684
704
  }
685
705
 
686
- export async function delete${pascalName}(id: number) {
706
+ export async function delete${pascalName}(id: ${idType}) {
687
707
  await db.delete(${camelPlural}).where(eq(${camelPlural}.id, id));
688
708
  revalidatePath("/${kebabPlural}");
689
709
  }
@@ -691,7 +711,7 @@ export async function delete${pascalName}(id: number) {
691
711
  }
692
712
 
693
713
  // src/generators/scaffold.ts
694
- import * as path4 from "path";
714
+ import * as path6 from "path";
695
715
  function generateScaffold(name, fieldArgs, options = {}) {
696
716
  validateModelName(name);
697
717
  const ctx = createModelContext(name);
@@ -710,25 +730,25 @@ Next steps:`);
710
730
  }
711
731
  function generatePages(ctx, fields, options = {}) {
712
732
  const { pascalName, pascalPlural, camelName, kebabPlural } = ctx;
713
- const basePath = path4.join(getAppPath(), kebabPlural);
733
+ const basePath = path6.join(getAppPath(), kebabPlural);
714
734
  writeFile(
715
- path4.join(basePath, "page.tsx"),
735
+ path6.join(basePath, "page.tsx"),
716
736
  generateIndexPage(pascalName, pascalPlural, camelName, kebabPlural, fields),
717
737
  options
718
738
  );
719
739
  writeFile(
720
- path4.join(basePath, "new", "page.tsx"),
740
+ path6.join(basePath, "new", "page.tsx"),
721
741
  generateNewPage(pascalName, camelName, kebabPlural, fields),
722
742
  options
723
743
  );
724
744
  writeFile(
725
- path4.join(basePath, "[id]", "page.tsx"),
726
- generateShowPage(pascalName, pascalPlural, camelName, kebabPlural, fields),
745
+ path6.join(basePath, "[id]", "page.tsx"),
746
+ generateShowPage(pascalName, pascalPlural, camelName, kebabPlural, fields, options),
727
747
  options
728
748
  );
729
749
  writeFile(
730
- path4.join(basePath, "[id]", "edit", "page.tsx"),
731
- generateEditPage(pascalName, camelName, kebabPlural, fields),
750
+ path6.join(basePath, "[id]", "edit", "page.tsx"),
751
+ generateEditPage(pascalName, camelName, kebabPlural, fields, options),
732
752
  options
733
753
  );
734
754
  }
@@ -833,7 +853,12 @@ ${fields.map((f) => generateFormField(f, camelName)).join("\n\n")}
833
853
  }
834
854
  `;
835
855
  }
836
- function generateShowPage(pascalName, _pascalPlural, camelName, kebabPlural, fields) {
856
+ function generateShowPage(pascalName, _pascalPlural, camelName, kebabPlural, fields, options = {}) {
857
+ const idHandling = options.uuid ? `const ${camelName} = await get${pascalName}(id);` : `const numericId = Number(id);
858
+ if (isNaN(numericId)) {
859
+ notFound();
860
+ }
861
+ const ${camelName} = await get${pascalName}(numericId);`;
837
862
  return `import { notFound } from "next/navigation";
838
863
  import Link from "next/link";
839
864
  import { get${pascalName} } from "../actions";
@@ -844,7 +869,7 @@ export default async function ${pascalName}Page({
844
869
  params: Promise<{ id: string }>;
845
870
  }) {
846
871
  const { id } = await params;
847
- const ${camelName} = await get${pascalName}(parseInt(id));
872
+ ${idHandling}
848
873
 
849
874
  if (!${camelName}) {
850
875
  notFound();
@@ -887,7 +912,13 @@ ${fields.map(
887
912
  }
888
913
  `;
889
914
  }
890
- function generateEditPage(pascalName, camelName, kebabPlural, fields) {
915
+ function generateEditPage(pascalName, camelName, kebabPlural, fields, options = {}) {
916
+ const idHandling = options.uuid ? `const ${camelName} = await get${pascalName}(id);` : `const numericId = Number(id);
917
+ if (isNaN(numericId)) {
918
+ notFound();
919
+ }
920
+ const ${camelName} = await get${pascalName}(numericId);`;
921
+ const updateId = options.uuid ? "id" : "numericId";
891
922
  return `import { notFound, redirect } from "next/navigation";
892
923
  import Link from "next/link";
893
924
  import { get${pascalName}, update${pascalName} } from "../../actions";
@@ -898,7 +929,7 @@ export default async function Edit${pascalName}Page({
898
929
  params: Promise<{ id: string }>;
899
930
  }) {
900
931
  const { id } = await params;
901
- const ${camelName} = await get${pascalName}(parseInt(id));
932
+ ${idHandling}
902
933
 
903
934
  if (!${camelName}) {
904
935
  notFound();
@@ -906,7 +937,7 @@ export default async function Edit${pascalName}Page({
906
937
 
907
938
  async function handleUpdate(formData: FormData) {
908
939
  "use server";
909
- await update${pascalName}(parseInt(id), {
940
+ await update${pascalName}(${updateId}, {
910
941
  ${fields.map((f) => ` ${f.name}: ${formDataValue(f)},`).join("\n")}
911
942
  });
912
943
  redirect("/${kebabPlural}");
@@ -1114,7 +1145,7 @@ Next steps:`);
1114
1145
  }
1115
1146
 
1116
1147
  // src/generators/api.ts
1117
- import * as path5 from "path";
1148
+ import * as path7 from "path";
1118
1149
  function generateApi(name, fieldArgs, options = {}) {
1119
1150
  validateModelName(name);
1120
1151
  const ctx = createModelContext(name);
@@ -1130,19 +1161,19 @@ Next steps:`);
1130
1161
  log.info(` 2. API available at /api/${ctx.kebabPlural}`);
1131
1162
  }
1132
1163
  function generateRoutes(camelPlural, kebabPlural, options) {
1133
- const basePath = path5.join(getAppPath(), "api", kebabPlural);
1164
+ const basePath = path7.join(getAppPath(), "api", kebabPlural);
1134
1165
  writeFile(
1135
- path5.join(basePath, "route.ts"),
1136
- generateCollectionRoute(camelPlural),
1166
+ path7.join(basePath, "route.ts"),
1167
+ generateCollectionRoute(camelPlural, kebabPlural),
1137
1168
  options
1138
1169
  );
1139
1170
  writeFile(
1140
- path5.join(basePath, "[id]", "route.ts"),
1141
- generateMemberRoute(camelPlural),
1171
+ path7.join(basePath, "[id]", "route.ts"),
1172
+ generateMemberRoute(camelPlural, kebabPlural, options),
1142
1173
  options
1143
1174
  );
1144
1175
  }
1145
- function generateCollectionRoute(camelPlural) {
1176
+ function generateCollectionRoute(camelPlural, kebabPlural) {
1146
1177
  const dbImport = getDbImport();
1147
1178
  const schemaImport = getSchemaImport();
1148
1179
  return `import { db } from "${dbImport}";
@@ -1158,7 +1189,8 @@ export async function GET() {
1158
1189
  .orderBy(desc(${camelPlural}.createdAt));
1159
1190
 
1160
1191
  return NextResponse.json(data);
1161
- } catch {
1192
+ } catch (error) {
1193
+ console.error("GET /api/${kebabPlural} failed:", error);
1162
1194
  return NextResponse.json(
1163
1195
  { error: "Failed to fetch records" },
1164
1196
  { status: 500 }
@@ -1172,7 +1204,14 @@ export async function POST(request: Request) {
1172
1204
  const result = await db.insert(${camelPlural}).values(body).returning();
1173
1205
 
1174
1206
  return NextResponse.json(result[0], { status: 201 });
1175
- } catch {
1207
+ } catch (error) {
1208
+ console.error("POST /api/${kebabPlural} failed:", error);
1209
+ if (error instanceof SyntaxError) {
1210
+ return NextResponse.json(
1211
+ { error: "Invalid JSON in request body" },
1212
+ { status: 400 }
1213
+ );
1214
+ }
1176
1215
  return NextResponse.json(
1177
1216
  { error: "Failed to create record" },
1178
1217
  { status: 500 }
@@ -1181,9 +1220,18 @@ export async function POST(request: Request) {
1181
1220
  }
1182
1221
  `;
1183
1222
  }
1184
- function generateMemberRoute(camelPlural) {
1223
+ function generateMemberRoute(camelPlural, kebabPlural, options = {}) {
1185
1224
  const dbImport = getDbImport();
1186
1225
  const schemaImport = getSchemaImport();
1226
+ const idValidation = options.uuid ? "" : `
1227
+ const numericId = Number(id);
1228
+ if (isNaN(numericId)) {
1229
+ return NextResponse.json(
1230
+ { error: "Invalid ID format" },
1231
+ { status: 400 }
1232
+ );
1233
+ }`;
1234
+ const idValue = options.uuid ? "id" : "numericId";
1187
1235
  return `import { db } from "${dbImport}";
1188
1236
  import { ${camelPlural} } from "${schemaImport}";
1189
1237
  import { eq } from "drizzle-orm";
@@ -1193,11 +1241,11 @@ type Params = { params: Promise<{ id: string }> };
1193
1241
 
1194
1242
  export async function GET(request: Request, { params }: Params) {
1195
1243
  try {
1196
- const { id } = await params;
1244
+ const { id } = await params;${idValidation}
1197
1245
  const result = await db
1198
1246
  .select()
1199
1247
  .from(${camelPlural})
1200
- .where(eq(${camelPlural}.id, parseInt(id)))
1248
+ .where(eq(${camelPlural}.id, ${idValue}))
1201
1249
  .limit(1);
1202
1250
 
1203
1251
  if (!result[0]) {
@@ -1208,7 +1256,8 @@ export async function GET(request: Request, { params }: Params) {
1208
1256
  }
1209
1257
 
1210
1258
  return NextResponse.json(result[0]);
1211
- } catch {
1259
+ } catch (error) {
1260
+ console.error("GET /api/${kebabPlural}/[id] failed:", error);
1212
1261
  return NextResponse.json(
1213
1262
  { error: "Failed to fetch record" },
1214
1263
  { status: 500 }
@@ -1218,12 +1267,12 @@ export async function GET(request: Request, { params }: Params) {
1218
1267
 
1219
1268
  export async function PATCH(request: Request, { params }: Params) {
1220
1269
  try {
1221
- const { id } = await params;
1270
+ const { id } = await params;${idValidation}
1222
1271
  const body = await request.json();
1223
1272
  const result = await db
1224
1273
  .update(${camelPlural})
1225
1274
  .set({ ...body, updatedAt: new Date() })
1226
- .where(eq(${camelPlural}.id, parseInt(id)))
1275
+ .where(eq(${camelPlural}.id, ${idValue}))
1227
1276
  .returning();
1228
1277
 
1229
1278
  if (!result[0]) {
@@ -1234,7 +1283,14 @@ export async function PATCH(request: Request, { params }: Params) {
1234
1283
  }
1235
1284
 
1236
1285
  return NextResponse.json(result[0]);
1237
- } catch {
1286
+ } catch (error) {
1287
+ console.error("PATCH /api/${kebabPlural}/[id] failed:", error);
1288
+ if (error instanceof SyntaxError) {
1289
+ return NextResponse.json(
1290
+ { error: "Invalid JSON in request body" },
1291
+ { status: 400 }
1292
+ );
1293
+ }
1238
1294
  return NextResponse.json(
1239
1295
  { error: "Failed to update record" },
1240
1296
  { status: 500 }
@@ -1244,11 +1300,12 @@ export async function PATCH(request: Request, { params }: Params) {
1244
1300
 
1245
1301
  export async function DELETE(request: Request, { params }: Params) {
1246
1302
  try {
1247
- const { id } = await params;
1248
- await db.delete(${camelPlural}).where(eq(${camelPlural}.id, parseInt(id)));
1303
+ const { id } = await params;${idValidation}
1304
+ await db.delete(${camelPlural}).where(eq(${camelPlural}.id, ${idValue}));
1249
1305
 
1250
1306
  return new NextResponse(null, { status: 204 });
1251
- } catch {
1307
+ } catch (error) {
1308
+ console.error("DELETE /api/${kebabPlural}/[id] failed:", error);
1252
1309
  return NextResponse.json(
1253
1310
  { error: "Failed to delete record" },
1254
1311
  { status: 500 }
@@ -1259,7 +1316,7 @@ export async function DELETE(request: Request, { params }: Params) {
1259
1316
  }
1260
1317
 
1261
1318
  // src/generators/destroy.ts
1262
- import * as path6 from "path";
1319
+ import * as path8 from "path";
1263
1320
  function destroyScaffold(name, options = {}) {
1264
1321
  validateModelName(name);
1265
1322
  const ctx = createModelContext(name);
@@ -1268,7 +1325,7 @@ function destroyScaffold(name, options = {}) {
1268
1325
  log.info(`
1269
1326
  ${prefix}Destroying scaffold ${ctx.pascalName}...
1270
1327
  `);
1271
- const basePath = path6.join(getAppPath(), ctx.kebabPlural);
1328
+ const basePath = path8.join(getAppPath(), ctx.kebabPlural);
1272
1329
  deleteDirectory(basePath, options);
1273
1330
  log.info(`
1274
1331
  Note: Schema in ${config.dbPath}/schema.ts was not modified.`);
@@ -1282,7 +1339,7 @@ function destroyResource(name, options = {}) {
1282
1339
  log.info(`
1283
1340
  ${prefix}Destroying resource ${ctx.pascalName}...
1284
1341
  `);
1285
- const basePath = path6.join(getAppPath(), ctx.kebabPlural);
1342
+ const basePath = path8.join(getAppPath(), ctx.kebabPlural);
1286
1343
  deleteDirectory(basePath, options);
1287
1344
  log.info(`
1288
1345
  Note: Schema in ${config.dbPath}/schema.ts was not modified.`);
@@ -1296,7 +1353,7 @@ function destroyApi(name, options = {}) {
1296
1353
  log.info(`
1297
1354
  ${prefix}Destroying API ${ctx.pascalName}...
1298
1355
  `);
1299
- const basePath = path6.join(getAppPath(), "api", ctx.kebabPlural);
1356
+ const basePath = path8.join(getAppPath(), "api", ctx.kebabPlural);
1300
1357
  deleteDirectory(basePath, options);
1301
1358
  log.info(`
1302
1359
  Note: Schema in ${config.dbPath}/schema.ts was not modified.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextjs-drizzle-gen",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Rails-like generators for Next.js + Drizzle ORM projects",
5
5
  "type": "module",
6
6
  "bin": {