@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.
Files changed (53) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +101 -0
  4. package/dist/chunk-FJWRITBO.js +52 -0
  5. package/dist/chunk-FJWRITBO.js.map +1 -0
  6. package/dist/chunk-UBXIGP5H.mjs +52 -0
  7. package/dist/chunk-UBXIGP5H.mjs.map +1 -0
  8. package/dist/index.d.ts +155 -0
  9. package/dist/index.js +1101 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/index.mjs +1101 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/dist/next/index.d.ts +8 -0
  14. package/dist/next/index.js +729 -0
  15. package/dist/next/index.js.map +1 -0
  16. package/dist/next/index.mjs +729 -0
  17. package/dist/next/index.mjs.map +1 -0
  18. package/dist/types.d-6817d247.d.ts +155 -0
  19. package/package.json +136 -0
  20. package/src/adapter/prisma/index.ts +241 -0
  21. package/src/adapter/prisma/types.d.ts +46 -0
  22. package/src/adapter/prisma/utils/models-to-route-names.ts +12 -0
  23. package/src/adapter/prisma/utils/parse-cursor.ts +26 -0
  24. package/src/adapter/prisma/utils/parse-order-by.ts +21 -0
  25. package/src/adapter/prisma/utils/parse-recursive.ts +26 -0
  26. package/src/adapter/prisma/utils/parse-where.ts +197 -0
  27. package/src/base-crud-handler.ts +181 -0
  28. package/src/handler/create.ts +21 -0
  29. package/src/handler/delete.ts +27 -0
  30. package/src/handler/list.ts +62 -0
  31. package/src/handler/read.ts +27 -0
  32. package/src/handler/update.ts +29 -0
  33. package/src/index.ts +27 -0
  34. package/src/next/api/edge/index.ts +23 -0
  35. package/src/next/api/node/index.ts +27 -0
  36. package/src/next/index.ts +2 -0
  37. package/src/query-parser.ts +94 -0
  38. package/src/swagger/adapter/prisma/index.ts +95 -0
  39. package/src/swagger/json-schema-parser.ts +456 -0
  40. package/src/swagger/parameters.ts +83 -0
  41. package/src/swagger/types.d.ts +53 -0
  42. package/src/swagger/utils/format-example-ref.ts +4 -0
  43. package/src/swagger/utils/format-schema-ref.ts +4 -0
  44. package/src/swagger/utils/get-models-accessible-routes.ts +23 -0
  45. package/src/swagger/utils/get-swagger-paths.ts +244 -0
  46. package/src/swagger/utils/get-swagger-tags.ts +13 -0
  47. package/src/types.d.ts +124 -0
  48. package/src/utils/format-resource-id.ts +3 -0
  49. package/src/utils/get-accessible-routes.ts +18 -0
  50. package/src/utils/get-resource-name-from-url.ts +23 -0
  51. package/src/utils/get-route-type.ts +99 -0
  52. package/src/utils/is-primitive.ts +5 -0
  53. package/src/utils/validate-adapter-methods.ts +15 -0
@@ -0,0 +1,83 @@
1
+ import { RouteType } from "../types.d";
2
+ import type { SwaggerParameter } from "./types.d";
3
+
4
+ const queryParameters: Record<string, SwaggerParameter> = {
5
+ select: {
6
+ name: "select",
7
+ description: "Fields to select. For nested fields, chain them separated with a dot, eg: user.posts",
8
+ schema: {
9
+ type: "string",
10
+ },
11
+ },
12
+ include: {
13
+ name: "include",
14
+ description: "Include relations, same as select",
15
+ schema: {
16
+ type: "string",
17
+ },
18
+ },
19
+ where: {
20
+ name: "where",
21
+ description: 'Fields to filter. See <a href="https://next-crud.js.org/query-params#where">the docs</a>',
22
+ schema: {
23
+ type: "string",
24
+ },
25
+ },
26
+ orderBy: {
27
+ name: "orderBy",
28
+ description: 'Field on which to order by a direction. See <a href="https://next-crud.js.org/query-params#orderBy">the docs</a>',
29
+ schema: {
30
+ type: "string",
31
+ },
32
+ },
33
+ limit: {
34
+ name: "limit",
35
+ description: "Maximum number of elements to retrieve",
36
+ schema: {
37
+ type: "integer",
38
+ minimum: 0,
39
+ },
40
+ },
41
+ skip: {
42
+ name: "skip",
43
+ description: "Number of rows to skip",
44
+ schema: {
45
+ type: "integer",
46
+ minimum: 0,
47
+ },
48
+ },
49
+ distinct: {
50
+ name: "distinct",
51
+ description: "Fields to distinctively retrieve",
52
+ schema: {
53
+ type: "string",
54
+ },
55
+ },
56
+ page: {
57
+ name: "page",
58
+ description: "Page number. Use only for pagination.",
59
+ schema: {
60
+ type: "integer",
61
+ minimum: 1,
62
+ },
63
+ },
64
+ };
65
+
66
+ export const commonQueryParameters = [queryParameters.select, queryParameters.include];
67
+ export const listQueryParameters = [
68
+ ...commonQueryParameters,
69
+ queryParameters.limit,
70
+ queryParameters.skip,
71
+ queryParameters.where,
72
+ queryParameters.orderBy,
73
+ queryParameters.page,
74
+ queryParameters.distinct,
75
+ ];
76
+
77
+ export const getQueryParameters = (routeType: RouteType, additionalQueryParameters: SwaggerParameter[] = []) => {
78
+ if (routeType === RouteType.READ_ALL) {
79
+ return [...listQueryParameters, ...additionalQueryParameters];
80
+ }
81
+
82
+ return [...commonQueryParameters, ...additionalQueryParameters];
83
+ };
@@ -0,0 +1,53 @@
1
+ import { RouteType } from "../types.d";
2
+
3
+ export type SwaggerType = {
4
+ name: string;
5
+ isArray?: boolean;
6
+ description?: string;
7
+ required?: boolean;
8
+ };
9
+
10
+ export type SwaggerOperation = {
11
+ summary?: string;
12
+ responses?: Record<number, any>;
13
+ body?: SwaggerType;
14
+ response: SwaggerType;
15
+ };
16
+
17
+ export type SwaggerTag = {
18
+ name?: string;
19
+ description?: string;
20
+ externalDocs?: {
21
+ description: string;
22
+ url: string;
23
+ };
24
+ };
25
+
26
+ export type SwaggerParameter = {
27
+ name: string;
28
+ description?: string;
29
+ schema: {
30
+ type: string;
31
+ } & any;
32
+ };
33
+
34
+ export type ModelsConfig = {
35
+ tag: SwaggerTag;
36
+ type?: SwaggerType;
37
+ routeTypes?: {
38
+ [RouteType.READ_ALL]?: SwaggerOperation;
39
+ [RouteType.READ_ONE]?: SwaggerOperation;
40
+ [RouteType.CREATE]?: SwaggerOperation;
41
+ [RouteType.UPDATE]?: SwaggerOperation;
42
+ [RouteType.DELETE]?: SwaggerOperation;
43
+ };
44
+ additionalQueryParams?: SwaggerParameter[];
45
+ };
46
+
47
+ export type SwaggerModelsConfig<M extends string> = {
48
+ [key in M]?: ModelsConfig;
49
+ };
50
+
51
+ export type Routes<M extends string> = {
52
+ [key in M]?: RouteType[];
53
+ };
@@ -0,0 +1,4 @@
1
+ // eslint-disable-next-line unicorn/prevent-abbreviations
2
+ const formatExampleReference = (schemaName: string) => `#/components/examples/${schemaName}`;
3
+
4
+ export default formatExampleReference;
@@ -0,0 +1,4 @@
1
+ // eslint-disable-next-line unicorn/prevent-abbreviations
2
+ const formatSchemaReference = (schemaName: string) => `#/components/schemas/${schemaName}`;
3
+
4
+ export default formatSchemaReference;
@@ -0,0 +1,23 @@
1
+ import type { ModelOption, ModelsOptions } from "../../types.d";
2
+ import getAccessibleRoutes from "../../utils/get-accessible-routes";
3
+ import type { Routes } from "../types.d";
4
+
5
+ const getModelsAccessibleRoutes = <M extends string>(
6
+ modelNames: M[],
7
+ models?: ModelsOptions<M>,
8
+ defaultExposeStrategy: "all" | "none" = "all",
9
+ ): Routes<M> => modelNames.reduce((accumulator, modelName) => {
10
+ if (models?.[modelName]) {
11
+ return {
12
+ ...accumulator,
13
+ [modelName]: getAccessibleRoutes((models[modelName] as ModelOption).only, (models[modelName] as ModelOption).exclude, defaultExposeStrategy),
14
+ };
15
+ }
16
+
17
+ return {
18
+ ...accumulator,
19
+ [modelName]: getAccessibleRoutes(undefined, undefined, defaultExposeStrategy),
20
+ };
21
+ }, {});
22
+
23
+ export default getModelsAccessibleRoutes;
@@ -0,0 +1,244 @@
1
+ import type { ModelOption, ModelsOptions } from "../../types.d";
2
+ import { RouteType } from "../../types.d";
3
+ import { getQueryParameters } from "../parameters";
4
+ import type { Routes, SwaggerModelsConfig } from "../types.d";
5
+ import formatExampleReference from "./format-example-ref";
6
+ import formatSchemaReference from "./format-schema-ref";
7
+
8
+ interface GenerateSwaggerPathObjectParameters<M extends string> {
9
+ tag: string;
10
+ routeTypes: RouteType[];
11
+ modelsConfig?: SwaggerModelsConfig<M>;
12
+ modelName: M;
13
+ hasId?: boolean;
14
+ }
15
+
16
+ type HttpMethod = "get" | "post" | "put" | "delete";
17
+
18
+ const generateContentForSchema = (schemaName: string, isArray?: boolean) => {
19
+ if (isArray) {
20
+ return {
21
+ type: "array",
22
+ items: {
23
+ $ref: formatSchemaReference(schemaName),
24
+ },
25
+ };
26
+ }
27
+
28
+ return {
29
+ $ref: formatSchemaReference(schemaName),
30
+ };
31
+ };
32
+
33
+ const generateSwaggerResponse = (routeType: RouteType, modelName: string): { statusCode: number; content: any } | undefined => {
34
+ if (routeType === RouteType.CREATE) {
35
+ return {
36
+ statusCode: 201,
37
+ content: {
38
+ description: `${modelName} created`,
39
+ content: {
40
+ "application/json": {
41
+ schema: generateContentForSchema(modelName),
42
+ },
43
+ },
44
+ },
45
+ };
46
+ }
47
+
48
+ if (routeType === RouteType.DELETE) {
49
+ return {
50
+ statusCode: 200,
51
+ content: {
52
+ description: `${modelName} item deleted`,
53
+ content: {
54
+ "application/json": {
55
+ schema: generateContentForSchema(modelName),
56
+ },
57
+ },
58
+ },
59
+ };
60
+ }
61
+
62
+ if (routeType === RouteType.READ_ALL) {
63
+ return {
64
+ statusCode: 200,
65
+ content: {
66
+ description: `${modelName} list retrieved`,
67
+ content: {
68
+ "application/json": {
69
+ schema: {
70
+ oneOf: [generateContentForSchema(modelName, true), generateContentForSchema(`${modelName}Page`, false)],
71
+ },
72
+ examples: {
73
+ Default: {
74
+ $ref: formatExampleReference(`${modelName}`),
75
+ },
76
+ Pagination: {
77
+ $ref: formatExampleReference(`${modelName}Page`),
78
+ },
79
+ },
80
+ },
81
+ },
82
+ },
83
+ };
84
+ }
85
+
86
+ if (routeType === RouteType.READ_ONE) {
87
+ return {
88
+ statusCode: 200,
89
+ content: {
90
+ description: `${modelName} item retrieved`,
91
+ content: {
92
+ "application/json": {
93
+ schema: generateContentForSchema(modelName),
94
+ },
95
+ },
96
+ },
97
+ };
98
+ }
99
+
100
+ if (routeType === RouteType.UPDATE) {
101
+ return {
102
+ statusCode: 200,
103
+ content: {
104
+ description: `${modelName} item updated`,
105
+ content: {
106
+ "application/json": {
107
+ schema: generateContentForSchema(modelName),
108
+ },
109
+ },
110
+ },
111
+ };
112
+ }
113
+
114
+ return undefined;
115
+ };
116
+
117
+ const generateRequestBody = (schemaStartName: string, modelName: string) => {
118
+ return {
119
+ content: {
120
+ "application/json": {
121
+ schema: {
122
+ $ref: formatSchemaReference(`${schemaStartName}${modelName}`),
123
+ },
124
+ },
125
+ },
126
+ };
127
+ };
128
+
129
+ const getRouteTypeMethod = (routeType: RouteType): HttpMethod => {
130
+ switch (routeType) {
131
+ case RouteType.CREATE: {
132
+ return "post";
133
+ }
134
+ case RouteType.READ_ALL:
135
+ case RouteType.READ_ONE: {
136
+ return "get";
137
+ }
138
+ case RouteType.UPDATE: {
139
+ return "put";
140
+ }
141
+ case RouteType.DELETE: {
142
+ return "delete";
143
+ }
144
+ default: {
145
+ throw new TypeError(`Method for route type ${routeType} was not found.`);
146
+ }
147
+ }
148
+ };
149
+
150
+ const generateSwaggerPathObject = <M extends string>({
151
+ tag, routeTypes, modelName, modelsConfig, hasId,
152
+ }: GenerateSwaggerPathObjectParameters<M>) => {
153
+ const methods: { [key: string]: any } = {};
154
+
155
+ routeTypes.forEach((routeType) => {
156
+ if (routeTypes.includes(routeType)) {
157
+ const returnType = modelsConfig?.[modelName]?.routeTypes?.[routeType]?.response?.name ?? modelsConfig?.[modelName]?.type?.name ?? modelName;
158
+ const method: HttpMethod = getRouteTypeMethod(routeType);
159
+ const response = generateSwaggerResponse(routeType, returnType);
160
+
161
+ if (typeof response === "undefined") {
162
+ throw new TypeError(`Route type ${routeType}; response config was not found.`);
163
+ }
164
+
165
+ methods[method as HttpMethod] = {
166
+ tags: [tag],
167
+ summary: modelsConfig?.[modelName]?.routeTypes?.[routeType]?.summary,
168
+ parameters: getQueryParameters(routeType).map((queryParameter) => {
169
+ return { ...queryParameter, in: "query" };
170
+ }),
171
+ responses: {
172
+ [response.statusCode]: response.content,
173
+ ...modelsConfig?.[modelName]?.routeTypes?.[routeType]?.responses,
174
+ },
175
+ };
176
+
177
+ if (hasId) {
178
+ methods[method as HttpMethod].parameters.push({
179
+ in: "path",
180
+ name: "id",
181
+ description: `ID of the ${modelName}`,
182
+ required: true,
183
+ schema: {
184
+ type: "string",
185
+ },
186
+ });
187
+ }
188
+
189
+ if (routeType === RouteType.UPDATE || routeType === RouteType.CREATE) {
190
+ if (routeType === RouteType.UPDATE) {
191
+ methods[method as HttpMethod].requestBody = generateRequestBody("Update", returnType);
192
+ } else if (routeType === RouteType.CREATE) {
193
+ methods[method as HttpMethod].requestBody = generateRequestBody("Create", returnType);
194
+ }
195
+ }
196
+ }
197
+ });
198
+
199
+ return methods;
200
+ };
201
+
202
+ interface GetSwaggerPathsParameters<M extends string> {
203
+ routes: Routes<M>;
204
+ modelsConfig?: SwaggerModelsConfig<M>;
205
+ models?: ModelsOptions<M>;
206
+ routesMap?: { [key in M]?: string };
207
+ }
208
+
209
+ const getSwaggerPaths = <M extends string>({
210
+ routes, models, modelsConfig, routesMap,
211
+ }: GetSwaggerPathsParameters<M>) => Object.keys(routes).reduce((accumulator: { [key: string]: any }, value: string | M) => {
212
+ const routeTypes = routes[value] as RouteType[];
213
+ const resourceName = models?.[value]?.name ? (models[value] as ModelOption).name : routesMap?.[value as M] || value;
214
+ const tag = modelsConfig?.[value]?.tag?.name || value;
215
+
216
+ if (routeTypes.includes(RouteType.CREATE) || routeTypes.includes(RouteType.READ_ALL)) {
217
+ const path = `/${resourceName}`;
218
+ const routeTypesToUse = [RouteType.READ_ALL, RouteType.CREATE].filter((routeType) => routeTypes.includes(routeType));
219
+
220
+ accumulator[path] = generateSwaggerPathObject({
221
+ tag,
222
+ modelName: value as M,
223
+ modelsConfig,
224
+ routeTypes: routeTypesToUse,
225
+ });
226
+ }
227
+
228
+ if (routeTypes.includes(RouteType.READ_ONE) || routeTypes.includes(RouteType.UPDATE) || routeTypes.includes(RouteType.DELETE)) {
229
+ const path = `/${resourceName}/{id}`;
230
+ const routeTypesToUse = [RouteType.READ_ONE, RouteType.UPDATE, RouteType.DELETE].filter((routeType) => routeTypes.includes(routeType));
231
+
232
+ accumulator[path] = generateSwaggerPathObject({
233
+ tag,
234
+ modelName: value as M,
235
+ modelsConfig,
236
+ routeTypes: routeTypesToUse,
237
+ hasId: true,
238
+ });
239
+ }
240
+
241
+ return accumulator;
242
+ }, {});
243
+
244
+ export default getSwaggerPaths;
@@ -0,0 +1,13 @@
1
+ import type { ModelsConfig, SwaggerModelsConfig, SwaggerTag } from "../types.d";
2
+
3
+ const getSwaggerTags = <M extends string>(modelNames: M[], modelsConfig?: SwaggerModelsConfig<M>): SwaggerTag[] => modelNames.map((modelName) => {
4
+ if (modelsConfig?.[modelName]?.tag) {
5
+ return (modelsConfig[modelName as M] as ModelsConfig).tag;
6
+ }
7
+
8
+ return {
9
+ name: modelName,
10
+ };
11
+ });
12
+
13
+ export default getSwaggerTags;
package/src/types.d.ts ADDED
@@ -0,0 +1,124 @@
1
+ import type { Handler as CreateHandler } from "./handler/create";
2
+ import type { Handler as DeleteHandler } from "./handler/delete";
3
+ import type { Handler as ListHandler } from "./handler/list";
4
+ import type { Handler as GetHandler } from "./handler/read";
5
+ import type { Handler as UpdateHandler } from "./handler/update";
6
+
7
+ export enum RouteType {
8
+ CREATE = "CREATE",
9
+ READ_ALL = "READ_ALL",
10
+ READ_ONE = "READ_ONE",
11
+ UPDATE = "UPDATE",
12
+ DELETE = "DELETE",
13
+ }
14
+
15
+ export type ModelOption = {
16
+ name?: string
17
+ only?: RouteType[]
18
+ exclude?: RouteType[]
19
+ formatResourceId?: (resourceId: string) => string | number
20
+ };
21
+
22
+ export type ModelsOptions<M extends string = string> = {
23
+ [key in M]?: ModelOption
24
+ };
25
+
26
+ export type HandlerOptions<M extends string = string> = {
27
+ formatResourceId?: (resourceId: string) => string | number;
28
+ models?: ModelsOptions<M>;
29
+ exposeStrategy?: "all" | "none";
30
+ pagination?: PaginationConfig,
31
+ handlers?: {
32
+ create?: CreateHandler;
33
+ delete?: DeleteHandler;
34
+ get?: GetHandler;
35
+ list?: ListHandler;
36
+ update?: UpdateHandler;
37
+ },
38
+ };
39
+
40
+ export type PaginationConfig = {
41
+ perPage: number
42
+ };
43
+
44
+ export interface HandlerParameters<T, Q> {
45
+ adapter: Adapter<T, Q>;
46
+ query: Q;
47
+ resourceName: string;
48
+ }
49
+
50
+ export interface UniqueResourceHandlerParameters<T, Q> {
51
+ adapter: Adapter<T, Q>;
52
+ query: Q;
53
+ resourceName: string;
54
+ resourceId: string | number;
55
+ }
56
+
57
+ export interface Adapter<T, Q, M extends string = string> {
58
+ models?: M[];
59
+ init?: () => Promise<void>;
60
+ parseQuery(resourceName: M, query: ParsedQueryParameters): Q;
61
+ getAll(resourceName: M, query: Q): Promise<T[]>;
62
+ getOne(resourceName: M, resourceId: string | number, query: Q): Promise<T>;
63
+ create(resourceName: M, data: any, query: Q): Promise<T>;
64
+ update(resourceName: M, resourceId: string | number, data: any, query: Q): Promise<T>;
65
+ delete(resourceName: M, resourceId: string | number, query: Q): Promise<T>;
66
+ getPaginationData(resourceName: M, query: Q): Promise<PaginationData>;
67
+ getModels(): M[];
68
+ connect?: () => Promise<void>;
69
+ disconnect?: () => Promise<void>;
70
+ handleError?: (error: Error) => void;
71
+ mapModelsToRouteNames?: () => Promise<{ [key in M]?: string }>;
72
+ }
73
+
74
+ export type PaginationData = {
75
+ total: number
76
+ pageCount: number
77
+ page: number
78
+ };
79
+
80
+ export type RecursiveField = {
81
+ [key: string]: boolean | TRecursiveField;
82
+ };
83
+
84
+ export type WhereOperator = "$eq" | "$neq" | "$in" | "$notin" | "$lt" | "$lte" | "$gt" | "$gte" | "$cont" | "$starts" | "$ends" | "$isnull";
85
+
86
+ export type SearchCondition = string | boolean | number | Date | null;
87
+
88
+ export type WhereCondition = {
89
+ [key in TWhereOperator]?: TSearchCondition;
90
+ };
91
+
92
+ export type Condition = {
93
+ [key: string]: TSearchCondition | TWhereCondition | TCondition;
94
+ };
95
+
96
+ export type WhereField = Condition & {
97
+ $and?: TCondition | TCondition[];
98
+ $or?: TCondition | TCondition[];
99
+ $not?: TCondition | TCondition[];
100
+ };
101
+
102
+ export type OrderByOperator = "$asc" | "$desc";
103
+
104
+ export type OrderByField = {
105
+ [key: string]: TOrderByOperator;
106
+ };
107
+
108
+ export interface ParsedQueryParameters {
109
+ select?: RecursiveField;
110
+ include?: RecursiveField;
111
+ where?: WhereField;
112
+ orderBy?: OrderByField;
113
+ limit?: number;
114
+ skip?: number;
115
+ distinct?: string;
116
+ page?: number;
117
+ originalQuery?: {
118
+ [key: string]: any;
119
+ };
120
+ }
121
+
122
+ export interface ExecuteHandler<Request, Response> {
123
+ (request: Request, response: Response): Promise<void>;
124
+ }
@@ -0,0 +1,3 @@
1
+ const formatResourceId = (resourceId: string): string | number => (Number.isSafeInteger(+resourceId) ? +resourceId : resourceId);
2
+
3
+ export default formatResourceId;
@@ -0,0 +1,18 @@
1
+ import { RouteType } from "../types.d";
2
+
3
+ const getAccessibleRoutes = (only?: RouteType[], exclude?: RouteType[], defaultExposeStrategy: "all" | "none" = "all"): RouteType[] => {
4
+ // eslint-disable-next-line max-len
5
+ let accessibleRoutes: RouteType[] = defaultExposeStrategy === "none" ? [] : [RouteType.READ_ALL, RouteType.READ_ONE, RouteType.UPDATE, RouteType.DELETE, RouteType.CREATE];
6
+
7
+ if (Array.isArray(only)) {
8
+ accessibleRoutes = only;
9
+ }
10
+
11
+ if (exclude?.length) {
12
+ accessibleRoutes = accessibleRoutes.filter((element) => !exclude.includes(element));
13
+ }
14
+
15
+ return accessibleRoutes;
16
+ };
17
+
18
+ export default getAccessibleRoutes;
@@ -0,0 +1,23 @@
1
+ export const ensureCamelCase = (string_: string) => `${string_.charAt(0).toLowerCase()}${string_.slice(1)}`;
2
+
3
+ export const getResourceNameFromUrl = <M extends string = string>(url: string, models: { [key in M]?: string }) => {
4
+ // Exclude the query params from the path
5
+ const realPath = url.split("?")[0];
6
+
7
+ if (typeof realPath === "undefined") {
8
+ throw new TypeError("Path is undefined");
9
+ }
10
+
11
+ const modelName = (Object.keys(models) as M[]).find((name) => {
12
+ const routeName = models[name] as string;
13
+ const camelCaseModel = ensureCamelCase(routeName);
14
+
15
+ // eslint-disable-next-line @rushstack/security/no-unsafe-regexp
16
+ return new RegExp(`(${routeName}|${camelCaseModel}$)|(${routeName}|${camelCaseModel}/)`, "g").test(realPath);
17
+ });
18
+
19
+ return {
20
+ modelName,
21
+ resourceName: models[modelName] as string,
22
+ };
23
+ };