diff --git a/README.md b/README.md index 67b33b5..2fbf797 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,10 @@ yarn add -D prisma-typebox-generator ```prisma generator typebox { provider = "prisma-typebox-generator" + // Optionally exclude relations from the generated types + includeRelations = false // default: true + // Optionally prefix all types with a string + prefix = "tbx_" // default: "" } ``` diff --git a/package.json b/package.json index 8dc9554..1151ff0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "prisma-typebox-generator", - "version": "2.0.2", + "name": "jsbrain-prisma-typebox-generator", + "version": "2.0.6", "main": "dist/index.js", "license": "MIT", "files": [ @@ -20,44 +20,44 @@ "typebox", "typebox-generator" ], - "homepage": "https://github.com/adeyahya/prisma-typebox-generator", + "homepage": "https://github.com/jsbrain/prisma-typebox-generator", "repository": { - "url": "https://github.com/adeyahya/prisma-typebox-generator.git" + "url": "https://github.com/jsbrain/prisma-typebox-generator.git" }, "bugs": { "email": "adeyahyaprasetyo@gmail.com", - "url": "https://github.com/adeyahya/prisma-typebox-generator/issues" + "url": "https://github.com/jsbrain/prisma-typebox-generator/issues" }, "dependencies": { - "@prisma/generator-helper": "^2.20.1", - "@prisma/sdk": "^2.20.1", + "@prisma/generator-helper": "^4.5.0", + "@prisma/sdk": "^4.0.0", "core-js": "3.10.0", - "prettier": "^2.3.0" + "prettier": "^2.7.1" }, "devDependencies": { "@babel/cli": "^7.13.14", "@babel/core": "^7.13.14", "@babel/preset-env": "^7.13.12", "@babel/preset-typescript": "^7.13.0", - "@prisma/client": "^2.20.1", + "@prisma/client": "^4.5.0", "@semantic-release/changelog": "^5.0.1", "@semantic-release/commit-analyzer": "^8.0.1", "@semantic-release/git": "^9.0.0", "@semantic-release/github": "^7.2.0", "@semantic-release/npm": "^7.1.0", "@semantic-release/release-notes-generator": "^9.0.2", - "@sinclair/typebox": "^0.16.7", + "@sinclair/typebox": "^0.25.2", "@types/jest": "26.0.22", "@types/node": "^14.14.37", - "ajv": "^8.0.5", - "ajv-formats": "^2.0.2", + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", "babel-jest": "^26.6.3", "jest": "26.6.3", - "prisma": "^2.20.1", + "prisma": "^4.5.0", "semantic-release": "^17.4.2", "ts-jest": "^26.5.4", "ts-node": "^9.1.1", - "typescript": "^4.2.3" + "typescript": "^4.8.4" }, "scripts": { "generate": "prisma generate", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 87477e6..e21c264 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -3,7 +3,9 @@ generator client { } generator docs { - provider = "node ./dist/index.js" + provider = "node ./dist/index.js" + includeRelations = false + prefix = "tbx_" } datasource db { @@ -28,6 +30,7 @@ model User { } model Post { + // id String @id @default(cuid()) id Int @id @default(autoincrement()) user User? @relation(fields: [userId], references: [id]) userId Int? diff --git a/prisma/typebox/Post.ts b/prisma/typebox/Post.ts index a46e9a8..feb11b4 100644 --- a/prisma/typebox/Post.ts +++ b/prisma/typebox/Post.ts @@ -1,25 +1,9 @@ import { Type, Static } from "@sinclair/typebox"; -import { Role } from "./Role"; +import { tbx_Role } from "./Role"; -export const Post = Type.Object({ +export const tbx_Post = Type.Object({ id: Type.Number(), - user: Type.Optional( - Type.Object({ - id: Type.Number(), - createdAt: Type.Optional(Type.String()), - email: Type.String(), - weight: Type.Optional(Type.Number()), - is18: Type.Optional(Type.Boolean()), - name: Type.Optional(Type.String()), - successorId: Type.Optional(Type.Number()), - role: Type.Optional(Role), - keywords: Type.Array(Type.String()), - biography: Type.String(), - decimal: Type.Number(), - biginteger: Type.Integer(), - }) - ), userId: Type.Optional(Type.Number()), }); -export type PostType = Static; +export type tbx_PostType = Static; diff --git a/prisma/typebox/PostInput.ts b/prisma/typebox/PostInput.ts index 43e96d1..97ed9b1 100644 --- a/prisma/typebox/PostInput.ts +++ b/prisma/typebox/PostInput.ts @@ -1,25 +1,9 @@ import { Type, Static } from "@sinclair/typebox"; -import { Role } from "./Role"; +import { tbx_Role } from "./Role"; -export const PostInput = Type.Object({ +export const tbx_PostInput = Type.Object({ id: Type.Optional(Type.Number()), - user: Type.Optional( - Type.Object({ - id: Type.Optional(Type.Number()), - createdAt: Type.Optional(Type.String()), - email: Type.String(), - weight: Type.Optional(Type.Number()), - is18: Type.Optional(Type.Boolean()), - name: Type.Optional(Type.String()), - successorId: Type.Optional(Type.Number()), - role: Type.Optional(Role), - keywords: Type.Array(Type.String()), - biography: Type.String(), - decimal: Type.Number(), - biginteger: Type.Integer(), - }) - ), userId: Type.Optional(Type.Number()), }); -export type PostInputType = Static; +export type tbx_PostInputType = Static; diff --git a/prisma/typebox/Role.ts b/prisma/typebox/Role.ts index ca45732..6ee7189 100644 --- a/prisma/typebox/Role.ts +++ b/prisma/typebox/Role.ts @@ -1,10 +1,10 @@ import { Type, Static } from "@sinclair/typebox"; -export const RoleConst = { +export const tbx_RoleConst = { USER: Type.Literal("USER"), ADMIN: Type.Literal("ADMIN"), }; -export const Role = Type.KeyOf(Type.Object(RoleConst)); +export const tbx_Role = Type.KeyOf(Type.Object(tbx_RoleConst)); -export type RoleType = Static; +export type tbx_RoleType = Static; diff --git a/prisma/typebox/User.ts b/prisma/typebox/User.ts index 4b56c55..128cf91 100644 --- a/prisma/typebox/User.ts +++ b/prisma/typebox/User.ts @@ -1,7 +1,7 @@ import { Type, Static } from "@sinclair/typebox"; -import { Role } from "./Role"; +import { tbx_Role } from "./Role"; -export const User = Type.Object({ +export const tbx_User = Type.Object({ id: Type.Number(), createdAt: Type.Optional(Type.String()), email: Type.String(), @@ -9,17 +9,11 @@ export const User = Type.Object({ is18: Type.Optional(Type.Boolean()), name: Type.Optional(Type.String()), successorId: Type.Optional(Type.Number()), - role: Type.Optional(Role), - posts: Type.Array( - Type.Object({ - id: Type.Number(), - userId: Type.Optional(Type.Number()), - }) - ), + role: Type.Optional(tbx_Role), keywords: Type.Array(Type.String()), biography: Type.String(), decimal: Type.Number(), biginteger: Type.Integer(), }); -export type UserType = Static; +export type tbx_UserType = Static; diff --git a/prisma/typebox/UserInput.ts b/prisma/typebox/UserInput.ts index 702b3bd..40c328c 100644 --- a/prisma/typebox/UserInput.ts +++ b/prisma/typebox/UserInput.ts @@ -1,7 +1,7 @@ import { Type, Static } from "@sinclair/typebox"; -import { Role } from "./Role"; +import { tbx_Role } from "./Role"; -export const UserInput = Type.Object({ +export const tbx_UserInput = Type.Object({ id: Type.Optional(Type.Number()), createdAt: Type.Optional(Type.String()), email: Type.String(), @@ -9,17 +9,11 @@ export const UserInput = Type.Object({ is18: Type.Optional(Type.Boolean()), name: Type.Optional(Type.String()), successorId: Type.Optional(Type.Number()), - role: Type.Optional(Role), - posts: Type.Array( - Type.Object({ - id: Type.Optional(Type.Number()), - userId: Type.Optional(Type.Number()), - }) - ), + role: Type.Optional(tbx_Role), keywords: Type.Array(Type.String()), biography: Type.String(), decimal: Type.Number(), biginteger: Type.Integer(), }); -export type UserInputType = Static; +export type tbx_UserInputType = Static; diff --git a/prisma/typebox/index.ts b/prisma/typebox/index.ts new file mode 100644 index 0000000..9005f99 --- /dev/null +++ b/prisma/typebox/index.ts @@ -0,0 +1,5 @@ +export * from './User'; +export * from './Post'; +export * from './UserInput'; +export * from './PostInput'; +export * from './Role'; diff --git a/src/generator/transformDMMF.ts b/src/generator/transformDMMF.ts index 919516c..80250c3 100644 --- a/src/generator/transformDMMF.ts +++ b/src/generator/transformDMMF.ts @@ -1,5 +1,17 @@ import type { DMMF } from '@prisma/generator-helper'; +type TransformDMMFOptions = { + prefix?: string; + includeRelations?: 'true' | 'false'; +}; + +function prefixName(name: string, prefix?: string) { + if (prefix) { + return `${prefix}${name}`; + } + return name; +} + const transformField = (field: DMMF.Field) => { const tokens = [field.name + ':']; let inputTokens = []; @@ -25,8 +37,8 @@ const transformField = (field: DMMF.Field) => { inputTokens = [...tokens]; - // @id cannot be optional except for input if it's auto increment - if (field.isId && (field?.default as any)?.name === 'autoincrement') { + // @id can be optional for input value if it has a default defined + if (field.isId && (field?.default as any)) { inputTokens.splice(1, 0, 'Type.Optional('); inputTokens.splice(inputTokens.length, 0, ')'); } @@ -42,17 +54,32 @@ const transformField = (field: DMMF.Field) => { str: tokens.join(' ').concat('\n'), strInput: inputTokens.join(' ').concat('\n'), deps, + original: field, }; }; -const transformFields = (fields: DMMF.Field[]) => { +const transformFields = ( + fields: DMMF.Field[], + config: TransformDMMFOptions, +) => { let dependencies = new Set(); const _fields: string[] = []; const _inputFields: string[] = []; fields.map(transformField).forEach((field) => { + // TODO: Remove and add raw models as separate files. + if ( + field.deps.size > 0 && + config.includeRelations === 'false' && + // Check if the field is a relation, so we'll still include enums. + !!field.original.relationName + ) { + return; + } + _fields.push(field.str); _inputFields.push(field.strInput); + [...field.deps].forEach((d) => { dependencies.add(d); }); @@ -65,15 +92,25 @@ const transformFields = (fields: DMMF.Field[]) => { }; }; -const transformModel = (model: DMMF.Model, models?: DMMF.Model[]) => { - const fields = transformFields(model.fields); +const transformModel = ( + config: TransformDMMFOptions, + model: DMMF.Model, + models?: DMMF.Model[], +) => { + const fields = transformFields(model.fields, config); let raw = [ - `${models ? '' : `export const ${model.name} = `}Type.Object({\n\t`, + `${ + models ? '' : `export const ${prefixName(model.name, config.prefix)} = ` + }Type.Object({\n\t`, fields.rawString, '})', ].join('\n'); let inputRaw = [ - `${models ? '' : `export const ${model.name}Input = `}Type.Object({\n\t`, + `${ + models + ? '' + : `export const ${prefixName(model.name, config.prefix)}Input = ` + }Type.Object({\n\t`, fields.rawInputString, '})', ].join('\n'); @@ -94,21 +131,44 @@ const transformModel = (model: DMMF.Model, models?: DMMF.Model[]) => { }; }; -export const transformEnum = (enm: DMMF.DatamodelEnum) => { +export const transformEnum = ( + enm: DMMF.DatamodelEnum, + config: TransformDMMFOptions, +) => { const values = enm.values .map((v) => `${v.name}: Type.Literal('${v.name}'),\n`) .join(''); return [ - `export const ${enm.name}Const = {`, + `export const ${prefixName(enm.name, config.prefix)}Const = {`, values, '}\n', - `export const ${enm.name} = Type.KeyOf(Type.Object(${enm.name}Const))\n`, - `export type ${enm.name}Type = Static`, + `export const ${prefixName( + enm.name, + config.prefix, + )} = Type.KeyOf(Type.Object(${prefixName( + enm.name, + config.prefix, + )}Const))\n`, + `export type ${prefixName( + enm.name, + config.prefix, + )}Type = Static`, ].join('\n'); }; -export function transformDMMF(dmmf: DMMF.Document) { +export function transformDMMF( + dmmf: DMMF.Document, + _config: TransformDMMFOptions, +) { + const { includeRelations = 'true', prefix = undefined } = _config; + + // Set default config! + const config: TransformDMMFOptions = { + includeRelations, + prefix, + }; + const { models, enums } = dmmf.datamodel; const importStatements = new Set([ 'import {Type, Static} from "@sinclair/typebox"', @@ -116,12 +176,12 @@ export function transformDMMF(dmmf: DMMF.Document) { return [ ...models.map((model) => { - let { raw, inputRaw, deps } = transformModel(model); + let { raw, inputRaw, deps } = transformModel(config, model); [...deps].forEach((d) => { const depsModel = models.find((m) => m.name === d) as DMMF.Model; if (depsModel) { - const replacer = transformModel(depsModel, models); + const replacer = transformModel(config, depsModel, models); const re = new RegExp(`::${d}::`, 'gm'); raw = raw.replace(re, replacer.raw); inputRaw = inputRaw.replace(re, replacer.inputRaw); @@ -131,23 +191,38 @@ export function transformDMMF(dmmf: DMMF.Document) { enums.forEach((enm) => { const re = new RegExp(`::${enm.name}::`, 'gm'); if (raw.match(re)) { - raw = raw.replace(re, enm.name); - inputRaw = inputRaw.replace(re, enm.name); - importStatements.add(`import { ${enm.name} } from './${enm.name}'`); + raw = raw.replace(re, prefixName(enm.name, config.prefix)); + inputRaw = inputRaw.replace(re, prefixName(enm.name, config.prefix)); + importStatements.add( + `import { ${prefixName(enm.name, config.prefix)} } from './${ + enm.name + }'`, + ); } }); return { + // TODO: Add prefix to file names as well? + // name: prefixName(model.name, config.prefix), name: model.name, rawString: [ [...importStatements].join('\n'), raw, - `export type ${model.name}Type = Static`, + `export type ${prefixName( + model.name, + config.prefix, + )}Type = Static`, ].join('\n\n'), inputRawString: [ [...importStatements].join('\n'), inputRaw, - `export type ${model.name}InputType = Static`, + `export type ${prefixName( + model.name, + config.prefix, + )}InputType = Static`, ].join('\n\n'), }; }), @@ -157,7 +232,7 @@ export function transformDMMF(dmmf: DMMF.Document) { inputRawString: null, rawString: 'import {Type, Static} from "@sinclair/typebox"\n\n' + - transformEnum(enm), + transformEnum(enm, config), }; }), ]; diff --git a/src/index.ts b/src/index.ts index 4a7e2e5..8bb3a2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ generatorHandler({ }; }, async onGenerate(options) { - const payload = transformDMMF(options.dmmf); + const payload = transformDMMF(options.dmmf, options.generator.config); if (options.generator.output) { const outputDir = // This ensures previous version of prisma are still supported