axe-api 1.0.0-rc12 → 1.0.0-rc15

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/build/index.d.ts CHANGED
@@ -6,4 +6,5 @@ import { IoCService, allow, deny, App, AxeRequest, AxeResponse } from "./src/Ser
6
6
  import { rateLimit } from "./src/Middlewares/RateLimit";
7
7
  export * from "./src/Enums";
8
8
  export * from "./src/Interfaces";
9
+ export * from "./src/Types";
9
10
  export { App, AxeRequest, AxeResponse, Server, Model, ApiError, DEFAULT_HANDLERS, DEFAULT_VERSION_CONFIG, IoCService, allow, deny, rateLimit, };
package/build/index.js CHANGED
@@ -38,3 +38,4 @@ const RateLimit_1 = require("./src/Middlewares/RateLimit");
38
38
  Object.defineProperty(exports, "rateLimit", { enumerable: true, get: function () { return RateLimit_1.rateLimit; } });
39
39
  __exportStar(require("./src/Enums"), exports);
40
40
  __exportStar(require("./src/Interfaces"), exports);
41
+ __exportStar(require("./src/Types"), exports);
@@ -91,7 +91,8 @@ class RouterBuilder {
91
91
  const relation = model.relations.find((relation) => relation.model === model.name &&
92
92
  relation.type === Enums_1.Relationships.HAS_MANY);
93
93
  if (relation) {
94
- yield this.createRouteByModel(model, `${urlPrefix}${resource}/:${(0, change_case_1.camelCase)(relation.foreignKey)}/`, model, relation, false);
94
+ const paramName = (0, change_case_1.camelCase)(`${model.name}-${relation.primaryKey}`);
95
+ yield this.createRouteByModel(model, `${urlPrefix}${resource}/:${paramName}/`, model, relation, false);
95
96
  }
96
97
  });
97
98
  }
@@ -106,7 +107,8 @@ class RouterBuilder {
106
107
  const child = model.children.find((item) => item.name === relation.model);
107
108
  // It should be recursive
108
109
  if (child) {
109
- yield this.createRouteByModel(child, `${urlPrefix}${resource}/:${(0, change_case_1.camelCase)(relation.foreignKey)}/`, model, relation);
110
+ const paramName = (0, change_case_1.camelCase)(`${model.name}-${relation.primaryKey}`);
111
+ yield this.createRouteByModel(child, `${urlPrefix}${resource}/:${paramName}/`, model, relation);
110
112
  }
111
113
  }
112
114
  });
@@ -124,7 +126,7 @@ class RouterBuilder {
124
126
  // Adding the route
125
127
  yield URLService_1.default.add(constants_1.HANDLER_METHOD_MAP[handlerType], url, data, middlewares);
126
128
  // Documentation
127
- docs.push(this.version, handlerType, constants_1.HANDLER_METHOD_MAP[handlerType], url, model);
129
+ docs.push(this.version, handlerType, constants_1.HANDLER_METHOD_MAP[handlerType], url, model, parentModel);
128
130
  });
129
131
  }
130
132
  getResourcePath(model, relation) {
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<any>;
2
+ export default _default;
@@ -0,0 +1,553 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ /* eslint-disable no-prototype-builtins */
39
+ const path_1 = __importDefault(require("path"));
40
+ const fs_1 = __importDefault(require("fs"));
41
+ const change_case_1 = require("change-case");
42
+ const Enums_1 = require("../Enums");
43
+ const node_cache_1 = __importDefault(require("node-cache"));
44
+ const Services_1 = require("../Services");
45
+ const cache = new node_cache_1.default();
46
+ const DATA_TYPE_MAP = [
47
+ {
48
+ type: "string",
49
+ values: [
50
+ "char",
51
+ "varchar",
52
+ "text",
53
+ "tinytext",
54
+ "mediumtext",
55
+ "longtext",
56
+ "enum",
57
+ "set",
58
+ "varbinary",
59
+ "blob",
60
+ "tinyblob",
61
+ "mediumblob",
62
+ "longblob",
63
+ "date",
64
+ "datetime",
65
+ "timestamp",
66
+ "time",
67
+ "year",
68
+ ],
69
+ },
70
+ {
71
+ type: "number",
72
+ values: [
73
+ "tinyint",
74
+ "smallint",
75
+ "mediumint",
76
+ "int",
77
+ "integer",
78
+ "bigint",
79
+ "float",
80
+ "double",
81
+ "decimal",
82
+ "numeric",
83
+ ],
84
+ },
85
+ {
86
+ type: "boolean",
87
+ values: ["boolean", "binary"],
88
+ },
89
+ ];
90
+ const SUMMARY_TEXTS = {
91
+ [Enums_1.HandlerTypes.INSERT]: "Add a new {model}",
92
+ [Enums_1.HandlerTypes.PAGINATE]: "Retrieve a paginated list of {model}s",
93
+ [Enums_1.HandlerTypes.SHOW]: "Retrieve details of a {model}",
94
+ [Enums_1.HandlerTypes.UPDATE]: "Update an existing {model}",
95
+ [Enums_1.HandlerTypes.DELETE]: "Delete a {model}",
96
+ [Enums_1.HandlerTypes.FORCE_DELETE]: "Force delete a {model}",
97
+ [Enums_1.HandlerTypes.PATCH]: "Apply partial updates to a {model}",
98
+ [Enums_1.HandlerTypes.ALL]: "Get all items on {model}",
99
+ };
100
+ const DESCRIPTION_TEXTS = {
101
+ [Enums_1.HandlerTypes.INSERT]: "Add a new {model}",
102
+ [Enums_1.HandlerTypes.PAGINATE]: "Retrieve a paginated list of {model}s",
103
+ [Enums_1.HandlerTypes.SHOW]: "Retrieve details of a {model} by primary key",
104
+ [Enums_1.HandlerTypes.UPDATE]: "Update an existing {model} by primary key",
105
+ [Enums_1.HandlerTypes.DELETE]: "Delete a {model} by primary key",
106
+ [Enums_1.HandlerTypes.FORCE_DELETE]: "Force delete a {model} by primary key",
107
+ [Enums_1.HandlerTypes.PATCH]: "Apply partial updates to a {model} by primary key",
108
+ [Enums_1.HandlerTypes.ALL]: "Get all items on {model}",
109
+ };
110
+ const SINGLE_RETURN_ENDPOINTS = [
111
+ Enums_1.HandlerTypes.INSERT,
112
+ Enums_1.HandlerTypes.PATCH,
113
+ Enums_1.HandlerTypes.SHOW,
114
+ Enums_1.HandlerTypes.UPDATE,
115
+ ];
116
+ const ALLOWED_2XX_HANDLERS = [
117
+ Enums_1.HandlerTypes.ALL,
118
+ Enums_1.HandlerTypes.PAGINATE,
119
+ Enums_1.HandlerTypes.PATCH,
120
+ Enums_1.HandlerTypes.SHOW,
121
+ Enums_1.HandlerTypes.UPDATE,
122
+ ];
123
+ const POSSIBLE_404_HANDLERS = [
124
+ Enums_1.HandlerTypes.DELETE,
125
+ Enums_1.HandlerTypes.FORCE_DELETE,
126
+ Enums_1.HandlerTypes.PATCH,
127
+ Enums_1.HandlerTypes.SHOW,
128
+ Enums_1.HandlerTypes.UPDATE,
129
+ ];
130
+ const NO_CONTENT_HANDLERS = [
131
+ Enums_1.HandlerTypes.DELETE,
132
+ Enums_1.HandlerTypes.FORCE_DELETE,
133
+ ];
134
+ const ALLOWED_REQUEST_BODY_HANDLERS = [
135
+ Enums_1.HandlerTypes.INSERT,
136
+ Enums_1.HandlerTypes.UPDATE,
137
+ Enums_1.HandlerTypes.PATCH,
138
+ ];
139
+ const ALLOWED_QUERY_HANDLERS = [
140
+ Enums_1.HandlerTypes.ALL,
141
+ Enums_1.HandlerTypes.SHOW,
142
+ Enums_1.HandlerTypes.PAGINATE,
143
+ ];
144
+ const PAGINATION_SCHEMA = {
145
+ type: "object",
146
+ properties: {
147
+ total: {
148
+ type: "integer",
149
+ default: 1,
150
+ },
151
+ lastPage: {
152
+ type: "integer",
153
+ default: 1,
154
+ },
155
+ perPage: {
156
+ type: "integer",
157
+ default: 10,
158
+ },
159
+ currentPage: {
160
+ type: "integer",
161
+ default: 1,
162
+ },
163
+ from: {
164
+ type: "integer",
165
+ default: 0,
166
+ },
167
+ to: {
168
+ type: "integer",
169
+ default: 10,
170
+ },
171
+ },
172
+ };
173
+ const ERROR_SCHEMA = {
174
+ type: "object",
175
+ properties: {
176
+ error: {
177
+ type: "string",
178
+ default: "An error occorred",
179
+ },
180
+ },
181
+ };
182
+ const VALIDATION_ERROR_SCHEMA = {
183
+ type: "object",
184
+ properties: {
185
+ errors: {
186
+ type: "object",
187
+ },
188
+ },
189
+ };
190
+ const toPropertyType = (datatype) => {
191
+ for (const item of DATA_TYPE_MAP) {
192
+ if (item.values.includes(datatype)) {
193
+ return item.type;
194
+ }
195
+ }
196
+ return datatype;
197
+ };
198
+ const toEndpointSummary = (endpoint) => {
199
+ const value = SUMMARY_TEXTS[endpoint.handler] || "";
200
+ return value.replace("{model}", endpoint.model.toLowerCase());
201
+ };
202
+ const toEndpointDescription = (endpoint) => {
203
+ const value = DESCRIPTION_TEXTS[endpoint.handler] || "";
204
+ return value.replace("{model}", endpoint.model.toLowerCase());
205
+ };
206
+ const to2XXResponse = (endpoint) => {
207
+ // Single item response
208
+ if (SINGLE_RETURN_ENDPOINTS.includes(endpoint.handler)) {
209
+ return {
210
+ content: {
211
+ "application/json": {
212
+ schema: {
213
+ $ref: `#/components/schemas/${endpoint.model}`,
214
+ },
215
+ },
216
+ },
217
+ };
218
+ }
219
+ // All response
220
+ if (endpoint.handler === Enums_1.HandlerTypes.ALL) {
221
+ return {
222
+ content: {
223
+ "application/json": {
224
+ schema: {
225
+ type: "array",
226
+ items: {
227
+ $ref: `#/components/schemas/${endpoint.model}`,
228
+ },
229
+ },
230
+ },
231
+ },
232
+ };
233
+ }
234
+ return {
235
+ content: {
236
+ "application/json": {
237
+ schema: {
238
+ type: "object",
239
+ properties: {
240
+ data: {
241
+ type: "array",
242
+ items: {
243
+ $ref: `#/components/schemas/${endpoint.model}`,
244
+ },
245
+ },
246
+ pagination: {
247
+ $ref: `#/components/schemas/Pagination`,
248
+ },
249
+ },
250
+ },
251
+ },
252
+ },
253
+ };
254
+ };
255
+ const toEndpointResponse = (endpoint) => {
256
+ const response = {};
257
+ if (ALLOWED_2XX_HANDLERS.includes(endpoint.handler)) {
258
+ response["200"] = Object.assign(Object.assign({}, to2XXResponse(endpoint)), { description: "OK" });
259
+ }
260
+ if (endpoint.handler === Enums_1.HandlerTypes.INSERT) {
261
+ response["201"] = Object.assign(Object.assign({}, to2XXResponse(endpoint)), { description: "Created" });
262
+ response["400"] = {
263
+ description: "Bad request",
264
+ content: {
265
+ "application/json": {
266
+ schema: {
267
+ $ref: `#/components/schemas/ValidationError`,
268
+ },
269
+ },
270
+ },
271
+ };
272
+ }
273
+ if (NO_CONTENT_HANDLERS.includes(endpoint.handler)) {
274
+ response["204"] = {
275
+ description: "No Content",
276
+ };
277
+ }
278
+ if (POSSIBLE_404_HANDLERS.includes(endpoint.handler) ||
279
+ endpoint.parentModel) {
280
+ response["404"] = {
281
+ description: "Not Found",
282
+ content: {
283
+ "application/json": {
284
+ schema: {
285
+ $ref: `#/components/schemas/Error`,
286
+ },
287
+ },
288
+ },
289
+ };
290
+ }
291
+ return response;
292
+ };
293
+ const toFillableFieldProperties = (model) => {
294
+ const fields = model.instance.getFillableFields(Enums_1.HttpMethods.POST);
295
+ const properties = {};
296
+ for (const field of fields) {
297
+ const column = model.columns.find((item) => item.name === field);
298
+ if (column) {
299
+ properties[column.name] = {
300
+ type: toPropertyType(column.data_type),
301
+ format: column.data_type,
302
+ };
303
+ }
304
+ }
305
+ return properties;
306
+ };
307
+ const toRequestBody = (endpoint) => {
308
+ if (ALLOWED_REQUEST_BODY_HANDLERS.includes(endpoint.handler) === false) {
309
+ return undefined;
310
+ }
311
+ const bodySchema = `${endpoint.model}${(0, change_case_1.pascalCase)(endpoint.method)}Body`;
312
+ return {
313
+ description: "Update an existent pet in the store",
314
+ content: {
315
+ "application/json": {
316
+ schema: {
317
+ $ref: `#/components/requestBodies/${bodySchema}`,
318
+ },
319
+ },
320
+ },
321
+ required: true,
322
+ };
323
+ };
324
+ const toRequestParameters = (endpoint) => {
325
+ const parameters = [
326
+ {
327
+ in: "header",
328
+ name: "Accept-Language",
329
+ schema: {
330
+ type: "string",
331
+ example: "en;q=0.8, de;q=0.7, *;q=0.5",
332
+ },
333
+ required: false,
334
+ },
335
+ ];
336
+ if (endpoint.url.includes(":")) {
337
+ const sections = endpoint.url
338
+ .split("/")
339
+ .filter((item) => item.includes(":"))
340
+ .map((item) => item.replace(":", ""));
341
+ for (const section of sections) {
342
+ parameters.push({
343
+ in: "path",
344
+ name: section,
345
+ required: true,
346
+ });
347
+ }
348
+ }
349
+ if (endpoint.handler === Enums_1.HandlerTypes.PAGINATE) {
350
+ parameters.push(...[
351
+ {
352
+ name: "page",
353
+ in: "query",
354
+ description: "The page number to list",
355
+ required: false,
356
+ schema: {
357
+ type: "integer",
358
+ default: 1,
359
+ },
360
+ },
361
+ {
362
+ name: "per_page",
363
+ in: "query",
364
+ description: "Number of records to list on a page",
365
+ required: false,
366
+ schema: {
367
+ type: "integer",
368
+ default: 10,
369
+ },
370
+ },
371
+ {
372
+ name: "sort",
373
+ in: "query",
374
+ description: "The field to sort the data (ASC: id, DESC: -id)",
375
+ required: false,
376
+ schema: {
377
+ type: "string",
378
+ },
379
+ },
380
+ ]);
381
+ }
382
+ if (ALLOWED_QUERY_HANDLERS.includes(endpoint.handler)) {
383
+ parameters.push(...[
384
+ {
385
+ name: "fields",
386
+ in: "query",
387
+ description: "The model fields that can be fetched",
388
+ required: false,
389
+ schema: {
390
+ type: "string",
391
+ },
392
+ },
393
+ {
394
+ name: "with",
395
+ in: "query",
396
+ description: "Listable related models",
397
+ required: false,
398
+ schema: {
399
+ type: "string",
400
+ },
401
+ },
402
+ {
403
+ name: "trashed",
404
+ in: "query",
405
+ description: "List of deleted data with soft-delete",
406
+ required: false,
407
+ schema: {
408
+ type: "integer",
409
+ default: 0,
410
+ },
411
+ },
412
+ {
413
+ name: "q",
414
+ in: "query",
415
+ description: "JSON query to filter data",
416
+ required: false,
417
+ schema: {
418
+ type: "string",
419
+ },
420
+ },
421
+ ]);
422
+ }
423
+ return parameters;
424
+ };
425
+ const deepMerge = (base, source) => {
426
+ // Check if either base or source is not an object, or if source is null
427
+ if (typeof base !== "object" ||
428
+ typeof source !== "object" ||
429
+ source === null) {
430
+ return source;
431
+ }
432
+ // Create a copy of the base object to avoid modifying it directly
433
+ const merged = Object.assign({}, base);
434
+ // Loop through all properties in the source object
435
+ for (const key in source) {
436
+ if (source.hasOwnProperty(key)) {
437
+ // If the key exists in the base object and both values are objects, merge recursively
438
+ if (base.hasOwnProperty(key) &&
439
+ typeof base[key] === "object" &&
440
+ typeof source[key] === "object") {
441
+ merged[key] = deepMerge(base[key], source[key]);
442
+ }
443
+ else {
444
+ // If the key does not exist in the base object or one of the values is not an object, assign the source value to the merged object
445
+ merged[key] = source[key];
446
+ }
447
+ }
448
+ }
449
+ return merged;
450
+ };
451
+ const generateDocumentation = () => __awaiter(void 0, void 0, void 0, function* () {
452
+ const docs = Services_1.DocumentationService.getInstance();
453
+ const api = Services_1.APIService.getInstance();
454
+ const swaggerBasePath = path_1.default.join(api.rootFolder, "swagger", "index.ts");
455
+ let baseSchema = {
456
+ info: {
457
+ title: "Axe API",
458
+ description: "Edit your swagger/index.ts file",
459
+ },
460
+ servers: [
461
+ {
462
+ url: "http://localhost:3000",
463
+ },
464
+ ],
465
+ };
466
+ if (fs_1.default.existsSync(swaggerBasePath)) {
467
+ const { default: userDefinedSchema } = yield Promise.resolve(`${swaggerBasePath}`).then(s => __importStar(require(s)));
468
+ baseSchema = userDefinedSchema;
469
+ }
470
+ const schemas = {};
471
+ const requestBodies = {};
472
+ const firstVersion = api.versions.at(0);
473
+ if (!firstVersion) {
474
+ throw new Error("The version is not found!");
475
+ }
476
+ for (const model of firstVersion.modelList.get()) {
477
+ const properties = {};
478
+ for (const column of model.columns) {
479
+ properties[column.name] = {
480
+ type: toPropertyType(column.data_type),
481
+ format: column.data_type,
482
+ };
483
+ }
484
+ schemas[model.name] = {
485
+ type: "object",
486
+ properties,
487
+ };
488
+ if (model.instance.handlers.includes(Enums_1.HandlerTypes.INSERT)) {
489
+ requestBodies[`${model.name}${(0, change_case_1.pascalCase)(Enums_1.HttpMethods.POST)}Body`] = {
490
+ type: "object",
491
+ properties: toFillableFieldProperties(model),
492
+ };
493
+ }
494
+ if (model.instance.handlers.includes(Enums_1.HandlerTypes.UPDATE)) {
495
+ requestBodies[`${model.name}${(0, change_case_1.pascalCase)(Enums_1.HttpMethods.PUT)}Body`] = {
496
+ type: "object",
497
+ properties: toFillableFieldProperties(model),
498
+ };
499
+ }
500
+ if (model.instance.handlers.includes(Enums_1.HandlerTypes.PATCH)) {
501
+ requestBodies[`${model.name}${(0, change_case_1.pascalCase)(Enums_1.HttpMethods.PATCH)}Body`] = {
502
+ type: "object",
503
+ properties: toFillableFieldProperties(model),
504
+ };
505
+ }
506
+ }
507
+ const paths = {};
508
+ for (const endpoint of docs.get()) {
509
+ if (paths[endpoint.url] === undefined) {
510
+ paths[endpoint.url] = {};
511
+ }
512
+ const path = {
513
+ tags: [endpoint.model],
514
+ summary: toEndpointSummary(endpoint),
515
+ description: toEndpointDescription(endpoint),
516
+ operationId: `${endpoint.handler}${endpoint.model}`,
517
+ responses: toEndpointResponse(endpoint),
518
+ parameters: toRequestParameters(endpoint),
519
+ };
520
+ const requestBody = toRequestBody(endpoint);
521
+ if (requestBody) {
522
+ path.requestBody = requestBody;
523
+ }
524
+ paths[endpoint.url][endpoint.method.toLowerCase()] = path;
525
+ }
526
+ // Added custom endpoint
527
+ for (const custom of docs.getCustoms()) {
528
+ if (paths[custom.url] === undefined) {
529
+ paths[custom.url] = {};
530
+ }
531
+ paths[custom.url][custom.method.toLowerCase()] = {
532
+ description: "Custom endpoint",
533
+ };
534
+ }
535
+ const builded = {
536
+ openapi: "3.1.0",
537
+ paths,
538
+ components: {
539
+ schemas: Object.assign(Object.assign({}, schemas), { Pagination: PAGINATION_SCHEMA, Error: ERROR_SCHEMA, ValidationError: VALIDATION_ERROR_SCHEMA }),
540
+ requestBodies,
541
+ },
542
+ };
543
+ return deepMerge(baseSchema, builded);
544
+ });
545
+ exports.default = () => __awaiter(void 0, void 0, void 0, function* () {
546
+ const cached = cache.get("axe-api-documentation");
547
+ if (cached) {
548
+ return JSON.parse(cached);
549
+ }
550
+ const documentation = yield generateDocumentation();
551
+ cache.set("axe-api-documentation", JSON.stringify(documentation));
552
+ return documentation;
553
+ });
@@ -7,7 +7,7 @@ import AxeRequest from "src/Services/AxeRequest";
7
7
  export declare const bindTimestampValues: (formData: Record<string, any>, model: IModelService, columnTypes?: TimestampColumns[]) => void;
8
8
  export declare const getMergedFormData: (req: AxeRequest, fillables: string[]) => Record<string, any>;
9
9
  export declare const callHooks: (model: IModelService, type: HookFunctionTypes, params: IContext) => Promise<void>;
10
- export declare const getParentColumn: (relation: IRelation | null) => string | null;
10
+ export declare const getParentColumn: (model: IModelService, relation: IRelation | null) => string | null;
11
11
  export declare const checkPrimaryKeyValueType: (model: IModelService, value: any) => void;
12
12
  export declare const addForeignKeyQuery: (request: AxeRequest, query: Knex.QueryBuilder, relation: IRelation | null, parentModel: IModelService | null) => void;
13
13
  export declare const serializeData: (version: IVersion, itemArray: any, modelSerializer: SerializationFunction | null, handler: HandlerTypes, request: AxeRequest) => Promise<any[]>;
@@ -57,11 +57,11 @@ const callHooks = (model, type, params) => __awaiter(void 0, void 0, void 0, fun
57
57
  }
58
58
  });
59
59
  exports.callHooks = callHooks;
60
- const getParentColumn = (relation) => {
60
+ const getParentColumn = (model, relation) => {
61
61
  if (!relation) {
62
62
  return null;
63
63
  }
64
- return (0, change_case_1.camelCase)(relation.foreignKey);
64
+ return (0, change_case_1.camelCase)(`${model.name}-${relation.primaryKey}`);
65
65
  };
66
66
  exports.getParentColumn = getParentColumn;
67
67
  const checkPrimaryKeyValueType = (model, value) => {
@@ -75,7 +75,7 @@ const checkPrimaryKeyValueType = (model, value) => {
75
75
  exports.checkPrimaryKeyValueType = checkPrimaryKeyValueType;
76
76
  const addForeignKeyQuery = (request, query, relation, parentModel) => {
77
77
  if (relation && parentModel) {
78
- const parentColumn = (0, exports.getParentColumn)(relation);
78
+ const parentColumn = (0, exports.getParentColumn)(parentModel, relation);
79
79
  if (parentColumn) {
80
80
  query.where(relation.foreignKey, request.params[parentColumn]);
81
81
  }
@@ -22,7 +22,7 @@ const return404 = (response) => {
22
22
  response.end();
23
23
  };
24
24
  exports.default = (request, response, next) => __awaiter(void 0, void 0, void 0, function* () {
25
- Services_1.LogService.debug(`${request.method} ${request.url}`);
25
+ Services_1.LogService.debug(`📥 ${request.method} ${request.url}`);
26
26
  const { axeRequest, axeResponse } = (0, ConverterService_1.toAxeRequestResponsePair)(request, response);
27
27
  const match = URLService_1.default.match(axeRequest);
28
28
  if (!match) {
@@ -83,7 +83,7 @@ exports.default = (request, response, next) => __awaiter(void 0, void 0, void 0,
83
83
  Services_1.LogService.debug(`\tResponse ${context.res.statusCode()}`);
84
84
  break;
85
85
  }
86
- Services_1.LogService.debug(`\tResponse ${context.res.statusCode()}`);
86
+ Services_1.LogService.debug(`\t🟢 Response ${context.res.statusCode()}`);
87
87
  // We should brake the for-loop
88
88
  break;
89
89
  }
@@ -8,17 +8,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
- const CLOUD_FRONT_DOMAIN = "https://dw7lgbuj348m4.cloudfront.net/v1";
15
+ const SwaggerBuilder_1 = __importDefault(require("../Builders/SwaggerBuilder"));
13
16
  exports.default = (req, res) => __awaiter(void 0, void 0, void 0, function* () {
14
17
  try {
15
- const result = yield fetch(`${CLOUD_FRONT_DOMAIN}/index.html`);
16
- const content = (yield result.text())
17
- .replaceAll(`src="`, `src="${CLOUD_FRONT_DOMAIN}`)
18
- .replaceAll(`href="`, `href="${CLOUD_FRONT_DOMAIN}`);
19
- res.send(content);
18
+ res.json(yield (0, SwaggerBuilder_1.default)());
20
19
  }
21
20
  catch (error) {
22
- res.send("404");
21
+ res.status(500).json({ error: "An error occurred!" });
23
22
  }
24
23
  });
@@ -77,6 +77,7 @@ export interface AxeConfig extends IConfig {
77
77
  pino: LoggerOptions;
78
78
  rateLimit: IRateLimitConfig;
79
79
  errorHandler: ErrorHandleFunction;
80
+ docs: boolean;
80
81
  }
81
82
  export type IApplicationConfig = Partial<AxeConfig>;
82
83
  export interface ILanguage {
@@ -171,6 +172,8 @@ export interface IContext extends IRouteData {
171
172
  export interface IRouteDocumentation {
172
173
  version: string;
173
174
  handler: string;
175
+ modelService: IModelService;
176
+ parentModel: IModelService | null;
174
177
  model: string;
175
178
  table: string;
176
179
  columns: IColumn[];
@@ -183,6 +186,10 @@ export interface IRouteDocumentation {
183
186
  queryLimits: IQueryLimitConfig[];
184
187
  queryDefaults: IQueryDefaultConfig;
185
188
  }
189
+ export interface ICustomRouteDocumentation {
190
+ method: HttpMethods;
191
+ url: string;
192
+ }
186
193
  export interface IRawQuery {
187
194
  q: string | null;
188
195
  page: string | null;
@@ -32,7 +32,7 @@ exports.default = (context) => __awaiter(void 0, void 0, void 0, function* () {
32
32
  }
33
33
  }
34
34
  if (context.relation && context.parentModel) {
35
- const parentColumn = (0, Helpers_1.getParentColumn)(context.relation);
35
+ const parentColumn = (0, Helpers_1.getParentColumn)(context.parentModel, context.relation);
36
36
  if (parentColumn) {
37
37
  context.formData[context.relation.foreignKey] =
38
38
  context.params[parentColumn];
@@ -23,6 +23,7 @@ const RESERVED_VERSION_FOLDERS = [
23
23
  "Hooks",
24
24
  "Models",
25
25
  "Serialization",
26
+ "Swagger",
26
27
  ];
27
28
  class VersionResolver {
28
29
  resolve() {
@@ -43,8 +43,7 @@ const knex_schema_inspector_1 = __importDefault(require("knex-schema-inspector")
43
43
  const knex_paginate_1 = require("knex-paginate");
44
44
  const Builders_1 = require("./Builders");
45
45
  const Services_1 = require("./Services");
46
- const MetadataHandler_1 = __importDefault(require("./Handlers/MetadataHandler"));
47
- const DocsHTMLHandler_1 = __importDefault(require("./Handlers/DocsHTMLHandler"));
46
+ const SwaggerHandler_1 = __importDefault(require("./Handlers/SwaggerHandler"));
48
47
  const RoutesHandler_1 = __importDefault(require("./Handlers/RoutesHandler"));
49
48
  const http_1 = __importDefault(require("http"));
50
49
  const RequestHandler_1 = __importDefault(require("./Handlers/RequestHandler"));
@@ -131,9 +130,8 @@ class Server {
131
130
  server.on("error", function (e) {
132
131
  Services_1.LogService.error(e.message);
133
132
  });
134
- if (api.config.env === "development") {
135
- app.get("/metadata", MetadataHandler_1.default);
136
- app.get("/docs", DocsHTMLHandler_1.default);
133
+ if (api.config.docs) {
134
+ app.get("/swagger", SwaggerHandler_1.default);
137
135
  app.get("/routes", RoutesHandler_1.default);
138
136
  }
139
137
  server.listen(api.config.port);
@@ -2,6 +2,7 @@ import connect, { NextHandleFunction, ErrorHandleFunction } from "connect";
2
2
  import { GeneralFunction } from "../Types";
3
3
  declare class App {
4
4
  private connect;
5
+ private docs;
5
6
  constructor();
6
7
  /**
7
8
  * Get the `connect` instance
@@ -33,9 +33,12 @@ const LogService_1 = __importDefault(require("./LogService"));
33
33
  const RateLimit_1 = __importStar(require("../Middlewares/RateLimit"));
34
34
  const APIService_1 = __importDefault(require("./APIService"));
35
35
  const ConverterService_1 = require("./ConverterService");
36
+ const DocumentationService_1 = __importDefault(require("./DocumentationService"));
37
+ const Enums_1 = require("../Enums");
36
38
  class App {
37
39
  constructor() {
38
40
  var _a;
41
+ this.docs = DocumentationService_1.default.getInstance();
39
42
  this.connect = (0, connect_1.default)();
40
43
  LogService_1.default.debug("Created a new connect() instance");
41
44
  this.connect.use(body_parser_1.default.urlencoded({ extended: true }));
@@ -88,6 +91,7 @@ class App {
88
91
  get(url, ...args) {
89
92
  const { handler, middlewares } = (0, ConverterService_1.resolveMiddlewares)(args);
90
93
  URLService_1.default.addHandler("GET", url, handler, middlewares);
94
+ this.docs.pushCustom(Enums_1.HttpMethods.GET, url);
91
95
  }
92
96
  /**
93
97
  * Add a POST request handler with middleware support
@@ -106,6 +110,7 @@ class App {
106
110
  post(url, ...args) {
107
111
  const { handler, middlewares } = (0, ConverterService_1.resolveMiddlewares)(args);
108
112
  URLService_1.default.addHandler("POST", url, handler, middlewares);
113
+ this.docs.pushCustom(Enums_1.HttpMethods.POST, url);
109
114
  }
110
115
  /**
111
116
  * Add a PUT request handler with middleware support
@@ -124,6 +129,7 @@ class App {
124
129
  put(url, ...args) {
125
130
  const { handler, middlewares } = (0, ConverterService_1.resolveMiddlewares)(args);
126
131
  URLService_1.default.addHandler("PUT", url, handler, middlewares);
132
+ this.docs.pushCustom(Enums_1.HttpMethods.PUT, url);
127
133
  }
128
134
  /**
129
135
  * Add a PATCH request handler with middleware support
@@ -142,6 +148,7 @@ class App {
142
148
  patch(url, ...args) {
143
149
  const { handler, middlewares } = (0, ConverterService_1.resolveMiddlewares)(args);
144
150
  URLService_1.default.addHandler("PATCH", url, handler, middlewares);
151
+ this.docs.pushCustom(Enums_1.HttpMethods.PATCH, url);
145
152
  }
146
153
  /**
147
154
  * Add a DELETE request handler with middleware support
@@ -160,6 +167,7 @@ class App {
160
167
  delete(url, ...args) {
161
168
  const { handler, middlewares } = (0, ConverterService_1.resolveMiddlewares)(args);
162
169
  URLService_1.default.addHandler("DELETE", url, handler, middlewares);
170
+ this.docs.pushCustom(Enums_1.HttpMethods.DELETE, url);
163
171
  }
164
172
  }
165
173
  exports.default = App;
@@ -64,7 +64,7 @@ declare class AxeRequest {
64
64
  * @param options
65
65
  * @returns
66
66
  */
67
- files(options?: FormOptions): Promise<[formidable.Fields, formidable.Files]>;
67
+ files(options?: FormOptions): Promise<[formidable.Fields<string>, formidable.Files<string>]>;
68
68
  /**
69
69
  * Get the original `IncomingMessage` request.
70
70
  *
@@ -40,5 +40,6 @@ declare class AxeResponse {
40
40
  noContent(): void;
41
41
  isResponded(): boolean;
42
42
  statusCode(): number;
43
+ header(key: string, value: string): void;
43
44
  }
44
45
  export default AxeResponse;
@@ -62,5 +62,8 @@ class AxeResponse {
62
62
  statusCode() {
63
63
  return this.response.statusCode;
64
64
  }
65
+ header(key, value) {
66
+ this.original.setHeader(key, value);
67
+ }
65
68
  }
66
69
  exports.default = AxeResponse;
@@ -1,11 +1,14 @@
1
- import { IModelService, IRouteDocumentation, IVersion } from "../Interfaces";
1
+ import { ICustomRouteDocumentation, IModelService, IRouteDocumentation, IVersion } from "../Interfaces";
2
2
  import { HandlerTypes, HttpMethods } from "../Enums";
3
3
  declare class DocumentationService {
4
4
  private static instance;
5
5
  private routes;
6
+ private customRoutes;
6
7
  constructor();
7
8
  static getInstance(): DocumentationService;
8
- push(version: IVersion, handler: HandlerTypes, method: HttpMethods, url: string, model: IModelService): void;
9
+ push(version: IVersion, handler: HandlerTypes, method: HttpMethods, url: string, model: IModelService, parentModel: IModelService | null): void;
10
+ pushCustom(method: HttpMethods, url: string): void;
9
11
  get(): IRouteDocumentation[];
12
+ getCustoms(): ICustomRouteDocumentation[];
10
13
  }
11
14
  export default DocumentationService;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  class DocumentationService {
4
4
  constructor() {
5
5
  this.routes = [];
6
+ this.customRoutes = [];
6
7
  }
7
8
  static getInstance() {
8
9
  if (!DocumentationService.instance) {
@@ -10,11 +11,13 @@ class DocumentationService {
10
11
  }
11
12
  return DocumentationService.instance;
12
13
  }
13
- push(version, handler, method, url, model) {
14
+ push(version, handler, method, url, model, parentModel) {
14
15
  var _a, _b, _c;
15
16
  this.routes.push({
16
17
  version: version.name,
17
18
  handler,
19
+ modelService: model,
20
+ parentModel,
18
21
  model: model.name,
19
22
  table: model.instance.table,
20
23
  columns: model.columns,
@@ -28,8 +31,17 @@ class DocumentationService {
28
31
  queryDefaults: (_c = (_b = (_a = version.config) === null || _a === void 0 ? void 0 : _a.query) === null || _b === void 0 ? void 0 : _b.defaults) !== null && _c !== void 0 ? _c : {},
29
32
  });
30
33
  }
34
+ pushCustom(method, url) {
35
+ this.customRoutes.push({
36
+ method,
37
+ url,
38
+ });
39
+ }
31
40
  get() {
32
41
  return this.routes;
33
42
  }
43
+ getCustoms() {
44
+ return this.customRoutes;
45
+ }
34
46
  }
35
47
  exports.default = DocumentationService;
@@ -331,6 +331,11 @@ class QueryService {
331
331
  }
332
332
  if (where.condition === Enums_1.ConditionTypes.LIKE ||
333
333
  where.condition === Enums_1.ConditionTypes["NOT LIKE"]) {
334
+ const queryColumn = where.model.columns.find((column) => column.name === where.field);
335
+ if (queryColumn &&
336
+ !constants_1.STRING_COLUMN_TYPES.includes(queryColumn.data_type.toLowerCase())) {
337
+ throw new ApiError_1.default(`Query field need to be string. Unacceptable query field: ${where.field}`);
338
+ }
334
339
  where.value = where.value.replace(/\*/g, "%");
335
340
  }
336
341
  // This means that the condition is related with another table
@@ -16,6 +16,7 @@ export declare const API_ROUTE_TEMPLATES: {
16
16
  export declare const ConditionQueryFeatureMap: Record<ConditionTypes, QueryFeature>;
17
17
  export declare const RelationQueryFeatureMap: Record<Relationships, QueryFeature>;
18
18
  export declare const NUMERIC_PRIMARY_KEY_TYPES: string[];
19
+ export declare const STRING_COLUMN_TYPES: string[];
19
20
  export declare const HANDLER_METHOD_MAP: Record<HandlerTypes, HttpMethods>;
20
21
  export declare const HANDLER_CYLES: Record<HandlerTypes, IStepDefinition[]>;
21
22
  export declare const DEFAULT_APP_CONFIG: AxeConfig;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DEFAULT_VERSION_CONFIG = exports.DEFAULT_APP_CONFIG = exports.HANDLER_CYLES = exports.HANDLER_METHOD_MAP = exports.NUMERIC_PRIMARY_KEY_TYPES = exports.RelationQueryFeatureMap = exports.ConditionQueryFeatureMap = exports.API_ROUTE_TEMPLATES = exports.DEFAULT_METHODS_OF_MODELS = exports.DEFAULT_HANDLERS = exports.RESERVED_KEYWORDS = void 0;
6
+ exports.DEFAULT_VERSION_CONFIG = exports.DEFAULT_APP_CONFIG = exports.HANDLER_CYLES = exports.HANDLER_METHOD_MAP = exports.STRING_COLUMN_TYPES = exports.NUMERIC_PRIMARY_KEY_TYPES = exports.RelationQueryFeatureMap = exports.ConditionQueryFeatureMap = exports.API_ROUTE_TEMPLATES = exports.DEFAULT_METHODS_OF_MODELS = exports.DEFAULT_HANDLERS = exports.RESERVED_KEYWORDS = void 0;
7
7
  const Enums_1 = require("./Enums");
8
8
  const LimitService_1 = require("./Services/LimitService");
9
9
  const Single_1 = __importDefault(require("./Phases/Single"));
@@ -31,6 +31,7 @@ exports.RESERVED_KEYWORDS = [
31
31
  "event",
32
32
  "events",
33
33
  "all",
34
+ "swagger",
34
35
  ];
35
36
  exports.DEFAULT_HANDLERS = [
36
37
  Enums_1.HandlerTypes.INSERT,
@@ -102,6 +103,16 @@ exports.RelationQueryFeatureMap = {
102
103
  [Enums_1.Relationships.HAS_MANY]: Enums_1.QueryFeature.WithHasMany,
103
104
  };
104
105
  exports.NUMERIC_PRIMARY_KEY_TYPES = ["integer", "bigint"];
106
+ exports.STRING_COLUMN_TYPES = [
107
+ "CHAR",
108
+ "VARCHAR",
109
+ "BINARY",
110
+ "VARBINARY",
111
+ "BLOB",
112
+ "TEXT",
113
+ "ENUM",
114
+ "SET",
115
+ ].map((item) => item.toLowerCase());
105
116
  exports.HANDLER_METHOD_MAP = {
106
117
  [Enums_1.HandlerTypes.INSERT]: Enums_1.HttpMethods.POST,
107
118
  [Enums_1.HandlerTypes.PAGINATE]: Enums_1.HttpMethods.GET,
@@ -221,6 +232,7 @@ exports.DEFAULT_APP_CONFIG = {
221
232
  prefix: "api",
222
233
  env: "production",
223
234
  port: 3000,
235
+ docs: true,
224
236
  pino: {
225
237
  level: "error",
226
238
  transport: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axe-api",
3
- "version": "1.0.0-rc12",
3
+ "version": "1.0.0-rc15",
4
4
  "description": "AXE API is a simple tool to create Rest APIs quickly.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -33,7 +33,9 @@
33
33
  "dev-kit": "ts-node-dev --respawn --clear dev-kit.ts",
34
34
  "dev-kit:install": "node ./scripts/dev-kit-install.js",
35
35
  "dev-kit:remove": "node ./scripts/dev-kit-remove.js",
36
+ "dev-kit:docs": "open-swagger-ui http://localhost:3000/swagger",
36
37
  "test": "jest --runInBand",
38
+ "test:all": "npm run lint && npm run prettier:check && npm run test && npm run test:sqlite",
37
39
  "test:dev": "jest --watch",
38
40
  "lint": "eslint --max-warnings=0 .",
39
41
  "lint:watch": "esw --watch --color",
@@ -49,6 +51,7 @@
49
51
  "test:mariadb": "sh ./scripts/test-mariadb.sh",
50
52
  "test:sqlite": "sh ./scripts/test-sqlite.sh",
51
53
  "prettier:check": "prettier --check .",
54
+ "prettier:write": "prettier --write .",
52
55
  "prepare": "husky install"
53
56
  },
54
57
  "dependencies": {
@@ -56,6 +59,7 @@
56
59
  "change-case": "^4.1.2",
57
60
  "connect": "^3.7.0",
58
61
  "dotenv": "^16.3.1",
62
+ "formidable": "^3.5.1",
59
63
  "knex": "^2.5.1",
60
64
  "knex-paginate": "^3.1.1",
61
65
  "knex-schema-inspector": "^3.0.1",
@@ -66,44 +70,44 @@
66
70
  "validatorjs": "^3.22.1"
67
71
  },
68
72
  "devDependencies": {
69
- "@babel/core": "^7.22.10",
70
- "@babel/preset-env": "^7.22.10",
71
- "@babel/preset-typescript": "^7.22.5",
73
+ "@babel/core": "^7.22.11",
74
+ "@babel/preset-env": "^7.22.14",
75
+ "@babel/preset-typescript": "^7.22.11",
72
76
  "@types/accept-language-parser": "^1.5.3",
73
77
  "@types/aws-lambda": "^8.10.119",
74
78
  "@types/cors": "^2.8.13",
75
- "@types/formidable": "^3.4.1",
79
+ "@types/formidable": "^3.4.2",
76
80
  "@types/multer": "^1.4.7",
77
81
  "@types/pluralize": "^0.0.30",
78
82
  "@types/validatorjs": "^3.15.0",
79
- "@typescript-eslint/eslint-plugin": "^5.62.0",
80
- "@typescript-eslint/parser": "^5.62.0",
81
- "babel-jest": "^29.6.2",
83
+ "@typescript-eslint/eslint-plugin": "^6.5.0",
84
+ "@typescript-eslint/parser": "^6.5.0",
85
+ "babel-jest": "^29.6.4",
82
86
  "cors": "^2.8.5",
83
- "eslint": "^7.32.0",
84
- "eslint-config-standard": "^16.0.3",
87
+ "eslint": "^8.48.0",
88
+ "eslint-config-standard": "^17.1.0",
85
89
  "eslint-plugin-import": "^2.28.1",
86
90
  "eslint-plugin-node": "^11.1.0",
87
- "eslint-plugin-promise": "^5.1.0",
88
- "eslint-plugin-unicorn": "^33.0.1",
89
- "eslint-watch": "^7.0.0",
90
- "formidable": "^3.5.1",
91
- "glob": "^9.3.5",
91
+ "eslint-plugin-promise": "^6.1.1",
92
+ "eslint-plugin-unicorn": "^48.0.1",
93
+ "eslint-watch": "^8.0.0",
94
+ "glob": "^10.3.4",
92
95
  "husky": "^8.0.3",
93
- "jest": "^29.6.2",
94
- "lint-staged": "^13.3.0",
96
+ "jest": "^29.6.4",
97
+ "lint-staged": "^14.0.1",
95
98
  "multer": "^1.4.5-lts.1",
96
99
  "mysql": "^2.18.1",
97
100
  "node-cache": "^5.1.2",
98
101
  "node-color-log": "^10.0.2",
99
- "nodemon": "^2.0.22",
102
+ "nodemon": "^3.0.1",
103
+ "open-swagger-ui": "^1.2.0",
100
104
  "pg": "^8.11.3",
101
- "prettier": "^2.8.8",
102
- "redis": "^4.6.7",
105
+ "prettier": "^3.0.3",
106
+ "redis": "^4.6.8",
103
107
  "set-value": ">=4.1.0",
104
108
  "sqlite3": "^5.1.6",
105
109
  "ts-node": "^10.9.1",
106
- "typescript": "^5.1.6"
110
+ "typescript": "^5.2.2"
107
111
  },
108
112
  "lint-staged": {
109
113
  "**/*": "prettier --write --ignore-unknown"
@@ -1,4 +0,0 @@
1
- import AxeRequest from "../Services/AxeRequest";
2
- import AxeResponse from "../Services/AxeResponse";
3
- declare const _default: (req: AxeRequest, res: AxeResponse) => Promise<void>;
4
- export default _default;
@@ -1,22 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- const Services_1 = require("../Services");
13
- exports.default = (req, res) => __awaiter(void 0, void 0, void 0, function* () {
14
- const docs = Services_1.DocumentationService.getInstance();
15
- const api = Services_1.APIService.getInstance();
16
- res.json({
17
- routes: docs.get(),
18
- versions: api.versions.map((version) => {
19
- return Object.assign(Object.assign({}, version), { config: Object.assign(Object.assign({}, version.config), { transaction: undefined }), folders: undefined, modelList: undefined, models: version.modelList.get() });
20
- }),
21
- });
22
- });