nest-prisma_doc-gen 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entities/dto-generator.d.ts +13 -0
- package/dist/entities/dto-generator.js +51 -0
- package/dist/entities/entity-generator.d.ts +12 -0
- package/dist/entities/entity-generator.js +47 -0
- package/dist/entities/enum.d.ts +22 -0
- package/dist/entities/enum.js +37 -0
- package/dist/entities/field.d.ts +24 -0
- package/dist/entities/field.js +169 -0
- package/dist/entities/model.d.ts +11 -0
- package/dist/entities/model.js +18 -0
- package/dist/entities/validator.js +12 -0
- package/dist/field.type.d.ts +7 -0
- package/dist/field.type.js +22 -0
- package/dist/file.d.ts +11 -0
- package/dist/file.js +26 -0
- package/dist/helpers/helpers.d.ts +14 -0
- package/dist/helpers/helpers.js +143 -0
- package/dist/helpers/loader.d.ts +4 -0
- package/dist/helpers/loader.js +50 -0
- package/dist/helpers/propeties.static.d.ts +2 -0
- package/dist/helpers/propeties.static.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/main.d.ts +13 -0
- package/dist/main.js +53 -0
- package/dist/rules.d.ts +11 -0
- package/dist/rules.js +19 -0
- package/dist/static.d.ts +6 -0
- package/dist/static.js +25 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.js +17 -0
- package/package.json +40 -0
- package/src/entities/dto-generator.ts +61 -0
- package/src/entities/entity-generator.ts +55 -0
- package/src/entities/enum.ts +47 -0
- package/src/entities/field.ts +188 -0
- package/src/entities/model.ts +23 -0
- package/src/entities/validator.ts +17 -0
- package/src/field.type.ts +27 -0
- package/src/file.ts +34 -0
- package/src/helpers/helpers.ts +152 -0
- package/src/helpers/loader.ts +60 -0
- package/src/helpers/propeties.static.ts +3 -0
- package/src/index.ts +2 -0
- package/src/main.ts +69 -0
- package/src/rules.ts +31 -0
- package/src/static.ts +28 -0
- package/src/types/global.d.ts +1 -0
- package/src/types.ts +109 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DocGenFile } from "../file.js";
|
|
2
|
+
import { Model } from "../types.js";
|
|
3
|
+
import { DocGenField } from "./field.js";
|
|
4
|
+
export declare class DocGenDto {
|
|
5
|
+
name: string;
|
|
6
|
+
file: DocGenFile;
|
|
7
|
+
fields: DocGenField[];
|
|
8
|
+
imports: Set<string>;
|
|
9
|
+
classValidators: Set<string>;
|
|
10
|
+
enums: Set<string>;
|
|
11
|
+
constructor(model: Model);
|
|
12
|
+
build(): string;
|
|
13
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { DocGenFile } from "../file.js";
|
|
2
|
+
import { Helper } from "../helpers/helpers.js";
|
|
3
|
+
import { Static } from "../static.js";
|
|
4
|
+
import { DocGenField } from "./field.js";
|
|
5
|
+
export class DocGenDto {
|
|
6
|
+
name;
|
|
7
|
+
file;
|
|
8
|
+
fields = [];
|
|
9
|
+
imports = new Set([`${Static.AUTO_GENERATED_COMMENT}`, `import { ApiProperty } from '@nestjs/swagger'`]);
|
|
10
|
+
classValidators = new Set();
|
|
11
|
+
enums = new Set();
|
|
12
|
+
constructor(model) {
|
|
13
|
+
this.name = model.name;
|
|
14
|
+
model.fields.forEach((field) => {
|
|
15
|
+
if (field.isUpdatedAt || field.isId || field.name === "createdAt" || field.kind === "object")
|
|
16
|
+
return;
|
|
17
|
+
this.fields.push(new DocGenField(field, "dto"));
|
|
18
|
+
});
|
|
19
|
+
this.file = new DocGenFile({
|
|
20
|
+
dir: "/dto",
|
|
21
|
+
fileName: `${Helper.toKebab(this.name)}.dto.ts`,
|
|
22
|
+
data: this.build(),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
build() {
|
|
26
|
+
const sanitizedFields = this.fields
|
|
27
|
+
.map((field) => {
|
|
28
|
+
field.validators.forEach((v) => this.classValidators.add(v.name));
|
|
29
|
+
if (field.isEntity) {
|
|
30
|
+
this.imports.add(`import { ${field.type} } from '../entities/${Helper.toKebab(field.scalarType)}.entity'`);
|
|
31
|
+
this.imports.add(`import { generateExample } from 'src/utils/functions/reflect'`);
|
|
32
|
+
}
|
|
33
|
+
else if (field.isEnum) {
|
|
34
|
+
this.enums.add(field.type);
|
|
35
|
+
}
|
|
36
|
+
return field.build();
|
|
37
|
+
})
|
|
38
|
+
.join("\n\n");
|
|
39
|
+
if (this.enums.size > 0) {
|
|
40
|
+
this.classValidators.add("IsEnum");
|
|
41
|
+
this.imports.add(`import { ${Array.from(this.enums)} } from '../enums';`);
|
|
42
|
+
}
|
|
43
|
+
this.imports.add(`import { ${Array.from(this.classValidators)} } from 'class-validator';`);
|
|
44
|
+
return [
|
|
45
|
+
`${Array.from(this.imports).join("\n")}`,
|
|
46
|
+
`export class ${this.name}Dto {
|
|
47
|
+
${sanitizedFields}
|
|
48
|
+
}`,
|
|
49
|
+
].join("\n\n");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DocGenFile } from "../file.js";
|
|
2
|
+
import { Model } from "../types.js";
|
|
3
|
+
import { DocGenField } from "./field.js";
|
|
4
|
+
export declare class DocGenEntity {
|
|
5
|
+
name: string;
|
|
6
|
+
file: DocGenFile;
|
|
7
|
+
fields: DocGenField[];
|
|
8
|
+
imports: Set<string>;
|
|
9
|
+
enums: Set<string>;
|
|
10
|
+
constructor(model: Model);
|
|
11
|
+
build(): string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DocGenFile } from "../file.js";
|
|
2
|
+
import { Helper } from "../helpers/helpers.js";
|
|
3
|
+
import { Static } from "../static.js";
|
|
4
|
+
import { DocGenField } from "./field.js";
|
|
5
|
+
export class DocGenEntity {
|
|
6
|
+
name;
|
|
7
|
+
file;
|
|
8
|
+
fields = [];
|
|
9
|
+
imports = new Set([`${Static.AUTO_GENERATED_COMMENT}`, `import { ApiProperty } from '@nestjs/swagger'`]);
|
|
10
|
+
enums = new Set();
|
|
11
|
+
constructor(model) {
|
|
12
|
+
this.name = model.name;
|
|
13
|
+
model.fields.forEach((field) => {
|
|
14
|
+
if (field.kind === "object")
|
|
15
|
+
return;
|
|
16
|
+
this.fields.push(new DocGenField(field, "entity"));
|
|
17
|
+
});
|
|
18
|
+
this.file = new DocGenFile({
|
|
19
|
+
dir: "/entity",
|
|
20
|
+
fileName: `${Helper.toKebab(this.name)}.entity.ts`,
|
|
21
|
+
data: this.build(),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
build() {
|
|
25
|
+
const sanitizedFields = this.fields
|
|
26
|
+
.map((field) => {
|
|
27
|
+
if (field.isEntity) {
|
|
28
|
+
this.imports.add(`import { ${field.type} } from './${Helper.toKebab(field.scalarType)}.entity'`);
|
|
29
|
+
this.imports.add(`import { generateExample } from 'src/utils/functions/reflect'`);
|
|
30
|
+
}
|
|
31
|
+
else if (field.isEnum) {
|
|
32
|
+
this.enums.add(field.type);
|
|
33
|
+
}
|
|
34
|
+
return field.build();
|
|
35
|
+
})
|
|
36
|
+
.join("\n\n");
|
|
37
|
+
if (this.enums.size > 0) {
|
|
38
|
+
this.imports.add(`import { ${Array.from(this.enums)} } from '../enums';`);
|
|
39
|
+
}
|
|
40
|
+
return [
|
|
41
|
+
`${Array.from(this.imports).join("\n")}`,
|
|
42
|
+
`export class ${this.name}Entity {
|
|
43
|
+
${sanitizedFields}
|
|
44
|
+
}`,
|
|
45
|
+
].join("\n\n");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { DocGenFile } from "../file.js";
|
|
2
|
+
import { DbName } from "../types.js";
|
|
3
|
+
export type EnumValue = {
|
|
4
|
+
name: string;
|
|
5
|
+
dbName: DbName;
|
|
6
|
+
};
|
|
7
|
+
export declare class DocGenEnum {
|
|
8
|
+
name: string;
|
|
9
|
+
values: EnumValue[];
|
|
10
|
+
dbName: DbName;
|
|
11
|
+
constructor(params: {
|
|
12
|
+
name: string;
|
|
13
|
+
values: EnumValue[];
|
|
14
|
+
dbName: DbName;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export declare class DocEnums {
|
|
18
|
+
enums: DocGenEnum[];
|
|
19
|
+
file: DocGenFile;
|
|
20
|
+
constructor(enums: DocGenEnum[]);
|
|
21
|
+
build(): string;
|
|
22
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { DocGenFile } from "../file.js";
|
|
2
|
+
export class DocGenEnum {
|
|
3
|
+
name;
|
|
4
|
+
values;
|
|
5
|
+
dbName;
|
|
6
|
+
constructor(params) {
|
|
7
|
+
const { dbName, name, values } = params;
|
|
8
|
+
this.dbName = dbName;
|
|
9
|
+
this.name = name;
|
|
10
|
+
this.values = values;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class DocEnums {
|
|
14
|
+
enums;
|
|
15
|
+
file;
|
|
16
|
+
constructor(enums) {
|
|
17
|
+
this.enums = enums;
|
|
18
|
+
this.file = new DocGenFile({
|
|
19
|
+
dir: "/",
|
|
20
|
+
fileName: "enums.ts",
|
|
21
|
+
data: this.build(),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
build() {
|
|
25
|
+
const enums = this.enums.map((en) => {
|
|
26
|
+
const enumName = en.name;
|
|
27
|
+
return `
|
|
28
|
+
export const ${enumName} = [${en.values.map((n) => `'${n.name}'`)}] as const;
|
|
29
|
+
export type ${enumName} = typeof ${enumName}[number];
|
|
30
|
+
`;
|
|
31
|
+
});
|
|
32
|
+
return `
|
|
33
|
+
// AUTO-GERADO: NÃO EDITAR MANUALMENTE. SUJEITO A PAULADAS!
|
|
34
|
+
${enums.join("\n")}
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FieldDefault, Scalar, FieldKind, FieldType, Field } from "../types.js";
|
|
2
|
+
export declare class DocGenField {
|
|
3
|
+
name: string;
|
|
4
|
+
isList: boolean;
|
|
5
|
+
default?: FieldDefault | string;
|
|
6
|
+
scalarType: Scalar;
|
|
7
|
+
kind: FieldKind;
|
|
8
|
+
type: string;
|
|
9
|
+
fieldType: FieldType;
|
|
10
|
+
isEnum: boolean;
|
|
11
|
+
isEntity: boolean;
|
|
12
|
+
isUpdatedAt: boolean;
|
|
13
|
+
isRequired: boolean;
|
|
14
|
+
validators: Set<string>;
|
|
15
|
+
readonly scalarField: Field;
|
|
16
|
+
constructor(field: Field, fieldType: FieldType);
|
|
17
|
+
private init;
|
|
18
|
+
private setValidators;
|
|
19
|
+
private setType;
|
|
20
|
+
private buildApiExample;
|
|
21
|
+
private sanitizeValidators;
|
|
22
|
+
private buildInfos;
|
|
23
|
+
build(): string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Helper } from "../helpers/helpers.js";
|
|
2
|
+
import { config } from "../helpers/loader.js";
|
|
3
|
+
import { Static } from "../static.js";
|
|
4
|
+
import { Validator } from "./validator.js";
|
|
5
|
+
const helpers = new Helper();
|
|
6
|
+
const rules = config;
|
|
7
|
+
export class DocGenField {
|
|
8
|
+
name;
|
|
9
|
+
isList;
|
|
10
|
+
default;
|
|
11
|
+
scalarType;
|
|
12
|
+
kind;
|
|
13
|
+
type;
|
|
14
|
+
fieldType;
|
|
15
|
+
isEnum = false;
|
|
16
|
+
isEntity = false;
|
|
17
|
+
isUpdatedAt = false;
|
|
18
|
+
isRequired;
|
|
19
|
+
validators = new Set();
|
|
20
|
+
scalarField;
|
|
21
|
+
constructor(field, fieldType) {
|
|
22
|
+
const { name, isList, type, kind, isRequired, isUpdatedAt } = field;
|
|
23
|
+
this.name = name;
|
|
24
|
+
this.isList = isList;
|
|
25
|
+
this.scalarType = type;
|
|
26
|
+
this.kind = kind;
|
|
27
|
+
this.isRequired = isRequired;
|
|
28
|
+
this.scalarField = field;
|
|
29
|
+
this.isUpdatedAt = isUpdatedAt;
|
|
30
|
+
this.fieldType = fieldType;
|
|
31
|
+
this.setType();
|
|
32
|
+
this.setValidators();
|
|
33
|
+
}
|
|
34
|
+
processValidator(name) {
|
|
35
|
+
const validator = new Validator({ name });
|
|
36
|
+
if (this.isList)
|
|
37
|
+
validator.content = "{ each: true }";
|
|
38
|
+
this.validators.add(validator);
|
|
39
|
+
}
|
|
40
|
+
setValidators() {
|
|
41
|
+
if (this.scalarType === "String" || this.scalarType === "DateTime") {
|
|
42
|
+
this.processValidator("IsString");
|
|
43
|
+
if (this.isRequired) {
|
|
44
|
+
this.processValidator("IsNotEmpty");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (this.scalarType === "Boolean") {
|
|
48
|
+
this.processValidator("IsBoolean");
|
|
49
|
+
}
|
|
50
|
+
else if (this.scalarType === "Int" ||
|
|
51
|
+
this.scalarType === "BigInt" ||
|
|
52
|
+
this.scalarType === "Float" ||
|
|
53
|
+
this.scalarType === "Decimal") {
|
|
54
|
+
this.processValidator("IsNumber");
|
|
55
|
+
}
|
|
56
|
+
if (this.isList) {
|
|
57
|
+
const validator = new Validator({ name: "IsArray" });
|
|
58
|
+
this.validators.add(validator);
|
|
59
|
+
}
|
|
60
|
+
if (!this.isRequired)
|
|
61
|
+
this.processValidator("IsOptional");
|
|
62
|
+
if (this.isEnum) {
|
|
63
|
+
this.validators.add(new Validator({
|
|
64
|
+
name: "IsEnum",
|
|
65
|
+
content: this.type,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
const findedDecorators = rules.validators.get(this.name);
|
|
69
|
+
if (findedDecorators) {
|
|
70
|
+
findedDecorators.forEach((name) => {
|
|
71
|
+
this.processValidator(name);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
setType() {
|
|
76
|
+
if (this.kind === "enum") {
|
|
77
|
+
this.isEnum = true;
|
|
78
|
+
this.type = this.scalarType;
|
|
79
|
+
}
|
|
80
|
+
else if (this.kind === "object") {
|
|
81
|
+
this.isEntity = true;
|
|
82
|
+
this.type = `${this.scalarType}Entity`;
|
|
83
|
+
}
|
|
84
|
+
else if (this.kind === "scalar") {
|
|
85
|
+
this.type = Helper.prismaScalarToTs(this.scalarType);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
buildApiExample() {
|
|
89
|
+
const fieldName = this.scalarField.name;
|
|
90
|
+
const props = [];
|
|
91
|
+
const scalarDbName = this.scalarField.dbName ?? "genericDbName";
|
|
92
|
+
if (this.isEntity) {
|
|
93
|
+
if (this.isList) {
|
|
94
|
+
props.push(`example: [generateExample(${this.type})]`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
props.push(`example: generateExample(${this.type})`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (rules.examples.has(fieldName)) {
|
|
101
|
+
props.push(`example: '${rules.examples.get(fieldName)?.example}'`);
|
|
102
|
+
}
|
|
103
|
+
else if (helpers.isDate(this.scalarField)) {
|
|
104
|
+
props.push(`example: '2025-09-03T03:00:00.000Z'`);
|
|
105
|
+
}
|
|
106
|
+
else if (this.scalarField.isId || (this.scalarField.isReadOnly && scalarDbName.split("_").includes("id"))) {
|
|
107
|
+
props.push(`example: 'cmfxu4njg000008l52v7t8qze'`);
|
|
108
|
+
}
|
|
109
|
+
else if (this.scalarField.type === "Boolean") {
|
|
110
|
+
props.push(`example: true`);
|
|
111
|
+
}
|
|
112
|
+
else if (this.scalarField.kind === "enum") {
|
|
113
|
+
props.push(`example: ${this.scalarField.type}[0]`);
|
|
114
|
+
}
|
|
115
|
+
else if (this.scalarField.type === "Int") {
|
|
116
|
+
props.push(`example: ${Static.getRandomNumber()}`);
|
|
117
|
+
}
|
|
118
|
+
props.push(`required: ${this.scalarField.isRequired}`);
|
|
119
|
+
return props;
|
|
120
|
+
}
|
|
121
|
+
sanitizeValidators() {
|
|
122
|
+
const sanitizedValidators = Array.from(this.validators).map((validator) => {
|
|
123
|
+
return validator.build();
|
|
124
|
+
});
|
|
125
|
+
return sanitizedValidators;
|
|
126
|
+
}
|
|
127
|
+
buildInfos() {
|
|
128
|
+
const key = this.isEnum ? "enum" : "type";
|
|
129
|
+
const apiType = () => {
|
|
130
|
+
if (this.type === "Date")
|
|
131
|
+
return `'string'`;
|
|
132
|
+
if (this.isList && this.isEntity) {
|
|
133
|
+
return `[${this.type}]`;
|
|
134
|
+
}
|
|
135
|
+
else if (this.isEnum) {
|
|
136
|
+
return this.type;
|
|
137
|
+
}
|
|
138
|
+
else if (this.isEntity) {
|
|
139
|
+
return `() => ${this.type}`;
|
|
140
|
+
}
|
|
141
|
+
return `'${this.type}'`;
|
|
142
|
+
};
|
|
143
|
+
const fieldType = () => {
|
|
144
|
+
if (this.isList) {
|
|
145
|
+
return `${this.type}[]`;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
return this.type;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const optionalFlag = this.isRequired ? "" : "?";
|
|
152
|
+
const validators = this.sanitizeValidators();
|
|
153
|
+
const apiExample = this.buildApiExample().join(", ");
|
|
154
|
+
return {
|
|
155
|
+
apiProperty: `@ApiProperty({ ${key}: ${apiType()}, ${apiExample} })`,
|
|
156
|
+
validators,
|
|
157
|
+
atributes: `${this.name}${optionalFlag}: ${fieldType()};`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
build() {
|
|
161
|
+
const { apiProperty, atributes, validators } = this.buildInfos();
|
|
162
|
+
if (this.fieldType === "dto") {
|
|
163
|
+
return [apiProperty, ...validators, atributes].join("\n");
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
return [apiProperty, atributes].join("\n");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Field, Model } from "../types.js";
|
|
2
|
+
import { DocGenDto } from "./dto-generator.js";
|
|
3
|
+
import { DocGenEntity } from "./entity-generator.js";
|
|
4
|
+
export declare class DocGenModel {
|
|
5
|
+
name: string;
|
|
6
|
+
entitie: DocGenEntity;
|
|
7
|
+
createDtos: DocGenDto;
|
|
8
|
+
fields: Field[];
|
|
9
|
+
constructor(model: Model);
|
|
10
|
+
save(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DocGenDto } from "./dto-generator.js";
|
|
2
|
+
import { DocGenEntity } from "./entity-generator.js";
|
|
3
|
+
export class DocGenModel {
|
|
4
|
+
name;
|
|
5
|
+
entitie;
|
|
6
|
+
createDtos;
|
|
7
|
+
fields;
|
|
8
|
+
constructor(model) {
|
|
9
|
+
this.name = model.name;
|
|
10
|
+
this.fields = model.fields;
|
|
11
|
+
this.entitie = new DocGenEntity(model);
|
|
12
|
+
this.createDtos = new DocGenDto(model);
|
|
13
|
+
}
|
|
14
|
+
save() {
|
|
15
|
+
this.entitie.file.save();
|
|
16
|
+
this.createDtos.file.save();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { DocGenFile } from "./file.js";
|
|
2
|
+
import { Static } from "./static.js";
|
|
3
|
+
export class DocFields {
|
|
4
|
+
fields;
|
|
5
|
+
file;
|
|
6
|
+
constructor(fields) {
|
|
7
|
+
this.fields = fields;
|
|
8
|
+
this.file = new DocGenFile({
|
|
9
|
+
dir: "/",
|
|
10
|
+
fileName: "fields.ts",
|
|
11
|
+
data: this.build(),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
build() {
|
|
15
|
+
const content = `
|
|
16
|
+
${Static.AUTO_GENERATED_COMMENT}
|
|
17
|
+
export const FIELD_NAMES = [${this.fields}] as const
|
|
18
|
+
export type FieldName = (typeof FIELD_NAMES)[number];
|
|
19
|
+
`;
|
|
20
|
+
return content;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/file.d.ts
ADDED
package/dist/file.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as prettier from "prettier";
|
|
4
|
+
const ROOT = process.cwd();
|
|
5
|
+
const OUT_DIR = path.join(ROOT, "generated/docgen");
|
|
6
|
+
export class DocGenFile {
|
|
7
|
+
outDir;
|
|
8
|
+
data;
|
|
9
|
+
constructor(params) {
|
|
10
|
+
const { fileName, dir, data, customDir } = params;
|
|
11
|
+
this.outDir = path.join(OUT_DIR, dir, fileName);
|
|
12
|
+
this.data = data;
|
|
13
|
+
if (customDir)
|
|
14
|
+
this.outDir = customDir;
|
|
15
|
+
}
|
|
16
|
+
async save() {
|
|
17
|
+
const dir = path.dirname(this.outDir);
|
|
18
|
+
await fs.mkdir(dir, { recursive: true });
|
|
19
|
+
const prettierConfig = await prettier.resolveConfig(this.outDir);
|
|
20
|
+
const formatted = await prettier.format(this.data, {
|
|
21
|
+
...prettierConfig,
|
|
22
|
+
filepath: this.outDir,
|
|
23
|
+
});
|
|
24
|
+
await fs.writeFile(this.outDir, formatted, "utf-8");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Scalar, Field, DocGenModel } from "../types.js";
|
|
2
|
+
export declare class Helper {
|
|
3
|
+
static prismaScalarToTs(s: Scalar): string;
|
|
4
|
+
static validatorForScalar(s: Scalar): string;
|
|
5
|
+
static swaggerType(field: Field): string | undefined;
|
|
6
|
+
static toKebab(s: string): string;
|
|
7
|
+
importsForModel(model: DocGenModel): {
|
|
8
|
+
enums: boolean;
|
|
9
|
+
hasDate: boolean;
|
|
10
|
+
};
|
|
11
|
+
static readPrismaFolderDatamodel(dir: string): Promise<string>;
|
|
12
|
+
findTypeForField(field: Field): string;
|
|
13
|
+
isDate(field: Field): boolean;
|
|
14
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
export class Helper {
|
|
4
|
+
static prismaScalarToTs(s) {
|
|
5
|
+
switch (s) {
|
|
6
|
+
case "String":
|
|
7
|
+
return "string";
|
|
8
|
+
case "Int":
|
|
9
|
+
return "number";
|
|
10
|
+
case "BigInt":
|
|
11
|
+
return "bigint";
|
|
12
|
+
case "Float":
|
|
13
|
+
case "Decimal":
|
|
14
|
+
return "number";
|
|
15
|
+
case "Boolean":
|
|
16
|
+
return "boolean";
|
|
17
|
+
case "DateTime":
|
|
18
|
+
return "Date";
|
|
19
|
+
case "Json":
|
|
20
|
+
return "any";
|
|
21
|
+
case "Bytes":
|
|
22
|
+
return "Buffer";
|
|
23
|
+
default: {
|
|
24
|
+
return "any";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
static validatorForScalar(s) {
|
|
29
|
+
switch (s) {
|
|
30
|
+
case "String":
|
|
31
|
+
return "IsString";
|
|
32
|
+
case "Int":
|
|
33
|
+
return "IsInt";
|
|
34
|
+
case "BigInt":
|
|
35
|
+
return "IsNumber"; // class-validator não tem IsBigInt
|
|
36
|
+
case "Float":
|
|
37
|
+
case "Decimal":
|
|
38
|
+
return "IsNumber";
|
|
39
|
+
case "Boolean":
|
|
40
|
+
return "IsBoolean";
|
|
41
|
+
case "DateTime":
|
|
42
|
+
return "IsDate";
|
|
43
|
+
case "Json":
|
|
44
|
+
return ""; // costuma ser livre
|
|
45
|
+
case "Bytes":
|
|
46
|
+
return ""; // Buffer não tem decorador nativo
|
|
47
|
+
default:
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
static swaggerType(field) {
|
|
52
|
+
// mapeia para Swagger 'type' básico quando possível
|
|
53
|
+
if (field.kind === "scalar") {
|
|
54
|
+
const t = field.type;
|
|
55
|
+
switch (t) {
|
|
56
|
+
case "String":
|
|
57
|
+
return "string";
|
|
58
|
+
case "Int":
|
|
59
|
+
case "Float":
|
|
60
|
+
case "Decimal":
|
|
61
|
+
return "number";
|
|
62
|
+
case "BigInt":
|
|
63
|
+
return "integer";
|
|
64
|
+
case "Boolean":
|
|
65
|
+
return "boolean";
|
|
66
|
+
case "DateTime":
|
|
67
|
+
return "string"; // format date-time
|
|
68
|
+
case "Json":
|
|
69
|
+
return "object";
|
|
70
|
+
case "Bytes":
|
|
71
|
+
return "string"; // base64
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
static toKebab(s) {
|
|
77
|
+
return s.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
78
|
+
}
|
|
79
|
+
importsForModel(model) {
|
|
80
|
+
const needsEnum = model.fields.some((field) => field.kind === "enum");
|
|
81
|
+
return {
|
|
82
|
+
enums: needsEnum,
|
|
83
|
+
hasDate: model.fields.some((field) => field.kind === "scalar" && field.type === "DateTime"),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
static async readPrismaFolderDatamodel(dir) {
|
|
87
|
+
const files = [];
|
|
88
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
89
|
+
const mainSchemaPath = path.join(dir, "schema.prisma");
|
|
90
|
+
try {
|
|
91
|
+
const mainStat = await fs.stat(mainSchemaPath);
|
|
92
|
+
if (mainStat.isFile())
|
|
93
|
+
files.push(mainSchemaPath);
|
|
94
|
+
}
|
|
95
|
+
catch { }
|
|
96
|
+
// varrer demais itens (exceto migrations e o schema.prisma já incluído)
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
if (entry.name === "migrations")
|
|
99
|
+
continue;
|
|
100
|
+
const full = path.join(dir, entry.name);
|
|
101
|
+
if (entry.isDirectory()) {
|
|
102
|
+
const nested = await this.readPrismaFolderDatamodel(full);
|
|
103
|
+
if (nested.trim())
|
|
104
|
+
files.push(`\n// ---- ${full} ----\n${nested}`);
|
|
105
|
+
}
|
|
106
|
+
else if (entry.isFile() && entry.name.endsWith(".prisma") && full !== mainSchemaPath) {
|
|
107
|
+
const content = await fs.readFile(full, "utf-8");
|
|
108
|
+
files.push(`\n// ---- ${full} ----\n${content}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// se não houver nada além de subpastas, retorna o que juntou nelas
|
|
112
|
+
if (!files.length)
|
|
113
|
+
return "";
|
|
114
|
+
// quando schema.prisma existe, ele já está no topo do array
|
|
115
|
+
if (files[0] === mainSchemaPath) {
|
|
116
|
+
const head = await fs.readFile(mainSchemaPath, "utf-8");
|
|
117
|
+
const tail = files.slice(1).join("\n");
|
|
118
|
+
return `${head}\n${tail}`;
|
|
119
|
+
}
|
|
120
|
+
// caso não exista schema.prisma, apenas concatena
|
|
121
|
+
const contents = await Promise.all(files.map(async (f) => (f.startsWith("\n// ---- ") ? f : fs.readFile(f, "utf-8"))));
|
|
122
|
+
return contents.join("\n");
|
|
123
|
+
}
|
|
124
|
+
findTypeForField(field) {
|
|
125
|
+
if (field.kind === "scalar") {
|
|
126
|
+
const base = Helper.prismaScalarToTs(field.type);
|
|
127
|
+
return field.isList ? `${base}[]` : base;
|
|
128
|
+
}
|
|
129
|
+
else if (field.kind === "enum") {
|
|
130
|
+
const base = field.type;
|
|
131
|
+
return field.isList ? `${base}[]` : base;
|
|
132
|
+
}
|
|
133
|
+
else if (field.kind === "object") {
|
|
134
|
+
const base = "any";
|
|
135
|
+
return field.isList ? `${base}[]` : base;
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
return "any";
|
|
139
|
+
}
|
|
140
|
+
isDate(field) {
|
|
141
|
+
return field.kind === "scalar" && field.type === "DateTime";
|
|
142
|
+
}
|
|
143
|
+
}
|