Skip to content

Commit

Permalink
feat: Improve record handling #25
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianwessel committed Jul 9, 2024
1 parent 55512f1 commit b0fd340
Show file tree
Hide file tree
Showing 8 changed files with 489 additions and 344 deletions.
744 changes: 429 additions & 315 deletions package-lock.json

Large diffs are not rendered by default.

26 changes: 6 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,7 @@
"node": ">=18"
},
"description": "A small tool which generates a typescript client for SurrealDB based on the schema of a given database",
"keywords": [
"typescript",
"surrealdb",
"client",
"javascript",
"zod",
"orm",
"database",
"generator",
"tool"
],
"keywords": ["typescript", "surrealdb", "client", "javascript", "zod", "orm", "database", "generator", "tool"],
"author": {
"name": "Sebastian Wessel",
"url": "https://github.com/sebastianwessel"
Expand All @@ -31,9 +21,7 @@
"email": "[email protected]"
},
"license": "MIT",
"files": [
"dist"
],
"files": ["dist"],
"bin": {
"surql-gen": "./dist/commonjs/index.js"
},
Expand All @@ -47,16 +35,16 @@
},
"devDependencies": {
"@biomejs/biome": "^1.8.3",
"@types/node": "^20.14.9",
"tshy": "^1.15.1",
"@types/node": "^20.14.10",
"tshy": "^3.0.2",
"tsx": "^4.15.7",
"typescript": "^5.5.2",
"vitest": "^1.6.0"
"vitest": "^2.0.1"
},
"dependencies": {
"commander": "^12.1.0",
"mkdirp": "^3.0.1",
"rimraf": "^5.0.7",
"rimraf": "^6.0.0",
"surrealdb.js": "^1.0.0-beta.9",
"zod": "^3.23.8"
},
Expand All @@ -70,12 +58,10 @@
"./package.json": "./package.json",
".": {
"import": {
"source": "./src/index.ts",
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"source": "./src/index.ts",
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
Expand Down
39 changes: 39 additions & 0 deletions src/genSchema/ensureRecordSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { existsSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { mkdirp } from 'mkdirp'

export const ensureRecordSchema = async (rootPath: string) => {
const content = `import z from 'zod';
import { RecordId } from 'surrealdb.js'
export const ZRecordIdInstanceOf = z.instanceof(RecordId);
export function recordId<Table extends string = string>(table?: Table) {
return z.custom<RecordId<\`$Table\`>>(
val => {
const instanceOfCheck = ZRecordIdInstanceOf.safeParse(val);
const tableCheck = table ? val?.tb === table : true;
return instanceOfCheck.success && tableCheck;
},
val => {
let msgArray: string[] = [];
const instanceOfCheck = ZRecordIdInstanceOf.safeParse(val);
if (!instanceOfCheck.success) msgArray.push('Must be a RecordId class');
const tableCheck = table ? val?.tb === table : true;
if (!tableCheck) msgArray.push(\`RecordId must be of type '\${table}', not '\${val?.tb}'\`);
return { message: msgArray.join("; ") };
}
);
}`

await mkdirp(rootPath)

const fileName = join(rootPath, 'recordSchema.ts')

console.log(fileName)
if (!existsSync(fileName)) {
writeFileSync(fileName, content, { flag: 'wx' })
}
}
12 changes: 6 additions & 6 deletions src/genSchema/generateTableSchema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ describe('generateTableSchema', () => {
'DEFINE TABLE test TYPE ANY SCHEMAFULL PERMISSIONS NONE',
)

expect(inputFields).toBe('z.object({name: z.string(),\n})')
expect(inputFields).toBe('const testInputSchemaGen = z.object({\nname: z.string(),\n})')

expect(outputFields).toBe('z.object({name: z.string(),\n})')
expect(outputFields).toBe('const testOutputSchemaGen = z.object({\nname: z.string(),\n})')
})

it('generates a schema for a SCHEMALESS table', async () => {
Expand All @@ -24,9 +24,9 @@ describe('generateTableSchema', () => {
'DEFINE TABLE test TYPE ANY SCHEMALESS PERMISSIONS NONE',
)

expect(inputFields).toBe('z.object({name: z.string(),\n}).passthrough()')
expect(inputFields).toBe('const testInputSchemaGen = z.object({\nname: z.string(),\n}).passthrough()')

expect(outputFields).toBe('z.object({name: z.string(),\n}).passthrough()')
expect(outputFields).toBe('const testOutputSchemaGen = z.object({\nname: z.string(),\n}).passthrough()')
})

it('generates a schema for a table without explicit SCHEMAFULL/SCHEMALESS setting', async () => {
Expand All @@ -35,8 +35,8 @@ describe('generateTableSchema', () => {
'DEFINE TABLE test TYPE ANY PERMISSIONS NONE',
)

expect(inputFields).toBe('z.object({name: z.string(),\n}).passthrough()')
expect(inputFields).toBe('const testInputSchemaGen = z.object({\nname: z.string(),\n}).passthrough()')

expect(outputFields).toBe('z.object({name: z.string(),\n}).passthrough()')
expect(outputFields).toBe('const testOutputSchemaGen = z.object({\nname: z.string(),\n}).passthrough()')
})
})
6 changes: 6 additions & 0 deletions src/genSchema/generateTableSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { rimraf } from 'rimraf'
import { getTableInfo } from '../database/getTableInfo.js'
import { toCamelCase } from '../helper/toCamelCase.js'
import { toUpperCamelCase } from '../helper/toUpperCamelCase.js'
import { ensureRecordSchema } from './ensureRecordSchema.js'
import { mergeNested } from './mergeNested.js'

export const generateSchemaForTable = async (name: string, tableInfo: string) => {
Expand Down Expand Up @@ -36,6 +37,8 @@ export const generateTableSchema = async (outFolder: string, tableInfo: Record<s

console.log('Generating schema in', genSchemaFolder)

await ensureRecordSchema(genSchemaFolder)

for (const name in tableInfo) {
// biome-ignore lint/style/noNonNullAssertion: During iteration over object, we know the key-value exist
const { inputFields, outputFields } = await generateSchemaForTable(name, tableInfo[name]!)
Expand All @@ -49,13 +52,16 @@ export const generateTableSchema = async (outFolder: string, tableInfo: Record<s
const genSchemaFileName = resolve(tableSchemaFolder, `${toCamelCase(tableName)}SchemaGen.ts`)
const genSchemaFile = createWriteStream(genSchemaFileName)

const injectRecordSchema = inputFields.includes('recordId(') || outputFields.includes('recordId(')

genSchemaFile.write(
`// ====================
// DO NOT EDIT THIS FILE!
// This file is autogenerated and will be overwritten during generation!
// ====================
import { z } from "zod";
${injectRecordSchema ? 'import { recordId } from "../recordSchema.js"' : ''}
// the create schema for table ${name}
export ${inputFields};
Expand Down
2 changes: 1 addition & 1 deletion src/genSchema/generateZodSchemaCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const generateZodSchemaCode = (fields: FieldDetail[], schemaName: string)
return entries.join(',\n ')
}

return `const ${schemaName} = z.object({\n ${buildObject(fieldMap)}\n})`
return `const ${schemaName} = z.object({\n${buildObject(fieldMap)},\n})`
}

const fieldMap: { [key: string]: unknown } = {}
Expand Down
2 changes: 1 addition & 1 deletion src/genSchema/getDetailsFromDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const getSchemaForType = (type: string, tokens: TokenizedDefinition, subSchema?:
case 'record': {
const type = tokens.type?.match(recordRegex)?.[1]
if (type) {
return `z.string().startsWith('${type}:')`
return `recordId('${type}')`
}
return 'z.string()'
}
Expand Down
2 changes: 1 addition & 1 deletion src/genSchema/tokenize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const tokenize = (definition: string): TokenizedDefinition => {
// Default makes no sense in base schemas.
// Get should only return existing values
// Create with default means optional in schema
//result.default = defaultMatch[1]
result.default = defaultMatch[1]
}

if (definition.match(/READONLY/im)) {
Expand Down

0 comments on commit b0fd340

Please sign in to comment.