UNPKG

prisma-typebox-generator

Version:

Typebox generator for prisma schema

205 lines (175 loc) 6.94 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createTransformer = createTransformer; function createTransformer(generatorName) { const transformField = field => { var _field$default; const lineRegex = new RegExp(`^@${generatorName}\\.([a-z]+) (.+)`); const tokens = [field.name + ':']; let inputTokens = []; const deps = new Set(); let overrideType; const options = []; const listOptions = []; const description = []; if (field.documentation) { const lines = field.documentation.split('\n'); for (let line of lines) { line = line.trim(); const match = line.match(lineRegex); if (match) { switch (match[1]) { case 'type': overrideType = match[2]; break; case 'opt': options.push(match[2]); break; case 'listopt': listOptions.push(match[2]); break; default: throw new Error(`${field.name}(${field.type}): uknown hint '@${generatorName}.${match[1]}'`); } } else if (line === `@${generatorName}.hide`) { return { str: '', strInput: '', deps: [] }; } else if (!line.startsWith('@')) { description.push(line); } } } if (description.length) { const opts = field.isList ? listOptions : options; if (!opts.some(opt => opt.indexOf('description') >= 0)) { opts.push('description: ' + JSON.stringify(description.join('\n').trim())); } } const optionsStr = options.length ? `{ ${options.join(', ')} }` : ''; let typeStr; if (['Int', 'Float', 'Decimal'].includes(field.type)) { typeStr = `Type.${overrideType || 'Number'}(${optionsStr})`; } else if (['BigInt'].includes(field.type)) { typeStr = `Type.${overrideType || 'Integer'}(${optionsStr})`; } else if (['String', 'DateTime', 'Json', 'Date'].includes(field.type)) { typeStr = `Type.${overrideType || 'String'}(${optionsStr})`; } else if (field.type === 'Boolean') { typeStr = `Type.${overrideType || 'Boolean'}(${optionsStr})`; } else { typeStr = `::${field.type}::`; deps.add(field.type); } if (field.isList) { const listOptionsStr = listOptions.length ? `, { ${listOptions.join(', ')} }` : ''; typeStr = `Type.Array(${typeStr}${listOptionsStr})`; } tokens.push(typeStr); inputTokens = [...tokens]; // @id cannot be optional except for input if it's auto increment if (field.isId && (field === null || field === void 0 ? void 0 : (_field$default = field.default) === null || _field$default === void 0 ? void 0 : _field$default.name) === 'autoincrement') { inputTokens.splice(1, 0, 'Type.Optional('); inputTokens.splice(inputTokens.length, 0, ')'); } if ((!field.isRequired || field.hasDefaultValue) && !field.isId) { tokens.splice(1, 0, 'Type.Optional('); tokens.splice(tokens.length, 0, ')'); inputTokens.splice(1, 0, 'Type.Optional('); inputTokens.splice(inputTokens.length, 0, ')'); } return { str: tokens.join(' ').concat('\n'), strInput: inputTokens.join(' ').concat('\n'), deps }; }; const transformFields = fields => { let dependencies = new Set(); const _fields = []; const _inputFields = []; fields.map(transformField).forEach(field => { _fields.push(field.str); _inputFields.push(field.strInput); [...field.deps].forEach(d => { dependencies.add(d); }); }); return { dependencies, rawString: _fields.filter(f => !!f).join(','), rawInputString: _inputFields.filter(f => !!f).join(',') }; }; const transformModel = (model, models) => { var _model$documentation; const description = (_model$documentation = model.documentation) === null || _model$documentation === void 0 ? void 0 : _model$documentation.split('\n').filter(line => !line.startsWith('@')).join('\n').trim(); const optionsStr = description !== null && description !== void 0 && description.length ? `, { description: ${JSON.stringify(description)} }` : ''; const fields = transformFields(model.fields); let raw = [`${models ? '' : `export const ${model.name} = `}Type.Object({\n\t`, fields.rawString, `}${optionsStr})`].join('\n'); let inputRaw = [`${models ? '' : `export const ${model.name}Input = `}Type.Object({\n\t`, fields.rawInputString, `}${optionsStr})`].join('\n'); if (Array.isArray(models)) { models.forEach(md => { const re = new RegExp(`.+::${md.name}.+\n`, 'gm'); const inputRe = new RegExp(`.+::${md.name}.+\n`, 'gm'); raw = raw.replace(re, ''); inputRaw = inputRaw.replace(inputRe, ''); }); } return { raw, inputRaw, deps: fields.dependencies }; }; const transformEnum = enm => { const values = enm.values.map(v => `${v.name}: Type.Literal('${v.name}'),\n`).join(''); return [`export const ${enm.name}Const = {`, values, '}\n', `export const ${enm.name} = Type.KeyOf(Type.Object(${enm.name}Const))\n`, `export type ${enm.name}Type = Static<typeof ${enm.name}>`].join('\n'); }; function transformDMMF(dmmf) { const { models, enums } = dmmf.datamodel; const mainImport = 'import {Type, Static} from "@sinclair/typebox"'; return [...models.map(model => { let { raw, inputRaw, deps } = transformModel(model); [...deps].forEach(d => { const depsModel = models.find(m => m.name === d); if (depsModel) { const replacer = transformModel(depsModel, models); const re = new RegExp(`::${d}::`, 'gm'); raw = raw.replace(re, replacer.raw); inputRaw = inputRaw.replace(re, replacer.inputRaw); } }); const importStatements = new Set(); 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}'`); } }); return { name: model.name, rawString: [[mainImport, ...importStatements].join('\n'), raw, `export type ${model.name}Type = Static<typeof ${model.name}>`].join('\n\n'), inputRawString: [[mainImport, ...importStatements].join('\n'), inputRaw, `export type ${model.name}InputType = Static<typeof ${model.name}Input>`].join('\n\n') }; }), ...enums.map(enm => { return { name: enm.name, inputRawString: null, rawString: 'import {Type, Static} from "@sinclair/typebox"\n\n' + transformEnum(enm) }; })]; } return transformDMMF; }