diff --git a/README.md b/README.md index e778f86..7f4d6c5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # prismabox Generate versatile [typebox](https://github.com/sinclairzx81/typebox) schemes from your [prisma](https://github.com/prisma) schema. -> Currently does not support [mongoDB composite types](https://www.prisma.io/docs/orm/prisma-schema/data-model/models#defining-composite-types) - Install it in your project, ```bash npm i -D prismabox diff --git a/package.json b/package.json index d1a3051..4f3fc3d 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "scripts": { "dev": "bun run build && bunx prisma generate", + "dev-mongo": "bun build.ts && bunx prisma generate --schema ./prisma/mongo-schema.prisma", "build": "bun run typecheck && bun build.ts", "lint": "biome check --write .", "typecheck": "tsc --noEmit" diff --git a/src/generators/composite.ts b/src/generators/composite.ts new file mode 100644 index 0000000..2abe437 --- /dev/null +++ b/src/generators/composite.ts @@ -0,0 +1,76 @@ +import type { DMMF } from "@prisma/generator-helper"; +import { extractAnnotations } from "../annotations/annotations"; +import { generateTypeboxOptions } from "../annotations/options"; +import { getConfig } from "../config"; +import type { ProcessedModel } from "../model"; +import { processedEnums } from "./enum"; +import { + type PrimitivePrismaFieldType, + isPrimitivePrismaFieldType, + stringifyPrimitiveType, +} from "./primitiveField"; +import { wrapWithArray } from "./wrappers/array"; +import { wrapWithNullable } from "./wrappers/nullable"; +import { wrapWithOptional } from "./wrappers/optional"; + +export const processedComposites: ProcessedModel[] = []; + +export function processComposites( + types: DMMF.Model[] | Readonly, +) { + for (const t of types) { + const o = stringifyComposite(t); + if (o) { + processedComposites.push({ name: t.name, stringRepresentation: o }); + } + } + Object.freeze(processedComposites); +} + +export function stringifyComposite( + data: DMMF.Model, + isInputModelCreate = false, + isInputModelUpdate = false, +) { + const annotations = extractAnnotations(data.documentation); + + if ( + annotations.isHidden || + ((isInputModelCreate || isInputModelUpdate) && annotations.isHiddenInput) || + (isInputModelCreate && annotations.isHiddenInputCreate) || + (isInputModelUpdate && annotations.isHiddenInputUpdate) + ) + return undefined; + + const fields = data.fields + .map((field) => { + let stringifiedType = `${getConfig().typeboxImportVariableName}.Object({ + })`; + + if (isPrimitivePrismaFieldType(field.type)) { + stringifiedType = stringifyPrimitiveType({ + fieldType: field.type as PrimitivePrismaFieldType, + options: generateTypeboxOptions({ input: annotations }), + }); + } else if (processedEnums.find((e) => e.name === field.type)) { + // biome-ignore lint/style/noNonNullAssertion: we checked this manually + stringifiedType = processedEnums.find( + (e) => e.name === field.type, + )!.stringRepresentation; + } + + if (field.kind === "object") { + stringifiedType = field.type; + } + + return `${field.name}: ${stringifiedType}`; + }) + .filter((x) => x) as string[]; + + return `${getConfig().typeboxImportVariableName}.Object({${[ + ...fields, + !(isInputModelCreate || isInputModelUpdate) + ? (getConfig().additionalFieldsPlain ?? []) + : [], + ].join(",")}},${generateTypeboxOptions({ input: annotations })})\n`; +} diff --git a/src/generators/plain.ts b/src/generators/plain.ts index d789a2c..29cd960 100644 --- a/src/generators/plain.ts +++ b/src/generators/plain.ts @@ -6,6 +6,7 @@ import { import { generateTypeboxOptions } from "../annotations/options"; import { getConfig } from "../config"; import type { ProcessedModel } from "../model"; +import { processedComposites } from "./composite"; import { processedEnums } from "./enum"; import { type PrimitivePrismaFieldType, @@ -113,6 +114,11 @@ export function stringifyPlain( stringifiedType = processedEnums.find( (e) => e.name === field.type, )!.stringRepresentation; + } else if (processedComposites.find((c) => c.name === field.type)) { + // biome-ignore lint/style/noNonNullAssertion: we checked this manually + stringifiedType = processedComposites.find( + (c) => c.name === field.type, + )!.stringRepresentation; } else { return undefined; } diff --git a/src/generators/relations.ts b/src/generators/relations.ts index f1c6fe4..658fc4d 100644 --- a/src/generators/relations.ts +++ b/src/generators/relations.ts @@ -3,6 +3,7 @@ import { extractAnnotations } from "../annotations/annotations"; import { generateTypeboxOptions } from "../annotations/options"; import { getConfig } from "../config"; import type { ProcessedModel } from "../model"; +import { processedComposites } from "./composite"; import { processedEnums } from "./enum"; import { processedPlain } from "./plain"; import { isPrimitivePrismaFieldType } from "./primitiveField"; @@ -35,7 +36,8 @@ export function stringifyRelations(data: DMMF.Model) { if ( annotations.isHidden || isPrimitivePrismaFieldType(field.type) || - processedEnums.find((e) => e.name === field.type) + processedEnums.find((e) => e.name === field.type) || + processedComposites.find((c) => c.name === field.type) ) { return undefined; } @@ -110,10 +112,10 @@ export function stringifyRelationsInputCreate( let typeboxIdType = "String"; - switch ( - allModels.find((m) => m.name === field.type)?.fields.find((f) => f.isId) - ?.type - ) { + const t = allModels + .find((m) => m.name === field.type) + ?.fields.find((f) => f.isId)?.type; + switch (t) { case "String": typeboxIdType = "String"; break; @@ -203,10 +205,10 @@ export function stringifyRelationsInputUpdate( let typeboxIdType = "String"; - switch ( - allModels.find((m) => m.name === field.type)?.fields.find((f) => f.isId) - ?.type - ) { + const t = allModels + .find((m) => m.name === field.type) + ?.fields.find((f) => f.isId)?.type; + switch (t) { case "String": typeboxIdType = "String"; break; diff --git a/src/index.ts b/src/index.ts index 00f42bc..5bbf20f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { access, mkdir, rm } from "node:fs/promises"; import { generatorHandler } from "@prisma/generator-helper"; import { getConfig, setConfig } from "./config"; +import { processComposites } from "./generators/composite"; import { processEnums } from "./generators/enum"; import { processInclude } from "./generators/include"; import { processOrderBy } from "./generators/orderBy"; @@ -38,6 +39,7 @@ generatorHandler({ await mkdir(getConfig().output, { recursive: true }); processEnums(options.dmmf.datamodel.enums); + processComposites(options.dmmf.datamodel.types); processPlain(options.dmmf.datamodel.models); processRelations(options.dmmf.datamodel.models); processWhere(options.dmmf.datamodel.models); diff --git a/src/model.ts b/src/model.ts index 8258755..1e388b2 100644 --- a/src/model.ts +++ b/src/model.ts @@ -1,4 +1,5 @@ import { getConfig } from "./config"; +import { processedComposites } from "./generators/composite"; import { processedEnums } from "./generators/enum"; import { processedInclude } from "./generators/include"; import { processedOrderBy } from "./generators/orderBy"; @@ -60,6 +61,7 @@ export function mapAllModelsForWrite() { process(processedEnums, ""); process(processedPlain, "Plain"); process(processedRelations, "Relations"); + process(processedComposites, ""); process(processedPlainInputCreate, "PlainInputCreate"); process(processedPlainInputUpdate, "PlainInputUpdate"); process(processedRelationsInputCreate, "RelationsInputCreate");