@visulima/crud 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/CHANGELOG.md +14 -0
- package/LICENSE.md +21 -0
- package/README.md +101 -0
- package/dist/chunk-FJWRITBO.js +52 -0
- package/dist/chunk-FJWRITBO.js.map +1 -0
- package/dist/chunk-UBXIGP5H.mjs +52 -0
- package/dist/chunk-UBXIGP5H.mjs.map +1 -0
- package/dist/index.d.ts +155 -0
- package/dist/index.js +1101 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1101 -0
- package/dist/index.mjs.map +1 -0
- package/dist/next/index.d.ts +8 -0
- package/dist/next/index.js +729 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/index.mjs +729 -0
- package/dist/next/index.mjs.map +1 -0
- package/dist/types.d-6817d247.d.ts +155 -0
- package/package.json +136 -0
- package/src/adapter/prisma/index.ts +241 -0
- package/src/adapter/prisma/types.d.ts +46 -0
- package/src/adapter/prisma/utils/models-to-route-names.ts +12 -0
- package/src/adapter/prisma/utils/parse-cursor.ts +26 -0
- package/src/adapter/prisma/utils/parse-order-by.ts +21 -0
- package/src/adapter/prisma/utils/parse-recursive.ts +26 -0
- package/src/adapter/prisma/utils/parse-where.ts +197 -0
- package/src/base-crud-handler.ts +181 -0
- package/src/handler/create.ts +21 -0
- package/src/handler/delete.ts +27 -0
- package/src/handler/list.ts +62 -0
- package/src/handler/read.ts +27 -0
- package/src/handler/update.ts +29 -0
- package/src/index.ts +27 -0
- package/src/next/api/edge/index.ts +23 -0
- package/src/next/api/node/index.ts +27 -0
- package/src/next/index.ts +2 -0
- package/src/query-parser.ts +94 -0
- package/src/swagger/adapter/prisma/index.ts +95 -0
- package/src/swagger/json-schema-parser.ts +456 -0
- package/src/swagger/parameters.ts +83 -0
- package/src/swagger/types.d.ts +53 -0
- package/src/swagger/utils/format-example-ref.ts +4 -0
- package/src/swagger/utils/format-schema-ref.ts +4 -0
- package/src/swagger/utils/get-models-accessible-routes.ts +23 -0
- package/src/swagger/utils/get-swagger-paths.ts +244 -0
- package/src/swagger/utils/get-swagger-tags.ts +13 -0
- package/src/types.d.ts +124 -0
- package/src/utils/format-resource-id.ts +3 -0
- package/src/utils/get-accessible-routes.ts +18 -0
- package/src/utils/get-resource-name-from-url.ts +23 -0
- package/src/utils/get-route-type.ts +99 -0
- package/src/utils/is-primitive.ts +5 -0
- package/src/utils/validate-adapter-methods.ts +15 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from "next";
|
|
2
|
+
|
|
3
|
+
import baseHandler from "../../../base-crud-handler";
|
|
4
|
+
import type {
|
|
5
|
+
Adapter, ExecuteHandler, HandlerOptions, ParsedQueryParameters,
|
|
6
|
+
} from "../../../types.d";
|
|
7
|
+
|
|
8
|
+
async function handler<
|
|
9
|
+
T,
|
|
10
|
+
Q extends ParsedQueryParameters = any,
|
|
11
|
+
R extends NextApiRequest = NextApiRequest,
|
|
12
|
+
Response extends NextApiResponse = NextApiResponse,
|
|
13
|
+
M extends string = string,
|
|
14
|
+
>(adapter: Adapter<T, Q>, options?: HandlerOptions<M>): Promise<ExecuteHandler<R, Response>> {
|
|
15
|
+
return baseHandler<R, Response, T, Q, M>(
|
|
16
|
+
async (response, responseConfig) => {
|
|
17
|
+
response.status(responseConfig.status).send(responseConfig.data);
|
|
18
|
+
},
|
|
19
|
+
async (response) => {
|
|
20
|
+
(response as Response).end();
|
|
21
|
+
},
|
|
22
|
+
adapter,
|
|
23
|
+
options,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default handler;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import set from "lodash.set";
|
|
2
|
+
import { parse } from "qs";
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
OrderByField, ParsedQueryParameters, RecursiveField, WhereField,
|
|
6
|
+
} from "./types.d";
|
|
7
|
+
|
|
8
|
+
const parseRecursive = (select: string): RecursiveField => {
|
|
9
|
+
if (typeof select === "string") {
|
|
10
|
+
const selectFields: RecursiveField = {};
|
|
11
|
+
|
|
12
|
+
const fields = select.split(",");
|
|
13
|
+
|
|
14
|
+
fields.forEach((field) => {
|
|
15
|
+
set(selectFields, field, true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return selectFields;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
throw new Error("select query param must be a string");
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const parseWhere = (where: string): WhereField => {
|
|
25
|
+
const whereObject = JSON.parse(where);
|
|
26
|
+
const parsed: WhereField = {};
|
|
27
|
+
|
|
28
|
+
Object.keys(whereObject).forEach((key) => {
|
|
29
|
+
set(parsed, key, whereObject[key]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return parsed;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const parseOrderBy = (orderBy: string): OrderByField => {
|
|
36
|
+
const parsed: OrderByField = {};
|
|
37
|
+
const orderByObject = JSON.parse(orderBy);
|
|
38
|
+
|
|
39
|
+
if (Object.keys(orderByObject).length > 0) {
|
|
40
|
+
const key = Object.keys(orderByObject)[0];
|
|
41
|
+
|
|
42
|
+
if (orderByObject[key as keyof typeof orderByObject] === "$asc" || orderByObject[key as keyof typeof orderByObject] === "$desc") {
|
|
43
|
+
parsed[key as string] = orderByObject[key as keyof typeof orderByObject];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (Object.keys(parsed).length !== 1) {
|
|
48
|
+
throw new Error("orderBy needs to be an object with exactly 1 property with either $asc or $desc value");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return parsed;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// eslint-disable-next-line radar/cognitive-complexity
|
|
55
|
+
const parseQuery = (queryString?: string): ParsedQueryParameters => {
|
|
56
|
+
if (queryString) {
|
|
57
|
+
const query = parse(queryString);
|
|
58
|
+
const parsedQuery: ParsedQueryParameters = {};
|
|
59
|
+
|
|
60
|
+
if (query.select) {
|
|
61
|
+
parsedQuery.select = parseRecursive(query.select as string);
|
|
62
|
+
}
|
|
63
|
+
if (query.include) {
|
|
64
|
+
parsedQuery.include = parseRecursive(query.include as string);
|
|
65
|
+
}
|
|
66
|
+
if (query.where) {
|
|
67
|
+
parsedQuery.where = parseWhere(query.where as string);
|
|
68
|
+
}
|
|
69
|
+
if (query.orderBy) {
|
|
70
|
+
parsedQuery.orderBy = parseOrderBy(query.orderBy as string);
|
|
71
|
+
}
|
|
72
|
+
if (typeof query.limit !== "undefined") {
|
|
73
|
+
parsedQuery.limit = Number.isFinite(+query.limit) ? +query.limit : undefined;
|
|
74
|
+
}
|
|
75
|
+
if (typeof query.skip !== "undefined") {
|
|
76
|
+
parsedQuery.skip = Number.isFinite(+query.skip) ? +query.skip : undefined;
|
|
77
|
+
}
|
|
78
|
+
if (query.distinct) {
|
|
79
|
+
parsedQuery.distinct = query.distinct as string;
|
|
80
|
+
}
|
|
81
|
+
if (query.page) {
|
|
82
|
+
parsedQuery.page = Number.isFinite(+query.page) ? +query.page : undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
originalQuery: query,
|
|
87
|
+
...parsedQuery,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default parseQuery;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
|
+
import {
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
PrismaClient,
|
|
5
|
+
} from "@prisma/client";
|
|
6
|
+
|
|
7
|
+
import modelsToRouteNames from "../../../adapter/prisma/utils/models-to-route-names";
|
|
8
|
+
import type { ModelsOptions } from "../../../types.d";
|
|
9
|
+
import PrismaJsonSchemaParser from "../../json-schema-parser";
|
|
10
|
+
import type { SwaggerModelsConfig } from "../../types.d";
|
|
11
|
+
import getModelsAccessibleRoutes from "../../utils/get-models-accessible-routes";
|
|
12
|
+
import getSwaggerPaths from "../../utils/get-swagger-paths";
|
|
13
|
+
import getSwaggerTags from "../../utils/get-swagger-tags";
|
|
14
|
+
|
|
15
|
+
const modelsToOpenApi = async <M extends string = string>({
|
|
16
|
+
prismaClient,
|
|
17
|
+
models: ctorModels,
|
|
18
|
+
swagger = { models: {}, allowedMediaTypes: { "application/json": true } },
|
|
19
|
+
crud = { models: {} },
|
|
20
|
+
defaultExposeStrategy = "all",
|
|
21
|
+
}: ModelsToOpenApiParameters<M>) => {
|
|
22
|
+
let dmmf: any;
|
|
23
|
+
let prismaDmmfModels: any;
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
26
|
+
if (prismaClient._dmmf) {
|
|
27
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
28
|
+
dmmf = prismaClient._dmmf;
|
|
29
|
+
prismaDmmfModels = dmmf?.mappingsMap;
|
|
30
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
31
|
+
} else if (prismaClient._getDmmf) {
|
|
32
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
33
|
+
dmmf = await prismaClient._getDmmf();
|
|
34
|
+
prismaDmmfModels = dmmf.mappingsMap;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof dmmf === undefined) {
|
|
38
|
+
throw new TypeError("Couldn't get prisma client models");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const parser = new PrismaJsonSchemaParser(dmmf);
|
|
42
|
+
|
|
43
|
+
const definitions = parser.parseModels();
|
|
44
|
+
const dModels = Object.keys(definitions);
|
|
45
|
+
|
|
46
|
+
const schema = JSON.stringify({
|
|
47
|
+
...definitions,
|
|
48
|
+
...parser.parseInputTypes(dModels),
|
|
49
|
+
...parser.getPaginationDataSchema(),
|
|
50
|
+
...parser.getPaginatedModelsSchemas(dModels),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (typeof ctorModels !== "undefined") {
|
|
54
|
+
ctorModels.forEach((model) => {
|
|
55
|
+
if (!Object.keys(prismaDmmfModels).includes(model)) {
|
|
56
|
+
throw new Error(`Model name ${model} is invalid.`);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// @ts-ignore
|
|
62
|
+
const models = ctorModels ?? (Object.keys(prismaDmmfModels) as M[]);
|
|
63
|
+
|
|
64
|
+
const swaggerRoutes = getModelsAccessibleRoutes(models, crud.models || {}, defaultExposeStrategy);
|
|
65
|
+
const swaggerTags = getSwaggerTags(models, swagger?.models || {});
|
|
66
|
+
const swaggerPaths = getSwaggerPaths({
|
|
67
|
+
routes: swaggerRoutes,
|
|
68
|
+
modelsConfig: swagger?.models || {},
|
|
69
|
+
models: crud.models || {},
|
|
70
|
+
routesMap: modelsToRouteNames(prismaDmmfModels, models),
|
|
71
|
+
});
|
|
72
|
+
const schemas = JSON.parse(schema.replace(/#\/definitions/g, "#/components/schemas"));
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
schemas,
|
|
76
|
+
examples: parser.getExampleModelsSchemas(dModels, schemas),
|
|
77
|
+
tags: swaggerTags,
|
|
78
|
+
paths: swaggerPaths,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export interface ModelsToOpenApiParameters<M extends string = string> {
|
|
83
|
+
prismaClient: PrismaClient;
|
|
84
|
+
defaultExposeStrategy?: "all" | "none";
|
|
85
|
+
models?: M[];
|
|
86
|
+
swagger?: Partial<{
|
|
87
|
+
models: SwaggerModelsConfig<M>;
|
|
88
|
+
allowedMediaTypes: { [key: string]: boolean };
|
|
89
|
+
}>;
|
|
90
|
+
crud?: {
|
|
91
|
+
models: ModelsOptions<M>;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default modelsToOpenApi;
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { getJSONSchemaProperty, transformDMMF } from "@visulima/prisma-dmmf-transformer";
|
|
2
|
+
import type { JSONSchema7 } from "json-schema";
|
|
3
|
+
import type { OpenAPIV3 } from "openapi-types";
|
|
4
|
+
|
|
5
|
+
import formatSchemaReference from "./utils/format-schema-ref";
|
|
6
|
+
|
|
7
|
+
const getJSONSchemaScalar = (fieldType: string | object) => {
|
|
8
|
+
switch (fieldType) {
|
|
9
|
+
case "Int":
|
|
10
|
+
case "BigInt": {
|
|
11
|
+
return "integer";
|
|
12
|
+
}
|
|
13
|
+
case "DateTime":
|
|
14
|
+
case "Bytes":
|
|
15
|
+
case "String": {
|
|
16
|
+
return "string";
|
|
17
|
+
}
|
|
18
|
+
case "Float":
|
|
19
|
+
case "Decimal": {
|
|
20
|
+
return "number";
|
|
21
|
+
}
|
|
22
|
+
case "Json": {
|
|
23
|
+
return "object";
|
|
24
|
+
}
|
|
25
|
+
case "Boolean": {
|
|
26
|
+
return "boolean";
|
|
27
|
+
}
|
|
28
|
+
case "Null": {
|
|
29
|
+
return "null";
|
|
30
|
+
}
|
|
31
|
+
default: {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const PAGINATION_SCHEMA_NAME = "PaginationData";
|
|
38
|
+
|
|
39
|
+
const methodsNames = [
|
|
40
|
+
{ methodStart: "createOne", schemaNameStart: "Create" },
|
|
41
|
+
{ methodStart: "updateOne", schemaNameStart: "Update" },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
class PrismaJsonSchemaParser {
|
|
45
|
+
schemaInputTypes: Map<string, any> = new Map<string, any>();
|
|
46
|
+
|
|
47
|
+
constructor(private dmmf: any) {}
|
|
48
|
+
|
|
49
|
+
public parseModels(): {
|
|
50
|
+
[key: string]: JSONSchema7;
|
|
51
|
+
} {
|
|
52
|
+
const modelsDefinitions = transformDMMF(this.dmmf).definitions as {
|
|
53
|
+
[key: string]: JSONSchema7;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
Object.keys(modelsDefinitions || {})?.forEach((definition: string | number) => {
|
|
57
|
+
// @TODO: added the correct type
|
|
58
|
+
// @ts-ignore
|
|
59
|
+
const { properties } = modelsDefinitions[definition];
|
|
60
|
+
|
|
61
|
+
Object.keys(properties).forEach((property: string) => {
|
|
62
|
+
if (Array.isArray(properties[property].type) && properties[property].type.includes("null")) {
|
|
63
|
+
properties[property].type = properties[property].type.filter((type: string) => type !== "null");
|
|
64
|
+
|
|
65
|
+
if (properties[property].type.length === 1) {
|
|
66
|
+
// eslint-disable-next-line prefer-destructuring
|
|
67
|
+
properties[property].type = properties[property].type[0];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
properties[property].nullable = true;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return modelsDefinitions;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// eslint-disable-next-line radar/cognitive-complexity
|
|
79
|
+
public parseInputTypes(models: string[]) {
|
|
80
|
+
// eslint-disable-next-line radar/cognitive-complexity
|
|
81
|
+
const definitions = models.reduce((accumulator: { [key: string]: any }, modelName) => {
|
|
82
|
+
const methods = methodsNames.map((method) => {
|
|
83
|
+
return {
|
|
84
|
+
name: `${method.methodStart}${modelName}`,
|
|
85
|
+
schemaName: `${method.schemaNameStart}${modelName}`,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
methods.forEach(({ name: method, schemaName }) => {
|
|
90
|
+
// @ts-ignore
|
|
91
|
+
const dataFields = this.dmmf.mutationType.fieldMap[method].args[0].inputTypes[0].type.fields;
|
|
92
|
+
const requiredProperties: string[] = [];
|
|
93
|
+
const properties = dataFields.reduce((propertiesAccumulator: any, field: any) => {
|
|
94
|
+
if (field.inputTypes[0].kind === "scalar") {
|
|
95
|
+
const schema = getJSONSchemaProperty(
|
|
96
|
+
this.dmmf.datamodel,
|
|
97
|
+
{},
|
|
98
|
+
)({
|
|
99
|
+
name: field.name,
|
|
100
|
+
...field.inputTypes[0],
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// @TODO: added the correct type
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
const { type: schemaType } = schema[1];
|
|
106
|
+
|
|
107
|
+
if (schemaType && Array.isArray(schemaType)) {
|
|
108
|
+
if (schemaType.includes("null")) {
|
|
109
|
+
// eslint-disable-next-line no-param-reassign
|
|
110
|
+
propertiesAccumulator[field.name] = {
|
|
111
|
+
...schemaType,
|
|
112
|
+
type: schemaType.filter((type: string) => type !== "null"),
|
|
113
|
+
nullable: true,
|
|
114
|
+
};
|
|
115
|
+
if (propertiesAccumulator[field.name].type.length === 1) {
|
|
116
|
+
// eslint-disable-next-line no-param-reassign
|
|
117
|
+
propertiesAccumulator[field.name] = {
|
|
118
|
+
...propertiesAccumulator[field.name],
|
|
119
|
+
type: propertiesAccumulator[field.name].type[0],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// eslint-disable-next-line no-param-reassign,prefer-destructuring
|
|
125
|
+
propertiesAccumulator[field.name] = schema[1];
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
const typeName = this.parseObjectInputType(field.inputTypes[0]);
|
|
129
|
+
|
|
130
|
+
// eslint-disable-next-line no-param-reassign
|
|
131
|
+
propertiesAccumulator[field.name] = {
|
|
132
|
+
...typeName,
|
|
133
|
+
nullable: field.isNullable,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (field.isRequired) {
|
|
138
|
+
requiredProperties.push(field.name);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return propertiesAccumulator;
|
|
142
|
+
}, {});
|
|
143
|
+
|
|
144
|
+
accumulator[schemaName] = {
|
|
145
|
+
type: "object",
|
|
146
|
+
xml: {
|
|
147
|
+
name: schemaName,
|
|
148
|
+
},
|
|
149
|
+
properties,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (requiredProperties.length > 0) {
|
|
153
|
+
accumulator[schemaName].required = requiredProperties;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return accumulator;
|
|
158
|
+
}, {});
|
|
159
|
+
|
|
160
|
+
this.schemaInputTypes.forEach((value, key) => {
|
|
161
|
+
definitions[key] = {
|
|
162
|
+
type: "object",
|
|
163
|
+
xml: {
|
|
164
|
+
name: key,
|
|
165
|
+
},
|
|
166
|
+
properties: value,
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return definitions;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// eslint-disable-next-line class-methods-use-this
|
|
174
|
+
public formatInputTypeData(inputType: any) {
|
|
175
|
+
if (inputType.kind === "object") {
|
|
176
|
+
const reference = formatSchemaReference(inputType.type.name);
|
|
177
|
+
|
|
178
|
+
if (inputType.isList) {
|
|
179
|
+
return {
|
|
180
|
+
type: "array",
|
|
181
|
+
xml: {
|
|
182
|
+
name: inputType.type.name,
|
|
183
|
+
wrapped: true,
|
|
184
|
+
},
|
|
185
|
+
items: {
|
|
186
|
+
$ref: reference,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return { $ref: reference };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const type = getJSONSchemaScalar(inputType.type);
|
|
195
|
+
|
|
196
|
+
if (inputType.isList) {
|
|
197
|
+
return {
|
|
198
|
+
type: "array",
|
|
199
|
+
xml: {
|
|
200
|
+
name: inputType.type.name,
|
|
201
|
+
wrapped: true,
|
|
202
|
+
},
|
|
203
|
+
items: {
|
|
204
|
+
type,
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { type };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// eslint-disable-next-line radar/cognitive-complexity
|
|
213
|
+
public parseObjectInputType(fieldType: any) {
|
|
214
|
+
if (fieldType.kind === "object") {
|
|
215
|
+
if (!this.schemaInputTypes.has(fieldType.type.name)) {
|
|
216
|
+
this.schemaInputTypes.set(fieldType.type.name, {});
|
|
217
|
+
|
|
218
|
+
fieldType.type.fields.forEach((field: any) => {
|
|
219
|
+
let fieldData: Record<string, any> = {};
|
|
220
|
+
|
|
221
|
+
if (field.inputTypes.length > 1) {
|
|
222
|
+
let nullable = false;
|
|
223
|
+
|
|
224
|
+
const anyOf = field.inputTypes
|
|
225
|
+
.map((inputType: any) => {
|
|
226
|
+
const inputTypeData = this.formatInputTypeData(inputType);
|
|
227
|
+
|
|
228
|
+
if (inputTypeData.type === "null") {
|
|
229
|
+
nullable = true;
|
|
230
|
+
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// eslint-disable-next-line consistent-return
|
|
235
|
+
return inputTypeData;
|
|
236
|
+
})
|
|
237
|
+
.filter(Boolean);
|
|
238
|
+
|
|
239
|
+
if (anyOf.length === 1) {
|
|
240
|
+
// eslint-disable-next-line prefer-destructuring
|
|
241
|
+
fieldData = anyOf[0];
|
|
242
|
+
} else {
|
|
243
|
+
fieldData.anyOf = anyOf;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (nullable) {
|
|
247
|
+
fieldData.nullable = true;
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
const inputType = field.inputTypes[0];
|
|
251
|
+
|
|
252
|
+
fieldData = this.formatInputTypeData(inputType);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.schemaInputTypes.set(fieldType.type.name, {
|
|
256
|
+
...this.schemaInputTypes.get(fieldType.type.name),
|
|
257
|
+
[field.name]: fieldData,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
field.inputTypes.forEach((inputType: any) => {
|
|
261
|
+
if (inputType.kind === "object") {
|
|
262
|
+
this.parseObjectInputType(inputType);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return { $ref: formatSchemaReference(fieldType.type.name) };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return { type: getJSONSchemaScalar(fieldType.type) };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// eslint-disable-next-line class-methods-use-this
|
|
275
|
+
public getPaginationDataSchema() {
|
|
276
|
+
return {
|
|
277
|
+
[PAGINATION_SCHEMA_NAME]: {
|
|
278
|
+
type: "object",
|
|
279
|
+
xml: {
|
|
280
|
+
name: PAGINATION_SCHEMA_NAME,
|
|
281
|
+
},
|
|
282
|
+
properties: {
|
|
283
|
+
total: {
|
|
284
|
+
type: "integer",
|
|
285
|
+
minimum: 0,
|
|
286
|
+
description: "Holds the value for the total number of rows in the database",
|
|
287
|
+
},
|
|
288
|
+
perPage: {
|
|
289
|
+
type: "integer",
|
|
290
|
+
minimum: 0,
|
|
291
|
+
description: "Returns the value for the limit passed to the paginate method",
|
|
292
|
+
},
|
|
293
|
+
page: {
|
|
294
|
+
type: "integer",
|
|
295
|
+
minimum: 1,
|
|
296
|
+
description: "Current page number",
|
|
297
|
+
},
|
|
298
|
+
lastPage: {
|
|
299
|
+
type: "integer",
|
|
300
|
+
minimum: 0,
|
|
301
|
+
description: "Returns the value for the last page by taking the total of rows into account",
|
|
302
|
+
},
|
|
303
|
+
firstPage: {
|
|
304
|
+
type: "integer",
|
|
305
|
+
minimum: 0,
|
|
306
|
+
description: "Returns the number for the first page. It is always 1",
|
|
307
|
+
},
|
|
308
|
+
firstPageUrl: {
|
|
309
|
+
type: "string",
|
|
310
|
+
description: "The URL for the first page",
|
|
311
|
+
},
|
|
312
|
+
lastPageUrl: {
|
|
313
|
+
type: "string",
|
|
314
|
+
description: "The URL for the last page",
|
|
315
|
+
},
|
|
316
|
+
nextPageUrl: {
|
|
317
|
+
type: "string",
|
|
318
|
+
description: "The URL for the next page",
|
|
319
|
+
},
|
|
320
|
+
previousPageUrl: {
|
|
321
|
+
type: "string",
|
|
322
|
+
description: "The URL for the previous page",
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
public getExampleModelsSchemas(
|
|
330
|
+
modelNames: string[],
|
|
331
|
+
schemas: {
|
|
332
|
+
[key: string]: OpenAPIV3.SchemaObject;
|
|
333
|
+
},
|
|
334
|
+
) {
|
|
335
|
+
const referenceToSchema = (reference: string) => {
|
|
336
|
+
const name = reference.replace("#/components/schemas/", "");
|
|
337
|
+
const model = schemas[name] as OpenAPIV3.SchemaObject;
|
|
338
|
+
|
|
339
|
+
const values: { [key: string]: string | object[] } = {};
|
|
340
|
+
|
|
341
|
+
Object.entries((model?.properties as OpenAPIV3.SchemaObject) || {}).forEach(([key, v]) => {
|
|
342
|
+
const type = (v as OpenAPIV3.SchemaObject).type as string;
|
|
343
|
+
|
|
344
|
+
if (type === "array") {
|
|
345
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
346
|
+
values[key] = [arrayItemsToSchema(v.items)];
|
|
347
|
+
} else {
|
|
348
|
+
values[key] = type;
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
return values;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const objectPropertiesToSchema = (objectProperties: { [name: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject }) => {
|
|
356
|
+
const values: { [key: string]: string | object | object[] } = {};
|
|
357
|
+
|
|
358
|
+
Object.entries(objectProperties).forEach(([key, value]) => {
|
|
359
|
+
if (typeof (value as OpenAPIV3.ReferenceObject).$ref !== "undefined") {
|
|
360
|
+
values[key] = referenceToSchema((value as OpenAPIV3.ReferenceObject).$ref);
|
|
361
|
+
} else {
|
|
362
|
+
values[key] = (value as OpenAPIV3.SchemaObject).type as string;
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
return values;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const arrayItemsToSchema = (items: OpenAPIV3.ArraySchemaObject) => {
|
|
370
|
+
const values: { [key: string]: object | object[] } = {};
|
|
371
|
+
|
|
372
|
+
Object.entries(items).forEach(([key, value]) => {
|
|
373
|
+
if (typeof value.items.$ref !== "undefined") {
|
|
374
|
+
values[key] = [referenceToSchema(value.items.$ref)];
|
|
375
|
+
} else if (value.type === "array") {
|
|
376
|
+
values[key] = [arrayItemsToSchema(value.items)];
|
|
377
|
+
} else if (value.type === "object") {
|
|
378
|
+
values[key] = objectPropertiesToSchema(value.properties);
|
|
379
|
+
} else {
|
|
380
|
+
values[key] = value.type;
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return values;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
return modelNames.reduce((accumulator, modelName) => {
|
|
388
|
+
const value: { [key: string]: string | object | object[] } = {};
|
|
389
|
+
const model = schemas[modelName] as OpenAPIV3.SchemaObject;
|
|
390
|
+
|
|
391
|
+
Object.entries(model.properties as OpenAPIV3.SchemaObject).forEach(([key, v]) => {
|
|
392
|
+
const type = (v as OpenAPIV3.SchemaObject).type as string;
|
|
393
|
+
|
|
394
|
+
if (type === "array") {
|
|
395
|
+
value[key] = [referenceToSchema(v.items.$ref)];
|
|
396
|
+
} else if (type === "object") {
|
|
397
|
+
value[key] = objectPropertiesToSchema(v.properties);
|
|
398
|
+
} else {
|
|
399
|
+
value[key] = type;
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const pagination = this.getPaginationDataSchema()[PAGINATION_SCHEMA_NAME];
|
|
404
|
+
const meta: { [key: string]: string } = {};
|
|
405
|
+
|
|
406
|
+
Object.entries(pagination.properties as OpenAPIV3.SchemaObject).forEach(([key, v]) => {
|
|
407
|
+
meta[key] = (v as OpenAPIV3.SchemaObject).type as string;
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
...accumulator,
|
|
412
|
+
[`${modelName}`]: {
|
|
413
|
+
value,
|
|
414
|
+
},
|
|
415
|
+
[`${modelName}Page`]: {
|
|
416
|
+
value: {
|
|
417
|
+
data: [value],
|
|
418
|
+
meta,
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
}, {});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// eslint-disable-next-line class-methods-use-this
|
|
426
|
+
public getPaginatedModelsSchemas(modelNames: string[]) {
|
|
427
|
+
return modelNames.reduce((accumulator, modelName) => {
|
|
428
|
+
return {
|
|
429
|
+
...accumulator,
|
|
430
|
+
[`${modelName}Page`]: {
|
|
431
|
+
type: "object",
|
|
432
|
+
xml: {
|
|
433
|
+
name: `${modelName}Page`,
|
|
434
|
+
},
|
|
435
|
+
properties: {
|
|
436
|
+
data: {
|
|
437
|
+
type: "array",
|
|
438
|
+
xml: {
|
|
439
|
+
name: "Data",
|
|
440
|
+
wrapped: true,
|
|
441
|
+
},
|
|
442
|
+
items: {
|
|
443
|
+
$ref: formatSchemaReference(modelName),
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
meta: {
|
|
447
|
+
$ref: formatSchemaReference(PAGINATION_SCHEMA_NAME),
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
}, {});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export default PrismaJsonSchemaParser;
|