Skip to content

Commit 7fbc960

Browse files
added automatic zod schema generation
Signed-off-by: Benjamin Strasser <bp.strasser@gmail.com>
1 parent 81a7737 commit 7fbc960

File tree

3 files changed

+66
-1
lines changed

3 files changed

+66
-1
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"migrate:latest": "pnpm run migrate -- latest",
2323
"migrate:up": "pnpm run migrate -- up",
2424
"migrate:down": "pnpm run migrate -- down",
25-
"sync:db": "cross-env DATABASE_URL=db.sqlite kysely-codegen",
25+
"sync:db": "cross-env DATABASE_URL=db.sqlite kysely-codegen --out-file services/src/generated/db.d.ts && tsx ./services/scripts/genTypes.ts && ts-to-zod ./services/src/generated/generated-db.d.ts ./services/src/generated/db.zod.ts",
2626
"---- DOCS ----------------------------------------------------------": "",
2727
"docs:dev": "vitepress dev docs",
2828
"docs:build": "vitepress build docs",

services/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
src/generated

services/scripts/genTypes.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import fs from 'fs'
2+
3+
const codgenPath = './services/src/generated/db.d.ts'
4+
5+
// read codegenpath file and overwrite first line with new content
6+
const fileContent = fs.readFileSync(codgenPath, 'utf8')
7+
8+
const generatedInterfaces = generateInterfaces(fileContent)
9+
10+
fs.writeFileSync('./services/src/generated/generated-db.d.ts', generatedInterfaces)
11+
12+
function generateInterfaces(fileContent: string): string {
13+
// Helper function to extract the type name and its content
14+
const extractInterfaceContent = (interfaceName: string, content: string) => {
15+
const regex = new RegExp(`export interface ${interfaceName} \\{([\\s\\S]*?)\\}`, 'm')
16+
const match = content.match(regex)
17+
18+
return match ? match[1].trim() : ''
19+
}
20+
21+
// Helper function to generate new interfaces based on rules
22+
const generateNewInterfaces = (interfaceName: string, interfaceContent: string) => {
23+
const lines = interfaceContent.split('\n').map((line) => line.trim())
24+
let selectable = `export interface Selectable${interfaceName} {\n`
25+
let insertable = `export interface Insertable${interfaceName} {\n`
26+
let updatable = `export interface Updatable${interfaceName} {\n`
27+
28+
for (const line of lines) {
29+
const [key, type] = line.split(':').map((part) => part.trim().replace(';', ''))
30+
if (type.startsWith('Generated<')) {
31+
const actualType = type.match(/Generated<([^>]*)>/)[1]
32+
selectable += ` ${key}: ${actualType};\n`
33+
updatable += ` ${key}: ${actualType};\n`
34+
} else {
35+
selectable += ` ${key}: ${type};\n`
36+
insertable += ` ${key}: ${type};\n`
37+
updatable += ` ${key}: ${type};\n`
38+
}
39+
}
40+
41+
insertable = insertable.replace(/ {2}\w+: Generated<.*>;\n/g, '')
42+
selectable += '}'
43+
insertable += '}'
44+
updatable += '}'
45+
46+
return `${selectable}\n\n${insertable}\n\n${updatable}\n\n`
47+
}
48+
49+
// Extract all interfaces
50+
const interfaceNames = Array.from(fileContent.matchAll(/export interface (\w+) \{/g)).map(
51+
(match) => match[1]
52+
)
53+
54+
let result = ''
55+
56+
for (const interfaceName of interfaceNames) {
57+
if (interfaceName !== 'DB' && interfaceName !== 'Generated') {
58+
const interfaceContent = extractInterfaceContent(interfaceName, fileContent)
59+
result += generateNewInterfaces(interfaceName, interfaceContent)
60+
}
61+
}
62+
63+
return result
64+
}

0 commit comments

Comments
 (0)