Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pnpm i -D prismabox
bun add -D prismabox
```

If you use Prisma 7+, configure your datasource URL in `prisma.config.ts` (instead of `schema.prisma`) before running `prisma generate`.

then add
```prisma
generator prismabox {
Expand All @@ -18,12 +20,23 @@ generator prismabox {
output = "./myCoolPrismaboxDirectory"
// if you want, you can customize the imported variable name that is used for the schemes. Defaults to "Type" which is what the standard typebox package offers
typeboxImportVariableName = "t"
// you also can specify the dependency from which the above import should happen. This is useful if a package re-exports the typebox package and you would like to use that
typeboxImportDependencyName = "elysia"
// you also can specify the dependency from which the above import should happen. Defaults to "typebox"
// this is useful if a package re-exports the typebox package and you would like to use that
typeboxImportDependencyName = "@sinclair/typebox" | "elysia"
// by default the generated schemes do not allow additional properties. You can allow them by setting this to true
additionalProperties = true
// optionally enable the data model generation. See the data model section below for more info
inputModel = true
// DateTime handling:
// false (default): use native date-compatible type
// true: emit string with format "date-time"
// "transformer": use generated transform helper (__transformDate__)
useJsonTypes = false
// recursion handling for where/whereUnique:
// true (default): generate recursive schema
// false: generate non-recursive schema
// in typebox mode this uses Type.Cyclic, in legacy mode Type.Recursive
allowRecursion = true
}
```
to your `prisma.schema`. You can modify the settings to your liking, please see the respective comments for info on what the option does.
Expand Down Expand Up @@ -67,6 +80,22 @@ enum Account {

```
> Please note that you cannot use multiple annotations in one line! Each needs to be in its own!

## TypeBox Compatibility
By default prismabox targets `typebox` (1.x). If you need legacy output, set `typeboxImportDependencyName = "@sinclair/typebox"`.

- `typebox` mode includes compatibility mappings for TypeBox 1.x:
- `Composite -> Evaluate(Intersect(...))`
- `Transform -> Codec`
- `Recursive -> Cyclic`
- `@sinclair/typebox` mode preserves legacy output behavior.

Dependency note:
- Default mode (`typeboxImportDependencyName = "typebox"`): install `typebox`.
- Legacy mode (`typeboxImportDependencyName = "@sinclair/typebox"`): install `@sinclair/typebox`.
- `useJsonTypes` default is `false`.
- `allowRecursion` default is `true`.

## Generated Schemes
The generator will output schema objects based on the models:
```ts
Expand Down Expand Up @@ -113,4 +142,3 @@ If enabled, the generator will additonally output more schemes for each model wh

Prismabox wraps nullable fields in a custom `__nullable__` method which allows `null` in addition to `undefined`. From the relevant [issue comment](https://github.com/m1212e/prismabox/issues/33#issuecomment-2708755442):
> prisma in some scenarios allows null OR undefined as types where optional only allows for undefined/is reflected as undefined in TS types

5 changes: 3 additions & 2 deletions build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const output = await build({
entryPoints: ["./src/cli.ts"],
outdir: "./dist",
platform: "node",
format: "cjs",
format: "esm",
sourcemap: "external",
minify: true,
bundle: true,
Expand All @@ -20,7 +20,7 @@ const output = await build({
],
});

if (output.errors) {
if (output.errors.length > 0) {
console.error(output.errors);
} else {
console.info("Built successfully!");
Expand All @@ -37,6 +37,7 @@ await writeFile(
"./dist/package.json",
JSON.stringify({
...packagejson,
type: "module",
version,
bin: { prismabox: "cli.js" },
}),
Expand Down
Binary file modified bun.lockb
Binary file not shown.
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,20 @@
"build": "bun run typecheck && bun build.ts",
"lint": "biome check --write .",
"format": "bun run lint",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"test:types": "bun test tests"
},
"dependencies": {
"@prisma/generator-helper": "^6.13.0",
"@sinclair/typebox": "^0.34.3",
"prettier": "^3.6.2"
"@prisma/generator-helper": "^7.4.2",
"prettier": "^3.6.2",
"typebox": "^1.1.6"
},
"devDependencies": {
"@biomejs/biome": "^2.1.3",
"@prisma/client": "6.13.0",
"@prisma/client": "7.4.2",
"@types/bun": "latest",
"esbuild": "^0.25.8",
"prisma": "6.13.0",
"prisma": "7.4.2",
"typescript": "^5.9.2"
}
}
1 change: 0 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

generator client {
Expand Down
17 changes: 9 additions & 8 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type Static, Type } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";
import { type Static, Type } from "typebox";
import { Value } from "typebox/value";

const configSchema = Type.Object(
{
Expand All @@ -14,7 +14,7 @@ const configSchema = Type.Object(
/**
* The name of the dependency to import the Type from typebox
*/
typeboxImportDependencyName: Type.String({ default: "@sinclair/typebox" }),
typeboxImportDependencyName: Type.String({ default: "typebox" }),
/**
* Whether to allow additional properties in the generated schemes
*/
Expand Down Expand Up @@ -49,7 +49,7 @@ const configSchema = Type.Object(
allowRecursion: Type.Boolean({ default: true }),
/**
* Additional fields to add to the generated schemes (must be valid strings in the context of usage)
* @example
* @example
* ```prisma
* generator prismabox {
provider = "node ./dist/cli.js"
Expand Down Expand Up @@ -93,12 +93,13 @@ let config: Static<typeof configSchema> = {} as unknown as any;

export function setConfig(input: unknown) {
try {
Value.Clean(configSchema, input);
Value.Default(configSchema, input);
config = Value.Decode(configSchema, Value.Convert(configSchema, input));
const converted = Value.Convert(configSchema, input);
Value.Default(configSchema, converted);
Value.Clean(configSchema, converted);
config = Value.Parse(configSchema, converted);
Object.freeze(config);
} catch (error) {
console.error(Value.Errors(configSchema, input).First);
console.error(Value.Errors(configSchema, input)[0]);
throw error;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/generators/orderBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function stringifyOrderBy(data: DMMF.Model) {
// `${getConfig().typeboxImportVariableName}.Literal('desc')`,
// ]
// )}})`;
return undefined;
})
.filter((x) => x) as string[];

Expand Down
12 changes: 12 additions & 0 deletions src/generators/primitiveField.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { getConfig } from "../config";
import {
TYPEBOX_DATE_NAME,
TYPEBOX_UINT8_ARRAY_NAME,
} from "./wrappers/typeboxCompat";

const PrimitiveFields = [
"Int",
Expand Down Expand Up @@ -56,6 +60,10 @@ export function stringifyPrimitiveType({
return `${config.typeboxImportVariableName}.String(${opts})`;
}

if (getConfig().typeboxImportDependencyName === "typebox") {
return `${TYPEBOX_DATE_NAME}(${options})`;
}

return `${getConfig().typeboxImportVariableName}.Date(${options})`;
}

Expand All @@ -68,6 +76,10 @@ export function stringifyPrimitiveType({
}

if (fieldType === "Bytes") {
if (getConfig().typeboxImportDependencyName === "typebox") {
return `${TYPEBOX_UINT8_ARRAY_NAME}(${options})`;
}

return `${getConfig().typeboxImportVariableName}.Uint8Array(${options})`;
}

Expand Down
33 changes: 26 additions & 7 deletions src/generators/transformDate.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
import { getConfig } from "../config";

export function transformDateType() {
return `import { ${getConfig().typeboxImportVariableName} } from "${getConfig().typeboxImportDependencyName}";
export const ${getConfig().transformDateName} = (options?: Parameters<typeof ${getConfig().typeboxImportVariableName}.String>[0]) => ${
getConfig().typeboxImportVariableName
}.Transform(${getConfig().typeboxImportVariableName}.String({ format: 'date-time', ...options }))
const {
typeboxImportDependencyName,
typeboxImportVariableName,
transformDateName,
} = getConfig();

if (typeboxImportDependencyName === "typebox") {
return `import ${
typeboxImportVariableName
} from "${typeboxImportDependencyName}";
export const ${transformDateName} = (options?: Parameters<typeof ${typeboxImportVariableName}.String>[0]) => ${
typeboxImportVariableName
}.Codec(${typeboxImportVariableName}.String({ format: 'date-time', ...options }))
.Decode((value) => new Date(value))
.Encode((value) => value.toISOString())\n`;
}

return `import { ${typeboxImportVariableName} } from "${typeboxImportDependencyName}";
export const ${transformDateName} = (options?: Parameters<typeof ${typeboxImportVariableName}.String>[0]) => ${
typeboxImportVariableName
}.Transform(${typeboxImportVariableName}.String({ format: 'date-time', ...options }))
.Decode((value) => new Date(value))
.Encode((value) => value.toISOString())\n`;
}

export function transformDateImportStatement() {
return `import { ${getConfig().transformDateName} } from "./${
getConfig().transformDateName
}${getConfig().importFileExtension}"\n`;
const { importFileExtension, transformDateName } = getConfig();

return `import { ${transformDateName} } from "./${
transformDateName
}${importFileExtension}"\n`;
}
46 changes: 39 additions & 7 deletions src/generators/where.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ import { makeUnion } from "./wrappers/union";

const selfReferenceName = "Self";

function selfReferenceToken() {
if (getConfig().typeboxImportDependencyName === "typebox") {
return `${getConfig().typeboxImportVariableName}.Ref("${selfReferenceName}")`;
}

return selfReferenceName;
}

export const processedWhere: ProcessedModel[] = [];

export function processWhere(models: DMMF.Model[] | Readonly<DMMF.Model[]>) {
Expand Down Expand Up @@ -76,6 +84,21 @@ export function stringifyWhere(data: DMMF.Model) {
.filter((x) => x) as string[];

if (getConfig().allowRecursion) {
if (getConfig().typeboxImportDependencyName === "typebox") {
return `${
getConfig().typeboxImportVariableName
}.Cyclic({ ${selfReferenceName}: ${wrapWithPartial(
`${
getConfig().typeboxImportVariableName
}.Object({${AND_OR_NOT()},${fields.join(",")}},${generateTypeboxOptions(
{
exludeAdditionalProperties: true,
input: annotations,
},
)})`,
)} }, "${selfReferenceName}", { $id: "${data.name}" })`;
}

return wrapWithPartial(
`${
getConfig().typeboxImportVariableName
Expand Down Expand Up @@ -253,9 +276,7 @@ export function stringifyWhereUnique(data: DMMF.Model) {
)}},${generateTypeboxOptions({ exludeAdditionalProperties: true, input: annotations })})`;

if (getConfig().allowRecursion) {
return `${
getConfig().typeboxImportVariableName
}.Recursive(${selfReferenceName} => ${makeIntersection([
const recursiveType = makeIntersection([
wrapWithPartial(uniqueBaseObject, true),
makeUnion(
[...uniqueFields, ...uniqueCompositeFields].map(
Expand All @@ -271,7 +292,17 @@ export function stringifyWhereUnique(data: DMMF.Model) {
getConfig().typeboxImportVariableName
}.Object({${allFields.join(",")}}, ${generateTypeboxOptions()})`,
),
])}, { $id: "${data.name}"})`;
]);

if (getConfig().typeboxImportDependencyName === "typebox") {
return `${
getConfig().typeboxImportVariableName
}.Cyclic({ ${selfReferenceName}: ${recursiveType} }, "${selfReferenceName}", { $id: "${data.name}" })`;
}

return `${
getConfig().typeboxImportVariableName
}.Recursive(${selfReferenceName} => ${recursiveType}, { $id: "${data.name}"})`;
}

return makeIntersection([
Expand All @@ -290,11 +321,12 @@ export function stringifyWhereUnique(data: DMMF.Model) {
}

function AND_OR_NOT() {
const token = selfReferenceToken();
return `AND: ${
getConfig().typeboxImportVariableName
}.Union([${selfReferenceName}, ${wrapWithArray(selfReferenceName)}]),
}.Union([${token}, ${wrapWithArray(token)}]),
NOT: ${
getConfig().typeboxImportVariableName
}.Union([${selfReferenceName}, ${wrapWithArray(selfReferenceName)}]),
OR: ${wrapWithArray(selfReferenceName)}`;
}.Union([${token}, ${wrapWithArray(token)}]),
OR: ${wrapWithArray(token)}`;
}
18 changes: 15 additions & 3 deletions src/generators/wrappers/composite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@ import { generateTypeboxOptions } from "../../annotations/options";
import { getConfig } from "../../config";

export function makeComposite(inputModels: string[]) {
return `${
getConfig().typeboxImportVariableName
}.Composite([${inputModels.map((i) => `${getConfig().exportedTypePrefix}${i}`).join(",")}], ${generateTypeboxOptions()})\n`;
const {
typeboxImportDependencyName,
typeboxImportVariableName,
exportedTypePrefix,
} = getConfig();

if (typeboxImportDependencyName === "typebox") {
return `${typeboxImportVariableName}.Evaluate(${typeboxImportVariableName}.Intersect([${inputModels
.map((i) => `${exportedTypePrefix}${i}`)
.join(",")}], ${generateTypeboxOptions()}))\n`;
}

return `${getConfig().typeboxImportVariableName}.Composite([${inputModels
.map((i) => `${getConfig().exportedTypePrefix}${i}`)
.join(",")}], ${generateTypeboxOptions()})\n`;
}
Loading
Loading