Skip to content

Commit 1456db2

Browse files
feat: add mongodb composite type generation
1 parent bad0dc8 commit 1456db2

File tree

8 files changed

+117
-31
lines changed

8 files changed

+117
-31
lines changed

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# prismabox
22
Generate versatile [typebox](https://github.com/sinclairzx81/typebox) schemes from your [prisma](https://github.com/prisma) schema.
33

4-
> Currently does not support [mongoDB composite types](https://www.prisma.io/docs/orm/prisma-schema/data-model/models#defining-composite-types)
5-
64
Install it in your project,
75
```bash
86
npm i -D prismabox

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
},
2323
"scripts": {
2424
"dev": "bun run build && bunx prisma generate",
25+
"dev-mongo": "bun build.ts && bunx prisma generate --schema ./prisma/mongo-schema.prisma",
2526
"build": "bun run typecheck && bun build.ts",
2627
"lint": "biome check --write .",
2728
"typecheck": "tsc --noEmit"

src/annotations/options.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function generateTypeboxOptions({
1717

1818
if (!exludeAdditionalProperties) {
1919
stringifiedOptions.push(
20-
`additionalProperties: ${getConfig().additionalProperties}`,
20+
`additionalProperties: ${getConfig().additionalProperties}`
2121
);
2222
}
2323

src/generators/composite.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import type { DMMF } from "@prisma/generator-helper";
2+
import { extractAnnotations } from "../annotations/annotations";
3+
import { generateTypeboxOptions } from "../annotations/options";
4+
import { getConfig } from "../config";
5+
import type { ProcessedModel } from "../model";
6+
import { processedEnums } from "./enum";
7+
import {
8+
type PrimitivePrismaFieldType,
9+
isPrimitivePrismaFieldType,
10+
stringifyPrimitiveType,
11+
} from "./primitiveField";
12+
import { wrapWithArray } from "./wrappers/array";
13+
import { wrapWithNullable } from "./wrappers/nullable";
14+
import { wrapWithOptional } from "./wrappers/optional";
15+
16+
export const processedComposites: ProcessedModel[] = [];
17+
18+
export function processComposites(
19+
types: DMMF.Model[] | Readonly<DMMF.Model[]>
20+
) {
21+
for (const t of types) {
22+
const o = stringifyComposite(t);
23+
if (o) {
24+
processedComposites.push({ name: t.name, stringRepresentation: o });
25+
}
26+
}
27+
Object.freeze(processedComposites);
28+
}
29+
30+
export function stringifyComposite(
31+
data: DMMF.Model,
32+
isInputModelCreate = false,
33+
isInputModelUpdate = false
34+
) {
35+
const annotations = extractAnnotations(data.documentation);
36+
37+
if (
38+
annotations.isHidden ||
39+
((isInputModelCreate || isInputModelUpdate) && annotations.isHiddenInput) ||
40+
(isInputModelCreate && annotations.isHiddenInputCreate) ||
41+
(isInputModelUpdate && annotations.isHiddenInputUpdate)
42+
)
43+
return undefined;
44+
45+
const fields = data.fields
46+
.map((field) => {
47+
let stringifiedType = `${getConfig().typeboxImportVariableName}.Object({
48+
})`;
49+
50+
if (isPrimitivePrismaFieldType(field.type)) {
51+
stringifiedType = stringifyPrimitiveType({
52+
fieldType: field.type as PrimitivePrismaFieldType,
53+
options: generateTypeboxOptions({ input: annotations }),
54+
});
55+
} else if (processedEnums.find((e) => e.name === field.type)) {
56+
// biome-ignore lint/style/noNonNullAssertion: we checked this manually
57+
stringifiedType = processedEnums.find(
58+
(e) => e.name === field.type
59+
)!.stringRepresentation;
60+
}
61+
62+
if (field.kind === "object") {
63+
stringifiedType = field.type;
64+
}
65+
66+
return `${field.name}: ${stringifiedType}`;
67+
})
68+
.filter((x) => x) as string[];
69+
70+
return `${getConfig().typeboxImportVariableName}.Object({${[
71+
...fields,
72+
!(isInputModelCreate || isInputModelUpdate)
73+
? (getConfig().additionalFieldsPlain ?? [])
74+
: [],
75+
].join(",")}},${generateTypeboxOptions({ input: annotations })})\n`;
76+
}

src/generators/plain.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { wrapWithArray } from "./wrappers/array";
1313
import { wrapWithNullable } from "./wrappers/nullable";
1414
import { wrapWithOptional } from "./wrappers/optional";
15+
import { processedComposites } from "./composite";
1516

1617
export const processedPlain: ProcessedModel[] = [];
1718

@@ -28,7 +29,7 @@ export function processPlain(models: DMMF.Model[] | Readonly<DMMF.Model[]>) {
2829
export function stringifyPlain(
2930
data: DMMF.Model,
3031
isInputModelCreate = false,
31-
isInputModelUpdate = false,
32+
isInputModelUpdate = false
3233
) {
3334
const annotations = extractAnnotations(data.documentation);
3435

@@ -100,7 +101,11 @@ export function stringifyPlain(
100101
} else if (processedEnums.find((e) => e.name === field.type)) {
101102
// biome-ignore lint/style/noNonNullAssertion: we checked this manually
102103
stringifiedType = processedEnums.find(
103-
(e) => e.name === field.type,
104+
(e) => e.name === field.type
105+
)!.stringRepresentation;
106+
} else if (processedComposites.find((c) => c.name === field.type)) {
107+
stringifiedType = processedComposites.find(
108+
(c) => c.name === field.type
104109
)!.stringRepresentation;
105110
} else {
106111
return undefined;

src/generators/relations.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import { isPrimitivePrismaFieldType } from "./primitiveField";
99
import { wrapWithArray } from "./wrappers/array";
1010
import { wrapWithNullable } from "./wrappers/nullable";
1111
import { wrapWithPartial } from "./wrappers/partial";
12+
import { processedComposites } from "./composite";
1213

1314
export const processedRelations: ProcessedModel[] = [];
1415

1516
export function processRelations(
16-
models: DMMF.Model[] | Readonly<DMMF.Model[]>,
17+
models: DMMF.Model[] | Readonly<DMMF.Model[]>
1718
) {
1819
for (const m of models) {
1920
const o = stringifyRelations(m);
@@ -35,13 +36,14 @@ export function stringifyRelations(data: DMMF.Model) {
3536
if (
3637
annotations.isHidden ||
3738
isPrimitivePrismaFieldType(field.type) ||
38-
processedEnums.find((e) => e.name === field.type)
39+
processedEnums.find((e) => e.name === field.type) ||
40+
processedComposites.find((c) => c.name === field.type)
3941
) {
4042
return undefined;
4143
}
4244

4345
let stringifiedType = processedPlain.find(
44-
(e) => e.name === field.type,
46+
(e) => e.name === field.type
4547
)?.stringRepresentation;
4648

4749
if (!stringifiedType) {
@@ -61,14 +63,14 @@ export function stringifyRelations(data: DMMF.Model) {
6163
.filter((x) => x) as string[];
6264

6365
return `${getConfig().typeboxImportVariableName}.Object({${fields.join(
64-
",",
66+
","
6567
)}},${generateTypeboxOptions({ input: annotations })})\n`;
6668
}
6769

6870
export const processedRelationsInputCreate: ProcessedModel[] = [];
6971

7072
export function processRelationsInputCreate(
71-
models: DMMF.Model[] | Readonly<DMMF.Model[]>,
73+
models: DMMF.Model[] | Readonly<DMMF.Model[]>
7274
) {
7375
for (const m of models) {
7476
const o = stringifyRelationsInputCreate(m, models);
@@ -84,7 +86,7 @@ export function processRelationsInputCreate(
8486

8587
export function stringifyRelationsInputCreate(
8688
data: DMMF.Model,
87-
allModels: DMMF.Model[] | Readonly<DMMF.Model[]>,
89+
allModels: DMMF.Model[] | Readonly<DMMF.Model[]>
8890
) {
8991
const annotations = extractAnnotations(data.documentation);
9092
if (
@@ -110,10 +112,10 @@ export function stringifyRelationsInputCreate(
110112

111113
let typeboxIdType = "String";
112114

113-
switch (
114-
allModels.find((m) => m.name === field.type)?.fields.find((f) => f.isId)
115-
?.type
116-
) {
115+
const t = allModels
116+
.find((m) => m.name === field.type)
117+
?.fields.find((f) => f.isId)?.type;
118+
switch (t) {
117119
case "String":
118120
typeboxIdType = "String";
119121
break;
@@ -124,7 +126,7 @@ export function stringifyRelationsInputCreate(
124126
typeboxIdType = "Integer";
125127
break;
126128
default:
127-
throw new Error("Unsupported id type");
129+
throw new Error(`Unsupported id type: '${t}'`);
128130
}
129131

130132
let connectString = `${getConfig().typeboxImportVariableName}.Object({
@@ -152,14 +154,14 @@ export function stringifyRelationsInputCreate(
152154
.filter((x) => x) as string[];
153155

154156
return `${getConfig().typeboxImportVariableName}.Object({${fields.join(
155-
",",
157+
","
156158
)}},${generateTypeboxOptions({ input: annotations })})\n`;
157159
}
158160

159161
export const processedRelationsInputUpdate: ProcessedModel[] = [];
160162

161163
export function processRelationsInputUpdate(
162-
models: DMMF.Model[] | Readonly<DMMF.Model[]>,
164+
models: DMMF.Model[] | Readonly<DMMF.Model[]>
163165
) {
164166
for (const m of models) {
165167
const o = stringifyRelationsInputUpdate(m, models);
@@ -175,7 +177,7 @@ export function processRelationsInputUpdate(
175177

176178
export function stringifyRelationsInputUpdate(
177179
data: DMMF.Model,
178-
allModels: DMMF.Model[] | Readonly<DMMF.Model[]>,
180+
allModels: DMMF.Model[] | Readonly<DMMF.Model[]>
179181
) {
180182
const annotations = extractAnnotations(data.documentation);
181183
if (
@@ -201,10 +203,10 @@ export function stringifyRelationsInputUpdate(
201203

202204
let typeboxIdType = "String";
203205

204-
switch (
205-
allModels.find((m) => m.name === field.type)?.fields.find((f) => f.isId)
206-
?.type
207-
) {
206+
const t = allModels
207+
.find((m) => m.name === field.type)
208+
?.fields.find((f) => f.isId)?.type;
209+
switch (t) {
208210
case "String":
209211
typeboxIdType = "String";
210212
break;
@@ -215,7 +217,7 @@ export function stringifyRelationsInputUpdate(
215217
typeboxIdType = "Integer";
216218
break;
217219
default:
218-
throw new Error("Unsupported id type");
220+
throw new Error(`Unsupported id type: '${t}'`);
219221
}
220222

221223
let stringifiedType: string;
@@ -264,7 +266,7 @@ export function stringifyRelationsInputUpdate(
264266

265267
return wrapWithPartial(
266268
`${getConfig().typeboxImportVariableName}.Object({${fields.join(
267-
",",
268-
)}},${generateTypeboxOptions({ input: annotations })})`,
269+
","
270+
)}},${generateTypeboxOptions({ input: annotations })})`
269271
);
270272
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import { processSelect } from "./generators/select";
1616
import { processWhere, processWhereUnique } from "./generators/where";
1717
import { write } from "./writer";
18+
import { processComposites } from "./generators/composite";
1819

1920
generatorHandler({
2021
onManifest() {
@@ -38,6 +39,7 @@ generatorHandler({
3839
await mkdir(getConfig().output, { recursive: true });
3940

4041
processEnums(options.dmmf.datamodel.enums);
42+
processComposites(options.dmmf.datamodel.types);
4143
processPlain(options.dmmf.datamodel.models);
4244
processRelations(options.dmmf.datamodel.models);
4345
processWhere(options.dmmf.datamodel.models);

src/model.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getConfig } from "./config";
2+
import { processedComposites } from "./generators/composite";
23
import { processedEnums } from "./generators/enum";
34
import { processedInclude } from "./generators/include";
45
import { processedOrderBy } from "./generators/orderBy";
@@ -25,7 +26,7 @@ export type ProcessedModel = {
2526
};
2627

2728
function convertModelToStandalone(
28-
input: Pick<ProcessedModel, "name" | "stringRepresentation">,
29+
input: Pick<ProcessedModel, "name" | "stringRepresentation">
2930
) {
3031
return `export const ${getConfig().exportedTypePrefix}${input.name} = ${input.stringRepresentation}\n`;
3132
}
@@ -60,6 +61,7 @@ export function mapAllModelsForWrite() {
6061
process(processedEnums, "");
6162
process(processedPlain, "Plain");
6263
process(processedRelations, "Relations");
64+
process(processedComposites, "");
6365
process(processedPlainInputCreate, "PlainInputCreate");
6466
process(processedPlainInputUpdate, "PlainInputUpdate");
6567
process(processedRelationsInputCreate, "RelationsInputCreate");
@@ -89,7 +91,7 @@ export function mapAllModelsForWrite() {
8991
`${value}\n${convertModelToStandalone({
9092
name: key,
9193
stringRepresentation: composite,
92-
})}`,
94+
})}`
9395
);
9496
}
9597

@@ -106,7 +108,7 @@ export function mapAllModelsForWrite() {
106108
`${value}\n${convertModelToStandalone({
107109
name: `${key}InputCreate`,
108110
stringRepresentation: composite,
109-
})}`,
111+
})}`
110112
);
111113
}
112114
}
@@ -124,15 +126,15 @@ export function mapAllModelsForWrite() {
124126
`${value}\n${convertModelToStandalone({
125127
name: `${key}InputUpdate`,
126128
stringRepresentation: composite,
127-
})}`,
129+
})}`
128130
);
129131
}
130132
}
131133

132134
for (const [key, value] of modelsPerName) {
133135
modelsPerName.set(
134136
key,
135-
`${typepoxImportStatement()}\n${transformDateImportStatement()}\n${nullableImport()}\n${value}`,
137+
`${typepoxImportStatement()}\n${transformDateImportStatement()}\n${nullableImport()}\n${value}`
136138
);
137139
}
138140

0 commit comments

Comments
 (0)